diff --git a/.github/workflows/ci_l1.yaml b/.github/workflows/ci_l1.yaml index 3c495456f..9ebf98baf 100644 --- a/.github/workflows/ci_l1.yaml +++ b/.github/workflows/ci_l1.yaml @@ -157,7 +157,7 @@ jobs: test_pattern: /AccountRange|StorageRanges|ByteCodes|TrieNodes - name: "Devp2p eth tests" simulation: devp2p - test_pattern: eth/Status|GetBlockHeaders|SimultaneousRequests|SameRequestID|ZeroRequestID|GetBlockBodies|MaliciousHandshake|MaliciousStatus|Transaction|InvalidTxs + test_pattern: eth/Status|GetBlockHeaders|SimultaneousRequests|SameRequestID|ZeroRequestID|GetBlockBodies|MaliciousHandshake|MaliciousStatus|Transaction|InvalidTxs|NewPooledTxs - name: "Engine Auth and EC tests" simulation: ethereum/engine test_pattern: engine-(auth|exchange-capabilities)/ diff --git a/crates/networking/p2p/rlpx/connection.rs b/crates/networking/p2p/rlpx/connection.rs index 1d4e5e09a..0e97fcd26 100644 --- a/crates/networking/p2p/rlpx/connection.rs +++ b/crates/networking/p2p/rlpx/connection.rs @@ -22,6 +22,7 @@ use crate::{ use super::{ error::RLPxError, + eth::transactions::GetPooledTransactions, frame, handshake::{decode_ack_message, decode_auth_message, encode_auth_message}, message::{self as rlpx}, @@ -29,7 +30,7 @@ use super::{ utils::{ecdh_xchng, pubkey2id}, }; use aes::cipher::KeyIvInit; -use ethrex_blockchain::mempool; +use ethrex_blockchain::mempool::{self}; use ethrex_core::{H256, H512}; use ethrex_rlp::decode::RLPDecode; use ethrex_storage::Store; @@ -37,6 +38,7 @@ use k256::{ ecdsa::{RecoveryId, Signature, SigningKey, VerifyingKey}, PublicKey, SecretKey, }; +use rand::random; use sha3::{Digest, Keccak256}; use tokio::{ io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, @@ -337,7 +339,7 @@ impl RLPxConnection { match message { Message::Disconnect(msg_data) => { debug!("Received Disconnect: {:?}", msg_data.reason); - // Returning a Disonnect error to be handled later at the call stack + // Returning a Disconnect error to be handled later at the call stack return Err(RLPxError::Disconnect()); } Message::Ping(_) => { @@ -377,6 +379,17 @@ impl RLPxConnection { }; self.send(Message::BlockBodies(response)).await?; } + Message::NewPooledTransactionHashes(new_pooled_transaction_hashes) + if peer_supports_eth => + { + //TODO(#1415): evaluate keeping track of requests to avoid sending the same twice. + let hashes = + new_pooled_transaction_hashes.get_transactions_to_request(&self.storage)?; + + //TODO(#1416): Evaluate keeping track of the request-id. + let request = GetPooledTransactions::new(random(), hashes); + self.send(Message::GetPooledTransactions(request)).await?; + } Message::GetStorageRanges(req) => { let response = process_storage_ranges_request(req, self.storage.clone())?; self.send(Message::StorageRanges(response)).await? diff --git a/crates/networking/p2p/rlpx/eth/transactions.rs b/crates/networking/p2p/rlpx/eth/transactions.rs index 0f2af96b0..0f8a614c4 100644 --- a/crates/networking/p2p/rlpx/eth/transactions.rs +++ b/crates/networking/p2p/rlpx/eth/transactions.rs @@ -1,9 +1,11 @@ use bytes::BufMut; +use bytes::Bytes; use ethrex_core::{types::Transaction, H256}; use ethrex_rlp::{ error::{RLPDecodeError, RLPEncodeError}, structs::{Decoder, Encoder}, }; +use ethrex_storage::{error::StoreError, Store}; use crate::rlpx::{ message::RLPxMessage, @@ -64,7 +66,7 @@ impl RLPxMessage for Transactions { // Broadcast message #[derive(Debug)] pub(crate) struct NewPooledTransactionHashes { - transaction_types: Vec, + transaction_types: Bytes, transaction_sizes: Vec, transaction_hashes: Vec, } @@ -88,11 +90,15 @@ impl NewPooledTransactionHashes { transaction_hashes.push(transaction_hash); } Self { - transaction_types, + transaction_types: transaction_types.into(), transaction_sizes, transaction_hashes, } } + + pub fn get_transactions_to_request(&self, storage: &Store) -> Result, StoreError> { + storage.filter_unknown_transactions(&self.transaction_hashes) + } } impl RLPxMessage for NewPooledTransactionHashes { @@ -112,8 +118,7 @@ impl RLPxMessage for NewPooledTransactionHashes { fn decode(msg_data: &[u8]) -> Result { let decompressed_data = snappy_decompress(msg_data)?; let decoder = Decoder::new(&decompressed_data)?; - let (transaction_types, decoder): (Vec, _) = - decoder.decode_field("transactionTypes")?; + let (transaction_types, decoder): (Bytes, _) = decoder.decode_field("transactionTypes")?; let (transaction_sizes, decoder): (Vec, _) = decoder.decode_field("transactionSizes")?; let (transaction_hashes, _): (Vec, _) = decoder.decode_field("transactionHashes")?; diff --git a/crates/networking/p2p/rlpx/message.rs b/crates/networking/p2p/rlpx/message.rs index 377d6966a..7176cf96b 100644 --- a/crates/networking/p2p/rlpx/message.rs +++ b/crates/networking/p2p/rlpx/message.rs @@ -5,7 +5,7 @@ use std::fmt::Display; use super::eth::blocks::{BlockBodies, BlockHeaders, GetBlockBodies, GetBlockHeaders}; use super::eth::receipts::Receipts; use super::eth::status::StatusMessage; -use super::eth::transactions::Transactions; +use super::eth::transactions::{GetPooledTransactions, NewPooledTransactionHashes, Transactions}; use super::p2p::{DisconnectMessage, HelloMessage, PingMessage, PongMessage}; use super::snap::{ AccountRange, ByteCodes, GetAccountRange, GetByteCodes, GetStorageRanges, GetTrieNodes, @@ -32,6 +32,8 @@ pub(crate) enum Message { Transactions(Transactions), GetBlockBodies(GetBlockBodies), BlockBodies(BlockBodies), + NewPooledTransactionHashes(NewPooledTransactionHashes), + GetPooledTransactions(GetPooledTransactions), Receipts(Receipts), // snap capability GetAccountRange(GetAccountRange), @@ -67,6 +69,12 @@ impl Message { 0x14 => Ok(Message::BlockHeaders(BlockHeaders::decode(msg_data)?)), 0x15 => Ok(Message::GetBlockBodies(GetBlockBodies::decode(msg_data)?)), 0x16 => Ok(Message::BlockBodies(BlockBodies::decode(msg_data)?)), + 0x18 => Ok(Message::NewPooledTransactionHashes( + NewPooledTransactionHashes::decode(msg_data)?, + )), + 0x19 => Ok(Message::GetPooledTransactions( + GetPooledTransactions::decode(msg_data)?, + )), 0x20 => Ok(Message::Receipts(Receipts::decode(msg_data)?)), 0x21 => Ok(Message::GetAccountRange(GetAccountRange::decode(msg_data)?)), 0x22 => Ok(Message::AccountRange(AccountRange::decode(msg_data)?)), @@ -124,6 +132,14 @@ impl Message { 0x16_u8.encode(buf); msg.encode(buf) } + Message::NewPooledTransactionHashes(msg) => { + 0x18_u8.encode(buf); + msg.encode(buf) + } + Message::GetPooledTransactions(msg) => { + 0x19_u8.encode(buf); + msg.encode(buf) + } Message::Receipts(msg) => { 0x20_u8.encode(buf); msg.encode(buf) @@ -175,6 +191,8 @@ impl Display for Message { Message::GetBlockHeaders(_) => "eth:getBlockHeaders".fmt(f), Message::BlockHeaders(_) => "eth:BlockHeaders".fmt(f), Message::BlockBodies(_) => "eth:BlockBodies".fmt(f), + Message::NewPooledTransactionHashes(_) => "eth:NewPooledTransactionHashes".fmt(f), + Message::GetPooledTransactions(_) => "eth::GetPooledTransactions".fmt(f), Message::Transactions(_) => "eth:TransactionsMessage".fmt(f), Message::GetBlockBodies(_) => "eth:GetBlockBodies".fmt(f), Message::Receipts(_) => "eth:Receipts".fmt(f), diff --git a/crates/storage/store/storage.rs b/crates/storage/store/storage.rs index 0a012b8d5..aa169b777 100644 --- a/crates/storage/store/storage.rs +++ b/crates/storage/store/storage.rs @@ -17,7 +17,7 @@ use ethrex_rlp::encode::RLPEncode; use ethrex_trie::Trie; use serde::{Deserialize, Serialize}; use sha3::{Digest as _, Keccak256}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::sync::{Arc, Mutex}; use tracing::info; @@ -325,6 +325,24 @@ impl Store { Ok(txs_by_sender) } + /// Gets hashes from possible_hashes that are not already known in the mempool. + pub fn filter_unknown_transactions( + &self, + possible_hashes: &[H256], + ) -> Result, StoreError> { + let mempool = self + .mempool + .lock() + .map_err(|error| StoreError::Custom(error.to_string()))?; + + let tx_set: HashSet<_> = mempool.iter().map(|(hash, _)| hash).collect(); + Ok(possible_hashes + .iter() + .filter(|hash| !tx_set.contains(hash)) + .copied() + .collect()) + } + fn add_account_code(&self, code_hash: H256, code: Bytes) -> Result<(), StoreError> { self.engine.add_account_code(code_hash, code) } diff --git a/crates/vm/levm/src/errors.rs b/crates/vm/levm/src/errors.rs index 0f76834e1..daba52547 100644 --- a/crates/vm/levm/src/errors.rs +++ b/crates/vm/levm/src/errors.rs @@ -78,6 +78,8 @@ pub enum VMError { Internal(#[from] InternalError), #[error("Transaction validation error: {0}")] TxValidation(#[from] TxValidationError), + #[error("Offset out of bounds")] + OutOfOffset, } impl VMError { diff --git a/crates/vm/levm/src/gas_cost.rs b/crates/vm/levm/src/gas_cost.rs index 944d97c22..8f9eecc3d 100644 --- a/crates/vm/levm/src/gas_cost.rs +++ b/crates/vm/levm/src/gas_cost.rs @@ -1,6 +1,5 @@ use crate::{ - call_frame::CallFrame, - constants::{WORD_SIZE, WORD_SIZE_IN_BYTES}, + constants::WORD_SIZE, errors::{InternalError, OutOfGasError, VMError}, memory, StorageSlot, }; @@ -159,108 +158,109 @@ pub const CALLDATA_COST_NON_ZERO_BYTE: U256 = U256([16, 0, 0, 0]); pub const BLOB_GAS_PER_BLOB: U256 = U256([131072, 0, 0, 0]); pub fn exp(exponent: U256) -> Result { - let exponent_byte_size = exponent - .checked_add(U256::from(7)) - .ok_or(OutOfGasError::GasCostOverflow)? - .checked_div(U256::from(8)) - .ok_or(OutOfGasError::ArithmeticOperationDividedByZero)?; // '8' will never be zero + 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) + .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: &CallFrame, + new_memory_size: usize, + current_memory_size: usize, size: usize, - dest_offset: usize, -) -> Result { +) -> Result { copy_behavior( + new_memory_size, + current_memory_size, + size, CALLDATACOPY_DYNAMIC_BASE, CALLDATACOPY_STATIC, - current_call_frame, - size, - dest_offset, ) } pub fn codecopy( - current_call_frame: &CallFrame, + new_memory_size: usize, + current_memory_size: usize, size: usize, - dest_offset: usize, -) -> Result { +) -> Result { copy_behavior( + new_memory_size, + current_memory_size, + size, CODECOPY_DYNAMIC_BASE, CODECOPY_STATIC, - current_call_frame, - size, - dest_offset, ) } pub fn returndatacopy( - current_call_frame: &CallFrame, + new_memory_size: usize, + current_memory_size: usize, size: usize, - dest_offset: usize, -) -> Result { +) -> Result { copy_behavior( + new_memory_size, + current_memory_size, + size, RETURNDATACOPY_DYNAMIC_BASE, RETURNDATACOPY_STATIC, - current_call_frame, - size, - dest_offset, ) } fn copy_behavior( + new_memory_size: usize, + current_memory_size: usize, + size: usize, dynamic_base: U256, static_cost: U256, - current_call_frame: &CallFrame, - size: usize, - offset: usize, -) -> Result { +) -> 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, size)?; + let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?; let minimum_word_size_cost = dynamic_base .checked_mul(minimum_word_size.into()) .ok_or(OutOfGasError::GasCostOverflow)?; - static_cost + Ok(static_cost .checked_add(minimum_word_size_cost) .ok_or(OutOfGasError::GasCostOverflow)? - .checked_add(memory_expansion_cost) - .ok_or(OutOfGasError::GasCostOverflow) + .checked_add(memory_expansion_cost.into()) + .ok_or(OutOfGasError::GasCostOverflow)?) } pub fn keccak256( - current_call_frame: &CallFrame, + new_memory_size: usize, + current_memory_size: usize, size: usize, - offset: usize, -) -> Result { +) -> Result { copy_behavior( + new_memory_size, + current_memory_size, + size, KECCAK25_DYNAMIC_BASE, KECCAK25_STATIC, - current_call_frame, - size, - offset, ) } pub fn log( - current_call_frame: &CallFrame, + new_memory_size: usize, + current_memory_size: usize, size: usize, - offset: usize, number_of_topics: u8, -) -> Result { - let memory_expansion_cost = current_call_frame.memory.expansion_cost(offset, size)?; +) -> Result { + let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?; let topics_cost = LOGN_DYNAMIC_BASE .checked_mul(number_of_topics.into()) @@ -268,39 +268,36 @@ pub fn log( let bytes_cost = LOGN_DYNAMIC_BYTE_BASE .checked_mul(size.into()) .ok_or(OutOfGasError::GasCostOverflow)?; - topics_cost + Ok(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) + .checked_add(memory_expansion_cost.into()) + .ok_or(OutOfGasError::GasCostOverflow)?) } -pub fn mload(current_call_frame: &CallFrame, offset: usize) -> Result { - mem_expansion_behavior(current_call_frame, offset, WORD_SIZE, MLOAD_STATIC) +pub fn mload(new_memory_size: usize, current_memory_size: usize) -> Result { + mem_expansion_behavior(new_memory_size, current_memory_size, MLOAD_STATIC) } -pub fn mstore(current_call_frame: &CallFrame, offset: usize) -> Result { - mem_expansion_behavior(current_call_frame, offset, WORD_SIZE, MSTORE_STATIC) +pub fn mstore(new_memory_size: usize, current_memory_size: usize) -> Result { + mem_expansion_behavior(new_memory_size, current_memory_size, MSTORE_STATIC) } -pub fn mstore8(current_call_frame: &CallFrame, offset: usize) -> Result { - mem_expansion_behavior(current_call_frame, offset, 1, MSTORE8_STATIC) +pub fn mstore8(new_memory_size: usize, current_memory_size: usize) -> Result { + mem_expansion_behavior(new_memory_size, current_memory_size, MSTORE8_STATIC) } fn mem_expansion_behavior( - current_call_frame: &CallFrame, - offset: usize, - offset_add: usize, + new_memory_size: usize, + current_memory_size: usize, static_cost: U256, -) -> Result { - let memory_expansion_cost = current_call_frame - .memory - .expansion_cost(offset, offset_add)?; - static_cost - .checked_add(memory_expansion_cost) - .ok_or(OutOfGasError::GasCostOverflow) +) -> Result { + let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?; + Ok(static_cost + .checked_add(memory_expansion_cost.into()) + .ok_or(OutOfGasError::GasCostOverflow)?) } pub fn sload(storage_slot_was_cold: bool) -> Result { @@ -348,74 +345,61 @@ pub fn sstore( } pub fn mcopy( - current_call_frame: &CallFrame, + new_memory_size: usize, + current_memory_size: usize, size: usize, - src_offset: usize, - dest_offset: usize, -) -> Result { +) -> Result { let words_copied = (size .checked_add(WORD_SIZE) .ok_or(OutOfGasError::GasCostOverflow)? .saturating_sub(1)) / WORD_SIZE; - let src_sum = src_offset - .checked_add(size) - .ok_or(OutOfGasError::GasCostOverflow)?; - let dest_sum = dest_offset - .checked_add(size) - .ok_or(OutOfGasError::GasCostOverflow)?; - - let memory_expansion_cost = if src_sum > dest_sum { - current_call_frame.memory.expansion_cost(src_offset, size)? - } else { - current_call_frame - .memory - .expansion_cost(dest_offset, size)? - }; + let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?; let copied_words_cost = MCOPY_DYNAMIC_BASE .checked_mul(words_copied.into()) .ok_or(OutOfGasError::GasCostOverflow)?; - MCOPY_STATIC + + Ok(MCOPY_STATIC .checked_add(copied_words_cost) .ok_or(OutOfGasError::GasCostOverflow)? - .checked_add(memory_expansion_cost) - .ok_or(OutOfGasError::GasCostOverflow) + .checked_add(memory_expansion_cost.into()) + .ok_or(OutOfGasError::GasCostOverflow)?) } pub fn create( - current_call_frame: &mut CallFrame, - code_offset_in_memory: usize, + new_memory_size: usize, + current_memory_size: usize, code_size_in_memory: usize, -) -> Result { +) -> Result { compute_gas_create( - current_call_frame, - code_offset_in_memory, + new_memory_size, + current_memory_size, code_size_in_memory, false, ) } pub fn create_2( - current_call_frame: &mut CallFrame, - code_offset_in_memory: usize, + new_memory_size: usize, + current_memory_size: usize, code_size_in_memory: usize, -) -> Result { +) -> Result { compute_gas_create( - current_call_frame, - code_offset_in_memory, + new_memory_size, + current_memory_size, code_size_in_memory, true, ) } fn compute_gas_create( - current_call_frame: &mut CallFrame, - code_offset_in_memory: usize, + new_memory_size: usize, + current_memory_size: usize, code_size_in_memory: usize, is_create_2: bool, -) -> Result { +) -> Result { let minimum_word_size = (code_size_in_memory .checked_add(31) .ok_or(OutOfGasError::GasCostOverflow)?) @@ -430,9 +414,7 @@ fn compute_gas_create( .checked_mul(CODE_DEPOSIT_COST.as_usize()) // will not panic since it's 200 .ok_or(OutOfGasError::GasCostOverflow)?; - let memory_expansion_cost = current_call_frame - .memory - .expansion_cost(code_offset_in_memory, code_size_in_memory)?; + let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?; let hash_cost = if is_create_2 { minimum_word_size @@ -442,7 +424,7 @@ fn compute_gas_create( 0 }; - memory_expansion_cost + Ok(U256::from(memory_expansion_cost) .checked_add(init_code_cost.into()) .ok_or(OutOfGasError::CreationCostIsTooHigh)? .checked_add(code_deposit_cost.into()) @@ -450,7 +432,7 @@ fn compute_gas_create( .checked_add(CREATE_BASE_COST) .ok_or(OutOfGasError::CreationCostIsTooHigh)? .checked_add(hash_cost.into()) - .ok_or(OutOfGasError::CreationCostIsTooHigh) + .ok_or(OutOfGasError::CreationCostIsTooHigh)?) } pub fn selfdestruct(address_was_cold: bool, account_is_empty: bool) -> Result { @@ -524,37 +506,6 @@ fn address_access_cost( .ok_or(OutOfGasError::GasCostOverflow)?) } -fn memory_access_cost( - new_memory_size: U256, - current_memory_size: U256, - static_cost: U256, - dynamic_base_cost: U256, -) -> Result { - let minimum_word_size = new_memory_size - .checked_add( - WORD_SIZE_IN_BYTES - .checked_sub(U256::one()) - .ok_or(InternalError::ArithmeticOperationUnderflow)?, - ) - .ok_or(OutOfGasError::MemoryExpansionCostOverflow)? - .checked_div(WORD_SIZE_IN_BYTES) - .ok_or(OutOfGasError::MemoryExpansionCostOverflow)?; - - let static_gas = static_cost; - let dynamic_cost = dynamic_base_cost - .checked_mul(minimum_word_size) - .ok_or(OutOfGasError::MemoryExpansionCostOverflow)? - .checked_add(memory::expansion_cost( - new_memory_size, - current_memory_size, - )?) - .ok_or(OutOfGasError::MemoryExpansionCostOverflow)?; - - Ok(static_gas - .checked_add(dynamic_cost) - .ok_or(OutOfGasError::GasCostOverflow)?) -} - pub fn balance(address_was_cold: bool) -> Result { address_access_cost( address_was_cold, @@ -574,11 +525,11 @@ pub fn extcodesize(address_was_cold: bool) -> Result { } pub fn extcodecopy( - new_memory_size: U256, - current_memory_size: U256, + new_memory_size: usize, + current_memory_size: usize, address_was_cold: bool, ) -> Result { - Ok(memory_access_cost( + Ok(memory::access_cost( new_memory_size, current_memory_size, EXTCODECOPY_STATIC, @@ -603,8 +554,8 @@ pub fn extcodehash(address_was_cold: bool) -> Result { } pub fn call( - new_memory_size: U256, - current_memory_size: U256, + new_memory_size: usize, + current_memory_size: usize, address_was_cold: bool, address_is_empty: bool, value_to_transfer: U256, @@ -632,7 +583,7 @@ pub fn call( }; // Note: code_execution_cost will be charged from the sub context post-state. - let dynamic_gas = memory_expansion_cost + let dynamic_gas = U256::from(memory_expansion_cost) .checked_add(address_access_cost) .ok_or(OutOfGasError::GasCostOverflow)? .checked_add(positive_value_cost) @@ -646,8 +597,8 @@ pub fn call( } pub fn callcode( - new_memory_size: U256, - current_memory_size: U256, + new_memory_size: usize, + current_memory_size: usize, address_was_cold: bool, value_to_transfer: U256, ) -> Result { @@ -669,7 +620,7 @@ pub fn callcode( }; // Note: code_execution_cost will be charged from the sub context post-state. - let dynamic_gas = memory_expansion_cost + let dynamic_gas = U256::from(memory_expansion_cost) .checked_add(address_access_cost) .ok_or(OutOfGasError::GasCostOverflow)? .checked_add(positive_value_cost) @@ -681,8 +632,8 @@ pub fn callcode( } pub fn delegatecall( - new_memory_size: U256, - current_memory_size: U256, + new_memory_size: usize, + current_memory_size: usize, address_was_cold: bool, ) -> Result { let static_gas = DELEGATECALL_STATIC; @@ -696,7 +647,7 @@ pub fn delegatecall( )?; // Note: code_execution_cost will be charged from the sub context post-state. - let dynamic_gas = memory_expansion_cost + let dynamic_gas = U256::from(memory_expansion_cost) .checked_add(address_access_cost) .ok_or(OutOfGasError::GasCostOverflow)?; @@ -706,8 +657,8 @@ pub fn delegatecall( } pub fn staticcall( - new_memory_size: U256, - current_memory_size: U256, + new_memory_size: usize, + current_memory_size: usize, address_was_cold: bool, ) -> Result { let static_gas = STATICCALL_STATIC; @@ -721,7 +672,7 @@ pub fn staticcall( )?; // Note: code_execution_cost will be charged from the sub context post-state. - let dynamic_gas = memory_expansion_cost + let dynamic_gas = U256::from(memory_expansion_cost) .checked_add(address_access_cost) .ok_or(OutOfGasError::GasCostOverflow)?; diff --git a/crates/vm/levm/src/memory.rs b/crates/vm/levm/src/memory.rs index d7f9ea7b3..6a2de0bf7 100644 --- a/crates/vm/levm/src/memory.rs +++ b/crates/vm/levm/src/memory.rs @@ -1,245 +1,209 @@ use crate::{ - constants::{MEMORY_EXPANSION_QUOTIENT, WORD_SIZE, WORD_SIZE_IN_BYTES}, + constants::{MEMORY_EXPANSION_QUOTIENT, WORD_SIZE_IN_BYTES_USIZE}, errors::{InternalError, OutOfGasError, VMError}, }; use ethrex_core::U256; -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Memory { - pub data: Vec, -} +pub type Memory = Vec; -impl From> for Memory { - fn from(data: Vec) -> Self { - Memory { data } +pub fn try_resize(memory: &mut Memory, unchecked_new_size: usize) -> Result<(), VMError> { + if unchecked_new_size == 0 || unchecked_new_size <= memory.len() { + return Ok(()); } -} -impl Memory { - pub fn new() -> Self { - Self { data: Vec::new() } - } + let new_size = unchecked_new_size + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?; - pub fn new_from_vec(data: Vec) -> Self { - Self { data } + if new_size > memory.len() { + let additional_size = new_size.checked_sub(memory.len()).ok_or(VMError::Internal( + InternalError::ArithmeticOperationUnderflow, + ))?; + memory + .try_reserve(additional_size) + .map_err(|_err| VMError::MemorySizeOverflow)?; + memory.resize(new_size, 0); } - fn resize(&mut self, offset: usize) -> Result<(), VMError> { - let new_offset = offset - .checked_next_multiple_of(WORD_SIZE) - .ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, - ))?; - if new_offset > self.data.len() { - // Expand the size - let size_to_expand = - new_offset - .checked_sub(self.data.len()) - .ok_or(VMError::Internal( - InternalError::ArithmeticOperationUnderflow, - ))?; - self.data - .try_reserve(size_to_expand) - .map_err(|_err| VMError::MemorySizeOverflow)?; - - // Fill the new space with zeros - self.data.extend(std::iter::repeat(0).take(size_to_expand)); - } - Ok(()) - } + Ok(()) +} - pub fn load(&mut self, offset: usize) -> Result { - self.resize(offset.checked_add(WORD_SIZE).ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, // MemoryLoadOutOfBounds? - ))?)?; - let value_bytes = self - .data - .get( - offset - ..offset.checked_add(WORD_SIZE).ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, // MemoryLoadOutOfBounds? - ))?, - ) - .ok_or(VMError::MemoryLoadOutOfBounds)?; +pub fn load_word(memory: &mut Memory, offset: usize) -> Result { + load_range(memory, offset, WORD_SIZE_IN_BYTES_USIZE).map(U256::from_big_endian) +} - Ok(U256::from_big_endian(value_bytes)) +pub fn load_range(memory: &mut Memory, offset: usize, size: usize) -> Result<&[u8], VMError> { + if size == 0 { + return Ok(&[]); } - pub fn load_range(&mut self, offset: usize, size: usize) -> Result, VMError> { - if size == 0 { - return Ok(Vec::new()); - } + try_resize( + memory, + offset.checked_add(size).ok_or(VMError::OutOfOffset)?, + )?; - let size_to_load = offset.checked_add(size).ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, - ))?; - self.resize(size_to_load)?; - self.data - .get(offset..size_to_load) - .ok_or(VMError::MemoryLoadOutOfBounds) - .map(|slice| slice.to_vec()) - } + memory + .get(offset..offset.checked_add(size).ok_or(VMError::OutOfOffset)?) + .ok_or(VMError::OutOfOffset) +} - pub fn store_bytes(&mut self, offset: usize, value: &[u8]) -> Result<(), VMError> { - let len = value.len(); - 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()); +pub fn try_store_word(memory: &mut Memory, offset: usize, word: U256) -> Result<(), VMError> { + try_resize( + memory, + offset + .checked_add(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?, + )?; + let mut word_bytes = [0u8; WORD_SIZE_IN_BYTES_USIZE]; + word.to_big_endian(&mut word_bytes); + try_store(memory, &word_bytes, offset, WORD_SIZE_IN_BYTES_USIZE) +} - Ok(()) - } +pub fn try_store_data(memory: &mut Memory, offset: usize, data: &[u8]) -> Result<(), VMError> { + try_resize( + memory, + offset.checked_add(data.len()).ok_or(VMError::OutOfOffset)?, + )?; + try_store(memory, data, offset, data.len()) +} - pub fn store_n_bytes( - &mut self, - offset: usize, - value: &[u8], - size: usize, - ) -> Result<(), VMError> { - 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()); +pub fn try_store_range( + memory: &mut Memory, + offset: usize, + size: usize, + data: &[u8], +) -> Result<(), VMError> { + try_resize( + memory, + offset.checked_add(size).ok_or(VMError::OutOfOffset)?, + )?; + try_store(memory, data, offset, size) +} - Ok(()) +fn try_store( + memory: &mut Memory, + data: &[u8], + at_offset: usize, + data_size: usize, +) -> Result<(), VMError> { + if data_size == 0 { + return Ok(()); } - pub fn size(&self) -> U256 { - U256::from(self.data.len()) + for (byte_to_store, memory_slot) in data.iter().zip( + memory + .get_mut( + at_offset + ..at_offset + .checked_add(data_size) + .ok_or(VMError::OutOfOffset)?, + ) + .ok_or(VMError::OutOfOffset)? + .iter_mut(), + ) { + *memory_slot = *byte_to_store; } + Ok(()) +} - pub fn copy( - &mut self, - src_offset: usize, - dest_offset: usize, - size: usize, - ) -> Result<(), VMError> { - 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)?; - } +pub fn try_copy_within( + memory: &mut Memory, + from_offset: usize, + to_offset: usize, + size: usize, +) -> Result<(), VMError> { + if size == 0 { + return Ok(()); + } - 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 - .data - .get( - src_offset - .checked_add(i) - .ok_or(VMError::MemoryLoadOutOfBounds)?, - ) - .unwrap_or(&0u8); - } + try_resize( + memory, + to_offset.checked_add(size).ok_or(VMError::OutOfOffset)?, + )?; + + let mut temporary_buffer = vec![0u8; size]; + for i in 0..size { + if let Some(temporary_buffer_byte) = temporary_buffer.get_mut(i) { + *temporary_buffer_byte = *memory + .get(from_offset.checked_add(i).ok_or(VMError::OutOfOffset)?) + .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); - } + for i in 0..size { + if let Some(memory_byte) = + memory.get_mut(to_offset.checked_add(i).ok_or(VMError::OutOfOffset)?) + { + *memory_byte = *temporary_buffer.get(i).unwrap_or(&0u8); } - Ok(()) } - pub fn expansion_cost(&self, offset: usize, size: usize) -> Result { - if size == 0 { - return Ok(U256::zero()); - } + Ok(()) +} - let memory_byte_size = offset - .checked_add(size) - .ok_or(OutOfGasError::GasCostOverflow)?; - if memory_byte_size <= self.data.len() { - return Ok(U256::zero()); - } +pub fn access_cost( + new_memory_size: usize, + current_memory_size: usize, + static_cost: U256, + dynamic_base_cost: U256, +) -> Result { + let minimum_word_size = new_memory_size + .checked_add( + WORD_SIZE_IN_BYTES_USIZE + .checked_sub(1) + .ok_or(InternalError::ArithmeticOperationUnderflow)?, + ) + .ok_or(OutOfGasError::MemoryExpansionCostOverflow)? + / WORD_SIZE_IN_BYTES_USIZE; - let new_memory_size_word = memory_byte_size - .checked_add(WORD_SIZE - 1) - .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(OutOfGasError::GasCostOverflow)?; - - let last_memory_size_word = self - .data - .len() - .checked_add(WORD_SIZE - 1) - .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(OutOfGasError::GasCostOverflow)?; - - Ok((new_memory_cost - .checked_sub(last_memory_cost) - .ok_or(OutOfGasError::GasCostOverflow)?) - .into()) - } + let static_gas = static_cost; + let dynamic_cost = dynamic_base_cost + .checked_mul(minimum_word_size.into()) + .ok_or(OutOfGasError::MemoryExpansionCostOverflow)? + .checked_add(expansion_cost(new_memory_size, current_memory_size)?.into()) + .ok_or(OutOfGasError::MemoryExpansionCostOverflow)?; + + Ok(static_gas + .checked_add(dynamic_cost) + .ok_or(OutOfGasError::GasCostOverflow)?) +} + +/// When a memory expansion is triggered, only the additional bytes of memory +/// must be paid for. +pub fn expansion_cost( + new_memory_size: usize, + current_memory_size: usize, +) -> Result { + let cost = if new_memory_size <= current_memory_size { + 0 + } else { + cost(new_memory_size)? + .checked_sub(cost(current_memory_size)?) + .ok_or(InternalError::ArithmeticOperationUnderflow)? + }; + Ok(cost) } /// The total cost for a given memory size. -pub fn cost(memory_size: U256) -> Result { +fn cost(memory_size: usize) -> Result { let memory_size_word = memory_size .checked_add( - WORD_SIZE_IN_BYTES - .checked_sub(U256::one()) + WORD_SIZE_IN_BYTES_USIZE + .checked_sub(1) .ok_or(InternalError::ArithmeticOperationUnderflow)?, ) .ok_or(OutOfGasError::MemoryExpansionCostOverflow)? - .checked_div(WORD_SIZE_IN_BYTES) - .ok_or(OutOfGasError::MemoryExpansionCostOverflow)?; + / WORD_SIZE_IN_BYTES_USIZE; Ok(memory_size_word - .checked_pow(U256::from(2)) + .checked_pow(2) .ok_or(OutOfGasError::MemoryExpansionCostOverflow)? - .checked_div(U256::from(512)) + .checked_div(MEMORY_EXPANSION_QUOTIENT) .ok_or(OutOfGasError::MemoryExpansionCostOverflow)? .checked_add( - U256::from(3) + 3usize .checked_mul(memory_size_word) .ok_or(OutOfGasError::MemoryExpansionCostOverflow)?, ) .ok_or(OutOfGasError::MemoryExpansionCostOverflow)?) } - -/// When a memory expansion is triggered, only the additional bytes of memory -/// must be paid for. -pub fn expansion_cost(new_memory_size: U256, old_memory_size: U256) -> Result { - let cost = if new_memory_size <= old_memory_size { - U256::zero() - } else { - cost(new_memory_size)? - .checked_sub(cost(old_memory_size)?) - .ok_or(InternalError::ArithmeticOperationUnderflow)? - }; - Ok(cost) -} diff --git a/crates/vm/levm/src/opcode_handlers/arithmetic.rs b/crates/vm/levm/src/opcode_handlers/arithmetic.rs index a12642868..2ed77cf8f 100644 --- a/crates/vm/levm/src/opcode_handlers/arithmetic.rs +++ b/crates/vm/levm/src/opcode_handlers/arithmetic.rs @@ -76,11 +76,18 @@ impl VM { return Ok(OpcodeSuccess::Continue); } - let dividend = abs(dividend); - let divisor = abs(divisor); - - let quotient = match dividend.checked_div(divisor) { - Some(quot) => quot, + let abs_dividend = abs(dividend); + let abs_divisor = abs(divisor); + + let quotient = match abs_dividend.checked_div(abs_divisor) { + Some(quot) => { + let quotient_is_negative = is_negative(dividend) ^ is_negative(divisor); + if quotient_is_negative { + negate(quot) + } else { + quot + } + } None => U256::zero(), }; diff --git a/crates/vm/levm/src/opcode_handlers/environment.rs b/crates/vm/levm/src/opcode_handlers/environment.rs index f106ff34f..642f84c48 100644 --- a/crates/vm/levm/src/opcode_handlers/environment.rs +++ b/crates/vm/levm/src/opcode_handlers/environment.rs @@ -1,8 +1,8 @@ use crate::{ call_frame::CallFrame, - constants::{WORD_SIZE, WORD_SIZE_IN_BYTES_USIZE}, - errors::{InternalError, OpcodeSuccess, OutOfGasError, VMError}, - gas_cost, + constants::WORD_SIZE_IN_BYTES_USIZE, + errors::{OpcodeSuccess, OutOfGasError, VMError}, + gas_cost, memory, vm::{word_to_address, VM}, }; use ethrex_core::U256; @@ -154,10 +154,18 @@ impl VM { .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; - 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)?; + self.increase_consumed_gas( + current_call_frame, + gas_cost::calldatacopy( + dest_offset + .checked_add(size) + .ok_or(VMError::OutOfOffset)? + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?, + current_call_frame.memory.len(), + size, + )?, + )?; if size == 0 { return Ok(OpcodeSuccess::Continue); @@ -176,7 +184,7 @@ impl VM { } } - current_call_frame.memory.store_bytes(dest_offset, &data)?; + memory::try_store_data(&mut current_call_frame.memory, dest_offset, &data)?; Ok(OpcodeSuccess::Continue) } @@ -226,53 +234,38 @@ impl VM { .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let gas_cost = gas_cost::codecopy(current_call_frame, size, destination_offset) - .map_err(VMError::OutOfGas)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; + self.increase_consumed_gas( + current_call_frame, + gas_cost::codecopy( + destination_offset + .checked_add(size) + .ok_or(VMError::OutOfOffset)? + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?, + current_call_frame.memory.len(), + size, + )?, + )?; if size == 0 { return Ok(OpcodeSuccess::Continue); } - let new_memory_size = (destination_offset - .checked_add(size) - .ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, - ))?) - .checked_next_multiple_of(WORD_SIZE) - .ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, - ))?; - let current_memory_size = current_call_frame.memory.data.len(); - - if current_memory_size < new_memory_size { - current_call_frame - .memory - .data - .try_reserve(new_memory_size) - .map_err(|_err| VMError::MemorySizeOverflow)?; - current_call_frame.memory.data.resize(new_memory_size, 0); - } - - for i in 0..size { - if let Some(memory_byte) = - current_call_frame - .memory - .data - .get_mut(destination_offset.checked_add(i).ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, - ))?) - { - *memory_byte = *current_call_frame - .bytecode - .get(code_offset.checked_add(i).ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, - ))?) - .unwrap_or(&0u8); + let mut data = vec![0u8; size]; + for (i, byte) in current_call_frame + .bytecode + .iter() + .skip(code_offset) + .take(size) + .enumerate() + { + if let Some(data_byte) = data.get_mut(i) { + *data_byte = *byte; } } + memory::try_store_data(&mut current_call_frame.memory, destination_offset, &data)?; + Ok(OpcodeSuccess::Continue) } @@ -330,22 +323,15 @@ impl VM { let (account_info, address_was_cold) = self.access_account(address); - let new_memory_size = dest_offset - .checked_add(size) - .ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, - ))? - .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) - .ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, - ))?; - let current_memory_size = current_call_frame.memory.data.len(); - self.increase_consumed_gas( current_call_frame, gas_cost::extcodecopy( - new_memory_size.into(), - current_memory_size.into(), + dest_offset + .checked_add(size) + .ok_or(VMError::OutOfOffset)? + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?, + current_call_frame.memory.len(), address_was_cold, )?, )?; @@ -354,36 +340,21 @@ impl VM { return Ok(OpcodeSuccess::Continue); } - if current_memory_size < new_memory_size { - current_call_frame - .memory - .data - .try_reserve(new_memory_size) - .map_err(|_err| VMError::MemorySizeOverflow)?; - current_call_frame - .memory - .data - .extend(std::iter::repeat(0).take(new_memory_size)); - } - - for i in 0..size { - if let Some(memory_byte) = - current_call_frame - .memory - .data - .get_mut(dest_offset.checked_add(i).ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, - ))?) - { - *memory_byte = *account_info - .bytecode - .get(offset.checked_add(i).ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, - ))?) - .unwrap_or(&0u8); + let mut data = vec![0u8; size]; + for (i, byte) in account_info + .bytecode + .iter() + .skip(offset) + .take(size) + .enumerate() + { + if let Some(data_byte) = data.get_mut(i) { + *data_byte = *byte; } } + memory::try_store_data(&mut current_call_frame.memory, dest_offset, &data)?; + Ok(OpcodeSuccess::Continue) } @@ -422,10 +393,18 @@ impl VM { .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - 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)?; + self.increase_consumed_gas( + current_call_frame, + gas_cost::returndatacopy( + dest_offset + .checked_add(size) + .ok_or(VMError::OutOfOffset)? + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?, + current_call_frame.memory.len(), + size, + )?, + )?; if size == 0 { return Ok(OpcodeSuccess::Continue); @@ -436,17 +415,21 @@ impl VM { if returndata_offset >= sub_return_data_len { return Err(VMError::VeryLargeNumber); // Maybe can create a new error instead of using this one } - let data = current_call_frame.sub_return_data.slice( - returndata_offset - ..(returndata_offset - .checked_add(size) - .ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, - ))?) - .min(sub_return_data_len), - ); - current_call_frame.memory.store_bytes(dest_offset, &data)?; + let mut data = vec![0u8; size]; + for (i, byte) in current_call_frame + .sub_return_data + .iter() + .skip(returndata_offset) + .take(size) + .enumerate() + { + if let Some(data_byte) = data.get_mut(i) { + *data_byte = *byte; + } + } + + memory::try_store_data(&mut current_call_frame.memory, dest_offset, &data)?; Ok(OpcodeSuccess::Continue) } diff --git a/crates/vm/levm/src/opcode_handlers/keccak.rs b/crates/vm/levm/src/opcode_handlers/keccak.rs index 29cd2872a..2be644af9 100644 --- a/crates/vm/levm/src/opcode_handlers/keccak.rs +++ b/crates/vm/levm/src/opcode_handlers/keccak.rs @@ -1,7 +1,8 @@ use crate::{ call_frame::CallFrame, + constants::WORD_SIZE_IN_BYTES_USIZE, errors::{OpcodeSuccess, VMError}, - gas_cost, + gas_cost, memory, vm::VM, }; use ethrex_core::U256; @@ -26,19 +27,25 @@ impl VM { .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let gas_cost = - gas_cost::keccak256(current_call_frame, size, offset).map_err(VMError::OutOfGas)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; - - let value_bytes = if size == 0 { - vec![] - } else { - current_call_frame.memory.load_range(offset, size)? - }; + self.increase_consumed_gas( + current_call_frame, + gas_cost::keccak256( + offset + .checked_add(size) + .ok_or(VMError::OutOfOffset)? + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?, + current_call_frame.memory.len(), + size, + )?, + )?; let mut hasher = Keccak256::new(); - hasher.update(value_bytes); + hasher.update(memory::load_range( + &mut current_call_frame.memory, + offset, + size, + )?); current_call_frame .stack .push(U256::from_big_endian(&hasher.finalize()))?; diff --git a/crates/vm/levm/src/opcode_handlers/logging.rs b/crates/vm/levm/src/opcode_handlers/logging.rs index 8efa48268..3b45a68b6 100644 --- a/crates/vm/levm/src/opcode_handlers/logging.rs +++ b/crates/vm/levm/src/opcode_handlers/logging.rs @@ -1,7 +1,8 @@ use crate::{ call_frame::CallFrame, + constants::WORD_SIZE_IN_BYTES_USIZE, errors::{OpcodeSuccess, VMError}, - gas_cost, + gas_cost, memory, opcodes::Opcode, vm::VM, }; @@ -44,16 +45,26 @@ impl VM { topics.push(H256::from_slice(&topic_bytes)); } - 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::log( + offset + .checked_add(size) + .ok_or(VMError::OutOfOffset)? + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?, + current_call_frame.memory.len(), + size, + number_of_topics, + )?, + )?; - self.increase_consumed_gas(current_call_frame, gas_cost)?; - - let data = current_call_frame.memory.load_range(offset, size)?; let log = Log { address: current_call_frame.msg_sender, // Should change the addr if we are on a Call/Create transaction (Call should be the contract we are calling, Create should be the original caller) topics, - data: Bytes::from(data), + data: Bytes::from( + memory::load_range(&mut current_call_frame.memory, offset, size)?.to_vec(), + ), }; current_call_frame.logs.push(log); 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 0c9ba084e..a64cab94a 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,8 +1,8 @@ use crate::{ call_frame::CallFrame, - constants::WORD_SIZE, + constants::{WORD_SIZE, WORD_SIZE_IN_BYTES_USIZE}, errors::{InternalError, OpcodeSuccess, OutOfGasError, VMError}, - gas_cost, + gas_cost, memory, vm::VM, }; use ethrex_core::{H256, U256}; @@ -63,12 +63,21 @@ impl VM { .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let gas_cost = gas_cost::mload(current_call_frame, offset).map_err(VMError::OutOfGas)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; + self.increase_consumed_gas( + current_call_frame, + gas_cost::mload( + offset + .checked_add(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)? + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?, + current_call_frame.memory.len(), + )?, + )?; - let value = current_call_frame.memory.load(offset)?; - current_call_frame.stack.push(value)?; + current_call_frame + .stack + .push(memory::load_word(&mut current_call_frame.memory, offset)?)?; Ok(OpcodeSuccess::Continue) } @@ -84,17 +93,23 @@ impl VM { .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; - let gas_cost = gas_cost::mstore(current_call_frame, offset).map_err(VMError::OutOfGas)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; + self.increase_consumed_gas( + current_call_frame, + gas_cost::mstore( + offset + .checked_add(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)? + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?, + current_call_frame.memory.len(), + )?, + )?; let value = current_call_frame.stack.pop()?; let mut value_bytes = [0u8; WORD_SIZE]; value.to_big_endian(&mut value_bytes); - current_call_frame - .memory - .store_bytes(offset, &value_bytes)?; + memory::try_store_data(&mut current_call_frame.memory, offset, &value_bytes)?; Ok(OpcodeSuccess::Continue) } @@ -111,17 +126,27 @@ impl VM { .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let gas_cost = gas_cost::mstore8(current_call_frame, offset).map_err(VMError::OutOfGas)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; + self.increase_consumed_gas( + current_call_frame, + gas_cost::mstore8( + offset + .checked_add(1) + .ok_or(VMError::OutOfOffset)? + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?, + current_call_frame.memory.len(), + )?, + )?; let value = current_call_frame.stack.pop()?; let mut value_bytes = [0u8; WORD_SIZE]; value.to_big_endian(&mut value_bytes); - current_call_frame - .memory - .store_bytes(offset, value_bytes[WORD_SIZE - 1..WORD_SIZE].as_ref())?; + memory::try_store_data( + &mut current_call_frame.memory, + offset, + &value_bytes[WORD_SIZE - 1..WORD_SIZE], + )?; Ok(OpcodeSuccess::Continue) } @@ -225,7 +250,7 @@ impl VM { self.increase_consumed_gas(current_call_frame, gas_cost::MSIZE)?; current_call_frame .stack - .push(current_call_frame.memory.size())?; + .push(current_call_frame.memory.len().into())?; Ok(OpcodeSuccess::Continue) } @@ -265,16 +290,33 @@ impl VM { .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let gas_cost = gas_cost::mcopy(current_call_frame, size, src_offset, dest_offset) - .map_err(VMError::OutOfGas)?; + let new_memory_size_for_dest = dest_offset + .checked_add(size) + .ok_or(VMError::OutOfOffset)? + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?; - self.increase_consumed_gas(current_call_frame, gas_cost)?; + let new_memory_size_for_src = src_offset + .checked_add(size) + .ok_or(VMError::OutOfOffset)? + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?; - if size > 0 { - current_call_frame - .memory - .copy(src_offset, dest_offset, size)?; - } + self.increase_consumed_gas( + current_call_frame, + gas_cost::mcopy( + new_memory_size_for_dest.max(new_memory_size_for_src), + current_call_frame.memory.len(), + size, + )?, + )?; + + memory::try_copy_within( + &mut current_call_frame.memory, + src_offset, + dest_offset, + size, + )?; Ok(OpcodeSuccess::Continue) } diff --git a/crates/vm/levm/src/opcode_handlers/system.rs b/crates/vm/levm/src/opcode_handlers/system.rs index 88458ecb9..973242785 100644 --- a/crates/vm/levm/src/opcode_handlers/system.rs +++ b/crates/vm/levm/src/opcode_handlers/system.rs @@ -1,8 +1,8 @@ use crate::{ call_frame::CallFrame, constants::WORD_SIZE_IN_BYTES_USIZE, - errors::{InternalError, OpcodeSuccess, ResultReason, VMError}, - gas_cost, + errors::{InternalError, OpcodeSuccess, OutOfGasError, ResultReason, VMError}, + gas_cost, memory, vm::{word_to_address, VM}, }; use ethrex_core::{types::TxKind, Address, U256}; @@ -60,15 +60,15 @@ impl VM { InternalError::ArithmeticOperationOverflow, ))?; let new_memory_size = new_memory_size_for_args.max(new_memory_size_for_return_data); - let current_memory_size = current_call_frame.memory.data.len(); + let current_memory_size = current_call_frame.memory.len(); let (account_info, address_was_cold) = self.access_account(callee); self.increase_consumed_gas( current_call_frame, gas_cost::call( - new_memory_size.into(), - current_memory_size.into(), + new_memory_size, + current_memory_size, address_was_cold, account_info.is_empty(), value_to_transfer, @@ -141,15 +141,15 @@ impl VM { InternalError::ArithmeticOperationOverflow, ))?; let new_memory_size = new_memory_size_for_args.max(new_memory_size_for_return_data); - let current_memory_size = current_call_frame.memory.data.len(); + let current_memory_size = current_call_frame.memory.len(); let (_account_info, address_was_cold) = self.access_account(code_address); self.increase_consumed_gas( current_call_frame, gas_cost::callcode( - new_memory_size.into(), - current_memory_size.into(), + new_memory_size, + current_memory_size, address_was_cold, value_to_transfer, )?, @@ -193,12 +193,18 @@ impl VM { .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let gas_cost = current_call_frame.memory.expansion_cost(offset, size)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; + let new_memory_size = offset + .checked_add(size) + .ok_or(VMError::OutOfGas(OutOfGasError::GasCostOverflow))?; + self.increase_consumed_gas( + current_call_frame, + memory::expansion_cost(new_memory_size, current_call_frame.memory.len())?.into(), + )?; - let return_data = current_call_frame.memory.load_range(offset, size)?.into(); - current_call_frame.returndata = return_data; + current_call_frame.returndata = + memory::load_range(&mut current_call_frame.memory, offset, size)? + .to_vec() + .into(); Ok(OpcodeSuccess::Result(ResultReason::Return)) } @@ -254,15 +260,11 @@ impl VM { InternalError::ArithmeticOperationOverflow, ))?; let new_memory_size = new_memory_size_for_args.max(new_memory_size_for_return_data); - let current_memory_size = current_call_frame.memory.data.len(); + let current_memory_size = current_call_frame.memory.len(); self.increase_consumed_gas( current_call_frame, - gas_cost::delegatecall( - new_memory_size.into(), - current_memory_size.into(), - address_was_cold, - )?, + gas_cost::delegatecall(new_memory_size, current_memory_size, address_was_cold)?, )?; self.generic_call( @@ -328,15 +330,11 @@ impl VM { InternalError::ArithmeticOperationOverflow, ))?; let new_memory_size = new_memory_size_for_args.max(new_memory_size_for_return_data); - let current_memory_size = current_call_frame.memory.data.len(); + let current_memory_size = current_call_frame.memory.len(); self.increase_consumed_gas( current_call_frame, - gas_cost::staticcall( - new_memory_size.into(), - current_memory_size.into(), - address_was_cold, - )?, + gas_cost::staticcall(new_memory_size, current_memory_size, address_was_cold)?, )?; let value = U256::zero(); @@ -367,7 +365,7 @@ impl VM { current_call_frame: &mut CallFrame, ) -> Result { let value_in_wei_to_send = current_call_frame.stack.pop()?; - let code_offset_in_memory = current_call_frame + let code_offset_in_memory: usize = current_call_frame .stack .pop()? .try_into() @@ -378,15 +376,18 @@ impl VM { .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; - // Gas Cost - let gas_cost = gas_cost::create( + self.increase_consumed_gas( current_call_frame, - code_offset_in_memory, - code_size_in_memory, - ) - .map_err(VMError::OutOfGas)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; + gas_cost::create( + code_offset_in_memory + .checked_add(code_size_in_memory) + .ok_or(VMError::OutOfOffset)? + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?, + current_call_frame.memory.len(), + code_size_in_memory, + )?, + )?; self.create( value_in_wei_to_send, @@ -416,15 +417,18 @@ impl VM { .map_err(|_err| VMError::VeryLargeNumber)?; let salt = current_call_frame.stack.pop()?; - // Gas Cost - let gas_cost = gas_cost::create_2( + self.increase_consumed_gas( current_call_frame, - code_offset_in_memory, - code_size_in_memory, - ) - .map_err(VMError::OutOfGas)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; + gas_cost::create_2( + code_offset_in_memory + .checked_add(code_size_in_memory) + .ok_or(VMError::OutOfOffset)? + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::OutOfOffset)?, + current_call_frame.memory.len(), + code_size_in_memory, + )?, + )?; self.create( value_in_wei_to_send, @@ -457,11 +461,18 @@ impl VM { .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; - let gas_cost = current_call_frame.memory.expansion_cost(offset, size)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; + let new_memory_size = offset + .checked_add(size) + .ok_or(VMError::OutOfGas(OutOfGasError::GasCostOverflow))?; + self.increase_consumed_gas( + current_call_frame, + memory::expansion_cost(new_memory_size, current_call_frame.memory.len())?.into(), + )?; - current_call_frame.returndata = current_call_frame.memory.load_range(offset, size)?.into(); + current_call_frame.returndata = + memory::load_range(&mut current_call_frame.memory, offset, size)? + .to_vec() + .into(); Err(VMError::RevertOpcode) } diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index aa8fd6b9c..47253e952 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -12,6 +12,7 @@ use crate::{ TxValidationError, VMError, }, gas_cost::{self, fake_exponential, BLOB_GAS_PER_BLOB, CREATE_BASE_COST}, + memory, opcodes::Opcode, AccountInfo, }; @@ -138,15 +139,13 @@ impl VM { let created_contract = Account::new(value, calldata.clone(), 1, HashMap::new()); cache::insert_account(&mut cache, new_contract_address, created_contract); - let bytecode: Bytes = calldata.clone(); - let initial_call_frame = CallFrame::new( env.origin, new_contract_address, new_contract_address, - bytecode, + Bytes::new(), // Bytecode is assigned after passing validations. value, - Bytes::new(), // Contract creation does not have calldata + calldata, // Calldata is removed after passing validations. false, env.gas_limit, U256::zero(), @@ -378,12 +377,7 @@ impl VM { .checked_add(CREATE_BASE_COST) .ok_or(OutOfGasError::ConsumedGasOverflow)?; - let number_of_words: u64 = initial_call_frame - .calldata - .chunks(WORD_SIZE) - .len() - .try_into() - .map_err(|_| InternalError::ConversionError)?; + let number_of_words = initial_call_frame.calldata.len().div_ceil(WORD_SIZE); intrinsic_gas = intrinsic_gas .checked_add( @@ -493,7 +487,7 @@ impl VM { // (4) INITCODE_SIZE_EXCEEDED if self.is_create() { // INITCODE_SIZE_EXCEEDED - if initial_call_frame.bytecode.len() > INIT_CODE_MAX_SIZE { + if initial_call_frame.calldata.len() > INIT_CODE_MAX_SIZE { return Err(VMError::TxValidation( TxValidationError::InitcodeSizeExceeded, )); @@ -578,6 +572,12 @@ impl VM { } } + if self.is_create() { + // Assign bytecode to context and empty calldata + initial_call_frame.bytecode = initial_call_frame.calldata.clone(); + initial_call_frame.calldata = Bytes::new(); + } + Ok(()) } @@ -774,10 +774,8 @@ impl VM { // self.cache.increment_account_nonce(&code_address); // Internal call doesn't increment account nonce. - let calldata = current_call_frame - .memory - .load_range(args_offset, args_size)? - .into(); + let calldata = + memory::load_range(&mut current_call_frame.memory, args_offset, args_size)?.to_vec(); // I don't know if this gas limit should be calculated before or after consuming gas let mut potential_remaining_gas = current_call_frame @@ -802,7 +800,7 @@ impl VM { code_address, code_account_info.bytecode, value, - calldata, + calldata.into(), is_static, gas_limit, U256::zero(), @@ -827,9 +825,12 @@ impl VM { .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 - .store_n_bytes(ret_offset, &tx_report.output, ret_size)?; + memory::try_store_range( + &mut current_call_frame.memory, + ret_offset, + ret_size, + &tx_report.output, + )?; current_call_frame.sub_return_data = tx_report.output; // What to do, depending on TxResult @@ -944,9 +945,12 @@ impl VM { }; let code = Bytes::from( - current_call_frame - .memory - .load_range(code_offset_in_memory, code_size_in_memory)?, + memory::load_range( + &mut current_call_frame.memory, + code_offset_in_memory, + code_size_in_memory, + )? + .to_vec(), ); let new_address = match salt { diff --git a/crates/vm/levm/tests/edge_case_tests.rs b/crates/vm/levm/tests/edge_case_tests.rs index 6a4456e3d..f390ed11c 100644 --- a/crates/vm/levm/tests/edge_case_tests.rs +++ b/crates/vm/levm/tests/edge_case_tests.rs @@ -1,3 +1,5 @@ +// Here add #![allow(clippy::)] if necessary, we don't want to lint the test code. + use std::str::FromStr; use bytes::Bytes; diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index 1b05547de..534be1910 100644 --- a/crates/vm/levm/tests/tests.rs +++ b/crates/vm/levm/tests/tests.rs @@ -8,7 +8,7 @@ use ethrex_levm::{ constants::*, db::{cache, CacheDB, Db}, errors::{TxResult, VMError}, - gas_cost, + gas_cost, memory, 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}, @@ -1490,7 +1490,8 @@ fn mstore_saves_correct_value() { let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame).unwrap(); - let stored_value = vm.current_call_frame_mut().unwrap().memory.load(0).unwrap(); + let stored_value = + memory::load_word(&mut vm.current_call_frame_mut().unwrap().memory, 0).unwrap(); assert_eq!(stored_value, U256::from(0x33333)); @@ -1513,7 +1514,8 @@ fn mstore8() { let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame).unwrap(); - let stored_value = vm.current_call_frame_mut().unwrap().memory.load(0).unwrap(); + let stored_value = + memory::load_word(&mut vm.current_call_frame_mut().unwrap().memory, 0).unwrap(); let mut value_bytes = [0u8; 32]; stored_value.to_big_endian(&mut value_bytes); @@ -1541,12 +1543,8 @@ fn mcopy() { let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame).unwrap(); - let copied_value = vm - .current_call_frame_mut() - .unwrap() - .memory - .load(64) - .unwrap(); + let copied_value = + memory::load_word(&mut vm.current_call_frame_mut().unwrap().memory, 64).unwrap(); assert_eq!(copied_value, U256::from(0x33333)); let memory_size = vm.current_call_frame_mut().unwrap().stack.pop().unwrap(); @@ -1789,12 +1787,10 @@ fn call_changes_callframe_and_stores() { let ret_size = current_call_frame.sub_return_data_size; // Return data of the sub-context will be in the memory position of the current context reserved for that purpose (ret_offset and ret_size) - let return_data = current_call_frame - .memory - .load_range(ret_offset, ret_size) - .unwrap(); + let return_data = + memory::load_range(&mut current_call_frame.memory, ret_offset, ret_size).unwrap(); - assert_eq!(U256::from_big_endian(&return_data), U256::from(0xAAAAAAA)); + assert_eq!(U256::from_big_endian(return_data), U256::from(0xAAAAAAA)); } #[test] @@ -1957,12 +1953,10 @@ fn staticcall_changes_callframe_is_static() { let ret_offset = 0; let ret_size = 32; - let return_data = current_call_frame - .memory - .load_range(ret_offset, ret_size) - .unwrap(); + let return_data = + memory::load_range(&mut current_call_frame.memory, ret_offset, ret_size).unwrap(); - assert_eq!(U256::from_big_endian(&return_data), U256::from(0xAAAAAAA)); + assert_eq!(U256::from_big_endian(return_data), U256::from(0xAAAAAAA)); assert!(current_call_frame.is_static); } @@ -2489,8 +2483,14 @@ 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()); + assert_eq!( + expected_data, + memory::load_word(&mut current_call_frame.memory, 0).unwrap() + ); + assert_eq!( + expected_data, + memory::load_word(&mut current_call_frame.memory, 0).unwrap() + ); } #[test] @@ -2528,7 +2528,7 @@ fn calldatacopy() { vm.execute(&mut current_call_frame).unwrap(); let current_call_frame = vm.current_call_frame_mut().unwrap(); - let memory = current_call_frame.memory.load_range(0, 2).unwrap(); + let memory = memory::load_range(&mut current_call_frame.memory, 0, 2).unwrap(); assert_eq!(memory, vec![0x22, 0x33]); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 18); } @@ -2568,7 +2568,7 @@ fn returndatacopy() { vm.execute(&mut current_call_frame).unwrap(); let current_call_frame = vm.current_call_frame_mut().unwrap(); - let memory = current_call_frame.memory.load_range(0, 2).unwrap(); + let memory = memory::load_range(&mut current_call_frame.memory, 0, 2).unwrap(); assert_eq!(memory, vec![0xBB, 0xCC]); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 18); } @@ -2618,7 +2618,7 @@ fn returndatacopy_being_set_by_parent() { let current_call_frame = vm.current_call_frame_mut().unwrap(); - let result = current_call_frame.memory.load(0).unwrap(); + let result = memory::load_word(&mut current_call_frame.memory, 0).unwrap(); assert_eq!(result, U256::from(0xAAAAAAA)); } @@ -4485,7 +4485,7 @@ fn codecopy_op() { vm.execute(&mut current_call_frame).unwrap(); assert_eq!( - vm.current_call_frame_mut().unwrap().memory.load(0).unwrap(), + memory::load_word(&mut vm.current_call_frame_mut().unwrap().memory, 0).unwrap(), expected_memory ); assert_eq!( @@ -4565,11 +4565,7 @@ fn extcodecopy_existing_account() { let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame).unwrap(); assert_eq!( - vm.current_call_frame_mut() - .unwrap() - .memory - .load_range(0, size) - .unwrap(), + memory::load_range(&mut vm.current_call_frame_mut().unwrap().memory, 0, size).unwrap(), vec![0x60] ); assert_eq!(vm.env.consumed_gas, 23616.into()); @@ -4594,11 +4590,7 @@ fn extcodecopy_non_existing_account() { let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame).unwrap(); assert_eq!( - vm.current_call_frame_mut() - .unwrap() - .memory - .load_range(0, size) - .unwrap(), + memory::load_range(&mut vm.current_call_frame_mut().unwrap().memory, 0, size).unwrap(), vec![0; size] ); assert_eq!(vm.env.consumed_gas, 23616.into());