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},