diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index 595edfa7..eb47e5cb 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -35,6 +35,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/core/terminate.asm"), include_str!("asm/core/transfer.asm"), include_str!("asm/core/util.asm"), + include_str!("asm/core/access_lists.asm"), include_str!("asm/curve/bls381/util.asm"), include_str!("asm/curve/bn254/curve_arithmetic/constants.asm"), include_str!("asm/curve/bn254/curve_arithmetic/curve_add.asm"), diff --git a/evm/src/cpu/kernel/asm/account_code.asm b/evm/src/cpu/kernel/asm/account_code.asm index 10d0a686..8cbc3221 100644 --- a/evm/src/cpu/kernel/asm/account_code.asm +++ b/evm/src/cpu/kernel/asm/account_code.asm @@ -1,8 +1,18 @@ global sys_extcodehash: // stack: kexit_info, address - // TODO: Charge gas. - SWAP1 + SWAP1 %u256_to_addr // stack: address, kexit_info + DUP1 %insert_accessed_addresses + // stack: cold_access, address, kexit_info + PUSH @GAS_COLDACCOUNTACCESS_MINUS_WARMACCESS + MUL + PUSH @GAS_WARMACCESS + ADD + %stack (gas, address, kexit_info) -> (gas, kexit_info, address) + %charge_gas + // stack: kexit_info, address + + SWAP1 %extcodehash // stack: hash, kexit_info SWAP1 @@ -41,7 +51,18 @@ retzero: global sys_extcodesize: // stack: kexit_info, address - // TODO: Charge gas. + SWAP1 %u256_to_addr + // stack: address, kexit_info + DUP1 %insert_accessed_addresses + // stack: cold_access, address, kexit_info + PUSH @GAS_COLDACCOUNTACCESS_MINUS_WARMACCESS + MUL + PUSH @GAS_WARMACCESS + ADD + %stack (gas, address, kexit_info) -> (gas, kexit_info, address) + %charge_gas + // stack: kexit_info, address + SWAP1 // stack: address, kexit_info %extcodesize @@ -69,6 +90,7 @@ global sys_extcodecopy: // TODO: Charge other gas. %stack (kexit_info, address, dest_offset, offset, size) -> (address, dest_offset, offset, size, kexit_info) + %u256_to_addr DUP1 %insert_accessed_addresses POP // TODO: Use return value in gas calculation. %extcodecopy // stack: kexit_info EXIT_KERNEL diff --git a/evm/src/cpu/kernel/asm/balance.asm b/evm/src/cpu/kernel/asm/balance.asm index 1c6429d2..f175d027 100644 --- a/evm/src/cpu/kernel/asm/balance.asm +++ b/evm/src/cpu/kernel/asm/balance.asm @@ -1,7 +1,17 @@ global sys_balance: // stack: kexit_info, address - // TODO: assuming a cold account access for now. - %charge_gas_const(@GAS_COLDACCOUNTACCESS) + SWAP1 %u256_to_addr + // stack: address, kexit_info + DUP1 %insert_accessed_addresses + // stack: cold_access, address, kexit_info + PUSH @GAS_COLDACCOUNTACCESS_MINUS_WARMACCESS + MUL + PUSH @GAS_WARMACCESS + ADD + %stack (gas, address, kexit_info) -> (gas, kexit_info, address) + %charge_gas + // stack: kexit_info, address + SWAP1 // stack: address, kexit_info %balance diff --git a/evm/src/cpu/kernel/asm/core/access_lists.asm b/evm/src/cpu/kernel/asm/core/access_lists.asm new file mode 100644 index 00000000..80d96e43 --- /dev/null +++ b/evm/src/cpu/kernel/asm/core/access_lists.asm @@ -0,0 +1,102 @@ +/// Access lists for addresses and storage keys. +/// The access list is stored in an array. The length of the array is stored in the global metadata. +/// For storage keys, the address and key are stored as two consecutive elements. +/// The array is stored in the SEGMENT_ACCESSED_ADDRESSES segment for addresses and in the SEGMENT_ACCESSED_STORAGE_KEYS segment for storage keys. +/// Both arrays are stored in the kernel memory (context=0). +/// Searching and inserting is done by doing a linear search through the array. +/// If the address/storage key isn't found in the array, it is inserted at the end. +/// TODO: Look into using a more efficient data structure for the access lists. + +%macro insert_accessed_addresses + %stack (addr) -> (addr, %%after) + %jump(insert_accessed_addresses) +%%after: + // stack: cold_access +%endmacro + +%macro insert_accessed_addresses_no_return + %insert_accessed_addresses + POP +%endmacro + +/// Inserts the address into the access list if it is not already present. +/// Return 1 if the address was inserted, 0 if it was already present. +global insert_accessed_addresses: + // stack: addr, retdest + %mload_global_metadata(@GLOBAL_METADATA_ACCESSED_ADDRESSES_LEN) + // stack: len, addr, retdest + PUSH 0 +insert_accessed_addresses_loop: + %stack (i, len, addr, retdest) -> (i, len, i, len, addr, retdest) + EQ %jumpi(insert_address) + // stack: i, len, addr, retdest + DUP1 %mload_kernel(@SEGMENT_ACCESSED_ADDRESSES) + // stack: loaded_addr, i, len, addr, retdest + DUP4 + // stack: addr, loaded_addr, i, len, addr, retdest + EQ %jumpi(insert_accessed_addresses_found) + // stack: i, len, addr, retdest + %increment + %jump(insert_accessed_addresses_loop) + +insert_address: + %stack (i, len, addr, retdest) -> (i, addr, len, retdest) + %mstore_kernel(@SEGMENT_ACCESSED_ADDRESSES) // Store new address at the end of the array. + // stack: len, retdest + %increment + %mstore_global_metadata(@GLOBAL_METADATA_ACCESSED_ADDRESSES_LEN) // Store new length. + PUSH 1 // Return 1 to indicate that the address was inserted. + SWAP1 JUMP + +insert_accessed_addresses_found: + %stack (i, len, addr, retdest) -> (retdest, 0) // Return 0 to indicate that the address was already present. + JUMP + + +%macro insert_accessed_storage_keys + %stack (addr, key) -> (addr, key, %%after) + %jump(insert_accessed_storage_keys) +%%after: + // stack: cold_access +%endmacro + +/// Inserts the storage key into the access list if it is not already present. +/// Return 1 if the storage key was inserted, 0 if it was already present. +global insert_accessed_storage_keys: + // stack: addr, key, retdest + %mload_global_metadata(@GLOBAL_METADATA_ACCESSED_STORAGE_KEYS_LEN) + // stack: len, addr, key, retdest + PUSH 0 +insert_accessed_storage_keys_loop: + %stack (i, len, addr, key, retdest) -> (i, len, i, len, addr, key, retdest) + EQ %jumpi(insert_storage_key) + // stack: i, len, addr, key, retdest + DUP1 %increment %mload_kernel(@SEGMENT_ACCESSED_STORAGE_KEYS) + // stack: loaded_key, i, len, addr, key, retdest + DUP2 %mload_kernel(@SEGMENT_ACCESSED_STORAGE_KEYS) + // stack: loaded_addr, loaded_key, i, len, addr, key, retdest + DUP5 EQ + // stack: loaded_addr==addr, loaded_key, i, len, addr, key, retdest + SWAP1 DUP6 EQ + // stack: loaded_key==key, loaded_addr==addr, i, len, addr, key, retdest + MUL // AND + %jumpi(insert_accessed_storage_keys_found) + // stack: i, len, addr, key, retdest + %add_const(2) + %jump(insert_accessed_storage_keys_loop) + +insert_storage_key: + // stack: i, len, addr, key, retdest + DUP1 %increment + %stack (i_plus_1, i, len, addr, key, retdest) -> (i, addr, i_plus_1, key, i_plus_1, retdest) + %mstore_kernel(@SEGMENT_ACCESSED_STORAGE_KEYS) // Store new address at the end of the array. + %mstore_kernel(@SEGMENT_ACCESSED_STORAGE_KEYS) // Store new key after that + // stack: i_plus_1, retdest + %increment + %mstore_global_metadata(@GLOBAL_METADATA_ACCESSED_STORAGE_KEYS_LEN) // Store new length in front of the array. + PUSH 1 // Return 1 to indicate that the storage key was inserted. + SWAP1 JUMP + +insert_accessed_storage_keys_found: + %stack (i, len, addr, key, retdest) -> (retdest, 0) // Return 0 to indicate that the storage key was already present. + JUMP diff --git a/evm/src/cpu/kernel/asm/core/call.asm b/evm/src/cpu/kernel/asm/core/call.asm index 103977b2..28f0f6e2 100644 --- a/evm/src/cpu/kernel/asm/core/call.asm +++ b/evm/src/cpu/kernel/asm/core/call.asm @@ -4,6 +4,12 @@ global sys_call: // stack: kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size // TODO: Charge gas. + SWAP2 + // stack: address, gas, kexit_info, value, args_offset, args_size, ret_offset, ret_size + %u256_to_addr // Truncate to 160 bits + DUP1 %insert_accessed_addresses POP // TODO: Use return value in gas calculation. + SWAP2 + // stack: kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size %create_context // stack: new_ctx, kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size @@ -29,6 +35,12 @@ global sys_call: global sys_callcode: // stack: kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size // TODO: Charge gas. + SWAP2 + // stack: address, gas, kexit_info, value, args_offset, args_size, ret_offset, ret_size + %u256_to_addr // Truncate to 160 bits + DUP1 %insert_accessed_addresses POP // TODO: Use return value in gas calculation. + SWAP2 + // stack: kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size %create_context // stack: new_ctx, kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size @@ -53,6 +65,12 @@ global sys_callcode: global sys_staticcall: // stack: kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size // TODO: Charge gas. + SWAP2 + // stack: address, gas, kexit_info, args_offset, args_size, ret_offset, ret_size + %u256_to_addr // Truncate to 160 bits + DUP1 %insert_accessed_addresses POP // TODO: Use return value in gas calculation. + SWAP2 + // stack: kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size %create_context // stack: new_ctx, kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size @@ -74,6 +92,12 @@ global sys_staticcall: global sys_delegatecall: // stack: kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size // TODO: Charge gas. + SWAP2 + // stack: address, gas, kexit_info, args_offset, args_size, ret_offset, ret_size + %u256_to_addr // Truncate to 160 bits + DUP1 %insert_accessed_addresses POP // TODO: Use return value in gas calculation. + SWAP2 + // stack: kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size %create_context // stack: new_ctx, kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size diff --git a/evm/src/cpu/kernel/asm/core/create.asm b/evm/src/cpu/kernel/asm/core/create.asm index 5360e616..3157a262 100644 --- a/evm/src/cpu/kernel/asm/core/create.asm +++ b/evm/src/cpu/kernel/asm/core/create.asm @@ -73,6 +73,7 @@ sys_create2_finish: // Note: CODE_ADDR refers to a (context, segment, offset) tuple. global create_inner: // stack: address, sender, endowment, CODE_ADDR, code_len, retdest + DUP1 %insert_accessed_addresses_no_return %stack (address, sender, endowment) -> (sender, address, endowment, sender, address) diff --git a/evm/src/cpu/kernel/asm/core/process_txn.asm b/evm/src/cpu/kernel/asm/core/process_txn.asm index acce98f9..1c30d88a 100644 --- a/evm/src/cpu/kernel/asm/core/process_txn.asm +++ b/evm/src/cpu/kernel/asm/core/process_txn.asm @@ -81,6 +81,8 @@ global process_contract_creation_txn: // stack: account_ptr, address, retdest DUP2 // stack: address, account_ptr, address, retdest + %addr_to_state_key + // stack: state_key, account_ptr, address, retdest %mpt_insert_state_trie // stack: address, retdest @@ -127,13 +129,25 @@ global process_message_txn: // stack: retdest %mload_txn_field(@TXN_FIELD_VALUE) %mload_txn_field(@TXN_FIELD_TO) + DUP1 %insert_accessed_addresses_no_return %mload_txn_field(@TXN_FIELD_ORIGIN) + DUP1 %insert_accessed_addresses_no_return // stack: from, to, amount, retdest %transfer_eth // stack: transfer_eth_status, retdest %jumpi(process_message_txn_insufficient_balance) // stack: retdest + // Add precompiles to accessed addresses. + PUSH @ECREC %insert_accessed_addresses_no_return + PUSH @SHA256 %insert_accessed_addresses_no_return + PUSH @RIP160 %insert_accessed_addresses_no_return + PUSH @ID %insert_accessed_addresses_no_return + PUSH @EXPMOD %insert_accessed_addresses_no_return + PUSH @BN_ADD %insert_accessed_addresses_no_return + PUSH @BN_MUL %insert_accessed_addresses_no_return + PUSH @SNARKV %insert_accessed_addresses_no_return + PUSH @BLAKE2_F %insert_accessed_addresses_no_return // TODO: Handle precompiles. // If to's code is empty, return. diff --git a/evm/src/cpu/kernel/asm/core/terminate.asm b/evm/src/cpu/kernel/asm/core/terminate.asm index 26fb113c..c124db34 100644 --- a/evm/src/cpu/kernel/asm/core/terminate.asm +++ b/evm/src/cpu/kernel/asm/core/terminate.asm @@ -19,8 +19,13 @@ global sys_return: %jump(terminate_common) global sys_selfdestruct: - // stack: kexit_info + // stack: kexit_info, address + SWAP1 %u256_to_addr + DUP1 %insert_accessed_addresses_no_return // TODO: Use return value in gas calculation. + // stack: address, kexit_info + SWAP1 // TODO: Charge gas. + // TODO: Add address to the access list. %consume_gas_const(@GAS_SELFDESTRUCT) %leftover_gas // stack: leftover_gas diff --git a/evm/src/cpu/kernel/asm/mpt/storage/storage_read.asm b/evm/src/cpu/kernel/asm/mpt/storage/storage_read.asm index cf7ed1e9..edb63e2d 100644 --- a/evm/src/cpu/kernel/asm/mpt/storage/storage_read.asm +++ b/evm/src/cpu/kernel/asm/mpt/storage/storage_read.asm @@ -5,9 +5,19 @@ global sys_sload: // stack: kexit_info, slot - // TODO: Charge gas. SWAP1 // stack: slot, kexit_info + DUP1 %address + // stack: addr, slot, slot, kexit_info + %insert_accessed_storage_keys PUSH @GAS_COLDSLOAD_MINUS_WARMACCESS + MUL + PUSH @GAS_WARMACCESS + ADD + %stack (gas, slot, kexit_info) -> (gas, kexit_info, slot) + %charge_gas + // stack: kexit_info, slot + + SWAP1 %stack (slot) -> (slot, after_storage_read) %slot_to_storage_key // stack: storage_key, after_storage_read, kexit_info diff --git a/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm b/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm index f56a5fdf..90fb0e0b 100644 --- a/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm +++ b/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm @@ -4,6 +4,8 @@ // Post stack: (empty) global sys_sstore: + %stack (kexit_info, slot, value) -> (slot, kexit_info, slot, value) + %address %insert_accessed_storage_keys POP // TODO: Use return value in gas calculation. // TODO: Assuming a cold zero -> nonzero write for now. PUSH @GAS_COLDSLOAD PUSH @GAS_SSET diff --git a/evm/src/cpu/kernel/asm/util/basic_macros.asm b/evm/src/cpu/kernel/asm/util/basic_macros.asm index 476f1c74..2ba6ad0b 100644 --- a/evm/src/cpu/kernel/asm/util/basic_macros.asm +++ b/evm/src/cpu/kernel/asm/util/basic_macros.asm @@ -345,3 +345,8 @@ %endrep // stack: a || b || c || d %endmacro + +%macro u256_to_addr + // stack: x + %mod_const(0x10000000000000000000000000000000000000000) // 2^160 +%endmacro diff --git a/evm/src/cpu/kernel/constants/global_metadata.rs b/evm/src/cpu/kernel/constants/global_metadata.rs index e9d694eb..f04c06e1 100644 --- a/evm/src/cpu/kernel/constants/global_metadata.rs +++ b/evm/src/cpu/kernel/constants/global_metadata.rs @@ -45,10 +45,14 @@ pub(crate) enum GlobalMetadata { /// Gas to refund at the end of the transaction. RefundCounter = 22, + /// Length of the addresses access list. + AccessedAddressesLen = 23, + /// Length of the storage keys access list. + AccessedStorageKeysLen = 24, } impl GlobalMetadata { - pub(crate) const COUNT: usize = 22; + pub(crate) const COUNT: usize = 24; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -74,6 +78,8 @@ impl GlobalMetadata { Self::BlockChainId, Self::BlockBaseFee, Self::RefundCounter, + Self::AccessedAddressesLen, + Self::AccessedStorageKeysLen, ] } @@ -102,6 +108,8 @@ impl GlobalMetadata { Self::BlockChainId => "GLOBAL_METADATA_BLOCK_CHAIN_ID", Self::BlockBaseFee => "GLOBAL_METADATA_BLOCK_BASE_FEE", Self::RefundCounter => "GLOBAL_METADATA_REFUND_COUNTER", + Self::AccessedAddressesLen => "GLOBAL_METADATA_ACCESSED_ADDRESSES_LEN", + Self::AccessedStorageKeysLen => "GLOBAL_METADATA_ACCESSED_STORAGE_KEYS_LEN", } } } diff --git a/evm/src/cpu/kernel/constants/mod.rs b/evm/src/cpu/kernel/constants/mod.rs index d0a079b8..2889e7c7 100644 --- a/evm/src/cpu/kernel/constants/mod.rs +++ b/evm/src/cpu/kernel/constants/mod.rs @@ -32,6 +32,10 @@ pub fn evm_constants() -> HashMap { c.insert(name.into(), U256::from(value)); } + for (name, value) in PRECOMPILES { + c.insert(name.into(), U256::from(value)); + } + for segment in Segment::all() { c.insert(segment.var_name().into(), (segment as u32).into()); } @@ -151,7 +155,7 @@ const EC_CONSTANTS: [(&str, [u8; 32]); 18] = [ ), ]; -const GAS_CONSTANTS: [(&str, u16); 36] = [ +const GAS_CONSTANTS: [(&str, u16); 38] = [ ("GAS_ZERO", 0), ("GAS_JUMPDEST", 1), ("GAS_BASE", 2), @@ -163,7 +167,9 @@ const GAS_CONSTANTS: [(&str, u16); 36] = [ ("GAS_ACCESSLISTADDRESS", 2_400), ("GAS_ACCESSLISTSTORAGE", 1_900), ("GAS_COLDACCOUNTACCESS", 2_600), + ("GAS_COLDACCOUNTACCESS_MINUS_WARMACCESS", 2_500), ("GAS_COLDSLOAD", 2_100), + ("GAS_COLDSLOAD_MINUS_WARMACCESS", 2_000), ("GAS_SSET", 20_000), ("GAS_SRESET", 2_900), ("REFUND_SCLEAR", 15_000), @@ -189,3 +195,15 @@ const GAS_CONSTANTS: [(&str, u16); 36] = [ ("GAS_COPY", 3), ("GAS_BLOCKHASH", 20), ]; + +const PRECOMPILES: [(&str, u16); 9] = [ + ("ECREC", 1), + ("SHA256", 2), + ("RIP160", 3), + ("ID", 4), + ("EXPMOD", 5), + ("BN_ADD", 6), + ("BN_MUL", 7), + ("SNARKV", 8), + ("BLAKE2_F", 9), +]; diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 408671ba..8347d896 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -59,6 +59,7 @@ pub fn run_interpreter( ) } +#[derive(Clone)] pub struct InterpreterMemoryInitialization { pub label: String, pub stack: Vec, diff --git a/evm/src/cpu/kernel/tests/core/access_lists.rs b/evm/src/cpu/kernel/tests/core/access_lists.rs new file mode 100644 index 00000000..2b647811 --- /dev/null +++ b/evm/src/cpu/kernel/tests/core/access_lists.rs @@ -0,0 +1,195 @@ +use std::collections::HashSet; + +use anyhow::Result; +use ethereum_types::{Address, U256}; +use rand::{thread_rng, Rng}; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::constants::global_metadata::GlobalMetadata::{ + AccessedAddressesLen, AccessedStorageKeysLen, +}; +use crate::cpu::kernel::interpreter::Interpreter; +use crate::memory::segments::Segment::{AccessedAddresses, AccessedStorageKeys, GlobalMetadata}; +use crate::witness::memory::MemoryAddress; + +#[test] +fn test_insert_accessed_addresses() -> Result<()> { + let insert_accessed_addresses = KERNEL.global_labels["insert_accessed_addresses"]; + + let retaddr = 0xdeadbeefu32.into(); + let mut rng = thread_rng(); + let n = rng.gen_range(1..10); + let addresses = (0..n) + .map(|_| rng.gen::
()) + .collect::>() + .into_iter() + .collect::>(); + let addr_in_list = addresses[rng.gen_range(0..n)]; + let addr_not_in_list = rng.gen::
(); + assert!( + !addresses.contains(&addr_not_in_list), + "Cosmic luck or bad RNG?" + ); + + // Test for address already in list. + let initial_stack = vec![retaddr, U256::from(addr_in_list.0.as_slice())]; + let mut interpreter = Interpreter::new_with_kernel(insert_accessed_addresses, initial_stack); + for i in 0..n { + let addr = U256::from(addresses[i].0.as_slice()); + interpreter + .generation_state + .memory + .set(MemoryAddress::new(0, AccessedAddresses, i), addr); + } + interpreter.generation_state.memory.set( + MemoryAddress::new(0, GlobalMetadata, AccessedAddressesLen as usize), + U256::from(n), + ); + interpreter.run()?; + assert_eq!(interpreter.stack(), &[U256::zero()]); + assert_eq!( + interpreter.generation_state.memory.get(MemoryAddress::new( + 0, + GlobalMetadata, + AccessedAddressesLen as usize + )), + U256::from(n) + ); + + // Test for address not in list. + let initial_stack = vec![retaddr, U256::from(addr_not_in_list.0.as_slice())]; + let mut interpreter = Interpreter::new_with_kernel(insert_accessed_addresses, initial_stack); + for i in 0..n { + let addr = U256::from(addresses[i].0.as_slice()); + interpreter + .generation_state + .memory + .set(MemoryAddress::new(0, AccessedAddresses, i), addr); + } + interpreter.generation_state.memory.set( + MemoryAddress::new(0, GlobalMetadata, AccessedAddressesLen as usize), + U256::from(n), + ); + interpreter.run()?; + assert_eq!(interpreter.stack(), &[U256::one()]); + assert_eq!( + interpreter.generation_state.memory.get(MemoryAddress::new( + 0, + GlobalMetadata, + AccessedAddressesLen as usize + )), + U256::from(n + 1) + ); + assert_eq!( + interpreter + .generation_state + .memory + .get(MemoryAddress::new(0, AccessedAddresses, n)), + U256::from(addr_not_in_list.0.as_slice()) + ); + + Ok(()) +} + +#[test] +fn test_insert_accessed_storage_keys() -> Result<()> { + let insert_accessed_storage_keys = KERNEL.global_labels["insert_accessed_storage_keys"]; + + let retaddr = 0xdeadbeefu32.into(); + let mut rng = thread_rng(); + let n = rng.gen_range(1..10); + let storage_keys = (0..n) + .map(|_| (rng.gen::
(), U256(rng.gen()))) + .collect::>() + .into_iter() + .collect::>(); + let storage_key_in_list = storage_keys[rng.gen_range(0..n)]; + let storage_key_not_in_list = (rng.gen::
(), U256(rng.gen())); + assert!( + !storage_keys.contains(&storage_key_not_in_list), + "Cosmic luck or bad RNG?" + ); + + // Test for storage key already in list. + let initial_stack = vec![ + retaddr, + storage_key_in_list.1, + U256::from(storage_key_in_list.0 .0.as_slice()), + ]; + let mut interpreter = Interpreter::new_with_kernel(insert_accessed_storage_keys, initial_stack); + for i in 0..n { + let addr = U256::from(storage_keys[i].0 .0.as_slice()); + interpreter + .generation_state + .memory + .set(MemoryAddress::new(0, AccessedStorageKeys, 2 * i), addr); + interpreter.generation_state.memory.set( + MemoryAddress::new(0, AccessedStorageKeys, 2 * i + 1), + storage_keys[i].1, + ); + } + interpreter.generation_state.memory.set( + MemoryAddress::new(0, GlobalMetadata, AccessedStorageKeysLen as usize), + U256::from(2 * n), + ); + interpreter.run()?; + assert_eq!(interpreter.stack(), &[U256::zero()]); + assert_eq!( + interpreter.generation_state.memory.get(MemoryAddress::new( + 0, + GlobalMetadata, + AccessedStorageKeysLen as usize + )), + U256::from(2 * n) + ); + + // Test for storage key not in list. + let initial_stack = vec![ + retaddr, + storage_key_not_in_list.1, + U256::from(storage_key_not_in_list.0 .0.as_slice()), + ]; + let mut interpreter = Interpreter::new_with_kernel(insert_accessed_storage_keys, initial_stack); + for i in 0..n { + let addr = U256::from(storage_keys[i].0 .0.as_slice()); + interpreter + .generation_state + .memory + .set(MemoryAddress::new(0, AccessedStorageKeys, 2 * i), addr); + interpreter.generation_state.memory.set( + MemoryAddress::new(0, AccessedStorageKeys, 2 * i + 1), + storage_keys[i].1, + ); + } + interpreter.generation_state.memory.set( + MemoryAddress::new(0, GlobalMetadata, AccessedStorageKeysLen as usize), + U256::from(2 * n), + ); + interpreter.run()?; + assert_eq!(interpreter.stack(), &[U256::one()]); + assert_eq!( + interpreter.generation_state.memory.get(MemoryAddress::new( + 0, + GlobalMetadata, + AccessedStorageKeysLen as usize + )), + U256::from(2 * (n + 1)) + ); + assert_eq!( + interpreter + .generation_state + .memory + .get(MemoryAddress::new(0, AccessedStorageKeys, 2 * n,)), + U256::from(storage_key_not_in_list.0 .0.as_slice()) + ); + assert_eq!( + interpreter.generation_state.memory.get(MemoryAddress::new( + 0, + AccessedStorageKeys, + 2 * n + 1, + )), + storage_key_not_in_list.1 + ); + + Ok(()) +} diff --git a/evm/src/cpu/kernel/tests/core/mod.rs b/evm/src/cpu/kernel/tests/core/mod.rs index 502c57f1..8d71051c 100644 --- a/evm/src/cpu/kernel/tests/core/mod.rs +++ b/evm/src/cpu/kernel/tests/core/mod.rs @@ -1,3 +1,4 @@ +mod access_lists; mod create_addresses; mod intrinsic_gas; mod jumpdest_analysis; diff --git a/evm/src/generation/prover_input.rs b/evm/src/generation/prover_input.rs index 74321562..ea834164 100644 --- a/evm/src/generation/prover_input.rs +++ b/evm/src/generation/prover_input.rs @@ -61,13 +61,9 @@ impl GenerationState { fn run_sf(&self, input_fn: &ProverInputFn) -> U256 { let field = EvmField::from_str(input_fn.0[1].as_str()).unwrap(); let inputs: [U256; 4] = match field { - Bls381Base => { - let mut inputs: [U256; 4] = [U256::zero(); 4]; - for i in 0..4 { - inputs[i] = stack_peek(self, i).expect("Empty stack"); - } - inputs - } + Bls381Base => std::array::from_fn(|i| { + stack_peek(self, i).expect("Insufficient number of items on stack") + }), _ => todo!(), }; match input_fn.0[2].as_str() { @@ -91,16 +87,12 @@ impl GenerationState { .unwrap() .parse::() .unwrap(); - let ptr = stack_peek(self, 11 - n).expect("Empty stack").as_usize(); + let ptr = stack_peek(self, 11 - n) + .expect("Insufficient number of items on stack") + .as_usize(); let f: [U256; 12] = match field { - Bn254Base => { - let mut f: [U256; 12] = [U256::zero(); 12]; - for i in 0..12 { - f[i] = kernel_peek(self, BnPairing, ptr + i); - } - f - } + Bn254Base => std::array::from_fn(|i| kernel_peek(self, BnPairing, ptr + i)), _ => todo!(), }; field.field_extension_inverse(n, f) diff --git a/evm/src/memory/segments.rs b/evm/src/memory/segments.rs index ee76cf7a..61c5a911 100644 --- a/evm/src/memory/segments.rs +++ b/evm/src/memory/segments.rs @@ -44,10 +44,14 @@ pub enum Segment { BnWnafB = 20, BnTableQ = 21, BnPairing = 22, + /// List of addresses that have been accessed in the current transaction. + AccessedAddresses = 23, + /// List of storage keys that have been accessed in the current transaction. + AccessedStorageKeys = 24, } impl Segment { - pub(crate) const COUNT: usize = 23; + pub(crate) const COUNT: usize = 25; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -74,6 +78,8 @@ impl Segment { Self::BnWnafB, Self::BnTableQ, Self::BnPairing, + Self::AccessedAddresses, + Self::AccessedStorageKeys, ] } @@ -103,6 +109,8 @@ impl Segment { Segment::BnWnafB => "SEGMENT_KERNEL_BN_WNAF_B", Segment::BnTableQ => "SEGMENT_KERNEL_BN_TABLE_Q", Segment::BnPairing => "SEGMENT_KERNEL_BN_PAIRING", + Segment::AccessedAddresses => "SEGMENT_ACCESSED_ADDRESSES", + Segment::AccessedStorageKeys => "SEGMENT_ACCESSED_STORAGE_KEYS", } } @@ -132,6 +140,8 @@ impl Segment { Segment::BnWnafB => 8, Segment::BnTableQ => 256, Segment::BnPairing => 256, + Segment::AccessedAddresses => 256, + Segment::AccessedStorageKeys => 256, } } }