From 6806eca95c4a5be17497dbc650056cdaba9e34f3 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Mon, 24 Apr 2023 17:28:41 +0200 Subject: [PATCH 1/9] EVM/Kernel: use evm-execution.run_transaction --- src/kernel_evm/ethereum/src/transaction.rs | 6 +- src/kernel_evm/kernel/src/block.rs | 282 ++++++++++----------- src/kernel_evm/kernel/src/error.rs | 3 + src/kernel_evm/kernel/src/helpers.rs | 11 - src/kernel_evm/kernel/src/lib.rs | 1 - 5 files changed, 145 insertions(+), 158 deletions(-) delete mode 100644 src/kernel_evm/kernel/src/helpers.rs diff --git a/src/kernel_evm/ethereum/src/transaction.rs b/src/kernel_evm/ethereum/src/transaction.rs index 1cae7399cb4d..3a73f1d58189 100644 --- a/src/kernel_evm/ethereum/src/transaction.rs +++ b/src/kernel_evm/ethereum/src/transaction.rs @@ -3,9 +3,7 @@ // SPDX-License-Identifier: MIT use crate::address::EthereumAddress; -use primitive_types::{H256, U256}; - -use crate::eth_gen::OwnedHash; +use primitive_types::{H160, H256, U256}; pub const TRANSACTION_HASH_SIZE: usize = 32; pub type TransactionHash = [u8; TRANSACTION_HASH_SIZE]; @@ -74,7 +72,7 @@ pub struct TransactionReceipt { /// The amount of gas used by this specific transaction alone. pub gas_used: U256, /// The contract address created, if the transaction was a contract creation, otherwise null. - pub contract_address: Option, + pub contract_address: Option, // The two following fields can be ignored for now // pub logs : unit, // pub logs_bloom : unit, diff --git a/src/kernel_evm/kernel/src/block.rs b/src/kernel_evm/kernel/src/block.rs index 7c385b22bf95..abd6f253840c 100644 --- a/src/kernel_evm/kernel/src/block.rs +++ b/src/kernel_evm/kernel/src/block.rs @@ -2,178 +2,175 @@ // // SPDX-License-Identifier: MIT +use std::panic::catch_unwind; + use crate::blueprint::Queue; -use crate::error::{Error, TransferError}; -use crate::helpers::address_to_hash; -use crate::inbox::Transaction; +use crate::error::Error; +use crate::error::StorageError::AccountInitialisation; +use crate::error::TransferError::CumulativeGasUsedOverflow; +use crate::error::TransferError::InvalidCallerAddress; use crate::storage; +use evm_execution::account_storage::init_account_storage; +use evm_execution::handler::ExecutionOutcome; +use evm_execution::{precompiles, run_transaction}; + use tezos_ethereum::address::EthereumAddress; -use tezos_ethereum::signatures::EthereumTransactionCommon; -use tezos_smart_rollup_host::path::OwnedPath; +use tezos_ethereum::transaction::TransactionHash; use tezos_smart_rollup_host::runtime::Runtime; use primitive_types::U256; -use tezos_ethereum::account::Account; use tezos_ethereum::block::L2Block; use tezos_ethereum::transaction::{ TransactionReceipt, TransactionStatus, TransactionType, }; -use tezos_ethereum::wei::Wei; - -fn get_tx_sender( - tx: &EthereumTransactionCommon, -) -> Result<(OwnedPath, EthereumAddress), Error> { - let address = tx - .caller() - .or(Err(Error::Transfer(TransferError::InvalidSignature)))?; - // We reencode in hexadecimal, since the accounts hash are encoded in - // hexadecimal in the storage. - let hash = address_to_hash(address); - let path = storage::account_path(&hash)?; - Ok((path, address)) -} -fn get_tx_receiver( - tx: &EthereumTransactionCommon, -) -> Result<(OwnedPath, EthereumAddress), Error> { - let hash = address_to_hash(tx.to); - let path = storage::account_path(&hash)?; - Ok((path, tx.to)) -} - -// Update an account with the given balance and nonce (if one is given), and -// initialize it if it doesn't already appear in the storage. -fn update_account( - host: &mut Host, - account_path: &OwnedPath, - balance: Wei, - nonce: Option, // if none is given, only the balance is updated. This - // avoids updating the storage with the same value. -) -> Result<(), Error> { - if storage::has_account(host, account_path)? { - storage::store_balance(host, account_path, balance)?; - if let Some(nonce) = nonce { - storage::store_nonce(host, account_path, nonce)? - }; - Ok(()) - } else { - let account = Account::with_assets(balance); - storage::store_account(host, &account, account_path) - } +struct TransactionReceiptInfo { + tx_hash: TransactionHash, + index: u32, + execution_outcome: Option, + caller: EthereumAddress, + to: EthereumAddress, } -fn make_receipt( - block: &L2Block, - tx: &Transaction, - status: TransactionStatus, +fn make_receipt_info( + tx_hash: TransactionHash, index: u32, - sender: EthereumAddress, + execution_outcome: Option, + caller: EthereumAddress, to: EthereumAddress, -) -> TransactionReceipt { - TransactionReceipt { - hash: tx.tx_hash, +) -> TransactionReceiptInfo { + TransactionReceiptInfo { + tx_hash, index, - block_hash: block.hash, - block_number: block.number, - from: sender, - to: Some(to), - cumulative_gas_used: U256::zero(), - effective_gas_price: U256::zero(), - gas_used: U256::zero(), - contract_address: None, - type_: TransactionType::Legacy, - status, + execution_outcome, + caller, + to, } } -// invariant: the transaction is valid -// NB: when applying a transaction, we omit the payment of gas as it is not -// properly implemented in the current state of the kernel. -fn apply_transaction( - host: &mut Host, +fn make_receipt( block: &L2Block, - transaction: &Transaction, - index: u32, + receipt_info: TransactionReceiptInfo, + cumulative_gas_used: &mut U256, ) -> Result { - let (sender_path, sender_address) = get_tx_sender(&transaction.tx)?; - let sender_balance = - storage::read_account_balance(host, &sender_path).unwrap_or_else(|_| Wei::zero()); - let sender_nonce = - storage::read_account_nonce(host, &sender_path).unwrap_or(U256::zero()); - let value: U256 = transaction.tx.value; - let (dst_path, dst_address) = get_tx_receiver(&transaction.tx)?; - - if sender_balance < value - || sender_nonce != transaction.tx.nonce - || transaction.tx.clone().verify_signature().is_err() - { - Ok(make_receipt( - block, - transaction, - TransactionStatus::Failure, + let hash = receipt_info.tx_hash; + let index = receipt_info.index; + let block_hash = block.hash; + let block_number = block.number; + let from = receipt_info.caller; + let to = Some(receipt_info.to); + let effective_gas_price = block.constants().gas_price; + + let tx_receipt = match receipt_info.execution_outcome { + Some(outcome) => TransactionReceipt { + hash, index, - sender_address, - dst_address, - )) - } else { - update_account( - host, - &sender_path, - sender_balance - value, - Some(sender_nonce + U256::one()), - )?; - - let dst_balance = storage::read_account_balance(host, &dst_path) - .unwrap_or_else(|_| Wei::zero()); - update_account(host, &dst_path, dst_balance + value, None)?; - - Ok(make_receipt( - block, - transaction, - TransactionStatus::Success, + block_hash, + block_number, + from, + to, + cumulative_gas_used: cumulative_gas_used + .checked_add(U256::from(outcome.gas_used)) + .ok_or(Error::Transfer(CumulativeGasUsedOverflow))?, + effective_gas_price, + gas_used: U256::from(outcome.gas_used), + contract_address: outcome.new_address, + type_: TransactionType::Legacy, + status: if outcome.is_success { + TransactionStatus::Success + } else { + TransactionStatus::Failure + }, + }, + None => TransactionReceipt { + hash, index, - sender_address, - dst_address, - )) - } -} - -// This function is only available in nightly, hence the need for redefinition -fn try_collect(vec: Vec>) -> Result, E> { - let mut new_vec = Vec::new(); - for v in vec { - match v { - Ok(v) => new_vec.push(v), - Err(e) => return Err(e), - } - } - Ok(new_vec) + block_hash, + block_number, + from, + to, + cumulative_gas_used: *cumulative_gas_used, + effective_gas_price, + gas_used: U256::zero(), + contract_address: None, + type_: TransactionType::Legacy, + status: TransactionStatus::Failure, + }, + }; + + Ok(tx_receipt) } -fn apply_transactions( - host: &mut Host, +fn make_receipts( block: &L2Block, - transactions: &[Transaction], + receipt_infos: Vec, ) -> Result, Error> { - try_collect( - transactions - .iter() - .enumerate() - .map(|(index, tx)| (apply_transaction(host, block, tx, index as u32))) - .collect(), - ) + let mut cumulative_gas_used = U256::zero(); + receipt_infos + .into_iter() + .map(|receipt_info| make_receipt(block, receipt_info, &mut cumulative_gas_used)) + .collect() } pub fn produce(host: &mut Host, queue: Queue) -> Result<(), Error> { + let mut current_block = storage::read_current_block(host)?; + let mut evm_account_storage = + init_account_storage().map_err(|_| Error::Storage(AccountInitialisation))?; + let precompiles = precompiles::precompile_set::(); + for proposal in queue.proposals { - let current_level = storage::read_current_block_number(host)?; - let next_level = current_level + 1; - let transaction_hashes = - proposal.transactions.iter().map(|tx| tx.tx_hash).collect(); - let valid_block = L2Block::new(next_level, transaction_hashes); - storage::store_current_block(host, &valid_block)?; - let receipts = apply_transactions(host, &valid_block, &proposal.transactions)?; - storage::store_transaction_receipts(host, &receipts)?; + let mut valid_txs = Vec::new(); + let mut receipts_infos = Vec::new(); + let transactions = proposal.transactions; + + for (transaction, index) in transactions.into_iter().zip(0u32..) { + // TODO: https://gitlab.com/tezos/tezos/-/issues/5487 + // gas_limit representation should be the same through all modules to avoid + // odd conversions + let gas_limit = catch_unwind(|| transaction.tx.gas_limit.as_u64()).ok(); + let caller = transaction + .tx + .caller() + .map_err(|_| Error::Transfer(InvalidCallerAddress))?; + let receipt_info = match run_transaction( + host, + ¤t_block.constants(), + &mut evm_account_storage, + &precompiles, + transaction.tx.to.into(), + caller.into(), + transaction.tx.data, + gas_limit, + Some(transaction.tx.value), + ) { + Ok(outcome) => { + valid_txs.push(transaction.tx_hash); + make_receipt_info( + transaction.tx_hash, + index, + Some(outcome), + caller, + transaction.tx.to, + ) + } + Err(_) => make_receipt_info( + transaction.tx_hash, + index, + None, + caller, + transaction.tx.to, + ), + }; + receipts_infos.push(receipt_info) + } + + let new_block = L2Block::new(current_block.number + 1, valid_txs); + storage::store_current_block(host, &new_block)?; + storage::store_transaction_receipts( + host, + &make_receipts(&new_block, receipts_infos)?, + )?; + current_block = new_block; } Ok(()) } @@ -187,7 +184,8 @@ mod tests { use crate::storage::read_transaction_receipt_status; use primitive_types::H256; use tezos_ethereum::address::EthereumAddress; - use tezos_ethereum::transaction::TRANSACTION_HASH_SIZE; + use tezos_ethereum::signatures::EthereumTransactionCommon; + use tezos_ethereum::transaction::{TransactionStatus, TRANSACTION_HASH_SIZE}; use tezos_smart_rollup_mock::MockHost; fn string_to_h256_unsafe(s: &str) -> H256 { diff --git a/src/kernel_evm/kernel/src/error.rs b/src/kernel_evm/kernel/src/error.rs index 8595d17649e6..bbac811c8acc 100644 --- a/src/kernel_evm/kernel/src/error.rs +++ b/src/kernel_evm/kernel/src/error.rs @@ -7,9 +7,11 @@ use tezos_smart_rollup_host::runtime::RuntimeError; #[derive(Debug)] pub enum TransferError { + InvalidCallerAddress, InvalidSignature, InvalidNonce, NotEnoughBalance, + CumulativeGasUsedOverflow, InvalidAddressFormat(Utf8Error), } @@ -17,6 +19,7 @@ pub enum TransferError { pub enum StorageError { Path(PathError), Runtime(RuntimeError), + AccountInitialisation, InvalidLoadValue { expected: usize, actual: usize }, InvalidEncoding { path: OwnedPath, value: Vec }, } diff --git a/src/kernel_evm/kernel/src/helpers.rs b/src/kernel_evm/kernel/src/helpers.rs deleted file mode 100644 index bcae33ac1984..000000000000 --- a/src/kernel_evm/kernel/src/helpers.rs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Nomadic Labs -// -// SPDX-License-Identifier: MIT - -use tezos_ethereum::address::EthereumAddress; -use tezos_ethereum::eth_gen::OwnedHash; - -pub fn address_to_hash(address: EthereumAddress) -> OwnedHash { - let str: String = address.into(); - str.as_bytes().to_vec() -} diff --git a/src/kernel_evm/kernel/src/lib.rs b/src/kernel_evm/kernel/src/lib.rs index 554e356d984e..2aa7c6d748f5 100644 --- a/src/kernel_evm/kernel/src/lib.rs +++ b/src/kernel_evm/kernel/src/lib.rs @@ -16,7 +16,6 @@ mod block; mod blueprint; mod error; mod genesis; -mod helpers; mod inbox; mod storage; -- GitLab From 5d8e048078aa4f45e27e2c20d04ceb2cd8d9d775 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Tue, 25 Apr 2023 14:08:26 +0200 Subject: [PATCH 2/9] EVM/Kernel: rely on evm execution's account storage --- src/kernel_evm/ethereum/src/account.rs | 31 -------- src/kernel_evm/ethereum/src/lib.rs | 1 - src/kernel_evm/kernel/src/error.rs | 2 + src/kernel_evm/kernel/src/genesis.rs | 50 +++++++------ src/kernel_evm/kernel/src/storage.rs | 100 ------------------------- 5 files changed, 30 insertions(+), 154 deletions(-) delete mode 100644 src/kernel_evm/ethereum/src/account.rs diff --git a/src/kernel_evm/ethereum/src/account.rs b/src/kernel_evm/ethereum/src/account.rs deleted file mode 100644 index 263871b70100..000000000000 --- a/src/kernel_evm/ethereum/src/account.rs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Nomadic Labs -// -// SPDX-License-Identifier: MIT - -use crate::eth_gen::OwnedHash; -use crate::wei::Wei; -use primitive_types::U256; - -// Simple representation of an account, only contains fixed-sized values (no -// code nor storage). -pub struct Account { - pub nonce: U256, // initially 0, updated after each transaction - pub balance: Wei, - pub code_hash: OwnedHash, // 256 bits hash -} - -impl Account { - pub fn blank() -> Self { - Self::with_assets(Wei::zero()) - } - - pub fn with_assets(balance: Wei) -> Self { - Self { - balance, - nonce: U256::zero(), - // TODO: https://gitlab.com/tezos/tezos/-/issues/4859 - // Use the hash corresponding to the empty code - code_hash: vec![65; 32], - } - } -} diff --git a/src/kernel_evm/ethereum/src/lib.rs b/src/kernel_evm/ethereum/src/lib.rs index 74c54cdd65d6..0299e3eaa5ff 100644 --- a/src/kernel_evm/ethereum/src/lib.rs +++ b/src/kernel_evm/ethereum/src/lib.rs @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: MIT -pub mod account; pub mod address; pub mod block; pub mod eth_gen; diff --git a/src/kernel_evm/kernel/src/error.rs b/src/kernel_evm/kernel/src/error.rs index bbac811c8acc..e467c76afa38 100644 --- a/src/kernel_evm/kernel/src/error.rs +++ b/src/kernel_evm/kernel/src/error.rs @@ -20,6 +20,7 @@ pub enum StorageError { Path(PathError), Runtime(RuntimeError), AccountInitialisation, + GenesisAccountInitialisation, InvalidLoadValue { expected: usize, actual: usize }, InvalidEncoding { path: OwnedPath, value: Vec }, } @@ -28,6 +29,7 @@ pub enum StorageError { pub enum Error { Transfer(TransferError), Storage(StorageError), + InvalidConversion, } impl From for Error { diff --git a/src/kernel_evm/kernel/src/genesis.rs b/src/kernel_evm/kernel/src/genesis.rs index 6182a4c0bd68..be7aca7a6902 100644 --- a/src/kernel_evm/kernel/src/genesis.rs +++ b/src/kernel_evm/kernel/src/genesis.rs @@ -2,12 +2,18 @@ // // SPDX-License-Identifier: MIT +use std::str::FromStr; + use crate::error::Error; +use crate::error::StorageError::{GenesisAccountInitialisation, Path}; use crate::storage; use crate::storage::receipt_path; use crate::L2Block; -use primitive_types::U256; -use tezos_ethereum::account::Account; +use evm_execution::account_storage::account_path; +use evm_execution::account_storage::init_account_storage; +use evm_execution::account_storage::AccountStorageError; +use evm_execution::account_storage::EthereumAccountStorage; +use primitive_types::{H160, U256}; use tezos_ethereum::address::EthereumAddress; use tezos_ethereum::transaction::TransactionHash; use tezos_ethereum::transaction::TransactionReceipt; @@ -16,7 +22,7 @@ use tezos_ethereum::transaction::TransactionType; use tezos_ethereum::transaction::TRANSACTION_HASH_SIZE; use tezos_ethereum::wei::{self, Wei}; use tezos_smart_rollup_debug::debug_msg; -use tezos_smart_rollup_host::path::OwnedPath; +use tezos_smart_rollup_host::path::PathError; use tezos_smart_rollup_host::runtime::Runtime; struct MintAccount { @@ -49,22 +55,15 @@ const MINT_ACCOUNTS: [MintAccount; MINT_ACCOUNTS_NUMBER] = [ }, ]; -fn store_genesis_mint_account( - host: &mut Host, - account: &Account, - path: &OwnedPath, -) -> Result<(), Error> { - storage::store_account(host, account, path) -} - fn forge_genesis_mint_account( host: &mut Host, - mint_address: &str, + mint_address: &H160, balance: Wei, -) -> Result<(), Error> { - let account = Account::with_assets(balance); - let path = storage::account_path(&mint_address.as_bytes().to_vec())?; - store_genesis_mint_account(host, &account, &path) + evm_account_storage: &mut EthereumAccountStorage, +) -> Result<(), AccountStorageError> { + let mut account = + evm_account_storage.get_or_create_account(host, &account_path(mint_address)?)?; + account.balance_add(host, balance) } fn collect_mint_transactions( @@ -83,13 +82,23 @@ fn collect_mint_transactions( fn bootstrap_genesis_accounts( host: &mut Host, ) -> Result, Error> { + let mut evm_account_storage = + init_account_storage().map_err(|_| Error::Storage(Path(PathError::PathEmpty)))?; let transactions_hashes = MINT_ACCOUNTS.map( |MintAccount { mint_address, genesis_tx_hash, eth_amount, }| { - forge_genesis_mint_account(host, mint_address, wei::from_eth(eth_amount))?; + let mint_address = + H160::from_str(mint_address).map_err(|_| Error::InvalidConversion)?; + forge_genesis_mint_account( + host, + &mint_address, + wei::from_eth(eth_amount), + &mut evm_account_storage, + ) + .map_err(|_| Error::Storage(GenesisAccountInitialisation))?; Ok(genesis_tx_hash) }, ); @@ -110,11 +119,8 @@ fn store_genesis_receipts( ) -> Result<(), Error> { let genesis_address: EthereumAddress = GENESIS_ADDRESSS.into(); - for (i, hash) in genesis_block.transactions.iter().enumerate() { - let mint_account = &MINT_ACCOUNTS[i]; - // There are very few genesis transactions, the conversion from usize to u32 is safe - // and truncate won't lead to a loss of data - let index = i as u32; + for (hash, index) in genesis_block.transactions.iter().zip(0u32..) { + let mint_account = &MINT_ACCOUNTS[index as usize]; let receipt = TransactionReceipt { hash: *hash, diff --git a/src/kernel_evm/kernel/src/storage.rs b/src/kernel_evm/kernel/src/storage.rs index 64cd7c4b21c0..8b10966cec9b 100644 --- a/src/kernel_evm/kernel/src/storage.rs +++ b/src/kernel_evm/kernel/src/storage.rs @@ -10,7 +10,6 @@ use tezos_smart_rollup_host::runtime::{Runtime, ValueType}; use std::str::from_utf8; use crate::error::{Error, StorageError}; -use tezos_ethereum::account::*; use tezos_ethereum::block::L2Block; use tezos_ethereum::eth_gen::Hash; use tezos_ethereum::transaction::{ @@ -23,12 +22,6 @@ use primitive_types::{H160, H256, U256}; const SMART_ROLLUP_ADDRESS: RefPath = RefPath::assert_from(b"/metadata/smart_rollup_address"); -const EVM_ACCOUNTS: RefPath = RefPath::assert_from(b"/eth_accounts"); - -const EVM_ACCOUNT_BALANCE: RefPath = RefPath::assert_from(b"/balance"); -const EVM_ACCOUNT_NONCE: RefPath = RefPath::assert_from(b"/nonce"); -const EVM_ACCOUNT_CODE_HASH: RefPath = RefPath::assert_from(b"/code_hash"); - const EVM_CURRENT_BLOCK: RefPath = RefPath::assert_from(b"/evm/blocks/current"); const EVM_BLOCKS: RefPath = RefPath::assert_from(b"/evm/blocks"); const EVM_BLOCKS_NUMBER: RefPath = RefPath::assert_from(b"/number"); @@ -129,11 +122,6 @@ fn address_path(address: Hash) -> Result { OwnedPath::try_from(address_path).map_err(Error::from) } -pub fn account_path(address: Hash) -> Result { - let address_hash = address_path(address)?; - concat(&EVM_ACCOUNTS, &address_hash).map_err(Error::from) -} - pub fn block_path(number: U256) -> Result { let number: &str = &number.to_string(); let raw_number_path: Vec = format!("/{}", &number).into(); @@ -147,94 +135,6 @@ pub fn receipt_path(receipt_hash: &TransactionHash) -> Result concat(&TRANSACTIONS_RECEIPTS, &receipt_path).map_err(Error::from) } -pub fn has_account( - host: &mut Host, - account_path: &OwnedPath, -) -> Result { - match host.store_has(account_path)? { - Some(ValueType::Subtree | ValueType::ValueWithSubtree) => Ok(true), - _ => Ok(false), - } -} - -pub fn read_account_nonce( - host: &mut Host, - account_path: &OwnedPath, -) -> Result { - let path = concat(account_path, &EVM_ACCOUNT_NONCE)?; - read_u256(host, &path) -} - -pub fn read_account_balance( - host: &mut Host, - account_path: &OwnedPath, -) -> Result { - let path = concat(account_path, &EVM_ACCOUNT_BALANCE)?; - read_u256(host, &path) -} - -pub fn read_account_code_hash( - host: &mut Host, - account_path: &OwnedPath, -) -> Result, Error> { - let path = concat(account_path, &EVM_ACCOUNT_CODE_HASH)?; - host.store_read(&path, 0, HASH_MAX_SIZE) - .map_err(Error::from) -} - -pub fn read_account( - host: &mut Host, - address: Hash, -) -> Result { - let account_path = account_path(address)?; - let nonce = read_account_nonce(host, &account_path)?; - let balance = read_account_balance(host, &account_path)?; - let code_hash = read_account_code_hash(host, &account_path)?; - - Ok(Account { - nonce, - balance, - code_hash, - }) -} - -pub fn store_nonce( - host: &mut Host, - account_path: &OwnedPath, - nonce: U256, -) -> Result<(), Error> { - let path = concat(account_path, &EVM_ACCOUNT_NONCE)?; - write_u256(host, &path, nonce) -} - -pub fn store_balance( - host: &mut Host, - account_path: &OwnedPath, - balance: Wei, -) -> Result<(), Error> { - let path = concat(account_path, &EVM_ACCOUNT_BALANCE)?; - write_u256(host, &path, balance) -} - -fn store_code_hash( - host: &mut Host, - account_path: &OwnedPath, - code_hash: Hash, -) -> Result<(), Error> { - let path = concat(account_path, &EVM_ACCOUNT_CODE_HASH)?; - host.store_write(&path, code_hash, 0).map_err(Error::from) -} - -pub fn store_account( - host: &mut Host, - account: &Account, - account_path: &OwnedPath, -) -> Result<(), Error> { - store_nonce(host, account_path, account.nonce)?; - store_balance(host, account_path, account.balance)?; - store_code_hash(host, account_path, &account.code_hash) -} - pub fn read_current_block_number(host: &mut Host) -> Result { let path = concat(&EVM_CURRENT_BLOCK, &EVM_BLOCKS_NUMBER)?; let mut buffer = [0_u8; 8]; -- GitLab From 1e71e546d6f367e2845d10d585bd4aa234f76071 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Tue, 25 Apr 2023 14:43:00 +0200 Subject: [PATCH 3/9] EVM/Kernel: test to check mint accounts balances with the new account storage from evm execution --- src/kernel_evm/kernel/src/genesis.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/kernel_evm/kernel/src/genesis.rs b/src/kernel_evm/kernel/src/genesis.rs index be7aca7a6902..1cdfab62d1f7 100644 --- a/src/kernel_evm/kernel/src/genesis.rs +++ b/src/kernel_evm/kernel/src/genesis.rs @@ -161,16 +161,38 @@ pub fn init_block(host: &mut Host) -> Result<(), Error> { mod tests { use super::*; + use tezos_ethereum::wei; use tezos_smart_rollup_mock::MockHost; + fn get_balance( + host: &mut MockHost, + evm_account_storage: &mut EthereumAccountStorage, + address: &H160, + ) -> U256 { + let account = evm_account_storage + .get_or_create_account(host, &account_path(address).unwrap()) + .unwrap(); + account.balance(host).unwrap() + } + #[test] - // Test if the genesis block can be initialized + // Test if the genesis block can be initialized and that the mint accounts + // are initialized with the appropriate amounts. fn test_init_genesis_block() { let mut host = MockHost::default(); + let mut evm_account_storage = init_account_storage().unwrap(); match init_block(&mut host) { Ok(()) => (), Err(_) => panic!("The initialization of block genesis failed."), } + + for account in MINT_ACCOUNTS { + let mint_address = H160::from_str(account.mint_address).unwrap(); + assert_eq!( + get_balance(&mut host, &mut evm_account_storage, &mint_address), + wei::from_eth(9999) + ); + } } } -- GitLab From b5a1db130571e7896edd7e6b880423604b59c51f Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Tue, 25 Apr 2023 15:02:51 +0200 Subject: [PATCH 4/9] EVM/Kernel: test if a valid transaction produces a valid receipt --- src/kernel_evm/kernel/src/block.rs | 64 +++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/kernel_evm/kernel/src/block.rs b/src/kernel_evm/kernel/src/block.rs index abd6f253840c..ee6753951e4f 100644 --- a/src/kernel_evm/kernel/src/block.rs +++ b/src/kernel_evm/kernel/src/block.rs @@ -182,7 +182,9 @@ mod tests { use crate::genesis; use crate::inbox::Transaction; use crate::storage::read_transaction_receipt_status; - use primitive_types::H256; + use evm_execution::account_storage::{account_path, EthereumAccountStorage}; + use primitive_types::{H160, H256}; + use std::str::FromStr; use tezos_ethereum::address::EthereumAddress; use tezos_ethereum::signatures::EthereumTransactionCommon; use tezos_ethereum::transaction::{TransactionStatus, TRANSACTION_HASH_SIZE}; @@ -194,7 +196,29 @@ mod tests { H256::from(v) } + fn set_balance( + host: &mut MockHost, + evm_account_storage: &mut EthereumAccountStorage, + address: &H160, + balance: U256, + ) { + let mut account = evm_account_storage + .get_or_create_account(host, &account_path(address).unwrap()) + .unwrap(); + let current_balance = account.balance(host).unwrap(); + if current_balance > balance { + account + .balance_remove(host, current_balance - balance) + .unwrap(); + } else { + account + .balance_add(host, balance - current_balance) + .unwrap(); + } + } + fn dummy_eth_transaction() -> EthereumTransactionCommon { + // corresponding caller's address is 0xaf1276cbb260bb13deddb4209ae99ae6e497f446 let nonce = U256::from(0); let gas_price = U256::from(40000000000u64); let gas_limit = U256::from(21000); @@ -251,4 +275,42 @@ mod tests { Err(_) => panic!("Reading the receipt failed."), } } + + #[test] + // Test if a valid transaction is producing a receipt with a success status + fn test_valid_transactions_receipt_status() { + let mut host = MockHost::default(); + let _ = genesis::init_block(&mut host); + + let tx_hash = [0; TRANSACTION_HASH_SIZE]; + + let valid_tx = Transaction { + tx_hash, + tx: dummy_eth_transaction(), + }; + + let transactions: Vec = vec![valid_tx]; + let queue = Queue { + proposals: vec![Blueprint { transactions }], + }; + + let sender = H160::from_str("af1276cbb260bb13deddb4209ae99ae6e497f446").unwrap(); + let mut evm_account_storage = init_account_storage().unwrap(); + set_balance( + &mut host, + &mut evm_account_storage, + &sender, + U256::from(5000000000000000u64), + ); + + produce(&mut host, queue).expect("The block production failed."); + + match read_transaction_receipt_status(&mut host, &tx_hash) { + Ok(TransactionStatus::Failure) => { + panic!("The receipt should have a success status.") + } + Ok(TransactionStatus::Success) => (), + Err(_) => panic!("Reading the receipt failed."), + } + } } -- GitLab From 7d961cbbec7b546cebc89aa24f24971ca21424e9 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Wed, 26 Apr 2023 10:15:09 +0200 Subject: [PATCH 5/9] EVM/Execution: store accounts values as little endian The reason for that is because cli likes eth-cli are decoding u256 as little endian values, which means that an account that holds 9999 ETH, will hold ~1.518E+72 ETH (when decoding as big endian) which is absurd. As a side result, some of our E2E tests are failing as well. Note that it doesn't change anything from evm-execution's perspective. --- src/kernel_evm/evm_execution/src/account_storage.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/kernel_evm/evm_execution/src/account_storage.rs b/src/kernel_evm/evm_execution/src/account_storage.rs index 5f55fa638e29..335fa9b13e3c 100644 --- a/src/kernel_evm/evm_execution/src/account_storage.rs +++ b/src/kernel_evm/evm_execution/src/account_storage.rs @@ -160,7 +160,7 @@ fn read_u256( if bytes.len() != WORD_SIZE { Ok(None) } else { - Ok(Some(U256::from_big_endian(&bytes))) + Ok(Some(U256::from_little_endian(&bytes))) } } @@ -227,7 +227,8 @@ impl EthereumAccount { .checked_add(U256::one()) .ok_or(AccountStorageError::NonceOverflow)?; - let new_value_bytes: [u8; WORD_SIZE] = new_value.into(); + let mut new_value_bytes: [u8; WORD_SIZE] = [0; WORD_SIZE]; + new_value.to_little_endian(&mut new_value_bytes); host.store_write(&path, &new_value_bytes, 0) .map_err(AccountStorageError::from) @@ -259,7 +260,8 @@ impl EthereumAccount { let value = self.balance(host)?; if let Some(new_value) = value.checked_add(amount) { - let new_value_bytes: [u8; WORD_SIZE] = new_value.into(); + let mut new_value_bytes: [u8; WORD_SIZE] = [0; WORD_SIZE]; + new_value.to_little_endian(&mut new_value_bytes); host.store_write(&path, &new_value_bytes, 0) .map_err(AccountStorageError::from) @@ -281,7 +283,8 @@ impl EthereumAccount { let value = self.balance(host)?; if let Some(new_value) = value.checked_sub(amount) { - let new_value_bytes: [u8; WORD_SIZE] = new_value.into(); + let mut new_value_bytes: [u8; WORD_SIZE] = [0; WORD_SIZE]; + new_value.to_little_endian(&mut new_value_bytes); host.store_write(&path, &new_value_bytes, 0) .map_err(AccountStorageError::from) -- GitLab From 8c595a04732d33f4de0801023ea399e0258f7b5f Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Tue, 2 May 2023 10:16:50 +0200 Subject: [PATCH 6/9] EVM/Kernel: test several transactions validity --- src/kernel_evm/kernel/src/block.rs | 54 ++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/kernel_evm/kernel/src/block.rs b/src/kernel_evm/kernel/src/block.rs index ee6753951e4f..c9d05948beec 100644 --- a/src/kernel_evm/kernel/src/block.rs +++ b/src/kernel_evm/kernel/src/block.rs @@ -217,6 +217,17 @@ mod tests { } } + fn get_balance( + host: &mut MockHost, + evm_account_storage: &mut EthereumAccountStorage, + address: &H160, + ) -> U256 { + let account = evm_account_storage + .get_or_create_account(host, &account_path(address).unwrap()) + .unwrap(); + account.balance(host).unwrap() + } + fn dummy_eth_transaction() -> EthereumTransactionCommon { // corresponding caller's address is 0xaf1276cbb260bb13deddb4209ae99ae6e497f446 let nonce = U256::from(0); @@ -313,4 +324,47 @@ mod tests { Err(_) => panic!("Reading the receipt failed."), } } + + #[test] + // Test if several valid transactions can be performed + fn test_several_valid_transactions() { + let mut host = MockHost::default(); + let _ = genesis::init_block(&mut host); + + let tx_hash_0 = [0; TRANSACTION_HASH_SIZE]; + let tx_hash_1 = [1; TRANSACTION_HASH_SIZE]; + + let transactions = vec![ + Transaction { + tx_hash: tx_hash_0, + tx: dummy_eth_transaction(), + }, + Transaction { + tx_hash: tx_hash_1, + tx: dummy_eth_transaction(), + }, + ]; + + let queue = Queue { + proposals: vec![Blueprint { transactions }], + }; + + let sender = H160::from_str("af1276cbb260bb13deddb4209ae99ae6e497f446").unwrap(); + let mut evm_account_storage = init_account_storage().unwrap(); + set_balance( + &mut host, + &mut evm_account_storage, + &sender, + U256::from(10000000000000000000u64), + ); + + produce(&mut host, queue).expect("The block production failed."); + + let dest_address = + H160::from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea").unwrap(); + let dest_balance = + get_balance(&mut host, &mut evm_account_storage, &dest_address); + + assert_eq!(dest_balance, U256::from(10000000000000000u64)) + } } -- GitLab From f76e13f9d5845000a62afdb16222fa36382c20da Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Tue, 2 May 2023 10:26:43 +0200 Subject: [PATCH 7/9] EVM/Kernel: test several proposals validity --- src/kernel_evm/kernel/src/block.rs | 49 ++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/kernel_evm/kernel/src/block.rs b/src/kernel_evm/kernel/src/block.rs index c9d05948beec..34050acce736 100644 --- a/src/kernel_evm/kernel/src/block.rs +++ b/src/kernel_evm/kernel/src/block.rs @@ -367,4 +367,53 @@ mod tests { assert_eq!(dest_balance, U256::from(10000000000000000u64)) } + + #[test] + // Test if several valid proposals can produce valid blocks + fn test_several_valid_proposals() { + let mut host = MockHost::default(); + let _ = genesis::init_block(&mut host); + + let tx_hash_0 = [0; TRANSACTION_HASH_SIZE]; + let tx_hash_1 = [1; TRANSACTION_HASH_SIZE]; + + let transaction_0 = vec![Transaction { + tx_hash: tx_hash_0, + tx: dummy_eth_transaction(), + }]; + + let transaction_1 = vec![Transaction { + tx_hash: tx_hash_1, + tx: dummy_eth_transaction(), + }]; + + let queue = Queue { + proposals: vec![ + Blueprint { + transactions: transaction_0, + }, + Blueprint { + transactions: transaction_1, + }, + ], + }; + + let sender = H160::from_str("af1276cbb260bb13deddb4209ae99ae6e497f446").unwrap(); + let mut evm_account_storage = init_account_storage().unwrap(); + set_balance( + &mut host, + &mut evm_account_storage, + &sender, + U256::from(10000000000000000000u64), + ); + + produce(&mut host, queue).expect("The block production failed."); + + let dest_address = + H160::from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea").unwrap(); + let dest_balance = + get_balance(&mut host, &mut evm_account_storage, &dest_address); + + assert_eq!(dest_balance, U256::from(10000000000000000u64)) + } } -- GitLab From a5f4f59720b78dc4a447e5d6354022e0b110d041 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Tue, 2 May 2023 10:45:55 +0200 Subject: [PATCH 8/9] EVM/Kernel: store/read receipt's cumulative gas used --- src/kernel_evm/kernel/src/storage.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/kernel_evm/kernel/src/storage.rs b/src/kernel_evm/kernel/src/storage.rs index 8b10966cec9b..999df9cc1793 100644 --- a/src/kernel_evm/kernel/src/storage.rs +++ b/src/kernel_evm/kernel/src/storage.rs @@ -35,6 +35,8 @@ const TRANSACTION_RECEIPT_BLOCK_HASH: RefPath = RefPath::assert_from(b"/block_ha const TRANSACTION_RECEIPT_BLOCK_NUMBER: RefPath = RefPath::assert_from(b"/block_number"); const TRANSACTION_RECEIPT_FROM: RefPath = RefPath::assert_from(b"/from"); const TRANSACTION_RECEIPT_TO: RefPath = RefPath::assert_from(b"/to"); +const TRANSACTION_CUMULATIVE_GAS_USED: RefPath = + RefPath::assert_from(b"/cumulative_gas_used"); const TRANSACTION_RECEIPT_TYPE: RefPath = RefPath::assert_from(b"/type"); const TRANSACTION_RECEIPT_STATUS: RefPath = RefPath::assert_from(b"/status"); @@ -269,6 +271,18 @@ pub fn store_transaction_receipt( let to_path = concat(receipt_path, &TRANSACTION_RECEIPT_TO)?; host.store_write(&to_path, to.as_bytes(), 0)?; }; + // Cumulative gas used + let cumulative_gas_used_path = + concat(receipt_path, &TRANSACTION_CUMULATIVE_GAS_USED)?; + let mut le_receipt_cumulative_gas_used: [u8; 32] = [0; 32]; + receipt + .cumulative_gas_used + .to_little_endian(&mut le_receipt_cumulative_gas_used); + host.store_write( + &cumulative_gas_used_path, + &le_receipt_cumulative_gas_used, + 0, + )?; Ok(()) } @@ -301,6 +315,16 @@ pub fn read_transaction_receipt_status( }) } +pub fn read_transaction_receipt_cumulative_gas_used( + host: &mut Host, + tx_hash: &TransactionHash, +) -> Result { + let receipt_path = receipt_path(tx_hash)?; + let cumulative_gas_used_path = + concat(&receipt_path, &TRANSACTION_CUMULATIVE_GAS_USED)?; + read_u256(host, &cumulative_gas_used_path) +} + const CHUNKED_TRANSACTIONS: RefPath = RefPath::assert_from(b"/chunked_transactions"); const CHUNKED_TRANSACTION_NUM_CHUNKS: RefPath = RefPath::assert_from(b"/num_chunks"); -- GitLab From 303358963abf01f2b5798d8384723f9fda7545bf Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Tue, 2 May 2023 10:46:37 +0200 Subject: [PATCH 9/9] EVM/Kernel: test transfers gas consumption consistency --- src/kernel_evm/kernel/src/block.rs | 55 +++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/kernel_evm/kernel/src/block.rs b/src/kernel_evm/kernel/src/block.rs index 34050acce736..372324348a6a 100644 --- a/src/kernel_evm/kernel/src/block.rs +++ b/src/kernel_evm/kernel/src/block.rs @@ -181,7 +181,9 @@ mod tests { use crate::blueprint::Blueprint; use crate::genesis; use crate::inbox::Transaction; - use crate::storage::read_transaction_receipt_status; + use crate::storage::{ + read_transaction_receipt_cumulative_gas_used, read_transaction_receipt_status, + }; use evm_execution::account_storage::{account_path, EthereumAccountStorage}; use primitive_types::{H160, H256}; use std::str::FromStr; @@ -416,4 +418,55 @@ mod tests { assert_eq!(dest_balance, U256::from(10000000000000000u64)) } + + #[test] + // Test transfers gas consumption consistency + fn test_cumulative_transfers_gas_consumption() { + let mut host = MockHost::default(); + let _ = genesis::init_block(&mut host); + + let tx_hash_0 = [0; TRANSACTION_HASH_SIZE]; + let tx_hash_1 = [1; TRANSACTION_HASH_SIZE]; + + let transactions = vec![ + Transaction { + tx_hash: tx_hash_0, + tx: dummy_eth_transaction(), + }, + Transaction { + tx_hash: tx_hash_1, + tx: dummy_eth_transaction(), + }, + ]; + + let queue = Queue { + proposals: vec![Blueprint { + transactions: transactions.clone(), + }], + }; + + let sender = H160::from_str("af1276cbb260bb13deddb4209ae99ae6e497f446").unwrap(); + let mut evm_account_storage = init_account_storage().unwrap(); + set_balance( + &mut host, + &mut evm_account_storage, + &sender, + U256::from(10000000000000000000u64), + ); + + produce(&mut host, queue).expect("The block production failed."); + + for transaction in transactions { + match read_transaction_receipt_cumulative_gas_used( + &mut host, + &transaction.tx_hash, + ) { + Ok(cumulative_gas_used) => { + // NB: we do not use any gas for now, hence the following assertion + assert_eq!(cumulative_gas_used, U256::zero()) + } + Err(_) => panic!("Reading the receipt's cumulative gas used failed."), + } + } + } } -- GitLab