From 2902d769acff2a0a6d6dd8b93a5d7eb344b2176b Mon Sep 17 00:00:00 2001 From: Mathieu <60658558+enitrat@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:57:33 +0200 Subject: [PATCH] tests: adapt testing strategy to leverage snforge feature (#881) * fix state tests * add py util to find a selector * add snforge utils to assert a call has / hasnt happened * migrate model+backend tests * migrate precompiles tests * add store_evm util to snforge utils * migrate memory operation tests * migrate block_information tests * migrate environmental_information tests * fix precompiles range to be dencun's one * migrate system_operations tests * use mock classes - addresses everywhere * fix process_transaction test --- Scarb.lock | 1 + .../contracts/tests/test_kakarot_core.cairo | 52 +- crates/evm/Scarb.toml | 13 - crates/evm/src/backend/starknet_backend.cairo | 133 ++++- .../src/instructions/block_information.cairo | 74 +-- .../environmental_information.cairo | 558 +++++------------- .../src/instructions/memory_operations.cairo | 168 ++---- .../src/instructions/system_operations.cairo | 520 ++++++++++------ crates/evm/src/model.cairo | 367 +++--------- crates/evm/src/model/account.cairo | 181 +++++- crates/evm/src/precompiles/blake2f.cairo | 11 +- crates/evm/src/precompiles/ec_recover.cairo | 11 +- crates/evm/src/precompiles/identity.cairo | 11 +- crates/evm/src/precompiles/modexp.cairo | 3 +- crates/evm/src/precompiles/p256verify.cairo | 11 +- crates/evm/src/precompiles/sha256.cairo | 11 +- crates/evm/src/state.cairo | 291 ++++----- crates/evm/src/test_utils.cairo | 90 ++- crates/snforge_utils/Scarb.toml | 7 + crates/snforge_utils/src/contracts.cairo | 51 ++ crates/snforge_utils/src/lib.cairo | 193 ++++++ scripts/find_selectory.py | 30 + 22 files changed, 1476 insertions(+), 1311 deletions(-) create mode 100644 crates/snforge_utils/src/contracts.cairo create mode 100644 scripts/find_selectory.py diff --git a/Scarb.lock b/Scarb.lock index b8367ea9b..cdb62e294 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -60,6 +60,7 @@ dependencies = [ name = "snforge_utils" version = "0.1.0" dependencies = [ + "evm", "snforge_std", ] diff --git a/crates/contracts/tests/test_kakarot_core.cairo b/crates/contracts/tests/test_kakarot_core.cairo index 4ee02c13d..e3ebdf3e4 100644 --- a/crates/contracts/tests/test_kakarot_core.cairo +++ b/crates/contracts/tests/test_kakarot_core.cairo @@ -28,9 +28,12 @@ use snforge_std::{ declare, DeclareResultTrait, start_cheat_caller_address, stop_cheat_caller_address, start_cheat_signature, stop_cheat_signature, start_cheat_chain_id, stop_cheat_chain_id, start_cheat_transaction_hash, stop_cheat_transaction_hash, spy_events, Event, EventSpyTrait, - test_address, cheat_caller_address, CheatSpan, store, load, EventSpyAssertionsTrait + test_address, cheat_caller_address, CheatSpan, store, load, EventSpyAssertionsTrait, + start_mock_call, stop_mock_call +}; +use snforge_utils::snforge_utils::{ + EventsFilterBuilderTrait, ContractEvents, ContractEventsTrait, store_evm }; -use snforge_utils::snforge_utils::{EventsFilterBuilderTrait, ContractEvents, ContractEventsTrait}; use starknet::storage::StorageTrait; use utils::eth_transaction::{EthereumTransaction, EthereumTransactionTrait, LegacyTransaction}; use utils::helpers::{EthAddressExTrait, u256_to_bytes_array}; @@ -268,25 +271,33 @@ fn test_eth_call() { } #[test] -#[ignore] -//TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_process_transaction() { - // Given - let (native_token, kakarot_core) = contract_utils::setup_contracts_for_testing(); + // Pre + test_utils::setup_test_storages(); + let chain_id = chain_id(); - let evm_address = test_utils::evm_address(); - let eoa = kakarot_core.deploy_externally_owned_account(evm_address); - contract_utils::fund_account_with_native_token( - eoa, native_token, 0xfffffffffffffffffffffffffff + // Given + let eoa_evm_address = test_utils::evm_address(); + let eoa_starknet_address = utils::helpers::compute_starknet_address( + test_address(), eoa_evm_address, test_utils::uninitialized_account() ); - let chain_id = chain_id(); + test_utils::register_account(eoa_evm_address, eoa_starknet_address); + start_mock_call::(eoa_starknet_address, selector!("get_nonce"), 0); + start_mock_call::>(eoa_starknet_address, selector!("bytecode"), [].span()); - let _account = contract_utils::deploy_contract_account( - kakarot_core, test_utils::other_evm_address(), counter_evm_bytecode() + let contract_evm_address = test_utils::other_evm_address(); + let contract_starknet_address = utils::helpers::compute_starknet_address( + test_address(), contract_evm_address, test_utils::uninitialized_account() ); + test_utils::register_account(contract_evm_address, contract_starknet_address); + start_mock_call::(contract_starknet_address, selector!("get_nonce"), 1); + start_mock_call::< + Span + >(contract_starknet_address, selector!("bytecode"), counter_evm_bytecode()); + start_mock_call::(contract_starknet_address, selector!("storage"), 0); let nonce = 0; - let to = Option::Some(test_utils::other_evm_address()); + let to = Option::Some(contract_evm_address); let gas_limit = test_utils::tx_gas_limit(); let gas_price = test_utils::gas_price(); let value = 0; @@ -300,18 +311,19 @@ fn test_process_transaction() { ); // When - let test_address: ContractAddress = test_address(); - start_cheat_caller_address(test_address, eoa); - //TODO(sn-foundry): fix this that fails because the local state doesn't have the correct - //addresses/classes let mut kakarot_core = KakarotCore::unsafe_new_contract_state(); + start_mock_call::< + u256 + >(test_utils::native_token(), selector!("balanceOf"), 0xfffffffffffffffffffffffffff); let result = kakarot_core - .process_transaction(origin: Address { evm: evm_address, starknet: eoa }, :tx); + .process_transaction( + origin: Address { evm: eoa_evm_address, starknet: eoa_starknet_address }, :tx + ); let return_data = result.return_data; // Then assert!(result.success); - assert(return_data == u256_to_bytes_array(0).span(), 'wrong result'); + assert_eq!(return_data, u256_to_bytes_array(0).span()); } #[test] diff --git a/crates/evm/Scarb.toml b/crates/evm/Scarb.toml index f2db44904..8fe56505a 100644 --- a/crates/evm/Scarb.toml +++ b/crates/evm/Scarb.toml @@ -17,19 +17,6 @@ snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag snforge_utils = { path = "../snforge_utils" } assert_macros = "0.1.0" -[[target.starknet-contract]] -casm = true -casm-add-pythonic-hints = true -build-external-contracts = [ - "openzeppelin::token::erc20::erc20::ERC20", - "contracts::uninitialized_account::UninitializedAccount", - "contracts::account_contract::AccountContract", - "contracts::kakarot_core::kakarot::KakarotCore", -] - - -[lib] -name = "evm" [tool] fmt.workspace = true diff --git a/crates/evm/src/backend/starknet_backend.cairo b/crates/evm/src/backend/starknet_backend.cairo index ce4d773ee..274389f1c 100644 --- a/crates/evm/src/backend/starknet_backend.cairo +++ b/crates/evm/src/backend/starknet_backend.cairo @@ -264,40 +264,139 @@ mod internals { mod tests { use contracts::account_contract::{IAccountDispatcher, IAccountDispatcherTrait}; use contracts::kakarot_core::KakarotCore; - use contracts::test_utils::setup_contracts_for_testing; + use core::starknet::ClassHash; use evm::backend::starknet_backend; use evm::errors::EVMErrorTrait; + use evm::model::Address; + use evm::model::account::{Account, AccountTrait}; + use evm::state::{State, StateTrait}; + use evm::test_utils::{ + setup_test_storages, uninitialized_account, account_contract, register_account + }; use evm::test_utils::{chain_id, evm_address, VMBuilderTrait}; - use evm::test_utils::{declare_and_store_classes}; use openzeppelin::token::erc20::interface::IERC20CamelDispatcherTrait; - use snforge_std::{spy_events, EventSpyTrait, test_address}; + use snforge_std::{spy_events, EventSpyTrait, test_address, start_mock_call, get_class_hash}; use snforge_utils::snforge_utils::{ - ContractEvents, ContractEventsTrait, EventsFilterBuilderTrait + ContractEvents, ContractEventsTrait, EventsFilterBuilderTrait, assert_not_called, + assert_called, assert_called_with }; + use utils::helpers::compute_starknet_address; #[test] #[ignore] - //TODO(sn-foundry): fix Entrypoint not found - //`0x11f99ee2dc5094f0126c3db5401e3a1a2b6b440f4740e6cce884709cd4526df` - fn test_account_deploy() { + //TODO(starknet-fonudry): it's impossible to deploy an un-declared class, nor is it possible to + //mock_deploy. + fn test_deploy() { // store the classes in the context of the local execution, to be used for deploying the // account class - declare_and_store_classes(); + setup_test_storages(); let test_address = test_address(); - let mut spy = spy_events(); + start_mock_call::< + ClassHash + >(test_address, selector!("get_account_contract_class_hash"), account_contract()); + start_mock_call::<()>(test_address, selector!("initialize"), ()); let eoa_address = starknet_backend::deploy(evm_address()) .expect('deployment of EOA failed'); - let expected = KakarotCore::Event::AccountDeployed( - KakarotCore::AccountDeployed { - evm_address: evm_address(), starknet_address: eoa_address.starknet - } + let class_hash = get_class_hash(eoa_address.starknet); + assert_eq!(class_hash, account_contract()); + } + + #[test] + #[ignore] + //TODO(starknet-foundry): it's impossible to deploy an un-declared class, nor is it possible to + //mock_deploy. + fn test_account_commit_undeployed_create_should_change_set_all() { + setup_test_storages(); + let test_address = test_address(); + let evm_address = evm_address(); + let starknet_address = compute_starknet_address( + test_address, evm_address, uninitialized_account() + ); + + let mut state: State = Default::default(); + + // When + let mut account = Account { + address: Address { evm: evm_address, starknet: starknet_address }, nonce: 420, code: [ + 0x1 + ].span(), balance: 0, selfdestruct: false, is_created: true, + }; + state.set_account(account); + + start_mock_call::<()>(starknet_address, selector!("set_nonce"), ()); + start_mock_call::< + ClassHash + >(test_address, selector!("get_account_contract_class_hash"), account_contract()); + starknet_backend::commit(ref state).expect('commitment failed'); + + // Then + //TODO(starknet-foundry): we should be able to assert this has been called with specific + //data, to pass in mock_call + assert_called(starknet_address, selector!("set_nonce")); + assert_not_called(starknet_address, selector!("write_bytecode")); + } + + #[test] + fn test_account_commit_deployed_and_created_should_write_code() { + setup_test_storages(); + let test_address = test_address(); + let evm_address = evm_address(); + let starknet_address = compute_starknet_address( + test_address, evm_address, uninitialized_account() ); + register_account(evm_address, starknet_address); + + let mut state: State = Default::default(); + let mut account = Account { + address: Address { evm: evm_address, starknet: starknet_address }, nonce: 420, code: [ + 0x1 + ].span(), balance: 0, selfdestruct: false, is_created: true, + }; + state.set_account(account); + + start_mock_call::<()>(starknet_address, selector!("write_bytecode"), ()); + start_mock_call::<()>(starknet_address, selector!("set_nonce"), ()); + starknet_backend::commit(ref state).expect('commitment failed'); + + // Then the account should have a new code. + //TODO(starknet-foundry): we should be able to assert this has been called with specific + //data, to pass in mock_call + assert_called(starknet_address, selector!("write_bytecode")); + assert_called(starknet_address, selector!("set_nonce")); + } + + #[test] + #[ignore] + //TODO(starknet-foundry): it's impossible to deploy an un-declared class, nor is it possible to + //mock_deploy. + fn test_exec_sstore_finalized() { // // Given + // setup_test_storages(); + // let mut vm = VMBuilderTrait::new_with_presets().build(); + // let evm_address = vm.message().target.evm; + // let starknet_address = compute_starknet_address( + // test_address(), evm_address, uninitialized_account() + // ); + // let account = Account { + // address: Address { evm: evm_address, starknet: starknet_address }, + // code: [].span(), + // nonce: 1, + // balance: 0, + // selfdestruct: false, + // is_created: false, + // }; + // let key: u256 = 0x100000000000000000000000000000001; + // let value: u256 = 0xABDE1E11A5; + // vm.stack.push(value).expect('push failed'); + // vm.stack.push(key).expect('push failed'); + + // // When + + // vm.exec_sstore().expect('exec_sstore failed'); + // starknet_backend::commit(ref vm.env.state).expect('commit storage failed'); - let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) - .with_contract_address(test_address) - .build(); - contract_events.assert_emitted(@expected); + // // Then + // assert(fetch_original_storage(@account, key) == value, 'wrong committed value') } } diff --git a/crates/evm/src/instructions/block_information.cairo b/crates/evm/src/instructions/block_information.cairo index d9806251b..84eeb1573 100644 --- a/crates/evm/src/instructions/block_information.cairo +++ b/crates/evm/src/instructions/block_information.cairo @@ -144,18 +144,21 @@ mod tests { IExtendedKakarotCoreDispatcher, IExtendedKakarotCoreDispatcherTrait }; - use contracts::test_utils::{ - setup_contracts_for_testing, fund_account_with_native_token, deploy_contract_account, - }; use core::result::ResultTrait; use core::starknet::testing::{set_contract_address, ContractAddress}; use evm::instructions::BlockInformationTrait; + use evm::model::account::{Account, AccountTrait}; + use evm::model::vm::VMTrait; use evm::stack::StackTrait; - use evm::test_utils::{evm_address, VMBuilderTrait, tx_gas_limit, gas_price}; + use evm::state::StateTrait; + use evm::test_utils::{ + evm_address, VMBuilderTrait, tx_gas_limit, gas_price, native_token, setup_test_storages, + register_account + }; use openzeppelin::token::erc20::interface::IERC20CamelDispatcherTrait; use snforge_std::{ start_cheat_block_number_global, start_cheat_block_timestamp_global, - start_cheat_caller_address, test_address + start_cheat_caller_address, test_address, start_mock_call }; use utils::constants; use utils::traits::{EthAddressIntoU256}; @@ -268,62 +271,25 @@ mod tests { // 0x47: SELFBALANCE // ************************************************************************* #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_selfbalance_eoa() { + fn test_exec_selfbalance_should_push_balance() { // Given - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let eoa = kakarot_core.deploy_externally_owned_account(evm_address()); - - fund_account_with_native_token(eoa, native_token, 0x1); - - // And - let mut vm = VMBuilderTrait::new_with_presets().build(); - - // When - start_cheat_caller_address(test_address(), kakarot_core.contract_address); - vm.exec_selfbalance().unwrap(); - - // Then - assert(vm.stack.peek().unwrap() == native_token.balanceOf(eoa), 'wrong balance'); - } - - #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0`. Needs to deploy an EOA to get - //the selfbalance. - fn test_exec_selfbalance_zero() { - // Given - let (_, kakarot_core) = setup_contracts_for_testing(); - - // And - let mut vm = VMBuilderTrait::new_with_presets().build(); - - // When - // start_cheat_caller_address(kakarot_core.contract_address, evm_address()); - vm.exec_selfbalance().unwrap(); - - // Then - assert(vm.stack.peek().unwrap() == 0x00, 'wrong balance'); - } - - #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_selfbalance_contract_account() { - // Given - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let mut ca_address = deploy_contract_account(kakarot_core, evm_address(), [].span()); - - fund_account_with_native_token(ca_address.starknet, native_token, 0x1); + setup_test_storages(); let mut vm = VMBuilderTrait::new_with_presets().build(); + let account = Account { + address: vm.message().target, + balance: 400, + nonce: 0, + code: [].span(), + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); // When - // start_cheat_caller_address(kakarot_core.contract_address, evm_address()); vm.exec_selfbalance().unwrap(); // Then - assert(vm.stack.peek().unwrap() == 0x1, 'wrong balance'); + assert_eq!(vm.stack.peek().unwrap(), 400); } diff --git a/crates/evm/src/instructions/environmental_information.cairo b/crates/evm/src/instructions/environmental_information.cairo index d312d7794..d8249ea3d 100644 --- a/crates/evm/src/instructions/environmental_information.cairo +++ b/crates/evm/src/instructions/environmental_information.cairo @@ -324,10 +324,8 @@ impl EnvironmentInformationImpl of EnvironmentInformationTrait { mod tests { use contracts::kakarot_core::{interface::IExtendedKakarotCoreDispatcherImpl, KakarotCore}; use contracts::test_data::counter_evm_bytecode; - use contracts::test_utils::{ - setup_contracts_for_testing, fund_account_with_native_token, deploy_contract_account - }; use core::num::traits::CheckedAdd; + use core::starknet::EthAddress; use core::starknet::testing::set_contract_address; use evm::errors::{EVMError, TYPE_CONVERSION_ERROR}; @@ -335,18 +333,20 @@ mod tests { use evm::memory::{InternalMemoryTrait, MemoryTrait}; use evm::model::vm::{VM, VMTrait}; - use evm::model::{Account}; + use evm::model::{Account, Address}; use evm::stack::StackTrait; use evm::state::StateTrait; use evm::test_utils::{ VMBuilderTrait, evm_address, origin, callvalue, native_token, other_address, gas_price, - tx_gas_limit + tx_gas_limit, register_account }; use openzeppelin::token::erc20::interface::IERC20CamelDispatcherTrait; - use snforge_std::{test_address, start_cheat_caller_address}; - use utils::helpers::{u256_to_bytes_array, ArrayExtTrait}; + use snforge_std::{test_address, start_mock_call}; + use utils::helpers::{u256_to_bytes_array, ArrayExtTrait, compute_starknet_address}; use utils::traits::{EthAddressIntoU256}; + const EMPTY_KECCAK: u256 = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // ************************************************************************* // 0x30: ADDRESS // ************************************************************************* @@ -360,88 +360,35 @@ mod tests { vm.exec_address().expect('exec_address failed'); // Then - assert(vm.stack.len() == 1, 'stack should have one element'); - assert(vm.stack.pop_eth_address().unwrap() == evm_address(), 'should be `evm_address`'); - } - - #[test] - #[ignore] - fn test_address_nested_call() { // A (EOA) -(calls)-> B (smart contract) -(calls)-> C (smart contract) - // TODO: Once we have ability to do nested smart contract calls, check that in `C`s context - // `ADDRESS` should return address `B` - // ref: https://github.com/kkrt-labs/kakarot-ssj/issues/183 + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.pop_eth_address().unwrap(), vm.message().target.evm.into()); } // ************************************************************************* // 0x31: BALANCE // ************************************************************************* #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_exec_balance_eoa() { // Given - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let eoa = kakarot_core.deploy_externally_owned_account(evm_address()); - - fund_account_with_native_token(eoa, native_token, 0x1); - - // And - let mut vm = VMBuilderTrait::new_with_presets().build(); - - vm.stack.push(evm_address().into()).unwrap(); - - // When - start_cheat_caller_address(test_address(), kakarot_core.contract_address); - vm.exec_balance().expect('exec_balance failed'); - - // Then - assert(vm.stack.peek().unwrap() == native_token.balanceOf(eoa), 'wrong balance'); - } - - #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_balance_zero() { - // Given - let (_, kakarot_core) = setup_contracts_for_testing(); - - // And let mut vm = VMBuilderTrait::new_with_presets().build(); - - vm.stack.push(evm_address().into()).unwrap(); - - // When - start_cheat_caller_address(test_address(), kakarot_core.contract_address); - vm.exec_balance().expect('exec_balance failed'); - - // Then - assert(vm.stack.peek().unwrap() == 0x00, 'wrong balance'); - } - - #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_balance_contract_account() { - // Given - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let mut ca_address = deploy_contract_account(kakarot_core, evm_address(), [].span()); - - fund_account_with_native_token(ca_address.starknet, native_token, 0x1); - - // And - let mut vm = VMBuilderTrait::new_with_presets().build(); - - vm.stack.push(evm_address().into()).unwrap(); + let account = Account { + address: vm.message().target, + balance: 400, + nonce: 0, + code: [].span(), + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(vm.message.target.evm.into()).unwrap(); // When - start_cheat_caller_address(test_address(), kakarot_core.contract_address); vm.exec_balance().expect('exec_balance failed'); // Then - assert(vm.stack.peek().unwrap() == 0x1, 'wrong balance'); + assert_eq!(vm.stack.peek().unwrap(), 400); } - // ************************************************************************* // 0x33: CALLER // ************************************************************************* @@ -454,8 +401,8 @@ mod tests { vm.exec_caller().expect('exec_caller failed'); // Then - assert(vm.stack.len() == 1, 'stack should have one element'); - assert(vm.stack.peek().unwrap() == origin().into(), 'should be evm_address'); + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), origin().into()); } @@ -471,15 +418,14 @@ mod tests { vm.exec_origin().expect('exec_origin failed'); // Then - assert(vm.stack.len() == 1, 'stack should have one element'); - assert(vm.stack.peek().unwrap() == origin().into(), 'should be `evm_address`'); + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), origin().into()); } // ************************************************************************* // 0x34: CALLVALUE // ************************************************************************* - #[test] fn test_exec_callvalue() { // Given @@ -489,8 +435,8 @@ mod tests { vm.exec_callvalue().expect('exec_callvalue failed'); // Then - assert(vm.stack.len() == 1, 'stack should have one element'); - assert(vm.stack.pop().unwrap() == callvalue(), 'should be `123456789'); + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.pop().unwrap(), callvalue()); } // ************************************************************************* @@ -513,10 +459,7 @@ mod tests { // Then let result: u256 = vm.stack.pop().unwrap(); - assert( - result == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, - 'wrong data value' - ); + assert_eq!(result, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); } #[test] @@ -537,10 +480,7 @@ mod tests { // Then let result: u256 = vm.stack.pop().unwrap(); - assert( - result == 0xFF00000000000000000000000000000000000000000000000000000000000000, - 'wrong results' - ); + assert_eq!(result, 0xFF00000000000000000000000000000000000000000000000000000000000000); } #[test] @@ -559,7 +499,7 @@ mod tests { // Then let result: u256 = vm.stack.pop().unwrap(); - assert(result == 0, 'result should be 0'); + assert_eq!(result, 0); } #[test] @@ -576,10 +516,7 @@ mod tests { // Then let result: u256 = vm.stack.pop().unwrap(); - assert( - result == 0x6d4ce63c00000000000000000000000000000000000000000000000000000000, - 'wrong result' - ); + assert_eq!(result, 0x6d4ce63c00000000000000000000000000000000000000000000000000000000); } @@ -597,11 +534,8 @@ mod tests { let result = vm.exec_calldataload(); // Then - assert(result.is_err(), 'should return error'); - assert( - result.unwrap_err() == EVMError::TypeConversionError(TYPE_CONVERSION_ERROR), - 'should return ConversionError' - ); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), EVMError::TypeConversionError(TYPE_CONVERSION_ERROR)); } // ************************************************************************* @@ -619,8 +553,8 @@ mod tests { vm.exec_calldatasize().expect('exec_calldatasize failed'); // Then - assert(vm.stack.len() == 1, 'stack should have one element'); - assert(vm.stack.peek().unwrap() == calldata.len().into(), 'stack top is not calldatasize'); + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), calldata.len().into()); } // ************************************************************************* @@ -649,11 +583,8 @@ mod tests { let res = vm.exec_calldatacopy(); // Then - assert(res.is_err(), 'should return error'); - assert( - res.unwrap_err() == EVMError::TypeConversionError(TYPE_CONVERSION_ERROR), - 'should return ConversionError' - ); + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), EVMError::TypeConversionError(TYPE_CONVERSION_ERROR)); } #[test] @@ -708,10 +639,7 @@ mod tests { let initial: u256 = vm.memory.load_internal(dest_offset + (i * 32)).into(); - assert( - initial == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, - 'memory has not been initialized' - ); + assert_eq!(initial, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); i += 1; }; @@ -720,12 +648,12 @@ mod tests { vm.exec_calldatacopy().expect('exec_calldatacopy failed'); // Then - assert(vm.stack.is_empty(), 'stack should be empty'); + assert!(vm.stack.is_empty()); let mut results: Array = ArrayTrait::new(); vm.memory.load_n_internal(size, ref results, dest_offset); - assert(results.span() == expected, 'wrong data value'); + assert_eq!(results.span(), expected); } // ************************************************************************* @@ -743,8 +671,8 @@ mod tests { vm.exec_codesize().expect('exec_codesize failed'); // Then - assert(vm.stack.len() == 1, 'stack should have one element'); - assert(vm.stack.pop().unwrap() == bytecode.len().into(), 'wrong codesize'); + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.pop().unwrap(), bytecode.len().into()); } // ************************************************************************* @@ -775,11 +703,8 @@ mod tests { let res = vm.exec_codecopy(); // Then - assert(res.is_err(), 'should return error'); - assert( - res.unwrap_err() == EVMError::TypeConversionError(TYPE_CONVERSION_ERROR), - 'should return ConversionError' - ); + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), EVMError::TypeConversionError(TYPE_CONVERSION_ERROR)); } #[test] @@ -820,16 +745,13 @@ mod tests { .memory .store(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, dest_offset); let initial: u256 = vm.memory.load_internal(dest_offset).into(); - assert( - initial == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, - 'memory has not been initialized' - ); + assert_eq!(initial, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); // When vm.exec_codecopy().expect('exec_codecopy failed'); // Then - assert(vm.stack.is_empty(), 'stack should be empty'); + assert!(vm.stack.is_empty()); let result: u256 = vm.memory.load_internal(dest_offset).into(); let mut results: Array = u256_to_bytes_array(result); @@ -838,9 +760,9 @@ mod tests { while i != size { // For out of bound bytes, 0s will be copied. if (i + offset >= bytecode.len()) { - assert(*results[i] == 0, 'wrong data value'); + assert_eq!(*results[i], 0); } else { - assert(*results[i] == *bytecode[i + offset], 'wrong data value'); + assert_eq!(*results[i], *bytecode[i + offset]); } i += 1; @@ -860,103 +782,76 @@ mod tests { vm.exec_gasprice().expect('exec_gasprice failed'); // Then - assert(vm.stack.len() == 1, 'stack should have one element'); - assert(vm.stack.peek().unwrap() == gas_price().into(), 'stack top should be gas_price'); + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), gas_price().into()); } // ************************************************************************* // 0x3B - EXTCODESIZE // ************************************************************************* #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_extcodesize_eoa() { + fn test_exec_extcodesize_should_push_bytecode_len_0() { // Given - let evm_address = evm_address(); let mut vm = VMBuilderTrait::new_with_presets().build(); - - let (_, kakarot_core) = setup_contracts_for_testing(); - let _expected_eoa_starknet_address = kakarot_core - .deploy_externally_owned_account(evm_address); - vm.stack.push(evm_address.into()).expect('push failed'); - - // When - vm.exec_extcodesize().unwrap(); - - // Then - assert(vm.stack.peek().unwrap() == 0, 'expected code size 0'); - } - - - #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_extcodesize_ca_empty() { - // Given - let evm_address = evm_address(); - let mut vm = VMBuilderTrait::new_with_presets().build(); - - let (_, kakarot_core) = setup_contracts_for_testing(); - - // The bytecode remains empty, and we expect the empty hash in return - deploy_contract_account(kakarot_core, evm_address, [].span()); - - vm.stack.push(evm_address.into()).expect('push failed'); + let account = Account { + address: vm.message().target, + balance: 1, + nonce: 1, + code: [].span(), + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(account.address.evm.into()).expect('push failed'); // When vm.exec_extcodesize().unwrap(); // Then - assert(vm.stack.peek().unwrap() == 0, 'expected code size 0'); + assert_eq!(vm.stack.peek().unwrap(), 0); } - #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_extcodesize_ca_with_bytecode() { + fn test_exec_extcodesize_should_push_bytecode_len() { // Given - let evm_address = evm_address(); let mut vm = VMBuilderTrait::new_with_presets().build(); + let account = Account { + address: vm.message().target, balance: 1, nonce: 1, code: [ + 0xff + ; 350].span(), selfdestruct: false, is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(account.address.evm.into()).expect('push failed'); - let (_, kakarot_core) = setup_contracts_for_testing(); - - // The bytecode stored is the bytecode of a Counter.sol smart contract - deploy_contract_account(kakarot_core, evm_address, counter_evm_bytecode()); - - vm.stack.push(evm_address.into()).expect('push failed'); // When vm.exec_extcodesize().unwrap(); // Then - assert( - vm.stack.peek() // extcodesize(Counter.sol) := 275 (source: remix) - .unwrap() == 473, - 'expected counter SC code size' - ); + assert_eq!(vm.stack.peek().unwrap(), 350); } - #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_extcodecopy_ca() { + fn test_exec_extcodecopy_should_copy_bytecode_slice() { // Given - let evm_address = evm_address(); let mut vm = VMBuilderTrait::new_with_presets().build(); + let account = Account { + address: vm.message().target, + balance: 1, + nonce: 1, + code: counter_evm_bytecode(), + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); - let (_, kakarot_core) = setup_contracts_for_testing(); - - // The bytecode stored is the bytecode of a Counter.sol smart contract - deploy_contract_account(kakarot_core, evm_address, counter_evm_bytecode()); - + vm.stack.push(account.address.evm.into()).expect('push failed'); // size vm.stack.push(50).expect('push failed'); // offset vm.stack.push(200).expect('push failed'); // destOffset (memory offset) vm.stack.push(20).expect('push failed'); - vm.stack.push(evm_address.into()).unwrap(); + vm.stack.push(account.address.evm.into()).unwrap(); // When vm.exec_extcodecopy().unwrap(); @@ -964,73 +859,26 @@ mod tests { // Then let mut bytecode_slice = array![]; vm.memory.load_n(50, ref bytecode_slice, 20); - assert(bytecode_slice.span() == counter_evm_bytecode().slice(200, 50), 'wrong bytecode'); + assert_eq!(bytecode_slice.span(), counter_evm_bytecode().slice(200, 50)); } // ************************************************************************* // 0x3C - EXTCODECOPY // ************************************************************************* #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_extcodecopy_ca_offset_out_of_bounds() { - // Given - let evm_address = evm_address(); - let mut vm = VMBuilderTrait::new_with_presets().build(); - - let (_, kakarot_core) = setup_contracts_for_testing(); - - // The bytecode stored is the bytecode of a Counter.sol smart contract - deploy_contract_account(kakarot_core, evm_address, counter_evm_bytecode()); - - // size - vm.stack.push(5).expect('push failed'); - // offset - vm.stack.push(5000).expect('push failed'); - // destOffset - vm.stack.push(20).expect('push failed'); - vm.stack.push(evm_address.into()).expect('push failed'); - - // When - vm.exec_extcodecopy().unwrap(); - // Then - let mut bytecode_slice = array![]; - vm.memory.load_n(5, ref bytecode_slice, 20); - assert(bytecode_slice.span() == [0, 0, 0, 0, 0].span(), 'wrong bytecode'); - } - - fn test_exec_extcodecopy_eoa() { + fn test_exec_extcodecopy_ca_offset_out_of_bounds_should_return_zeroes() { // Given - let evm_address = evm_address(); let mut vm = VMBuilderTrait::new_with_presets().build(); - - let (_, kakarot_core) = setup_contracts_for_testing(); - kakarot_core.deploy_externally_owned_account(evm_address); - - // size - vm.stack.push(5).expect('push failed'); - // offset - vm.stack.push(5000).expect('push failed'); - // destOffset - vm.stack.push(20).expect('push failed'); - vm.stack.push(evm_address.into()).expect('push failed'); - - // When - vm.exec_extcodecopy().unwrap(); - - // Then - let mut bytecode_slice = array![]; - vm.memory.load_n(5, ref bytecode_slice, 20); - assert(bytecode_slice.span() == [0, 0, 0, 0, 0].span(), 'wrong bytecode'); - } - - - fn test_exec_extcodecopy_account_none() { - // Given - let evm_address = evm_address(); - let mut vm = VMBuilderTrait::new_with_presets().build(); - - setup_contracts_for_testing(); + let account = Account { + address: vm.message().target, + balance: 1, + nonce: 1, + code: counter_evm_bytecode(), + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(account.address.evm.into()).expect('push failed'); // size vm.stack.push(5).expect('push failed'); @@ -1038,7 +886,7 @@ mod tests { vm.stack.push(5000).expect('push failed'); // destOffset vm.stack.push(20).expect('push failed'); - vm.stack.push(evm_address.into()).expect('push failed'); + vm.stack.push(account.address.evm.into()).expect('push failed'); // When vm.exec_extcodecopy().unwrap(); @@ -1046,10 +894,9 @@ mod tests { // Then let mut bytecode_slice = array![]; vm.memory.load_n(5, ref bytecode_slice, 20); - assert(bytecode_slice.span() == [0, 0, 0, 0, 0].span(), 'wrong bytecode'); + assert_eq!(bytecode_slice.span(), [0, 0, 0, 0, 0].span()); } - #[test] fn test_exec_returndatasize() { // Given @@ -1063,8 +910,8 @@ mod tests { vm.exec_returndatasize().expect('exec_returndatasize failed'); // Then - assert(vm.stack.len() == 1, 'stack should have one element'); - assert(vm.stack.pop().unwrap() == size.into(), 'wrong returndatasize'); + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.pop().unwrap(), size.into()); } // ************************************************************************* @@ -1096,10 +943,7 @@ mod tests { let res = vm.exec_returndatacopy(); // Then - assert( - res.unwrap_err() == EVMError::TypeConversionError(TYPE_CONVERSION_ERROR), - 'should return ConversionError' - ); + assert_eq!(res.unwrap_err(), EVMError::TypeConversionError(TYPE_CONVERSION_ERROR)); } #[test] @@ -1184,23 +1028,17 @@ mod tests { let res = vm.exec_returndatacopy(); // Then - assert(vm.stack.is_empty(), 'stack should be empty'); + assert!(vm.stack.is_empty()); match offset.checked_add(size) { Option::Some(x) => { if (x > return_data.len()) { - assert( - res.unwrap_err() == EVMError::ReturnDataOutOfBounds, - 'should return out of bounds' - ); + assert_eq!(res.unwrap_err(), EVMError::ReturnDataOutOfBounds); return; } }, Option::None => { - assert( - res.unwrap_err() == EVMError::ReturnDataOutOfBounds, - 'should return out of bounds' - ); + assert_eq!(res.unwrap_err(), EVMError::ReturnDataOutOfBounds); return; } } @@ -1221,80 +1059,34 @@ mod tests { i += 1; }; - assert(results.span() == return_data.span().slice(offset, size), 'wrong data value'); + assert_eq!(results.span(), return_data.span().slice(offset, size)); } // ************************************************************************* // 0x3F: EXTCODEHASH // ************************************************************************* #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_exec_extcodehash_precompile() { // Given - let evm_address = 0x05.try_into().unwrap(); let mut vm = VMBuilderTrait::new_with_presets().build(); - - let (_, kakarot_core) = setup_contracts_for_testing(); - kakarot_core.deploy_externally_owned_account(evm_address); - vm.stack.push(evm_address.into()).expect('push failed'); - start_cheat_caller_address(test_address(), kakarot_core.contract_address); - - // When - vm.exec_extcodehash().unwrap(); - - // Then - assert(vm.stack.peek().unwrap() == 0, 'expected 0'); - } - - //TODO: restore after selfdestruct - // #[test] - // fn test_exec_extcodehash_selfdestructed() { - // // Given - // let evm_address = evm_address(); - // let mut vm = VMBuilderTrait::new_with_presets().build(); - - // let (_, kakarot_core) = setup_contracts_for_testing(); - - // // The bytecode remains empty, and we expect the empty hash in return - // let mut ca_address = deploy_contract_account(kakarot_core,evm_address, [].span()); - // let account = Account { - // - // address: ca_address, - // code: [].span(), - // nonce: 1, - // balance: 1, - // selfdestruct: false - // }; - // account.selfdestruct(); - - // vm.stack.push(evm_address.into()).expect('push failed'); - - // // When - // vm.exec_extcodehash().unwrap(); - - // // Then - // assert( - // vm - // .stack - // .peek() - // .unwrap() == 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470, - // 'expected empty hash' - // ); - // } - - #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_extcodehash_eoa_empty_eoa() { - // Given - let evm_address = evm_address(); - let mut vm = VMBuilderTrait::new_with_presets().build(); - - let (_, kakarot_core) = setup_contracts_for_testing(); - kakarot_core.deploy_externally_owned_account(evm_address); - - vm.stack.push(evm_address.into()).expect('push failed'); + let precompile_evm_address: EthAddress = evm::model::LAST_ETHEREUM_PRECOMPILE_ADDRESS + .try_into() + .unwrap(); + let precompile_starknet_address = compute_starknet_address( + test_address(), precompile_evm_address, 0.try_into().unwrap() + ); + let account = Account { + address: Address { + evm: precompile_evm_address, starknet: precompile_starknet_address, + }, + balance: 1, + nonce: 0, + code: [].span(), + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(precompile_evm_address.into()).expect('push failed'); // When vm.exec_extcodehash().unwrap(); @@ -1303,100 +1095,76 @@ mod tests { assert_eq!(vm.stack.peek().unwrap(), 0); } - #[test] - #[ignore] - fn test_exec_extcodehash_ca_empty() { + fn test_exec_extcodehash_empty_account() { // Given - let evm_address = evm_address(); let mut vm = VMBuilderTrait::new_with_presets().build(); - - let (_, kakarot_core) = setup_contracts_for_testing(); - // The bytecode remains empty, and we expect the empty hash in return - deploy_contract_account(kakarot_core, evm_address, [].span()); - - vm.stack.push(evm_address.into()).expect('push failed'); + let account = Account { + address: vm.message().target, + balance: 0, + nonce: 0, + code: [].span(), + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(account.address.evm.into()).expect('push failed'); // When vm.exec_extcodehash().unwrap(); // Then - assert( - vm - .stack - .peek() - .unwrap() == 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470, - 'expected empty hash' - ); + assert_eq!(vm.stack.peek().unwrap(), 0); } #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_extcodehash_unknown_account() { + fn test_exec_extcodehash_no_bytecode() { // Given - let evm_address = evm_address(); let mut vm = VMBuilderTrait::new_with_presets().build(); - - setup_contracts_for_testing(); - - vm.stack.push(evm_address.into()).expect('push failed'); + let account = Account { + address: vm.message().target, + balance: 1, + nonce: 1, + code: [].span(), + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(account.address.evm.into()).expect('push failed'); // When vm.exec_extcodehash().unwrap(); // Then - assert(vm.stack.peek().unwrap() == 0, 'expected stack top to be 0'); + assert_eq!(vm.stack.peek().unwrap(), EMPTY_KECCAK); } #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_extcodehash_ca_with_bytecode() { + fn test_exec_extcodehash_with_bytecode() { // Given - let evm_address = evm_address(); let mut vm = VMBuilderTrait::new_with_presets().build(); + let account = Account { + address: vm.message().target, + balance: 1, + nonce: 1, + code: counter_evm_bytecode(), + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(account.address.evm.into()).expect('push failed'); - let (_, kakarot_core) = setup_contracts_for_testing(); - - // The bytecode stored is the bytecode of a Counter.sol smart contract - deploy_contract_account(kakarot_core, evm_address, counter_evm_bytecode()); - - vm.stack.push(evm_address.into()).expect('push failed'); // When vm.exec_extcodehash().unwrap(); // Then - assert( - vm - .stack - .peek() - // extcodehash(Counter.sol) := - // 0x82abf19c13d2262cc530f54956af7e4ec1f45f637238ed35ed7400a3409fd275 (source: - // remix) - // - .unwrap() == 0xec976f44607e73ea88910411e3da156757b63bea5547b169e1e0d733443f73b0, - 'expected counter SC code hash' + assert_eq!( + vm.stack.peek() // extcodehash(Counter.sol) := + // 0x82abf19c13d2262cc530f54956af7e4ec1f45f637238ed35ed7400a3409fd275 (source: + // remix) + // + .unwrap(), + 0xec976f44607e73ea88910411e3da156757b63bea5547b169e1e0d733443f73b0, ); } - - #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_extcodehash_precompiles() { - // Given - let mut vm = VMBuilderTrait::new_with_presets().build(); - setup_contracts_for_testing(); - - let mut i = 0; - while i != 0x10 { - vm.stack.push(i.into()).expect('push failed'); - // When - vm.exec_extcodehash().unwrap(); - - // Then - assert(vm.stack.pop().unwrap() == 0, 'expected 0 for precompiles'); - i += 1; - }; - } } diff --git a/crates/evm/src/instructions/memory_operations.cairo b/crates/evm/src/instructions/memory_operations.cairo index c54859270..7713a7ae4 100644 --- a/crates/evm/src/instructions/memory_operations.cairo +++ b/crates/evm/src/instructions/memory_operations.cairo @@ -305,8 +305,7 @@ impl MemoryOperation of MemoryOperationTrait { #[cfg(test)] mod tests { - use contracts::account_contract::{IAccountDispatcher, IAccountDispatcherTrait}; - use contracts::test_utils::{setup_contracts_for_testing, deploy_contract_account}; + use contracts::account_contract::{AccountContract}; use core::cmp::max; use core::num::traits::Bounded; use core::result::ResultTrait; @@ -317,12 +316,18 @@ mod tests { use evm::gas; use evm::instructions::{MemoryOperationTrait, EnvironmentInformationTrait}; use evm::memory::{InternalMemoryTrait, MemoryTrait}; + use evm::model::Address; use evm::model::vm::{VM, VMTrait}; use evm::model::{Account, AccountTrait}; use evm::stack::StackTrait; use evm::state::{StateTrait, compute_storage_address}; - use evm::test_utils::{evm_address, VMBuilderTrait}; - use snforge_std::{start_cheat_caller_address, stop_cheat_caller_address}; + use evm::test_utils::{ + evm_address, VMBuilderTrait, setup_test_storages, register_account, uninitialized_account, + native_token + }; + use snforge_std::{test_address, start_mock_call, store}; + use snforge_utils::snforge_utils::store_evm; + use utils::helpers::compute_starknet_address; #[test] fn test_pc_basic() { @@ -773,10 +778,6 @@ mod tests { assert(pc == old_pc, 'PC should be same'); } - // TODO: This is third edge case in which `0x5B` is part of PUSHN instruction and hence - // not a valid opcode to jump to - // - // Remove ignore once its handled #[test] #[should_panic(expected: ('exec_jump should throw error',))] fn test_exec_jumpi_inside_pushn() { @@ -799,14 +800,15 @@ mod tests { } #[test] - fn test_exec_sload_from_state() { + fn test_exec_sload_should_push_value_on_stack() { // Given + setup_test_storages(); let mut vm = VMBuilderTrait::new_with_presets().build(); - let key: u256 = 0x100000000000000000000000000000001; - let value = 0x02; - // `evm_address` must match the one used to instantiate the vm - vm.env.state.write_state(vm.message().target.evm, key, value); + let evm_address = vm.message().target.evm; + let key: u256 = 0x100000000000000000000000000000001; + let value: u256 = 0x02; + vm.env.state.write_state(evm_address, key, value); vm.stack.push(key.into()).expect('push failed'); // When @@ -819,114 +821,95 @@ mod tests { } #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0`. Needs to deploy an EOA to get - //the selfbalance. - fn test_exec_sload_from_storage() { + fn test_exec_sstore_on_account_in_st() { // Given - let (_, kakarot_core) = setup_contracts_for_testing(); + setup_test_storages(); let mut vm = VMBuilderTrait::new_with_presets().build(); - let mut ca_address = deploy_contract_account( - kakarot_core, vm.message().target.evm, [].span() + let test_address = test_address(); + let evm_address = vm.message().target.evm; + let starknet_address = compute_starknet_address( + test_address, evm_address, uninitialized_account() ); let account = Account { - address: ca_address, code: [ + address: Address { evm: evm_address, starknet: starknet_address }, code: [ 0xab, 0xcd, 0xef ].span(), nonce: 1, balance: 0, selfdestruct: false, is_created: false, }; - let key: u256 = 0x100000000000000000000000000000001; - let value: u256 = 0xABDE1E11A5; - start_cheat_caller_address(ca_address.starknet, kakarot_core.contract_address); - IAccountDispatcher { contract_address: account.starknet_address() } - .write_storage(key, value); - stop_cheat_caller_address(ca_address.starknet); - - vm.stack.push(key.into()).expect('push failed'); - - // When - let result = vm.exec_sload(); - - // Then - assert(result.is_ok(), 'should have succeeded'); - assert(vm.stack.len() == 1, 'stack should have one element'); - assert(vm.stack.pop().unwrap() == value, 'sload failed'); - } + vm.env.state.set_account(account); - #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_sstore_from_state() { - // Given - let (_, kakarot_core) = setup_contracts_for_testing(); - let mut vm = VMBuilderTrait::new_with_presets().build(); - let mut ca_address = deploy_contract_account( - kakarot_core, vm.message().target.evm, [].span() - ); let key: u256 = 0x100000000000000000000000000000001; let value: u256 = 0xABDE1E11A5; + vm.stack.push(value).expect('push failed'); vm.stack.push(key).expect('push failed'); // When - vm.exec_sstore().expect('exec sstore failed'); + start_mock_call::(starknet_address, selector!("storage"), 0); + let result = vm.exec_sstore(); // Then - assert(vm.env.state.read_state(evm_address(), key) == value, 'wrong value in state') + assert(result.is_ok(), 'exec sstore failed'); + assert(vm.env.state.read_state(evm_address, key) == value, 'wrong value in state'); } - #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_exec_sstore_on_account_undeployed() { // Given - setup_contracts_for_testing(); + setup_test_storages(); let mut vm = VMBuilderTrait::new_with_presets().build(); + let evm_address = vm.message().target.evm; let key: u256 = 0x100000000000000000000000000000001; let value: u256 = 0xABDE1E11A5; + vm.stack.push(value).expect('push failed'); vm.stack.push(key).expect('push failed'); // When - vm.exec_sstore().expect('exec sstore failed'); + start_mock_call::(native_token(), selector!("balanceOf"), 0); + let result = vm.exec_sstore(); // Then - assert(vm.env.state.read_state(evm_address(), key) == value, 'wrong value in state') + assert(result.is_ok(), 'exec sstore failed'); + assert(vm.env.state.read_state(evm_address, key) == value, 'wrong value in state'); } #[test] fn test_exec_sstore_on_contract_account_alive() { // Given - setup_contracts_for_testing(); + setup_test_storages(); let mut vm = VMBuilderTrait::new_with_presets().build(); + let test_address = test_address(); + let evm_address = vm.message().target.evm; + let starknet_address = compute_starknet_address( + test_address, evm_address, uninitialized_account() + ); + let account = Account { + address: Address { evm: evm_address, starknet: starknet_address }, code: [ + 0xab, 0xcd, 0xef + ].span(), nonce: 1, balance: 0, selfdestruct: false, is_created: false, + }; let key: u256 = 0x100000000000000000000000000000001; let value: u256 = 0xABDE1E11A5; + vm.stack.push(value).expect('push failed'); vm.stack.push(key).expect('push failed'); - // When - let account = Account { - address: vm.message().target, - code: [].span(), - nonce: 1, - balance: 0, - selfdestruct: false, - is_created: false, - }; vm.env.state.set_account(account); assert!(vm.env.state.is_account_alive(account.address.evm)); - vm.exec_sstore().expect('exec sstore failed'); + + // When + start_mock_call::(account.starknet_address(), selector!("storage"), 0); + let result = vm.exec_sstore(); // Then - assert(vm.env.state.read_state(evm_address(), key) == value, 'wrong value in state') + assert(result.is_ok(), 'exec sstore failed'); + assert(vm.env.state.read_state(evm_address, key) == value, 'wrong value in state'); } - #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_exec_sstore_should_fail_static_call() { // Given + setup_test_storages(); let mut vm = VMBuilderTrait::new_with_presets().with_read_only().build(); let key: u256 = 0x100000000000000000000000000000001; let value: u256 = 0xABDE1E11A5; @@ -934,6 +917,8 @@ mod tests { vm.stack.push(key).expect('push failed'); // When + start_mock_call::(vm.message().target.starknet, selector!("storage"), 0); + start_mock_call::(native_token(), selector!("balanceOf"), 0); let result = vm.exec_sstore(); // Then @@ -942,17 +927,23 @@ mod tests { } #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_exec_sstore_should_fail_gas_left_inf_call_stipend_eip_1706() { // Given + setup_test_storages(); let mut vm = VMBuilderTrait::new_with_presets().with_gas_left(gas::CALL_STIPEND).build(); + let test_address = test_address(); + let evm_address = vm.message().target.evm; + let starknet_address = compute_starknet_address( + test_address, evm_address, uninitialized_account() + ); let key: u256 = 0x100000000000000000000000000000001; let value: u256 = 0xABDE1E11A5; vm.stack.push(value).expect('push failed'); vm.stack.push(key).expect('push failed'); // When + start_mock_call::(starknet_address, selector!("storage"), 0); + start_mock_call::(native_token(), selector!("balanceOf"), 0); let result = vm.exec_sstore(); // Then @@ -960,37 +951,6 @@ mod tests { assert(result.unwrap_err() == EVMError::OutOfGas, 'wrong error returned'); } - #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_exec_sstore_finalized() { - // Given - // Setting the contract address is required so that `get_contract_address` in - // `CA::deploy` returns the kakarot address - let (_, kakarot_core) = setup_contracts_for_testing(); - let mut vm = VMBuilderTrait::new_with_presets().build(); - // Deploys the contract account to be able to commit storage changes. - let ca_address = deploy_contract_account(kakarot_core, vm.message().target.evm, [].span()); - let account = Account { - address: ca_address, - code: [].span(), - nonce: 1, - balance: 0, - selfdestruct: false, - is_created: false, - }; - let key: u256 = 0x100000000000000000000000000000001; - let value: u256 = 0xABDE1E11A5; - vm.stack.push(value).expect('push failed'); - vm.stack.push(key).expect('push failed'); - - // When - vm.exec_sstore().expect('exec_sstore failed'); - starknet_backend::commit(ref vm.env.state).expect('commit storage failed'); - - // Then - assert(fetch_original_storage(@account, key) == value, 'wrong committed value') - } #[test] fn test_gas_should_push_gas_left_to_stack() { diff --git a/crates/evm/src/instructions/system_operations.cairo b/crates/evm/src/instructions/system_operations.cairo index 5eef9a894..3efc3b6be 100644 --- a/crates/evm/src/instructions/system_operations.cairo +++ b/crates/evm/src/instructions/system_operations.cairo @@ -406,10 +406,6 @@ impl SystemOperations of SystemOperationsTrait { mod tests { use contracts::kakarot_core::interface::IExtendedKakarotCoreDispatcherTrait; use contracts::test_data::{storage_evm_bytecode, storage_evm_initcode}; - use contracts::test_utils::{ - fund_account_with_native_token, setup_contracts_for_testing, deploy_contract_account, - deploy_eoa - }; use core::result::ResultTrait; use core::starknet::EthAddress; use core::starknet::testing::set_contract_address; @@ -427,10 +423,12 @@ mod tests { use evm::stack::StackTrait; use evm::state::{StateTrait, State}; use evm::test_utils::{ - VMBuilderTrait, initialize_contract_account, native_token, evm_address, test_address, - other_evm_address, + VMBuilderTrait, native_token, evm_address, test_dual_address, other_evm_address, + setup_test_storages, register_account, origin, uninitialized_account }; use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait}; + use snforge_std::{test_address, start_mock_call}; + use utils::helpers::compute_starknet_address; use utils::helpers::load_word; use utils::traits::{EthAddressIntoU256}; @@ -493,15 +491,8 @@ mod tests { } #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_exec_call() { // Given - let (_, kakarot_core) = setup_contracts_for_testing(); - - let evm_address = evm_address(); - kakarot_core.deploy_externally_owned_account(evm_address); - // Set vm bytecode // (call 0xffffff 0xabfa740ccd 0 0 0 0 1) let bytecode = [ @@ -529,8 +520,16 @@ mod tests { 0xf1, 0x00 ].span(); - let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + let eoa_account = Account { + address: vm.message().target, + balance: 0, + code: [].span(), + nonce: 0, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(eoa_account); // Deploy bytecode at 0xabfa740ccd // ret (+ 0x1 0x1) @@ -538,8 +537,18 @@ mod tests { 0x60, 0x01, 0x60, 0x01, 0x01, 0x60, 0x00, 0x53, 0x60, 0x20, 0x60, 0x00, 0xf3 ].span(); let eth_address: EthAddress = 0xabfa740ccd_u256.into(); - initialize_contract_account(kakarot_core, eth_address, deployed_bytecode, [].span()) - .expect('set code failed'); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 0, + code: deployed_bytecode, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(contract_account); // When EVMTrait::execute_code(ref vm); @@ -551,14 +560,8 @@ mod tests { } #[test] - #[ignore] - //TODO(sn-foundry): fix because internal function fetch doesn't use kakarot's contract state fn test_exec_call_no_return() { // Given - let (_, kakarot_core) = setup_contracts_for_testing(); - - let evm_address = evm_address(); - kakarot_core.deploy_externally_owned_account(evm_address); // Set vm bytecode // (call 0xffffff 0xabfa740ccd 0 0 0 0 1) @@ -587,15 +590,33 @@ mod tests { 0xf1, 0x00 ].span(); - let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + let caller_account = Account { + address: vm.message().target, + balance: 0, + code: bytecode, + nonce: 0, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(caller_account); // Deploy bytecode at 0xabfa740ccd // (+ 0x1 0x1) let deployed_bytecode = [0x60, 0x01, 0x60, 0x01, 0x01, 0x60, 0x00, 0x53, 0x00].span(); let eth_address: EthAddress = 0xabfa740ccd_u256.into(); - initialize_contract_account(kakarot_core, eth_address, deployed_bytecode, [].span()) - .expect('set code failed'); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 0, + code: deployed_bytecode, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(contract_account); // When EVMTrait::execute_code(ref vm); @@ -603,21 +624,14 @@ mod tests { // Then assert(!vm.is_running(), 'run should be success'); assert(vm.return_data().is_empty(), 'Wrong return_data len'); - assert(!vm.is_running(), 'vm should be stopped') + assert(!vm.is_running(), 'vm should be stopped'); } #[test] - #[ignore] - //TODO(sn-foundry): fix because internal function fetch doesn't use kakarot's contract state fn test_exec_staticcall() { // Given - let (_, kakarot_core) = setup_contracts_for_testing(); - - let evm_address = evm_address(); - kakarot_core.deploy_externally_owned_account(evm_address); - // Set vm bytecode - // (call 0xffffff 0xabfa740ccd 0 0 0 0 1) + // (staticcall 0xffffff 0xabfa740ccd 0 0 0 0 1) let bytecode = [ 0x60, 0x01, @@ -643,34 +657,45 @@ mod tests { ].span(); let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + let caller_account = Account { + address: vm.message().target, + balance: 0, + code: bytecode, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(caller_account); // Deploy bytecode at 0xabfa740ccd // ret (+ 0x1 0x1) let deployed_bytecode = [ 0x60, 0x01, 0x60, 0x01, 0x01, 0x60, 0x00, 0x53, 0x60, 0x20, 0x60, 0x00, 0xf3 ].span(); let eth_address: EthAddress = 0xabfa740ccd_u256.into(); - initialize_contract_account(kakarot_core, eth_address, deployed_bytecode, [].span()) - .expect('set code failed'); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 0, + code: deployed_bytecode, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(contract_account); // When EVMTrait::execute_code(ref vm); // Then - assert(2 == load_word(1, vm.return_data()), 'Wrong return_data'); - assert(!vm.is_running(), 'vm should be stopped') + assert_eq!(2, load_word(1, vm.return_data())); + assert(!vm.is_running(), 'vm should be stopped'); } #[test] - #[ignore] - //TODO(sn-foundry): fix because internal function fetch doesn't use kakarot's contract state fn test_exec_staticcall_no_return() { // Given - - let (_, kakarot_core) = setup_contracts_for_testing(); - - let evm_address = evm_address(); - kakarot_core.deploy_externally_owned_account(evm_address); - // Set vm bytecode // (call 0xffffff 0xabfa740ccd 0 0 0 0 1) let bytecode = [ @@ -700,33 +725,45 @@ mod tests { ].span(); let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + let caller_account = Account { + address: vm.message().target, + balance: 0, + code: bytecode, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(caller_account); // Deploy bytecode at 0xabfa740ccd // (+ 0x1 0x1) let deployed_bytecode = [0x60, 0x01, 0x60, 0x01, 0x01, 0x60, 0x00, 0x53, 0x00].span(); let eth_address: EthAddress = 0xabfa740ccd_u256.into(); - initialize_contract_account(kakarot_core, eth_address, deployed_bytecode, [].span()) - .expect('set code failed'); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 0, + code: deployed_bytecode, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(contract_account); // When EVMTrait::execute_code(ref vm); // Then + assert(!vm.is_running(), 'run should be success'); assert(vm.return_data().is_empty(), 'Wrong return_data len'); assert(!vm.is_running(), 'vm should be stopped') } #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_exec_call_code() { // Given - - let (_, kakarot_core) = setup_contracts_for_testing(); - - let evm_address = evm_address(); - deploy_contract_account(kakarot_core, evm_address, [].span()); - // Set vm bytecode // (call 0xffffff 0x100 0 0 0 0 1) let bytecode = [ @@ -754,6 +791,16 @@ mod tests { 0x00 ].span(); let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + let eoa_account = Account { + address: vm.message().target, + balance: 0, + code: [].span(), + nonce: 0, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(eoa_account); + // Deploy bytecode at 0x100 // ret (+ 0x1 0x1) let deployed_bytecode = [ @@ -776,9 +823,19 @@ mod tests { 0x00, 0xf3 ].span(); - let eth_address: EthAddress = 0x100_u256.into(); - initialize_contract_account(kakarot_core, eth_address, deployed_bytecode, [].span()) - .expect('set code failed'); + let eth_address: EthAddress = 0x100.try_into().unwrap(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 0, + code: deployed_bytecode, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(contract_account); // When EVMTrait::execute_code(ref vm); @@ -788,22 +845,15 @@ mod tests { assert(2 == load_word(1, vm.return_data()), 'Wrong return_data'); assert(!vm.is_running(), 'vm should be stopped'); - let storage_val = vm.env.state.read_state(evm_address, 0x42); + let storage_val = vm.env.state.read_state(vm.message.target.evm, 0x42); assert(storage_val == 0x42, 'storage value is not 0x42'); } #[test] - #[ignore] - //TODO(sn-foundry): fix Contract not deployed at address: 0x0 fn test_exec_delegatecall() { // Given - let (_, kakarot_core) = setup_contracts_for_testing(); - - let evm_address = evm_address(); - deploy_contract_account(kakarot_core, evm_address, [].span()); - // Set vm bytecode // (call 0xffffff 0x100 0 0 0 0 1) let bytecode = [ @@ -828,9 +878,18 @@ mod tests { 0xf4, 0x00 ].span(); - let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + let eoa_account = Account { + address: vm.message().target, + balance: 0, + code: [].span(), + nonce: 0, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(eoa_account); + // Deploy bytecode at 0x100 // ret (+ 0x1 0x1) let deployed_bytecode = [ 0x60, @@ -852,9 +911,19 @@ mod tests { 0x00, 0xf3 ].span(); - let eth_address: EthAddress = 0x100_u256.into(); - initialize_contract_account(kakarot_core, eth_address, deployed_bytecode, [].span()) - .expect('set code failed'); + let eth_address: EthAddress = 0x100.try_into().unwrap(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 0, + code: deployed_bytecode, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(contract_account); // When EVMTrait::execute_code(ref vm); @@ -864,26 +933,51 @@ mod tests { assert(2 == load_word(1, vm.return_data()), 'Wrong return_data'); assert(!vm.is_running(), 'vm should be stopped'); - let storage_val = vm.env.state.read_state(evm_address, 0x42); + let storage_val = vm.env.state.read_state(vm.message.target.evm, 0x42); assert(storage_val == 0x42, 'storage value is not 0x42'); } + //! In the exec_create tests, we query the balance of the contract being created by doing a + //! starknet_call to the native token. + //! Thus, we must store the native token address in the Kakarot storage preemptively. + //! As such, the address computation uses the uninitialized account class. #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_exec_create_no_value_transfer() { // Given - let (native_token, kakarot_core) = setup_contracts_for_testing(); + setup_test_storages(); + let deployed_bytecode = [0xff].span(); let eth_address: EthAddress = evm_address(); - let contract_address = deploy_contract_account( - kakarot_core, eth_address, deployed_bytecode + let starknet_address = compute_starknet_address( + test_address(), eth_address, uninitialized_account() ); - - let mut vm = VMBuilderTrait::new_with_presets().with_target(contract_address).build(); - - fund_account_with_native_token(contract_address.starknet, native_token, 2); + let origin_account = Account { + address: Address { + evm: origin(), + starknet: compute_starknet_address( + test_address(), origin(), uninitialized_account() + ) + }, + balance: 2, + code: [].span(), + nonce: 0, + is_created: false, + selfdestruct: false, + }; + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 2, + code: deployed_bytecode, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + let mut vm = VMBuilderTrait::new_with_presets() + .with_target(contract_account.address) + .build(); + vm.env.state.set_account(contract_account); + vm.env.state.set_account(origin_account); // Load into memory the bytecode of Storage.sol let storage_initcode = storage_evm_initcode(); @@ -894,40 +988,62 @@ mod tests { vm.stack.push(0).expect('push failed'); // When + start_mock_call::(native_token(), selector!("balanceOf"), 0); vm.exec_create().unwrap(); EVMTrait::execute_code(ref vm); // computed using `compute_create_address` script - // run `bun run compute_create_address` -> CREATE -> EthAddress = evm_address() -> nonce = 1 + // run `bun run compute_create_address` -> CREATE -> EthAddress = evm_address() -> + //nonce = 1 let account = vm .env .state .get_account(0x930b3d8D35621F2e27Db700cA5D16Df771642fdD.try_into().unwrap()); assert_eq!(account.nonce(), 1); - assert(account.code == storage_evm_bytecode(), 'wrong bytecode'); + assert_eq!(account.code, storage_evm_bytecode()); assert_eq!(account.balance(), 0); let deployer = vm.env.state.get_account(eth_address); assert_eq!(deployer.nonce(), 2); assert_eq!(deployer.balance(), 2); } - //TODO add test with value transfer #[test] - #[ignore] fn test_exec_create_failure() { // Given - let (native_token, kakarot_core) = setup_contracts_for_testing(); + setup_test_storages(); let deployed_bytecode = [0xFF].span(); let eth_address: EthAddress = evm_address(); - let contract_address = deploy_contract_account( - kakarot_core, eth_address, deployed_bytecode + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() ); - fund_account_with_native_token(contract_address.starknet, native_token, 2); - let mut vm = VMBuilderTrait::new_with_presets().with_target(contract_address).build(); + let origin_account = Account { + address: Address { + evm: origin(), + starknet: compute_starknet_address( + test_address(), origin(), uninitialized_account() + ), + }, + balance: 2, + code: [].span(), + nonce: 0, + is_created: false, + selfdestruct: false, + }; + let deployer = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 2, + code: deployed_bytecode, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + let mut vm = VMBuilderTrait::new_with_presets().with_target(deployer.address).build(); + vm.env.state.set_account(deployer); + vm.env.state.set_account(origin_account); // Load into memory the bytecode to init, which is the revert opcode let revert_initcode = [0xFD].span(); @@ -938,6 +1054,7 @@ mod tests { vm.stack.push(1).expect('push failed'); // When + start_mock_call::(native_token(), selector!("balanceOf"), 0); vm.exec_create().expect('exec_create failed'); EVMTrait::execute_code(ref vm); @@ -950,23 +1067,47 @@ mod tests { assert_eq!(account.balance(), 0); let deployer = vm.env.state.get_account(eth_address); - assert_eq!(deployer.nonce(), 1); + assert_eq!(deployer.nonce(), 2); assert_eq!(deployer.balance(), 2); } #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_exec_create2() { // Given - let (_, kakarot_core) = setup_contracts_for_testing(); + setup_test_storages(); let deployed_bytecode = [0xff].span(); let eth_address: EthAddress = evm_address(); - let contract_address = deploy_contract_account( - kakarot_core, eth_address, deployed_bytecode + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() ); - let mut vm = VMBuilderTrait::new_with_presets().with_caller(contract_address).build(); + let origin_account = Account { + address: Address { + evm: origin(), + starknet: compute_starknet_address( + test_address(), origin(), uninitialized_account() + ), + }, + balance: 2, + code: [].span(), + nonce: 0, + is_created: false, + selfdestruct: false, + }; + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 2, + code: deployed_bytecode, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + let mut vm = VMBuilderTrait::new_with_presets() + .with_caller(contract_account.address) + .build(); + + vm.env.state.set_account(origin_account); + vm.env.state.set_account(contract_account); // Load into memory the bytecode of Storage.sol let storage_initcode = storage_evm_initcode(); @@ -978,6 +1119,7 @@ mod tests { vm.stack.push(0).expect('push failed'); // When + start_mock_call::(native_token(), selector!("balanceOf"), 0); vm.exec_create2().unwrap(); EVMTrait::execute_code(ref vm); @@ -988,9 +1130,10 @@ mod tests { // const address = getContractAddress({ // bytecode: - // '0x608060405234801561000f575f80fd5b506101438061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c80632e64cec1146100385780636057361d14610056575b5f80fd5b610040610072565b60405161004d919061009b565b60405180910390f35b610070600480360381019061006b91906100e2565b61007a565b005b5f8054905090565b805f8190555050565b5f819050919050565b61009581610083565b82525050565b5f6020820190506100ae5f83018461008c565b92915050565b5f80fd5b6100c181610083565b81146100cb575f80fd5b50565b5f813590506100dc816100b8565b92915050565b5f602082840312156100f7576100f66100b4565b5b5f610104848285016100ce565b9150509291505056fea2646970667358221220b5c3075f2f2034d039a227fac6dd314b052ffb2b3da52c7b6f5bc374d528ed3664736f6c63430008140033', + // + // '0x608060405234801561000f575f80fd5b506101438061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c80632e64cec1146100385780636057361d14610056575b5f80fd5b610040610072565b60405161004d919061009b565b60405180910390f35b610070600480360381019061006b91906100e2565b61007a565b005b5f8054905090565b805f8190555050565b5f819050919050565b61009581610083565b82525050565b5f6020820190506100ae5f83018461008c565b92915050565b5f80fd5b6100c181610083565b81146100cb575f80fd5b50565b5f813590506100dc816100b8565b92915050565b5f602082840312156100f7576100f66100b4565b5b5f610104848285016100ce565b9150509291505056fea2646970667358221220b5c3075f2f2034d039a227fac6dd314b052ffb2b3da52c7b6f5bc374d528ed3664736f6c63430008140033', // from: '0x00000000000000000065766d5f61646472657373', opcode: 'CREATE2', - // salt: '0x00', + //salt: '0x00', // }); // console.log(address) let account = vm @@ -1003,107 +1146,128 @@ mod tests { } #[test] - #[ignore] - fn test_exec_selfdestruct_existing_ca() { + fn test_exec_selfdestruct_should_fail_if_readonly() { // Given - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let destroyed_address = test_address().evm; // address in vm call context - let ca_address = deploy_contract_account( - kakarot_core, destroyed_address, [0x1, 0x2, 0x3].span() + let deployed_bytecode = [0xff].span(); + let eth_address: EthAddress = evm_address(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() ); - fund_account_with_native_token(ca_address.starknet, native_token, 1000); - let recipient = starknet_backend::deploy(other_evm_address()) - .expect('failed deploying eoa'); - let mut vm = VMBuilderTrait::new_with_presets().with_target(ca_address).build(); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 2, + code: deployed_bytecode, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + let mut vm = VMBuilderTrait::new_with_presets() + .with_target(contract_account.address) + .with_read_only() + .build(); + vm.env.state.set_account(contract_account); + // When - vm.stack.push(recipient.evm.into()).unwrap(); - vm.exec_selfdestruct().expect('selfdestruct failed'); - starknet_backend::commit(ref vm.env.state).expect('commit state failed'); - vm.env.state = Default::default(); //empty state to force re-fetch from SN + vm.stack.push(contract_account.address.evm.into()).unwrap(); + let res = vm.exec_selfdestruct(); // Then - let destructed = vm.env.state.get_account(ca_address.evm); - - assert(destructed.nonce() == 0, 'destructed nonce should be 0'); - assert(destructed.balance() == 0, 'destructed balance should be 0'); - assert(destructed.bytecode().len() == 0, 'bytecode should be empty'); - - let recipient = vm.env.state.get_account(recipient.evm); - assert_eq!(recipient.balance(), 1000); + assert!(res.is_err()) } #[test] - #[ignore] - fn test_selfdestruct_undeployed_ca() { - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let evm_address: EthAddress = 'ca_address'.try_into().unwrap(); - let ca_address: Address = Address { - evm: evm_address, starknet: kakarot_core.compute_starknet_address(evm_address) + fn test_exec_selfdestruct_should_burn_tokens_if_created_same_tx_and_recipient_self() { + // Given + let deployed_bytecode = [0xff].span(); + let eth_address: EthAddress = evm_address(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 2, + code: deployed_bytecode, + nonce: 1, + is_created: true, + selfdestruct: false, + }; + let burn_account = Account { + address: Address { + evm: 0.try_into().unwrap(), + starknet: compute_starknet_address( + test_address(), 0.try_into().unwrap(), 0.try_into().unwrap() + ), + }, + balance: 0, + code: [].span(), + nonce: 0, + is_created: false, + selfdestruct: false, }; - let recipient_address: EthAddress = 'recipient_address'.try_into().unwrap(); - deploy_eoa(kakarot_core, recipient_address); - let ca_balance = 1000; - fund_account_with_native_token(ca_address.starknet, native_token, ca_balance); - let mut vm = VMBuilderTrait::new_with_presets().with_target(ca_address).build(); - // - call `get_account` on an undeployed account, set its type to CA, its nonce to 1, its - // code to something to mock a cached CA that has not been committed yet. - let mut ca_account = vm.env.state.get_account(ca_address.evm); - ca_account.set_code([0x1, 0x2, 0x3].span()); - ca_account.set_nonce(1); - vm.env.state.set_account(ca_account); - // - call selfdestruct and commit the state - vm.stack.push(recipient_address.into()).expect('push failed'); + let mut vm = VMBuilderTrait::new_with_presets() + .with_target(contract_account.address) + .build(); + vm.env.state.set_account(burn_account); + vm.env.state.set_account(contract_account); + + // When + vm.stack.push(contract_account.address.evm.into()).unwrap(); vm.exec_selfdestruct().expect('selfdestruct failed'); - starknet_backend::commit(ref vm.env.state).expect('commit state failed'); - vm.env.state = Default::default(); //empty state to force re-fetch from SN // Then - let destructed = vm.env.state.get_account(ca_address.evm); - assert(destructed.nonce() == 0, 'destructed nonce should be 0'); - assert(destructed.balance() == 0, 'destructed balance should be 0'); - assert(destructed.bytecode().len() == 0, 'bytecode should be empty'); - let recipient = vm.env.state.get_account(recipient_address); - assert(recipient.balance() == ca_balance, 'wrong recipient balance'); + let contract_account = vm.env.state.get_account(contract_account.address.evm); + assert!(contract_account.is_selfdestruct()); + assert_eq!(contract_account.balance(), 0); + + let burn_account = vm.env.state.get_account(burn_account.address.evm); + assert_eq!(burn_account.balance(), 2); } #[test] - #[ignore] - fn test_exec_selfdestruct_add_transfer_post_selfdestruct() { + fn test_exec_selfdestruct_should_transfer_balance_to_recipient() { // Given - let (native_token, kakarot_core) = setup_contracts_for_testing(); - - // Deploy sender and recipiens EOAs, and CA that will be selfdestructed and funded with 100 - // tokens - let sender = starknet_backend::deploy('sender'.try_into().unwrap()) - .expect('failed deploy EOA',); - let recipient = starknet_backend::deploy('recipient'.try_into().unwrap()) - .expect('failed deploy EOA',); - let ca_address = deploy_contract_account( - kakarot_core, 'contract'.try_into().unwrap(), [].span() + let deployed_bytecode = [0xff].span(); + let eth_address: EthAddress = evm_address(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() ); - fund_account_with_native_token(sender.starknet, native_token, 150); - fund_account_with_native_token(ca_address.starknet, native_token, 100); - let mut vm = VMBuilderTrait::new_with_presets().with_target(ca_address).build(); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 2, + code: deployed_bytecode, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + let recipient = Account { + address: Address { + evm: 'recipient'.try_into().unwrap(), + starknet: compute_starknet_address( + test_address(), 'recipient'.try_into().unwrap(), 0.try_into().unwrap() + ), + }, + balance: 0, + code: [].span(), + nonce: 0, + is_created: false, + selfdestruct: false, + }; - // Cache the CA into state - vm.env.state.get_account('contract'.try_into().unwrap()); + let mut vm = VMBuilderTrait::new_with_presets() + .with_target(contract_account.address) + .build(); + vm.env.state.set_account(contract_account); + vm.env.state.set_account(recipient); // When - vm.stack.push(recipient.evm.into()).unwrap(); + vm.stack.push(recipient.address.evm.into()).unwrap(); vm.exec_selfdestruct().expect('selfdestruct failed'); - // Add a transfer from sender to CA - after it was selfdestructed in local state. This - // transfer should go through. - let transfer = Transfer { sender, recipient: ca_address, amount: 150 }; - vm.env.state.add_transfer(transfer).unwrap(); - starknet_backend::commit(ref vm.env.state).expect('commit state failed'); - vm.env.state = Default::default(); //empty state to force re-fetch from SN // Then - let recipient_balance = native_token.balanceOf(recipient.starknet); - let sender_balance = native_token.balanceOf(sender.starknet); - let ca_balance = native_token.balanceOf(ca_address.starknet); + let contract_account = vm.env.state.get_account(contract_account.address.evm); + assert!(contract_account.is_selfdestruct()); + assert_eq!(contract_account.balance(), 0); - assert(recipient_balance == 100, 'recipient wrong balance'); - assert(sender_balance == 0, 'sender wrong balance'); - assert(ca_balance == 150, 'ca wrong balance'); + let recipient = vm.env.state.get_account(recipient.address.evm); + assert_eq!(recipient.balance(), 2); } } diff --git a/crates/evm/src/model.cairo b/crates/evm/src/model.cairo index 830fee26a..0b6e4491c 100644 --- a/crates/evm/src/model.cairo +++ b/crates/evm/src/model.cairo @@ -144,14 +144,14 @@ impl AddressImpl of AddressTrait { fn is_precompile(self: EthAddress) -> bool { let self: felt252 = self.into(); return self != ZERO - && (self.into() < LAST_ETHEREUM_PRECOMPILE_ADDRESS + && (self.into() <= LAST_ETHEREUM_PRECOMPILE_ADDRESS || self.into() == FIRST_ROLLUP_PRECOMPILE_ADDRESS); } } /// A struct to save native token transfers to be made when finalizing /// a tx -#[derive(Copy, Drop, PartialEq)] +#[derive(Copy, Drop, PartialEq, Debug)] struct Transfer { sender: Address, recipient: Address, @@ -162,322 +162,91 @@ struct Transfer { mod tests { use contracts::account_contract::{IAccountDispatcher, IAccountDispatcherTrait}; use contracts::kakarot_core::interface::IExtendedKakarotCoreDispatcherTrait; - use contracts::test_utils::{ - setup_contracts_for_testing, fund_account_with_native_token, deploy_contract_account, - deploy_eoa - }; use core::starknet::EthAddress; use evm::backend::starknet_backend; use evm::model::account::AccountTrait; - use evm::model::{Address, Account, AddressTrait}; use evm::state::StateTrait; use evm::state::{State, StateChangeLog, StateChangeLogTrait}; - use evm::test_utils::{declare_and_store_classes}; - use evm::test_utils::{evm_address}; + use evm::test_utils; use openzeppelin::token::erc20::interface::IERC20CamelDispatcherTrait; - use snforge_std::{start_cheat_caller_address, stop_cheat_caller_address, test_address}; - #[test] - #[ignore] - //TODO(sn-foundry): fix Class with hash - //0x0000000000000000000000000000000000000000000000000000000000000000 is not declared. - fn test_is_deployed_eoa_exists() { - // Given - let (_, kakarot_core) = setup_contracts_for_testing(); - starknet_backend::deploy(evm_address()).expect('failed deploy eoa account',); + mod test_is_deployed { + use evm::model::{Address, Account, AddressTrait}; + use evm::test_utils; + use snforge_std::{test_address, start_mock_call}; + use utils::helpers::compute_starknet_address; - // When - // start_cheat_caller_address(kakarot_core.contract_address, evm_address()); - let is_deployed = evm_address().is_deployed(); - // Then - assert(is_deployed, 'account should be deployed'); - } - - #[test] - #[ignore] - //TODO(sn-foundry): fix because internal function context is not deployed kakarot context - fn test_is_deployed_returns_true_if_in_registry() { - // Given - let (_, kakarot_core) = setup_contracts_for_testing(); - deploy_contract_account(kakarot_core, evm_address(), [].span()); - - // When - let is_deployed = evm_address().is_deployed(); - - // Then - assert(is_deployed, 'account should be deployed'); - } - - #[test] - fn test_is_deployed_undeployed() { - // Given - let (_, kakarot_core) = setup_contracts_for_testing(); - - // When - // set_contract_address(kakarot_core.contract_address); - let is_deployed = evm_address().is_deployed(); - - // Then - assert(!is_deployed, 'account shouldnt be deployed'); - } - - #[test] - #[ignore] - //TODO(sn-foundry): fix because internal function fetch doesn't use kakarot's contract state - fn test_account_balance_eoa() { - // Given - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let eoa_address = deploy_eoa(kakarot_core, evm_address()); - - fund_account_with_native_token(eoa_address.contract_address, native_token, 0x1); - - // When - // set_contract_address(kakarot_core.contract_address); - let account = AccountTrait::fetch(evm_address()).unwrap(); - let balance = account.balance(); - - // Then - assert(balance == native_token.balanceOf(eoa_address.contract_address), 'wrong balance'); - } - - #[test] - #[ignore] - //TODO(sn-foundry): fix because internal function fetch doesn't use kakarot's contract state - fn test_address_balance_eoa() { - // Given - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let eoa_address = starknet_backend::deploy(evm_address()) - .expect('failed deploy eoa account',); - - fund_account_with_native_token(eoa_address.starknet, native_token, 0x1); - - // When - // set_contract_address(kakarot_core.contract_address); - let account = AccountTrait::fetch(evm_address()).unwrap(); - let balance = account.balance(); - - // Then - assert(balance == native_token.balanceOf(eoa_address.starknet), 'wrong balance'); - } - - - #[test] - #[ignore] - //TODO(sn-foundry): fix Entry point - //EntryPointSelector(0x11f99ee2dc5094f0126c3db5401e3a1a2b6b440f4740e6cce884709cd4526df) not - //found in contract. Probably due to how snfoundry handles deployments. - fn test_account_has_code_or_nonce_empty() { - // Given - declare_and_store_classes(); - let mut _eoa_address = starknet_backend::deploy(evm_address()).expect('failed deploy eoa',); - - // When - let account = AccountTrait::fetch(evm_address()).unwrap(); - - // Then - assert_eq!(account.has_code_or_nonce(), false); - } - - - #[test] - #[ignore] - //TODO(sn-foundry): fix because internal function fetch doesn't use kakarot's contract state - fn test_account_has_code_or_nonce_contract_account() { - // Given - let (_, kakarot_core) = setup_contracts_for_testing(); - let mut _ca_address = deploy_contract_account(kakarot_core, evm_address(), [].span()); - - // When - let account = AccountTrait::fetch(evm_address()).unwrap(); - - // Then - assert(account.has_code_or_nonce() == true, 'account shouldhave codeornonce'); - } - - - #[test] - #[ignore] - //TODO(sn-foundry): Contract not deployed at address: 0x0 - fn test_account_has_code_or_nonce_undeployed() { - // Given - setup_contracts_for_testing(); - - // When - let account = AccountTrait::fetch_or_create(evm_address()); - - // Then - assert(account.has_code_or_nonce() == false, 'account has codeornonce'); - } + #[test] + fn test_is_deployed_returns_true_if_in_registry() { + // Given + test_utils::setup_test_storages(); + let starknet_address = compute_starknet_address( + test_address(), test_utils::evm_address(), test_utils::uninitialized_account() + ); + test_utils::register_account(test_utils::evm_address(), starknet_address); - #[test] - #[ignore] - //TODO(sn-foundry): fix Contract not deployed at address: 0x0 - fn test_account_has_code_or_nonce_account_to_deploy() { - // Given - setup_contracts_for_testing(); + // When + let is_deployed = test_utils::evm_address().is_deployed(); - // When - let mut account = AccountTrait::fetch_or_create(evm_address()); - // Mock account as an existing contract account in the cached state. - account.nonce = 1; - account.code = [0x1].span(); - - // Then - assert(account.has_code_or_nonce() == true, 'account should exist'); - } - - - #[test] - #[ignore] - //TODO(sn-foundry): fix because internal function fetch doesn't use kakarot's contract state - fn test_account_balance_contract_account() { - // Given - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let mut ca_address = deploy_contract_account(kakarot_core, evm_address(), [].span()); - - fund_account_with_native_token(ca_address.starknet, native_token, 0x1); - - // When - let account = AccountTrait::fetch(evm_address()).unwrap(); - let balance = account.balance(); - - // Then - assert(balance == native_token.balanceOf(ca_address.starknet), 'wrong balance'); - } - - #[test] - #[ignore] - //TODO(sn-foundry): fix because internal function fetch doesn't use kakarot's contract state - fn test_account_commit_already_deployed_should_not_change_code() { - let (_, kakarot_core) = setup_contracts_for_testing(); - let mut ca_address = deploy_contract_account(kakarot_core, evm_address(), [].span()); - - let mut state: State = Default::default(); - - // When - let mut account = AccountTrait::fetch(evm_address()).unwrap(); - account.nonce = 420; - account.code = [0x1].span(); - state.set_account(account); - starknet_backend::commit(ref state).expect('commitment failed'); - - // Then - let account_dispatcher = IAccountDispatcher { contract_address: ca_address.starknet }; - let nonce = account_dispatcher.get_nonce(); - let code = account_dispatcher.bytecode(); - assert(nonce == 420, 'account should be committed'); - assert!(code != [0x1].span(), "code should not be mofidied unless account is created") - } - - #[test] - #[ignore] - //TODO(sn-foundry): fix because internal function fetch doesn't use kakarot's contract state - fn test_account_commit_created_but_already_deployed() { - let (_, kakarot_core) = setup_contracts_for_testing(); - let mut ca_address = deploy_contract_account(kakarot_core, evm_address(), [].span()); - - // When created in this same tx, the account should have a new code. - - let mut state: State = Default::default(); - let mut account = AccountTrait::fetch(evm_address()).unwrap(); - account.set_created(true); - account.nonce = 420; - account.code = [0x1].span(); - state.set_account(account); - starknet_backend::commit(ref state).expect('commitment failed'); - - // Then - let account_dispatcher = IAccountDispatcher { contract_address: ca_address.starknet }; - let nonce = account_dispatcher.get_nonce(); - let code = account_dispatcher.bytecode(); - assert(nonce == 420, 'nonce should be modified'); - assert(code == [0x1].span(), 'code should be modified'); - } - - #[test] - #[ignore] - //TODO(sn-foundry): fix Requested - // ContractAddress(PatriciaKey(0x4ece8ed414f739b8be684d13c9094571064726869b0f34b9468e2fd73c01f4b)) - //is unavailable for deployment. - fn test_account_commit_undeployed() { - let (_, kakarot_core) = setup_contracts_for_testing(); - declare_and_store_classes(); - - let evm = evm_address(); - let starknet = kakarot_core.compute_starknet_address(evm); - let mut state: State = Default::default(); - // When - let mut account = Account { - address: Address { evm, starknet }, nonce: 420, code: [ - 0x69 - ].span(), balance: 0, selfdestruct: false, is_created: true, - }; - state.set_account(account); - starknet_backend::commit(ref state).expect('commitment failed'); - - // Then - let account_dispatcher = IAccountDispatcher { contract_address: starknet }; - let nonce = account_dispatcher.get_nonce(); - let code = account_dispatcher.bytecode(); - assert(nonce == 420, 'nonce should be committed'); - assert(code == [0x69].span(), 'code should be committed'); - } - - #[test] - #[ignore] - //TODO(sn-foundry): fix because internal function fetch doesn't use kakarot's contract state - fn test_address_balance_contract_account() { - // Given - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let mut ca_address = deploy_contract_account(kakarot_core, evm_address(), [].span()); - - fund_account_with_native_token(ca_address.starknet, native_token, 0x1); - - // When - let account = AccountTrait::fetch(evm_address()).unwrap(); - let balance = account.balance(); + // Then + assert!(is_deployed); + } - // Then - assert(balance == native_token.balanceOf(ca_address.starknet), 'wrong balance'); - } + #[test] + fn test_is_deployed_undeployed() { + // Given + test_utils::setup_test_storages(); - #[test] - fn test_is_precompile() { - // Given - let valid_precompiles = array![0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x100]; + // When + let is_deployed = test_utils::evm_address().is_deployed(); - //When - for el in valid_precompiles { - let evm_address: EthAddress = (el).try_into().unwrap(); - //Then - assert_eq!(true, evm_address.is_precompile()); - }; + // Then + assert!(!is_deployed); + } } + mod test_is_precompile { + use core::starknet::EthAddress; + use evm::model::{AddressTrait}; + #[test] + fn test_is_precompile() { + // Given + let valid_precompiles = array![ + 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x0a, 0x100 + ]; + + //When + for el in valid_precompiles { + let evm_address: EthAddress = (el).try_into().unwrap(); + //Then + assert_eq!(true, evm_address.is_precompile()); + }; + } - #[test] - fn test_is_precompile_zero() { - // Given - let evm_address: EthAddress = 0x0.try_into().unwrap(); - - // When - let is_precompile = evm_address.is_precompile(); + #[test] + fn test_is_precompile_zero() { + // Given + let evm_address: EthAddress = 0x0.try_into().unwrap(); - // Then - assert_eq!(false, is_precompile); - } + // When + let is_precompile = evm_address.is_precompile(); - #[test] - fn test_is_not_precompile() { - // Given - let not_valid_precompiles = array![0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x99]; + // Then + assert_eq!(false, is_precompile); + } - //When - for el in not_valid_precompiles { - let evm_address: EthAddress = (el).try_into().unwrap(); - //Then - assert_eq!(false, evm_address.is_precompile()); - }; + #[test] + fn test_is_not_precompile() { + // Given + let not_valid_precompiles = array![0xb, 0xc, 0xd, 0xe, 0xf, 0x99]; + + //When + for el in not_valid_precompiles { + let evm_address: EthAddress = (el).try_into().unwrap(); + //Then + assert_eq!(false, evm_address.is_precompile()); + }; + } } } diff --git a/crates/evm/src/model/account.cairo b/crates/evm/src/model/account.cairo index 19b54fa50..1f39eddb1 100644 --- a/crates/evm/src/model/account.cairo +++ b/crates/evm/src/model/account.cairo @@ -67,7 +67,7 @@ impl AccountBuilderImpl of AccountBuilderTrait { } } -#[derive(Copy, Drop, PartialEq)] +#[derive(Copy, Drop, PartialEq, Debug)] struct Account { address: Address, code: Span, @@ -95,8 +95,8 @@ impl AccountImpl of AccountTrait { let kakarot_state = KakarotCore::unsafe_new_contract_state(); let starknet_address = kakarot_state.compute_starknet_address(evm_address); // If no account exists at `address`, then we are trying to - // access an undeployed account (CA or EOA). We create an - // empty account with the correct address and return it. + // access an undeployed account. We create an + // empty account with the correct address, fetch the balance, and return it. AccountBuilderTrait::new(Address { starknet: starknet_address, evm: evm_address }) .fetch_balance() .build() @@ -255,3 +255,178 @@ impl AccountInternals of AccountInternalTrait { self.balance = value; } } + +#[cfg(test)] +mod tests { + mod test_has_code_or_nonce { + use core::starknet::{ContractAddress, EthAddress}; + use evm::model::account::{Account, AccountTrait, Address}; + + #[test] + fn test_should_return_false_when_empty() { + let account = Account { + address: Address { evm: 1.try_into().unwrap(), starknet: 1.try_into().unwrap() }, + nonce: 0, + code: [].span(), + balance: 0, + selfdestruct: false, + is_created: false, + }; + + assert!(!account.has_code_or_nonce()); + } + + #[test] + fn test_should_return_true_when_code() { + let account = Account { + address: Address { evm: 1.try_into().unwrap(), starknet: 1.try_into().unwrap() }, + nonce: 1, + code: [ + 0x5b + ].span(), balance: 0, selfdestruct: false, is_created: false, + }; + + assert!(account.has_code_or_nonce()); + } + + #[test] + fn test_should_return_true_when_nonce() { + let account = Account { + address: Address { evm: 1.try_into().unwrap(), starknet: 1.try_into().unwrap() }, + nonce: 1, + code: [].span(), + balance: 0, + selfdestruct: false, + is_created: false, + }; + + assert!(account.has_code_or_nonce()); + } + } + + mod test_fetch { + use evm::model::account::{Account, AccountTrait, Address}; + use evm::test_utils::{ + register_account, setup_test_storages, uninitialized_account, evm_address, native_token, + }; + use snforge_std::{test_address, start_mock_call}; + use snforge_utils::snforge_utils::{assert_called, assert_called_with}; + use utils::helpers::compute_starknet_address; + + #[test] + fn test_should_fetch_data_from_storage_if_registered() { + // Given + setup_test_storages(); + let starknet_address = compute_starknet_address( + test_address(), evm_address(), uninitialized_account() + ); + register_account(evm_address(), starknet_address); + + let expected = Account { + address: Address { evm: evm_address(), starknet: starknet_address }, + nonce: 1, + code: [].span(), + balance: 100, + selfdestruct: false, + is_created: false, + }; + + // When + start_mock_call::(native_token(), selector!("balanceOf"), 100); + start_mock_call::(starknet_address, selector!("get_nonce"), 1); + start_mock_call::>(starknet_address, selector!("bytecode"), [].span()); + let account = AccountTrait::fetch(evm_address()).expect('Account should exist'); + + // Then + assert_eq!(account, expected); + assert_called(starknet_address, selector!("get_nonce")); + assert_called(starknet_address, selector!("bytecode")); + //TODO(starknet-foundry): we mocked the balanceOf call, but we should also check if it + //was called with the right data + assert_called(native_token(), selector!("balanceOf")); + } + + #[test] + fn test_should_return_none_if_not_registered() { + // Given + setup_test_storages(); + let starknet_address = compute_starknet_address( + test_address(), evm_address(), uninitialized_account() + ); + + assert!(AccountTrait::fetch(evm_address()).is_none()); + } + } + + mod test_fetch_or_create { + use evm::model::account::{Account, AccountTrait, Address}; + use evm::test_utils::{ + register_account, setup_test_storages, uninitialized_account, evm_address, native_token, + }; + use snforge_std::{test_address, start_mock_call}; + use snforge_utils::snforge_utils::{assert_called, assert_called_with}; + use utils::helpers::compute_starknet_address; + + #[test] + fn test_should_fetch_data_from_storage_if_registered() { + // Given + setup_test_storages(); + let starknet_address = compute_starknet_address( + test_address(), evm_address(), uninitialized_account() + ); + register_account(evm_address(), starknet_address); + + let expected = Account { + address: Address { evm: evm_address(), starknet: starknet_address }, + nonce: 1, + code: [].span(), + balance: 100, + selfdestruct: false, + is_created: false, + }; + + // When + start_mock_call::(native_token(), selector!("balanceOf"), 100); + start_mock_call::(starknet_address, selector!("get_nonce"), 1); + start_mock_call::>(starknet_address, selector!("bytecode"), [].span()); + let account = AccountTrait::fetch_or_create(evm_address()); + + // Then + assert_eq!(account, expected); + assert_called(starknet_address, selector!("get_nonce")); + assert_called(starknet_address, selector!("bytecode")); + //TODO(starknet-foundry): we mocked the balanceOf call, but we should also check if it + //was called with the right data + assert_called(native_token(), selector!("balanceOf")); + } + + #[test] + fn test_should_create_new_account_with_starknet_balance_if_not_registered() { + // Given + setup_test_storages(); + let starknet_address = compute_starknet_address( + test_address(), evm_address(), uninitialized_account() + ); + + let expected = Account { + address: Address { evm: evm_address(), starknet: starknet_address }, + nonce: 0, + code: [].span(), + balance: 50, + selfdestruct: false, + is_created: false, + }; + + // When + start_mock_call::(native_token(), selector!("balanceOf"), 50); + let account = AccountTrait::fetch_or_create(evm_address()); + + // Then + assert_eq!(account, expected); + //TODO(starknet-foundry): we mocked the balanceOf call, but we should also check if it + //was called with the right data + assert_called(native_token(), selector!("balanceOf")); + } + } + //TODO(starknet-foundry): add a test for get_jumpdests +} diff --git a/crates/evm/src/precompiles/blake2f.cairo b/crates/evm/src/precompiles/blake2f.cairo index 52dd9dc83..1056579ce 100644 --- a/crates/evm/src/precompiles/blake2f.cairo +++ b/crates/evm/src/precompiles/blake2f.cairo @@ -83,7 +83,6 @@ impl Blake2f of Precompile { #[cfg(test)] mod tests { - use contracts::test_utils::{setup_contracts_for_testing}; use core::array::SpanTrait; use core::starknet::testing::set_contract_address; use evm::errors::EVMError; @@ -100,7 +99,10 @@ mod tests { blake2_precompile_fail_wrong_length_input_3_test_case, blake2_precompile_pass_1_test_case, blake2_precompile_pass_0_test_case, blake2_precompile_pass_2_test_case }; - use evm::test_utils::{VMBuilderTrait, native_token, other_starknet_address}; + use evm::test_utils::{ + VMBuilderTrait, native_token, other_starknet_address, setup_test_storages + }; + use snforge_std::{start_mock_call, test_address}; use utils::helpers::FromBytes; #[test] @@ -170,10 +172,8 @@ mod tests { // source: // #[test] - #[ignore] - //TODO(sn-foundry): fix or delete fn test_blake2_precompile_static_call() { - let (_, _) = setup_contracts_for_testing(); + setup_test_storages(); let mut vm = VMBuilderTrait::new_with_presets().build(); @@ -212,6 +212,7 @@ mod tests { vm.stack.push(9).unwrap(); // address vm.stack.push(0xFFFFFFFF).unwrap(); // gas + start_mock_call::(native_token(), selector!("balanceOf"), 0); vm.exec_staticcall().unwrap(); let mut result: Array = Default::default(); diff --git a/crates/evm/src/precompiles/ec_recover.cairo b/crates/evm/src/precompiles/ec_recover.cairo index 6510dbd49..7774b3db6 100644 --- a/crates/evm/src/precompiles/ec_recover.cairo +++ b/crates/evm/src/precompiles/ec_recover.cairo @@ -72,7 +72,6 @@ impl EcRecover of Precompile { #[cfg(test)] mod tests { - use contracts::test_utils::setup_contracts_for_testing; use core::array::ArrayTrait; use evm::instructions::system_operations::SystemOperationsTrait; use evm::memory::InternalMemoryTrait; @@ -80,7 +79,9 @@ mod tests { use evm::precompiles::ec_recover::EcRecover; use evm::stack::StackTrait; + use evm::test_utils::setup_test_storages; use evm::test_utils::{VMBuilderTrait, native_token, other_starknet_address}; + use snforge_std::{start_mock_call, test_address}; use utils::helpers::{U256Trait, ToBytes, FromBytes}; @@ -88,8 +89,6 @@ mod tests { // #[test] fn test_ec_recover_precompile() { - let (_, _) = setup_contracts_for_testing(); - let msg_hash = 0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3_u256 .to_be_bytes_padded(); let v = 28_u256.to_be_bytes_padded(); @@ -114,11 +113,8 @@ mod tests { // source: // #[test] - #[ignore] - //TODO(sn-foundry): fix Contract not deployed at address: 0x0 fn test_ec_precompile_static_call() { - let (_, _) = setup_contracts_for_testing(); - + setup_test_storages(); let mut vm = VMBuilderTrait::new_with_presets().build(); vm @@ -141,6 +137,7 @@ mod tests { vm.stack.push(0x1).unwrap(); // address vm.stack.push(0xFFFFFFFF).unwrap(); // gas + start_mock_call::(native_token(), selector!("balanceOf"), 0); vm.exec_staticcall().unwrap(); let result = vm.memory.load(0x80); diff --git a/crates/evm/src/precompiles/identity.cairo b/crates/evm/src/precompiles/identity.cairo index 76334bc6f..c9f602f33 100644 --- a/crates/evm/src/precompiles/identity.cairo +++ b/crates/evm/src/precompiles/identity.cairo @@ -23,7 +23,6 @@ impl Identity of Precompile { #[cfg(test)] mod tests { - use contracts::test_utils::{setup_contracts_for_testing}; use core::clone::Clone; use core::result::ResultTrait; use core::starknet::testing::set_contract_address; @@ -32,7 +31,10 @@ mod tests { use evm::memory::MemoryTrait; use evm::precompiles::identity::Identity; use evm::stack::StackTrait; - use evm::test_utils::{VMBuilderTrait, native_token, other_starknet_address}; + use evm::test_utils::{ + VMBuilderTrait, native_token, other_starknet_address, setup_test_storages + }; + use snforge_std::{start_mock_call, test_address}; // source: // @@ -51,10 +53,8 @@ mod tests { // //TODO(sn-foundry): fix or delete #[test] - #[ignore] fn test_identity_precompile_static_call() { - let (_, _) = setup_contracts_for_testing(); - + setup_test_storages(); let mut vm = VMBuilderTrait::new_with_presets().build(); vm.stack.push(0x20).unwrap(); // retSize @@ -66,6 +66,7 @@ mod tests { vm.memory.store(0x2A, 0x1F); + start_mock_call::(native_token(), selector!("balanceOf"), 0); vm.exec_staticcall().unwrap(); let result = vm.memory.load(0x3F); diff --git a/crates/evm/src/precompiles/modexp.cairo b/crates/evm/src/precompiles/modexp.cairo index bf059f082..d95eb5fa3 100644 --- a/crates/evm/src/precompiles/modexp.cairo +++ b/crates/evm/src/precompiles/modexp.cairo @@ -154,7 +154,6 @@ fn calculate_iteration_count(exp_length: u64, exp_highp: u256) -> u64 { #[cfg(tests)] mod tests { - use contracts::test_utils::{setup_contracts_for_testing}; use core::result::ResultTrait; use core::starknet::EthAddress; use core::starknet::testing::set_contract_address; @@ -165,13 +164,13 @@ mod tests { use evm::precompiles::Precompiles; use evm::stack::StackTrait; use evm::test_utils::{VMBuilderTrait, native_token, other_starknet_address}; - use evm_tests::test_precompiles::test_data::test_data_modexp::{ test_modexp_modsize0_returndatasizeFiller_data, test_modexp_create2callPrecompiles_test0_berlin_data, test_modexp_eip198_example_1_data, test_modexp_eip198_example_2_data, test_modexp_nagydani_1_square_data, test_modexp_nagydani_1_qube_data }; + use snforge_std::{start_mock_call, test_address}; use utils::helpers::U256Trait; // the tests are taken from diff --git a/crates/evm/src/precompiles/p256verify.cairo b/crates/evm/src/precompiles/p256verify.cairo index 8af9e810b..0105e53f3 100644 --- a/crates/evm/src/precompiles/p256verify.cairo +++ b/crates/evm/src/precompiles/p256verify.cairo @@ -68,7 +68,6 @@ impl P256Verify of Precompile { #[cfg(test)] mod tests { - use contracts::test_utils::setup_contracts_for_testing; use core::array::ArrayTrait; use evm::instructions::system_operations::SystemOperationsTrait; use evm::memory::InternalMemoryTrait; @@ -77,6 +76,8 @@ mod tests { use evm::precompiles::p256verify::P256Verify; use evm::stack::StackTrait; use evm::test_utils::{VMBuilderTrait}; + use evm::test_utils::{setup_test_storages, native_token}; + use snforge_std::{start_mock_call, test_address}; use utils::helpers::{U256Trait, ToBytes, FromBytes}; @@ -112,10 +113,9 @@ mod tests { // source: // #[test] - #[ignore] //TODO(sn-foundry): fix or delete fn test_p256verify_precompile_static_call() { - let (_, _) = setup_contracts_for_testing(); + setup_test_storages(); let mut vm = VMBuilderTrait::new_with_presets().build(); @@ -144,6 +144,7 @@ mod tests { vm.stack.push(0x100).unwrap(); // address vm.stack.push(0xFFFFFFFF).unwrap(); // gas + start_mock_call::(native_token(), selector!("balanceOf"), 0); vm.exec_staticcall().unwrap(); let mut result = Default::default(); @@ -177,9 +178,8 @@ mod tests { //TODO(sn-foundry): fix or delete #[test] - #[ignore] fn test_p256verify_precompile_input_too_short_static_call() { - let (_, _) = setup_contracts_for_testing(); + setup_test_storages(); let mut vm = VMBuilderTrait::new_with_presets().build(); @@ -205,6 +205,7 @@ mod tests { vm.stack.push(0x100).unwrap(); // address vm.stack.push(0xFFFFFFFF).unwrap(); // gas + start_mock_call::(native_token(), selector!("balanceOf"), 0); vm.exec_staticcall().unwrap(); let mut result = Default::default(); diff --git a/crates/evm/src/precompiles/sha256.cairo b/crates/evm/src/precompiles/sha256.cairo index d36fcc90d..437f910f5 100644 --- a/crates/evm/src/precompiles/sha256.cairo +++ b/crates/evm/src/precompiles/sha256.cairo @@ -54,7 +54,6 @@ impl Sha256 of Precompile { #[cfg(test)] mod tests { - use contracts::test_utils::{setup_contracts_for_testing}; use core::result::ResultTrait; use core::starknet::testing::set_contract_address; use evm::instructions::system_operations::SystemOperationsTrait; @@ -62,7 +61,10 @@ mod tests { use evm::memory::MemoryTrait; use evm::precompiles::sha256::Sha256; use evm::stack::StackTrait; - use evm::test_utils::{VMBuilderTrait, native_token, other_starknet_address}; + use evm::test_utils::{ + VMBuilderTrait, native_token, other_starknet_address, setup_test_storages + }; + use snforge_std::{start_mock_call}; use utils::helpers::ToBytes; use utils::helpers::{FromBytes}; @@ -151,10 +153,8 @@ mod tests { // source: // #[test] - #[ignore] - //TODO(sn-foundry): fix or delete fn test_sha_256_precompile_static_call() { - let (_, _) = setup_contracts_for_testing(); + setup_test_storages(); let mut vm = VMBuilderTrait::new_with_presets().build(); @@ -167,6 +167,7 @@ mod tests { vm.memory.store(0xFF, 0x0); + start_mock_call::(native_token(), selector!("balanceOf"), 0); vm.exec_staticcall().unwrap(); let result = vm.memory.load(0x20); diff --git a/crates/evm/src/state.cairo b/crates/evm/src/state.cairo index dfe639114..f8dda1633 100644 --- a/crates/evm/src/state.cairo +++ b/crates/evm/src/state.cairo @@ -241,10 +241,9 @@ fn compute_storage_address(key: u256) -> StorageBaseAddress { let hash = PoseidonTrait::new().update_with(key).finalize(); storage_base_address_from_felt252(hash) } + #[cfg(test)] mod tests { - use contracts::test_utils::{deploy_contract_account, deploy_eoa}; - use evm::state::compute_storage_key; use evm::test_utils; @@ -286,7 +285,7 @@ mod tests { fn test_read_empty_log() { let mut changelog: StateChangeLog = Default::default(); let key = test_utils::storage_base_address().into(); - assert(changelog.read(key).is_none(), 'should return None'); + assert!(changelog.read(key).is_none()) } #[test] @@ -295,30 +294,24 @@ mod tests { let key = test_utils::storage_base_address().into(); changelog.write(key, 42); - assert(changelog.read(key).unwrap() == 42, 'value not stored correctly'); - assert(changelog.keyset.len() == 1, 'should add a key to tracking'); + assert_eq!(changelog.read(key).unwrap(), 42); + assert_eq!(changelog.keyset.len(), 1); changelog.write(key, 43); - assert(changelog.read(key).unwrap() == 43, 'value should have been updated'); - assert(changelog.keyset.len() == 1, 'keys should not be added twice'); + assert_eq!(changelog.read(key).unwrap(), 43); + assert_eq!(changelog.keyset.len(), 1); // Write multiple keys let second_key = 'second_location'; changelog.write(second_key, 1337.into()); - assert(changelog.read(second_key).unwrap() == 1337, 'wrong second value'); - assert(changelog.keyset.len() == 2, 'should have two keys'); + assert_eq!(changelog.read(second_key).unwrap(), 1337); + assert_eq!(changelog.keyset.len(), 2); } } mod test_state { - use contracts::account_contract::{IAccountDispatcher, IAccountDispatcherTrait}; - use contracts::kakarot_core::interface::{IExtendedKakarotCoreDispatcherTrait}; - use contracts::test_utils as contract_utils; - use contracts::uninitialized_account::UninitializedAccount; use core::starknet::EthAddress; - use core::starknet::testing::set_contract_address; - use evm::backend::starknet_backend; use evm::model::account::{Account, AccountTrait, AccountInternalTrait}; use evm::model::{Event, Transfer, Address}; use evm::state::{State, StateTrait}; @@ -326,24 +319,23 @@ mod tests { use openzeppelin::token::erc20::interface::{ IERC20CamelDispatcher, IERC20CamelDispatcherTrait }; - use snforge_std::{declare, DeclareResultTrait, start_cheat_caller_address, test_address}; + use snforge_std::{ + declare, DeclareResultTrait, start_cheat_caller_address, test_address, start_mock_call, + stop_mock_call + }; use utils::helpers::compute_starknet_address; use utils::set::{Set, SetTrait}; #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` - fn test_get_account_when_not_present() { + fn test_get_account_when_inexistant() { + test_utils::setup_test_storages(); + let deployer = test_address(); let mut state: State = Default::default(); + // Transfer native tokens to sender - let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); - let uninitialized_account_class_hash = declare("UninitializedAccount") - .unwrap() - .contract_class() - .class_hash; let evm_address: EthAddress = test_utils::evm_address(); let starknet_address = compute_starknet_address( - kakarot_core.contract_address.into(), evm_address, *uninitialized_account_class_hash + deployer, evm_address, test_utils::uninitialized_account() ); let expected_account = Account { address: Address { evm: evm_address, starknet: starknet_address }, @@ -354,24 +346,22 @@ mod tests { is_created: false, }; + start_mock_call::(test_utils::native_token(), selector!("balanceOf"), 0); let account = state.get_account(evm_address); - assert(account == expected_account, 'Account mismatch'); - assert(state.accounts.keyset.len() == 1, 'Account not written in context'); + assert_eq!(account, expected_account); + assert_eq!(state.accounts.keyset.len(), 1); } #[test] - fn test_get_account_when_present() { + fn test_get_account_when_cached() { + test_utils::setup_test_storages(); + let deployer = test_address(); let mut state: State = Default::default(); - let deployer = test_utils::kakarot_address(); let evm_address: EthAddress = test_utils::evm_address(); - let uninitialized_account_class_hash = declare("UninitializedAccount") - .unwrap() - .contract_class() - .class_hash; let starknet_address = compute_starknet_address( - deployer.into(), evm_address, *uninitialized_account_class_hash + deployer.into(), evm_address, test_utils::uninitialized_account() ); let expected_account = Account { address: Address { evm: evm_address, starknet: starknet_address }, code: [ @@ -382,32 +372,36 @@ mod tests { state.set_account(expected_account); let account = state.get_account(evm_address); - assert(account == expected_account, 'Account mismatch'); - assert(state.accounts.keyset.len() == 1, 'Account not written in context'); + assert_eq!(account, expected_account); + assert_eq!(state.accounts.keyset.len(), 1); } #[test] - #[ignore] fn test_get_account_when_deployed() { + test_utils::setup_test_storages(); + let deployer = test_address(); let mut state: State = Default::default(); - let (native_token, kakarot_core) = contract_utils::setup_contracts_for_testing(); + let evm_address: EthAddress = test_utils::evm_address(); - let ca = contract_utils::deploy_contract_account( - kakarot_core, evm_address, [0xab, 0xcd, 0xef].span() + let starknet_address = compute_starknet_address( + deployer, evm_address, test_utils::uninitialized_account() ); - contract_utils::fund_account_with_native_token(ca.starknet, native_token, 420); - - let starknet_address = kakarot_core.compute_starknet_address(evm_address); + test_utils::register_account(evm_address, starknet_address); let expected_account = Account { address: Address { evm: evm_address, starknet: starknet_address }, code: [ 0xab, 0xcd, 0xef ].span(), nonce: 1, balance: 420, selfdestruct: false, is_created: false, }; + start_mock_call::(test_utils::native_token(), selector!("balanceOf"), 420); + start_mock_call::< + Span + >(starknet_address, selector!("bytecode"), [0xab, 0xcd, 0xef].span()); + start_mock_call::(starknet_address, selector!("get_nonce"), 1); let account = state.get_account(evm_address); - assert(account == expected_account, 'Account mismatch'); - assert(state.accounts.keyset.len() == 1, 'Account not written in context'); + assert_eq!(account, expected_account); + assert_eq!(state.accounts.keyset.len(), 1); } #[test] @@ -424,30 +418,26 @@ mod tests { } #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_read_state_from_sn_storage() { - // Transfer native tokens to sender - let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); - let evm_address: EthAddress = test_utils::evm_address(); - let mut ca_address = contract_utils::deploy_contract_account( - kakarot_core, evm_address, [].span() + test_utils::setup_test_storages(); + let starknet_address = compute_starknet_address( + test_address(), test_utils::evm_address(), test_utils::uninitialized_account() ); + test_utils::register_account(test_utils::evm_address(), starknet_address); let mut state: State = Default::default(); let key = 10; let value = 100; - let account = Account { - address: ca_address, code: [ - 0xab, 0xcd, 0xef - ].span(), nonce: 1, balance: 0, selfdestruct: false, is_created: false, - }; - IAccountDispatcher { contract_address: account.starknet_address() } - .write_storage(key, value); - let read_value = state.read_state(evm_address, key); + start_mock_call::(starknet_address, selector!("storage"), value); + start_mock_call::(test_utils::native_token(), selector!("balanceOf"), 10000); + start_mock_call::< + Span + >(starknet_address, selector!("bytecode"), [0xab, 0xcd, 0xef].span()); + start_mock_call::(starknet_address, selector!("get_nonce"), 1); + let read_value = state.read_state(test_utils::evm_address(), key); - assert(value == read_value, 'Storage mismatch'); + assert_eq!(value, read_value) } #[test] @@ -462,66 +452,73 @@ mod tests { } #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_add_transfer() { //Given let mut state: State = Default::default(); - let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + test_utils::setup_test_storages(); + let deployer = test_address(); let sender_evm_address = test_utils::evm_address(); - let sender_starknet_address = contract_utils::deploy_eoa( - kakarot_core, sender_evm_address - ) - .contract_address; + let sender_starknet_address = compute_starknet_address( + deployer, sender_evm_address, test_utils::uninitialized_account() + ); let sender_address = Address { evm: sender_evm_address, starknet: sender_starknet_address }; + let recipient_evm_address = test_utils::other_evm_address(); - let recipient_starknet_address = contract_utils::deploy_eoa( - kakarot_core, recipient_evm_address - ) - .contract_address; + let recipient_starknet_address = compute_starknet_address( + deployer, recipient_evm_address, test_utils::uninitialized_account() + ); let recipient_address = Address { evm: recipient_evm_address, starknet: recipient_starknet_address }; let transfer = Transfer { sender: sender_address, recipient: recipient_address, amount: 100 }; + // Write user balances in cache to avoid fetching from SN storage - let mut sender = state.get_account(sender_address.evm); - sender.set_balance(300); + let mut sender = Account { + address: sender_address, + balance: 300, + code: [].span(), + nonce: 0, + selfdestruct: false, + is_created: false, + }; state.set_account(sender); + let mut recipient = Account { + address: recipient_address, + balance: 0, + code: [].span(), + nonce: 0, + selfdestruct: false, + is_created: false, + }; + state.set_account(recipient); // When state.add_transfer(transfer).unwrap(); // Then, transfer appended to log and cached balances updated - assert(state.transfers.len() == 1, 'Transfer not added'); - assert(*state.transfers[0] == transfer, 'Transfer mismatch'); + assert_eq!(state.transfers.len(), 1); + assert_eq!(*(state.transfers[0]), transfer); - assert( - state.get_account(sender_address.evm).balance() == 200, 'Sender balance mismatch' - ); - assert( - state.get_account(recipient_address.evm).balance() == 100, - 'Recipient balance mismatch' - ); + assert_eq!(state.get_account(sender_address.evm).balance(), 200); + assert_eq!(state.get_account(recipient_address.evm).balance(), 100); } #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_add_transfer_with_same_sender_and_recipient() { //Given let mut state: State = Default::default(); - let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + test_utils::setup_test_storages(); + let deployer = test_address(); let sender_evm_address = test_utils::evm_address(); - let sender_starknet_address = contract_utils::deploy_eoa( - kakarot_core, sender_evm_address - ) - .contract_address; + let sender_starknet_address = compute_starknet_address( + deployer, sender_evm_address, test_utils::uninitialized_account() + ); let sender_address = Address { evm: sender_evm_address, starknet: sender_starknet_address }; @@ -532,42 +529,44 @@ mod tests { }; // Write user balances in cache to avoid fetching from SN storage - let mut sender = state.get_account(sender_address.evm); - sender.balance = 300; + let mut sender = Account { + address: sender_address, + balance: 300, + code: [].span(), + nonce: 0, + selfdestruct: false, + is_created: false, + }; state.set_account(sender); // When state.add_transfer(transfer).unwrap(); // Then, no transfer appended to log and cached balances updated - assert(state.transfers.len() == 0, 'Transfer added'); + assert_eq!(state.transfers.len(), 0); - assert( - state.get_account(sender_address.evm).balance() == 300, 'Sender balance mismatch' - ); + assert_eq!(state.get_account(sender_address.evm).balance(), 300); } #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_add_transfer_when_amount_is_zero() { //Given let mut state: State = Default::default(); - let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + test_utils::setup_test_storages(); + let deployer = test_address(); let sender_evm_address = test_utils::evm_address(); - let sender_starknet_address = contract_utils::deploy_eoa( - kakarot_core, sender_evm_address - ) - .contract_address; + let sender_starknet_address = compute_starknet_address( + deployer, sender_evm_address, test_utils::uninitialized_account() + ); let sender_address = Address { evm: sender_evm_address, starknet: sender_starknet_address }; + let recipient_evm_address = test_utils::other_evm_address(); - let recipient_starknet_address = contract_utils::deploy_eoa( - kakarot_core, recipient_evm_address - ) - .contract_address; + let recipient_starknet_address = compute_starknet_address( + deployer, recipient_evm_address, test_utils::uninitialized_account() + ); let recipient_address = Address { evm: recipient_evm_address, starknet: recipient_starknet_address }; @@ -575,71 +574,77 @@ mod tests { sender: sender_address, recipient: recipient_address, amount: 0 }; // Write user balances in cache to avoid fetching from SN storage - // Write user balances in cache to avoid fetching from SN storage - let mut sender = state.get_account(sender_address.evm); - sender.balance = 300; + let mut sender = Account { + address: sender_address, + balance: 300, + code: [].span(), + nonce: 0, + selfdestruct: false, + is_created: false, + }; state.set_account(sender); - let mut recipient = state.get_account(recipient_address.evm); - recipient.set_balance(0); + let mut recipient = Account { + address: recipient_address, + balance: 0, + code: [].span(), + nonce: 0, + selfdestruct: false, + is_created: false, + }; state.set_account(recipient); // When state.add_transfer(transfer).unwrap(); // Then, no transfer appended to log and cached balances updated - assert(state.transfers.len() == 0, 'Transfer added'); - - assert( - state.get_account(sender_address.evm).balance() == 300, 'Sender balance mismatch' - ); - assert( - state.get_account(recipient_address.evm).balance() == 0, - 'Recipient balance mismatch' - ); + assert_eq!(state.transfers.len(), 0); + assert_eq!(state.get_account(sender_address.evm).balance(), 300); + assert_eq!(state.get_account(recipient_address.evm).balance(), 0); } #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_read_balance_cached() { let mut state: State = Default::default(); - let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + test_utils::setup_test_storages(); + let deployer = test_address(); let evm_address = test_utils::evm_address(); - let starknet_address = contract_utils::deploy_eoa(kakarot_core, evm_address) - .contract_address; + let starknet_address = compute_starknet_address( + deployer, evm_address, test_utils::uninitialized_account() + ); let address = Address { evm: evm_address, starknet: starknet_address }; let balance = 100; - let mut account = state.get_account(address.evm); - account.set_balance(balance); + let mut account = Account { + address, balance, code: [].span(), nonce: 0, selfdestruct: false, is_created: false, + }; state.set_account(account); let read_balance = state.get_account(address.evm).balance(); - assert(balance == read_balance, 'Balance mismatch'); + assert_eq!(balance, read_balance,); } #[test] - #[ignore] - //TODO(sn-foundry): fix `Contract not deployed at address: 0x0` fn test_read_balance_from_storage() { - // Transfer native tokens to sender - let (native_token, kakarot_core) = contract_utils::setup_contracts_for_testing(); - let evm_address: EthAddress = test_utils::evm_address(); - let eoa_account = contract_utils::deploy_eoa(kakarot_core, evm_address); - // Transfer native tokens to sender - we need to set the contract address for this - start_cheat_caller_address( - native_token.contract_address, contract_utils::constants::ETH_BANK() + test_utils::setup_test_storages(); + let deployer = test_address(); + let evm_address = test_utils::evm_address(); + let starknet_address = compute_starknet_address( + deployer, evm_address, test_utils::uninitialized_account() ); - IERC20CamelDispatcher { contract_address: native_token.contract_address } - .transfer(eoa_account.contract_address, 10000); - // Revert back to contract_address = kakarot for the test - start_cheat_caller_address(test_address(), kakarot_core.contract_address); + test_utils::register_account(evm_address, starknet_address); + + // Transfer native tokens to sender let mut state: State = Default::default(); + start_mock_call::(test_utils::native_token(), selector!("balanceOf"), 10000); + start_mock_call::< + Span + >(starknet_address, selector!("bytecode"), [0xab, 0xcd, 0xef].span()); + start_mock_call::(starknet_address, selector!("get_nonce"), 1); let read_balance = state.get_account(evm_address).balance(); - assert(read_balance == 10000, 'Balance mismatch'); + assert_eq!(read_balance, 10000); } #[test] diff --git a/crates/evm/src/test_utils.cairo b/crates/evm/src/test_utils.cairo index d4facbacf..56bdaf485 100644 --- a/crates/evm/src/test_utils.cairo +++ b/crates/evm/src/test_utils.cairo @@ -8,9 +8,11 @@ use contracts::uninitialized_account::UninitializedAccount; use core::nullable::{match_nullable, FromNullableResult}; use core::ops::DerefMut; use core::ops::SnapshotDeref; +use core::starknet::storage::{StorageMapWriteAccess, StoragePathEntry}; use core::starknet::{ StorageBaseAddress, storage_base_address_from_felt252, contract_address_try_from_felt252, - ContractAddress, EthAddress, deploy_syscall, get_contract_address, contract_address_const + ContractAddress, EthAddress, deploy_syscall, get_contract_address, contract_address_const, + ClassHash, class_hash_const }; use core::traits::TryInto; use evm::errors::{EVMError}; @@ -19,31 +21,34 @@ use evm::model::vm::{VM, VMTrait}; use evm::model::{Message, Environment, Address, Account, AccountTrait}; use evm::state::State; use evm::{stack::{Stack, StackTrait}, memory::{Memory, MemoryTrait}}; -use snforge_std::{declare, DeclareResultTrait, ContractClassTrait}; +use snforge_std::{declare, DeclareResultTrait, ContractClassTrait, store, test_address}; use starknet::storage::StorageTraitMut; use utils::constants; -fn declare_and_store_classes() { - // Declare the contract classes - let native_token = deploy_native_token(); - let uninitialized_account = declare("UninitializedAccount") - .unwrap() - .contract_class() - .class_hash; - let account_contract = declare("AccountContract").unwrap().contract_class().class_hash; - let kakarot_core = declare("KakarotCore").unwrap().contract_class().class_hash; - - // Get the test address, which is the one that will be used when testing internals - let test_address = get_contract_address(); - - // Store the contract classes in the same storage slots as Kakarot - let mut kakarot_state = KakarotCore::contract_state_for_testing(); - let mut kakarot_storage = kakarot_state.deref_mut().storage_mut(); - kakarot_storage.Kakarot_account_contract_class_hash.write(*account_contract); - kakarot_storage.Kakarot_uninitialized_account_class_hash.write(*uninitialized_account); - kakarot_storage.Kakarot_native_token_address.write(native_token.contract_address); +fn uninitialized_account() -> ClassHash { + class_hash_const::<'uninitialized_account'>() } +fn account_contract() -> ClassHash { + class_hash_const::<'account_contract'>() +} + + +fn setup_test_storages() { + let mut kakarot_core = KakarotCore::contract_state_for_testing(); + let mut kakarot_storage = kakarot_core.deref_mut().storage_mut(); + kakarot_storage.Kakarot_account_contract_class_hash.write(account_contract()); + kakarot_storage.Kakarot_uninitialized_account_class_hash.write(uninitialized_account()); + kakarot_storage.Kakarot_native_token_address.write(native_token()); +} + +fn register_account(evm_address: EthAddress, starknet_address: ContractAddress) { + let mut kakarot_core = KakarotCore::contract_state_for_testing(); + let mut kakarot_storage = kakarot_core.deref_mut().storage_mut(); + kakarot_storage.Kakarot_evm_to_starknet_address.entry(evm_address).write(starknet_address); +} + + #[derive(Destruct)] struct VMBuilder { vm: VM @@ -140,7 +145,7 @@ fn evm_address() -> EthAddress { 'evm_address'.try_into().unwrap() } -fn test_address() -> Address { +fn test_dual_address() -> Address { Address { evm: evm_address(), starknet: starknet_address() } } @@ -210,19 +215,20 @@ fn preset_message() -> Message { let code: Span = [0x00].span(); let data: Span = [4, 5, 6].span(); let value: u256 = callvalue(); - let uninitialized_account_class_hash = declare("UninitializedAccount") - .unwrap() - .contract_class() - .class_hash; let caller = Address { evm: origin(), starknet: utils::helpers::compute_starknet_address( - get_contract_address(), origin(), *uninitialized_account_class_hash + test_address(), origin(), uninitialized_account() + ) + }; + let target = Address { + evm: evm_address(), + starknet: utils::helpers::compute_starknet_address( + test_address(), evm_address(), uninitialized_account() ) }; let read_only = false; let tx_gas_limit = tx_gas_limit(); - let target = test_address(); Message { target, @@ -275,31 +281,3 @@ fn preset_vm() -> VM { gas_refund: 0, } } - - -/// Initializes the contract account by setting the bytecode, the storage -/// and incrementing the nonce to 1. -fn initialize_contract_account( - kakarot_core: IExtendedKakarotCoreDispatcher, - eth_address: EthAddress, - bytecode: Span, - storage: Span<(u256, u256)> -) -> Result { - let mut ca_address = deploy_contract_account(kakarot_core, eth_address, bytecode); - // Set the storage of the contract account - let account = Account { - address: ca_address, code: [ - 0xab, 0xcd, 0xef - ].span(), nonce: 1, balance: 0, selfdestruct: false, is_created: false, - }; - - let mut i = 0; - while i != storage.len() { - let (key, value) = storage.get(i).unwrap().unbox(); - IAccountDispatcher { contract_address: account.starknet_address() } - .write_storage(*key, *value); - i += 1; - }; - - Result::Ok(ca_address) -} diff --git a/crates/snforge_utils/Scarb.toml b/crates/snforge_utils/Scarb.toml index 28fb61458..9efe533f8 100644 --- a/crates/snforge_utils/Scarb.toml +++ b/crates/snforge_utils/Scarb.toml @@ -7,10 +7,17 @@ edition = "2023_11" [dependencies] starknet = "2.7.1" +evm = { path = "../evm" } [dev-dependencies] snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.28.0" } +[[target.starknet-contract]] +sierra = true +casm = true +casm-add-pythonic-hints = true + + [lib] name = "snforge_utils" diff --git a/crates/snforge_utils/src/contracts.cairo b/crates/snforge_utils/src/contracts.cairo new file mode 100644 index 000000000..db2207da9 --- /dev/null +++ b/crates/snforge_utils/src/contracts.cairo @@ -0,0 +1,51 @@ +#[starknet::interface] +trait IHello { + fn hello(self: @T); + fn bar(self: @T); +} + +#[starknet::contract] +mod HelloContract { + use super::{IHelloDispatcher, IHelloDispatcherTrait}; + use core::starknet::get_contract_address; + + #[storage] + struct Storage {} + + #[external(v0)] + fn hello(self: @ContractState) {} + + #[external(v0)] + fn bar(self: @ContractState) { + IHelloDispatcher { contract_address: get_contract_address() }.hello(); + } +} + +#[cfg(test)] +mod tests { + use snforge_std::{declare, DeclareResultTrait, ContractClassTrait}; + use snforge_utils::snforge_utils::{assert_called, assert_called_with}; + use super::{IHelloDispatcher, IHelloDispatcherTrait}; + + #[test] + fn test_calltrace_entrypoint() { + let helloclass = declare("HelloContract").unwrap().contract_class(); + let (hellocontract, _) = helloclass.deploy(@array![]).unwrap(); + + IHelloDispatcher { contract_address: hellocontract }.hello(); + + assert_called(hellocontract, selector!("hello")); + assert_called_with(hellocontract, selector!("hello"), [].span()); + } + + #[test] + fn test_calltrace_rec() { + let helloclass = declare("HelloContract").unwrap().contract_class(); + let (hellocontract, _) = helloclass.deploy(@array![]).unwrap(); + + IHelloDispatcher { contract_address: hellocontract }.bar(); + + assert_called(hellocontract, selector!("hello")); + assert_called_with(hellocontract, selector!("hello"), [].span()); + } +} diff --git a/crates/snforge_utils/src/lib.cairo b/crates/snforge_utils/src/lib.cairo index e2791ed27..16507e6f1 100644 --- a/crates/snforge_utils/src/lib.cairo +++ b/crates/snforge_utils/src/lib.cairo @@ -1,14 +1,194 @@ +mod contracts; + #[cfg(target: 'test')] pub mod snforge_utils { use core::array::ArrayTrait; use core::option::OptionTrait; use starknet::testing::cheatcode; + use evm::state::compute_storage_key; use starknet::ContractAddress; + use evm::model::Address; use snforge_std::cheatcodes::handle_cheatcode; + use snforge_std::cheatcodes::storage::store_felt252; use snforge_std::{Event, spy_events, EventSpy, EventSpyAssertionsTrait, EventSpyTrait}; use snforge_std::cheatcodes::events::{Events}; use array_utils::ArrayExtTrait; + use snforge_std::trace::{ + get_call_trace, CallTrace, CallEntryPoint, CallResult, EntryPointType, CallType, CallFailure + }; + + impl CloneEntryPointType of Clone { + fn clone(self: @EntryPointType) -> EntryPointType { + match self { + EntryPointType::Constructor => EntryPointType::Constructor, + EntryPointType::External => EntryPointType::External, + EntryPointType::L1Handler => EntryPointType::L1Handler, + } + } + } + + impl CloneCallEntryPoint of Clone { + fn clone(self: @CallEntryPoint) -> CallEntryPoint { + CallEntryPoint { + entry_point_type: self.entry_point_type.clone(), + entry_point_selector: self.entry_point_selector.clone(), + calldata: self.calldata.clone(), + contract_address: self.contract_address.clone(), + caller_address: self.caller_address.clone(), + call_type: self.call_type.clone(), + } + } + } + + impl CloneCallType of Clone { + fn clone(self: @CallType) -> CallType { + match self { + CallType::Call => CallType::Call, + CallType::Delegate => CallType::Delegate, + } + } + } + + impl CloneCallResult of Clone { + fn clone(self: @CallResult) -> CallResult { + match self { + CallResult::Success(val) => CallResult::Success(val.clone()), + CallResult::Failure(val) => CallResult::Failure(val.clone()), + } + } + } + + impl CloneCallFailure of Clone { + fn clone(self: @CallFailure) -> CallFailure { + match self { + CallFailure::Panic(val) => CallFailure::Panic(val.clone()), + CallFailure::Error(val) => CallFailure::Error(val.clone()), + } + } + } + + + impl CloneCallTrace of Clone { + fn clone(self: @CallTrace) -> CallTrace { + CallTrace { + entry_point: self.entry_point.clone(), + nested_calls: self.nested_calls.clone(), + result: self.result.clone(), + } + } + } + + pub fn is_called(contract_address: ContractAddress, selector: felt252) -> bool { + let call_trace = get_call_trace(); + + // Check if the top-level call matches + if check_call_match(call_trace.entry_point, contract_address, selector) { + return true; + } + + // Check nested calls recursively + if check_nested_calls(call_trace.nested_calls, contract_address, selector) { + return true; + } + + false + } + + pub fn assert_called(contract_address: ContractAddress, selector: felt252) { + assert!(is_called(contract_address, selector), "Expected call not found in trace"); + } + + pub fn assert_not_called(contract_address: ContractAddress, selector: felt252) { + assert!(!is_called(contract_address, selector), "Unexpected call found in trace"); + } + + fn check_call_match( + entry_point: CallEntryPoint, contract_address: ContractAddress, selector: felt252 + ) -> bool { + entry_point.contract_address == contract_address + && entry_point.entry_point_selector == selector + } + + fn check_nested_calls( + calls: Array, contract_address: ContractAddress, selector: felt252 + ) -> bool { + let mut i = 0; + loop { + if i == calls.len() { + break false; + } + let call = calls.at(i).clone(); + if check_call_match(call.entry_point, contract_address, selector) { + break true; + } + if check_nested_calls(call.nested_calls, contract_address, selector) { + break true; + } + i += 1; + } + } + + pub fn assert_called_with( + contract_address: ContractAddress, selector: felt252, calldata: Span + ) { + assert!( + is_called_with(contract_address, selector, calldata), + "Expected call with specific data not found in trace" + ); + } + + + pub fn is_called_with( + contract_address: ContractAddress, selector: felt252, calldata: Span + ) -> bool { + let call_trace = get_call_trace(); + + if check_call_match_with_data( + call_trace.entry_point, contract_address, selector, calldata + ) { + return true; + } + + check_nested_calls_with_data(call_trace.nested_calls, contract_address, selector, calldata) + } + + + fn check_call_match_with_data( + call: CallEntryPoint, + contract_address: ContractAddress, + selector: felt252, + calldata: Span + ) -> bool { + call.contract_address == contract_address + && call.entry_point_selector == selector + && call.calldata.span() == calldata + } + + fn check_nested_calls_with_data( + calls: Array, + contract_address: ContractAddress, + selector: felt252, + calldata: Span + ) -> bool { + let mut i = 0; + loop { + if i == calls.len() { + break false; + } + let call = calls.at(i).clone(); + if check_call_match_with_data(call.entry_point, contract_address, selector, calldata) { + break true; + } + if check_nested_calls_with_data( + call.nested_calls, contract_address, selector, calldata + ) { + break true; + } + i += 1; + } + } + mod array_utils { #[generate_trait] pub impl ArrayExtImpl, +Drop> of ArrayExtTrait { @@ -183,4 +363,17 @@ pub mod snforge_utils { } } } + + /// Stores a value in the EVM storage of a given Starknet contract. + pub fn store_evm(target: Address, evm_key: u256, evm_value: u256) { + let storage_address = compute_storage_key(target.evm, evm_key); + let serialized_value = [evm_value.low.into(), evm_value.high.into()].span(); + let mut offset: usize = 0; + while offset != serialized_value.len() { + store_felt252( + target.starknet, storage_address + offset.into(), *serialized_value.at(offset) + ); + offset += 1; + } + } } diff --git a/scripts/find_selectory.py b/scripts/find_selectory.py new file mode 100644 index 000000000..e126ad432 --- /dev/null +++ b/scripts/find_selectory.py @@ -0,0 +1,30 @@ +import os +import re + +from starkware.starknet.public.abi import get_selector_from_name + + +def find_cairo_functions(directory): + return [ + match + for root, _, files in os.walk(directory) + for file in files + if file.endswith(".cairo") + for match in re.findall(r"fn\s+(\w+)\(", open(os.path.join(root, file)).read()) + ] + + +def map_selectors(functions): + return {get_selector_from_name(function): function for function in functions} + + +def get_function_from_selector(selectors): + selector = int(input("Enter the hexadecimal selector: "), 16) + print(f"Corresponding function: {selectors.get(selector, 'Not found')}") + + +if __name__ == "__main__": + directory = "." + functions = find_cairo_functions(directory) + selectors = map_selectors(functions) + get_function_from_selector(selectors)