From c517060654315419d4b5e4d04633282ccc4cad51 Mon Sep 17 00:00:00 2001 From: Mathieu <60658558+enitrat@users.noreply.github.com> Date: Tue, 21 Nov 2023 18:27:17 +0100 Subject: [PATCH] feat: compute contract address (#563) * rename is_deployed / exists * feat: compute_contract_address & rlp encode_sequence * tests: comput address * feat: compute contract address * feat: compute contract address * address pr review * address pr review --- .../contracts/src/kakarot_core/kakarot.cairo | 6 +- crates/evm/src/create_helpers.cairo | 50 +---- crates/evm/src/model.cairo | 4 +- crates/evm/src/model/account.cairo | 18 +- crates/evm/src/model/eoa.cairo | 2 +- .../evm/src/tests/test_create_helpers.cairo | 33 +-- .../test_system_operations.cairo | 2 +- crates/evm/src/tests/test_model.cairo | 47 ++-- crates/utils/src/address.cairo | 66 ++++++ crates/utils/src/helpers.cairo | 96 +++++++- crates/utils/src/lib.cairo | 5 +- crates/utils/src/rlp.cairo | 73 +++--- crates/utils/src/tests.cairo | 1 + crates/utils/src/tests/test_address.cairo | 58 +++++ crates/utils/src/tests/test_rlp.cairo | 207 +++++++++++++----- crates/utils/src/traits.cairo | 10 + 16 files changed, 487 insertions(+), 191 deletions(-) create mode 100644 crates/utils/src/address.cairo create mode 100644 crates/utils/src/tests/test_address.cairo diff --git a/crates/contracts/src/kakarot_core/kakarot.cairo b/crates/contracts/src/kakarot_core/kakarot.cairo index 0876f496a..ba78b458d 100644 --- a/crates/contracts/src/kakarot_core/kakarot.cairo +++ b/crates/contracts/src/kakarot_core/kakarot.cairo @@ -38,7 +38,8 @@ mod KakarotCore { }; use super::{INVOKE_ETH_CALL_FORBIDDEN}; use super::{StoredAccountType}; - use utils::helpers::{compute_starknet_address}; + use utils::helpers::{compute_starknet_address, EthAddressExTrait}; + use utils::rlp::RLPTrait; component!(path: ownable_component, storage: ownable, event: OwnableEvent); component!(path: upgradeable_component, storage: upgradeable, event: UpgradeableEvent); @@ -392,8 +393,7 @@ mod KakarotCore { return Result::Ok(execution_result); }, Option::None => { - let bytecode = data; - // TODO: compute_evm_address + // Deploy tx case. // HASH(RLP(deployer_address, deployer_nonce))[0..20] //TODO manually set target account type to CA in state panic_with_felt252('deploy tx flow unimplemented') diff --git a/crates/evm/src/create_helpers.cairo b/crates/evm/src/create_helpers.cairo index 9db799152..76bb6e28b 100644 --- a/crates/evm/src/create_helpers.cairo +++ b/crates/evm/src/create_helpers.cairo @@ -14,8 +14,9 @@ use evm::stack::StackTrait; use evm::state::StateTrait; use keccak::cairo_keccak; use starknet::{EthAddress, get_tx_info}; +use utils::address::{compute_contract_address, compute_create2_contract_address}; use utils::helpers::ArrayExtTrait; -use utils::helpers::{ResultExTrait, EthAddressExt, U256Trait, U8SpanExTrait}; +use utils::helpers::{ResultExTrait, EthAddressExTrait, U256Trait, U8SpanExTrait}; use utils::traits::{ BoolIntoNumeric, EthAddressIntoU256, U256TryIntoResult, SpanU8TryIntoResultEthAddress }; @@ -48,15 +49,16 @@ impl MachineCreateHelpersImpl of MachineCreateHelpers { // TODO(state): when the tx starts, // store get_tx_info().unbox().nonce inside the sender account nonce - let sender_nonce = get_tx_info().unbox().nonce; + // so that we can call self.nonce() instead of get_tx_info().unbox().nonce let to = match create_type { - CreateType::CreateOrDeployTx => self - .get_create_address(self.address().evm, sender_nonce)?, - CreateType::Create2 => self - .get_create2_address( - self.address().evm, salt: self.stack.pop()?, bytecode: bytecode.span() - )?, + CreateType::CreateOrDeployTx => { + let nonce = self.state.get_account(self.address().evm)?.nonce(); + compute_contract_address(self.address().evm, sender_nonce: nonce) + }, + CreateType::Create2 => compute_create2_contract_address( + self.address().evm, salt: self.stack.pop()?, bytecode: bytecode.span() + )?, }; Result::Ok(CreateArgs { to, value, bytecode: bytecode.span() }) @@ -98,7 +100,7 @@ impl MachineCreateHelpersImpl of MachineCreateHelpers { // - contract is already deployed at this location (type fetched from storage) // - Contract has been scheduled for deployment (type set in cache) // If the AccountType is unknown, then there's no collision. - if target_account.is_deployed() { + if target_account.exists() { return self.stack.push(0); }; @@ -172,34 +174,4 @@ impl MachineCreateHelpersImpl of MachineCreateHelpers { }, } } - - fn get_create_address( - ref self: Machine, sender_address: EthAddress, sender_nounce: felt252 - ) -> Result { - panic_with_felt252('get_create_address todo') - } - - - fn get_create2_address( - self: @Machine, sender_address: EthAddress, salt: u256, bytecode: Span - ) -> Result { - let hash = bytecode.compute_keccak256_hash().to_bytes(); - - let sender_address = sender_address.to_bytes(); - - let salt = salt.to_bytes(); - - let mut preimage: Array = array![]; - - preimage.concat(array![0xff].span()); - preimage.concat(sender_address); - preimage.concat(salt); - preimage.concat(hash); - - let address_hash = preimage.span().compute_keccak256_hash().to_bytes(); - - let address: EthAddress = address_hash.slice(12, 20).try_into_result()?; - - Result::Ok(address) - } } diff --git a/crates/evm/src/model.cairo b/crates/evm/src/model.cairo index f564d345e..989c9278a 100644 --- a/crates/evm/src/model.cairo +++ b/crates/evm/src/model.cairo @@ -31,9 +31,9 @@ struct Address { #[generate_trait] impl AddressImpl of AddressTrait { - fn is_registered(evm_address: EthAddress) -> bool { + fn is_deployed(self: EthAddress) -> bool { let mut kakarot_state = KakarotCore::unsafe_new_contract_state(); - let maybe_account = kakarot_state.address_registry(evm_address); + let maybe_account = kakarot_state.address_registry(self); match maybe_account { Option::Some(_) => true, Option::None => false diff --git a/crates/evm/src/model/account.cairo b/crates/evm/src/model/account.cairo index c8f3bf7e1..a4ae5b958 100644 --- a/crates/evm/src/model/account.cairo +++ b/crates/evm/src/model/account.cairo @@ -161,8 +161,8 @@ impl AccountImpl of AccountTrait { /// be registered already, and the nonce must not be 0 or the code must not /// be empty #[inline(always)] - fn should_deploy(self: @Account, is_registered: bool) -> bool { - if !is_registered && self.is_ca() && (*self.nonce != 0 || !(*self.code).is_empty()) { + fn should_deploy(self: @Account) -> bool { + if self.is_ca() && (*self.nonce != 0 || !(*self.code).is_empty()) { return true; }; false @@ -181,9 +181,9 @@ impl AccountImpl of AccountTrait { /// `Ok(())` if the commit was successful, otherwise an `EVMError`. fn commit(self: @Account) -> Result<(), EVMError> { // Case account exists and is already on chain - let is_registered = AddressTrait::is_registered(self.address().evm); + let is_deployed = self.address().evm.is_deployed(); - if is_registered { + if is_deployed { match self.account_type { AccountType::EOA(eoa) => { // no - op @@ -203,7 +203,7 @@ impl AccountImpl of AccountTrait { }, AccountType::Unknown => { Result::Ok(()) } } - } else if self.should_deploy(is_registered) { + } else if self.should_deploy() { //Case new account // If SELFDESTRUCT, just do nothing if (*self.selfdestruct == true) { @@ -231,19 +231,19 @@ impl AccountImpl of AccountTrait { false } - /// Returns whether an accound is deployed at the given address. + /// Returns whether an accound is exists at the given address. /// /// Based on the state of the account in the cache - the account can - /// not be commited on-chain, but already be deployed in the KakarotState. + /// not be deployed on-chain yet, but already exist in the KakarotState. /// # Arguments /// /// * `address` - The Ethereum address to look up. /// /// # Returns /// - /// `true` if an account is deployed at this address, `false` otherwise. + /// `true` if an account exists at this address, `false` otherwise. #[inline(always)] - fn is_deployed(self: @Account) -> bool { + fn exists(self: @Account) -> bool { let is_known = *self.account_type != AccountType::Unknown; //TODO(account) verify whether is_known is a sufficient condition diff --git a/crates/evm/src/model/eoa.cairo b/crates/evm/src/model/eoa.cairo index 5ef0f9852..3e7519915 100644 --- a/crates/evm/src/model/eoa.cairo +++ b/crates/evm/src/model/eoa.cairo @@ -18,7 +18,7 @@ impl EOAImpl of EOATrait { /// * `evm_address` - The EVM address of the EOA to deploy. fn deploy(evm_address: EthAddress) -> Result { // Unlike CAs, there is not check for the existence of an EOA prealably to calling `EOATrait::deploy` - therefore, we need to check that there is no collision. - let mut is_deployed = AddressTrait::is_registered(evm_address); + let mut is_deployed = evm_address.is_deployed(); if is_deployed { return Result::Err(EVMError::DeployError(EOA_EXISTS)); } diff --git a/crates/evm/src/tests/test_create_helpers.cairo b/crates/evm/src/tests/test_create_helpers.cairo index ce814dc45..8c4fa4cdd 100644 --- a/crates/evm/src/tests/test_create_helpers.cairo +++ b/crates/evm/src/tests/test_create_helpers.cairo @@ -2,36 +2,5 @@ use contracts::tests::test_data::counter_evm_bytecode; use evm::create_helpers::MachineCreateHelpers; use evm::tests::test_utils::setup_machine; use starknet::EthAddress; +use utils::address::{compute_contract_address, compute_create2_contract_address}; -#[test] -#[available_gas(3_000_000_000_000)] -fn test_get_create2_address() { - let machine = setup_machine(); - let bytecode = counter_evm_bytecode(); - let salt = 0xbeef; - let from: EthAddress = 0xF39FD6E51AAD88F6F4CE6AB8827279CFFFB92266 - .try_into() - .expect('Wrong Eth address'); - - let address = machine - .get_create2_address(from, salt, bytecode) - .expect('get_create2_address fail'); - - // TODO - // add SNJS script for: - // import { getContractAddress } from 'viem' - // const address = getContractAddress({ - // bytecode: '0x6080604052348015600f57600080fd5b506004361060465760003560e01c806306661abd14604b578063371303c01460655780636d4ce63c14606d578063b3bcfa82146074575b600080fd5b605360005481565b60405190815260200160405180910390f35b606b607a565b005b6000546053565b606b6091565b6001600080828254608a919060b7565b9091555050565b6001600080828254608a919060cd565b634e487b7160e01b600052601160045260246000fd5b8082018082111560c75760c760a1565b92915050565b8181038181111560c75760c760a156fea2646970667358221220f379b9089b70e8e00da8545f9a86f648441fdf27ece9ade2c71653b12fb80c7964736f6c63430008120033', - // from: '0xF39FD6E51AAD88F6F4CE6AB8827279CFFFB92266', - // opcode: 'CREATE2', - // salt: '0xbeef', - // }); - - // console.log(address) - assert( - address == 0xaE6b9c5FD4C9037511100FFb6813D0f607a49f3A - .try_into() - .expect('Wrong Eth Address'), - 'wrong create2 address' - ); -} diff --git a/crates/evm/src/tests/test_instructions/test_system_operations.cairo b/crates/evm/src/tests/test_instructions/test_system_operations.cairo index 6d33c882b..ace0aa857 100644 --- a/crates/evm/src/tests/test_instructions/test_system_operations.cairo +++ b/crates/evm/src/tests/test_instructions/test_system_operations.cairo @@ -172,7 +172,7 @@ fn test_exec_call() { } #[test] -#[available_gas(50000000)] +#[available_gas(70000000)] fn test_exec_call_no_return() { // Given let mut interpreter = EVMInterpreterTrait::new(); diff --git a/crates/evm/src/tests/test_model.cairo b/crates/evm/src/tests/test_model.cairo index c08c8cd5c..b9131e2cd 100644 --- a/crates/evm/src/tests/test_model.cairo +++ b/crates/evm/src/tests/test_model.cairo @@ -10,7 +10,7 @@ use starknet::testing::set_contract_address; #[test] #[available_gas(20000000)] -fn test_is_registered_eoa_exists() { +fn test_is_deployed_eoa_exists() { // Given let (native_token, kakarot_core) = setup_contracts_for_testing(); let eoa_address = EOATrait::deploy(evm_address()).expect('failed deploy contract account',); @@ -19,39 +19,39 @@ fn test_is_registered_eoa_exists() { // When set_contract_address(kakarot_core.contract_address); - let is_registered = AddressTrait::is_registered(evm_address()); + let is_deployed = evm_address().is_deployed(); // Then - assert(is_registered, 'account should be deployed'); + assert(is_deployed, 'account should be deployed'); } #[test] #[available_gas(20000000)] -fn test_is_registered_ca_exists() { +fn test_is_deployed_ca_exists() { // Given let (native_token, kakarot_core) = setup_contracts_for_testing(); ContractAccountTrait::deploy(evm_address(), array![].span()) .expect('failed deploy contract account',); // When - let is_registered = AddressTrait::is_registered(evm_address()); + let is_deployed = evm_address().is_deployed(); // Then - assert(is_registered, 'account should be deployed'); + assert(is_deployed, 'account should be deployed'); } #[test] #[available_gas(20000000)] -fn test_is_registered_undeployed() { +fn test_is_deployed_undeployed() { // Given let (native_token, kakarot_core) = setup_contracts_for_testing(); // When set_contract_address(kakarot_core.contract_address); - let is_registered = AddressTrait::is_registered(evm_address()); + let is_deployed = evm_address().is_deployed(); // Then - assert(!is_registered, 'account should be undeployed'); + assert(!is_deployed, 'account should be undeployed'); } @@ -94,7 +94,7 @@ fn test_address_balance_eoa() { #[test] #[available_gas(5000000)] -fn test_account_is_deployed_eoa() { +fn test_account_exists_eoa() { // Given let (native_token, kakarot_core) = setup_contracts_for_testing(); let mut eoa_address = EOATrait::deploy(evm_address()).expect('failed deploy eoa',); @@ -103,13 +103,13 @@ fn test_account_is_deployed_eoa() { let account = AccountTrait::fetch(evm_address()).unwrap().unwrap(); // Then - assert(account.is_deployed() == true, 'account should be deployed'); + assert(account.exists() == true, 'account should be deployed'); } #[test] #[available_gas(5000000)] -fn test_account_is_deployed_contract_account() { +fn test_account_exists_contract_account() { // Given let (native_token, kakarot_core) = setup_contracts_for_testing(); let mut ca_address = ContractAccountTrait::deploy(evm_address(), array![].span()) @@ -119,13 +119,13 @@ fn test_account_is_deployed_contract_account() { let account = AccountTrait::fetch(evm_address()).unwrap().unwrap(); // Then - assert(account.is_deployed() == true, 'account should be deployed'); + assert(account.exists() == true, 'account should be deployed'); } #[test] #[available_gas(5000000)] -fn test_account_is_deployed_undeployed() { +fn test_account_exists_undeployed() { // Given let (native_token, kakarot_core) = setup_contracts_for_testing(); @@ -133,7 +133,24 @@ fn test_account_is_deployed_undeployed() { let account = AccountTrait::fetch_or_create(evm_address()).unwrap(); // Then - assert(account.is_deployed() == false, 'account should be deployed'); + assert(account.exists() == false, 'account should be deployed'); +} + +#[test] +#[available_gas(5000000)] +fn test_account_exists_account_to_deploy() { + // Given + let (native_token, kakarot_core) = setup_contracts_for_testing(); + + // When + let mut account = AccountTrait::fetch_or_create(evm_address()).unwrap(); + // Mock account as an existing contract account in the cached state. + account.account_type = AccountType::ContractAccount; + account.nonce = 1; + account.code = array![0x1].span(); + + // Then + assert(account.exists() == true, 'account should exist'); } diff --git a/crates/utils/src/address.cairo b/crates/utils/src/address.cairo new file mode 100644 index 000000000..84a0bcfd7 --- /dev/null +++ b/crates/utils/src/address.cairo @@ -0,0 +1,66 @@ +use core::array::ArrayTrait; +use core::traits::TryInto; + +use debug::PrintTrait; +use evm::errors::EVMError; +use starknet::EthAddress; +use utils::errors::RLPErrorTrait; +use utils::helpers::{U8SpanExTrait, U64Trait, U256Trait, EthAddressExTrait, ArrayExtTrait}; +use utils::math::WrappingBitshift; +use utils::rlp::{RLPTrait, RLPItem}; +use utils::traits::{TryIntoResult, U256TryIntoEthAddress}; +/// Computes the address of the new account that needs to be created. +/// +/// # Arguments +/// +/// * `sender_address`: The address of the account that wants to create the new account. +/// * `sender_nonce`: The transaction count of the account that wants to create the new account. +/// +/// # Returns +/// +/// The computed address of the new account. +fn compute_contract_address(sender_address: EthAddress, sender_nonce: u64) -> EthAddress { + let mut sender_address: RLPItem = RLPItem::String(sender_address.to_bytes().span()); + let sender_nonce: RLPItem = RLPItem::String(sender_nonce.to_bytes().span()); + let computed_address = U8SpanExTrait::compute_keccak256_hash( + RLPTrait::encode_sequence(array![sender_address, sender_nonce].span()) + ); + let canonical_address = computed_address & 0xffffffffffffffffffffffffffffffffffffffff; + canonical_address.try_into().unwrap() +} + + +/// Computes the address of the new account that needs to be created, which is +/// based on the sender address, salt, and the call data. +/// +/// # Parameters +/// +/// * `sender_address`: The address of the account that wants to create the new account. +/// * `salt`: Address generation salt. +/// * `bytecode`: The code of the new account to be created. +/// +/// # Returns +/// +/// The computed address of the new account. +fn compute_create2_contract_address( + sender_address: EthAddress, salt: u256, bytecode: Span +) -> Result { + let hash = bytecode.compute_keccak256_hash().to_bytes(); + + let sender_address = sender_address.to_bytes().span(); + + let salt = salt.to_bytes(); + + let mut preimage: Array = array![]; + + preimage.append_span(array![0xff].span()); + preimage.append_span(sender_address); + preimage.append_span(salt); + preimage.append_span(hash); + + let address_hash = preimage.span().compute_keccak256_hash().to_bytes(); + + let address: EthAddress = address_hash.slice(12, 20).try_into_result()?; + + Result::Ok(address) +} diff --git a/crates/utils/src/helpers.cairo b/crates/utils/src/helpers.cairo index ed09f5ea5..74f4efa34 100644 --- a/crates/utils/src/helpers.cairo +++ b/crates/utils/src/helpers.cairo @@ -15,7 +15,8 @@ use utils::constants::{ use utils::constants::{CONTRACT_ADDRESS_PREFIX, MAX_ADDRESS}; use utils::math::{Bitshift, WrappingBitshift}; use utils::num::{Zero, One, SizeOf}; -use utils::traits::{U256TryIntoContractAddress, EthAddressIntoU256}; +use utils::traits::TryIntoResult; +use utils::traits::{U256TryIntoContractAddress, EthAddressIntoU256, U256TryIntoEthAddress}; /// Ceils a number of bits to the next word (32 bytes) /// /// # Arguments @@ -457,7 +458,7 @@ impl ArrayExtension> of ArrayExtTrait { Option::Some(elem) => self.append(*elem), Option::None => { break; } }; - } + }; } /// Reverses an array @@ -655,6 +656,65 @@ impl U32Impl of U32Trait { } } + +#[generate_trait] +impl U64Impl of U64Trait { + /// Unpacks a u64 into an array of bytes + /// # Arguments + /// * `self` a `u64` value. + /// # Returns + /// * The bytes array representation of the value. + fn to_bytes(mut self: u64) -> Array { + let bytes_used: u64 = self.bytes_used().into(); + let mut bytes: Array = Default::default(); + let mut i = 0; + loop { + if i == bytes_used { + break (); + } + let val = self.shr(8 * (bytes_used.try_into().unwrap() - i - 1)); + bytes.append((val & 0xFF).try_into().unwrap()); + i += 1; + }; + + bytes + } + + /// Returns the number of bytes used to represent a `u64` value. + /// # Arguments + /// * `self` - The value to check. + /// # Returns + /// The number of bytes used to represent the value. + fn bytes_used(self: u64) -> u8 { + if self < 0x10000 { // 256^2 + if self < 0x100 { // 256^1 + return if self == 0 { + 0 + } else { + 1 + }; + } else { + return if self < 0x1000 { + 2 + } else { + 3 + }; + } + } else { + if self < 0x1000000 { // 256^6 + return if self < 0x100000 { + 4 + } else { + 5 + }; + } else { + return 6; + } + } + } +} + + #[generate_trait] impl U128Impl of U128Trait { /// Packs 16 bytes into a u128 @@ -930,8 +990,8 @@ fn compute_starknet_address( #[generate_trait] -impl EthAddressExtTrait of EthAddressExt { - fn to_bytes(self: EthAddress) -> Span { +impl EthAddressExImpl of EthAddressExTrait { + fn to_bytes(self: EthAddress) -> Array { let bytes_used: u256 = 20; let value: u256 = self.into(); let mut bytes: Array = Default::default(); @@ -945,6 +1005,32 @@ impl EthAddressExtTrait of EthAddressExt { i += 1; }; - bytes.span() + bytes + } + + /// Packs 20 bytes into a EthAddress + /// # Arguments + /// * `input` a Span of len == 20 + /// # Returns + /// * Option::Some(EthAddress) if the operation succeeds + /// * Option::None otherwise + fn from_bytes(input: Span) -> EthAddress { + let len = input.len(); + if len != 20 { + panic_with_felt252('EthAddress::from_bytes != 20b') + } + let offset: u32 = len - 1; + let mut result: u256 = 0; + let mut i: u32 = 0; + loop { + if i == len { + break (); + } + let byte: u256 = (*input.at(i)).into(); + result += byte.shl((8 * (offset - i)).into()); + + i += 1; + }; + result.try_into().unwrap() } } diff --git a/crates/utils/src/lib.cairo b/crates/utils/src/lib.cairo index 94d55f342..981eff136 100644 --- a/crates/utils/src/lib.cairo +++ b/crates/utils/src/lib.cairo @@ -1,8 +1,9 @@ +//! Utilities for kakarot standard library. + +mod address; mod constants; mod errors; mod eth_transaction; -//! Utilities for kakarot standard library. - mod helpers; mod i256; mod math; diff --git a/crates/utils/src/rlp.cairo b/crates/utils/src/rlp.cairo index d69dfaaa0..422797d32 100644 --- a/crates/utils/src/rlp.cairo +++ b/crates/utils/src/rlp.cairo @@ -64,63 +64,76 @@ impl RLPImpl of RLPTrait { } } - /// RLP encodes multiple RLPItem + /// RLP encodes a sequence of RLPItem /// # Arguments /// * `input` - Span of RLPItem to encode /// # Returns /// * `ByteArray - RLP encoded ByteArray /// # Errors /// * RLPError::RlpEmptyInput - if the input is empty - fn encode(mut input: Span) -> Result, RLPError> { - if input.len() == 0 { - return Result::Err(RLPError::EmptyInput(RLP_EMPTY_INPUT)); - } - - let mut output: Array = Default::default(); - let item = input.pop_front().unwrap(); - - match item { - RLPItem::String(string) => { output.concat(RLPTrait::encode_string(*string)?); }, - RLPItem::List(list) => { panic_with_felt252('List encoding unimplemented') } - } - - if input.len() > 0 { - output.concat(RLPTrait::encode(input)?); + fn encode_sequence(mut input: Span) -> Span { + let mut joined_encodings: Array = Default::default(); + loop { + match input.pop_front() { + Option::Some(item) => { + match item { + RLPItem::String(string) => { + joined_encodings.append_span(RLPTrait::encode_string(*string)); + }, + RLPItem::List(list) => { panic_with_felt252('List encoding unimplemented') } + } + }, + Option::None => { break; } + } + }; + let len_joined_encodings = joined_encodings.len(); + if len_joined_encodings < 0x38 { + let mut result: Array = array![0xC0 + len_joined_encodings.try_into().unwrap()]; + result.append_span(joined_encodings.span()); + return result.span(); + } else { + // Actual implementation of long list encoding is commented out + // as we should never reach this point in the current implementation + // let bytes_count_len_joined_encodings = len_joined_encodings.bytes_used(); + // let len_joined_encodings: Span = len_joined_encodings.to_bytes(); + // let mut result = array![0xF7 + bytes_count_len_joined_encodings]; + // result.append_span(len_joined_encodings); + // result.append_span(joined_encodings.span()); + // return result.span(); + return panic_with_felt252('Shouldnt encode long sequence'); } - - Result::Ok(output.span()) } - /// RLP encodes a ByteArray, which is the underlying type used to represent + /// RLP encodes a Span, which is the underlying type used to represent /// string data in Cairo. Since RLP encoding is only used for eth_address /// computation by calculating the RLP::encode(deployer_address, deployer_nonce) - /// and then hash it, the input is a ByteArray and not a Span + /// and then hash it, the input is a Span /// # Arguments /// * `input` - ByteArray to encode /// # Returns /// * `ByteArray - RLP encoded ByteArray /// # Errors /// * RLPError::RlpEmptyInput - if the input is empty - fn encode_string(input: Span) -> Result, RLPError> { + fn encode_string(input: Span) -> Span { let len = input.len(); if len == 0 { - return Result::Ok(array![0x80].span()); + return array![0x80].span(); } else if len == 1 && *input[0] < 0x80 { - return Result::Ok(input); + return input; } else if len < 56 { let mut encoding: Array = Default::default(); encoding.append(0x80 + len.try_into().unwrap()); - encoding.concat(input); - return Result::Ok(encoding.span()); + encoding.append_span(input); + return encoding.span(); } else { let mut encoding: Array = Default::default(); let len_as_bytes = len.to_bytes(); let len_bytes_count = len_as_bytes.len(); let prefix = 0xb7 + len_bytes_count.try_into().unwrap(); encoding.append(prefix); - encoding.concat(len_as_bytes); - encoding.concat(input); - return Result::Ok(encoding.span()); + encoding.append_span(len_as_bytes); + encoding.append_span(input); + return encoding.span(); } } @@ -165,7 +178,9 @@ impl RLPImpl of RLPTrait { let total_item_len = len + offset; if total_item_len < input_len { output - .concat(RLPTrait::decode(input.slice(total_item_len, input_len - total_item_len))?); + .append_span( + RLPTrait::decode(input.slice(total_item_len, input_len - total_item_len))? + ); } Result::Ok(output.span()) diff --git a/crates/utils/src/tests.cairo b/crates/utils/src/tests.cairo index f14c2a8c8..5c7b83980 100644 --- a/crates/utils/src/tests.cairo +++ b/crates/utils/src/tests.cairo @@ -1,3 +1,4 @@ +mod test_address; mod test_data; mod test_eth_transaction; mod test_helpers; diff --git a/crates/utils/src/tests/test_address.cairo b/crates/utils/src/tests/test_address.cairo new file mode 100644 index 000000000..600f92b78 --- /dev/null +++ b/crates/utils/src/tests/test_address.cairo @@ -0,0 +1,58 @@ +use contracts::tests::test_data::counter_evm_bytecode; +use starknet::EthAddress; +use utils::address::{compute_contract_address, compute_create2_contract_address}; + +#[test] +#[available_gas(3_000_000_000_000)] +fn test_compute_create2_contract_address() { + let bytecode = counter_evm_bytecode(); + let salt = 0xbeef; + let from: EthAddress = 0xF39FD6E51AAD88F6F4CE6AB8827279CFFFB92266 + .try_into() + .expect('Wrong Eth address'); + + let address = compute_create2_contract_address(from, salt, bytecode) + .expect('create2_contract_address fail'); + + // TODO + // add SNJS script for: + // import { getContractAddress } from 'viem' + // const address = getContractAddress({ + // bytecode: '0x6080604052348015600f57600080fd5b506004361060465760003560e01c806306661abd14604b578063371303c01460655780636d4ce63c14606d578063b3bcfa82146074575b600080fd5b605360005481565b60405190815260200160405180910390f35b606b607a565b005b6000546053565b606b6091565b6001600080828254608a919060b7565b9091555050565b6001600080828254608a919060cd565b634e487b7160e01b600052601160045260246000fd5b8082018082111560c75760c760a1565b92915050565b8181038181111560c75760c760a156fea2646970667358221220f379b9089b70e8e00da8545f9a86f648441fdf27ece9ade2c71653b12fb80c7964736f6c63430008120033', + // from: '0xF39FD6E51AAD88F6F4CE6AB8827279CFFFB92266', + // opcode: 'CREATE2', + // salt: '0xbeef', + // }); + + // console.log(address) + assert( + address == 0xaE6b9c5FD4C9037511100FFb6813D0f607a49f3A + .try_into() + .expect('Wrong Eth Address'), + 'wrong create2 address' + ); +} + +#[test] +#[available_gas(3_000_000_000_000)] +fn test_compute_contract_address() { + let nonce = 420; + let from: EthAddress = 0xF39FD6E51AAD88F6F4CE6AB8827279CFFFB92266 + .try_into() + .expect('Wrong Eth address'); + + let address = compute_contract_address(from, nonce); + + // TODO + // add SNJS script for: + // import { getContractAddress } from "viem"; + // const address = getContractAddress({ + // opcode: "CREATE", + // from: "0xF39FD6E51AAD88F6F4CE6AB8827279CFFFB92266", + // nonce: BigInt(420), + // }); + + // console.log(address); + + assert(address.into() == 0x40A633EeF249F21D95C8803b7144f19AAfeEF7ae, 'wrong create address'); +} diff --git a/crates/utils/src/tests/test_rlp.cairo b/crates/utils/src/tests/test_rlp.cairo index 1b0f82f1c..020fa1f7a 100644 --- a/crates/utils/src/tests/test_rlp.cairo +++ b/crates/utils/src/tests/test_rlp.cairo @@ -91,39 +91,6 @@ fn test_rlp_empty() { assert(res.unwrap_err() == RLPError::EmptyInput(RLP_EMPTY_INPUT), 'err != EmptyInput'); } -#[test] -#[available_gas(20000000)] -fn test_rlp_encode_empty_input_should_fail() { - let mut input = array![]; - - let res = RLPTrait::encode(input.span()); - - assert(res.is_err(), 'should return an error'); - assert(res.unwrap_err() == RLPError::EmptyInput(RLP_EMPTY_INPUT), 'err != EmptyInput'); -} - -#[test] -#[available_gas(20000000)] -fn test_rlp_encode_default_value() { - let mut input = RLPItem::String(array![].span()); - - let res = RLPTrait::encode(array![input].span()).unwrap(); - - assert(res.len() == 1, 'wrong len'); - assert(*res[0] == 0x80, 'wrong encoded value'); -} - - -#[test] -#[available_gas(20000000)] -fn test_rlp_encode_string_empty_input() { - let mut input: Array = Default::default(); - - let res = RLPTrait::encode_string(input.span()).unwrap(); - - assert(res.len() == 1, 'wrong len'); - assert(*res[0] == 0x80, 'wrong encoded value'); -} #[test] #[available_gas(20000000)] @@ -131,7 +98,7 @@ fn test_rlp_encode_string_single_byte_lt_0x80() { let mut input: Array = Default::default(); input.append(0x40); - let res = RLPTrait::encode_string(input.span()).unwrap(); + let res = RLPTrait::encode_string(input.span()); assert(res.len() == 1, 'wrong len'); assert(*res[0] == 0x40, 'wrong encoded value'); @@ -143,7 +110,7 @@ fn test_rlp_encode_string_single_byte_ge_0x80() { let mut input: Array = Default::default(); input.append(0x80); - let res = RLPTrait::encode_string(input.span()).unwrap(); + let res = RLPTrait::encode_string(input.span()); assert(res.len() == 2, 'wrong len'); assert(*res[0] == 0x81, 'wrong prefix'); @@ -157,7 +124,7 @@ fn test_rlp_encode_string_length_between_2_and_55() { input.append(0x40); input.append(0x50); - let res = RLPTrait::encode_string(input.span()).unwrap(); + let res = RLPTrait::encode_string(input.span()); assert(res.len() == 3, 'wrong len'); assert(*res[0] == 0x82, 'wrong prefix'); @@ -178,7 +145,7 @@ fn test_rlp_encode_string_length_exactly_56() { i += 1; }; - let res = RLPTrait::encode_string(input.span()).unwrap(); + let res = RLPTrait::encode_string(input.span()); assert(res.len() == 58, 'wrong len'); assert(*res[0] == 0xb8, 'wrong prefix'); @@ -206,7 +173,7 @@ fn test_rlp_encode_string_length_greater_than_56() { i += 1; }; - let res = RLPTrait::encode_string(input.span()).unwrap(); + let res = RLPTrait::encode_string(input.span()); assert(res.len() == 62, 'wrong len'); assert(*res[0] == 0xb8, 'wrong prefix'); @@ -234,7 +201,7 @@ fn test_rlp_encode_string_large_bytearray_inputs() { i += 1; }; - let res = RLPTrait::encode_string(input.span()).unwrap(); + let res = RLPTrait::encode_string(input.span()); assert(res.len() == 503, 'wrong len'); assert(*res[0] == 0xb9, 'wrong prefix'); @@ -252,26 +219,160 @@ fn test_rlp_encode_string_large_bytearray_inputs() { #[test] #[available_gas(20000000)] -fn test_rlp_encode_mutilple_string() { - let mut input = array![]; - input.append(RLPItem::String(array![0x40, 0x53, 0x15, 0x94, 0x50, 0x40, 0x40, 0x40].span())); - input.append(RLPItem::String(array![0x03].span())); +fn test_rlp_encode_sequence_empty() { + let res = RLPTrait::encode_sequence(array![].span()); - let res = RLPTrait::encode(input.span()).unwrap(); + assert(res.len() == 1, 'wrong len'); + assert(*res[0] == 0xC0, 'wrong encoded value'); +} - assert(res.len() == 10, 'wrong len'); - assert(*res[0] == 0x88, 'wrong prefix'); - assert(*res[1] == 0x40, 'wrong first value'); - assert(*res[2] == 0x53, 'wrong second value'); +#[test] +#[available_gas(20000000)] +fn test_rlp_encode_sequence() { + let mut input: Array> = Default::default(); + let cat = RLPItem::String(array![0x63, 0x61, 0x74].span()); + let dog = RLPItem::String(array![0x64, 0x6f, 0x67].span()); + let input = array![cat, dog]; + + let encoding = RLPTrait::encode_sequence(input.span()); + + let expected = array![0xc8, 0x83, 0x63, 0x61, 0x74, 0x83, 0x64, 0x6f, 0x67].span(); + assert(expected == encoding, 'wrong rlp encoding') } #[test] -#[available_gas(99999999)] -#[should_panic(expected: ('List encoding unimplemented',))] -fn test_rlp_encode_list() { - let mut input = array![RLPItem::List(array![].span())]; +#[available_gas(20000000)] +#[should_panic(expected: ('Shouldnt encode long sequence',))] +fn test_rlp_encode_sequence_long_sequence() { + // encoding of a sequence with more than 55 bytes + let mut lorem_ipsum = RLPItem::String( + array![ + 0x4c, + 0x6f, + 0x72, + 0x65, + 0x6d, + 0x20, + 0x69, + 0x70, + 0x73, + 0x75, + 0x6d, + 0x20, + 0x64, + 0x6f, + 0x6c, + 0x6f, + 0x72, + 0x20, + 0x73, + 0x69, + 0x74, + 0x20, + 0x61, + 0x6d, + 0x65, + 0x74, + 0x2c, + 0x20, + 0x63, + 0x6f, + 0x6e, + 0x73, + 0x65, + 0x63, + 0x74, + 0x65, + 0x74, + 0x75, + 0x72, + 0x20, + 0x61, + 0x64, + 0x69, + 0x70, + 0x69, + 0x73, + 0x69, + 0x63, + 0x69, + 0x6e, + 0x67, + 0x20, + 0x65, + 0x6c, + 0x69, + 0x74 + ] + .span() + ); + let input = array![lorem_ipsum].span(); + let encoding = RLPTrait::encode_sequence(input); - let res = RLPTrait::encode(input.span()).unwrap(); + let expected = array![ + 0xf8, + 0x3a, + 0xb8, + 0x38, + 0x4c, + 0x6f, + 0x72, + 0x65, + 0x6d, + 0x20, + 0x69, + 0x70, + 0x73, + 0x75, + 0x6d, + 0x20, + 0x64, + 0x6f, + 0x6c, + 0x6f, + 0x72, + 0x20, + 0x73, + 0x69, + 0x74, + 0x20, + 0x61, + 0x6d, + 0x65, + 0x74, + 0x2c, + 0x20, + 0x63, + 0x6f, + 0x6e, + 0x73, + 0x65, + 0x63, + 0x74, + 0x65, + 0x74, + 0x75, + 0x72, + 0x20, + 0x61, + 0x64, + 0x69, + 0x70, + 0x69, + 0x73, + 0x69, + 0x63, + 0x69, + 0x6e, + 0x67, + 0x20, + 0x65, + 0x6c, + 0x69, + 0x74 + ] + .span(); + assert(expected == encoding, 'wrong rlp encoding') } #[test] diff --git a/crates/utils/src/traits.cairo b/crates/utils/src/traits.cairo index d0b5c59e2..e10198c22 100644 --- a/crates/utils/src/traits.cairo +++ b/crates/utils/src/traits.cairo @@ -129,3 +129,13 @@ impl U256TryIntoResult> of TryIntoResult { } } } + +impl U256TryIntoEthAddress of TryInto { + fn try_into(self: u256) -> Option { + let maybe_value: Option = self.try_into(); + match maybe_value { + Option::Some(value) => value.try_into(), + Option::None => Option::None, + } + } +}