diff --git a/.gitignore b/.gitignore index 61f4a4a9..6b863e65 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ .env vscode +.idea/ broadcast .DS_Store diff --git a/crates/ef-testing/src/evm_sequencer/account/v0.rs b/crates/ef-testing/src/evm_sequencer/account/v0.rs index 2900be0a..e66485e0 100644 --- a/crates/ef-testing/src/evm_sequencer/account/v0.rs +++ b/crates/ef-testing/src/evm_sequencer/account/v0.rs @@ -83,7 +83,7 @@ impl KakarotAccount { }; let jumdpests_storage_address = get_storage_var_address(ACCOUNT_VALID_JUMPDESTS, &[]); - let jumdpests_storage_address = *jumdpests_storage_address.0.key(); + let jumdpests_storage_address = Felt::from(jumdpests_storage_address); valid_jumpdests.into_iter().for_each(|index| { storage.push(( (jumdpests_storage_address + Felt::from(index)) diff --git a/crates/ef-testing/src/evm_sequencer/account/v1.rs b/crates/ef-testing/src/evm_sequencer/account/v1.rs index b34f5371..2bca3ad6 100644 --- a/crates/ef-testing/src/evm_sequencer/account/v1.rs +++ b/crates/ef-testing/src/evm_sequencer/account/v1.rs @@ -1,56 +1,28 @@ use blockifier::abi::abi_utils::get_storage_var_address; -use reth_primitives::{Address, Bytes, U256}; +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::KakarotAccount; use super::{inner_byte_array_pointer, pack_byte_array_to_starkfelt_array}; use crate::evm_sequencer::constants::storage_variables::{ - ACCOUNT_EVM_ADDRESS, ACCOUNT_IS_INITIALIZED, ACCOUNT_NONCE, ACCOUNT_STORAGE, + ACCOUNT_BYTECODE_LEN, ACCOUNT_CODE_HASH, ACCOUNT_EVM_ADDRESS, ACCOUNT_IS_INITIALIZED, + ACCOUNT_NONCE, ACCOUNT_STORAGE, ACCOUNT_VALID_JUMPDESTS, }; use crate::evm_sequencer::{ constants::storage_variables::ACCOUNT_BYTECODE, types::felt::FeltSequencer, utils::split_u256, }; -use crate::{ - evm_sequencer::evm_state::v1::{compute_storage_base_address, offset_storage_key}, - starknet_storage, -}; - -/// The layout of a `ByteArray` in storage is as follows: -/// * Only the length in bytes is stored in the original address where the byte array is logically -/// stored. -/// * The actual data is stored in chunks of 256 `bytes31`s in another place in storage -/// determined by the hash of: -/// - The address storing the length of the array. -/// - The chunk index. -/// - The short string `ByteArray`. -fn prepare_bytearray_storage(code: &Bytes) -> Vec<(StorageKey, Felt)> { - let bytecode_base_address = get_storage_var_address(ACCOUNT_BYTECODE, &[]); - let mut bytearray = vec![(bytecode_base_address, Felt::from(code.len()))]; - - let bytecode_storage: Vec<_> = pack_byte_array_to_starkfelt_array(code) - .enumerate() - .map(|(index, b)| { - let offset = index % 256; - let index = index / 256; - let key = inner_byte_array_pointer(*bytecode_base_address.0.key(), index.into()); - ( - offset_storage_key(key.try_into().unwrap(), offset as i64), - b, - ) - }) - .collect(); - bytearray.extend(bytecode_storage); - - bytearray -} +use crate::starknet_storage; impl KakarotAccount { pub fn new( evm_address: &Address, code: &Bytes, nonce: U256, - _balance: U256, + balance: U256, evm_storage: &[(U256, U256)], ) -> Result { let nonce = Felt::from(TryInto::::try_into(nonce).map_err(|err| { @@ -66,25 +38,70 @@ impl KakarotAccount { 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 vars. - // Assumes that the bytecode is stored as a ByteArray type, following the Store implementation of - // the cairo core library - let mut bytecode_storage = prepare_bytearray_storage(code); + // 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 = compute_storage_base_address(ACCOUNT_STORAGE, &keys); - let high_key = offset_storage_key(low_key, 1); + 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(); @@ -97,35 +114,3 @@ impl KakarotAccount { }) } } - -#[cfg(test)] -mod tests { - use super::*; - use starknet::core::types::Felt; - - #[test] - fn test_prepare_bytearray_storage() { - // Given - let code = Bytes::from(vec![0x01, 0x02, 0x03, 0x04, 0x05]); - let bytecode_base_address = get_storage_var_address(ACCOUNT_BYTECODE, &[]); - - // When - let result = prepare_bytearray_storage(&code); - - // Then - let expected_result = vec![ - (bytecode_base_address, Felt::from(code.len())), - ( - offset_storage_key( - inner_byte_array_pointer(*bytecode_base_address.0.key(), Felt::ZERO) - .try_into() - .unwrap(), - 0, - ), - Felt::from(0x0102030405u64), - ), - ]; - - assert_eq!(result, expected_result); - } -} diff --git a/crates/ef-testing/src/evm_sequencer/constants.rs b/crates/ef-testing/src/evm_sequencer/constants.rs index 7b1623f3..823e8d1c 100644 --- a/crates/ef-testing/src/evm_sequencer/constants.rs +++ b/crates/ef-testing/src/evm_sequencer/constants.rs @@ -4,9 +4,9 @@ use lazy_static::lazy_static; use reth_primitives::alloy_primitives::{address, Address}; use serde::de::DeserializeOwned; use starknet::core::types::contract::CompiledClass; -use starknet_api::felt; use starknet::signers::VerifyingKey; use starknet::{core::types::contract::legacy::LegacyContractClass, signers::SigningKey}; +use starknet_api::felt; use starknet_api::{ contract_address, core::{ClassHash, ContractAddress, PatriciaKey}, @@ -86,7 +86,7 @@ lazy_static! { // Main contract classes v1 pub static ref KAKAROT_CLASS: CompiledClass = load_contract_class("../../build/v1/contracts_KakarotCore.compiled_contract_class.json").expect("Failed to load Kakarot contract class"); pub static ref ACCOUNT_CONTRACT_CLASS: CompiledClass = load_contract_class("../../build/v1/contracts_AccountContract.compiled_contract_class.json").expect("Failed to load ContractAccount contract class"); - pub static ref UNINITIALIZED_ACCOUNT_CLASS: CompiledClass = load_contract_class("../../build/v1/contracts_UninitializedAccount.compiled_contract_class.json").expect("Failed to load uninitialized account c contract class"); + pub static ref UNINITIALIZED_ACCOUNT_CLASS: CompiledClass = load_contract_class("../../build/v1/contracts_UninitializedAccount.compiled_contract_class.json").expect("Failed to load uninitialized account contract class"); // Main class hashes pub static ref KAKAROT_CLASS_HASH: ClassHash = ClassHash(KAKAROT_CLASS.class_hash().unwrap()); @@ -116,7 +116,7 @@ pub mod storage_variables { pub const ACCOUNT_NONCE: &str = "Account_nonce"; pub const ACCOUNT_KAKAROT_ADDRESS: &str = "Account_kakarot_address"; pub const ACCOUNT_IMPLEMENTATION: &str = "Account_implementation"; - pub const ACCOUNT_VALID_JUMPDESTS : &str = "Account_valid_jumpdests"; + pub const ACCOUNT_VALID_JUMPDESTS: &str = "Account_valid_jumpdests"; pub const ACCOUNT_PUBLIC_KEY: &str = "Account_public_key"; pub const ACCOUNT_CODE_HASH: &str = "Account_code_hash"; @@ -152,4 +152,4 @@ pub mod tests { pub static ref TEST_CONTRACT_ADDRESS: Address = Address::left_padding_from(&0xdeadbeefu64.to_be_bytes()); } -} \ No newline at end of file +} diff --git a/crates/ef-testing/src/evm_sequencer/evm_state/v1.rs b/crates/ef-testing/src/evm_sequencer/evm_state/v1.rs index 6d1c7b1b..844ba177 100644 --- a/crates/ef-testing/src/evm_sequencer/evm_state/v1.rs +++ b/crates/ef-testing/src/evm_sequencer/evm_state/v1.rs @@ -18,6 +18,8 @@ use starknet_api::{core::L2_ADDRESS_UPPER_BOUND, state::StorageKey}; use starknet_crypto::{poseidon_hash_many, 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::{inner_byte_array_pointer, KakarotAccount}, @@ -185,13 +187,8 @@ impl Evm for KakarotSequencer { fn nonce_at(&mut self, evm_address: &Address) -> StateResult { let starknet_address = self.compute_starknet_address(evm_address)?; - let nonce = self - .state_mut() - .get_storage_at( - starknet_address, - get_storage_var_address(ACCOUNT_NONCE, &[]), - ) - .unwrap(); + 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())) } @@ -203,47 +200,31 @@ impl Evm for KakarotSequencer { fn code_at(&mut self, evm_address: &Address) -> StateResult { // Get all storage addresses. let starknet_address = self.compute_starknet_address(evm_address)?; - let bytecode_base_address = get_storage_var_address(ACCOUNT_BYTECODE, &[]); - // Handle early return. - let bytecode_len = self - .state_mut() - .get_storage_at(starknet_address, bytecode_base_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()); } - // Bytecode is stored in chunks of 31 bytes. At bytecode_base_address, - // we store the number of chunks. - let (num_chunks, pending_word_len) = bytecode_len.div_rem(&31); - let mut bytecode: Vec = Vec::with_capacity(bytecode_len as usize * 31); + // 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 index = chunk_index / 256; - let offset = chunk_index % 256; - let storage_pointer = - inner_byte_array_pointer(*bytecode_base_address.0.key(), Felt::from(index)); - let key = offset_storage_key(storage_pointer.try_into().unwrap(), offset as i64); + let key = StorageKey::from(chunk_index); let code = self.state_mut().get_storage_at(starknet_address, key)?; - bytecode.append(&mut code.to_bytes_be()[1..].to_vec()); + bytecode.append(&mut felt_to_bytes(&code, 1).to_vec()); } - if pending_word_len != 0 { - let storage_chunk_index = num_chunks / 256; - let offset_in_chunk = num_chunks % 256; - let storage_pointer = inner_byte_array_pointer( - *bytecode_base_address.0.key(), - storage_chunk_index.into(), - ); - let key = - offset_storage_key(storage_pointer.try_into().unwrap(), offset_in_chunk as i64); - - let pending_word = self.state_mut().get_storage_at(starknet_address, key)?; - bytecode - .append(&mut pending_word.to_bytes_be()[32 - pending_word_len as usize..].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)) } @@ -321,11 +302,6 @@ pub(crate) fn compute_storage_base_address(storage_var_name: &str, keys: &[Felt] key_floored.try_into().unwrap() // infallible } -pub(crate) fn offset_storage_key(key: StorageKey, offset: i64) -> StorageKey { - let base_address = *key.0.key() + Felt::from(offset); - base_address.try_into().unwrap() // infallible -} - #[cfg(test)] mod tests { use super::*; @@ -343,20 +319,6 @@ mod tests { use reth_primitives::{sign_message, Signature, TransactionSigned, TxLegacy, B256}; use starknet::core::types::Felt; - #[test] - fn test_offset_storage_base_address() { - // Given - let base_address = StorageKey(Felt::from(0x0102030405060708u64).try_into().unwrap()); - let offset = -1; - - // When - let result = offset_storage_key(base_address, offset); - - // Then - let expected = StorageKey(Felt::from(0x0102030405060707u64).try_into().unwrap()); - assert!(result == expected); - } - #[test] fn test_store_bytecode() { // Given diff --git a/crates/ef-testing/src/evm_sequencer/utils.rs b/crates/ef-testing/src/evm_sequencer/utils.rs index 4856a23b..5d6fb3ae 100644 --- a/crates/ef-testing/src/evm_sequencer/utils.rs +++ b/crates/ef-testing/src/evm_sequencer/utils.rs @@ -116,12 +116,14 @@ pub fn to_broadcasted_starknet_transaction( } #[cfg(feature = "v1")] { - vec![ + let mut execute_calldata = vec![ Felt::ONE, // call_array_len *KAKAROT_ADDRESS.0.key(), // CallArray to selector!("eth_send_transaction"), // CallArray selector calldata.len().into(), // CallArray data_len - ] + ]; + execute_calldata.append(&mut calldata); + execute_calldata } #[cfg(not(any(feature = "v0", feature = "v1")))] {