use crate::{
EVMConfig, Environment,
account::{AccountStatus, LevmAccount},
call_frame::CallFrameBackup,
constants::*,
db::gen_db::GeneralizedDatabase,
errors::{ExceptionalHalt, InternalError, TxValidationError, VMError},
gas_cost::{
self, ACCESS_LIST_ADDRESS_COST, ACCESS_LIST_STORAGE_KEY_COST, BLOB_GAS_PER_BLOB,
COLD_ADDRESS_ACCESS_COST, CREATE_BASE_COST, REGULAR_GAS_CREATE, STANDARD_TOKEN_COST,
STATE_BYTES_PER_AUTH_TOTAL, STATE_BYTES_PER_NEW_ACCOUNT, WARM_ADDRESS_ACCESS_COST,
cost_per_state_byte, floor_tokens_in_access_list, total_cost_floor_per_token,
},
vm::{Substate, VM},
};
use ExceptionalHalt::OutOfGas;
use bytes::Bytes;
use ethrex_common::constants::SYSTEM_ADDRESS;
use ethrex_common::types::Log;
use ethrex_common::{
Address, H256, U256,
evm::calculate_create_address,
types::{Account, Code, Fork, Transaction, fake_exponential, tx_fields::*},
utils::{keccak, u256_to_big_endian},
};
use ethrex_common::{types::TxKind, utils::u256_from_big_endian_const};
use ethrex_rlp;
use rustc_hash::FxHashMap;
pub type Storage = FxHashMap<U256, H256>;
pub fn address_to_word(address: Address) -> U256 {
let mut word = [0u8; 32];
for (word_byte, address_byte) in word.iter_mut().skip(12).zip(address.as_bytes().iter()) {
*word_byte = *address_byte;
}
u256_from_big_endian_const(word)
}
pub fn calculate_create2_address(
sender_address: Address,
initialization_code: &Bytes,
salt: U256,
) -> Result<Address, InternalError> {
let init_code_hash = keccak(initialization_code);
let generated_address = Address::from_slice(
keccak(
[
&[0xff],
sender_address.as_bytes(),
&salt.to_big_endian(),
init_code_hash.as_bytes(),
]
.concat(),
)
.as_bytes()
.get(12..)
.ok_or(InternalError::Slicing)?,
);
Ok(generated_address)
}
pub fn restore_cache_state(
db: &mut GeneralizedDatabase,
callframe_backup: CallFrameBackup,
) -> Result<(), VMError> {
for (address, account) in callframe_backup.original_accounts_info {
if let Some(current_account) = db.current_accounts_state.get_mut(&address) {
current_account.info = account.info;
current_account.status = account.status;
current_account.has_storage = account.has_storage;
current_account.exists = account.exists;
}
}
for (address, storage) in callframe_backup.original_account_storage_slots {
let account = db
.current_accounts_state
.get_mut(&address)
.ok_or(InternalError::AccountNotFound)?;
for (key, value) in storage {
account.storage.insert(key, value);
}
}
for code_hash in callframe_backup.inserted_code_hashes {
db.codes.remove(&code_hash);
}
if let Some(checkpoint) = callframe_backup.bal_checkpoint
&& let Some(recorder) = db.bal_recorder.as_mut()
{
recorder.restore(checkpoint);
}
Ok(())
}
pub fn get_base_fee_per_blob_gas(
block_excess_blob_gas: Option<u64>,
evm_config: &EVMConfig,
) -> Result<U256, VMError> {
let base_fee_update_fraction = evm_config.blob_schedule.base_fee_update_fraction;
let excess_blob_gas = block_excess_blob_gas.unwrap_or_default();
fake_exponential(
MIN_BASE_FEE_PER_BLOB_GAS.into(),
excess_blob_gas.into(),
base_fee_update_fraction,
)
.map_err(|err| VMError::Internal(InternalError::FakeExponentialError(err)))
}
pub fn get_max_blob_gas_price(
tx_blob_hashes: &[H256],
tx_max_fee_per_blob_gas: Option<U256>,
) -> Result<U256, VMError> {
let blobhash_amount: u64 = tx_blob_hashes
.len()
.try_into()
.map_err(|_| InternalError::TypeConversion)?;
let blob_gas_used: u64 = blobhash_amount
.checked_mul(BLOB_GAS_PER_BLOB)
.unwrap_or_default();
let max_blob_gas_cost = tx_max_fee_per_blob_gas
.unwrap_or_default()
.checked_mul(blob_gas_used.into())
.ok_or(InternalError::Overflow)?;
Ok(max_blob_gas_cost)
}
pub fn calculate_blob_gas_cost(
tx_blob_hashes: &[H256],
base_blob_fee_per_gas: U256,
) -> Result<U256, VMError> {
let blobhash_amount: u64 = tx_blob_hashes
.len()
.try_into()
.map_err(|_| InternalError::TypeConversion)?;
let blob_gas_used: u64 = blobhash_amount
.checked_mul(BLOB_GAS_PER_BLOB)
.unwrap_or_default();
let blob_gas_used: U256 = blob_gas_used.into();
let blob_fee: U256 = blob_gas_used
.checked_mul(base_blob_fee_per_gas)
.ok_or(InternalError::Overflow)?;
Ok(blob_fee)
}
pub fn word_to_address(word: U256) -> Address {
Address::from_slice(&u256_to_big_endian(word)[12..])
}
pub fn code_has_delegation(code: &[u8]) -> Result<bool, VMError> {
if code.len() == EIP7702_DELEGATED_CODE_LEN {
let first_3_bytes = &code.get(..3).ok_or(InternalError::Slicing)?;
return Ok(*first_3_bytes == SET_CODE_DELEGATION_BYTES);
}
Ok(false)
}
pub fn get_authorized_address_from_code(code: &[u8]) -> Result<Address, VMError> {
if code_has_delegation(code)? {
let address_bytes = &code
.get(SET_CODE_DELEGATION_BYTES.len()..)
.ok_or(InternalError::Slicing)?;
let address = Address::from_slice(address_bytes);
Ok(address)
} else {
Err(InternalError::AccountNotDelegated.into())
}
}
pub fn eip7702_recover_address(
auth_tuple: &AuthorizationTuple,
crypto: &dyn ethrex_crypto::Crypto,
) -> Result<Option<Address>, VMError> {
use ethrex_rlp::encode::RLPEncode;
if auth_tuple.s_signature > *SECP256K1_ORDER_OVER2 || U256::zero() >= auth_tuple.s_signature {
return Ok(None);
}
if auth_tuple.r_signature > *SECP256K1_ORDER || U256::zero() >= auth_tuple.r_signature {
return Ok(None);
}
if auth_tuple.y_parity != U256::one() && auth_tuple.y_parity != U256::zero() {
return Ok(None);
}
let mut rlp_buf = Vec::with_capacity(128);
rlp_buf.push(MAGIC);
(auth_tuple.chain_id, auth_tuple.address, auth_tuple.nonce).encode(&mut rlp_buf);
let msg = crypto.keccak256(&rlp_buf);
let y_parity: u8 =
TryInto::<u8>::try_into(auth_tuple.y_parity).map_err(|_| InternalError::TypeConversion)?;
let mut sig = [0u8; 65];
sig[..32].copy_from_slice(&auth_tuple.r_signature.to_big_endian());
sig[32..64].copy_from_slice(&auth_tuple.s_signature.to_big_endian());
sig[64] = y_parity;
match crypto.recover_signer(&sig, &msg) {
Ok(address) => Ok(Some(address)),
Err(_) => Ok(None),
}
}
pub fn eip7702_get_code(
db: &mut GeneralizedDatabase,
accrued_substate: &mut Substate,
address: Address,
) -> Result<(bool, u64, Address, Code), VMError> {
let (bytecode, delegation) = eip7702_peek_delegation(db, accrued_substate, address)?;
let Some((auth_address, access_cost)) = delegation else {
return Ok((false, 0, address, bytecode));
};
accrued_substate.add_accessed_address(auth_address);
let authorized_bytecode = db.get_account_code(auth_address)?.clone();
Ok((true, access_cost, auth_address, authorized_bytecode))
}
pub fn eip7702_peek_delegation(
db: &mut GeneralizedDatabase,
substate: &Substate,
address: Address,
) -> Result<(Code, Option<(Address, u64)>), VMError> {
let bytecode = db.get_account_code(address)?.clone();
if !code_has_delegation(bytecode.code())? {
return Ok((bytecode, None));
}
let auth_address = get_authorized_address_from_code(bytecode.code())?;
let access_cost = if substate.is_address_accessed(&auth_address) {
WARM_ADDRESS_ACCESS_COST
} else {
COLD_ADDRESS_ACCESS_COST
};
Ok((bytecode, Some((auth_address, access_cost))))
}
#[derive(Clone, Copy, Debug)]
pub struct IntrinsicGas {
pub regular: u64,
pub state: u64,
pub calldata_cost: u64,
}
impl<'a> VM<'a> {
pub fn eip7702_set_access_code(&mut self) -> Result<(), VMError> {
let mut refunded_gas: u64 = 0;
for auth_tuple in self.tx.authorization_list().cloned().unwrap_or_default() {
let chain_id_not_equals_this_chain_id = auth_tuple.chain_id != self.env.chain_id;
let chain_id_not_zero = !auth_tuple.chain_id.is_zero();
if chain_id_not_zero && chain_id_not_equals_this_chain_id {
continue;
}
if auth_tuple.nonce == u64::MAX {
continue;
}
let Some(authority_address) = eip7702_recover_address(&auth_tuple, self.crypto)? else {
continue;
};
let authority_account = self.db.get_account(authority_address)?;
let authority_exists = authority_account.exists;
let authority_info = authority_account.info.clone();
let authority_code = self.db.get_code(authority_info.code_hash)?;
self.substate.add_accessed_address(authority_address);
let authority_code_is_empty = authority_code.is_empty();
let empty_or_delegated =
authority_code_is_empty || code_has_delegation(authority_code.code())?;
if let Some(recorder) = self.db.bal_recorder.as_mut() {
recorder.record_touched_address(authority_address);
}
if !empty_or_delegated {
continue;
}
if authority_info.nonce != auth_tuple.nonce {
continue;
}
if authority_exists {
if self.env.config.fork >= Fork::Amsterdam {
let refund = self.state_gas_new_account;
self.state_gas_reservoir = self
.state_gas_reservoir
.checked_add(refund)
.ok_or(InternalError::Overflow)?;
self.state_refund = self
.state_refund
.checked_add(refund)
.ok_or(InternalError::Overflow)?;
} else {
refunded_gas = refunded_gas
.checked_add(REFUND_AUTH_PER_EXISTING_ACCOUNT)
.ok_or(InternalError::Overflow)?;
}
}
let writes_no_new_indicator =
!authority_code_is_empty || auth_tuple.address == Address::zero();
if self.env.config.fork >= Fork::Amsterdam && writes_no_new_indicator {
let refund = self.state_gas_auth_base;
self.state_gas_reservoir = self
.state_gas_reservoir
.checked_add(refund)
.ok_or(InternalError::Overflow)?;
self.state_refund = self
.state_refund
.checked_add(refund)
.ok_or(InternalError::Overflow)?;
}
let delegation_bytes = [
&SET_CODE_DELEGATION_BYTES[..],
auth_tuple.address.as_bytes(),
]
.concat();
let code = if auth_tuple.address != Address::zero() {
delegation_bytes.into()
} else {
Bytes::new()
};
self.update_account_bytecode(
authority_address,
Code::from_bytecode(code, self.crypto),
)?;
self.increment_account_nonce(authority_address)
.map_err(|_| TxValidationError::NonceIsMax)?;
}
self.substate.refunded_gas = self
.substate
.refunded_gas
.checked_add(refunded_gas)
.ok_or(InternalError::Overflow)?;
Ok(())
}
pub fn add_intrinsic_gas(&mut self, intrinsic: &IntrinsicGas) -> Result<(), VMError> {
let regular_gas = intrinsic.regular;
let state_gas = intrinsic.state;
let total_gas = regular_gas.checked_add(state_gas).ok_or(OutOfGas)?;
self.current_call_frame
.increase_consumed_gas(total_gas)
.map_err(|_| TxValidationError::IntrinsicGasTooLow)?;
self.state_gas_used = self
.state_gas_used
.checked_add(i64::try_from(state_gas).map_err(|_| InternalError::Overflow)?)
.ok_or(InternalError::Overflow)?;
debug_assert_eq!(self.intrinsic_state_gas, 0, "intrinsic_state_gas set twice");
self.intrinsic_state_gas = state_gas;
if self.env.config.fork >= Fork::Amsterdam {
if self.env.is_system_call {
let sys_reservoir = self
.state_gas_storage_set
.saturating_mul(SYSTEM_MAX_SSTORES_PER_CALL);
self.state_gas_reservoir = sys_reservoir;
self.state_gas_reservoir_initial = sys_reservoir;
} else {
let gas_limit = self.tx.gas_limit();
let execution_gas = gas_limit.saturating_sub(total_gas);
let regular_gas_budget = TX_MAX_GAS_LIMIT_AMSTERDAM.saturating_sub(regular_gas);
let gas_left = regular_gas_budget.min(execution_gas);
let reservoir = execution_gas.saturating_sub(gas_left);
if reservoir > 0 {
let reservoir_i64 =
i64::try_from(reservoir).map_err(|_| InternalError::Overflow)?;
self.current_call_frame.gas_remaining = self
.current_call_frame
.gas_remaining
.checked_sub(reservoir_i64)
.ok_or(InternalError::Overflow)?;
self.state_gas_reservoir = reservoir;
}
self.state_gas_reservoir_initial = reservoir;
}
}
Ok(())
}
pub fn get_intrinsic_gas(&self) -> Result<IntrinsicGas, VMError> {
let mut regular_gas: u64 = 0;
let mut state_gas: u64 = 0;
let fork = self.env.config.fork;
let calldata_cost = gas_cost::tx_calldata(&self.current_call_frame.calldata)?;
regular_gas = regular_gas.checked_add(calldata_cost).ok_or(OutOfGas)?;
regular_gas = regular_gas.checked_add(TX_BASE_COST).ok_or(OutOfGas)?;
if self.is_create()? {
if fork >= Fork::Amsterdam {
regular_gas = regular_gas
.checked_add(REGULAR_GAS_CREATE)
.ok_or(OutOfGas)?;
state_gas = state_gas
.checked_add(self.state_gas_new_account)
.ok_or(OutOfGas)?;
} else {
regular_gas = regular_gas.checked_add(CREATE_BASE_COST).ok_or(OutOfGas)?;
}
if fork >= Fork::Shanghai {
let number_of_words = &self.current_call_frame.calldata.len().div_ceil(WORD_SIZE);
let double_number_of_words: u64 = number_of_words
.checked_mul(2)
.ok_or(OutOfGas)?
.try_into()
.map_err(|_| InternalError::TypeConversion)?;
regular_gas = regular_gas
.checked_add(double_number_of_words)
.ok_or(OutOfGas)?;
}
}
let mut access_lists_cost: u64 = 0;
for (_, keys) in self.tx.access_list() {
access_lists_cost = access_lists_cost
.checked_add(ACCESS_LIST_ADDRESS_COST)
.ok_or(OutOfGas)?;
for _ in keys {
access_lists_cost = access_lists_cost
.checked_add(ACCESS_LIST_STORAGE_KEY_COST)
.ok_or(OutOfGas)?;
}
}
if fork >= Fork::Amsterdam {
let al_floor_tokens = floor_tokens_in_access_list(self.tx.access_list());
let al_data_cost = al_floor_tokens
.checked_mul(total_cost_floor_per_token(fork))
.ok_or(InternalError::Overflow)?;
access_lists_cost = access_lists_cost
.checked_add(al_data_cost)
.ok_or(InternalError::Overflow)?;
}
regular_gas = regular_gas.checked_add(access_lists_cost).ok_or(OutOfGas)?;
let amount_of_auth_tuples: u64 = match self.tx.authorization_list() {
None => 0,
Some(list) => list
.len()
.try_into()
.map_err(|_| InternalError::TypeConversion)?,
};
if fork >= Fork::Amsterdam {
let regular_auth_cost = PER_AUTH_BASE_COST
.checked_mul(amount_of_auth_tuples)
.ok_or(InternalError::Overflow)?;
regular_gas = regular_gas.checked_add(regular_auth_cost).ok_or(OutOfGas)?;
let state_auth_cost = self
.state_gas_auth_total
.checked_mul(amount_of_auth_tuples)
.ok_or(InternalError::Overflow)?;
state_gas = state_gas.checked_add(state_auth_cost).ok_or(OutOfGas)?;
} else {
let authorization_list_cost = PER_EMPTY_ACCOUNT_COST
.checked_mul(amount_of_auth_tuples)
.ok_or(InternalError::Overflow)?;
regular_gas = regular_gas
.checked_add(authorization_list_cost)
.ok_or(OutOfGas)?;
}
Ok(IntrinsicGas {
regular: regular_gas,
state: state_gas,
calldata_cost,
})
}
pub fn get_min_gas_used(&self) -> Result<u64, VMError> {
let fork = self.env.config.fork;
let calldata = if self.is_create()? {
self.current_call_frame.bytecode.code()
} else {
self.current_call_frame.calldata.as_ref()
};
let mut tokens_in_calldata: u64 = if fork >= Fork::Amsterdam {
let total_bytes: u64 = calldata
.len()
.try_into()
.map_err(|_| InternalError::TypeConversion)?;
total_bytes
.checked_mul(STANDARD_TOKEN_COST)
.ok_or(InternalError::Overflow)?
} else {
gas_cost::tx_calldata(calldata)? / STANDARD_TOKEN_COST
};
if fork >= Fork::Amsterdam {
let al_floor_tokens = floor_tokens_in_access_list(self.tx.access_list());
tokens_in_calldata = tokens_in_calldata
.checked_add(al_floor_tokens)
.ok_or(InternalError::Overflow)?;
}
let mut min_gas_used: u64 = tokens_in_calldata
.checked_mul(total_cost_floor_per_token(fork))
.ok_or(InternalError::Overflow)?;
min_gas_used = min_gas_used
.checked_add(TX_BASE_COST)
.ok_or(InternalError::Overflow)?;
Ok(min_gas_used)
}
pub fn get_tx_callee(
tx: &Transaction,
db: &mut GeneralizedDatabase,
env: &Environment,
substate: &mut Substate,
) -> Result<(Address, bool), VMError> {
match tx.to() {
TxKind::Call(address_to) => {
substate.add_accessed_address(address_to);
Ok((address_to, false))
}
TxKind::Create => {
let sender_nonce = db.get_account(env.origin)?.info.nonce;
let created_address = calculate_create_address(env.origin, sender_nonce);
substate.add_accessed_address(created_address);
substate.add_created_account(created_address);
Ok((created_address, true))
}
}
}
}
pub fn intrinsic_gas_dimensions(
tx: &Transaction,
fork: Fork,
block_gas_limit: u64,
) -> Result<(u64, u64), VMError> {
let mut regular_gas: u64 = 0;
let mut state_gas: u64 = 0;
let (state_gas_new_account, state_gas_auth_total) = if fork >= Fork::Amsterdam {
let cpsb = cost_per_state_byte(block_gas_limit);
(
STATE_BYTES_PER_NEW_ACCOUNT
.checked_mul(cpsb)
.ok_or(InternalError::Overflow)?,
STATE_BYTES_PER_AUTH_TOTAL
.checked_mul(cpsb)
.ok_or(InternalError::Overflow)?,
)
} else {
(0, 0)
};
let calldata_cost = gas_cost::tx_calldata(tx.data())?;
regular_gas = regular_gas.checked_add(calldata_cost).ok_or(OutOfGas)?;
regular_gas = regular_gas.checked_add(TX_BASE_COST).ok_or(OutOfGas)?;
let is_create = matches!(tx.to(), TxKind::Create);
if is_create {
if fork >= Fork::Amsterdam {
regular_gas = regular_gas
.checked_add(REGULAR_GAS_CREATE)
.ok_or(OutOfGas)?;
state_gas = state_gas
.checked_add(state_gas_new_account)
.ok_or(OutOfGas)?;
} else {
regular_gas = regular_gas.checked_add(CREATE_BASE_COST).ok_or(OutOfGas)?;
}
if fork >= Fork::Shanghai {
let words = tx.data().len().div_ceil(WORD_SIZE);
let double_words: u64 = words
.checked_mul(2)
.ok_or(OutOfGas)?
.try_into()
.map_err(|_| InternalError::TypeConversion)?;
regular_gas = regular_gas.checked_add(double_words).ok_or(OutOfGas)?;
}
}
let mut access_lists_cost: u64 = 0;
for (_, keys) in tx.access_list() {
access_lists_cost = access_lists_cost
.checked_add(ACCESS_LIST_ADDRESS_COST)
.ok_or(OutOfGas)?;
for _ in keys {
access_lists_cost = access_lists_cost
.checked_add(ACCESS_LIST_STORAGE_KEY_COST)
.ok_or(OutOfGas)?;
}
}
if fork >= Fork::Amsterdam {
let al_floor_tokens = floor_tokens_in_access_list(tx.access_list());
let al_data_cost = al_floor_tokens
.checked_mul(total_cost_floor_per_token(fork))
.ok_or(InternalError::Overflow)?;
access_lists_cost = access_lists_cost
.checked_add(al_data_cost)
.ok_or(InternalError::Overflow)?;
}
regular_gas = regular_gas.checked_add(access_lists_cost).ok_or(OutOfGas)?;
let amount_of_auth_tuples: u64 = match tx.authorization_list() {
None => 0,
Some(list) => list
.len()
.try_into()
.map_err(|_| InternalError::TypeConversion)?,
};
if fork >= Fork::Amsterdam {
let regular_auth_cost = PER_AUTH_BASE_COST
.checked_mul(amount_of_auth_tuples)
.ok_or(InternalError::Overflow)?;
regular_gas = regular_gas.checked_add(regular_auth_cost).ok_or(OutOfGas)?;
let state_auth_cost = state_gas_auth_total
.checked_mul(amount_of_auth_tuples)
.ok_or(InternalError::Overflow)?;
state_gas = state_gas.checked_add(state_auth_cost).ok_or(OutOfGas)?;
} else {
let auth_cost = PER_EMPTY_ACCOUNT_COST
.checked_mul(amount_of_auth_tuples)
.ok_or(InternalError::Overflow)?;
regular_gas = regular_gas.checked_add(auth_cost).ok_or(OutOfGas)?;
}
Ok((regular_gas, state_gas))
}
pub fn intrinsic_gas_floor(tx: &Transaction, fork: Fork) -> Result<u64, VMError> {
let calldata = tx.data();
let mut tokens_in_calldata: u64 = if fork >= Fork::Amsterdam {
let total_bytes: u64 = calldata
.len()
.try_into()
.map_err(|_| InternalError::TypeConversion)?;
total_bytes
.checked_mul(STANDARD_TOKEN_COST)
.ok_or(InternalError::Overflow)?
} else {
gas_cost::tx_calldata(calldata)? / STANDARD_TOKEN_COST
};
if fork >= Fork::Amsterdam {
let al_floor_tokens = floor_tokens_in_access_list(tx.access_list());
tokens_in_calldata = tokens_in_calldata
.checked_add(al_floor_tokens)
.ok_or(InternalError::Overflow)?;
}
tokens_in_calldata
.checked_mul(total_cost_floor_per_token(fork))
.ok_or(InternalError::Overflow)?
.checked_add(TX_BASE_COST)
.ok_or(InternalError::Overflow.into())
}
pub fn account_to_levm_account(account: Account) -> (LevmAccount, Code) {
(
LevmAccount {
info: account.info,
has_storage: !account.storage.is_empty(), storage: account.storage,
status: AccountStatus::Unmodified,
exists: true,
},
account.code,
)
}
#[expect(clippy::as_conversions)]
pub fn u256_to_usize(val: U256) -> Result<usize, VMError> {
if val.0[0] > u32::MAX as u64 || val.0[1] != 0 || val.0[2] != 0 || val.0[3] != 0 {
return Err(VMError::ExceptionalHalt(ExceptionalHalt::VeryLargeNumber));
}
Ok(val.0[0] as usize)
}
pub fn size_offset_to_usize(size: U256, offset: U256) -> Result<(usize, usize), VMError> {
if size.is_zero() {
Ok((0, 0))
} else {
Ok((u256_to_usize(size)?, u256_to_usize(offset)?))
}
}
#[inline]
pub fn create_eth_transfer_log(from: Address, to: Address, value: U256) -> Log {
let mut from_topic = [0u8; 32];
from_topic[12..].copy_from_slice(from.as_bytes());
let mut to_topic = [0u8; 32];
to_topic[12..].copy_from_slice(to.as_bytes());
let data = value.to_big_endian();
Log {
address: SYSTEM_ADDRESS,
topics: vec![
TRANSFER_EVENT_TOPIC,
H256::from(from_topic),
H256::from(to_topic),
],
data: Bytes::from(data.to_vec()),
}
}
#[inline]
pub fn create_burn_log(address: Address, amount: U256) -> Log {
let mut address_topic = [0u8; 32];
address_topic[12..].copy_from_slice(address.as_bytes());
let data = amount.to_big_endian();
Log {
address: SYSTEM_ADDRESS,
topics: vec![BURN_EVENT_TOPIC, H256::from(address_topic)],
data: Bytes::from(data.to_vec()),
}
}