From 56e0a009e35c67ac3798292834949933070aa0e8 Mon Sep 17 00:00:00 2001 From: ermvrs Date: Wed, 11 Dec 2024 18:16:23 +0300 Subject: [PATCH 1/2] validation with calldata --- src/rosettanet.cairo | 3 ++ tests/accounts_tests.cairo | 89 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/rosettanet.cairo b/src/rosettanet.cairo index d0a6858..ebd989b 100644 --- a/src/rosettanet.cairo +++ b/src/rosettanet.cairo @@ -71,6 +71,9 @@ pub mod Rosettanet { fn constructor(ref self: ContractState, developer: ContractAddress, strk: ContractAddress) { self.dev.write(developer); self.strk.write(strk); + + let strk_eth_address = self.generate_eth_address(strk); + self.update_registry(strk, strk_eth_address); } #[abi(embed_v0)] diff --git a/tests/accounts_tests.cairo b/tests/accounts_tests.cairo index 20c11b7..19917dd 100644 --- a/tests/accounts_tests.cairo +++ b/tests/accounts_tests.cairo @@ -100,7 +100,70 @@ fn test_transaction_validation_value_transfer_only() { #[test] fn test_transaction_validation_calldata() { - // TODO: test with calldata, example transfer of usdc + // Example usdc transfer + let eth_address: EthAddress = 0xE4306a06B19Fdc04FDf98cF3c00472f29254c0e1.try_into().unwrap(); + let tx = RosettanetCall { + to: 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238.try_into().unwrap(), // we dont need to deploy account, we only check validation here + nonce: 77, + max_priority_fee_per_gas: 1638611, + max_fee_per_gas: 18610805637, + gas_limit: 45439, + value: 0, + calldata: array![0xa9059cbb, 0xb756b1bc042fa70d85ee84eab646a3b438a285ee, 0xf4240, 0x0].span(), + directives: array![0x2,0x1,0x0].span(), + target_function: array![0x7472616E7366657228616464726573732C75696E7432353629].span() // transfer(address,uint256) + }; + + let signature = array![0x6ddb2d56bf6b847af890501e1a44bf19, 0xcc8d431460ddb8f3a228d1cdfe069be1, 0xdbeff1d03deae8859e16491d3c7d4b89, 0x62b4b646ff3c09068d04eb98eec04413, 0x1b, 0x0, 0x0]; + let unsigned_tx_hash: u256 = 0xcd53d7d0bc548f2e2b18cde82b5078144230cc1154354e3e1696f622a8783a30; + + let generated_tx_hash: u256 = generate_tx_hash(tx); + assert_eq!(generated_tx_hash, unsigned_tx_hash); + + let (_, account) = deploy_account_from_rosettanet(eth_address); + assert_eq!(account.get_ethereum_address(), eth_address); + + start_cheat_nonce_global(tx.nonce.into()); + start_cheat_signature_global(signature.span()); + let validation = account.__validate__(tx); + stop_cheat_signature_global(); + stop_cheat_nonce_global(); + + assert_eq!(validation, starknet::VALIDATED); +} + +#[test] +#[should_panic(expected:'calldata target mismatch')] +fn test_transaction_validation_calldata_wrong_target_function() { + let eth_address: EthAddress = 0xE4306a06B19Fdc04FDf98cF3c00472f29254c0e1.try_into().unwrap(); + let tx = RosettanetCall { + to: 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238.try_into().unwrap(), // we dont need to deploy account, we only check validation here + nonce: 77, + max_priority_fee_per_gas: 1638611, + max_fee_per_gas: 18610805637, + gas_limit: 45439, + value: 0, + calldata: array![0xa9059cbb, 0xb756b1bc042fa70d85ee84eab646a3b438a285ee, 0xf4240, 0x0].span(), + directives: array![0x2,0x1,0x0].span(), + target_function: array![0x17228616464726573732C75696E7432353619].span() // random hex + }; + + let signature = array![0x6ddb2d56bf6b847af890501e1a44bf19, 0xcc8d431460ddb8f3a228d1cdfe069be1, 0xdbeff1d03deae8859e16491d3c7d4b89, 0x62b4b646ff3c09068d04eb98eec04413, 0x1b, 0x0, 0x0]; + let unsigned_tx_hash: u256 = 0xcd53d7d0bc548f2e2b18cde82b5078144230cc1154354e3e1696f622a8783a30; + + let generated_tx_hash: u256 = generate_tx_hash(tx); + assert_eq!(generated_tx_hash, unsigned_tx_hash); + + let (_, account) = deploy_account_from_rosettanet(eth_address); + assert_eq!(account.get_ethereum_address(), eth_address); + + start_cheat_nonce_global(tx.nonce.into()); + start_cheat_signature_global(signature.span()); + let validation = account.__validate__(tx); + stop_cheat_signature_global(); + stop_cheat_nonce_global(); + + assert_eq!(validation, starknet::VALIDATED); } #[test] @@ -163,7 +226,7 @@ fn test_execute_value_transfer_wrong_value_on_sig() { let (rosettanet, account, _) = deploy_funded_account_from_rosettanet(eth_address); - let receiver = deploy_account_from_existing_rosettanet(receiver_address, rosettanet.contract_address); + let _ = deploy_account_from_existing_rosettanet(receiver_address, rosettanet.contract_address); start_cheat_nonce_global(tx.nonce.into()); start_cheat_signature_global(signature.span()); @@ -197,7 +260,7 @@ fn test_execute_value_transfer_not_enough_balance() { let (rosettanet, account) = deploy_account_from_rosettanet(eth_address); - let receiver = deploy_account_from_existing_rosettanet(receiver_address, rosettanet.contract_address); + let _ = deploy_account_from_existing_rosettanet(receiver_address, rosettanet.contract_address); start_cheat_nonce_global(tx.nonce.into()); start_cheat_signature_global(signature.span()); @@ -245,4 +308,24 @@ fn test_execute_value_transfer() { assert_eq!(execution, array![array![].span()]); } +#[test] +fn test_execute_erc20_transfer() { + // Example usdc transfer + let eth_address: EthAddress = 0xE4306a06B19Fdc04FDf98cF3c00472f29254c0e1.try_into().unwrap(); + let tx = RosettanetCall { + to: 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238.try_into().unwrap(), // we dont need to deploy account, we only check validation here + nonce: 77, + max_priority_fee_per_gas: 1638611, + max_fee_per_gas: 18610805637, + gas_limit: 45439, + value: 0, + calldata: array![0xa9059cbb, 0xb756b1bc042fa70d85ee84eab646a3b438a285ee, 0xf4240, 0x0].span(), + directives: array![0x2,0x1,0x0].span(), + target_function: array![0x7472616E7366657228616464726573732C75696E7432353629].span() // transfer(address,uint256) + }; + + let signature = array![0x6ddb2d56bf6b847af890501e1a44bf19, 0xcc8d431460ddb8f3a228d1cdfe069be1, 0xdbeff1d03deae8859e16491d3c7d4b89, 0x62b4b646ff3c09068d04eb98eec04413, 0x1b, 0x0, 0x0]; + let (rosettanet, account, strk) = deploy_funded_account_from_rosettanet(eth_address); + // Complete this one +} // TODO: tests with calldata \ No newline at end of file From 119b6bf6bca579eba1e9705c0b3dcf7bb35b4d4d Mon Sep 17 00:00:00 2001 From: ermvrs Date: Wed, 11 Dec 2024 20:53:33 +0300 Subject: [PATCH 2/2] execution tests complete, access list fails --- .snfoundry_cache/.prev_tests_failed | 2 + src/accounts/base.cairo | 1 + src/accounts/encoding.cairo | 32 +++++ src/accounts/utils.cairo | 7 +- src/rosettanet.cairo | 13 +++ src/utils/transaction/eip2930.cairo | 6 +- tests/accounts_tests.cairo | 173 +++++++++++++++++++++++++++- tests/test_utils.cairo | 34 ++++++ 8 files changed, 258 insertions(+), 10 deletions(-) diff --git a/.snfoundry_cache/.prev_tests_failed b/.snfoundry_cache/.prev_tests_failed index e69de29..8127f6e 100644 --- a/.snfoundry_cache/.prev_tests_failed +++ b/.snfoundry_cache/.prev_tests_failed @@ -0,0 +1,2 @@ +rosettacontracts_integrationtest::accounts_tests::test_validation_with_access_list +rosettacontracts::accounts::encoding::tests::rlp_encode_access_list diff --git a/src/accounts/base.cairo b/src/accounts/base.cairo index 5bac0b3..de09e28 100644 --- a/src/accounts/base.cairo +++ b/src/accounts/base.cairo @@ -246,6 +246,7 @@ pub mod RosettaAccount { if(current_directive == 2_u8) { let eth_address: EthAddress = (*calldata.at(index)).try_into().unwrap(); let sn_address = IRosettanetDispatcher{contract_address: self.registry.read()}.get_starknet_address(eth_address); + assert(sn_address != starknet::contract_address_const::<0>(), 'calldata address not registered'); updated_array.append(sn_address.into()); } else { updated_array.append(*calldata.at(index)); diff --git a/src/accounts/encoding.cairo b/src/accounts/encoding.cairo index c181033..c064c5b 100644 --- a/src/accounts/encoding.cairo +++ b/src/accounts/encoding.cairo @@ -77,6 +77,15 @@ pub fn deserialize_bytes(value: felt252, len: usize) -> Span { ba.into_bytes() } +pub fn deserialize_u256_with_zeroes(value:u256) -> Span { + let mut ba: core::byte_array::ByteArray = Default::default(); + let low_bytes = deserialize_bytes(value.low.into(), 16); + let high_bytes = deserialize_bytes(value.high.into(), 16); + ba.append(@ByteArrayExTrait::from_bytes(low_bytes)); + ba.append(@ByteArrayExTrait::from_bytes(high_bytes)); + ba.into_bytes() +} + pub fn deserialize_u256(value: u256) -> Span { // Bu fonksiyonu tamamla let mut ba: core::byte_array::ByteArray = Default::default(); @@ -124,6 +133,7 @@ pub fn deserialize_bytes_non_zeroes(value: felt252, len: usize) -> Span { #[cfg(test)] mod tests { use crate::accounts::encoding::{Eip1559Transaction, rlp_encode_eip1559, deserialize_bytes_non_zeroes, bytes_from_felts, deserialize_u256, deserialize_u256_span}; + use crate::utils::transaction::eip2930::{AccessListItem}; use core::num::traits::{Bounded}; #[test] @@ -147,6 +157,28 @@ mod tests { assert_eq!(*encoded.at(3), 0x0B); assert_eq!(*encoded.at(4), 0x75); } + + #[test] + fn rlp_encode_access_list() { + let access_list_item = AccessListItem { + ethereum_address: 0x5703ff58bB0CA34F870a8bC18dDd541f29375978.try_into().unwrap(), + storage_keys: array![0_u256, 1_u256].span() + }; + let tx = Eip1559Transaction { + chain_id: 11155111, + nonce: 87, + max_priority_fee_per_gas: 1638611, + max_fee_per_gas: 16357352599, + gas_limit: 21000, + to: 0xC7f5D5D3725f36CF36477B84010EB8DdE42D3636.try_into().unwrap(), + value: 0x0, + input: array![0xf4,0xac,0xc7,0xb5].span(), + access_list: array![access_list_item].span(), + }; + + let encoded = rlp_encode_eip1559(tx); + assert_eq!(encoded.len(), 142); + } #[test] fn rlp_encode_transaction_value() { diff --git a/src/accounts/utils.cairo b/src/accounts/utils.cairo index 80eea0c..99b20d8 100644 --- a/src/accounts/utils.cairo +++ b/src/accounts/utils.cairo @@ -4,6 +4,7 @@ use crate::accounts::encoding::{Eip1559Transaction, deserialize_bytes, deseriali use crate::utils::constants::{POW_2_250}; use crate::utils::traits::SpanDefault; use crate::utils::bytes::{U8SpanExTrait, ByteArrayExTrait}; +use crate::utils::transaction::eip2930::{AccessListItem}; use starknet::eth_signature::{verify_eth_signature}; pub const CHAIN_ID: u64 = 11155111; // TODO: Correct it @@ -24,6 +25,7 @@ pub struct RosettanetCall { pub gas_limit: u64, pub value: u256, // To be used future pub calldata: Span, // Calldata len must be +1 directive len + pub access_list: Span, pub directives: Span, // 0 -> do nothing, 1 -> u256, 2-> address pub target_function: Span // Function name and types to used to calculate eth func signature } @@ -62,7 +64,7 @@ pub fn parse_transaction(call: RosettanetCall) -> Eip1559Transaction { gas_limit: call.gas_limit, to: call.to, value: call.value, - access_list: array![].span(), // Do we need these? + access_list: call.access_list, input: calldata_bytes }; @@ -209,6 +211,7 @@ mod tests { gas_limit: 21000, value: 1, calldata: array![].span(), + access_list: array![].span(), directives: array![].span(), target_function: array![].span() }; @@ -231,6 +234,7 @@ mod tests { gas_limit: 21000, value: 0, calldata: calldata, + access_list: array![].span(), directives: directives, target_function: target_function }; @@ -255,6 +259,7 @@ mod tests { gas_limit: 21000, value: 0, calldata: calldata, + access_list: array![].span(), directives: directives, target_function: target_function }; diff --git a/src/rosettanet.cairo b/src/rosettanet.cairo index ebd989b..58985e3 100644 --- a/src/rosettanet.cairo +++ b/src/rosettanet.cairo @@ -5,6 +5,7 @@ pub trait IRosettanet { fn register_contract(ref self: TState, address: ContractAddress); // Registers existing starknet contract to registry fn deploy_account(ref self: TState, eth_address: EthAddress) -> ContractAddress; // Deploys starknet account and returns address fn set_account_class(ref self: TState, class: ClassHash); // Sets account class, this function will be removed after stable account + fn register_matched_addresses(ref self: TState, sn_address: ContractAddress, eth_address: EthAddress); // Will be used during alpha fn upgrade(ref self: TState, class: ClassHash); // Upgrades contract // Read methods fn get_starknet_address(self: @TState, eth_address: EthAddress) -> ContractAddress; @@ -117,6 +118,18 @@ pub mod Rosettanet { self.emit(AccountClassChanged {changer: get_caller_address(), new_class: class}); } + /// Updates registry without generating eth address + /// # Arguments + /// * `sn_address` - Starknet address + /// * `eth_address` - Ethereum address + fn register_matched_addresses(ref self: ContractState, sn_address: ContractAddress, eth_address: EthAddress) { + assert(get_caller_address() == self.dev.read(), 'only dev'); + + self.update_registry(sn_address, eth_address); + + self.emit(AddressRegistered {sn_address: sn_address, eth_address: eth_address}); + } + /// Updates this contracts class /// # Arguments /// * `class` - New class hash diff --git a/src/utils/transaction/eip2930.cairo b/src/utils/transaction/eip2930.cairo index 495e7ad..1ebd7e2 100644 --- a/src/utils/transaction/eip2930.cairo +++ b/src/utils/transaction/eip2930.cairo @@ -2,7 +2,7 @@ use core::starknet::EthAddress; use crate::utils::transaction::common::TxKind; use alexandria_encoding::rlp::{RLPItem}; use crate::utils::traits::SpanDefault; -use crate::accounts::encoding::{deserialize_bytes}; +use crate::accounts::encoding::{deserialize_bytes, deserialize_u256_with_zeroes}; #[derive(Copy, Drop, Serde, PartialEq, Debug)] @@ -28,8 +28,8 @@ pub impl AccessListItemImpl of AccessListItemTrait { let AccessListItem { ethereum_address, mut storage_keys } = *self; let mut storage_keys_arr = array![]; - for storage_key in storage_keys { // deserialize u256 kullan TODO - storage_keys_arr.append(RLPItem::String(deserialize_bytes((*storage_key).try_into().unwrap(), 32))); // TODO deserialize 32 bytes olmaz 16 low high al + for storage_key in storage_keys { + storage_keys_arr.append(RLPItem::String(deserialize_u256_with_zeroes(*storage_key))); }; let addr = RLPItem::String(deserialize_bytes(ethereum_address.into(), 20)); diff --git a/tests/accounts_tests.cairo b/tests/accounts_tests.cairo index 19917dd..47157df 100644 --- a/tests/accounts_tests.cairo +++ b/tests/accounts_tests.cairo @@ -1,10 +1,11 @@ use snforge_std::{start_cheat_signature_global, stop_cheat_signature_global, start_cheat_nonce_global, stop_cheat_nonce_global, start_cheat_caller_address, stop_cheat_caller_address}; use rosettacontracts::accounts::utils::{RosettanetCall, generate_tx_hash}; +use rosettacontracts::utils::transaction::eip2930::{AccessListItem}; use rosettacontracts::accounts::base::{IRosettaAccountDispatcherTrait}; use rosettacontracts::mocks::erc20::{IMockERC20DispatcherTrait}; use starknet::{EthAddress}; -use rosettacontracts_integrationtest::test_utils::{eth_account, deploy_account_from_rosettanet, deploy_funded_account_from_rosettanet, deploy_account_from_existing_rosettanet}; +use rosettacontracts_integrationtest::test_utils::{eth_account, deploy_account_from_rosettanet, deploy_funded_account_from_rosettanet, deploy_account_from_existing_rosettanet, manipulate_rosettanet_registry, deploy_erc20}; // TODO: test deploying account from its own #[test] @@ -76,6 +77,7 @@ fn test_transaction_validation_value_transfer_only() { gas_limit: 21000, value: 1, calldata: array![].span(), + access_list: array![].span(), directives: array![].span(), target_function: array![].span() }; @@ -110,6 +112,7 @@ fn test_transaction_validation_calldata() { gas_limit: 45439, value: 0, calldata: array![0xa9059cbb, 0xb756b1bc042fa70d85ee84eab646a3b438a285ee, 0xf4240, 0x0].span(), + access_list: array![].span(), directives: array![0x2,0x1,0x0].span(), target_function: array![0x7472616E7366657228616464726573732C75696E7432353629].span() // transfer(address,uint256) }; @@ -144,6 +147,7 @@ fn test_transaction_validation_calldata_wrong_target_function() { gas_limit: 45439, value: 0, calldata: array![0xa9059cbb, 0xb756b1bc042fa70d85ee84eab646a3b438a285ee, 0xf4240, 0x0].span(), + access_list: array![].span(), directives: array![0x2,0x1,0x0].span(), target_function: array![0x17228616464726573732C75696E7432353619].span() // random hex }; @@ -172,6 +176,45 @@ fn test_transaction_validation_calldata_and_value_transfer() { // No execution just validate } +#[test] +fn test_validation_with_access_list() { + let eth_address: EthAddress = 0xE4306a06B19Fdc04FDf98cF3c00472f29254c0e1.try_into().unwrap(); + let target: EthAddress = 0xC7f5D5D3725f36CF36477B84010EB8DdE42D3636.try_into().unwrap(); + let access_list_item = AccessListItem { + ethereum_address: 0x5703ff58bB0CA34F870a8bC18dDd541f29375978.try_into().unwrap(), + storage_keys: array![0_u256, 1_u256].span() + }; + + let tx = RosettanetCall { + to: target, // we dont need to deploy account, we only check validation here + nonce: 87, + max_priority_fee_per_gas: 1638611, + max_fee_per_gas: 16357352599, + gas_limit: 210000, + value: 0, + calldata: array![0xf4acc7b5].span(), // sends 1000000 tokens + access_list: array![access_list_item].span(), + directives: array![].span(), + target_function: array![0x63616C6C43616C63756C61746F722829].span() // COMPLETE + }; + + let signature = array![0xc7ac6350bd17348d16f37c3e16e32f38, 0x4f3595825b9a4f9b3bc433a373aba603, 0x309f20124684d93997be0ebaecec49c0, 0x6a79d47f800e637b21026ba1591cee5b, 0x1b, 0x0, 0x0]; + let unsigned_tx_hash: u256 = 0xdd014b10515a451a59e9f92a9bbfa7ce01cc208856b5862d38f88d17ee4cf3d8; + + let generated_tx_hash: u256 = generate_tx_hash(tx); + assert_eq!(generated_tx_hash, unsigned_tx_hash); + + let (_, account) = deploy_account_from_rosettanet(eth_address); + + start_cheat_nonce_global(tx.nonce.into()); + start_cheat_signature_global(signature.span()); + let validation = account.__validate__(tx); + stop_cheat_signature_global(); + stop_cheat_nonce_global(); + + assert_eq!(validation, starknet::VALIDATED); +} + // Execution tests #[test] @@ -186,6 +229,7 @@ fn test_execute_value_transfer_to_non_registered() { gas_limit: 21000, value: 1, calldata: array![].span(), + access_list: array![].span(), directives: array![].span(), target_function: array![].span() }; @@ -218,6 +262,7 @@ fn test_execute_value_transfer_wrong_value_on_sig() { gas_limit: 21000, value: 1, calldata: array![].span(), + access_list: array![].span(), directives: array![].span(), target_function: array![].span() }; @@ -252,6 +297,7 @@ fn test_execute_value_transfer_not_enough_balance() { gas_limit: 21000, value: 1, calldata: array![].span(), + access_list: array![].span(), directives: array![].span(), target_function: array![].span() }; @@ -285,6 +331,7 @@ fn test_execute_value_transfer() { gas_limit: 21000, value: 1, calldata: array![].span(), + access_list: array![].span(), directives: array![].span(), target_function: array![].span() }; @@ -309,23 +356,137 @@ fn test_execute_value_transfer() { } #[test] -fn test_execute_erc20_transfer() { +#[should_panic(expected:'calldata address not registered')] +fn test_execute_erc20_transfer_receiver_not_registered() { // Example usdc transfer let eth_address: EthAddress = 0xE4306a06B19Fdc04FDf98cF3c00472f29254c0e1.try_into().unwrap(); + let erc20_eth: EthAddress = 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238.try_into().unwrap(); let tx = RosettanetCall { - to: 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238.try_into().unwrap(), // we dont need to deploy account, we only check validation here + to: erc20_eth, // we dont need to deploy account, we only check validation here nonce: 77, max_priority_fee_per_gas: 1638611, max_fee_per_gas: 18610805637, gas_limit: 45439, value: 0, calldata: array![0xa9059cbb, 0xb756b1bc042fa70d85ee84eab646a3b438a285ee, 0xf4240, 0x0].span(), + access_list: array![].span(), directives: array![0x2,0x1,0x0].span(), target_function: array![0x7472616E7366657228616464726573732C75696E7432353629].span() // transfer(address,uint256) }; let signature = array![0x6ddb2d56bf6b847af890501e1a44bf19, 0xcc8d431460ddb8f3a228d1cdfe069be1, 0xdbeff1d03deae8859e16491d3c7d4b89, 0x62b4b646ff3c09068d04eb98eec04413, 0x1b, 0x0, 0x0]; - let (rosettanet, account, strk) = deploy_funded_account_from_rosettanet(eth_address); - // Complete this one + let (rosettanet, account, _) = deploy_funded_account_from_rosettanet(eth_address); + + let erc20 = deploy_erc20(); + + manipulate_rosettanet_registry(rosettanet.contract_address, erc20.contract_address, erc20_eth); + + start_cheat_nonce_global(tx.nonce.into()); + start_cheat_signature_global(signature.span()); + start_cheat_caller_address(account.contract_address, starknet::contract_address_const::<0>()); + account.__execute__(tx); + stop_cheat_caller_address(account.contract_address); + stop_cheat_signature_global(); + stop_cheat_nonce_global(); +} + +#[test] +fn test_execute_erc20_transfer() { + // Example usdc transfer + let eth_address: EthAddress = 0xE4306a06B19Fdc04FDf98cF3c00472f29254c0e1.try_into().unwrap(); + let erc20_eth: EthAddress = 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238.try_into().unwrap(); + let tx = RosettanetCall { + to: erc20_eth, // we dont need to deploy account, we only check validation here + nonce: 77, + max_priority_fee_per_gas: 1638611, + max_fee_per_gas: 18610805637, + gas_limit: 45439, + value: 0, + calldata: array![0xa9059cbb, 0xb756b1bc042fa70d85ee84eab646a3b438a285ee, 0xf4240, 0x0].span(), // sends 1000000 tokens + access_list: array![].span(), + directives: array![0x2,0x1,0x0].span(), + target_function: array![0x7472616E7366657228616464726573732C75696E7432353629].span() // transfer(address,uint256) + }; + + let signature = array![0x6ddb2d56bf6b847af890501e1a44bf19, 0xcc8d431460ddb8f3a228d1cdfe069be1, 0xdbeff1d03deae8859e16491d3c7d4b89, 0x62b4b646ff3c09068d04eb98eec04413, 0x1b, 0x0, 0x0]; + let (rosettanet, account, _) = deploy_funded_account_from_rosettanet(eth_address); + let receiver = deploy_account_from_existing_rosettanet(0xb756b1bc042fa70d85ee84eab646a3b438a285ee.try_into().unwrap(), rosettanet.contract_address); + + let erc20 = deploy_erc20(); + + erc20.mint(account.contract_address ,1500000); // Fund account + + manipulate_rosettanet_registry(rosettanet.contract_address, erc20.contract_address, erc20_eth); + + start_cheat_nonce_global(tx.nonce.into()); + start_cheat_signature_global(signature.span()); + start_cheat_caller_address(account.contract_address, starknet::contract_address_const::<0>()); + let execution = account.__execute__(tx); + stop_cheat_caller_address(account.contract_address); + stop_cheat_signature_global(); + stop_cheat_nonce_global(); + + assert_eq!(erc20.balance_of(receiver.contract_address), 1000000); + assert_eq!(erc20.balance_of(account.contract_address), 500000); + assert_eq!(execution, array![array![0x1].span()]); // erc20 transfer returns true +} + +#[test] +#[should_panic(expected:'ERC20: insufficient balance')] +fn test_execute_erc20_transfer_exceeds_balance() { + // Example usdc transfer + let eth_address: EthAddress = 0xE4306a06B19Fdc04FDf98cF3c00472f29254c0e1.try_into().unwrap(); + let erc20_eth: EthAddress = 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238.try_into().unwrap(); + let tx = RosettanetCall { + to: erc20_eth, // we dont need to deploy account, we only check validation here + nonce: 77, + max_priority_fee_per_gas: 1638611, + max_fee_per_gas: 18610805637, + gas_limit: 45439, + value: 0, + calldata: array![0xa9059cbb, 0xb756b1bc042fa70d85ee84eab646a3b438a285ee, 0xf4240, 0x0].span(), // sends 1000000 tokens + access_list: array![].span(), + directives: array![0x2,0x1,0x0].span(), + target_function: array![0x7472616E7366657228616464726573732C75696E7432353629].span() // transfer(address,uint256) + }; + + let signature = array![0x6ddb2d56bf6b847af890501e1a44bf19, 0xcc8d431460ddb8f3a228d1cdfe069be1, 0xdbeff1d03deae8859e16491d3c7d4b89, 0x62b4b646ff3c09068d04eb98eec04413, 0x1b, 0x0, 0x0]; + let (rosettanet, account, _) = deploy_funded_account_from_rosettanet(eth_address); + deploy_account_from_existing_rosettanet(0xb756b1bc042fa70d85ee84eab646a3b438a285ee.try_into().unwrap(), rosettanet.contract_address); + + let erc20 = deploy_erc20(); + + erc20.mint(account.contract_address ,500000); // Fund account but not enough + + manipulate_rosettanet_registry(rosettanet.contract_address, erc20.contract_address, erc20_eth); + + start_cheat_nonce_global(tx.nonce.into()); + start_cheat_signature_global(signature.span()); + start_cheat_caller_address(account.contract_address, starknet::contract_address_const::<0>()); + account.__execute__(tx); + stop_cheat_caller_address(account.contract_address); + stop_cheat_signature_global(); + stop_cheat_nonce_global(); +} + +#[test] +fn test_execute_erc20_transfer_with_value() { + +} + +#[test] +fn test_execute_with_access_list() { + let target: EthAddress = 0xC7f5D5D3725f36CF36477B84010EB8DdE42D3636.try_into().unwrap(); + let tx = RosettanetCall { + to: target, // we dont need to deploy account, we only check validation here + nonce: 87, + max_priority_fee_per_gas: 1638611, + max_fee_per_gas: 16357352599, + gas_limit: 210000, + value: 0, + calldata: array![0xf4acc7b5].span(), // sends 1000000 tokens + access_list: array![].span(),// COMPLETE ACCESS LIST ITEM + directives: array![].span(), + target_function: array![0x7472616E7366657228616464726573732C75696E7432353629].span() // transfer(address,uint256) + }; } -// TODO: tests with calldata \ No newline at end of file diff --git a/tests/test_utils.cairo b/tests/test_utils.cairo index cf4b0fc..bf08cd7 100644 --- a/tests/test_utils.cairo +++ b/tests/test_utils.cairo @@ -1,11 +1,22 @@ use snforge_std::{declare, ContractClassTrait, DeclareResultTrait, start_cheat_caller_address, stop_cheat_caller_address}; use starknet::{ClassHash, ContractAddress, EthAddress}; +use core::pedersen::PedersenTrait; +use core::hash::{HashStateExTrait, HashStateTrait}; use rosettacontracts::rosettanet::{ IRosettanetDispatcher, IRosettanetDispatcherTrait }; use rosettacontracts::accounts::base::{IRosettaAccountDispatcher}; use rosettacontracts::mocks::erc20::{IMockERC20Dispatcher, IMockERC20DispatcherTrait}; +fn compute_hash_on_elements(data: Span) -> felt252 { + let mut state = PedersenTrait::new(0); + for elem in data { + state = state.update_with(*elem); + }; + + state.update_with(data.len()).finalize() +} + pub fn declare_erc20() -> ClassHash { let class = declare("MockERC20").unwrap().contract_class(); *class.class_hash @@ -84,4 +95,27 @@ pub fn deploy_funded_account_from_rosettanet(eth_address: EthAddress) -> (IRoset assert_eq!(strk.balance_of(account.contract_address), 1000000); (rosettanet, account, strk) +} + +// Forcely matches these addresses +pub fn manipulate_rosettanet_registry(rosettanet_contract: ContractAddress, sn_address: ContractAddress, eth_address: EthAddress) { + // Currently we use function in registry + // After alpha version, we have to remove that function. So this function also needs to be rewritten with store function in foundry + start_cheat_caller_address(rosettanet_contract, developer()); + IRosettanetDispatcher { contract_address: rosettanet_contract }.register_matched_addresses(sn_address, eth_address); + stop_cheat_caller_address(rosettanet_contract); + +} + +#[test] +fn test_storage_manipulation() { + let rosettanet = deploy_rosettanet(); + + let eth_address: EthAddress = 0x123.try_into().unwrap(); + let sn_address: ContractAddress = 0xFFF.try_into().unwrap(); + + manipulate_rosettanet_registry(rosettanet.contract_address, sn_address, eth_address); + + assert_eq!(rosettanet.get_starknet_address(eth_address), sn_address); + assert_eq!(rosettanet.get_ethereum_address(sn_address), eth_address); } \ No newline at end of file