diff --git a/crates/contracts/src/account_contract.cairo b/crates/contracts/src/account_contract.cairo index 45628e297..26f139973 100644 --- a/crates/contracts/src/account_contract.cairo +++ b/crates/contracts/src/account_contract.cairo @@ -49,29 +49,27 @@ pub mod AccountContract { use contracts::components::ownable::IOwnable; use contracts::components::ownable::ownable_component::InternalTrait; use contracts::components::ownable::ownable_component; - use contracts::errors::{KAKAROT_VALIDATION_FAILED, KAKAROT_REENTRANCY}; + use contracts::errors::KAKAROT_REENTRANCY; use contracts::kakarot_core::interface::{IKakarotCoreDispatcher, IKakarotCoreDispatcherTrait}; use contracts::storage::StorageBytecode; + use core::cmp::min; use core::num::traits::Bounded; use core::num::traits::zero::Zero; - use core::panic_with_felt252; - use core::starknet::SyscallResultTrait; use core::starknet::account::{Call}; + use core::starknet::eth_signature::verify_eth_signature; use core::starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess }; - use core::starknet::syscalls::{call_contract_syscall, replace_class_syscall}; + use core::starknet::syscalls::call_contract_syscall; use core::starknet::{ - EthAddress, ClassHash, VALIDATED, get_caller_address, get_tx_info, get_block_timestamp + EthAddress, ClassHash, get_caller_address, get_tx_info, get_block_timestamp }; use core::traits::TryInto; use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait}; - use super::{IAccountLibraryDispatcher, IAccountDispatcherTrait, OutsideExecution}; + use super::OutsideExecution; use utils::constants::{POW_2_32}; use utils::eth_transaction::transaction::{TransactionUnsignedTrait, Transaction}; - use utils::eth_transaction::validation::validate_eth_tx; - use utils::eth_transaction::{TransactionMetadata}; use utils::serialization::{deserialize_signature, deserialize_bytes, serialize_bytes}; use utils::traits::DefaultSignature; @@ -163,95 +161,16 @@ pub mod AccountContract { // EOA functions fn __validate__(ref self: ContractState, calls: Array) -> felt252 { - let tx_info = get_tx_info().unbox(); - assert(get_caller_address().is_zero(), 'EOA: reentrant call'); - assert(calls.len() == 1, 'EOA: multicall not supported'); - // todo: activate check once using snfoundry - // assert(tx_info.version.try_into().unwrap() >= 1_u128, 'EOA: deprecated tx version'); - assert(self.Account_bytecode_len.read().is_zero(), 'EOAs: Cannot have code'); - assert(tx_info.signature.len() == 5, 'EOA: invalid signature length'); - - let call = calls.at(0); - assert(*call.to == self.ownable.owner(), 'to is not kakarot core'); - assert!( - *call.selector == selector!("eth_send_transaction"), - "Validate: selector must be eth_send_transaction" - ); - - let chain_id: u64 = tx_info.chain_id.try_into().unwrap() % POW_2_32.try_into().unwrap(); - let signature = deserialize_signature(tx_info.signature, chain_id) - .expect('EOA: invalid signature'); - - let tx_metadata = TransactionMetadata { - address: self.Account_evm_address.read(), - chain_id, - account_nonce: tx_info.nonce.try_into().unwrap(), - signature - }; - - let mut encoded_tx = deserialize_bytes(*call.calldata) - .expect('conversion to Span failed') - .span(); - let unsigned_transaction = TransactionUnsignedTrait::decode_enveloped(ref encoded_tx) - .expect('EOA: could not decode tx'); - let validation_result = validate_eth_tx(tx_metadata, unsigned_transaction) - .expect('failed to validate eth tx'); - - assert(validation_result, 'transaction validation failed'); - - VALIDATED + panic!("EOA: __validate__ not supported") } /// Validate Declare is not used for Kakarot fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 { - panic_with_felt252('Cannot Declare EOA') + panic!("EOA: declare not supported") } fn __execute__(ref self: ContractState, calls: Array) -> Array> { - let caller = get_caller_address(); - let tx_info = get_tx_info().unbox(); - assert(caller.is_zero(), 'EOA: reentrant call'); - assert(calls.len() == 1, 'EOA: multicall not supported'); - // todo: activate check once using snfoundry - // assert(tx_info.version.try_into().unwrap() >= 1_u128, 'EOA: deprecated tx version'); - - let kakarot = IKakarotCoreDispatcher { contract_address: self.ownable.owner() }; - let latest_class = kakarot.get_account_contract_class_hash(); - let this_class = self.Account_implementation.read(); - - if (latest_class != this_class) { - self.Account_implementation.write(latest_class); - let response = IAccountLibraryDispatcher { class_hash: latest_class } - .__execute__(calls); - replace_class_syscall(latest_class).unwrap_syscall(); - return response; - } - - // Increment nonce to match protocol's nonce for EOAs. - self.Account_nonce.write(tx_info.nonce.try_into().unwrap() + 1); - - let call: @Call = calls[0]; - - let mut encoded_tx = deserialize_bytes(*call.calldata) - .expect('conversion to Span failed') - .span(); - let unsigned_transaction = TransactionUnsignedTrait::decode_enveloped(ref encoded_tx) - .expect('EOA: could not decode tx'); - - //TODO: validation of EIP-1559 transactions - // Not done because this endpoint will end up deprecated after EIP-1559 - let is_valid = true; - - let (success, return_data, gas_used) = if is_valid { - kakarot.eth_send_transaction(unsigned_transaction.transaction) - } else { - (false, KAKAROT_VALIDATION_FAILED.span(), 0) - }; - let return_data = serialize_bytes(return_data).span(); - - self.emit(TransactionExecuted { response: return_data, success: success, gas_used }); - - array![return_data] + panic!("EOA: __execute__ not supported") } fn write_bytecode(ref self: ContractState, bytecode: Span) { @@ -304,6 +223,7 @@ pub mod AccountContract { let caller = get_caller_address(); let tx_info = get_tx_info(); + // SNIP-9 Validation if (outside_execution.caller.into() != 'ANY_CALLER') { assert(caller == outside_execution.caller, 'SNIP9: Invalid caller'); } @@ -312,20 +232,18 @@ pub mod AccountContract { assert(block_timestamp > outside_execution.execute_after, 'SNIP9: Too early call'); assert(block_timestamp < outside_execution.execute_before, 'SNIP9: Too late call'); - assert(outside_execution.calls.len() == 1, 'Multicall not supported'); - assert(self.Account_bytecode_len.read().is_zero(), 'EOAs cannot have code'); - assert(tx_info.version.into() >= 1_u256, 'Deprecated tx version'); - assert(signature.len() == 5, 'Invalid signature length'); - - let call = outside_execution.calls.at(0); - assert(*call.to == self.ownable.owner(), 'to is not kakarot core'); - assert!( - *call.selector == selector!("eth_send_transaction"), - "selector must be eth_send_transaction" - ); + // Kakarot-Specific Validation + assert(outside_execution.calls.len() == 1, 'KKRT: Multicall not supported'); + assert(tx_info.version.into() >= 1_u256, 'KKRT: Deprecated tx version: 0'); + + // EOA Validation + assert(self.Account_bytecode_len.read().is_zero(), 'EOA: cannot have code'); + let chain_id: u64 = tx_info.chain_id.try_into().unwrap() % POW_2_32.try_into().unwrap(); + assert(signature.len() == 5, 'EOA: Invalid signature length'); let signature = deserialize_signature(signature, chain_id) .expect('EOA: invalid signature'); + let mut encoded_tx_data = deserialize_bytes((*outside_execution.calls[0]).calldata) .expect('conversion to Span failed') .span(); @@ -333,37 +251,29 @@ pub mod AccountContract { ref encoded_tx_data ) .expect('EOA: could not decode tx'); - // TODO(execute-from-outside): move validation to KakarotCore - let tx_metadata = TransactionMetadata { - address: self.Account_evm_address.read(), - chain_id, - account_nonce: self.Account_nonce.read().into(), - signature - }; - - let validation_result = validate_eth_tx(tx_metadata, unsigned_transaction) - .expect('failed to validate eth tx'); - - assert(validation_result, 'transaction validation failed'); - - //TODO: validate eip1559 transactions - // let is_valid = match tx.try_into_fee_market_transaction() { - // Option::Some(tx_fee_infos) => { self.validate_eip1559_tx(@tx, tx_fee_infos) }, - // Option::None => true - // }; - let is_valid = true; - let kakarot = IKakarotCoreDispatcher { contract_address: self.ownable.owner() }; + let address = self.Account_evm_address.read(); + verify_eth_signature(unsigned_transaction.hash, signature, address); - let return_data = if is_valid { - let (_, return_data, _) = kakarot - .eth_send_transaction(unsigned_transaction.transaction); - return_data - } else { - KAKAROT_VALIDATION_FAILED.span() - }; + let kakarot = IKakarotCoreDispatcher { contract_address: self.ownable.owner() }; + let (success, return_data, gas_used) = kakarot + .eth_send_transaction(unsigned_transaction.transaction); let return_data = serialize_bytes(return_data).span(); + // See Argent account + // https://github.com/argentlabs/argent-contracts-starknet/blob/1352198956f36fb35fa544c4e46a3507a3ec20e3/src/presets/user_account.cairo#L211-L213 + // See 300 max data_len for events + // https://github.com/starkware-libs/blockifier/blob/9bfb3d4c8bf1b68a0c744d1249b32747c75a4d87/crates/blockifier/resources/versioned_constants.json + // The whole data_len should be less than 300, so it's the return_data should be less + // than 297 (+3 for return_data_len, success, gas_used) + self + .emit( + TransactionExecuted { + response: return_data.slice(0, min(297, return_data.len())), + success: success, + gas_used + } + ); array![return_data] } } diff --git a/crates/contracts/src/kakarot_core/interface.cairo b/crates/contracts/src/kakarot_core/interface.cairo index 7addae72e..8c014f3bf 100644 --- a/crates/contracts/src/kakarot_core/interface.cairo +++ b/crates/contracts/src/kakarot_core/interface.cairo @@ -55,6 +55,8 @@ pub trait IKakarotCore { // Getter for the Base Fee fn get_base_fee(self: @TContractState) -> u64; + /// Setter for the base fee + fn set_base_fee(ref self: TContractState, base_fee: u64); // Getter for the Starknet Address fn get_starknet_address(self: @TContractState, evm_address: EthAddress) -> ContractAddress; @@ -113,6 +115,8 @@ pub trait IExtendedKakarotCore { fn get_block_gas_limit(self: @TContractState) -> u64; // Getter for the Base Fee fn get_base_fee(self: @TContractState) -> u64; + /// Setter for the base fee + fn set_base_fee(ref self: TContractState, base_fee: u64); // Getter for the Starknet Address fn get_starknet_address(self: @TContractState, evm_address: EthAddress) -> ContractAddress; diff --git a/crates/contracts/src/kakarot_core/kakarot.cairo b/crates/contracts/src/kakarot_core/kakarot.cairo index 954a40468..1a6056655 100644 --- a/crates/contracts/src/kakarot_core/kakarot.cairo +++ b/crates/contracts/src/kakarot_core/kakarot.cairo @@ -18,6 +18,7 @@ pub mod KakarotCore { get_caller_address }; use evm::backend::starknet_backend; + use evm::backend::validation::validate_eth_tx; use evm::errors::{EVMError, ensure, EVMErrorTrait,}; use evm::gas; use evm::model::account::AccountTrait; @@ -158,12 +159,14 @@ pub mod KakarotCore { let origin = Address { evm: origin, starknet: self.compute_starknet_address(origin) }; let TransactionResult { success, return_data, gas_used, state: _ } = self - .process_transaction(origin, tx); + .process_transaction(origin, tx, tx.effective_gas_price(Option::None)); (success, return_data, gas_used) } fn eth_send_transaction(ref self: ContractState, tx: Transaction) -> (bool, Span, u64) { + let gas_price = validate_eth_tx(@self, tx); + let starknet_caller_address = get_caller_address(); let account = IAccountDispatcher { contract_address: starknet_caller_address }; let origin = Address { @@ -171,7 +174,7 @@ pub mod KakarotCore { }; let TransactionResult { success, return_data, gas_used, mut state } = self - .process_transaction(origin, tx); + .process_transaction(origin, tx, gas_price); starknet_backend::commit(ref state).expect('Committing state failed'); (success, return_data, gas_used) } @@ -219,6 +222,10 @@ pub mod KakarotCore { self.Kakarot_block_gas_limit.read() } + fn set_base_fee(ref self: ContractState, base_fee: u64) { + self.ownable.assert_only_owner(); + self.Kakarot_base_fee.write(base_fee); + } fn get_base_fee(self: @ContractState) -> u64 { self.Kakarot_base_fee.read() @@ -264,10 +271,8 @@ pub mod KakarotCore { fn process_transaction( - self: @ContractState, origin: Address, tx: Transaction + self: @ContractState, origin: Address, tx: Transaction, gas_price: u128 ) -> TransactionResult { - //TODO(gas) handle FeeMarketTransaction - let gas_price = tx.max_fee_per_gas(); let gas_limit = tx.gas_limit(); let mut env = starknet_backend::get_env(origin.evm, gas_price); @@ -275,6 +280,8 @@ pub mod KakarotCore { let gas_fee = gas_limit.into() * gas_price; let mut sender_account = env.state.get_account(origin.evm); let sender_balance = sender_account.balance(); + sender_account.set_nonce(sender_account.nonce() + 1); + env.state.set_account(sender_account); match ensure( sender_balance >= gas_fee.into() + tx.value(), EVMError::InsufficientBalance ) { @@ -343,7 +350,6 @@ pub mod KakarotCore { accessed_addresses: accessed_addresses.spanset(), accessed_storage_keys: accessed_storage_keys.spanset(), }; - let mut summary = EVMTrait::process_message_call(message, env, is_deploy_tx); // Gas refunds diff --git a/crates/contracts/src/test_data.cairo b/crates/contracts/src/test_data.cairo index 913ee4d91..b56a08684 100644 --- a/crates/contracts/src/test_data.cairo +++ b/crates/contracts/src/test_data.cairo @@ -1733,8 +1733,12 @@ pub fn storage_evm_bytecode() -> Span { pub fn eip_2930_rlp_encoded_counter_inc_tx() -> Span { [ 1, - 231, - 1, + 235, + 132, + 75, + 75, + 82, + 84, 128, 132, 59, diff --git a/crates/contracts/src/test_utils.cairo b/crates/contracts/src/test_utils.cairo index e391d35fa..933184115 100644 --- a/crates/contracts/src/test_utils.cairo +++ b/crates/contracts/src/test_utils.cairo @@ -12,7 +12,7 @@ use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDi use snforge_std::start_cheat_chain_id_global; use snforge_std::{ declare, DeclareResultTrait, start_cheat_caller_address, start_cheat_sequencer_address_global, - stop_cheat_caller_address, start_cheat_caller_address_global + stop_cheat_caller_address, start_cheat_caller_address_global, cheat_caller_address, CheatSpan }; use utils::constants::BLOCK_GAS_LIMIT; use utils::eth_transaction::legacy::TxLegacy; @@ -80,12 +80,18 @@ pub fn deploy_kakarot_core( (*kakarot_core_class_hash).into(), 0, calldata.span(), false ); - match maybe_address { + let kakarot_core = match maybe_address { Result::Ok(( contract_address, _ )) => { IExtendedKakarotCoreDispatcher { contract_address } }, Result::Err(err) => panic(err) - } + }; + cheat_caller_address( + kakarot_core.contract_address, other_starknet_address(), CheatSpan::TargetCalls(1) + ); + kakarot_core.set_base_fee(1000); + + kakarot_core } pub fn deploy_contract_account( @@ -130,12 +136,9 @@ pub fn fund_account_with_native_token( } pub fn setup_contracts_for_testing() -> (IERC20CamelDispatcher, IExtendedKakarotCoreDispatcher) { - let native_token = deploy_native_token(); - let kakarot_core = deploy_kakarot_core( - native_token.contract_address, [sequencer_evm_address()].span() - ); - let sequencer: EthAddress = sequencer_evm_address(); + let native_token = deploy_native_token(); + let kakarot_core = deploy_kakarot_core(native_token.contract_address, [sequencer].span()); let sequencer_sn_address = kakarot_core.address_registry(sequencer); start_cheat_sequencer_address_global(sequencer_sn_address); diff --git a/crates/contracts/tests/lib.cairo b/crates/contracts/tests/lib.cairo index ca7f92038..df3d540ee 100644 --- a/crates/contracts/tests/lib.cairo +++ b/crates/contracts/tests/lib.cairo @@ -1,8 +1,5 @@ +mod test_account_contract; mod test_cairo1_helpers; -mod test_contract_account; - - -mod test_eoa; mod test_execution_from_outside; diff --git a/crates/contracts/tests/test_contract_account.cairo b/crates/contracts/tests/test_account_contract.cairo similarity index 92% rename from crates/contracts/tests/test_contract_account.cairo rename to crates/contracts/tests/test_account_contract.cairo index 7ac0c8f5f..663c62c79 100644 --- a/crates/contracts/tests/test_contract_account.cairo +++ b/crates/contracts/tests/test_account_contract.cairo @@ -1,13 +1,12 @@ use contracts::errors::KAKAROT_REENTRANCY; use contracts::test_data::counter_evm_bytecode; use contracts::test_utils::{ - setup_contracts_for_testing, deploy_contract_account, fund_account_with_native_token + setup_contracts_for_testing, deploy_contract_account, fund_account_with_native_token, deploy_eoa }; use contracts::{IAccountDispatcher, IAccountDispatcherTrait}; -use core::starknet::ContractAddress; use core::starknet::account::{Call}; -use core::starknet::testing; -use evm::test_utils::{ca_address, native_token}; +use core::starknet::{EthAddress, ContractAddress}; +use evm::test_utils::{ca_address, native_token, eoa_address}; use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait}; use snforge_std::{start_cheat_caller_address, stop_cheat_caller_address}; @@ -54,6 +53,16 @@ fn test_ca_get_nonce() { assert(nonce == expected_nonce, 'wrong contract nonce'); } +#[test] +fn test_get_evm_address() { + let expected_address: EthAddress = eoa_address(); + let (_, kakarot_core) = setup_contracts_for_testing(); + + let eoa_contract = deploy_eoa(kakarot_core, eoa_address()); + + assert(eoa_contract.get_evm_address() == expected_address, 'wrong evm_address'); +} + #[test] fn test_ca_storage() { diff --git a/crates/contracts/tests/test_eoa.cairo b/crates/contracts/tests/test_eoa.cairo deleted file mode 100644 index 8372fa82b..000000000 --- a/crates/contracts/tests/test_eoa.cairo +++ /dev/null @@ -1,377 +0,0 @@ -use contracts::account_contract::AccountContract::TransactionExecuted; -use contracts::account_contract::{AccountContract, IAccountDispatcher, IAccountDispatcherTrait}; -use contracts::kakarot_core::{ - IKakarotCore, KakarotCore, KakarotCore::KakarotCoreInternal, - interface::IExtendedKakarotCoreDispatcherTrait -}; -use contracts::test_contracts::test_upgradeable::{ - IMockContractUpgradeableDispatcher, IMockContractUpgradeableDispatcherTrait, - MockContractUpgradeableV1 -}; -use contracts::test_data::{counter_evm_bytecode, eip_2930_rlp_encoded_counter_inc_tx,}; -use contracts::test_utils::{ - setup_contracts_for_testing, deploy_eoa, deploy_contract_account, - fund_account_with_native_token, call_transaction -}; -use core::array::SpanTrait; -use core::box::BoxTrait; -use core::starknet::account::{Call}; -use core::starknet::secp256_trait::Signature; -use core::starknet::syscalls::deploy_syscall; -use core::starknet::{ - ContractAddress, ClassHash, VALIDATED, get_contract_address, contract_address_const, EthAddress, - get_tx_info, Event -}; - -use evm::model::{Address, AddressTrait}; -use evm::test_utils::{ - kakarot_address, evm_address, other_evm_address, other_starknet_address, eoa_address, chain_id, - tx_gas_limit, gas_price, VMBuilderTrait -}; -use openzeppelin::token::erc20::interface::IERC20CamelDispatcherTrait; -use snforge_std::{ - 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, EventSpyTrait, EventsFilterTrait, CheatSpan, - cheat_caller_address -}; -use snforge_utils::snforge_utils::{ContractEvents, ContractEventsTrait, EventsFilterBuilderTrait}; -use utils::eth_transaction::transaction::Transaction; -use utils::eth_transaction::tx_type::TxType; -use utils::helpers::{U8SpanExTrait, u256_to_bytes_array}; -use utils::serialization::{serialize_bytes, serialize_transaction_signature}; -use utils::test_data::{legacy_rlp_encoded_tx, eip_2930_encoded_tx, eip_1559_encoded_tx}; - - -#[test] -fn test_get_evm_address() { - let expected_address: EthAddress = eoa_address(); - let (_, kakarot_core) = setup_contracts_for_testing(); - - let eoa_contract = deploy_eoa(kakarot_core, eoa_address()); - - assert(eoa_contract.get_evm_address() == expected_address, 'wrong evm_address'); -} - -#[test] -#[available_gas(200000000000000)] -fn test___execute__a() { - let (native_token, kakarot_core) = setup_contracts_for_testing(); - - let evm_address = evm_address(); - let eoa = kakarot_core.deploy_externally_owned_account(evm_address); - fund_account_with_native_token(eoa, native_token, 0xfffffffffffffffffffffffffff); - - let kakarot_address = kakarot_core.contract_address; - - deploy_contract_account(kakarot_core, other_evm_address(), counter_evm_bytecode()); - - start_cheat_caller_address(kakarot_address, eoa); - let eoa_contract = IAccountDispatcher { contract_address: eoa }; - - // Then - // selector: function get() - let data_get_tx = [0x6d, 0x4c, 0xe6, 0x3c].span(); - - // check counter value is 0 before doing inc - let tx = call_transaction(chain_id(), Option::Some(other_evm_address()), data_get_tx); - - let (_, return_data, _) = kakarot_core - .eth_call(origin: evm_address, tx: Transaction::Legacy(tx),); - - assert_eq!(return_data, u256_to_bytes_array(0).span()); - - // perform inc on the counter - let encoded_tx = eip_2930_rlp_encoded_counter_inc_tx(); - - let call = Call { - to: kakarot_address, - selector: selector!("eth_send_transaction"), - calldata: serialize_bytes(encoded_tx).span() - }; - - start_cheat_transaction_hash(eoa, selector!("transaction_hash")); - cheat_caller_address(eoa, contract_address_const::<0>(), CheatSpan::TargetCalls(1)); - let mut spy = spy_events(); - let result = eoa_contract.__execute__(array![call]); - assert_eq!(result.len(), 1); - - let expected_event = AccountContract::Event::transaction_executed( - AccountContract::TransactionExecuted { - response: *result.span()[0], success: true, gas_used: 0 - } - ); - let mut keys = array![]; - let mut data = array![]; - expected_event.append_keys_and_data(ref keys, ref data); - let mut contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) - .with_contract_address(eoa) - .with_keys(keys.span()) - .build(); - - let mut received_keys = contract_events.events[0].keys.span(); - let mut received_data = contract_events.events[0].data.span(); - let deserialized_received: AccountContract::Event = Event::deserialize( - ref received_keys, ref received_data - ) - .unwrap(); - if let AccountContract::Event::transaction_executed(transaction_executed) = - deserialized_received { - let expected_response = *result.span()[0]; - let expected_success = true; - let not_expected_gas_used = 0; - assert_eq!(transaction_executed.response, expected_response); - assert_eq!(transaction_executed.success, expected_success); - assert_ne!(transaction_executed.gas_used, not_expected_gas_used); - } else { - panic!("Expected transaction_executed event"); - } - // check counter value has increased - let tx = call_transaction(chain_id(), Option::Some(other_evm_address()), data_get_tx); - let (_, return_data, _) = kakarot_core - .eth_call(origin: evm_address, tx: Transaction::Legacy(tx),); - assert_eq!(return_data, u256_to_bytes_array(1).span()); -} - -#[test] -#[should_panic(expected: 'EOA: multicall not supported')] -fn test___execute___should_fail_with_zero_calls() { - let (_, kakarot_core) = setup_contracts_for_testing(); - - let eoa_contract = deploy_eoa(kakarot_core, eoa_address()); - let eoa_contract = IAccountDispatcher { contract_address: eoa_contract.contract_address }; - - cheat_caller_address( - eoa_contract.contract_address, contract_address_const::<0>(), CheatSpan::TargetCalls(1) - ); - eoa_contract.__execute__(array![]); -} - -#[test] -#[should_panic(expected: 'EOA: reentrant call')] -fn test___validate__fail__caller_not_0() { - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let evm_address = evm_address(); - let eoa = kakarot_core.deploy_externally_owned_account(evm_address); - fund_account_with_native_token(eoa, native_token, 0xfffffffffffffffffffffffffff); - let eoa_contract = IAccountDispatcher { contract_address: eoa }; - - start_cheat_caller_address(eoa_contract.contract_address, other_starknet_address()); - - let calls = array![ - Call { - to: kakarot_core.contract_address, - selector: selector!("eth_send_transaction"), - calldata: [].span() - } - ]; - cheat_caller_address( - eoa_contract.contract_address, contract_address_const::<1>(), CheatSpan::TargetCalls(1) - ); - eoa_contract.__validate__(calls); -} - -#[test] -#[should_panic(expected: 'EOA: multicall not supported')] -fn test___validate__fail__call_data_len_not_1() { - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let evm_address = evm_address(); - let eoa = kakarot_core.deploy_externally_owned_account(evm_address); - fund_account_with_native_token(eoa, native_token, 0xfffffffffffffffffffffffffff); - let eoa_contract = IAccountDispatcher { contract_address: eoa }; - - let calls = array![]; - cheat_caller_address( - eoa_contract.contract_address, contract_address_const::<0>(), CheatSpan::TargetCalls(1) - ); - eoa_contract.__validate__(calls); -} - -#[test] -#[should_panic(expected: 'to is not kakarot core')] -fn test___validate__fail__to_address_not_kakarot_core() { - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let evm_address = evm_address(); - let eoa = kakarot_core.deploy_externally_owned_account(evm_address); - fund_account_with_native_token(eoa, native_token, 0xfffffffffffffffffffffffffff); - let eoa_contract = IAccountDispatcher { contract_address: eoa }; - - // to reproduce locally: - // run: cp .env.example .env - // bun install & bun run scripts/compute_rlp_encoding.ts - let signature = Signature { - r: 0xaae7c4f6e4caa03257e37a6879ed5b51a6f7db491d559d10a0594f804aa8d797, - s: 0x2f3d9634f8cb9b9a43b048ee3310be91c2d3dc3b51a3313b473ef2260bbf6bc7, - y_parity: true - }; - start_cheat_signature( - eoa_contract.contract_address, - serialize_transaction_signature(signature, TxType::Legacy, 1).span() - ); - - let call = Call { - to: other_starknet_address(), - selector: selector!("eth_send_transaction"), - calldata: [].span() - }; - - cheat_caller_address( - eoa_contract.contract_address, contract_address_const::<0>(), CheatSpan::TargetCalls(1) - ); - eoa_contract.__validate__(array![call]); -} - -#[test] -#[should_panic(expected: "Validate: selector must be eth_send_transaction")] -fn test___validate__fail__selector_not_eth_send_transaction() { - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let evm_address = evm_address(); - let eoa = kakarot_core.deploy_externally_owned_account(evm_address); - fund_account_with_native_token(eoa, native_token, 0xfffffffffffffffffffffffffff); - let eoa_contract = IAccountDispatcher { contract_address: eoa }; - - start_cheat_chain_id(eoa_contract.contract_address, chain_id().into()); - let mut vm = VMBuilderTrait::new_with_presets().build(); - let chain_id = vm.env.chain_id; - start_cheat_caller_address(eoa_contract.contract_address, contract_address_const::<0>()); - - // to reproduce locally: - // run: cp .env.example .env - // bun install & bun run scripts/compute_rlp_encoding.ts - let signature = Signature { - r: 0xaae7c4f6e4caa03257e37a6879ed5b51a6f7db491d559d10a0594f804aa8d797, - s: 0x2f3d9634f8cb9b9a43b048ee3310be91c2d3dc3b51a3313b473ef2260bbf6bc7, - y_parity: true - }; - start_cheat_signature( - eoa_contract.contract_address, - serialize_transaction_signature(signature, TxType::Legacy, chain_id).span() - ); - - let call = Call { - to: kakarot_core.contract_address, selector: selector!("eth_call"), calldata: [].span() - }; - - cheat_caller_address( - eoa_contract.contract_address, contract_address_const::<0>(), CheatSpan::TargetCalls(1) - ); - eoa_contract.__validate__(array![call]); -} - -#[test] -fn test___validate__legacy_transaction() { - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let evm_address: EthAddress = 0xaA36F24f65b5F0f2c642323f3d089A3F0f2845Bf_u256.into(); - let eoa = kakarot_core.deploy_externally_owned_account(evm_address); - fund_account_with_native_token(eoa, native_token, 0xfffffffffffffffffffffffffff); - - let eoa_contract = IAccountDispatcher { contract_address: eoa }; - - start_cheat_chain_id(eoa_contract.contract_address, chain_id().into()); - let mut vm = VMBuilderTrait::new_with_presets().build(); - let chain_id = vm.env.chain_id; - - // to reproduce locally: - // run: cp .env.example .env - // bun install & bun run scripts/compute_rlp_encoding.ts - let signature = Signature { - r: 0x5e5202c7e9d6d0964a1f48eaecf12eef1c3cafb2379dfeca7cbd413cedd4f2c7, - s: 0x66da52d0b666fc2a35895e0c91bc47385fe3aa347c7c2a129ae2b7b06cb5498b, - y_parity: false - }; - start_cheat_signature( - eoa_contract.contract_address, - serialize_transaction_signature(signature, TxType::Legacy, chain_id).span() - ); - - let call = Call { - to: kakarot_core.contract_address, - selector: selector!("eth_send_transaction"), - calldata: serialize_bytes(legacy_rlp_encoded_tx()).span() - }; - - cheat_caller_address( - eoa_contract.contract_address, contract_address_const::<0>(), CheatSpan::TargetCalls(1) - ); - let result = eoa_contract.__validate__(array![call]); - assert(result == VALIDATED, 'validation failed'); -} - -#[test] -fn test___validate__eip_2930_transaction() { - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let evm_address: EthAddress = 0xaA36F24f65b5F0f2c642323f3d089A3F0f2845Bf_u256.into(); - let eoa = kakarot_core.deploy_externally_owned_account(evm_address); - fund_account_with_native_token(eoa, native_token, 0xfffffffffffffffffffffffffff); - - let eoa_contract = IAccountDispatcher { contract_address: eoa }; - - start_cheat_chain_id(eoa_contract.contract_address, chain_id().into()); - let mut vm = VMBuilderTrait::new_with_presets().build(); - let chain_id = vm.env.chain_id; - - // to reproduce locally: - // run: cp .env.example .env - // bun install & bun run scripts/compute_rlp_encoding.ts - let signature = Signature { - r: 0xbced8d81c36fe13c95b883b67898b47b4b70cae79e89fa27856ddf8c533886d1, - s: 0x3de0109f00bc3ed95ffec98edd55b6f750cb77be8e755935dbd6cfec59da7ad0, - y_parity: true - }; - - start_cheat_signature( - eoa_contract.contract_address, - serialize_transaction_signature(signature, TxType::Eip2930, chain_id).span() - ); - - let call = Call { - to: kakarot_core.contract_address, - selector: selector!("eth_send_transaction"), - calldata: serialize_bytes(eip_2930_encoded_tx()).span() - }; - - cheat_caller_address( - eoa_contract.contract_address, contract_address_const::<0>(), CheatSpan::TargetCalls(1) - ); - let result = eoa_contract.__validate__(array![call]); - assert(result == VALIDATED, 'validation failed'); -} - -#[test] -fn test___validate__eip_1559_transaction() { - let (native_token, kakarot_core) = setup_contracts_for_testing(); - let evm_address: EthAddress = 0xaA36F24f65b5F0f2c642323f3d089A3F0f2845Bf_u256.into(); - let eoa = kakarot_core.deploy_externally_owned_account(evm_address); - fund_account_with_native_token(eoa, native_token, 0xfffffffffffffffffffffffffff); - - let eoa_contract = IAccountDispatcher { contract_address: eoa }; - - start_cheat_chain_id(eoa_contract.contract_address, chain_id().into()); - let mut vm = VMBuilderTrait::new_with_presets().build(); - let chain_id = vm.env.chain_id; - - // to reproduce locally: - // run: cp .env.example .env - // bun install & bun run scripts/compute_rlp_encoding.ts - let signature = Signature { - r: 0x0f9a716653c19fefc240d1da2c5759c50f844fc8835c82834ea3ab7755f789a0, - s: 0x71506d904c05c6e5ce729b5dd88bcf29db9461c8d72413b864923e8d8f6650c0, - y_parity: true - }; - - let call = Call { - to: kakarot_core.contract_address, - selector: selector!("eth_send_transaction"), - calldata: serialize_bytes(eip_1559_encoded_tx()).span() - }; - - start_cheat_signature( - eoa_contract.contract_address, - serialize_transaction_signature(signature, TxType::Eip1559, chain_id).span() - ); - cheat_caller_address( - eoa_contract.contract_address, contract_address_const::<0>(), CheatSpan::TargetCalls(1) - ); - let result = eoa_contract.__validate__(array![call]); - assert(result == VALIDATED, 'validation failed'); -} diff --git a/crates/contracts/tests/test_execution_from_outside.cairo b/crates/contracts/tests/test_execution_from_outside.cairo index 98a126fa3..f9aa9416e 100644 --- a/crates/contracts/tests/test_execution_from_outside.cairo +++ b/crates/contracts/tests/test_execution_from_outside.cairo @@ -1,29 +1,52 @@ -use contracts::account_contract::{IAccountDispatcher, IAccountDispatcherTrait, OutsideExecution}; -use contracts::kakarot_core::interface::IExtendedKakarotCoreDispatcher; -use contracts::test_utils::{setup_contracts_for_testing, deploy_eoa}; -use core::starknet::ContractAddress; +use contracts::account_contract::{ + AccountContract, IAccountDispatcher, IAccountDispatcherTrait, OutsideExecution +}; +use contracts::kakarot_core::interface::{ + IExtendedKakarotCoreDispatcher, IExtendedKakarotCoreDispatcherTrait +}; +use contracts::test_data::{counter_evm_bytecode, eip_2930_rlp_encoded_counter_inc_tx,}; +use contracts::test_utils::{ + setup_contracts_for_testing, deploy_eoa, deploy_contract_account, + fund_account_with_native_token, call_transaction +}; +use core::num::traits::Bounded; use core::starknet::account::Call; -use core::starknet::contract_address_const; use core::starknet::secp256_trait::Signature; +use core::starknet::{ + ContractAddress, ClassHash, VALIDATED, get_contract_address, contract_address_const, EthAddress, + get_tx_info, Event +}; +use evm::test_utils::{ + kakarot_address, other_evm_address, other_starknet_address, eoa_address, tx_gas_limit, + gas_price, VMBuilderTrait +}; use evm::test_utils::{ca_address, chain_id}; +use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait}; use snforge_std::{ - start_cheat_caller_address, stop_cheat_caller_address, start_cheat_block_timestamp, - stop_cheat_block_timestamp, start_cheat_chain_id_global, stop_cheat_chain_id_global, - start_mock_call, stop_mock_call + 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, EventSpyTrait, EventsFilterTrait, CheatSpan, + cheat_caller_address, stop_mock_call, stop_cheat_block_timestamp, start_cheat_block_timestamp, + start_cheat_chain_id_global, stop_cheat_chain_id_global, start_mock_call, + start_cheat_caller_address_global, stop_cheat_caller_address_global }; + +use snforge_utils::snforge_utils::{ContractEvents, ContractEventsTrait, EventsFilterBuilderTrait}; +use utils::eth_transaction::transaction::{Transaction, TransactionTrait}; use utils::eth_transaction::tx_type::TxType; +use utils::helpers::{U8SpanExTrait, u256_to_bytes_array}; use utils::serialization::{serialize_bytes, serialize_transaction_signature}; -use utils::test_data::eip_2930_encoded_tx; +use utils::test_data::{ + legacy_rlp_encoded_tx, eip_2930_encoded_tx, eip_1559_encoded_tx, legacy_rlp_encoded_deploy_tx +}; -const VALID_SIGNATURE: [felt252; 5] = [1, 2, 3, 4, 0]; +fn transaction_signer() -> EthAddress { + 0x6Bd85F39321B00c6d603474C5B2fddEB9c92A466.try_into().unwrap() +} -const EIP2930_CALLER: felt252 = 0xaA36F24f65b5F0f2c642323f3d089A3F0f2845Bf; -const VALID_EIP2930_SIGNATURE: Signature = - Signature { - r: 0xbced8d81c36fe13c95b883b67898b47b4b70cae79e89fa27856ddf8c533886d1, - s: 0x3de0109f00bc3ed95ffec98edd55b6f750cb77be8e755935dbd6cfec59da7ad0, - y_parity: true - }; +const PLACEHOLDER_SIGNATURE: [felt252; 5] = [1, 2, 3, 4, 0]; + +const SNIP9_CALLER: felt252 = 0xaA36F24f65b5F0f2c642323f3d089A3F0f2845Bf; #[derive(Drop)] struct CallBuilder { @@ -52,8 +75,8 @@ impl CallBuilderImpl of CallBuilderTrait { self } - fn with_calldata(mut self: CallBuilder, calldata: Span) -> CallBuilder { - self.call.calldata = calldata; + fn with_calldata(mut self: CallBuilder, calldata: Span) -> CallBuilder { + self.call.calldata = serialize_bytes(calldata).span(); self } @@ -119,14 +142,22 @@ impl OutsideExecutionBuilderImpl of OutsideExecutionBuilderTrait { } } -fn set_up() -> (IExtendedKakarotCoreDispatcher, IAccountDispatcher) { - let (_, kakarot_core) = setup_contracts_for_testing(); - let eoa = deploy_eoa(kakarot_core, EIP2930_CALLER.try_into().unwrap()); +fn set_up() -> (IExtendedKakarotCoreDispatcher, IAccountDispatcher, IERC20CamelDispatcher) { + let (native_token, kakarot_core) = setup_contracts_for_testing(); + // When we deploy the EOA, we use get_caller_address to get the address of the KakarotCore + // contract and set the caller address to that. + // Therefore, we need to stop the global caller address cheat so that the EOA is deployed + // by the real KakarotCore contract and not the one impersonated by the cheat + stop_cheat_caller_address_global(); + let eoa = IAccountDispatcher { + contract_address: kakarot_core.deploy_externally_owned_account(transaction_signer()) + }; + start_cheat_caller_address_global(kakarot_core.contract_address); start_cheat_block_timestamp(eoa.contract_address, 999); start_cheat_chain_id_global(chain_id().into()); - (kakarot_core, eoa) + (kakarot_core, eoa, native_token) } fn tear_down(contract_account: IAccountDispatcher) { @@ -137,12 +168,12 @@ fn tear_down(contract_account: IAccountDispatcher) { #[test] #[should_panic(expected: 'SNIP9: Invalid caller')] fn test_execute_from_outside_invalid_caller() { - let (kakarot_core, contract_account) = set_up(); + let (kakarot_core, contract_account, _) = set_up(); let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) .with_caller(contract_address_const::<0xb0b>()) .build(); - let signature = VALID_SIGNATURE.span(); + let signature = PLACEHOLDER_SIGNATURE.span(); let _ = contract_account.execute_from_outside(outside_execution, signature); @@ -152,12 +183,12 @@ fn test_execute_from_outside_invalid_caller() { #[test] #[should_panic(expected: 'SNIP9: Too early call')] fn test_execute_from_outside_too_early_call() { - let (kakarot_core, contract_account) = set_up(); + let (kakarot_core, contract_account, _) = set_up(); let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) .with_execute_after(999) .build(); - let signature = VALID_SIGNATURE.span(); + let signature = PLACEHOLDER_SIGNATURE.span(); let _ = contract_account.execute_from_outside(outside_execution, signature); @@ -167,12 +198,12 @@ fn test_execute_from_outside_too_early_call() { #[test] #[should_panic(expected: 'SNIP9: Too late call')] fn test_execute_from_outside_too_late_call() { - let (kakarot_core, contract_account) = set_up(); + let (kakarot_core, contract_account, _) = set_up(); let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) .with_execute_before(999) .build(); - let signature = VALID_SIGNATURE.span(); + let signature = PLACEHOLDER_SIGNATURE.span(); let _ = contract_account.execute_from_outside(outside_execution, signature); @@ -180,9 +211,9 @@ fn test_execute_from_outside_too_late_call() { } #[test] -#[should_panic(expected: 'Invalid signature length')] -fn test_execute_from_outside_invalid_signature_length() { - let (kakarot_core, contract_account) = set_up(); +#[should_panic(expected: 'EOA: Invalid signature length')] +fn test_execute_from_outside_inPLACEHOLDER_SIGNATURE_length() { + let (kakarot_core, contract_account, _) = set_up(); let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) .build(); @@ -193,9 +224,9 @@ fn test_execute_from_outside_invalid_signature_length() { } #[test] -#[should_panic(expected: 'Multicall not supported')] +#[should_panic(expected: 'KKRT: Multicall not supported')] fn test_execute_from_outside_multicall_not_supported() { - let (kakarot_core, contract_account) = set_up(); + let (kakarot_core, contract_account, _) = set_up(); let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) .with_calls( @@ -205,7 +236,7 @@ fn test_execute_from_outside_multicall_not_supported() { ].span() ) .build(); - let signature = VALID_SIGNATURE.span(); + let signature = PLACEHOLDER_SIGNATURE.span(); let _ = contract_account.execute_from_outside(outside_execution, signature); @@ -213,35 +244,47 @@ fn test_execute_from_outside_multicall_not_supported() { } #[test] -#[should_panic(expected: 'to is not kakarot core')] -fn test_execute_from_outside_to_is_not_kakarot_core() { - let (kakarot_core, contract_account) = set_up(); +#[should_panic(expected: 'EOA: invalid signature')] +fn test_execute_from_outside_inPLACEHOLDER_SIGNATURE() { + let (kakarot_core, contract_account, _) = set_up(); + + let caller = contract_address_const::(); let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) - .with_calls([CallBuilderTrait::new(contract_address_const::<0xb0b>()).build()].span()) + .with_caller(caller) .build(); - let signature = VALID_SIGNATURE.span(); + let signature: Span = [1, 2, 3, 4, (chain_id() * 2 + 40).into()].span(); + start_cheat_caller_address(contract_account.contract_address, caller); let _ = contract_account.execute_from_outside(outside_execution, signature); tear_down(contract_account); } #[test] -#[should_panic(expected: "selector must be eth_send_transaction")] -fn test_execute_from_outside_wrong_selector() { - let (kakarot_core, contract_account) = set_up(); +#[should_panic(expected: 'EOA: could not decode tx')] +fn test_execute_from_outside_invalid_tx() { + let (kakarot_core, contract_account, _) = set_up(); + + let mut faulty_eip_2930_tx = eip_2930_encoded_tx(); + let signature = Signature { + r: 0x5c4ae1ed01c8df4277f02aa3443f8183ed44627217fd7f27badaed8795906e78, + s: 0x4d2af576441428d47c174ffddc6e70b980527a57795b3c87a71878f97ecef274, + y_parity: true + }; + let _ = faulty_eip_2930_tx.pop_front(); let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) .with_calls( [ CallBuilderTrait::new(kakarot_core.contract_address) - .with_selector('bad_selector') + .with_calldata(faulty_eip_2930_tx) .build() ].span() ) .build(); - let signature = VALID_SIGNATURE.span(); + + let signature = serialize_transaction_signature(signature, TxType::Eip2930, chain_id()).span(); let _ = contract_account.execute_from_outside(outside_execution, signature); @@ -249,90 +292,264 @@ fn test_execute_from_outside_wrong_selector() { } #[test] -#[should_panic(expected: 'EOA: invalid signature')] -fn test_execute_from_outside_invalid_signature() { - let (kakarot_core, contract_account) = set_up(); - - let caller = contract_address_const::(); +#[should_panic(expected: 'KKRT: Multicall not supported')] +fn test_execute_from_outside_should_fail_with_zero_calls() { + let (kakarot_core, contract_account, _) = set_up(); let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) - .with_caller(caller) + .with_calls([].span()) .build(); - let signature: Span = [1, 2, 3, 4, (chain_id() * 2 + 40).into()].span(); + let signature = PLACEHOLDER_SIGNATURE.span(); - start_cheat_caller_address(contract_account.contract_address, caller); + let _ = contract_account.execute_from_outside(outside_execution, signature); - start_mock_call::< - (bool, Span, u128) - >( - kakarot_core.contract_address, - selector!("eth_send_transaction"), - (true, [1, 2, 3].span(), 0) - ); + tear_down(contract_account); +} + +#[test] +#[should_panic(expected: 'EOA: cannot have code')] +fn test_execute_from_outside_should_fail_account_with_code() { + let (kakarot_core, _, _) = set_up(); + let contract_address = deploy_contract_account( + kakarot_core, other_evm_address(), counter_evm_bytecode() + ) + .starknet; + let contract_account = IAccountDispatcher { contract_address }; + + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .build(); + let signature = PLACEHOLDER_SIGNATURE.span(); + + start_cheat_block_timestamp(contract_account.contract_address, 999); let _ = contract_account.execute_from_outside(outside_execution, signature); tear_down(contract_account); } + #[test] -#[should_panic(expected: 'EOA: could not decode tx')] -fn test_execute_from_outside_invalid_tx() { - let (kakarot_core, contract_account) = set_up(); +#[should_panic(expected: 'KKRT: Multicall not supported')] +fn test_execute_from_outside_should_fail_with_multi_calls() { + let (kakarot_core, eoa, _) = set_up(); + + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .with_calls( + [ + CallBuilderTrait::new(kakarot_core.contract_address) + .with_calldata(legacy_rlp_encoded_tx()) + .build() + ; 2].span() + ) + .build(); + let signature = PLACEHOLDER_SIGNATURE.span(); + + let _ = eoa.execute_from_outside(outside_execution, signature); + + tear_down(eoa); +} - let mut faulty_eip_2930_tx = eip_2930_encoded_tx(); - let _ = faulty_eip_2930_tx.pop_front(); + +#[test] +fn test_execute_from_outside_legacy_tx() { + let (kakarot_core, eoa, native_token) = set_up(); + fund_account_with_native_token(eoa.contract_address, native_token, Bounded::::MAX.into()); + + let caller = contract_address_const::(); let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .with_caller(caller) .with_calls( [ CallBuilderTrait::new(kakarot_core.contract_address) - .with_calldata(serialize_bytes(faulty_eip_2930_tx).span()) + .with_calldata(legacy_rlp_encoded_tx()) .build() ].span() ) .build(); + // to reproduce locally: + // run: cp .env.example .env + // bun install & bun run scripts/compute_rlp_encoding.ts + let signature = serialize_transaction_signature( - VALID_EIP2930_SIGNATURE, TxType::Eip2930, chain_id() + Signature { + r: 0xf2805d01dd4fa240c79039c85a77554fc186cc73c2025d7f8c02bc8fe1a982b5, + s: 0x27ff351275563c1a29ab9eaeec4a3b63fbc4035d6da6b8b6af52c7993b5869ec, + y_parity: true + }, + TxType::Legacy, + chain_id() ) .span(); - let _ = contract_account.execute_from_outside(outside_execution, signature); + cheat_caller_address(eoa.contract_address, caller, CheatSpan::TargetCalls(1)); + stop_cheat_caller_address(kakarot_core.contract_address); + let data = eoa.execute_from_outside(outside_execution, signature); - tear_down(contract_account); + assert_eq!(data.len(), 1); + assert_eq!(*data[0], [].span()); + + stop_cheat_caller_address(eoa.contract_address); + tear_down(eoa); } #[test] -fn test_execute_from_outside_a() { - let (kakarot_core, contract_account) = set_up(); +fn test_execute_from_outside_eip2930_tx() { + let (kakarot_core, eoa, native_token) = set_up(); + fund_account_with_native_token(eoa.contract_address, native_token, Bounded::::MAX.into()); + let caller = contract_address_const::(); + + // Signature for the default eip2930 tx + let signature = Signature { + r: 0x5c4ae1ed01c8df4277f02aa3443f8183ed44627217fd7f27badaed8795906e78, + s: 0x4d2af576441428d47c174ffddc6e70b980527a57795b3c87a71878f97ecef274, + y_parity: true + }; + + // Defaults with an eip2930 tx + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .with_caller(caller) + .build(); + let signature = serialize_transaction_signature(signature, TxType::Eip2930, chain_id()).span(); + + cheat_caller_address(eoa.contract_address, caller, CheatSpan::TargetCalls(1)); + stop_cheat_caller_address(kakarot_core.contract_address); + let data = eoa.execute_from_outside(outside_execution, signature); - let caller = contract_address_const::(); + assert_eq!(data.len(), 1); + assert_eq!(*data[0], [].span()); + + stop_cheat_caller_address(eoa.contract_address); + tear_down(eoa); +} + + +#[test] +fn test_execute_from_outside_eip1559_tx() { + let (kakarot_core, eoa, native_token) = set_up(); + fund_account_with_native_token(eoa.contract_address, native_token, Bounded::::MAX.into()); + + let caller = contract_address_const::(); let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) .with_caller(caller) + .with_calls( + [ + CallBuilderTrait::new(kakarot_core.contract_address) + .with_calldata(eip_1559_encoded_tx()) + .build() + ].span() + ) .build(); - let signature = serialize_transaction_signature( - VALID_EIP2930_SIGNATURE, TxType::Eip2930, chain_id() - ) - .span(); - start_cheat_caller_address(contract_account.contract_address, caller); + // to reproduce locally: + // run: cp .env.example .env + // bun install & bun run scripts/compute_rlp_encoding.ts + let signature = Signature { + r: 0xb2563dbafa29dd6f126f0e6581b772d3f07063e2f07fb7bdf73aad34a04c4283, + s: 0x73df539e40359b81b8f260ed04431de098fc149bc5e27120e6711acabaecd067, + y_parity: true + }; + let signature = serialize_transaction_signature(signature, TxType::Eip1559, chain_id()).span(); - start_mock_call::< - (bool, Span, u128) - >( - kakarot_core.contract_address, - selector!("eth_send_transaction"), - (true, [1, 2, 3].span(), 0) - ); + // Stop all cheats and only mock the EFO caller. + stop_cheat_caller_address_global(); + cheat_caller_address(eoa.contract_address, caller, CheatSpan::TargetCalls(1)); + let data = eoa.execute_from_outside(outside_execution, signature); - let data = contract_account.execute_from_outside(outside_execution, signature); + assert_eq!(data.len(), 1); + assert_eq!(*data[0], [].span()); - assert(data.len() == 1, 'bad length'); - assert(*data.at(0) == [1, 2, 3].span(), 'bad data'); + tear_down(eoa); +} - stop_mock_call(kakarot_core.contract_address, selector!("eth_send_transaction")); - stop_cheat_caller_address(contract_account.contract_address); - tear_down(contract_account); +#[test] +fn test_execute_from_outside_eip_2930_counter_inc_tx() { + let (kakarot_core, eoa, native_token) = set_up(); + fund_account_with_native_token(eoa.contract_address, native_token, Bounded::::MAX.into()); + + let kakarot_address = kakarot_core.contract_address; + + deploy_contract_account(kakarot_core, other_evm_address(), counter_evm_bytecode()); + + start_cheat_caller_address(kakarot_address, eoa.contract_address); + + // Then + // selector: function get() + let data_get_tx = [0x6d, 0x4c, 0xe6, 0x3c].span(); + + // check counter value is 0 before doing inc + let tx = call_transaction(chain_id(), Option::Some(other_evm_address()), data_get_tx); + + let (_, return_data, _) = kakarot_core + .eth_call(origin: transaction_signer(), tx: Transaction::Legacy(tx),); + + assert_eq!(return_data, u256_to_bytes_array(0).span()); + + // perform inc on the counter + let call = Call { + to: kakarot_address, + selector: selector!("eth_send_transaction"), + calldata: serialize_bytes(eip_2930_rlp_encoded_counter_inc_tx()).span() + }; + + start_cheat_transaction_hash(eoa.contract_address, selector!("transaction_hash")); + start_cheat_block_timestamp(eoa.contract_address, 100); + cheat_caller_address( + eoa.contract_address, contract_address_const::<0>(), CheatSpan::TargetCalls(1) + ); + let mut spy = spy_events(); + let outside_execution = OutsideExecution { + caller: contract_address_const::<'ANY_CALLER'>(), + nonce: 0, + execute_after: 0, + execute_before: 10000000, + calls: array![call].span() + }; + let signature = Signature { + r: 0x8cd55583b5da62b3fd23586bf4f1ffd496046b9d248a7983ec41bd6fb673f379, + s: 0x09432a74ec3720a226ac040ce828f92e22350c4d8f7b188693cad035e99372ed, + y_parity: true + }; + let signature = serialize_transaction_signature(signature, TxType::Eip2930, chain_id()).span(); + stop_cheat_caller_address(kakarot_core.contract_address); + let result = eoa.execute_from_outside(outside_execution, signature); + assert_eq!(result.len(), 1); + + let expected_event = AccountContract::Event::transaction_executed( + AccountContract::TransactionExecuted { + response: *result.span()[0], success: true, gas_used: 0 + } + ); + let mut keys = array![]; + let mut data = array![]; + expected_event.append_keys_and_data(ref keys, ref data); + let mut contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(eoa.contract_address) + .with_keys(keys.span()) + .build(); + + let mut received_keys = contract_events.events[0].keys.span(); + let mut received_data = contract_events.events[0].data.span(); + let deserialized_received: AccountContract::Event = Event::deserialize( + ref received_keys, ref received_data + ) + .unwrap(); + if let AccountContract::Event::transaction_executed(transaction_executed) = + deserialized_received { + let expected_response = *result.span()[0]; + let expected_success = true; + let not_expected_gas_used = 0; + assert_eq!(transaction_executed.response, expected_response); + assert_eq!(transaction_executed.success, expected_success); + assert_ne!(transaction_executed.gas_used, not_expected_gas_used); + } else { + panic!("Expected transaction_executed event"); + } + // check counter value has increased + let tx = call_transaction(chain_id(), Option::Some(other_evm_address()), data_get_tx); + let (_, return_data, _) = kakarot_core + .eth_call(origin: transaction_signer(), tx: Transaction::Legacy(tx),); + assert_eq!(return_data, u256_to_bytes_array(1).span()); } diff --git a/crates/contracts/tests/test_kakarot_core.cairo b/crates/contracts/tests/test_kakarot_core.cairo index 9638316a3..65c9c656b 100644 --- a/crates/contracts/tests/test_kakarot_core.cairo +++ b/crates/contracts/tests/test_kakarot_core.cairo @@ -171,7 +171,6 @@ fn test_kakarot_core_upgrade_contract() { } #[test] -#[available_gas(2000000000000000000)] fn test_eth_send_transaction_non_deploy_tx() { // Given let (native_token, kakarot_core) = contract_utils::setup_contracts_for_testing(); @@ -318,7 +317,8 @@ fn test_process_transaction() { let result = kakarot_core .process_transaction( origin: Address { evm: eoa_evm_address, starknet: eoa_starknet_address }, - tx: Transaction::Legacy(tx) + tx: Transaction::Legacy(tx), + :gas_price ); let return_data = result.return_data; @@ -361,14 +361,14 @@ fn test_eth_send_transaction_deploy_tx() { let expected_address: EthAddress = 0x19587b345dcadfe3120272bd0dbec24741891759 .try_into() .unwrap(); - assert(deploy_result == expected_address.to_bytes().span(), 'returndata not counter bytecode'); + assert_eq!(deploy_result, expected_address.to_bytes().span()); // Set back the contract address to Kakarot for the calculation of the deployed SN contract // address, where we use a kakarot internal functions and thus must "mock" its address. let computed_sn_addr = kakarot_core.compute_starknet_address(expected_address); let CA = IAccountDispatcher { contract_address: computed_sn_addr }; let bytecode = CA.bytecode(); - assert(bytecode == counter_evm_bytecode(), 'wrong bytecode'); + assert_eq!(bytecode, counter_evm_bytecode()); // Check that the account was created and `get` returns 0. let input = [0x6d, 0x4c, 0xe6, 0x3c].span(); diff --git a/crates/evm/src/backend.cairo b/crates/evm/src/backend.cairo index f9215a84b..49f713b44 100644 --- a/crates/evm/src/backend.cairo +++ b/crates/evm/src/backend.cairo @@ -1 +1,2 @@ pub mod starknet_backend; +pub mod validation; diff --git a/crates/evm/src/backend/validation.cairo b/crates/evm/src/backend/validation.cairo new file mode 100644 index 000000000..8f288b686 --- /dev/null +++ b/crates/evm/src/backend/validation.cairo @@ -0,0 +1,51 @@ +use contracts::IKakarotCore; +use contracts::account_contract::{IAccountDispatcher, IAccountDispatcherTrait}; +use contracts::kakarot_core::KakarotCore; +use core::ops::SnapshotDeref; +use core::starknet::storage::{StoragePointerReadAccess}; +use core::starknet::{get_caller_address, get_tx_info}; +use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait}; +use starknet::storage::StorageTrait; +use utils::constants::POW_2_32; +use utils::eth_transaction::get_effective_gas_price; +use utils::eth_transaction::transaction::{Transaction, TransactionTrait}; + +pub fn validate_eth_tx(kakarot_state: @KakarotCore::ContractState, tx: Transaction) -> u128 { + let kakarot_storage = kakarot_state.snapshot_deref().storage(); + // Validate transaction + + // Validate chain_id for post eip155 + let tx_chain_id = tx.chain_id(); + let kakarot_chain_id: u64 = get_tx_info() + .chain_id + .try_into() + .unwrap() % POW_2_32 + .try_into() + .unwrap(); + if (tx_chain_id.is_some()) { + assert(tx_chain_id.unwrap() == kakarot_chain_id, 'Invalid chain id'); + } + + // Validate nonce + let starknet_caller_address = get_caller_address(); + let account = IAccountDispatcher { contract_address: starknet_caller_address }; + assert(account.get_nonce() == tx.nonce(), 'Invalid nonce'); + + // Validate gas + assert(tx.gas_limit() <= kakarot_state.get_block_gas_limit(), 'Tx gas > Block gas'); + let block_base_fee = kakarot_storage.Kakarot_base_fee.read(); + let effective_gas_price = get_effective_gas_price( + Option::Some(tx.max_fee_per_gas()), tx.max_priority_fee_per_gas(), block_base_fee.into() + ); + assert!(effective_gas_price.is_ok(), "{:?}", effective_gas_price.unwrap_err()); + + // Validate balance + let balance = IERC20CamelDispatcher { + contract_address: kakarot_storage.Kakarot_native_token_address.read() + } + .balanceOf(starknet_caller_address); + let max_gas_fee = tx.gas_limit().into() * tx.max_fee_per_gas(); + let tx_cost = tx.value() + max_gas_fee.into(); + assert(tx_cost <= balance, 'Not enough ETH'); + effective_gas_price.unwrap() +} diff --git a/crates/evm/src/test_utils.cairo b/crates/evm/src/test_utils.cairo index a1a264c73..d961a0484 100644 --- a/crates/evm/src/test_utils.cairo +++ b/crates/evm/src/test_utils.cairo @@ -200,8 +200,10 @@ pub fn tx_gas_limit() -> u64 { constants::BLOCK_GAS_LIMIT } +pub const BASE_FEE: u128 = 1000; + pub fn gas_price() -> u128 { - 32 + BASE_FEE + 1 } pub fn value() -> u256 { diff --git a/crates/utils/src/eth_transaction.cairo b/crates/utils/src/eth_transaction.cairo index 95172c245..1cf35294e 100644 --- a/crates/utils/src/eth_transaction.cairo +++ b/crates/utils/src/eth_transaction.cairo @@ -23,9 +23,9 @@ pub struct TransactionMetadata { /// Get the effective gas price of a transaction as specfified in EIP-1559 with relevant /// checks. -fn get_effective_gas_price( - max_fee_per_gas: Option, max_priority_fee_per_gas: Option, block_base_fee: u256, -) -> Result { +pub fn get_effective_gas_price( + max_fee_per_gas: Option, max_priority_fee_per_gas: Option, block_base_fee: u128, +) -> Result { match max_fee_per_gas { Option::Some(max_fee) => { let max_priority_fee_per_gas = max_priority_fee_per_gas.unwrap_or(0); diff --git a/scripts/compute_rlp_encoding.ts b/scripts/compute_rlp_encoding.ts index 29cf8d38c..a9d00be58 100644 --- a/scripts/compute_rlp_encoding.ts +++ b/scripts/compute_rlp_encoding.ts @@ -34,8 +34,10 @@ const main = async () => { const wallet = new Wallet(process.env.PRIVATE_KEY); console.log("address of the wallet is", wallet.address); - const tx_type = parseInt( - await question("enter `tx_type`, 0: legacy, 1: 2930, 2:1559 : "), + let tx_type = parseInt( + await question( + "enter transaction, 0: legacy, 1: 2930, 2:1559, 3: inc_counter ", + ), ); // for type 0 and type 1 @@ -57,6 +59,15 @@ const main = async () => { readFileSync("./scripts/data/input_fee_tx.json", "utf-8"), ); break; + case 3: + tx_type = 1; + tx = JSON.parse( + readFileSync( + "./scripts/data/input_eip_2930_counter_inc_tx.json", + "utf-8", + ), + ); + break; default: throw new Error( `transaction type ${tx_type} isn't a valid transaction type`, diff --git a/scripts/data/input_eip_2930_counter_inc_tx.json b/scripts/data/input_eip_2930_counter_inc_tx.json new file mode 100644 index 000000000..9792f0baa --- /dev/null +++ b/scripts/data/input_eip_2930_counter_inc_tx.json @@ -0,0 +1,10 @@ +{ + "chainId": "0x4b4b5254", + "nonce": "0x0", + "gasPrice": "0x3b9aca00", + "gasLimit": "0x1e8480", + "to": "0x0000006f746865725f65766d5f61646472657373", + "value": "0x0", + "data": "0x371303c0", + "accessList": [] +}