diff --git a/Cargo.lock b/Cargo.lock index a8a72552de..3d28071ff1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,7 +132,7 @@ dependencies = [ "derive_more 1.0.0", "once_cell", "serde", - "sha2", + "sha2 0.10.8", ] [[package]] @@ -432,7 +432,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f99acddb34000d104961897dbb0240298e8b775a7efffb9fda2a1a3efedd65b3" dependencies = [ "alloy-json-rpc", - "base64", + "base64 0.22.1", "futures-util", "futures-utils-wasm", "serde", @@ -651,6 +651,12 @@ dependencies = [ "rand", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.4" @@ -744,6 +750,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.22.1" @@ -802,6 +814,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -1247,7 +1268,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", "crypto-common", "subtle", @@ -1460,9 +1481,24 @@ name = "example-database-components" version = "0.0.0" dependencies = [ "auto_impl", + "derive_more 1.0.0", "revm", ] +[[package]] +name = "example-erc20-gas" +version = "0.0.0" +dependencies = [ + "alloy-provider", + "alloy-sol-types", + "alloy-transport-http", + "anyhow", + "reqwest", + "revm", + "revm-database", + "tokio", +] + [[package]] name = "example-uniswap-get-reserves" version = "0.0.0" @@ -2110,7 +2146,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "sha2", + "sha2 0.10.8", "signature", ] @@ -2141,7 +2177,7 @@ checksum = "0850eb19206463a61bede4f7b7e6b21731807137619044b1f3c287ebcfe2b3b0" dependencies = [ "ff", "hex", - "sha2", + "sha2 0.10.8", "sp1_bls12_381", "spin", ] @@ -2167,6 +2203,52 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64 0.13.1", + "digest 0.9.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand", + "serde", + "sha2 0.9.9", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2397,6 +2479,12 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "open-fastrlp" version = "0.1.4" @@ -2475,7 +2563,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.8", ] [[package]] @@ -2923,7 +3011,7 @@ version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", @@ -3144,6 +3232,7 @@ dependencies = [ "eyre", "k256", "kzg-rs", + "libsecp256k1", "once_cell", "p256", "rand", @@ -3156,7 +3245,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "sha2", + "sha2 0.10.8", "substrate-bn", ] @@ -3444,7 +3533,7 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64", + "base64 0.22.1", "rustls-pki-types", ] @@ -3673,6 +3762,19 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.8" diff --git a/Cargo.toml b/Cargo.toml index 91d9fcc32b..ccf0f86cf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ members = [ "examples/database_components", "examples/uniswap_get_reserves", "examples/uniswap_v2_usdc_swap", + "examples/erc20_gas", #"examples/custom_opcodes", ] resolver = "2" diff --git a/bins/revme/src/cmd/statetest/runner.rs b/bins/revme/src/cmd/statetest/runner.rs index bfdc5c0ae3..656fb8e9d8 100644 --- a/bins/revme/src/cmd/statetest/runner.rs +++ b/bins/revme/src/cmd/statetest/runner.rs @@ -286,11 +286,16 @@ pub fn execute_test_suite( cfg.chain_id = 1; // Block env - block.number = unit.env.current_number; + block.number = unit.env.current_number.try_into().unwrap_or(u64::MAX); block.beneficiary = unit.env.current_coinbase; - block.timestamp = unit.env.current_timestamp; - block.gas_limit = unit.env.current_gas_limit; - block.basefee = unit.env.current_base_fee.unwrap_or_default(); + block.timestamp = unit.env.current_timestamp.try_into().unwrap_or(u64::MAX); + block.gas_limit = unit.env.current_gas_limit.try_into().unwrap_or(u64::MAX); + block.basefee = unit + .env + .current_base_fee + .unwrap_or_default() + .try_into() + .unwrap_or(u64::MAX); block.difficulty = unit.env.current_difficulty; // After the Merge prevrandao replaces mix_hash field in block and replaced difficulty opcode in EVM. block.prevrandao = unit.env.current_random; diff --git a/crates/context/interface/src/block.rs b/crates/context/interface/src/block.rs index 956d6dfe60..994e1e26e4 100644 --- a/crates/context/interface/src/block.rs +++ b/crates/context/interface/src/block.rs @@ -10,28 +10,28 @@ use std::boxed::Box; #[auto_impl(&, &mut, Box, Arc)] pub trait Block { /// The number of ancestor blocks of this block (block height). - fn number(&self) -> &U256; + fn number(&self) -> u64; /// Beneficiary (Coinbase, miner) is a address that have signed the block. /// /// This is the receiver address of priority gas rewards. - fn beneficiary(&self) -> &Address; + fn beneficiary(&self) -> Address; /// The timestamp of the block in seconds since the UNIX epoch. - fn timestamp(&self) -> &U256; + fn timestamp(&self) -> u64; /// The gas limit of the block. - fn gas_limit(&self) -> &U256; + fn gas_limit(&self) -> u64; /// The base fee per gas, added in the London upgrade with [EIP-1559]. /// /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 - fn basefee(&self) -> &U256; + fn basefee(&self) -> u64; /// The difficulty of the block. /// /// Unused after the Paris (AKA the merge) upgrade, and replaced by `prevrandao`. - fn difficulty(&self) -> &U256; + fn difficulty(&self) -> U256; /// The output of the randomness beacon provided by the beacon chain. /// @@ -40,7 +40,7 @@ pub trait Block { /// Note: `prevrandao` can be found in a block in place of `mix_hash`. /// /// [EIP-4399]: https://eips.ethereum.org/EIPS/eip-4399 - fn prevrandao(&self) -> Option<&B256>; + fn prevrandao(&self) -> Option; /// Excess blob gas and blob gasprice. /// See also [`calc_excess_blob_gas`] @@ -49,7 +49,7 @@ pub trait Block { /// Incorporated as part of the Cancun upgrade via [EIP-4844]. /// /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 - fn blob_excess_gas_and_price(&self) -> Option<&BlobExcessGasAndPrice>; + fn blob_excess_gas_and_price(&self) -> Option; /// See [EIP-4844] and [`calc_blob_gasprice`]. /// diff --git a/crates/context/interface/src/block/blob.rs b/crates/context/interface/src/block/blob.rs index 2ee4b7fac9..da1aa40a1e 100644 --- a/crates/context/interface/src/block/blob.rs +++ b/crates/context/interface/src/block/blob.rs @@ -7,7 +7,7 @@ use specification::eip4844::{ /// Incorporated as part of the Cancun upgrade via [EIP-4844]. /// /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BlobExcessGasAndPrice { /// The excess blob gas of the block diff --git a/crates/context/interface/src/journaled_state.rs b/crates/context/interface/src/journaled_state.rs index 7ec3f4840a..c4eee4717d 100644 --- a/crates/context/interface/src/journaled_state.rs +++ b/crates/context/interface/src/journaled_state.rs @@ -1,6 +1,6 @@ use core::ops::{Deref, DerefMut}; use database_interface::{Database, DatabaseGetter}; -use primitives::{Address, Log, B256, U256}; +use primitives::{Address, HashSet, Log, B256, U256}; use specification::hardfork::SpecId; use state::{Account, Bytecode}; use std::boxed::Box; @@ -17,10 +17,10 @@ pub trait Journal { fn new(database: Self::Database) -> Self; /// Returns the database. - fn db(&self) -> &Self::Database; + fn db_ref(&self) -> &Self::Database; /// Returns the mutable database. - fn db_mut(&mut self) -> &mut Self::Database; + fn db(&mut self) -> &mut Self::Database; /// Returns the storage value from Journal state. /// @@ -63,6 +63,10 @@ pub trait Journal { fn warm_account(&mut self, address: Address); + fn warm_precompiles(&mut self, addresses: HashSet
); + + fn precompile_addresses(&self) -> &HashSet
; + fn set_spec_id(&mut self, spec_id: SpecId); fn touch_account(&mut self, address: Address); diff --git a/crates/context/interface/src/result.rs b/crates/context/interface/src/result.rs index ea79bdfb78..dafd85ccc3 100644 --- a/crates/context/interface/src/result.rs +++ b/crates/context/interface/src/result.rs @@ -54,6 +54,15 @@ impl ExecutionResult { matches!(self, Self::Success { .. }) } + /// Returns created address if execution is Create transaction + /// and Contract was created. + pub fn created_address(&self) -> Option
{ + match self { + Self::Success { output, .. } => output.address().cloned(), + _ => None, + } + } + /// Returns true if execution result is a Halt. pub fn is_halt(&self) -> bool { matches!(self, Self::Halt { .. }) @@ -175,9 +184,9 @@ impl FromStringError for EVMError { } } -impl> From for EVMError { +impl From for EVMError { fn from(value: InvalidTransaction) -> Self { - Self::Transaction(value.into()) + Self::Transaction(value) } } diff --git a/crates/context/interface/src/transaction.rs b/crates/context/interface/src/transaction.rs index dc70c29010..fa69e5904f 100644 --- a/crates/context/interface/src/transaction.rs +++ b/crates/context/interface/src/transaction.rs @@ -19,7 +19,7 @@ pub use transaction_type::TransactionType; use auto_impl::auto_impl; use core::cmp::min; use core::fmt::Debug; -use primitives::{TxKind, U256}; +use primitives::TxKind; use std::boxed::Box; /// Transaction validity error types. @@ -105,11 +105,11 @@ pub trait Transaction { /// Returns effective gas price is gas price field for Legacy and Eip2930 transaction. /// /// While for transactions after Eip1559 it is minimum of max_fee and `base + max_priority_fee`. - fn effective_gas_price(&self, base_fee: U256) -> U256 { + fn effective_gas_price(&self, base_fee: u128) -> u128 { let tx_type = self.tx_type().into(); let (max_fee, max_priority_fee) = match tx_type { - TransactionType::Legacy => return U256::from(self.legacy().gas_price()), - TransactionType::Eip2930 => return U256::from(self.eip2930().gas_price()), + TransactionType::Legacy => return self.legacy().gas_price(), + TransactionType::Eip2930 => return self.eip2930().gas_price(), TransactionType::Eip1559 => ( self.eip1559().max_fee_per_gas(), self.eip1559().max_priority_fee_per_gas(), @@ -125,7 +125,7 @@ pub trait Transaction { TransactionType::Custom => unimplemented!("Custom tx not supported"), }; - min(U256::from(max_fee), base_fee + U256::from(max_priority_fee)) + min(max_fee, base_fee.saturating_add(max_priority_fee)) } /// Returns transaction kind. diff --git a/crates/context/src/block.rs b/crates/context/src/block.rs index 48cfad02ee..987cbbacab 100644 --- a/crates/context/src/block.rs +++ b/crates/context/src/block.rs @@ -6,20 +6,20 @@ use primitives::{Address, B256, U256}; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BlockEnv { /// The number of ancestor blocks of this block (block height) - pub number: U256, + pub number: u64, /// Beneficiary (Coinbase or miner) is a address that have signed the block /// /// This is the receiver address of all the gas spent in the block. pub beneficiary: Address, /// The timestamp of the block in seconds since the UNIX epoch - pub timestamp: U256, + pub timestamp: u64, /// The gas limit of the block - pub gas_limit: U256, + pub gas_limit: u64, /// The base fee per gas, added in the London upgrade with [EIP-1559] /// /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 - pub basefee: U256, + pub basefee: u64, /// The difficulty of the block /// /// Unused after the Paris (AKA the merge) upgrade, and replaced by `prevrandao`. @@ -53,54 +53,54 @@ impl BlockEnv { impl Block for BlockEnv { #[inline] - fn number(&self) -> &U256 { - &self.number + fn number(&self) -> u64 { + self.number } #[inline] - fn beneficiary(&self) -> &Address { - &self.beneficiary + fn beneficiary(&self) -> Address { + self.beneficiary } #[inline] - fn timestamp(&self) -> &U256 { - &self.timestamp + fn timestamp(&self) -> u64 { + self.timestamp } #[inline] - fn gas_limit(&self) -> &U256 { - &self.gas_limit + fn gas_limit(&self) -> u64 { + self.gas_limit } #[inline] - fn basefee(&self) -> &U256 { - &self.basefee + fn basefee(&self) -> u64 { + self.basefee } #[inline] - fn difficulty(&self) -> &U256 { - &self.difficulty + fn difficulty(&self) -> U256 { + self.difficulty } #[inline] - fn prevrandao(&self) -> Option<&B256> { - self.prevrandao.as_ref() + fn prevrandao(&self) -> Option { + self.prevrandao } #[inline] - fn blob_excess_gas_and_price(&self) -> Option<&BlobExcessGasAndPrice> { - self.blob_excess_gas_and_price.as_ref() + fn blob_excess_gas_and_price(&self) -> Option { + self.blob_excess_gas_and_price } } impl Default for BlockEnv { fn default() -> Self { Self { - number: U256::ZERO, + number: 0, beneficiary: Address::ZERO, - timestamp: U256::from(1), - gas_limit: U256::MAX, - basefee: U256::ZERO, + timestamp: 1, + gas_limit: u64::MAX, + basefee: 0, difficulty: U256::ZERO, prevrandao: Some(B256::ZERO), blob_excess_gas_and_price: Some(BlobExcessGasAndPrice::new(0)), diff --git a/crates/context/src/context.rs b/crates/context/src/context.rs index 21383a0713..9979c5b520 100644 --- a/crates/context/src/context.rs +++ b/crates/context/src/context.rs @@ -10,7 +10,7 @@ use context_interface::{ }; use database_interface::{Database, EmptyDB}; use derive_where::derive_where; -use interpreter::{as_u64_saturated, Host, SStoreResult, SelfDestructResult, StateLoad}; +use interpreter::{Host, SStoreResult, SelfDestructResult, StateLoad}; use primitives::{Address, Bytes, Log, B256, BLOCK_HASH_HISTORY, U256}; use specification::hardfork::SpecId; @@ -313,7 +313,7 @@ where where F: FnOnce(&mut DB), { - f(self.journaled_state.db_mut()); + f(self.journaled_state.db()); } pub fn modify_journal(&mut self, f: F) @@ -381,7 +381,7 @@ where JOURNAL: Journal, { fn block_hash(&mut self, requested_number: u64) -> Option { - let block_number = as_u64_saturated!(*self.block().number()); + let block_number = self.block().number(); let Some(diff) = block_number.checked_sub(requested_number) else { return Some(B256::ZERO); @@ -395,7 +395,7 @@ where if diff <= BLOCK_HASH_HISTORY { return self .journaled_state - .db_mut() + .db() .block_hash(requested_number) .map_err(|e| self.error = Err(e)) .ok(); @@ -508,7 +508,11 @@ where type Database = DB; fn db(&mut self) -> &mut Self::Database { - self.journaled_state.db_mut() + self.journaled_state.db() + } + + fn db_ref(&self) -> &Self::Database { + self.journaled_state.db_ref() } } diff --git a/crates/context/src/journaled_state.rs b/crates/context/src/journaled_state.rs index 7badf33c8c..9926f3a098 100644 --- a/crates/context/src/journaled_state.rs +++ b/crates/context/src/journaled_state.rs @@ -52,6 +52,8 @@ pub struct JournaledState { /// Note that this not include newly loaded accounts, account and storage /// is considered warm if it is found in the `State`. pub warm_preloaded_addresses: HashSet
, + /// Precompile addresses + pub precompiles: HashSet
, } impl Journal for JournaledState { @@ -63,11 +65,11 @@ impl Journal for JournaledState { Self::new(SpecId::LATEST, database) } - fn db(&self) -> &Self::Database { + fn db_ref(&self) -> &Self::Database { &self.database } - fn db_mut(&mut self) -> &mut Self::Database { + fn db(&mut self) -> &mut Self::Database { &mut self.database } @@ -112,6 +114,17 @@ impl Journal for JournaledState { self.warm_preloaded_addresses.insert(address); } + fn warm_precompiles(&mut self, address: HashSet
) { + self.precompiles = address; + self.warm_preloaded_addresses + .extend(self.precompiles.iter()); + } + + #[inline] + fn precompile_addresses(&self) -> &HashSet
{ + &self.precompiles + } + /// Returns call depth. #[inline] fn depth(&self) -> usize { @@ -212,6 +225,7 @@ impl Journal for JournaledState { spec: _, database: _, warm_preloaded_addresses: _, + precompiles: _, } = self; *transient_storage = TransientStorage::default(); @@ -243,6 +257,7 @@ impl JournaledState { depth: 0, spec, warm_preloaded_addresses: HashSet::default(), + precompiles: HashSet::default(), } } diff --git a/crates/database/interface/src/async_db.rs b/crates/database/interface/src/async_db.rs index 2b99a6bff2..0b85216e71 100644 --- a/crates/database/interface/src/async_db.rs +++ b/crates/database/interface/src/async_db.rs @@ -1,11 +1,11 @@ use core::future::Future; +use crate::{DBErrorMarker, Database, DatabaseRef}; +use core::error::Error; use primitives::{Address, B256, U256}; use state::{AccountInfo, Bytecode}; use tokio::runtime::{Handle, Runtime}; -use crate::{DBErrorMarker, Database, DatabaseRef}; - /// The async EVM database interface /// /// Contains the same methods as [Database], but it returns [Future] type instead. @@ -13,7 +13,7 @@ use crate::{DBErrorMarker, Database, DatabaseRef}; /// Use [WrapDatabaseAsync] to provide [Database] implementation for a type that only implements this trait. pub trait DatabaseAsync { /// The database error type - type Error: Send + DBErrorMarker; + type Error: Send + DBErrorMarker + Error; /// Gets basic account information. fn basic_async( @@ -48,7 +48,7 @@ pub trait DatabaseAsync { /// Use [WrapDatabaseAsync] to provide [DatabaseRef] implementation for a type that only implements this trait. pub trait DatabaseAsyncRef { /// The database error type - type Error: Send + DBErrorMarker; + type Error: Send + DBErrorMarker + Error; /// Gets basic account information. fn basic_async_ref( diff --git a/crates/database/interface/src/empty_db.rs b/crates/database/interface/src/empty_db.rs index ec373139bd..db810a7872 100644 --- a/crates/database/interface/src/empty_db.rs +++ b/crates/database/interface/src/empty_db.rs @@ -1,4 +1,5 @@ use crate::{DBErrorMarker, Database, DatabaseRef}; +use core::error::Error; use core::{convert::Infallible, fmt, marker::PhantomData}; use primitives::{keccak256, Address, B256, U256}; use state::{AccountInfo, Bytecode}; @@ -52,7 +53,7 @@ impl EmptyDBTyped { } } -impl Database for EmptyDBTyped { +impl Database for EmptyDBTyped { type Error = E; #[inline] @@ -76,7 +77,7 @@ impl Database for EmptyDBTyped { } } -impl DatabaseRef for EmptyDBTyped { +impl DatabaseRef for EmptyDBTyped { type Error = E; #[inline] diff --git a/crates/database/interface/src/lib.rs b/crates/database/interface/src/lib.rs index d1678b6be5..a8772af3d4 100644 --- a/crates/database/interface/src/lib.rs +++ b/crates/database/interface/src/lib.rs @@ -8,6 +8,7 @@ extern crate alloc as std; use core::convert::Infallible; use auto_impl::auto_impl; +use core::error::Error; use primitives::{Address, HashMap, B256, U256}; use state::{Account, AccountInfo, Bytecode}; use std::string::String; @@ -35,7 +36,7 @@ impl DBErrorMarker for String {} #[auto_impl(&mut, Box)] pub trait Database { /// The database error type. - type Error: DBErrorMarker; + type Error: DBErrorMarker + Error; //type Bytecode: BytecodeTrait; /// Gets basic account information. @@ -67,7 +68,7 @@ pub trait DatabaseCommit { #[auto_impl(&, &mut, Box, Rc, Arc)] pub trait DatabaseRef { /// The database error type. - type Error: DBErrorMarker; + type Error: DBErrorMarker + Error; /// Gets basic account information. fn basic_ref(&self, address: Address) -> Result, Self::Error>; @@ -129,4 +130,6 @@ pub trait DatabaseGetter { type Database: Database; fn db(&mut self) -> &mut Self::Database; + + fn db_ref(&self) -> &Self::Database; } diff --git a/crates/database/src/alloydb.rs b/crates/database/src/alloydb.rs index 0ae8531ffb..8121acb7bf 100644 --- a/crates/database/src/alloydb.rs +++ b/crates/database/src/alloydb.rs @@ -7,15 +7,25 @@ use alloy_provider::{ Network, Provider, }; use alloy_transport::{Transport, TransportError}; +use core::error::Error; use database_interface::{async_db::DatabaseAsyncRef, DBErrorMarker}; use primitives::{Address, B256, U256}; use state::{AccountInfo, Bytecode}; +use std::fmt::Display; #[derive(Debug)] pub struct DBTransportError(pub TransportError); impl DBErrorMarker for DBTransportError {} +impl Display for DBTransportError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Transport error: {}", self.0) + } +} + +impl Error for DBTransportError {} + impl From for DBTransportError { fn from(e: TransportError) -> Self { Self(e) diff --git a/crates/database/src/states/state_builder.rs b/crates/database/src/states/state_builder.rs index 4ae5292d8d..fc4cd0c00c 100644 --- a/crates/database/src/states/state_builder.rs +++ b/crates/database/src/states/state_builder.rs @@ -85,7 +85,7 @@ impl StateBuilder { } /// With boxed version of database. - pub fn with_database_boxed( + pub fn with_database_boxed( self, database: DBBox<'_, Error>, ) -> StateBuilder> { diff --git a/crates/handler/interface/src/execution.rs b/crates/handler/interface/src/execution.rs index 5a15b118ea..19e4bd3e54 100644 --- a/crates/handler/interface/src/execution.rs +++ b/crates/handler/interface/src/execution.rs @@ -32,7 +32,7 @@ pub trait ExecutionHandler { let frame = frame_stack.last_mut().unwrap(); let call_or_result = frame.run(context)?; - let result = match call_or_result { + let mut result = match call_or_result { FrameOrResultGen::Frame(init) => match frame.init(context, init)? { FrameOrResultGen::Frame(new_frame) => { frame_stack.push(new_frame); @@ -49,6 +49,7 @@ pub trait ExecutionHandler { }; let Some(frame) = frame_stack.last_mut() else { + Self::Frame::final_return(context, &mut result)?; return self.last_frame_result(context, result); }; frame.return_result(context, result)?; diff --git a/crates/handler/interface/src/frame.rs b/crates/handler/interface/src/frame.rs index 9452c91eec..6012946dc1 100644 --- a/crates/handler/interface/src/frame.rs +++ b/crates/handler/interface/src/frame.rs @@ -12,6 +12,11 @@ pub trait Frame: Sized { frame_input: Self::FrameInit, ) -> Result, Self::Error>; + fn final_return( + context: &mut Self::Context, + result: &mut Self::FrameResult, + ) -> Result<(), Self::Error>; + fn init( &self, context: &mut Self::Context, diff --git a/crates/handler/src/frame.rs b/crates/handler/src/frame.rs index df37db4274..9b2e8f81fb 100644 --- a/crates/handler/src/frame.rs +++ b/crates/handler/src/frame.rs @@ -483,6 +483,13 @@ where Self::init_with_context(0, frame_input, memory, precompiles, instructions, context) } + fn final_return( + _context: &mut Self::Context, + _result: &mut Self::FrameResult, + ) -> Result<(), Self::Error> { + Ok(()) + } + fn init( &self, context: &mut CTX, diff --git a/crates/handler/src/post_execution.rs b/crates/handler/src/post_execution.rs index 217ce4ce17..2e95f67858 100644 --- a/crates/handler/src/post_execution.rs +++ b/crates/handler/src/post_execution.rs @@ -65,7 +65,7 @@ where context: &mut Self::Context, exec_result: &mut Self::ExecResult, ) -> Result<(), Self::Error> { - let basefee = *context.block().basefee(); + let basefee = context.block().basefee() as u128; let caller = context.tx().common_fields().caller(); let effective_gas_price = context.tx().effective_gas_price(basefee); let gas = exec_result.gas(); @@ -73,9 +73,13 @@ where // Return balance of not spend gas. let caller_account = context.journal().load_account(caller)?; - let reimbursed = effective_gas_price * U256::from(gas.remaining() + gas.refunded() as u64); - caller_account.data.info.balance = - caller_account.data.info.balance.saturating_add(reimbursed); + let reimbursed = + effective_gas_price.saturating_mul((gas.remaining() + gas.refunded() as u64) as u128); + caller_account.data.info.balance = caller_account + .data + .info + .balance + .saturating_add(U256::from(reimbursed)); Ok(()) } @@ -87,8 +91,8 @@ where ) -> Result<(), Self::Error> { let block = context.block(); let tx = context.tx(); - let beneficiary = *block.beneficiary(); - let basefee = *block.basefee(); + let beneficiary = block.beneficiary(); + let basefee = block.basefee() as u128; let effective_gas_price = tx.effective_gas_price(basefee); let gas = exec_result.gas(); @@ -104,9 +108,13 @@ where coinbase_account.data.mark_touch(); coinbase_account.data.info.balance = - coinbase_account.data.info.balance.saturating_add( - coinbase_gas_price * U256::from(gas.spent() - gas.refunded() as u64), - ); + coinbase_account + .data + .info + .balance + .saturating_add(U256::from( + coinbase_gas_price * (gas.spent() - gas.refunded() as u64) as u128, + )); Ok(()) } diff --git a/crates/handler/src/pre_execution.rs b/crates/handler/src/pre_execution.rs index ccda8ee2ae..ef44ce558a 100644 --- a/crates/handler/src/pre_execution.rs +++ b/crates/handler/src/pre_execution.rs @@ -49,7 +49,7 @@ where // Load coinbase // EIP-3651: Warm COINBASE. Starts the `COINBASE` address warm if spec.is_enabled_in(SpecId::SHANGHAI) { - let coinbase = *context.block().beneficiary(); + let coinbase = context.block().beneficiary(); context.journal().warm_account(coinbase); } @@ -83,17 +83,17 @@ where #[inline] fn deduct_caller(&self, context: &mut Self::Context) -> Result<(), Self::Error> { - let basefee = *context.block().basefee(); - let blob_price = U256::from(context.block().blob_gasprice().unwrap_or_default()); - let effective_gas_price = context.tx().effective_gas_price(basefee); + let basefee = context.block().basefee(); + let blob_price = context.block().blob_gasprice().unwrap_or_default(); + let effective_gas_price = context.tx().effective_gas_price(basefee as u128); // Subtract gas costs from the caller's account. // We need to saturate the gas cost to prevent underflow in case that `disable_balance_check` is enabled. - let mut gas_cost = U256::from(context.tx().common_fields().gas_limit()) - .saturating_mul(effective_gas_price); + let mut gas_cost = + (context.tx().common_fields().gas_limit() as u128).saturating_mul(effective_gas_price); // EIP-4844 if context.tx().tx_type().into() == TransactionType::Eip4844 { - let blob_gas = U256::from(context.tx().eip4844().total_blob_gas()); + let blob_gas = context.tx().eip4844().total_blob_gas() as u128; gas_cost = gas_cost.saturating_add(blob_price.saturating_mul(blob_gas)); } @@ -103,7 +103,10 @@ where // Load caller's account. let caller_account = context.journal().load_account(caller)?.data; // Set new caller account balance. - caller_account.info.balance = caller_account.info.balance.saturating_sub(gas_cost); + caller_account.info.balance = caller_account + .info + .balance + .saturating_sub(U256::from(gas_cost)); // Bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. if is_call { diff --git a/crates/handler/src/validation.rs b/crates/handler/src/validation.rs index 0b61c07ce9..08d1aff5dd 100644 --- a/crates/handler/src/validation.rs +++ b/crates/handler/src/validation.rs @@ -83,7 +83,7 @@ where pub fn validate_priority_fee_tx( max_fee: u128, max_priority_fee: u128, - base_fee: Option, + base_fee: Option, ) -> Result<(), InvalidTransaction> { if max_priority_fee > max_fee { // Or gas_max_fee for eip1559 @@ -92,10 +92,7 @@ pub fn validate_priority_fee_tx( // Check minimal cost against basefee if let Some(base_fee) = base_fee { - let effective_gas_price = cmp::min( - U256::from(max_fee), - base_fee.saturating_add(U256::from(max_priority_fee)), - ); + let effective_gas_price = cmp::min(max_fee, base_fee.saturating_add(max_priority_fee)); if effective_gas_price < base_fee { return Err(InvalidTransaction::GasPriceLessThanBasefee); } @@ -153,7 +150,7 @@ where let base_fee = if context.cfg().is_base_fee_check_disabled() { None } else { - Some(*context.block().basefee()) + Some(context.block().basefee() as u128) }; match tx_type { @@ -168,7 +165,7 @@ where } // Gas price must be at least the basefee. if let Some(base_fee) = base_fee { - if U256::from(tx.gas_price()) < base_fee { + if tx.gas_price() < base_fee { return Err(InvalidTransaction::GasPriceLessThanBasefee.into()); } } @@ -186,7 +183,7 @@ where // Gas price must be at least the basefee. if let Some(base_fee) = base_fee { - if U256::from(tx.gas_price()) < base_fee { + if tx.gas_price() < base_fee { return Err(InvalidTransaction::GasPriceLessThanBasefee.into()); } } @@ -266,7 +263,7 @@ where // Check if gas_limit is more than block_gas_limit if !context.cfg().is_block_gas_limit_disabled() - && U256::from(common_field.gas_limit()) > *context.block().gas_limit() + && common_field.gas_limit() > context.block().gas_limit() { return Err(InvalidTransaction::CallerGasLimitMoreThanBlock.into()); } diff --git a/crates/inspector/src/inspector.rs b/crates/inspector/src/inspector.rs index be25d415b3..7b7833789d 100644 --- a/crates/inspector/src/inspector.rs +++ b/crates/inspector/src/inspector.rs @@ -27,6 +27,7 @@ use revm::{ }, precompile::PrecompileErrors, primitives::{Address, Bytes, Log, B256, U256}, + state::EvmState, Context, Error, Evm, JournalEntry, }; use std::{rc::Rc, vec::Vec}; @@ -396,6 +397,10 @@ where fn db(&mut self) -> &mut Self::Database { self.inner.db() } + + fn db_ref(&self) -> &Self::Database { + self.inner.db_ref() + } } impl ErrorGetter for InspectorContext @@ -527,6 +532,10 @@ pub trait JournalExt { fn logs(&self) -> &[Log]; fn last_journal(&self) -> &[JournalEntry]; + + fn evm_state(&self) -> &EvmState; + + fn evm_state_mut(&mut self) -> &mut EvmState; } impl JournalExt for JournaledState { @@ -537,6 +546,14 @@ impl JournalExt for JournaledState { fn last_journal(&self) -> &[JournalEntry] { self.journal.last().expect("Journal is never empty") } + + fn evm_state(&self) -> &EvmState { + &self.state + } + + fn evm_state_mut(&mut self) -> &mut EvmState { + &mut self.state + } } #[auto_impl(&, &mut, Box, Arc)] @@ -699,10 +716,17 @@ where } _ => (), } - ret } + fn final_return( + context: &mut Self::Context, + result: &mut Self::FrameResult, + ) -> Result<(), Self::Error> { + context.frame_end(result); + Ok(()) + } + fn init( &self, context: &mut CTX, @@ -719,9 +743,6 @@ where if let Ok(FrameOrResultGen::Frame(frame)) = &mut ret { context.initialize_interp(&mut frame.eth_frame.interpreter); } - - // TODO : Handle last frame_end. MAKE a separate function for `last_return_result`. - ret } diff --git a/crates/interpreter/src/instructions/block_info.rs b/crates/interpreter/src/instructions/block_info.rs index cbbececaef..5b4d907fde 100644 --- a/crates/interpreter/src/instructions/block_info.rs +++ b/crates/interpreter/src/instructions/block_info.rs @@ -32,7 +32,7 @@ pub fn timestamp( host: &mut H, ) { gas!(interpreter, gas::BASE); - push!(interpreter, *host.block().timestamp()); + push!(interpreter, U256::from(host.block().timestamp())); } pub fn block_number( @@ -40,7 +40,7 @@ pub fn block_number( host: &mut H, ) { gas!(interpreter, gas::BASE); - push!(interpreter, *host.block().number()); + push!(interpreter, U256::from(host.block().number())); } pub fn difficulty( @@ -52,10 +52,10 @@ pub fn difficulty( // Unwrap is safe as this fields is checked in validation handler. push!( interpreter, - (*host.block().prevrandao().unwrap()).into_u256() + (host.block().prevrandao().unwrap()).into_u256() ); } else { - push!(interpreter, *host.block().difficulty()); + push!(interpreter, host.block().difficulty()); } } @@ -64,7 +64,7 @@ pub fn gaslimit( host: &mut H, ) { gas!(interpreter, gas::BASE); - push!(interpreter, *host.block().gas_limit()); + push!(interpreter, U256::from(host.block().gas_limit())); } /// EIP-3198: BASEFEE opcode @@ -74,7 +74,7 @@ pub fn basefee( ) { check!(interpreter, LONDON); gas!(interpreter, gas::BASE); - push!(interpreter, *host.block().basefee()); + push!(interpreter, U256::from(host.block().basefee())); } /// EIP-7516: BLOBBASEFEE opcode diff --git a/crates/interpreter/src/instructions/tx_info.rs b/crates/interpreter/src/instructions/tx_info.rs index 2b84b4822f..42b29bc804 100644 --- a/crates/interpreter/src/instructions/tx_info.rs +++ b/crates/interpreter/src/instructions/tx_info.rs @@ -12,8 +12,11 @@ pub fn gasprice( host: &mut H, ) { gas!(interpreter, gas::BASE); - let basefee = *host.block().basefee(); - push!(interpreter, host.tx().effective_gas_price(basefee)); + let basefee = host.block().basefee(); + push!( + interpreter, + U256::from(host.tx().effective_gas_price(basefee as u128)) + ); } pub fn origin( diff --git a/crates/optimism/src/handler.rs b/crates/optimism/src/handler.rs index 692b1ca5b7..616dbb809c 100644 --- a/crates/optimism/src/handler.rs +++ b/crates/optimism/src/handler.rs @@ -9,7 +9,6 @@ use crate::{ L1BlockInfoGetter, OpSpec, OpSpecId, OpTransactionError, OptimismHaltReason, BASE_FEE_RECIPIENT, L1_FEE_RECIPIENT, }; -use core::ops::Mul; use precompiles::OpPrecompileProvider; use revm::{ context_interface::{ @@ -70,7 +69,6 @@ where if tx_type == OpTransactionType::Deposit { let tx = context.op_tx().deposit(); // Do not allow for a system transaction to be processed if Regolith is enabled. - // TODO : Check if this is correct. if tx.is_system_transaction() && context.cfg().spec().is_enabled_in(OpSpecId::REGOLITH) { return Err(OpTransactionError::DepositSystemTxPostRegolith.into()); @@ -338,7 +336,7 @@ where // Transfer fee to coinbase/beneficiary. if !is_deposit { self.eth.reward_beneficiary(context, exec_result)?; - let basefee = *context.block().basefee(); + let basefee = context.block().basefee() as u128; // If the transaction is not a deposit transaction, fees are paid out // to both the Base Fee Vault as well as the L1 Fee Vault. @@ -360,8 +358,8 @@ where // Send the base fee of the transaction to the Base Fee Vault. let mut base_fee_vault_account = context.journal().load_account(BASE_FEE_RECIPIENT)?; base_fee_vault_account.mark_touch(); - base_fee_vault_account.info.balance += basefee.mul(U256::from( - exec_result.gas().spent() - exec_result.gas().refunded() as u64, + base_fee_vault_account.info.balance += U256::from(basefee.saturating_mul( + (exec_result.gas().spent() - exec_result.gas().refunded() as u64) as u128, )); } Ok(()) diff --git a/crates/optimism/src/transaction/abstraction.rs b/crates/optimism/src/transaction/abstraction.rs index 27fd621b0c..8ff2e9c5ab 100644 --- a/crates/optimism/src/transaction/abstraction.rs +++ b/crates/optimism/src/transaction/abstraction.rs @@ -114,7 +114,7 @@ impl Transaction for OpTransaction { } } - fn effective_gas_price(&self, base_fee: revm::primitives::U256) -> revm::primitives::U256 { + fn effective_gas_price(&self, base_fee: u128) -> u128 { match self { Self::Base { tx, .. } => tx.effective_gas_price(base_fee), Self::Deposit(_) => base_fee, @@ -218,7 +218,7 @@ mod tests { assert_eq!(op_tx.common_fields().gas_limit(), 0); assert_eq!(op_tx.kind(), revm::primitives::TxKind::Call(Address::ZERO)); // Verify gas related calculations - assert_eq!(op_tx.effective_gas_price(U256::from(100)), U256::from(100)); + assert_eq!(op_tx.effective_gas_price(100), 100); assert_eq!(op_tx.max_fee(), 0); } } diff --git a/crates/precompile/Cargo.toml b/crates/precompile/Cargo.toml index 968cfd13a4..2168bd1939 100644 --- a/crates/precompile/Cargo.toml +++ b/crates/precompile/Cargo.toml @@ -38,6 +38,9 @@ secp256k1 = { version = ">=0.28, <=0.29", default-features = false, features = [ "rand", "global-context", ], optional = true } +libsecp256k1 = { version = "0.7", default-features = false, package = "libsecp256k1", features = [ + "static-context", +], optional = true } # SHA2-256 and RIPEMD-160 sha2 = { version = "0.10", default-features = false } @@ -87,6 +90,7 @@ std = [ "sha2/std", "c-kzg?/std", "secp256k1?/std", + "libsecp256k1?/std", ] hashbrown = ["primitives/hashbrown"] asm-keccak = ["primitives/asm-keccak"] @@ -107,6 +111,7 @@ portable = ["c-kzg"] # The problem that `secp256k1` has is it fails to build for `wasm` target on Windows and Mac as it is c lib. # In Linux it passes. If you don't require to build wasm on win/mac, it is safe to use it and it is enabled by default. secp256k1 = ["dep:secp256k1"] +libsecp256k1 = ["dep:libsecp256k1"] # Enables the BLS12-381 precompiles. blst = ["dep:blst"] diff --git a/crates/precompile/src/secp256k1.rs b/crates/precompile/src/secp256k1.rs index 0cd459723e..6911027cf6 100644 --- a/crates/precompile/src/secp256k1.rs +++ b/crates/precompile/src/secp256k1.rs @@ -1,3 +1,9 @@ +#[cfg(feature = "secp256k1")] +pub mod bitcoin_secp256k1; +pub mod k256; +#[cfg(feature = "libsecp256k1")] +pub mod parity_libsecp256k1; + use crate::{ utilities::right_pad, PrecompileError, PrecompileOutput, PrecompileResult, PrecompileWithAddress, @@ -7,65 +13,6 @@ use primitives::{alloy_primitives::B512, Bytes, B256}; pub const ECRECOVER: PrecompileWithAddress = PrecompileWithAddress(crate::u64_to_address(1), ec_recover_run); -pub use self::secp256k1::ecrecover; - -#[cfg(not(feature = "secp256k1"))] -#[allow(clippy::module_inception)] -mod secp256k1 { - use k256::ecdsa::{Error, RecoveryId, Signature, VerifyingKey}; - use primitives::{alloy_primitives::B512, keccak256, B256}; - - pub fn ecrecover(sig: &B512, mut recid: u8, msg: &B256) -> Result { - // Parse signature - let mut sig = Signature::from_slice(sig.as_slice())?; - - // Normalize signature and flip recovery id if needed. - if let Some(sig_normalized) = sig.normalize_s() { - sig = sig_normalized; - recid ^= 1; - } - let recid = RecoveryId::from_byte(recid).expect("recovery ID is valid"); - - // Recover key - let recovered_key = VerifyingKey::recover_from_prehash(&msg[..], &sig, recid)?; - // Hash it - let mut hash = keccak256( - &recovered_key - .to_encoded_point(/* compress = */ false) - .as_bytes()[1..], - ); - - // Truncate to 20 bytes - hash[..12].fill(0); - Ok(hash) - } -} - -#[cfg(feature = "secp256k1")] -#[allow(clippy::module_inception)] -mod secp256k1 { - use primitives::{alloy_primitives::B512, keccak256, B256}; - use secp256k1::{ - ecdsa::{RecoverableSignature, RecoveryId}, - Message, SECP256K1, - }; - - // Silence the unused crate dependency warning. - use k256 as _; - - pub fn ecrecover(sig: &B512, recid: u8, msg: &B256) -> Result { - let recid = RecoveryId::from_i32(recid as i32).expect("recovery ID is valid"); - let sig = RecoverableSignature::from_compact(sig.as_slice(), recid)?; - - let msg = Message::from_digest(msg.0); - let public = SECP256K1.recover_ecdsa(&msg, &sig)?; - - let mut hash = keccak256(&public.serialize_uncompressed()[1..]); - hash[..12].fill(0); - Ok(hash) - } -} - pub fn ec_recover_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { const ECRECOVER_BASE: u64 = 3_000; @@ -84,8 +31,16 @@ pub fn ec_recover_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { let recid = input[63] - 27; let sig = <&B512>::try_from(&input[64..128]).unwrap(); - let out = secp256k1::ecrecover(sig, recid, msg) - .map(|o| o.to_vec().into()) - .unwrap_or_default(); + cfg_if::cfg_if! { + if #[cfg(feature = "secp256k1")] { + let res = bitcoin_secp256k1::ecrecover(sig, recid, msg); + } else if #[cfg(feature = "libsecp256k1")] { + let res = parity_libsecp256k1::ecrecover(sig, recid, msg); + } else { + let res = k256::ecrecover(sig, recid, msg); + } + }; + + let out = res.map(|o| o.to_vec().into()).unwrap_or_default(); Ok(PrecompileOutput::new(ECRECOVER_BASE, out)) } diff --git a/crates/precompile/src/secp256k1/bitcoin_secp256k1.rs b/crates/precompile/src/secp256k1/bitcoin_secp256k1.rs new file mode 100644 index 0000000000..7444dd61f1 --- /dev/null +++ b/crates/precompile/src/secp256k1/bitcoin_secp256k1.rs @@ -0,0 +1,20 @@ +use primitives::{alloy_primitives::B512, keccak256, B256}; +use secp256k1::{ + ecdsa::{RecoverableSignature, RecoveryId}, + Message, SECP256K1, +}; + +// Silence the unused crate dependency warning. +use k256 as _; + +pub fn ecrecover(sig: &B512, recid: u8, msg: &B256) -> Result { + let recid = RecoveryId::from_i32(recid as i32).expect("recovery ID is valid"); + let sig = RecoverableSignature::from_compact(sig.as_slice(), recid)?; + + let msg = Message::from_digest(msg.0); + let public = SECP256K1.recover_ecdsa(&msg, &sig)?; + + let mut hash = keccak256(&public.serialize_uncompressed()[1..]); + hash[..12].fill(0); + Ok(hash) +} diff --git a/crates/precompile/src/secp256k1/k256.rs b/crates/precompile/src/secp256k1/k256.rs new file mode 100644 index 0000000000..6e16952c06 --- /dev/null +++ b/crates/precompile/src/secp256k1/k256.rs @@ -0,0 +1,27 @@ +use k256::ecdsa::{Error, RecoveryId, Signature, VerifyingKey}; +use primitives::{alloy_primitives::B512, keccak256, B256}; + +pub fn ecrecover(sig: &B512, mut recid: u8, msg: &B256) -> Result { + // parse signature + let mut sig = Signature::from_slice(sig.as_slice())?; + + // normalize signature and flip recovery id if needed. + if let Some(sig_normalized) = sig.normalize_s() { + sig = sig_normalized; + recid ^= 1; + } + let recid = RecoveryId::from_byte(recid).expect("recovery ID is valid"); + + // recover key + let recovered_key = VerifyingKey::recover_from_prehash(&msg[..], &sig, recid)?; + // hash it + let mut hash = keccak256( + &recovered_key + .to_encoded_point(/* compress = */ false) + .as_bytes()[1..], + ); + + // truncate to 20 bytes + hash[..12].fill(0); + Ok(hash) +} diff --git a/crates/precompile/src/secp256k1/parity_libsecp256k1.rs b/crates/precompile/src/secp256k1/parity_libsecp256k1.rs new file mode 100644 index 0000000000..b9ea28de0f --- /dev/null +++ b/crates/precompile/src/secp256k1/parity_libsecp256k1.rs @@ -0,0 +1,15 @@ +use libsecp256k1::{recover, Error, Message, RecoveryId, Signature}; +use primitives::{alloy_primitives::B512, keccak256, B256}; + +pub fn ecrecover(sig: &B512, recid: u8, msg: &B256) -> Result { + let recid = RecoveryId::parse(recid)?; + let sig = Signature::parse_standard(sig)?; + let msg = Message::parse(msg.as_ref()); + + // uses static context. + let public = recover(&msg, &sig, &recid)?; + + let mut hash = keccak256(&public.serialize()[1..]); + hash[..12].fill(0); + Ok(hash) +} diff --git a/crates/revm/src/exec.rs b/crates/revm/src/exec.rs index 3963e97fb2..2d48b049eb 100644 --- a/crates/revm/src/exec.rs +++ b/crates/revm/src/exec.rs @@ -10,10 +10,20 @@ pub trait EvmExec { fn set_tx(&mut self, tx: Self::Transaction); fn exec(&mut self) -> Self::Output; + + fn exec_with_tx(&mut self, tx: Self::Transaction) -> Self::Output { + self.set_tx(tx); + self.exec() + } } pub trait EvmCommit: EvmExec { type CommitOutput; fn exec_commit(&mut self) -> Self::CommitOutput; + + fn exec_commit_with_tx(&mut self, tx: Self::Transaction) -> Self::CommitOutput { + self.set_tx(tx); + self.exec_commit() + } } diff --git a/examples/block_traces/src/main.rs b/examples/block_traces/src/main.rs index 1be8697258..d053aa58a2 100644 --- a/examples/block_traces/src/main.rs +++ b/examples/block_traces/src/main.rs @@ -85,17 +85,13 @@ async fn main() -> anyhow::Result<()> { Context::builder() .with_db(&mut state) .modify_block_chained(|b| { - b.number = U256::from(block.header.number); + b.number = block.header.number; b.beneficiary = block.header.beneficiary; - b.timestamp = U256::from(block.header.timestamp); + b.timestamp = block.header.timestamp; b.difficulty = block.header.difficulty; - b.gas_limit = U256::from(block.header.gas_limit); - b.basefee = block - .header - .base_fee_per_gas - .map(U256::from) - .unwrap_or_default(); + b.gas_limit = block.header.gas_limit; + b.basefee = block.header.base_fee_per_gas.unwrap_or_default(); }) .modify_cfg_chained(|c| { c.chain_id = chain_id; diff --git a/examples/database_components/Cargo.toml b/examples/database_components/Cargo.toml index 5e1fe4bd61..202adb075c 100644 --- a/examples/database_components/Cargo.toml +++ b/examples/database_components/Cargo.toml @@ -27,3 +27,4 @@ revm.workspace = true # mics auto_impl.workspace = true +derive_more = { version = "1.0", default-features = false } diff --git a/examples/database_components/src/block_hash.rs b/examples/database_components/src/block_hash.rs index dd95c3d172..858d48e177 100644 --- a/examples/database_components/src/block_hash.rs +++ b/examples/database_components/src/block_hash.rs @@ -1,13 +1,13 @@ //! BlockHash database component from [`revm::Database`] use auto_impl::auto_impl; -use core::ops::Deref; +use core::{error::Error as StdError, ops::Deref}; use revm::primitives::B256; use std::sync::Arc; #[auto_impl(&mut, Box)] pub trait BlockHash { - type Error; + type Error: StdError; /// Gets block hash by block number. fn block_hash(&mut self, number: u64) -> Result; @@ -15,7 +15,7 @@ pub trait BlockHash { #[auto_impl(&, &mut, Box, Rc, Arc)] pub trait BlockHashRef { - type Error; + type Error: StdError; /// Gets block hash by block number. fn block_hash(&self, number: u64) -> Result; diff --git a/examples/database_components/src/lib.rs b/examples/database_components/src/lib.rs index 16fc50aed8..e9ec25bb4c 100644 --- a/examples/database_components/src/lib.rs +++ b/examples/database_components/src/lib.rs @@ -8,6 +8,8 @@ pub mod state; pub use block_hash::{BlockHash, BlockHashRef}; pub use state::{State, StateRef}; +use core::{error::Error as StdError, fmt::Debug}; +use derive_more::Display; use revm::{ database_interface::{DBErrorMarker, Database, DatabaseCommit, DatabaseRef}, primitives::{Address, HashMap, B256, U256}, @@ -20,12 +22,14 @@ pub struct DatabaseComponents { pub block_hash: BH, } -#[derive(Debug)] +#[derive(Debug, Display)] pub enum DatabaseComponentError { State(SE), BlockHash(BHE), } +impl StdError for DatabaseComponentError {} + impl DBErrorMarker for DatabaseComponentError {} impl Database for DatabaseComponents { diff --git a/examples/database_components/src/state.rs b/examples/database_components/src/state.rs index 0499cce405..0e64bf07e8 100644 --- a/examples/database_components/src/state.rs +++ b/examples/database_components/src/state.rs @@ -6,11 +6,11 @@ use revm::{ primitives::{Address, B256, U256}, state::{AccountInfo, Bytecode}, }; -use std::sync::Arc; +use std::{error::Error as StdError, sync::Arc}; #[auto_impl(&mut, Box)] pub trait State { - type Error; + type Error: StdError; /// Gets basic account information. fn basic(&mut self, address: Address) -> Result, Self::Error>; @@ -24,7 +24,7 @@ pub trait State { #[auto_impl(&, &mut, Box, Rc, Arc)] pub trait StateRef { - type Error; + type Error: StdError; /// Gets basic account information. fn basic(&self, address: Address) -> Result, Self::Error>; diff --git a/examples/erc20_gas/Cargo.toml b/examples/erc20_gas/Cargo.toml new file mode 100644 index 0000000000..2d9648712b --- /dev/null +++ b/examples/erc20_gas/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "example-erc20-gas" +version = "0.0.0" +publish = false +authors.workspace = true +edition.workspace = true +keywords.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true + + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[lints.rust] +unreachable_pub = "warn" +unused_must_use = "deny" +rust_2018_idioms = "deny" + +[lints.rustdoc] +all = "warn" + +[dependencies] +revm.workspace = true +database = { workspace = true, features = ["std", "alloydb"] } + +# tokio +tokio = { version = "1.40", features = ["rt-multi-thread", "macros"] } + +# alloy +alloy-sol-types = { version = "0.8.2", default-features = false, features = [ + "std", +] } +alloy-transport-http = "0.6" +alloy-provider = "0.6" +reqwest = { version = "0.12" } +anyhow = "1.0.89" diff --git a/examples/erc20_gas/src/handlers/mod.rs b/examples/erc20_gas/src/handlers/mod.rs new file mode 100644 index 0000000000..cc25fb1e21 --- /dev/null +++ b/examples/erc20_gas/src/handlers/mod.rs @@ -0,0 +1,34 @@ +pub mod post_execution; +pub mod pre_execution; +pub mod validation; + +pub use post_execution::Erc20PostExecution; +pub use pre_execution::Erc20PreExecution; +pub use validation::Erc20Validation; + +use revm::{ + context::{block::BlockEnv, tx::TxEnv, CfgEnv, Context}, + context_interface::result::{EVMError, InvalidTransaction}, + database_interface::Database, + handler::{EthExecution, EthHandler}, + Evm, +}; + +pub type Erc20GasError = EVMError<::Error, InvalidTransaction>; + +pub type Erc20GasContext = Context; + +pub type CustomHandler< + CTX, + ERROR, + VAL = Erc20Validation, + PREEXEC = Erc20PreExecution, + EXEC = EthExecution, + POSTEXEC = Erc20PostExecution, +> = EthHandler; + +pub type CustomEvm = Evm< + Erc20GasError, + Erc20GasContext, + CustomHandler, Erc20GasError>, +>; diff --git a/examples/erc20_gas/src/handlers/post_execution.rs b/examples/erc20_gas/src/handlers/post_execution.rs new file mode 100644 index 0000000000..d145dfe1d3 --- /dev/null +++ b/examples/erc20_gas/src/handlers/post_execution.rs @@ -0,0 +1,109 @@ +use crate::{token_operation, TREASURY}; +use revm::{ + context::Cfg, + context_interface::{ + result::{HaltReason, HaltReasonTrait, InvalidHeader, InvalidTransaction, ResultAndState}, + Block, JournalDBError, Transaction, TransactionGetter, + }, + handler::{EthPostExecution, EthPostExecutionContext, EthPostExecutionError, FrameResult}, + handler_interface::PostExecutionHandler, + precompile::PrecompileErrors, + primitives::U256, + specification::hardfork::SpecId, +}; + +pub struct Erc20PostExecution { + inner: EthPostExecution, +} + +impl Erc20PostExecution { + pub fn new() -> Self { + Self { + inner: EthPostExecution::new(), + } + } +} + +impl Default for Erc20PostExecution { + fn default() -> Self { + Self::new() + } +} + +impl PostExecutionHandler for Erc20PostExecution +where + CTX: EthPostExecutionContext, + ERROR: EthPostExecutionError + + From + + From + + From> + + From, + HALTREASON: HaltReasonTrait, +{ + type Context = CTX; + type Error = ERROR; + type ExecResult = FrameResult; + type Output = ResultAndState; + + fn refund( + &self, + context: &mut Self::Context, + exec_result: &mut Self::ExecResult, + eip7702_refund: i64, + ) { + self.inner.refund(context, exec_result, eip7702_refund) + } + + fn reimburse_caller( + &self, + context: &mut Self::Context, + exec_result: &mut Self::ExecResult, + ) -> Result<(), Self::Error> { + let basefee = context.block().basefee() as u128; + let caller = context.tx().common_fields().caller(); + let effective_gas_price = context.tx().effective_gas_price(basefee); + let gas = exec_result.gas(); + + let reimbursement = + effective_gas_price.saturating_mul((gas.remaining() + gas.refunded() as u64) as u128); + token_operation::(context, TREASURY, caller, U256::from(reimbursement))?; + + Ok(()) + } + + fn reward_beneficiary( + &self, + context: &mut Self::Context, + exec_result: &mut Self::ExecResult, + ) -> Result<(), Self::Error> { + let tx = context.tx(); + let beneficiary = context.block().beneficiary(); + let basefee = context.block().basefee() as u128; + let effective_gas_price = tx.effective_gas_price(basefee); + let gas = exec_result.gas(); + + let coinbase_gas_price = if context.cfg().spec().into().is_enabled_in(SpecId::LONDON) { + effective_gas_price.saturating_sub(basefee) + } else { + effective_gas_price + }; + + let reward = + coinbase_gas_price.saturating_mul((gas.spent() - gas.refunded() as u64) as u128); + token_operation::(context, TREASURY, beneficiary, U256::from(reward))?; + + Ok(()) + } + + fn output( + &self, + context: &mut Self::Context, + result: Self::ExecResult, + ) -> Result { + self.inner.output(context, result) + } + + fn clear(&self, context: &mut Self::Context) { + self.inner.clear(context) + } +} diff --git a/examples/erc20_gas/src/handlers/pre_execution.rs b/examples/erc20_gas/src/handlers/pre_execution.rs new file mode 100644 index 0000000000..b9cfc3c87c --- /dev/null +++ b/examples/erc20_gas/src/handlers/pre_execution.rs @@ -0,0 +1,65 @@ +use crate::{token_operation, TREASURY}; +use revm::{ + context_interface::{ + result::InvalidHeader, transaction::Eip4844Tx, Block, Transaction, TransactionGetter, + TransactionType, + }, + handler::{EthPreExecution, EthPreExecutionContext, EthPreExecutionError}, + handler_interface::PreExecutionHandler, + precompile::PrecompileErrors, + primitives::U256, +}; + +pub struct Erc20PreExecution { + inner: EthPreExecution, +} + +impl Erc20PreExecution { + pub fn new() -> Self { + Self { + inner: EthPreExecution::new(), + } + } +} + +impl Default for Erc20PreExecution { + fn default() -> Self { + Self::new() + } +} + +impl PreExecutionHandler for Erc20PreExecution +where + CTX: EthPreExecutionContext, + ERROR: EthPreExecutionError + From + From, +{ + type Context = CTX; + type Error = ERROR; + + fn load_accounts(&self, context: &mut Self::Context) -> Result<(), Self::Error> { + self.inner.load_accounts(context) + } + + fn apply_eip7702_auth_list(&self, context: &mut Self::Context) -> Result { + self.inner.apply_eip7702_auth_list(context) + } + + fn deduct_caller(&self, context: &mut Self::Context) -> Result<(), Self::Error> { + let basefee = context.block().basefee() as u128; + let blob_price = context.block().blob_gasprice().unwrap_or_default(); + let effective_gas_price = context.tx().effective_gas_price(basefee); + + let mut gas_cost = + (context.tx().common_fields().gas_limit() as u128).saturating_mul(effective_gas_price); + + if context.tx().tx_type().into() == TransactionType::Eip4844 { + let blob_gas = context.tx().eip4844().total_blob_gas() as u128; + gas_cost = gas_cost.saturating_add(blob_price.saturating_mul(blob_gas)); + } + + let caller = context.tx().common_fields().caller(); + token_operation::(context, caller, TREASURY, U256::from(gas_cost))?; + + Ok(()) + } +} diff --git a/examples/erc20_gas/src/handlers/validation.rs b/examples/erc20_gas/src/handlers/validation.rs new file mode 100644 index 0000000000..3d7e64d34d --- /dev/null +++ b/examples/erc20_gas/src/handlers/validation.rs @@ -0,0 +1,104 @@ +use crate::TOKEN; +use alloy_sol_types::SolValue; +use revm::{ + context::Cfg, + context_interface::{ + result::InvalidTransaction, transaction::Eip4844Tx, Journal, Transaction, + TransactionGetter, TransactionType, + }, + handler::{EthValidation, EthValidationContext, EthValidationError}, + handler_interface::ValidationHandler, + primitives::{keccak256, U256}, +}; +use std::cmp::Ordering; + +pub struct Erc20Validation { + inner: EthValidation, +} + +impl Erc20Validation { + pub fn new() -> Self { + Self { + inner: EthValidation::new(), + } + } +} + +impl Default for Erc20Validation { + fn default() -> Self { + Self::new() + } +} + +impl ValidationHandler for Erc20Validation +where + CTX: EthValidationContext, + ERROR: EthValidationError, +{ + type Context = CTX; + type Error = ERROR; + + fn validate_env(&self, context: &Self::Context) -> Result<(), Self::Error> { + self.inner.validate_env(context) + } + + fn validate_tx_against_state(&self, context: &mut Self::Context) -> Result<(), Self::Error> { + let caller = context.tx().common_fields().caller(); + let caller_nonce = context.journal().load_account(caller)?.data.info.nonce; + let token_account = context.journal().load_account(TOKEN)?.data.clone(); + + if !context.cfg().is_nonce_check_disabled() { + let tx_nonce = context.tx().common_fields().nonce(); + let state_nonce = caller_nonce; + match tx_nonce.cmp(&state_nonce) { + Ordering::Less => { + return Err(ERROR::from(InvalidTransaction::NonceTooLow { + tx: tx_nonce, + state: state_nonce, + })) + } + Ordering::Greater => { + return Err(ERROR::from(InvalidTransaction::NonceTooHigh { + tx: tx_nonce, + state: state_nonce, + })) + } + _ => (), + } + } + + let mut balance_check = U256::from(context.tx().common_fields().gas_limit()) + .checked_mul(U256::from(context.tx().max_fee())) + .and_then(|gas_cost| gas_cost.checked_add(context.tx().common_fields().value())) + .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?; + + if context.tx().tx_type().into() == TransactionType::Eip4844 { + let tx = context.tx().eip4844(); + let data_fee = tx.calc_max_data_fee(); + balance_check = balance_check + .checked_add(data_fee) + .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?; + } + + let account_balance_slot: U256 = keccak256((caller, U256::from(3)).abi_encode()).into(); + let account_balance = token_account + .storage + .get(&account_balance_slot) + .expect("Balance slot not found") + .present_value(); + + if account_balance < balance_check && !context.cfg().is_balance_check_disabled() { + return Err(InvalidTransaction::LackOfFundForMaxFee { + fee: Box::new(balance_check), + balance: Box::new(account_balance), + } + .into()); + }; + + Ok(()) + } + + fn validate_initial_tx_gas(&self, context: &Self::Context) -> Result { + self.inner.validate_initial_tx_gas(context) + } +} diff --git a/examples/erc20_gas/src/main.rs b/examples/erc20_gas/src/main.rs new file mode 100644 index 0000000000..126f301f30 --- /dev/null +++ b/examples/erc20_gas/src/main.rs @@ -0,0 +1,213 @@ +//! Example of a custom handler for ERC20 gas calculation. +//! +//! Gas is going to be deducted from ERC20 token. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +use alloy_provider::{network::Ethereum, ProviderBuilder, RootProvider}; +use alloy_sol_types::{sol, SolCall, SolValue}; +use alloy_transport_http::Http; +use anyhow::{anyhow, Result}; +use database::{AlloyDB, BlockId, CacheDB}; +use reqwest::{Client, Url}; +use revm::{ + context_interface::{ + result::{ExecutionResult, InvalidHeader, InvalidTransaction, Output}, + Journal, JournalDBError, JournalGetter, + }, + database_interface::WrapDatabaseAsync, + handler::EthExecution, + precompile::PrecompileErrors, + primitives::{address, keccak256, Address, Bytes, TxKind, U256}, + state::{AccountInfo, EvmStorageSlot}, + Context, EvmCommit, MainEvm, +}; + +pub mod handlers; +use handlers::{CustomEvm, CustomHandler, Erc20PostExecution, Erc20PreExecution, Erc20Validation}; + +type AlloyCacheDB = + CacheDB, Ethereum, RootProvider>>>>; + +// Constants +pub const TOKEN: Address = address!("1234567890123456789012345678901234567890"); +pub const TREASURY: Address = address!("0000000000000000000000000000000000000001"); + +#[tokio::main] +async fn main() -> Result<()> { + // Set up the HTTP transport which is consumed by the RPC client. + let rpc_url: Url = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27".parse()?; + + let client = ProviderBuilder::new().on_http(rpc_url); + + let alloy = WrapDatabaseAsync::new(AlloyDB::new(client, BlockId::latest())).unwrap(); + let mut cache_db = CacheDB::new(alloy); + + // Random empty account: From + let account = address!("18B06aaF27d44B756FCF16Ca20C1f183EB49111f"); + // Random empty account: To + let account_to = address!("21a4B6F62E51e59274b6Be1705c7c68781B87C77"); + + let usdc = address!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"); + + // USDC has 6 decimals + let hundred_tokens = U256::from(100_000_000_000_000_000u128); + + let balance_slot = keccak256((account, U256::from(3)).abi_encode()).into(); + + cache_db + .insert_account_storage(usdc, balance_slot, hundred_tokens) + .unwrap(); + cache_db.insert_account_info( + account, + AccountInfo { + nonce: 0, + balance: hundred_tokens, + code_hash: keccak256(Bytes::new()), + code: None, + }, + ); + + let balance_before = balance_of(usdc, account, &mut cache_db).unwrap(); + + // Transfer 100 tokens from account to account_to + // Magic happens here with custom handlers + transfer(account, account_to, hundred_tokens, usdc, &mut cache_db)?; + + let balance_after = balance_of(usdc, account, &mut cache_db)?; + + println!("Balance before: {balance_before}"); + println!("Balance after: {balance_after}"); + + Ok(()) +} + +/// Helpers +pub fn token_operation( + context: &mut CTX, + sender: Address, + recipient: Address, + amount: U256, +) -> Result<(), ERROR> +where + CTX: JournalGetter, + ERROR: From + + From + + From> + + From, +{ + let token_account = context.journal().load_account(TOKEN)?.data; + + let sender_balance_slot: U256 = keccak256((sender, U256::from(3)).abi_encode()).into(); + let sender_balance = token_account + .storage + .get(&sender_balance_slot) + .expect("Balance slot not found") + .present_value(); + + if sender_balance < amount { + return Err(ERROR::from( + InvalidTransaction::MaxFeePerBlobGasNotSupported, + )); + } + // Subtract the amount from the sender's balance + let sender_new_balance = sender_balance.saturating_sub(amount); + token_account.storage.insert( + sender_balance_slot, + EvmStorageSlot::new_changed(sender_balance, sender_new_balance), + ); + + // Add the amount to the recipient's balance + let recipient_balance_slot: U256 = keccak256((recipient, U256::from(3)).abi_encode()).into(); + let recipient_balance = token_account + .storage + .get(&recipient_balance_slot) + .expect("To balance slot not found") + .present_value(); + let recipient_new_balance = recipient_balance.saturating_add(amount); + token_account.storage.insert( + recipient_balance_slot, + EvmStorageSlot::new_changed(recipient_balance, recipient_new_balance), + ); + + Ok(()) +} + +fn balance_of(token: Address, address: Address, alloy_db: &mut AlloyCacheDB) -> Result { + sol! { + function balanceOf(address account) public returns (uint256); + } + + let encoded = balanceOfCall { account: address }.abi_encode(); + + let mut evm = MainEvm::new( + Context::builder() + .with_db(alloy_db) + .modify_tx_chained(|tx| { + // 0x1 because calling USDC proxy from zero address fails + tx.caller = address!("0000000000000000000000000000000000000001"); + tx.transact_to = TxKind::Call(token); + tx.data = encoded.into(); + tx.value = U256::from(0); + }), + CustomHandler::default(), + ); + + let ref_tx = evm.exec_commit().unwrap(); + let value = match ref_tx { + ExecutionResult::Success { + output: Output::Call(value), + .. + } => value, + result => return Err(anyhow!("'balanceOf' execution failed: {result:?}")), + }; + + let balance = ::abi_decode(&value, false)?; + + Ok(balance) +} + +fn transfer( + from: Address, + to: Address, + amount: U256, + token: Address, + cache_db: &mut AlloyCacheDB, +) -> Result<()> { + sol! { + function transfer(address to, uint amount) external returns (bool); + } + + let encoded = transferCall { to, amount }.abi_encode(); + + let mut evm = CustomEvm::new( + Context::builder() + .with_db(cache_db) + .modify_tx_chained(|tx| { + tx.caller = from; + tx.transact_to = TxKind::Call(token); + tx.data = encoded.into(); + tx.value = U256::from(0); + }), + CustomHandler::new( + Erc20Validation::new(), + Erc20PreExecution::new(), + EthExecution::new(), + Erc20PostExecution::new(), + ), + ); + let ref_tx = evm.exec_commit().unwrap(); + let success: bool = match ref_tx { + ExecutionResult::Success { + output: Output::Call(value), + .. + } => ::abi_decode(&value, false)?, + result => return Err(anyhow!("'transfer' execution failed: {result:?}")), + }; + + if !success { + return Err(anyhow!("'transfer' failed")); + } + + Ok(()) +}