diff --git a/crates/precompile/src/bls12_381/utils.rs b/crates/precompile/src/bls12_381/utils.rs index ba18475c67..a2ed3cd885 100644 --- a/crates/precompile/src/bls12_381/utils.rs +++ b/crates/precompile/src/bls12_381/utils.rs @@ -1,8 +1,7 @@ -use core::cmp::Ordering; - use blst::{ blst_bendian_from_fp, blst_fp, blst_fp_from_bendian, blst_scalar, blst_scalar_from_bendian, }; +use core::cmp::Ordering; use revm_primitives::PrecompileError; /// Number of bits used in the BLS12-381 curve finite field elements. diff --git a/crates/precompile/src/lib.rs b/crates/precompile/src/lib.rs index 6f85a874d0..e930f2cdfc 100644 --- a/crates/precompile/src/lib.rs +++ b/crates/precompile/src/lib.rs @@ -28,9 +28,9 @@ use once_cell::race::OnceBox; pub use revm_primitives as primitives; pub use revm_primitives::{ precompile::{PrecompileError as Error, *}, - Address, Bytes, HashMap, Log, B256, + Address, Bytes, HashMap, HashSet, Log, B256, }; -use std::boxed::Box; +use std::{boxed::Box, vec::Vec}; pub fn calc_linear_cost_u32(len: usize, base: u64, word: u64) -> u64 { (len as u64 + 32 - 1) / 32 * word + base @@ -39,7 +39,9 @@ pub fn calc_linear_cost_u32(len: usize, base: u64, word: u64) -> u64 { #[derive(Clone, Default, Debug)] pub struct Precompiles { /// Precompiles. - pub inner: HashMap, + inner: HashMap, + /// Addresses of precompile. + addresses: HashSet
, } impl Precompiles { @@ -71,6 +73,11 @@ impl Precompiles { }) } + /// Returns inner HashMap of precompiles. + pub fn inner(&self) -> &HashMap { + &self.inner + } + /// Returns precompiles for Byzantium spec. pub fn byzantium() -> &'static Self { static INSTANCE: OnceBox = OnceBox::new(); @@ -168,13 +175,13 @@ impl Precompiles { /// Returns an iterator over the precompiles addresses. #[inline] - pub fn addresses(&self) -> impl Iterator { + pub fn addresses(&self) -> impl ExactSizeIterator { self.inner.keys() } /// Consumes the type and returns all precompile addresses. #[inline] - pub fn into_addresses(self) -> impl Iterator { + pub fn into_addresses(self) -> impl ExactSizeIterator { self.inner.into_keys() } @@ -206,11 +213,19 @@ impl Precompiles { self.inner.len() } + /// Returns the precompiles addresses as a set. + pub fn addresses_set(&self) -> &HashSet
{ + &self.addresses + } + /// Extends the precompiles with the given precompiles. /// /// Other precompiles with overwrite existing precompiles. + #[inline] pub fn extend(&mut self, other: impl IntoIterator) { - self.inner.extend(other.into_iter().map(Into::into)); + let items = other.into_iter().collect::>(); + self.addresses.extend(items.iter().map(|p| *p.address())); + self.inner.extend(items.into_iter().map(Into::into)); } } @@ -229,6 +244,20 @@ impl From for (Address, Precompile) { } } +impl PrecompileWithAddress { + /// Returns reference of address. + #[inline] + pub fn address(&self) -> &Address { + &self.0 + } + + /// Returns reference of precompile. + #[inline] + pub fn precompile(&self) -> &Precompile { + &self.1 + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] pub enum PrecompileSpecId { HOMESTEAD, diff --git a/crates/primitives/src/precompile.rs b/crates/primitives/src/precompile.rs index 275244f849..58f7cfe953 100644 --- a/crates/primitives/src/precompile.rs +++ b/crates/primitives/src/precompile.rs @@ -110,11 +110,25 @@ impl Precompile { /// Call the precompile with the given input and gas limit and return the result. pub fn call(&mut self, bytes: &Bytes, gas_price: u64, env: &Env) -> PrecompileResult { - match self { + match *self { + Precompile::Standard(p) => p(bytes, gas_price), + Precompile::Env(p) => p(bytes, gas_price, env), + Precompile::Stateful(ref p) => p.call(bytes, gas_price, env), + Precompile::StatefulMut(ref mut p) => p.call_mut(bytes, gas_price, env), + } + } + + /// Call the precompile with the given input and gas limit and return the result. + /// + /// Returns an error if the precompile is mutable. + pub fn call_ref(&self, bytes: &Bytes, gas_price: u64, env: &Env) -> PrecompileResult { + match *self { Precompile::Standard(p) => p(bytes, gas_price), Precompile::Env(p) => p(bytes, gas_price, env), - Precompile::Stateful(p) => p.call(bytes, gas_price, env), - Precompile::StatefulMut(p) => p.call_mut(bytes, gas_price, env), + Precompile::Stateful(ref p) => p.call(bytes, gas_price, env), + Precompile::StatefulMut(_) => Err(PrecompileErrors::Fatal { + msg: "call_ref on mutable stateful precompile".into(), + }), } } } diff --git a/crates/revm/src/context/context_precompiles.rs b/crates/revm/src/context/context_precompiles.rs index a6fdc5e67e..a47cef7132 100644 --- a/crates/revm/src/context/context_precompiles.rs +++ b/crates/revm/src/context/context_precompiles.rs @@ -1,15 +1,13 @@ +use super::InnerEvmContext; use crate::{ precompile::{Precompile, PrecompileResult}, - primitives::{db::Database, Address, Bytes, HashMap}, + primitives::{db::Database, Address, Bytes, HashMap, HashSet}, }; -use core::ops::{Deref, DerefMut}; use dyn_clone::DynClone; -use revm_precompile::Precompiles; +use revm_precompile::{PrecompileSpecId, PrecompileWithAddress, Precompiles}; use std::{boxed::Box, sync::Arc}; -use super::InnerEvmContext; - -/// Precompile and its handlers. +/// A single precompile handler. pub enum ContextPrecompile { /// Ordinary precompiles Ordinary(Precompile), @@ -24,53 +22,160 @@ pub enum ContextPrecompile { impl Clone for ContextPrecompile { fn clone(&self) -> Self { match self { - Self::Ordinary(arg0) => Self::Ordinary(arg0.clone()), - Self::ContextStateful(arg0) => Self::ContextStateful(arg0.clone()), - Self::ContextStatefulMut(arg0) => Self::ContextStatefulMut(arg0.clone()), + Self::Ordinary(p) => Self::Ordinary(p.clone()), + Self::ContextStateful(p) => Self::ContextStateful(p.clone()), + Self::ContextStatefulMut(p) => Self::ContextStatefulMut(p.clone()), } } } -#[derive(Clone)] +enum PrecompilesCow { + /// Default precompiles, returned by `Precompiles::new`. Used to fast-path the default case. + StaticRef(&'static Precompiles), + Owned(HashMap>), +} + +impl Clone for PrecompilesCow { + fn clone(&self) -> Self { + match *self { + PrecompilesCow::StaticRef(p) => PrecompilesCow::StaticRef(p), + PrecompilesCow::Owned(ref inner) => PrecompilesCow::Owned(inner.clone()), + } + } +} + +/// Precompiles context. pub struct ContextPrecompiles { - inner: HashMap>, + inner: PrecompilesCow, +} + +impl Clone for ContextPrecompiles { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } } impl ContextPrecompiles { - /// Returns precompiles addresses. + /// Creates a new precompiles context at the given spec ID. + /// + /// This is a cheap operation that does not allocate by reusing the global precompiles. #[inline] - pub fn addresses(&self) -> impl Iterator { - self.inner.keys() + pub fn new(spec_id: PrecompileSpecId) -> Self { + Self::from_static_precompiles(Precompiles::new(spec_id)) } - /// Extends the precompiles with the given precompiles. + /// Creates a new precompiles context from the given static precompiles. /// - /// Other precompiles with overwrite existing precompiles. + /// NOTE: The internal precompiles must not be `StatefulMut` or `call` will panic. + /// This is done because the default precompiles are not stateful. #[inline] - pub fn extend( - &mut self, - other: impl IntoIterator)>>, - ) { - self.inner.extend(other.into_iter().map(Into::into)); + pub fn from_static_precompiles(precompiles: &'static Precompiles) -> Self { + Self { + inner: PrecompilesCow::StaticRef(precompiles), + } + } + + /// Creates a new precompiles context from the given precompiles. + #[inline] + pub fn from_precompiles(precompiles: HashMap>) -> Self { + Self { + inner: PrecompilesCow::Owned(precompiles), + } + } + + /// Returns precompiles addresses as a HashSet. + pub fn addresses_set(&self) -> HashSet
{ + match self.inner { + PrecompilesCow::StaticRef(inner) => inner.addresses_set().clone(), + PrecompilesCow::Owned(ref inner) => inner.keys().cloned().collect(), + } + } + + /// Returns precompiles addresses. + #[inline] + pub fn addresses<'a>(&'a self) -> Box + 'a> { + match self.inner { + PrecompilesCow::StaticRef(inner) => Box::new(inner.addresses()), + PrecompilesCow::Owned(ref inner) => Box::new(inner.keys()), + } + } + + /// Returns `true` if the precompiles contains the given address. + #[inline] + pub fn contains(&self, address: &Address) -> bool { + match self.inner { + PrecompilesCow::StaticRef(inner) => inner.contains(address), + PrecompilesCow::Owned(ref inner) => inner.contains_key(address), + } } /// Call precompile and executes it. Returns the result of the precompile execution. - /// None if the precompile does not exist. + /// + /// Returns `None` if the precompile does not exist. #[inline] pub fn call( &mut self, - addess: Address, + address: &Address, bytes: &Bytes, gas_price: u64, evmctx: &mut InnerEvmContext, ) -> Option { - let precompile = self.inner.get_mut(&addess)?; + Some(match self.inner { + PrecompilesCow::StaticRef(p) => p.get(address)?.call_ref(bytes, gas_price, &evmctx.env), + PrecompilesCow::Owned(ref mut owned) => match owned.get_mut(address)? { + ContextPrecompile::Ordinary(p) => p.call(bytes, gas_price, &evmctx.env), + ContextPrecompile::ContextStateful(p) => p.call(bytes, gas_price, evmctx), + ContextPrecompile::ContextStatefulMut(p) => p.call_mut(bytes, gas_price, evmctx), + }, + }) + } - match precompile { - ContextPrecompile::Ordinary(p) => Some(p.call(bytes, gas_price, &evmctx.env)), - ContextPrecompile::ContextStatefulMut(p) => Some(p.call_mut(bytes, gas_price, evmctx)), - ContextPrecompile::ContextStateful(p) => Some(p.call(bytes, gas_price, evmctx)), + /// Returns a mutable reference to the precompiles map. + /// + /// Clones the precompiles map if it is shared. + #[inline] + pub fn to_mut(&mut self) -> &mut HashMap> { + if let PrecompilesCow::StaticRef(_) = self.inner { + self.mutate_into_owned(); } + + let PrecompilesCow::Owned(inner) = &mut self.inner else { + unreachable!("self is mutated to Owned.") + }; + inner + } + + /// Mutates Self into Owned variant, or do nothing if it is already Owned. + /// Mutation will clone all precompiles. + #[cold] + fn mutate_into_owned(&mut self) { + let PrecompilesCow::StaticRef(precompiles) = self.inner else { + return; + }; + self.inner = PrecompilesCow::Owned( + precompiles + .inner() + .iter() + .map(|(k, v)| (*k, v.clone().into())) + .collect(), + ); + } +} + +impl Extend<(Address, ContextPrecompile)> for ContextPrecompiles { + fn extend)>>(&mut self, iter: T) { + self.to_mut().extend(iter.into_iter().map(Into::into)) + } +} + +impl Extend for ContextPrecompiles { + fn extend>(&mut self, iter: T) { + self.to_mut().extend(iter.into_iter().map(|precompile| { + let (address, precompile) = precompile.into(); + (address, precompile.into()) + })); } } @@ -82,17 +187,9 @@ impl Default for ContextPrecompiles { } } -impl Deref for ContextPrecompiles { - type Target = HashMap>; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl DerefMut for ContextPrecompiles { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner +impl Default for PrecompilesCow { + fn default() -> Self { + Self::Owned(Default::default()) } } @@ -132,22 +229,24 @@ impl From for ContextPrecompile { } } -impl From for ContextPrecompiles { - fn from(p: Precompiles) -> Self { - ContextPrecompiles { - inner: p.inner.into_iter().map(|(k, v)| (k, v.into())).collect(), - } - } -} - -impl From<&Precompiles> for ContextPrecompiles { - fn from(p: &Precompiles) -> Self { - ContextPrecompiles { - inner: p - .inner - .iter() - .map(|(&k, v)| (k, v.clone().into())) - .collect(), - } +#[cfg(test)] +mod tests { + use super::*; + use crate::db::EmptyDB; + + #[test] + fn test_precompiles_context() { + let custom_address = Address::with_last_byte(0xff); + + let mut precompiles = ContextPrecompiles::::new(PrecompileSpecId::HOMESTEAD); + assert_eq!(precompiles.addresses().count(), 4); + assert!(matches!(precompiles.inner, PrecompilesCow::StaticRef(_))); + assert!(!precompiles.contains(&custom_address)); + + let precompile = Precompile::Standard(|_, _| panic!()); + precompiles.extend([(custom_address, precompile.into())]); + assert_eq!(precompiles.addresses().count(), 5); + assert!(matches!(precompiles.inner, PrecompilesCow::Owned(_))); + assert!(precompiles.contains(&custom_address)); } } diff --git a/crates/revm/src/context/evm_context.rs b/crates/revm/src/context/evm_context.rs index 0cb969f6f6..1ff5f2cafe 100644 --- a/crates/revm/src/context/evm_context.rs +++ b/crates/revm/src/context/evm_context.rs @@ -7,7 +7,7 @@ use crate::{ interpreter::{ return_ok, CallInputs, Contract, Gas, InstructionResult, Interpreter, InterpreterResult, }, - primitives::{Address, Bytes, EVMError, Env, HashSet, U256}, + primitives::{Address, Bytes, EVMError, Env, U256}, ContextPrecompiles, FrameOrResult, CALL_STACK_LIMIT, }; use core::{ @@ -96,8 +96,7 @@ impl EvmContext { #[inline] pub fn set_precompiles(&mut self, precompiles: ContextPrecompiles) { // set warm loaded addresses. - self.journaled_state.warm_preloaded_addresses = - precompiles.addresses().copied().collect::>(); + self.journaled_state.warm_preloaded_addresses = precompiles.addresses_set(); self.precompiles = precompiles; } @@ -105,7 +104,7 @@ impl EvmContext { #[inline] fn call_precompile( &mut self, - address: Address, + address: &Address, input_data: &Bytes, gas: Gas, ) -> Result, EVMError> { @@ -199,7 +198,7 @@ impl EvmContext { _ => {} }; - if let Some(result) = self.call_precompile(inputs.bytecode_address, &inputs.input, gas)? { + if let Some(result) = self.call_precompile(&inputs.bytecode_address, &inputs.input, gas)? { if matches!(result.result, return_ok!()) { self.journaled_state.checkpoint_commit(); } else { @@ -232,7 +231,7 @@ pub(crate) mod test_utils { use crate::{ db::{CacheDB, EmptyDB}, journaled_state::JournaledState, - primitives::{address, SpecId, B256}, + primitives::{address, HashSet, SpecId, B256}, }; /// Mock caller address. diff --git a/crates/revm/src/handler/mainnet/pre_execution.rs b/crates/revm/src/handler/mainnet/pre_execution.rs index c0f4fe7bde..d3b9759dcf 100644 --- a/crates/revm/src/handler/mainnet/pre_execution.rs +++ b/crates/revm/src/handler/mainnet/pre_execution.rs @@ -3,7 +3,7 @@ //! They handle initial setup of the EVM, call loop and the final return of the EVM use crate::{ - precompile::{PrecompileSpecId, Precompiles}, + precompile::PrecompileSpecId, primitives::{ db::Database, Account, EVMError, Env, Spec, @@ -16,9 +16,7 @@ use crate::{ /// Main precompile load #[inline] pub fn load_precompiles() -> ContextPrecompiles { - Precompiles::new(PrecompileSpecId::from_spec_id(SPEC::SPEC_ID)) - .clone() - .into() + ContextPrecompiles::new(PrecompileSpecId::from_spec_id(SPEC::SPEC_ID)) } /// Main load handle diff --git a/crates/revm/src/optimism/handler_register.rs b/crates/revm/src/optimism/handler_register.rs index d4996cca21..899ba41279 100644 --- a/crates/revm/src/optimism/handler_register.rs +++ b/crates/revm/src/optimism/handler_register.rs @@ -14,7 +14,7 @@ use crate::{ Context, ContextPrecompiles, FrameResult, }; use core::ops::Mul; -use revm_precompile::{secp256r1, PrecompileSpecId, Precompiles}; +use revm_precompile::{secp256r1, PrecompileSpecId}; use std::string::ToString; use std::sync::Arc; @@ -143,7 +143,7 @@ pub fn last_frame_return( /// Load precompiles for Optimism chain. #[inline] pub fn load_precompiles() -> ContextPrecompiles { - let mut precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(SPEC::SPEC_ID)).clone(); + let mut precompiles = ContextPrecompiles::new(PrecompileSpecId::from_spec_id(SPEC::SPEC_ID)); if SPEC::enabled(SpecId::FJORD) { precompiles.extend([ @@ -152,7 +152,7 @@ pub fn load_precompiles() -> ContextPrecompiles