diff --git a/crates/ef-testing/src/evm_sequencer/account/mod.rs b/crates/ef-testing/src/evm_sequencer/account/mod.rs index fddbd93d..f9d80535 100644 --- a/crates/ef-testing/src/evm_sequencer/account/mod.rs +++ b/crates/ef-testing/src/evm_sequencer/account/mod.rs @@ -1,9 +1,17 @@ -#[cfg(feature = "v0")] -pub mod v0; -#[cfg(feature = "v1")] -pub mod v1; - +use crate::evm_sequencer::constants::storage_variables::{ + ACCOUNT_BYTECODE_LEN, ACCOUNT_CODE_HASH, ACCOUNT_EVM_ADDRESS, ACCOUNT_IS_INITIALIZED, + ACCOUNT_NONCE, ACCOUNT_STORAGE, ACCOUNT_VALID_JUMPDESTS, +}; +use crate::evm_sequencer::{types::felt::FeltSequencer, utils::split_u256}; +use crate::starknet_storage; +use blockifier::abi::{abi_utils::get_storage_var_address, sierra_types::next_storage_key}; +use reth_primitives::alloy_primitives::keccak256; +use reth_primitives::KECCAK_EMPTY; +use reth_primitives::{Address, U256}; +use revm_interpreter::analysis::to_analysed; +use revm_primitives::{Bytecode, Bytes}; use starknet::core::utils::cairo_short_string_to_felt; +use starknet_api::StarknetApiError; use starknet_api::{core::Nonce, state::StorageKey}; use starknet_crypto::{poseidon_permute_comp, Felt}; @@ -59,27 +67,98 @@ pub enum AccountType { Contract = 2, } -#[cfg(not(any(feature = "v0", feature = "v1")))] -pub mod kkrt_account { - use super::KakarotAccount; - use reth_primitives::{Address, Bytes, U256}; - use starknet::core::types::Felt; - use starknet_api::{core::Nonce, StarknetApiError}; - - impl KakarotAccount { - pub fn new( - _evm_address: &Address, - _code: &Bytes, - _nonce: U256, - _balance: U256, - _evm_storage: &[(U256, U256)], - ) -> Result { - Ok(Self { - evm_address: Felt::default(), - nonce: Nonce::default(), - storage: vec![], +impl KakarotAccount { + pub fn new( + evm_address: &Address, + code: &Bytes, + nonce: U256, + balance: U256, + evm_storage: &[(U256, U256)], + ) -> Result { + let nonce = Felt::from(TryInto::::try_into(nonce).map_err(|err| { + StarknetApiError::OutOfRange { + string: err.to_string(), + } + })?); + + let evm_address = TryInto::::try_into(*evm_address) + .unwrap() // infallible + .into(); + + let mut storage = vec![ + starknet_storage!(ACCOUNT_EVM_ADDRESS, evm_address), + starknet_storage!(ACCOUNT_IS_INITIALIZED, 1u8), + starknet_storage!(ACCOUNT_BYTECODE_LEN, code.len() as u32), + starknet_storage!(ACCOUNT_NONCE, nonce), + ]; + + // Initialize the bytecode storage var. + let mut bytecode_storage = pack_byte_array_to_starkfelt_array(code) + .enumerate() + .map(|(i, bytes)| (StorageKey::from(i as u32), bytes)) + .collect(); + storage.append(&mut bytecode_storage); + + // Initialize the code hash var + let account_is_empty = code.is_empty() && nonce == Felt::ZERO && balance == U256::ZERO; + let code_hash = if account_is_empty { + U256::ZERO + } else if code.is_empty() { + U256::from_be_slice(KECCAK_EMPTY.as_slice()) + } else { + U256::from_be_slice(keccak256(code).as_slice()) + }; + + let code_hash_values = split_u256(code_hash); + let code_hash_low_key = get_storage_var_address(ACCOUNT_CODE_HASH, &[]); + let code_hash_high_key = next_storage_key(&code_hash_low_key)?; + storage.extend([ + (code_hash_low_key, Felt::from(code_hash_values[0])), + (code_hash_high_key, Felt::from(code_hash_values[1])), + ]); + + // Initialize the bytecode jumpdests. + let bytecode = to_analysed(Bytecode::new_raw(code.clone())); + let valid_jumpdests: Vec = match bytecode { + Bytecode::LegacyAnalyzed(legacy_analyzed_bytecode) => legacy_analyzed_bytecode + .jump_table() + .0 + .iter() + .enumerate() + .filter_map(|(index, bit)| bit.as_ref().then(|| index)) + .collect(), + _ => unreachable!("Bytecode should be analysed"), + }; + + let jumdpests_storage_address = get_storage_var_address(ACCOUNT_VALID_JUMPDESTS, &[]); + let jumdpests_storage_address = Felt::from(jumdpests_storage_address); + valid_jumpdests.into_iter().for_each(|index| { + storage.push(( + (jumdpests_storage_address + Felt::from(index)) + .try_into() + .unwrap(), + Felt::ONE, + )) + }); + + // Initialize the storage vars. + let mut evm_storage_storage: Vec<(StorageKey, Felt)> = evm_storage + .iter() + .flat_map(|(k, v)| { + let keys = split_u256(*k).map(Into::into); + let values = split_u256(*v).map(Into::::into); + let low_key = get_storage_var_address(ACCOUNT_STORAGE, &keys); + let high_key = next_storage_key(&low_key).unwrap(); // can fail only if low is the max key + vec![(low_key, values[0]), (high_key, values[1])] }) - } + .collect(); + storage.append(&mut evm_storage_storage); + + Ok(Self { + storage, + evm_address, + nonce: Nonce(nonce), + }) } } diff --git a/crates/ef-testing/src/evm_sequencer/account/v0.rs b/crates/ef-testing/src/evm_sequencer/account/v0.rs deleted file mode 100644 index e66485e0..00000000 --- a/crates/ef-testing/src/evm_sequencer/account/v0.rs +++ /dev/null @@ -1,115 +0,0 @@ -use blockifier::abi::{abi_utils::get_storage_var_address, sierra_types::next_storage_key}; -use reth_primitives::alloy_primitives::keccak256; -use reth_primitives::KECCAK_EMPTY; -use reth_primitives::{Address, U256}; - -use revm_interpreter::analysis::to_analysed; -use revm_primitives::{Bytecode, Bytes}; -use starknet_api::{core::Nonce, state::StorageKey, StarknetApiError}; -use starknet_crypto::Felt; - -use super::{pack_byte_array_to_starkfelt_array, KakarotAccount}; -use crate::evm_sequencer::constants::storage_variables::{ - ACCOUNT_BYTECODE_LEN, ACCOUNT_CODE_HASH, ACCOUNT_EVM_ADDRESS, ACCOUNT_IS_INITIALIZED, - ACCOUNT_NONCE, ACCOUNT_STORAGE, ACCOUNT_VALID_JUMPDESTS, -}; -use crate::evm_sequencer::{types::felt::FeltSequencer, utils::split_u256}; -use crate::starknet_storage; - -impl KakarotAccount { - pub fn new( - evm_address: &Address, - code: &Bytes, - nonce: U256, - balance: U256, - evm_storage: &[(U256, U256)], - ) -> Result { - let nonce = Felt::from(TryInto::::try_into(nonce).map_err(|err| { - StarknetApiError::OutOfRange { - string: err.to_string(), - } - })?); - - let evm_address = TryInto::::try_into(*evm_address) - .unwrap() // infallible - .into(); - - let mut storage = vec![ - starknet_storage!(ACCOUNT_EVM_ADDRESS, evm_address), - starknet_storage!(ACCOUNT_IS_INITIALIZED, 1u8), - starknet_storage!(ACCOUNT_BYTECODE_LEN, code.len() as u32), - ]; - - // Write the nonce of the account is written to storage after each tx. - storage.append(&mut vec![starknet_storage!(ACCOUNT_NONCE, nonce)]); - - // Initialize the bytecode storage var. - let mut bytecode_storage = pack_byte_array_to_starkfelt_array(code) - .enumerate() - .map(|(i, bytes)| (StorageKey::from(i as u32), bytes)) - .collect(); - storage.append(&mut bytecode_storage); - - // Initialize the code hash var - let account_is_empty = - code.is_empty() && nonce == Felt::from(0) && balance == U256::from(0); - let code_hash = if account_is_empty { - U256::from(0) - } else if code.is_empty() { - U256::from_be_slice(KECCAK_EMPTY.as_slice()) - } else { - U256::from_be_slice(keccak256(code).as_slice()) - }; - - let code_hash_values = split_u256(code_hash); - let code_hash_low_key = get_storage_var_address(ACCOUNT_CODE_HASH, &[]); - let code_hash_high_key = next_storage_key(&code_hash_low_key)?; - storage.extend([ - (code_hash_low_key, Felt::from(code_hash_values[0])), - (code_hash_high_key, Felt::from(code_hash_values[1])), - ]); - - // Initialize the bytecode jumpdests. - let bytecode = to_analysed(Bytecode::new_raw(code.clone())); - let valid_jumpdests: Vec = match bytecode { - Bytecode::LegacyAnalyzed(legacy_analyzed_bytecode) => legacy_analyzed_bytecode - .jump_table() - .0 - .iter() - .enumerate() - .filter_map(|(index, bit)| bit.as_ref().then(|| index)) - .collect(), - _ => unreachable!("Bytecode should be analysed"), - }; - - let jumdpests_storage_address = get_storage_var_address(ACCOUNT_VALID_JUMPDESTS, &[]); - let jumdpests_storage_address = Felt::from(jumdpests_storage_address); - valid_jumpdests.into_iter().for_each(|index| { - storage.push(( - (jumdpests_storage_address + Felt::from(index)) - .try_into() - .unwrap(), - Felt::ONE, - )) - }); - - // Initialize the storage vars. - let mut evm_storage_storage: Vec<(StorageKey, Felt)> = evm_storage - .iter() - .flat_map(|(k, v)| { - let keys = split_u256(*k).map(Into::into); - let values = split_u256(*v).map(Into::::into); - let low_key = get_storage_var_address(ACCOUNT_STORAGE, &keys); - let high_key = next_storage_key(&low_key).unwrap(); // can fail only if low is the max key - vec![(low_key, values[0]), (high_key, values[1])] - }) - .collect(); - storage.append(&mut evm_storage_storage); - - Ok(Self { - storage, - evm_address, - nonce: Nonce(nonce), - }) - } -} diff --git a/crates/ef-testing/src/evm_sequencer/account/v1.rs b/crates/ef-testing/src/evm_sequencer/account/v1.rs deleted file mode 100644 index 2a2dad65..00000000 --- a/crates/ef-testing/src/evm_sequencer/account/v1.rs +++ /dev/null @@ -1,114 +0,0 @@ -use blockifier::abi::abi_utils::get_storage_var_address; -use blockifier::abi::sierra_types::next_storage_key; -use reth_primitives::{keccak256, Address, Bytes, KECCAK_EMPTY, U256}; -use revm_interpreter::analysis::to_analysed; -use revm_primitives::Bytecode; -use starknet::core::types::Felt; -use starknet_api::{core::Nonce, state::StorageKey, StarknetApiError}; - -use super::pack_byte_array_to_starkfelt_array; -use super::KakarotAccount; -use crate::evm_sequencer::constants::storage_variables::{ - ACCOUNT_BYTECODE_LEN, ACCOUNT_CODE_HASH, ACCOUNT_EVM_ADDRESS, ACCOUNT_IS_INITIALIZED, - ACCOUNT_NONCE, ACCOUNT_STORAGE, ACCOUNT_VALID_JUMPDESTS, -}; -use crate::evm_sequencer::{types::felt::FeltSequencer, utils::split_u256}; -use crate::starknet_storage; - -impl KakarotAccount { - pub fn new( - evm_address: &Address, - code: &Bytes, - nonce: U256, - balance: U256, - evm_storage: &[(U256, U256)], - ) -> Result { - let nonce = Felt::from(TryInto::::try_into(nonce).map_err(|err| { - StarknetApiError::OutOfRange { - string: err.to_string(), - } - })?); - - let evm_address = TryInto::::try_into(*evm_address) - .unwrap() // infallible - .into(); - - let mut storage = vec![ - starknet_storage!(ACCOUNT_EVM_ADDRESS, evm_address), - starknet_storage!(ACCOUNT_IS_INITIALIZED, 1u8), - starknet_storage!(ACCOUNT_BYTECODE_LEN, code.len() as u32), - ]; - - // Write the nonce of the account is written to storage after each tx. - storage.append(&mut vec![starknet_storage!(ACCOUNT_NONCE, nonce)]); - - // Initialize the bytecode storage var. - let mut bytecode_storage = pack_byte_array_to_starkfelt_array(code) - .enumerate() - .map(|(i, bytes)| (StorageKey::from(i as u32), bytes)) - .collect(); - storage.append(&mut bytecode_storage); - - // Initialize the code hash var - let account_is_empty = - code.is_empty() && nonce == Felt::from(0) && balance == U256::from(0); - let code_hash = if account_is_empty { - U256::from(0) - } else if code.is_empty() { - U256::from_be_slice(KECCAK_EMPTY.as_slice()) - } else { - U256::from_be_slice(keccak256(code).as_slice()) - }; - - let code_hash_values = split_u256(code_hash); - let code_hash_low_key = get_storage_var_address(ACCOUNT_CODE_HASH, &[]); - let code_hash_high_key = next_storage_key(&code_hash_low_key)?; - storage.extend([ - (code_hash_low_key, Felt::from(code_hash_values[0])), - (code_hash_high_key, Felt::from(code_hash_values[1])), - ]); - - // Initialize the bytecode jumpdests. - let bytecode = to_analysed(Bytecode::new_raw(code.clone())); - let valid_jumpdests: Vec = match bytecode { - Bytecode::LegacyAnalyzed(legacy_analyzed_bytecode) => legacy_analyzed_bytecode - .jump_table() - .0 - .iter() - .enumerate() - .filter_map(|(index, bit)| bit.as_ref().then(|| index)) - .collect(), - _ => unreachable!("Bytecode should be analysed"), - }; - - let jumdpests_storage_address = get_storage_var_address(ACCOUNT_VALID_JUMPDESTS, &[]); - let jumdpests_storage_address = Felt::from(jumdpests_storage_address); - valid_jumpdests.into_iter().for_each(|index| { - storage.push(( - (jumdpests_storage_address + Felt::from(index)) - .try_into() - .unwrap(), - Felt::ONE, - )) - }); - - // Initialize the storage vars. - let mut evm_storage_storage: Vec<(StorageKey, Felt)> = evm_storage - .iter() - .flat_map(|(k, v)| { - let keys = split_u256(*k).map(Into::into); - let values = split_u256(*v).map(Into::::into); - let low_key = get_storage_var_address(ACCOUNT_STORAGE, &keys); - let high_key = next_storage_key(&low_key).unwrap(); // can fail only if low is the max key - vec![(low_key, values[0]), (high_key, values[1])] - }) - .collect(); - storage.append(&mut evm_storage_storage); - - Ok(Self { - storage, - evm_address, - nonce: Nonce(nonce), - }) - } -} diff --git a/crates/ef-testing/src/evm_sequencer/constants.rs b/crates/ef-testing/src/evm_sequencer/constants.rs index 823e8d1c..753cf37b 100644 --- a/crates/ef-testing/src/evm_sequencer/constants.rs +++ b/crates/ef-testing/src/evm_sequencer/constants.rs @@ -60,6 +60,9 @@ lazy_static! { pub static ref CAIRO1_HELPERS_CLASS: CompiledClass = load_contract_class("../../build/common/cairo1_helpers.json").expect("Failed to load precompiles contract class"); pub static ref CAIRO1_HELPERS_CLASS_HASH: ClassHash = ClassHash(CAIRO1_HELPERS_CLASS.class_hash().unwrap()); + pub static ref OPENZEPPELIN_ACCOUNT_CLASS_HASH: ClassHash = ClassHash(OPENZEPPELIN_ACCOUNT_CLASS.class_hash().unwrap()); + pub static ref OPENZEPPELIN_ACCOUNT_CLASS: LegacyContractClass = load_contract_class("../../build/v0/OpenzeppelinAccount.json").expect("Failed to load openzeppelin account contract class"); + pub static ref RELAYER_SIGNING_KEY: SigningKey = SigningKey::from_random(); pub static ref RELAYER_VERIFYING_KEY: VerifyingKey = RELAYER_SIGNING_KEY.verifying_key(); pub static ref RELAYER_BALANCE: Felt = Felt::from(1_000_000_000_000_000_000_000_000u128); @@ -72,13 +75,11 @@ lazy_static! { pub static ref KAKAROT_CLASS: LegacyContractClass = load_contract_class("../../build/v0/kakarot.json").expect("Failed to load Kakarot contract class"); pub static ref ACCOUNT_CONTRACT_CLASS: LegacyContractClass = load_contract_class("../../build/v0/account_contract.json").expect("Failed to load ContractAccount contract class"); pub static ref UNINITIALIZED_ACCOUNT_CLASS: LegacyContractClass = load_contract_class("../../build/v0/uninitialized_account.json").expect("Failed to load uninitialized account c contract class"); - pub static ref OPENZEPPELIN_ACCOUNT_CLASS: LegacyContractClass = load_contract_class("../../build/v0/OpenzeppelinAccount.json").expect("Failed to load openzeppelin account contract class"); // Main class hashes pub static ref KAKAROT_CLASS_HASH: ClassHash = ClassHash(KAKAROT_CLASS.class_hash().unwrap()); pub static ref ACCOUNT_CONTRACT_CLASS_HASH: ClassHash = ClassHash(ACCOUNT_CONTRACT_CLASS.class_hash().unwrap()); pub static ref UNINITIALIZED_ACCOUNT_CLASS_HASH: ClassHash = ClassHash(UNINITIALIZED_ACCOUNT_CLASS.class_hash().unwrap()); - pub static ref OPENZEPPELIN_ACCOUNT_CLASS_HASH: ClassHash = ClassHash(OPENZEPPELIN_ACCOUNT_CLASS.class_hash().unwrap()); } #[cfg(feature = "v1")] @@ -105,6 +106,14 @@ lazy_static! { panic!("Proxy class hash not defined, use features flag \"v0\" or \"v1\""); pub static ref UNINITIALIZED_ACCOUNT_CLASS_HASH: ClassHash = panic!("Uninitialized account class hash not defined, use features flag \"v0\" or \"v1\""); + pub static ref ACCOUNT_CONTRACT_CLASS: LegacyContractClass = + panic!("Account contract class not defined, use features flag \"v0\" or \"v1\""); + pub static ref KAKAROT_CLASS: LegacyContractClass = + panic!("Kakarot contract class not defined, use features flag \"v0\" or \"v1\""); + pub static ref KAKAROT_CLASS_HASH: ClassHash = + panic!("Kakarot class hash not defined, use features flag \"v0\" or \"v1\""); + pub static ref UNINITIALIZED_ACCOUNT_CLASS: LegacyContractClass = + panic!("Uninitialized account class not defined, use features flag \"v0\" or \"v1\""); } pub mod storage_variables { diff --git a/crates/ef-testing/src/evm_sequencer/evm_state/mod.rs b/crates/ef-testing/src/evm_sequencer/evm_state/mod.rs index e211c0cc..661dbaab 100644 --- a/crates/ef-testing/src/evm_sequencer/evm_state/mod.rs +++ b/crates/ef-testing/src/evm_sequencer/evm_state/mod.rs @@ -1,15 +1,40 @@ -#[cfg(feature = "v0")] -pub mod v0; -#[cfg(feature = "v1")] -pub mod v1; - +use crate::evm_sequencer::constants::storage_variables::ACCOUNT_BYTECODE_LEN; +use crate::evm_sequencer::constants::RELAYER_ADDRESS; +use crate::evm_sequencer::utils::felt_to_bytes; +use crate::{ + evm_sequencer::{ + account::KakarotAccount, + constants::{ + storage_variables::{ + ACCOUNT_IMPLEMENTATION, ACCOUNT_NONCE, ACCOUNT_STORAGE, KAKAROT_BASE_FEE, + KAKAROT_BLOCK_GAS_LIMIT, KAKAROT_COINBASE, KAKAROT_EVM_TO_STARKNET_ADDRESS, + KAKAROT_PREV_RANDAO, OWNABLE_OWNER, + }, + ETH_FEE_TOKEN_ADDRESS, KAKAROT_ADDRESS, + }, + sequencer::KakarotSequencer, + types::felt::FeltSequencer, + utils::{split_u256, to_broadcasted_starknet_transaction}, + }, + starknet_storage, +}; use blockifier::{ - state::state_api::StateResult, - transaction::objects::{TransactionExecutionInfo, TransactionExecutionResult}, + abi::{ + abi_utils::{get_fee_token_var_address, get_storage_var_address}, + sierra_types::next_storage_key, + }, + execution::errors::EntryPointExecutionError, + state::state_api::{State as _, StateReader as _, StateResult}, + transaction::{ + errors::TransactionExecutionError, + objects::{TransactionExecutionInfo, TransactionExecutionResult}, + }, }; use reth_primitives::{Address, Bytes, TransactionSigned, U256}; - -use super::account::KakarotAccount; +use sequencer::{execution::Execution as _, transaction::BroadcastedTransactionWrapper}; +use starknet::core::types::BroadcastedTransaction; +use starknet_api::state::StorageKey; +use starknet_crypto::Felt; /// EVM state interface. Used to setup the evm state, EOA and contract accounts, /// fund them and get their state (balance, nonce, code, storage). @@ -58,5 +83,350 @@ pub trait Evm { } } -#[cfg(not(any(feature = "v0", feature = "v1")))] -impl Evm for super::sequencer::KakarotSequencer {} +impl Evm for KakarotSequencer { + /// Sets up the evm state (coinbase, block number, etc.) + fn setup_state( + &mut self, + base_fee: U256, + prev_randao: U256, + block_gas_limit: U256, + ) -> StateResult<()> { + let kakarot_address = self.environment.kakarot_address; + let coinbase_address: FeltSequencer = (*self.address()).try_into().unwrap(); // infallible + + // Set the coinbase address. + self.state_mut().set_storage_at( + kakarot_address, + get_storage_var_address(KAKAROT_COINBASE, &[]), + coinbase_address.into(), + )?; + + // Set the base fee. + let [low_fee, high_fee] = split_u256(base_fee); + let basefee_address = get_storage_var_address(KAKAROT_BASE_FEE, &[]); + self.state_mut() + .set_storage_at(kakarot_address, basefee_address, low_fee.into())?; + self.state_mut().set_storage_at( + kakarot_address, + next_storage_key(&basefee_address)?, + high_fee.into(), + )?; + + // Set the previous randao. + let [low_prev_randao, high_prev_randao] = split_u256(prev_randao); + let prev_randao_address = get_storage_var_address(KAKAROT_PREV_RANDAO, &[]); + self.state_mut().set_storage_at( + kakarot_address, + prev_randao_address, + low_prev_randao.into(), + )?; + self.state_mut().set_storage_at( + kakarot_address, + next_storage_key(&prev_randao_address)?, + high_prev_randao.into(), + )?; + + // Set the block gas limit, considering it fits in a felt. + let [block_gas_limit, _] = split_u256(block_gas_limit); + let block_gas_limit_address = get_storage_var_address(KAKAROT_BLOCK_GAS_LIMIT, &[]); + self.state_mut().set_storage_at( + kakarot_address, + block_gas_limit_address, + block_gas_limit.into(), + )?; + + Ok(()) + } + + /// Sets up an EOA or contract account. Writes nonce, code and storage to the sequencer storage. + fn setup_account(&mut self, account: KakarotAccount) -> StateResult<()> { + let evm_address = &account.evm_address().to_bytes_be()[12..]; + let evm_address = Address::from_slice(evm_address); + let mut storage = account.storage; + let starknet_address = self.compute_starknet_address(&evm_address)?; + + self.state_mut().set_nonce(starknet_address, account.nonce); + + storage.append(&mut vec![ + starknet_storage!( + ACCOUNT_IMPLEMENTATION, + self.environment.account_contract_class_hash.0 + ), + starknet_storage!(OWNABLE_OWNER, *self.environment.kakarot_address.0.key()), + ]); + + // Write all the storage vars to the sequencer state. + for (k, v) in storage { + self.state_mut().set_storage_at(starknet_address, k, v)?; + } + + let class_hash = self.environment.account_contract_class_hash; + // Set up the contract class hash + self.state_mut() + .set_class_hash_at(starknet_address, class_hash)?; + + // Add the address to the Kakarot evm to starknet mapping + let kakarot_address = self.environment.kakarot_address; + self.state_mut().set_storage_at( + kakarot_address, + get_storage_var_address(KAKAROT_EVM_TO_STARKNET_ADDRESS, &[account.evm_address]), + *starknet_address.0.key(), + )?; + Ok(()) + } + + /// Funds an EOA or contract account. Also gives allowance to the Kakarot contract. + fn fund(&mut self, evm_address: &Address, balance: U256) -> StateResult<()> { + let starknet_address = self.compute_starknet_address(evm_address)?; + let balance_values = split_u256(balance); + let mut storage = vec![]; + + // Initialize the balance storage var. + let balance_key_low = get_fee_token_var_address(starknet_address); + let balance_key_high = next_storage_key(&balance_key_low)?; + storage.append(&mut vec![ + (balance_key_low, balance_values[0].into()), + (balance_key_high, balance_values[1].into()), + ]); + + // Initialize the allowance storage var. + let allowance_key_low = get_storage_var_address( + "ERC20_allowances", + &[*starknet_address.0.key(), *KAKAROT_ADDRESS.0.key()], + ); + let allowance_key_high = next_storage_key(&allowance_key_low)?; + storage.append(&mut vec![ + (allowance_key_low, u128::MAX.into()), + (allowance_key_high, u128::MAX.into()), + ]); + + // Write all the storage vars to the sequencer state. + for (k, v) in storage { + self.state_mut() + .set_storage_at(*ETH_FEE_TOKEN_ADDRESS, k, v)?; + } + Ok(()) + } + + /// Returns the storage value at the given key evm storage key. + fn storage_at(&mut self, evm_address: &Address, key: U256) -> StateResult { + let keys = split_u256(key).map(Into::into); + let key_low = get_storage_var_address(ACCOUNT_STORAGE, &keys); + let key_high = next_storage_key(&key_low)?; + + let starknet_address = self.compute_starknet_address(evm_address)?; + + let low = self.state_mut().get_storage_at(starknet_address, key_low)?; + let high = self + .state_mut() + .get_storage_at(starknet_address, key_high)?; + + let low = U256::from_be_bytes(low.to_bytes_be()); + let high = U256::from_be_bytes(high.to_bytes_be()); + + Ok(high << 128 | low) + } + + /// Returns the nonce of the given address. + /// Uses the Kakarot managed nonce stored in the contract account's storage. + fn nonce_at(&mut self, evm_address: &Address) -> StateResult { + let starknet_address = self.compute_starknet_address(evm_address)?; + + let key = get_storage_var_address(ACCOUNT_NONCE, &[]); + let nonce = self.state_mut().get_storage_at(starknet_address, key)?; + + Ok(U256::from_be_bytes(nonce.to_bytes_be())) + } + + /// Returns the bytecode of the given address. For an EOA, the bytecode_len_ storage variable will return 0, + /// and the function will return an empty vector. For a contract account, the function will return the bytecode + /// stored in the contract_account_bytecode storage variables. The function assumes that the bytecode is stored + /// in 31 byte big-endian chunks. + fn code_at(&mut self, evm_address: &Address) -> StateResult { + // Get all storage addresses. + let starknet_address = self.compute_starknet_address(evm_address)?; + + let bytecode_len = self.state_mut().get_storage_at( + starknet_address, + get_storage_var_address(ACCOUNT_BYTECODE_LEN, &[]), + )?; + let bytecode_len: u64 = bytecode_len.to_biguint().try_into()?; + + if bytecode_len == 0 { + return Ok(Bytes::default()); + } + + // Assumes that the bytecode is stored in 31 byte chunks. + let num_chunks = bytecode_len / 31; + let mut bytecode: Vec = Vec::with_capacity(bytecode_len as usize); + + for chunk_index in 0..num_chunks { + let key = StorageKey::from(chunk_index); + let code = self.state_mut().get_storage_at(starknet_address, key)?; + bytecode.append(&mut felt_to_bytes(&code, 1).to_vec()); + } + + let remainder = bytecode_len % 31; + let key = StorageKey::from(num_chunks); + let code = self.state_mut().get_storage_at(starknet_address, key)?; + bytecode.append(&mut felt_to_bytes(&code, (32 - remainder) as usize).to_vec()); + + Ok(Bytes::from(bytecode)) + } + + /// Returns the balance of native tokens at the given address. + /// Makes use of the default StateReader implementation from Blockifier. + fn balance_at(&mut self, evm_address: &Address) -> StateResult { + let starknet_address = self.compute_starknet_address(evm_address)?; + let (low, high) = self + .state_mut() + .get_fee_token_balance(starknet_address, *ETH_FEE_TOKEN_ADDRESS)?; + + let low = U256::from_be_bytes(low.to_bytes_be()); + let high = U256::from_be_bytes(high.to_bytes_be()); + + Ok(high << 128 | low) + } + + /// Converts the given signed transaction to a Starknet-rs transaction and executes it. + // Since we are still missing the validate for the EOA, the signature is not added + // to the transaction. + fn execute_transaction( + &mut self, + transaction: TransactionSigned, + ) -> TransactionExecutionResult { + let evm_address = transaction.recover_signer().ok_or_else(|| { + TransactionExecutionError::ValidateTransactionError { + error: EntryPointExecutionError::InvalidExecutionInput { + input_descriptor: String::from("Signed transaction"), + info: "Missing signer in signed transaction".to_string(), + }, + class_hash: Default::default(), + storage_address: Default::default(), + selector: Default::default(), + } + })?; + let starknet_address = self.compute_starknet_address(&evm_address)?; + let relayer_nonce = self.state_mut().get_nonce_at(*RELAYER_ADDRESS).unwrap(); + + let starknet_transaction = + BroadcastedTransactionWrapper::new(BroadcastedTransaction::Invoke( + to_broadcasted_starknet_transaction( + &transaction, + Felt::from(starknet_address), + relayer_nonce.0.into(), + ) + .map_err(|err| { + TransactionExecutionError::ValidateTransactionError { + error: EntryPointExecutionError::InvalidExecutionInput { + input_descriptor: String::from("Failed to convert transaction"), + info: err.to_string(), + }, + class_hash: Default::default(), + storage_address: Default::default(), + selector: Default::default(), + } + })?, + )); + + let chain_id = self.chain_id(); + self.execute( + starknet_transaction + .try_into_execution_transaction(Felt::from(chain_id)) + .unwrap(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + evm_sequencer::{ + constants::{ + tests::{PRIVATE_KEY, PUBLIC_KEY, TEST_CONTRACT_ADDRESS}, + ACCOUNT_CONTRACT_CLASS_HASH, CAIRO1_HELPERS_CLASS_HASH, CHAIN_ID, KAKAROT_ADDRESS, + UNINITIALIZED_ACCOUNT_CLASS_HASH, + }, + sequencer::{KakarotEnvironment, INITIAL_SEQUENCER_STATE}, + }, + models::result::extract_output_and_log_execution_result, + }; + use reth_primitives::{ + sign_message, AccessList, Signature, TransactionSigned, TxEip1559, B256, + }; + + #[test] + fn test_execute_simple_contract() { + // Given + let kakarot_environment = KakarotEnvironment::new( + *KAKAROT_ADDRESS, + *UNINITIALIZED_ACCOUNT_CLASS_HASH, + *CAIRO1_HELPERS_CLASS_HASH, + *ACCOUNT_CONTRACT_CLASS_HASH, + ); + let coinbase_address = Address::left_padding_from(&0xC01BA5Eu64.to_be_bytes()); + let mut sequencer = KakarotSequencer::new( + INITIAL_SEQUENCER_STATE.clone(), + kakarot_environment, + coinbase_address, + CHAIN_ID, + 0, + 1, + ); + + let mut transaction = TransactionSigned { + hash: B256::default(), + signature: Signature::default(), + transaction: reth_primitives::Transaction::Eip1559(TxEip1559 { + chain_id: CHAIN_ID, + nonce: 0, + gas_limit: 1_000_000, + max_fee_per_gas: 0, + max_priority_fee_per_gas: 0, + to: reth_primitives::TxKind::Call(*TEST_CONTRACT_ADDRESS), + value: U256::ZERO, + access_list: AccessList::default(), + input: Bytes::default(), + }), + }; + let signature = + sign_message(*PRIVATE_KEY, transaction.transaction.signature_hash()).unwrap(); + transaction.signature = signature; + let eoa_nonce = U256::ZERO; + let contract_bytecode = Bytes::from(vec![96, 1, 96, 0, 85]); // PUSH 01 PUSH 00 SSTORE + let contract_nonce = U256::from(1); + + // When + let contract = KakarotAccount::new( + &TEST_CONTRACT_ADDRESS, + &contract_bytecode, + contract_nonce, + U256::ZERO, + &[], + ) + .unwrap(); + let eoa = KakarotAccount::new(&PUBLIC_KEY, &Bytes::default(), eoa_nonce, U256::ZERO, &[]) + .unwrap(); + sequencer.setup_account(contract).unwrap(); + sequencer.setup_account(eoa).unwrap(); + let execution_result = sequencer.execute_transaction(transaction); + + // Update the output with the execution result of the current transaction + let tx_output = extract_output_and_log_execution_result( + &execution_result, + "test_case", + "test_category", + ) + .unwrap_or_default(); + + assert!(tx_output.success); + + // Then + let storage = sequencer + .storage_at(&TEST_CONTRACT_ADDRESS, U256::ZERO) + .unwrap(); + + assert_eq!(storage, U256::from(1_u64)); + } +} diff --git a/crates/ef-testing/src/evm_sequencer/evm_state/v0.rs b/crates/ef-testing/src/evm_sequencer/evm_state/v0.rs deleted file mode 100644 index 3f965974..00000000 --- a/crates/ef-testing/src/evm_sequencer/evm_state/v0.rs +++ /dev/null @@ -1,372 +0,0 @@ -use blockifier::abi::abi_utils::{get_fee_token_var_address, get_storage_var_address}; -use blockifier::abi::sierra_types::next_storage_key; -use blockifier::execution::errors::EntryPointExecutionError; -use blockifier::state::state_api::{State, StateReader, StateResult}; -use blockifier::transaction::errors::TransactionExecutionError; -use blockifier::transaction::objects::{TransactionExecutionInfo, TransactionExecutionResult}; -use reth_primitives::{Address, Bytes, TransactionSigned, U256}; -use sequencer::execution::Execution as _; -use sequencer::transaction::BroadcastedTransactionWrapper; -use starknet::core::types::{BroadcastedTransaction, Felt}; -use starknet_api::state::StorageKey; - -use super::Evm; -use crate::evm_sequencer::account::KakarotAccount; -use crate::evm_sequencer::constants::storage_variables::{ - ACCOUNT_BYTECODE_LEN, ACCOUNT_IMPLEMENTATION, ACCOUNT_NONCE, ACCOUNT_STORAGE, KAKAROT_BASE_FEE, - KAKAROT_BLOCK_GAS_LIMIT, KAKAROT_COINBASE, KAKAROT_EVM_TO_STARKNET_ADDRESS, - KAKAROT_PREV_RANDAO, OWNABLE_OWNER, -}; -use crate::evm_sequencer::constants::{ETH_FEE_TOKEN_ADDRESS, RELAYER_ADDRESS}; -use crate::evm_sequencer::sequencer::KakarotSequencer; -use crate::evm_sequencer::types::felt::FeltSequencer; -use crate::evm_sequencer::utils::{felt_to_bytes, split_u256, to_broadcasted_starknet_transaction}; -use crate::starknet_storage; - -impl Evm for KakarotSequencer { - /// Sets up the evm state (coinbase, block number, etc.) - fn setup_state( - &mut self, - base_fee: U256, - prev_randao: U256, - block_gas_limit: U256, - ) -> StateResult<()> { - let kakarot_address = self.environment.kakarot_address; - let coinbase_address: FeltSequencer = (*self.address()).try_into().unwrap(); // infallible - - // Set the coinbase address. - self.state_mut().set_storage_at( - kakarot_address, - get_storage_var_address(KAKAROT_COINBASE, &[]), - coinbase_address.into(), - )?; - - // Set the base fee. - let [low_fee, high_fee] = split_u256(base_fee); - let basefee_address = get_storage_var_address(KAKAROT_BASE_FEE, &[]); - self.state_mut() - .set_storage_at(kakarot_address, basefee_address, low_fee.into())?; - self.state_mut().set_storage_at( - kakarot_address, - next_storage_key(&basefee_address)?, - high_fee.into(), - )?; - - // Set the previous randao. - let [low_prev_randao, high_prev_randao] = split_u256(prev_randao); - let prev_randao_address = get_storage_var_address(KAKAROT_PREV_RANDAO, &[]); - self.state_mut().set_storage_at( - kakarot_address, - prev_randao_address, - low_prev_randao.into(), - )?; - self.state_mut().set_storage_at( - kakarot_address, - next_storage_key(&prev_randao_address)?, - high_prev_randao.into(), - )?; - - // Set the block gas limit, considering it fits in a felt. - let [block_gas_limit, _] = split_u256(block_gas_limit); - let block_gas_limit_address = get_storage_var_address(KAKAROT_BLOCK_GAS_LIMIT, &[]); - self.state_mut().set_storage_at( - kakarot_address, - block_gas_limit_address, - block_gas_limit.into(), - )?; - - Ok(()) - } - - /// Sets up an EOA or contract account. Writes nonce, code and storage to the sequencer storage. - /// Uses the KakarotSequencer environment to set the class hash, contract owner and Kakarot address. - fn setup_account(&mut self, account: KakarotAccount) -> StateResult<()> { - let evm_address = &account.evm_address().to_bytes_be()[12..]; - let evm_address = Address::from_slice(evm_address); - let mut storage = account.storage; - let starknet_address = self.compute_starknet_address(&evm_address)?; - - self.state_mut().set_nonce(starknet_address, account.nonce); - - storage.append(&mut vec![ - starknet_storage!( - ACCOUNT_IMPLEMENTATION, - self.environment.account_contract_class_hash.0 - ), - starknet_storage!(OWNABLE_OWNER, *self.environment.kakarot_address.0.key()), - ]); - - // Write all the storage vars to the sequencer state. - for (k, v) in storage { - self.state_mut().set_storage_at(starknet_address, k, v)?; - } - - let class_hash = self.environment.account_contract_class_hash; - // Set up the contract class hash - self.state_mut() - .set_class_hash_at(starknet_address, class_hash)?; - - // Add the address to the Kakarot evm to starknet mapping - let kakarot_address = self.environment.kakarot_address; - self.state_mut().set_storage_at( - kakarot_address, - get_storage_var_address(KAKAROT_EVM_TO_STARKNET_ADDRESS, &[account.evm_address]), - *starknet_address.0.key(), - )?; - Ok(()) - } - - /// Funds an EOA or contract account. Also gives allowance to the Kakarot contract. - fn fund(&mut self, evm_address: &Address, balance: U256) -> StateResult<()> { - let starknet_address = self.compute_starknet_address(evm_address)?; - let balance_values = split_u256(balance); - let mut storage = vec![]; - - // Initialize the balance storage var. - let balance_low_key = get_fee_token_var_address(starknet_address); - let balance_high_key = next_storage_key(&balance_low_key)?; - storage.append(&mut vec![ - (balance_low_key, balance_values[0].into()), - (balance_high_key, balance_values[1].into()), - ]); - - // Initialize the allowance storage var. - let allowance_key_low = get_storage_var_address( - "ERC20_allowances", - &[ - *starknet_address.0.key(), - *self.environment.kakarot_address.0.key(), - ], - ); - let allowance_key_high = next_storage_key(&allowance_key_low)?; - storage.append(&mut vec![ - (allowance_key_low, u128::MAX.into()), - (allowance_key_high, u128::MAX.into()), - ]); - - // Write all the storage vars to the sequencer state. - for (k, v) in storage { - self.state_mut() - .set_storage_at(*ETH_FEE_TOKEN_ADDRESS, k, v)?; - } - Ok(()) - } - - /// Returns the storage value at the given key evm storage key. - fn storage_at(&mut self, evm_address: &Address, key: U256) -> StateResult { - let keys = split_u256(key).map(Into::into); - let key_low = get_storage_var_address(ACCOUNT_STORAGE, &keys); - let key_high = next_storage_key(&key_low)?; - - let starknet_address = self.compute_starknet_address(evm_address)?; - - let low = self.state_mut().get_storage_at(starknet_address, key_low)?; - let high = self - .state_mut() - .get_storage_at(starknet_address, key_high)?; - - let low = U256::from_be_bytes(low.to_bytes_be()); - let high = U256::from_be_bytes(high.to_bytes_be()); - - Ok(high << 128 | low) - } - - /// Returns the nonce of the given address. - fn nonce_at(&mut self, evm_address: &Address) -> StateResult { - let starknet_address = self.compute_starknet_address(evm_address)?; - - let key = get_storage_var_address(ACCOUNT_NONCE, &[]); - let nonce = self.state_mut().get_storage_at(starknet_address, key)?; - - Ok(U256::from_be_bytes(nonce.to_bytes_be())) - } - - /// Returns the bytecode of the given address. For an EOA, the bytecode_len_ storage variable will return 0, - /// and the function will return an empty vector. For a contract account, the function will return the bytecode - /// stored in the bytecode_ storage variables. The function assumes that the bytecode is stored in 31 byte big-endian chunks. - fn code_at(&mut self, evm_address: &Address) -> StateResult { - let starknet_address = self.compute_starknet_address(evm_address)?; - - let bytecode_len = self.state_mut().get_storage_at( - starknet_address, - get_storage_var_address(ACCOUNT_BYTECODE_LEN, &[]), - )?; - - let bytecode_len: u64 = bytecode_len.to_biguint().try_into()?; - - if bytecode_len == 0 { - return Ok(Bytes::default()); - } - - // Assumes that the bytecode is stored in 31 byte chunks. - let num_chunks = bytecode_len / 31; - let mut bytecode: Vec = Vec::with_capacity(bytecode_len as usize); - - for chunk_index in 0..num_chunks { - let key = StorageKey::from(chunk_index); - let code = self.state_mut().get_storage_at(starknet_address, key)?; - bytecode.append(&mut felt_to_bytes(&code, 1).to_vec()); - } - - let remainder = bytecode_len % 31; - let key = StorageKey::from(num_chunks); - let code = self.state_mut().get_storage_at(starknet_address, key)?; - bytecode.append(&mut felt_to_bytes(&code, (32 - remainder) as usize).to_vec()); - - Ok(Bytes::from(bytecode)) - } - - /// Returns the balance of native tokens at the given address. - /// Makes use of the default StateReader implementation from Blockifier. - fn balance_at(&mut self, evm_address: &Address) -> StateResult { - let starknet_address = self.compute_starknet_address(evm_address)?; - let (low, high) = self - .state_mut() - .get_fee_token_balance(starknet_address, *ETH_FEE_TOKEN_ADDRESS)?; - - let low = U256::from_be_bytes(low.to_bytes_be()); - let high = U256::from_be_bytes(high.to_bytes_be()); - - Ok(high << 128 | low) - } - - /// Converts the given signed transaction to a Starknet-rs transaction and executes it. - fn execute_transaction( - &mut self, - transaction: TransactionSigned, - ) -> TransactionExecutionResult { - let evm_address = transaction.recover_signer().ok_or_else(|| { - TransactionExecutionError::ValidateTransactionError { - error: EntryPointExecutionError::InvalidExecutionInput { - input_descriptor: String::from("Signed transaction"), - info: "Missing signer in signed transaction".to_string(), - }, - class_hash: Default::default(), - storage_address: Default::default(), - selector: Default::default(), - } - })?; - let starknet_address = self.compute_starknet_address(&evm_address)?; - let relayer_nonce = self.state_mut().get_nonce_at(*RELAYER_ADDRESS).unwrap(); - - let starknet_transaction = - BroadcastedTransactionWrapper::new(BroadcastedTransaction::Invoke( - to_broadcasted_starknet_transaction( - &transaction, - Felt::from(starknet_address), - relayer_nonce.0.into(), - ) - .map_err(|err| { - TransactionExecutionError::ValidateTransactionError { - error: EntryPointExecutionError::InvalidExecutionInput { - input_descriptor: String::from("Signed transaction"), - info: err.to_string(), - }, - class_hash: Default::default(), - storage_address: Default::default(), - selector: Default::default(), - } - })?, - )); - - let chain_id = self.chain_id(); - self.execute( - starknet_transaction - .try_into_execution_transaction(Felt::from(chain_id)) - .unwrap(), - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - evm_sequencer::{ - constants::{ - tests::{PRIVATE_KEY, PUBLIC_KEY, TEST_CONTRACT_ADDRESS}, - ACCOUNT_CONTRACT_CLASS_HASH, CAIRO1_HELPERS_CLASS_HASH, CHAIN_ID, KAKAROT_ADDRESS, - UNINITIALIZED_ACCOUNT_CLASS_HASH, - }, - sequencer::{v0::INITIAL_SEQUENCER_STATE, KakarotEnvironment}, - }, - models::result::extract_output_and_log_execution_result, - }; - use reth_primitives::{ - sign_message, AccessList, Signature, TransactionSigned, TxEip1559, B256, - }; - - #[test] - fn test_execute_simple_contract() { - // Given - let kakarot_environment = KakarotEnvironment::new( - *KAKAROT_ADDRESS, - *UNINITIALIZED_ACCOUNT_CLASS_HASH, - *CAIRO1_HELPERS_CLASS_HASH, - *ACCOUNT_CONTRACT_CLASS_HASH, - ); - let coinbase_address = Address::left_padding_from(&0xC01BA5Eu64.to_be_bytes()); - let mut sequencer = KakarotSequencer::new( - INITIAL_SEQUENCER_STATE.clone(), - kakarot_environment, - coinbase_address, - CHAIN_ID, - 0, - 1, - ); - - let mut transaction = TransactionSigned { - hash: B256::default(), - signature: Signature::default(), - transaction: reth_primitives::Transaction::Eip1559(TxEip1559 { - chain_id: CHAIN_ID, - nonce: 0, - gas_limit: 1_000_000, - max_fee_per_gas: 0, - max_priority_fee_per_gas: 0, - to: reth_primitives::TxKind::Call(*TEST_CONTRACT_ADDRESS), - value: U256::ZERO, - access_list: AccessList::default(), - input: Bytes::default(), - }), - }; - let signature = - sign_message(*PRIVATE_KEY, transaction.transaction.signature_hash()).unwrap(); - transaction.signature = signature; - let eoa_nonce = U256::from(0); - let contract_bytecode = Bytes::from(vec![96, 1, 96, 0, 85]); // PUSH 01 PUSH 00 SSTORE - let contract_nonce = U256::from(1); - - // When - let contract = KakarotAccount::new( - &TEST_CONTRACT_ADDRESS, - &contract_bytecode, - contract_nonce, - U256::ZERO, - &[], - ) - .unwrap(); - let eoa = KakarotAccount::new(&PUBLIC_KEY, &Bytes::default(), eoa_nonce, U256::ZERO, &[]) - .unwrap(); - sequencer.setup_account(contract).unwrap(); - sequencer.setup_account(eoa).unwrap(); - let execution_result = sequencer.execute_transaction(transaction); - - // Update the output with the execution result of the current transaction - let tx_output = extract_output_and_log_execution_result( - &execution_result, - "test_case", - "test_category", - ) - .unwrap_or_default(); - - assert!(tx_output.success); - - // Then - let storage = sequencer - .storage_at(&TEST_CONTRACT_ADDRESS, U256::ZERO) - .unwrap(); - - assert_eq!(storage, U256::from(1_u64)); - } -} diff --git a/crates/ef-testing/src/evm_sequencer/evm_state/v1.rs b/crates/ef-testing/src/evm_sequencer/evm_state/v1.rs deleted file mode 100644 index d59336fa..00000000 --- a/crates/ef-testing/src/evm_sequencer/evm_state/v1.rs +++ /dev/null @@ -1,424 +0,0 @@ -use blockifier::{ - abi::{ - abi_utils::{get_fee_token_var_address, get_storage_var_address}, - sierra_types::next_storage_key, - }, - execution::errors::EntryPointExecutionError, - state::state_api::{State as _, StateReader as _, StateResult}, - transaction::{ - errors::TransactionExecutionError, - objects::{TransactionExecutionInfo, TransactionExecutionResult}, - }, -}; -use reth_primitives::{Address, Bytes, TransactionSigned, U256}; -use sequencer::{execution::Execution as _, transaction::BroadcastedTransactionWrapper}; -use starknet::core::types::BroadcastedTransaction; -use starknet_api::state::StorageKey; -use starknet_crypto::Felt; - -use super::Evm; -use crate::evm_sequencer::constants::storage_variables::ACCOUNT_BYTECODE_LEN; -use crate::evm_sequencer::utils::felt_to_bytes; -use crate::{ - evm_sequencer::{ - account::KakarotAccount, - constants::{ - storage_variables::{ - ACCOUNT_IMPLEMENTATION, ACCOUNT_NONCE, ACCOUNT_STORAGE, KAKAROT_BASE_FEE, - KAKAROT_BLOCK_GAS_LIMIT, KAKAROT_COINBASE, KAKAROT_EVM_TO_STARKNET_ADDRESS, - KAKAROT_PREV_RANDAO, OWNABLE_OWNER, - }, - ETH_FEE_TOKEN_ADDRESS, KAKAROT_ADDRESS, - }, - sequencer::KakarotSequencer, - types::felt::FeltSequencer, - utils::{split_u256, to_broadcasted_starknet_transaction}, - }, - starknet_storage, -}; - -impl Evm for KakarotSequencer { - /// Sets up the evm state (coinbase, block number, etc.) - fn setup_state( - &mut self, - base_fee: U256, - prev_randao: U256, - block_gas_limit: U256, - ) -> StateResult<()> { - let kakarot_address = self.environment.kakarot_address; - let coinbase_address: FeltSequencer = (*self.address()).try_into().unwrap(); // infallible - - // Set the coinbase address. - self.state_mut().set_storage_at( - kakarot_address, - get_storage_var_address(KAKAROT_COINBASE, &[]), - coinbase_address.into(), - )?; - - // Set the base fee. - let [low_fee, high_fee] = split_u256(base_fee); - let basefee_address = get_storage_var_address(KAKAROT_BASE_FEE, &[]); - self.state_mut() - .set_storage_at(kakarot_address, basefee_address, low_fee.into())?; - self.state_mut().set_storage_at( - kakarot_address, - next_storage_key(&basefee_address)?, - high_fee.into(), - )?; - - // Set the previous randao. - let [low_prev_randao, high_prev_randao] = split_u256(prev_randao); - let prev_randao_address = get_storage_var_address(KAKAROT_PREV_RANDAO, &[]); - self.state_mut().set_storage_at( - kakarot_address, - prev_randao_address, - low_prev_randao.into(), - )?; - self.state_mut().set_storage_at( - kakarot_address, - next_storage_key(&prev_randao_address)?, - high_prev_randao.into(), - )?; - - // Set the block gas limit, considering it fits in a felt. - let [block_gas_limit, _] = split_u256(block_gas_limit); - let block_gas_limit_address = get_storage_var_address(KAKAROT_BLOCK_GAS_LIMIT, &[]); - self.state_mut().set_storage_at( - kakarot_address, - block_gas_limit_address, - block_gas_limit.into(), - )?; - - Ok(()) - } - - /// Sets up an EOA or contract account. Writes nonce, code and storage to the sequencer storage. - fn setup_account(&mut self, account: KakarotAccount) -> StateResult<()> { - let evm_address = &account.evm_address().to_bytes_be()[12..]; - let evm_address = Address::from_slice(evm_address); - let mut storage = account.storage; - let starknet_address = self.compute_starknet_address(&evm_address)?; - - self.state_mut().set_nonce(starknet_address, account.nonce); - - storage.append(&mut vec![ - starknet_storage!( - ACCOUNT_IMPLEMENTATION, - self.environment.account_contract_class_hash.0 - ), - starknet_storage!(OWNABLE_OWNER, *self.environment.kakarot_address.0.key()), - ]); - - // Write all the storage vars to the sequencer state. - for (k, v) in storage { - self.state_mut().set_storage_at(starknet_address, k, v)?; - } - - let class_hash = self.environment.account_contract_class_hash; - // Set up the contract class hash - self.state_mut() - .set_class_hash_at(starknet_address, class_hash)?; - - // Add the address to the Kakarot evm to starknet mapping - let kakarot_address = self.environment.kakarot_address; - self.state_mut().set_storage_at( - kakarot_address, - get_storage_var_address(KAKAROT_EVM_TO_STARKNET_ADDRESS, &[account.evm_address]), - *starknet_address.0.key(), - )?; - Ok(()) - } - - /// Funds an EOA or contract account. Also gives allowance to the Kakarot contract. - fn fund(&mut self, evm_address: &Address, balance: U256) -> StateResult<()> { - let starknet_address = self.compute_starknet_address(evm_address)?; - let balance_values = split_u256(balance); - let mut storage = vec![]; - - // Initialize the balance storage var. - let balance_key_low = get_fee_token_var_address(starknet_address); - let balance_key_high = next_storage_key(&balance_key_low)?; - storage.append(&mut vec![ - (balance_key_low, balance_values[0].into()), - (balance_key_high, balance_values[1].into()), - ]); - - // Initialize the allowance storage var. - let allowance_key_low = get_storage_var_address( - "ERC20_allowances", - &[*starknet_address.0.key(), *KAKAROT_ADDRESS.0.key()], - ); - let allowance_key_high = next_storage_key(&allowance_key_low)?; - storage.append(&mut vec![ - (allowance_key_low, u128::MAX.into()), - (allowance_key_high, u128::MAX.into()), - ]); - - // Write all the storage vars to the sequencer state. - for (k, v) in storage { - self.state_mut() - .set_storage_at(*ETH_FEE_TOKEN_ADDRESS, k, v)?; - } - Ok(()) - } - - /// Returns the storage value at the given key evm storage key. - fn storage_at(&mut self, evm_address: &Address, key: U256) -> StateResult { - let keys = split_u256(key).map(Into::into); - let key_low = get_storage_var_address(ACCOUNT_STORAGE, &keys); - let key_high = next_storage_key(&key_low)?; - - let starknet_address = self.compute_starknet_address(evm_address)?; - - let low = self.state_mut().get_storage_at(starknet_address, key_low)?; - let high = self - .state_mut() - .get_storage_at(starknet_address, key_high)?; - - let low = U256::from_be_bytes(low.to_bytes_be()); - let high = U256::from_be_bytes(high.to_bytes_be()); - - Ok(high << 128 | low) - } - - /// Returns the nonce of the given address. - /// Uses the Kakarot managed nonce stored in the contract account's storage. - fn nonce_at(&mut self, evm_address: &Address) -> StateResult { - let starknet_address = self.compute_starknet_address(evm_address)?; - - let key = get_storage_var_address(ACCOUNT_NONCE, &[]); - let nonce = self.state_mut().get_storage_at(starknet_address, key)?; - - Ok(U256::from_be_bytes(nonce.to_bytes_be())) - } - - /// Returns the bytecode of the given address. For an EOA, the bytecode_len_ storage variable will return 0, - /// and the function will return an empty vector. For a contract account, the function will return the bytecode - /// stored in the contract_account_bytecode storage variables. The function assumes that the bytecode is stored - /// in 31 byte big-endian chunks. - fn code_at(&mut self, evm_address: &Address) -> StateResult { - // Get all storage addresses. - let starknet_address = self.compute_starknet_address(evm_address)?; - - let bytecode_len = self.state_mut().get_storage_at( - starknet_address, - get_storage_var_address(ACCOUNT_BYTECODE_LEN, &[]), - )?; - let bytecode_len: u64 = bytecode_len.to_biguint().try_into()?; - - if bytecode_len == 0 { - return Ok(Bytes::default()); - } - - // Assumes that the bytecode is stored in 31 byte chunks. - let num_chunks = bytecode_len / 31; - let mut bytecode: Vec = Vec::with_capacity(bytecode_len as usize); - - for chunk_index in 0..num_chunks { - let key = StorageKey::from(chunk_index); - let code = self.state_mut().get_storage_at(starknet_address, key)?; - bytecode.append(&mut felt_to_bytes(&code, 1).to_vec()); - } - - let remainder = bytecode_len % 31; - let key = StorageKey::from(num_chunks); - let code = self.state_mut().get_storage_at(starknet_address, key)?; - bytecode.append(&mut felt_to_bytes(&code, (32 - remainder) as usize).to_vec()); - - Ok(Bytes::from(bytecode)) - } - - /// Returns the balance of native tokens at the given address. - /// Makes use of the default StateReader implementation from Blockifier. - fn balance_at(&mut self, evm_address: &Address) -> StateResult { - let starknet_address = self.compute_starknet_address(evm_address)?; - let (low, high) = self - .state_mut() - .get_fee_token_balance(starknet_address, *ETH_FEE_TOKEN_ADDRESS)?; - - let low = U256::from_be_bytes(low.to_bytes_be()); - let high = U256::from_be_bytes(high.to_bytes_be()); - - Ok(high << 128 | low) - } - - /// Converts the given signed transaction to a Starknet-rs transaction and executes it. - // Since we are still missing the validate for the EOA, the signature is not added - // to the transaction. - fn execute_transaction( - &mut self, - transaction: TransactionSigned, - ) -> TransactionExecutionResult { - let evm_address = transaction.recover_signer().ok_or_else(|| { - TransactionExecutionError::ValidateTransactionError { - error: EntryPointExecutionError::InvalidExecutionInput { - input_descriptor: String::from("Signed transaction"), - info: "Missing signer in signed transaction".to_string(), - }, - class_hash: Default::default(), - storage_address: Default::default(), - selector: Default::default(), - } - })?; - let starknet_address = self.compute_starknet_address(&evm_address)?; - - let starknet_transaction = - BroadcastedTransactionWrapper::new(BroadcastedTransaction::Invoke( - to_broadcasted_starknet_transaction( - &transaction, - Felt::from(starknet_address), - None, - ) - .map_err(|err| { - TransactionExecutionError::ValidateTransactionError { - error: EntryPointExecutionError::InvalidExecutionInput { - input_descriptor: String::from("Failed to convert transaction"), - info: err.to_string(), - }, - class_hash: Default::default(), - storage_address: Default::default(), - selector: Default::default(), - } - })?, - )); - - let chain_id = self.chain_id(); - self.execute( - starknet_transaction - .try_into_execution_transaction(Felt::from(chain_id)) - .unwrap(), - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - evm_sequencer::{ - constants::{ - tests::{PRIVATE_KEY, PUBLIC_KEY, TEST_CONTRACT_ADDRESS}, - ACCOUNT_CONTRACT_CLASS_HASH, CAIRO1_HELPERS_CLASS_HASH, CHAIN_ID, - UNINITIALIZED_ACCOUNT_CLASS_HASH, - }, - sequencer::{KakarotEnvironment, INITIAL_SEQUENCER_STATE}, - }, - models::result::extract_output_and_log_execution_result, - }; - use reth_primitives::{sign_message, Signature, TransactionSigned, TxLegacy, B256}; - - #[test] - fn test_store_bytecode() { - // Given - let kakarot_environment = KakarotEnvironment::new( - *KAKAROT_ADDRESS, - *UNINITIALIZED_ACCOUNT_CLASS_HASH, - *CAIRO1_HELPERS_CLASS_HASH, - *ACCOUNT_CONTRACT_CLASS_HASH, - ); - let coinbase_address = Address::left_padding_from(&1234u64.to_be_bytes()); - let mut sequencer = KakarotSequencer::new( - INITIAL_SEQUENCER_STATE.clone(), - kakarot_environment, - coinbase_address, - CHAIN_ID, - 0, - 0, - ); - let bytecode = Bytes::from(vec![ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - ]); - - // When - let account = KakarotAccount::new( - &TEST_CONTRACT_ADDRESS, - &bytecode, - U256::ZERO, - U256::ZERO, - &[], - ) - .unwrap(); - sequencer.setup_account(account).unwrap(); - - // Then - let code = sequencer.code_at(&TEST_CONTRACT_ADDRESS).unwrap(); - assert_eq!(code, bytecode); - } - - #[test] - fn test_execute_simple_contract() { - // Given - let kakarot_environment = KakarotEnvironment::new( - *KAKAROT_ADDRESS, - *UNINITIALIZED_ACCOUNT_CLASS_HASH, - *CAIRO1_HELPERS_CLASS_HASH, - *ACCOUNT_CONTRACT_CLASS_HASH, - ); - let coinbase_address = Address::left_padding_from(&0xC01BA5Eu64.to_be_bytes()); - let mut sequencer = KakarotSequencer::new( - INITIAL_SEQUENCER_STATE.clone(), - kakarot_environment, - coinbase_address, - CHAIN_ID, - 0, - 0, - ); - - let mut transaction = TransactionSigned { - hash: B256::default(), - signature: Signature::default(), - transaction: reth_primitives::Transaction::Legacy(TxLegacy { - chain_id: Some(CHAIN_ID), - gas_limit: 1_000_000, - to: reth_primitives::TxKind::Call(*TEST_CONTRACT_ADDRESS), - ..Default::default() - }), - }; - let signature = - sign_message(*PRIVATE_KEY, transaction.transaction.signature_hash()).unwrap(); - transaction.signature = signature; - let eoa_nonce = U256::from(0); - let contract_bytecode = Bytes::from(vec![96, 1, 96, 0, 85]); // PUSH 01 PUSH 00 SSTORE - let contract_nonce = U256::from(1); - - // When - let contract = KakarotAccount::new( - &TEST_CONTRACT_ADDRESS, - &contract_bytecode, - contract_nonce, - U256::ZERO, - &[], - ) - .unwrap(); - let eoa = KakarotAccount::new(&PUBLIC_KEY, &Bytes::default(), eoa_nonce, U256::ZERO, &[]) - .unwrap(); - sequencer.setup_account(contract).unwrap(); - sequencer.setup_account(eoa).unwrap(); - let execution_result = sequencer.execute_transaction(transaction); - - // Update the output with the execution result of the current transaction - let tx_output = extract_output_and_log_execution_result( - &execution_result, - "test_case", - "test_category", - ) - .unwrap_or_default(); - - assert!(tx_output.success); - - // Then - let storage = sequencer - .storage_at(&TEST_CONTRACT_ADDRESS, U256::ZERO) - .unwrap(); - - assert_eq!(storage, U256::from(1_u64)); - } -} diff --git a/crates/ef-testing/src/evm_sequencer/sequencer/mod.rs b/crates/ef-testing/src/evm_sequencer/sequencer/mod.rs index 4ec82d02..ed1dd7e3 100644 --- a/crates/ef-testing/src/evm_sequencer/sequencer/mod.rs +++ b/crates/ef-testing/src/evm_sequencer/sequencer/mod.rs @@ -1,26 +1,24 @@ -#[cfg(feature = "v0")] -pub mod v0; -#[cfg(feature = "v1")] -pub mod v1; - use blockifier::bouncer::BouncerConfig; -#[cfg(feature = "v0")] -pub use v0::INITIAL_SEQUENCER_STATE; - -#[cfg(feature = "v1")] -pub use v1::INITIAL_SEQUENCER_STATE; - -#[cfg(not(any(feature = "v0", feature = "v1")))] -use lazy_static::lazy_static; -#[cfg(not(any(feature = "v0", feature = "v1")))] -lazy_static! { - pub static ref INITIAL_SEQUENCER_STATE: State = State::default(); -} - use starknet::core::types::Felt; use std::ops::{Deref, DerefMut}; use crate::evm_sequencer::types::felt::FeltSequencer; +use crate::evm_sequencer::{ + constants::{ + storage_variables::{ + ACCOUNT_PUBLIC_KEY, ERC20_BALANCES, KAKAROT_ACCOUNT_CONTRACT_CLASS_HASH, + KAKAROT_BLOCK_GAS_LIMIT, KAKAROT_NATIVE_TOKEN_ADDRESS, + KAKAROT_UNINITIALIZED_ACCOUNT_CLASS_HASH, OWNABLE_OWNER, + }, + ACCOUNT_CONTRACT_CLASS, ACCOUNT_CONTRACT_CLASS_HASH, BLOCK_GAS_LIMIT, + ETH_FEE_TOKEN_ADDRESS, FEE_TOKEN_CLASS, FEE_TOKEN_CLASS_HASH, KAKAROT_ADDRESS, + KAKAROT_CLASS, KAKAROT_CLASS_HASH, KAKAROT_OWNER_ADDRESS, OPENZEPPELIN_ACCOUNT_CLASS, + OPENZEPPELIN_ACCOUNT_CLASS_HASH, RELAYER_ADDRESS, RELAYER_BALANCE, RELAYER_VERIFYING_KEY, + STRK_FEE_TOKEN_ADDRESS, UNINITIALIZED_ACCOUNT_CLASS, UNINITIALIZED_ACCOUNT_CLASS_HASH, + }, + types::contract_class::CasmContractClassWrapper, + utils::compute_starknet_address, +}; use blockifier::blockifier::block::{BlockInfo, GasPrices}; use blockifier::context::ChainInfo; use blockifier::context::{BlockContext, FeeTokenAddresses}; @@ -40,11 +38,18 @@ use starknet_api::{ }; use std::num::NonZeroU128; -use super::{ - constants::{ETH_FEE_TOKEN_ADDRESS, STRK_FEE_TOKEN_ADDRESS}, - types::contract_class::CasmContractClassWrapper, - utils::compute_starknet_address, +#[cfg(feature = "v0")] +use crate::evm_sequencer::constants::{ + storage_variables::KAKAROT_CAIRO1_HELPERS_CLASS_HASH, CAIRO1_HELPERS_CLASS, + CAIRO1_HELPERS_CLASS_HASH, }; +use blockifier::abi::abi_utils::get_storage_var_address; +#[allow(unused_imports)] +use blockifier::state::state_api::{ + State as BlockifierState, StateReader as BlockifierStateReader, +}; +use lazy_static::lazy_static; +use sequencer::state::State as SequencerState; /// Kakarot wrapper around a sequencer. #[derive(Clone)] @@ -197,3 +202,81 @@ pub fn convert_contract_class_v1(class: &CompiledClass) -> Result