From f5eb5c5b1fc9b7c56cda872c5e9525932a38edc7 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Wed, 13 Nov 2024 12:48:08 -0300 Subject: [PATCH 01/17] fix(l2): add platform and args to the contract_deployer service (#1150) **Motivation** Right now it was needed to build the contract_deployer manually on mac due to solc not having target for arm **Description** This PR add the platform parameter to the contract_deployer service in the compose yaml as well as adding the BUILDPLATFORM env variable in the Dockerfile where solce is used. --------- Co-authored-by: Javier Chatruc --- .github/workflows/hive_and_assertoor.yaml | 6 ++---- crates/l2/contracts/Dockerfile | 8 +++----- crates/l2/docker-compose-l2.yaml | 3 +++ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/hive_and_assertoor.yaml b/.github/workflows/hive_and_assertoor.yaml index 9688bc2d8..22a25aa6c 100644 --- a/.github/workflows/hive_and_assertoor.yaml +++ b/.github/workflows/hive_and_assertoor.yaml @@ -2,15 +2,13 @@ name: "Hive & Assertoor" on: merge_group: paths-ignore: - - "crates/l2/**" - 'README.md' - 'LICENSE' - "**/README.md" - "**/docs/**" pull_request: branches: ["**"] - paths-ignore: - - "crates/l2/**" + paths-ignore: - 'README.md' - 'LICENSE' - "**/README.md" @@ -116,7 +114,7 @@ jobs: - name: Load image run: | - docker load --input /tmp/ethereum_rust_image.tar + docker load --input /tmp/ethereum_rust_image.tar - name: Setup kurtosis testnet and run assertoor tests uses: ethpandaops/kurtosis-assertoor-github-action@v1 diff --git a/crates/l2/contracts/Dockerfile b/crates/l2/contracts/Dockerfile index 02404e53a..62b11c645 100644 --- a/crates/l2/contracts/Dockerfile +++ b/crates/l2/contracts/Dockerfile @@ -47,13 +47,11 @@ RUN cargo chef cook --release --recipe-path recipe.json --manifest-path crates/l COPY . . RUN cargo build --release --manifest-path crates/l2/contracts/Cargo.toml -FROM ubuntu:24.04 +FROM --platform=${BUILDPLATFORM} ubuntu:24.04 WORKDIR /usr/local/bin -RUN apt-get update && apt-get -y install git gnupg -RUN echo 'deb https://ppa.launchpadcontent.net/ethereum/ethereum/ubuntu noble main' > /etc/apt/sources.list.d/ethereum-ethereum-noble.list && \ - echo 'deb-src https://ppa.launchpadcontent.net/ethereum/ethereum/ubuntu noble main' >> /etc/apt/sources.list.d/ethereum-ethereum-noble.list -RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E66CB8FE57F8218E96B13446B50ABD2EE384B7D4 +RUN apt-get update && apt-get -y install git gnupg software-properties-common +RUN add-apt-repository ppa:ethereum/ethereum RUN apt-get update && apt-get -y install solc COPY --from=builder ethereum_rust/target/release/ethereum_rust_l2_l1_deployer . diff --git a/crates/l2/docker-compose-l2.yaml b/crates/l2/docker-compose-l2.yaml index 6058a8b06..6c3e35a4e 100644 --- a/crates/l2/docker-compose-l2.yaml +++ b/crates/l2/docker-compose-l2.yaml @@ -3,10 +3,13 @@ include: services: contract_deployer: + platform: linux/amd64 container_name: contract_deployer image: ethereum_rust_l2_contract_deployer build: context: ../../ + args: + - BUILDPLATFORM=linux/amd64 dockerfile: ./crates/l2/contracts/Dockerfile volumes: - ./contracts:/contracts From a206a8804e6af434d58d398ac0196387d46f01cb Mon Sep 17 00:00:00 2001 From: Federico Borello <156438142+fborello-lambda@users.noreply.github.com> Date: Wed, 13 Nov 2024 14:28:36 -0300 Subject: [PATCH 02/17] feat(l2): write contracts' addresses to .env file (#1106) **Motivation** After the contracts are deployed, the addresses will differ from the `.env.example` provided. To make the deployment process more automatic, the `.env` file addresses should be changed. **Description** - Read `.env` file in order to change `PROPOSER_ON_CHAIN_PROPOSER_ADDRESS` and `L1_WATCHER_BRIDGE_ADDRESS ` with its corresponding addresses. Closes #1072 --------- Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: Javier Chatruc --- crates/l2/contracts/deployer.rs | 23 ++++++++++++++++++++++- crates/l2/utils/config/mod.rs | 27 ++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/crates/l2/contracts/deployer.rs b/crates/l2/contracts/deployer.rs index fd9495fec..2fa9f0f25 100644 --- a/crates/l2/contracts/deployer.rs +++ b/crates/l2/contracts/deployer.rs @@ -1,7 +1,7 @@ use bytes::Bytes; use ethereum_rust_core::types::{TxKind, GAS_LIMIT_ADJUSTMENT_FACTOR, GAS_LIMIT_MINIMUM}; use ethereum_rust_l2::utils::{ - config::read_env_file, + config::{read_env_as_lines, read_env_file, write_env}, eth_client::{eth_sender::Overrides, EthClient}, }; use ethereum_types::{Address, H160, H256}; @@ -36,6 +36,27 @@ async fn main() { ð_client, ) .await; + + let env_lines = read_env_as_lines().expect("Failed to read env file as lines."); + + let mut wr_lines: Vec = Vec::new(); + for line in env_lines { + let mut line = line.unwrap(); + if let Some(eq) = line.find('=') { + let (envar, _) = line.split_at(eq); + line = match envar { + "PROPOSER_ON_CHAIN_PROPOSER_ADDRESS" => { + format!("{envar}={on_chain_proposer:#x}") + } + "L1_WATCHER_BRIDGE_ADDRESS" => { + format!("{envar}={bridge_address:#x}") + } + _ => line, + }; + } + wr_lines.push(line); + } + write_env(wr_lines).expect("Failed to write changes to the .env file."); } fn setup() -> (Address, SecretKey, EthClient, PathBuf) { diff --git a/crates/l2/utils/config/mod.rs b/crates/l2/utils/config/mod.rs index 400e3fb3f..5786ddb3b 100644 --- a/crates/l2/utils/config/mod.rs +++ b/crates/l2/utils/config/mod.rs @@ -1,4 +1,4 @@ -use std::io::BufRead; +use std::io::{BufRead, Write}; use tracing::debug; @@ -38,3 +38,28 @@ pub fn read_env_file() -> Result<(), errors::ConfigError> { Ok(()) } + +pub fn read_env_as_lines( +) -> Result>, errors::ConfigError> { + let env_file_name = std::env::var("ENV_FILE").unwrap_or(".env".to_owned()); + let env_file = std::fs::File::open(env_file_name)?; + let reader = std::io::BufReader::new(env_file); + + Ok(reader.lines()) +} + +pub fn write_env(lines: Vec) -> Result<(), errors::ConfigError> { + let env_file_name = std::env::var("ENV_FILE").unwrap_or_else(|_| ".env".to_string()); + + let file = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .open(&env_file_name)?; + + let mut writer = std::io::BufWriter::new(file); + for line in lines { + writeln!(writer, "{line}")?; + } + + Ok(()) +} From 84b177b337612cef890ee1c97f476920fd716c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jerem=C3=ADas=20Salom=C3=B3n?= <48994069+JereSalo@users.noreply.github.com> Date: Wed, 13 Nov 2024 14:59:24 -0300 Subject: [PATCH 03/17] fix(levm): handle type conversions (#1110) **Motivation** **Description** Partially Closes #1074 --------- Co-authored-by: Juani Medone Co-authored-by: maximopalopoli Co-authored-by: Javier Chatruc Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri Co-authored-by: Maximo Palopoli <96491141+maximopalopoli@users.noreply.github.com> --- crates/vm/levm/Cargo.toml | 16 ++ crates/vm/levm/src/account.rs | 3 +- crates/vm/levm/src/constants.rs | 10 +- crates/vm/levm/src/errors.rs | 51 ++++- crates/vm/levm/src/lib.rs | 1 - .../vm/levm/src/opcode_handlers/arithmetic.rs | 8 +- crates/vm/levm/src/opcode_handlers/dup.rs | 7 +- .../levm/src/opcode_handlers/environment.rs | 8 +- .../vm/levm/src/opcode_handlers/exchange.rs | 8 +- crates/vm/levm/src/opcode_handlers/keccak.rs | 6 +- crates/vm/levm/src/opcode_handlers/logging.rs | 12 +- crates/vm/levm/src/opcode_handlers/push.rs | 6 +- .../stack_memory_storage_flow.rs | 12 +- crates/vm/levm/src/opcode_handlers/system.rs | 22 +-- crates/vm/levm/src/opcodes.rs | 14 ++ crates/vm/levm/src/operations.rs | 184 +++++++++--------- crates/vm/levm/src/vm.rs | 17 +- 17 files changed, 241 insertions(+), 144 deletions(-) diff --git a/crates/vm/levm/Cargo.toml b/crates/vm/levm/Cargo.toml index 02a21deca..fae47df14 100644 --- a/crates/vm/levm/Cargo.toml +++ b/crates/vm/levm/Cargo.toml @@ -14,6 +14,7 @@ serde = { version = "1.0.203", features = ["derive", "rc"] } serde_json = { version = "1.0.117" } walkdir = "2.5.0" keccak-hash = "0.11.0" +thiserror = "2.0.3" [dev-dependencies] hex = "0.4.3" @@ -23,5 +24,20 @@ spinoff = "0.8.0" [features] ethereum_foundation_tests = [] +# [lints.rust] +# unsafe_code = "forbid" +# warnings = "warn" +# rust_2018_idioms = "warn" + +[lints.clippy] +as_conversions = "deny" +# unwrap_used = "deny" +# expect_used = "deny" +# panic = "deny" +unnecessary_cast = "warn" +# deref_by_slicing = "warn" +# indexing_slicing = "warn" +# manual_unwrap_or = "warn" +# manual_unwrap_or_default = "warn" [lib] path = "./src/lib.rs" diff --git a/crates/vm/levm/src/account.rs b/crates/vm/levm/src/account.rs index c45ace97b..28cd2dd46 100644 --- a/crates/vm/levm/src/account.rs +++ b/crates/vm/levm/src/account.rs @@ -1,8 +1,7 @@ -use std::{collections::HashMap, str::FromStr}; - use bytes::Bytes; use ethereum_rust_core::{H256, U256}; use keccak_hash::keccak; +use std::{collections::HashMap, str::FromStr}; use crate::constants::EMPTY_CODE_HASH_STR; diff --git a/crates/vm/levm/src/constants.rs b/crates/vm/levm/src/constants.rs index 8d6c01c94..7487c6efd 100644 --- a/crates/vm/levm/src/constants.rs +++ b/crates/vm/levm/src/constants.rs @@ -1,3 +1,4 @@ +use crate::errors::{InternalError, VMError}; use ethereum_rust_core::U256; pub const SUCCESS_FOR_CALL: i32 = 1; @@ -120,12 +121,13 @@ pub const MAX_CREATE_CODE_SIZE: usize = 2 * MAX_CODE_SIZE; pub const INVALID_CONTRACT_PREFIX: u8 = 0xef; // Costs in gas for init word and init code (in wei) -pub const INIT_WORD_COST: i64 = 2; +pub const INIT_WORD_COST: u64 = 2; -pub fn init_code_cost(init_code_length: usize) -> u64 { - INIT_WORD_COST as u64 * (init_code_length as u64 + 31) / 32 +pub fn init_code_cost(init_code_length: usize) -> Result { + let length_u64 = u64::try_from(init_code_length) + .map_err(|_| VMError::Internal(InternalError::ConversionError))?; + Ok(INIT_WORD_COST * (length_u64 + 31) / 32) } - pub mod create_opcode { use ethereum_rust_core::U256; diff --git a/crates/vm/levm/src/errors.rs b/crates/vm/levm/src/errors.rs index 5eb268f63..cd9250c00 100644 --- a/crates/vm/levm/src/errors.rs +++ b/crates/vm/levm/src/errors.rs @@ -4,41 +4,88 @@ use ethereum_rust_core::{types::Log, Address}; use std::collections::HashMap; /// Errors that halt the program -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] pub enum VMError { + #[error("Stack underflow")] StackUnderflow, + #[error("Stack overflow")] StackOverflow, + #[error("Invalid jump")] InvalidJump, + #[error("Opcode not allowed in static context")] OpcodeNotAllowedInStaticContext, + #[error("Opcode not found")] OpcodeNotFound, + #[error("Invalid bytecode")] InvalidBytecode, + #[error("Out of gas")] OutOfGas, + #[error("Very large number")] VeryLargeNumber, + #[error("Overflow in arithmetic operation")] OverflowInArithmeticOp, + #[error("Fatal error")] FatalError, + #[error("Invalid transaction")] InvalidTransaction, + #[error("Revert opcode")] RevertOpcode, + #[error("Invalid opcode")] InvalidOpcode, + #[error("Missing blob hashes")] MissingBlobHashes, + #[error("Blob hash index out of bounds")] BlobHashIndexOutOfBounds, + #[error("Sender account does not exist")] SenderAccountDoesNotExist, + #[error("Address does not match an account")] AddressDoesNotMatchAnAccount, + #[error("Sender account should not have bytecode")] SenderAccountShouldNotHaveBytecode, + #[error("Sender balance should contain transfer value")] SenderBalanceShouldContainTransferValue, + #[error("Gas price is lower than base fee")] GasPriceIsLowerThanBaseFee, + #[error("Address already occupied")] AddressAlreadyOccupied, + #[error("Contract output too big")] ContractOutputTooBig, + #[error("Invalid initial byte")] InvalidInitialByte, + #[error("Nonce overflow")] NonceOverflow, + #[error("Internal error")] InternalError, + #[error("Memory load out of bounds")] MemoryLoadOutOfBounds, + #[error("Gas limit price product overflow")] GasLimitPriceProductOverflow, + #[error("Slicing error")] + SlicingError, + #[error("Indexing error")] + IndexingError, + #[error("Fatal unwrap")] + FatalUnwrap, // I will use this generic error for things that shouldn't fail + #[error("Account should have been cached")] + AccountShouldHaveBeenCached, + #[error("Data size overflow")] DataSizeOverflow, - Internal, + #[error("Gas cost overflow")] GasCostOverflow, + #[error("Offset overflow")] OffsetOverflow, + #[error("Creation cost is too high")] CreationCostIsTooHigh, + #[error("Max gas limit exceeded")] MaxGasLimitExceeded, + #[error("Internal error: {0}")] + Internal(#[from] InternalError), +} + +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +pub enum InternalError { + #[error("Tried to convert one type to another")] + ConversionError, } #[derive(Debug, Clone)] diff --git a/crates/vm/levm/src/lib.rs b/crates/vm/levm/src/lib.rs index 9ffbbfd75..258631fe4 100644 --- a/crates/vm/levm/src/lib.rs +++ b/crates/vm/levm/src/lib.rs @@ -10,6 +10,5 @@ pub mod opcodes; pub mod operations; pub mod utils; pub mod vm; - pub use account::*; pub use environment::*; diff --git a/crates/vm/levm/src/opcode_handlers/arithmetic.rs b/crates/vm/levm/src/opcode_handlers/arithmetic.rs index b3702d0e9..188897e41 100644 --- a/crates/vm/levm/src/opcode_handlers/arithmetic.rs +++ b/crates/vm/levm/src/opcode_handlers/arithmetic.rs @@ -1,7 +1,7 @@ use crate::{ call_frame::CallFrame, constants::gas_cost, - errors::{OpcodeSuccess, VMError}, + errors::{InternalError, OpcodeSuccess, VMError}, vm::VM, }; use ethereum_rust_core::{U256, U512}; @@ -214,7 +214,11 @@ impl VM { let base = current_call_frame.stack.pop()?; let exponent = current_call_frame.stack.pop()?; - let exponent_byte_size = (exponent.bits() as u64 + 7) / 8; + let exponent_bits: u64 = exponent + .bits() + .try_into() + .map_err(|_| VMError::Internal(InternalError::ConversionError))?; + let exponent_byte_size = (exponent_bits + 7) / 8; let gas_cost = gas_cost::EXP_STATIC + gas_cost::EXP_DYNAMIC_BASE * exponent_byte_size; self.increase_consumed_gas(current_call_frame, gas_cost)?; diff --git a/crates/vm/levm/src/opcode_handlers/dup.rs b/crates/vm/levm/src/opcode_handlers/dup.rs index 7590f8e6e..0a0694b88 100644 --- a/crates/vm/levm/src/opcode_handlers/dup.rs +++ b/crates/vm/levm/src/opcode_handlers/dup.rs @@ -17,20 +17,21 @@ impl VM { op: Opcode, ) -> Result { // Calculate the depth based on the opcode - let depth = (op as u8) - (Opcode::DUP1 as u8) + 1; + + let depth = usize::from(op) - usize::from(Opcode::DUP1) + 1; // Increase the consumed gas self.increase_consumed_gas(current_call_frame, gas_cost::DUPN)?; // Ensure the stack has enough elements to duplicate - if current_call_frame.stack.len() < depth as usize { + if current_call_frame.stack.len() < depth { return Err(VMError::StackUnderflow); } // Get the value at the specified depth let value_at_depth = current_call_frame .stack - .get(current_call_frame.stack.len() - depth as usize)?; + .get(current_call_frame.stack.len() - depth)?; // Push the duplicated value onto the stack current_call_frame.stack.push(*value_at_depth)?; diff --git a/crates/vm/levm/src/opcode_handlers/environment.rs b/crates/vm/levm/src/opcode_handlers/environment.rs index f0178384c..737fba08d 100644 --- a/crates/vm/levm/src/opcode_handlers/environment.rs +++ b/crates/vm/levm/src/opcode_handlers/environment.rs @@ -104,7 +104,7 @@ impl VM { .stack .pop()? .try_into() - .unwrap_or(usize::MAX); + .map_err(|_| VMError::VeryLargeNumber)?; // This check is because if offset is larger than the calldata then we should push 0 to the stack. let result = if offset < current_call_frame.calldata.len() { @@ -404,17 +404,17 @@ impl VM { .stack .pop()? .try_into() - .unwrap_or(usize::MAX); + .map_err(|_| VMError::VeryLargeNumber)?; let returndata_offset: usize = current_call_frame .stack .pop()? .try_into() - .unwrap_or(usize::MAX); + .map_err(|_| VMError::VeryLargeNumber)?; let size: usize = current_call_frame .stack .pop()? .try_into() - .unwrap_or(usize::MAX); + .map_err(|_| VMError::VeryLargeNumber)?; let minimum_word_size = (size + WORD_SIZE - 1) / WORD_SIZE; let memory_expansion_cost = current_call_frame.memory.expansion_cost( diff --git a/crates/vm/levm/src/opcode_handlers/exchange.rs b/crates/vm/levm/src/opcode_handlers/exchange.rs index dd36bedfd..0835abcc4 100644 --- a/crates/vm/levm/src/opcode_handlers/exchange.rs +++ b/crates/vm/levm/src/opcode_handlers/exchange.rs @@ -18,14 +18,18 @@ impl VM { ) -> Result { self.increase_consumed_gas(current_call_frame, gas_cost::SWAPN)?; - let depth = op as u8 - Opcode::SWAP1 as u8 + 1; + let depth = usize::from(op) - usize::from(Opcode::SWAP1) + 1; let stack_top_index = current_call_frame .stack .len() .checked_sub(1) .ok_or(VMError::StackUnderflow)?; + + if current_call_frame.stack.len() < depth { + return Err(VMError::StackUnderflow); + } let to_swap_index = stack_top_index - .checked_sub(depth.into()) + .checked_sub(depth) .ok_or(VMError::StackUnderflow)?; current_call_frame diff --git a/crates/vm/levm/src/opcode_handlers/keccak.rs b/crates/vm/levm/src/opcode_handlers/keccak.rs index cba4d2f01..449a7888c 100644 --- a/crates/vm/levm/src/opcode_handlers/keccak.rs +++ b/crates/vm/levm/src/opcode_handlers/keccak.rs @@ -15,16 +15,16 @@ impl VM { &mut self, current_call_frame: &mut CallFrame, ) -> Result { - let offset = current_call_frame + let offset: usize = current_call_frame .stack .pop()? .try_into() - .unwrap_or(usize::MAX); + .map_err(|_| VMError::VeryLargeNumber)?; let size = current_call_frame .stack .pop()? .try_into() - .unwrap_or(usize::MAX); + .map_err(|_| VMError::VeryLargeNumber)?; let minimum_word_size = (size + WORD_SIZE - 1) / WORD_SIZE; let memory_expansion_cost = current_call_frame.memory.expansion_cost( diff --git a/crates/vm/levm/src/opcode_handlers/logging.rs b/crates/vm/levm/src/opcode_handlers/logging.rs index b5caf2063..f7b93be38 100644 --- a/crates/vm/levm/src/opcode_handlers/logging.rs +++ b/crates/vm/levm/src/opcode_handlers/logging.rs @@ -1,6 +1,3 @@ -// Logging Operations (5) -// Opcodes: LOG0 ... LOG4 - use crate::{ call_frame::CallFrame, constants::gas_cost, @@ -11,6 +8,9 @@ use crate::{ use bytes::Bytes; use ethereum_rust_core::{types::Log, H256}; +// Logging Operations (5) +// Opcodes: LOG0 ... LOG4 + impl VM { // LOG operation pub fn op_log( @@ -22,17 +22,17 @@ impl VM { return Err(VMError::OpcodeNotAllowedInStaticContext); } - let number_of_topics = (op as u8) - (Opcode::LOG0 as u8); + let number_of_topics = u8::from(op) - u8::from(Opcode::LOG0); let offset: usize = current_call_frame .stack .pop()? .try_into() - .map_err(|_err| VMError::VeryLargeNumber)?; + .map_err(|_| VMError::VeryLargeNumber)?; let size = current_call_frame .stack .pop()? .try_into() - .map_err(|_err| VMError::VeryLargeNumber)?; + .map_err(|_| VMError::VeryLargeNumber)?; let mut topics = Vec::new(); for _ in 0..number_of_topics { let topic = current_call_frame.stack.pop()?; diff --git a/crates/vm/levm/src/opcode_handlers/push.rs b/crates/vm/levm/src/opcode_handlers/push.rs index 613e5dba2..11fefb963 100644 --- a/crates/vm/levm/src/opcode_handlers/push.rs +++ b/crates/vm/levm/src/opcode_handlers/push.rs @@ -19,18 +19,18 @@ impl VM { ) -> Result { self.increase_consumed_gas(current_call_frame, gas_cost::PUSHN)?; - let n_bytes = (op as u8) - (Opcode::PUSH1 as u8) + 1; + let n_bytes = usize::from(op) - usize::from(Opcode::PUSH1) + 1; let next_n_bytes = current_call_frame .bytecode - .get(current_call_frame.pc()..current_call_frame.pc() + n_bytes as usize) + .get(current_call_frame.pc()..current_call_frame.pc() + n_bytes) .ok_or(VMError::InvalidBytecode)?; // This shouldn't happen during execution let value_to_push = U256::from(next_n_bytes); current_call_frame.stack.push(value_to_push)?; - current_call_frame.increment_pc_by(n_bytes as usize); + current_call_frame.increment_pc_by(n_bytes); Ok(OpcodeSuccess::Continue) } diff --git a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs index ecfd591dc..841693b84 100644 --- a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs +++ b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs @@ -59,11 +59,11 @@ impl VM { &mut self, current_call_frame: &mut CallFrame, ) -> Result { - let offset = current_call_frame + let offset: usize = current_call_frame .stack .pop()? .try_into() - .unwrap_or(usize::MAX); + .map_err(|_| VMError::VeryLargeNumber)?; let memory_expansion_cost = current_call_frame.memory.expansion_cost( offset .checked_add(WORD_SIZE) @@ -254,21 +254,21 @@ impl VM { &mut self, current_call_frame: &mut CallFrame, ) -> Result { - let dest_offset = current_call_frame + let dest_offset: usize = current_call_frame .stack .pop()? .try_into() - .unwrap_or(usize::MAX); + .map_err(|_| VMError::VeryLargeNumber)?; let src_offset: usize = current_call_frame .stack .pop()? .try_into() - .unwrap_or(usize::MAX); + .map_err(|_| VMError::VeryLargeNumber)?; let size: usize = current_call_frame .stack .pop()? .try_into() - .unwrap_or(usize::MAX); + .map_err(|_| VMError::VeryLargeNumber)?; let words_copied = (size + WORD_SIZE - 1) / WORD_SIZE; diff --git a/crates/vm/levm/src/opcode_handlers/system.rs b/crates/vm/levm/src/opcode_handlers/system.rs index 2c07fde39..ff9778bdf 100644 --- a/crates/vm/levm/src/opcode_handlers/system.rs +++ b/crates/vm/levm/src/opcode_handlers/system.rs @@ -18,26 +18,26 @@ impl VM { let gas = current_call_frame.stack.pop()?; let code_address = word_to_address(current_call_frame.stack.pop()?); let value = current_call_frame.stack.pop()?; - let args_offset = current_call_frame + let args_offset: usize = current_call_frame .stack .pop()? .try_into() - .unwrap_or(usize::MAX); - let args_size = current_call_frame + .map_err(|_| VMError::VeryLargeNumber)?; + let args_size: usize = current_call_frame .stack .pop()? .try_into() - .unwrap_or(usize::MAX); - let ret_offset = current_call_frame + .map_err(|_| VMError::VeryLargeNumber)?; + let ret_offset: usize = current_call_frame .stack .pop()? .try_into() - .unwrap_or(usize::MAX); - let ret_size = current_call_frame + .map_err(|_| VMError::VeryLargeNumber)?; + let ret_size: usize = current_call_frame .stack .pop()? .try_into() - .unwrap_or(usize::MAX); + .map_err(|_| VMError::VeryLargeNumber)?; if current_call_frame.is_static && !value.is_zero() { return Err(VMError::OpcodeNotAllowedInStaticContext); @@ -168,16 +168,16 @@ impl VM { &mut self, current_call_frame: &mut CallFrame, ) -> Result { - let offset = current_call_frame + let offset: usize = current_call_frame .stack .pop()? .try_into() - .unwrap_or(usize::MAX); + .map_err(|_| VMError::VeryLargeNumber)?; let size = current_call_frame .stack .pop()? .try_into() - .unwrap_or(usize::MAX); + .map_err(|_| VMError::VeryLargeNumber)?; let gas_cost = current_call_frame.memory.expansion_cost( offset diff --git a/crates/vm/levm/src/opcodes.rs b/crates/vm/levm/src/opcodes.rs index e239030f2..db6662e65 100644 --- a/crates/vm/levm/src/opcodes.rs +++ b/crates/vm/levm/src/opcodes.rs @@ -333,3 +333,17 @@ impl TryFrom for Opcode { Ok(op) } } + +impl From for u8 { + #[allow(clippy::as_conversions)] + fn from(opcode: Opcode) -> Self { + opcode as u8 + } +} + +impl From for usize { + #[allow(clippy::as_conversions)] + fn from(opcode: Opcode) -> Self { + opcode as usize + } +} diff --git a/crates/vm/levm/src/operations.rs b/crates/vm/levm/src/operations.rs index 800581f16..07221e004 100644 --- a/crates/vm/levm/src/operations.rs +++ b/crates/vm/levm/src/operations.rs @@ -93,117 +93,119 @@ pub enum Operation { impl Operation { pub fn to_bytecode(&self) -> Result { let bytecode = match self { - Operation::Stop => Bytes::copy_from_slice(&[Opcode::STOP as u8]), - Operation::Add => Bytes::copy_from_slice(&[Opcode::ADD as u8]), - Operation::Mul => Bytes::copy_from_slice(&[Opcode::MUL as u8]), - Operation::Sub => Bytes::copy_from_slice(&[Opcode::SUB as u8]), - Operation::Div => Bytes::copy_from_slice(&[Opcode::DIV as u8]), - Operation::Sdiv => Bytes::copy_from_slice(&[Opcode::SDIV as u8]), - Operation::Mod => Bytes::copy_from_slice(&[Opcode::MOD as u8]), - Operation::SMod => Bytes::copy_from_slice(&[Opcode::SMOD as u8]), - Operation::Addmod => Bytes::copy_from_slice(&[Opcode::ADDMOD as u8]), - Operation::Mulmod => Bytes::copy_from_slice(&[Opcode::MULMOD as u8]), - Operation::Exp => Bytes::copy_from_slice(&[Opcode::EXP as u8]), - Operation::SignExtend => Bytes::copy_from_slice(&[Opcode::SIGNEXTEND as u8]), - Operation::Lt => Bytes::copy_from_slice(&[Opcode::LT as u8]), - Operation::Gt => Bytes::copy_from_slice(&[Opcode::GT as u8]), - Operation::Slt => Bytes::copy_from_slice(&[Opcode::SLT as u8]), - Operation::Sgt => Bytes::copy_from_slice(&[Opcode::SGT as u8]), - Operation::Eq => Bytes::copy_from_slice(&[Opcode::EQ as u8]), - Operation::IsZero => Bytes::copy_from_slice(&[Opcode::ISZERO as u8]), - Operation::And => Bytes::copy_from_slice(&[Opcode::AND as u8]), - Operation::Or => Bytes::copy_from_slice(&[Opcode::OR as u8]), - Operation::Xor => Bytes::copy_from_slice(&[Opcode::XOR as u8]), - Operation::Not => Bytes::copy_from_slice(&[Opcode::NOT as u8]), - Operation::Byte => Bytes::copy_from_slice(&[Opcode::BYTE as u8]), - Operation::Shl => Bytes::copy_from_slice(&[Opcode::SHL as u8]), - Operation::Shr => Bytes::copy_from_slice(&[Opcode::SHR as u8]), - Operation::Sar => Bytes::copy_from_slice(&[Opcode::SAR as u8]), - Operation::Keccak256 => Bytes::copy_from_slice(&[Opcode::KECCAK256 as u8]), - Operation::Address => Bytes::copy_from_slice(&[Opcode::ADDRESS as u8]), - Operation::Balance => Bytes::copy_from_slice(&[Opcode::BALANCE as u8]), - Operation::Origin => Bytes::copy_from_slice(&[Opcode::ORIGIN as u8]), - Operation::Caller => Bytes::copy_from_slice(&[Opcode::CALLER as u8]), - Operation::Callvalue => Bytes::copy_from_slice(&[Opcode::CALLVALUE as u8]), - Operation::CallDataLoad => Bytes::copy_from_slice(&[Opcode::CALLDATALOAD as u8]), - Operation::CallDataSize => Bytes::copy_from_slice(&[Opcode::CALLDATASIZE as u8]), - Operation::CallDataCopy => Bytes::copy_from_slice(&[Opcode::CALLDATACOPY as u8]), - Operation::Codesize => Bytes::copy_from_slice(&[Opcode::CODESIZE as u8]), - Operation::Codecopy => Bytes::copy_from_slice(&[Opcode::CODECOPY as u8]), - Operation::Gasprice => Bytes::copy_from_slice(&[Opcode::GASPRICE as u8]), - Operation::ExtcodeSize => Bytes::copy_from_slice(&[Opcode::EXTCODESIZE as u8]), - Operation::ExtcodeCopy => Bytes::copy_from_slice(&[Opcode::EXTCODECOPY as u8]), - Operation::ReturnDataSize => Bytes::copy_from_slice(&[Opcode::RETURNDATASIZE as u8]), - Operation::ReturnDataCopy => Bytes::copy_from_slice(&[Opcode::RETURNDATACOPY as u8]), - Operation::ExtcodeHash => Bytes::copy_from_slice(&[Opcode::EXTCODEHASH as u8]), - Operation::BlockHash => Bytes::copy_from_slice(&[Opcode::BLOCKHASH as u8]), - Operation::Coinbase => Bytes::copy_from_slice(&[Opcode::COINBASE as u8]), - Operation::Timestamp => Bytes::copy_from_slice(&[Opcode::TIMESTAMP as u8]), - Operation::Number => Bytes::copy_from_slice(&[Opcode::NUMBER as u8]), - Operation::Prevrandao => Bytes::copy_from_slice(&[Opcode::PREVRANDAO as u8]), - Operation::Gaslimit => Bytes::copy_from_slice(&[Opcode::GASLIMIT as u8]), - Operation::Chainid => Bytes::copy_from_slice(&[Opcode::CHAINID as u8]), - Operation::SelfBalance => Bytes::copy_from_slice(&[Opcode::SELFBALANCE as u8]), - Operation::Basefee => Bytes::copy_from_slice(&[Opcode::BASEFEE as u8]), - Operation::BlobHash => Bytes::copy_from_slice(&[Opcode::BLOBHASH as u8]), - Operation::BlobBaseFee => Bytes::copy_from_slice(&[Opcode::BLOBBASEFEE as u8]), - Operation::Pop => Bytes::copy_from_slice(&[Opcode::POP as u8]), - Operation::Mload => Bytes::copy_from_slice(&[Opcode::MLOAD as u8]), - Operation::Mstore => Bytes::copy_from_slice(&[Opcode::MSTORE as u8]), - Operation::Mstore8 => Bytes::copy_from_slice(&[Opcode::MSTORE8 as u8]), - Operation::Sload => Bytes::copy_from_slice(&[Opcode::SLOAD as u8]), - Operation::Sstore => Bytes::copy_from_slice(&[Opcode::SSTORE as u8]), - Operation::Jump => Bytes::copy_from_slice(&[Opcode::JUMP as u8]), - Operation::Jumpi => Bytes::copy_from_slice(&[Opcode::JUMPI as u8]), - Operation::PC => Bytes::copy_from_slice(&[Opcode::PC as u8]), - Operation::Msize => Bytes::copy_from_slice(&[Opcode::MSIZE as u8]), - Operation::Gas => Bytes::copy_from_slice(&[Opcode::GAS as u8]), - Operation::Jumpdest => Bytes::copy_from_slice(&[Opcode::JUMPDEST as u8]), - Operation::Tload => Bytes::copy_from_slice(&[Opcode::TLOAD as u8]), - Operation::Tstore => Bytes::copy_from_slice(&[Opcode::TSTORE as u8]), - Operation::Mcopy => Bytes::copy_from_slice(&[Opcode::MCOPY as u8]), - Operation::Push0 => Bytes::copy_from_slice(&[Opcode::PUSH0 as u8]), + Operation::Stop => Bytes::copy_from_slice(&[u8::from(Opcode::STOP)]), + Operation::Add => Bytes::copy_from_slice(&[u8::from(Opcode::ADD)]), + Operation::Mul => Bytes::copy_from_slice(&[u8::from(Opcode::MUL)]), + Operation::Sub => Bytes::copy_from_slice(&[u8::from(Opcode::SUB)]), + Operation::Div => Bytes::copy_from_slice(&[u8::from(Opcode::DIV)]), + Operation::Sdiv => Bytes::copy_from_slice(&[u8::from(Opcode::SDIV)]), + Operation::Mod => Bytes::copy_from_slice(&[u8::from(Opcode::MOD)]), + Operation::SMod => Bytes::copy_from_slice(&[u8::from(Opcode::SMOD)]), + Operation::Addmod => Bytes::copy_from_slice(&[u8::from(Opcode::ADDMOD)]), + Operation::Mulmod => Bytes::copy_from_slice(&[u8::from(Opcode::MULMOD)]), + Operation::Exp => Bytes::copy_from_slice(&[u8::from(Opcode::EXP)]), + Operation::SignExtend => Bytes::copy_from_slice(&[u8::from(Opcode::SIGNEXTEND)]), + Operation::Lt => Bytes::copy_from_slice(&[u8::from(Opcode::LT)]), + Operation::Gt => Bytes::copy_from_slice(&[u8::from(Opcode::GT)]), + Operation::Slt => Bytes::copy_from_slice(&[u8::from(Opcode::SLT)]), + Operation::Sgt => Bytes::copy_from_slice(&[u8::from(Opcode::SGT)]), + Operation::Eq => Bytes::copy_from_slice(&[u8::from(Opcode::EQ)]), + Operation::IsZero => Bytes::copy_from_slice(&[u8::from(Opcode::ISZERO)]), + Operation::And => Bytes::copy_from_slice(&[u8::from(Opcode::AND)]), + Operation::Or => Bytes::copy_from_slice(&[u8::from(Opcode::OR)]), + Operation::Xor => Bytes::copy_from_slice(&[u8::from(Opcode::XOR)]), + Operation::Not => Bytes::copy_from_slice(&[u8::from(Opcode::NOT)]), + Operation::Byte => Bytes::copy_from_slice(&[u8::from(Opcode::BYTE)]), + Operation::Shl => Bytes::copy_from_slice(&[u8::from(Opcode::SHL)]), + Operation::Shr => Bytes::copy_from_slice(&[u8::from(Opcode::SHR)]), + Operation::Sar => Bytes::copy_from_slice(&[u8::from(Opcode::SAR)]), + Operation::Keccak256 => Bytes::copy_from_slice(&[u8::from(Opcode::KECCAK256)]), + Operation::Address => Bytes::copy_from_slice(&[u8::from(Opcode::ADDRESS)]), + Operation::Balance => Bytes::copy_from_slice(&[u8::from(Opcode::BALANCE)]), + Operation::Origin => Bytes::copy_from_slice(&[u8::from(Opcode::ORIGIN)]), + Operation::Caller => Bytes::copy_from_slice(&[u8::from(Opcode::CALLER)]), + Operation::Callvalue => Bytes::copy_from_slice(&[u8::from(Opcode::CALLVALUE)]), + Operation::CallDataLoad => Bytes::copy_from_slice(&[u8::from(Opcode::CALLDATALOAD)]), + Operation::CallDataSize => Bytes::copy_from_slice(&[u8::from(Opcode::CALLDATASIZE)]), + Operation::CallDataCopy => Bytes::copy_from_slice(&[u8::from(Opcode::CALLDATACOPY)]), + Operation::Codesize => Bytes::copy_from_slice(&[u8::from(Opcode::CODESIZE)]), + Operation::Codecopy => Bytes::copy_from_slice(&[u8::from(Opcode::CODECOPY)]), + Operation::Gasprice => Bytes::copy_from_slice(&[u8::from(Opcode::GASPRICE)]), + Operation::ExtcodeSize => Bytes::copy_from_slice(&[u8::from(Opcode::EXTCODESIZE)]), + Operation::ExtcodeCopy => Bytes::copy_from_slice(&[u8::from(Opcode::EXTCODECOPY)]), + Operation::ReturnDataSize => { + Bytes::copy_from_slice(&[u8::from(Opcode::RETURNDATASIZE)]) + } + Operation::ReturnDataCopy => { + Bytes::copy_from_slice(&[u8::from(Opcode::RETURNDATACOPY)]) + } + Operation::ExtcodeHash => Bytes::copy_from_slice(&[u8::from(Opcode::EXTCODEHASH)]), + Operation::BlockHash => Bytes::copy_from_slice(&[u8::from(Opcode::BLOCKHASH)]), + Operation::Coinbase => Bytes::copy_from_slice(&[u8::from(Opcode::COINBASE)]), + Operation::Timestamp => Bytes::copy_from_slice(&[u8::from(Opcode::TIMESTAMP)]), + Operation::Number => Bytes::copy_from_slice(&[u8::from(Opcode::NUMBER)]), + Operation::Prevrandao => Bytes::copy_from_slice(&[u8::from(Opcode::PREVRANDAO)]), + Operation::Gaslimit => Bytes::copy_from_slice(&[u8::from(Opcode::GASLIMIT)]), + Operation::Chainid => Bytes::copy_from_slice(&[u8::from(Opcode::CHAINID)]), + Operation::SelfBalance => Bytes::copy_from_slice(&[u8::from(Opcode::SELFBALANCE)]), + Operation::Basefee => Bytes::copy_from_slice(&[u8::from(Opcode::BASEFEE)]), + Operation::BlobHash => Bytes::copy_from_slice(&[u8::from(Opcode::BLOBHASH)]), + Operation::BlobBaseFee => Bytes::copy_from_slice(&[u8::from(Opcode::BLOBBASEFEE)]), + Operation::Pop => Bytes::copy_from_slice(&[u8::from(Opcode::POP)]), + Operation::Mload => Bytes::copy_from_slice(&[u8::from(Opcode::MLOAD)]), + Operation::Mstore => Bytes::copy_from_slice(&[u8::from(Opcode::MSTORE)]), + Operation::Mstore8 => Bytes::copy_from_slice(&[u8::from(Opcode::MSTORE8)]), + Operation::Sload => Bytes::copy_from_slice(&[u8::from(Opcode::SLOAD)]), + Operation::Sstore => Bytes::copy_from_slice(&[u8::from(Opcode::SSTORE)]), + Operation::Jump => Bytes::copy_from_slice(&[u8::from(Opcode::JUMP)]), + Operation::Jumpi => Bytes::copy_from_slice(&[u8::from(Opcode::JUMPI)]), + Operation::PC => Bytes::copy_from_slice(&[u8::from(Opcode::PC)]), + Operation::Msize => Bytes::copy_from_slice(&[u8::from(Opcode::MSIZE)]), + Operation::Gas => Bytes::copy_from_slice(&[u8::from(Opcode::GAS)]), + Operation::Jumpdest => Bytes::copy_from_slice(&[u8::from(Opcode::JUMPDEST)]), + Operation::Tload => Bytes::copy_from_slice(&[u8::from(Opcode::TLOAD)]), + Operation::Tstore => Bytes::copy_from_slice(&[u8::from(Opcode::TSTORE)]), + Operation::Mcopy => Bytes::copy_from_slice(&[u8::from(Opcode::MCOPY)]), + Operation::Push0 => Bytes::copy_from_slice(&[u8::from(Opcode::PUSH0)]), Operation::Push((n, value)) => { + let n_usize: usize = (*n).into(); assert!(*n <= 32, "PUSH32 is the max"); - // the amount of bytes needed to represent the value must - // be less than the n in PUSHn assert!( - value.bits().div_ceil(8) <= *n as usize, + value.bits().div_ceil(8) <= n_usize, "value doesn't fit in n bytes" ); let mut word_buffer = [0; 32]; value.to_big_endian(&mut word_buffer); - // extract the last n bytes to push - let value_to_push = &word_buffer[((32 - *n) as usize)..]; - assert_eq!(value_to_push.len(), *n as usize); - let opcode = Opcode::try_from(Opcode::PUSH0 as u8 + *n)?; - let mut bytes = vec![opcode as u8]; + let value_to_push = &word_buffer[(32 - n_usize)..]; + assert_eq!(value_to_push.len(), n_usize); + let opcode = Opcode::try_from(u8::from(Opcode::PUSH0) + *n)?; + let mut bytes = vec![u8::from(opcode)]; bytes.extend_from_slice(value_to_push); Bytes::copy_from_slice(&bytes) } Operation::Dup(n) => { assert!(*n <= 16, "DUP16 is the max"); - Bytes::copy_from_slice(&[Opcode::DUP1 as u8 + n - 1]) + Bytes::copy_from_slice(&[u8::from(Opcode::DUP1) + n - 1]) } Operation::Swap(n) => { assert!(*n <= 16, "SWAP16 is the max"); - Bytes::copy_from_slice(&[Opcode::SWAP1 as u8 + n - 1]) + Bytes::copy_from_slice(&[u8::from(Opcode::SWAP1) + n - 1]) } Operation::Log(n) => { assert!(*n <= 4, "LOG4 is the max"); - Bytes::copy_from_slice(&[Opcode::LOG0 as u8 + n]) + Bytes::copy_from_slice(&[u8::from(Opcode::LOG0) + n]) } - Operation::Create => Bytes::copy_from_slice(&[Opcode::CREATE as u8]), - Operation::Call => Bytes::copy_from_slice(&[Opcode::CALL as u8]), - Operation::CallCode => Bytes::copy_from_slice(&[Opcode::CALLCODE as u8]), - Operation::Return => Bytes::copy_from_slice(&[Opcode::RETURN as u8]), - Operation::DelegateCall => Bytes::copy_from_slice(&[Opcode::DELEGATECALL as u8]), - Operation::Create2 => Bytes::copy_from_slice(&[Opcode::CREATE2 as u8]), - Operation::StaticCall => Bytes::copy_from_slice(&[Opcode::STATICCALL as u8]), - Operation::Revert => Bytes::copy_from_slice(&[Opcode::REVERT as u8]), - Operation::Invalid => Bytes::copy_from_slice(&[Opcode::INVALID as u8]), - Operation::SelfDestruct => Bytes::copy_from_slice(&[Opcode::SELFDESTRUCT as u8]), + Operation::Create => Bytes::copy_from_slice(&[u8::from(Opcode::CREATE)]), + Operation::Call => Bytes::copy_from_slice(&[u8::from(Opcode::CALL)]), + Operation::CallCode => Bytes::copy_from_slice(&[u8::from(Opcode::CALLCODE)]), + Operation::Return => Bytes::copy_from_slice(&[u8::from(Opcode::RETURN)]), + Operation::DelegateCall => Bytes::copy_from_slice(&[u8::from(Opcode::DELEGATECALL)]), + Operation::Create2 => Bytes::copy_from_slice(&[u8::from(Opcode::CREATE2)]), + Operation::StaticCall => Bytes::copy_from_slice(&[u8::from(Opcode::STATICCALL)]), + Operation::Revert => Bytes::copy_from_slice(&[u8::from(Opcode::REVERT)]), + Operation::Invalid => Bytes::copy_from_slice(&[u8::from(Opcode::INVALID)]), + Operation::SelfDestruct => Bytes::copy_from_slice(&[u8::from(Opcode::SELFDESTRUCT)]), }; Ok(bytecode) } diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 59f2a1856..82c3bea4e 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -4,7 +4,7 @@ use crate::{ constants::*, db::{Cache, Database}, environment::Environment, - errors::{OpcodeSuccess, ResultReason, TransactionReport, TxResult, VMError}, + errors::{InternalError, OpcodeSuccess, ResultReason, TransactionReport, TxResult, VMError}, opcodes::Opcode, }; use bytes::Bytes; @@ -440,13 +440,22 @@ impl VM { // If the initialization code completes successfully, a final contract-creation cost is paid, // the code-deposit cost, c, proportional to the size of the created contract’s code - let mut creation_cost = 200 * contract_code.len() as u64; + let code_length: u64 = contract_code + .len() + .try_into() + .map_err(|_| VMError::Internal(InternalError::ConversionError))?; + let mut creation_cost = 200 * code_length; creation_cost += 32000; report.add_gas_with_max(creation_cost, max_gas); // Charge 22100 gas for each storage variable set // GInitCodeword * number_of_words rounded up. GinitCodeWord = 2 - let number_of_words = initial_call_frame.calldata.chunks(32).len() as u64; + let number_of_words: u64 = initial_call_frame + .calldata + .chunks(WORD_SIZE) + .len() + .try_into() + .map_err(|_| VMError::Internal(InternalError::ConversionError))?; report.add_gas_with_max(number_of_words * 2, max_gas); let contract_address = initial_call_frame.to; @@ -662,7 +671,7 @@ impl VM { .checked_add(U256::from(31)) .ok_or(VMError::DataSizeOverflow)?) .checked_div(U256::from(32)) - .ok_or(VMError::Internal)?; // '32' will never be zero + .ok_or(VMError::OverflowInArithmeticOp)?; // '32' will never be zero. The error is wrong but will see later... let init_code_cost = minimum_word_size .checked_mul(INIT_CODE_WORD_COST) From 166d01ddc9953be432814146028a56a7e675556c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20I=C3=B1aki=20Bilbao?= Date: Wed, 13 Nov 2024 15:03:01 -0300 Subject: [PATCH 04/17] refactor(l2): simplify transactions creation (#1142) **Motivation** We need a more centralized way to create and send transactions, so it's more difficult to make a mistake creating one manually **Description** This PR makes a big refactor on how transactions are built. EthClient's `build_X_transaction` and `send_X_transaction` are used to build and sign/send transactions respectively. `libsecp256k1` was replaced with `secp256k1` to unify libraries across crates. Closes #1024 --- Cargo.toml | 5 + cmd/ethereum_rust_l2/Cargo.toml | 2 +- cmd/ethereum_rust_l2/src/commands/stack.rs | 4 +- cmd/ethereum_rust_l2/src/commands/test.rs | 41 +-- cmd/ethereum_rust_l2/src/commands/wallet.rs | 102 ++++---- cmd/ethereum_rust_l2/src/config.rs | 2 +- cmd/ethereum_rust_l2/src/utils/config/mod.rs | 12 +- crates/common/Cargo.toml | 5 +- crates/common/rlp/encode.rs | 9 + crates/common/types/transaction.rs | 262 ++++++++++++++++++- crates/l2/Cargo.toml | 2 +- crates/l2/contracts/Cargo.toml | 2 +- crates/l2/contracts/deployer.rs | 61 +++-- crates/l2/proposer/l1_watcher.rs | 75 +++--- crates/l2/proposer/mod.rs | 106 +++----- crates/l2/sdk/Cargo.toml | 2 +- crates/l2/sdk/src/sdk.rs | 59 +++-- crates/l2/tests/tests.rs | 15 +- crates/l2/utils/config/l1_watcher.rs | 2 +- crates/l2/utils/config/proposer.rs | 2 +- crates/l2/utils/eth_client/errors.rs | 2 + crates/l2/utils/eth_client/eth_sender.rs | 72 +---- crates/l2/utils/eth_client/mod.rs | 228 ++++++++++++---- crates/l2/utils/eth_client/transaction.rs | 62 ----- crates/l2/utils/mod.rs | 6 +- 25 files changed, 712 insertions(+), 428 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index abce9d21e..fc7f99494 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,3 +64,8 @@ rand = "0.8.5" cfg-if = "1.0.0" reqwest = { version = "0.12.7", features = ["json"] } snap = "1.1.1" +secp256k1 = { version = "0.29", default-features = false, features = [ + "global-context", + "recovery", + "rand", +] } diff --git a/cmd/ethereum_rust_l2/Cargo.toml b/cmd/ethereum_rust_l2/Cargo.toml index 3434f2422..0cbcd346e 100644 --- a/cmd/ethereum_rust_l2/Cargo.toml +++ b/cmd/ethereum_rust_l2/Cargo.toml @@ -23,7 +23,7 @@ colored = "2.1.0" spinoff = "0.8.0" itertools = "0.13.0" strum = "0.26.3" -libsecp256k1 = "0.7.1" +secp256k1.workspace = true keccak-hash = "0.10.0" ethereum_rust-l2.workspace = true diff --git a/cmd/ethereum_rust_l2/src/commands/stack.rs b/cmd/ethereum_rust_l2/src/commands/stack.rs index 4c90649bc..13285c474 100644 --- a/cmd/ethereum_rust_l2/src/commands/stack.rs +++ b/cmd/ethereum_rust_l2/src/commands/stack.rs @@ -1,7 +1,7 @@ use crate::{config::EthereumRustL2Config, utils::config::confirm}; use clap::Subcommand; use eyre::ContextCompat; -use libsecp256k1::SecretKey; +use secp256k1::SecretKey; use std::path::{Path, PathBuf}; pub const CARGO_MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); @@ -188,7 +188,7 @@ fn deploy_l1( .arg("--rpc-url") .arg(l1_rpc_url) .arg("--private-key") - .arg(hex::encode(deployer_private_key.serialize())) // TODO: In the future this must be the proposer's private key. + .arg(hex::encode(deployer_private_key.secret_bytes())) // TODO: In the future this must be the proposer's private key. .arg("--broadcast") .arg("--use") .arg(solc_path) diff --git a/cmd/ethereum_rust_l2/src/commands/test.rs b/cmd/ethereum_rust_l2/src/commands/test.rs index 92a79a185..3e78b1631 100644 --- a/cmd/ethereum_rust_l2/src/commands/test.rs +++ b/cmd/ethereum_rust_l2/src/commands/test.rs @@ -1,11 +1,11 @@ use crate::config::EthereumRustL2Config; +use bytes::Bytes; use clap::Subcommand; use ethereum_rust_blockchain::constants::TX_GAS_COST; -use ethereum_rust_core::types::{EIP1559Transaction, TxKind}; -use ethereum_rust_l2::utils::eth_client::EthClient; +use ethereum_rust_l2::utils::eth_client::{eth_sender::Overrides, EthClient}; use ethereum_types::{Address, H160, H256, U256}; use keccak_hash::keccak; -use libsecp256k1::SecretKey; +use secp256k1::SecretKey; use std::{ fs::File, io::{self, BufRead}, @@ -70,10 +70,10 @@ async fn transfer_from( cfg: EthereumRustL2Config, ) -> u64 { let client = EthClient::new(&cfg.network.l2_rpc_url); - let private_key = SecretKey::parse(pk.parse::().unwrap().as_fixed_bytes()).unwrap(); + let private_key = SecretKey::from_slice(pk.parse::().unwrap().as_bytes()).unwrap(); let mut buffer = [0u8; 64]; - let public_key = libsecp256k1::PublicKey::from_secret_key(&private_key).serialize(); + let public_key = private_key.public_key(secp256k1::SECP256K1).serialize(); buffer.copy_from_slice(&public_key[1..]); let address = H160::from(keccak(buffer)); @@ -86,18 +86,27 @@ async fn transfer_from( println!("transfer {i} from {pk}"); } - let mut tx = EIP1559Transaction { - to: TxKind::Call(to_address), - chain_id: cfg.network.l2_chain_id, - nonce: i, - gas_limit: TX_GAS_COST, - value, - max_fee_per_gas: 3121115334, - max_priority_fee_per_gas: 3000000000, - ..Default::default() - }; + let tx = client + .build_eip1559_transaction( + to_address, + Bytes::new(), + Overrides { + chain_id: Some(cfg.network.l2_chain_id), + nonce: Some(i), + value: Some(value), + gas_price: Some(3121115334), + priority_gas_price: Some(3000000000), + gas_limit: Some(TX_GAS_COST), + ..Default::default() + }, + ) + .await + .unwrap(); - while let Err(e) = client.send_eip1559_transaction(&mut tx, private_key).await { + while let Err(e) = client + .send_eip1559_transaction(tx.clone(), &private_key) + .await + { println!("Transaction failed (PK: {pk} - Nonce: {}): {e}", tx.nonce); retries += 1; sleep(std::time::Duration::from_secs(2)); diff --git a/cmd/ethereum_rust_l2/src/commands/wallet.rs b/cmd/ethereum_rust_l2/src/commands/wallet.rs index b3f2d3c6b..d9de3f43e 100644 --- a/cmd/ethereum_rust_l2/src/commands/wallet.rs +++ b/cmd/ethereum_rust_l2/src/commands/wallet.rs @@ -1,9 +1,7 @@ use crate::{commands::utils::encode_calldata, config::EthereumRustL2Config}; use bytes::Bytes; use clap::Subcommand; -use ethereum_rust_core::types::{ - EIP1559Transaction, PrivilegedL2Transaction, PrivilegedTxType, Transaction, TxKind, -}; +use ethereum_rust_core::types::{PrivilegedTxType, Transaction}; use ethereum_rust_l2::utils::{ eth_client::{eth_sender::Overrides, EthClient}, merkle_tree::merkle_proof, @@ -340,18 +338,20 @@ impl Command { hex::encode(claim_withdrawal_data.clone()) ); - let tx_hash = eth_client - .send( + let tx = eth_client + .build_eip1559_transaction( + cfg.contracts.common_bridge, claim_withdrawal_data.into(), - from, - TxKind::Call(cfg.contracts.common_bridge), - cfg.wallet.private_key, Overrides { chain_id: Some(cfg.network.l1_chain_id), + from: Some(cfg.wallet.address), ..Default::default() }, ) .await?; + let tx_hash = eth_client + .send_eip1559_transaction(tx, &cfg.wallet.private_key) + .await?; println!("Withdrawal claim sent: {tx_hash:#x}"); @@ -374,26 +374,27 @@ impl Command { let client = if l1 { eth_client } else { rollup_client }; - let mut transfer_transaction = EIP1559Transaction { - to: TxKind::Call(to), - value: amount, - chain_id: if l1 { - cfg.network.l1_chain_id - } else { - cfg.network.l2_chain_id - }, - nonce: nonce.unwrap_or(client.get_nonce(from).await?), - max_fee_per_gas: client.get_gas_price().await?.as_u64() * 100, - gas_limit: 21000 * 100, - ..Default::default() - }; - - // transfer_transaction.gas_limit = client - // .estimate_gas(transfer_transaction.clone().into()) - // .await?; + let transfer_tx = client + .build_eip1559_transaction( + to, + Bytes::new(), + Overrides { + value: Some(amount), + chain_id: if l1 { + Some(cfg.network.l1_chain_id) + } else { + Some(cfg.network.l2_chain_id) + }, + nonce, + from: Some(cfg.wallet.address), + gas_limit: Some(21000 * 100), + ..Default::default() + }, + ) + .await?; let tx_hash = client - .send_eip1559_transaction(&mut transfer_transaction, cfg.wallet.private_key) + .send_eip1559_transaction(transfer_tx, &cfg.wallet.private_key) .await?; println!( @@ -413,19 +414,24 @@ impl Command { wait_for_receipt, explorer_url: _, } => { - let withdraw_transaction = PrivilegedL2Transaction { - to: TxKind::Call(to.unwrap_or(cfg.wallet.address)), - value: amount, - chain_id: cfg.network.l2_chain_id, - nonce: nonce.unwrap_or(rollup_client.get_nonce(from).await?), - max_fee_per_gas: 800000000, - tx_type: PrivilegedTxType::Withdrawal, - gas_limit: 21000 * 2, - ..Default::default() - }; + let withdraw_transaction = rollup_client + .build_privileged_transaction( + PrivilegedTxType::Withdrawal, + to.unwrap_or(cfg.wallet.address), + Bytes::new(), + Overrides { + nonce, + from: Some(cfg.wallet.address), + value: Some(amount), + gas_limit: Some(21000 * 2), + gas_price: Some(800000000), + ..Default::default() + }, + ) + .await?; let tx_hash = rollup_client - .send_privileged_l2_transaction(withdraw_transaction, cfg.wallet.private_key) + .send_privileged_l2_transaction(withdraw_transaction, &cfg.wallet.private_key) .await?; println!("Withdrawal sent: {tx_hash:#x}"); @@ -461,23 +467,31 @@ impl Command { false => rollup_client, }; - let tx_hash = client - .send( + let tx = client + .build_eip1559_transaction( + to, calldata, - from, - TxKind::Call(to), - cfg.wallet.private_key, Overrides { - value: value.into(), + value: Some(value), + chain_id: if let Some(chain_id) = chain_id { + Some(chain_id) + } else if l1 { + Some(cfg.network.l1_chain_id) + } else { + Some(cfg.network.l2_chain_id) + }, nonce, - chain_id, gas_limit, gas_price, priority_gas_price, + from: Some(cfg.wallet.address), ..Default::default() }, ) .await?; + let tx_hash = client + .send_eip1559_transaction(tx, &cfg.wallet.private_key) + .await?; println!( "[{}] Transaction sent: {tx_hash:#x}", diff --git a/cmd/ethereum_rust_l2/src/config.rs b/cmd/ethereum_rust_l2/src/config.rs index 3b94aaecd..523febdcf 100644 --- a/cmd/ethereum_rust_l2/src/config.rs +++ b/cmd/ethereum_rust_l2/src/config.rs @@ -7,7 +7,7 @@ use crate::{ }; use ethereum_types::Address; use eyre::Context; -use libsecp256k1::SecretKey; +use secp256k1::SecretKey; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Clone)] diff --git a/cmd/ethereum_rust_l2/src/utils/config/mod.rs b/cmd/ethereum_rust_l2/src/utils/config/mod.rs index b4c80825c..9177ee225 100644 --- a/cmd/ethereum_rust_l2/src/utils/config/mod.rs +++ b/cmd/ethereum_rust_l2/src/utils/config/mod.rs @@ -11,7 +11,7 @@ use crate::{ use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select}; use ethereum_types::H256; use eyre::ContextCompat; -use libsecp256k1::SecretKey; +use secp256k1::SecretKey; use std::{path::PathBuf, str::FromStr}; pub mod default_values; @@ -112,11 +112,11 @@ pub fn prompt_config() -> eyre::Result { format!( "0x{}", hex::encode( - SecretKey::parse(DEFAULT_PRIVATE_KEY.as_fixed_bytes())?.serialize(), + SecretKey::from_slice(DEFAULT_PRIVATE_KEY.as_bytes())?.secret_bytes(), ) ), )?; - SecretKey::parse(H256::from_str(&prompted_private_key[2..])?.as_fixed_bytes())? + SecretKey::from_slice(H256::from_str(&prompted_private_key[2..])?.as_fixed_bytes())? }, address: prompt(ADDRESS_PROMPT_MSG, DEFAULT_ADDRESS)?, }, @@ -218,10 +218,10 @@ pub fn edit_existing_config_interactively( PRIVATE_KEY_PROMPT_MSG, format!( "0x{}", - hex::encode(existing_config.wallet.private_key.serialize()) + hex::encode(existing_config.wallet.private_key.secret_bytes()) ), )?; - SecretKey::parse(H256::from_str(&prompted_private_key[2..])?.as_fixed_bytes())? + SecretKey::from_slice(H256::from_str(&prompted_private_key[2..])?.as_fixed_bytes())? }, address: prompt(ADDRESS_PROMPT_MSG, existing_config.wallet.address)?, }, @@ -265,7 +265,7 @@ pub fn edit_existing_config_non_interactively( private_key: opts .private_key .map(|pk| { - SecretKey::parse(H256::from_str(&pk[2..]).unwrap().as_fixed_bytes()).unwrap() + SecretKey::from_slice(H256::from_str(&pk[2..]).unwrap().as_bytes()).unwrap() }) .unwrap_or(existing_config.wallet.private_key), }, diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 2e0fee151..5aa9a95cb 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -16,10 +16,7 @@ serde_json.workspace = true thiserror.workspace = true keccak-hash = "0.10.0" sha3.workspace = true -secp256k1 = { version = "0.29", default-features = false, features = [ - "global-context", - "recovery", -] } +secp256k1.workspace = true once_cell = "1.20.2" crc32fast.workspace = true bytes.workspace = true diff --git a/crates/common/rlp/encode.rs b/crates/common/rlp/encode.rs index 2f25755d7..a092a23a4 100644 --- a/crates/common/rlp/encode.rs +++ b/crates/common/rlp/encode.rs @@ -392,6 +392,15 @@ impl RLPEncode for ethereum_types::Bloom { } } +pub trait PayloadRLPEncode { + fn encode_payload(&self, buf: &mut dyn bytes::BufMut); + fn encode_payload_to_vec(&self) -> Vec { + let mut buf = Vec::new(); + self.encode_payload(&mut buf); + buf + } +} + #[cfg(test)] mod tests { use std::net::IpAddr; diff --git a/crates/common/types/transaction.rs b/crates/common/types/transaction.rs index 94ce0f59f..e1ea3fefb 100644 --- a/crates/common/types/transaction.rs +++ b/crates/common/types/transaction.rs @@ -2,8 +2,9 @@ use std::cmp::min; use bytes::Bytes; use ethereum_types::{Address, H256, U256}; +use keccak_hash::keccak; pub use mempool::MempoolTransaction; -use secp256k1::{ecdsa::RecoveryId, Message, SECP256K1}; +use secp256k1::{ecdsa::RecoveryId, Message, SecretKey}; use serde::{ser::SerializeStruct, Deserialize, Serialize}; pub use serde_impl::{AccessListEntry, GenericTransaction}; use sha3::{Digest, Keccak256}; @@ -11,7 +12,7 @@ use sha3::{Digest, Keccak256}; use ethereum_rust_rlp::{ constants::RLP_NULL, decode::{get_rlp_bytes_item_payload, is_encoded_as_bytes, RLPDecode}, - encode::RLPEncode, + encode::{PayloadRLPEncode, RLPEncode}, error::RLPDecodeError, structs::{Decoder, Encoder}, }; @@ -134,6 +135,19 @@ pub enum TxType { Privileged = 0x7e, } +pub trait Signable { + fn sign(&self, private_key: &SecretKey) -> Self + where + Self: Sized, + Self: Clone, + { + let mut signable = self.clone(); + signable.sign_inplace(private_key); + signable + } + fn sign_inplace(&mut self, private_key: &SecretKey); +} + impl Transaction { pub fn tx_type(&self) -> TxType { match self { @@ -375,6 +389,97 @@ impl RLPEncode for PrivilegedL2Transaction { } } +impl PayloadRLPEncode for Transaction { + fn encode_payload(&self, buf: &mut dyn bytes::BufMut) { + match self { + Transaction::LegacyTransaction(tx) => tx.encode_payload(buf), + Transaction::EIP1559Transaction(tx) => tx.encode_payload(buf), + Transaction::EIP2930Transaction(tx) => tx.encode_payload(buf), + Transaction::EIP4844Transaction(tx) => tx.encode_payload(buf), + Transaction::PrivilegedL2Transaction(tx) => tx.encode_payload(buf), + } + } +} + +impl PayloadRLPEncode for LegacyTransaction { + fn encode_payload(&self, buf: &mut dyn bytes::BufMut) { + Encoder::new(buf) + .encode_field(&self.nonce) + .encode_field(&self.gas_price) + .encode_field(&self.gas) + .encode_field(&self.to) + .encode_field(&self.value) + .encode_field(&self.data) + .finish(); + } +} + +impl PayloadRLPEncode for EIP1559Transaction { + fn encode_payload(&self, buf: &mut dyn bytes::BufMut) { + Encoder::new(buf) + .encode_field(&self.chain_id) + .encode_field(&self.nonce) + .encode_field(&self.max_priority_fee_per_gas) + .encode_field(&self.max_fee_per_gas) + .encode_field(&self.gas_limit) + .encode_field(&self.to) + .encode_field(&self.value) + .encode_field(&self.data) + .encode_field(&self.access_list) + .finish(); + } +} + +impl PayloadRLPEncode for EIP2930Transaction { + fn encode_payload(&self, buf: &mut dyn bytes::BufMut) { + Encoder::new(buf) + .encode_field(&self.chain_id) + .encode_field(&self.nonce) + .encode_field(&self.gas_price) + .encode_field(&self.gas_limit) + .encode_field(&self.to) + .encode_field(&self.value) + .encode_field(&self.data) + .encode_field(&self.access_list) + .finish(); + } +} + +impl PayloadRLPEncode for EIP4844Transaction { + fn encode_payload(&self, buf: &mut dyn bytes::BufMut) { + Encoder::new(buf) + .encode_field(&self.chain_id) + .encode_field(&self.nonce) + .encode_field(&self.max_priority_fee_per_gas) + .encode_field(&self.max_fee_per_gas) + .encode_field(&self.gas) + .encode_field(&self.to) + .encode_field(&self.value) + .encode_field(&self.data) + .encode_field(&self.access_list) + .encode_field(&self.max_fee_per_blob_gas) + .encode_field(&self.blob_versioned_hashes) + .finish(); + } +} + +impl PayloadRLPEncode for PrivilegedL2Transaction { + fn encode_payload(&self, buf: &mut dyn bytes::BufMut) { + Encoder::new(buf) + .encode_field(&self.chain_id) + .encode_field(&self.nonce) + .encode_field(&self.max_priority_fee_per_gas) + .encode_field(&self.max_fee_per_gas) + .encode_field(&self.gas_limit) + .encode_field(&self.to) + .encode_field(&self.value) + .encode_field(&self.data) + .encode_field(&self.access_list) + .encode_field(&self.tx_type) + .finish(); + } +} + impl RLPDecode for LegacyTransaction { fn decode_unfinished(rlp: &[u8]) -> Result<(LegacyTransaction, &[u8]), RLPDecodeError> { let decoder = Decoder::new(rlp)?; @@ -546,6 +651,125 @@ impl RLPDecode for PrivilegedL2Transaction { } } +impl Signable for Transaction { + fn sign_inplace(&mut self, private_key: &SecretKey) { + match self { + Transaction::LegacyTransaction(tx) => tx.sign_inplace(private_key), + Transaction::EIP2930Transaction(tx) => tx.sign_inplace(private_key), + Transaction::EIP1559Transaction(tx) => tx.sign_inplace(private_key), + Transaction::EIP4844Transaction(tx) => tx.sign_inplace(private_key), + Transaction::PrivilegedL2Transaction(tx) => tx.sign_inplace(private_key), + } + } +} + +impl Signable for LegacyTransaction { + fn sign_inplace(&mut self, private_key: &SecretKey) { + let data = Message::from_digest_slice(&keccak(self.encode_payload_to_vec()).0).unwrap(); + + let (recovery_id, signature) = secp256k1::SECP256K1 + .sign_ecdsa_recoverable(&data, private_key) + .serialize_compact(); + + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + r.copy_from_slice(&signature[..32]); + s.copy_from_slice(&signature[32..]); + + self.r = U256::from(&r); + self.s = U256::from(&s); + self.v = U256::from(recovery_id.to_i32()); + } +} + +impl Signable for EIP1559Transaction { + fn sign_inplace(&mut self, private_key: &SecretKey) { + let mut payload = vec![TxType::EIP1559 as u8]; + payload.append(self.encode_payload_to_vec().as_mut()); + let data = Message::from_digest_slice(&keccak(payload).0).unwrap(); + + let (recovery_id, signature) = secp256k1::SECP256K1 + .sign_ecdsa_recoverable(&data, private_key) + .serialize_compact(); + + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + r.copy_from_slice(&signature[..32]); + s.copy_from_slice(&signature[32..]); + let parity = recovery_id.to_i32() != 0; + + self.signature_r = U256::from(&r); + self.signature_s = U256::from(&s); + self.signature_y_parity = parity; + } +} + +impl Signable for EIP2930Transaction { + fn sign_inplace(&mut self, private_key: &SecretKey) { + let mut payload = vec![TxType::EIP2930 as u8]; + payload.append(self.encode_payload_to_vec().as_mut()); + let data = Message::from_digest_slice(&keccak(payload).0).unwrap(); + + let (recovery_id, signature) = secp256k1::SECP256K1 + .sign_ecdsa_recoverable(&data, private_key) + .serialize_compact(); + + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + r.copy_from_slice(&signature[..32]); + s.copy_from_slice(&signature[32..]); + let parity = recovery_id.to_i32() != 0; + + self.signature_r = U256::from(&r); + self.signature_s = U256::from(&s); + self.signature_y_parity = parity; + } +} + +impl Signable for EIP4844Transaction { + fn sign_inplace(&mut self, private_key: &SecretKey) { + let mut payload = vec![TxType::EIP4844 as u8]; + payload.append(self.encode_payload_to_vec().as_mut()); + let data = Message::from_digest_slice(&keccak(payload).0).unwrap(); + + let (recovery_id, signature) = secp256k1::SECP256K1 + .sign_ecdsa_recoverable(&data, private_key) + .serialize_compact(); + + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + r.copy_from_slice(&signature[..32]); + s.copy_from_slice(&signature[32..]); + let parity = recovery_id.to_i32() != 0; + + self.signature_r = U256::from(&r); + self.signature_s = U256::from(&s); + self.signature_y_parity = parity; + } +} + +impl Signable for PrivilegedL2Transaction { + fn sign_inplace(&mut self, private_key: &SecretKey) { + let mut payload = vec![TxType::Privileged as u8]; + payload.append(self.encode_payload_to_vec().as_mut()); + let data = Message::from_digest_slice(&keccak(payload).0).unwrap(); + + let (recovery_id, signature) = secp256k1::SECP256K1 + .sign_ecdsa_recoverable(&data, private_key) + .serialize_compact(); + + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + r.copy_from_slice(&signature[..32]); + s.copy_from_slice(&signature[32..]); + let parity = recovery_id.to_i32() != 0; + + self.signature_r = U256::from(&r); + self.signature_s = U256::from(&s); + self.signature_y_parity = parity; + } +} + impl Transaction { pub fn sender(&self) -> Address { match self { @@ -847,7 +1071,7 @@ fn recover_address( .finalize() .into(); // Recover public key - let public = SECP256K1 + let public = secp256k1::SECP256K1 .recover_ecdsa(&Message::from_digest(msg_digest), &signature) .unwrap(); // Hash public key to obtain address @@ -1829,7 +2053,7 @@ mod serde_impl { blob_versioned_hashes: vec![], blobs: vec![], chain_id: Some(value.chain_id), - ..Default::default() + from: Address::default(), } } } @@ -1854,8 +2078,34 @@ mod serde_impl { .collect(), blob_versioned_hashes: value.blob_versioned_hashes, blobs: vec![], - chain_id: None, - ..Default::default() + chain_id: Some(value.chain_id), + from: Address::default(), + } + } + } + + impl From for GenericTransaction { + fn from(value: PrivilegedL2Transaction) -> Self { + Self { + r#type: TxType::Privileged, + nonce: Some(value.nonce), + to: value.to, + gas: Some(value.gas_limit), + value: value.value, + input: value.data, + gas_price: value.max_fee_per_gas, + max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas), + max_fee_per_gas: Some(value.max_fee_per_gas), + max_fee_per_blob_gas: None, + access_list: value + .access_list + .iter() + .map(AccessListEntry::from) + .collect(), + blob_versioned_hashes: vec![], + blobs: vec![], + chain_id: Some(value.chain_id), + from: Address::default(), } } } diff --git a/crates/l2/Cargo.toml b/crates/l2/Cargo.toml index b4d8f5b85..2a08fea5e 100644 --- a/crates/l2/Cargo.toml +++ b/crates/l2/Cargo.toml @@ -23,7 +23,7 @@ ethereum_rust-dev = { path = "../../crates/blockchain/dev" } hex.workspace = true bytes.workspace = true jsonwebtoken.workspace = true -libsecp256k1 = "0.7.1" +secp256k1.workspace = true keccak-hash = "0.10.0" envy = "0.4.2" thiserror.workspace = true diff --git a/crates/l2/contracts/Cargo.toml b/crates/l2/contracts/Cargo.toml index 0ee41fdf2..ba042c0d5 100644 --- a/crates/l2/contracts/Cargo.toml +++ b/crates/l2/contracts/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" tokio = { version = "1.38.0", features = ["full"] } ethereum-types = { version = "0.14.1", features = ["serialize"] } bytes = { version = "1.6.0", features = ["serde"] } -libsecp256k1 = "0.7.1" +secp256k1.workspace = true keccak-hash = "0.10.0" hex = "0.4.3" tracing.workspace = true diff --git a/crates/l2/contracts/deployer.rs b/crates/l2/contracts/deployer.rs index 2fa9f0f25..8c5865784 100644 --- a/crates/l2/contracts/deployer.rs +++ b/crates/l2/contracts/deployer.rs @@ -1,12 +1,12 @@ use bytes::Bytes; -use ethereum_rust_core::types::{TxKind, GAS_LIMIT_ADJUSTMENT_FACTOR, GAS_LIMIT_MINIMUM}; +use ethereum_rust_core::types::{GAS_LIMIT_ADJUSTMENT_FACTOR, GAS_LIMIT_MINIMUM}; use ethereum_rust_l2::utils::{ config::{read_env_as_lines, read_env_file, write_env}, eth_client::{eth_sender::Overrides, EthClient}, }; use ethereum_types::{Address, H160, H256}; use keccak_hash::keccak; -use libsecp256k1::SecretKey; +use secp256k1::SecretKey; use std::{ path::{Path, PathBuf}, process::Command, @@ -69,7 +69,7 @@ fn setup() -> (Address, SecretKey, EthClient, PathBuf) { .expect("DEPLOYER_ADDRESS not set") .parse() .expect("Malformed DEPLOYER_ADDRESS"); - let deployer_private_key = SecretKey::parse( + let deployer_private_key = SecretKey::from_slice( H256::from_str( std::env::var("DEPLOYER_PRIVATE_KEY") .expect("DEPLOYER_PRIVATE_KEY not set") @@ -77,7 +77,7 @@ fn setup() -> (Address, SecretKey, EthClient, PathBuf) { .expect("Malformed DEPLOYER_ADDRESS (strip_prefix(\"0x\"))"), ) .expect("Malformed DEPLOYER_ADDRESS (H256::from_str)") - .as_fixed_bytes(), + .as_bytes(), ) .expect("Malformed DEPLOYER_PRIVATE_KEY (SecretKey::parse)"); let contracts_path = Path::new( @@ -264,16 +264,21 @@ async fn create2_deploy( eth_client: &EthClient, ) -> (H256, Address) { let calldata = [SALT.as_bytes(), init_code].concat(); - let deploy_tx_hash = eth_client - .send( + let deploy_tx = eth_client + .build_eip1559_transaction( + DETERMINISTIC_CREATE2_ADDRESS, calldata.into(), - deployer, - TxKind::Call(DETERMINISTIC_CREATE2_ADDRESS), - deployer_private_key, - overrides, + Overrides { + from: Some(deployer), + ..overrides + }, ) .await - .unwrap(); + .expect("Failed to build create2 deploy tx"); + let deploy_tx_hash = eth_client + .send_eip1559_transaction(deploy_tx, &deployer_private_key) + .await + .expect("Failed to send create2 deploy tx"); wait_for_transaction_receipt(deploy_tx_hash, eth_client).await; @@ -348,15 +353,20 @@ async fn initialize_on_chain_proposer( .extend_from_slice(&on_chain_proposer_initialize_selector); on_chain_proposer_initialization_calldata.extend_from_slice(&encoded_bridge); - let initialize_tx_hash = eth_client - .send( + let initialize_tx = eth_client + .build_eip1559_transaction( + on_chain_proposer, on_chain_proposer_initialization_calldata.into(), - deployer, - TxKind::Call(on_chain_proposer), - deployer_private_key, - Overrides::default(), + Overrides { + from: Some(deployer), + ..Default::default() + }, ) .await + .expect("Failed to build initialize transaction"); + let initialize_tx_hash = eth_client + .send_eip1559_transaction(initialize_tx, &deployer_private_key) + .await .expect("Failed to send initialize transaction"); wait_for_transaction_receipt(initialize_tx_hash, eth_client).await; @@ -387,15 +397,20 @@ async fn initialize_bridge( bridge_initialization_calldata.extend_from_slice(&bridge_initialize_selector); bridge_initialization_calldata.extend_from_slice(&encoded_on_chain_proposer); - let initialize_tx_hash = eth_client - .send( + let initialize_tx = eth_client + .build_eip1559_transaction( + bridge, bridge_initialization_calldata.into(), - deployer, - TxKind::Call(bridge), - deployer_private_key, - Overrides::default(), + Overrides { + from: Some(deployer), + ..Default::default() + }, ) .await + .expect("Failed to build initialize transaction"); + let initialize_tx_hash = eth_client + .send_eip1559_transaction(initialize_tx, &deployer_private_key) + .await .expect("Failed to send initialize transaction"); wait_for_transaction_receipt(initialize_tx_hash, eth_client).await; diff --git a/crates/l2/proposer/l1_watcher.rs b/crates/l2/proposer/l1_watcher.rs index a61cdd362..179bc0c20 100644 --- a/crates/l2/proposer/l1_watcher.rs +++ b/crates/l2/proposer/l1_watcher.rs @@ -2,19 +2,17 @@ use crate::{ proposer::errors::L1WatcherError, utils::{ config::{eth::EthConfig, l1_watcher::L1WatcherConfig}, - eth_client::{transaction::PayloadRLPEncode, EthClient}, + eth_client::{eth_sender::Overrides, EthClient}, }, }; +use bytes::Bytes; use ethereum_rust_blockchain::{constants::TX_GAS_COST, mempool}; -use ethereum_rust_core::types::{ - PrivilegedL2Transaction, PrivilegedTxType, Transaction, TxKind, TxType, -}; -use ethereum_rust_rlp::encode::RLPEncode; +use ethereum_rust_core::types::PrivilegedTxType; +use ethereum_rust_core::types::{Signable, Transaction}; use ethereum_rust_rpc::types::receipt::RpcLog; use ethereum_rust_storage::Store; use ethereum_types::{Address, BigEndianHash, H256, U256}; -use keccak_hash::keccak; -use libsecp256k1::{sign, Message, SecretKey}; +use secp256k1::SecretKey; use std::{cmp::min, ops::Mul, time::Duration}; use tokio::time::sleep; use tracing::{debug, info, warn}; @@ -128,42 +126,35 @@ impl L1Watcher { info!("Initiating mint transaction for {beneficiary:#x} with value {mint_value:#x}",); - let mut mint_transaction = PrivilegedL2Transaction { - tx_type: PrivilegedTxType::Deposit, - to: TxKind::Call(beneficiary), - chain_id: store - .get_chain_config() - .map_err(|e| L1WatcherError::FailedToRetrieveChainConfig(e.to_string()))? - .chain_id, - ..Default::default() - }; - - mint_transaction.nonce = operator_nonce; - operator_nonce += 1; - - mint_transaction.max_fee_per_gas = self.eth_client.get_gas_price().await?.as_u64(); - // TODO(IMPORTANT): gas_limit should come in the log and must - // not be calculated in here. The reason for this is that the - // gas_limit for this transaction is payed by the caller in - // the L1 as part of the deposited funds. - mint_transaction.gas_limit = TX_GAS_COST.mul(2); - mint_transaction.value = mint_value; - - let mut payload = vec![TxType::Privileged as u8]; - payload.append(mint_transaction.encode_payload_to_vec().as_mut()); + let mut mint_transaction = self + .eth_client + .build_privileged_transaction( + PrivilegedTxType::Deposit, + beneficiary, + Bytes::new(), + Overrides { + chain_id: Some( + store + .get_chain_config() + .map_err(|e| { + L1WatcherError::FailedToRetrieveChainConfig(e.to_string()) + })? + .chain_id, + ), + nonce: Some(operator_nonce), + value: Some(mint_value), + // TODO(IMPORTANT): gas_limit should come in the log and must + // not be calculated in here. The reason for this is that the + // gas_limit for this transaction is payed by the caller in + // the L1 as part of the deposited funds. + gas_limit: Some(TX_GAS_COST.mul(2)), + ..Default::default() + }, + ) + .await?; + mint_transaction.sign_inplace(&self.l2_proposer_pk); - let data = Message::parse(&keccak(payload).0); - let signature = sign(&data, &self.l2_proposer_pk); - - mint_transaction.signature_r = U256::from(signature.0.r.b32()); - mint_transaction.signature_s = U256::from(signature.0.s.b32()); - mint_transaction.signature_y_parity = signature.1.serialize() != 0; - - let mut encoded_tx = Vec::new(); - mint_transaction.encode(&mut encoded_tx); - - let mut data = vec![TxType::Privileged as u8]; - data.append(&mut encoded_tx); + operator_nonce += 1; match mempool::add_transaction( Transaction::PrivilegedL2Transaction(mint_transaction), diff --git a/crates/l2/proposer/mod.rs b/crates/l2/proposer/mod.rs index 2a9d9ab1e..056539aa9 100644 --- a/crates/l2/proposer/mod.rs +++ b/crates/l2/proposer/mod.rs @@ -1,26 +1,22 @@ use crate::utils::{ config::{eth::EthConfig, proposer::ProposerConfig, read_env_file}, - eth_client::{transaction::blob_from_bytes, EthClient}, + eth_client::{eth_sender::Overrides, transaction::blob_from_bytes, EthClient}, merkle_tree::merkelize, }; use bytes::Bytes; use c_kzg::{Bytes48, KzgSettings}; use errors::ProposerError; -use ethereum_rust_blockchain::constants::TX_GAS_COST; use ethereum_rust_core::types::{ - BlobsBundle, Block, EIP1559Transaction, EIP4844Transaction, GenericTransaction, - PrivilegedL2Transaction, PrivilegedTxType, Transaction, TxKind, BYTES_PER_BLOB, + BlobsBundle, Block, PrivilegedL2Transaction, PrivilegedTxType, Transaction, TxKind, + BYTES_PER_BLOB, }; use ethereum_rust_dev::utils::engine_client::{config::EngineApiConfig, EngineClient}; -use ethereum_rust_rpc::types::{ - fork_choice::{ForkChoiceState, PayloadAttributesV3}, - transaction::WrappedEIP4844Transaction, -}; +use ethereum_rust_rpc::types::fork_choice::{ForkChoiceState, PayloadAttributesV3}; use ethereum_rust_storage::Store; use ethereum_rust_vm::{evm_state, execute_block, get_state_transitions}; use ethereum_types::{Address, H256, U256}; use keccak_hash::keccak; -use libsecp256k1::SecretKey; +use secp256k1::SecretKey; use sha2::{Digest, Sha256}; use state_diff::{AccountStateDiff, DepositLog, StateDiff, WithdrawalLog}; use std::{ @@ -432,26 +428,6 @@ impl Proposer { calldata.extend(withdrawal_logs_merkle_root.0); calldata.extend(deposit_logs_hash.0); - let mut tx = EIP4844Transaction { - to: self.on_chain_proposer_address, - data: Bytes::from(calldata), - max_fee_per_gas: self.eth_client.get_gas_price().await?.as_u64(), - nonce: self.eth_client.get_nonce(self.l1_address).await?, - chain_id: self.eth_client.get_chain_id().await?.as_u64(), - blob_versioned_hashes: vec![H256::from_slice(&blob_versioned_hash)], - max_fee_per_blob_gas: U256::from_dec_str("100000000000000").unwrap(), - ..Default::default() - }; - - let mut generic_tx = GenericTransaction::from(tx.clone()); - generic_tx.from = self.l1_address; - - tx.gas = self - .eth_client - .estimate_gas(generic_tx) - .await? - .saturating_add(TX_GAS_COST); - let mut buf = [0u8; BYTES_PER_BLOB]; buf.copy_from_slice( blob_from_bytes(blob_data) @@ -460,18 +436,29 @@ impl Proposer { .as_slice(), ); - let mut wrapped_tx = WrappedEIP4844Transaction { - tx, - blobs_bundle: BlobsBundle { - blobs: vec![buf], - commitments: vec![commitment], - proofs: vec![proof], - }, + let blobs_bundle = BlobsBundle { + blobs: vec![buf], + commitments: vec![commitment], + proofs: vec![proof], }; + let wrapped_tx = self + .eth_client + .build_eip4844_transaction( + self.on_chain_proposer_address, + Bytes::from(calldata), + Overrides { + from: Some(self.l1_address), + gas_price_per_blob: Some(U256::from_dec_str("100000000000000").unwrap()), + ..Default::default() + }, + blobs_bundle, + ) + .await + .map_err(ProposerError::from)?; let commit_tx_hash = self .eth_client - .send_eip4844_transaction(&mut wrapped_tx, self.l1_private_key) + .send_eip4844_transaction(wrapped_tx, &self.l1_private_key) .await .map_err(ProposerError::from)?; @@ -500,14 +487,26 @@ impl Proposer { let mut block_number_bytes = [0_u8; 32]; U256::from(block_number).to_big_endian(&mut block_number_bytes); calldata.extend(block_number_bytes); - calldata.extend(H256::from_low_u64_be(32).as_bytes()); + calldata.extend(H256::from_low_u64_be(64).as_bytes()); calldata.extend(H256::from_low_u64_be(block_proof.len() as u64).as_bytes()); calldata.extend(block_proof); let leading_zeros = 32 - ((calldata.len() - 4) % 32); calldata.extend(vec![0; leading_zeros]); + let verify_tx = self + .eth_client + .build_eip1559_transaction( + self.on_chain_proposer_address, + calldata.into(), + Overrides { + from: Some(self.l1_address), + ..Default::default() + }, + ) + .await?; let verify_tx_hash = self - .send_transaction_with_calldata(self.on_chain_proposer_address, calldata.into()) + .eth_client + .send_eip1559_transaction(verify_tx, &self.l1_private_key) .await?; info!("Proof sent: {verify_tx_hash:#x}"); @@ -523,33 +522,4 @@ impl Proposer { Ok(verify_tx_hash) } - - async fn send_transaction_with_calldata( - &self, - to: Address, - calldata: Bytes, - ) -> Result { - let mut tx = EIP1559Transaction { - to: TxKind::Call(to), - data: calldata, - max_fee_per_gas: self.eth_client.get_gas_price().await?.as_u64(), - nonce: self.eth_client.get_nonce(self.l1_address).await?, - chain_id: self.eth_client.get_chain_id().await?.as_u64(), - ..Default::default() - }; - - let mut generic_tx = GenericTransaction::from(tx.clone()); - generic_tx.from = self.l1_address; - - tx.gas_limit = self - .eth_client - .estimate_gas(generic_tx) - .await? - .saturating_add(TX_GAS_COST); - - self.eth_client - .send_eip1559_transaction(&mut tx, self.l1_private_key) - .await - .map_err(ProposerError::from) - } } diff --git a/crates/l2/sdk/Cargo.toml b/crates/l2/sdk/Cargo.toml index 5cdb1ddd4..76f61c4a8 100644 --- a/crates/l2/sdk/Cargo.toml +++ b/crates/l2/sdk/Cargo.toml @@ -12,7 +12,7 @@ ethereum-types.workspace = true tokio.workspace = true hex.workspace = true keccak-hash = "0.11.0" -libsecp256k1 = "0.7.1" +secp256k1.workspace = true itertools = "0.13.0" [lib] diff --git a/crates/l2/sdk/src/sdk.rs b/crates/l2/sdk/src/sdk.rs index f01e4ec52..145a9cf47 100644 --- a/crates/l2/sdk/src/sdk.rs +++ b/crates/l2/sdk/src/sdk.rs @@ -1,4 +1,4 @@ -use ethereum_rust_core::types::{PrivilegedL2Transaction, PrivilegedTxType, Transaction, TxKind}; +use ethereum_rust_core::types::{PrivilegedTxType, Transaction}; use ethereum_rust_l2::utils::{ eth_client::{ errors::{EthClientError, GetTransactionReceiptError}, @@ -11,7 +11,7 @@ use ethereum_rust_rpc::types::{block::BlockBodyWrapper, receipt::RpcReceipt}; use ethereum_types::{Address, H160, H256, U256}; use itertools::Itertools; use keccak_hash::keccak; -use libsecp256k1::SecretKey; +use secp256k1::SecretKey; // 0x6bf26397c5676a208d5c4e5f35cb479bacbbe454 pub const DEFAULT_BRIDGE_ADDRESS: Address = H160([ @@ -68,18 +68,18 @@ pub async fn transfer( from = from, to = to ); - client - .send( + let tx = client + .build_eip1559_transaction( + to, Default::default(), - from, - TxKind::Call(to), - private_key, Overrides { value: Some(amount), + from: Some(from), ..Default::default() }, ) - .await + .await?; + client.send_eip1559_transaction(tx, &private_key).await } pub async fn deposit( @@ -98,19 +98,23 @@ pub async fn withdraw( from_pk: SecretKey, proposer_client: &EthClient, ) -> Result { - let withdraw_transaction = PrivilegedL2Transaction { - to: TxKind::Call(from), - value: amount, - chain_id: proposer_client.get_chain_id().await?.as_u64(), - nonce: proposer_client.get_nonce(from).await?, - max_fee_per_gas: 800000000, - tx_type: PrivilegedTxType::Withdrawal, - gas_limit: 21000 * 2, - ..Default::default() - }; + let withdraw_transaction = proposer_client + .build_privileged_transaction( + PrivilegedTxType::Withdrawal, + from, + Default::default(), + Overrides { + value: Some(amount), + from: Some(from), + gas_price: Some(800000000), + gas_limit: Some(21000 * 2), + ..Default::default() + }, + ) + .await?; proposer_client - .send_privileged_l2_transaction(withdraw_transaction, from_pk) + .send_privileged_l2_transaction(withdraw_transaction, &from_pk) .await } @@ -187,14 +191,19 @@ pub async fn claim_withdraw( hex::encode(&claim_withdrawal_data) ); - eth_client - .send( + let claim_tx = eth_client + .build_eip1559_transaction( + bridge_address(), claim_withdrawal_data.into(), - from, - TxKind::Call(bridge_address()), - from_pk, - Overrides::default(), + Overrides { + from: Some(from), + ..Default::default() + }, ) + .await?; + + eth_client + .send_eip1559_transaction(claim_tx, &from_pk) .await } diff --git a/crates/l2/tests/tests.rs b/crates/l2/tests/tests.rs index ee01215b7..0f341c976 100644 --- a/crates/l2/tests/tests.rs +++ b/crates/l2/tests/tests.rs @@ -1,7 +1,7 @@ use ethereum_rust_l2::utils::eth_client::EthClient; use ethereum_types::{Address, H160, U256}; use keccak_hash::H256; -use libsecp256k1::{PublicKey, SecretKey}; +use secp256k1::SecretKey; use std::str::FromStr; const DEFAULT_ETH_URL: &str = "http://localhost:8545"; @@ -17,8 +17,8 @@ const DEFAULT_L1_RICH_WALLET_PRIVATE_KEY: H256 = H256([ 0x33, 0xf6, 0x6b, 0x39, 0x60, 0xd9, 0xe6, 0x22, 0x9c, 0x1c, 0xd2, 0x14, 0xed, 0x3b, 0xbe, 0x31, ]); -const L1_GAS_COST_MAX_DELTA: U256 = U256([1_000_000_000, 0, 0, 0]); -const L2_GAS_COST_MAX_DELTA: U256 = U256([1_000_000_000_000, 0, 0, 0]); +const L1_GAS_COST_MAX_DELTA: U256 = U256([100_000_000_000_000, 0, 0, 0]); +const L2_GAS_COST_MAX_DELTA: U256 = U256([100_000_000_000_000, 0, 0, 0]); /// Test the full flow of depositing, transferring, and withdrawing funds /// from L1 to L2 and back. @@ -273,13 +273,12 @@ fn l1_rich_wallet_address() -> Address { fn l1_rich_wallet_private_key() -> SecretKey { std::env::var("L1_RICH_WALLET_PRIVATE_KEY") - .map(|s| SecretKey::parse(H256::from_str(&s).unwrap().as_fixed_bytes()).unwrap()) - .unwrap_or(SecretKey::parse(DEFAULT_L1_RICH_WALLET_PRIVATE_KEY.as_fixed_bytes()).unwrap()) + .map(|s| SecretKey::from_slice(H256::from_str(&s).unwrap().as_bytes()).unwrap()) + .unwrap_or(SecretKey::from_slice(DEFAULT_L1_RICH_WALLET_PRIVATE_KEY.as_bytes()).unwrap()) } fn random_account() -> (Address, SecretKey) { - let sk = SecretKey::random(&mut rand::thread_rng()); - let pk = PublicKey::from_secret_key(&sk); - let address = Address::from_slice(pk.serialize()[45..].try_into().unwrap()); + let (sk, pk) = secp256k1::generate_keypair(&mut rand::thread_rng()); + let address = Address::from(keccak_hash::keccak(&pk.serialize_uncompressed()[1..])); (address, sk) } diff --git a/crates/l2/utils/config/l1_watcher.rs b/crates/l2/utils/config/l1_watcher.rs index 9e82ae276..00a34a3f5 100644 --- a/crates/l2/utils/config/l1_watcher.rs +++ b/crates/l2/utils/config/l1_watcher.rs @@ -1,6 +1,6 @@ use crate::utils::secret_key_deserializer; use ethereum_types::{Address, H256, U256}; -use libsecp256k1::SecretKey; +use secp256k1::SecretKey; use serde::Deserialize; use super::errors::ConfigError; diff --git a/crates/l2/utils/config/proposer.rs b/crates/l2/utils/config/proposer.rs index 26d245e9c..e61bb5848 100644 --- a/crates/l2/utils/config/proposer.rs +++ b/crates/l2/utils/config/proposer.rs @@ -1,6 +1,6 @@ use crate::utils::secret_key_deserializer; use ethereum_types::Address; -use libsecp256k1::SecretKey; +use secp256k1::SecretKey; use serde::Deserialize; use super::errors::ConfigError; diff --git a/crates/l2/utils/eth_client/errors.rs b/crates/l2/utils/eth_client/errors.rs index 26915ee7a..80db0773a 100644 --- a/crates/l2/utils/eth_client/errors.rs +++ b/crates/l2/utils/eth_client/errors.rs @@ -30,6 +30,8 @@ pub enum EthClientError { GetBalanceError(#[from] GetBalanceError), #[error("eth_getTransactionByHash request error: {0}")] GetTransactionByHashError(#[from] GetTransactionByHashError), + #[error("Unreachable nonce")] + UnrecheableNonce, } #[derive(Debug, thiserror::Error)] diff --git a/crates/l2/utils/eth_client/eth_sender.rs b/crates/l2/utils/eth_client/eth_sender.rs index 82174b943..e7b6a89c2 100644 --- a/crates/l2/utils/eth_client/eth_sender.rs +++ b/crates/l2/utils/eth_client/eth_sender.rs @@ -3,12 +3,12 @@ use crate::utils::eth_client::{ EthClient, RpcResponse, }; use bytes::Bytes; -use ethereum_rust_core::types::{EIP1559Transaction, GenericTransaction, TxKind, TxType}; +use ethereum_rust_core::types::{GenericTransaction, TxKind}; use ethereum_rust_rlp::encode::RLPEncode; use ethereum_rust_rpc::utils::{RpcRequest, RpcRequestId}; use ethereum_types::{Address, U256}; use keccak_hash::{keccak, H256}; -use libsecp256k1::SecretKey; +use secp256k1::SecretKey; use serde_json::json; #[derive(Default, Clone)] @@ -20,6 +20,8 @@ pub struct Overrides { pub gas_limit: Option, pub gas_price: Option, pub priority_gas_price: Option, + pub access_list: Vec<(Address, Vec)>, + pub gas_price_per_blob: Option, } impl EthClient { @@ -70,22 +72,6 @@ impl EthClient { } } - pub async fn send( - &self, - calldata: Bytes, - from: Address, - to: TxKind, - sender_private_key: SecretKey, - overrides: Overrides, - ) -> Result { - let mut tx = self - .make_eip1559_transaction(to, from, calldata, overrides) - .await?; - - self.send_eip1559_transaction(&mut tx, sender_private_key) - .await - } - pub async fn deploy( &self, deployer: Address, @@ -93,14 +79,12 @@ impl EthClient { init_code: Bytes, overrides: Overrides, ) -> Result<(H256, Address), EthClientError> { + let mut deploy_tx = self + .build_eip1559_transaction(Address::zero(), init_code, overrides) + .await?; + deploy_tx.to = TxKind::Create; let deploy_tx_hash = self - .send( - init_code, - deployer, - TxKind::Create, - deployer_private_key, - overrides, - ) + .send_eip1559_transaction(deploy_tx, &deployer_private_key) .await?; let encoded_from = deployer.encode_to_vec(); @@ -113,42 +97,4 @@ impl EthClient { Ok((deploy_tx_hash, deployed_address)) } - - async fn make_eip1559_transaction( - &self, - to: TxKind, - from: Address, - data: Bytes, - overrides: Overrides, - ) -> Result { - let generic_transaction = GenericTransaction { - r#type: TxType::EIP1559, - from, - to: to.clone(), - input: data.clone(), - value: overrides.value.unwrap_or_default(), - nonce: overrides.nonce.or(self.get_nonce(from).await.ok()), - ..Default::default() - }; - - let mut tx = EIP1559Transaction { - to, - data, - value: overrides.value.unwrap_or_default(), - chain_id: overrides - .chain_id - .unwrap_or(self.get_chain_id().await?.as_u64()), - nonce: overrides.nonce.unwrap_or(self.get_nonce(from).await?), - max_fee_per_gas: overrides - .gas_price - .unwrap_or(self.get_gas_price().await?.as_u64()), - max_priority_fee_per_gas: overrides.priority_gas_price.unwrap_or_default(), - ..Default::default() - }; - tx.gas_limit = overrides - .gas_limit - .unwrap_or(self.estimate_gas(generic_transaction).await?); - - Ok(tx) - } } diff --git a/crates/l2/utils/eth_client/mod.rs b/crates/l2/utils/eth_client/mod.rs index a62e7bede..7462fd1f3 100644 --- a/crates/l2/utils/eth_client/mod.rs +++ b/crates/l2/utils/eth_client/mod.rs @@ -1,11 +1,14 @@ use crate::utils::config::eth::EthConfig; +use bytes::Bytes; use errors::{ EstimateGasPriceError, EthClientError, GetBalanceError, GetBlockByHashError, GetBlockNumberError, GetGasPriceError, GetLogsError, GetNonceError, GetTransactionByHashError, GetTransactionReceiptError, SendRawTransactionError, }; +use eth_sender::Overrides; use ethereum_rust_core::types::{ - EIP1559Transaction, GenericTransaction, PrivilegedL2Transaction, TxKind, TxType, + BlobsBundle, EIP1559Transaction, EIP4844Transaction, GenericTransaction, + PrivilegedL2Transaction, PrivilegedTxType, Signable, TxKind, TxType, }; use ethereum_rust_rlp::encode::RLPEncode; use ethereum_rust_rpc::{ @@ -17,12 +20,11 @@ use ethereum_rust_rpc::{ utils::{RpcErrorResponse, RpcRequest, RpcRequestId, RpcSuccessResponse}, }; use ethereum_types::{Address, H256, U256}; -use keccak_hash::keccak; -use libsecp256k1::{sign, Message, SecretKey}; use reqwest::Client; +use secp256k1::SecretKey; use serde::{Deserialize, Serialize}; use serde_json::json; -use transaction::PayloadRLPEncode; +use sha2::{Digest, Sha256}; pub mod errors; pub mod eth_sender; @@ -93,42 +95,23 @@ impl EthClient { pub async fn send_eip1559_transaction( &self, - tx: &mut EIP1559Transaction, - private_key: SecretKey, + tx: EIP1559Transaction, + private_key: &SecretKey, ) -> Result { - let mut payload = vec![TxType::EIP1559 as u8]; - payload.append(tx.encode_payload_to_vec().as_mut()); + let signed_tx = tx.sign(private_key); - let data = Message::parse(&keccak(payload).0); - let signature = sign(&data, &private_key); + let mut encoded_tx = signed_tx.encode_to_vec(); + encoded_tx.insert(0, TxType::EIP1559 as u8); - tx.signature_r = U256::from(signature.0.r.b32()); - tx.signature_s = U256::from(signature.0.s.b32()); - tx.signature_y_parity = signature.1.serialize() != 0; - - let mut encoded_tx = Vec::new(); - tx.encode(&mut encoded_tx); - - let mut data = vec![TxType::EIP1559 as u8]; - data.append(&mut encoded_tx); - - self.send_raw_transaction(data.as_slice()).await + self.send_raw_transaction(encoded_tx.as_slice()).await } pub async fn send_eip4844_transaction( &self, - wrapped_tx: &mut WrappedEIP4844Transaction, - private_key: SecretKey, + mut wrapped_tx: WrappedEIP4844Transaction, + private_key: &SecretKey, ) -> Result { - let mut payload = vec![TxType::EIP4844 as u8]; - payload.append(wrapped_tx.tx.encode_payload_to_vec().as_mut()); - - let data = Message::parse(&keccak(payload).0); - let signature = sign(&data, &private_key); - - wrapped_tx.tx.signature_r = U256::from(signature.0.r.b32()); - wrapped_tx.tx.signature_s = U256::from(signature.0.s.b32()); - wrapped_tx.tx.signature_y_parity = signature.1.serialize() != 0; + wrapped_tx.tx.sign_inplace(private_key); let mut encoded_tx = wrapped_tx.encode_to_vec(); encoded_tx.insert(0, TxType::EIP4844 as u8); @@ -138,26 +121,15 @@ impl EthClient { pub async fn send_privileged_l2_transaction( &self, - mut tx: PrivilegedL2Transaction, - private_key: SecretKey, + tx: PrivilegedL2Transaction, + private_key: &SecretKey, ) -> Result { - let mut payload = vec![TxType::Privileged as u8]; - payload.append(tx.encode_payload_to_vec().as_mut()); - - let data = Message::parse(&keccak(payload).0); - let signature = sign(&data, &private_key); - - tx.signature_r = U256::from(signature.0.r.b32()); - tx.signature_s = U256::from(signature.0.s.b32()); - tx.signature_y_parity = signature.1.serialize() != 0; + let signed_tx = tx.sign(private_key); - let mut encoded_tx = Vec::new(); - tx.encode(&mut encoded_tx); + let mut encoded_tx = signed_tx.encode_to_vec(); + encoded_tx.insert(0, TxType::Privileged as u8); - let mut data = vec![TxType::Privileged as u8]; - data.append(&mut encoded_tx); - - self.send_raw_transaction(data.as_slice()).await + self.send_raw_transaction(encoded_tx.as_slice()).await } pub async fn estimate_gas( @@ -413,6 +385,164 @@ impl EthClient { Err(error) => Err(error), } } + + /// Build an EIP1559 transaction with the given parameters. + /// Either `overrides.nonce` or `overrides.from` must be provided. + /// If `overrides.gas_price`, `overrides.chain_id` or `overrides.gas_price` + /// are not provided, the client will fetch them from the network. + /// If `overrides.gas_limit` is not provided, the client will estimate the tx cost. + pub async fn build_eip1559_transaction( + &self, + to: Address, + calldata: Bytes, + overrides: Overrides, + ) -> Result { + let mut tx = EIP1559Transaction { + to: TxKind::Call(to), + chain_id: if let Some(chain_id) = overrides.chain_id { + chain_id + } else { + self.get_chain_id().await?.as_u64() + }, + nonce: self.get_nonce_from_overrides(&overrides).await?, + max_priority_fee_per_gas: overrides.priority_gas_price.unwrap_or_default(), + max_fee_per_gas: if let Some(gas_price) = overrides.gas_price { + gas_price + } else { + self.get_gas_price().await?.as_u64() + }, + value: overrides.value.unwrap_or_default(), + data: calldata, + access_list: overrides.access_list, + ..Default::default() + }; + + tx.gas_limit = if let Some(gas_limit) = overrides.gas_limit { + gas_limit + } else { + let mut generic_tx = GenericTransaction::from(tx.clone()); + if let Some(from) = overrides.from { + generic_tx.from = from; + } + self.estimate_gas(generic_tx).await? + }; + + Ok(tx) + } + + /// Build an EIP4844 transaction with the given parameters. + /// Either `overrides.nonce` or `overrides.from` must be provided. + /// If `overrides.gas_price`, `overrides.chain_id` or `overrides.gas_price` + /// are not provided, the client will fetch them from the network. + /// If `overrides.gas_limit` is not provided, the client will estimate the tx cost. + pub async fn build_eip4844_transaction( + &self, + to: Address, + calldata: Bytes, + overrides: Overrides, + blobs_bundle: BlobsBundle, + ) -> Result { + let blob_versioned_hashes = blobs_bundle + .commitments + .iter() + .map(|commitment| { + let mut hasher = Sha256::new(); + hasher.update(commitment); + let mut blob_versioned_hash = hasher.finalize(); + blob_versioned_hash[0] = 0x01; // EIP-4844 versioning + H256::from_slice(blob_versioned_hash.as_slice()) + }) + .collect::>(); + + let mut tx = EIP4844Transaction { + to, + chain_id: if let Some(chain_id) = overrides.chain_id { + chain_id + } else { + self.get_chain_id().await?.as_u64() + }, + nonce: self.get_nonce_from_overrides(&overrides).await?, + max_priority_fee_per_gas: overrides.priority_gas_price.unwrap_or_default(), + max_fee_per_gas: if let Some(gas_price) = overrides.gas_price { + gas_price + } else { + self.get_gas_price().await?.as_u64() + }, + value: overrides.value.unwrap_or_default(), + data: calldata, + access_list: overrides.access_list, + max_fee_per_blob_gas: overrides.gas_price_per_blob.unwrap_or_default(), + blob_versioned_hashes, + ..Default::default() + }; + + tx.gas = if let Some(gas_limit) = overrides.gas_limit { + gas_limit + } else { + let mut generic_tx = GenericTransaction::from(tx.clone()); + if let Some(from) = overrides.from { + generic_tx.from = from; + } + self.estimate_gas(generic_tx).await? + }; + + Ok(WrappedEIP4844Transaction { tx, blobs_bundle }) + } + + /// Build a PrivilegedL2 transaction with the given parameters. + /// Either `overrides.nonce` or `overrides.from` must be provided. + /// If `overrides.gas_price`, `overrides.chain_id` or `overrides.gas_price` + /// are not provided, the client will fetch them from the network. + /// If `overrides.gas_limit` is not provided, the client will estimate the tx cost. + pub async fn build_privileged_transaction( + &self, + tx_type: PrivilegedTxType, + to: Address, + calldata: Bytes, + overrides: Overrides, + ) -> Result { + let mut tx = PrivilegedL2Transaction { + tx_type, + to: TxKind::Call(to), + chain_id: if let Some(chain_id) = overrides.chain_id { + chain_id + } else { + self.get_chain_id().await?.as_u64() + }, + nonce: self.get_nonce_from_overrides(&overrides).await?, + max_priority_fee_per_gas: overrides.priority_gas_price.unwrap_or_default(), + max_fee_per_gas: if let Some(gas_price) = overrides.gas_price { + gas_price + } else { + self.get_gas_price().await?.as_u64() + }, + value: overrides.value.unwrap_or_default(), + data: calldata, + access_list: overrides.access_list, + ..Default::default() + }; + + tx.gas_limit = if let Some(gas_limit) = overrides.gas_limit { + gas_limit + } else { + let mut generic_tx = GenericTransaction::from(tx.clone()); + if let Some(from) = overrides.from { + generic_tx.from = from; + } + self.estimate_gas(generic_tx).await? + }; + + Ok(tx) + } + + async fn get_nonce_from_overrides(&self, overrides: &Overrides) -> Result { + if let Some(nonce) = overrides.nonce { + return Ok(nonce); + } + + let address = overrides.from.ok_or(EthClientError::UnrecheableNonce)?; + self.get_nonce(address).await + } } #[derive(Serialize, Deserialize, Debug)] diff --git a/crates/l2/utils/eth_client/transaction.rs b/crates/l2/utils/eth_client/transaction.rs index 7252d811a..212538857 100644 --- a/crates/l2/utils/eth_client/transaction.rs +++ b/crates/l2/utils/eth_client/transaction.rs @@ -1,7 +1,5 @@ use bytes::Bytes; use c_kzg::{Blob, BYTES_PER_BLOB}; -use ethereum_rust_core::types::{EIP1559Transaction, EIP4844Transaction, PrivilegedL2Transaction}; -use ethereum_rust_rlp::structs::Encoder; pub fn blob_from_bytes(bytes: Bytes) -> Result { // We set the first byte of every 32-bytes chunk to 0x00 @@ -24,63 +22,3 @@ pub fn blob_from_bytes(bytes: Bytes) -> Result { Blob::from_bytes(&buf) } - -pub trait PayloadRLPEncode { - fn encode_payload(&self, buf: &mut dyn bytes::BufMut); - fn encode_payload_to_vec(&self) -> Vec { - let mut buf = Vec::new(); - self.encode_payload(&mut buf); - buf - } -} - -impl PayloadRLPEncode for EIP1559Transaction { - fn encode_payload(&self, buf: &mut dyn bytes::BufMut) { - Encoder::new(buf) - .encode_field(&self.chain_id) - .encode_field(&self.nonce) - .encode_field(&self.max_priority_fee_per_gas) - .encode_field(&self.max_fee_per_gas) - .encode_field(&self.gas_limit) - .encode_field(&self.to) - .encode_field(&self.value) - .encode_field(&self.data) - .encode_field(&self.access_list) - .finish(); - } -} - -impl PayloadRLPEncode for EIP4844Transaction { - fn encode_payload(&self, buf: &mut dyn bytes::BufMut) { - Encoder::new(buf) - .encode_field(&self.chain_id) - .encode_field(&self.nonce) - .encode_field(&self.max_priority_fee_per_gas) - .encode_field(&self.max_fee_per_gas) - .encode_field(&self.gas) - .encode_field(&self.to) - .encode_field(&self.value) - .encode_field(&self.data) - .encode_field(&self.access_list) - .encode_field(&self.max_fee_per_blob_gas) - .encode_field(&self.blob_versioned_hashes) - .finish(); - } -} - -impl PayloadRLPEncode for PrivilegedL2Transaction { - fn encode_payload(&self, buf: &mut dyn bytes::BufMut) { - Encoder::new(buf) - .encode_field(&self.chain_id) - .encode_field(&self.nonce) - .encode_field(&self.max_priority_fee_per_gas) - .encode_field(&self.max_fee_per_gas) - .encode_field(&self.gas_limit) - .encode_field(&self.to) - .encode_field(&self.value) - .encode_field(&self.data) - .encode_field(&self.access_list) - .encode_field(&self.tx_type) - .finish(); - } -} diff --git a/crates/l2/utils/mod.rs b/crates/l2/utils/mod.rs index 6b6123054..c1de728c3 100644 --- a/crates/l2/utils/mod.rs +++ b/crates/l2/utils/mod.rs @@ -1,5 +1,5 @@ use keccak_hash::H256; -use libsecp256k1::SecretKey; +use secp256k1::SecretKey; use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub mod config; @@ -12,13 +12,13 @@ where D: Deserializer<'de>, { let hex = H256::deserialize(deserializer)?; - SecretKey::parse(hex.as_fixed_bytes()).map_err(serde::de::Error::custom) + SecretKey::from_slice(hex.as_bytes()).map_err(serde::de::Error::custom) } pub fn secret_key_serializer(secret_key: &SecretKey, serializer: S) -> Result where S: Serializer, { - let hex = H256::from_slice(&secret_key.serialize()); + let hex = H256::from_slice(&secret_key.secret_bytes()); hex.serialize(serializer) } From 30232b40b0da803dea38fc94095121a89d76798d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jerem=C3=ADas=20Salom=C3=B3n?= <48994069+JereSalo@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:03:48 -0300 Subject: [PATCH 05/17] fix(levm): handle expects (#1109) **Motivation** **Description** Partially Closes #1074 --------- Co-authored-by: Juani Medone Co-authored-by: maximopalopoli Co-authored-by: Javier Chatruc Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri Co-authored-by: Maximo Palopoli <96491141+maximopalopoli@users.noreply.github.com> --- crates/vm/levm/Cargo.toml | 15 ++++++-------- crates/vm/levm/src/db.rs | 17 +++++++++++++--- crates/vm/levm/src/errors.rs | 20 +++++++++---------- crates/vm/levm/src/opcode_handlers/block.rs | 4 +--- .../stack_memory_storage_flow.rs | 7 ++----- crates/vm/levm/src/vm.rs | 10 ++++++---- crates/vm/levm/tests/tests.rs | 3 ++- 7 files changed, 40 insertions(+), 36 deletions(-) diff --git a/crates/vm/levm/Cargo.toml b/crates/vm/levm/Cargo.toml index fae47df14..5baa52c41 100644 --- a/crates/vm/levm/Cargo.toml +++ b/crates/vm/levm/Cargo.toml @@ -24,20 +24,17 @@ spinoff = "0.8.0" [features] ethereum_foundation_tests = [] -# [lints.rust] -# unsafe_code = "forbid" -# warnings = "warn" -# rust_2018_idioms = "warn" +[lints.rust] +unsafe_code = "forbid" +warnings = "warn" +rust_2018_idioms = "warn" [lints.clippy] as_conversions = "deny" # unwrap_used = "deny" -# expect_used = "deny" +expect_used = "deny" # panic = "deny" unnecessary_cast = "warn" -# deref_by_slicing = "warn" -# indexing_slicing = "warn" -# manual_unwrap_or = "warn" -# manual_unwrap_or_default = "warn" + [lib] path = "./src/lib.rs" diff --git a/crates/vm/levm/src/db.rs b/crates/vm/levm/src/db.rs index 83f8f8816..19ce3e645 100644 --- a/crates/vm/levm/src/db.rs +++ b/crates/vm/levm/src/db.rs @@ -1,4 +1,7 @@ -use crate::account::{Account, AccountInfo, StorageSlot}; +use crate::{ + account::{Account, AccountInfo, StorageSlot}, + errors::{InternalError, VMError}, +}; use ethereum_rust_core::{Address, H256, U256}; use std::collections::HashMap; @@ -93,12 +96,20 @@ impl Cache { self.accounts.insert(*address, account.clone()); } - pub fn write_account_storage(&mut self, address: &Address, key: H256, slot: StorageSlot) { + pub fn write_account_storage( + &mut self, + address: &Address, + key: H256, + slot: StorageSlot, + ) -> Result<(), VMError> { self.accounts .get_mut(address) - .expect("Account should have been cached") + .ok_or(VMError::Internal( + InternalError::AccountShouldHaveBeenCached, + ))? .storage .insert(key, slot); + Ok(()) } pub fn increment_account_nonce(&mut self, address: &Address) { diff --git a/crates/vm/levm/src/errors.rs b/crates/vm/levm/src/errors.rs index cd9250c00..d4e367c61 100644 --- a/crates/vm/levm/src/errors.rs +++ b/crates/vm/levm/src/errors.rs @@ -54,20 +54,12 @@ pub enum VMError { InvalidInitialByte, #[error("Nonce overflow")] NonceOverflow, - #[error("Internal error")] - InternalError, #[error("Memory load out of bounds")] MemoryLoadOutOfBounds, + #[error("Memory store out of bounds")] + MemoryStoreOutOfBounds, #[error("Gas limit price product overflow")] GasLimitPriceProductOverflow, - #[error("Slicing error")] - SlicingError, - #[error("Indexing error")] - IndexingError, - #[error("Fatal unwrap")] - FatalUnwrap, // I will use this generic error for things that shouldn't fail - #[error("Account should have been cached")] - AccountShouldHaveBeenCached, #[error("Data size overflow")] DataSizeOverflow, #[error("Gas cost overflow")] @@ -84,8 +76,14 @@ pub enum VMError { #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] pub enum InternalError { + #[error("Accound should have been cached")] + AccountShouldHaveBeenCached, #[error("Tried to convert one type to another")] ConversionError, + #[error("Failed computing CREATE2 address")] + CouldNotComputeCreate2Address, + #[error("Division error")] + DivisionError, } #[derive(Debug, Clone)] @@ -94,7 +92,7 @@ pub enum OpcodeSuccess { Result(ResultReason), } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ResultReason { Stop, Revert, diff --git a/crates/vm/levm/src/opcode_handlers/block.rs b/crates/vm/levm/src/opcode_handlers/block.rs index b66a7ab26..8a9709109 100644 --- a/crates/vm/levm/src/opcode_handlers/block.rs +++ b/crates/vm/levm/src/opcode_handlers/block.rs @@ -134,9 +134,7 @@ impl VM { // the current account should have been cached when the contract was called let balance = self - .cache - .get_account(current_call_frame.code_address) - .expect("The current account should always be cached") + .get_account(¤t_call_frame.code_address) .info .balance; diff --git a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs index 841693b84..3086f73b1 100644 --- a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs +++ b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs @@ -154,10 +154,7 @@ impl VM { // If slot is warm (cached) add 100 to base_dynamic_gas base_dynamic_gas += WARM_ADDRESS_ACCESS_COST; - self.cache - .get_storage_slot(address, key) - .expect("Should be already cached") // Because entered the if is_slot_cached - .current_value + self.get_storage_slot(&address, key).current_value } else { // If slot is cold (not cached) add 2100 to base_dynamic_gas base_dynamic_gas += COLD_STORAGE_ACCESS_COST; @@ -222,7 +219,7 @@ impl VM { original_value: storage_slot.original_value, current_value: value, }, - ); + )?; Ok(OpcodeSuccess::Continue) } diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 82c3bea4e..ac3061afb 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -646,7 +646,7 @@ impl VM { sender_address: Address, initialization_code: &Bytes, salt: U256, - ) -> Address { + ) -> Result { let mut hasher = Keccak256::new(); hasher.update(initialization_code.clone()); let initialization_code_hash = hasher.finalize(); @@ -657,7 +657,9 @@ impl VM { hasher.update(sender_address.as_bytes()); hasher.update(salt_bytes); hasher.update(initialization_code_hash); - Address::from_slice(&hasher.finalize()[12..]) + Ok(Address::from_slice(hasher.finalize().get(12..).ok_or( + VMError::Internal(InternalError::CouldNotComputeCreate2Address), + )?)) } fn compute_gas_create( @@ -671,7 +673,7 @@ impl VM { .checked_add(U256::from(31)) .ok_or(VMError::DataSizeOverflow)?) .checked_div(U256::from(32)) - .ok_or(VMError::OverflowInArithmeticOp)?; // '32' will never be zero. The error is wrong but will see later... + .ok_or(VMError::Internal(InternalError::DivisionError))?; // '32' will never be zero let init_code_cost = minimum_word_size .checked_mul(INIT_CODE_WORD_COST) @@ -782,7 +784,7 @@ impl VM { let new_address = match salt { Some(salt) => { - Self::calculate_create2_address(current_call_frame.msg_sender, &code, salt) + Self::calculate_create2_address(current_call_frame.msg_sender, &code, salt)? } None => Self::calculate_create_address( current_call_frame.msg_sender, diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index b12bee073..1edd74d3d 100644 --- a/crates/vm/levm/tests/tests.rs +++ b/crates/vm/levm/tests/tests.rs @@ -3908,7 +3908,8 @@ fn create2_happy_path() { sender_addr, &Bytes::from(initialization_code.clone()), U256::from(salt), - ); + ) + .unwrap(); let operations = vec![ // Store initialization code in memory From 90ab39db03bdf5d6e20edf669e96dd9ca17148d8 Mon Sep 17 00:00:00 2001 From: Federico Borello <156438142+fborello-lambda@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:20:15 -0300 Subject: [PATCH 06/17] chore(l2): add better ui and random salt for deployer (#1137) **Motivation** When the contracts are being deployed there is nothing showing progress. Also, the `salt` used to deploy the contracts via `create2` was fixed. **Description** - Give the possibility to `randomize` the salt, do not randomize it by default with the `.env.example`. - Some tests depend on the salt to be zero. - Add spinners to show progress and colorize the hashes. Output: deployer --------- Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: Javier Chatruc --- crates/l2/.env.example | 2 + crates/l2/contracts/Cargo.toml | 3 ++ crates/l2/contracts/deployer.rs | 86 +++++++++++++++++++++++++++------ 3 files changed, 76 insertions(+), 15 deletions(-) diff --git a/crates/l2/.env.example b/crates/l2/.env.example index e02c9c21d..3a8ffed0f 100644 --- a/crates/l2/.env.example +++ b/crates/l2/.env.example @@ -1,6 +1,8 @@ ETH_RPC_URL=http://localhost:8545 DEPLOYER_ADDRESS=0x3d1e15a1a55578f7c920884a9943b3b35d0d885b DEPLOYER_PRIVATE_KEY=0x385c546456b6a603a1cfcaa9ec9494ba4832da08dd6bcf4de9a71e4a01b74924 +# If set to false, the salt will be randomized. +DEPLOYER_SALT_IS_ZERO=true L1_WATCHER_BRIDGE_ADDRESS=0xca829334b66d86ea2c35ca4c4fa94e99851ef587 L1_WATCHER_TOPICS=0x6f65d68a35457dd88c1f8641be5da191aa122bc76de22ab0789dcc71929d7d37 L1_WATCHER_CHECK_INTERVAL_MS=5000 diff --git a/crates/l2/contracts/Cargo.toml b/crates/l2/contracts/Cargo.toml index ba042c0d5..76df9717f 100644 --- a/crates/l2/contracts/Cargo.toml +++ b/crates/l2/contracts/Cargo.toml @@ -12,6 +12,9 @@ bytes = { version = "1.6.0", features = ["serde"] } secp256k1.workspace = true keccak-hash = "0.10.0" hex = "0.4.3" +spinoff = "0.8.0" +colored = "2.1.0" +lazy_static = "1.5.0" tracing.workspace = true ethereum_rust-l2 = { path = "../../l2" } diff --git a/crates/l2/contracts/deployer.rs b/crates/l2/contracts/deployer.rs index 8c5865784..c8dcad59b 100644 --- a/crates/l2/contracts/deployer.rs +++ b/crates/l2/contracts/deployer.rs @@ -1,4 +1,5 @@ use bytes::Bytes; +use colored::Colorize; use ethereum_rust_core::types::{GAS_LIMIT_ADJUSTMENT_FACTOR, GAS_LIMIT_MINIMUM}; use ethereum_rust_l2::utils::{ config::{read_env_as_lines, read_env_file, write_env}, @@ -7,6 +8,7 @@ use ethereum_rust_l2::utils::{ use ethereum_types::{Address, H160, H256}; use keccak_hash::keccak; use secp256k1::SecretKey; +use spinoff::{spinner, spinners, Color, Spinner}; use std::{ path::{Path, PathBuf}, process::Command, @@ -19,7 +21,10 @@ const DETERMINISTIC_CREATE2_ADDRESS: Address = H160([ 0x4e, 0x59, 0xb4, 0x48, 0x47, 0xb3, 0x79, 0x57, 0x85, 0x88, 0x92, 0x0c, 0xa7, 0x8f, 0xbf, 0x26, 0xc0, 0xb4, 0x95, 0x6c, ]); -const SALT: H256 = H256::zero(); + +lazy_static::lazy_static! { + static ref SALT: std::sync::Mutex = std::sync::Mutex::new(H256::zero()); +} #[tokio::main] async fn main() { @@ -87,6 +92,18 @@ fn setup() -> (Address, SecretKey, EthClient, PathBuf) { ) .to_path_buf(); + // If not set, randomize the SALT + let input = std::env::var("DEPLOYER_SALT_IS_ZERO").unwrap_or("false".to_owned()); + match input.trim().to_lowercase().as_str() { + "true" | "1" => (), + "false" | "0" => { + let mut salt = SALT.lock().unwrap(); + *salt = H256::random(); + println!("SALT: {salt:?}"); + } + _ => panic!("Invalid boolean string: {input}"), + }; + (deployer, deployer_private_key, eth_client, contracts_path) } @@ -166,6 +183,14 @@ async fn deploy_contracts( ..Default::default() }; + let deploy_frames = spinner!(["📭❱❱", "❱📬❱", "❱❱📫"], 220); + + let mut spinner = Spinner::new( + deploy_frames.clone(), + "Deploying OnChainProposer", + Color::Cyan, + ); + let (on_chain_proposer_deployment_tx_hash, on_chain_proposer_address) = deploy_on_chain_proposer( deployer, @@ -175,11 +200,15 @@ async fn deploy_contracts( contracts_path, ) .await; - println!( - "OnChainProposer deployed at address {:#x} with tx hash {:#x}", - on_chain_proposer_address, on_chain_proposer_deployment_tx_hash + + let msg = format!( + "OnChainProposer:\n\tDeployed at address {} with tx hash {}", + format!("{on_chain_proposer_address:#x}").bright_green(), + format!("{on_chain_proposer_deployment_tx_hash:#x}").bright_cyan() ); + spinner.success(&msg); + let mut spinner = Spinner::new(deploy_frames, "Deploying CommonBridge", Color::Cyan); let (bridge_deployment_tx_hash, bridge_address) = deploy_bridge( deployer, deployer_private_key, @@ -188,10 +217,13 @@ async fn deploy_contracts( contracts_path, ) .await; - println!( - "Bridge deployed at address {:#x} with tx hash {:#x}", - bridge_address, bridge_deployment_tx_hash + + let msg = format!( + "CommonBridge:\n\tDeployed at address {} with tx hash {}", + format!("{bridge_address:#x}").bright_green(), + format!("{bridge_deployment_tx_hash:#x}").bright_cyan(), ); + spinner.success(&msg); (on_chain_proposer_address, bridge_address) } @@ -263,7 +295,7 @@ async fn create2_deploy( overrides: Overrides, eth_client: &EthClient, ) -> (H256, Address) { - let calldata = [SALT.as_bytes(), init_code].concat(); + let calldata = [SALT.lock().unwrap().as_bytes(), init_code].concat(); let deploy_tx = eth_client .build_eip1559_transaction( DETERMINISTIC_CREATE2_ADDRESS, @@ -293,7 +325,7 @@ fn create2_address(init_code_hash: H256) -> Address { [ &[0xff], DETERMINISTIC_CREATE2_ADDRESS.as_bytes(), - SALT.as_bytes(), + SALT.lock().unwrap().as_bytes(), init_code_hash.as_bytes(), ] .concat(), @@ -311,7 +343,15 @@ async fn initialize_contracts( bridge: Address, eth_client: &EthClient, ) { - initialize_on_chain_proposer( + let initialize_frames = spinner!(["🪄❱❱", "❱🪄❱", "❱❱🪄"], 200); + + let mut spinner = Spinner::new( + initialize_frames.clone(), + "Initilazing OnChainProposer", + Color::Cyan, + ); + + let initialize_tx_hash = initialize_on_chain_proposer( on_chain_proposer, bridge, deployer, @@ -319,7 +359,18 @@ async fn initialize_contracts( eth_client, ) .await; - initialize_bridge( + let msg = format!( + "OnChainProposer:\n\tInitialized with tx hash {}", + format!("{initialize_tx_hash:#x}").bright_cyan() + ); + spinner.success(&msg); + + let mut spinner = Spinner::new( + initialize_frames.clone(), + "Initilazing CommonBridge", + Color::Cyan, + ); + let initialize_tx_hash = initialize_bridge( on_chain_proposer, bridge, deployer, @@ -327,6 +378,11 @@ async fn initialize_contracts( eth_client, ) .await; + let msg = format!( + "CommonBridge:\n\tInitialized with tx hash {}", + format!("{initialize_tx_hash:#x}").bright_cyan() + ); + spinner.success(&msg); } async fn initialize_on_chain_proposer( @@ -335,7 +391,7 @@ async fn initialize_on_chain_proposer( deployer: Address, deployer_private_key: SecretKey, eth_client: &EthClient, -) { +) -> H256 { let on_chain_proposer_initialize_selector = keccak(b"initialize(address)") .as_bytes() .get(..4) @@ -371,7 +427,7 @@ async fn initialize_on_chain_proposer( wait_for_transaction_receipt(initialize_tx_hash, eth_client).await; - println!("OnChainProposer initialized with tx hash {initialize_tx_hash:#x}\n"); + initialize_tx_hash } async fn initialize_bridge( @@ -380,7 +436,7 @@ async fn initialize_bridge( deployer: Address, deployer_private_key: SecretKey, eth_client: &EthClient, -) { +) -> H256 { let bridge_initialize_selector = keccak(b"initialize(address)") .as_bytes() .get(..4) @@ -415,7 +471,7 @@ async fn initialize_bridge( wait_for_transaction_receipt(initialize_tx_hash, eth_client).await; - println!("Bridge initialized with tx hash {initialize_tx_hash:#x}\n"); + initialize_tx_hash } async fn wait_for_transaction_receipt(tx_hash: H256, eth_client: &EthClient) { From 6abc2522ff3713d8670c8e6ff446ddaf04369417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Arjovsky?= Date: Wed, 13 Nov 2024 20:21:32 +0100 Subject: [PATCH 07/17] fix(l1): bump tokio to 1.41.1 to fix id errors (#1159) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fc7f99494..ab1b5cb26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ serde = { version = "1.0.203", features = ["derive"] } serde_json = "1.0.117" libmdbx = { version = "0.5.0", features = ["orm"] } bytes = { version = "1.6.0", features = ["serde"] } -tokio = { version = "1.38.0", features = ["full"] } +tokio = { version = "1.41.1", features = ["full"] } thiserror = "1.0.61" hex = "0.4.3" hex-literal = "0.4.1" From b659e91dd71245f5be6d198c2d3587577dd6d2f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jerem=C3=ADas=20Salom=C3=B3n?= <48994069+JereSalo@users.noreply.github.com> Date: Wed, 13 Nov 2024 17:12:11 -0300 Subject: [PATCH 08/17] fix(levm): handle unwraps (#1108) **Motivation** **Description** This PR not only handles `unwraps` but also fixes tests that broke because of this error handling. Partially Closes #1074 --------- Co-authored-by: Juani Medone Co-authored-by: maximopalopoli Co-authored-by: Javier Chatruc Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri Co-authored-by: Maximo Palopoli <96491141+maximopalopoli@users.noreply.github.com> --- crates/vm/levm/Cargo.toml | 2 +- .../vm/levm/bench/revm_comparison/src/lib.rs | 8 +- crates/vm/levm/src/account.rs | 9 +- crates/vm/levm/src/constants.rs | 8 +- crates/vm/levm/src/errors.rs | 14 +- crates/vm/levm/src/memory.rs | 9 +- .../src/opcode_handlers/bitwise_comparison.rs | 10 +- crates/vm/levm/src/opcode_handlers/block.rs | 24 +- .../levm/src/opcode_handlers/environment.rs | 26 +- .../stack_memory_storage_flow.rs | 14 +- crates/vm/levm/src/opcode_handlers/system.rs | 2 +- crates/vm/levm/src/utils.rs | 23 +- crates/vm/levm/src/vm.rs | 83 +- crates/vm/levm/tests/ef/mod.rs | 2 + crates/vm/levm/tests/ef/runner.rs | 2 + crates/vm/levm/tests/tests.rs | 1017 ++++++++++------- 16 files changed, 715 insertions(+), 538 deletions(-) diff --git a/crates/vm/levm/Cargo.toml b/crates/vm/levm/Cargo.toml index 5baa52c41..01cd8421c 100644 --- a/crates/vm/levm/Cargo.toml +++ b/crates/vm/levm/Cargo.toml @@ -31,7 +31,7 @@ rust_2018_idioms = "warn" [lints.clippy] as_conversions = "deny" -# unwrap_used = "deny" +unwrap_used = "deny" expect_used = "deny" # panic = "deny" unnecessary_cast = "warn" diff --git a/crates/vm/levm/bench/revm_comparison/src/lib.rs b/crates/vm/levm/bench/revm_comparison/src/lib.rs index d4ef0b712..fa88a2993 100644 --- a/crates/vm/levm/bench/revm_comparison/src/lib.rs +++ b/crates/vm/levm/bench/revm_comparison/src/lib.rs @@ -20,14 +20,14 @@ pub fn run_with_levm(program: &str, runs: usize, number_of_iterations: u32) { call_frame.calldata = Bytes::from(calldata); for _ in 0..runs - 1 { - let mut vm = new_vm_with_bytecode(Bytes::new()); - *vm.current_call_frame_mut() = call_frame.clone(); + let mut vm = new_vm_with_bytecode(Bytes::new()).unwrap(); + *vm.current_call_frame_mut().unwrap() = call_frame.clone(); let mut current_call_frame = vm.call_frames.pop().unwrap(); let tx_report = black_box(vm.execute(&mut current_call_frame)); assert!(tx_report.result == TxResult::Success); } - let mut vm = new_vm_with_bytecode(Bytes::new()); - *vm.current_call_frame_mut() = call_frame.clone(); + let mut vm = new_vm_with_bytecode(Bytes::new()).unwrap(); + *vm.current_call_frame_mut().unwrap() = call_frame.clone(); let mut current_call_frame = vm.call_frames.pop().unwrap(); let tx_report = black_box(vm.execute(&mut current_call_frame)); assert!(tx_report.result == TxResult::Success); diff --git a/crates/vm/levm/src/account.rs b/crates/vm/levm/src/account.rs index 28cd2dd46..fa064f5ea 100644 --- a/crates/vm/levm/src/account.rs +++ b/crates/vm/levm/src/account.rs @@ -1,9 +1,9 @@ use bytes::Bytes; use ethereum_rust_core::{H256, U256}; use keccak_hash::keccak; -use std::{collections::HashMap, str::FromStr}; +use std::collections::HashMap; -use crate::constants::EMPTY_CODE_HASH_STR; +use crate::{constants::EMPTY_CODE_HASH, errors::VMError}; #[derive(Clone, Default, Debug, PartialEq, Eq)] pub struct AccountInfo { @@ -47,9 +47,8 @@ impl Account { } } - pub fn has_code(&self) -> bool { - !(self.info.bytecode.is_empty() - || self.bytecode_hash() == H256::from_str(EMPTY_CODE_HASH_STR).unwrap()) + pub fn has_code(&self) -> Result { + Ok(!(self.info.bytecode.is_empty() || self.bytecode_hash() == EMPTY_CODE_HASH)) } pub fn bytecode_hash(&self) -> H256 { diff --git a/crates/vm/levm/src/constants.rs b/crates/vm/levm/src/constants.rs index 7487c6efd..1b0b7d980 100644 --- a/crates/vm/levm/src/constants.rs +++ b/crates/vm/levm/src/constants.rs @@ -1,5 +1,5 @@ use crate::errors::{InternalError, VMError}; -use ethereum_rust_core::U256; +use ethereum_rust_core::{H256, U256}; pub const SUCCESS_FOR_CALL: i32 = 1; pub const REVERT_FOR_CALL: i32 = 0; @@ -107,8 +107,10 @@ pub const STACK_LIMIT: usize = 1024; pub const GAS_REFUND_DENOMINATOR: u64 = 5; -pub const EMPTY_CODE_HASH_STR: &str = - "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; +pub const EMPTY_CODE_HASH: H256 = H256([ + 0xc5, 0xd2, 0x46, 0x01, 0x86, 0xf7, 0x23, 0x3c, 0x92, 0x7e, 0x7d, 0xb2, 0xdc, 0xc7, 0x03, 0xc0, + 0xe5, 0x00, 0xb6, 0x53, 0xca, 0x82, 0x27, 0x3b, 0x7b, 0xfa, 0xd8, 0x04, 0x5d, 0x85, 0xa4, 0x70, +]); pub const MEMORY_EXPANSION_QUOTIENT: usize = 512; diff --git a/crates/vm/levm/src/errors.rs b/crates/vm/levm/src/errors.rs index d4e367c61..b28d88c81 100644 --- a/crates/vm/levm/src/errors.rs +++ b/crates/vm/levm/src/errors.rs @@ -60,6 +60,8 @@ pub enum VMError { MemoryStoreOutOfBounds, #[error("Gas limit price product overflow")] GasLimitPriceProductOverflow, + #[error("Internal error: {0}")] + Internal(#[from] InternalError), #[error("Data size overflow")] DataSizeOverflow, #[error("Gas cost overflow")] @@ -70,12 +72,20 @@ pub enum VMError { CreationCostIsTooHigh, #[error("Max gas limit exceeded")] MaxGasLimitExceeded, - #[error("Internal error: {0}")] - Internal(#[from] InternalError), } #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] pub enum InternalError { + #[error("Could not access last callframe")] + CouldNotAccessLastCallframe, + #[error("Could not pop callframe")] + CouldNotPopCallframe, + #[error("Account not found")] + AccountNotFound, + #[error("ExcessBlobGas should not be None")] + ExcessBlobGasShouldNotBeNone, + #[error("Error in utils file")] + UtilsError, #[error("Accound should have been cached")] AccountShouldHaveBeenCached, #[error("Tried to convert one type to another")] diff --git a/crates/vm/levm/src/memory.rs b/crates/vm/levm/src/memory.rs index 3245e5854..214c13389 100644 --- a/crates/vm/levm/src/memory.rs +++ b/crates/vm/levm/src/memory.rs @@ -36,7 +36,7 @@ impl Memory { .checked_add(32) .ok_or(VMError::MemoryLoadOutOfBounds)?, ); - let value_bytes: [u8; 32] = self + let value_bytes = self .data .get( offset @@ -44,10 +44,9 @@ impl Memory { .checked_add(32) .ok_or(VMError::MemoryLoadOutOfBounds)?, ) - .ok_or(VMError::MemoryLoadOutOfBounds)? - .try_into() - .unwrap(); - Ok(U256::from(value_bytes)) + .ok_or(VMError::MemoryLoadOutOfBounds)?; + + Ok(U256::from_big_endian(value_bytes)) } pub fn load_range(&mut self, offset: usize, size: usize) -> Result, VMError> { diff --git a/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs b/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs index b7c180940..331a10b7b 100644 --- a/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs +++ b/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs @@ -213,7 +213,7 @@ impl VM { let shift = current_call_frame.stack.pop()?; let value = current_call_frame.stack.pop()?; let res = if shift < U256::from(256) { - arithmetic_shift_right(value, shift) + arithmetic_shift_right(value, shift)? } else if value.bit(255) { U256::MAX } else { @@ -225,15 +225,15 @@ impl VM { } } -pub fn arithmetic_shift_right(value: U256, shift: U256) -> U256 { - let shift_usize: usize = shift.try_into().unwrap(); // we know its not bigger than 256 +pub fn arithmetic_shift_right(value: U256, shift: U256) -> Result { + let shift_usize: usize = shift.try_into().map_err(|_| VMError::VeryLargeNumber)?; // we know its not bigger than 256 if value.bit(255) { // if negative fill with 1s let shifted = value >> shift_usize; let mask = U256::MAX << (256 - shift_usize); - shifted | mask + Ok(shifted | mask) } else { - value >> shift_usize + Ok(value >> shift_usize) } } diff --git a/crates/vm/levm/src/opcode_handlers/block.rs b/crates/vm/levm/src/opcode_handlers/block.rs index 8a9709109..3ec47474a 100644 --- a/crates/vm/levm/src/opcode_handlers/block.rs +++ b/crates/vm/levm/src/opcode_handlers/block.rs @@ -1,14 +1,13 @@ use crate::{ call_frame::CallFrame, constants::{gas_cost, LAST_AVAILABLE_BLOCK_LIMIT}, - errors::{OpcodeSuccess, VMError}, - vm::VM, + errors::{InternalError, OpcodeSuccess, VMError}, + vm::{address_to_word, VM}, }; use ethereum_rust_core::{ types::{BLOB_BASE_FEE_UPDATE_FRACTION, MIN_BASE_FEE_PER_BLOB_GAS}, - Address, H256, U256, + H256, U256, }; -use std::str::FromStr; // Block Information (11) // Opcodes: BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, CHAINID, SELFBALANCE, BASEFEE, BLOBHASH, BLOBBASEFEE @@ -184,13 +183,15 @@ impl VM { Ok(OpcodeSuccess::Continue) } - fn get_blob_gasprice(&mut self) -> U256 { - fake_exponential( + fn get_blob_gasprice(&mut self) -> Result { + Ok(fake_exponential( MIN_BASE_FEE_PER_BLOB_GAS.into(), // Use unwrap because env should have a Some value in excess_blob_gas attribute - self.env.block_excess_blob_gas.unwrap(), + self.env.block_excess_blob_gas.ok_or(VMError::Internal( + InternalError::ExcessBlobGasShouldNotBeNone, + ))?, BLOB_BASE_FEE_UPDATE_FRACTION.into(), - ) + )) } // BLOBBASEFEE operation @@ -200,7 +201,7 @@ impl VM { ) -> Result { self.increase_consumed_gas(current_call_frame, gas_cost::BLOBBASEFEE)?; - let blob_base_fee = self.get_blob_gasprice(); + let blob_base_fee = self.get_blob_gasprice()?; current_call_frame.stack.push(blob_base_fee)?; @@ -208,11 +209,6 @@ impl VM { } } -fn address_to_word(address: Address) -> U256 { - // This unwrap can't panic, as Address are 20 bytes long and U256 use 32 bytes - U256::from_str(&format!("{address:?}")).unwrap() -} - // Fuction inspired in EIP 4844 helpers. Link: https://eips.ethereum.org/EIPS/eip-4844#helpers fn fake_exponential(factor: U256, numerator: U256, denominator: U256) -> U256 { let mut i = U256::one(); diff --git a/crates/vm/levm/src/opcode_handlers/environment.rs b/crates/vm/levm/src/opcode_handlers/environment.rs index 737fba08d..472df2399 100644 --- a/crates/vm/levm/src/opcode_handlers/environment.rs +++ b/crates/vm/levm/src/opcode_handlers/environment.rs @@ -43,7 +43,7 @@ impl VM { self.cache_from_db(address); }; - let balance = self.cache.get_account(*address).unwrap().info.balance; + let balance = self.get_account(address).info.balance; current_call_frame.stack.push(balance)?; Ok(OpcodeSuccess::Continue) @@ -298,13 +298,7 @@ impl VM { self.cache_from_db(&address); }; - let bytecode = self - .cache - .get_account(address) - .unwrap() - .info - .bytecode - .clone(); + let bytecode = self.get_account(&address).info.bytecode; current_call_frame.stack.push(bytecode.len().into())?; Ok(OpcodeSuccess::Continue) @@ -348,13 +342,7 @@ impl VM { self.cache_from_db(&address); }; - let mut bytecode = self - .cache - .get_account(address) - .unwrap() - .info - .bytecode - .clone(); + let mut bytecode = self.get_account(&address).info.bytecode; if bytecode.len() < offset @@ -464,13 +452,7 @@ impl VM { self.cache_from_db(&address); }; - let bytecode = self - .cache - .get_account(address) - .unwrap() - .info - .bytecode - .clone(); + let bytecode = self.get_account(&address).info.bytecode; let mut hasher = Keccak256::new(); hasher.update(bytecode); diff --git a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs index 3086f73b1..f54f87d17 100644 --- a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs +++ b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs @@ -114,7 +114,11 @@ impl VM { &mut self, current_call_frame: &mut CallFrame, ) -> Result { - let offset: usize = current_call_frame.stack.pop()?.try_into().unwrap(); + let offset: usize = current_call_frame + .stack + .pop()? + .try_into() + .map_err(|_| VMError::VeryLargeNumber)?; let memory_expansion_cost = current_call_frame.memory.expansion_cost( offset .checked_add(1) @@ -189,15 +193,13 @@ impl VM { let mut base_dynamic_gas: U256 = U256::zero(); - let storage_slot = if self.cache.is_slot_cached(&address, key) { - self.cache.get_storage_slot(address, key).unwrap() - } else { + if !self.cache.is_slot_cached(&address, key) { // If slot is cold 2100 is added to base_dynamic_gas base_dynamic_gas += U256::from(2100); - - self.get_storage_slot(&address, key) // it is not in cache because of previous if }; + let storage_slot = self.get_storage_slot(&address, key); + base_dynamic_gas += if value == storage_slot.current_value { U256::from(100) } else if storage_slot.current_value == storage_slot.original_value { diff --git a/crates/vm/levm/src/opcode_handlers/system.rs b/crates/vm/levm/src/opcode_handlers/system.rs index ff9778bdf..4f448780e 100644 --- a/crates/vm/levm/src/opcode_handlers/system.rs +++ b/crates/vm/levm/src/opcode_handlers/system.rs @@ -65,7 +65,7 @@ impl VM { } else { call_opcode::WARM_ADDRESS_ACCESS_COST }; - let account = self.cache.get_account(code_address).unwrap().clone(); + let account = self.get_account(&code_address); let value_to_empty_account_cost = if !value.is_zero() && account.is_empty() { call_opcode::VALUE_TO_EMPTY_ACCOUNT_COST diff --git a/crates/vm/levm/src/utils.rs b/crates/vm/levm/src/utils.rs index 584acbbec..dd8a037d4 100644 --- a/crates/vm/levm/src/utils.rs +++ b/crates/vm/levm/src/utils.rs @@ -2,6 +2,7 @@ use crate::{ account::{Account, AccountInfo}, db::{Cache, Db}, environment::Environment, + errors::{InternalError, VMError}, operations::Operation, vm::VM, }; @@ -9,15 +10,18 @@ use bytes::Bytes; use ethereum_rust_core::{types::TxKind, Address, U256}; use std::{collections::HashMap, sync::Arc}; -pub fn ops_to_bytecode(operations: &[Operation]) -> Bytes { +pub fn ops_to_bytecode(operations: &[Operation]) -> Result { let mut bytecode = Vec::new(); for op in operations { - bytecode.extend_from_slice(&op.to_bytecode().unwrap()); + bytecode.extend_from_slice( + &op.to_bytecode() + .map_err(|_| VMError::Internal(InternalError::UtilsError))?, + ); // for now it is just a utils error... } - bytecode.into() + Ok(bytecode.into()) } -pub fn new_vm_with_bytecode(bytecode: Bytes) -> VM { +pub fn new_vm_with_bytecode(bytecode: Bytes) -> Result { new_vm_with_ops_addr_bal_db( bytecode, Address::from_low_u64_be(100), @@ -27,8 +31,8 @@ pub fn new_vm_with_bytecode(bytecode: Bytes) -> VM { ) } -pub fn new_vm_with_ops(operations: &[Operation]) -> VM { - let bytecode = ops_to_bytecode(operations); +pub fn new_vm_with_ops(operations: &[Operation]) -> Result { + let bytecode = ops_to_bytecode(operations)?; new_vm_with_ops_addr_bal_db( bytecode, Address::from_low_u64_be(100), @@ -38,8 +42,8 @@ pub fn new_vm_with_ops(operations: &[Operation]) -> VM { ) } -pub fn new_vm_with_ops_db(operations: &[Operation], db: Db) -> VM { - let bytecode = ops_to_bytecode(operations); +pub fn new_vm_with_ops_db(operations: &[Operation], db: Db) -> Result { + let bytecode = ops_to_bytecode(operations)?; new_vm_with_ops_addr_bal_db( bytecode, Address::from_low_u64_be(100), @@ -56,7 +60,7 @@ pub fn new_vm_with_ops_addr_bal_db( sender_balance: U256, mut db: Db, mut cache: Cache, -) -> VM { +) -> Result { let accounts = [ // This is the contract account that is going to be executed ( @@ -100,5 +104,4 @@ pub fn new_vm_with_ops_addr_bal_db( Arc::new(db), cache, ) - .unwrap() } diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index ac3061afb..47980a4a3 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -16,7 +16,6 @@ use gas_cost::KECCAK25_DYNAMIC_BASE; use sha3::{Digest, Keccak256}; use std::{ collections::{HashMap, HashSet}, - str::FromStr, sync::Arc, }; @@ -44,9 +43,15 @@ pub struct VM { pub tx_kind: TxKind, } -fn address_to_word(address: Address) -> U256 { +pub fn address_to_word(address: Address) -> U256 { // This unwrap can't panic, as Address are 20 bytes long and U256 use 32 bytes - U256::from_str(&format!("{address:?}")).unwrap() + let mut word = [0u8; 32]; + + for (word_byte, address_byte) in word.iter_mut().skip(12).zip(address.as_bytes().iter()) { + *word_byte = *address_byte; + } + + U256::from_big_endian(&word) } pub fn word_to_address(word: U256) -> Address { @@ -301,7 +306,7 @@ impl VM { self.env.refunded_gas = backup_refunded_gas; } - // let account = self.db.accounts.get(&self.env.origin).unwrap(); + // let account = self.db.accounts.get(&self.env.origin).ok_or(VMError::FatalUnwrap)?; /// Based on Ethereum yellow paper's initial tests of intrinsic validity (Section 6). The last version is /// Shanghai, so there are probably missing Cancun validations. The intrinsic validations are: /// @@ -322,18 +327,24 @@ impl VM { fn validate_transaction(&mut self) -> Result<(), VMError> { // Validations (1), (2), (3), (5), and (8) are assumed done in upper layers. + let call_frame = self + .call_frames + .last() + .ok_or(VMError::Internal( + InternalError::CouldNotAccessLastCallframe, + ))? + .clone(); + if self.is_create() { // If address is already in db, there's an error - let new_address_acc = self - .db - .get_account_info(self.call_frames.first().unwrap().to); + let new_address_acc = self.db.get_account_info(call_frame.to); if !new_address_acc.is_empty() { return Err(VMError::AddressAlreadyOccupied); } } let origin = self.env.origin; - let to = self.call_frames[0].to; + let to = call_frame.to; let mut receiver_account = self.get_account(&to); let mut sender_account = self.get_account(&origin); @@ -346,16 +357,16 @@ impl VM { .ok_or(VMError::NonceOverflow)?; // (4) - if sender_account.has_code() { + if sender_account.has_code()? { return Err(VMError::SenderAccountShouldNotHaveBytecode); } // (6) - if sender_account.info.balance < self.call_frames[0].msg_value { + if sender_account.info.balance < call_frame.msg_value { return Err(VMError::SenderBalanceShouldContainTransferValue); } // TODO: This belongs elsewhere. - sender_account.info.balance -= self.call_frames[0].msg_value; - receiver_account.info.balance += self.call_frames[0].msg_value; + sender_account.info.balance -= call_frame.msg_value; + receiver_account.info.balance += call_frame.msg_value; self.cache.add_account(&origin, &sender_account); self.cache.add_account(&to, &receiver_account); @@ -373,19 +384,27 @@ impl VM { fn revert_create(&mut self) -> Result<(), VMError> { // Note: currently working with copies - let sender = self.call_frames.first().unwrap().msg_sender; + let call_frame = self + .call_frames + .last() + .ok_or(VMError::Internal( + InternalError::CouldNotAccessLastCallframe, + ))? + .clone(); + + let sender = call_frame.msg_sender; let mut sender_account = self.get_account(&sender); sender_account.info.nonce -= 1; - let new_contract_address = self.call_frames.first().unwrap().to; + let new_contract_address = call_frame.to; if self.cache.accounts.remove(&new_contract_address).is_none() { return Err(VMError::AddressDoesNotMatchAnAccount); // Should not be this error } // Should revert this? - // sender_account.info.balance -= self.call_frames.first().unwrap().msg_value; + // sender_account.info.balance -= self.call_frames.first().ok_or(VMError::FatalUnwrap)?.msg_value; Ok(()) } @@ -397,10 +416,22 @@ impl VM { self.env.consumed_gas = initial_gas; - let mut initial_call_frame = self.call_frames.pop().unwrap(); - let sender = initial_call_frame.msg_sender; + let mut current_call_frame = self + .call_frames + .pop() + .ok_or(VMError::Internal(InternalError::CouldNotPopCallframe))?; + + let mut report = self.execute(&mut current_call_frame); - let mut report = self.execute(&mut initial_call_frame); + let initial_call_frame = self + .call_frames + .last() + .ok_or(VMError::Internal( + InternalError::CouldNotAccessLastCallframe, + ))? + .clone(); + + let sender = initial_call_frame.msg_sender; // This cost applies both for call and create // 4 gas for each zero byte in the transaction data 16 gas for each non-zero byte in the transaction. @@ -516,8 +547,10 @@ impl VM { Ok(report) } - pub fn current_call_frame_mut(&mut self) -> &mut CallFrame { - self.call_frames.last_mut().unwrap() + pub fn current_call_frame_mut(&mut self) -> Result<&mut CallFrame, VMError> { + self.call_frames.last_mut().ok_or(VMError::Internal( + InternalError::CouldNotAccessLastCallframe, + )) } // TODO: Improve and test REVERT behavior for XCALL opcodes. Issue: https://github.com/lambdaclass/lambda_ethereum_rust/issues/1061 @@ -642,6 +675,7 @@ impl VM { /// initialization_code = memory[offset:offset+size] /// /// address = keccak256(0xff + sender_address + salt + keccak256(initialization_code))[12:] + /// pub fn calculate_create2_address( sender_address: Address, initialization_code: &Bytes, @@ -755,7 +789,7 @@ impl VM { let sender_account = self .cache .get_mut_account(current_call_frame.msg_sender) - .unwrap(); + .ok_or(VMError::Internal(InternalError::AccountNotFound))?; if sender_account.info.balance < value_in_wei_to_send { current_call_frame @@ -821,8 +855,11 @@ impl VM { code_size_in_memory, )?; - // Erases the success value in the stack result of calling generic call - current_call_frame.stack.pop().unwrap(); + // Erases the success value in the stack result of calling generic call, probably this should be refactored soon... + current_call_frame + .stack + .pop() + .map_err(|_| VMError::StackUnderflow)?; Ok(OpcodeSuccess::Continue) } diff --git a/crates/vm/levm/tests/ef/mod.rs b/crates/vm/levm/tests/ef/mod.rs index 3678062e4..9de0c0234 100644 --- a/crates/vm/levm/tests/ef/mod.rs +++ b/crates/vm/levm/tests/ef/mod.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unwrap_used)] + mod deserialize; mod report; mod runner; diff --git a/crates/vm/levm/tests/ef/runner.rs b/crates/vm/levm/tests/ef/runner.rs index b158cda06..cef9fa246 100644 --- a/crates/vm/levm/tests/ef/runner.rs +++ b/crates/vm/levm/tests/ef/runner.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unwrap_used)] + use crate::ef::{report::EFTestsReport, test::EFTest}; use ethereum_rust_core::{H256, U256}; use ethereum_rust_levm::{ diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index 1edd74d3d..d0da808be 100644 --- a/crates/vm/levm/tests/tests.rs +++ b/crates/vm/levm/tests/tests.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unwrap_used)] + use bytes::Bytes; use ethereum_rust_core::{types::TxKind, Address, H256, U256}; use ethereum_rust_levm::{ @@ -32,7 +34,7 @@ fn callee_return_bytecode(return_value: U256) -> Bytes { Operation::Return, ]; - ops_to_bytecode(&ops) + ops_to_bytecode(&ops).unwrap() } pub fn store_data_in_memory_operations(data: &[u8], memory_offset: usize) -> Vec { @@ -50,13 +52,14 @@ fn add_op() { Operation::Push((32, U256::zero())), Operation::Add, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::one()); - assert!(vm.current_call_frame_mut().pc() == 68); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::one()); + assert!(vm.current_call_frame_mut().unwrap().pc() == 68); } #[test] @@ -66,12 +69,13 @@ fn mul_op() { Operation::Push((1, U256::from(4))), Operation::Mul, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::from(8)); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::from(8)); } #[test] @@ -81,12 +85,13 @@ fn sub_op() { Operation::Push((1, U256::from(5))), Operation::Sub, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::from(2)); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::from(2)); } #[test] @@ -97,12 +102,13 @@ fn div_op() { Operation::Push((1, U256::from(11))), Operation::Div, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::from(5)); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::from(5)); // In EVM: 10 / 0 = 0 let mut vm = new_vm_with_ops(&[ @@ -110,12 +116,13 @@ fn div_op() { Operation::Push((1, U256::from(10))), Operation::Div, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::zero()); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::zero()); } #[test] @@ -126,12 +133,13 @@ fn sdiv_op() { Operation::Push((32, U256::MAX - 1)), Operation::Sdiv, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::from(2)); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::from(2)); } #[test] @@ -142,12 +150,13 @@ fn mod_op() { Operation::Push((1, U256::from(10))), Operation::Mod, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::from(1)); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::from(1)); } #[test] @@ -159,12 +168,13 @@ fn smod_op() { Operation::Push((1, U256::from(10))), Operation::SMod, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::from(1)); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::from(1)); // Second Example // Example taken from evm.codes @@ -185,7 +195,8 @@ fn smod_op() { Operation::Push((32, b)), Operation::SMod, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -196,7 +207,7 @@ fn smod_op() { ) .unwrap(); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == c); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == c); } #[test] @@ -208,12 +219,13 @@ fn addmod_op() { Operation::Push((1, U256::from(10))), Operation::Addmod, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::from(4)); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::from(4)); } #[test] @@ -225,12 +237,13 @@ fn mulmod_op() { Operation::Push((1, U256::from(10))), Operation::Mulmod, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::from(4)); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::from(4)); } #[test] @@ -241,12 +254,13 @@ fn exp_op() { Operation::Push((1, U256::from(10))), Operation::Exp, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::from(100)); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::from(100)); } #[test] @@ -257,11 +271,12 @@ fn sign_extend_op() { Operation::Push((1, U256::zero())), Operation::SignExtend, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::from(0x7F)); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::from(0x7F)); // Case 2: Input: 0, 0xFF. Output: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF let mut vm = new_vm_with_ops(&[ @@ -269,11 +284,12 @@ fn sign_extend_op() { Operation::Push((1, U256::zero())), Operation::SignExtend, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::MAX); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::MAX); } #[test] @@ -284,12 +300,13 @@ fn lt_op() { Operation::Push((1, U256::from(9))), Operation::Lt, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::one()); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::one()); } #[test] @@ -300,12 +317,13 @@ fn gt_op() { Operation::Push((1, U256::from(10))), Operation::Gt, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::one()); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::one()); } #[test] @@ -316,12 +334,13 @@ fn slt_op() { Operation::Push((32, U256::MAX)), Operation::Slt, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::one()); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::one()); } #[test] @@ -332,12 +351,13 @@ fn sgt_op() { Operation::Push((32, U256::zero())), Operation::Sgt, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::one()); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::one()); } #[test] @@ -348,11 +368,12 @@ fn eq_op() { Operation::Push((1, U256::from(10))), Operation::Eq, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::one()); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::one()); // Case 2: Input: 10, 20. Output: 0 (false) let mut vm = new_vm_with_ops(&[ @@ -360,11 +381,12 @@ fn eq_op() { Operation::Push((1, U256::from(20))), Operation::Eq, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::zero()); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::zero()); } #[test] @@ -374,22 +396,24 @@ fn is_zero_op() { Operation::Push((1, U256::zero())), Operation::IsZero, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::one()); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::one()); // Case 2: Input is non-zero (e.g., 10), Output should be 0 (since 10 != 0 is false) let mut vm = new_vm_with_ops(&[ Operation::Push((1, U256::from(10))), Operation::IsZero, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert!(vm.current_call_frame_mut().stack.pop().unwrap() == U256::zero()); + assert!(vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::zero()); } #[test] @@ -399,12 +423,13 @@ fn and_basic() { Operation::Push((32, U256::from(0b1100))), Operation::And, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0b1000)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); } @@ -416,14 +441,15 @@ fn and_binary_with_zero() { Operation::Push((32, U256::zero())), Operation::And, Operation::Stop, - ]); + ]) + .unwrap(); let expected_consumed_gas = TX_BASE_COST + gas_cost::AND + gas_cost::PUSHN.checked_mul(U256::from(2)).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::zero()); assert_eq!(vm.env.consumed_gas, expected_consumed_gas); } @@ -435,12 +461,13 @@ fn and_with_hex_numbers() { Operation::Push((32, U256::from(0xF0F0))), Operation::And, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xF0F0)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -449,12 +476,13 @@ fn and_with_hex_numbers() { Operation::Push((32, U256::from(0xF0F0))), Operation::And, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xF000)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -463,12 +491,13 @@ fn and_with_hex_numbers() { Operation::Push((32, U256::from(0x1F0F))), Operation::And, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0b1000000000000)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); } @@ -480,12 +509,13 @@ fn or_basic() { Operation::Push((32, U256::from(0b1100))), Operation::Or, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0b1110)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -494,12 +524,13 @@ fn or_basic() { Operation::Push((32, U256::zero())), Operation::Or, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0b1010)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -508,12 +539,13 @@ fn or_basic() { Operation::Push((32, U256::zero())), Operation::Or, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xFFFFFFFFFFFFFFFF_u64)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); } @@ -525,12 +557,13 @@ fn or_with_hex_numbers() { Operation::Push((32, U256::from(0xF0F0))), Operation::Or, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xFFFF)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -539,12 +572,13 @@ fn or_with_hex_numbers() { Operation::Push((32, U256::from(0xF0F0))), Operation::Or, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xF0F0)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -553,12 +587,13 @@ fn or_with_hex_numbers() { Operation::Push((32, U256::from(0x1F0F))), Operation::Or, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0b1011111100101111)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); } @@ -570,12 +605,13 @@ fn xor_basic() { Operation::Push((32, U256::from(0b1100))), Operation::Xor, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0b110)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -584,12 +620,13 @@ fn xor_basic() { Operation::Push((32, U256::zero())), Operation::Xor, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0b1010)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -598,12 +635,13 @@ fn xor_basic() { Operation::Push((32, U256::zero())), Operation::Xor, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(u64::MAX)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -612,12 +650,13 @@ fn xor_basic() { Operation::Push((32, U256::from(u64::MAX))), Operation::Xor, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::zero()); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); } @@ -629,12 +668,13 @@ fn xor_with_hex_numbers() { Operation::Push((32, U256::from(0xF))), Operation::Xor, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xFF)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -643,12 +683,13 @@ fn xor_with_hex_numbers() { Operation::Push((32, U256::from(0xFF))), Operation::Xor, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::zero()); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -657,12 +698,13 @@ fn xor_with_hex_numbers() { Operation::Push((32, U256::from(0xF0F0))), Operation::Xor, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xF0F)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -671,12 +713,13 @@ fn xor_with_hex_numbers() { Operation::Push((32, U256::from(0xF0F0))), Operation::Xor, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xF0)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -685,12 +728,13 @@ fn xor_with_hex_numbers() { Operation::Push((32, U256::from(0x3A4B))), Operation::Xor, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0b111011001000100)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); } @@ -701,12 +745,13 @@ fn not() { Operation::Push((32, U256::from(0b1010))), Operation::Not, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); let expected = !U256::from(0b1010); assert_eq!(result, expected); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 6); @@ -715,12 +760,13 @@ fn not() { Operation::Push((32, U256::MAX)), Operation::Not, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::zero()); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 6); @@ -728,12 +774,13 @@ fn not() { Operation::Push((32, U256::zero())), Operation::Not, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::MAX); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 6); @@ -741,12 +788,13 @@ fn not() { Operation::Push((32, U256::from(1))), Operation::Not, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::MAX - 1); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 6); } @@ -758,12 +806,13 @@ fn byte_basic() { Operation::Push((32, U256::from(31))), Operation::Byte, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xF1)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -772,12 +821,13 @@ fn byte_basic() { Operation::Push((32, U256::from(30))), Operation::Byte, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0x33)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); } @@ -789,12 +839,13 @@ fn byte_edge_cases() { Operation::Push((32, U256::from(0))), Operation::Byte, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xFF)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -803,12 +854,13 @@ fn byte_edge_cases() { Operation::Push((32, U256::from(12))), Operation::Byte, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xFF)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -817,12 +869,13 @@ fn byte_edge_cases() { Operation::Push((32, U256::from(29))), Operation::Byte, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0x0D)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -831,12 +884,13 @@ fn byte_edge_cases() { Operation::Push((32, U256::from(50))), Operation::Byte, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::zero()); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -845,12 +899,13 @@ fn byte_edge_cases() { Operation::Push((32, U256::from(32))), Operation::Byte, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::zero()); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -859,12 +914,13 @@ fn byte_edge_cases() { Operation::Push((32, U256::from(15))), Operation::Byte, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::zero()); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -879,12 +935,13 @@ fn byte_edge_cases() { Operation::Push((32, U256::from(10))), Operation::Byte, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0x90)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -893,12 +950,13 @@ fn byte_edge_cases() { Operation::Push((32, U256::from(7))), Operation::Byte, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0x57)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -907,12 +965,13 @@ fn byte_edge_cases() { Operation::Push((32, U256::from(19))), Operation::Byte, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xDD)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -921,12 +980,13 @@ fn byte_edge_cases() { Operation::Push((32, U256::from(31))), Operation::Byte, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0x40)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); } @@ -938,12 +998,13 @@ fn shl_basic() { Operation::Push((32, U256::from(0))), Operation::Shl, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xDDDD)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -952,12 +1013,13 @@ fn shl_basic() { Operation::Push((32, U256::from(1))), Operation::Shl, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0x2468acf0)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -966,12 +1028,13 @@ fn shl_basic() { Operation::Push((32, U256::from(4))), Operation::Shl, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(4886718336_u64)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -980,12 +1043,13 @@ fn shl_basic() { Operation::Push((32, U256::from(4))), Operation::Shl, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xFF << 4)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); } @@ -997,12 +1061,13 @@ fn shl_edge_cases() { Operation::Push((32, U256::from(256))), Operation::Shl, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::zero()); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -1011,12 +1076,13 @@ fn shl_edge_cases() { Operation::Push((32, U256::from(200))), Operation::Shl, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::zero()); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -1025,12 +1091,13 @@ fn shl_edge_cases() { Operation::Push((32, U256::from(1))), Operation::Shl, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::MAX - 1); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); } @@ -1042,12 +1109,13 @@ fn shr_basic() { Operation::Push((32, U256::from(0))), Operation::Shr, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xDDDD)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -1056,12 +1124,13 @@ fn shr_basic() { Operation::Push((32, U256::from(1))), Operation::Shr, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0x91a2b3c)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -1070,12 +1139,13 @@ fn shr_basic() { Operation::Push((32, U256::from(4))), Operation::Shr, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0x1234567)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -1084,12 +1154,13 @@ fn shr_basic() { Operation::Push((32, U256::from(4))), Operation::Shr, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0xF)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); } @@ -1101,12 +1172,13 @@ fn shr_edge_cases() { Operation::Push((32, U256::from(256))), Operation::Shr, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::zero()); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -1115,12 +1187,13 @@ fn shr_edge_cases() { Operation::Push((32, U256::from(200))), Operation::Shr, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::zero()); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); @@ -1129,12 +1202,13 @@ fn shr_edge_cases() { Operation::Push((32, U256::from(1))), Operation::Shr, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::MAX >> 1); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); } @@ -1146,12 +1220,13 @@ fn sar_shift_by_0() { Operation::Push((32, U256::from(0))), Operation::Sar, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0x12345678)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); } @@ -1169,12 +1244,13 @@ fn sar_shifting_large_value_with_all_bits_set() { Operation::Push((32, U256::from(8))), Operation::Sar, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); let expected = U256::from_big_endian(&[ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, @@ -1197,12 +1273,13 @@ fn sar_shifting_negative_value_and_small_shift() { Operation::Push((32, U256::from(4))), Operation::Sar, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); let expected = U256::from_big_endian(&[ 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -1219,12 +1296,13 @@ fn sar_shift_positive_value() { Operation::Push((32, U256::from(4))), Operation::Sar, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(result, U256::from(0x07FFFF)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 9); } @@ -1242,12 +1320,13 @@ fn sar_shift_negative_value() { Operation::Push((32, U256::from(4))), Operation::Sar, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let result = vm.current_call_frame_mut().stack.pop().unwrap(); + let result = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); let expected = U256::from_big_endian(&[ 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, @@ -1275,16 +1354,16 @@ fn keccak256_zero_offset_size_four() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from("0x29045a592007d0c246ef02c2223570da9522d0cf0f73282c79a1bc8f0bb2c238") ); - assert_eq!(vm.current_call_frame_mut().pc(), 40); + assert_eq!(vm.current_call_frame_mut().unwrap().pc(), 40); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 52); } @@ -1305,16 +1384,16 @@ fn keccak256_zero_offset_size_bigger_than_actual_memory() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert!( - vm.current_call_frame_mut().stack.pop().unwrap() + vm.current_call_frame_mut().unwrap().stack.pop().unwrap() == U256::from("0xae75624a7d0413029c1e0facdd38cc8e177d9225892e2490a69c2f1f89512061") ); - assert_eq!(vm.current_call_frame_mut().pc(), 40); + assert_eq!(vm.current_call_frame_mut().unwrap().pc(), 40); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 61); } @@ -1327,16 +1406,16 @@ fn keccak256_zero_offset_zero_size() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") ); - assert_eq!(vm.current_call_frame_mut().pc(), 4); + assert_eq!(vm.current_call_frame_mut().unwrap().pc(), 4); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 34); } @@ -1357,16 +1436,16 @@ fn keccak256_offset_four_size_four() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from("0xe8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c") ); - assert_eq!(vm.current_call_frame_mut().pc(), 41); + assert_eq!(vm.current_call_frame_mut().unwrap().pc(), 41); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 53); } @@ -1378,16 +1457,17 @@ fn mstore() { Operation::Mstore, Operation::Msize, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from(32) ); - assert_eq!(vm.current_call_frame_mut().pc(), 69); + assert_eq!(vm.current_call_frame_mut().unwrap().pc(), 69); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 14); } @@ -1399,16 +1479,17 @@ fn mstore_saves_correct_value() { Operation::Mstore, Operation::Msize, Operation::Stop, - ]); + ]) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let stored_value = vm.current_call_frame_mut().memory.load(0).unwrap(); + let stored_value = vm.current_call_frame_mut().unwrap().memory.load(0).unwrap(); assert_eq!(stored_value, U256::from(0x33333)); - let memory_size = vm.current_call_frame_mut().stack.pop().unwrap(); + let memory_size = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(memory_size, U256::from(32)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 14); } @@ -1422,12 +1503,12 @@ fn mstore8() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let stored_value = vm.current_call_frame_mut().memory.load(0).unwrap(); + let stored_value = vm.current_call_frame_mut().unwrap().memory.load(0).unwrap(); let mut value_bytes = [0u8; 32]; stored_value.to_big_endian(&mut value_bytes); @@ -1450,15 +1531,20 @@ fn mcopy() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let copied_value = vm.current_call_frame_mut().memory.load(64).unwrap(); + let copied_value = vm + .current_call_frame_mut() + .unwrap() + .memory + .load(64) + .unwrap(); assert_eq!(copied_value, U256::from(0x33333)); - let memory_size = vm.current_call_frame_mut().stack.pop().unwrap(); + let memory_size = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(memory_size, U256::from(96)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 35); } @@ -1474,12 +1560,12 @@ fn mload() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let loaded_value = vm.current_call_frame_mut().stack.pop().unwrap(); + let loaded_value = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(loaded_value, U256::from(0x33333)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 18); } @@ -1488,12 +1574,12 @@ fn mload() { fn msize() { let operations = [Operation::Msize, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let initial_size = vm.current_call_frame_mut().stack.pop().unwrap(); + let initial_size = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(initial_size, U256::from(0)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 2); @@ -1505,12 +1591,12 @@ fn msize() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let after_store_size = vm.current_call_frame_mut().stack.pop().unwrap(); + let after_store_size = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(after_store_size, U256::from(32)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 14); @@ -1522,12 +1608,12 @@ fn msize() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let final_size = vm.current_call_frame_mut().stack.pop().unwrap(); + let final_size = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(final_size, U256::from(96)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 20); } @@ -1544,13 +1630,13 @@ fn mstore_mload_offset_not_multiple_of_32() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let memory_size = vm.current_call_frame_mut().stack.pop().unwrap(); - let loaded_value = vm.current_call_frame_mut().stack.pop().unwrap(); + let memory_size = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); + let loaded_value = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(loaded_value, U256::from(0xabcdef)); assert_eq!(memory_size, U256::from(64)); @@ -1568,13 +1654,13 @@ fn mstore_mload_offset_not_multiple_of_32() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let memory_size = vm.current_call_frame_mut().stack.pop().unwrap(); - let loaded_value = vm.current_call_frame_mut().stack.pop().unwrap(); + let memory_size = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); + let loaded_value = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(loaded_value, U256::from(0x123456)); assert_eq!(memory_size, U256::from(2048)); @@ -1590,13 +1676,13 @@ fn mload_uninitialized_memory() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let memory_size = vm.current_call_frame_mut().stack.pop().unwrap(); - let loaded_value = vm.current_call_frame_mut().stack.pop().unwrap(); + let memory_size = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); + let loaded_value = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(loaded_value, U256::zero()); assert_eq!(memory_size, U256::from(96)); @@ -1633,17 +1719,18 @@ fn call_returns_if_bytecode_empty() { cache.add_account(&callee_address, &callee_account); let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&caller_ops), + ops_to_bytecode(&caller_ops).unwrap(), Address::from_low_u64_be(U256::from(1).low_u64()), U256::zero(), db, cache, - ); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let success = vm.current_call_frame_mut().stack.pop().unwrap(); + let success = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); assert_eq!(success, U256::one()); } @@ -1676,17 +1763,18 @@ fn call_changes_callframe_and_stores() { cache.add_account(&callee_address, &callee_account); let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&caller_ops), + ops_to_bytecode(&caller_ops).unwrap(), Address::from_low_u64_be(U256::from(1).low_u64()), U256::zero(), db, cache, - ); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let current_call_frame = vm.current_call_frame_mut(); + let current_call_frame = vm.current_call_frame_mut().unwrap(); let success = current_call_frame.stack.pop().unwrap() == U256::one(); assert!(success); @@ -1742,7 +1830,7 @@ fn nested_calls() { callee2_ops.extend(callee2_return_bytecode); - let callee2_bytecode = ops_to_bytecode(&callee2_ops); + let callee2_bytecode = ops_to_bytecode(&callee2_ops).unwrap(); let callee2_address = Address::from_low_u64_be(U256::from(2).low_u64()); let callee2_address_u256 = U256::from(2); @@ -1777,17 +1865,18 @@ fn nested_calls() { cache.add_account(&callee3_address, &callee3_account); let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&caller_ops), + ops_to_bytecode(&caller_ops).unwrap(), caller_address, caller_balance, db, cache, - ); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let current_call_frame = vm.current_call_frame_mut(); + let current_call_frame = vm.current_call_frame_mut().unwrap(); let success = current_call_frame.stack.pop().unwrap(); assert_eq!(success, U256::one()); @@ -1822,7 +1911,7 @@ fn staticcall_changes_callframe_is_static() { Operation::Stop, ]; - let callee_bytecode = ops_to_bytecode(&callee_ops); + let callee_bytecode = ops_to_bytecode(&callee_ops).unwrap(); let callee_address = Address::from_low_u64_be(U256::from(2).low_u64()); let callee_address_u256 = U256::from(2); @@ -1848,12 +1937,13 @@ fn staticcall_changes_callframe_is_static() { cache.add_account(&callee_address, &callee_account); let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&caller_ops), + ops_to_bytecode(&caller_ops).unwrap(), Address::from_low_u64_be(U256::from(1).low_u64()), U256::zero(), db, cache, - ); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -1875,7 +1965,7 @@ fn staticcall_changes_callframe_is_static() { fn pop_on_empty_stack() { let operations = [Operation::Pop, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); let tx_report = vm.execute(&mut current_call_frame); @@ -1892,13 +1982,13 @@ fn pop_on_empty_stack() { #[test] fn pc_op() { let operations = [Operation::PC, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from(0) ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 2); @@ -1912,13 +2002,13 @@ fn pc_op_with_push_offset() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from(33) ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 5); @@ -1963,14 +2053,14 @@ fn pc_op_with_push_offset() { // cache.add_account(&callee_address, &callee_account); // let mut vm = new_vm_with_ops_addr_bal_db( -// ops_to_bytecode(&caller_ops), +// ops_to_bytecode(&caller_ops).unwrap(), // Address::from_low_u64_be(U256::from(1).low_u64()), // U256::from(1000), // db, // cache, // ); -// let current_call_frame = vm.current_call_frame_mut(); +// let current_call_frame = vm.current_call_frame_mut().unwrap(); // current_call_frame.msg_sender = Address::from_low_u64_be(U256::from(1).low_u64()); // current_call_frame.to = Address::from_low_u64_be(U256::from(5).low_u64()); @@ -2027,14 +2117,14 @@ fn pc_op_with_push_offset() { // cache.add_account(&callee_address, &callee_account); // let mut vm = new_vm_with_ops_addr_bal_db( -// ops_to_bytecode(&caller_ops), +// ops_to_bytecode(&caller_ops).unwrap(), // Address::from_low_u64_be(U256::from(1).low_u64()), // U256::zero(), // db, // cache // ); -// let current_call_frame = vm.current_call_frame_mut(); +// let current_call_frame = vm.current_call_frame_mut().unwrap(); // current_call_frame.msg_sender = Address::from_low_u64_be(U256::from(1).low_u64()); // current_call_frame.to = Address::from_low_u64_be(U256::from(5).low_u64()); @@ -2089,21 +2179,21 @@ fn pc_op_with_push_offset() { // cache.add_account(&callee_address, &callee_account); // let mut vm = new_vm_with_ops_addr_bal_db( -// ops_to_bytecode(&caller_ops), +// ops_to_bytecode(&caller_ops).unwrap(), // Address::from_low_u64_be(U256::from(1).low_u64()), // U256::from(1000), // db, // cache // ); -// let current_call_frame = vm.current_call_frame_mut(); +// let current_call_frame = vm.current_call_frame_mut().unwrap(); // current_call_frame.msg_sender = Address::from_low_u64_be(U256::from(1).low_u64()); // current_call_frame.to = Address::from_low_u64_be(U256::from(5).low_u64()); // let mut current_call_frame = vm.call_frames.pop().unwrap(); // vm.execute(&mut current_call_frame); -// let current_call_frame = vm.current_call_frame_mut(); +// let current_call_frame = vm.current_call_frame_mut().unwrap(); // assert_eq!( // current_call_frame.msg_sender, @@ -2150,7 +2240,7 @@ fn pc_op_with_push_offset() { // cache.add_account(&callee_address, &callee_account); // let mut vm = new_vm_with_ops_addr_bal_db( -// ops_to_bytecode(&caller_ops), +// ops_to_bytecode(&caller_ops).unwrap(), // Address::from_low_u64_be(U256::from(1).low_u64()), // U256::from(1000), // db, @@ -2188,7 +2278,7 @@ fn jump_position_bigger_than_program_bytecode_size() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); let tx_report = vm.execute(&mut current_call_frame); @@ -2210,13 +2300,13 @@ fn jumpi_not_zero() { Operation::Push((32, U256::from(10))), Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from(10) ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 20); @@ -2235,13 +2325,13 @@ fn jumpi_for_zero() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from(100) ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 19); @@ -2305,13 +2395,13 @@ fn calldataload() { Operation::CallDataLoad, Operation::Stop, ]; - let mut vm = new_vm_with_ops(&ops); + let mut vm = new_vm_with_ops(&ops).unwrap(); - vm.current_call_frame_mut().calldata = calldata; + vm.current_call_frame_mut().unwrap().calldata = calldata; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let current_call_frame = vm.current_call_frame_mut(); + let current_call_frame = vm.current_call_frame_mut().unwrap(); let top_of_stack = current_call_frame.stack.pop().unwrap(); assert_eq!( @@ -2337,7 +2427,7 @@ fn calldataload_being_set_by_parent() { Operation::Return, ]; - let callee_bytecode = ops_to_bytecode(&ops); + let callee_bytecode = ops_to_bytecode(&ops).unwrap(); let callee_address = Address::from_low_u64_be(U256::from(2).low_u64()); let callee_address_u256 = U256::from(2); @@ -2373,17 +2463,18 @@ fn calldataload_being_set_by_parent() { cache.add_account(&callee_address, &callee_account); let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&caller_ops), + ops_to_bytecode(&caller_ops).unwrap(), Address::from_low_u64_be(U256::from(1).low_u64()), U256::zero(), db, cache, - ); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let current_call_frame = vm.current_call_frame_mut(); + let current_call_frame = vm.current_call_frame_mut().unwrap(); let calldata = [ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, @@ -2394,20 +2485,21 @@ fn calldataload_being_set_by_parent() { let expected_data = U256::from_big_endian(&calldata[..32]); assert_eq!(expected_data, current_call_frame.memory.load(0).unwrap()); + assert_eq!(expected_data, current_call_frame.memory.load(0).unwrap()); } #[test] fn calldatasize() { let calldata = vec![0x11, 0x22, 0x33].into(); let ops = vec![Operation::CallDataSize, Operation::Stop]; - let mut vm = new_vm_with_ops(&ops); + let mut vm = new_vm_with_ops(&ops).unwrap(); - vm.current_call_frame_mut().calldata = calldata; + vm.current_call_frame_mut().unwrap().calldata = calldata; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let current_call_frame = vm.current_call_frame_mut(); + let current_call_frame = vm.current_call_frame_mut().unwrap(); let top_of_stack = current_call_frame.stack.pop().unwrap(); assert_eq!(top_of_stack, U256::from(3)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 2); @@ -2423,14 +2515,14 @@ fn calldatacopy() { Operation::CallDataCopy, Operation::Stop, ]; - let mut vm = new_vm_with_ops(&ops); + let mut vm = new_vm_with_ops(&ops).unwrap(); - vm.current_call_frame_mut().calldata = calldata; + vm.current_call_frame_mut().unwrap().calldata = calldata; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let current_call_frame = vm.current_call_frame_mut(); + let current_call_frame = vm.current_call_frame_mut().unwrap(); let memory = current_call_frame.memory.load_range(0, 2).unwrap(); assert_eq!(memory, vec![0x22, 0x33]); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 18); @@ -2440,14 +2532,14 @@ fn calldatacopy() { fn returndatasize() { let returndata = vec![0xAA, 0xBB, 0xCC].into(); let ops = vec![Operation::ReturnDataSize, Operation::Stop]; - let mut vm = new_vm_with_ops(&ops); + let mut vm = new_vm_with_ops(&ops).unwrap(); - vm.current_call_frame_mut().sub_return_data = returndata; + vm.current_call_frame_mut().unwrap().sub_return_data = returndata; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let current_call_frame = vm.current_call_frame_mut(); + let current_call_frame = vm.current_call_frame_mut().unwrap(); let top_of_stack = current_call_frame.stack.pop().unwrap(); assert_eq!(top_of_stack, U256::from(3)); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 2); @@ -2463,14 +2555,14 @@ fn returndatacopy() { Operation::ReturnDataCopy, Operation::Stop, ]; - let mut vm = new_vm_with_ops(&ops); + let mut vm = new_vm_with_ops(&ops).unwrap(); - vm.current_call_frame_mut().sub_return_data = returndata; + vm.current_call_frame_mut().unwrap().sub_return_data = returndata; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let current_call_frame = vm.current_call_frame_mut(); + let current_call_frame = vm.current_call_frame_mut().unwrap(); let memory = current_call_frame.memory.load_range(0, 2).unwrap(); assert_eq!(memory, vec![0xBB, 0xCC]); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 18); @@ -2508,17 +2600,18 @@ fn returndatacopy_being_set_by_parent() { cache.add_account(&callee_address, &callee_account); let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&caller_ops), + ops_to_bytecode(&caller_ops).unwrap(), Address::from_low_u64_be(U256::from(1).low_u64()), U256::zero(), db, cache, - ); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let current_call_frame = vm.current_call_frame_mut(); + let current_call_frame = vm.current_call_frame_mut().unwrap(); let result = current_call_frame.memory.load(0).unwrap(); @@ -2542,12 +2635,13 @@ fn blockhash_op() { db.add_block_hashes(vec![(block_number, block_hash)]); let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&operations), + ops_to_bytecode(&operations).unwrap(), Address::default(), U256::MAX, db, Cache::default(), - ); + ) + .unwrap(); vm.env.block_number = current_block_number; @@ -2555,7 +2649,7 @@ fn blockhash_op() { vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), expected_block_hash ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 23); @@ -2574,7 +2668,7 @@ fn blockhash_same_block_number() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut storage = Storage::default(); storage.insert(block_number, H256::from_low_u64_be(block_hash)); // vm.world_state.insert( @@ -2587,7 +2681,7 @@ fn blockhash_same_block_number() { vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), expected_block_hash ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 23); @@ -2609,12 +2703,13 @@ fn blockhash_block_number_not_from_recent_256() { let mut db = Db::new(); db.add_block_hashes(vec![(block_number, block_hash)]); let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&operations), + ops_to_bytecode(&operations).unwrap(), Address::default(), U256::MAX, db, Cache::default(), - ); + ) + .unwrap(); vm.env.block_number = current_block_number; @@ -2622,7 +2717,7 @@ fn blockhash_block_number_not_from_recent_256() { vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), expected_block_hash ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 23); @@ -2634,14 +2729,14 @@ fn coinbase_op() { let operations = [Operation::Coinbase, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); vm.env.coinbase = Address::from_low_u64_be(coinbase_address); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from(coinbase_address) ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 2); @@ -2653,13 +2748,16 @@ fn timestamp_op() { let operations = [Operation::Timestamp, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); vm.env.timestamp = timestamp; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert_eq!(vm.current_call_frame_mut().stack.pop().unwrap(), timestamp); + assert_eq!( + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), + timestamp + ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 2); } @@ -2669,14 +2767,14 @@ fn number_op() { let operations = [Operation::Number, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); vm.env.block_number = block_number; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), block_number ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 2); @@ -2688,14 +2786,14 @@ fn prevrandao_op() { let operations = [Operation::Prevrandao, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); vm.env.prev_randao = Some(prevrandao); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from_big_endian(&prevrandao.0) ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 2); @@ -2707,13 +2805,16 @@ fn gaslimit_op() { let operations = [Operation::Gaslimit, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); vm.env.gas_limit = gas_limit; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert_eq!(vm.current_call_frame_mut().stack.pop().unwrap(), gas_limit); + assert_eq!( + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), + gas_limit + ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 2); } @@ -2723,13 +2824,16 @@ fn chain_id_op() { let operations = [Operation::Chainid, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); vm.env.chain_id = chain_id; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert_eq!(vm.current_call_frame_mut().stack.pop().unwrap(), chain_id); + assert_eq!( + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), + chain_id + ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 2); } @@ -2739,14 +2843,14 @@ fn basefee_op() { let operations = [Operation::Basefee, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); vm.env.base_fee_per_gas = base_fee_per_gas; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), base_fee_per_gas ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 2); @@ -2757,7 +2861,7 @@ fn basefee_op() { fn blobbasefee_op() { let operations = [Operation::BlobBaseFee, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); vm.env.block_excess_blob_gas = Some(TARGET_BLOB_GAS_PER_BLOCK * 8); vm.env.block_blob_gas_used = Some(U256::zero()); @@ -2765,7 +2869,7 @@ fn blobbasefee_op() { vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from(2) ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 2); @@ -2776,7 +2880,7 @@ fn blobbasefee_op() { fn blobbasefee_minimum_cost() { let operations = [Operation::BlobBaseFee, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); vm.env.block_excess_blob_gas = Some(U256::zero()); vm.env.block_blob_gas_used = Some(U256::zero()); @@ -2784,7 +2888,7 @@ fn blobbasefee_minimum_cost() { vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::one() ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 2); @@ -2799,13 +2903,13 @@ fn pop_op() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::one() ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 8); @@ -2822,16 +2926,16 @@ fn jump_op() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from(10) ); - assert_eq!(vm.current_call_frame_mut().pc(), 70); + assert_eq!(vm.current_call_frame_mut().unwrap().pc(), 70); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 15); } @@ -2845,7 +2949,7 @@ fn jump_not_jumpdest_position() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); let tx_report = vm.execute(&mut current_call_frame); @@ -2870,9 +2974,9 @@ fn sstore_op() { // We don't need to add address to database because if it doesn't exist it returns and empty account, so no problem there. - let mut vm = new_vm_with_ops(&operations); - vm.current_call_frame_mut().to = sender_address; - vm.current_call_frame_mut().code_address = sender_address; + let mut vm = new_vm_with_ops(&operations).unwrap(); + vm.current_call_frame_mut().unwrap().to = sender_address; + vm.current_call_frame_mut().unwrap().code_address = sender_address; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -2898,8 +3002,8 @@ fn sstore_reverts_when_called_in_static() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); - vm.current_call_frame_mut().is_static = true; + let mut vm = new_vm_with_ops(&operations).unwrap(); + vm.current_call_frame_mut().unwrap().is_static = true; let mut current_call_frame = vm.call_frames.pop().unwrap(); let tx_report = vm.execute(&mut current_call_frame); @@ -2926,13 +3030,16 @@ fn sload_op() { let mut db = Db::new(); db.add_accounts(vec![(sender_address, Account::default())]); - let mut vm = new_vm_with_ops_db(&operations, db); - vm.current_call_frame_mut().msg_sender = sender_address; + let mut vm = new_vm_with_ops_db(&operations, db).unwrap(); + vm.current_call_frame_mut().unwrap().msg_sender = sender_address; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert_eq!(value, vm.current_call_frame_mut().stack.pop().unwrap()); + assert_eq!( + value, + vm.current_call_frame_mut().unwrap().stack.pop().unwrap() + ); } #[test] @@ -2944,15 +3051,15 @@ fn sload_untouched_key_of_storage() { let mut db = Db::new(); db.add_accounts(vec![(sender_address, Account::default())]); - let mut vm = new_vm_with_ops_db(&operations, db); - vm.current_call_frame_mut().msg_sender = sender_address; + let mut vm = new_vm_with_ops_db(&operations, db).unwrap(); + vm.current_call_frame_mut().unwrap().msg_sender = sender_address; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( U256::zero(), - vm.current_call_frame_mut().stack.pop().unwrap() + vm.current_call_frame_mut().unwrap().stack.pop().unwrap() ); } @@ -2962,15 +3069,15 @@ fn sload_on_not_existing_account() { let sender_address = Address::from_low_u64_be(3000); let operations = vec![Operation::Push((2, key)), Operation::Sload, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); - vm.current_call_frame_mut().msg_sender = sender_address; + let mut vm = new_vm_with_ops(&operations).unwrap(); + vm.current_call_frame_mut().unwrap().msg_sender = sender_address; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( U256::zero(), - vm.current_call_frame_mut().stack.pop().unwrap() + vm.current_call_frame_mut().unwrap().stack.pop().unwrap() ); } @@ -2988,11 +3095,11 @@ fn log0() { ]; operations.append(&mut log_operations); - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let logs = &vm.current_call_frame_mut().logs; + let logs = &vm.current_call_frame_mut().unwrap().logs; let data = [0xff_u8; 32].as_slice(); assert_eq!(logs.len(), 1); assert_eq!(logs[0].data, data.to_vec()); @@ -3018,11 +3125,11 @@ fn log1() { ]; operations.append(&mut log_operations); - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let logs = &vm.current_call_frame_mut().logs; + let logs = &vm.current_call_frame_mut().unwrap().logs; let data = [0xff_u8; 32].as_slice(); assert_eq!(logs.len(), 1); assert_eq!(logs[0].data, data.to_vec()); @@ -3051,11 +3158,11 @@ fn log2() { ]; operations.append(&mut log_operations); - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let logs = &vm.current_call_frame_mut().logs; + let logs = &vm.current_call_frame_mut().unwrap().logs; let data = [0xff_u8; 32].as_slice(); assert_eq!(logs.len(), 1); assert_eq!(logs[0].data, data.to_vec()); @@ -3090,11 +3197,11 @@ fn log3() { ]; operations.append(&mut log_operations); - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let logs = &vm.current_call_frame_mut().logs; + let logs = &vm.current_call_frame_mut().unwrap().logs; let data = [0xff_u8; 32].as_slice(); assert_eq!(logs.len(), 1); assert_eq!(logs[0].data, data.to_vec()); @@ -3136,11 +3243,11 @@ fn log4() { ]; operations.append(&mut log_operations); - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let logs = &vm.current_call_frame_mut().logs; + let logs = &vm.current_call_frame_mut().unwrap().logs; let data = [0xff_u8; 32].as_slice(); assert_eq!(logs.len(), 1); assert_eq!(logs[0].data, data.to_vec()); @@ -3170,11 +3277,11 @@ fn log_with_0_data_size() { ]; operations.append(&mut log_operations); - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let logs = &vm.current_call_frame_mut().logs; + let logs = &vm.current_call_frame_mut().unwrap().logs; assert_eq!(logs.len(), 1); assert_eq!(logs[0].data, Vec::new()); assert_eq!(logs[0].topics.len(), 0); @@ -3195,8 +3302,8 @@ fn cant_create_log_in_static_context() { ]; operations.append(&mut log_operations); - let mut vm: VM = new_vm_with_ops(&operations); - vm.current_call_frame_mut().is_static = true; + let mut vm: VM = new_vm_with_ops(&operations).unwrap(); + vm.current_call_frame_mut().unwrap().is_static = true; let mut current_call_frame = vm.call_frames.pop().unwrap(); let tx_report = vm.execute(&mut current_call_frame); @@ -3220,11 +3327,11 @@ fn log_with_data_in_memory_smaller_than_size() { ]; operations.append(&mut log_operations); - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let logs = &vm.current_call_frame_mut().logs; + let logs = &vm.current_call_frame_mut().unwrap().logs; let mut data = vec![0_u8; 16]; data.extend(vec![0xff_u8; 16]); @@ -3255,11 +3362,11 @@ fn multiple_logs_of_different_types() { ]; operations.append(&mut log_operations); - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let logs = &vm.current_call_frame_mut().logs; + let logs = &vm.current_call_frame_mut().unwrap().logs; let data = [0xff_u8; 32].as_slice(); assert_eq!(logs.len(), 2); assert_eq!(logs[0].data, data.to_vec()); @@ -3284,7 +3391,7 @@ fn logs_from_multiple_callers() { Operation::Stop, ]; operations.append(&mut log_operations); - let callee_bytecode = ops_to_bytecode(&operations); + let callee_bytecode = ops_to_bytecode(&operations).unwrap(); let callee_account = Account::new(U256::from(500000), callee_bytecode, 0, HashMap::new()); let mut caller_ops = vec![ @@ -3307,12 +3414,13 @@ fn logs_from_multiple_callers() { cache.add_account(&callee_address, &callee_account); let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&caller_ops), + ops_to_bytecode(&caller_ops).unwrap(), Address::from_low_u64_be(U256::from(1).low_u64()), U256::zero(), db, cache, - ); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -3363,72 +3471,75 @@ fn logs_from_multiple_callers() { // vm.execute(&mut current_call_frame); // assert_eq!( -// vm.current_call_frame_mut().stack.pop().unwrap(), +// vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), // U256::from(HALT_FOR_CALL) // ); // } #[test] fn push0_ok() { - let mut vm = new_vm_with_ops(&[Operation::Push0, Operation::Stop]); + let mut vm = new_vm_with_ops(&[Operation::Push0, Operation::Stop]).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert_eq!(vm.current_call_frame_mut().stack.stack[0], U256::zero()); - assert_eq!(vm.current_call_frame_mut().pc(), 2); + assert_eq!( + vm.current_call_frame_mut().unwrap().stack.stack[0], + U256::zero() + ); + assert_eq!(vm.current_call_frame_mut().unwrap().pc(), 2); } #[test] fn push1_ok() { let to_push = U256::from_big_endian(&[0xff]); let operations = [Operation::Push((1, to_push)), Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert_eq!(vm.current_call_frame_mut().stack.stack[0], to_push); - assert_eq!(vm.current_call_frame_mut().pc(), 3); + assert_eq!(vm.current_call_frame_mut().unwrap().stack.stack[0], to_push); + assert_eq!(vm.current_call_frame_mut().unwrap().pc(), 3); } #[test] fn push5_ok() { let to_push = U256::from_big_endian(&[0xff, 0xff, 0xff, 0xff, 0xff]); let operations = [Operation::Push((5, to_push)), Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert_eq!(vm.current_call_frame_mut().stack.stack[0], to_push); - assert_eq!(vm.current_call_frame_mut().pc(), 7); + assert_eq!(vm.current_call_frame_mut().unwrap().stack.stack[0], to_push); + assert_eq!(vm.current_call_frame_mut().unwrap().pc(), 7); } #[test] fn push31_ok() { let to_push = U256::from_big_endian(&[0xff; 31]); let operations = [Operation::Push((31, to_push)), Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert_eq!(vm.current_call_frame_mut().stack.stack[0], to_push); - assert_eq!(vm.current_call_frame_mut().pc(), 33); + assert_eq!(vm.current_call_frame_mut().unwrap().stack.stack[0], to_push); + assert_eq!(vm.current_call_frame_mut().unwrap().pc(), 33); } #[test] fn push32_ok() { let to_push = U256::from_big_endian(&[0xff; 32]); let operations = [Operation::Push((32, to_push)), Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert_eq!(vm.current_call_frame_mut().stack.stack[0], to_push); - assert_eq!(vm.current_call_frame_mut().pc(), 34); + assert_eq!(vm.current_call_frame_mut().unwrap().stack.stack[0], to_push); + assert_eq!(vm.current_call_frame_mut().unwrap().pc(), 34); } #[test] @@ -3439,21 +3550,21 @@ fn dup1_ok() { Operation::Dup(1), Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let stack_len = vm.current_call_frame_mut().stack.len(); + let stack_len = vm.current_call_frame_mut().unwrap().stack.len(); assert_eq!(stack_len, 2); - assert_eq!(vm.current_call_frame_mut().pc(), 4); + assert_eq!(vm.current_call_frame_mut().unwrap().pc(), 4); assert_eq!( - vm.current_call_frame_mut().stack.stack[stack_len - 1], + vm.current_call_frame_mut().unwrap().stack.stack[stack_len - 1], value ); assert_eq!( - vm.current_call_frame_mut().stack.stack[stack_len - 2], + vm.current_call_frame_mut().unwrap().stack.stack[stack_len - 2], value ); } @@ -3465,21 +3576,21 @@ fn dup16_ok() { operations.extend(vec![Operation::Push0; 15]); operations.extend(vec![Operation::Dup(16), Operation::Stop]); - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let stack_len = vm.current_call_frame_mut().stack.len(); + let stack_len = vm.current_call_frame_mut().unwrap().stack.len(); assert_eq!(stack_len, 17); - assert_eq!(vm.current_call_frame_mut().pc, 19); + assert_eq!(vm.current_call_frame_mut().unwrap().pc, 19); assert_eq!( - vm.current_call_frame_mut().stack.stack[stack_len - 1], + vm.current_call_frame_mut().unwrap().stack.stack[stack_len - 1], value ); assert_eq!( - vm.current_call_frame_mut().stack.stack[stack_len - 17], + vm.current_call_frame_mut().unwrap().stack.stack[stack_len - 17], value ); } @@ -3487,7 +3598,7 @@ fn dup16_ok() { #[test] fn dup_halts_if_stack_underflow() { let operations = [Operation::Dup(5), Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); let tx_report = vm.execute(&mut current_call_frame); @@ -3508,14 +3619,14 @@ fn swap1_ok() { Operation::Swap(1), Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert_eq!(vm.current_call_frame_mut().stack.len(), 2); - assert_eq!(vm.current_call_frame_mut().pc(), 6); - assert_eq!(vm.current_call_frame_mut().stack.stack[0], top); - assert_eq!(vm.current_call_frame_mut().stack.stack[1], bottom); + assert_eq!(vm.current_call_frame_mut().unwrap().stack.len(), 2); + assert_eq!(vm.current_call_frame_mut().unwrap().pc(), 6); + assert_eq!(vm.current_call_frame_mut().unwrap().stack.stack[0], top); + assert_eq!(vm.current_call_frame_mut().unwrap().stack.stack[1], bottom); } #[test] @@ -3527,20 +3638,20 @@ fn swap16_ok() { operations.extend(vec![Operation::Push((1, top))]); operations.extend(vec![Operation::Swap(16), Operation::Stop]); - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let stack_len = vm.current_call_frame_mut().stack.len(); + let stack_len = vm.current_call_frame_mut().unwrap().stack.len(); assert_eq!(stack_len, 17); - assert_eq!(vm.current_call_frame_mut().pc(), 21); + assert_eq!(vm.current_call_frame_mut().unwrap().pc(), 21); assert_eq!( - vm.current_call_frame_mut().stack.stack[stack_len - 1], + vm.current_call_frame_mut().unwrap().stack.stack[stack_len - 1], bottom ); assert_eq!( - vm.current_call_frame_mut().stack.stack[stack_len - 1 - 16], + vm.current_call_frame_mut().unwrap().stack.stack[stack_len - 1 - 16], top ); } @@ -3548,7 +3659,7 @@ fn swap16_ok() { #[test] fn swap_halts_if_stack_underflow() { let operations = [Operation::Swap(5), Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); let tx_report = vm.execute(&mut current_call_frame); @@ -3571,16 +3682,16 @@ fn transient_store() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); - let current_call_frame = vm.current_call_frame_mut(); + let current_call_frame = vm.current_call_frame_mut().unwrap(); assert!(current_call_frame.transient_storage.is_empty()); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let current_call_frame = vm.current_call_frame_mut(); + let current_call_frame = vm.current_call_frame_mut().unwrap(); assert_eq!( *current_call_frame @@ -3595,8 +3706,12 @@ fn transient_store() { fn transient_store_stack_underflow() { let operations = [Operation::Tstore, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); - assert!(vm.current_call_frame_mut().transient_storage.is_empty()); + let mut vm = new_vm_with_ops(&operations).unwrap(); + assert!(vm + .current_call_frame_mut() + .unwrap() + .transient_storage + .is_empty()); let mut current_call_frame = vm.call_frames.pop().unwrap(); let tx_report = vm.execute(&mut current_call_frame); @@ -3618,11 +3733,12 @@ fn transient_load() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); - let caller = vm.current_call_frame_mut().msg_sender; + let caller = vm.current_call_frame_mut().unwrap().msg_sender; vm.current_call_frame_mut() + .unwrap() .transient_storage .insert((caller, key), value); @@ -3630,7 +3746,12 @@ fn transient_load() { vm.execute(&mut current_call_frame); assert_eq!( - *vm.current_call_frame_mut().stack.stack.last().unwrap(), + *vm.current_call_frame_mut() + .unwrap() + .stack + .stack + .last() + .unwrap(), value ) } @@ -3658,18 +3779,19 @@ fn create_happy_path() { .concat(); let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&operations), + ops_to_bytecode(&operations).unwrap(), sender_addr, sender_balance, Db::new(), Cache::default(), - ); - vm.current_call_frame_mut().msg_sender = sender_addr; + ) + .unwrap(); + vm.current_call_frame_mut().unwrap().msg_sender = sender_addr; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let call_frame = vm.current_call_frame_mut(); + let call_frame = vm.current_call_frame_mut().unwrap(); let returned_address = call_frame.stack.pop().unwrap(); let expected_address = VM::calculate_create_address(sender_addr, sender_nonce + 1); @@ -3704,18 +3826,19 @@ fn cant_create_with_size_longer_than_max_code_size() { let operations = create_opcodes(size, offset, value_to_transfer); let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&operations), + ops_to_bytecode(&operations).unwrap(), sender_addr, sender_balance, Db::new(), Cache::default(), - ); - vm.current_call_frame_mut().msg_sender = sender_addr; + ) + .unwrap(); + vm.current_call_frame_mut().unwrap().msg_sender = sender_addr; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let call_frame = vm.current_call_frame_mut(); + let call_frame = vm.current_call_frame_mut().unwrap(); let create_return_value = call_frame.stack.pop().unwrap(); assert_eq!(create_return_value, U256::from(REVERT_FOR_CREATE)); @@ -3737,19 +3860,20 @@ fn cant_create_on_static_contexts() { let operations = create_opcodes(size, offset, value_to_transfer); let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&operations), + ops_to_bytecode(&operations).unwrap(), sender_addr, sender_balance, Db::new(), Cache::default(), - ); - vm.current_call_frame_mut().msg_sender = sender_addr; - vm.current_call_frame_mut().is_static = true; + ) + .unwrap(); + vm.current_call_frame_mut().unwrap().msg_sender = sender_addr; + vm.current_call_frame_mut().unwrap().is_static = true; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let call_frame = vm.current_call_frame_mut(); + let call_frame = vm.current_call_frame_mut().unwrap(); let create_return_value = call_frame.stack.pop().unwrap(); assert_eq!(create_return_value, U256::from(REVERT_FOR_CREATE)); @@ -3771,18 +3895,19 @@ fn cant_create_if_transfer_value_bigger_than_balance() { let operations = create_opcodes(size, offset, value_to_transfer); let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&operations), + ops_to_bytecode(&operations).unwrap(), sender_addr, sender_balance, Db::new(), Cache::default(), - ); - vm.current_call_frame_mut().msg_sender = sender_addr; + ) + .unwrap(); + vm.current_call_frame_mut().unwrap().msg_sender = sender_addr; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let call_frame = vm.current_call_frame_mut(); + let call_frame = vm.current_call_frame_mut().unwrap(); let create_return_value = call_frame.stack.pop().unwrap(); assert_eq!(create_return_value, U256::from(REVERT_FOR_CREATE)); @@ -3809,14 +3934,14 @@ fn cant_create_if_sender_nonce_would_overflow() { Account::new(sender_balance, Bytes::new(), sender_nonce, HashMap::new()), )]); - let mut vm = new_vm_with_ops_db(&operations, db); + let mut vm = new_vm_with_ops_db(&operations, db).unwrap(); - vm.current_call_frame_mut().msg_sender = sender_addr; + vm.current_call_frame_mut().unwrap().msg_sender = sender_addr; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let call_frame = vm.current_call_frame_mut(); + let call_frame = vm.current_call_frame_mut().unwrap(); let create_return_value = call_frame.stack.pop().unwrap(); assert_eq!(create_return_value, U256::from(REVERT_FOR_CREATE)); @@ -3848,19 +3973,19 @@ fn cant_create_if_sender_nonce_would_overflow() { // ] // .concat(); -// let mut vm = new_vm_with_ops(&operations); +// let mut vm = new_vm_with_ops(&operations).unwrap(); // vm.db.accounts.insert( // sender_addr, // Account::default() // .with_balance(sender_balance) // .with_nonce(sender_nonce), // ); -// vm.current_call_frame_mut().msg_sender = sender_addr; +// vm.current_call_frame_mut().unwrap().msg_sender = sender_addr; // let mut current_call_frame = vm.call_frames.pop().unwrap(); // vm.execute(&mut current_call_frame); -// let call_frame = vm.current_call_frame_mut(); +// let call_frame = vm.current_call_frame_mut().unwrap(); // let return_of_created_callframe = call_frame.stack.pop().unwrap(); @@ -3880,14 +4005,14 @@ fn cant_create_if_sender_nonce_would_overflow() { // // after a happy create, we do again a create with same inputs, this should revert as we will create // // an account with the same address // sender_account.nonce = sender_nonce; -// let mut new_vm = new_vm_with_ops(&operations); +// let mut new_vm = new_vm_with_ops(&operations).unwrap(); // new_vm.db = vm.db.clone(); // new_vm.db.accounts = vm.db.accounts.clone(); -// new_vm.current_call_frame_mut().msg_sender = sender_addr; +// new_vm.current_call_frame_mut().unwrap().msg_sender = sender_addr; // let mut current_call_frame = new_vm.call_frames.pop().unwrap(); // new_vm.execute(&mut current_call_frame); -// let call_frame = new_vm.current_call_frame_mut(); +// let call_frame = new_vm.current_call_frame_mut().unwrap(); // let return_of_created_callframe = call_frame.stack.pop().unwrap(); // assert_eq!(return_of_created_callframe, U256::from(REVERT_FOR_CREATE)); // } @@ -3926,18 +4051,19 @@ fn create2_happy_path() { ]; let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&operations), + ops_to_bytecode(&operations).unwrap(), sender_addr, sender_balance, Db::new(), Cache::default(), - ); - vm.current_call_frame_mut().msg_sender = sender_addr; + ) + .unwrap(); + vm.current_call_frame_mut().unwrap().msg_sender = sender_addr; let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let call_frame = vm.current_call_frame_mut(); + let call_frame = vm.current_call_frame_mut().unwrap(); let returned_address = call_frame.stack.pop().unwrap(); assert_eq!(word_to_address(returned_address), expected_address); // check the created account is correct @@ -3975,9 +4101,9 @@ fn create2_happy_path() { // ] // .concat(); -// let mut vm = new_vm_with_ops_addr_bal(ops_to_bytecode(&operations), sender_addr, sender_balance); +// let mut vm = new_vm_with_ops_addr_bal(ops_to_bytecode(&operations).unwrap(), sender_addr, sender_balance); -// vm.current_call_frame_mut().msg_sender = sender_addr; +// vm.current_call_frame_mut().unwrap().msg_sender = sender_addr; // let mut current_call_frame = vm.call_frames.pop().unwrap(); // vm.execute(&mut current_call_frame); @@ -3994,13 +4120,13 @@ fn caller_op() { let mut db = Db::default(); db.add_accounts(vec![( address_that_has_the_code, - Account::default().with_bytecode(ops_to_bytecode(&operations)), + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); let mut cache = Cache::default(); cache.add_account( &address_that_has_the_code, - &Account::default().with_bytecode(ops_to_bytecode(&operations)), + &Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), ); let env = Environment::default_from_address(caller); @@ -4019,7 +4145,7 @@ fn caller_op() { vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from(caller.as_bytes()) ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + gas_cost::CALLER); @@ -4035,13 +4161,13 @@ fn origin_op() { let mut db = Db::default(); db.add_accounts(vec![( address_that_has_the_code, - Account::default().with_bytecode(ops_to_bytecode(&operations)), + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); let mut cache = Cache::default(); cache.add_account( &msg_sender, - &Account::default().with_bytecode(ops_to_bytecode(&operations)), + &Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), ); let env = Environment::default_from_address(msg_sender); @@ -4060,7 +4186,7 @@ fn origin_op() { vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from(msg_sender.as_bytes()) ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + gas_cost::ORIGIN); @@ -4077,18 +4203,19 @@ fn balance_op() { ]; let mut vm = new_vm_with_ops_addr_bal_db( - ops_to_bytecode(&operations), + ops_to_bytecode(&operations).unwrap(), Address::from_low_u64_be(address), U256::from(1234), Db::new(), Cache::default(), - ); + ) + .unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from(1234) ) } @@ -4102,13 +4229,13 @@ fn address_op() { let mut db = Db::default(); db.add_accounts(vec![( address_that_has_the_code, - Account::default().with_bytecode(ops_to_bytecode(&operations)), + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); let mut cache = Cache::default(); cache.add_account( &address_that_has_the_code, - &Account::default().with_bytecode(ops_to_bytecode(&operations)), + &Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), ); let env = Environment::default_from_address(Address::from_low_u64_be(42)); @@ -4122,11 +4249,12 @@ fn address_op() { cache, ) .unwrap(); + let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from(address_that_has_the_code.as_bytes()) ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + gas_cost::ADDRESS); @@ -4143,7 +4271,7 @@ fn selfbalance_op() { db.add_accounts(vec![( address_that_has_the_code, Account::default() - .with_bytecode(ops_to_bytecode(&operations)) + .with_bytecode(ops_to_bytecode(&operations).unwrap()) .with_balance(balance), )]); @@ -4151,7 +4279,7 @@ fn selfbalance_op() { cache.add_account( &address_that_has_the_code, &Account::default() - .with_bytecode(ops_to_bytecode(&operations)) + .with_bytecode(ops_to_bytecode(&operations).unwrap()) .with_balance(balance), ); @@ -4166,10 +4294,14 @@ fn selfbalance_op() { cache, ) .unwrap(); + let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert_eq!(vm.current_call_frame_mut().stack.pop().unwrap(), balance); + assert_eq!( + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), + balance + ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + gas_cost::SELFBALANCE); } @@ -4184,13 +4316,13 @@ fn callvalue_op() { db.add_accounts(vec![( address_that_has_the_code, - Account::default().with_bytecode(ops_to_bytecode(&operations)), + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); let mut cache = Cache::default(); cache.add_account( &address_that_has_the_code, - &Account::default().with_bytecode(ops_to_bytecode(&operations)), + &Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), ); let env = Environment::default_from_address(Address::from_low_u64_be(42)); @@ -4208,7 +4340,10 @@ fn callvalue_op() { let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert_eq!(vm.current_call_frame_mut().stack.pop().unwrap(), value); + assert_eq!( + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), + value + ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + gas_cost::CALLVALUE); } @@ -4222,13 +4357,13 @@ fn codesize_op() { db.add_accounts(vec![( address_that_has_the_code, - Account::default().with_bytecode(ops_to_bytecode(&operations)), + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); let mut cache = Cache::default(); cache.add_account( &address_that_has_the_code, - &Account::default().with_bytecode(ops_to_bytecode(&operations)), + &Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), ); let env = Environment::default_from_address(Address::from_low_u64_be(42)); @@ -4246,7 +4381,7 @@ fn codesize_op() { let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from(2) ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + gas_cost::CODESIZE); @@ -4261,13 +4396,13 @@ fn gasprice_op() { db.add_accounts(vec![( address_that_has_the_code, - Account::default().with_bytecode(ops_to_bytecode(&operations)), + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); let mut cache = Cache::default(); cache.add_account( &address_that_has_the_code, - &Account::default().with_bytecode(ops_to_bytecode(&operations)), + &Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), ); let mut env = Environment::default_from_address(Address::from_low_u64_be(42)); @@ -4286,7 +4421,7 @@ fn gasprice_op() { let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), U256::from(0x9876) ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + gas_cost::GASPRICE); @@ -4318,13 +4453,13 @@ fn codecopy_op() { db.add_accounts(vec![( address_that_has_the_code, - Account::default().with_bytecode(ops_to_bytecode(&operations)), + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); let mut cache = Cache::default(); cache.add_account( &address_that_has_the_code, - &Account::default().with_bytecode(ops_to_bytecode(&operations)), + &Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), ); let env = Environment::default_from_address(Address::from_low_u64_be(42)); @@ -4343,7 +4478,7 @@ fn codecopy_op() { vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().memory.load(0).unwrap(), + vm.current_call_frame_mut().unwrap().memory.load(0).unwrap(), expected_memory ); assert_eq!( @@ -4364,14 +4499,17 @@ fn extcodesize_existing_account() { let mut db = Db::default(); db.add_accounts(vec![( address_with_code, - Account::default().with_bytecode(ops_to_bytecode(&operations)), + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); - let mut vm = new_vm_with_ops_db(&operations, db); + let mut vm = new_vm_with_ops_db(&operations, db).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert_eq!(vm.current_call_frame_mut().stack.pop().unwrap(), 23.into()); + assert_eq!( + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), + 23.into() + ); assert_eq!(vm.env.consumed_gas, 23603.into()); } @@ -4384,11 +4522,14 @@ fn extcodesize_non_existing_account() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - assert_eq!(vm.current_call_frame_mut().stack.pop().unwrap(), 0.into()); + assert_eq!( + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), + 0.into() + ); assert_eq!(vm.env.consumed_gas, 23603.into()); } @@ -4409,15 +4550,16 @@ fn extcodecopy_existing_account() { let mut db = Db::new(); db.add_accounts(vec![( address_with_code, - Account::default().with_bytecode(ops_to_bytecode(&operations)), + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); - let mut vm = new_vm_with_ops_db(&operations, db); + let mut vm = new_vm_with_ops_db(&operations, db).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( vm.current_call_frame_mut() + .unwrap() .memory .load_range(0, size) .unwrap(), @@ -4440,12 +4582,13 @@ fn extcodecopy_non_existing_account() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( vm.current_call_frame_mut() + .unwrap() .memory .load_range(0, size) .unwrap(), @@ -4466,12 +4609,12 @@ fn extcodehash_account_with_empty_code() { let mut db = Db::default(); db.add_accounts(vec![(address_with_code, Account::default())]); - let mut vm = new_vm_with_ops_db(&operations, db); + let mut vm = new_vm_with_ops_db(&operations, db).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470".into() ); assert_eq!(vm.env.consumed_gas, 23603.into()); @@ -4486,12 +4629,12 @@ fn extcodehash_non_existing_account() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); assert_eq!( - vm.current_call_frame_mut().stack.pop().unwrap(), + vm.current_call_frame_mut().unwrap().stack.pop().unwrap(), "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470".into() ); assert_eq!(vm.env.consumed_gas, 23603.into()); @@ -4501,7 +4644,7 @@ fn extcodehash_non_existing_account() { fn invalid_opcode() { let operations = [Operation::Invalid, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut vm = new_vm_with_ops(&operations).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); let tx_report = vm.execute(&mut current_call_frame); @@ -4524,7 +4667,7 @@ fn revert_opcode() { Operation::Revert, ]; - let mut vm = new_vm_with_ops(&ops); + let mut vm = new_vm_with_ops(&ops).unwrap(); let mut current_call_frame = vm.call_frames.pop().unwrap(); let tx_report = vm.execute(&mut current_call_frame); @@ -4549,8 +4692,8 @@ fn revert_sstore() { Operation::Revert, ]; - let mut vm = new_vm_with_ops(&operations); - vm.current_call_frame_mut().code_address = sender_address; + let mut vm = new_vm_with_ops(&operations).unwrap(); + vm.current_call_frame_mut().unwrap().code_address = sender_address; vm.cache.add_account(&sender_address, &Account::default()); let mut current_call_frame = vm.call_frames.pop().unwrap(); From f2c0e5c448f156ec0108d6e67f56f52e4714f8e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jerem=C3=ADas=20Salom=C3=B3n?= <48994069+JereSalo@users.noreply.github.com> Date: Wed, 13 Nov 2024 17:50:09 -0300 Subject: [PATCH 09/17] fix(levm): handle slicing and indexing errors (#1107) **Motivation** **Description** Partially Closes #1074 --------- Co-authored-by: Juani Medone Co-authored-by: maximopalopoli Co-authored-by: Javier Chatruc Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri Co-authored-by: Maximo Palopoli <96491141+maximopalopoli@users.noreply.github.com> --- crates/vm/levm/Cargo.toml | 8 +- crates/vm/levm/src/errors.rs | 14 +++- crates/vm/levm/src/memory.rs | 39 ++++++---- .../levm/src/opcode_handlers/environment.rs | 74 +++++++++---------- crates/vm/levm/src/operations.rs | 9 ++- crates/vm/levm/src/vm.rs | 29 ++++++-- crates/vm/levm/tests/tests.rs | 3 +- 7 files changed, 111 insertions(+), 65 deletions(-) diff --git a/crates/vm/levm/Cargo.toml b/crates/vm/levm/Cargo.toml index 01cd8421c..c1e7a193e 100644 --- a/crates/vm/levm/Cargo.toml +++ b/crates/vm/levm/Cargo.toml @@ -30,11 +30,15 @@ warnings = "warn" rust_2018_idioms = "warn" [lints.clippy] +panic = "deny" +unnecessary_cast = "warn" +deref_by_slicing = "warn" +indexing_slicing = "warn" +manual_unwrap_or = "warn" +manual_unwrap_or_default = "warn" as_conversions = "deny" unwrap_used = "deny" expect_used = "deny" -# panic = "deny" -unnecessary_cast = "warn" [lib] path = "./src/lib.rs" diff --git a/crates/vm/levm/src/errors.rs b/crates/vm/levm/src/errors.rs index b28d88c81..3250a32ca 100644 --- a/crates/vm/levm/src/errors.rs +++ b/crates/vm/levm/src/errors.rs @@ -76,8 +76,16 @@ pub enum VMError { #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] pub enum InternalError { - #[error("Could not access last callframe")] - CouldNotAccessLastCallframe, + #[error("Tried to access last call frame but found none")] + CouldNotAccessLastCallframe, // Last callframe before execution is the same as the first, but after execution the last callframe is actually the initial CF + #[error("Tried to read from empty code")] + TriedToIndexEmptyCode, + #[error("Failed computing CREATE address")] + CouldNotComputeCreateAddress, + #[error("Failed computing CREATE2 address")] + CouldNotComputeCreate2Address, + #[error("Tried to slice non-existing data")] + SlicingError, #[error("Could not pop callframe")] CouldNotPopCallframe, #[error("Account not found")] @@ -90,8 +98,6 @@ pub enum InternalError { AccountShouldHaveBeenCached, #[error("Tried to convert one type to another")] ConversionError, - #[error("Failed computing CREATE2 address")] - CouldNotComputeCreate2Address, #[error("Division error")] DivisionError, } diff --git a/crates/vm/levm/src/memory.rs b/crates/vm/levm/src/memory.rs index 214c13389..3f4a9ff1f 100644 --- a/crates/vm/levm/src/memory.rs +++ b/crates/vm/levm/src/memory.rs @@ -122,22 +122,35 @@ impl Memory { .checked_add(size) .ok_or(VMError::MemoryLoadOutOfBounds)?, ); - self.resize(max_size); - let mut temp = vec![0u8; size]; - temp.copy_from_slice( - &self.data[src_offset - ..src_offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?], - ); + if max_size > self.data.len() { + self.resize(max_size); + } - self.data[dest_offset - ..dest_offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?] - .copy_from_slice(&temp); + let mut temp = vec![0u8; size]; + + for i in 0..size { + if let Some(temp_byte) = temp.get_mut(i) { + *temp_byte = *self + .data + .get( + src_offset + .checked_add(i) + .ok_or(VMError::MemoryLoadOutOfBounds)?, + ) + .unwrap_or(&0u8); + } + } + for i in 0..size { + if let Some(memory_byte) = self.data.get_mut( + dest_offset + .checked_add(i) + .ok_or(VMError::MemoryLoadOutOfBounds)?, + ) { + *memory_byte = *temp.get(i).unwrap_or(&0u8); + } + } Ok(()) } diff --git a/crates/vm/levm/src/opcode_handlers/environment.rs b/crates/vm/levm/src/opcode_handlers/environment.rs index 472df2399..7cddfc8f9 100644 --- a/crates/vm/levm/src/opcode_handlers/environment.rs +++ b/crates/vm/levm/src/opcode_handlers/environment.rs @@ -4,7 +4,7 @@ use crate::{ call_opcode::{COLD_ADDRESS_ACCESS_COST, WARM_ADDRESS_ACCESS_COST}, gas_cost, WORD_SIZE, }, - errors::{OpcodeSuccess, VMError}, + errors::{InternalError, OpcodeSuccess, VMError}, vm::{word_to_address, VM}, }; use bytes::Bytes; @@ -106,21 +106,20 @@ impl VM { .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - // This check is because if offset is larger than the calldata then we should push 0 to the stack. - let result = if offset < current_call_frame.calldata.len() { - // Read calldata from offset to the end - let calldata = current_call_frame.calldata.slice(offset..); - - // Get the 32 bytes from the data slice, padding with 0 if fewer than 32 bytes are available - let mut padded_calldata = [0u8; 32]; - let data_len_to_copy = calldata.len().min(32); - - padded_calldata[..data_len_to_copy].copy_from_slice(&calldata[..data_len_to_copy]); - - U256::from_big_endian(&padded_calldata) - } else { - U256::zero() - }; + // All bytes after the end of the calldata are set to 0. + let mut data = [0u8; 32]; + for (i, byte) in current_call_frame + .calldata + .iter() + .skip(offset) + .take(32) + .enumerate() + { + if let Some(data_byte) = data.get_mut(i) { + *data_byte = *byte; + } + } + let result = U256::from_big_endian(&data); current_call_frame.stack.push(result)?; @@ -178,25 +177,20 @@ impl VM { return Ok(OpcodeSuccess::Continue); } - // This check is because if offset is larger than the calldata then we should push 0 to the stack. - let result = if calldata_offset < current_call_frame.calldata.len() { - // Read calldata from offset to the end - let calldata = current_call_frame.calldata.slice(calldata_offset..); - - // Get the 32 bytes from the data slice, padding with 0 if fewer than 32 bytes are available - let mut padded_calldata = vec![0u8; size]; - let data_len_to_copy = calldata.len().min(size); - - padded_calldata[..data_len_to_copy].copy_from_slice(&calldata[..data_len_to_copy]); - - padded_calldata - } else { - vec![0u8; size] - }; + let mut data = [0u8; 32]; + for (i, byte) in current_call_frame + .calldata + .iter() + .skip(calldata_offset) + .take(32) + .enumerate() + { + if let Some(data_byte) = data.get_mut(i) { + *data_byte = *byte; + } + } - current_call_frame - .memory - .store_bytes(dest_offset, &result)?; + current_call_frame.memory.store_bytes(dest_offset, &data)?; Ok(OpcodeSuccess::Continue) } @@ -360,10 +354,14 @@ impl VM { } current_call_frame.memory.store_bytes( dest_offset, - &bytecode[offset - ..offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?], + bytecode + .get( + offset + ..offset + .checked_add(size) + .ok_or(VMError::MemoryLoadOutOfBounds)?, + ) + .ok_or(VMError::Internal(InternalError::SlicingError))?, // bytecode can be "refactored" in order to avoid handling the error. )?; Ok(OpcodeSuccess::Continue) diff --git a/crates/vm/levm/src/operations.rs b/crates/vm/levm/src/operations.rs index 07221e004..b55d899c1 100644 --- a/crates/vm/levm/src/operations.rs +++ b/crates/vm/levm/src/operations.rs @@ -1,4 +1,7 @@ -use crate::{errors::VMError, opcodes::Opcode}; +use crate::{ + errors::{InternalError, VMError}, + opcodes::Opcode, +}; use bytes::Bytes; use ethereum_rust_core::U256; @@ -176,7 +179,9 @@ impl Operation { ); let mut word_buffer = [0; 32]; value.to_big_endian(&mut word_buffer); - let value_to_push = &word_buffer[(32 - n_usize)..]; + let value_to_push = &word_buffer + .get((32 - n_usize)..) + .ok_or(VMError::Internal(InternalError::SlicingError))?; assert_eq!(value_to_push.len(), n_usize); let opcode = Opcode::try_from(u8::from(Opcode::PUSH0) + *n)?; let mut bytes = vec![u8::from(opcode)]; diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 47980a4a3..1fb95cf93 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -105,7 +105,9 @@ impl VM { // (2) let new_contract_address = - VM::calculate_create_address(env.origin, sender_account_info.nonce); + VM::calculate_create_address(env.origin, sender_account_info.nonce).map_err( + |_| VMError::Internal(InternalError::CouldNotComputeCreateAddress), + )?; // TODO: Remove after merging the PR that removes unwraps. // (3) let created_contract = Account::new(value, calldata.clone(), 1, HashMap::new()); @@ -433,6 +435,14 @@ impl VM { let sender = initial_call_frame.msg_sender; + let initial_call_frame = self + .call_frames + .last() + .ok_or(VMError::Internal( + InternalError::CouldNotAccessLastCallframe, + ))? + .clone(); + // This cost applies both for call and create // 4 gas for each zero byte in the transaction data 16 gas for each non-zero byte in the transaction. let mut calldata_cost = 0; @@ -465,7 +475,11 @@ impl VM { return Err(VMError::ContractOutputTooBig); } // Supposing contract code has contents - if contract_code[0] == INVALID_CONTRACT_PREFIX { + if *contract_code + .first() + .ok_or(VMError::Internal(InternalError::TriedToIndexEmptyCode))? + == INVALID_CONTRACT_PREFIX + { return Err(VMError::InvalidInitialByte); } @@ -662,12 +676,17 @@ impl VM { /// Calculates the address of a new conctract using the CREATE opcode as follow /// /// address = keccak256(rlp([sender_address,sender_nonce]))[12:] - pub fn calculate_create_address(sender_address: Address, sender_nonce: u64) -> Address { + pub fn calculate_create_address( + sender_address: Address, + sender_nonce: u64, + ) -> Result { let mut encoded = Vec::new(); (sender_address, sender_nonce).encode(&mut encoded); let mut hasher = Keccak256::new(); hasher.update(encoded); - Address::from_slice(&hasher.finalize()[12..]) + Ok(Address::from_slice(hasher.finalize().get(12..).ok_or( + VMError::Internal(InternalError::CouldNotComputeCreateAddress), + )?)) } /// Calculates the address of a new contract using the CREATE2 opcode as follow @@ -823,7 +842,7 @@ impl VM { None => Self::calculate_create_address( current_call_frame.msg_sender, sender_account.info.nonce, - ), + )?, }; if self.cache.accounts.contains_key(&new_address) { diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index d0da808be..f54a48c67 100644 --- a/crates/vm/levm/tests/tests.rs +++ b/crates/vm/levm/tests/tests.rs @@ -1,3 +1,4 @@ +#![allow(clippy::indexing_slicing)] #![allow(clippy::unwrap_used)] use bytes::Bytes; @@ -3794,7 +3795,7 @@ fn create_happy_path() { let call_frame = vm.current_call_frame_mut().unwrap(); let returned_address = call_frame.stack.pop().unwrap(); - let expected_address = VM::calculate_create_address(sender_addr, sender_nonce + 1); + let expected_address = VM::calculate_create_address(sender_addr, sender_nonce + 1).unwrap(); assert_eq!(word_to_address(returned_address), expected_address); // check the created account is correct From 6df349e7f72be62d1af9b4f31f353ce315f72e93 Mon Sep 17 00:00:00 2001 From: Maximo Palopoli <96491141+maximopalopoli@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:11:52 -0300 Subject: [PATCH 10/17] fix(levm): create2 address generation (#1149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Motivation** Previously the address was not generated as expected, since comparations did not match. **Description** Also, fixed the test that checked that functionality, because checked a wrong behavior. --------- Co-authored-by: JereSalo Co-authored-by: Juani Medone Co-authored-by: Javier Chatruc Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri Co-authored-by: Jeremías Salomón <48994069+JereSalo@users.noreply.github.com> --- crates/vm/levm/src/vm.rs | 35 +++++++++++++++++++++-------------- crates/vm/levm/tests/tests.rs | 2 +- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 1fb95cf93..28cd0ee25 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -13,6 +13,7 @@ use ethereum_rust_core::{types::TxKind, Address, H256, U256}; use ethereum_rust_rlp; use ethereum_rust_rlp::encode::RLPEncode; use gas_cost::KECCAK25_DYNAMIC_BASE; +use keccak_hash::keccak; use sha3::{Digest, Keccak256}; use std::{ collections::{HashMap, HashSet}, @@ -700,19 +701,27 @@ impl VM { initialization_code: &Bytes, salt: U256, ) -> Result { - let mut hasher = Keccak256::new(); - hasher.update(initialization_code.clone()); - let initialization_code_hash = hasher.finalize(); - let mut hasher = Keccak256::new(); + let init_code_hash = keccak(initialization_code); let mut salt_bytes = [0; 32]; salt.to_big_endian(&mut salt_bytes); - hasher.update([0xff]); - hasher.update(sender_address.as_bytes()); - hasher.update(salt_bytes); - hasher.update(initialization_code_hash); - Ok(Address::from_slice(hasher.finalize().get(12..).ok_or( - VMError::Internal(InternalError::CouldNotComputeCreate2Address), - )?)) + + let generated_address = Address::from_slice( + keccak( + [ + &[0xff], + sender_address.as_bytes(), + &salt_bytes, + init_code_hash.as_bytes(), + ] + .concat(), + ) + .as_bytes() + .get(12..) + .ok_or(VMError::Internal( + InternalError::CouldNotComputeCreate2Address, + ))?, + ); + Ok(generated_address) } fn compute_gas_create( @@ -836,9 +845,7 @@ impl VM { ); let new_address = match salt { - Some(salt) => { - Self::calculate_create2_address(current_call_frame.msg_sender, &code, salt)? - } + Some(salt) => Self::calculate_create2_address(current_call_frame.to, &code, salt)?, None => Self::calculate_create_address( current_call_frame.msg_sender, sender_account.info.nonce, diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index f54a48c67..896aec6aa 100644 --- a/crates/vm/levm/tests/tests.rs +++ b/crates/vm/levm/tests/tests.rs @@ -4031,7 +4031,7 @@ fn create2_happy_path() { // Code that returns the value 0xffffffff putting it in memory let initialization_code = hex::decode("63FFFFFFFF6000526004601CF3").unwrap(); let expected_address = VM::calculate_create2_address( - sender_addr, + Address::from_low_u64_be(42), // this is the addr initializated in new_vm_with_ops_addr_bal_db &Bytes::from(initialization_code.clone()), U256::from(salt), ) From b9ac4201ef8598116a729b23c923e5c49ec62b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jerem=C3=ADas=20Salom=C3=B3n?= <48994069+JereSalo@users.noreply.github.com> Date: Wed, 13 Nov 2024 19:32:45 -0300 Subject: [PATCH 11/17] feat(levm): add gas refunds for sstore (#1113) **Motivation** **Description** Closes #1087 --------- Co-authored-by: Juani Medone Co-authored-by: maximopalopoli Co-authored-by: Javier Chatruc Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri --- crates/vm/levm/src/errors.rs | 4 ++ .../stack_memory_storage_flow.rs | 40 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/crates/vm/levm/src/errors.rs b/crates/vm/levm/src/errors.rs index 3250a32ca..603b20735 100644 --- a/crates/vm/levm/src/errors.rs +++ b/crates/vm/levm/src/errors.rs @@ -72,6 +72,10 @@ pub enum VMError { CreationCostIsTooHigh, #[error("Max gas limit exceeded")] MaxGasLimitExceeded, + #[error("Gas refunds underflow")] + GasRefundsUnderflow, + #[error("Gas refunds overflow")] + GasRefundsOverflow, } #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] diff --git a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs index f54f87d17..6d71ea29b 100644 --- a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs +++ b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs @@ -185,6 +185,7 @@ impl VM { let key = current_call_frame.stack.pop()?; let value = current_call_frame.stack.pop()?; + // Convert key from U256 to H256 let mut bytes = [0u8; 32]; key.to_big_endian(&mut bytes); let key = H256::from(bytes); @@ -214,6 +215,45 @@ impl VM { self.increase_consumed_gas(current_call_frame, base_dynamic_gas)?; + // Gas Refunds + // TODO: Think about what to do in case of underflow of gas refunds (when we try to substract from it if the value is low) + let mut gas_refunds = U256::zero(); + if value != storage_slot.current_value { + if storage_slot.current_value == storage_slot.original_value { + if storage_slot.original_value.is_zero() && value.is_zero() { + gas_refunds = gas_refunds + .checked_add(U256::from(4800)) + .ok_or(VMError::GasRefundsOverflow)?; + } + } else if !storage_slot.original_value.is_zero() { + if storage_slot.current_value.is_zero() { + gas_refunds = gas_refunds + .checked_sub(U256::from(4800)) + .ok_or(VMError::GasRefundsUnderflow)?; + } else if value.is_zero() { + gas_refunds = gas_refunds + .checked_add(U256::from(4800)) + .ok_or(VMError::GasRefundsOverflow)?; + } + } else if value == storage_slot.original_value { + if storage_slot.original_value.is_zero() { + gas_refunds = gas_refunds + .checked_add(U256::from(19900)) + .ok_or(VMError::GasRefundsOverflow)?; + } else { + gas_refunds = gas_refunds + .checked_add(U256::from(2800)) + .ok_or(VMError::GasRefundsOverflow)?; + } + } + }; + + self.env.refunded_gas = self + .env + .refunded_gas + .checked_add(gas_refunds) + .ok_or(VMError::GasLimitPriceProductOverflow)?; + self.cache.write_account_storage( &address, key, From 9076208bf9db9f807307dc2cff34f30b61843531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jerem=C3=ADas=20Salom=C3=B3n?= <48994069+JereSalo@users.noreply.github.com> Date: Wed, 13 Nov 2024 21:06:53 -0300 Subject: [PATCH 12/17] feat(levm): initialize cache with sender, recipient and coinbase accounts (#1114) **Motivation** **Description** As stated in [evm.codes](https://www.evm.codes/about#access_list), this PR initializes the cache with the corresponding addresses. (Without implementing yet the concept of an Access List transaction) Notes: Our `Cache` struct replaces the need for a `Substate` that keeps warm addresses and storage slots because we know that if an account or storage slot is in our cache then it is already warm. Closes #1088 --------- Co-authored-by: Juani Medone Co-authored-by: maximopalopoli Co-authored-by: Javier Chatruc Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri --- crates/vm/levm/src/account.rs | 9 +++++++++ crates/vm/levm/src/vm.rs | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/vm/levm/src/account.rs b/crates/vm/levm/src/account.rs index fa064f5ea..672563477 100644 --- a/crates/vm/levm/src/account.rs +++ b/crates/vm/levm/src/account.rs @@ -30,6 +30,15 @@ pub struct StorageSlot { pub current_value: U256, } +impl From for Account { + fn from(info: AccountInfo) -> Self { + Self { + info, + storage: HashMap::new(), + } + } +} + impl Account { pub fn new( balance: U256, diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 28cd0ee25..3351dcc09 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -74,14 +74,25 @@ impl VM { ) -> Result { // Maybe this decision should be made in an upper layer + // Add sender, coinbase and recipient (in the case of a Call) to cache [https://www.evm.codes/about#access_list] + let sender_account_info = db.get_account_info(env.origin); + cache.add_account(&env.origin, &Account::from(sender_account_info.clone())); + + let coinbase_account_info = db.get_account_info(env.coinbase); + cache.add_account(&env.coinbase, &Account::from(coinbase_account_info)); + match to { TxKind::Call(address_to) => { + // add address_to to cache + let recipient_account_info = db.get_account_info(address_to); + cache.add_account(&address_to, &Account::from(recipient_account_info.clone())); + // CALL tx let initial_call_frame = CallFrame::new( env.origin, address_to, address_to, - db.get_account_info(address_to).bytecode, + recipient_account_info.bytecode, value, calldata.clone(), false, @@ -101,8 +112,6 @@ impl VM { } TxKind::Create => { // CREATE tx - let sender_account_info = db.get_account_info(env.origin); - // Note that this is a copy of account, not the real one // (2) let new_contract_address = From 15cf7e2171556f6c733f6b040a86ccd8a4596b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jerem=C3=ADas=20Salom=C3=B3n?= <48994069+JereSalo@users.noreply.github.com> Date: Thu, 14 Nov 2024 09:36:59 -0300 Subject: [PATCH 13/17] fix(levm): fixing main (#1161) **Motivation** - Test changes merged to main and fix problems that arose **Description** - Value transfer between sender and recipient was done twice, I removed it in `validate_transaction()` Closes #issue_number --- crates/vm/levm/src/vm.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 3351dcc09..1a15ae948 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -356,9 +356,7 @@ impl VM { } let origin = self.env.origin; - let to = call_frame.to; - let mut receiver_account = self.get_account(&to); let mut sender_account = self.get_account(&origin); // See if it's raised in upper layers @@ -376,12 +374,8 @@ impl VM { if sender_account.info.balance < call_frame.msg_value { return Err(VMError::SenderBalanceShouldContainTransferValue); } - // TODO: This belongs elsewhere. - sender_account.info.balance -= call_frame.msg_value; - receiver_account.info.balance += call_frame.msg_value; self.cache.add_account(&origin, &sender_account); - self.cache.add_account(&to, &receiver_account); // (7) if self.env.gas_price < self.env.base_fee_per_gas { From 108163a5ee4191645ead8cdfe02fe45f29c779d4 Mon Sep 17 00:00:00 2001 From: Maximo Palopoli <96491141+maximopalopoli@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:09:42 -0300 Subject: [PATCH 14/17] fix(levm): prevent unsafe arithmetic (#1095) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Motivation** The idea is to prevent arithmetic overflows and underflows in the levm code. **Description** Design desitions: - For the gas increment I used `checked_add` and added a `ConsumedGasOverflow` error. - For depth used `checked_add` and the `StackOverflow` error - In the `VM::execute` method I decided to do not raise an error, because the previous behavior was to do not raise errors, so used the saturating functions - For opcode operations used `InvalidOpcode` error Closes #1075 --------- Co-authored-by: JereSalo Co-authored-by: Juani Medone Co-authored-by: Javier Chatruc Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri Co-authored-by: Jeremías Salomón <48994069+JereSalo@users.noreply.github.com> --- crates/vm/levm/Cargo.toml | 5 + crates/vm/levm/src/account.rs | 16 +- crates/vm/levm/src/call_frame.rs | 25 +- crates/vm/levm/src/constants.rs | 106 +-- crates/vm/levm/src/db.rs | 10 +- crates/vm/levm/src/errors.rs | 115 ++-- crates/vm/levm/src/gas_cost.rs | 620 ++++++++++++++++++ crates/vm/levm/src/lib.rs | 1 + crates/vm/levm/src/memory.rs | 110 ++-- .../vm/levm/src/opcode_handlers/arithmetic.rs | 63 +- .../src/opcode_handlers/bitwise_comparison.rs | 93 ++- crates/vm/levm/src/opcode_handlers/block.rs | 42 +- crates/vm/levm/src/opcode_handlers/dup.rs | 19 +- .../levm/src/opcode_handlers/environment.rs | 112 ++-- .../vm/levm/src/opcode_handlers/exchange.rs | 9 +- crates/vm/levm/src/opcode_handlers/keccak.rs | 15 +- crates/vm/levm/src/opcode_handlers/logging.rs | 18 +- crates/vm/levm/src/opcode_handlers/push.rs | 20 +- .../stack_memory_storage_flow.rs | 109 ++- crates/vm/levm/src/opcode_handlers/system.rs | 200 +++--- crates/vm/levm/src/operations.rs | 37 +- crates/vm/levm/src/vm.rs | 223 ++++--- crates/vm/levm/tests/ef/report.rs | 4 + crates/vm/levm/tests/tests.rs | 1 + 24 files changed, 1340 insertions(+), 633 deletions(-) create mode 100644 crates/vm/levm/src/gas_cost.rs diff --git a/crates/vm/levm/Cargo.toml b/crates/vm/levm/Cargo.toml index c1e7a193e..8c6cd951a 100644 --- a/crates/vm/levm/Cargo.toml +++ b/crates/vm/levm/Cargo.toml @@ -40,5 +40,10 @@ as_conversions = "deny" unwrap_used = "deny" expect_used = "deny" +arithmetic_side_effects = "deny" +overflow_check_conditional = "warn" +manual_saturating_arithmetic = "warn" + + [lib] path = "./src/lib.rs" diff --git a/crates/vm/levm/src/account.rs b/crates/vm/levm/src/account.rs index 672563477..558d131bf 100644 --- a/crates/vm/levm/src/account.rs +++ b/crates/vm/levm/src/account.rs @@ -1,10 +1,12 @@ +use crate::{ + constants::EMPTY_CODE_HASH, + errors::{InternalError, VMError}, +}; use bytes::Bytes; use ethereum_rust_core::{H256, U256}; use keccak_hash::keccak; use std::collections::HashMap; -use crate::{constants::EMPTY_CODE_HASH, errors::VMError}; - #[derive(Clone, Default, Debug, PartialEq, Eq)] pub struct AccountInfo { pub balance: U256, @@ -88,7 +90,13 @@ impl Account { self } - pub fn increment_nonce(&mut self) { - self.info.nonce += 1; + // TODO: Replace nonce increments with this or cache's analog (currently does not have senders) + pub fn increment_nonce(&mut self) -> Result<(), VMError> { + self.info.nonce = self + .info + .nonce + .checked_add(1) + .ok_or(VMError::Internal(InternalError::NonceOverflowed))?; + Ok(()) } } diff --git a/crates/vm/levm/src/call_frame.rs b/crates/vm/levm/src/call_frame.rs index 47c3b32b9..a3d237a63 100644 --- a/crates/vm/levm/src/call_frame.rs +++ b/crates/vm/levm/src/call_frame.rs @@ -1,4 +1,9 @@ -use crate::{constants::STACK_LIMIT, errors::VMError, memory::Memory, opcodes::Opcode}; +use crate::{ + constants::STACK_LIMIT, + errors::{InternalError, VMError}, + memory::Memory, + opcodes::Opcode, +}; use bytes::Bytes; use ethereum_rust_core::{types::Log, Address, U256}; use std::collections::HashMap; @@ -114,18 +119,22 @@ impl CallFrame { } } - pub fn next_opcode(&mut self) -> Option { + pub fn next_opcode(&mut self) -> Result, VMError> { let opcode = self.opcode_at(self.pc); - self.increment_pc(); - opcode + self.increment_pc()?; + Ok(opcode) } - pub fn increment_pc_by(&mut self, count: usize) { - self.pc += count; + pub fn increment_pc_by(&mut self, count: usize) -> Result<(), VMError> { + self.pc = self + .pc + .checked_add(count) + .ok_or(VMError::Internal(InternalError::PCOverflowed))?; + Ok(()) } - pub fn increment_pc(&mut self) { - self.increment_pc_by(1); + pub fn increment_pc(&mut self) -> Result<(), VMError> { + self.increment_pc_by(1) } pub fn pc(&self) -> usize { diff --git a/crates/vm/levm/src/constants.rs b/crates/vm/levm/src/constants.rs index 1b0b7d980..8984a6673 100644 --- a/crates/vm/levm/src/constants.rs +++ b/crates/vm/levm/src/constants.rs @@ -1,4 +1,3 @@ -use crate::errors::{InternalError, VMError}; use ethereum_rust_core::{H256, U256}; pub const SUCCESS_FOR_CALL: i32 = 1; @@ -8,101 +7,6 @@ pub const SUCCESS_FOR_RETURN: i32 = 1; pub const REVERT_FOR_CREATE: i32 = 0; pub const WORD_SIZE: usize = 32; -/// Contains the gas costs of the EVM instructions (in wei) -pub mod gas_cost { - use ethereum_rust_core::U256; - - pub const ADD: U256 = U256([3, 0, 0, 0]); - pub const MUL: U256 = U256([5, 0, 0, 0]); - pub const SUB: U256 = U256([3, 0, 0, 0]); - pub const DIV: U256 = U256([5, 0, 0, 0]); - pub const SDIV: U256 = U256([5, 0, 0, 0]); - pub const MOD: U256 = U256([5, 0, 0, 0]); - pub const SMOD: U256 = U256([5, 0, 0, 0]); - pub const ADDMOD: U256 = U256([8, 0, 0, 0]); - pub const MULMOD: U256 = U256([8, 0, 0, 0]); - pub const EXP_STATIC: U256 = U256([10, 0, 0, 0]); - pub const EXP_DYNAMIC_BASE: U256 = U256([50, 0, 0, 0]); - pub const SIGNEXTEND: U256 = U256([5, 0, 0, 0]); - pub const LT: U256 = U256([3, 0, 0, 0]); - pub const GT: U256 = U256([3, 0, 0, 0]); - pub const SLT: U256 = U256([3, 0, 0, 0]); - pub const SGT: U256 = U256([3, 0, 0, 0]); - pub const EQ: U256 = U256([3, 0, 0, 0]); - pub const ISZERO: U256 = U256([3, 0, 0, 0]); - pub const AND: U256 = U256([3, 0, 0, 0]); - pub const OR: U256 = U256([3, 0, 0, 0]); - pub const XOR: U256 = U256([3, 0, 0, 0]); - pub const NOT: U256 = U256([3, 0, 0, 0]); - pub const BYTE: U256 = U256([3, 0, 0, 0]); - pub const SHL: U256 = U256([3, 0, 0, 0]); - pub const SHR: U256 = U256([3, 0, 0, 0]); - pub const SAR: U256 = U256([3, 0, 0, 0]); - pub const KECCAK25_STATIC: U256 = U256([30, 0, 0, 0]); - pub const KECCAK25_DYNAMIC_BASE: U256 = U256([6, 0, 0, 0]); - pub const CALLDATALOAD: U256 = U256([3, 0, 0, 0]); - pub const CALLDATASIZE: U256 = U256([2, 0, 0, 0]); - pub const CALLDATACOPY_STATIC: U256 = U256([3, 0, 0, 0]); - pub const CALLDATACOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]); - pub const RETURNDATASIZE: U256 = U256([2, 0, 0, 0]); - pub const RETURNDATACOPY_STATIC: U256 = U256([3, 0, 0, 0]); - pub const RETURNDATACOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]); - pub const ADDRESS: U256 = U256([2, 0, 0, 0]); - pub const ORIGIN: U256 = U256([2, 0, 0, 0]); - pub const CALLER: U256 = U256([2, 0, 0, 0]); - pub const BLOCKHASH: U256 = U256([20, 0, 0, 0]); - pub const COINBASE: U256 = U256([2, 0, 0, 0]); - pub const TIMESTAMP: U256 = U256([2, 0, 0, 0]); - pub const NUMBER: U256 = U256([2, 0, 0, 0]); - pub const PREVRANDAO: U256 = U256([2, 0, 0, 0]); - pub const GASLIMIT: U256 = U256([2, 0, 0, 0]); - pub const CHAINID: U256 = U256([2, 0, 0, 0]); - pub const SELFBALANCE: U256 = U256([5, 0, 0, 0]); - pub const BASEFEE: U256 = U256([2, 0, 0, 0]); - pub const BLOBHASH: U256 = U256([3, 0, 0, 0]); - pub const BLOBBASEFEE: U256 = U256([2, 0, 0, 0]); - pub const POP: U256 = U256([2, 0, 0, 0]); - pub const MLOAD_STATIC: U256 = U256([3, 0, 0, 0]); - pub const MSTORE_STATIC: U256 = U256([3, 0, 0, 0]); - pub const MSTORE8_STATIC: U256 = U256([3, 0, 0, 0]); - pub const JUMP: U256 = U256([8, 0, 0, 0]); - pub const JUMPI: U256 = U256([10, 0, 0, 0]); - pub const PC: U256 = U256([2, 0, 0, 0]); - pub const MSIZE: U256 = U256([2, 0, 0, 0]); - pub const GAS: U256 = U256([2, 0, 0, 0]); - pub const JUMPDEST: U256 = U256([1, 0, 0, 0]); - pub const TLOAD: U256 = U256([100, 0, 0, 0]); - pub const TSTORE: U256 = U256([100, 0, 0, 0]); - pub const MCOPY_STATIC: U256 = U256([3, 0, 0, 0]); - pub const MCOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]); - pub const PUSH0: U256 = U256([2, 0, 0, 0]); - pub const PUSHN: U256 = U256([3, 0, 0, 0]); - pub const DUPN: U256 = U256([3, 0, 0, 0]); - pub const SWAPN: U256 = U256([3, 0, 0, 0]); - pub const LOGN_STATIC: U256 = U256([375, 0, 0, 0]); - pub const LOGN_DYNAMIC_BASE: U256 = U256([375, 0, 0, 0]); - pub const LOGN_DYNAMIC_BYTE_BASE: U256 = U256([8, 0, 0, 0]); - pub const CALLVALUE: U256 = U256([2, 0, 0, 0]); - pub const CODESIZE: U256 = U256([2, 0, 0, 0]); - pub const CODECOPY_STATIC: U256 = U256([3, 0, 0, 0]); - pub const CODECOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]); - pub const GASPRICE: U256 = U256([2, 0, 0, 0]); - pub const EXTCODECOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]); - pub const SELFDESTRUCT_STATIC: U256 = U256([5000, 0, 0, 0]); - pub const SELFDESTRUCT_DYNAMIC: U256 = U256([25000, 0, 0, 0]); - pub const COLD_ADDRESS_ACCESS_COST: U256 = U256([2600, 0, 0, 0]); -} - -// Costs in gas for call opcodes (in wei) -pub mod call_opcode { - use ethereum_rust_core::U256; - - pub const WARM_ADDRESS_ACCESS_COST: U256 = U256([100, 0, 0, 0]); - pub const COLD_ADDRESS_ACCESS_COST: U256 = U256([2600, 0, 0, 0]); - pub const NON_ZERO_VALUE_COST: U256 = U256([9000, 0, 0, 0]); - pub const BASIC_FALLBACK_FUNCTION_STIPEND: U256 = U256([2300, 0, 0, 0]); - pub const VALUE_TO_EMPTY_ACCOUNT_COST: U256 = U256([25000, 0, 0, 0]); -} pub const STACK_LIMIT: usize = 1024; pub const GAS_REFUND_DENOMINATOR: u64 = 5; @@ -122,14 +26,6 @@ pub const MAX_CREATE_CODE_SIZE: usize = 2 * MAX_CODE_SIZE; pub const INVALID_CONTRACT_PREFIX: u8 = 0xef; -// Costs in gas for init word and init code (in wei) -pub const INIT_WORD_COST: u64 = 2; - -pub fn init_code_cost(init_code_length: usize) -> Result { - let length_u64 = u64::try_from(init_code_length) - .map_err(|_| VMError::Internal(InternalError::ConversionError))?; - Ok(INIT_WORD_COST * (length_u64 + 31) / 32) -} pub mod create_opcode { use ethereum_rust_core::U256; @@ -148,6 +44,8 @@ pub const BLOB_BASE_FEE_UPDATE_FRACTION: U256 = U256([3338477, 0, 0, 0]); // Storage constants pub const COLD_STORAGE_ACCESS_COST: U256 = U256([2100, 0, 0, 0]); +pub const WARM_ADDRESS_ACCESS_COST: U256 = U256([100, 0, 0, 0]); +pub const BALANCE_COLD_ADDRESS_ACCESS_COST: U256 = U256([2600, 0, 0, 0]); // Block constants pub const LAST_AVAILABLE_BLOCK_LIMIT: U256 = U256([256, 0, 0, 0]); diff --git a/crates/vm/levm/src/db.rs b/crates/vm/levm/src/db.rs index 19ce3e645..f1b124550 100644 --- a/crates/vm/levm/src/db.rs +++ b/crates/vm/levm/src/db.rs @@ -112,10 +112,16 @@ impl Cache { Ok(()) } - pub fn increment_account_nonce(&mut self, address: &Address) { + // TODO: Replace nonce increments with this (currently does not have senders) + pub fn increment_account_nonce(&mut self, address: &Address) -> Result<(), VMError> { if let Some(account) = self.accounts.get_mut(address) { - account.info.nonce += 1; + account.info.nonce = account + .info + .nonce + .checked_add(1) + .ok_or(VMError::Internal(InternalError::NonceOverflowed))?; } + Ok(()) } pub fn is_account_cached(&self, address: &Address) -> bool { diff --git a/crates/vm/levm/src/errors.rs b/crates/vm/levm/src/errors.rs index 603b20735..4638b1bc5 100644 --- a/crates/vm/levm/src/errors.rs +++ b/crates/vm/levm/src/errors.rs @@ -2,84 +2,113 @@ use crate::account::Account; use bytes::Bytes; use ethereum_rust_core::{types::Log, Address}; use std::collections::HashMap; +use thiserror; /// Errors that halt the program #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] pub enum VMError { - #[error("Stack underflow")] + #[error("Stack Underflow")] StackUnderflow, - #[error("Stack overflow")] + #[error("Stack Overflow")] StackOverflow, - #[error("Invalid jump")] + #[error("Invalid Jump")] InvalidJump, - #[error("Opcode not allowed in static context")] + #[error("Opcode Not Allowed In Static Context")] OpcodeNotAllowedInStaticContext, - #[error("Opcode not found")] + #[error("Opcode Not Found")] OpcodeNotFound, - #[error("Invalid bytecode")] + #[error("Invalid Bytecode")] InvalidBytecode, - #[error("Out of gas")] - OutOfGas, - #[error("Very large number")] + #[error("Very Large Number")] VeryLargeNumber, - #[error("Overflow in arithmetic operation")] - OverflowInArithmeticOp, - #[error("Fatal error")] + #[error("Fatal Error")] FatalError, - #[error("Invalid transaction")] + #[error("Invalid Transaction")] InvalidTransaction, - #[error("Revert opcode")] + #[error("Revert Opcode")] RevertOpcode, - #[error("Invalid opcode")] + #[error("Invalid Opcode")] InvalidOpcode, - #[error("Missing blob hashes")] + #[error("Missing Blob Hashes")] MissingBlobHashes, - #[error("Blob hash index out of bounds")] + #[error("Blob Hash Index Out Of Bounds")] BlobHashIndexOutOfBounds, - #[error("Sender account does not exist")] + #[error("Sender Account Does Not Exist")] SenderAccountDoesNotExist, - #[error("Address does not match an account")] + #[error("Address Does Not Match An Account")] AddressDoesNotMatchAnAccount, - #[error("Sender account should not have bytecode")] + #[error("Sender Account Should Not Have Bytecode")] SenderAccountShouldNotHaveBytecode, - #[error("Sender balance should contain transfer value")] + #[error("Sender Balance Should Contain Transfer Value")] SenderBalanceShouldContainTransferValue, - #[error("Gas price is lower than base fee")] + #[error("Gas Price Is Lower Than Base Fee")] GasPriceIsLowerThanBaseFee, - #[error("Address already occupied")] + #[error("Address Already Occupied")] AddressAlreadyOccupied, - #[error("Contract output too big")] + #[error("Contract Output Too Big")] ContractOutputTooBig, - #[error("Invalid initial byte")] + #[error("Invalid Initial Byte")] InvalidInitialByte, - #[error("Nonce overflow")] - NonceOverflow, - #[error("Memory load out of bounds")] + #[error("Memory Load Out Of Bounds")] MemoryLoadOutOfBounds, - #[error("Memory store out of bounds")] + #[error("Memory Store Out Of Bounds")] MemoryStoreOutOfBounds, #[error("Gas limit price product overflow")] GasLimitPriceProductOverflow, + #[error("Balance Overflow")] + BalanceOverflow, + #[error("Balance Underflow")] + BalanceUnderflow, + #[error("Gas refunds underflow")] + GasRefundsUnderflow, + #[error("Gas refunds overflow")] + GasRefundsOverflow, + // OutOfGas + #[error("Out Of Gas")] + OutOfGas(#[from] OutOfGasError), + // Internal #[error("Internal error: {0}")] Internal(#[from] InternalError), - #[error("Data size overflow")] - DataSizeOverflow, - #[error("Gas cost overflow")] +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] +pub enum OutOfGasError { + #[error("Gas Cost Overflow")] GasCostOverflow, - #[error("Offset overflow")] - OffsetOverflow, - #[error("Creation cost is too high")] + #[error("Gas Used Overflow")] + GasUsedOverflow, + #[error("Creation Cost Is Too High")] CreationCostIsTooHigh, - #[error("Max gas limit exceeded")] + #[error("Consumed Gas Overflow")] + ConsumedGasOverflow, + #[error("Max Gas Limit Exceeded")] MaxGasLimitExceeded, - #[error("Gas refunds underflow")] - GasRefundsUnderflow, - #[error("Gas refunds overflow")] - GasRefundsOverflow, + #[error("Arithmetic operation divided by zero in gas calculation")] + ArithmeticOperationDividedByZero, } #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] pub enum InternalError { + #[error("Overflowed when incrementing nonce")] + NonceOverflowed, + #[error("Underflowed when incrementing nonce")] + NonceUnderflowed, + #[error("Overflowed when incrementing program counter")] + PCOverflowed, + #[error("Underflowed when decrementing program counter")] + PCUnderflowed, + #[error("Arithmetic operation overflowed")] + ArithmeticOperationOverflow, + #[error("Arithmetic operation underflowed")] + ArithmeticOperationUnderflow, + #[error("Arithmetic operation divided by zero")] + ArithmeticOperationDividedByZero, + #[error("Accound should have been cached")] + AccountShouldHaveBeenCached, + #[error("Tried to convert one type to another")] + ConversionError, + #[error("Division error")] + DivisionError, #[error("Tried to access last call frame but found none")] CouldNotAccessLastCallframe, // Last callframe before execution is the same as the first, but after execution the last callframe is actually the initial CF #[error("Tried to read from empty code")] @@ -98,12 +127,6 @@ pub enum InternalError { ExcessBlobGasShouldNotBeNone, #[error("Error in utils file")] UtilsError, - #[error("Accound should have been cached")] - AccountShouldHaveBeenCached, - #[error("Tried to convert one type to another")] - ConversionError, - #[error("Division error")] - DivisionError, } #[derive(Debug, Clone)] diff --git a/crates/vm/levm/src/gas_cost.rs b/crates/vm/levm/src/gas_cost.rs new file mode 100644 index 000000000..5b1e31a47 --- /dev/null +++ b/crates/vm/levm/src/gas_cost.rs @@ -0,0 +1,620 @@ +use crate::{ + call_frame::CallFrame, + constants::{COLD_STORAGE_ACCESS_COST, WORD_SIZE}, + errors::OutOfGasError, + StorageSlot, +}; +use bytes::Bytes; +/// Contains the gas costs of the EVM instructions (in wei) +use ethereum_rust_core::U256; + +// Opcodes cost +pub const ADD: U256 = U256([3, 0, 0, 0]); +pub const MUL: U256 = U256([5, 0, 0, 0]); +pub const SUB: U256 = U256([3, 0, 0, 0]); +pub const DIV: U256 = U256([5, 0, 0, 0]); +pub const SDIV: U256 = U256([5, 0, 0, 0]); +pub const MOD: U256 = U256([5, 0, 0, 0]); +pub const SMOD: U256 = U256([5, 0, 0, 0]); +pub const ADDMOD: U256 = U256([8, 0, 0, 0]); +pub const MULMOD: U256 = U256([8, 0, 0, 0]); +pub const EXP_STATIC: U256 = U256([10, 0, 0, 0]); +pub const EXP_DYNAMIC_BASE: U256 = U256([50, 0, 0, 0]); +pub const SIGNEXTEND: U256 = U256([5, 0, 0, 0]); +pub const LT: U256 = U256([3, 0, 0, 0]); +pub const GT: U256 = U256([3, 0, 0, 0]); +pub const SLT: U256 = U256([3, 0, 0, 0]); +pub const SGT: U256 = U256([3, 0, 0, 0]); +pub const EQ: U256 = U256([3, 0, 0, 0]); +pub const ISZERO: U256 = U256([3, 0, 0, 0]); +pub const AND: U256 = U256([3, 0, 0, 0]); +pub const OR: U256 = U256([3, 0, 0, 0]); +pub const XOR: U256 = U256([3, 0, 0, 0]); +pub const NOT: U256 = U256([3, 0, 0, 0]); +pub const BYTE: U256 = U256([3, 0, 0, 0]); +pub const SHL: U256 = U256([3, 0, 0, 0]); +pub const SHR: U256 = U256([3, 0, 0, 0]); +pub const SAR: U256 = U256([3, 0, 0, 0]); +pub const KECCAK25_STATIC: U256 = U256([30, 0, 0, 0]); +pub const KECCAK25_DYNAMIC_BASE: U256 = U256([6, 0, 0, 0]); +pub const CALLDATALOAD: U256 = U256([3, 0, 0, 0]); +pub const CALLDATASIZE: U256 = U256([2, 0, 0, 0]); +pub const CALLDATACOPY_STATIC: U256 = U256([3, 0, 0, 0]); +pub const CALLDATACOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]); +pub const RETURNDATASIZE: U256 = U256([2, 0, 0, 0]); +pub const RETURNDATACOPY_STATIC: U256 = U256([3, 0, 0, 0]); +pub const RETURNDATACOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]); +pub const ADDRESS: U256 = U256([2, 0, 0, 0]); +pub const ORIGIN: U256 = U256([2, 0, 0, 0]); +pub const CALLER: U256 = U256([2, 0, 0, 0]); +pub const BLOCKHASH: U256 = U256([20, 0, 0, 0]); +pub const COINBASE: U256 = U256([2, 0, 0, 0]); +pub const TIMESTAMP: U256 = U256([2, 0, 0, 0]); +pub const NUMBER: U256 = U256([2, 0, 0, 0]); +pub const PREVRANDAO: U256 = U256([2, 0, 0, 0]); +pub const GASLIMIT: U256 = U256([2, 0, 0, 0]); +pub const CHAINID: U256 = U256([2, 0, 0, 0]); +pub const SELFBALANCE: U256 = U256([5, 0, 0, 0]); +pub const BASEFEE: U256 = U256([2, 0, 0, 0]); +pub const BLOBHASH: U256 = U256([3, 0, 0, 0]); +pub const BLOBBASEFEE: U256 = U256([2, 0, 0, 0]); +pub const POP: U256 = U256([2, 0, 0, 0]); +pub const MLOAD_STATIC: U256 = U256([3, 0, 0, 0]); +pub const MSTORE_STATIC: U256 = U256([3, 0, 0, 0]); +pub const MSTORE8_STATIC: U256 = U256([3, 0, 0, 0]); +pub const JUMP: U256 = U256([8, 0, 0, 0]); +pub const JUMPI: U256 = U256([10, 0, 0, 0]); +pub const PC: U256 = U256([2, 0, 0, 0]); +pub const MSIZE: U256 = U256([2, 0, 0, 0]); +pub const GAS: U256 = U256([2, 0, 0, 0]); +pub const JUMPDEST: U256 = U256([1, 0, 0, 0]); +pub const TLOAD: U256 = U256([100, 0, 0, 0]); +pub const TSTORE: U256 = U256([100, 0, 0, 0]); +pub const MCOPY_STATIC: U256 = U256([3, 0, 0, 0]); +pub const MCOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]); +pub const PUSH0: U256 = U256([2, 0, 0, 0]); +pub const PUSHN: U256 = U256([3, 0, 0, 0]); +pub const DUPN: U256 = U256([3, 0, 0, 0]); +pub const SWAPN: U256 = U256([3, 0, 0, 0]); +pub const LOGN_STATIC: U256 = U256([375, 0, 0, 0]); +pub const LOGN_DYNAMIC_BASE: U256 = U256([375, 0, 0, 0]); +pub const LOGN_DYNAMIC_BYTE_BASE: U256 = U256([8, 0, 0, 0]); +pub const CALLVALUE: U256 = U256([2, 0, 0, 0]); +pub const CODESIZE: U256 = U256([2, 0, 0, 0]); +pub const CODECOPY_STATIC: U256 = U256([3, 0, 0, 0]); +pub const CODECOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]); +pub const GASPRICE: U256 = U256([2, 0, 0, 0]); +pub const EXTCODECOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]); +pub const SELFDESTRUCT_STATIC: U256 = U256([5000, 0, 0, 0]); +pub const SELFDESTRUCT_DYNAMIC: U256 = U256([25000, 0, 0, 0]); + +// Costs in gas for call opcodes (in wei) +pub const WARM_ADDRESS_ACCESS_COST: U256 = U256([100, 0, 0, 0]); +pub const COLD_ADDRESS_ACCESS_COST: U256 = U256([2600, 0, 0, 0]); +pub const NON_ZERO_VALUE_COST: U256 = U256([9000, 0, 0, 0]); +pub const BASIC_FALLBACK_FUNCTION_STIPEND: U256 = U256([2300, 0, 0, 0]); +pub const VALUE_TO_EMPTY_ACCOUNT_COST: U256 = U256([25000, 0, 0, 0]); + +// Costs in gas for create opcodes (in wei) +pub const INIT_CODE_WORD_COST: U256 = U256([2, 0, 0, 0]); +pub const CODE_DEPOSIT_COST: U256 = U256([200, 0, 0, 0]); +pub const CREATE_BASE_COST: U256 = U256([32000, 0, 0, 0]); + +pub fn exp(exponent_bits: u64) -> Result { + let exponent_byte_size = (exponent_bits + .checked_add(7) + .ok_or(OutOfGasError::GasCostOverflow)?) + / 8; + let exponent_byte_size_cost = EXP_DYNAMIC_BASE + .checked_mul(exponent_byte_size.into()) + .ok_or(OutOfGasError::GasCostOverflow)?; + EXP_STATIC + .checked_add(exponent_byte_size_cost) + .ok_or(OutOfGasError::GasCostOverflow) +} + +pub fn calldatacopy( + current_call_frame: &mut CallFrame, + size: usize, + dest_offset: usize, +) -> Result { + copy_behavior( + CALLDATACOPY_DYNAMIC_BASE, + CALLDATACOPY_STATIC, + current_call_frame, + size, + dest_offset, + ) +} + +pub fn codecopy( + current_call_frame: &mut CallFrame, + size: usize, + dest_offset: usize, +) -> Result { + copy_behavior( + CODECOPY_DYNAMIC_BASE, + CODECOPY_STATIC, + current_call_frame, + size, + dest_offset, + ) +} + +pub fn extcodecopy( + current_call_frame: &mut CallFrame, + size: usize, + dest_offset: usize, + is_cached: bool, +) -> Result { + let address_access_cost = if is_cached { + WARM_ADDRESS_ACCESS_COST + } else { + COLD_ADDRESS_ACCESS_COST + }; + + // address_access_cost is not a static cost, but there's no static + // cost and there is the address_access_cost + copy_behavior( + EXTCODECOPY_DYNAMIC_BASE, + address_access_cost, + current_call_frame, + size, + dest_offset, + ) +} + +pub fn returndatacopy( + current_call_frame: &mut CallFrame, + size: usize, + dest_offset: usize, +) -> Result { + copy_behavior( + RETURNDATACOPY_DYNAMIC_BASE, + RETURNDATACOPY_STATIC, + current_call_frame, + size, + dest_offset, + ) +} + +fn copy_behavior( + dynamic_base: U256, + static_cost: U256, + current_call_frame: &mut CallFrame, + size: usize, + offset: usize, +) -> Result { + let minimum_word_size = (size + .checked_add(WORD_SIZE) + .ok_or(OutOfGasError::GasCostOverflow)? + .saturating_sub(1)) + / WORD_SIZE; + + let memory_expansion_cost = current_call_frame.memory.expansion_cost( + offset + .checked_add(size) + .ok_or(OutOfGasError::GasCostOverflow)?, + )?; + + let minimum_word_size_cost = dynamic_base + .checked_mul(minimum_word_size.into()) + .ok_or(OutOfGasError::GasCostOverflow)?; + static_cost + .checked_add(minimum_word_size_cost) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_add(memory_expansion_cost) + .ok_or(OutOfGasError::GasCostOverflow) +} + +pub fn keccak256( + current_call_frame: &mut CallFrame, + size: usize, + offset: usize, +) -> Result { + copy_behavior( + KECCAK25_DYNAMIC_BASE, + KECCAK25_STATIC, + current_call_frame, + size, + offset, + ) +} + +pub fn log( + current_call_frame: &mut CallFrame, + size: usize, + offset: usize, + number_of_topics: u8, +) -> Result { + let memory_expansion_cost = current_call_frame.memory.expansion_cost( + offset + .checked_add(size) + .ok_or(OutOfGasError::GasCostOverflow)?, + )?; + + let topics_cost = LOGN_DYNAMIC_BASE + .checked_mul(number_of_topics.into()) + .ok_or(OutOfGasError::GasCostOverflow)?; + let bytes_cost = LOGN_DYNAMIC_BYTE_BASE + .checked_mul(size.into()) + .ok_or(OutOfGasError::GasCostOverflow)?; + topics_cost + .checked_add(LOGN_STATIC) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_add(bytes_cost) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_add(memory_expansion_cost) + .ok_or(OutOfGasError::GasCostOverflow) +} + +pub fn mload(current_call_frame: &mut CallFrame, offset: usize) -> Result { + mem_expansion_behavior(current_call_frame, offset, WORD_SIZE, MLOAD_STATIC) +} + +pub fn mstore(current_call_frame: &mut CallFrame, offset: usize) -> Result { + mem_expansion_behavior(current_call_frame, offset, WORD_SIZE, MSTORE_STATIC) +} + +pub fn mstore8(current_call_frame: &mut CallFrame, offset: usize) -> Result { + mem_expansion_behavior(current_call_frame, offset, 1, MSTORE8_STATIC) +} + +fn mem_expansion_behavior( + current_call_frame: &mut CallFrame, + offset: usize, + offset_add: usize, + static_cost: U256, +) -> Result { + let memory_expansion_cost = current_call_frame.memory.expansion_cost( + offset + .checked_add(offset_add) + .ok_or(OutOfGasError::GasCostOverflow)?, + )?; + static_cost + .checked_add(memory_expansion_cost) + .ok_or(OutOfGasError::GasCostOverflow) +} + +pub fn sload(is_cached: bool) -> U256 { + if is_cached { + // If slot is warm (cached) add 100 to base_dynamic_gas + WARM_ADDRESS_ACCESS_COST + } else { + // If slot is cold (not cached) add 2100 to base_dynamic_gas + COLD_STORAGE_ACCESS_COST + } +} + +pub fn sstore( + value: U256, + is_cached: bool, + storage_slot: &StorageSlot, +) -> Result { + let mut base_dynamic_gas: U256 = U256::zero(); + + if !is_cached { + // If slot is cold 2100 is added to base_dynamic_gas + base_dynamic_gas = base_dynamic_gas + .checked_add(U256::from(2100)) + .ok_or(OutOfGasError::GasCostOverflow)?; + }; + + let sstore_gas_cost = if value == storage_slot.current_value { + U256::from(100) + } else if storage_slot.current_value == storage_slot.original_value { + if storage_slot.original_value == U256::zero() { + U256::from(20000) + } else { + U256::from(2900) + } + } else { + U256::from(100) + }; + + base_dynamic_gas + .checked_add(sstore_gas_cost) + .ok_or(OutOfGasError::GasCostOverflow) +} + +pub fn mcopy( + current_call_frame: &mut CallFrame, + size: usize, + src_offset: usize, + dest_offset: usize, +) -> Result { + let words_copied = (size + .checked_add(WORD_SIZE) + .ok_or(OutOfGasError::GasCostOverflow)? + .saturating_sub(1)) + / WORD_SIZE; + + let memory_byte_size = src_offset + .checked_add(size) + .and_then(|src_sum| { + dest_offset + .checked_add(size) + .map(|dest_sum| src_sum.max(dest_sum)) + }) + .ok_or(OutOfGasError::GasCostOverflow)?; + + let memory_expansion_cost = current_call_frame.memory.expansion_cost(memory_byte_size)?; + let copied_words_cost = MCOPY_DYNAMIC_BASE + .checked_mul(words_copied.into()) + .ok_or(OutOfGasError::GasCostOverflow)?; + MCOPY_STATIC + .checked_add(copied_words_cost) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_add(memory_expansion_cost) + .ok_or(OutOfGasError::GasCostOverflow) +} + +#[allow(clippy::too_many_arguments)] +pub fn call( + current_call_frame: &mut CallFrame, + args_size: usize, + args_offset: usize, + ret_size: usize, + ret_offset: usize, + value: U256, + is_cached: bool, + account_is_empty: bool, +) -> Result { + let memory_byte_size = args_size + .checked_add(args_offset) + .ok_or(OutOfGasError::GasCostOverflow)? + .max( + ret_size + .checked_add(ret_offset) + .ok_or(OutOfGasError::GasCostOverflow)?, + ); + let memory_expansion_cost = current_call_frame.memory.expansion_cost(memory_byte_size)?; + + let positive_value_cost = if !value.is_zero() { + NON_ZERO_VALUE_COST + .checked_add(BASIC_FALLBACK_FUNCTION_STIPEND) + .ok_or(OutOfGasError::GasCostOverflow)? + } else { + U256::zero() + }; + + let address_access_cost = if !is_cached { + COLD_ADDRESS_ACCESS_COST + } else { + WARM_ADDRESS_ACCESS_COST + }; + + let value_to_empty_account_cost = if !value.is_zero() && account_is_empty { + VALUE_TO_EMPTY_ACCOUNT_COST + } else { + U256::zero() + }; + + memory_expansion_cost + .checked_add(address_access_cost) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_add(positive_value_cost) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_add(value_to_empty_account_cost) + .ok_or(OutOfGasError::GasCostOverflow) +} + +pub fn callcode( + current_call_frame: &mut CallFrame, + args_size: usize, + args_offset: usize, + ret_size: usize, + ret_offset: usize, + value: U256, + is_cached: bool, +) -> Result { + let transfer_cost = if value == U256::zero() { + U256::zero() + } else { + NON_ZERO_VALUE_COST + // Should also add BASIC_FALLBACK_FUNCTION_STIPEND?? + // See https://www.evm.codes/?fork=cancun#f2 and call impl + }; + + compute_gas_call( + current_call_frame, + args_size, + args_offset, + ret_size, + ret_offset, + is_cached, + )? + .checked_add(transfer_cost) + .ok_or(OutOfGasError::GasCostOverflow) +} + +pub fn delegatecall( + current_call_frame: &mut CallFrame, + args_size: usize, + args_offset: usize, + ret_size: usize, + ret_offset: usize, + is_cached: bool, +) -> Result { + compute_gas_call( + current_call_frame, + args_size, + args_offset, + ret_size, + ret_offset, + is_cached, + ) +} + +pub fn staticcall( + current_call_frame: &mut CallFrame, + args_size: usize, + args_offset: usize, + ret_size: usize, + ret_offset: usize, + is_cached: bool, +) -> Result { + compute_gas_call( + current_call_frame, + args_size, + args_offset, + ret_size, + ret_offset, + is_cached, + ) +} + +fn compute_gas_call( + current_call_frame: &mut CallFrame, + args_size: usize, + args_offset: usize, + ret_size: usize, + ret_offset: usize, + is_cached: bool, +) -> Result { + let memory_byte_size = args_offset + .checked_add(args_size) + .and_then(|src_sum| { + ret_offset + .checked_add(ret_size) + .map(|dest_sum| src_sum.max(dest_sum)) + }) + .ok_or(OutOfGasError::GasCostOverflow)?; + let memory_expansion_cost = current_call_frame.memory.expansion_cost(memory_byte_size)?; + + let access_cost = if is_cached { + WARM_ADDRESS_ACCESS_COST + } else { + COLD_ADDRESS_ACCESS_COST + }; + + memory_expansion_cost + .checked_add(access_cost) + .ok_or(OutOfGasError::GasCostOverflow) +} + +pub fn create( + current_call_frame: &mut CallFrame, + code_offset_in_memory: U256, + code_size_in_memory: U256, +) -> Result { + compute_gas_create( + current_call_frame, + code_offset_in_memory, + code_size_in_memory, + false, + ) +} + +pub fn create_2( + current_call_frame: &mut CallFrame, + code_offset_in_memory: U256, + code_size_in_memory: U256, +) -> Result { + compute_gas_create( + current_call_frame, + code_offset_in_memory, + code_size_in_memory, + true, + ) +} + +fn compute_gas_create( + current_call_frame: &mut CallFrame, + code_offset_in_memory: U256, + code_size_in_memory: U256, + is_create_2: bool, +) -> Result { + let minimum_word_size = (code_size_in_memory + .checked_add(U256::from(31)) + .ok_or(OutOfGasError::GasCostOverflow)?) + .checked_div(U256::from(32)) + .ok_or(OutOfGasError::ArithmeticOperationDividedByZero)?; // '32' will never be zero + + let init_code_cost = minimum_word_size + .checked_mul(INIT_CODE_WORD_COST) + .ok_or(OutOfGasError::GasCostOverflow)?; + + let code_deposit_cost = code_size_in_memory + .checked_mul(CODE_DEPOSIT_COST) + .ok_or(OutOfGasError::GasCostOverflow)?; + + let memory_expansion_cost = current_call_frame.memory.expansion_cost( + code_size_in_memory + .checked_add(code_offset_in_memory) + .ok_or(OutOfGasError::GasCostOverflow)? + .try_into() + .map_err(|_err| OutOfGasError::GasCostOverflow)?, + )?; + + let hash_cost = if is_create_2 { + minimum_word_size + .checked_mul(KECCAK25_DYNAMIC_BASE) + .ok_or(OutOfGasError::GasCostOverflow)? + } else { + U256::zero() + }; + + init_code_cost + .checked_add(memory_expansion_cost) + .ok_or(OutOfGasError::CreationCostIsTooHigh)? + .checked_add(code_deposit_cost) + .ok_or(OutOfGasError::CreationCostIsTooHigh)? + .checked_add(CREATE_BASE_COST) + .ok_or(OutOfGasError::CreationCostIsTooHigh)? + .checked_add(hash_cost) + .ok_or(OutOfGasError::CreationCostIsTooHigh) +} + +pub fn selfdestruct(is_cached: bool, account_is_empty: bool) -> Result { + let mut gas_cost = SELFDESTRUCT_STATIC; + + if !is_cached { + gas_cost = gas_cost + .checked_add(COLD_ADDRESS_ACCESS_COST) + .ok_or(OutOfGasError::GasCostOverflow)?; + } + + if account_is_empty { + gas_cost = gas_cost + .checked_add(SELFDESTRUCT_DYNAMIC) + .ok_or(OutOfGasError::GasCostOverflow)?; + } + + Ok(gas_cost) +} + +pub fn tx_calldata(calldata: &Bytes) -> Result { + // This cost applies both for call and create + // 4 gas for each zero byte in the transaction data 16 gas for each non-zero byte in the transaction. + let mut calldata_cost: u64 = 0; + for byte in calldata { + if *byte != 0 { + calldata_cost = calldata_cost + .checked_add(16) + .ok_or(OutOfGasError::GasUsedOverflow)?; + } else { + calldata_cost = calldata_cost + .checked_add(4) + .ok_or(OutOfGasError::GasUsedOverflow)?; + } + } + Ok(calldata_cost) +} + +pub fn tx_creation(code_length: u64, number_of_words: u64) -> Result { + let mut creation_cost = code_length + .checked_mul(200) + .ok_or(OutOfGasError::CreationCostIsTooHigh)?; + creation_cost = creation_cost + .checked_add(32000) + .ok_or(OutOfGasError::CreationCostIsTooHigh)?; + + // GInitCodeword * number_of_words rounded up. GinitCodeWord = 2 + let words_cost = number_of_words + .checked_mul(2) + .ok_or(OutOfGasError::GasCostOverflow)?; + creation_cost + .checked_add(words_cost) + .ok_or(OutOfGasError::GasUsedOverflow) +} diff --git a/crates/vm/levm/src/lib.rs b/crates/vm/levm/src/lib.rs index 258631fe4..6f8ee4060 100644 --- a/crates/vm/levm/src/lib.rs +++ b/crates/vm/levm/src/lib.rs @@ -4,6 +4,7 @@ pub mod constants; pub mod db; pub mod environment; pub mod errors; +pub mod gas_cost; pub mod memory; pub mod opcode_handlers; pub mod opcodes; diff --git a/crates/vm/levm/src/memory.rs b/crates/vm/levm/src/memory.rs index 3f4a9ff1f..69f1f0c87 100644 --- a/crates/vm/levm/src/memory.rs +++ b/crates/vm/levm/src/memory.rs @@ -1,6 +1,6 @@ use crate::{ constants::{MEMORY_EXPANSION_QUOTIENT, WORD_SIZE}, - errors::VMError, + errors::{InternalError, OutOfGasError, VMError}, }; use ethereum_rust_core::U256; @@ -25,24 +25,22 @@ impl Memory { } fn resize(&mut self, offset: usize) { - if offset.next_multiple_of(32) > self.data.len() { - self.data.resize(offset.next_multiple_of(32), 0); + if offset.next_multiple_of(WORD_SIZE) > self.data.len() { + self.data.resize(offset.next_multiple_of(WORD_SIZE), 0); } } pub fn load(&mut self, offset: usize) -> Result { - self.resize( - offset - .checked_add(32) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - ); + self.resize(offset.checked_add(WORD_SIZE).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, // MemoryLoadOutOfBounds? + ))?); let value_bytes = self .data .get( offset - ..offset - .checked_add(32) - .ok_or(VMError::MemoryLoadOutOfBounds)?, + ..offset.checked_add(WORD_SIZE).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, // MemoryLoadOutOfBounds? + ))?, ) .ok_or(VMError::MemoryLoadOutOfBounds)?; @@ -50,36 +48,25 @@ impl Memory { } pub fn load_range(&mut self, offset: usize, size: usize) -> Result, VMError> { - self.resize( - offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - ); + let size_to_load = offset.checked_add(size).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + self.resize(size_to_load); self.data - .get( - offset - ..offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - ) + .get(offset..size_to_load) .ok_or(VMError::MemoryLoadOutOfBounds) .map(|slice| slice.to_vec()) } pub fn store_bytes(&mut self, offset: usize, value: &[u8]) -> Result<(), VMError> { let len = value.len(); - self.resize( - offset - .checked_add(len) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - ); - self.data.splice( - offset - ..offset - .checked_add(len) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - value.iter().copied(), - ); + let size_to_store = offset.checked_add(len).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + self.resize(size_to_store); + self.data + .splice(offset..size_to_store, value.iter().copied()); + Ok(()) } @@ -89,18 +76,13 @@ impl Memory { value: &[u8], size: usize, ) -> Result<(), VMError> { - self.resize( - offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - ); - self.data.splice( - offset - ..offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - value.iter().copied(), - ); + let size_to_store = offset.checked_add(size).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + self.resize(size_to_store); + self.data + .splice(offset..size_to_store, value.iter().copied()); + Ok(()) } @@ -114,14 +96,13 @@ impl Memory { dest_offset: usize, size: usize, ) -> Result<(), VMError> { - let max_size = std::cmp::max( - src_offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - dest_offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - ); + let src_copy_size = src_offset.checked_add(size).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + let dest_copy_size = dest_offset.checked_add(size).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + let max_size = std::cmp::max(src_copy_size, dest_copy_size); if max_size > self.data.len() { self.resize(max_size); @@ -129,6 +110,12 @@ impl Memory { let mut temp = vec![0u8; size]; + temp.copy_from_slice( + self.data + .get(src_offset..src_copy_size) + .ok_or(VMError::Internal(InternalError::SlicingError))?, + ); + for i in 0..size { if let Some(temp_byte) = temp.get_mut(i) { *temp_byte = *self @@ -154,35 +141,38 @@ impl Memory { Ok(()) } - pub fn expansion_cost(&self, memory_byte_size: usize) -> Result { + pub fn expansion_cost(&self, memory_byte_size: usize) -> Result { if memory_byte_size <= self.data.len() { return Ok(U256::zero()); } let new_memory_size_word = memory_byte_size .checked_add(WORD_SIZE - 1) - .ok_or(VMError::OverflowInArithmeticOp)? + .ok_or(OutOfGasError::GasCostOverflow)? / WORD_SIZE; let new_memory_cost = new_memory_size_word .checked_mul(new_memory_size_word) .map(|square| square / MEMORY_EXPANSION_QUOTIENT) .and_then(|cost| cost.checked_add(new_memory_size_word.checked_mul(3)?)) - .ok_or(VMError::OverflowInArithmeticOp)?; + .ok_or(OutOfGasError::GasCostOverflow)?; let last_memory_size_word = self .data .len() .checked_add(WORD_SIZE - 1) - .ok_or(VMError::OverflowInArithmeticOp)? + .ok_or(OutOfGasError::GasCostOverflow)? / WORD_SIZE; let last_memory_cost = last_memory_size_word .checked_mul(last_memory_size_word) .map(|square| square / MEMORY_EXPANSION_QUOTIENT) .and_then(|cost| cost.checked_add(last_memory_size_word.checked_mul(3)?)) - .ok_or(VMError::OverflowInArithmeticOp)?; + .ok_or(OutOfGasError::GasCostOverflow)?; - Ok((new_memory_cost - last_memory_cost).into()) + Ok((new_memory_cost + .checked_sub(last_memory_cost) + .ok_or(OutOfGasError::GasCostOverflow)?) + .into()) } } diff --git a/crates/vm/levm/src/opcode_handlers/arithmetic.rs b/crates/vm/levm/src/opcode_handlers/arithmetic.rs index 188897e41..50d3bf81f 100644 --- a/crates/vm/levm/src/opcode_handlers/arithmetic.rs +++ b/crates/vm/levm/src/opcode_handlers/arithmetic.rs @@ -1,7 +1,8 @@ use crate::{ call_frame::CallFrame, - constants::gas_cost, errors::{InternalError, OpcodeSuccess, VMError}, + gas_cost, + opcode_handlers::bitwise_comparison::checked_shift_left, vm::VM, }; use ethereum_rust_core::{U256, U512}; @@ -117,7 +118,9 @@ impl VM { current_call_frame.stack.push(U256::zero())?; return Ok(OpcodeSuccess::Continue); } - let remainder = dividend % divisor; + let remainder = dividend.checked_rem(divisor).ok_or(VMError::Internal( + InternalError::ArithmeticOperationDividedByZero, + ))?; // Cannot be zero bc if above; current_call_frame.stack.push(remainder)?; Ok(OpcodeSuccess::Continue) @@ -138,7 +141,13 @@ impl VM { let normalized_dividend = abs(dividend); let normalized_divisor = abs(divisor); - let mut remainder = normalized_dividend % normalized_divisor; + let mut remainder = + normalized_dividend + .checked_rem(normalized_divisor) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationDividedByZero, + ))?; // Cannot be zero bc if above; + // The remainder should have the same sign as the dividend if is_negative(dividend) { remainder = negate(remainder); @@ -165,7 +174,9 @@ impl VM { return Ok(OpcodeSuccess::Continue); } let (sum, overflow) = augend.overflowing_add(addend); - let mut remainder = sum % divisor; + let mut remainder = sum.checked_rem(divisor).ok_or(VMError::Internal( + InternalError::ArithmeticOperationDividedByZero, + ))?; // Cannot be zero bc if above; if overflow || remainder > divisor { remainder = remainder.overflowing_sub(divisor).0; } @@ -191,7 +202,9 @@ impl VM { } let (product, overflow) = multiplicand.overflowing_mul(multiplier); - let mut remainder = product % divisor; + let mut remainder = product.checked_rem(divisor).ok_or(VMError::Internal( + InternalError::ArithmeticOperationDividedByZero, + ))?; // Cannot be zero bc if above if overflow || remainder > divisor { remainder = remainder.overflowing_sub(divisor).0; } @@ -218,8 +231,8 @@ impl VM { .bits() .try_into() .map_err(|_| VMError::Internal(InternalError::ConversionError))?; - let exponent_byte_size = (exponent_bits + 7) / 8; - let gas_cost = gas_cost::EXP_STATIC + gas_cost::EXP_DYNAMIC_BASE * exponent_byte_size; + + let gas_cost = gas_cost::exp(exponent_bits).map_err(VMError::OutOfGas)?; self.increase_consumed_gas(current_call_frame, gas_cost)?; @@ -236,17 +249,39 @@ impl VM { ) -> Result { self.increase_consumed_gas(current_call_frame, gas_cost::SIGNEXTEND)?; - let byte_size = current_call_frame.stack.pop()?; + let byte_size: usize = current_call_frame + .stack + .pop()? + .try_into() + .map_err(|_| VMError::VeryLargeNumber)?; + let value_to_extend = current_call_frame.stack.pop()?; - let bits_per_byte = U256::from(8); + let bits_per_byte: usize = 8; let sign_bit_position_on_byte = 7; - let max_byte_size = 31; - let byte_size = byte_size.min(U256::from(max_byte_size)); - let sign_bit_index = bits_per_byte * byte_size + sign_bit_position_on_byte; - let is_negative = value_to_extend.bit(sign_bit_index.as_usize()); - let sign_bit_mask = (U256::one() << sign_bit_index) - U256::one(); + let max_byte_size: usize = 31; + let byte_size: usize = byte_size.min(max_byte_size); + + let total_bits = bits_per_byte + .checked_mul(byte_size) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + let sign_bit_index = + total_bits + .checked_add(sign_bit_position_on_byte) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + + let is_negative = value_to_extend.bit(sign_bit_index); + let sign_bit_mask = checked_shift_left(U256::one(), sign_bit_index)? + .checked_sub(U256::one()) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationUnderflow, + ))?; //Shifted should be at least one + let result = if is_negative { value_to_extend | !sign_bit_mask } else { diff --git a/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs b/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs index 331a10b7b..aef3fb81c 100644 --- a/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs +++ b/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs @@ -1,7 +1,8 @@ use crate::{ call_frame::CallFrame, - constants::{gas_cost, WORD_SIZE}, - errors::{OpcodeSuccess, VMError}, + constants::WORD_SIZE, + errors::{InternalError, OpcodeSuccess, VMError}, + gas_cost, vm::VM, }; use ethereum_rust_core::U256; @@ -169,9 +170,18 @@ impl VM { let byte_index = op1.try_into().unwrap_or(usize::MAX); if byte_index < WORD_SIZE { + let byte_to_push = WORD_SIZE + .checked_sub(byte_index) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationUnderflow, + ))? + .checked_sub(1) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationUnderflow, + ))?; // Same case as above current_call_frame .stack - .push(U256::from(op2.byte(WORD_SIZE - 1 - byte_index)))?; + .push(U256::from(op2.byte(byte_to_push)))?; } else { current_call_frame.stack.push(U256::zero())?; } @@ -184,8 +194,13 @@ impl VM { self.increase_consumed_gas(current_call_frame, gas_cost::SHL)?; let shift = current_call_frame.stack.pop()?; let value = current_call_frame.stack.pop()?; + + let shift_usize: usize = shift.try_into().map_err(|_| VMError::VeryLargeNumber)?; // we know its not bigger than 256 + if shift < U256::from(256) { - current_call_frame.stack.push(value << shift)?; + current_call_frame + .stack + .push(checked_shift_left(value, shift_usize)?)?; } else { current_call_frame.stack.push(U256::zero())?; } @@ -198,8 +213,13 @@ impl VM { self.increase_consumed_gas(current_call_frame, gas_cost::SHR)?; let shift = current_call_frame.stack.pop()?; let value = current_call_frame.stack.pop()?; + + let shift_usize: usize = shift.try_into().map_err(|_| VMError::VeryLargeNumber)?; // we know its not bigger than 256 + if shift < U256::from(256) { - current_call_frame.stack.push(value >> shift)?; + current_call_frame + .stack + .push(checked_shift_right(value, shift_usize)?)?; } else { current_call_frame.stack.push(U256::zero())?; } @@ -230,10 +250,67 @@ pub fn arithmetic_shift_right(value: U256, shift: U256) -> Result if value.bit(255) { // if negative fill with 1s - let shifted = value >> shift_usize; - let mask = U256::MAX << (256 - shift_usize); + let shifted = checked_shift_right(value, shift_usize)?; + let mask = checked_shift_left( + U256::MAX, + 256_usize.checked_sub(shift_usize).ok_or(VMError::Internal( + InternalError::ArithmeticOperationUnderflow, + ))?, // Note that this is already checked in op_sar + )?; + Ok(shifted | mask) } else { - Ok(value >> shift_usize) + Ok(checked_shift_right(value, shift_usize)?) } } + +/// Instead of using unsafe <<, uses checked_mul n times, replicating n shifts. +/// Note: These (checked_shift_left and checked_shift_right) are done because +/// are not available in U256 +pub fn checked_shift_left(value: U256, shift: usize) -> Result { + let mut result = value; + let mut shifts_left = shift; + + while shifts_left > 0 { + result = match result.checked_mul(U256::from(2)) { + Some(num) => num, + None => { + let only_most_representative_bit_on = U256::from(2) + .checked_pow(U256::from(255)) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + let partial_result = result.checked_sub(only_most_representative_bit_on).ok_or( + VMError::Internal(InternalError::ArithmeticOperationUnderflow), + )?; //Should not happen bc checked_mul overflows + partial_result + .checked_mul(2.into()) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))? + } + }; + shifts_left = shifts_left.checked_sub(1).ok_or(VMError::Internal( + InternalError::ArithmeticOperationUnderflow, + ))?; // Should not reach negative values + } + + Ok(result) +} + +// Instead of using unsafe >>, uses checked_div n times, replicating n shifts +fn checked_shift_right(value: U256, shift: usize) -> Result { + let mut result = value; + let mut shifts_left = shift; + + while shifts_left > 0 { + result = result.checked_div(U256::from(2)).ok_or(VMError::Internal( + InternalError::ArithmeticOperationDividedByZero, + ))?; // '2' will never be zero + shifts_left = shifts_left.checked_sub(1).ok_or(VMError::Internal( + InternalError::ArithmeticOperationUnderflow, + ))?; // Should not reach negative values + } + + Ok(result) +} diff --git a/crates/vm/levm/src/opcode_handlers/block.rs b/crates/vm/levm/src/opcode_handlers/block.rs index 3ec47474a..a54b2ceaf 100644 --- a/crates/vm/levm/src/opcode_handlers/block.rs +++ b/crates/vm/levm/src/opcode_handlers/block.rs @@ -1,7 +1,8 @@ use crate::{ call_frame::CallFrame, - constants::{gas_cost, LAST_AVAILABLE_BLOCK_LIMIT}, + constants::LAST_AVAILABLE_BLOCK_LIMIT, errors::{InternalError, OpcodeSuccess, VMError}, + gas_cost, vm::{address_to_word, VM}, }; use ethereum_rust_core::{ @@ -184,14 +185,14 @@ impl VM { } fn get_blob_gasprice(&mut self) -> Result { - Ok(fake_exponential( + fake_exponential( MIN_BASE_FEE_PER_BLOB_GAS.into(), // Use unwrap because env should have a Some value in excess_blob_gas attribute self.env.block_excess_blob_gas.ok_or(VMError::Internal( InternalError::ExcessBlobGasShouldNotBeNone, ))?, BLOB_BASE_FEE_UPDATE_FRACTION.into(), - )) + ) } // BLOBBASEFEE operation @@ -210,14 +211,37 @@ impl VM { } // Fuction inspired in EIP 4844 helpers. Link: https://eips.ethereum.org/EIPS/eip-4844#helpers -fn fake_exponential(factor: U256, numerator: U256, denominator: U256) -> U256 { +fn fake_exponential(factor: U256, numerator: U256, denominator: U256) -> Result { let mut i = U256::one(); let mut output = U256::zero(); - let mut numerator_accum = factor * denominator; + let mut numerator_accum = factor.checked_mul(denominator).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; while numerator_accum > U256::zero() { - output += numerator_accum; - numerator_accum = (numerator_accum * numerator) / (denominator * i); - i += U256::one(); + output = output + .checked_add(numerator_accum) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + let mult_numerator = numerator_accum + .checked_mul(numerator) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + let mult_denominator = denominator.checked_mul(i).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + numerator_accum = + (mult_numerator) + .checked_div(mult_denominator) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationDividedByZero, + ))?; // Neither denominator or i can be zero + i = i.checked_add(U256::one()).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; } - output / denominator + output.checked_div(denominator).ok_or(VMError::Internal( + InternalError::ArithmeticOperationDividedByZero, + )) // Denominator is a const } diff --git a/crates/vm/levm/src/opcode_handlers/dup.rs b/crates/vm/levm/src/opcode_handlers/dup.rs index 0a0694b88..6500965e2 100644 --- a/crates/vm/levm/src/opcode_handlers/dup.rs +++ b/crates/vm/levm/src/opcode_handlers/dup.rs @@ -1,7 +1,7 @@ use crate::{ call_frame::CallFrame, - constants::gas_cost, errors::{OpcodeSuccess, VMError}, + gas_cost, opcodes::Opcode, vm::VM, }; @@ -17,8 +17,11 @@ impl VM { op: Opcode, ) -> Result { // Calculate the depth based on the opcode - - let depth = usize::from(op) - usize::from(Opcode::DUP1) + 1; + let depth = (usize::from(op)) + .checked_sub(usize::from(Opcode::DUP1)) + .ok_or(VMError::InvalidOpcode)? + .checked_add(1) + .ok_or(VMError::InvalidOpcode)?; // Increase the consumed gas self.increase_consumed_gas(current_call_frame, gas_cost::DUPN)?; @@ -29,9 +32,13 @@ impl VM { } // Get the value at the specified depth - let value_at_depth = current_call_frame - .stack - .get(current_call_frame.stack.len() - depth)?; + let value_at_depth = current_call_frame.stack.get( + current_call_frame + .stack + .len() + .checked_sub(depth) + .ok_or(VMError::StackUnderflow)?, + )?; // Push the duplicated value onto the stack current_call_frame.stack.push(*value_at_depth)?; diff --git a/crates/vm/levm/src/opcode_handlers/environment.rs b/crates/vm/levm/src/opcode_handlers/environment.rs index 7cddfc8f9..17793cf3d 100644 --- a/crates/vm/levm/src/opcode_handlers/environment.rs +++ b/crates/vm/levm/src/opcode_handlers/environment.rs @@ -1,10 +1,8 @@ use crate::{ call_frame::CallFrame, - constants::{ - call_opcode::{COLD_ADDRESS_ACCESS_COST, WARM_ADDRESS_ACCESS_COST}, - gas_cost, WORD_SIZE, - }, - errors::{InternalError, OpcodeSuccess, VMError}, + constants::{BALANCE_COLD_ADDRESS_ACCESS_COST, WARM_ADDRESS_ACCESS_COST}, + errors::{InternalError, OpcodeSuccess, OutOfGasError, VMError}, + gas_cost, vm::{word_to_address, VM}, }; use bytes::Bytes; @@ -39,7 +37,7 @@ impl VM { if self.cache.is_account_cached(address) { self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?; } else { - self.increase_consumed_gas(current_call_frame, COLD_ADDRESS_ACCESS_COST)?; + self.increase_consumed_gas(current_call_frame, BALANCE_COLD_ADDRESS_ACCESS_COST)?; self.cache_from_db(address); }; @@ -161,15 +159,8 @@ impl VM { .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; - let minimum_word_size = (size + WORD_SIZE - 1) / WORD_SIZE; - let memory_expansion_cost = current_call_frame.memory.expansion_cost( - dest_offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - )?; - let gas_cost = gas_cost::CALLDATACOPY_STATIC - + gas_cost::CALLDATACOPY_DYNAMIC_BASE * minimum_word_size - + memory_expansion_cost; + let gas_cost = gas_cost::calldatacopy(current_call_frame, size, dest_offset) + .map_err(VMError::OutOfGas)?; self.increase_consumed_gas(current_call_frame, gas_cost)?; @@ -200,8 +191,14 @@ impl VM { &mut self, current_call_frame: &mut CallFrame, ) -> Result { - if self.env.consumed_gas + gas_cost::CODESIZE > self.env.gas_limit { - return Err(VMError::OutOfGas); + if self + .env + .consumed_gas + .checked_add(gas_cost::CODESIZE) + .ok_or(VMError::OutOfGas(OutOfGasError::ConsumedGasOverflow))? + > self.env.gas_limit + { + return Err(VMError::OutOfGas(OutOfGasError::MaxGasLimitExceeded)); } current_call_frame @@ -234,17 +231,8 @@ impl VM { .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let minimum_word_size = (size + WORD_SIZE - 1) / WORD_SIZE; - - let memory_expansion_cost = current_call_frame.memory.expansion_cost( - dest_offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - )?; - - let gas_cost = gas_cost::CODECOPY_STATIC - + gas_cost::CODECOPY_DYNAMIC_BASE * minimum_word_size - + memory_expansion_cost; + let gas_cost = + gas_cost::codecopy(current_call_frame, size, dest_offset).map_err(VMError::OutOfGas)?; self.increase_consumed_gas(current_call_frame, gas_cost)?; @@ -252,9 +240,9 @@ impl VM { let code = if offset < bytecode_len { current_call_frame.bytecode.slice( offset - ..(offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?) + ..(offset.checked_add(size).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?) .min(bytecode_len), ) } else { @@ -288,7 +276,7 @@ impl VM { if self.cache.is_account_cached(&address) { self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?; } else { - self.increase_consumed_gas(current_call_frame, COLD_ADDRESS_ACCESS_COST)?; + self.increase_consumed_gas(current_call_frame, BALANCE_COLD_ADDRESS_ACCESS_COST)?; self.cache_from_db(&address); }; @@ -320,47 +308,32 @@ impl VM { .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let minimum_word_size = (size + WORD_SIZE - 1) / WORD_SIZE; - let memory_expansion_cost = current_call_frame.memory.expansion_cost( - dest_offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - )?; - let gas_cost = - gas_cost::EXTCODECOPY_DYNAMIC_BASE * minimum_word_size + memory_expansion_cost; + let is_cached = self.cache.is_account_cached(&address); - if self.cache.is_account_cached(&address) { - self.increase_consumed_gas(current_call_frame, gas_cost + WARM_ADDRESS_ACCESS_COST)?; - } else { - self.increase_consumed_gas(current_call_frame, gas_cost + COLD_ADDRESS_ACCESS_COST)?; + let gas_cost = gas_cost::extcodecopy(current_call_frame, size, dest_offset, is_cached) + .map_err(VMError::OutOfGas)?; + + self.increase_consumed_gas(current_call_frame, gas_cost)?; + + if !is_cached { self.cache_from_db(&address); }; let mut bytecode = self.get_account(&address).info.bytecode; - if bytecode.len() - < offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)? - { + let new_offset = offset.checked_add(size).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + + if bytecode.len() < new_offset { let mut extended_code = bytecode.to_vec(); - extended_code.resize( - offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - 0, - ); + extended_code.resize(new_offset, 0); bytecode = Bytes::from(extended_code); } current_call_frame.memory.store_bytes( dest_offset, bytecode - .get( - offset - ..offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - ) + .get(offset..new_offset) .ok_or(VMError::Internal(InternalError::SlicingError))?, // bytecode can be "refactored" in order to avoid handling the error. )?; @@ -402,15 +375,8 @@ impl VM { .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let minimum_word_size = (size + WORD_SIZE - 1) / WORD_SIZE; - let memory_expansion_cost = current_call_frame.memory.expansion_cost( - dest_offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - )?; - let gas_cost = gas_cost::RETURNDATACOPY_STATIC - + gas_cost::RETURNDATACOPY_DYNAMIC_BASE * minimum_word_size - + memory_expansion_cost; + let gas_cost = gas_cost::returndatacopy(current_call_frame, size, dest_offset) + .map_err(VMError::OutOfGas)?; self.increase_consumed_gas(current_call_frame, gas_cost)?; @@ -424,7 +390,9 @@ impl VM { returndata_offset ..(returndata_offset .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?) .min(sub_return_data_len), ) } else { @@ -446,7 +414,7 @@ impl VM { if self.cache.is_account_cached(&address) { self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?; } else { - self.increase_consumed_gas(current_call_frame, COLD_ADDRESS_ACCESS_COST)?; + self.increase_consumed_gas(current_call_frame, BALANCE_COLD_ADDRESS_ACCESS_COST)?; self.cache_from_db(&address); }; diff --git a/crates/vm/levm/src/opcode_handlers/exchange.rs b/crates/vm/levm/src/opcode_handlers/exchange.rs index 0835abcc4..e01e1d41a 100644 --- a/crates/vm/levm/src/opcode_handlers/exchange.rs +++ b/crates/vm/levm/src/opcode_handlers/exchange.rs @@ -1,7 +1,7 @@ use crate::{ call_frame::CallFrame, - constants::gas_cost, errors::{OpcodeSuccess, VMError}, + gas_cost, opcodes::Opcode, vm::VM, }; @@ -18,7 +18,11 @@ impl VM { ) -> Result { self.increase_consumed_gas(current_call_frame, gas_cost::SWAPN)?; - let depth = usize::from(op) - usize::from(Opcode::SWAP1) + 1; + let depth = (usize::from(op)) + .checked_sub(usize::from(Opcode::SWAP1)) + .ok_or(VMError::InvalidOpcode)? + .checked_add(1) + .ok_or(VMError::InvalidOpcode)?; let stack_top_index = current_call_frame .stack .len() @@ -31,7 +35,6 @@ impl VM { let to_swap_index = stack_top_index .checked_sub(depth) .ok_or(VMError::StackUnderflow)?; - current_call_frame .stack .swap(stack_top_index, to_swap_index)?; diff --git a/crates/vm/levm/src/opcode_handlers/keccak.rs b/crates/vm/levm/src/opcode_handlers/keccak.rs index 449a7888c..7b002e8bd 100644 --- a/crates/vm/levm/src/opcode_handlers/keccak.rs +++ b/crates/vm/levm/src/opcode_handlers/keccak.rs @@ -1,7 +1,7 @@ use crate::{ call_frame::CallFrame, - constants::{gas_cost, WORD_SIZE}, errors::{OpcodeSuccess, VMError}, + gas_cost, vm::VM, }; use ethereum_rust_core::U256; @@ -20,21 +20,14 @@ impl VM { .pop()? .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let size = current_call_frame + let size: usize = current_call_frame .stack .pop()? .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let minimum_word_size = (size + WORD_SIZE - 1) / WORD_SIZE; - let memory_expansion_cost = current_call_frame.memory.expansion_cost( - offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - )?; - let gas_cost = gas_cost::KECCAK25_STATIC - + gas_cost::KECCAK25_DYNAMIC_BASE * minimum_word_size - + memory_expansion_cost; + let gas_cost = + gas_cost::keccak256(current_call_frame, size, offset).map_err(VMError::OutOfGas)?; self.increase_consumed_gas(current_call_frame, gas_cost)?; diff --git a/crates/vm/levm/src/opcode_handlers/logging.rs b/crates/vm/levm/src/opcode_handlers/logging.rs index f7b93be38..88d29a255 100644 --- a/crates/vm/levm/src/opcode_handlers/logging.rs +++ b/crates/vm/levm/src/opcode_handlers/logging.rs @@ -1,7 +1,7 @@ use crate::{ call_frame::CallFrame, - constants::gas_cost, errors::{OpcodeSuccess, VMError}, + gas_cost, opcodes::Opcode, vm::VM, }; @@ -22,7 +22,10 @@ impl VM { return Err(VMError::OpcodeNotAllowedInStaticContext); } - let number_of_topics = u8::from(op) - u8::from(Opcode::LOG0); + let number_of_topics = (u8::from(op)) + .checked_sub(u8::from(Opcode::LOG0)) + .ok_or(VMError::InvalidOpcode)?; + let offset: usize = current_call_frame .stack .pop()? @@ -41,15 +44,8 @@ impl VM { topics.push(H256::from_slice(&topic_bytes)); } - let memory_expansion_cost = current_call_frame.memory.expansion_cost( - offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - )?; - let gas_cost = gas_cost::LOGN_STATIC - + gas_cost::LOGN_DYNAMIC_BASE * number_of_topics - + gas_cost::LOGN_DYNAMIC_BYTE_BASE * size - + memory_expansion_cost; + let gas_cost = gas_cost::log(current_call_frame, size, offset, number_of_topics) + .map_err(VMError::OutOfGas)?; self.increase_consumed_gas(current_call_frame, gas_cost)?; diff --git a/crates/vm/levm/src/opcode_handlers/push.rs b/crates/vm/levm/src/opcode_handlers/push.rs index 11fefb963..39bed978b 100644 --- a/crates/vm/levm/src/opcode_handlers/push.rs +++ b/crates/vm/levm/src/opcode_handlers/push.rs @@ -1,7 +1,7 @@ use crate::{ call_frame::CallFrame, - constants::gas_cost, - errors::{OpcodeSuccess, VMError}, + errors::{InternalError, OpcodeSuccess, VMError}, + gas_cost, opcodes::Opcode, vm::VM, }; @@ -19,18 +19,28 @@ impl VM { ) -> Result { self.increase_consumed_gas(current_call_frame, gas_cost::PUSHN)?; - let n_bytes = usize::from(op) - usize::from(Opcode::PUSH1) + 1; + let n_bytes = (usize::from(op)) + .checked_sub(usize::from(Opcode::PUSH1)) + .ok_or(VMError::InvalidOpcode)? + .checked_add(1) + .ok_or(VMError::InvalidOpcode)?; let next_n_bytes = current_call_frame .bytecode - .get(current_call_frame.pc()..current_call_frame.pc() + n_bytes) + .get( + current_call_frame.pc() + ..current_call_frame + .pc() + .checked_add(n_bytes) + .ok_or(VMError::Internal(InternalError::PCOverflowed))?, + ) .ok_or(VMError::InvalidBytecode)?; // This shouldn't happen during execution let value_to_push = U256::from(next_n_bytes); current_call_frame.stack.push(value_to_push)?; - current_call_frame.increment_pc_by(n_bytes); + current_call_frame.increment_pc_by(n_bytes)?; Ok(OpcodeSuccess::Continue) } diff --git a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs index 6d71ea29b..a3b22e08b 100644 --- a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs +++ b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs @@ -1,10 +1,9 @@ use crate::{ account::StorageSlot, call_frame::CallFrame, - constants::{ - call_opcode::WARM_ADDRESS_ACCESS_COST, gas_cost, COLD_STORAGE_ACCESS_COST, WORD_SIZE, - }, - errors::{OpcodeSuccess, VMError}, + constants::{COLD_STORAGE_ACCESS_COST, WARM_ADDRESS_ACCESS_COST, WORD_SIZE}, + errors::{InternalError, OpcodeSuccess, OutOfGasError, VMError}, + gas_cost, vm::VM, }; use ethereum_rust_core::{H256, U256}; @@ -64,12 +63,8 @@ impl VM { .pop()? .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let memory_expansion_cost = current_call_frame.memory.expansion_cost( - offset - .checked_add(WORD_SIZE) - .ok_or(VMError::OverflowInArithmeticOp)?, - )?; - let gas_cost = gas_cost::MLOAD_STATIC + memory_expansion_cost; + + let gas_cost = gas_cost::mload(current_call_frame, offset).map_err(VMError::OutOfGas)?; self.increase_consumed_gas(current_call_frame, gas_cost)?; @@ -89,12 +84,8 @@ impl VM { .pop()? .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; - let memory_expansion_cost = current_call_frame.memory.expansion_cost( - offset - .checked_add(WORD_SIZE) - .ok_or(VMError::OverflowInArithmeticOp)?, - )?; - let gas_cost = gas_cost::MSTORE_STATIC + memory_expansion_cost; + + let gas_cost = gas_cost::mstore(current_call_frame, offset).map_err(VMError::OutOfGas)?; self.increase_consumed_gas(current_call_frame, gas_cost)?; @@ -114,17 +105,14 @@ impl VM { &mut self, current_call_frame: &mut CallFrame, ) -> Result { + // TODO: modify expansion cost to accept U256 let offset: usize = current_call_frame .stack .pop()? .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let memory_expansion_cost = current_call_frame.memory.expansion_cost( - offset - .checked_add(1) - .ok_or(VMError::OverflowInArithmeticOp)?, - )?; - let gas_cost = gas_cost::MSTORE8_STATIC + memory_expansion_cost; + + let gas_cost = gas_cost::mstore8(current_call_frame, offset).map_err(VMError::OutOfGas)?; self.increase_consumed_gas(current_call_frame, gas_cost)?; @@ -152,21 +140,19 @@ impl VM { key.to_big_endian(&mut bytes); let key = H256::from(bytes); - let mut base_dynamic_gas: U256 = U256::zero(); + let is_cached = self.cache.is_slot_cached(&address, key); - let current_value = if self.cache.is_slot_cached(&address, key) { - // If slot is warm (cached) add 100 to base_dynamic_gas - base_dynamic_gas += WARM_ADDRESS_ACCESS_COST; - - self.get_storage_slot(&address, key).current_value + let gas_cost = if is_cached { + // If slot is warm (cached) add 100 to gas_cost + WARM_ADDRESS_ACCESS_COST } else { - // If slot is cold (not cached) add 2100 to base_dynamic_gas - base_dynamic_gas += COLD_STORAGE_ACCESS_COST; - - self.get_storage_slot(&address, key).current_value + // If slot is cold (not cached) add 2100 to gas_cost + COLD_STORAGE_ACCESS_COST }; - self.increase_consumed_gas(current_call_frame, base_dynamic_gas)?; + let current_value = self.get_storage_slot(&address, key).current_value; + + self.increase_consumed_gas(current_call_frame, gas_cost)?; current_call_frame.stack.push(current_value)?; Ok(OpcodeSuccess::Continue) @@ -192,28 +178,14 @@ impl VM { let address = current_call_frame.to; - let mut base_dynamic_gas: U256 = U256::zero(); - - if !self.cache.is_slot_cached(&address, key) { - // If slot is cold 2100 is added to base_dynamic_gas - base_dynamic_gas += U256::from(2100); - }; + let is_cached = self.cache.is_slot_cached(&address, key); let storage_slot = self.get_storage_slot(&address, key); - base_dynamic_gas += if value == storage_slot.current_value { - U256::from(100) - } else if storage_slot.current_value == storage_slot.original_value { - if storage_slot.original_value == U256::zero() { - U256::from(20000) - } else { - U256::from(2900) - } - } else { - U256::from(100) - }; + let gas_cost = + gas_cost::sstore(value, is_cached, &storage_slot).map_err(VMError::OutOfGas)?; - self.increase_consumed_gas(current_call_frame, base_dynamic_gas)?; + self.increase_consumed_gas(current_call_frame, gas_cost)?; // Gas Refunds // TODO: Think about what to do in case of underflow of gas refunds (when we try to substract from it if the value is low) @@ -282,7 +254,14 @@ impl VM { pub fn op_gas(&mut self, current_call_frame: &mut CallFrame) -> Result { self.increase_consumed_gas(current_call_frame, gas_cost::GAS)?; - let remaining_gas = self.env.gas_limit - self.env.consumed_gas - gas_cost::GAS; + let remaining_gas = self + .env + .gas_limit + .checked_sub(self.env.consumed_gas) + .ok_or(VMError::OutOfGas(OutOfGasError::ConsumedGasOverflow))? + .checked_sub(gas_cost::GAS) + .ok_or(VMError::OutOfGas(OutOfGasError::ConsumedGasOverflow))?; + // Note: These are not consumed gas calculations, but are related, so I used this wrapping here current_call_frame.stack.push(remaining_gas)?; Ok(OpcodeSuccess::Continue) @@ -309,21 +288,8 @@ impl VM { .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let words_copied = (size + WORD_SIZE - 1) / WORD_SIZE; - - let memory_byte_size = src_offset - .checked_add(size) - .and_then(|src_sum| { - dest_offset - .checked_add(size) - .map(|dest_sum| src_sum.max(dest_sum)) - }) - .ok_or(VMError::OverflowInArithmeticOp)?; - - let memory_expansion_cost = current_call_frame.memory.expansion_cost(memory_byte_size)?; - let gas_cost = gas_cost::MCOPY_STATIC - + gas_cost::MCOPY_DYNAMIC_BASE * words_copied - + memory_expansion_cost; + let gas_cost = gas_cost::mcopy(current_call_frame, size, src_offset, dest_offset) + .map_err(VMError::OutOfGas)?; self.increase_consumed_gas(current_call_frame, gas_cost)?; @@ -380,9 +346,12 @@ impl VM { pub fn op_pc(&mut self, current_call_frame: &mut CallFrame) -> Result { self.increase_consumed_gas(current_call_frame, gas_cost::PC)?; - current_call_frame - .stack - .push(U256::from(current_call_frame.pc - 1))?; + current_call_frame.stack.push(U256::from( + current_call_frame + .pc + .checked_sub(1) + .ok_or(VMError::Internal(InternalError::PCUnderflowed))?, + ))?; Ok(OpcodeSuccess::Continue) } diff --git a/crates/vm/levm/src/opcode_handlers/system.rs b/crates/vm/levm/src/opcode_handlers/system.rs index 4f448780e..e108c7b8b 100644 --- a/crates/vm/levm/src/opcode_handlers/system.rs +++ b/crates/vm/levm/src/opcode_handlers/system.rs @@ -1,7 +1,8 @@ use crate::{ call_frame::CallFrame, - constants::{call_opcode, gas_cost, SUCCESS_FOR_RETURN}, - errors::{OpcodeSuccess, ResultReason, VMError}, + constants::SUCCESS_FOR_RETURN, + errors::{InternalError, OpcodeSuccess, ResultReason, VMError}, + gas_cost, vm::{word_to_address, VM}, }; use ethereum_rust_core::{types::TxKind, U256}; @@ -43,40 +44,25 @@ impl VM { return Err(VMError::OpcodeNotAllowedInStaticContext); } - let memory_byte_size = (args_offset - .checked_add(args_size) - .ok_or(VMError::MemoryLoadOutOfBounds)?) - .max( - ret_offset - .checked_add(ret_size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - ); - let memory_expansion_cost = current_call_frame.memory.expansion_cost(memory_byte_size)?; - - let positive_value_cost = if !value.is_zero() { - call_opcode::NON_ZERO_VALUE_COST + call_opcode::BASIC_FALLBACK_FUNCTION_STIPEND - } else { - U256::zero() - }; + let is_cached = self.cache.is_account_cached(&code_address); - let address_access_cost = if !self.cache.is_account_cached(&code_address) { + if !is_cached { self.cache_from_db(&code_address); - call_opcode::COLD_ADDRESS_ACCESS_COST - } else { - call_opcode::WARM_ADDRESS_ACCESS_COST - }; - let account = self.get_account(&code_address); + } - let value_to_empty_account_cost = if !value.is_zero() && account.is_empty() { - call_opcode::VALUE_TO_EMPTY_ACCOUNT_COST - } else { - U256::zero() - }; + let account_is_empty = self.get_account(&code_address).clone().is_empty(); - let gas_cost = memory_expansion_cost - + address_access_cost - + positive_value_cost - + value_to_empty_account_cost; + let gas_cost = gas_cost::call( + current_call_frame, + args_size, + args_offset, + ret_size, + ret_offset, + value, + is_cached, + account_is_empty, + ) + .map_err(VMError::OutOfGas)?; self.increase_consumed_gas(current_call_frame, gas_cost)?; @@ -130,17 +116,25 @@ impl VM { .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; - let memory_byte_size = args_offset - .checked_add(args_size) - .and_then(|src_sum| { - ret_offset - .checked_add(ret_size) - .map(|dest_sum| src_sum.max(dest_sum)) - }) - .ok_or(VMError::OverflowInArithmeticOp)?; - let memory_expansion_cost = current_call_frame.memory.expansion_cost(memory_byte_size)?; + // Gas consumed + let is_cached = self.cache.is_account_cached(&code_address); - self.increase_consumed_gas(current_call_frame, memory_expansion_cost)?; + if !is_cached { + self.cache_from_db(&code_address); + }; + + let gas_cost = gas_cost::callcode( + current_call_frame, + args_size, + args_offset, + ret_size, + ret_offset, + value, + is_cached, + ) + .map_err(VMError::OutOfGas)?; + + self.increase_consumed_gas(current_call_frame, gas_cost)?; // Sender and recipient are the same in this case. But the code executed is from another account. let msg_sender = current_call_frame.to; @@ -179,11 +173,12 @@ impl VM { .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let gas_cost = current_call_frame.memory.expansion_cost( - offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - )?; + let gas_cost = + current_call_frame + .memory + .expansion_cost(offset.checked_add(size).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?)?; self.increase_consumed_gas(current_call_frame, gas_cost)?; @@ -230,17 +225,23 @@ impl VM { let to = current_call_frame.to; let is_static = current_call_frame.is_static; - let memory_byte_size = args_offset - .checked_add(args_size) - .and_then(|src_sum| { - ret_offset - .checked_add(ret_size) - .map(|dest_sum| src_sum.max(dest_sum)) - }) - .ok_or(VMError::OverflowInArithmeticOp)?; - let memory_expansion_cost = current_call_frame.memory.expansion_cost(memory_byte_size)?; + // Gas consumed + let is_cached = self.cache.is_account_cached(&code_address); + if !is_cached { + self.cache_from_db(&code_address); + }; + + let gas_cost = gas_cost::delegatecall( + current_call_frame, + args_size, + args_offset, + ret_size, + ret_offset, + is_cached, + ) + .map_err(VMError::OutOfGas)?; - self.increase_consumed_gas(current_call_frame, memory_expansion_cost)?; + self.increase_consumed_gas(current_call_frame, gas_cost)?; self.generic_call( current_call_frame, @@ -291,17 +292,24 @@ impl VM { let msg_sender = current_call_frame.to; // The new sender will be the current contract. let to = code_address; // In this case code_address and the sub-context account are the same. Unlike CALLCODE or DELEGATECODE. - let memory_byte_size = args_offset - .checked_add(args_size) - .and_then(|src_sum| { - ret_offset - .checked_add(ret_size) - .map(|dest_sum| src_sum.max(dest_sum)) - }) - .ok_or(VMError::OverflowInArithmeticOp)?; - let memory_expansion_cost = current_call_frame.memory.expansion_cost(memory_byte_size)?; + // Gas consumed + let is_cached = self.cache.is_account_cached(&code_address); - self.increase_consumed_gas(current_call_frame, memory_expansion_cost)?; + if !is_cached { + self.cache_from_db(&code_address); + }; + + let gas_cost = gas_cost::staticcall( + current_call_frame, + args_size, + args_offset, + ret_size, + ret_offset, + is_cached, + ) + .map_err(VMError::OutOfGas)?; + + self.increase_consumed_gas(current_call_frame, gas_cost)?; self.generic_call( current_call_frame, @@ -329,6 +337,16 @@ impl VM { let code_offset_in_memory = current_call_frame.stack.pop()?; let code_size_in_memory = current_call_frame.stack.pop()?; + // Gas Cost + let gas_cost = gas_cost::create( + current_call_frame, + code_offset_in_memory, + code_size_in_memory, + ) + .map_err(VMError::OutOfGas)?; + + self.increase_consumed_gas(current_call_frame, gas_cost)?; + self.create( value_in_wei_to_send, code_offset_in_memory, @@ -349,6 +367,16 @@ impl VM { let code_size_in_memory = current_call_frame.stack.pop()?; let salt = current_call_frame.stack.pop()?; + // Gas Cost + let gas_cost = gas_cost::create_2( + current_call_frame, + code_offset_in_memory, + code_size_in_memory, + ) + .map_err(VMError::OutOfGas)?; + + self.increase_consumed_gas(current_call_frame, gas_cost)?; + self.create( value_in_wei_to_send, code_offset_in_memory, @@ -372,11 +400,12 @@ impl VM { let size = current_call_frame.stack.pop()?.as_usize(); - let gas_cost = current_call_frame.memory.expansion_cost( - offset - .checked_add(size) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - )?; + let gas_cost = + current_call_frame + .memory + .expansion_cost(offset.checked_add(size).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?)?; self.increase_consumed_gas(current_call_frame, gas_cost)?; @@ -411,12 +440,6 @@ impl VM { return Err(VMError::OpcodeNotAllowedInStaticContext); } - // Gas costs variables - let static_gas_cost = gas_cost::SELFDESTRUCT_STATIC; - let dynamic_gas_cost = gas_cost::SELFDESTRUCT_DYNAMIC; - let cold_gas_cost = gas_cost::COLD_ADDRESS_ACCESS_COST; - let mut gas_cost = static_gas_cost; - // 1. Pop the target address from the stack let target_address = word_to_address(current_call_frame.stack.pop()?); @@ -426,17 +449,20 @@ impl VM { current_account.info.balance = U256::zero(); - // 3 & 4. Get target account and add the balance of the current account to it - // TODO: If address is cold, there is an additional cost of 2600. - if !self.cache.is_account_cached(&target_address) { - gas_cost += cold_gas_cost; - } + let is_cached = self.cache.is_account_cached(&target_address); + // 3 & 4. Get target account and add the balance of the current account to it let mut target_account = self.get_account(&target_address); - if target_account.is_empty() { - gas_cost += dynamic_gas_cost; - } - target_account.info.balance += current_account_balance; + let account_is_empty = target_account.is_empty(); + + let gas_cost = + gas_cost::selfdestruct(is_cached, account_is_empty).map_err(VMError::OutOfGas)?; + + target_account.info.balance = target_account + .info + .balance + .checked_add(current_account_balance) + .ok_or(VMError::BalanceOverflow)?; // 5. Register account to be destroyed in accrued substate IF executed in the same transaction a contract was created if self.tx_kind == TxKind::Create { diff --git a/crates/vm/levm/src/operations.rs b/crates/vm/levm/src/operations.rs index b55d899c1..5f36d17f5 100644 --- a/crates/vm/levm/src/operations.rs +++ b/crates/vm/levm/src/operations.rs @@ -179,27 +179,54 @@ impl Operation { ); let mut word_buffer = [0; 32]; value.to_big_endian(&mut word_buffer); + // extract the last n bytes to push let value_to_push = &word_buffer - .get((32 - n_usize)..) + .get( + (32_usize.checked_sub(n_usize).ok_or(VMError::Internal( + InternalError::ArithmeticOperationUnderflow, + ))?).., + ) .ok_or(VMError::Internal(InternalError::SlicingError))?; assert_eq!(value_to_push.len(), n_usize); - let opcode = Opcode::try_from(u8::from(Opcode::PUSH0) + *n)?; + let opcode = Opcode::try_from((u8::from(Opcode::PUSH0)).checked_add(*n).ok_or( + VMError::Internal(InternalError::ArithmeticOperationOverflow), + )?)?; let mut bytes = vec![u8::from(opcode)]; bytes.extend_from_slice(value_to_push); Bytes::copy_from_slice(&bytes) } Operation::Dup(n) => { + assert!(*n >= 1, "DUP1 is the min"); assert!(*n <= 16, "DUP16 is the max"); - Bytes::copy_from_slice(&[u8::from(Opcode::DUP1) + n - 1]) + Bytes::copy_from_slice(&[(u8::from(Opcode::DUP1)) + .checked_add(*n) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))? + .checked_sub(1) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationUnderflow, + ))?]) } Operation::Swap(n) => { + assert!(*n >= 1, "SWAP1 is the min"); assert!(*n <= 16, "SWAP16 is the max"); - Bytes::copy_from_slice(&[u8::from(Opcode::SWAP1) + n - 1]) + Bytes::copy_from_slice(&[(u8::from(Opcode::SWAP1)) + .checked_add(*n) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))? + .checked_sub(1) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationUnderflow, + ))?]) } Operation::Log(n) => { assert!(*n <= 4, "LOG4 is the max"); - Bytes::copy_from_slice(&[u8::from(Opcode::LOG0) + n]) + Bytes::copy_from_slice(&[(u8::from(Opcode::LOG0)).checked_add(*n).ok_or( + VMError::Internal(InternalError::ArithmeticOperationOverflow), + )?]) } Operation::Create => Bytes::copy_from_slice(&[u8::from(Opcode::CREATE)]), Operation::Call => Bytes::copy_from_slice(&[u8::from(Opcode::CALL)]), diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 1a15ae948..613d5c55a 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -4,15 +4,17 @@ use crate::{ constants::*, db::{Cache, Database}, environment::Environment, - errors::{InternalError, OpcodeSuccess, ResultReason, TransactionReport, TxResult, VMError}, + errors::{ + InternalError, OpcodeSuccess, OutOfGasError, ResultReason, TransactionReport, TxResult, + VMError, + }, + gas_cost, opcodes::Opcode, }; use bytes::Bytes; -use create_opcode::{CODE_DEPOSIT_COST, CREATE_BASE_COST, INIT_CODE_WORD_COST}; use ethereum_rust_core::{types::TxKind, Address, H256, U256}; use ethereum_rust_rlp; use ethereum_rust_rlp::encode::RLPEncode; -use gas_cost::KECCAK25_DYNAMIC_BASE; use keccak_hash::keccak; use sha3::{Digest, Keccak256}; use std::{ @@ -56,7 +58,7 @@ pub fn address_to_word(address: Address) -> U256 { } pub fn word_to_address(word: U256) -> Address { - let mut bytes = [0u8; 32]; + let mut bytes = [0u8; WORD_SIZE]; word.to_big_endian(&mut bytes); Address::from_slice(&bytes[12..]) } @@ -161,7 +163,21 @@ impl VM { ); loop { - let opcode = current_call_frame.next_opcode().unwrap_or(Opcode::STOP); + let opcode = match current_call_frame.next_opcode() { + Ok(opt) => opt.unwrap_or(Opcode::STOP), + Err(e) => { + return TransactionReport { + result: TxResult::Revert(e), + new_state: self.cache.accounts.clone(), + gas_used: current_call_frame.gas_used.low_u64(), + gas_refunded: self.env.refunded_gas.low_u64(), + output: current_call_frame.returndata.clone(), // Bytes::new() if error is not RevertOpcode + logs: current_call_frame.logs.clone(), + created_address: None, + }; + } + }; + // Note: This is commented because it's used for debugging purposes in development. // dbg!(¤t_call_frame.gas_used); // dbg!(&opcode); @@ -286,9 +302,12 @@ impl VM { // Unless error is from Revert opcode, all gas is consumed if error != VMError::RevertOpcode { - let left_gas = current_call_frame.gas_limit - current_call_frame.gas_used; - current_call_frame.gas_used += left_gas; - self.env.consumed_gas += left_gas; + let left_gas = current_call_frame + .gas_limit + .saturating_sub(current_call_frame.gas_used); + current_call_frame.gas_used = + current_call_frame.gas_used.saturating_add(left_gas); + self.env.consumed_gas = self.env.consumed_gas.saturating_add(left_gas); } self.restore_state(backup_db, backup_substate, backup_refunded_gas); @@ -364,12 +383,13 @@ impl VM { .info .nonce .checked_add(1) - .ok_or(VMError::NonceOverflow)?; + .ok_or(VMError::Internal(InternalError::NonceOverflowed))?; // (4) if sender_account.has_code()? { return Err(VMError::SenderAccountShouldNotHaveBytecode); } + // (6) if sender_account.info.balance < call_frame.msg_value { return Err(VMError::SenderBalanceShouldContainTransferValue); @@ -401,7 +421,11 @@ impl VM { let sender = call_frame.msg_sender; let mut sender_account = self.get_account(&sender); - sender_account.info.nonce -= 1; + sender_account.info.nonce = sender_account + .info + .nonce + .checked_sub(1) + .ok_or(VMError::Internal(InternalError::NonceUnderflowed))?; let new_contract_address = call_frame.to; @@ -447,19 +471,13 @@ impl VM { ))? .clone(); - // This cost applies both for call and create - // 4 gas for each zero byte in the transaction data 16 gas for each non-zero byte in the transaction. - let mut calldata_cost = 0; - for byte in &initial_call_frame.calldata { - if *byte != 0 { - calldata_cost += 16; - } else { - calldata_cost += 4; - } - } + let calldata_cost = + gas_cost::tx_calldata(&initial_call_frame.calldata).map_err(VMError::OutOfGas)?; - let max_gas = self.env.gas_limit.low_u64(); - report.add_gas_with_max(calldata_cost, max_gas); + report.gas_used = report + .gas_used + .checked_add(calldata_cost) + .ok_or(VMError::OutOfGas(OutOfGasError::GasUsedOverflow))?; if self.is_create() { // If create should check if transaction failed. If failed should revert (delete created contract, ) @@ -489,25 +507,28 @@ impl VM { // If the initialization code completes successfully, a final contract-creation cost is paid, // the code-deposit cost, c, proportional to the size of the created contract’s code - let code_length: u64 = contract_code + let number_of_words: u64 = initial_call_frame + .calldata + .chunks(WORD_SIZE) .len() .try_into() .map_err(|_| VMError::Internal(InternalError::ConversionError))?; - let mut creation_cost = 200 * code_length; - creation_cost += 32000; - report.add_gas_with_max(creation_cost, max_gas); - // Charge 22100 gas for each storage variable set - // GInitCodeword * number_of_words rounded up. GinitCodeWord = 2 - let number_of_words: u64 = initial_call_frame - .calldata - .chunks(WORD_SIZE) + let code_length: u64 = contract_code .len() .try_into() .map_err(|_| VMError::Internal(InternalError::ConversionError))?; - report.add_gas_with_max(number_of_words * 2, max_gas); + + let creation_cost = + gas_cost::tx_creation(code_length, number_of_words).map_err(VMError::OutOfGas)?; + report.gas_used = report + .gas_used + .checked_add(creation_cost) + .ok_or(VMError::OutOfGas(OutOfGasError::GasUsedOverflow))?; + // Charge 22100 gas for each storage variable set let contract_address = initial_call_frame.to; + let mut created_contract = self.get_account(&contract_address); created_contract.info.bytecode = contract_code; @@ -526,7 +547,7 @@ impl VM { .checked_mul(self.env.gas_price) .ok_or(VMError::GasLimitPriceProductOverflow)?, ) - .ok_or(VMError::OutOfGas)?; + .ok_or(VMError::BalanceUnderflow)?; let receiver_address = initial_call_frame.to; let mut receiver_account = self.get_account(&receiver_address); @@ -537,12 +558,12 @@ impl VM { .info .balance .checked_sub(initial_call_frame.msg_value) - .ok_or(VMError::OutOfGas)?; // This error shouldn't be OutOfGas + .ok_or(VMError::BalanceUnderflow)?; receiver_account.info.balance = receiver_account .info .balance .checked_add(initial_call_frame.msg_value) - .ok_or(VMError::OutOfGas)?; // This error shouldn't be OutOfGas + .ok_or(VMError::BalanceUnderflow)?; } // Note: This is commented because it's used for debugging purposes in development. @@ -552,11 +573,21 @@ impl VM { self.cache.add_account(&receiver_address, &receiver_account); // Send coinbase fee - let priority_fee_per_gas = self.env.gas_price - self.env.base_fee_per_gas; - let coinbase_fee = (U256::from(report.gas_used)) * priority_fee_per_gas; + let priority_fee_per_gas = self + .env + .gas_price + .checked_sub(self.env.base_fee_per_gas) + .ok_or(VMError::GasPriceIsLowerThanBaseFee)?; + let coinbase_fee = (U256::from(report.gas_used)) + .checked_mul(priority_fee_per_gas) + .ok_or(VMError::BalanceOverflow)?; let mut coinbase_account = self.get_account(&coinbase_address); - coinbase_account.info.balance += coinbase_fee; + coinbase_account.info.balance = coinbase_account + .info + .balance + .checked_add(coinbase_fee) + .ok_or(VMError::BalanceOverflow)?; self.cache.add_account(&coinbase_address, &coinbase_account); @@ -598,8 +629,16 @@ impl VM { let mut recipient_account = self.get_account(&to); // transfer value - sender_account.info.balance -= value; - recipient_account.info.balance += value; + sender_account.info.balance = sender_account + .info + .balance + .checked_sub(value) + .ok_or(VMError::BalanceUnderflow)?; + recipient_account.info.balance = recipient_account + .info + .balance + .checked_add(value) + .ok_or(VMError::BalanceOverflow)?; let code_address_bytecode = self.get_account(&code_address).info.bytecode; @@ -618,10 +657,21 @@ impl VM { .into(); // I don't know if this gas limit should be calculated before or after consuming gas - let gas_limit = std::cmp::min(gas_limit, { - let remaining_gas = current_call_frame.gas_limit - current_call_frame.gas_used; - remaining_gas - remaining_gas / 64 - }); + let mut potential_remaining_gas = current_call_frame + .gas_limit + .checked_sub(current_call_frame.gas_used) + .ok_or(VMError::OutOfGas(OutOfGasError::MaxGasLimitExceeded))?; + potential_remaining_gas = potential_remaining_gas + .checked_sub(potential_remaining_gas.checked_div(64.into()).ok_or( + VMError::Internal(InternalError::ArithmeticOperationOverflow), + )?) + .ok_or(VMError::OutOfGas(OutOfGasError::MaxGasLimitExceeded))?; + let gas_limit = std::cmp::min(gas_limit, potential_remaining_gas); + + let new_depth = current_call_frame + .depth + .checked_add(1) + .ok_or(VMError::StackOverflow)?; // Maybe could be depthOverflow but in concept is quite similar let mut new_call_frame = CallFrame::new( msg_sender, @@ -633,14 +683,14 @@ impl VM { is_static, gas_limit, U256::zero(), - current_call_frame.depth + 1, + new_depth, ); // TODO: Increase this to 1024 if new_call_frame.depth > 10 { current_call_frame.stack.push(U256::from(REVERT_FOR_CALL))?; // return Ok(OpcodeSuccess::Result(ResultReason::Revert)); - return Err(VMError::OutOfGas); // This is wrong but it is for testing purposes. + return Err(VMError::StackOverflow); // This is wrong but it is for testing purposes. } current_call_frame.sub_return_data_offset = ret_offset; @@ -654,7 +704,11 @@ impl VM { // self.call_frames.push(new_call_frame.clone()); let tx_report = self.execute(&mut new_call_frame); - current_call_frame.gas_used += tx_report.gas_used.into(); // Add gas used by the sub-context to the current one after it's execution. + // Add gas used by the sub-context to the current one after it's execution. + current_call_frame.gas_used = current_call_frame + .gas_used + .checked_add(tx_report.gas_used.into()) + .ok_or(VMError::OutOfGas(OutOfGasError::ConsumedGasOverflow))?; current_call_frame.logs.extend(tx_report.logs); current_call_frame .memory @@ -727,54 +781,6 @@ impl VM { Ok(generated_address) } - fn compute_gas_create( - &mut self, - current_call_frame: &mut CallFrame, - code_offset_in_memory: U256, - code_size_in_memory: U256, - is_create_2: bool, - ) -> Result { - let minimum_word_size = (code_size_in_memory - .checked_add(U256::from(31)) - .ok_or(VMError::DataSizeOverflow)?) - .checked_div(U256::from(32)) - .ok_or(VMError::Internal(InternalError::DivisionError))?; // '32' will never be zero - - let init_code_cost = minimum_word_size - .checked_mul(INIT_CODE_WORD_COST) - .ok_or(VMError::GasCostOverflow)?; - - let code_deposit_cost = code_size_in_memory - .checked_mul(CODE_DEPOSIT_COST) - .ok_or(VMError::GasCostOverflow)?; - - let memory_expansion_cost = current_call_frame.memory.expansion_cost( - code_size_in_memory - .checked_add(code_offset_in_memory) - .ok_or(VMError::OffsetOverflow)? - .try_into() - .map_err(|_err| VMError::OffsetOverflow)?, - )?; - - let hash_cost = if is_create_2 { - minimum_word_size - .checked_mul(KECCAK25_DYNAMIC_BASE) - .ok_or(VMError::GasCostOverflow)? - } else { - U256::zero() - }; - - init_code_cost - .checked_add(memory_expansion_cost) - .ok_or(VMError::CreationCostIsTooHigh)? - .checked_add(code_deposit_cost) - .ok_or(VMError::CreationCostIsTooHigh)? - .checked_add(CREATE_BASE_COST) - .ok_or(VMError::CreationCostIsTooHigh)? - .checked_add(hash_cost) - .ok_or(VMError::CreationCostIsTooHigh) - } - /// Common behavior for CREATE and CREATE2 opcodes /// /// Could be used for CREATE type transactions @@ -787,15 +793,6 @@ impl VM { salt: Option, current_call_frame: &mut CallFrame, ) -> Result { - let gas_cost = self.compute_gas_create( - current_call_frame, - code_offset_in_memory, - code_size_in_memory, - false, - )?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; - let code_size_in_memory = code_size_in_memory .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; @@ -899,11 +896,21 @@ impl VM { current_call_frame: &mut CallFrame, gas: U256, ) -> Result<(), VMError> { - if current_call_frame.gas_used + gas > current_call_frame.gas_limit { - return Err(VMError::OutOfGas); + let potential_consumed_gas = current_call_frame + .gas_used + .checked_add(gas) + .ok_or(VMError::OutOfGas(OutOfGasError::ConsumedGasOverflow))?; + if potential_consumed_gas > current_call_frame.gas_limit { + return Err(VMError::OutOfGas(OutOfGasError::MaxGasLimitExceeded)); } - current_call_frame.gas_used += gas; - self.env.consumed_gas += gas; + + current_call_frame.gas_used = potential_consumed_gas; + self.env.consumed_gas = self + .env + .consumed_gas + .checked_add(gas) + .ok_or(VMError::OutOfGas(OutOfGasError::ConsumedGasOverflow))?; + Ok(()) } diff --git a/crates/vm/levm/tests/ef/report.rs b/crates/vm/levm/tests/ef/report.rs index 283a39ccf..3069f90cf 100644 --- a/crates/vm/levm/tests/ef/report.rs +++ b/crates/vm/levm/tests/ef/report.rs @@ -1,3 +1,7 @@ +// Note: I use this to do not affect the EF tests logic with this side effects +// The cost to add this would be to return a Result<(), InternalError> in EFTestsReport methods +#![allow(clippy::arithmetic_side_effects)] + use colored::Colorize; use std::fmt; diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index 896aec6aa..cb2e64048 100644 --- a/crates/vm/levm/tests/tests.rs +++ b/crates/vm/levm/tests/tests.rs @@ -8,6 +8,7 @@ use ethereum_rust_levm::{ constants::*, db::{Cache, Db}, errors::{TxResult, VMError}, + gas_cost, operations::Operation, utils::{new_vm_with_ops, new_vm_with_ops_addr_bal_db, new_vm_with_ops_db, ops_to_bytecode}, vm::{word_to_address, Storage, VM}, From ee714e43702f862ad266f92f47da83c169ee7e20 Mon Sep 17 00:00:00 2001 From: Maximo Palopoli <96491141+maximopalopoli@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:56:24 -0300 Subject: [PATCH 15/17] fix(levm): remove implicit as conversions (#1170) **Motivation** We have recently removed as conversions because they panicked, but there were left some implicit conversions left. **Description** This is done by using `.try_into()` and then maping the error to a `VMError::VeryLargeNumber` --- crates/vm/levm/src/call_frame.rs | 18 +++++++++++------- .../vm/levm/src/opcode_handlers/arithmetic.rs | 4 +--- crates/vm/levm/src/opcode_handlers/block.rs | 10 ++++++++-- .../stack_memory_storage_flow.rs | 4 ++-- crates/vm/levm/src/opcode_handlers/system.rs | 12 ++++++++++-- 5 files changed, 32 insertions(+), 16 deletions(-) diff --git a/crates/vm/levm/src/call_frame.rs b/crates/vm/levm/src/call_frame.rs index a3d237a63..28535a27f 100644 --- a/crates/vm/levm/src/call_frame.rs +++ b/crates/vm/levm/src/call_frame.rs @@ -142,16 +142,20 @@ impl CallFrame { } /// Jump to the given address, returns false if the jump position wasn't a JUMPDEST - pub fn jump(&mut self, jump_address: U256) -> bool { - if !self.valid_jump(jump_address) { - return false; + pub fn jump(&mut self, jump_address: U256) -> Result { + let jump_address_usize = jump_address + .try_into() + .map_err(|_err| VMError::VeryLargeNumber)?; + + if !self.valid_jump(jump_address_usize) { + return Ok(false); } - self.pc = jump_address.as_usize(); - true + self.pc = jump_address_usize; + Ok(true) } - fn valid_jump(&self, jump_address: U256) -> bool { - self.opcode_at(jump_address.as_usize()) + fn valid_jump(&self, jump_address: usize) -> bool { + self.opcode_at(jump_address) .map(|opcode| opcode.eq(&Opcode::JUMPDEST)) .is_some_and(|is_jumpdest| is_jumpdest) } diff --git a/crates/vm/levm/src/opcode_handlers/arithmetic.rs b/crates/vm/levm/src/opcode_handlers/arithmetic.rs index 50d3bf81f..3ab87d470 100644 --- a/crates/vm/levm/src/opcode_handlers/arithmetic.rs +++ b/crates/vm/levm/src/opcode_handlers/arithmetic.rs @@ -262,7 +262,6 @@ impl VM { let max_byte_size: usize = 31; let byte_size: usize = byte_size.min(max_byte_size); - let total_bits = bits_per_byte .checked_mul(byte_size) .ok_or(VMError::Internal( @@ -274,14 +273,13 @@ impl VM { .ok_or(VMError::Internal( InternalError::ArithmeticOperationOverflow, ))?; - let is_negative = value_to_extend.bit(sign_bit_index); + let sign_bit_mask = checked_shift_left(U256::one(), sign_bit_index)? .checked_sub(U256::one()) .ok_or(VMError::Internal( InternalError::ArithmeticOperationUnderflow, ))?; //Shifted should be at least one - let result = if is_negative { value_to_extend | !sign_bit_mask } else { diff --git a/crates/vm/levm/src/opcode_handlers/block.rs b/crates/vm/levm/src/opcode_handlers/block.rs index a54b2ceaf..1b092900f 100644 --- a/crates/vm/levm/src/opcode_handlers/block.rs +++ b/crates/vm/levm/src/opcode_handlers/block.rs @@ -35,7 +35,9 @@ impl VM { return Ok(OpcodeSuccess::Continue); } - let block_number = block_number.as_u64(); + let block_number: u64 = block_number + .try_into() + .map_err(|_err| VMError::VeryLargeNumber)?; if let Some(block_hash) = self.db.get_block_hash(block_number) { current_call_frame @@ -162,7 +164,11 @@ impl VM { ) -> Result { self.increase_consumed_gas(current_call_frame, gas_cost::BLOBHASH)?; - let index = current_call_frame.stack.pop()?.as_usize(); + let index: usize = current_call_frame + .stack + .pop()? + .try_into() + .map_err(|_err| VMError::VeryLargeNumber)?; let blob_hash: H256 = match &self.env.tx_blob_hashes { Some(vec) => match vec.get(index) { diff --git a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs index a3b22e08b..bb70985d6 100644 --- a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs +++ b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs @@ -310,7 +310,7 @@ impl VM { self.increase_consumed_gas(current_call_frame, gas_cost::JUMP)?; let jump_address = current_call_frame.stack.pop()?; - if !current_call_frame.jump(jump_address) { + if !current_call_frame.jump(jump_address)? { return Err(VMError::InvalidJump); } @@ -325,7 +325,7 @@ impl VM { let jump_address = current_call_frame.stack.pop()?; let condition = current_call_frame.stack.pop()?; - if condition != U256::zero() && !current_call_frame.jump(jump_address) { + if condition != U256::zero() && !current_call_frame.jump(jump_address)? { return Err(VMError::InvalidJump); } diff --git a/crates/vm/levm/src/opcode_handlers/system.rs b/crates/vm/levm/src/opcode_handlers/system.rs index e108c7b8b..ab42f8837 100644 --- a/crates/vm/levm/src/opcode_handlers/system.rs +++ b/crates/vm/levm/src/opcode_handlers/system.rs @@ -396,9 +396,17 @@ impl VM { // Notes: // The actual reversion of changes is made in the execute() function. - let offset = current_call_frame.stack.pop()?.as_usize(); + let offset: usize = current_call_frame + .stack + .pop()? + .try_into() + .map_err(|_err| VMError::VeryLargeNumber)?; - let size = current_call_frame.stack.pop()?.as_usize(); + let size = current_call_frame + .stack + .pop()? + .try_into() + .map_err(|_err| VMError::VeryLargeNumber)?; let gas_cost = current_call_frame From b804aa205a9a46c332ec5a000cf67b9b35025040 Mon Sep 17 00:00:00 2001 From: Federico Borello <156438142+fborello-lambda@users.noreply.github.com> Date: Thu, 14 Nov 2024 14:21:37 -0300 Subject: [PATCH 16/17] feat(l2): verify proof on chain (#1115) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Motivation** After sending the commitment, we should generate the zkProof and then verify it on chain. **Description** Using the Risc0Groth16Verifier in Sepolia. Steps: Use the `.env.example` but change the following variables: ``` RISC0_DEV_MODE=0 ETH_RPC_URL= DEPLOYER_CONTRACT_VERIFIER=0xd9b0d07CeCd808a8172F21fA7C97992168f045CA (`Risc0Groth16Verifier`) ``` 1. cd `~/lambda_ethereum_rust/crates/l2` 2. run `make deploy-l1` -- if it fails change the `SALT` in `crates/l2/contracts/deployer.rs` 1. Copy the `OnChainProposer` address given in the .env file (`PROPOSER_ON_CHAIN_PROPOSER_ADDRESS`) 2. Copy the `Bridge address` given in the .env file (`L1_WATCHER_BRIDGE_ADDRESS`) 3. rm the libmdbx files `rm -rf ~/.local/share/ethereum_rust` 4. run the proposer/sequencer: `make init-l2` 5. In a new tab/window run the prover: `make init-l2-prover-gpu` --------- Co-authored-by: Manuel Iñaki Bilbao Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> --- cmd/ethereum_rust/ethereum_rust.rs | 17 +- crates/l2/.env.example | 21 +- crates/l2/Makefile | 35 +- crates/l2/contracts/deployer.rs | 44 +- .../l2/contracts/src/l1/OnChainProposer.sol | 55 +- .../src/l1/interfaces/IOnChainProposer.sol | 12 +- .../src/l1/interfaces/IRiscZeroVerifier.sol | 49 ++ crates/l2/docs/prover.md | 87 ++- crates/l2/proposer/errors.rs | 32 +- crates/l2/proposer/l1_committer.rs | 503 ++++++++++++++++++ crates/l2/proposer/mod.rs | 390 +------------- crates/l2/proposer/prover_server.rs | 380 ++++++++++--- crates/l2/prover/src/main.rs | 3 +- crates/l2/prover/src/prover.rs | 2 +- crates/l2/prover/src/prover_client.rs | 102 ++-- crates/l2/utils/config/committer.rs | 23 + crates/l2/utils/config/mod.rs | 5 +- crates/l2/utils/config/proposer.rs | 7 - crates/l2/utils/config/prover_client.rs | 1 + crates/l2/utils/config/prover_server.rs | 12 +- crates/l2/utils/eth_client/eth_sender.rs | 4 +- crates/l2/utils/eth_client/mod.rs | 22 +- 22 files changed, 1220 insertions(+), 586 deletions(-) create mode 100644 crates/l2/contracts/src/l1/interfaces/IRiscZeroVerifier.sol create mode 100644 crates/l2/proposer/l1_committer.rs create mode 100644 crates/l2/utils/config/committer.rs diff --git a/cmd/ethereum_rust/ethereum_rust.rs b/cmd/ethereum_rust/ethereum_rust.rs index e5c8a86f2..d9e1702fe 100644 --- a/cmd/ethereum_rust/ethereum_rust.rs +++ b/cmd/ethereum_rust/ethereum_rust.rs @@ -26,15 +26,15 @@ use tracing_subscriber::{EnvFilter, FmtSubscriber}; mod cli; mod decode; +const DEFAULT_DATADIR: &str = "ethereum_rust"; #[tokio::main] async fn main() { let matches = cli::cli().get_matches(); if let Some(matches) = matches.subcommand_matches("removedb") { - let default_datadir = get_default_datadir(); let data_dir = matches .get_one::("datadir") - .unwrap_or(&default_datadir); + .map_or(set_datadir(DEFAULT_DATADIR), |datadir| set_datadir(datadir)); let path = Path::new(&data_dir); if path.exists() { std::fs::remove_dir_all(path).expect("Failed to remove data directory"); @@ -110,11 +110,11 @@ async fn main() { let tcp_socket_addr = parse_socket_addr(tcp_addr, tcp_port).expect("Failed to parse addr and port"); - let default_datadir = get_default_datadir(); let data_dir = matches .get_one::("datadir") - .unwrap_or(&default_datadir); - let store = Store::new(data_dir, EngineType::Libmdbx).expect("Failed to create Store"); + .map_or(set_datadir(DEFAULT_DATADIR), |datadir| set_datadir(datadir)); + + let store = Store::new(&data_dir, EngineType::Libmdbx).expect("Failed to create Store"); let genesis = read_genesis_file(genesis_file_path); store @@ -204,7 +204,7 @@ async fn main() { // We do not want to start the networking module if the l2 feature is enabled. cfg_if::cfg_if! { if #[cfg(feature = "l2")] { - let l2_proposer = ethereum_rust_l2::start_proposer(store.clone()).into_future(); + let l2_proposer = ethereum_rust_l2::start_proposer(store).into_future(); tracker.spawn(l2_proposer); } else if #[cfg(feature = "dev")] { use ethereum_rust_dev; @@ -283,9 +283,8 @@ fn parse_socket_addr(addr: &str, port: &str) -> io::Result { )) } -fn get_default_datadir() -> String { - let project_dir = - ProjectDirs::from("", "", "ethereum_rust").expect("Couldn't find home directory"); +fn set_datadir(datadir: &str) -> String { + let project_dir = ProjectDirs::from("", "", datadir).expect("Couldn't find home directory"); project_dir .data_local_dir() .to_str() diff --git a/crates/l2/.env.example b/crates/l2/.env.example index 3a8ffed0f..a90392b37 100644 --- a/crates/l2/.env.example +++ b/crates/l2/.env.example @@ -1,4 +1,9 @@ ETH_RPC_URL=http://localhost:8545 +# If set to 0xAA skip proof verification. +# Only use in dev mode. +DEPLOYER_CONTRACT_VERIFIER=0x00000000000000000000000000000000000000AA +# Risc0Groth16Verifier Sepolia Address +# DEPLOYER_CONTRACT_VERIFIER=0xd9b0d07CeCd808a8172F21fA7C97992168f045CA DEPLOYER_ADDRESS=0x3d1e15a1a55578f7c920884a9943b3b35d0d885b DEPLOYER_PRIVATE_KEY=0x385c546456b6a603a1cfcaa9ec9494ba4832da08dd6bcf4de9a71e4a01b74924 # If set to false, the salt will be randomized. @@ -9,16 +14,22 @@ L1_WATCHER_CHECK_INTERVAL_MS=5000 L1_WATCHER_MAX_BLOCK_STEP=5000 L1_WATCHER_L2_PROPOSER_PRIVATE_KEY=0x385c546456b6a603a1cfcaa9ec9494ba4832da08dd6bcf4de9a71e4a01b74924 L1_WATCHER_L2_PROPOSER_ADDRESS=0x3d1e15a1a55578f7c920884a9943b3b35d0d885b -ENGINE_API_RPC_URL=http://localhost:8551 +ENGINE_API_RPC_URL=http://localhost:8552 ENGINE_API_JWT_PATH=./jwt.hex PROVER_SERVER_LISTEN_IP=127.0.0.1 PROVER_SERVER_LISTEN_PORT=3000 +# Not the same account as the COMMITTER_L1 Account +# The proposer is in charge of blob commitments. +# The prover_server is in charge of verifying the zkProofs. +PROVER_SERVER_VERIFIER_ADDRESS=0xE25583099BA105D9ec0A67f5Ae86D90e50036425 +PROVER_SERVER_VERIFIER_PRIVATE_KEY=0x39725efee3fb28614de3bacaffe4cc4bd8c436257e2c8bb887c4b5c4be45e76d PROVER_CLIENT_PROVER_SERVER_ENDPOINT=localhost:3000 -PROPOSER_ON_CHAIN_PROPOSER_ADDRESS=0xe9927d77c931f8648da4cc6751ef4e5e2ce74608 -PROPOSER_L1_ADDRESS=0x3d1e15a1a55578f7c920884a9943b3b35d0d885b -PROPOSER_L1_PRIVATE_KEY=0x385c546456b6a603a1cfcaa9ec9494ba4832da08dd6bcf4de9a71e4a01b74924 +COMMITTER_ON_CHAIN_PROPOSER_ADDRESS=0xe9927d77c931f8648da4cc6751ef4e5e2ce74608 +COMMITTER_L1_ADDRESS=0x3d1e15a1a55578f7c920884a9943b3b35d0d885b +COMMITTER_L1_PRIVATE_KEY=0x385c546456b6a603a1cfcaa9ec9494ba4832da08dd6bcf4de9a71e4a01b74924 +COMMITTER_INTERVAL_MS=1000 PROPOSER_INTERVAL_MS=5000 # https://dev.risczero.com/api/generating-proofs/dev-mode # 1/true means fake proofs +# The RISC0_DEV_MODE=1 should only be used with DEPLOYER_CONTRACT_VERIFIER=0xAA RISC0_DEV_MODE=1 -RUST_LOG="[executor]=info" diff --git a/crates/l2/Makefile b/crates/l2/Makefile index 08cf945cf..e6ca8a2e0 100644 --- a/crates/l2/Makefile +++ b/crates/l2/Makefile @@ -3,6 +3,7 @@ .PHONY: help init down clean init-local-l1 down-local-l1 clean-local-l1 init-l2 down-l2 deploy-l1 deploy-block-executor deploy-inbox setup-prover test L2_GENESIS_FILE_PATH=../../test_data/genesis-l2.json +L1_GENESIS_FILE_PATH=../../test_data/genesis-l1.json help: ## 📚 Show help for each of the Makefile recipes @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @@ -29,10 +30,26 @@ ETHEREUM_RUST_L2_CONTRACTS_PATH=./contracts L1_RPC_URL=http://localhost:8545 L1_PRIVATE_KEY=0x385c546456b6a603a1cfcaa9ec9494ba4832da08dd6bcf4de9a71e4a01b74924 +ETHEREUM_RUST_L2_DEV_LIBMDBX=dev_ethereum_rust_l2 +ETHEREUM_RUST_L1_DEV_LIBMDBX=dev_ethereum_rust_l1 +L1_PORT=8545 +L2_PORT=1729 +L1_AUTH_PORT=8551 +# Used in the .env file. Ensure the same port is used for `ENGINE_API_RPC_URL`. +L2_AUTH_PORT=8552 + # Local L1 -init-local-l1: ## 🚀 Initializes an L1 Lambda Ethereum Rust Client +init-local-l1: ## 🚀 Initializes an L1 Lambda Ethereum Rust Client with Docker (Used with make init) docker compose -f ${ETHEREUM_RUST_DEV_DOCKER_COMPOSE_PATH} up -d + +init-l1: ## 🚀 Initializes an L1 Lambda Ethereum Rust Client + cargo run --release --manifest-path ../../Cargo.toml --bin ethereum_rust --features dev -- \ + --network ${L1_GENESIS_FILE_PATH} \ + --http.port ${L1_PORT} \ + --http.addr 0.0.0.0 \ + --authrpc.port ${L1_AUTH_PORT} \ + --datadir ${ETHEREUM_RUST_L1_DEV_LIBMDBX} down-local-l1: ## 🛑 Shuts down the L1 Lambda Ethereum Rust Client docker compose -f ${ETHEREUM_RUST_DEV_DOCKER_COMPOSE_PATH} down @@ -40,6 +57,9 @@ down-local-l1: ## 🛑 Shuts down the L1 Lambda Ethereum Rust Client restart-local-l1: down-local-l1 init-local-l1 ## 🔄 Restarts the L1 Lambda Ethereum Rust Client +rm_dev_libmdbx_l1: ## 🛑 Removes the Libmdbx DB used by the L1 + cargo run --release --manifest-path ../../Cargo.toml --bin ethereum_rust -- removedb --datadir ${ETHEREUM_RUST_L1_DEV_LIBMDBX} + # Contracts clean-contract-deps: ## 🧹 Cleans the dependencies for the L1 contracts. @@ -54,7 +74,12 @@ deploy-l1: ## 📜 Deploys the L1 contracts # L2 init-l2: ## 🚀 Initializes an L2 Lambda Ethereum Rust Client - cargo run --release --manifest-path ../../Cargo.toml --bin ethereum_rust --features l2 -- --network ${L2_GENESIS_FILE_PATH} --http.port 1729 + cargo run --release --manifest-path ../../Cargo.toml --bin ethereum_rust --features l2 -- \ + --network ${L2_GENESIS_FILE_PATH} \ + --http.port ${L2_PORT} \ + --http.addr 0.0.0.0 \ + --authrpc.port ${L2_AUTH_PORT} \ + --datadir ${ETHEREUM_RUST_L2_DEV_LIBMDBX} down-l2: ## 🛑 Shuts down the L2 Lambda Ethereum Rust Client pkill -f ethereum_rust || exit 0 @@ -65,7 +90,11 @@ init-l2-prover: ## 🚀 Initializes the Prover cargo run --release --features build_zkvm --manifest-path ../../Cargo.toml --bin ethereum_rust_prover init-l2-prover-gpu: ## 🚀 Initializes the Prover with GPU support - cargo run --release --features "build_zkvm,cuda" --manifest-path ../../Cargo.toml --bin ethereum_rust_prover + cargo run --release --features "build_zkvm,gpu" --manifest-path ../../Cargo.toml --bin ethereum_rust_prover + +rm_dev_libmdbx_l2: ## 🛑 Removes the Libmdbx DB used by the L2 + cargo run --release --manifest-path ../../Cargo.toml --bin ethereum_rust -- removedb --datadir ${ETHEREUM_RUST_L2_DEV_LIBMDBX} + # CI Testing diff --git a/crates/l2/contracts/deployer.rs b/crates/l2/contracts/deployer.rs index c8dcad59b..602bd21b3 100644 --- a/crates/l2/contracts/deployer.rs +++ b/crates/l2/contracts/deployer.rs @@ -28,7 +28,8 @@ lazy_static::lazy_static! { #[tokio::main] async fn main() { - let (deployer, deployer_private_key, eth_client, contracts_path) = setup(); + let (deployer, deployer_private_key, contract_verifier_address, eth_client, contracts_path) = + setup(); download_contract_deps(&contracts_path); compile_contracts(&contracts_path); let (on_chain_proposer, bridge_address) = @@ -38,6 +39,7 @@ async fn main() { deployer_private_key, on_chain_proposer, bridge_address, + contract_verifier_address, ð_client, ) .await; @@ -50,7 +52,7 @@ async fn main() { if let Some(eq) = line.find('=') { let (envar, _) = line.split_at(eq); line = match envar { - "PROPOSER_ON_CHAIN_PROPOSER_ADDRESS" => { + "COMMITTER_ON_CHAIN_PROPOSER_ADDRESS" => { format!("{envar}={on_chain_proposer:#x}") } "L1_WATCHER_BRIDGE_ADDRESS" => { @@ -64,7 +66,7 @@ async fn main() { write_env(wr_lines).expect("Failed to write changes to the .env file."); } -fn setup() -> (Address, SecretKey, EthClient, PathBuf) { +fn setup() -> (Address, SecretKey, Address, EthClient, PathBuf) { if let Err(e) = read_env_file() { warn!("Failed to read .env file: {e}"); } @@ -99,12 +101,20 @@ fn setup() -> (Address, SecretKey, EthClient, PathBuf) { "false" | "0" => { let mut salt = SALT.lock().unwrap(); *salt = H256::random(); - println!("SALT: {salt:?}"); } _ => panic!("Invalid boolean string: {input}"), }; - - (deployer, deployer_private_key, eth_client, contracts_path) + let contract_verifier_address = std::env::var("DEPLOYER_CONTRACT_VERIFIER") + .expect("DEPLOYER_CONTRACT_VERIFIER not set") + .parse() + .expect("Malformed DEPLOYER_CONTRACT_VERIFIER"); + ( + deployer, + deployer_private_key, + contract_verifier_address, + eth_client, + contracts_path, + ) } fn download_contract_deps(contracts_path: &Path) { @@ -177,9 +187,15 @@ async fn deploy_contracts( eth_client: &EthClient, contracts_path: &Path, ) -> (Address, Address) { + let gas_price = if eth_client.url.contains("localhost:8545") { + Some(1_000_000_000) + } else { + Some(eth_client.get_gas_price().await.unwrap().as_u64() * 2) + }; + let overrides = Overrides { gas_limit: Some(GAS_LIMIT_MINIMUM * GAS_LIMIT_ADJUSTMENT_FACTOR), - gas_price: Some(1_000_000_000), + gas_price, ..Default::default() }; @@ -307,6 +323,7 @@ async fn create2_deploy( ) .await .expect("Failed to build create2 deploy tx"); + let deploy_tx_hash = eth_client .send_eip1559_transaction(deploy_tx, &deployer_private_key) .await @@ -341,6 +358,7 @@ async fn initialize_contracts( deployer_private_key: SecretKey, on_chain_proposer: Address, bridge: Address, + contract_verifier_address: Address, eth_client: &EthClient, ) { let initialize_frames = spinner!(["🪄❱❱", "❱🪄❱", "❱❱🪄"], 200); @@ -354,6 +372,7 @@ async fn initialize_contracts( let initialize_tx_hash = initialize_on_chain_proposer( on_chain_proposer, bridge, + contract_verifier_address, deployer, deployer_private_key, eth_client, @@ -388,11 +407,12 @@ async fn initialize_contracts( async fn initialize_on_chain_proposer( on_chain_proposer: Address, bridge: Address, + contract_verifier_address: Address, deployer: Address, deployer_private_key: SecretKey, eth_client: &EthClient, ) -> H256 { - let on_chain_proposer_initialize_selector = keccak(b"initialize(address)") + let on_chain_proposer_initialize_selector = keccak(b"initialize(address,address)") .as_bytes() .get(..4) .expect("Failed to get initialize selector") @@ -404,10 +424,18 @@ async fn initialize_on_chain_proposer( encoded_bridge }; + let encoded_contract_verifier = { + let offset = 32 - contract_verifier_address.as_bytes().len() % 32; + let mut encoded_contract_verifier = vec![0; offset]; + encoded_contract_verifier.extend_from_slice(contract_verifier_address.as_bytes()); + encoded_contract_verifier + }; + let mut on_chain_proposer_initialization_calldata = Vec::new(); on_chain_proposer_initialization_calldata .extend_from_slice(&on_chain_proposer_initialize_selector); on_chain_proposer_initialization_calldata.extend_from_slice(&encoded_bridge); + on_chain_proposer_initialization_calldata.extend_from_slice(&encoded_contract_verifier); let initialize_tx = eth_client .build_eip1559_transaction( diff --git a/crates/l2/contracts/src/l1/OnChainProposer.sol b/crates/l2/contracts/src/l1/OnChainProposer.sol index 0cc1fcfd2..303c558cb 100644 --- a/crates/l2/contracts/src/l1/OnChainProposer.sol +++ b/crates/l2/contracts/src/l1/OnChainProposer.sol @@ -6,6 +6,7 @@ import "../../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol"; import "./interfaces/IOnChainProposer.sol"; import {CommonBridge} from "./CommonBridge.sol"; import {ICommonBridge} from "./interfaces/ICommonBridge.sol"; +import {IRiscZeroVerifier} from "./interfaces/IRiscZeroVerifier.sol"; /// @title OnChainProposer contract. /// @author LambdaClass @@ -28,10 +29,27 @@ contract OnChainProposer is IOnChainProposer, ReentrancyGuard { /// @dev This is crucial for ensuring that only valid and confirmed blocks are processed in the contract. uint256 public lastVerifiedBlock; + /// @notice The latest committed block number. + /// @dev This variable holds the block number of the most recently committed block. + /// @dev All blocks with a block number less than or equal to `lastCommittedBlock` are considered committed. + /// @dev Blocks with a block number greater than `lastCommittedBlock` have not been committed yet. + /// @dev This is crucial for ensuring that only subsequents blocks are committed in the contract. + /// @dev In the initialize function, `lastCommittedBlock` is set to u64::MAX == 0xFFFFFFFFFFFFFFFF, this value is used to allow the block 0 to be committed. + uint256 public lastCommittedBlock; + address public BRIDGE; + address public R0VERIFIER; + + /// @notice Address used to avoid the verification process. + /// @dev If the `R0VERIFIER` contract address is set to this address, the verification process will not happen. + /// @dev Used only in dev mode. + address public constant DEV_MODE = address(0xAA); /// @inheritdoc IOnChainProposer - function initialize(address bridge) public nonReentrant { + function initialize( + address bridge, + address r0verifier + ) public nonReentrant { require( BRIDGE == address(0), "OnChainProposer: contract already initialized" @@ -45,6 +63,22 @@ contract OnChainProposer is IOnChainProposer, ReentrancyGuard { "OnChainProposer: bridge is the contract address" ); BRIDGE = bridge; + + require( + R0VERIFIER == address(0), + "OnChainProposer: contract already initialized" + ); + require( + r0verifier != address(0), + "OnChainProposer: r0verifier is the zero address" + ); + require( + r0verifier != address(this), + "OnChainProposer: r0verifier is the contract address" + ); + R0VERIFIER = r0verifier; + + lastCommittedBlock = 0xFFFFFFFFFFFFFFFF; } /// @inheritdoc IOnChainProposer @@ -55,8 +89,9 @@ contract OnChainProposer is IOnChainProposer, ReentrancyGuard { bytes32 depositLogs ) external override { require( - blockNumber == lastVerifiedBlock + 1, - "OnChainProposer: block already verified" + blockNumber == lastCommittedBlock + 1 || + (blockNumber == 0 && lastCommittedBlock == 0xFFFFFFFFFFFFFFFF), + "OnChainProposer: blockNumber is not the immediate succesor of lastCommittedBlock" ); require( blockCommitments[blockNumber].commitmentHash == bytes32(0), @@ -82,13 +117,16 @@ contract OnChainProposer is IOnChainProposer, ReentrancyGuard { commitment, depositLogs ); + lastCommittedBlock = blockNumber; emit BlockCommitted(commitment); } /// @inheritdoc IOnChainProposer function verify( uint256 blockNumber, - bytes calldata // blockProof + bytes calldata blockProof, + bytes32 imageId, + bytes32 journalDigest ) external override { require( blockCommitments[blockNumber].commitmentHash != bytes32(0), @@ -99,6 +137,15 @@ contract OnChainProposer is IOnChainProposer, ReentrancyGuard { "OnChainProposer: block already verified" ); + if (R0VERIFIER != DEV_MODE) { + // If the verification fails, it will revert. + IRiscZeroVerifier(R0VERIFIER).verify( + blockProof, + imageId, + journalDigest + ); + } + lastVerifiedBlock = blockNumber; ICommonBridge(BRIDGE).removeDepositLogs( // The first 2 bytes are the number of deposits. diff --git a/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol b/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol index b60a15da3..01013ec98 100644 --- a/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol +++ b/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol @@ -21,7 +21,8 @@ interface IOnChainProposer { /// @dev This method is called only once after the contract is deployed. /// @dev It sets the bridge address. /// @param bridge the address of the bridge contract. - function initialize(address bridge) external; + /// @param r0verifier the address of the risc0 groth16 verifier. + function initialize(address bridge, address r0verifier) external; /// @notice Commits to an L2 block. /// @dev Committing to an L2 block means to store the block's commitment @@ -43,5 +44,12 @@ interface IOnChainProposer { /// verified (this is after proved). /// @param blockNumber is the number of the block to be verified. /// @param blockProof is the proof of the block to be verified. - function verify(uint256 blockNumber, bytes calldata blockProof) external; + /// @param imageId Digest of the zkVM imageid. + /// @param journalDigest Digest of the public_inputs aka journal + function verify( + uint256 blockNumber, + bytes calldata blockProof, + bytes32 imageId, + bytes32 journalDigest + ) external; } diff --git a/crates/l2/contracts/src/l1/interfaces/IRiscZeroVerifier.sol b/crates/l2/contracts/src/l1/interfaces/IRiscZeroVerifier.sol new file mode 100644 index 000000000..2d92013a6 --- /dev/null +++ b/crates/l2/contracts/src/l1/interfaces/IRiscZeroVerifier.sol @@ -0,0 +1,49 @@ +// Copyright 2024 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +// +// NOTICE: +// Modified from the original file. +// Making use of the IRiscZeroVerifier interface and nothing else. + +pragma solidity ^0.8.9; + +/// @notice A receipt attesting to the execution of a guest program. +/// @dev A receipt contains two parts: a seal and a claim. The seal is a zero-knowledge proof +/// attesting to knowledge of a zkVM execution resulting in the claim. The claim is a set of public +/// outputs for the execution. Crucially, the claim includes the journal and the image ID. The +/// image ID identifies the program that was executed, and the journal is the public data written +/// by the program. Note that this struct only contains the claim digest, as can be obtained with +/// the `digest()` function on `ReceiptClaimLib`. +struct Receipt { + bytes seal; + bytes32 claimDigest; +} + +/// @notice Error raised when cryptographic verification of the zero-knowledge proof fails. +error VerificationFailed(); + +/// @notice Verifier interface for RISC Zero receipts of execution. +interface IRiscZeroVerifier { + /// @notice Verify that the given seal is a valid RISC Zero proof of execution with the + /// given image ID and journal digest. Reverts on failure. + /// @dev This method additionally ensures that the input hash is all-zeros (i.e. no + /// committed input), the exit code is (Halted, 0), and there are no assumptions (i.e. the + /// receipt is unconditional). + /// @param seal The encoded cryptographic proof (i.e. SNARK). + /// @param imageId The identifier for the guest program. + /// @param journalDigest The SHA-256 digest of the journal bytes. + function verify(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) external view; +} diff --git a/crates/l2/docs/prover.md b/crates/l2/docs/prover.md index df3c45060..9726c88a3 100644 --- a/crates/l2/docs/prover.md +++ b/crates/l2/docs/prover.md @@ -8,12 +8,14 @@ - [How](#how) - [Dev Mode](#dev-mode) - [Quick Test](#quick-test) + - [Run the whole system with the prover](#run-the-whole-system-with-the-prover) - [GPU mode](#gpu-mode) - [Proving Process Test](#proving-process-test) + - [Run the whole system with the prover in Sepolia](#run-the-whole-system-with-the-prover-in-sepolia) - [Configuration](#configuration) >[!NOTE] -> The shipping/deploying process and the `Prover` itself is under development. +> The shipping/deploying process and the `Prover` itself are under development. ## What @@ -28,13 +30,13 @@ The `Prover Server` monitors requests for new jobs from the `Prover Client`, whi ```mermaid sequenceDiagram - participant Prover + participant zkVM participant ProverClient participant ProverServer ProverClient->>+ProverServer: ProofData::Request ProverServer-->>-ProverClient: ProofData::Response(block_number, ProverInputs) - ProverClient->>+Prover: Prove(block_number, ProverInputs) - Prover-->>-ProverClient: Creates zkProof + ProverClient->>+zkVM: Prove(ProverInputs) + zkVM-->>-ProverClient: Creates zkProof ProverClient->>+ProverServer: ProofData::Submit(block_number, zkProof) ProverServer-->>-ProverClient: ProofData::SubmitAck(block_number) ``` @@ -65,26 +67,38 @@ cd crates/l2/prover make perf_test_proving ``` -### GPU mode +#### Run the whole system with the prover -**Dependencies (based on the Docker CUDA image):** +1. `cd crates/l2` +2. `make rm_dev_libmdbx_l2 && make down` + - It will remove any old database, if present, stored in your computer. The absolute path of libmdbx is defined by [data_dir](https://docs.rs/dirs/latest/dirs/fn.data_dir.html). +3. `cp .env.example .env` → check if you want to change any config. +4. `make init` + - Init the L1 in a docker container on port `8545`. + - Deploy the needed contracts for the L2 on the L1. + - Start the L2 locally on port `1729`. +5. In a new terminal → `make init-l2-prover`. ->[!NOTE] -> If you don't want to run it inside a Docker container based on the NVIDIA CUDA image, [the following steps from RISC0](https://dev.risczero.com/api/generating-proofs/local-proving) may be helpful. +After this initialization the system has to be running in `dev-mode` → No proof verification. -- [Rust](https://www.rust-lang.org/tools/install) -- [RISC0](https://dev.risczero.com/api/zkvm/install) +### GPU mode -Next, install the following packages: +**Steps for Ubuntu 22.04 with Nvidia A4000:** -```sh -sudo apt-get install libssl-dev pkg-config libclang-dev clang -``` - -To start the `prover_client`, use the following command: +1. Install `docker` → using the [Ubuntu apt repository](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) + - Add the `user` you are using to the `docker` group → command: `sudo usermod -aG docker $USER`. (needs reboot, doing it after CUDA installation) + - `id -nG` after reboot to check if the user is in the group. +2. Install [Rust](https://www.rust-lang.org/tools/install) +3. Install [RISC0](https://dev.risczero.com/api/zkvm/install) +4. Install [CUDA for Ubuntu](https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=22.04&target_type=deb_local) + - Install `CUDA Toolkit Installer` first. Then the `nvidia-open` drivers. +5. Reboot +6. Run the following commands: ```sh -make init-l2-prover-gpu +sudo apt-get install libssl-dev pkg-config libclang-dev clang +echo 'export PATH=/usr/local/cuda/bin:$PATH' >> ~/.bashrc +echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc ``` #### Proving Process Test @@ -96,6 +110,40 @@ cd crates/l2/prover make perf_gpu ``` +#### Run the whole system with the prover in Sepolia + +Two servers are required: one for the `prover` and another for the `proposer`. If you run both components on the same machine, the `prover` may consume all available resources, leading to potential stuttering or performance issues for the `proposer`/`node`. + +1. `prover`/`zkvm` → prover with gpu, make sure to have all the required dependencies described at the beginning of [Gpu Mode](#gpu-mode) section. + 1. `cd lambda_ethereum_rust/crates/l2` + 2. `cp .example.env` and change the `PROVER_CLIENT_PROVER_SERVER_ENDPOINT` with the ip of the other server. + +The env variables needed are: + +```sh +PROVER_CLIENT_PROVER_SERVER_ENDPOINT=:3000 +RISC0_DEV_MODE=0 +``` + +Finally, to start the `prover_client`/`zkvm`, run: + +- `make init-l2-prover-gpu` + +2.  `proposer` → this server just needs rust installed. + 1. `cd lambda_ethereum_rust/crates/l2` + 2. `cp .example.env` and change the addresses and the following fields: + - `PROVER_SERVER_LISTEN_IP=0.0.0.0` → used to handle the tcp communication with the other server. + - The `COMMITTER` and `PROVER_SERVER_VERIFIER` must be different accounts, the `DEPLOYER_ADDRESS` as well as the `L1_WATCHER` may be the same account used by the `COMMITTER` + - `DEPLOYER_CONTRACT_VERIFIER=0xd9b0d07CeCd808a8172F21fA7C97992168f045CA` → risc0’s verifier contract deployed on Sepolia. + - Set the `ETH_RPC_URL` to any Sepolia's endpoint. + +>[!NOTE] +> Make sure to have funds, if you want to perform a quick test `0.2[ether]` on each account should be enough. + +Finally, to start the `proposer`/`l2 node`, run: + - `make rm_dev_libmdbx_l2 && make down` + - `make init` + ## Configuration The following environment variables are available to configure the prover: @@ -103,3 +151,8 @@ The following environment variables are available to configure the prover: - `PROVER_SERVER_LISTEN_IP`: IP used to start the Server. - `PROVER_SERVER_LISTEN_PORT`: Port used to start the Server. - `PROVER_CLIENT_PROVER_SERVER_ENDPOINT`: Prover Server's Endpoint used to connect the Client to the Server. +- `PROVER_SERVER_VERIFIER_ADDRESS`: The address of the account that sends the zkProofs on-chain and interacts with the `OnChainProposer` `verify()` function. +- `PROVER_SERVER_VERIFIER_PRIVATE_KEY`: The private key of the account that sends the zkProofs on-chain and interacts with the `OnChainProposer` `verify()` function. + +>[!NOTE] +> The `PROVER_SERVER_VERIFIER` account must differ from the `COMMITTER_L1` account. diff --git a/crates/l2/proposer/errors.rs b/crates/l2/proposer/errors.rs index 2ff47bc4b..df37f122c 100644 --- a/crates/l2/proposer/errors.rs +++ b/crates/l2/proposer/errors.rs @@ -1,6 +1,8 @@ use crate::utils::{config::errors::ConfigError, eth_client::errors::EthClientError}; use ethereum_rust_dev::utils::engine_client::errors::EngineClientError; +use ethereum_rust_storage::error::StoreError; use ethereum_rust_vm::EvmError; +use ethereum_types::FromStrRadixErr; #[derive(Debug, thiserror::Error)] pub enum L1WatcherError { @@ -22,28 +24,42 @@ pub enum L1WatcherError { pub enum ProverServerError { #[error("ProverServer connection failed: {0}")] ConnectionError(#[from] std::io::Error), + #[error("ProverServer failed because of an EthClient error: {0}")] + EthClientError(#[from] EthClientError), + #[error("ProverServer failed to send transaction: {0}")] + FailedToVerifyProofOnChain(String), } #[derive(Debug, thiserror::Error)] pub enum ProposerError { - #[error("Proposer failed because of an EthClient error: {0}")] - EthClientError(#[from] EthClientError), #[error("Proposer failed because of an EngineClient error: {0}")] EngineClientError(#[from] EngineClientError), #[error("Proposer failed to produce block: {0}")] FailedToProduceBlock(String), #[error("Proposer failed to prepare PayloadAttributes timestamp: {0}")] FailedToGetSystemTime(#[from] std::time::SystemTimeError), - #[error("Proposer failed to serialize block: {0}")] - FailedToRetrieveBlockFromStorage(String), - #[error("Proposer failed to encode state diff: {0}")] +} + +#[derive(Debug, thiserror::Error)] +pub enum CommitterError { + #[error("Committer failed because of an EthClient error: {0}")] + EthClientError(#[from] EthClientError), + #[error("Committer failed to {0}")] + FailedToParseLastCommittedBlock(#[from] FromStrRadixErr), + #[error("Committer failed retrieve block from storage: {0}")] + FailedToRetrieveBlockFromStorage(#[from] StoreError), + #[error("Committer failed to get information from storage")] + FailedToGetInformationFromStorage(String), + #[error("Committer failed to encode state diff: {0}")] FailedToEncodeStateDiff(#[from] StateDiffError), - #[error("Proposer failed to open Points file: {0}")] + #[error("Committer failed to open Points file: {0}")] FailedToOpenPointsFile(#[from] std::io::Error), - #[error("Proposer failed to re-execute block: {0}")] + #[error("Committer failed to re-execute block: {0}")] FailedToReExecuteBlock(#[from] EvmError), - #[error("Proposer failed to make KZG operations: {0}")] + #[error("Committer failed to make KZG operations: {0}")] KZGError(#[from] c_kzg::Error), + #[error("Committer failed to send transaction: {0}")] + FailedToSendCommitment(String), } #[derive(Debug, thiserror::Error)] diff --git a/crates/l2/proposer/l1_committer.rs b/crates/l2/proposer/l1_committer.rs new file mode 100644 index 000000000..c803d90c4 --- /dev/null +++ b/crates/l2/proposer/l1_committer.rs @@ -0,0 +1,503 @@ +use crate::{ + proposer::{ + errors::CommitterError, + state_diff::{AccountStateDiff, DepositLog, StateDiff, WithdrawalLog}, + }, + utils::{ + config::{committer::CommitterConfig, eth::EthConfig}, + eth_client::{ + errors::EthClientError, eth_sender::Overrides, transaction::blob_from_bytes, EthClient, + }, + merkle_tree::merkelize, + }, +}; +use bytes::Bytes; +use c_kzg::{Bytes48, KzgSettings}; +use ethereum_rust_blockchain::constants::TX_GAS_COST; +use ethereum_rust_core::{ + types::{ + BlobsBundle, Block, EIP1559Transaction, GenericTransaction, PrivilegedL2Transaction, + PrivilegedTxType, Transaction, TxKind, BYTES_PER_BLOB, + }, + Address, H256, U256, +}; +use ethereum_rust_rpc::types::transaction::WrappedEIP4844Transaction; +use ethereum_rust_storage::Store; +use ethereum_rust_vm::{evm_state, execute_block, get_state_transitions}; +use keccak_hash::keccak; +use secp256k1::SecretKey; +use sha2::{Digest, Sha256}; +use std::ops::Div; +use std::{collections::HashMap, time::Duration}; +use tokio::time::sleep; +use tracing::{error, info, warn}; + +const COMMIT_FUNCTION_SELECTOR: [u8; 4] = [132, 97, 12, 179]; + +pub struct Committer { + eth_client: EthClient, + on_chain_proposer_address: Address, + store: Store, + l1_address: Address, + l1_private_key: SecretKey, + interval_ms: u64, + kzg_settings: &'static KzgSettings, +} + +pub async fn start_l1_commiter(store: Store) { + let eth_config = EthConfig::from_env().expect("EthConfig::from_env()"); + let committer_config = CommitterConfig::from_env().expect("CommitterConfig::from_env"); + let committer = Committer::new_from_config(&committer_config, eth_config, store); + committer.start().await.expect("committer.start()"); +} + +impl Committer { + pub fn new_from_config( + committer_config: &CommitterConfig, + eth_config: EthConfig, + store: Store, + ) -> Self { + Self { + eth_client: EthClient::new(ð_config.rpc_url), + on_chain_proposer_address: committer_config.on_chain_proposer_address, + store, + l1_address: committer_config.l1_address, + l1_private_key: committer_config.l1_private_key, + interval_ms: committer_config.interval_ms, + kzg_settings: c_kzg::ethereum_kzg_settings(), + } + } + + pub async fn start(&self) -> Result<(), CommitterError> { + loop { + let last_committed_block = get_last_committed_block( + &self.eth_client, + self.on_chain_proposer_address, + Overrides::default(), + ) + .await?; + + let last_committed_block = last_committed_block + .strip_prefix("0x") + .expect("Couldn't strip prefix from last_committed_block."); + + if last_committed_block.is_empty() { + error!("Failed to fetch last_committed_block"); + panic!("Failed to fetch last_committed_block. Manual intervention required"); + } + + let last_committed_block = U256::from_str_radix(last_committed_block, 16) + .map_err(CommitterError::from)? + .as_u64(); + + let block_number_to_fetch = if last_committed_block == u64::MAX { + 0 + } else { + last_committed_block + 1 + }; + + if let Some(block_to_commit_body) = self + .store + .get_block_body(block_number_to_fetch) + .map_err(CommitterError::from)? + { + let block_to_commit_header = self + .store + .get_block_header(block_number_to_fetch) + .map_err(CommitterError::from)? + .ok_or(CommitterError::FailedToGetInformationFromStorage( + "Failed to get_block_header() after get_block_body()".to_owned(), + ))?; + + let block_to_commit = Block::new(block_to_commit_header, block_to_commit_body); + + let withdrawals = self.get_block_withdrawals(&block_to_commit)?; + let deposits = self.get_block_deposits(&block_to_commit)?; + + let withdrawal_logs_merkle_root = self.get_withdrawals_merkle_root( + withdrawals.iter().map(|(hash, _tx)| *hash).collect(), + ); + let deposit_logs_hash = self.get_deposit_hash( + deposits + .iter() + .filter_map(|tx| tx.get_deposit_hash()) + .collect(), + ); + + let state_diff = self.prepare_state_diff( + &block_to_commit, + self.store.clone(), + withdrawals, + deposits, + )?; + + let (blob_commitment, blob_proof) = + self.prepare_blob_commitment(state_diff.clone())?; + + let head_block_hash = block_to_commit.hash(); + match self + .send_commitment( + block_to_commit.header.number, + withdrawal_logs_merkle_root, + deposit_logs_hash, + blob_commitment, + blob_proof, + state_diff.encode()?, + ) + .await + { + Ok(commit_tx_hash) => { + info!( + "Sent commitment to block {head_block_hash:#x}, with transaction hash {commit_tx_hash:#x}" + ); + } + Err(error) => { + error!("Failed to send commitment to block {head_block_hash:#x}. Manual intervention required: {error}"); + panic!("Failed to send commitment to block {head_block_hash:#x}. Manual intervention required: {error}"); + } + } + } + + sleep(Duration::from_millis(self.interval_ms)).await; + } + } + + pub fn get_block_withdrawals( + &self, + block: &Block, + ) -> Result, CommitterError> { + let withdrawals = block + .body + .transactions + .iter() + .filter_map(|tx| match tx { + Transaction::PrivilegedL2Transaction(priv_tx) + if priv_tx.tx_type == PrivilegedTxType::Withdrawal => + { + Some((tx.compute_hash(), priv_tx.clone())) + } + _ => None, + }) + .collect(); + + Ok(withdrawals) + } + + pub fn get_withdrawals_merkle_root(&self, withdrawals_hashes: Vec) -> H256 { + if !withdrawals_hashes.is_empty() { + merkelize(withdrawals_hashes) + } else { + H256::zero() + } + } + + pub fn get_block_deposits( + &self, + block: &Block, + ) -> Result, CommitterError> { + let deposits = block + .body + .transactions + .iter() + .filter_map(|tx| match tx { + Transaction::PrivilegedL2Transaction(tx) + if tx.tx_type == PrivilegedTxType::Deposit => + { + Some(tx.clone()) + } + _ => None, + }) + .collect(); + + Ok(deposits) + } + + pub fn get_deposit_hash(&self, deposit_hashes: Vec) -> H256 { + if !deposit_hashes.is_empty() { + H256::from_slice( + [ + &(deposit_hashes.len() as u16).to_be_bytes(), + &keccak( + deposit_hashes + .iter() + .map(H256::as_bytes) + .collect::>() + .concat(), + ) + .as_bytes()[2..32], + ] + .concat() + .as_slice(), + ) + } else { + H256::zero() + } + } + /// Prepare the state diff for the block. + pub fn prepare_state_diff( + &self, + block: &Block, + store: Store, + withdrawals: Vec<(H256, PrivilegedL2Transaction)>, + deposits: Vec, + ) -> Result { + info!("Preparing state diff for block {}", block.header.number); + + let mut state = evm_state(store.clone(), block.header.parent_hash); + execute_block(block, &mut state).map_err(CommitterError::from)?; + let account_updates = get_state_transitions(&mut state); + + let mut modified_accounts = HashMap::new(); + account_updates.iter().for_each(|account_update| { + modified_accounts.insert( + account_update.address, + AccountStateDiff { + new_balance: account_update.info.clone().map(|info| info.balance), + nonce_diff: account_update.info.clone().map(|info| info.nonce as u16), + storage: account_update.added_storage.clone().into_iter().collect(), + bytecode: account_update.code.clone(), + bytecode_hash: None, + }, + ); + }); + + let state_diff = StateDiff { + modified_accounts, + version: StateDiff::default().version, + withdrawal_logs: withdrawals + .iter() + .map(|(hash, tx)| WithdrawalLog { + address: match tx.to { + TxKind::Call(address) => address, + TxKind::Create => Address::zero(), + }, + amount: tx.value, + tx_hash: *hash, + }) + .collect(), + deposit_logs: deposits + .iter() + .map(|tx| DepositLog { + address: match tx.to { + TxKind::Call(address) => address, + TxKind::Create => Address::zero(), + }, + amount: tx.value, + }) + .collect(), + }; + + Ok(state_diff) + } + + /// Generate the KZG commitment and proof for the blob. This commitment can then be used + /// to calculate the blob versioned hash, necessary for the EIP-4844 transaction. + pub fn prepare_blob_commitment( + &self, + state_diff: StateDiff, + ) -> Result<([u8; 48], [u8; 48]), CommitterError> { + let blob_data = state_diff.encode().map_err(CommitterError::from)?; + + let blob = blob_from_bytes(blob_data).map_err(CommitterError::from)?; + + let commitment = c_kzg::KzgCommitment::blob_to_kzg_commitment(&blob, self.kzg_settings) + .map_err(CommitterError::from)?; + let commitment_bytes = + Bytes48::from_bytes(commitment.as_slice()).map_err(CommitterError::from)?; + let proof = + c_kzg::KzgProof::compute_blob_kzg_proof(&blob, &commitment_bytes, self.kzg_settings) + .map_err(CommitterError::from)?; + + let mut commitment_bytes = [0u8; 48]; + commitment_bytes.copy_from_slice(commitment.as_slice()); + let mut proof_bytes = [0u8; 48]; + proof_bytes.copy_from_slice(proof.as_slice()); + + Ok((commitment_bytes, proof_bytes)) + } + + pub async fn send_commitment( + &self, + block_number: u64, + withdrawal_logs_merkle_root: H256, + deposit_logs_hash: H256, + commitment: [u8; 48], + proof: [u8; 48], + blob_data: Bytes, + ) -> Result { + info!("Sending commitment for block {block_number}"); + + let mut hasher = Sha256::new(); + hasher.update(commitment); + let mut blob_versioned_hash = hasher.finalize(); + blob_versioned_hash[0] = 0x01; // EIP-4844 versioning + + let mut calldata = Vec::with_capacity(132); + calldata.extend(COMMIT_FUNCTION_SELECTOR); + let mut block_number_bytes = [0_u8; 32]; + U256::from(block_number).to_big_endian(&mut block_number_bytes); + calldata.extend(block_number_bytes); + calldata.extend(blob_versioned_hash); + calldata.extend(withdrawal_logs_merkle_root.0); + calldata.extend(deposit_logs_hash.0); + + let mut buf = [0u8; BYTES_PER_BLOB]; + buf.copy_from_slice( + blob_from_bytes(blob_data) + .map_err(CommitterError::from)? + .iter() + .as_slice(), + ); + + let blobs_bundle = BlobsBundle { + blobs: vec![buf], + commitments: vec![commitment], + proofs: vec![proof], + }; + let wrapped_tx = self + .eth_client + .build_eip4844_transaction( + self.on_chain_proposer_address, + Bytes::from(calldata), + Overrides { + from: Some(self.l1_address), + gas_price_per_blob: Some(U256::from_dec_str("100000000000").unwrap()), + ..Default::default() + }, + blobs_bundle, + ) + .await + .map_err(CommitterError::from)?; + + let commit_tx_hash = self + .eth_client + .send_eip4844_transaction(wrapped_tx.clone(), &self.l1_private_key) + .await + .map_err(CommitterError::from)?; + + let commit_tx_hash = wrapped_eip4844_transaction_handler( + &self.eth_client, + &wrapped_tx, + &self.l1_private_key, + commit_tx_hash, + 10, + ) + .await?; + + info!("Commitment sent: {commit_tx_hash:#x}"); + + Ok(commit_tx_hash) + } +} + +pub async fn send_transaction_with_calldata( + eth_client: &EthClient, + l1_address: Address, + l1_private_key: SecretKey, + to: Address, + nonce: Option, + calldata: Bytes, +) -> Result { + let mut tx = EIP1559Transaction { + to: TxKind::Call(to), + data: calldata, + max_fee_per_gas: eth_client.get_gas_price().await?.as_u64() * 2, + nonce: nonce.unwrap_or(eth_client.get_nonce(l1_address).await?), + chain_id: eth_client.get_chain_id().await?.as_u64(), + // Should the max_priority_fee_per_gas be dynamic? + max_priority_fee_per_gas: 10u64, + ..Default::default() + }; + + let mut generic_tx = GenericTransaction::from(tx.clone()); + generic_tx.from = l1_address; + + tx.gas_limit = eth_client + .estimate_gas(generic_tx) + .await? + .saturating_add(TX_GAS_COST); + + eth_client + .send_eip1559_transaction(tx, &l1_private_key) + .await +} + +async fn get_last_committed_block( + eth_client: &EthClient, + contract_address: Address, + overrides: Overrides, +) -> Result { + let selector = keccak(b"lastCommittedBlock()") + .as_bytes() + .get(..4) + .expect("Failed to get initialize selector") + .to_vec(); + + let mut calldata = Vec::new(); + calldata.extend_from_slice(&selector); + + let leading_zeros = 32 - ((calldata.len() - 4) % 32); + calldata.extend(vec![0; leading_zeros]); + + eth_client + .call(contract_address, calldata.into(), overrides) + .await +} + +async fn wrapped_eip4844_transaction_handler( + eth_client: &EthClient, + wrapped_eip4844: &WrappedEIP4844Transaction, + l1_private_key: &SecretKey, + commit_tx_hash: H256, + max_retries: u32, +) -> Result { + let mut retries = 0; + let max_receipt_retries: u32 = 60 * 2; // 2 minutes + let mut commit_tx_hash = commit_tx_hash; + let mut wrapped_tx = wrapped_eip4844.clone(); + + while retries < max_retries { + if (eth_client.get_transaction_receipt(commit_tx_hash).await?).is_some() { + // If the tx_receipt was found, return the tx_hash. + return Ok(commit_tx_hash); + } else { + // Else, wait for receipt and send again if necessary. + let mut receipt_retries = 0; + + // Try for 2 minutes with an interval of 1 second to get the tx_receipt. + while receipt_retries < max_receipt_retries { + match eth_client.get_transaction_receipt(commit_tx_hash).await? { + Some(_) => return Ok(commit_tx_hash), + None => { + receipt_retries += 1; + sleep(Duration::from_secs(1)).await; + } + } + } + + // If receipt was not found, send the same tx(same nonce) but with more gas. + // Sometimes the penalty is a 100% + warn!("Transaction not confirmed, resending with 110% more gas..."); + // Increase max fee per gas by 110% (set it to 210% of the original) + wrapped_tx.tx.max_fee_per_gas = + (wrapped_tx.tx.max_fee_per_gas as f64 * 2.1).round() as u64; + wrapped_tx.tx.max_priority_fee_per_gas = + (wrapped_tx.tx.max_priority_fee_per_gas as f64 * 2.1).round() as u64; + wrapped_tx.tx.max_fee_per_blob_gas = wrapped_tx + .tx + .max_fee_per_blob_gas + .saturating_mul(U256::from(20)) + .div(10); + + commit_tx_hash = eth_client + .send_eip4844_transaction(wrapped_tx.clone(), l1_private_key) + .await + .map_err(CommitterError::from)?; + + retries += 1; + } + } + Err(CommitterError::FailedToSendCommitment( + "Error handling eip4844".to_owned(), + )) +} diff --git a/crates/l2/proposer/mod.rs b/crates/l2/proposer/mod.rs index 056539aa9..ec5d1aa22 100644 --- a/crates/l2/proposer/mod.rs +++ b/crates/l2/proposer/mod.rs @@ -1,48 +1,23 @@ -use crate::utils::{ - config::{eth::EthConfig, proposer::ProposerConfig, read_env_file}, - eth_client::{eth_sender::Overrides, transaction::blob_from_bytes, EthClient}, - merkle_tree::merkelize, -}; -use bytes::Bytes; -use c_kzg::{Bytes48, KzgSettings}; +use crate::utils::config::{proposer::ProposerConfig, read_env_file}; use errors::ProposerError; -use ethereum_rust_core::types::{ - BlobsBundle, Block, PrivilegedL2Transaction, PrivilegedTxType, Transaction, TxKind, - BYTES_PER_BLOB, -}; use ethereum_rust_dev::utils::engine_client::{config::EngineApiConfig, EngineClient}; use ethereum_rust_rpc::types::fork_choice::{ForkChoiceState, PayloadAttributesV3}; use ethereum_rust_storage::Store; -use ethereum_rust_vm::{evm_state, execute_block, get_state_transitions}; -use ethereum_types::{Address, H256, U256}; -use keccak_hash::keccak; -use secp256k1::SecretKey; -use sha2::{Digest, Sha256}; -use state_diff::{AccountStateDiff, DepositLog, StateDiff, WithdrawalLog}; -use std::{ - collections::HashMap, - time::{Duration, SystemTime, UNIX_EPOCH}, -}; +use ethereum_types::{Address, H256}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::time::sleep; use tracing::{error, info, warn}; +pub mod l1_committer; pub mod l1_watcher; pub mod prover_server; pub mod state_diff; pub mod errors; -const COMMIT_FUNCTION_SELECTOR: [u8; 4] = [132, 97, 12, 179]; -const VERIFY_FUNCTION_SELECTOR: [u8; 4] = [133, 133, 44, 228]; - pub struct Proposer { - eth_client: EthClient, engine_client: EngineClient, - on_chain_proposer_address: Address, - l1_address: Address, - l1_private_key: SecretKey, block_production_interval: Duration, - kzg_settings: &'static KzgSettings, } pub async fn start_proposer(store: Store) { @@ -53,12 +28,12 @@ pub async fn start_proposer(store: Store) { } let l1_watcher = tokio::spawn(l1_watcher::start_l1_watcher(store.clone())); + let l1_committer = tokio::spawn(l1_committer::start_l1_commiter(store.clone())); let prover_server = tokio::spawn(prover_server::start_prover_server(store.clone())); let proposer = tokio::spawn(async move { - let eth_config = EthConfig::from_env().expect("EthConfig::from_env"); let proposer_config = ProposerConfig::from_env().expect("ProposerConfig::from_env"); let engine_config = EngineApiConfig::from_env().expect("EngineApiConfig::from_env"); - let proposer = Proposer::new_from_config(&proposer_config, eth_config, engine_config) + let proposer = Proposer::new_from_config(&proposer_config, engine_config) .expect("Proposer::new_from_config"); let head_block_hash = { let current_block_number = store @@ -71,31 +46,25 @@ pub async fn start_proposer(store: Store) { .expect("store.get_canonical_block_hash returned None") }; proposer - .start(head_block_hash, store) + .start(head_block_hash) .await .expect("Proposer::start"); }); - tokio::try_join!(l1_watcher, prover_server, proposer).expect("tokio::try_join"); + tokio::try_join!(l1_watcher, l1_committer, prover_server, proposer).expect("tokio::try_join"); } impl Proposer { pub fn new_from_config( proposer_config: &ProposerConfig, - eth_config: EthConfig, engine_config: EngineApiConfig, ) -> Result { Ok(Self { - eth_client: EthClient::new(ð_config.rpc_url), engine_client: EngineClient::new_from_config(engine_config)?, - on_chain_proposer_address: proposer_config.on_chain_proposer_address, - l1_address: proposer_config.l1_address, - l1_private_key: proposer_config.l1_private_key, block_production_interval: Duration::from_millis(proposer_config.interval_ms), - kzg_settings: c_kzg::ethereum_kzg_settings(), }) } - pub async fn start(&self, head_block_hash: H256, store: Store) -> Result<(), ProposerError> { + pub async fn start(&self, head_block_hash: H256) -> Result<(), ProposerError> { let mut head_block_hash = head_block_hash; loop { head_block_hash = self.produce_block(head_block_hash).await?; @@ -106,72 +75,6 @@ impl Proposer { continue; } - let block = store - .get_block_by_hash(head_block_hash) - .map_err(|error| { - ProposerError::FailedToRetrieveBlockFromStorage(error.to_string()) - })? - .ok_or(ProposerError::FailedToProduceBlock( - "Failed to get block by hash from storage".to_string(), - ))?; - - let withdrawals = self.get_block_withdrawals(&block)?; - let deposits = self.get_block_deposits(&block)?; - - let withdrawal_logs_merkle_root = self.get_withdrawals_merkle_root( - withdrawals - .iter() - .map(|(_hash, tx)| tx.get_withdrawal_hash().unwrap()) - .collect(), - ); - let deposit_logs_hash = self.get_deposit_hash( - deposits - .iter() - .filter_map(|tx| tx.get_deposit_hash()) - .collect(), - ); - - let state_diff = - self.prepare_state_diff(&block, store.clone(), withdrawals, deposits)?; - - let (blob_commitment, blob_proof) = self.prepare_blob_commitment(state_diff.clone())?; - - match self - .send_commitment( - block.header.number, - withdrawal_logs_merkle_root, - deposit_logs_hash, - blob_commitment, - blob_proof, - state_diff.encode()?, - ) - .await - { - Ok(commit_tx_hash) => { - info!( - "Sent commitment to block {head_block_hash:#x}, with transaction hash {commit_tx_hash:#x}" - ); - } - Err(error) => { - error!("Failed to send commitment to block {head_block_hash:#x}. Manual intervention required: {error}"); - panic!("Failed to send commitment to block {head_block_hash:#x}. Manual intervention required: {error}"); - } - } - - let proof = Vec::new(); - - match self.send_proof(block.header.number, &proof).await { - Ok(verify_tx_hash) => { - info!( - "Sent proof for block {head_block_hash}, with transaction hash {verify_tx_hash:#x}" - ); - } - Err(error) => { - error!("Failed to send proof to block {head_block_hash:#x}. Manual intervention required: {error}"); - panic!("Failed to send proof to block {head_block_hash:#x}. Manual intervention required: {error}"); - } - } - sleep(self.block_production_interval).await; } } @@ -247,279 +150,4 @@ impl Proposer { info!("Produced block {produced_block_hash:#x}"); Ok(produced_block_hash) } - - pub fn get_block_withdrawals( - &self, - block: &Block, - ) -> Result, ProposerError> { - let withdrawals = block - .body - .transactions - .iter() - .filter_map(|tx| match tx { - Transaction::PrivilegedL2Transaction(priv_tx) - if priv_tx.tx_type == PrivilegedTxType::Withdrawal => - { - Some((tx.compute_hash(), priv_tx.clone())) - } - _ => None, - }) - .collect(); - - Ok(withdrawals) - } - - pub fn get_withdrawals_merkle_root(&self, withdrawals_hashes: Vec) -> H256 { - if !withdrawals_hashes.is_empty() { - merkelize(withdrawals_hashes) - } else { - H256::zero() - } - } - - pub fn get_block_deposits( - &self, - block: &Block, - ) -> Result, ProposerError> { - let deposits = block - .body - .transactions - .iter() - .filter_map(|tx| match tx { - Transaction::PrivilegedL2Transaction(tx) - if tx.tx_type == PrivilegedTxType::Deposit => - { - Some(tx.clone()) - } - _ => None, - }) - .collect(); - - Ok(deposits) - } - - pub fn get_deposit_hash(&self, deposit_hashes: Vec) -> H256 { - if !deposit_hashes.is_empty() { - H256::from_slice( - [ - &(deposit_hashes.len() as u16).to_be_bytes(), - &keccak( - deposit_hashes - .iter() - .map(H256::as_bytes) - .collect::>() - .concat(), - ) - .as_bytes()[2..32], - ] - .concat() - .as_slice(), - ) - } else { - H256::zero() - } - } - - /// Prepare the state diff for the block. - pub fn prepare_state_diff( - &self, - block: &Block, - store: Store, - withdrawals: Vec<(H256, PrivilegedL2Transaction)>, - deposits: Vec, - ) -> Result { - info!("Preparing state diff for block {}", block.header.number); - - let mut state = evm_state(store.clone(), block.header.parent_hash); - execute_block(block, &mut state).map_err(ProposerError::from)?; - let account_updates = get_state_transitions(&mut state); - - let mut modified_accounts = HashMap::new(); - account_updates.iter().for_each(|account_update| { - modified_accounts.insert( - account_update.address, - AccountStateDiff { - new_balance: account_update.info.clone().map(|info| info.balance), - nonce_diff: account_update.info.clone().map(|info| info.nonce as u16), - storage: account_update.added_storage.clone().into_iter().collect(), - bytecode: account_update.code.clone(), - bytecode_hash: None, - }, - ); - }); - - let state_diff = StateDiff { - modified_accounts, - version: StateDiff::default().version, - withdrawal_logs: withdrawals - .iter() - .map(|(hash, tx)| WithdrawalLog { - address: match tx.to { - TxKind::Call(address) => address, - TxKind::Create => Address::zero(), - }, - amount: tx.value, - tx_hash: *hash, - }) - .collect(), - deposit_logs: deposits - .iter() - .map(|tx| DepositLog { - address: match tx.to { - TxKind::Call(address) => address, - TxKind::Create => Address::zero(), - }, - amount: tx.value, - }) - .collect(), - }; - - Ok(state_diff) - } - - /// Generate the KZG commitment and proof for the blob. This commitment can then be used - /// to calculate the blob versioned hash, necessary for the EIP-4844 transaction. - pub fn prepare_blob_commitment( - &self, - state_diff: StateDiff, - ) -> Result<([u8; 48], [u8; 48]), ProposerError> { - let blob_data = state_diff.encode().map_err(ProposerError::from)?; - - let blob = blob_from_bytes(blob_data).map_err(ProposerError::from)?; - - let commitment = c_kzg::KzgCommitment::blob_to_kzg_commitment(&blob, self.kzg_settings) - .map_err(ProposerError::from)?; - let commitment_bytes = - Bytes48::from_bytes(commitment.as_slice()).map_err(ProposerError::from)?; - let proof = - c_kzg::KzgProof::compute_blob_kzg_proof(&blob, &commitment_bytes, self.kzg_settings) - .map_err(ProposerError::from)?; - - let mut commitment_bytes = [0u8; 48]; - commitment_bytes.copy_from_slice(commitment.as_slice()); - let mut proof_bytes = [0u8; 48]; - proof_bytes.copy_from_slice(proof.as_slice()); - - Ok((commitment_bytes, proof_bytes)) - } - - pub async fn send_commitment( - &self, - block_number: u64, - withdrawal_logs_merkle_root: H256, - deposit_logs_hash: H256, - commitment: [u8; 48], - proof: [u8; 48], - blob_data: Bytes, - ) -> Result { - info!("Sending commitment for block {block_number}"); - - let mut hasher = Sha256::new(); - hasher.update(commitment); - let mut blob_versioned_hash = hasher.finalize(); - blob_versioned_hash[0] = 0x01; // EIP-4844 versioning - - let mut calldata = Vec::with_capacity(132); - calldata.extend(COMMIT_FUNCTION_SELECTOR); - let mut block_number_bytes = [0_u8; 32]; - U256::from(block_number).to_big_endian(&mut block_number_bytes); - calldata.extend(block_number_bytes); - calldata.extend(blob_versioned_hash); - calldata.extend(withdrawal_logs_merkle_root.0); - calldata.extend(deposit_logs_hash.0); - - let mut buf = [0u8; BYTES_PER_BLOB]; - buf.copy_from_slice( - blob_from_bytes(blob_data) - .map_err(ProposerError::from)? - .iter() - .as_slice(), - ); - - let blobs_bundle = BlobsBundle { - blobs: vec![buf], - commitments: vec![commitment], - proofs: vec![proof], - }; - let wrapped_tx = self - .eth_client - .build_eip4844_transaction( - self.on_chain_proposer_address, - Bytes::from(calldata), - Overrides { - from: Some(self.l1_address), - gas_price_per_blob: Some(U256::from_dec_str("100000000000000").unwrap()), - ..Default::default() - }, - blobs_bundle, - ) - .await - .map_err(ProposerError::from)?; - - let commit_tx_hash = self - .eth_client - .send_eip4844_transaction(wrapped_tx, &self.l1_private_key) - .await - .map_err(ProposerError::from)?; - - info!("Commitment sent: {commit_tx_hash:#x}"); - - while self - .eth_client - .get_transaction_receipt(commit_tx_hash) - .await? - .is_none() - { - sleep(Duration::from_secs(1)).await; - } - - Ok(commit_tx_hash) - } - - pub async fn send_proof( - &self, - block_number: u64, - block_proof: &[u8], - ) -> Result { - info!("Sending proof"); - let mut calldata = Vec::new(); - calldata.extend(VERIFY_FUNCTION_SELECTOR); - let mut block_number_bytes = [0_u8; 32]; - U256::from(block_number).to_big_endian(&mut block_number_bytes); - calldata.extend(block_number_bytes); - calldata.extend(H256::from_low_u64_be(64).as_bytes()); - calldata.extend(H256::from_low_u64_be(block_proof.len() as u64).as_bytes()); - calldata.extend(block_proof); - let leading_zeros = 32 - ((calldata.len() - 4) % 32); - calldata.extend(vec![0; leading_zeros]); - - let verify_tx = self - .eth_client - .build_eip1559_transaction( - self.on_chain_proposer_address, - calldata.into(), - Overrides { - from: Some(self.l1_address), - ..Default::default() - }, - ) - .await?; - let verify_tx_hash = self - .eth_client - .send_eip1559_transaction(verify_tx, &self.l1_private_key) - .await?; - - info!("Proof sent: {verify_tx_hash:#x}"); - - while self - .eth_client - .get_transaction_receipt(verify_tx_hash) - .await? - .is_none() - { - sleep(Duration::from_secs(1)).await; - } - - Ok(verify_tx_hash) - } } diff --git a/crates/l2/proposer/prover_server.rs b/crates/l2/proposer/prover_server.rs index 1c752337b..8522af1b6 100644 --- a/crates/l2/proposer/prover_server.rs +++ b/crates/l2/proposer/prover_server.rs @@ -1,17 +1,26 @@ -use crate::utils::eth_client::RpcResponse; use ethereum_rust_storage::Store; use ethereum_rust_vm::execution_db::ExecutionDB; -use reqwest::Client; +use keccak_hash::keccak; +use secp256k1::SecretKey; use serde::{Deserialize, Serialize}; use std::{ io::{BufReader, BufWriter}, net::{IpAddr, Shutdown, TcpListener, TcpStream}, sync::mpsc::{self, Receiver}, + time::Duration, +}; +use tokio::{ + signal::unix::{signal, SignalKind}, + time::sleep, +}; +use tracing::{debug, error, info, warn}; + +use ethereum_rust_core::{ + types::{Block, BlockHeader, EIP1559Transaction}, + Address, H256, }; -use tokio::signal::unix::{signal, SignalKind}; -use tracing::{debug, info, warn}; -use ethereum_rust_core::types::{Block, BlockHeader}; +use risc0_zkvm::sha::{Digest, Digestible}; #[derive(Debug, Serialize, Deserialize, Default)] pub struct ProverInputData { @@ -20,13 +29,19 @@ pub struct ProverInputData { pub parent_header: BlockHeader, } -use crate::utils::config::prover_server::ProverServerConfig; +use crate::utils::{ + config::{committer::CommitterConfig, eth::EthConfig, prover_server::ProverServerConfig}, + eth_client::{eth_sender::Overrides, EthClient}, +}; use super::errors::ProverServerError; pub async fn start_prover_server(store: Store) { - let config = ProverServerConfig::from_env().expect("ProverServerConfig::from_env()"); - let prover_server = ProverServer::new_from_config(config.clone(), store); + let server_config = ProverServerConfig::from_env().expect("ProverServerConfig::from_env()"); + let eth_config = EthConfig::from_env().expect("EthConfig::from_env()"); + let proposer_config = CommitterConfig::from_env().expect("CommitterConfig::from_env()"); + let mut prover_server = + ProverServer::new_from_config(server_config.clone(), &proposer_config, eth_config, store); let (tx, rx) = mpsc::channel(); @@ -37,40 +52,68 @@ pub async fn start_prover_server(store: Store) { .expect("prover_server.start()") }); - ProverServer::handle_sigint(tx, config).await; + ProverServer::handle_sigint(tx, server_config).await; tokio::try_join!(server).expect("tokio::try_join!()"); } +/// Enum for the ProverServer <--> ProverClient Communication Protocol. #[derive(Debug, Serialize, Deserialize)] pub enum ProofData { - Request {}, + /// 1. + /// The Client initiates the connection with a Request. + /// Asking for the ProverInputData the prover_server considers/needs. + Request, + + /// 2. + /// The Server responds with a Response containing the ProverInputData. + /// If the Response will is ProofData::Response{None, None}, the Client knows that the Request couldn't be performed. Response { block_number: Option, - input: ProverInputData, + input: Option, }, + + /// 3. + /// The Client submits the zk Proof generated by the prover + /// for the specified block. Submit { block_number: u64, // zk Proof - receipt: Box, - }, - SubmitAck { - block_number: u64, + receipt: Box<(risc0_zkvm::Receipt, Vec)>, }, + + /// 4. + /// The Server acknowledges the receipt of the proof and updates its state, + SubmitAck { block_number: u64 }, } struct ProverServer { ip: IpAddr, port: u16, store: Store, + eth_client: EthClient, + on_chain_proposer_address: Address, + verifier_address: Address, + verifier_private_key: SecretKey, + latest_proven_block: u64, } impl ProverServer { - pub fn new_from_config(config: ProverServerConfig, store: Store) -> Self { + pub fn new_from_config( + config: ProverServerConfig, + committer_config: &CommitterConfig, + eth_config: EthConfig, + store: Store, + ) -> Self { Self { ip: config.listen_ip, port: config.listen_port, store, + eth_client: EthClient::new(ð_config.rpc_url), + on_chain_proposer_address: committer_config.on_chain_proposer_address, + verifier_address: config.verifier_address, + verifier_private_key: config.verifier_private_key, + latest_proven_block: 0, } } @@ -84,11 +127,9 @@ impl ProverServer { .expect("TcpStream::shutdown()"); } - pub async fn start(&self, rx: Receiver<()>) -> Result<(), ProverServerError> { + pub async fn start(&mut self, rx: Receiver<()>) -> Result<(), ProverServerError> { let listener = TcpListener::bind(format!("{}:{}", self.ip, self.port))?; - let mut last_proved_block = 0; - info!("Starting TCP server at {}:{}", self.ip, self.port); for stream in listener.incoming() { if let Ok(()) = rx.try_recv() { @@ -97,19 +138,21 @@ impl ProverServer { } debug!("Connection established!"); - self.handle_connection(stream?, &mut last_proved_block) - .await; + self.handle_connection(stream?).await?; } Ok(()) } - async fn handle_connection(&self, mut stream: TcpStream, last_proved_block: &mut u64) { + async fn handle_connection(&mut self, mut stream: TcpStream) -> Result<(), ProverServerError> { let buf_reader = BufReader::new(&stream); let data: Result = serde_json::de::from_reader(buf_reader); match data { - Ok(ProofData::Request {}) => { - if let Err(e) = self.handle_request(&mut stream, *last_proved_block).await { + Ok(ProofData::Request) => { + if let Err(e) = self + .handle_request(&mut stream, self.latest_proven_block + 1) + .await + { warn!("Failed to handle request: {e}"); } } @@ -117,10 +160,15 @@ impl ProverServer { block_number, receipt, }) => { - if let Err(e) = self.handle_submit(&mut stream, block_number, receipt) { - warn!("Failed to handle submit: {e}"); + if let Err(e) = self.handle_submit(&mut stream, block_number) { + error!("Failed to handle submit_ack: {e}"); + panic!("Failed to handle submit_ack: {e}"); } - *last_proved_block += 1; + // Seems to be stopping the prover_server <--> prover_client + self.handle_proof_submission(block_number, receipt).await?; + + assert!(block_number == (self.latest_proven_block + 1), "Prover Client submitted an invalid block_number: {block_number}. The last_proved_block is: {}", self.latest_proven_block); + self.latest_proven_block = block_number; } Err(e) => { warn!("Failed to parse request: {e}"); @@ -131,84 +179,122 @@ impl ProverServer { } debug!("Connection closed"); - } - - async fn _get_last_block_number() -> Result { - let response = Client::new() - .post("http://localhost:8551") - .header("content-type", "application/json") - .body( - r#"{ - "jsonrpc": "2.0", - "method": "eth_blockNumber", - "params": [], - "id": 1 - }"#, - ) - .send() - .await - .map_err(|e| e.to_string())? - .json::() - .await - .map_err(|e| e.to_string())?; - - if let RpcResponse::Success(r) = response { - u64::from_str_radix( - r.result - .as_str() - .ok_or("Response format error".to_string())? - .strip_prefix("0x") - .ok_or("Response format error".to_string())?, - 16, - ) - .map_err(|e| e.to_string()) - } else { - Err("Failed to get last block number".to_string()) - } + Ok(()) } async fn handle_request( &self, stream: &mut TcpStream, - last_proved_block: u64, + block_number: u64, ) -> Result<(), String> { debug!("Request received"); - let last_block_number = self + let latest_block_number = self .store .get_latest_block_number() .map_err(|e| e.to_string())? - .ok_or("missing latest block number".to_string())?; - let input = self.create_prover_input(last_block_number)?; + .unwrap(); - let response = if last_block_number > last_proved_block { - ProofData::Response { - block_number: Some(last_block_number), - input, - } - } else { - ProofData::Response { + let response = if block_number > latest_block_number { + let response = ProofData::Response { block_number: None, - input, - } + input: None, + }; + warn!("Didn't send response"); + response + } else { + let input = self.create_prover_input(block_number)?; + let response = ProofData::Response { + block_number: Some(block_number), + input: Some(input), + }; + info!("Sent Response for block_number: {block_number}"); + response }; + let writer = BufWriter::new(stream); serde_json::to_writer(writer, &response).map_err(|e| e.to_string()) } - fn handle_submit( - &self, - stream: &mut TcpStream, - block_number: u64, - receipt: Box, - ) -> Result<(), String> { - debug!("Submit received. ID: {block_number}, proof: {:?}", receipt); + fn handle_submit(&self, stream: &mut TcpStream, block_number: u64) -> Result<(), String> { + debug!("Submit received for BlockNumber: {block_number}"); let response = ProofData::SubmitAck { block_number }; let writer = BufWriter::new(stream); serde_json::to_writer(writer, &response).map_err(|e| e.to_string()) } + async fn handle_proof_submission( + &self, + block_number: u64, + receipt: Box<(risc0_zkvm::Receipt, Vec)>, + ) -> Result<(), ProverServerError> { + // Send Tx + // If we run the prover_client with RISC0_DEV_MODE=0 we will have a groth16 proof + // Else, we will have a fake proof. + // + // The RISC0_DEV_MODE=1 should only be used with DEPLOYER_CONTRACT_VERIFIER=0xAA + let seal = match receipt.0.inner.groth16() { + Ok(inner) => { + // The SELECTOR is used to perform an extra check inside the groth16 verifier contract. + let mut selector = + hex::encode(inner.verifier_parameters.as_bytes().get(..4).unwrap()); + let seal = hex::encode(inner.clone().seal); + selector.push_str(&seal); + hex::decode(selector).unwrap() + } + Err(_) => vec![32; 0], + }; + + let mut image_id: [u32; 8] = [0; 8]; + for (i, b) in image_id.iter_mut().enumerate() { + *b = *receipt.1.get(i).unwrap(); + } + + let image_id: risc0_zkvm::sha::Digest = image_id.into(); + + let journal_digest = Digestible::digest(&receipt.0.journal); + + // Retry proof verification, the transaction may fail if the blobs commited were not included. + // The error message is `address already reserved`. Retrying 100 times, if there is another error it panics. + let mut attempts = 0; + let max_retries = 100; + let retry_secs = std::time::Duration::from_secs(5); + while attempts < max_retries { + match self + .send_proof(block_number, &seal, image_id, journal_digest) + .await + { + Ok(tx_hash) => { + info!( + "Sent proof for block {block_number}, with transaction hash {tx_hash:#x}" + ); + break; // Exit the while loop + } + + Err(e) => { + warn!("Failed to send proof to block {block_number:#x}. Error: {e}"); + let eth_client_error = format!("{e}"); + if eth_client_error.contains("block not committed") { + attempts += 1; + if attempts < max_retries { + warn!("Retrying... Attempt {}/{}", attempts, max_retries); + sleep(retry_secs).await; // Wait before retrying + } else { + error!("Max retries reached. Giving up on sending proof for block {block_number:#x}."); + panic!("Failed to send proof after {} attempts.", max_retries); + } + } else { + error!("Failed to send proof to block {block_number:#x}. Manual intervention required: {e}"); + panic!("Failed to send proof to block {block_number:#x}. Manual intervention required: {e}"); + } + } + } + } + + Ok(()) + } + fn create_prover_input(&self, block_number: u64) -> Result { let header = self .store @@ -239,4 +325,136 @@ impl ProverServer { parent_header, }) } + + pub async fn send_proof( + &self, + block_number: u64, + seal: &[u8], + image_id: Digest, + journal_digest: Digest, + ) -> Result { + info!("Sending proof"); + let mut calldata = Vec::new(); + + // IOnChainProposer + // function verify(uint256,bytes,bytes32,bytes32) + // Verifier + // function verify(bytes,bytes32,bytes32) + // blockNumber, seal, imageId, journalDigest + // From crates/l2/contracts/l1/interfaces/IOnChainProposer.sol + let verify_proof_selector = keccak(b"verify(uint256,bytes,bytes32,bytes32)") + .as_bytes() + .get(..4) + .expect("Failed to get initialize selector") + .to_vec(); + calldata.extend(verify_proof_selector); + + // The calldata has to be structured in the following way: + // block_number + // size in bytes + // image_id digest + // journal digest + // size of seal + // seal + + // extend with block_number + calldata.extend(H256::from_low_u64_be(block_number).as_bytes()); + + // extend with size in bytes + // 4 u256 goes after this field so: 0x80 == 128bytes == 32bytes * 4 + calldata.extend(H256::from_low_u64_be(4 * 32).as_bytes()); + + // extend with image_id + calldata.extend(image_id.as_bytes()); + + // extend with journal_digest + calldata.extend(journal_digest.as_bytes()); + + // extend with size of seal + calldata.extend(H256::from_low_u64_be(seal.len() as u64).as_bytes()); + // extend with seal + calldata.extend(seal); + // extend with zero padding + let leading_zeros = 32 - ((calldata.len() - 4) % 32); + calldata.extend(vec![0; leading_zeros]); + + let verify_tx = self + .eth_client + .build_eip1559_transaction( + self.on_chain_proposer_address, + calldata.into(), + Overrides { + from: Some(self.verifier_address), + ..Default::default() + }, + ) + .await?; + let verify_tx_hash = self + .eth_client + .send_eip1559_transaction(verify_tx.clone(), &self.verifier_private_key) + .await?; + + eip1559_transaction_handler( + &self.eth_client, + &verify_tx, + &self.verifier_private_key, + verify_tx_hash, + 20, + ) + .await?; + + Ok(verify_tx_hash) + } +} + +async fn eip1559_transaction_handler( + eth_client: &EthClient, + eip1559: &EIP1559Transaction, + l1_private_key: &SecretKey, + verify_tx_hash: H256, + max_retries: u32, +) -> Result { + let mut retries = 0; + let max_receipt_retries: u32 = 60 * 2; // 2 minutes + let mut verify_tx_hash = verify_tx_hash; + let mut tx = eip1559.clone(); + + while retries < max_retries { + if (eth_client.get_transaction_receipt(verify_tx_hash).await?).is_some() { + // If the tx_receipt was found, return the tx_hash. + return Ok(verify_tx_hash); + } else { + // Else, wait for receipt and send again if necessary. + let mut receipt_retries = 0; + + // Try for 2 minutes with an interval of 1 second to get the tx_receipt. + while receipt_retries < max_receipt_retries { + match eth_client.get_transaction_receipt(verify_tx_hash).await? { + Some(_) => return Ok(verify_tx_hash), + None => { + receipt_retries += 1; + sleep(Duration::from_secs(1)).await; + } + } + } + + // If receipt was not found, send the same tx(same nonce) but with more gas. + // Sometimes the penalty is a 100% + warn!("Transaction not confirmed, resending with 110% more gas..."); + // Increase max fee per gas by 110% (set it to 210% of the original) + tx.max_fee_per_gas = (tx.max_fee_per_gas as f64 * 2.1).round() as u64; + tx.max_priority_fee_per_gas += + (tx.max_priority_fee_per_gas as f64 * 2.1).round() as u64; + + verify_tx_hash = eth_client + .send_eip1559_transaction(tx.clone(), l1_private_key) + .await + .map_err(ProverServerError::from)?; + + retries += 1; + } + } + Err(ProverServerError::FailedToVerifyProofOnChain( + "Error handling eip1559".to_owned(), + )) } diff --git a/crates/l2/prover/src/main.rs b/crates/l2/prover/src/main.rs index 9333fb753..403c65d04 100644 --- a/crates/l2/prover/src/main.rs +++ b/crates/l2/prover/src/main.rs @@ -6,7 +6,8 @@ use tracing::{self, debug, warn, Level}; #[tokio::main] async fn main() { let subscriber = tracing_subscriber::FmtSubscriber::builder() - .with_max_level(Level::DEBUG) + // Hiding debug!() logs. + .with_max_level(Level::INFO) .finish(); tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); diff --git a/crates/l2/prover/src/prover.rs b/crates/l2/prover/src/prover.rs index 1840d2b25..05e8fc90b 100644 --- a/crates/l2/prover/src/prover.rs +++ b/crates/l2/prover/src/prover.rs @@ -28,7 +28,7 @@ pub struct ProverOutputData { pub struct Prover<'a> { env_builder: ExecutorEnvBuilder<'a>, elf: &'a [u8], - id: [u32; 8], + pub id: [u32; 8], } impl<'a> Default for Prover<'a> { diff --git a/crates/l2/prover/src/prover_client.rs b/crates/l2/prover/src/prover_client.rs index 93c5a1e2d..21626b9ac 100644 --- a/crates/l2/prover/src/prover_client.rs +++ b/crates/l2/prover/src/prover_client.rs @@ -15,18 +15,20 @@ use ethereum_rust_l2::{ use super::prover::Prover; pub async fn start_proof_data_client(config: ProverClientConfig) { - let proof_data_client = ProverClient::new(config.prover_server_endpoint.clone()); + let proof_data_client = ProverClient::new(config); proof_data_client.start().await; } struct ProverClient { prover_server_endpoint: String, + interval_ms: u64, } impl ProverClient { - pub fn new(prover_server_endpoint: String) -> Self { + pub fn new(config: ProverClientConfig) -> Self { Self { - prover_server_endpoint, + prover_server_endpoint: config.prover_server_endpoint, + interval_ms: config.interval_ms, } } @@ -34,78 +36,86 @@ impl ProverClient { let mut prover = Prover::new(); loop { - match self.request_new_data() { - Ok((Some(block_number), input)) => { + match self.request_new_input() { + Ok((block_number, input)) => { match prover.set_input(input).prove() { Ok(proof) => { - if let Err(e) = self.submit_proof(block_number, proof) { - // TODO: Retry + if let Err(e) = + self.submit_proof(block_number, proof, prover.id.to_vec()) + { + // TODO: Retry? warn!("Failed to submit proof: {e}"); } } Err(e) => error!(e), }; } - Ok((None, _)) => sleep(Duration::from_secs(10)).await, Err(e) => { - sleep(Duration::from_secs(10)).await; + sleep(Duration::from_millis(self.interval_ms)).await; warn!("Failed to request new data: {e}"); } } } } - fn request_new_data(&self) -> Result<(Option, ProverInputData), String> { - let stream = TcpStream::connect(&self.prover_server_endpoint) - .map_err(|e| format!("Failed to connect to Prover Server: {e}"))?; - let buf_writer = BufWriter::new(&stream); - - debug!("Connection established!"); - - let request = ProofData::Request {}; - serde_json::ser::to_writer(buf_writer, &request).map_err(|e| e.to_string())?; - stream - .shutdown(std::net::Shutdown::Write) - .map_err(|e| e.to_string())?; - - let buf_reader = BufReader::new(&stream); - let response: ProofData = serde_json::de::from_reader(buf_reader) - .map_err(|e| format!("Invalid response format: {e}"))?; + fn request_new_input(&self) -> Result<(u64, ProverInputData), String> { + // Request the input with the correct block_number + let request = ProofData::Request; + let response = connect_to_prover_server_wr(&self.prover_server_endpoint, &request) + .map_err(|e| format!("Failed to get Response: {e}"))?; match response { ProofData::Response { block_number, input, - } => Ok((block_number, input)), - _ => Err(format!("Unexpected response {response:?}")), + } => match (block_number, input) { + (Some(n), Some(i)) => { + info!("Received Response for block_number: {n}"); + Ok((n, i)) + } + _ => Err( + "Received Empty Response, meaning that the ProverServer doesn't have blocks to prove.\nThe Prover may be advancing faster than the Proposer." + .to_owned(), + ), + }, + _ => Err(format!("Expecting ProofData::Response {response:?}")), } } - fn submit_proof(&self, block_number: u64, receipt: risc0_zkvm::Receipt) -> Result<(), String> { - let stream = TcpStream::connect(&self.prover_server_endpoint) - .map_err(|e| format!("Failed to connect to Prover Server: {e}"))?; - let buf_writer = BufWriter::new(&stream); - + fn submit_proof( + &self, + block_number: u64, + receipt: risc0_zkvm::Receipt, + prover_id: Vec, + ) -> Result<(), String> { let submit = ProofData::Submit { block_number, - receipt: Box::new(receipt), + receipt: Box::new((receipt, prover_id)), }; - serde_json::ser::to_writer(buf_writer, &submit).map_err(|e| e.to_string())?; - stream - .shutdown(std::net::Shutdown::Write) - .map_err(|e| e.to_string())?; + let submit_ack = connect_to_prover_server_wr(&self.prover_server_endpoint, &submit) + .map_err(|e| format!("Failed to get SubmitAck: {e}"))?; - let buf_reader = BufReader::new(&stream); - let response: ProofData = serde_json::de::from_reader(buf_reader) - .map_err(|e| format!("Invalid response format: {e}"))?; - match response { - ProofData::SubmitAck { - block_number: res_id, - } => { - info!("Received submit ack: {res_id}"); + match submit_ack { + ProofData::SubmitAck { block_number } => { + info!("Received submit ack for block_number: {}", block_number); Ok(()) } - _ => Err(format!("Unexpected response {response:?}")), + _ => Err(format!("Expecting ProofData::SubmitAck {submit_ack:?}")), } } } + +fn connect_to_prover_server_wr( + addr: &str, + write: &ProofData, +) -> Result> { + let stream = TcpStream::connect(addr)?; + let buf_writer = BufWriter::new(&stream); + debug!("Connection established!"); + serde_json::ser::to_writer(buf_writer, write)?; + stream.shutdown(std::net::Shutdown::Write)?; + + let buf_reader = BufReader::new(&stream); + let response: ProofData = serde_json::de::from_reader(buf_reader)?; + Ok(response) +} diff --git a/crates/l2/utils/config/committer.rs b/crates/l2/utils/config/committer.rs new file mode 100644 index 000000000..2cd653b3f --- /dev/null +++ b/crates/l2/utils/config/committer.rs @@ -0,0 +1,23 @@ +use crate::utils::secret_key_deserializer; +use ethereum_types::Address; +use secp256k1::SecretKey; +use serde::Deserialize; + +use super::errors::ConfigError; + +#[derive(Deserialize)] +pub struct CommitterConfig { + pub on_chain_proposer_address: Address, + pub l1_address: Address, + #[serde(deserialize_with = "secret_key_deserializer")] + pub l1_private_key: SecretKey, + pub interval_ms: u64, +} + +impl CommitterConfig { + pub fn from_env() -> Result { + envy::prefixed("COMMITTER_") + .from_env::() + .map_err(ConfigError::from) + } +} diff --git a/crates/l2/utils/config/mod.rs b/crates/l2/utils/config/mod.rs index 5786ddb3b..6cce03aa0 100644 --- a/crates/l2/utils/config/mod.rs +++ b/crates/l2/utils/config/mod.rs @@ -2,6 +2,7 @@ use std::io::{BufRead, Write}; use tracing::debug; +pub mod committer; pub mod eth; pub mod l1_watcher; pub mod proposer; @@ -11,7 +12,7 @@ pub mod prover_server; pub mod errors; pub fn read_env_file() -> Result<(), errors::ConfigError> { - let env_file_name = std::env::var("ENV_FILE").unwrap_or_else(|_| ".env".to_string()); + let env_file_name = std::env::var("ENV_FILE").unwrap_or(".env".to_string()); let env_file = std::fs::File::open(env_file_name)?; let reader = std::io::BufReader::new(env_file); @@ -49,7 +50,7 @@ pub fn read_env_as_lines( } pub fn write_env(lines: Vec) -> Result<(), errors::ConfigError> { - let env_file_name = std::env::var("ENV_FILE").unwrap_or_else(|_| ".env".to_string()); + let env_file_name = std::env::var("ENV_FILE").unwrap_or(".env".to_string()); let file = std::fs::OpenOptions::new() .write(true) diff --git a/crates/l2/utils/config/proposer.rs b/crates/l2/utils/config/proposer.rs index e61bb5848..068ab33ff 100644 --- a/crates/l2/utils/config/proposer.rs +++ b/crates/l2/utils/config/proposer.rs @@ -1,16 +1,9 @@ -use crate::utils::secret_key_deserializer; -use ethereum_types::Address; -use secp256k1::SecretKey; use serde::Deserialize; use super::errors::ConfigError; #[derive(Deserialize)] pub struct ProposerConfig { - pub on_chain_proposer_address: Address, - pub l1_address: Address, - #[serde(deserialize_with = "secret_key_deserializer")] - pub l1_private_key: SecretKey, pub interval_ms: u64, } diff --git a/crates/l2/utils/config/prover_client.rs b/crates/l2/utils/config/prover_client.rs index 36b460efe..c37e2be18 100644 --- a/crates/l2/utils/config/prover_client.rs +++ b/crates/l2/utils/config/prover_client.rs @@ -5,6 +5,7 @@ use super::errors::ConfigError; #[derive(Deserialize, Debug)] pub struct ProverClientConfig { pub prover_server_endpoint: String, + pub interval_ms: u64, } impl ProverClientConfig { diff --git a/crates/l2/utils/config/prover_server.rs b/crates/l2/utils/config/prover_server.rs index 60fb2e2ee..2fd9acbf4 100644 --- a/crates/l2/utils/config/prover_server.rs +++ b/crates/l2/utils/config/prover_server.rs @@ -1,13 +1,17 @@ -use std::net::IpAddr; - -use serde::Deserialize; - use super::errors::ConfigError; +use crate::utils::secret_key_deserializer; +use ethereum_types::Address; +use secp256k1::SecretKey; +use serde::Deserialize; +use std::net::IpAddr; #[derive(Clone, Deserialize)] pub struct ProverServerConfig { pub listen_ip: IpAddr, pub listen_port: u16, + pub verifier_address: Address, + #[serde(deserialize_with = "secret_key_deserializer")] + pub verifier_private_key: SecretKey, } impl ProverServerConfig { diff --git a/crates/l2/utils/eth_client/eth_sender.rs b/crates/l2/utils/eth_client/eth_sender.rs index e7b6a89c2..16969aeb6 100644 --- a/crates/l2/utils/eth_client/eth_sender.rs +++ b/crates/l2/utils/eth_client/eth_sender.rs @@ -53,8 +53,8 @@ impl EthClient { TxKind::Call(addr) => format!("{addr:#x}"), TxKind::Create => format!("{:#x}", Address::zero()), }, - "input": format!("{:#x}", tx.input), - "value": format!("{}", tx.value), + "input": format!("0x{:#x}", tx.input), + "value": format!("{:#x}", tx.value), "from": format!("{:#x}", tx.from), }), json!("latest"), diff --git a/crates/l2/utils/eth_client/mod.rs b/crates/l2/utils/eth_client/mod.rs index 7462fd1f3..30be0d3d8 100644 --- a/crates/l2/utils/eth_client/mod.rs +++ b/crates/l2/utils/eth_client/mod.rs @@ -39,7 +39,7 @@ pub enum RpcResponse { pub struct EthClient { client: Client, - url: String, + pub url: String, } // 0x08c379a0 == Error(String) @@ -142,7 +142,7 @@ impl EthClient { }; let mut data = json!({ "to": format!("{to:#x}"), - "input": format!("{:#x}", transaction.input), + "input": format!("0x{:#x}", transaction.input), "from": format!("{:#x}", transaction.from), "value": format!("{:#x}", transaction.value), }); @@ -405,7 +405,11 @@ impl EthClient { self.get_chain_id().await?.as_u64() }, nonce: self.get_nonce_from_overrides(&overrides).await?, - max_priority_fee_per_gas: overrides.priority_gas_price.unwrap_or_default(), + max_priority_fee_per_gas: if let Some(gas_price) = overrides.priority_gas_price { + gas_price + } else { + self.get_gas_price().await?.as_u64() + }, max_fee_per_gas: if let Some(gas_price) = overrides.gas_price { gas_price } else { @@ -462,7 +466,11 @@ impl EthClient { self.get_chain_id().await?.as_u64() }, nonce: self.get_nonce_from_overrides(&overrides).await?, - max_priority_fee_per_gas: overrides.priority_gas_price.unwrap_or_default(), + max_priority_fee_per_gas: if let Some(gas_price) = overrides.priority_gas_price { + gas_price + } else { + self.get_gas_price().await?.as_u64() + }, max_fee_per_gas: if let Some(gas_price) = overrides.gas_price { gas_price } else { @@ -510,7 +518,11 @@ impl EthClient { self.get_chain_id().await?.as_u64() }, nonce: self.get_nonce_from_overrides(&overrides).await?, - max_priority_fee_per_gas: overrides.priority_gas_price.unwrap_or_default(), + max_priority_fee_per_gas: if let Some(gas_price) = overrides.priority_gas_price { + gas_price + } else { + self.get_gas_price().await?.as_u64() + }, max_fee_per_gas: if let Some(gas_price) = overrides.gas_price { gas_price } else { From 0e12f1a7e57ed16b0ac7fcd0481ea4016e0eb26d Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:38:57 -0300 Subject: [PATCH 17/17] feat(levm): move `levm` EF tests to `cmd/ef_tests` dir (#1172) **Motivation** We need `ethereum_rust` as a dependency for running the EF tests, specifically for the post-state validation. If we import it inside the crate `levm`, we have a circular dependency as `ethereum_rust` also imports `levm`. **Description** - Move `ethereum_rust` EF tests to `cmd/ef_tests/ethereum_rust`. - Update `ethereum_rust` Makefile targets that run the EF tests according to the previous change - Move `levm` EF tests to `cmd/ef_tests/levm`. - Update `ethereum_rust` Makefile targets that run the EF tests to skip `levm` EF tests run (this will be done in the crate `levm`) - Update `levm` Makefile targets that run the EF tests according to the changes above. --- .gitignore | 3 +- Cargo.toml | 3 +- Makefile | 4 +-- cmd/ef_tests/{ => ethereum_rust}/Cargo.toml | 2 +- cmd/ef_tests/{ => ethereum_rust}/ef_tests.rs | 0 cmd/ef_tests/{ => ethereum_rust}/network.rs | 0 .../{ => ethereum_rust}/test_runner.rs | 0 .../{ => ethereum_rust}/tests/cancun.rs | 2 +- .../{ => ethereum_rust}/tests/shanghai.rs | 2 +- cmd/ef_tests/{ => ethereum_rust}/types.rs | 0 cmd/ef_tests/levm/Cargo.toml | 28 +++++++++++++++++++ .../ef => cmd/ef_tests/levm}/deserialize.rs | 2 +- cmd/ef_tests/levm/ef_tests.rs | 4 +++ .../tests/ef => cmd/ef_tests/levm}/report.rs | 1 - .../tests/ef => cmd/ef_tests/levm}/runner.rs | 7 ++--- cmd/ef_tests/levm/tests/test.rs | 6 ++++ .../ef/test.rs => cmd/ef_tests/levm/types.rs | 2 +- crates/vm/Cargo.toml | 14 +++++----- crates/vm/levm/Cargo.toml | 2 +- crates/vm/levm/Makefile | 7 +++-- .../vm/levm/bench/revm_comparison/Cargo.toml | 2 +- crates/vm/levm/tests/ef/mod.rs | 13 --------- crates/vm/levm/tests/lib.rs | 2 -- 23 files changed, 64 insertions(+), 42 deletions(-) rename cmd/ef_tests/{ => ethereum_rust}/Cargo.toml (94%) rename cmd/ef_tests/{ => ethereum_rust}/ef_tests.rs (100%) rename cmd/ef_tests/{ => ethereum_rust}/network.rs (100%) rename cmd/ef_tests/{ => ethereum_rust}/test_runner.rs (100%) rename cmd/ef_tests/{ => ethereum_rust}/tests/cancun.rs (91%) rename cmd/ef_tests/{ => ethereum_rust}/tests/shanghai.rs (88%) rename cmd/ef_tests/{ => ethereum_rust}/types.rs (100%) create mode 100644 cmd/ef_tests/levm/Cargo.toml rename {crates/vm/levm/tests/ef => cmd/ef_tests/levm}/deserialize.rs (99%) create mode 100644 cmd/ef_tests/levm/ef_tests.rs rename {crates/vm/levm/tests/ef => cmd/ef_tests/levm}/report.rs (97%) rename {crates/vm/levm/tests/ef => cmd/ef_tests/levm}/runner.rs (97%) create mode 100644 cmd/ef_tests/levm/tests/test.rs rename crates/vm/levm/tests/ef/test.rs => cmd/ef_tests/levm/types.rs (99%) delete mode 100644 crates/vm/levm/tests/ef/mod.rs delete mode 100644 crates/vm/levm/tests/lib.rs diff --git a/.gitignore b/.gitignore index a596bad39..cbdf31f53 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,8 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb -cmd/ef_tests/vectors +cmd/ef_tests/ethereum_rust/vectors +cmd/ef_tests/levm/vectors # Repos checked out by make target hive/ diff --git a/Cargo.toml b/Cargo.toml index ab1b5cb26..446ea3fa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,8 @@ members = [ "crates/storage/trie", "crates/common/rlp", "cmd/ethereum_rust", - "cmd/ef_tests", + "cmd/ef_tests/ethereum_rust", + "cmd/ef_tests/levm", "cmd/ethereum_rust_l2", "crates/vm/levm", "crates/vm/levm/bench/revm_comparison", diff --git a/Makefile b/Makefile index 68ef1d9bc..63cd3c479 100644 --- a/Makefile +++ b/Makefile @@ -12,11 +12,11 @@ lint: ## 🧹 Linter check SPECTEST_VERSION := v3.0.0 SPECTEST_ARTIFACT := tests_$(SPECTEST_VERSION).tar.gz -SPECTEST_VECTORS_DIR := cmd/ef_tests/vectors +SPECTEST_VECTORS_DIR := cmd/ef_tests/ethereum_rust/vectors CRATE ?= * test: $(SPECTEST_VECTORS_DIR) ## 🧪 Run each crate's tests - cargo test -p '$(CRATE)' --workspace --exclude ethereum_rust-prover -- --skip test_contract_compilation --skip testito + cargo test -p '$(CRATE)' --workspace --exclude ethereum_rust-prover --exclude ethereum_rust-levm --exclude ef_tests-levm -- --skip test_contract_compilation --skip testito clean: clean-vectors ## 🧹 Remove build artifacts cargo clean diff --git a/cmd/ef_tests/Cargo.toml b/cmd/ef_tests/ethereum_rust/Cargo.toml similarity index 94% rename from cmd/ef_tests/Cargo.toml rename to cmd/ef_tests/ethereum_rust/Cargo.toml index 1895ce82e..abd9c11c4 100644 --- a/cmd/ef_tests/Cargo.toml +++ b/cmd/ef_tests/ethereum_rust/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ef_tests" +name = "ef_tests-ethereum_rust" version.workspace = true edition.workspace = true diff --git a/cmd/ef_tests/ef_tests.rs b/cmd/ef_tests/ethereum_rust/ef_tests.rs similarity index 100% rename from cmd/ef_tests/ef_tests.rs rename to cmd/ef_tests/ethereum_rust/ef_tests.rs diff --git a/cmd/ef_tests/network.rs b/cmd/ef_tests/ethereum_rust/network.rs similarity index 100% rename from cmd/ef_tests/network.rs rename to cmd/ef_tests/ethereum_rust/network.rs diff --git a/cmd/ef_tests/test_runner.rs b/cmd/ef_tests/ethereum_rust/test_runner.rs similarity index 100% rename from cmd/ef_tests/test_runner.rs rename to cmd/ef_tests/ethereum_rust/test_runner.rs diff --git a/cmd/ef_tests/tests/cancun.rs b/cmd/ef_tests/ethereum_rust/tests/cancun.rs similarity index 91% rename from cmd/ef_tests/tests/cancun.rs rename to cmd/ef_tests/ethereum_rust/tests/cancun.rs index c80ba7e17..4ed4ad94d 100644 --- a/cmd/ef_tests/tests/cancun.rs +++ b/cmd/ef_tests/ethereum_rust/tests/cancun.rs @@ -1,6 +1,6 @@ use std::path::Path; -use ef_tests::test_runner::{parse_test_file, run_ef_test}; +use ef_tests_ethereum_rust::test_runner::{parse_test_file, run_ef_test}; fn parse_and_execute(path: &Path) -> datatest_stable::Result<()> { let tests = parse_test_file(path); diff --git a/cmd/ef_tests/tests/shanghai.rs b/cmd/ef_tests/ethereum_rust/tests/shanghai.rs similarity index 88% rename from cmd/ef_tests/tests/shanghai.rs rename to cmd/ef_tests/ethereum_rust/tests/shanghai.rs index 900191b5e..d3689f939 100644 --- a/cmd/ef_tests/tests/shanghai.rs +++ b/cmd/ef_tests/ethereum_rust/tests/shanghai.rs @@ -1,6 +1,6 @@ use std::path::Path; -use ef_tests::test_runner::{parse_test_file, run_ef_test}; +use ef_tests_ethereum_rust::test_runner::{parse_test_file, run_ef_test}; fn parse_and_execute(path: &Path) -> datatest_stable::Result<()> { let tests = parse_test_file(path); diff --git a/cmd/ef_tests/types.rs b/cmd/ef_tests/ethereum_rust/types.rs similarity index 100% rename from cmd/ef_tests/types.rs rename to cmd/ef_tests/ethereum_rust/types.rs diff --git a/cmd/ef_tests/levm/Cargo.toml b/cmd/ef_tests/levm/Cargo.toml new file mode 100644 index 000000000..c9589dbb8 --- /dev/null +++ b/cmd/ef_tests/levm/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "ef_tests-levm" +version.workspace = true +edition.workspace = true + +[dependencies] +ethereum_rust-blockchain.workspace = true +ethereum_rust-core.workspace = true +ethereum_rust-storage.workspace = true +ethereum_rust-rlp.workspace = true +ethereum_rust-levm = { path = "../../../crates/vm/levm" } +serde.workspace = true +serde_json.workspace = true +bytes.workspace = true +hex.workspace = true +keccak-hash = "0.11.0" +colored = "2.1.0" +spinoff = "0.8.0" + +[dev-dependencies] +hex = "0.4.3" + +[lib] +path = "./ef_tests.rs" + +[[test]] +name = "test" +harness = false diff --git a/crates/vm/levm/tests/ef/deserialize.rs b/cmd/ef_tests/levm/deserialize.rs similarity index 99% rename from crates/vm/levm/tests/ef/deserialize.rs rename to cmd/ef_tests/levm/deserialize.rs index 6f872e8da..ca731bacb 100644 --- a/crates/vm/levm/tests/ef/deserialize.rs +++ b/cmd/ef_tests/levm/deserialize.rs @@ -1,4 +1,4 @@ -use crate::ef::test::EFTest; +use crate::types::EFTest; use bytes::Bytes; use ethereum_rust_core::U256; use serde::Deserialize; diff --git a/cmd/ef_tests/levm/ef_tests.rs b/cmd/ef_tests/levm/ef_tests.rs new file mode 100644 index 000000000..f117e2a37 --- /dev/null +++ b/cmd/ef_tests/levm/ef_tests.rs @@ -0,0 +1,4 @@ +mod deserialize; +mod report; +pub mod runner; +mod types; diff --git a/crates/vm/levm/tests/ef/report.rs b/cmd/ef_tests/levm/report.rs similarity index 97% rename from crates/vm/levm/tests/ef/report.rs rename to cmd/ef_tests/levm/report.rs index 3069f90cf..e34b4aa3d 100644 --- a/crates/vm/levm/tests/ef/report.rs +++ b/cmd/ef_tests/levm/report.rs @@ -1,6 +1,5 @@ // Note: I use this to do not affect the EF tests logic with this side effects // The cost to add this would be to return a Result<(), InternalError> in EFTestsReport methods -#![allow(clippy::arithmetic_side_effects)] use colored::Colorize; use std::fmt; diff --git a/crates/vm/levm/tests/ef/runner.rs b/cmd/ef_tests/levm/runner.rs similarity index 97% rename from crates/vm/levm/tests/ef/runner.rs rename to cmd/ef_tests/levm/runner.rs index cef9fa246..0ef441ea7 100644 --- a/crates/vm/levm/tests/ef/runner.rs +++ b/cmd/ef_tests/levm/runner.rs @@ -1,6 +1,4 @@ -#![allow(clippy::unwrap_used)] - -use crate::ef::{report::EFTestsReport, test::EFTest}; +use crate::{report::EFTestsReport, types::EFTest}; use ethereum_rust_core::{H256, U256}; use ethereum_rust_levm::{ db::{Cache, Db}, @@ -15,7 +13,7 @@ use std::{error::Error, sync::Arc}; pub fn run_ef_tests() -> Result> { let mut report = EFTestsReport::default(); let cargo_manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let ef_general_state_tests_path = cargo_manifest_dir.join("tests/ef/tests/GeneralStateTests"); + let ef_general_state_tests_path = cargo_manifest_dir.join("vectors/GeneralStateTests"); let mut spinner = Spinner::new(Dots, report.to_string(), Color::Cyan); for test_dir in std::fs::read_dir(ef_general_state_tests_path)?.flatten() { for test in std::fs::read_dir(test_dir.path())? @@ -54,7 +52,6 @@ pub fn run_ef_tests() -> Result> { } pub fn run_ef_test(test: EFTest, report: &mut EFTestsReport) -> Result<(), Box> { - dbg!(&test.name); let mut evm = prepare_vm(&test, report)?; ensure_pre_state(&evm, &test, report)?; let execution_result = evm.transact(); diff --git a/cmd/ef_tests/levm/tests/test.rs b/cmd/ef_tests/levm/tests/test.rs new file mode 100644 index 000000000..e726edb91 --- /dev/null +++ b/cmd/ef_tests/levm/tests/test.rs @@ -0,0 +1,6 @@ +use ef_tests_levm::runner; + +fn main() { + let report = runner::run_ef_tests().unwrap(); + println!("{report}"); +} diff --git a/crates/vm/levm/tests/ef/test.rs b/cmd/ef_tests/levm/types.rs similarity index 99% rename from crates/vm/levm/tests/ef/test.rs rename to cmd/ef_tests/levm/types.rs index eee3882db..c003c33b9 100644 --- a/crates/vm/levm/tests/ef/test.rs +++ b/cmd/ef_tests/levm/types.rs @@ -1,4 +1,4 @@ -use crate::ef::deserialize::{ +use crate::deserialize::{ deserialize_ef_post_value_indexes, deserialize_hex_bytes, deserialize_hex_bytes_vec, deserialize_u256_optional_safe, deserialize_u256_safe, deserialize_u256_valued_hashmap_safe, deserialize_u256_vec_safe, diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index a6433297b..616712a43 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -6,15 +6,15 @@ edition = "2021" [dependencies] ethereum_rust-core = { path = "../common", default-features = false } ethereum_rust-storage = { path = "../storage/store", default-features = false } -ethereum_rust_levm = { path = "./levm", optional = true } +ethereum_rust-levm = { path = "./levm", optional = true } ethereum_rust-trie = { path = "../storage/trie", default-features = false } ethereum_rust-rlp = { path = "../common/rlp", default-features = false } revm = { version = "14.0.3", features = [ - "serde", - "std", - "serde-json", - "optional_no_base_fee", - "optional_block_gas_limit", + "serde", + "std", + "serde-json", + "optional_no_base_fee", + "optional_block_gas_limit", ], default-features = false } # These dependencies must be kept up to date with the corresponding revm version, otherwise errors may pop up because of trait implementation mismatches @@ -43,7 +43,7 @@ l2 = [] c-kzg = ["revm/c-kzg"] blst = ["revm/blst"] libmdbx = ["ethereum_rust-storage/default", "ethereum_rust-core/libmdbx"] -levm = ["ethereum_rust_levm"] +levm = ["ethereum_rust-levm"] [profile.test] opt-level = 3 diff --git a/crates/vm/levm/Cargo.toml b/crates/vm/levm/Cargo.toml index 8c6cd951a..2d0400fa6 100644 --- a/crates/vm/levm/Cargo.toml +++ b/crates/vm/levm/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ethereum_rust_levm" +name = "ethereum_rust-levm" version.workspace = true edition.workspace = true diff --git a/crates/vm/levm/Makefile b/crates/vm/levm/Makefile index c3c6eac9c..316dd74a1 100644 --- a/crates/vm/levm/Makefile +++ b/crates/vm/levm/Makefile @@ -6,7 +6,7 @@ help: ## 📚 Show help for each of the Makefile recipes @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' test: ## 🧪 Runs all tests except Ethereum tests - cargo test -p ethereum_rust_levm + cargo test -p ethereum_rust-levm lint: ## 🧹 Linter check cargo clippy --all-targets --all-features -- -D warnings @@ -18,7 +18,7 @@ fmt: ## 📄 Runs rustfmt SPECTEST_VERSION := v14.1 SPECTEST_ARTIFACT := tests_$(SPECTEST_VERSION).tar.gz -SPECTEST_VECTORS_DIR := tests/ef/tests +SPECTEST_VECTORS_DIR := ../../../cmd/ef_tests/levm/vectors $(SPECTEST_ARTIFACT): rm -f tests_*.tar.gz # Delete older versions @@ -32,7 +32,8 @@ $(SPECTEST_VECTORS_DIR): $(SPECTEST_ARTIFACT) download-ef-tests: $(SPECTEST_VECTORS_DIR) ## 📥 Download EF Tests run-ef-tests: ## 🏃‍♂️ Run EF Tests - cargo test ef::testito -- --ignored + cd ../../../ && \ + cargo test -p ef_tests-levm --tests test clean-ef-tests: ## 🗑️ Clean test vectors rm -rf $(SPECTEST_VECTORS_DIR) diff --git a/crates/vm/levm/bench/revm_comparison/Cargo.toml b/crates/vm/levm/bench/revm_comparison/Cargo.toml index 6c1c1e2a6..a97b97b81 100644 --- a/crates/vm/levm/bench/revm_comparison/Cargo.toml +++ b/crates/vm/levm/bench/revm_comparison/Cargo.toml @@ -8,7 +8,7 @@ name = "revm_comparison" path = "src/lib.rs" [dependencies] -ethereum_rust_levm = { path = "../../" } +ethereum_rust-levm = { path = "../../" } hex = "0.4.3" revm = "9.0.0" bytes = "1.8.0" diff --git a/crates/vm/levm/tests/ef/mod.rs b/crates/vm/levm/tests/ef/mod.rs deleted file mode 100644 index 9de0c0234..000000000 --- a/crates/vm/levm/tests/ef/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![allow(clippy::unwrap_used)] - -mod deserialize; -mod report; -mod runner; -mod test; - -#[test] -#[ignore] -fn testito() { - let report = runner::run_ef_tests().unwrap(); - println!("{report}"); -} diff --git a/crates/vm/levm/tests/lib.rs b/crates/vm/levm/tests/lib.rs deleted file mode 100644 index 2ad8b400f..000000000 --- a/crates/vm/levm/tests/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod ef; -pub mod tests;