diff --git a/Cargo.lock b/Cargo.lock index bfa4a9d8..87dc075d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,10 +44,21 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "alloy-chains" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd47e5f8545bdf53beb545d3c039b4afa16040bdf69c50100581579b08776afd" +dependencies = [ + "num_enum", + "strum", +] + [[package]] name = "alloy-consensus" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=44b8a6d#44b8a6da656602fd9fd2c52f28ecffa4acaed96c" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cc7579e4fb5558af44810f542c90d1145dba8b92c08211c215196160c48d2ea" dependencies = [ "alloy-eips", "alloy-primitives", @@ -59,8 +70,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=44b8a6d#44b8a6da656602fd9fd2c52f28ecffa4acaed96c" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bdbc8d98cc36ebe17bb5b42d0873137bc76628a4ee0f7e7acad5b8fc59d3597" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -71,21 +83,11 @@ dependencies = [ "sha2 0.10.8", ] -[[package]] -name = "alloy-genesis" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=44b8a6d#44b8a6da656602fd9fd2c52f28ecffa4acaed96c" -dependencies = [ - "alloy-primitives", - "alloy-serde", - "serde", - "serde_json", -] - [[package]] name = "alloy-json-rpc" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=44b8a6d#44b8a6da656602fd9fd2c52f28ecffa4acaed96c" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06d33b79246313c4103ef9596c721674a926f1ddc8b605aa2bac4d8ba94ee34" dependencies = [ "alloy-primitives", "serde", @@ -96,26 +98,29 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=44b8a6d#44b8a6da656602fd9fd2c52f28ecffa4acaed96c" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef742b478a2db5c27063cde82128dfbecffcd38237d7f682a91d3ecf6aa1836c" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-json-rpc", "alloy-primitives", - "alloy-rpc-types", + "alloy-rpc-types-eth", + "alloy-serde", "alloy-signer", "alloy-sol-types", "async-trait", + "auto_impl", "futures-utils-wasm", "thiserror", ] [[package]] name = "alloy-primitives" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8aa973e647ec336810a9356af8aea787249c9d00b1525359f3db29a68d231b" +checksum = "f783611babedbbe90db3478c120fb5f5daacceffc210b39adc0af4fe0da70bad" dependencies = [ "alloy-rlp", "arbitrary", @@ -140,16 +145,18 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=44b8a6d#44b8a6da656602fd9fd2c52f28ecffa4acaed96c" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200b786259a17acf318b9c423afe9669bec24ce9cdf59de153ff9a4009914bb6" dependencies = [ + "alloy-chains", + "alloy-consensus", "alloy-eips", "alloy-json-rpc", "alloy-network", "alloy-primitives", "alloy-rpc-client", - "alloy-rpc-types", - "alloy-rpc-types-trace", + "alloy-rpc-types-eth", "alloy-transport", "alloy-transport-http", "async-stream", @@ -159,7 +166,9 @@ dependencies = [ "futures", "futures-utils-wasm", "lru", + "pin-project", "reqwest 0.12.4", + "serde", "serde_json", "tokio", "tracing", @@ -190,8 +199,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=44b8a6d#44b8a6da656602fd9fd2c52f28ecffa4acaed96c" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328a6a14aba6152ddf6d01bac5e17a70dbe9d6f343bf402b995c30bac63a1fbf" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -209,39 +219,28 @@ dependencies = [ ] [[package]] -name = "alloy-rpc-types" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=44b8a6d#44b8a6da656602fd9fd2c52f28ecffa4acaed96c" +name = "alloy-rpc-types-eth" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce0676f144be1eae71122d1d417885a3b063add0353b35e46cdf1440d6b33b1" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-genesis", "alloy-primitives", "alloy-rlp", "alloy-serde", "alloy-sol-types", - "itertools 0.12.1", + "itertools 0.13.0", "serde", "serde_json", "thiserror", ] -[[package]] -name = "alloy-rpc-types-trace" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=44b8a6d#44b8a6da656602fd9fd2c52f28ecffa4acaed96c" -dependencies = [ - "alloy-primitives", - "alloy-rpc-types", - "alloy-serde", - "serde", - "serde_json", -] - [[package]] name = "alloy-serde" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=44b8a6d#44b8a6da656602fd9fd2c52f28ecffa4acaed96c" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c224916316519558d8c2b6a60dc7626688c08f1b8951774702562dbcb8666ee" dependencies = [ "alloy-primitives", "serde", @@ -250,8 +249,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=44b8a6d#44b8a6da656602fd9fd2c52f28ecffa4acaed96c" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "227c5fd0ed6e06e1ccc30593f8ff6d9fb907ac5f03a709a6d687f0943494a229" dependencies = [ "alloy-primitives", "async-trait", @@ -263,9 +263,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dbd17d67f3e89478c8a634416358e539e577899666c927bc3d2b1328ee9b6ca" +checksum = "4bad41a7c19498e3f6079f7744656328699f8ea3e783bdd10d85788cd439f572" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -277,13 +277,13 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6da95adcf4760bb4b108fefa51d50096c5e5fdd29ee72fed3e86ee414f2e34" +checksum = "fd9899da7d011b4fe4c406a524ed3e3f963797dbc93b45479d60341d3a27b252" dependencies = [ "alloy-sol-macro-input", "const-hex", - "heck 0.4.1", + "heck 0.5.0", "indexmap", "proc-macro-error", "proc-macro2", @@ -295,9 +295,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c8da04c1343871fb6ce5a489218f9c85323c8340a36e9106b5fc98d4dd59d5" +checksum = "d32d595768fdc61331a132b6f65db41afae41b9b97d36c21eb1b955c422a7e60" dependencies = [ "const-hex", "dunce", @@ -310,9 +310,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a64d2d2395c1ac636b62419a7b17ec39031d6b2367e66e9acbf566e6055e9c" +checksum = "a49042c6d3b66a9fe6b2b5a8bf0d39fc2ae1ee0310a2a26ffedd79fb097878dd" dependencies = [ "alloy-primitives", "alloy-sol-macro", @@ -322,8 +322,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=44b8a6d#44b8a6da656602fd9fd2c52f28ecffa4acaed96c" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3628d81530263fe837a09cd527022f5728202a669973f04270942f4d390b5f5" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -335,19 +336,20 @@ dependencies = [ "tokio", "tower", "url", - "wasm-bindgen-futures", ] [[package]] name = "alloy-transport-http" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=44b8a6d#44b8a6da656602fd9fd2c52f28ecffa4acaed96c" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f35d34e7a51503c9ff267404a5850bd58f991b7ab524b892f364901e3576376" dependencies = [ "alloy-json-rpc", "alloy-transport", "reqwest 0.12.4", "serde_json", "tower", + "tracing", "url", ] @@ -847,9 +849,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c94087b935a822949d3291a9989ad2b2051ea141eda0fd4e478a75f6aa3e604b" +checksum = "62dc83a094a71d43eeadd254b1ec2d24cb6a0bb6cadce00df51f0db594711a32" dependencies = [ "cc", "glob", @@ -2658,6 +2660,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -3683,9 +3694,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -3810,9 +3821,10 @@ dependencies = [ name = "revm" version = "9.0.0" dependencies = [ + "alloy-eips", "alloy-provider", "alloy-sol-types", - "alloy-transport-http", + "alloy-transport", "anyhow", "auto_impl", "cfg-if", @@ -4027,9 +4039,9 @@ dependencies = [ [[package]] name = "rstest" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5316d2a1479eeef1ea21e7f9ddc67c191d497abc8fc3ba2467857abbb68330" +checksum = "9afd55a67069d6e434a95161415f5beeada95a01c7b815508a82dcb0e1593682" dependencies = [ "futures", "futures-timer", @@ -4039,12 +4051,13 @@ dependencies = [ [[package]] name = "rstest_macros" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04a9df72cc1f67020b0d63ad9bfe4a323e459ea7eb68e03bd9824db49f9a4c25" +checksum = "4165dfae59a39dd41d8dec720d3cbfbc71f69744efb480a3920f5d4e0cc6798d" dependencies = [ "cfg-if", "glob", + "proc-macro-crate", "proc-macro2", "quote", "regex", @@ -4056,9 +4069,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.12.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f308135fef9fc398342da5472ce7c484529df23743fb7c734e0f3d472971e62" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" dependencies = [ "alloy-rlp", "arbitrary", @@ -4081,9 +4094,9 @@ dependencies = [ [[package]] name = "ruint-macro" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86854cf50259291520509879a5c294c3c9a4c334e9ff65071c51e42ef1e2343" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-demangle" @@ -4725,9 +4738,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8db114c44cf843a8bacd37a146e37987a0b823a0e8bc4fdc610c9c72ab397a5" +checksum = "8d71e19bca02c807c9faa67b5a47673ff231b6e7449b251695188522f1dc44b2" dependencies = [ "paste", "proc-macro2", @@ -4956,9 +4969,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes 1.6.0", @@ -4973,9 +4986,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 7001c19d..426d6e40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = [ "crates/revm", "crates/primitives", "crates/interpreter", - "crates/precompile", + "crates/precompile", ] resolver = "2" default-members = ["crates/revm"] diff --git a/README.md b/README.md index 0d748be0..254952bb 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ cargo run -p revm --features std,serde-json,ethersdb --example generate_block_tr * [VERBS](https://github.com/simtopia/verbs) an open-source Ethereum agent-based modelling and simulation library with a Python API. * [Hardhat](https://github.com/NomicFoundation/hardhat) is a development environment to compile, deploy, test, and debug your Ethereum software. * [Trin](https://github.com/ethereum/trin) is Portal Network client. An execution and consensus layer Ethereum light client written in Rust. Portal Network client's provide complete, provable, and distributed execution archival access. +* [Simular](https://github.com/simular-fi/simular/) is a Python smart-contract API with a fast, embedded, Ethereum Virtual Machine. * ... (If you want to add project to the list, ping me or open the PR) diff --git a/bins/revm-test/Cargo.toml b/bins/revm-test/Cargo.toml index ef50c940..01a5ab9a 100644 --- a/bins/revm-test/Cargo.toml +++ b/bins/revm-test/Cargo.toml @@ -11,7 +11,7 @@ revm = { path = "../../crates/revm", version = "9.0.0", default-features=false } microbench = "0.5" alloy-sol-macro = "0.7.0" alloy-sol-types = "0.7.0" -regex = "1.10.4" +regex = "1.10.5" eyre = "0.6.12" diff --git a/bins/revme/src/cmd/statetest/models/spec.rs b/bins/revme/src/cmd/statetest/models/spec.rs index 53c134e4..dac05f04 100644 --- a/bins/revme/src/cmd/statetest/models/spec.rs +++ b/bins/revme/src/cmd/statetest/models/spec.rs @@ -23,6 +23,7 @@ pub enum SpecName { Merge, Shanghai, Cancun, + Prague, #[serde(other)] Unknown, } @@ -44,6 +45,7 @@ impl SpecName { Self::Merge => SpecId::MERGE, Self::Shanghai => SpecId::SHANGHAI, Self::Cancun => SpecId::CANCUN, + Self::Prague => SpecId::PRAGUE, Self::ByzantiumToConstantinopleAt5 | Self::Constantinople => { panic!("Overridden with PETERSBURG") } diff --git a/bins/revme/src/cmd/statetest/runner.rs b/bins/revme/src/cmd/statetest/runner.rs index e349378b..26604d0f 100644 --- a/bins/revme/src/cmd/statetest/runner.rs +++ b/bins/revme/src/cmd/statetest/runner.rs @@ -9,8 +9,8 @@ use revm::{ inspector_handle_register, inspectors::TracerEip3155, primitives::{ - calc_excess_blob_gas, keccak256, Bytecode, Bytes, EVMResultGeneric, Env, ExecutionResult, - SpecId, TransactTo, B256, U256, + calc_excess_blob_gas, keccak256, Bytecode, Bytes, EVMResultGeneric, Env, Eof, + ExecutionResult, SpecId, TransactTo, B256, EOF_MAGIC_BYTES, U256, }, Evm, State, }; @@ -258,10 +258,17 @@ pub fn execute_test_suite( // Create database and insert cache let mut cache_state = revm::CacheState::new(false); for (address, info) in unit.pre { + let code_hash = keccak256(&info.code); + let bytecode = match info.code.get(..2) { + Some(magic) if magic == &EOF_MAGIC_BYTES => { + Bytecode::Eof(Eof::decode(info.code.clone()).unwrap().into()) + } + _ => Bytecode::new_raw(info.code), + }; let acc_info = revm::primitives::AccountInfo { balance: info.balance, - code_hash: keccak256(&info.code), - code: Some(Bytecode::new_raw(info.code)), + code_hash, + code: Some(bytecode), nonce: info.nonce, }; cache_state.insert_account_with_storage(address, acc_info, info.storage); diff --git a/crates/interpreter/src/gas.rs b/crates/interpreter/src/gas.rs index ac0af307..01a3ebea 100644 --- a/crates/interpreter/src/gas.rs +++ b/crates/interpreter/src/gas.rs @@ -66,13 +66,6 @@ impl Gas { self.limit - self.remaining } - #[doc(hidden)] - #[inline] - #[deprecated(note = "use `spent` instead")] - pub const fn spend(&self) -> u64 { - self.spent() - } - /// Returns the amount of gas remaining. #[inline] pub const fn remaining(&self) -> u64 { diff --git a/crates/interpreter/src/gas/constants.rs b/crates/interpreter/src/gas/constants.rs index 9475e3b3..7d7956a4 100644 --- a/crates/interpreter/src/gas/constants.rs +++ b/crates/interpreter/src/gas/constants.rs @@ -5,7 +5,7 @@ pub const VERYLOW: u64 = 3; pub const DATA_LOADN_GAS: u64 = 3; pub const CONDITION_JUMP_GAS: u64 = 4; -pub const RETF_GAS: u64 = 4; +pub const RETF_GAS: u64 = 3; pub const DATA_LOAD_GAS: u64 = 4; pub const LOW: u64 = 5; diff --git a/crates/interpreter/src/host.rs b/crates/interpreter/src/host.rs index 49ffbd02..b432209b 100644 --- a/crates/interpreter/src/host.rs +++ b/crates/interpreter/src/host.rs @@ -1,4 +1,4 @@ -use crate::primitives::{Address, Bytecode, Env, Log, B256, U256}; +use crate::primitives::{Address, Bytes, Env, Log, B256, U256}; mod dummy; pub use dummy::DummyHost; @@ -23,7 +23,7 @@ pub trait Host { fn balance(&mut self, address: Address) -> Option<(U256, bool)>; /// Get code of `address` and if the account is cold. - fn code(&mut self, address: Address) -> Option<(Bytecode, bool)>; + fn code(&mut self, address: Address) -> Option<(Bytes, bool)>; /// Get code hash of `address` and if the account is cold. fn code_hash(&mut self, address: Address) -> Option<(B256, bool)>; diff --git a/crates/interpreter/src/host/dummy.rs b/crates/interpreter/src/host/dummy.rs index 4a069bfa..47320b08 100644 --- a/crates/interpreter/src/host/dummy.rs +++ b/crates/interpreter/src/host/dummy.rs @@ -1,6 +1,5 @@ -use crate::primitives::{hash_map::Entry, Bytecode, HashMap, U256}; use crate::{ - primitives::{Address, Env, Log, B256, KECCAK_EMPTY}, + primitives::{hash_map::Entry, Address, Bytes, Env, HashMap, Log, B256, KECCAK_EMPTY, U256}, Host, SStoreResult, SelfDestructResult, }; use std::vec::Vec; @@ -61,8 +60,8 @@ impl Host for DummyHost { } #[inline] - fn code(&mut self, _address: Address) -> Option<(Bytecode, bool)> { - Some((Bytecode::default(), false)) + fn code(&mut self, _address: Address) -> Option<(Bytes, bool)> { + Some((Bytes::default(), false)) } #[inline] diff --git a/crates/interpreter/src/instruction_result.rs b/crates/interpreter/src/instruction_result.rs index 9e0f6e12..121e12ea 100644 --- a/crates/interpreter/src/instruction_result.rs +++ b/crates/interpreter/src/instruction_result.rs @@ -61,6 +61,7 @@ impl From for InstructionResult { SuccessReason::Return => InstructionResult::Return, SuccessReason::Stop => InstructionResult::Stop, SuccessReason::SelfDestruct => InstructionResult::SelfDestruct, + SuccessReason::EofReturnContract => InstructionResult::ReturnContract, } } } @@ -269,9 +270,7 @@ impl From for SuccessOrHalt { InstructionResult::FatalExternalError => Self::FatalExternalError, InstructionResult::EOFOpcodeDisabledInLegacy => Self::Halt(HaltReason::OpcodeNotFound), InstructionResult::EOFFunctionStackOverflow => Self::FatalExternalError, - InstructionResult::ReturnContract => { - panic!("Unexpected EOF internal Return Contract") - } + InstructionResult::ReturnContract => Self::Success(SuccessReason::EofReturnContract), } } } diff --git a/crates/interpreter/src/instructions/bitwise.rs b/crates/interpreter/src/instructions/bitwise.rs index 62edf11c..a0205841 100644 --- a/crates/interpreter/src/instructions/bitwise.rs +++ b/crates/interpreter/src/instructions/bitwise.rs @@ -112,25 +112,12 @@ pub fn sar(interpreter: &mut Interpreter, _host: & pop_top!(interpreter, op1, op2); let shift = as_usize_saturated!(op1); - *op2 = if shift >= 256 { - // If the shift is 256 or more, the result depends on the sign of the last bit. - if op2.bit(255) { - U256::MAX // Negative number, all bits set to one. - } else { - U256::ZERO // Non-negative number, all bits set to zero. - } + *op2 = if shift < 256 { + op2.arithmetic_shr(shift) + } else if op2.bit(255) { + U256::MAX } else { - // Normal shift - if op2.bit(255) { - // Check the most significant bit. - // Arithmetic right shift for negative numbers. - let shifted_value = *op2 >> shift; - let mask = U256::MAX << (256 - shift); // Mask for the sign bits. - shifted_value | mask // Apply the mask to simulate the filling of sign bits. - } else { - // Logical right shift for non-negative numbers. - *op2 >> shift - } + U256::ZERO }; } diff --git a/crates/interpreter/src/instructions/contract.rs b/crates/interpreter/src/instructions/contract.rs index 9e2cd81d..f57a7869 100644 --- a/crates/interpreter/src/instructions/contract.rs +++ b/crates/interpreter/src/instructions/contract.rs @@ -1,41 +1,18 @@ mod call_helpers; -pub use call_helpers::{ - calc_call_gas, get_memory_input_and_out_ranges, resize_memory_and_return_range, -}; +pub use call_helpers::{calc_call_gas, get_memory_input_and_out_ranges, resize_memory}; use revm_primitives::{keccak256, BerlinSpec}; use crate::{ gas::{self, cost_per_word, EOF_CREATE_GAS, KECCAK256WORD}, - instructions::utility::read_u16, interpreter::Interpreter, primitives::{Address, Bytes, Eof, Spec, SpecId::*, U256}, - CallInputs, CallScheme, CallValue, CreateInputs, CreateScheme, EOFCreateInput, Host, + CallInputs, CallScheme, CallValue, CreateInputs, CreateScheme, EOFCreateInputs, Host, InstructionResult, InterpreterAction, InterpreterResult, LoadAccountResult, MAX_INITCODE_SIZE, }; -use core::{cmp::max, ops::Range}; +use core::cmp::max; use std::boxed::Box; -/// Resize memory and return memory range if successful. -/// Return `None` if there is not enough gas. And if `len` -/// is zero return `Some(usize::MAX..usize::MAX)`. -pub fn resize_memory( - interpreter: &mut Interpreter, - offset: U256, - len: U256, -) -> Option> { - let len = as_usize_or_fail_ret!(interpreter, len, None); - if len != 0 { - let offset = as_usize_or_fail_ret!(interpreter, offset, None); - resize_memory!(interpreter, offset, len, None); - // range is checked in resize_memory! macro and it is bounded by usize. - Some(offset..offset + len) - } else { - //unrealistic value so we are sure it is not used - Some(usize::MAX..usize::MAX) - } -} - /// EOF Create instruction pub fn eofcreate(interpreter: &mut Interpreter, _host: &mut H) { require_eof!(interpreter); @@ -53,10 +30,20 @@ pub fn eofcreate(interpreter: &mut Interpreter, _host: &mut H) .expect("EOF is checked"); // resize memory and get return range. - let Some(return_range) = resize_memory(interpreter, data_offset, data_size) else { + let Some(input_range) = resize_memory(interpreter, data_offset, data_size) else { return; }; + let input = if !input_range.is_empty() { + interpreter + .shared_memory + .slice_range(input_range) + .to_vec() + .into() + } else { + Bytes::new() + }; + let eof = Eof::decode(sub_container.clone()).expect("Subcontainer is verified"); if !eof.body.is_data_filled { @@ -72,18 +59,23 @@ pub fn eofcreate(interpreter: &mut Interpreter, _host: &mut H) let created_address = interpreter .contract - .caller + .target_address .create2(salt.to_be_bytes(), keccak256(sub_container)); + let gas_reduce = max(interpreter.gas.remaining() / 64, 5000); + let gas_limit = interpreter.gas().remaining().saturating_sub(gas_reduce); + gas!(interpreter, gas_limit); + // Send container for execution container is preverified. + interpreter.instruction_result = InstructionResult::CallOrCreate; interpreter.next_action = InterpreterAction::EOFCreate { - inputs: Box::new(EOFCreateInput::new( + inputs: Box::new(EOFCreateInputs::new( interpreter.contract.target_address, created_address, value, eof, - interpreter.gas().remaining(), - return_range, + gas_limit, + input, )), }; @@ -92,7 +84,7 @@ pub fn eofcreate(interpreter: &mut Interpreter, _host: &mut H) pub fn return_contract(interpreter: &mut Interpreter, _host: &mut H) { require_init_eof!(interpreter); - let deploy_container_index = unsafe { read_u16(interpreter.instruction_pointer) }; + let deploy_container_index = unsafe { *interpreter.instruction_pointer }; pop!(interpreter, aux_data_offset, aux_data_size); let aux_data_size = as_usize_or_fail!(interpreter, aux_data_size); // important: offset must be ignored if len is zeros @@ -147,8 +139,11 @@ pub fn return_contract(interpreter: &mut Interpreter, _host: & pub fn extcall_input(interpreter: &mut Interpreter) -> Option { pop_ret!(interpreter, input_offset, input_size, None); - let return_memory_offset = - resize_memory_and_return_range(interpreter, input_offset, input_size)?; + let return_memory_offset = resize_memory(interpreter, input_offset, input_size)?; + + if return_memory_offset.is_empty() { + return Some(Bytes::new()); + } Some(Bytes::copy_from_slice( interpreter @@ -168,10 +163,6 @@ pub fn extcall_gas_calc( return None; }; - if load_result.is_cold { - gas!(interpreter, gas::COLD_ACCOUNT_ACCESS_COST, None); - } - // TODO(EOF) is_empty should only be checked on delegatecall let call_cost = gas::call_cost( BerlinSpec::SPEC_ID, @@ -193,8 +184,6 @@ pub fn extcall_gas_calc( return None; } - // TODO check remaining gas more then N - gas!(interpreter, gas_limit, None); Some(gas_limit) } @@ -203,6 +192,8 @@ pub fn extcall(interpreter: &mut Interpreter, host require_eof!(interpreter); pop_address!(interpreter, target_address); + // TODO check if target is left paddded with zeroes. + // input call let Some(input) = extcall_input(interpreter) else { return; @@ -238,6 +229,8 @@ pub fn extdelegatecall(interpreter: &mut Interpret require_eof!(interpreter); pop_address!(interpreter, target_address); + // TODO check if target is left paddded with zeroes. + // input call let Some(input) = extcall_input(interpreter) else { return; @@ -271,6 +264,8 @@ pub fn extstaticcall(interpreter: &mut Interpreter, host: &mut require_eof!(interpreter); pop_address!(interpreter, target_address); + // TODO check if target is left paddded with zeroes. + // input call let Some(input) = extcall_input(interpreter) else { return; @@ -290,7 +285,7 @@ pub fn extstaticcall(interpreter: &mut Interpreter, host: &mut bytecode_address: target_address, value: CallValue::Transfer(U256::ZERO), scheme: CallScheme::Call, - is_static: interpreter.is_static, + is_static: true, is_eof: true, return_memory_offset: 0..0, }), diff --git a/crates/interpreter/src/instructions/contract/call_helpers.rs b/crates/interpreter/src/instructions/contract/call_helpers.rs index 5570efe3..f9acc749 100644 --- a/crates/interpreter/src/instructions/contract/call_helpers.rs +++ b/crates/interpreter/src/instructions/contract/call_helpers.rs @@ -11,21 +11,21 @@ pub fn get_memory_input_and_out_ranges( ) -> Option<(Bytes, Range)> { pop_ret!(interpreter, in_offset, in_len, out_offset, out_len, None); - let in_range = resize_memory_and_return_range(interpreter, in_offset, in_len)?; + let in_range = resize_memory(interpreter, in_offset, in_len)?; let mut input = Bytes::new(); if !in_range.is_empty() { input = Bytes::copy_from_slice(interpreter.shared_memory.slice_range(in_range)); } - let ret_range = resize_memory_and_return_range(interpreter, out_offset, out_len)?; + let ret_range = resize_memory(interpreter, out_offset, out_len)?; Some((input, ret_range)) } /// Resize memory and return range of memory. /// If `len` is 0 dont touch memory and return `usize::MAX` as offset and 0 as length. #[inline] -pub fn resize_memory_and_return_range( +pub fn resize_memory( interpreter: &mut Interpreter, offset: U256, len: U256, diff --git a/crates/interpreter/src/instructions/control.rs b/crates/interpreter/src/instructions/control.rs index db2bc70a..4aacffd9 100644 --- a/crates/interpreter/src/instructions/control.rs +++ b/crates/interpreter/src/instructions/control.rs @@ -204,6 +204,8 @@ pub fn unknown(interpreter: &mut Interpreter, _host: &mut H) { #[cfg(test)] mod test { + use std::sync::Arc; + use revm_primitives::{bytes, eof::TypesSection, Bytecode, Eof, PragueSpec}; use super::*; @@ -322,7 +324,7 @@ mod test { eof.body.code_section.push(bytes2.clone()); eof.body.types_section.push(types); - let mut interp = Interpreter::new_bytecode(Bytecode::Eof(eof)); + let mut interp = Interpreter::new_bytecode(Bytecode::Eof(Arc::new(eof))); interp.gas = Gas::new(10000); interp } diff --git a/crates/interpreter/src/instructions/data.rs b/crates/interpreter/src/instructions/data.rs index 5d18a898..dada108c 100644 --- a/crates/interpreter/src/instructions/data.rs +++ b/crates/interpreter/src/instructions/data.rs @@ -1,5 +1,5 @@ use crate::{ - gas::{BASE, DATA_LOAD_GAS, VERYLOW}, + gas::{cost_per_word, BASE, DATA_LOAD_GAS, VERYLOW}, instructions::utility::read_u16, interpreter::Interpreter, primitives::U256, @@ -70,8 +70,10 @@ pub fn data_copy(interpreter: &mut Interpreter, _host: &mut H) let mem_offset = as_usize_or_fail!(interpreter, mem_offset); resize_memory!(interpreter, mem_offset, size); + gas_or_fail!(interpreter, cost_per_word(size as u64, VERYLOW)); + let offset = as_usize_saturated!(offset); - let data = interpreter.contract.bytecode.eof().expect("EOF").data(); + let data = interpreter.contract.bytecode.eof().expect("eof").data(); // set data from the eof to the shared memory. Padd it with zeros. interpreter @@ -82,6 +84,7 @@ pub fn data_copy(interpreter: &mut Interpreter, _host: &mut H) #[cfg(test)] mod test { use revm_primitives::{b256, bytes, Bytecode, Bytes, Eof, PragueSpec}; + use std::sync::Arc; use super::*; use crate::{ @@ -99,7 +102,7 @@ mod test { eof.header.code_sizes[0] = code_bytes.len() as u16; eof.body.code_section[0] = code_bytes; - Bytecode::Eof(eof) + Bytecode::Eof(Arc::new(eof)) } #[test] diff --git a/crates/interpreter/src/instructions/host.rs b/crates/interpreter/src/instructions/host.rs index 0bc777c3..a25f5148 100644 --- a/crates/interpreter/src/instructions/host.rs +++ b/crates/interpreter/src/instructions/host.rs @@ -99,7 +99,7 @@ pub fn extcodecopy(interpreter: &mut Interpreter, // Note: this can't panic because we resized memory to fit. interpreter .shared_memory - .set_data(memory_offset, code_offset, len, &code.original_bytes()); + .set_data(memory_offset, code_offset, len, &code); } pub fn blockhash(interpreter: &mut Interpreter, host: &mut H) { diff --git a/crates/interpreter/src/instructions/macros.rs b/crates/interpreter/src/instructions/macros.rs index 2592956e..6938eead 100644 --- a/crates/interpreter/src/instructions/macros.rs +++ b/crates/interpreter/src/instructions/macros.rs @@ -165,7 +165,7 @@ macro_rules! pop { $crate::pop_ret!($interp, $x1, $x2, $x3, $x4, ()) }; ($interp:expr, $x1:ident, $x2:ident, $x3:ident, $x4:ident, $x5:ident) => { - pop_ret!($interp, $x1, $x2, $x3, $x4, $x5, ()) + $crate::pop_ret!($interp, $x1, $x2, $x3, $x4, $x5, ()) }; } @@ -206,7 +206,7 @@ macro_rules! pop_ret { let ($x1, $x2, $x3, $x4) = unsafe { $interp.stack.pop4_unsafe() }; }; ($interp:expr, $x1:ident, $x2:ident, $x3:ident, $x4:ident, $x5:ident, $ret:expr) => { - if $interp.stack.len() < 4 { + if $interp.stack.len() < 5 { $interp.instruction_result = $crate::InstructionResult::StackUnderflow; return $ret; } diff --git a/crates/interpreter/src/instructions/system.rs b/crates/interpreter/src/instructions/system.rs index 0887a874..513d334e 100644 --- a/crates/interpreter/src/instructions/system.rs +++ b/crates/interpreter/src/instructions/system.rs @@ -125,22 +125,36 @@ pub fn returndatasize(interpreter: &mut Interprete pub fn returndatacopy(interpreter: &mut Interpreter, _host: &mut H) { check!(interpreter, BYZANTIUM); pop!(interpreter, memory_offset, offset, len); + let len = as_usize_or_fail!(interpreter, len); gas_or_fail!(interpreter, gas::verylowcopy_cost(len as u64)); + let data_offset = as_usize_saturated!(offset); let data_end = data_offset.saturating_add(len); - if data_end > interpreter.return_data_buffer.len() { + + // Old legacy behavior is to panic if data_end is out of scope of return buffer. + // This behavior is changed in EOF. + if data_end > interpreter.return_data_buffer.len() && !interpreter.is_eof { interpreter.instruction_result = InstructionResult::OutOfOffset; return; } - if len != 0 { - let memory_offset = as_usize_or_fail!(interpreter, memory_offset); - resize_memory!(interpreter, memory_offset, len); - interpreter.shared_memory.set( - memory_offset, - &interpreter.return_data_buffer[data_offset..data_end], - ); + + // if len is zero memory is not resized. + if len == 0 { + return; } + + // resize memory + let memory_offset = as_usize_or_fail!(interpreter, memory_offset); + resize_memory!(interpreter, memory_offset, len); + + // Note: this can't panic because we resized memory to fit. + interpreter.shared_memory.set_data( + memory_offset, + data_offset, + len, + &interpreter.return_data_buffer, + ); } /// Part of EOF ``. @@ -149,13 +163,20 @@ pub fn returndataload(interpreter: &mut Interpreter, _host: &m gas!(interpreter, gas::VERYLOW); pop_top!(interpreter, offset); let offset_usize = as_usize_or_fail!(interpreter, offset); - if offset_usize.saturating_add(32) > interpreter.return_data_buffer.len() { - // TODO(EOF) proper error. - interpreter.instruction_result = InstructionResult::OutOfOffset; - return; + + let mut output = [0u8; 32]; + if let Some(available) = interpreter + .return_data_buffer + .len() + .checked_sub(offset_usize) + { + let copy_len = available.min(32); + output[..copy_len].copy_from_slice( + &interpreter.return_data_buffer[offset_usize..offset_usize + copy_len], + ); } - *offset = - B256::from_slice(&interpreter.return_data_buffer[offset_usize..offset_usize + 32]).into(); + + *offset = B256::from(output).into(); } pub fn gas(interpreter: &mut Interpreter, _host: &mut H) { @@ -167,9 +188,9 @@ pub fn gas(interpreter: &mut Interpreter, _host: &mut H) { mod test { use super::*; use crate::{ - opcode::{make_instruction_table, RETURNDATALOAD}, + opcode::{make_instruction_table, RETURNDATACOPY, RETURNDATALOAD}, primitives::{bytes, Bytecode, PragueSpec}, - DummyHost, Gas, + DummyHost, Gas, InstructionResult, }; #[test] @@ -178,7 +199,13 @@ mod test { let mut host = DummyHost::default(); let mut interp = Interpreter::new_bytecode(Bytecode::LegacyRaw( - [RETURNDATALOAD, RETURNDATALOAD, RETURNDATALOAD].into(), + [ + RETURNDATALOAD, + RETURNDATALOAD, + RETURNDATALOAD, + RETURNDATALOAD, + ] + .into(), )); interp.is_eof = true; interp.gas = Gas::new(10000); @@ -203,8 +230,112 @@ mod test { ); let _ = interp.stack.pop(); - let _ = interp.stack.push(U256::from(2)); + let _ = interp.stack.push(U256::from(32)); + interp.step(&table, &mut host); + assert_eq!(interp.instruction_result, InstructionResult::Continue); + assert_eq!( + interp.stack.data(), + &vec![U256::from_limbs([0x00, 0x00, 0x00, 0x00])] + ); + + // Offset right at the boundary of the return data buffer size + let _ = interp.stack.pop(); + let _ = interp + .stack + .push(U256::from(interp.return_data_buffer.len())); interp.step(&table, &mut host); - assert_eq!(interp.instruction_result, InstructionResult::OutOfOffset); + assert_eq!(interp.instruction_result, InstructionResult::Continue); + assert_eq!( + interp.stack.data(), + &vec![U256::from_limbs([0x00, 0x00, 0x00, 0x00])] + ); + } + + #[test] + fn returndatacopy() { + let table = make_instruction_table::<_, PragueSpec>(); + let mut host = DummyHost::default(); + + let mut interp = Interpreter::new_bytecode(Bytecode::LegacyRaw( + [ + RETURNDATACOPY, + RETURNDATACOPY, + RETURNDATACOPY, + RETURNDATACOPY, + RETURNDATACOPY, + RETURNDATACOPY, + ] + .into(), + )); + interp.is_eof = true; + interp.gas = Gas::new(10000); + + interp.return_data_buffer = + bytes!("000000000000000400000000000000030000000000000002000000000000000100"); + interp.shared_memory.resize(256); + + // Copying within bounds + interp.stack.push(U256::from(32)).unwrap(); + interp.stack.push(U256::from(0)).unwrap(); + interp.stack.push(U256::from(0)).unwrap(); + interp.step(&table, &mut host); + assert_eq!(interp.instruction_result, InstructionResult::Continue); + assert_eq!( + interp.shared_memory.slice(0, 32), + &interp.return_data_buffer[0..32] + ); + + // Copying with partial out-of-bounds (should zero pad) + interp.stack.push(U256::from(64)).unwrap(); + interp.stack.push(U256::from(16)).unwrap(); + interp.stack.push(U256::from(64)).unwrap(); + interp.step(&table, &mut host); + assert_eq!(interp.instruction_result, InstructionResult::Continue); + assert_eq!( + interp.shared_memory.slice(64, 16), + &interp.return_data_buffer[16..32] + ); + assert_eq!(&interp.shared_memory.slice(80, 48), &[0u8; 48]); + + // Completely out-of-bounds (should be all zeros) + interp.stack.push(U256::from(32)).unwrap(); + interp.stack.push(U256::from(96)).unwrap(); + interp.stack.push(U256::from(128)).unwrap(); + interp.step(&table, &mut host); + assert_eq!(interp.instruction_result, InstructionResult::Continue); + assert_eq!(&interp.shared_memory.slice(128, 32), &[0u8; 32]); + + // Large offset + interp.stack.push(U256::from(32)).unwrap(); + interp.stack.push(U256::MAX).unwrap(); + interp.stack.push(U256::from(0)).unwrap(); + interp.step(&table, &mut host); + assert_eq!(interp.instruction_result, InstructionResult::Continue); + assert_eq!(&interp.shared_memory.slice(0, 32), &[0u8; 32]); + + // Offset just before the boundary of the return data buffer size + interp.stack.push(U256::from(32)).unwrap(); + interp + .stack + .push(U256::from(interp.return_data_buffer.len() - 32)) + .unwrap(); + interp.stack.push(U256::from(0)).unwrap(); + interp.step(&table, &mut host); + assert_eq!(interp.instruction_result, InstructionResult::Continue); + assert_eq!( + interp.shared_memory.slice(0, 32), + &interp.return_data_buffer[interp.return_data_buffer.len() - 32..] + ); + + // Offset right at the boundary of the return data buffer size + interp.stack.push(U256::from(32)).unwrap(); + interp + .stack + .push(U256::from(interp.return_data_buffer.len())) + .unwrap(); + interp.stack.push(U256::from(0)).unwrap(); + interp.step(&table, &mut host); + assert_eq!(interp.instruction_result, InstructionResult::Continue); + assert_eq!(&interp.shared_memory.slice(0, 32), &[0u8; 32]); } } diff --git a/crates/interpreter/src/interpreter.rs b/crates/interpreter/src/interpreter.rs index 21a2d1b3..0806332b 100644 --- a/crates/interpreter/src/interpreter.rs +++ b/crates/interpreter/src/interpreter.rs @@ -17,6 +17,7 @@ use crate::{ use core::cmp::min; use revm_primitives::{Bytecode, Eof, U256}; use std::borrow::ToOwned; +use std::sync::Arc; /// EVM bytecode interpreter. #[derive(Debug)] @@ -64,22 +65,10 @@ pub struct Interpreter { impl Default for Interpreter { fn default() -> Self { - Self::new(Contract::default(), 0, false) + Self::new(Contract::default(), u64::MAX, false) } } -/// The result of an interpreter operation. -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct InterpreterResult { - /// The result of the instruction execution. - pub result: InstructionResult, - /// The output of the instruction execution. - pub output: Bytes, - /// The gas usage information. - pub gas: Gas, -} - impl Interpreter { /// Create new interpreter pub fn new(contract: Contract, gas_limit: u64, is_static: bool) -> Self { @@ -112,7 +101,7 @@ impl Interpreter { } #[inline] - pub fn eof(&self) -> Option<&Eof> { + pub fn eof(&self) -> Option<&Arc> { self.contract.bytecode.eof() } @@ -204,6 +193,7 @@ impl Interpreter { } pub fn insert_eofcreate_outcome(&mut self, create_outcome: EOFCreateOutcome) { + self.instruction_result = InstructionResult::Continue; let instruction_result = create_outcome.instruction_result(); self.return_data_buffer = if *instruction_result == InstructionResult::Revert { @@ -262,32 +252,53 @@ impl Interpreter { call_outcome: CallOutcome, ) { self.instruction_result = InstructionResult::Continue; - self.return_data_buffer.clone_from(call_outcome.output()); let out_offset = call_outcome.memory_start(); let out_len = call_outcome.memory_length(); + let out_ins_result = *call_outcome.instruction_result(); + let out_gas = call_outcome.gas(); + self.return_data_buffer = call_outcome.result.output; let target_len = min(out_len, self.return_data_buffer.len()); - match call_outcome.instruction_result() { + match out_ins_result { return_ok!() => { // return unspend gas. - let remaining = call_outcome.gas().remaining(); - let refunded = call_outcome.gas().refunded(); - self.gas.erase_cost(remaining); - self.gas.record_refund(refunded); + self.gas.erase_cost(out_gas.remaining()); + self.gas.record_refund(out_gas.refunded()); shared_memory.set(out_offset, &self.return_data_buffer[..target_len]); - push!(self, U256::from(1)); + push!( + self, + if self.is_eof { + U256::ZERO + } else { + U256::from(1) + } + ); } return_revert!() => { - self.gas.erase_cost(call_outcome.gas().remaining()); + self.gas.erase_cost(out_gas.remaining()); shared_memory.set(out_offset, &self.return_data_buffer[..target_len]); - push!(self, U256::ZERO); + push!( + self, + if self.is_eof { + U256::from(1) + } else { + U256::ZERO + } + ); } InstructionResult::FatalExternalError => { panic!("Fatal external error in insert_call_outcome"); } _ => { - push!(self, U256::ZERO); + push!( + self, + if self.is_eof { + U256::from(2) + } else { + U256::ZERO + } + ); } } } @@ -389,7 +400,28 @@ impl Interpreter { } } +/// The result of an interpreter operation. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct InterpreterResult { + /// The result of the instruction execution. + pub result: InstructionResult, + /// The output of the instruction execution. + pub output: Bytes, + /// The gas usage information. + pub gas: Gas, +} + impl InterpreterResult { + /// Returns a new `InterpreterResult` with the given values. + pub fn new(result: InstructionResult, output: Bytes, gas: Gas) -> Self { + Self { + result, + output, + gas, + } + } + /// Returns whether the instruction result is a success. #[inline] pub const fn is_ok(&self) -> bool { diff --git a/crates/interpreter/src/interpreter/analysis.rs b/crates/interpreter/src/interpreter/analysis.rs index 2b2d89f7..73d33d09 100644 --- a/crates/interpreter/src/interpreter/analysis.rs +++ b/crates/interpreter/src/interpreter/analysis.rs @@ -589,4 +589,19 @@ mod test { )) ); } + + #[test] + fn test4() { + //0xef0001010004020001000e04000000008000045f6000e100025f5f6000e1fffd00 + // result:Result { result: false, exception: Some("EOF_InvalidNumberOfOutputs") } + let err = validate_raw_eof( + hex!("ef0001010004020001000e04000000008000045f6000e100025f5f6000e1fffd00").into(), + ); + assert_eq!( + err, + Err(EofError::Validation( + EofValidationError::BackwardJumpBiggestNumMismatch + )) + ); + } } diff --git a/crates/interpreter/src/interpreter/shared_memory.rs b/crates/interpreter/src/interpreter/shared_memory.rs index cc63e1bf..601379b5 100644 --- a/crates/interpreter/src/interpreter/shared_memory.rs +++ b/crates/interpreter/src/interpreter/shared_memory.rs @@ -256,7 +256,7 @@ impl SharedMemory { /// /// # Panics /// - /// Panics on out of bounds. + /// Panics if memory is out of bounds. #[inline] #[cfg_attr(debug_assertions, track_caller)] pub fn set_data(&mut self, memory_offset: usize, data_offset: usize, len: usize, data: &[u8]) { diff --git a/crates/interpreter/src/interpreter_action.rs b/crates/interpreter/src/interpreter_action.rs index 0581e220..1459b022 100644 --- a/crates/interpreter/src/interpreter_action.rs +++ b/crates/interpreter/src/interpreter_action.rs @@ -9,7 +9,7 @@ pub use call_inputs::{CallInputs, CallScheme, CallValue}; pub use call_outcome::CallOutcome; pub use create_inputs::{CreateInputs, CreateScheme}; pub use create_outcome::CreateOutcome; -pub use eof_create_inputs::EOFCreateInput; +pub use eof_create_inputs::EOFCreateInputs; pub use eof_create_outcome::EOFCreateOutcome; use crate::InterpreterResult; @@ -24,7 +24,7 @@ pub enum InterpreterAction { /// CREATE or CREATE2 instruction called. Create { inputs: Box }, /// EOF CREATE instruction called. - EOFCreate { inputs: Box }, + EOFCreate { inputs: Box }, /// Interpreter finished execution. Return { result: InterpreterResult }, /// No action diff --git a/crates/interpreter/src/interpreter_action/eof_create_inputs.rs b/crates/interpreter/src/interpreter_action/eof_create_inputs.rs index 25dac1e0..4dc8190b 100644 --- a/crates/interpreter/src/interpreter_action/eof_create_inputs.rs +++ b/crates/interpreter/src/interpreter_action/eof_create_inputs.rs @@ -1,10 +1,10 @@ -use crate::primitives::{Address, Eof, U256}; -use core::ops::Range; +use crate::primitives::{eof::EofDecodeError, Address, Bytes, Eof, TxEnv, U256}; +use std::boxed::Box; /// Inputs for EOF create call. #[derive(Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct EOFCreateInput { +pub struct EOFCreateInputs { /// Caller of Eof Craate pub caller: Address, /// New contract address. @@ -13,14 +13,34 @@ pub struct EOFCreateInput { pub value: U256, /// Init eof code that is going to be executed. pub eof_init_code: Eof, + /// Call data the input of the EOFCREATE call. + pub input: Bytes, /// Gas limit for the create call. pub gas_limit: u64, - /// Return memory range. If EOF creation Reverts it can return the - /// the memory range. - pub return_memory_range: Range, } -impl EOFCreateInput { +impl EOFCreateInputs { + /// Returns boxed EOFCreateInput or error. + /// Internally calls [`Self::new_tx`]. + pub fn new_tx_boxed(tx: &TxEnv, nonce: u64) -> Result, EofDecodeError> { + Ok(Box::new(Self::new_tx(tx, nonce)?)) + } + + /// Create new EOF crate input from transaction that has concatenated eof init code and calldata. + /// + /// Legacy transaction still have optional nonce so we need to obtain it. + pub fn new_tx(tx: &TxEnv, nonce: u64) -> Result { + let (eof_init_code, input) = Eof::decode_dangling(tx.data.clone())?; + Ok(EOFCreateInputs { + caller: tx.caller, + created_address: tx.caller.create(nonce), + value: tx.value, + eof_init_code, + gas_limit: tx.gas_limit, + input, + }) + } + /// Returns a new instance of EOFCreateInput. pub fn new( caller: Address, @@ -28,15 +48,15 @@ impl EOFCreateInput { value: U256, eof_init_code: Eof, gas_limit: u64, - return_memory_range: Range, - ) -> EOFCreateInput { - EOFCreateInput { + input: Bytes, + ) -> EOFCreateInputs { + EOFCreateInputs { caller, created_address, value, eof_init_code, gas_limit, - return_memory_range, + input, } } } diff --git a/crates/interpreter/src/interpreter_action/eof_create_outcome.rs b/crates/interpreter/src/interpreter_action/eof_create_outcome.rs index 9ec669a0..beeda24e 100644 --- a/crates/interpreter/src/interpreter_action/eof_create_outcome.rs +++ b/crates/interpreter/src/interpreter_action/eof_create_outcome.rs @@ -1,5 +1,3 @@ -use core::ops::Range; - use crate::{Gas, InstructionResult, InterpreterResult}; use revm_primitives::{Address, Bytes}; @@ -14,8 +12,6 @@ pub struct EOFCreateOutcome { pub result: InterpreterResult, /// An optional address associated with the create operation. pub address: Address, - /// Return memory range. If EOF creation Reverts it can return bytes from the memory. - pub return_memory_range: Range, } impl EOFCreateOutcome { @@ -30,16 +26,8 @@ impl EOFCreateOutcome { /// # Returns /// /// A new [`EOFCreateOutcome`] instance. - pub fn new( - result: InterpreterResult, - address: Address, - return_memory_range: Range, - ) -> Self { - Self { - result, - address, - return_memory_range, - } + pub fn new(result: InterpreterResult, address: Address) -> Self { + Self { result, address } } /// Retrieves a reference to the [`InstructionResult`] from the [`InterpreterResult`]. @@ -80,9 +68,4 @@ impl EOFCreateOutcome { pub fn gas(&self) -> &Gas { &self.result.gas } - - /// Returns the memory range that Revert bytes are going to be written. - pub fn return_range(&self) -> Range { - self.return_memory_range.clone() - } } diff --git a/crates/interpreter/src/lib.rs b/crates/interpreter/src/lib.rs index 01db0327..64167dd4 100644 --- a/crates/interpreter/src/lib.rs +++ b/crates/interpreter/src/lib.rs @@ -37,7 +37,7 @@ pub use interpreter::{ }; pub use interpreter_action::{ CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome, CreateScheme, - EOFCreateInput, EOFCreateOutcome, InterpreterAction, + EOFCreateInputs, EOFCreateOutcome, InterpreterAction, }; pub use opcode::{Instruction, OpCode, OPCODE_INFO_JUMPTABLE}; pub use primitives::{MAX_CODE_SIZE, MAX_INITCODE_SIZE}; diff --git a/crates/interpreter/src/opcode.rs b/crates/interpreter/src/opcode.rs index b5b6ffd1..5617a0d7 100644 --- a/crates/interpreter/src/opcode.rs +++ b/crates/interpreter/src/opcode.rs @@ -2,128 +2,15 @@ pub mod eof_printer; -use crate::{instructions::*, primitives::Spec, Host, Interpreter}; +mod tables; +pub use tables::{ + make_boxed_instruction_table, make_instruction_table, update_boxed_instruction, + BoxedInstruction, BoxedInstructionTable, DynInstruction, Instruction, InstructionTable, + InstructionTables, +}; + +use crate::{instructions::*, primitives::Spec, Host}; use core::{fmt, ptr::NonNull}; -use std::boxed::Box; - -/// EVM opcode function signature. -pub type Instruction = fn(&mut Interpreter, &mut H); - -/// Instruction table is list of instruction function pointers mapped to -/// 256 EVM opcodes. -pub type InstructionTable = [Instruction; 256]; - -/// EVM opcode function signature. -pub type BoxedInstruction<'a, H> = Box; - -/// A table of instructions. -pub type BoxedInstructionTable<'a, H> = [BoxedInstruction<'a, H>; 256]; - -/// Instruction set that contains plain instruction table that contains simple `fn` function pointer. -/// and Boxed `Fn` variant that contains `Box` function pointer that can be used with closured. -/// -/// Note that `Plain` variant gives us 10-20% faster Interpreter execution. -/// -/// Boxed variant can be used to wrap plain function pointer with closure. -pub enum InstructionTables<'a, H> { - Plain(InstructionTable), - Boxed(BoxedInstructionTable<'a, H>), -} - -impl InstructionTables<'_, H> { - /// Creates a plain instruction table for the given spec. - #[inline] - pub const fn new_plain() -> Self { - Self::Plain(make_instruction_table::()) - } -} - -impl<'a, H: Host + 'a> InstructionTables<'a, H> { - /// Inserts a boxed instruction into the table with the specified index. - /// - /// This will convert the table into the [BoxedInstructionTable] variant if it is currently a - /// plain instruction table, before inserting the instruction. - #[inline] - pub fn insert_boxed(&mut self, opcode: u8, instruction: BoxedInstruction<'a, H>) { - // first convert the table to boxed variant - self.convert_boxed(); - - // now we can insert the instruction - match self { - Self::Plain(_) => { - unreachable!("we already converted the table to boxed variant"); - } - Self::Boxed(table) => { - table[opcode as usize] = Box::new(instruction); - } - } - } - - /// Inserts the instruction into the table with the specified index. - #[inline] - pub fn insert(&mut self, opcode: u8, instruction: Instruction) { - match self { - Self::Plain(table) => { - table[opcode as usize] = instruction; - } - Self::Boxed(table) => { - table[opcode as usize] = Box::new(instruction); - } - } - } - - /// Converts the current instruction table to a boxed variant. If the table is already boxed, - /// this is a no-op. - #[inline] - pub fn convert_boxed(&mut self) { - match self { - Self::Plain(table) => { - *self = Self::Boxed(core::array::from_fn(|i| { - let instruction: BoxedInstruction<'a, H> = Box::new(table[i]); - instruction - })); - } - Self::Boxed(_) => {} - }; - } -} - -/// Make instruction table. -#[inline] -pub const fn make_instruction_table() -> InstructionTable { - // Force const-eval of the table creation, making this function trivial. - // TODO: Replace this with a `const {}` block once it is stable. - struct ConstTable { - _host: core::marker::PhantomData, - _spec: core::marker::PhantomData, - } - impl ConstTable { - const NEW: InstructionTable = { - let mut tables: InstructionTable = [control::unknown; 256]; - let mut i = 0; - while i < 256 { - tables[i] = instruction::(i as u8); - i += 1; - } - tables - }; - } - ConstTable::::NEW -} - -/// Make boxed instruction table that calls `outer` closure for every instruction. -#[inline] -pub fn make_boxed_instruction_table<'a, H, SPEC, FN>( - table: InstructionTable, - mut outer: FN, -) -> BoxedInstructionTable<'a, H> -where - H: Host, - SPEC: Spec + 'a, - FN: FnMut(Instruction) -> BoxedInstruction<'a, H>, -{ - core::array::from_fn(|i| outer(table[i])) -} /// An error indicating that an opcode is invalid. #[derive(Debug, PartialEq, Eq)] diff --git a/crates/interpreter/src/opcode/eof_printer.rs b/crates/interpreter/src/opcode/eof_printer.rs index dc226882..e48f153d 100644 --- a/crates/interpreter/src/opcode/eof_printer.rs +++ b/crates/interpreter/src/opcode/eof_printer.rs @@ -26,11 +26,13 @@ pub fn print_eof_code(code: &[u8]) { print!("{}", opcode.name()); if opcode.immediate_size() != 0 { - print!( - " : 0x{:}", - hex::encode(&code[i + 1..i + 1 + opcode.immediate_size() as usize]) - ); + let immediate = &code[i + 1..i + 1 + opcode.immediate_size() as usize]; + print!(" : 0x{:}", hex::encode(immediate)); + if opcode.immediate_size() == 2 { + print!(" ({})", i16::from_be_bytes(immediate.try_into().unwrap())); + } } + println!(); let mut rjumpv_additional_immediates = 0; if op == RJUMPV { @@ -47,7 +49,7 @@ pub fn print_eof_code(code: &[u8]) { for vtablei in 0..len { let offset = unsafe { read_i16(code.as_ptr().add(i + 2 + 2 * vtablei)) } as isize; - println!("RJUMPV[{vtablei}]: 0x{offset:04X}({offset})"); + println!("RJUMPV[{vtablei}]: 0x{offset:04X} ({offset})"); } } diff --git a/crates/interpreter/src/opcode/tables.rs b/crates/interpreter/src/opcode/tables.rs new file mode 100644 index 00000000..48a1b6cc --- /dev/null +++ b/crates/interpreter/src/opcode/tables.rs @@ -0,0 +1,179 @@ +#![allow(clippy::wrong_self_convention)] + +use super::instruction; +use crate::{instructions::control, primitives::Spec, Host, Interpreter}; +use std::boxed::Box; + +/// EVM opcode function signature. +pub type Instruction = fn(&mut Interpreter, &mut H); + +/// Instruction table is list of instruction function pointers mapped to 256 EVM opcodes. +pub type InstructionTable = [Instruction; 256]; + +/// EVM dynamic opcode function signature. +pub type DynInstruction<'a, H> = dyn Fn(&mut Interpreter, &mut H) + 'a; + +/// EVM boxed dynamic opcode function signature. +pub type BoxedInstruction<'a, H> = Box>; + +/// A table of boxed instructions. +pub type BoxedInstructionTable<'a, H> = [BoxedInstruction<'a, H>; 256]; + +/// Either a plain, static instruction table, or a boxed, dynamic instruction table. +/// +/// Note that `Plain` variant is about 10-20% faster in Interpreter execution. +pub enum InstructionTables<'a, H: ?Sized> { + Plain(InstructionTable), + Boxed(BoxedInstructionTable<'a, H>), +} + +impl<'a, H: Host + ?Sized> InstructionTables<'a, H> { + /// Creates a plain instruction table for the given spec. See [`make_instruction_table`]. + #[inline] + pub const fn new_plain() -> Self { + Self::Plain(make_instruction_table::()) + } +} + +impl<'a, H: Host + ?Sized + 'a> InstructionTables<'a, H> { + /// Inserts the instruction into the table with the specified index. + #[inline] + pub fn insert(&mut self, opcode: u8, instruction: Instruction) { + match self { + Self::Plain(table) => table[opcode as usize] = instruction, + Self::Boxed(table) => table[opcode as usize] = Box::new(instruction), + } + } + + /// Converts the current instruction table to a boxed variant if it is not already, and returns + /// a mutable reference to the boxed table. + #[inline] + pub fn to_boxed(&mut self) -> &mut BoxedInstructionTable<'a, H> { + self.to_boxed_with(|i| Box::new(i)) + } + + /// Converts the current instruction table to a boxed variant if it is not already with `f`, + /// and returns a mutable reference to the boxed table. + #[inline] + pub fn to_boxed_with(&mut self, f: F) -> &mut BoxedInstructionTable<'a, H> + where + F: FnMut(Instruction) -> BoxedInstruction<'a, H>, + { + match self { + Self::Plain(_) => self.to_boxed_with_slow(f), + Self::Boxed(boxed) => boxed, + } + } + + #[cold] + fn to_boxed_with_slow(&mut self, f: F) -> &mut BoxedInstructionTable<'a, H> + where + F: FnMut(Instruction) -> BoxedInstruction<'a, H>, + { + let Self::Plain(table) = self else { + unreachable!() + }; + *self = Self::Boxed(make_boxed_instruction_table(table, f)); + let Self::Boxed(boxed) = self else { + unreachable!() + }; + boxed + } + + /// Returns a mutable reference to the boxed instruction at the specified index. + #[inline] + pub fn get_boxed(&mut self, opcode: u8) -> &mut BoxedInstruction<'a, H> { + &mut self.to_boxed()[opcode as usize] + } + + /// Inserts a boxed instruction into the table at the specified index. + #[inline] + pub fn insert_boxed(&mut self, opcode: u8, instruction: BoxedInstruction<'a, H>) { + *self.get_boxed(opcode) = instruction; + } + + /// Replaces a boxed instruction into the table at the specified index, returning the previous + /// instruction. + #[inline] + pub fn replace_boxed( + &mut self, + opcode: u8, + instruction: BoxedInstruction<'a, H>, + ) -> BoxedInstruction<'a, H> { + core::mem::replace(self.get_boxed(opcode), instruction) + } + + /// Updates a single instruction in the table at the specified index with `f`. + #[inline] + pub fn update_boxed(&mut self, opcode: u8, f: F) + where + F: Fn(&DynInstruction<'a, H>, &mut Interpreter, &mut H) + 'a, + { + update_boxed_instruction(self.get_boxed(opcode), f) + } + + /// Updates every instruction in the table by calling `f`. + #[inline] + pub fn update_all(&mut self, f: F) + where + F: Fn(&DynInstruction<'a, H>, &mut Interpreter, &mut H) + Copy + 'a, + { + // Don't go through `to_boxed` to avoid allocating the plain table twice. + match self { + Self::Plain(_) => { + self.to_boxed_with(|prev| Box::new(move |i, h| f(&prev, i, h))); + } + Self::Boxed(boxed) => boxed + .iter_mut() + .for_each(|instruction| update_boxed_instruction(instruction, f)), + } + } +} + +/// Make instruction table. +#[inline] +pub const fn make_instruction_table() -> InstructionTable { + // Force const-eval of the table creation, making this function trivial. + // TODO: Replace this with a `const {}` block once it is stable. + struct ConstTable { + _host: core::marker::PhantomData, + _spec: core::marker::PhantomData, + } + impl ConstTable { + const NEW: InstructionTable = { + let mut tables: InstructionTable = [control::unknown; 256]; + let mut i = 0; + while i < 256 { + tables[i] = instruction::(i as u8); + i += 1; + } + tables + }; + } + ConstTable::::NEW +} + +/// Make boxed instruction table that calls `f` closure for every instruction. +#[inline] +pub fn make_boxed_instruction_table<'a, H, FN>( + table: &InstructionTable, + mut f: FN, +) -> BoxedInstructionTable<'a, H> +where + H: Host + ?Sized, + FN: FnMut(Instruction) -> BoxedInstruction<'a, H>, +{ + core::array::from_fn(|i| f(table[i])) +} + +/// Updates a boxed instruction with a new one. +#[inline] +pub fn update_boxed_instruction<'a, H, F>(instruction: &mut BoxedInstruction<'a, H>, f: F) +where + H: Host + ?Sized + 'a, + F: Fn(&DynInstruction<'a, H>, &mut Interpreter, &mut H) + 'a, +{ + // NOTE: This first allocation gets elided by the compiler. + let prev = core::mem::replace(instruction, Box::new(|_, _| {})); + *instruction = Box::new(move |i, h| f(&prev, i, h)); +} diff --git a/crates/precompile/Cargo.toml b/crates/precompile/Cargo.toml index 5f066031..84cbd575 100644 --- a/crates/precompile/Cargo.toml +++ b/crates/precompile/Cargo.toml @@ -21,20 +21,18 @@ rust_2018_idioms = "deny" all = "warn" [dependencies] +alloy-rlp = { version = "0.3.4", default-features = false, features = [ + "arrayvec", + "derive", +] } +alloy-primitives = {version = "0.7.2"} + revm-primitives = { path = "../primitives", version = "4.0.0", default-features = false } -bn = { package = "substrate-bn", version = "0.6", default-features = false } once_cell = { version = "1.19", default-features = false, features = ["alloc"] } -ripemd = { version = "0.1", default-features = false } -sha2 = { version = "0.10", default-features = false } -# modexp precompile -aurora-engine-modexp = { version = "1.1", default-features = false } - -# Optional KZG point evaluation precompile -c-kzg = { version = "1.0.2", default-features = false, optional = true } -# ecRecover precompile +# ecRecover k256 = { version = "0.13.3", default-features = false, features = ["ecdsa"] } -secp256k1 = { version = "0.29.0", default-features = false, features = [ +secp256k1 = { version = ">=0.28, <=0.29", default-features = false, features = [ "alloc", "recovery", "rand", @@ -48,23 +46,33 @@ prost = { version = "0.12.3" } bls_on_arkworks = "0.3.0" tendermint = { git = "https://github.com/bnb-chain/tendermint-rs-parlia", tag = "v0.1.0-beta.1", features = ["secp256k1"] } parity-bytes = { version = "0.1.2", default-features = false } -alloy-rlp = { version = "0.3", default-features = false, features = [ - "arrayvec", - "derive", -] } -alloy-primitives = {version = "0.7.0"} -# p256verify precompile -p256 = { version = "0.13.2", optional = true, default-features = false, features = ["ecdsa"] } +# SHA2-256 and RIPEMD-160 +sha2 = { version = "0.10", default-features = false } +ripemd = { version = "0.1", default-features = false } + +# modexp +aurora-engine-modexp = { version = "1.1", default-features = false } + +# ecAdd, ecMul, ecPairing +bn = { package = "substrate-bn", version = "0.6", default-features = false } + +# KZG point evaluation precompile +c-kzg = { version = "1.0.2", default-features = false, optional = true } # BLS12-381 precompiles -blst = { version = "0.3.11", optional = true } +blst = { version = "0.3.12", optional = true } + +# p256verify precompile +p256 = { version = "0.13.2", optional = true, default-features = false, features = [ + "ecdsa", +] } [dev-dependencies] -criterion = { version = "0.5" } +criterion = "0.5" rand = { version = "0.8", features = ["std"] } eyre = "0.6.12" -rstest = "0.19.0" +rstest = "0.21.0" serde = "1.0" serde_json = "1.0" serde_derive = "1.0" diff --git a/crates/precompile/benches/bench.rs b/crates/precompile/benches/bench.rs index 48452b4a..faae103a 100644 --- a/crates/precompile/benches/bench.rs +++ b/crates/precompile/benches/bench.rs @@ -1,8 +1,9 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use revm_precompile::{ bn128::{ + add::ISTANBUL_ADD_GAS_COST, pair::{ISTANBUL_PAIR_BASE, ISTANBUL_PAIR_PER_POINT}, - run_pair, + run_add, run_pair, }, kzg_point_evaluation::run, secp256k1::ec_recover_run, @@ -44,10 +45,26 @@ pub fn benchmark_crypto_precompiles(c: &mut Criterion) { u64::MAX, ) .unwrap() - .0; + .gas_used; println!("gas used by regular pairing call: {:?}", res); + // === BN128 ADD === + + let ecadd_input = hex::decode( + "\ + 18b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9\ + 063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f37266\ + 07c2b7f58a84bd6145f00c9c2bc0bb1a187f20ff2c92963a88019e7c6a014eed\ + 06614e20c147e940f2d70da3f74c9a17df361706a4485c742bd6788478fa17d7", + ) + .unwrap(); + + let res = run_add(&ecadd_input, ISTANBUL_ADD_GAS_COST, 150) + .unwrap() + .gas_used; + println!("gas used by bn128 add precompile: {:?}", res); + // === ECRECOVER === // generate secp256k1 signature @@ -71,7 +88,9 @@ pub fn benchmark_crypto_precompiles(c: &mut Criterion) { message_and_signature[64..128].copy_from_slice(&data); let message_and_signature = Bytes::from(message_and_signature); - let gas = ec_recover_run(&message_and_signature, u64::MAX).unwrap(); + let gas = ec_recover_run(&message_and_signature, u64::MAX) + .unwrap() + .gas_used; println!("gas used by ecrecover precompile: {:?}", gas); // === POINT_EVALUATION === @@ -88,8 +107,8 @@ pub fn benchmark_crypto_precompiles(c: &mut Criterion) { let gas = 50000; let env = Env::default(); - let (actual_gas, _actual_output) = run(&kzg_input, gas, &env).unwrap(); - println!("gas used by kzg precompile: {:?}", actual_gas); + let output = run(&kzg_input, gas, &env).unwrap(); + println!("gas used by kzg precompile: {:?}", output.gas_used); group.bench_function(group_name("ecrecover precompile"), |b| { b.iter(|| { @@ -98,6 +117,13 @@ pub fn benchmark_crypto_precompiles(c: &mut Criterion) { }) }); + group.bench_function(group_name("bn128 add precompile"), |b| { + b.iter(|| { + run_add(&ecadd_input, ISTANBUL_ADD_GAS_COST, 150).unwrap(); + black_box(()) + }) + }); + group.bench_function(group_name("ecpairing precompile"), |b| { b.iter(|| { run_pair( diff --git a/crates/precompile/src/blake2.rs b/crates/precompile/src/blake2.rs index 057bba74..1b4ab925 100644 --- a/crates/precompile/src/blake2.rs +++ b/crates/precompile/src/blake2.rs @@ -1,5 +1,5 @@ use crate::{Error, Precompile, PrecompileResult, PrecompileWithAddress}; -use revm_primitives::Bytes; +use revm_primitives::{Bytes, PrecompileOutput}; const F_ROUND: u64 = 1; const INPUT_LENGTH: usize = 213; @@ -14,20 +14,20 @@ pub fn run(input: &Bytes, gas_limit: u64) -> PrecompileResult { let input = &input[..]; if input.len() != INPUT_LENGTH { - return Err(Error::Blake2WrongLength); + return Err(Error::Blake2WrongLength.into()); } let f = match input[212] { 1 => true, 0 => false, - _ => return Err(Error::Blake2WrongFinalIndicatorFlag), + _ => return Err(Error::Blake2WrongFinalIndicatorFlag.into()), }; // rounds 4 bytes let rounds = u32::from_be_bytes(input[..4].try_into().unwrap()) as usize; let gas_used = rounds as u64 * F_ROUND; if gas_used > gas_limit { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } let mut h = [0u64; 8]; @@ -51,7 +51,7 @@ pub fn run(input: &Bytes, gas_limit: u64) -> PrecompileResult { out[i..i + 8].copy_from_slice(&h.to_le_bytes()); } - Ok((gas_used, out.into())) + Ok(PrecompileOutput::new(gas_used, out.into())) } pub mod algo { diff --git a/crates/precompile/src/bls.rs b/crates/precompile/src/bls.rs index 0f73bb3b..bcece2bc 100644 --- a/crates/precompile/src/bls.rs +++ b/crates/precompile/src/bls.rs @@ -1,6 +1,6 @@ use crate::{Error, Precompile, PrecompileResult, PrecompileWithAddress}; use bls_on_arkworks as bls; -use revm_primitives::Bytes; +use revm_primitives::{Bytes, PrecompileOutput}; use std::vec::Vec; pub(crate) const BLS_SIGNATURE_VALIDATION: PrecompileWithAddress = PrecompileWithAddress( @@ -21,7 +21,7 @@ const BLS_DST: &[u8] = bls::DST_ETHEREUM.as_bytes(); fn bls_signature_validation_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { let cost = calc_gas_cost(input); if cost > gas_limit { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } let msg_and_sig_length = BLS_MSG_HASH_LENGTH + BLS_SIGNATURE_LENGTH; @@ -29,7 +29,7 @@ fn bls_signature_validation_run(input: &Bytes, gas_limit: u64) -> PrecompileResu if (input_length <= msg_and_sig_length) || ((input_length - msg_and_sig_length) % BLS_SINGLE_PUBKEY_LENGTH != 0) { - return Err(Error::Reverted(cost)); + return Err(Error::Reverted(cost).into()); } let msg_hash: &Vec = &input[..BLS_MSG_HASH_LENGTH as usize].to_vec(); @@ -38,7 +38,7 @@ fn bls_signature_validation_run(input: &Bytes, gas_limit: u64) -> PrecompileResu // check signature format if bls::signature_to_point(&signature.to_vec()).is_err() { - return Err(Error::Reverted(cost)); + return Err(Error::Reverted(cost).into()); } let pub_key_count = (input_length - msg_and_sig_length) / BLS_SINGLE_PUBKEY_LENGTH; @@ -50,13 +50,13 @@ fn bls_signature_validation_run(input: &Bytes, gas_limit: u64) -> PrecompileResu let pub_key = &pub_keys_data[i as usize * BLS_SINGLE_PUBKEY_LENGTH as usize ..(i + 1) as usize * BLS_SINGLE_PUBKEY_LENGTH as usize]; if !bls::key_validate(&pub_key.to_vec()) { - return Err(Error::Reverted(cost)); + return Err(Error::Reverted(cost).into()); } pub_keys.push(pub_key.to_vec()); msg_hashes.push(msg_hash.clone().to_vec()); } if pub_keys.is_empty() { - return Err(Error::Reverted(cost)); + return Err(Error::Reverted(cost).into()); } // verify signature @@ -67,7 +67,7 @@ fn bls_signature_validation_run(input: &Bytes, gas_limit: u64) -> PrecompileResu output = Bytes::from(vec![0]); } - Ok((cost, output)) + Ok(PrecompileOutput::new(cost, output)) } fn calc_gas_cost(input: &Bytes) -> u64 { @@ -104,7 +104,7 @@ mod tests { let excepted_output = Bytes::from(vec![1]); let result = match bls_signature_validation_run(&Bytes::from(input.clone()), 100_000_000) { - Ok((_, output)) => output, + Ok(o) => o.bytes, Err(e) => panic!("BLS signature validation failed, {:?}", e), }; assert_eq!(result, excepted_output); @@ -120,7 +120,7 @@ mod tests { let excepted_output = Bytes::from(vec![0]); let result = match bls_signature_validation_run(&Bytes::from(input.clone()), 100_000_000) { - Ok((_, output)) => output, + Ok(o) => o.bytes, Err(e) => panic!("BLS signature validation failed, {:?}", e), }; assert_eq!(result, excepted_output); @@ -136,7 +136,7 @@ mod tests { match bls_signature_validation_run(&Bytes::from(input.clone()), 100_000_000) { Ok(_) => panic!("BLS signature validation failed, expect error"), - Err(e) => assert_eq!(e, Error::Reverted(4500)), + Err(e) => assert_eq!(e, Error::Reverted(4500).into()), } // wrong pubkey @@ -150,7 +150,7 @@ mod tests { match bls_signature_validation_run(&Bytes::from(input.clone()), 100_000_000) { Ok(_) => panic!("BLS signature validation failed, expect error"), - Err(e) => assert_eq!(e, Error::Reverted(4500)), + Err(e) => assert_eq!(e, Error::Reverted(4500).into()), } } @@ -170,7 +170,7 @@ mod tests { let excepted_output = Bytes::from(vec![1]); let result = match bls_signature_validation_run(&Bytes::from(input.clone()), 100_000_000) { - Ok((_, output)) => output, + Ok(o) => o.bytes, Err(e) => panic!("BLS signature validation failed, {:?}", e), }; assert_eq!(result, excepted_output); @@ -188,10 +188,11 @@ mod tests { input.extend_from_slice(&pub_key2); input.extend_from_slice(&pub_key3); let excepted_output = Bytes::from(vec![0]); - match bls_signature_validation_run(&Bytes::from(input.clone()), 100_000_000) { - Ok((_, output)) => assert_eq!(output, excepted_output), + let result = match bls_signature_validation_run(&Bytes::from(input.clone()), 100_000_000) { + Ok(o) => o.bytes, Err(e) => panic!("BLS signature validation failed, {:?}", e), - } + }; + assert_eq!(result, excepted_output); // wrong signature let msg_hash = hex!("1377c7e66081cb65e473c1b95db5195a27d04a7108b468890224bedbe1a8a6eb"); @@ -208,7 +209,7 @@ mod tests { match bls_signature_validation_run(&Bytes::from(input.clone()), 100_000_000) { Ok(_) => panic!("BLS signature validation failed, expect error"), - Err(e) => assert_eq!(e, Error::Reverted(11500)), + Err(e) => assert_eq!(e, Error::Reverted(11500).into()), } // invalid pubkey @@ -226,7 +227,7 @@ mod tests { match bls_signature_validation_run(&Bytes::from(input.clone()), 100_000_000) { Ok(_) => panic!("BLS signature validation failed, expect error"), - Err(e) => assert_eq!(e, Error::Reverted(11500)), + Err(e) => assert_eq!(e, Error::Reverted(11500).into()), } // duplicate pubkey @@ -242,9 +243,10 @@ mod tests { input.extend_from_slice(&pub_key2); input.extend_from_slice(&pub_key3); let excepted_output = Bytes::from(vec![0]); - match bls_signature_validation_run(&Bytes::from(input.clone()), 100_000_000) { - Ok((_, output)) => assert_eq!(output, excepted_output), + let result = match bls_signature_validation_run(&Bytes::from(input.clone()), 100_000_000) { + Ok(o) => o.bytes, Err(e) => panic!("BLS signature validation failed, {:?}", e), - } + }; + assert_eq!(result, excepted_output); } } diff --git a/crates/precompile/src/bls12_381.rs b/crates/precompile/src/bls12_381.rs index a020e8b1..75e774de 100644 --- a/crates/precompile/src/bls12_381.rs +++ b/crates/precompile/src/bls12_381.rs @@ -109,20 +109,23 @@ mod test { let Some(gas) = vector.gas else { panic!("gas is missing in {test_name}"); }; - let (actual_gas, actual_output) = - res.unwrap_or_else(|e| panic!("precompile call failed for {test_name}: {e}")); + let outcome = res.unwrap_or_else(|e: revm_primitives::PrecompileErrors| { + panic!("precompile call failed for {test_name}: {e}") + }); assert_eq!( - gas, actual_gas, + gas, outcome.gas_used, "expected gas: {}, actual gas: {} in {test_name}", - gas, actual_gas + gas, outcome.gas_used ); let Some(expected) = vector.expected else { panic!("expected output is missing in {test_name}"); }; let expected_output = Bytes::from_hex(expected).unwrap(); assert_eq!( - expected_output, actual_output, - "expected output: {expected_output}, actual output: {actual_output} in {test_name}"); + expected_output, outcome.bytes, + "expected output: {expected_output}, actual output: {:?} in {test_name}", + outcome.bytes + ); } } } diff --git a/crates/precompile/src/bls12_381/g1.rs b/crates/precompile/src/bls12_381/g1.rs index 6e4b73fa..30374b96 100644 --- a/crates/precompile/src/bls12_381/g1.rs +++ b/crates/precompile/src/bls12_381/g1.rs @@ -69,7 +69,7 @@ pub(super) fn extract_g1_input( // As endomorphism acceleration requires input on the correct subgroup, implementers MAY // use endomorphism acceleration. if unsafe { !blst_p1_affine_in_g1(&out) } { - return Err(PrecompileError::Other("Element not in G2".to_string())); + return Err(PrecompileError::Other("Element not in G1".to_string())); } } else { // From EIP-2537: @@ -80,13 +80,13 @@ pub(super) fn extract_g1_input( // // NB: There is no subgroup check for the G1 addition precompile. // - // We use blst_p1_affine_on_curve instead of blst_p1_affine_in_g2 because the latter performs + // We use blst_p1_affine_on_curve instead of blst_p1_affine_in_g1 because the latter performs // the subgroup check. // // SAFETY: out is a blst value. if unsafe { !blst_p1_affine_on_curve(&out) } { return Err(PrecompileError::Other( - "Element not on G2 curve".to_string(), + "Element not on G1 curve".to_string(), )); } } diff --git a/crates/precompile/src/bls12_381/g1_add.rs b/crates/precompile/src/bls12_381/g1_add.rs index 9fed002d..da671aab 100644 --- a/crates/precompile/src/bls12_381/g1_add.rs +++ b/crates/precompile/src/bls12_381/g1_add.rs @@ -3,7 +3,7 @@ use crate::{u64_to_address, PrecompileWithAddress}; use blst::{ blst_p1, blst_p1_add_or_double_affine, blst_p1_affine, blst_p1_from_affine, blst_p1_to_affine, }; -use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult}; +use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileOutput, PrecompileResult}; /// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G1ADD precompile. pub const PRECOMPILE: PrecompileWithAddress = @@ -23,14 +23,15 @@ const INPUT_LENGTH: usize = 256; /// See also: pub(super) fn g1_add(input: &Bytes, gas_limit: u64) -> PrecompileResult { if BASE_GAS_FEE > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileError::OutOfGas.into()); } if input.len() != INPUT_LENGTH { return Err(PrecompileError::Other(format!( "G1ADD input should be {INPUT_LENGTH} bytes, was {}", input.len() - ))); + )) + .into()); } // NB: There is no subgroup check for the G1 addition precompile. @@ -52,5 +53,5 @@ pub(super) fn g1_add(input: &Bytes, gas_limit: u64) -> PrecompileResult { unsafe { blst_p1_to_affine(&mut p_aff, &p) }; let out = encode_g1_point(&p_aff); - Ok((BASE_GAS_FEE, out)) + Ok(PrecompileOutput::new(BASE_GAS_FEE, out)) } diff --git a/crates/precompile/src/bls12_381/g1_msm.rs b/crates/precompile/src/bls12_381/g1_msm.rs index f0003a32..b62077c6 100644 --- a/crates/precompile/src/bls12_381/g1_msm.rs +++ b/crates/precompile/src/bls12_381/g1_msm.rs @@ -6,7 +6,7 @@ use super::{ }; use crate::{u64_to_address, PrecompileWithAddress}; use blst::{blst_p1, blst_p1_affine, blst_p1_from_affine, blst_p1_to_affine, p1_affines}; -use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult}; +use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileOutput, PrecompileResult}; /// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G1MSM precompile. pub const PRECOMPILE: PrecompileWithAddress = @@ -30,13 +30,14 @@ pub(super) fn g1_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { "G1MSM input length should be multiple of {}, was {}", g1_mul::INPUT_LENGTH, input_len - ))); + )) + .into()); } let k = input_len / g1_mul::INPUT_LENGTH; let required_gas = msm_required_gas(k, g1_mul::BASE_GAS_FEE); if required_gas > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileError::OutOfGas.into()); } let mut g1_points: Vec = Vec::with_capacity(k); @@ -45,8 +46,8 @@ pub(super) fn g1_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { let slice = &input[i * g1_mul::INPUT_LENGTH..i * g1_mul::INPUT_LENGTH + G1_INPUT_ITEM_LENGTH]; - // BLST batch API for p1_affines blows up when you pass it a point at infinity and returns - // point at infinity so we just skip the element, and return 128 bytes in the response + // BLST batch API for p1_affines blows up when you pass it a point at infinity, so we must + // filter points at infinity (and their corresponding scalars) from the input. if slice.iter().all(|i| *i == 0) { continue; } @@ -72,7 +73,7 @@ pub(super) fn g1_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { // return infinity point if all points are infinity if g1_points.is_empty() { - return Ok((required_gas, [0; 128].into())); + return Ok(PrecompileOutput::new(required_gas, [0; 128].into())); } let points = p1_affines::from(&g1_points); @@ -83,5 +84,5 @@ pub(super) fn g1_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { unsafe { blst_p1_to_affine(&mut multiexp_aff, &multiexp) }; let out = encode_g1_point(&multiexp_aff); - Ok((required_gas, out)) + Ok(PrecompileOutput::new(required_gas, out)) } diff --git a/crates/precompile/src/bls12_381/g1_mul.rs b/crates/precompile/src/bls12_381/g1_mul.rs index 2b62a39c..aee95244 100644 --- a/crates/precompile/src/bls12_381/g1_mul.rs +++ b/crates/precompile/src/bls12_381/g1_mul.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::{u64_to_address, PrecompileWithAddress}; use blst::{blst_p1, blst_p1_affine, blst_p1_from_affine, blst_p1_mult, blst_p1_to_affine}; -use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult}; +use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileOutput, PrecompileResult}; /// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G1MUL precompile. pub const PRECOMPILE: PrecompileWithAddress = @@ -25,13 +25,14 @@ pub(super) const INPUT_LENGTH: usize = 160; /// See also: pub(super) fn g1_mul(input: &Bytes, gas_limit: u64) -> PrecompileResult { if BASE_GAS_FEE > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileError::OutOfGas.into()); } if input.len() != INPUT_LENGTH { return Err(PrecompileError::Other(format!( "G1MUL input should be {INPUT_LENGTH} bytes, was {}", input.len() - ))); + )) + .into()); } // NB: Scalar multiplications, MSMs and pairings MUST perform a subgroup check. @@ -55,5 +56,5 @@ pub(super) fn g1_mul(input: &Bytes, gas_limit: u64) -> PrecompileResult { unsafe { blst_p1_to_affine(&mut p_aff, &p) }; let out = encode_g1_point(&p_aff); - Ok((BASE_GAS_FEE, out)) + Ok(PrecompileOutput::new(BASE_GAS_FEE, out)) } diff --git a/crates/precompile/src/bls12_381/g2.rs b/crates/precompile/src/bls12_381/g2.rs index 58536895..c6520c64 100644 --- a/crates/precompile/src/bls12_381/g2.rs +++ b/crates/precompile/src/bls12_381/g2.rs @@ -68,7 +68,7 @@ pub(super) fn extract_g2_input( ))); } - let mut input_fps: [&[u8; FP_LENGTH]; 4] = [&[0; FP_LENGTH]; 4]; + let mut input_fps = [&[0; FP_LENGTH]; 4]; for i in 0..4 { input_fps[i] = remove_padding(&input[i * PADDED_FP_LENGTH..(i + 1) * PADDED_FP_LENGTH])?; } diff --git a/crates/precompile/src/bls12_381/g2_add.rs b/crates/precompile/src/bls12_381/g2_add.rs index a8f5b85a..9b1e26f1 100644 --- a/crates/precompile/src/bls12_381/g2_add.rs +++ b/crates/precompile/src/bls12_381/g2_add.rs @@ -3,7 +3,7 @@ use crate::{u64_to_address, PrecompileWithAddress}; use blst::{ blst_p2, blst_p2_add_or_double_affine, blst_p2_affine, blst_p2_from_affine, blst_p2_to_affine, }; -use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult}; +use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileOutput, PrecompileResult}; /// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G2ADD precompile. pub const PRECOMPILE: PrecompileWithAddress = @@ -24,14 +24,15 @@ const INPUT_LENGTH: usize = 512; /// See also pub(super) fn g2_add(input: &Bytes, gas_limit: u64) -> PrecompileResult { if BASE_GAS_FEE > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileError::OutOfGas.into()); } if input.len() != INPUT_LENGTH { return Err(PrecompileError::Other(format!( "G2ADD input should be {INPUT_LENGTH} bytes, was {}", input.len() - ))); + )) + .into()); } // NB: There is no subgroup check for the G2 addition precompile. @@ -53,5 +54,5 @@ pub(super) fn g2_add(input: &Bytes, gas_limit: u64) -> PrecompileResult { unsafe { blst_p2_to_affine(&mut p_aff, &p) }; let out = encode_g2_point(&p_aff); - Ok((BASE_GAS_FEE, out)) + Ok(PrecompileOutput::new(BASE_GAS_FEE, out)) } diff --git a/crates/precompile/src/bls12_381/g2_msm.rs b/crates/precompile/src/bls12_381/g2_msm.rs index cedc73a1..456a6651 100644 --- a/crates/precompile/src/bls12_381/g2_msm.rs +++ b/crates/precompile/src/bls12_381/g2_msm.rs @@ -6,7 +6,7 @@ use super::{ }; use crate::{u64_to_address, PrecompileWithAddress}; use blst::{blst_p2, blst_p2_affine, blst_p2_from_affine, blst_p2_to_affine, p2_affines}; -use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult}; +use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileOutput, PrecompileResult}; /// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G2MSM precompile. pub const PRECOMPILE: PrecompileWithAddress = @@ -30,13 +30,14 @@ pub(super) fn g2_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { "G2MSM input length should be multiple of {}, was {}", g2_mul::INPUT_LENGTH, input_len - ))); + )) + .into()); } let k = input_len / g2_mul::INPUT_LENGTH; let required_gas = msm_required_gas(k, g2_mul::BASE_GAS_FEE); if required_gas > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileError::OutOfGas.into()); } let mut g2_points: Vec = Vec::with_capacity(k); @@ -44,8 +45,8 @@ pub(super) fn g2_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { for i in 0..k { let slice = &input[i * g2_mul::INPUT_LENGTH..i * g2_mul::INPUT_LENGTH + G2_INPUT_ITEM_LENGTH]; - // BLST batch API for p2_affines blows up when you pass it a point at infinity and returns - // point at infinity so we just skip the element, and return 256 bytes in the response + // BLST batch API for p2_affines blows up when you pass it a point at infinity, so we must + // filter points at infinity (and their corresponding scalars) from the input. if slice.iter().all(|i| *i == 0) { continue; } @@ -72,7 +73,7 @@ pub(super) fn g2_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { // return infinity point if all points are infinity if g2_points.is_empty() { - return Ok((required_gas, [0; 256].into())); + return Ok(PrecompileOutput::new(required_gas, [0; 256].into())); } let points = p2_affines::from(&g2_points); @@ -83,5 +84,5 @@ pub(super) fn g2_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { unsafe { blst_p2_to_affine(&mut multiexp_aff, &multiexp) }; let out = encode_g2_point(&multiexp_aff); - Ok((required_gas, out)) + Ok(PrecompileOutput::new(required_gas, out)) } diff --git a/crates/precompile/src/bls12_381/g2_mul.rs b/crates/precompile/src/bls12_381/g2_mul.rs index 62cb903e..e39edf07 100644 --- a/crates/precompile/src/bls12_381/g2_mul.rs +++ b/crates/precompile/src/bls12_381/g2_mul.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::{u64_to_address, PrecompileWithAddress}; use blst::{blst_p2, blst_p2_affine, blst_p2_from_affine, blst_p2_mult, blst_p2_to_affine}; -use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult}; +use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileOutput, PrecompileResult}; /// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G2MUL precompile. pub const PRECOMPILE: PrecompileWithAddress = @@ -25,13 +25,14 @@ pub(super) const INPUT_LENGTH: usize = 288; /// See also: pub(super) fn g2_mul(input: &Bytes, gas_limit: u64) -> PrecompileResult { if BASE_GAS_FEE > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileError::OutOfGas.into()); } if input.len() != INPUT_LENGTH { return Err(PrecompileError::Other(format!( "G2MUL input should be {INPUT_LENGTH} bytes, was {}", input.len() - ))); + )) + .into()); } // NB: Scalar multiplications, MSMs and pairings MUST perform a subgroup check. @@ -52,5 +53,5 @@ pub(super) fn g2_mul(input: &Bytes, gas_limit: u64) -> PrecompileResult { unsafe { blst_p2_to_affine(&mut p_aff, &p) }; let out = encode_g2_point(&p_aff); - Ok((BASE_GAS_FEE, out)) + Ok(PrecompileOutput::new(BASE_GAS_FEE, out)) } diff --git a/crates/precompile/src/bls12_381/map_fp2_to_g2.rs b/crates/precompile/src/bls12_381/map_fp2_to_g2.rs index 30c3ab5b..584008ec 100644 --- a/crates/precompile/src/bls12_381/map_fp2_to_g2.rs +++ b/crates/precompile/src/bls12_381/map_fp2_to_g2.rs @@ -5,7 +5,7 @@ use super::{ }; use crate::{u64_to_address, PrecompileWithAddress}; use blst::{blst_map_to_g2, blst_p2, blst_p2_affine, blst_p2_to_affine}; -use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult}; +use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileOutput, PrecompileResult}; /// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_MAP_FP2_TO_G2 precompile. pub const PRECOMPILE: PrecompileWithAddress = @@ -17,20 +17,21 @@ pub const ADDRESS: u64 = 0x13; /// Base gas fee for BLS12-381 map_fp2_to_g2 operation. const BASE_GAS_FEE: u64 = 75000; -/// Field-to-curve call expects 128 bytes as an input that is interpreted as a +/// Field-to-curve call expects 128 bytes as an input that is interpreted as /// an element of Fp2. Output of this call is 256 bytes and is an encoded G2 /// point. /// See also: pub(super) fn map_fp2_to_g2(input: &Bytes, gas_limit: u64) -> PrecompileResult { if BASE_GAS_FEE > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileError::OutOfGas.into()); } if input.len() != PADDED_FP2_LENGTH { return Err(PrecompileError::Other(format!( "MAP_FP2_TO_G2 input should be {PADDED_FP2_LENGTH} bytes, was {}", input.len() - ))); + )) + .into()); } let input_p0_x = remove_padding(&input[..PADDED_FP_LENGTH])?; @@ -47,5 +48,5 @@ pub(super) fn map_fp2_to_g2(input: &Bytes, gas_limit: u64) -> PrecompileResult { unsafe { blst_p2_to_affine(&mut p_aff, &p) }; let out = encode_g2_point(&p_aff); - Ok((BASE_GAS_FEE, out)) + Ok(PrecompileOutput::new(BASE_GAS_FEE, out)) } diff --git a/crates/precompile/src/bls12_381/map_fp_to_g1.rs b/crates/precompile/src/bls12_381/map_fp_to_g1.rs index ddbebb37..b92df60a 100644 --- a/crates/precompile/src/bls12_381/map_fp_to_g1.rs +++ b/crates/precompile/src/bls12_381/map_fp_to_g1.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::{u64_to_address, PrecompileWithAddress}; use blst::{blst_map_to_g1, blst_p1, blst_p1_affine, blst_p1_to_affine}; -use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult}; +use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileOutput, PrecompileResult}; /// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_MAP_FP_TO_G1 precompile. pub const PRECOMPILE: PrecompileWithAddress = @@ -21,14 +21,15 @@ const MAP_FP_TO_G1_BASE: u64 = 5500; /// See also: pub(super) fn map_fp_to_g1(input: &Bytes, gas_limit: u64) -> PrecompileResult { if MAP_FP_TO_G1_BASE > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileError::OutOfGas.into()); } if input.len() != PADDED_FP_LENGTH { return Err(PrecompileError::Other(format!( "MAP_FP_TO_G1 input should be {PADDED_FP_LENGTH} bytes, was {}", input.len() - ))); + )) + .into()); } let input_p0 = remove_padding(input)?; @@ -44,5 +45,21 @@ pub(super) fn map_fp_to_g1(input: &Bytes, gas_limit: u64) -> PrecompileResult { unsafe { blst_p1_to_affine(&mut p_aff, &p) }; let out = encode_g1_point(&p_aff); - Ok((MAP_FP_TO_G1_BASE, out)) + Ok(PrecompileOutput::new(MAP_FP_TO_G1_BASE, out)) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::primitives::hex; + + #[test] + fn sanity_test() { + let input = Bytes::from(hex!("000000000000000000000000000000006900000000000000636f6e7472616374595a603f343061cd305a03f40239f5ffff31818185c136bc2595f2aa18e08f17")); + let fail = map_fp_to_g1(&input, MAP_FP_TO_G1_BASE); + assert_eq!( + fail, + Err(PrecompileError::Other("non-canonical fp value".to_string()).into()) + ); + } } diff --git a/crates/precompile/src/bls12_381/pairing.rs b/crates/precompile/src/bls12_381/pairing.rs index 2a699a08..e0c50dd8 100644 --- a/crates/precompile/src/bls12_381/pairing.rs +++ b/crates/precompile/src/bls12_381/pairing.rs @@ -4,7 +4,9 @@ use super::{ }; use crate::{u64_to_address, PrecompileWithAddress}; use blst::{blst_final_exp, blst_fp12, blst_fp12_is_one, blst_fp12_mul, blst_miller_loop}; -use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult, B256}; +use revm_primitives::{ + Bytes, Precompile, PrecompileError, PrecompileOutput, PrecompileResult, B256, +}; /// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_PAIRING precompile. pub const PRECOMPILE: PrecompileWithAddress = @@ -16,7 +18,7 @@ pub const ADDRESS: u64 = 0x11; const PAIRING_MULTIPLIER_BASE: u64 = 43000; /// Offset gas fee for BLS12-381 pairing operation. const PAIRING_OFFSET_BASE: u64 = 65000; -/// Input length of paitring operation. +/// Input length of pairing operation. const INPUT_LENGTH: usize = 384; /// Pairing call expects 384*k (k being a positive integer) bytes as an inputs @@ -25,7 +27,7 @@ const INPUT_LENGTH: usize = 384; /// * 128 bytes of G1 point encoding /// * 256 bytes of G2 point encoding /// Each point is expected to be in the subgroup of order q. -/// Output is a 32 bytes where first 31 bytes are equal to 0x00 and the last byte +/// Output is 32 bytes where first 31 bytes are equal to 0x00 and the last byte /// is 0x01 if pairing result is equal to the multiplicative identity in a pairing /// target field and 0x00 otherwise. /// See also: @@ -34,16 +36,17 @@ pub(super) fn pairing(input: &Bytes, gas_limit: u64) -> PrecompileResult { if input_len == 0 || input_len % INPUT_LENGTH != 0 { return Err(PrecompileError::Other(format!( "Pairing input length should be multiple of {INPUT_LENGTH}, was {input_len}" - ))); + )) + .into()); } let k = input_len / INPUT_LENGTH; let required_gas: u64 = PAIRING_MULTIPLIER_BASE * k as u64 + PAIRING_OFFSET_BASE; if required_gas > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileError::OutOfGas.into()); } - // accumulator for the fp12 multiplications of the miller loops. + // Accumulator for the fp12 multiplications of the miller loops. let mut acc = blst_fp12::default(); for i in 0..k { // NB: Scalar multiplications, MSMs and pairings MUST perform a subgroup check. @@ -64,7 +67,7 @@ pub(super) fn pairing(input: &Bytes, gas_limit: u64) -> PrecompileResult { )?; if i > 0 { - // after the first slice (i>0) we use cur_ml to store the current + // After the first slice (i>0) we use cur_ml to store the current // miller loop and accumulate with the previous results using a fp12 // multiplication. let mut cur_ml = blst_fp12::default(); @@ -76,7 +79,7 @@ pub(super) fn pairing(input: &Bytes, gas_limit: u64) -> PrecompileResult { } acc = res; } else { - // on the first slice (i==0) there is no previous results and no need + // On the first slice (i==0) there is no previous results and no need // to accumulate. // SAFETY: acc, p1_aff and p2_aff are blst values. unsafe { @@ -98,5 +101,8 @@ pub(super) fn pairing(input: &Bytes, gas_limit: u64) -> PrecompileResult { result = 1; } } - Ok((required_gas, B256::with_last_byte(result).into())) + Ok(PrecompileOutput::new( + required_gas, + B256::with_last_byte(result).into(), + )) } diff --git a/crates/precompile/src/bls12_381/utils.rs b/crates/precompile/src/bls12_381/utils.rs index ba18475c..a2ed3cd8 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/bn128.rs b/crates/precompile/src/bn128.rs index ce08da79..c9ce059d 100644 --- a/crates/precompile/src/bn128.rs +++ b/crates/precompile/src/bn128.rs @@ -3,6 +3,7 @@ use crate::{ Address, Error, Precompile, PrecompileResult, PrecompileWithAddress, }; use bn::{AffineG1, AffineG2, Fq, Fq2, Group, Gt, G1, G2}; +use revm_primitives::PrecompileOutput; pub mod add { use super::*; @@ -122,7 +123,7 @@ pub fn new_g1_point(px: Fq, py: Fq) -> Result { pub fn run_add(input: &[u8], gas_cost: u64, gas_limit: u64) -> PrecompileResult { if gas_cost > gas_limit { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } let input = right_pad::(input); @@ -135,12 +136,12 @@ pub fn run_add(input: &[u8], gas_cost: u64, gas_limit: u64) -> PrecompileResult sum.x().to_big_endian(&mut output[..32]).unwrap(); sum.y().to_big_endian(&mut output[32..]).unwrap(); } - Ok((gas_cost, output.into())) + Ok(PrecompileOutput::new(gas_cost, output.into())) } pub fn run_mul(input: &[u8], gas_cost: u64, gas_limit: u64) -> PrecompileResult { if gas_cost > gas_limit { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } let input = right_pad::(input); @@ -155,7 +156,7 @@ pub fn run_mul(input: &[u8], gas_cost: u64, gas_limit: u64) -> PrecompileResult mul.x().to_big_endian(&mut output[..32]).unwrap(); mul.y().to_big_endian(&mut output[32..]).unwrap(); } - Ok((gas_cost, output.into())) + Ok(PrecompileOutput::new(gas_cost, output.into())) } pub fn run_pair( @@ -166,11 +167,11 @@ pub fn run_pair( ) -> PrecompileResult { let gas_used = (input.len() / PAIR_ELEMENT_LEN) as u64 * pair_per_point_cost + pair_base_cost; if gas_used > gas_limit { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } if input.len() % PAIR_ELEMENT_LEN != 0 { - return Err(Error::Bn128PairLength); + return Err(Error::Bn128PairLength.into()); } let success = if input.is_empty() { @@ -211,7 +212,7 @@ pub fn run_pair( mul == Gt::one() }; - Ok((gas_used, bool_to_bytes32(success))) + Ok(PrecompileOutput::new(gas_used, bool_to_bytes32(success))) } #[cfg(test)] @@ -219,7 +220,7 @@ mod tests { use crate::bn128::add::BYZANTIUM_ADD_GAS_COST; use crate::bn128::mul::BYZANTIUM_MUL_GAS_COST; use crate::bn128::pair::{BYZANTIUM_PAIR_BASE, BYZANTIUM_PAIR_PER_POINT}; - use revm_primitives::hex; + use revm_primitives::{hex, PrecompileErrors}; use super::*; @@ -240,8 +241,8 @@ mod tests { ) .unwrap(); - let (_, res) = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap(); - assert_eq!(res, expected); + let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap(); + assert_eq!(outcome.bytes, expected); // zero sum test let input = hex::decode( @@ -259,8 +260,8 @@ mod tests { ) .unwrap(); - let (_, res) = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap(); - assert_eq!(res, expected); + let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap(); + assert_eq!(outcome.bytes, expected); // out of gas test let input = hex::decode( @@ -274,7 +275,7 @@ mod tests { let res = run_add(&input, BYZANTIUM_ADD_GAS_COST, 499); println!("{:?}", res); - assert!(matches!(res, Err(Error::OutOfGas))); + assert!(matches!(res, Err(PrecompileErrors::Error(Error::OutOfGas)))); // no input test let input = [0u8; 0]; @@ -285,8 +286,8 @@ mod tests { ) .unwrap(); - let (_, res) = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap(); - assert_eq!(res, expected); + let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap(); + assert_eq!(outcome.bytes, expected); // point not on curve fail let input = hex::decode( @@ -299,7 +300,10 @@ mod tests { .unwrap(); let res = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500); - assert!(matches!(res, Err(Error::Bn128AffineGFailedToCreate))); + assert!(matches!( + res, + Err(PrecompileErrors::Error(Error::Bn128AffineGFailedToCreate)) + )); } #[test] @@ -318,8 +322,8 @@ mod tests { ) .unwrap(); - let (_, res) = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap(); - assert_eq!(res, expected); + let outcome = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap(); + assert_eq!(outcome.bytes, expected); // out of gas test let input = hex::decode( @@ -331,7 +335,7 @@ mod tests { .unwrap(); let res = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 39_999); - assert!(matches!(res, Err(Error::OutOfGas))); + assert!(matches!(res, Err(PrecompileErrors::Error(Error::OutOfGas)))); // zero multiplication test let input = hex::decode( @@ -348,8 +352,8 @@ mod tests { ) .unwrap(); - let (_, res) = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap(); - assert_eq!(res, expected); + let outcome = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap(); + assert_eq!(outcome.bytes, expected); // no input test let input = [0u8; 0]; @@ -360,8 +364,8 @@ mod tests { ) .unwrap(); - let (_, res) = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap(); - assert_eq!(res, expected); + let outcome = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap(); + assert_eq!(outcome.bytes, expected); // point not on curve fail let input = hex::decode( @@ -373,7 +377,10 @@ mod tests { .unwrap(); let res = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000); - assert!(matches!(res, Err(Error::Bn128AffineGFailedToCreate))); + assert!(matches!( + res, + Err(PrecompileErrors::Error(Error::Bn128AffineGFailedToCreate)) + )); } #[test] @@ -398,14 +405,14 @@ mod tests { hex::decode("0000000000000000000000000000000000000000000000000000000000000001") .unwrap(); - let (_, res) = run_pair( + let outcome = run_pair( &input, BYZANTIUM_PAIR_PER_POINT, BYZANTIUM_PAIR_BASE, 260_000, ) .unwrap(); - assert_eq!(res, expected); + assert_eq!(outcome.bytes, expected); // out of gas test let input = hex::decode( @@ -431,7 +438,7 @@ mod tests { BYZANTIUM_PAIR_BASE, 259_999, ); - assert!(matches!(res, Err(Error::OutOfGas))); + assert!(matches!(res, Err(PrecompileErrors::Error(Error::OutOfGas)))); // no input test let input = [0u8; 0]; @@ -439,14 +446,14 @@ mod tests { hex::decode("0000000000000000000000000000000000000000000000000000000000000001") .unwrap(); - let (_, res) = run_pair( + let outcome = run_pair( &input, BYZANTIUM_PAIR_PER_POINT, BYZANTIUM_PAIR_BASE, 260_000, ) .unwrap(); - assert_eq!(res, expected); + assert_eq!(outcome.bytes, expected); // point not on curve fail let input = hex::decode( @@ -466,7 +473,10 @@ mod tests { BYZANTIUM_PAIR_BASE, 260_000, ); - assert!(matches!(res, Err(Error::Bn128AffineGFailedToCreate))); + assert!(matches!( + res, + Err(PrecompileErrors::Error(Error::Bn128AffineGFailedToCreate)) + )); // invalid input length let input = hex::decode( @@ -484,6 +494,9 @@ mod tests { BYZANTIUM_PAIR_BASE, 260_000, ); - assert!(matches!(res, Err(Error::Bn128PairLength))); + assert!(matches!( + res, + Err(PrecompileErrors::Error(Error::Bn128PairLength)) + )); } } diff --git a/crates/precompile/src/cometbft.rs b/crates/precompile/src/cometbft.rs index f3f0df6e..52f3f6ad 100644 --- a/crates/precompile/src/cometbft.rs +++ b/crates/precompile/src/cometbft.rs @@ -11,7 +11,7 @@ use cometbft_light_client_verifier::{ }; use cometbft_proto::types::v1::LightBlock as TmLightBlock; use prost::Message; -use revm_primitives::Bytes; +use revm_primitives::{Bytes, PrecompileOutput}; use std::{borrow::ToOwned, string::String, vec::Vec}; pub(crate) const COMETBFT_LIGHT_BLOCK_VALIDATION: PrecompileWithAddress = PrecompileWithAddress( @@ -66,22 +66,22 @@ fn cometbft_light_block_validation_run_inner( const COMETBFT_LIGHT_BLOCK_VALIDATION_BASE: u64 = 3_000; if COMETBFT_LIGHT_BLOCK_VALIDATION_BASE > gas_limit { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } let (mut consensus_state, tm_light_block) = match decode_light_block_validation_input(input) { Ok(result) => result, - Err(e) => return Err(e), + Err(e) => return Err(e.into()), }; let light_block = match convert_light_block_from_proto(&tm_light_block) { Ok(lb) => lb, - Err(e) => return Err(e), + Err(e) => return Err(e.into()), }; let mut validator_set_changed = match consensus_state.apply_light_block(&light_block) { Ok(validator_set_changed) => validator_set_changed, - Err(e) => return Err(e), + Err(e) => return Err(e.into()), }; if !is_hertz { validator_set_changed = false; @@ -89,10 +89,10 @@ fn cometbft_light_block_validation_run_inner( let consensus_state_bytes = match consensus_state.encode() { Ok(cs) => cs, - Err(e) => return Err(e), + Err(e) => return Err(e.into()), }; - Ok(( + Ok(PrecompileOutput::new( COMETBFT_LIGHT_BLOCK_VALIDATION_BASE, encode_light_block_validation_result(validator_set_changed, consensus_state_bytes), )) @@ -429,12 +429,12 @@ mod tests { )); let result = cometbft_light_block_validation_run(&input, 100_000); - let (gas_used, output) = match result { - Ok(result) => result, + let PrecompileOutput { gas_used, bytes } = match result { + Ok(output) => output, Err(_) => panic!("cometbft_light_block_validation_run failed"), }; assert_eq!(gas_used, 3_000); - assert_eq!(output, except_output); + assert_eq!(bytes, except_output); } // apply light block failed { @@ -443,7 +443,7 @@ mod tests { )); let result = cometbft_light_block_validation_run(&input, 100_000); - let expected = Err(Error::CometBftApplyBlockFailed); + let expected = Err(Error::CometBftApplyBlockFailed.into()); assert_eq!(result, expected); } // consensus height >= light block height @@ -453,7 +453,7 @@ mod tests { )); let result = cometbft_light_block_validation_run(&input, 100_000); - let expected = Err(Error::CometBftInvalidInput); + let expected = Err(Error::CometBftInvalidInput.into()); assert_eq!(result, expected); } // chain id mismatch @@ -463,7 +463,7 @@ mod tests { )); let result = cometbft_light_block_validation_run(&input, 100_000); - let expected = Err(Error::CometBftInvalidInput); + let expected = Err(Error::CometBftInvalidInput.into()); assert_eq!(result, expected); } } @@ -732,11 +732,11 @@ mod tests { )); let result = cometbft_light_block_validation_run_before_hertz(&input, 100_000); - let (gas_used, output) = match result { - Ok(result) => result, + let PrecompileOutput { gas_used, bytes } = match result { + Ok(output) => output, Err(_) => panic!("cometbft_light_block_validation_run failed"), }; assert_eq!(gas_used, 3_000); - assert_eq!(output, except_output_after_hertz); + assert_eq!(bytes, except_output_after_hertz); } } diff --git a/crates/precompile/src/double_sign.rs b/crates/precompile/src/double_sign.rs index f7ad52d4..fca24e86 100644 --- a/crates/precompile/src/double_sign.rs +++ b/crates/precompile/src/double_sign.rs @@ -4,7 +4,7 @@ use alloy_primitives::{BlockNumber, ChainId, U256}; use alloy_rlp::{Decodable, RlpDecodable, RlpEncodable}; use core::cmp::Ordering; use revm_primitives::alloy_primitives::B512; -use revm_primitives::{keccak256, B256}; +use revm_primitives::{keccak256, PrecompileOutput, B256}; /// Double sign evidence validation precompile for BSC. pub(crate) const DOUBLE_SIGN_EVIDENCE_VALIDATION: PrecompileWithAddress = PrecompileWithAddress( @@ -76,41 +76,41 @@ fn double_sign_evidence_validation_run(input: &Bytes, gas_limit: u64) -> Precomp const DOUBLE_SIGN_EVIDENCE_VALIDATION_BASE: u64 = 10_000; if DOUBLE_SIGN_EVIDENCE_VALIDATION_BASE > gas_limit { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } let evidence = match DoubleSignEvidence::decode(&mut input.iter().as_ref()) { Ok(e) => e, - Err(_) => return Err(Error::Reverted(DOUBLE_SIGN_EVIDENCE_VALIDATION_BASE)), + Err(_) => return Err(Error::Reverted(DOUBLE_SIGN_EVIDENCE_VALIDATION_BASE).into()), }; let header1 = match Header::decode(&mut evidence.header_bytes1.as_ref()) { Ok(e) => e, - Err(_) => return Err(Error::Reverted(DOUBLE_SIGN_EVIDENCE_VALIDATION_BASE)), + Err(_) => return Err(Error::Reverted(DOUBLE_SIGN_EVIDENCE_VALIDATION_BASE).into()), }; let header2 = match Header::decode(&mut evidence.header_bytes2.as_ref()) { Ok(e) => e, - Err(_) => return Err(Error::Reverted(DOUBLE_SIGN_EVIDENCE_VALIDATION_BASE)), + Err(_) => return Err(Error::Reverted(DOUBLE_SIGN_EVIDENCE_VALIDATION_BASE).into()), }; // basic check if header1.number.to_be_bytes().len() > 32 || header2.number.to_be_bytes().len() > 32 { - return Err(Error::other("invalid evidence")); + return Err(Error::other("invalid evidence").into()); } if header1.number != header2.number { - return Err(Error::other("invalid evidence")); + return Err(Error::other("invalid evidence").into()); } if header1.parent_hash.cmp(&header2.parent_hash) != Ordering::Equal { - return Err(Error::other("invalid evidence")); + return Err(Error::other("invalid evidence").into()); } if header1.extra.len() < EXTRA_SEAL_LENGTH || header1.extra.len() < EXTRA_SEAL_LENGTH { - return Err(Error::other("invalid evidence")); + return Err(Error::other("invalid evidence").into()); } let sig1 = &header1.extra[header1.extra.len() - EXTRA_SEAL_LENGTH..]; let sig2 = &header2.extra[header2.extra.len() - EXTRA_SEAL_LENGTH..]; if sig1.eq(sig2) { - return Err(Error::other("invalid evidence")); + return Err(Error::other("invalid evidence").into()); } // check signature @@ -118,25 +118,25 @@ fn double_sign_evidence_validation_run(input: &Bytes, gas_limit: u64) -> Precomp let msg_hash2 = seal_hash(&header2, evidence.chain_id); if msg_hash1.eq(&msg_hash2) { - return Err(Error::other("invalid evidence")); + return Err(Error::other("invalid evidence").into()); } let recid1 = sig1[64]; let sig1 = <&B512>::try_from(&sig1[..64]).unwrap(); let addr1 = match secp256k1::ecrecover(sig1, recid1, &msg_hash1) { Ok(pk) => pk, - Err(_) => return Err(Error::Reverted(DOUBLE_SIGN_EVIDENCE_VALIDATION_BASE)), + Err(_) => return Err(Error::Reverted(DOUBLE_SIGN_EVIDENCE_VALIDATION_BASE).into()), }; let recid2 = sig2[64]; let sig2 = <&B512>::try_from(&sig2[..64]).unwrap(); let addr2 = match secp256k1::ecrecover(sig2, recid2, &msg_hash2) { Ok(pk) => pk, - Err(_) => return Err(Error::Reverted(DOUBLE_SIGN_EVIDENCE_VALIDATION_BASE)), + Err(_) => return Err(Error::Reverted(DOUBLE_SIGN_EVIDENCE_VALIDATION_BASE).into()), }; if !addr1.eq(&addr2) { - return Err(Error::other("invalid evidence")); + return Err(Error::other("invalid evidence").into()); } let mut res = [0; 52]; @@ -144,7 +144,7 @@ fn double_sign_evidence_validation_run(input: &Bytes, gas_limit: u64) -> Precomp res[..20].clone_from_slice(signer); res[52 - header1.number.to_be_bytes().len()..].clone_from_slice(&header1.number.to_be_bytes()); - Ok(( + Ok(PrecompileOutput::new( DOUBLE_SIGN_EVIDENCE_VALIDATION_BASE, Bytes::copy_from_slice(&res), )) @@ -185,10 +185,10 @@ mod tests { let res = double_sign_evidence_validation_run(&Bytes::from(input), 10_000).unwrap(); - let gas = res.0; + let gas = res.gas_used; assert_eq!(gas, 10_000u64); - let res = hex::encode(res.1); + let res = hex::encode(res.bytes); assert_eq!(res, "15d34aaf54267db7d7c367839aaf71a00a2c6a650000000000000000000000000000000000000000000000000000000000000cdf") } @@ -197,6 +197,6 @@ mod tests { let input = hex::decode("f9066b38b90332f9032fa01062d3d5015b9242bc193a9b0769f3d3780ecb55f97f40a752ae26d0b68cd0d8a0fae1a05fcb14bfd9b8a9f2b65007a9b6c2000de0627a73be644dd993d32342c494df87f0e2b8519ea2dd4abd8b639cdd628497ed25a0f385cc58ed297ff0d66eb5580b02853d3478ba418b1819ac659ee05df49b9794a0bf88464af369ed6b8cf02db00f0b9556ffa8d49cd491b00952a7f83431446638a00a6d0870e586a76278fbfdcedf76ef6679af18fc1f9137cfad495f434974ea81b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a1010000000000000000000000000000000000000000000000000000000000000000830f4240830f42408465bc6996b90115d983010306846765746889676f312e32302e3131856c696e7578000053474aa9f8b25fb860b0844a5082bfaa2299d2a23f076e2f6b17b15f839cc3e7d5a875656f6733fd4b87ba3401f906d15f3dea263cd9a6076107c7db620a4630dd3832c4a4b57eb8f497e28a3d69e5c03b30205c4b45675747d513e1accd66329770f3c35b18c9d023f84c84023a5ad6a086a28d985d9a6c8e7f9a4feadd5ace0adba9818e1e1727edca755fcc0bd8344684023a5ad7a0bc3492196b2e68b8e6ceea87cfa7588b4d590089eb885c4f2c1e9d9fb450f7b980988e1b9d0beb91dab063e04879a24c43d33baae3759dee41fd62ffa83c77fd202bea27a829b49e8025bdd198393526dd12b223ab16052fd26a43f3aabf63e76901a0232c9ba2d41b40d36ed794c306747bcbc49bf61a0f37409c18bfe2b5bef26a2d880000000000000000b90332f9032fa01062d3d5015b9242bc193a9b0769f3d3780ecb55f97f40a752ae26d0b68cd0d8a0b2789a5357827ed838335283e15c4dcc42b9bebcbf2919a18613246787e2f96094df87f0e2b8519ea2dd4abd8b639cdd628497ed25a071ce4c09ee275206013f0063761bc19c93c13990582f918cc57333634c94ce89a00e095703e5c9b149f253fe89697230029e32484a410b4b1f2c61442d73c3095aa0d317ae19ede7c8a2d3ac9ef98735b049bcb7278d12f48c42b924538b60a25e12b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a1010000000000000000000000000000000000000000000000000000000000000000830f4240830f42408465bc6996b90115d983010306846765746889676f312e32302e3131856c696e7578000053474aa9f8b25fb860b0844a5082bfaa2299d2a23f076e2f6b17b15f839cc3e7d5a875656f6733fd4b87ba3401f906d15f3dea263cd9a6076107c7db620a4630dd3832c4a4b57eb8f497e28a3d69e5c03b30205c4b45675747d513e1accd66329770f3c35b18c9d023f84c84023a5ad6a086a28d985d9a6c8e7f9a4feadd5ace0adba9818e1e1727edca755fcc0bd8344684023a5ad7a0bc3492196b2e68b8e6ceea87cfa7588b4d590089eb885c4f2c1e9d9fb450f7b9804c71ed015dd0c5c2d7393b68c2927f83f0a5da4c66f761f09e2f950cc610832c7876144599368404096ddef0eadacfde57717e2c7d23982b927285b797d41bfa00a0b56228685be711834d0f154292d07826dea42a0fad3e4f56c31470b7fbfbea26880000000000000000").unwrap(); let res = double_sign_evidence_validation_run(&Bytes::from(input), 10_000); - assert_eq!(res.err(), Some(Error::Reverted(10000))); + assert_eq!(res.err(), Some(Error::Reverted(10000).into())); } } diff --git a/crates/precompile/src/hash.rs b/crates/precompile/src/hash.rs index 9fc6a8ae..9457302b 100644 --- a/crates/precompile/src/hash.rs +++ b/crates/precompile/src/hash.rs @@ -1,6 +1,6 @@ use super::calc_linear_cost_u32; use crate::{Error, Precompile, PrecompileResult, PrecompileWithAddress}; -use revm_primitives::Bytes; +use revm_primitives::{Bytes, PrecompileOutput}; use sha2::Digest; pub const SHA256: PrecompileWithAddress = @@ -17,10 +17,10 @@ pub const RIPEMD160: PrecompileWithAddress = PrecompileWithAddress( pub fn sha256_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { let cost = calc_linear_cost_u32(input.len(), 60, 12); if cost > gas_limit { - Err(Error::OutOfGas) + Err(Error::OutOfGas.into()) } else { let output = sha2::Sha256::digest(input); - Ok((cost, output.to_vec().into())) + Ok(PrecompileOutput::new(cost, output.to_vec().into())) } } @@ -30,13 +30,13 @@ pub fn sha256_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { pub fn ripemd160_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { let gas_used = calc_linear_cost_u32(input.len(), 600, 120); if gas_used > gas_limit { - Err(Error::OutOfGas) + Err(Error::OutOfGas.into()) } else { let mut hasher = ripemd::Ripemd160::new(); hasher.update(input); let mut output = [0u8; 32]; hasher.finalize_into((&mut output[12..]).into()); - Ok((gas_used, output.to_vec().into())) + Ok(PrecompileOutput::new(gas_used, output.to_vec().into())) } } diff --git a/crates/precompile/src/iavl.rs b/crates/precompile/src/iavl.rs index 6ad3fecc..47593ab5 100644 --- a/crates/precompile/src/iavl.rs +++ b/crates/precompile/src/iavl.rs @@ -2,6 +2,7 @@ use crate::{Bytes, Error, Precompile, PrecompileError, PrecompileResult, PrecompileWithAddress}; use parity_bytes::BytesRef; +use revm_primitives::PrecompileOutput; use tendermint::lite::iavl_proof; /// Iavl proof validation precompile for BSC. @@ -41,7 +42,7 @@ fn iavl_proof_validation_run(input: &Bytes, gas_limit: u64) -> PrecompileResult /// Run Iavl proof validation with Nano hardfork. fn iavl_proof_validation_run_nano(_input: &Bytes, _gas_limit: u64) -> PrecompileResult { - Err(PrecompileError::other("suspended")) + Err(PrecompileError::other("suspended").into()) } /// Run Iavl proof validation with Moran hardfork. @@ -70,18 +71,18 @@ fn iavl_proof_validation_run_inner( const IAVL_PROOF_VALIDATION_BASE: u64 = 3_000; if IAVL_PROOF_VALIDATION_BASE > gas_limit { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } let mut output = [0u8; 32]; let mut bytes = BytesRef::Fixed(&mut output); let res = iavl_proof::execute(input.as_ref(), &mut bytes, is_moran, is_planck, is_plato); match res { - Ok(()) => Ok(( + Ok(()) => Ok(PrecompileOutput::new( IAVL_PROOF_VALIDATION_BASE, Bytes::copy_from_slice(&output[..]), )), - Err(str) => Err(PrecompileError::other(str)), + Err(str) => Err(PrecompileError::other(str).into()), } } @@ -95,10 +96,10 @@ mod tests { let input = hex::decode("00000000000000000000000000000000000000000000000000000000000007306163630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c6163636f756e743a8a4e2eb018bdf98a8f53ec755740ffc728637a1d000000000000000000000000000000000000000000000000000000000000007b4bdc4c270a750a148a4e2eb018bdf98a8f53ec755740ffc728637a1d12110a0941544348412d3733301080f69bf321120b0a03424e4210e8baeb8d44120f0a075050432d303041108094ebdc031a26eb5ae98721031c199c92e5b0080967da99be27cf2da53317441b4a663e6d9c6caf02be1fdbdc20d7962b28152c69c314b4de5c8035253c8bc0771d9ca17b1b23a57c0c6d068b57579791cae20add070a066961766c3a76121c6163636f756e743a8a4e2eb018bdf98a8f53ec755740ffc728637a1d1ab407b2070aaf070a2d081810cdfd2b188096a82222209f223f804e2d94ac51c4321b0687397012e6d95eb9783b03bc790da631004c7c0a2d081710adb31a18f395a8222a20d2a38865de82383ccce0140513b65cec1bf2ae6cd7dfeb22eb6faadb4e26b26f0a2d081510b2990b18f395a82222208a02bbd5a695dfc772627ac8744aa9cf30ae26575bdce8c96a9a0d0999175b430a2d081410e6ff0418f395a8222a20d39619c779be909e67f23499fb74eb2c19afd7f21523401d4ccf7e917db5cd600a2d081210e3fe0118f395a8222a20a10cc73843f889d9e03a463eb135e928bb980e19734344cba0fbf4e8a4c5258b0a2c081010dd6518f395a8222a2007fd15843a2fd3f58d021b0e072a6c70742d7a3d993a922445e3491e1c14ee8e0a2c080f10cc2a18eda6a7222a20088942d7b30abd021d8e9505cc41313fad87c8c10a799f3b51018b7b2cfe4ad90a2c080d10b70d18eda6a7222a2091a37bc44d0c61e3752ddc59eb390355ab65e8a9fb453be4f0acec537f1ca14f0a2c080c10890818eda6a72222201cfc317855a06667c45812fe36efe33af05671dfe0d9b56b02662011af2e79e30a2c080b10ac0318c4b0ee212220aeb454a4b3243b6269a2fd8841dca9a951c53b30f1e27da91063dae7224402c70a2c080910e40118c4b0ee212a20441340a4de6498f861b97b3f3ad9603af055e5af51a0d96fff2ae28e3c5c6c9a0a2c0808108d0118c4b0ee212220ae32ea4b9ab7b53571da320e2815fd8b2c278124961cca4a1849a799842424450a2b0807104d18c4b0ee212220e2804c9b7f045ec0b4ab20920a937b82fda8b7a9ddd12b21637335b915cfda550a2b0806102418a5f4c7192a20ec85f22addedfc82c771af5b4c77544b7c1d7c5bbac33f2712dfba1045ebdbd00a2b0805101118a5f4c7192a2071ade34dcc447a0ba8adc603080633d15c06f3525830c86ebce35eca0a4921fc0a2b0804100c18a5f4c7192a205190bce93993e65b266a3417ed511df8897a812cb4b62569e5afcfbec10b69cd0a2b0803100618a5f4c7192220b76c6884f1d412ac10bfb3987fb7d26f0330b2a85539509ebc5c6bdec2f95d520a2b0802100418a5f4c71922206a285b4a4f9d1c687bbafa1f3649b6a6e32b1a85dd0402421210683e846cf0020a2b0801100218a5f4c7192220033b3f7c6dcb258b6e55545e7a4f51539447cd595eb8a2e373ba0015502da1051a450a1c6163636f756e743a8a4e2eb018bdf98a8f53ec755740ffc728637a1d12201a272295e94cf1d8090bdb019dde48e9dab026ad2c3e43aaa7e61cc954a9245d18a5f4c7190ab6040a0a6d756c746973746f726512036163631aa204a0040a9d040a300a0364657812290a27088496a822122038fc49f49648fec62acc434151a51eaa378c1b20a730a749548e36f1529422500a300a03676f7612290a27088496a8221220a78ce489bdf08b9ee869c184876e1623dc38b3e64a5cf1a0005f97976c64deac0a380a0b61746f6d69635f7377617012290a27088496a8221220544c2fa38f61e10a39ec00b3e724d5834761268bb455cdbf5843bcf1531f8fbc0a300a0376616c12290a27088496a82212201f71082c9f6f45fb456b2c00b41e50d2f662f2dfec3cb6965f19d214bf02f3980a0f0a046d61696e12070a05088496a8220a320a057374616b6512290a27088496a82212200dd467343c718f240e50b4feac42970fc8c1c69a018be955f9c27913ac1f8b3c0a300a0361636312290a27088496a8221220270c19ccc9c40c5176b3dfbd8af734c97a307e0dbd8df9e286dcd5d709f973ed0a330a06746f6b656e7312290a27088496a8221220c4f96eedf50c83964de9df013afec2e545012d92528b643a5166c828774187b60a320a05706169727312290a27088496a8221220351c55cfda84596ecd22ebc77013662aba97f81f19d9ef3d150213bb07c823060a360a0974696d655f6c6f636b12290a27088496a8221220e7adf5bd30ce022decf0e9341bf05c464ed70cdbc97423bd2bab8f3571e5179b0a330a06706172616d7312290a27088496a822122042a9dfc356ca435db131eb41fb1975c8482f2434537918665e530b0b4633b5f9").unwrap(); let res = iavl_proof_validation_run(&Bytes::from(input), 3_000u64).unwrap(); - let gas = res.0; + let gas = res.gas_used; assert_eq!(gas, 3_000u64); - let res = hex::encode(res.1); + let res = hex::encode(res.bytes); assert_eq!( res, "0000000000000000000000000000000000000000000000000000000000000001" @@ -110,10 +111,10 @@ mod tests { let input = hex::decode("00000000000000000000000000000000000000000000000000000000000007306163630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c6163636f756e743a8a4e2eb018bdf98a8f53ec755740ffc728637a1d000000000000000000000000000000000000000000000000000000000000007b4bdc4c270a750a148a4e2eb018bdf98a8f53ec755740ffc728637a1d12110a0941544348412d3733301080f69bf321120b0a03424e4210e8baeb8d44120f0a075050432d303041108094ebdc031a26eb5ae98721031c199c92e5b0080967da99be27cf2da53317441b4a663e6d9c6caf02be1fdbdc20d7962b28152c69c314b4de5c8035253c8bc0771d9ca17b1b23a57c0c6d068b57579791cae20add070a066961766c3a76121c6163636f756e743a8a4e2eb018bdf98a8f53ec755740ffc728637a1d1ab407b2070aaf070a2d081810cdfd2b188096a82222209f223f804e2d94ac51c4321b0687397012e6d95eb9783b03bc790da631004c7c0a2d081710adb31a18f395a8222a20d2a38865de82383ccce0140513b65cec1bf2ae6cd7dfeb22eb6faadb4e26b26f0a2d081510b2990b18f395a82222208a02bbd5a695dfc772627ac8744aa9cf30ae26575bdce8c96a9a0d0999175b430a2d081410e6ff0418f395a8222a20d39619c779be909e67f23499fb74eb2c19afd7f21523401d4ccf7e917db5cd600a2d081210e3fe0118f395a8222a20a10cc73843f889d9e03a463eb135e928bb980e19734344cba0fbf4e8a4c5258b0a2c081010dd6518f395a8222a2007fd15843a2fd3f58d021b0e072a6c70742d7a3d993a922445e3491e1c14ee8e0a2c080f10cc2a18eda6a7222a20088942d7b30abd021d8e9505cc41313fad87c8c10a799f3b51018b7b2cfe4ad90a2c080d10b70d18eda6a7222a2091a37bc44d0c61e3752ddc59eb390355ab65e8a9fb453be4f0acec537f1ca14f0a2c080c10890818eda6a72222201cfc317855a06667c45812fe36efe33af05671dfe0d9b56b02662011af2e79e30a2c080b10ac0318c4b0ee212220aeb454a4b3243b6269a2fd8841dca9a951c53b30f1e27da91063dae7224402c70a2c080910e40118c4b0ee212a20441340a4de6498f861b97b3f3ad9603af055e5af51a0d96fff2ae28e3c5c6c9a0a2c0808108d0118c4b0ee212220ae32ea4b9ab7b53571da320e2815fd8b2c278124961cca4a1849a799842424450a2b0807104d18c4b0ee212220e2804c9b7f045ec0b4ab20920a937b82fda8b7a9ddd12b21637335b915cfda550a2b0806102418a5f4c7192a20ec85f22addedfc82c771af5b4c77544b7c1d7c5bbac33f2712dfba1045ebdbd00a2b0805101118a5f4c7192a2071ade34dcc447a0ba8adc603080633d15c06f3525830c86ebce35eca0a4921fc0a2b0804100c18a5f4c7192a205190bce93993e65b266a3417ed511df8897a812cb4b62569e5afcfbec10b69cd0a2b0803100618a5f4c7192220b76c6884f1d412ac10bfb3987fb7d26f0330b2a85539509ebc5c6bdec2f95d520a2b0802100418a5f4c71922206a285b4a4f9d1c687bbafa1f3649b6a6e32b1a85dd0402421210683e846cf0020a2b0801100218a5f4c7192220033b3f7c6dcb258b6e55545e7a4f51539447cd595eb8a2e373ba0015502da1051a450a1c6163636f756e743a8a4e2eb018bdf98a8f53ec755740ffc728637a1d12201a272295e94cf1d8090bdb019dde48e9dab026ad2c3e43aaa7e61cc954a9245d18a5f4c7190ab6040a0a6d756c746973746f726512036163631aa204a0040a9d040a300a0364657812290a27088496a822122038fc49f49648fec62acc434151a51eaa378c1b20a730a749548e36f1529422500a300a03676f7612290a27088496a8221220a78ce489bdf08b9ee869c184876e1623dc38b3e64a5cf1a0005f97976c64deac0a380a0b61746f6d69635f7377617012290a27088496a8221220544c2fa38f61e10a39ec00b3e724d5834761268bb455cdbf5843bcf1531f8fbc0a300a0376616c12290a27088496a82212201f71082c9f6f45fb456b2c00b41e50d2f662f2dfec3cb6965f19d214bf02f3980a0f0a046d61696e12070a05088496a8220a320a057374616b6512290a27088496a82212200dd467343c718f240e50b4feac42970fc8c1c69a018be955f9c27913ac1f8b3c0a300a0361636312290a27088496a8221220270c19ccc9c40c5176b3dfbd8af734c97a307e0dbd8df9e286dcd5d709f973ed0a330a06746f6b656e7312290a27088496a8221220c4f96eedf50c83964de9df013afec2e545012d92528b643a5166c828774187b60a320a05706169727312290a27088496a8221220351c55cfda84596ecd22ebc77013662aba97f81f19d9ef3d150213bb07c823060a360a0974696d655f6c6f636b12290a27088496a8221220e7adf5bd30ce022decf0e9341bf05c464ed70cdbc97423bd2bab8f3571e5179b0a330a06706172616d7312290a27088496a822122042a9dfc356ca435db131eb41fb1975c8482f2434537918665e530b0b4633b5f9").unwrap(); let res = iavl_proof_validation_run_moran(&Bytes::from(input), 3_000u64).unwrap(); - let gas = res.0; + let gas = res.gas_used; assert_eq!(gas, 3_000u64); - let res = hex::encode(res.1); + let res = hex::encode(res.bytes); assert_eq!( res, "0000000000000000000000000000000000000000000000000000000000000001" @@ -127,7 +128,7 @@ mod tests { assert_eq!( res.err(), - Some(PrecompileError::other("invalid merkle proof")) + Some(PrecompileError::other("invalid merkle proof").into()) ); } @@ -148,7 +149,7 @@ mod tests { assert_eq!( res.err(), - Some(PrecompileError::other("invalid merkle proof")) + Some(PrecompileError::other("invalid merkle proof").into()) ); }); @@ -157,7 +158,7 @@ mod tests { assert_eq!( res.err(), - Some(PrecompileError::other("invalid merkle proof")) + Some(PrecompileError::other("invalid merkle proof").into()) ); } @@ -166,10 +167,10 @@ mod tests { let input = hex::decode("00000000000000000000000000000000000000000000000000000000000005086962630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000100380200000000010dd9ac0000000000000000000000000000000000000000000000000000000000000093000000000000000000000000000000000000000000000000000000000000000000f870a0424e4200000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000008ad3c21bcecceda100000094489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec94489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec846553f10072cda827a83531ca0fd7ac917a6b65649719aab0836722caafe0603147a523180a8d020a066961766c3a76120e00000100380200000000010dd9ac1af201f0010aed010a2b0802100318b091c73422200c10f902d266c238a4ca9e26fa9bc36483cd3ebee4e263012f5e7f40c22ee4d20a4d0801100218b091c7342220e4fd47bffd1c06e67edad92b2bf9ca63631978676288a2aa99f95c459436ef632a20121a1f9c4eca726c725796c5375fc4158986ced08e498dc8268ef94d8ed1891612001a370a0e0000010038020000000000000002122011056c6919f02d966991c10721684a8d1542e44003f9ffb47032c18995d4ac7f18b091c7341a340a0e00000100380200000000010dd9ac12202c3a561458f8527b002b5ec3cab2d308662798d6245d4588a4e6a80ebdfe30ac18010ad4050a0a6d756c746973746f726512036962631ac005be050abb050a110a066f7261636c6512070a0508b891c7340a0f0a046d61696e12070a0508b891c7340a350a08736c617368696e6712290a2708b891c7341220c8ccf341e6e695e7e1cb0ce4bf347eea0cc16947d8b4e934ec400b57c59d6f860a380a0b61746f6d69635f7377617012290a2708b891c734122042d4ecc9468f71a70288a95d46564bfcaf2c9f811051dcc5593dbef152976b010a110a0662726964676512070a0508b891c7340a300a0364657812290a2708b891c73412201773be443c27f61075cecdc050ce22eb4990c54679089e90afdc4e0e88182a230a2f0a02736312290a2708b891c7341220df7a0484b7244f76861b1642cfb7a61d923794bd2e076c8dbd05fc4ee29f3a670a330a06746f6b656e7312290a2708b891c734122064958c2f76fec1fa5d1828296e51264c259fa264f499724795a740f48fc4731b0a320a057374616b6512290a2708b891c734122015d2c302143bdf029d58fe381cc3b54cedf77ecb8834dfc5dc3e1555d68f19ab0a330a06706172616d7312290a2708b891c734122050abddcb7c115123a5a4247613ab39e6ba935a3d4f4b9123c4fedfa0895c040a0a300a0361636312290a2708b891c734122079fb5aecc4a9b87e56231103affa5e515a1bdf3d0366490a73e087980b7f1f260a0e0a0376616c12070a0508b891c7340a300a0369626312290a2708b891c7341220e09159530585455058cf1785f411ea44230f39334e6e0f6a3c54dbf069df2b620a300a03676f7612290a2708b891c7341220db85ddd37470983b14186e975a175dfb0bf301b43de685ced0aef18d28b4e0420a320a05706169727312290a2708b891c7341220a78b556bc9e73d86b4c63ceaf146db71b12ac80e4c10dd0ce6eb09c99b0c7cfe0a360a0974696d655f6c6f636b12290a2708b891c73412204775dbe01d41cab018c21ba5c2af94720e4d7119baf693670e70a40ba2a52143").unwrap(); let res = iavl_proof_validation_run(&Bytes::from(input), 3_000u64).unwrap(); - let gas = res.0; + let gas = res.gas_used; assert_eq!(gas, 3_000u64); - let res = hex::encode(res.1); + let res = hex::encode(res.bytes); assert_eq!( res, "0000000000000000000000000000000000000000000000000000000000000001" @@ -181,10 +182,10 @@ mod tests { let input = hex::decode("000000000000000000000000000000000000000000000000000000000000015b6962630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000477696e640000000000000000000000000000000000000000000000000000000000000005626c6f7773ae6d1123fc362b3297bfb19c9f9fabbcbd1e2555b923dead261905b8a2ff6db60a300a0a69637332333a6961766c120477696e641a1c0a1a0a0477696e641205626c6f77731a0b0801180120012a030002040a9d010a0c69637332333a73696d706c6512036962631a87010a84010a036962631220141acb8632cfb808f293f2649cb9aabaca74fc18640900ffd0d48e2994b2a1521a090801180120012a0100222708011201011a205f0ba08283de309300409486e978a3ea59d82bccc838b07c7d39bd87c16a5034222708011201011a20455b81ef5591150bd24d3e57a769f65518b16de93487f0fab02271b3d69e2852").unwrap(); let res = iavl_proof_validation_run_planck(&Bytes::from(input), 3_000u64).unwrap(); - let gas = res.0; + let gas = res.gas_used; assert_eq!(gas, 3_000u64); - let res = hex::encode(res.1); + let res = hex::encode(res.bytes); assert_eq!( res, "0000000000000000000000000000000000000000000000000000000000000001" @@ -196,10 +197,10 @@ mod tests { let input = hex::decode("000000000000000000000000000000000000000000000000000000000000015b6962630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000477696e640000000000000000000000000000000000000000000000000000000000000005626c6f7773ae6d1123fc362b3297bfb19c9f9fabbcbd1e2555b923dead261905b8a2ff6db60a300a0a69637332333a6961766c120477696e641a1c0a1a0a0477696e641205626c6f77731a0b0801180120012a030002040a9d010a0c69637332333a73696d706c6512036962631a87010a84010a036962631220141acb8632cfb808f293f2649cb9aabaca74fc18640900ffd0d48e2994b2a1521a090801180120012a0100222708011201011a205f0ba08283de309300409486e978a3ea59d82bccc838b07c7d39bd87c16a5034222708011201011a20455b81ef5591150bd24d3e57a769f65518b16de93487f0fab02271b3d69e2852").unwrap(); let res = iavl_proof_validation_run_plato(&Bytes::from(input), 3_000u64).unwrap(); - let gas = res.0; + let gas = res.gas_used; assert_eq!(gas, 3_000u64); - let res = hex::encode(res.1); + let res = hex::encode(res.bytes); assert_eq!( res, "0000000000000000000000000000000000000000000000000000000000000001" diff --git a/crates/precompile/src/identity.rs b/crates/precompile/src/identity.rs index 85722ea8..ab869cd0 100644 --- a/crates/precompile/src/identity.rs +++ b/crates/precompile/src/identity.rs @@ -1,6 +1,6 @@ use super::calc_linear_cost_u32; use crate::{Error, Precompile, PrecompileResult, PrecompileWithAddress}; -use revm_primitives::Bytes; +use revm_primitives::{Bytes, PrecompileOutput}; pub const FUN: PrecompileWithAddress = PrecompileWithAddress(crate::u64_to_address(4), Precompile::Standard(identity_run)); @@ -17,7 +17,7 @@ pub const IDENTITY_PER_WORD: u64 = 3; pub fn identity_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { let gas_used = calc_linear_cost_u32(input.len(), IDENTITY_BASE, IDENTITY_PER_WORD); if gas_used > gas_limit { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } - Ok((gas_used, input.clone())) + Ok(PrecompileOutput::new(gas_used, input.clone())) } diff --git a/crates/precompile/src/kzg_point_evaluation.rs b/crates/precompile/src/kzg_point_evaluation.rs index 5790186a..22192ce5 100644 --- a/crates/precompile/src/kzg_point_evaluation.rs +++ b/crates/precompile/src/kzg_point_evaluation.rs @@ -1,6 +1,6 @@ use crate::{Address, Error, Precompile, PrecompileResult, PrecompileWithAddress}; use c_kzg::{Bytes32, Bytes48, KzgProof, KzgSettings}; -use revm_primitives::{hex_literal::hex, Bytes, Env}; +use revm_primitives::{hex_literal::hex, Bytes, Env, PrecompileOutput}; use sha2::{Digest, Sha256}; pub const POINT_EVALUATION: PrecompileWithAddress = @@ -26,19 +26,19 @@ pub const RETURN_VALUE: &[u8; 64] = &hex!( /// with z and y being padded 32 byte big endian values pub fn run(input: &Bytes, gas_limit: u64, env: &Env) -> PrecompileResult { if gas_limit < GAS_COST { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } // Verify input length. if input.len() != 192 { - return Err(Error::BlobInvalidInputLength); + return Err(Error::BlobInvalidInputLength.into()); } // Verify commitment matches versioned_hash let versioned_hash = &input[..32]; let commitment = &input[96..144]; if kzg_to_versioned_hash(commitment) != versioned_hash { - return Err(Error::BlobMismatchedVersion); + return Err(Error::BlobMismatchedVersion.into()); } // Verify KZG proof with z and y in big endian format @@ -47,11 +47,11 @@ pub fn run(input: &Bytes, gas_limit: u64, env: &Env) -> PrecompileResult { let y = as_bytes32(&input[64..96]); let proof = as_bytes48(&input[144..192]); if !verify_kzg_proof(commitment, z, y, proof, env.cfg.kzg_settings.get()) { - return Err(Error::BlobVerifyKzgProofFailed); + return Err(Error::BlobVerifyKzgProofFailed.into()); } // Return FIELD_ELEMENTS_PER_BLOB and BLS_MODULUS as padded 32 byte big endian values - Ok((GAS_COST, RETURN_VALUE.into())) + Ok(PrecompileOutput::new(GAS_COST, RETURN_VALUE.into())) } /// `VERSIONED_HASH_VERSION_KZG ++ sha256(commitment)[1..]` @@ -113,8 +113,8 @@ mod tests { let expected_output = hex!("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"); let gas = 50000; let env = Env::default(); - let (actual_gas, actual_output) = run(&input.into(), gas, &env).unwrap(); - assert_eq!(actual_gas, gas); - assert_eq!(actual_output[..], expected_output); + let output = run(&input.into(), gas, &env).unwrap(); + assert_eq!(output.gas_used, gas); + assert_eq!(output.bytes[..], expected_output); } } diff --git a/crates/precompile/src/lib.rs b/crates/precompile/src/lib.rs index a63d84c2..54be1931 100644 --- a/crates/precompile/src/lib.rs +++ b/crates/precompile/src/lib.rs @@ -35,7 +35,7 @@ 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, vec::Vec}; @@ -43,27 +43,12 @@ pub fn calc_linear_cost_u32(len: usize, base: u64, word: u64) -> u64 { (len as u64 + 32 - 1) / 32 * word + base } -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -pub struct PrecompileOutput { - pub cost: u64, - pub output: Vec, - pub logs: Vec, -} - -impl PrecompileOutput { - pub fn without_logs(cost: u64, output: Vec) -> Self { - Self { - cost, - output, - logs: Vec::new(), - } - } -} - #[derive(Clone, Default, Debug)] pub struct Precompiles { /// Precompiles. - pub inner: HashMap, + inner: HashMap, + /// Addresses of precompile. + addresses: HashSet
, } impl Precompiles { @@ -104,6 +89,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(); @@ -128,12 +118,12 @@ impl Precompiles { INSTANCE.get_or_init(|| { let mut precompiles = Self::byzantium().clone(); precompiles.extend([ - // EIP-152: Add BLAKE2 compression function `F` precompile. - blake2::FUN, // EIP-1108: Reduce alt_bn128 precompile gas costs. bn128::add::ISTANBUL, bn128::mul::ISTANBUL, bn128::pair::ISTANBUL, + // EIP-152: Add BLAKE2 compression function `F` precompile. + blake2::FUN, ]); #[cfg(feature = "bsc")] @@ -348,13 +338,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() } @@ -386,11 +376,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)); } } @@ -409,6 +407,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/precompile/src/modexp.rs b/crates/precompile/src/modexp.rs index a55b4459..8d9e5da8 100644 --- a/crates/precompile/src/modexp.rs +++ b/crates/precompile/src/modexp.rs @@ -5,7 +5,7 @@ use crate::{ }; use aurora_engine_modexp::modexp; use core::cmp::{max, min}; -use revm_primitives::Bytes; +use revm_primitives::{Bytes, PrecompileOutput}; pub const BYZANTIUM: PrecompileWithAddress = PrecompileWithAddress( crate::u64_to_address(5), @@ -50,7 +50,7 @@ where { // If there is no minimum gas, return error. if min_gas > gas_limit { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } // The format of input is: @@ -66,20 +66,20 @@ where // cast base and modulus to usize, it does not make sense to handle larger values let Ok(base_len) = usize::try_from(base_len) else { - return Err(Error::ModexpBaseOverflow); + return Err(Error::ModexpBaseOverflow.into()); }; let Ok(mod_len) = usize::try_from(mod_len) else { - return Err(Error::ModexpModOverflow); + return Err(Error::ModexpModOverflow.into()); }; // Handle a special case when both the base and mod length are zero. if base_len == 0 && mod_len == 0 { - return Ok((min_gas, Bytes::new())); + return Ok(PrecompileOutput::new(min_gas, Bytes::new())); } // Cast exponent length to usize, since it does not make sense to handle larger values. let Ok(exp_len) = usize::try_from(exp_len) else { - return Err(Error::ModexpModOverflow); + return Err(Error::ModexpModOverflow.into()); }; // Used to extract ADJUSTED_EXPONENT_LENGTH. @@ -99,7 +99,7 @@ where // Check if we have enough gas. let gas_cost = calc_gas(base_len as u64, exp_len as u64, mod_len as u64, &exp_highp); if gas_cost > gas_limit { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } // Padding is needed if the input does not contain all 3 values. @@ -113,7 +113,10 @@ where let output = modexp(base, exponent, modulus); // left pad the result to modulus length. bytes will always by less or equal to modulus length. - Ok((gas_cost, left_pad_vec(&output, mod_len).into_owned().into())) + Ok(PrecompileOutput::new( + gas_cost, + left_pad_vec(&output, mod_len).into_owned().into(), + )) } pub fn byzantium_gas_calc(base_len: u64, exp_len: u64, mod_len: u64, exp_highp: &U256) -> u64 { @@ -350,11 +353,11 @@ mod tests { let res = byzantium_run(&input, 100_000_000).unwrap(); let expected = hex::decode(test.expected).unwrap(); assert_eq!( - res.0, test_gas, + res.gas_used, test_gas, "used gas not matching for test: {}", test.name ); - assert_eq!(res.1, expected, "test:{}", test.name); + assert_eq!(res.bytes, expected, "test:{}", test.name); } } @@ -365,11 +368,11 @@ mod tests { let res = berlin_run(&input, 100_000_000).unwrap(); let expected = hex::decode(test.expected).unwrap(); assert_eq!( - res.0, test_gas, + res.gas_used, test_gas, "used gas not matching for test: {}", test.name ); - assert_eq!(res.1, expected, "test:{}", test.name); + assert_eq!(res.bytes, expected, "test:{}", test.name); } } @@ -377,6 +380,6 @@ mod tests { fn test_berlin_modexp_empty_input() { let res = berlin_run(&Bytes::new(), 100_000).unwrap(); let expected: Vec = Vec::new(); - assert_eq!(res.1, expected) + assert_eq!(res.bytes, expected) } } diff --git a/crates/precompile/src/secp256k1.rs b/crates/precompile/src/secp256k1.rs index 4ad079e1..86794b38 100644 --- a/crates/precompile/src/secp256k1.rs +++ b/crates/precompile/src/secp256k1.rs @@ -1,5 +1,5 @@ use crate::{utilities::right_pad, Error, Precompile, PrecompileResult, PrecompileWithAddress}; -use revm_primitives::{alloy_primitives::B512, Bytes, B256}; +use revm_primitives::{alloy_primitives::B512, Bytes, PrecompileOutput, B256}; pub const ECRECOVER: PrecompileWithAddress = PrecompileWithAddress( crate::u64_to_address(1), @@ -70,14 +70,14 @@ pub fn ec_recover_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { const ECRECOVER_BASE: u64 = 3_000; if ECRECOVER_BASE > gas_limit { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } let input = right_pad::<128>(input); // `v` must be a 32-byte big-endian integer equal to 27 or 28. if !(input[32..63].iter().all(|&b| b == 0) && matches!(input[63], 27 | 28)) { - return Ok((ECRECOVER_BASE, Bytes::new())); + return Ok(PrecompileOutput::new(ECRECOVER_BASE, Bytes::new())); } let msg = <&B256>::try_from(&input[0..32]).unwrap(); @@ -87,5 +87,5 @@ pub fn ec_recover_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { let out = secp256k1::ecrecover(sig, recid, msg) .map(|o| o.to_vec().into()) .unwrap_or_default(); - Ok((ECRECOVER_BASE, out)) + Ok(PrecompileOutput::new(ECRECOVER_BASE, out)) } diff --git a/crates/precompile/src/secp256r1.rs b/crates/precompile/src/secp256r1.rs index e2c9951a..46f00b74 100644 --- a/crates/precompile/src/secp256r1.rs +++ b/crates/precompile/src/secp256r1.rs @@ -8,7 +8,7 @@ //! with the address that it is currently deployed at. use crate::{u64_to_address, Precompile, PrecompileWithAddress}; use p256::ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey}; -use revm_primitives::{Bytes, PrecompileError, PrecompileResult, B256}; +use revm_primitives::{Bytes, PrecompileError, PrecompileOutput, PrecompileResult, B256}; /// Base gas fee for secp256r1 p256verify operation. const P256VERIFY_BASE: u64 = 3450; @@ -33,14 +33,14 @@ pub const P256VERIFY: PrecompileWithAddress = /// | 32 | 32 | 32 | 32 | 32 | pub fn p256_verify(input: &Bytes, gas_limit: u64) -> PrecompileResult { if P256VERIFY_BASE > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileError::OutOfGas.into()); } let result = if verify_impl(input).is_some() { B256::with_last_byte(1).into() } else { Bytes::new() }; - Ok((P256VERIFY_BASE, result)) + Ok(PrecompileOutput::new(P256VERIFY_BASE, result)) } /// Returns `Some(())` if the signature included in the input byte slice is @@ -73,7 +73,7 @@ pub fn verify_impl(input: &[u8]) -> Option<()> { #[cfg(test)] mod test { use super::*; - use revm_primitives::hex::FromHex; + use crate::primitives::{hex::FromHex, PrecompileErrors}; use rstest::rstest; #[rstest] @@ -96,14 +96,14 @@ mod test { fn test_sig_verify(#[case] input: &str, #[case] expect_success: bool) { let input = Bytes::from_hex(input).unwrap(); let target_gas = 3_500u64; - let (gas_used, res) = p256_verify(&input, target_gas).unwrap(); - assert_eq!(gas_used, 3_450u64); + let outcome = p256_verify(&input, target_gas).unwrap(); + assert_eq!(outcome.gas_used, 3_450u64); let expected_result = if expect_success { B256::with_last_byte(1).into() } else { Bytes::new() }; - assert_eq!(res, expected_result); + assert_eq!(outcome.bytes, expected_result); } #[rstest] @@ -113,7 +113,10 @@ mod test { let result = p256_verify(&input, target_gas); assert!(result.is_err()); - assert_eq!(result.err(), Some(PrecompileError::OutOfGas)); + assert_eq!( + result.err(), + Some(PrecompileErrors::Error(PrecompileError::OutOfGas)) + ); } #[rstest] diff --git a/crates/precompile/src/tendermint.rs b/crates/precompile/src/tendermint.rs index ef06c96e..01289472 100644 --- a/crates/precompile/src/tendermint.rs +++ b/crates/precompile/src/tendermint.rs @@ -1,5 +1,6 @@ use crate::{Bytes, Error, Precompile, PrecompileError, PrecompileResult, PrecompileWithAddress}; use parity_bytes::BytesRef; +use revm_primitives::PrecompileOutput; use tendermint::lite::light_client; /// Tendermint precompile for BSC. @@ -16,7 +17,7 @@ pub(crate) const TENDERMINT_HEADER_VALIDATION_NANO: PrecompileWithAddress = Prec /// Run the Tendermint header validation precompile after Nano hardfork. fn tendermint_header_validation_run_nano(_input: &Bytes, _gas_limit: u64) -> PrecompileResult { - Err(PrecompileError::other("suspended")) + Err(PrecompileError::other("suspended").into()) } /// Run the Tendermint header validation precompile. @@ -24,15 +25,18 @@ fn tendermint_header_validation_run(input: &Bytes, gas_limit: u64) -> Precompile const TENDERMINT_HEADER_VALIDATION_BASE: u64 = 3_000; if TENDERMINT_HEADER_VALIDATION_BASE > gas_limit { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } let mut output = vec![0u8, 0, 0]; let mut bytes = BytesRef::Flexible(&mut output); let res = light_client::TmHeaderVerifier::execute(input.as_ref(), &mut bytes); match res { - Ok(()) => Ok((TENDERMINT_HEADER_VALIDATION_BASE, Bytes::from(output))), - Err(str) => Err(PrecompileError::other(str)), + Ok(()) => Ok(PrecompileOutput::new( + TENDERMINT_HEADER_VALIDATION_BASE, + Bytes::from(output), + )), + Err(str) => Err(PrecompileError::other(str).into()), } } @@ -46,10 +50,10 @@ mod tests { let input = hex::decode("0000000000000000000000000000000000000000000000000000000000001325000000000000000000000000000000000000000000000000000000000000022042696e616e63652d436861696e2d4e696c6500000000000000000000000000000000000003fc05e2b7029751d2a6581efc2f79712ec44d8b4981850325a7feadaa58ef4ddaa18a9380d9ab0fc10d18ca0e0832d5f4c063c5489ec1443dfb738252d038a82131b27ae17cbe9c20cdcfdf876b3b12978d3264a007fcaaa71c4cdb701d9ebc0323f44f000000174876e800184e7b103d34c41003f9b864d5f8c1adda9bd0436b253bb3c844bc739c1e77c9000000174876e8004d420aea843e92a0cfe69d89696dff6827769f9cb52a249af537ce89bf2a4b74000000174876e800bd03de9f8ab29e2800094e153fac6f696cfa512536c9c2f804dcb2c2c4e4aed6000000174876e8008f4a74a07351895ddf373057b98fae6dfaf2cd21f37a063e19601078fe470d53000000174876e8004a5d4753eb79f92e80efe22df7aca4f666a4f44bf81c536c4a09d4b9c5b654b5000000174876e800c80e9abef7ff439c10c68fe8f1303deddfc527718c3b37d8ba6807446e3c827a000000174876e8009142afcc691b7cc05d26c7b0be0c8b46418294171730e079f384fde2fa50bafc000000174876e80049b288e4ebbb3a281c2d546fc30253d5baf08993b6e5d295fb787a5b314a298e000000174876e80004224339688f012e649de48e241880092eaa8f6aa0f4f14bfcf9e0c76917c0b6000000174876e8004034b37ceda8a0bf13b1abaeee7a8f9383542099a554d219b93d0ce69e3970e8000000174876e800e3210a92130abb020a02080a121242696e616e63652d436861696e2d4e696c6518e38bf01f220c08e191aef20510f5f4e4c70230dae0c7173a480a20102b54820dd8fb5bc2c4e875ee573fa294d9b7b7ceb362aa8fd21b33dee41b1c12240801122082f341511f3e6b89d6177fd31f8a106013ba09d6e12ef40a7dec885d81b687634220b1b77e6977e0cd0177e3102a78833c9e152aa646ed4fb5a77e8af58c9867eec0522080d9ab0fc10d18ca0e0832d5f4c063c5489ec1443dfb738252d038a82131b27a5a2080d9ab0fc10d18ca0e0832d5f4c063c5489ec1443dfb738252d038a82131b27a6220294d8fbd0b94b767a7eba9840f299a3586da7fe6b5dead3b7eecba193c400f936a20a3e248bc209955054d880e4d89ff3c0419c0cd77681f4b4c6649ead5545054b982011462633d9db7ed78e951f79913fdc8231aa77ec12b12d1100a480a207eaabf7df1081377e06e08efe7ad17974049380bdd65a9b053c099ef80ff6e6f122408011220d153cc308d9cb96ca43ffeceaae1ee85794c83d17408ff76cfee92f5e91d0be212b601080210e38bf01f22480a207eaabf7df1081377e06e08efe7ad17974049380bdd65a9b053c099ef80ff6e6f122408011220d153cc308d9cb96ca43ffeceaae1ee85794c83d17408ff76cfee92f5e91d0be22a0b08e291aef20510cebfe23e321406fd60078eb4c2356137dd50036597db267cf61642409276f20ad4b152f91c344bd63ac691bad66e04e228a8b58dca293ff0bd10f8aef6dfbcecae49e32b09d89e10b771a6c01628628596a95e126b04763560c66c0f12b801080210e38bf01f22480a207eaabf7df1081377e06e08efe7ad17974049380bdd65a9b053c099ef80ff6e6f122408011220d153cc308d9cb96ca43ffeceaae1ee85794c83d17408ff76cfee92f5e91d0be22a0b08e291aef20510a4caa532321418e69cc672973992bb5f76d049a5b2c5ddf77436380142409ed2b74fa835296d552e68c439dd4ee3fa94fb197282edcc1cc815c863ca42a2c9a73475ff6be9064371a61655a3c31d2f0acc89c3a4489ad4c2671aef52360512b801080210e38bf01f22480a207eaabf7df1081377e06e08efe7ad17974049380bdd65a9b053c099ef80ff6e6f122408011220d153cc308d9cb96ca43ffeceaae1ee85794c83d17408ff76cfee92f5e91d0be22a0b08e291aef20510a69eca2f3214344c39bb8f4512d6cab1f6aafac1811ef9d8afdf38024240de2768ead90011bcbb1914abc1572749ab7b81382eb81cff3b41c56edc12470a7b8a4d61f8b4ca7b2cb7e24706edd219455796b4db74cd36965859f91dc8910312b801080210e38bf01f22480a207eaabf7df1081377e06e08efe7ad17974049380bdd65a9b053c099ef80ff6e6f122408011220d153cc308d9cb96ca43ffeceaae1ee85794c83d17408ff76cfee92f5e91d0be22a0b08e291aef20510dcdd833b321437ef19af29679b368d2b9e9de3f8769b357866763803424072ddfe0aeb13616b3f17eb60b19a923ec51fcc726625094aa069255c829c8cdd9e242080a1e559b0030fe9a0db19fd34e392bd78df12a9caff9f2b811bc1ac0a12b801080210e38bf01f22480a207eaabf7df1081377e06e08efe7ad17974049380bdd65a9b053c099ef80ff6e6f122408011220d153cc308d9cb96ca43ffeceaae1ee85794c83d17408ff76cfee92f5e91d0be22a0b08e291aef20510e9f2f859321462633d9db7ed78e951f79913fdc8231aa77ec12b38044240f5f61c640ab2402b44936de0d24e7b439df78bc3ef15467ecb29b92ece4aa0550790d5ce80761f2ac4b0e3283969725c42343749d9b44b179b2d4fced66c5d0412b801080210e38bf01f22480a207eaabf7df1081377e06e08efe7ad17974049380bdd65a9b053c099ef80ff6e6f122408011220d153cc308d9cb96ca43ffeceaae1ee85794c83d17408ff76cfee92f5e91d0be22a0b08e291aef20510ff90f55532147b343e041ca130000a8bc00c35152bd7e774003738054240df6e298b3efd42eb536e68a0210bc921e8b5dc145fe965f63f4d3490064f239f2a54a6db16c96086e4ae52280c04ad8b32b44f5ff3d41f0c364949ccb628c50312b801080210e38bf01f22480a207eaabf7df1081377e06e08efe7ad17974049380bdd65a9b053c099ef80ff6e6f122408011220d153cc308d9cb96ca43ffeceaae1ee85794c83d17408ff76cfee92f5e91d0be22a0b08e291aef20510cad7c931321491844d296bd8e591448efc65fd6ad51a888d58fa3806424030298627da1afd28229aac150f553724b594989e59136d6a175d84e45a4dee344ff9e0eeb69fdf29abb6d833adc3e1ccdc87b2a65019ef5fb627c44d9d132c0012b801080210e38bf01f22480a207eaabf7df1081377e06e08efe7ad17974049380bdd65a9b053c099ef80ff6e6f122408011220d153cc308d9cb96ca43ffeceaae1ee85794c83d17408ff76cfee92f5e91d0be22a0b08e291aef20510c8c296323214b3727172ce6473bc780298a2d66c12f1a14f5b2a38074240918491100730b4523f0c85409f6d1cca9ebc4b8ca6df8d55fe3d85158fa43286608693c50332953e1d3b93e3e78b24e158d6a2275ce8c6c7c07a7a646a19200312b801080210e38bf01f22480a207eaabf7df1081377e06e08efe7ad17974049380bdd65a9b053c099ef80ff6e6f122408011220d153cc308d9cb96ca43ffeceaae1ee85794c83d17408ff76cfee92f5e91d0be22a0b08e291aef2051086f1a2403214b6f20c7faa2b2f6f24518fa02b71cb5f4a09fba338084240ca59c9fc7f6ab660e9970fc03e5ed588ccb8be43fe5a3e8450287b726f29d039e53fe888438f178ac63c3d2ca969cd8c2fbc8606f067634339b6a94a7382960212b801080210e38bf01f22480a207eaabf7df1081377e06e08efe7ad17974049380bdd65a9b053c099ef80ff6e6f122408011220d153cc308d9cb96ca43ffeceaae1ee85794c83d17408ff76cfee92f5e91d0be22a0b08e291aef2051080efbb543214e0dd72609cc106210d1aa13936cb67b93a0aee2138094240e787a21f5cb7052624160759a9d379dd9db144f2b498bca026375c9ce8ecdc2a0936af1c309b3a0f686c92bf5578b595a4ca99036a19c9fc50d3718fd454b30012b801080210e38bf01f22480a207eaabf7df1081377e06e08efe7ad17974049380bdd65a9b053c099ef80ff6e6f122408011220d153cc308d9cb96ca43ffeceaae1ee85794c83d17408ff76cfee92f5e91d0be22a0b08e291aef20510ddf8d85a3214fc3108dc3814888f4187452182bc1baf83b71bc9380a4240d51ea31f6449eed71de22339722af1edbb0b21401037d85882b32a2ed8ae9127f2df4d1da2092729e582812856227ed6cdf98a3f60203d1ff80bd635fb03bb0912a4070a4f0a1406fd60078eb4c2356137dd50036597db267cf61612251624de6420e17cbe9c20cdcfdf876b3b12978d3264a007fcaaa71c4cdb701d9ebc0323f44f1880d0dbc3f4022080e0ebdaf2e2ffffff010a4b0a1418e69cc672973992bb5f76d049a5b2c5ddf7743612251624de6420184e7b103d34c41003f9b864d5f8c1adda9bd0436b253bb3c844bc739c1e77c91880d0dbc3f4022080d0dbc3f4020a4b0a14344c39bb8f4512d6cab1f6aafac1811ef9d8afdf12251624de64204d420aea843e92a0cfe69d89696dff6827769f9cb52a249af537ce89bf2a4b741880d0dbc3f4022080d0dbc3f4020a4b0a1437ef19af29679b368d2b9e9de3f8769b3578667612251624de6420bd03de9f8ab29e2800094e153fac6f696cfa512536c9c2f804dcb2c2c4e4aed61880d0dbc3f4022080d0dbc3f4020a4b0a1462633d9db7ed78e951f79913fdc8231aa77ec12b12251624de64208f4a74a07351895ddf373057b98fae6dfaf2cd21f37a063e19601078fe470d531880d0dbc3f4022080d0dbc3f4020a4b0a147b343e041ca130000a8bc00c35152bd7e774003712251624de64204a5d4753eb79f92e80efe22df7aca4f666a4f44bf81c536c4a09d4b9c5b654b51880d0dbc3f4022080d0dbc3f4020a4b0a1491844d296bd8e591448efc65fd6ad51a888d58fa12251624de6420c80e9abef7ff439c10c68fe8f1303deddfc527718c3b37d8ba6807446e3c827a1880d0dbc3f4022080d0dbc3f4020a4b0a14b3727172ce6473bc780298a2d66c12f1a14f5b2a12251624de64209142afcc691b7cc05d26c7b0be0c8b46418294171730e079f384fde2fa50bafc1880d0dbc3f4022080d0dbc3f4020a4b0a14b6f20c7faa2b2f6f24518fa02b71cb5f4a09fba312251624de642049b288e4ebbb3a281c2d546fc30253d5baf08993b6e5d295fb787a5b314a298e1880d0dbc3f4022080d0dbc3f4020a4b0a14e0dd72609cc106210d1aa13936cb67b93a0aee2112251624de642004224339688f012e649de48e241880092eaa8f6aa0f4f14bfcf9e0c76917c0b61880d0dbc3f4022080d0dbc3f4020a4b0a14fc3108dc3814888f4187452182bc1baf83b71bc912251624de64204034b37ceda8a0bf13b1abaeee7a8f9383542099a554d219b93d0ce69e3970e81880d0dbc3f4022080d0dbc3f402124f0a1406fd60078eb4c2356137dd50036597db267cf61612251624de6420e17cbe9c20cdcfdf876b3b12978d3264a007fcaaa71c4cdb701d9ebc0323f44f1880d0dbc3f4022080e0ebdaf2e2ffffff011aa4070a4f0a1406fd60078eb4c2356137dd50036597db267cf61612251624de6420e17cbe9c20cdcfdf876b3b12978d3264a007fcaaa71c4cdb701d9ebc0323f44f1880d0dbc3f4022080e0ebdaf2e2ffffff010a4b0a1418e69cc672973992bb5f76d049a5b2c5ddf7743612251624de6420184e7b103d34c41003f9b864d5f8c1adda9bd0436b253bb3c844bc739c1e77c91880d0dbc3f4022080d0dbc3f4020a4b0a14344c39bb8f4512d6cab1f6aafac1811ef9d8afdf12251624de64204d420aea843e92a0cfe69d89696dff6827769f9cb52a249af537ce89bf2a4b741880d0dbc3f4022080d0dbc3f4020a4b0a1437ef19af29679b368d2b9e9de3f8769b3578667612251624de6420bd03de9f8ab29e2800094e153fac6f696cfa512536c9c2f804dcb2c2c4e4aed61880d0dbc3f4022080d0dbc3f4020a4b0a1462633d9db7ed78e951f79913fdc8231aa77ec12b12251624de64208f4a74a07351895ddf373057b98fae6dfaf2cd21f37a063e19601078fe470d531880d0dbc3f4022080d0dbc3f4020a4b0a147b343e041ca130000a8bc00c35152bd7e774003712251624de64204a5d4753eb79f92e80efe22df7aca4f666a4f44bf81c536c4a09d4b9c5b654b51880d0dbc3f4022080d0dbc3f4020a4b0a1491844d296bd8e591448efc65fd6ad51a888d58fa12251624de6420c80e9abef7ff439c10c68fe8f1303deddfc527718c3b37d8ba6807446e3c827a1880d0dbc3f4022080d0dbc3f4020a4b0a14b3727172ce6473bc780298a2d66c12f1a14f5b2a12251624de64209142afcc691b7cc05d26c7b0be0c8b46418294171730e079f384fde2fa50bafc1880d0dbc3f4022080d0dbc3f4020a4b0a14b6f20c7faa2b2f6f24518fa02b71cb5f4a09fba312251624de642049b288e4ebbb3a281c2d546fc30253d5baf08993b6e5d295fb787a5b314a298e1880d0dbc3f4022080d0dbc3f4020a4b0a14e0dd72609cc106210d1aa13936cb67b93a0aee2112251624de642004224339688f012e649de48e241880092eaa8f6aa0f4f14bfcf9e0c76917c0b61880d0dbc3f4022080d0dbc3f4020a4b0a14fc3108dc3814888f4187452182bc1baf83b71bc912251624de64204034b37ceda8a0bf13b1abaeee7a8f9383542099a554d219b93d0ce69e3970e81880d0dbc3f4022080d0dbc3f402124f0a1406fd60078eb4c2356137dd50036597db267cf61612251624de6420e17cbe9c20cdcfdf876b3b12978d3264a007fcaaa71c4cdb701d9ebc0323f44f1880d0dbc3f4022080e0ebdaf2e2ffffff01").unwrap(); let res = tendermint_header_validation_run(&Bytes::from(input), 3_000u64).unwrap(); - let gas = res.0; + let gas = res.gas_used; assert_eq!(gas, 3_000u64); - let output = hex::encode(res.1); + let output = hex::encode(res.bytes); assert_eq!(output, "000000000000000000000000000000000000000000000000000000000000022042696e616e63652d436861696e2d4e696c6500000000000000000000000000000000000003fc05e3a3e248bc209955054d880e4d89ff3c0419c0cd77681f4b4c6649ead5545054b980d9ab0fc10d18ca0e0832d5f4c063c5489ec1443dfb738252d038a82131b27ae17cbe9c20cdcfdf876b3b12978d3264a007fcaaa71c4cdb701d9ebc0323f44f000000174876e800184e7b103d34c41003f9b864d5f8c1adda9bd0436b253bb3c844bc739c1e77c9000000174876e8004d420aea843e92a0cfe69d89696dff6827769f9cb52a249af537ce89bf2a4b74000000174876e800bd03de9f8ab29e2800094e153fac6f696cfa512536c9c2f804dcb2c2c4e4aed6000000174876e8008f4a74a07351895ddf373057b98fae6dfaf2cd21f37a063e19601078fe470d53000000174876e8004a5d4753eb79f92e80efe22df7aca4f666a4f44bf81c536c4a09d4b9c5b654b5000000174876e800c80e9abef7ff439c10c68fe8f1303deddfc527718c3b37d8ba6807446e3c827a000000174876e8009142afcc691b7cc05d26c7b0be0c8b46418294171730e079f384fde2fa50bafc000000174876e80049b288e4ebbb3a281c2d546fc30253d5baf08993b6e5d295fb787a5b314a298e000000174876e80004224339688f012e649de48e241880092eaa8f6aa0f4f14bfcf9e0c76917c0b6000000174876e8004034b37ceda8a0bf13b1abaeee7a8f9383542099a554d219b93d0ce69e3970e8000000174876e800"); } } diff --git a/crates/precompile/src/tm_secp256k1.rs b/crates/precompile/src/tm_secp256k1.rs index 7dacc9e2..e06cfa9b 100644 --- a/crates/precompile/src/tm_secp256k1.rs +++ b/crates/precompile/src/tm_secp256k1.rs @@ -1,4 +1,5 @@ use crate::{Bytes, Error, Precompile, PrecompileError, PrecompileResult, PrecompileWithAddress}; +use revm_primitives::PrecompileOutput; use secp256k1::{ecdsa, Message, PublicKey}; use tendermint::{account, public_key}; @@ -23,19 +24,19 @@ fn tm_secp256k1_signature_recover_run(input: &Bytes, gas_limit: u64) -> Precompi const TM_SECP256K1_SIGNATURE_RECOVER_BASE: u64 = 3_000; if TM_SECP256K1_SIGNATURE_RECOVER_BASE > gas_limit { - return Err(Error::OutOfGas); + return Err(Error::OutOfGas.into()); } let input_length = input.len(); if input_length != SECP256K1_PUBKEY_LENGTH + SECP256K1_SIGNATURE_LENGTH + SECP256K1_SIGNATURE_MSGHASH_LENGTH { - return Err(PrecompileError::other("invalid input")); + return Err(PrecompileError::other("invalid input").into()); } let public_key = match PublicKey::from_slice(&input[..SECP256K1_PUBKEY_LENGTH]) { Ok(pk) => pk, - Err(_) => return Err(PrecompileError::other("invalid pubkey")), + Err(_) => return Err(PrecompileError::other("invalid pubkey").into()), }; let message = Message::from_digest( @@ -48,22 +49,22 @@ fn tm_secp256k1_signature_recover_run(input: &Bytes, gas_limit: u64) -> Precompi &input[SECP256K1_PUBKEY_LENGTH..SECP256K1_PUBKEY_LENGTH + SECP256K1_SIGNATURE_LENGTH], ) { Ok(s) => s, - Err(_) => return Err(PrecompileError::other("invalid signature")), + Err(_) => return Err(PrecompileError::other("invalid signature").into()), }; let res = sig.verify(&message, &public_key).is_ok(); if !res { - return Err(PrecompileError::other("invalid signature")); + return Err(PrecompileError::other("invalid signature").into()); } let tm_pub_key = match public_key::PublicKey::from_raw_secp256k1(&input[..SECP256K1_PUBKEY_LENGTH]) { Some(pk) => pk, - None => return Err(PrecompileError::other("invalid pubkey")), + None => return Err(PrecompileError::other("invalid pubkey").into()), }; - return Ok(( + return Ok(PrecompileOutput::new( TM_SECP256K1_SIGNATURE_RECOVER_BASE, Bytes::copy_from_slice(account::Id::from(tm_pub_key).as_bytes()), )); @@ -94,10 +95,10 @@ mod tests { let input = revm_primitives::Bytes::copy_from_slice(&input); let res = tm_secp256k1_signature_recover_run(&input, 3_000u64).unwrap(); - let gas = res.0; + let gas = res.gas_used; assert_eq!(gas, 3_000u64); - let res = res.1; + let res = res.bytes; assert_eq!(res, Bytes::from(expect_address)); } @@ -121,10 +122,10 @@ mod tests { let input = revm_primitives::Bytes::copy_from_slice(&input); let res = tm_secp256k1_signature_recover_run(&input, 3_000u64).unwrap(); - let gas = res.0; + let gas = res.gas_used; assert_eq!(gas, 3_000u64); - let res = res.1; + let res = res.bytes; assert_eq!(res, Bytes::from(expect_address)); } } diff --git a/crates/primitives/src/bytecode.rs b/crates/primitives/src/bytecode.rs index 232d61f6..75abcdd6 100644 --- a/crates/primitives/src/bytecode.rs +++ b/crates/primitives/src/bytecode.rs @@ -1,8 +1,9 @@ pub mod eof; pub mod legacy; -pub use eof::Eof; +pub use eof::{Eof, EOF_MAGIC, EOF_MAGIC_BYTES, EOF_MAGIC_HASH}; pub use legacy::{JumpTable, LegacyAnalyzedBytecode}; +use std::sync::Arc; use crate::{keccak256, Bytes, B256, KECCAK_EMPTY}; @@ -15,7 +16,7 @@ pub enum Bytecode { /// The bytecode has been analyzed for valid jump destinations. LegacyAnalyzed(LegacyAnalyzedBytecode), /// Ethereum Object Format - Eof(Eof), + Eof(Arc), } impl Default for Bytecode { @@ -53,7 +54,7 @@ impl Bytecode { /// Return reference to the EOF if bytecode is EOF. #[inline] - pub const fn eof(&self) -> Option<&Eof> { + pub const fn eof(&self) -> Option<&Arc> { match self { Self::Eof(eof) => Some(eof), _ => None, @@ -166,3 +167,27 @@ impl Bytecode { self.len() == 0 } } + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + + #[test] + fn eof_arc_clone() { + let eof = Arc::new(Eof::default()); + let bytecode = Bytecode::Eof(Arc::clone(&eof)); + + // Cloning the Bytecode should not clone the underlying Eof + let cloned_bytecode = bytecode.clone(); + if let Bytecode::Eof(original_arc) = bytecode { + if let Bytecode::Eof(cloned_arc) = cloned_bytecode { + assert!(Arc::ptr_eq(&original_arc, &cloned_arc)); + } else { + panic!("Cloned bytecode is not Eof"); + } + } else { + panic!("Original bytecode is not Eof"); + } + } +} diff --git a/crates/primitives/src/bytecode/eof.rs b/crates/primitives/src/bytecode/eof.rs index 67580c15..db6814b7 100644 --- a/crates/primitives/src/bytecode/eof.rs +++ b/crates/primitives/src/bytecode/eof.rs @@ -7,10 +7,20 @@ pub use body::EofBody; pub use header::EofHeader; pub use types_section::TypesSection; -use crate::Bytes; +use crate::{b256, bytes, Bytes, B256}; use core::cmp::min; use std::{vec, vec::Vec}; +/// Hash of EF00 bytes that is used for EXTCODEHASH when called from legacy bytecode. +pub const EOF_MAGIC_HASH: B256 = + b256!("9dbf3648db8210552e9c4f75c6a1c3057c0ca432043bd648be15fe7be05646f5"); + +/// EOF Magic in u16 form. +pub const EOF_MAGIC: u16 = 0xEF00; + +/// EOF magic number in array form. +pub static EOF_MAGIC_BYTES: Bytes = bytes!("ef00"); + /// EOF - Ethereum Object Format. /// /// It consist of a header, body and raw original bytes Specified in EIP. @@ -76,6 +86,26 @@ impl Eof { buffer.into() } + /// Decode EOF that have additional dangling bytes. + /// Assume that data section is fully filled. + pub fn decode_dangling(mut eof: Bytes) -> Result<(Self, Bytes), EofDecodeError> { + let (header, _) = EofHeader::decode(&eof)?; + let eof_size = header.body_size() + header.size(); + if eof_size > eof.len() { + return Err(EofDecodeError::MissingInput); + } + let dangling_data = eof.split_off(eof_size); + let body = EofBody::decode(&eof, &header)?; + Ok(( + Self { + header, + body, + raw: eof, + }, + dangling_data, + )) + } + /// Decode EOF from raw bytes. pub fn decode(raw: Bytes) -> Result { let (header, _) = EofHeader::decode(&raw)?; @@ -140,6 +170,42 @@ mod test { assert_eq!(bytes, eof.encode_slow()); } + #[test] + fn decode_eof_dangling() { + let test_cases = [ + ( + bytes!("ef000101000402000100010400000000800000fe"), + bytes!("010203"), + false, + ), + ( + bytes!("ef000101000402000100010400000000800000fe"), + bytes!(""), + false, + ), + ( + bytes!("ef000101000402000100010400000000800000"), + bytes!(""), + true, + ), + ]; + + for (eof_bytes, dangling_data, is_err) in test_cases { + let mut raw = eof_bytes.to_vec(); + raw.extend(&dangling_data); + let raw = Bytes::from(raw); + + let result = Eof::decode_dangling(raw.clone()); + assert_eq!(result.is_err(), is_err); + if is_err { + continue; + } + let (decoded_eof, decoded_dangling) = result.unwrap(); + assert_eq!(eof_bytes, decoded_eof.encode_slow()); + assert_eq!(decoded_dangling, dangling_data); + } + } + #[test] fn data_slice() { let bytes = bytes!("ef000101000402000100010400000000800000fe"); diff --git a/crates/primitives/src/bytecode/eof/header.rs b/crates/primitives/src/bytecode/eof/header.rs index bd7a33d7..1074f066 100644 --- a/crates/primitives/src/bytecode/eof/header.rs +++ b/crates/primitives/src/bytecode/eof/header.rs @@ -90,7 +90,10 @@ impl EofHeader { /// Returns body size. It is sum of code sizes, container sizes and data size. pub fn body_size(&self) -> usize { - self.sum_code_sizes + self.sum_container_sizes + self.data_size as usize + self.types_size as usize + + self.sum_code_sizes + + self.sum_container_sizes + + self.data_size as usize } /// Returns raw size of the EOF. diff --git a/crates/primitives/src/db.rs b/crates/primitives/src/db.rs index 3b1c6dc8..ca4ced98 100644 --- a/crates/primitives/src/db.rs +++ b/crates/primitives/src/db.rs @@ -103,43 +103,3 @@ impl DatabaseCommit for WrapDatabaseRef { self.0.commit(changes) } } - -/// Wraps a `dyn DatabaseRef` to provide a [`Database`] implementation. -#[doc(hidden)] -#[deprecated = "use `WrapDatabaseRef` instead"] -pub struct RefDBWrapper<'a, E> { - pub db: &'a dyn DatabaseRef, -} - -#[allow(deprecated)] -impl<'a, E> RefDBWrapper<'a, E> { - #[inline] - pub fn new(db: &'a dyn DatabaseRef) -> Self { - Self { db } - } -} - -#[allow(deprecated)] -impl<'a, E> Database for RefDBWrapper<'a, E> { - type Error = E; - - #[inline] - fn basic(&mut self, address: Address) -> Result, Self::Error> { - self.db.basic_ref(address) - } - - #[inline] - fn code_by_hash(&mut self, code_hash: B256) -> Result { - self.db.code_by_hash_ref(code_hash) - } - - #[inline] - fn storage(&mut self, address: Address, index: U256) -> Result { - self.db.storage_ref(address, index) - } - - #[inline] - fn block_hash(&mut self, number: U256) -> Result { - self.db.block_hash_ref(number) - } -} diff --git a/crates/primitives/src/kzg/env_settings.rs b/crates/primitives/src/kzg/env_settings.rs index 849ec3c4..d9b5f243 100644 --- a/crates/primitives/src/kzg/env_settings.rs +++ b/crates/primitives/src/kzg/env_settings.rs @@ -2,13 +2,12 @@ use super::{ trusted_setup_points::{G1_POINTS, G2_POINTS}, KzgSettings, }; -use core::hash::{Hash, Hasher}; use once_cell::race::OnceBox; use std::{boxed::Box, sync::Arc}; /// KZG Settings that allow us to specify a custom trusted setup. /// or use hardcoded default settings. -#[derive(Debug, Clone, Default, Eq)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] pub enum EnvKzgSettings { /// Default mainnet trusted setup #[default] @@ -17,27 +16,6 @@ pub enum EnvKzgSettings { Custom(Arc), } -// Implement PartialEq and Hash manually because `c_kzg::KzgSettings` does not implement them -impl PartialEq for EnvKzgSettings { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Default, Self::Default) => true, - (Self::Custom(a), Self::Custom(b)) => Arc::ptr_eq(a, b), - _ => false, - } - } -} - -impl Hash for EnvKzgSettings { - fn hash(&self, state: &mut H) { - core::mem::discriminant(self).hash(state); - match self { - Self::Default => {} - Self::Custom(settings) => Arc::as_ptr(settings).hash(state), - } - } -} - impl EnvKzgSettings { /// Return set KZG settings. /// diff --git a/crates/primitives/src/precompile.rs b/crates/primitives/src/precompile.rs index 903013ff..983879da 100644 --- a/crates/primitives/src/precompile.rs +++ b/crates/primitives/src/precompile.rs @@ -1,12 +1,28 @@ use crate::{Bytes, Env}; -use core::fmt; +use core::fmt::{self}; use dyn_clone::DynClone; use std::{boxed::Box, string::String, sync::Arc}; /// A precompile operation result. /// /// Returns either `Ok((gas_used, return_bytes))` or `Err(error)`. -pub type PrecompileResult = Result<(u64, Bytes), PrecompileError>; +pub type PrecompileResult = Result; + +/// Precompile execution output +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct PrecompileOutput { + /// Gas used by the precompile. + pub gas_used: u64, + /// Output bytes. + pub bytes: Bytes, +} + +impl PrecompileOutput { + /// Returns new precompile output with the given gas used and output bytes. + pub fn new(gas_used: u64, bytes: Bytes) -> Self { + Self { gas_used, bytes } + } +} pub type StandardPrecompileFn = fn(&Bytes, u64) -> PrecompileResult; pub type EnvPrecompileFn = fn(&Bytes, u64, env: &Env) -> PrecompileResult; @@ -94,11 +110,44 @@ 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(), + }), + } + } +} + +/// Precompile errors. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum PrecompileErrors { + Error(PrecompileError), + Fatal { msg: String }, +} + +#[cfg(feature = "std")] +impl std::error::Error for PrecompileErrors {} + +impl fmt::Display for PrecompileErrors { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Error(e) => e.fmt(f), + Self::Fatal { msg } => f.write_str(msg), } } } @@ -140,9 +189,26 @@ pub enum PrecompileError { } impl PrecompileError { + /// Returns an other error with the given message. pub fn other(err: impl Into) -> Self { Self::Other(err.into()) } + + /// Returns true if the error is out of gas. + pub fn is_oog(&self) -> bool { + matches!(self, Self::OutOfGas) + } + + /// Returns true if the error is reverted + pub fn is_reverted(&self) -> bool { + matches!(self, Self::Reverted(_)) + } +} + +impl From for PrecompileErrors { + fn from(err: PrecompileError) -> Self { + PrecompileErrors::Error(err) + } } #[cfg(feature = "std")] @@ -189,7 +255,7 @@ mod test { _gas_price: u64, _env: &Env, ) -> PrecompileResult { - PrecompileResult::Err(PrecompileError::OutOfGas) + Err(PrecompileError::OutOfGas.into()) } } diff --git a/crates/primitives/src/result.rs b/crates/primitives/src/result.rs index 438451e2..28f35ce8 100644 --- a/crates/primitives/src/result.rs +++ b/crates/primitives/src/result.rs @@ -148,6 +148,8 @@ pub enum EVMError { /// /// Useful for handler registers where custom logic would want to return their own custom error. Custom(String), + /// Precompile error. + Precompile(String), } #[cfg(feature = "std")] @@ -157,7 +159,7 @@ impl std::error::Error for EVMError Some(e), Self::Header(e) => Some(e), Self::Database(e) => Some(e), - Self::Custom(_) => None, + Self::Precompile(_) | Self::Custom(_) => None, } } } @@ -168,7 +170,7 @@ impl fmt::Display for EVMError { Self::Transaction(e) => write!(f, "transaction validation error: {e}"), Self::Header(e) => write!(f, "header validation error: {e}"), Self::Database(e) => write!(f, "database error: {e}"), - Self::Custom(e) => f.write_str(e), + Self::Precompile(e) | Self::Custom(e) => f.write_str(e), } } } @@ -388,6 +390,7 @@ pub enum SuccessReason { Stop, Return, SelfDestruct, + EofReturnContract, } /// Indicates that the EVM has experienced an exceptional halt. This causes execution to diff --git a/crates/primitives/src/state.rs b/crates/primitives/src/state.rs index 37315d3b..d1f6c9df 100644 --- a/crates/primitives/src/state.rs +++ b/crates/primitives/src/state.rs @@ -41,6 +41,8 @@ bitflags! { /// used only for pre spurious dragon hardforks where existing and empty were two separate states. /// it became same state after EIP-161: State trie clearing const LoadedAsNotExisting = 0b0001000; + /// used to mark account as cold + const Cold = 0b0010000; } } @@ -100,6 +102,21 @@ impl Account { self.status -= AccountStatus::Created; } + /// Mark account as cold. + pub fn mark_cold(&mut self) { + self.status |= AccountStatus::Cold; + } + + /// Mark account as warm and return true if it was previously cold. + pub fn mark_warm(&mut self) -> bool { + if self.status.contains(AccountStatus::Cold) { + self.status -= AccountStatus::Cold; + true + } else { + false + } + } + /// Is account loaded as not existing from database /// This is needed for pre spurious dragon hardforks where /// existing and empty were two separate states. @@ -143,6 +160,8 @@ pub struct EvmStorageSlot { pub original_value: U256, /// Present value of the storage slot. pub present_value: U256, + /// Represents if the storage slot is cold. + pub is_cold: bool, } impl EvmStorageSlot { @@ -151,6 +170,7 @@ impl EvmStorageSlot { Self { original_value: original, present_value: original, + is_cold: false, } } @@ -159,6 +179,7 @@ impl EvmStorageSlot { Self { original_value, present_value, + is_cold: false, } } /// Returns true if the present value differs from the original value @@ -175,6 +196,16 @@ impl EvmStorageSlot { pub fn present_value(&self) -> U256 { self.present_value } + + /// Marks the storage slot as cold. + pub fn mark_cold(&mut self) { + self.is_cold = true; + } + + /// Marks the storage slot as warm and returns a bool indicating if it was previously cold. + pub fn mark_warm(&mut self) -> bool { + core::mem::replace(&mut self.is_cold, false) + } } /// AccountInfo account information. @@ -343,4 +374,24 @@ mod tests { assert!(account.is_touched()); assert!(!account.is_selfdestructed()); } + + #[test] + fn account_is_cold() { + let mut account = Account::default(); + + // Account is not cold by default + assert!(!account.status.contains(crate::AccountStatus::Cold)); + + // When marking warm account as warm again, it should return false + assert!(!account.mark_warm()); + + // Mark account as cold + account.mark_cold(); + + // Account is cold + assert!(account.status.contains(crate::AccountStatus::Cold)); + + // When marking cold account as warm, it should return true + assert!(account.mark_warm()); + } } diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 55a048a3..21914f40 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -41,7 +41,7 @@ serde_json = { version = "1.0", default-features = false, features = [ ], optional = true } # ethersdb -tokio = { version = "1.37", features = [ +tokio = { version = "1.38", features = [ "rt-multi-thread", "macros", ], optional = true } @@ -49,9 +49,9 @@ ethers-providers = { version = "2.0", optional = true } ethers-core = { version = "2.0", optional = true } # alloydb -# alloy-provider = { git = "https://github.com/alloy-rs/alloy.git", rev = "44b8a6d", optional = true, default-features = false } -# alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy.git", rev = "44b8a6d", optional = true, default-features = false } -# alloy-transport = { git = "https://github.com/alloy-rs/alloy.git", rev = "44b8a6d", optional = true, default-features = false } +alloy-provider = { version = "0.1", optional = true, default-features = false } +alloy-eips = { version = "0.1", optional = true, default-features = false } +alloy-transport = { version = "0.1", optional = true, default-features = false } [dev-dependencies] alloy-sol-types = { version = "0.7.0", default-features = false, features = [ @@ -62,13 +62,9 @@ anyhow = "1.0.83" criterion = "0.5" indicatif = "0.17" reqwest = { version = "0.12" } -rstest = "0.19.0" +rstest = "0.21.0" -alloy-provider = { git = "https://github.com/alloy-rs/alloy.git", rev = "44b8a6d", default-features = false, features = [ - "reqwest", -] } -# needed for enabling TLS to use HTTPS connections when testing alloy DB -alloy-transport-http = { git = "https://github.com/alloy-rs/alloy.git", rev = "44b8a6d" } +alloy-provider = "0.1" [features] default = ["std", "c-kzg", "secp256k1", "portable", "blst"] @@ -101,21 +97,15 @@ negate-optimism-default-handler = [ "revm-interpreter/negate-optimism-default-handler", ] -ethersdb = [ +ethersdb = ["std", "dep:tokio", "dep:ethers-providers", "dep:ethers-core"] + +alloydb = [ "std", - "tokio", - "ethers-providers", - "ethers-core", -] # Negate optimism default handler - -# -# alloydb = [ -# "std", -# "tokio", -# "alloy-provider", -# "alloy-rpc-types", -# "alloy-transport", -# ] + "dep:tokio", + "dep:alloy-provider", + "dep:alloy-eips", + "dep:alloy-transport", +] dev = [ "memory_limit", diff --git a/crates/revm/src/builder.rs b/crates/revm/src/builder.rs index d15ed12c..2720bbc4 100644 --- a/crates/revm/src/builder.rs +++ b/crates/revm/src/builder.rs @@ -460,6 +460,7 @@ mod test { Context, ContextPrecompile, ContextStatefulPrecompile, Evm, InMemoryDB, InnerEvmContext, }; use revm_interpreter::{gas, Host, Interpreter}; + use revm_precompile::PrecompileOutput; use std::{cell::RefCell, rc::Rc, sync::Arc}; /// Custom evm context @@ -625,7 +626,7 @@ mod test { _gas_price: u64, _context: &mut InnerEvmContext, ) -> PrecompileResult { - Ok((10, Bytes::new())) + Ok(PrecompileOutput::new(10, Bytes::new())) } } diff --git a/crates/revm/src/context.rs b/crates/revm/src/context.rs index 6cdbdfe6..cc1fd20d 100644 --- a/crates/revm/src/context.rs +++ b/crates/revm/src/context.rs @@ -13,7 +13,7 @@ use revm_interpreter::as_usize_saturated; use crate::{ db::{Database, EmptyDB}, interpreter::{Host, LoadAccountResult, SStoreResult, SelfDestructResult}, - primitives::{Address, Bytecode, Env, HandlerCfg, Log, B256, BLOCK_HASH_HISTORY, U256}, + primitives::{Address, Bytes, Env, HandlerCfg, Log, B256, BLOCK_HASH_HISTORY, U256}, }; use std::boxed::Box; @@ -98,6 +98,8 @@ where } impl Host for Context { + /// Returns reference to Environment. + #[inline] fn env(&self) -> &Env { &self.evm.env } @@ -144,7 +146,7 @@ impl Host for Context { .ok() } - fn code(&mut self, address: Address) -> Option<(Bytecode, bool)> { + fn code(&mut self, address: Address) -> Option<(Bytes, bool)> { self.evm .code(address) .map_err(|e| self.evm.error = Err(e)) diff --git a/crates/revm/src/context/context_precompiles.rs b/crates/revm/src/context/context_precompiles.rs index a6fdc5e6..a47cef71 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 85a3d7e9..d8270c7e 100644 --- a/crates/revm/src/context/evm_context.rs +++ b/crates/revm/src/context/evm_context.rs @@ -1,4 +1,5 @@ use revm_interpreter::CallValue; +use revm_precompile::PrecompileErrors; use super::inner_evm_context::InnerEvmContext; use crate::{ @@ -6,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::{ @@ -95,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; } @@ -104,13 +104,16 @@ impl EvmContext { #[inline] fn call_precompile( &mut self, - address: Address, + address: &Address, input_data: &Bytes, gas: Gas, - ) -> Option { - let out = self - .precompiles - .call(address, input_data, gas.limit(), &mut self.inner)?; + ) -> Result, EVMError> { + let Some(outcome) = + self.precompiles + .call(address, input_data, gas.limit(), &mut self.inner) + else { + return Ok(None); + }; let mut result = InterpreterResult { result: InstructionResult::Return, @@ -118,31 +121,35 @@ impl EvmContext { output: Bytes::new(), }; - match out { - Ok((gas_used, data)) => { - if result.gas.record_cost(gas_used) { + match outcome { + Ok(output) => { + if result.gas.record_cost(output.gas_used) { result.result = InstructionResult::Return; - result.output = data; + result.output = output.bytes; } else { result.result = InstructionResult::PrecompileOOG; } } - Err(e) => { - result.result = match e { - crate::precompile::Error::OutOfGas => InstructionResult::PrecompileOOG, + Err(PrecompileErrors::Error(e)) => { + result.result = if e.is_oog() { + InstructionResult::PrecompileOOG + } else if e.is_reverted() { // for BSC compatibility - crate::precompile::Error::Reverted(gas_used) => { - if result.gas.record_cost(gas_used) { - InstructionResult::Revert - } else { - InstructionResult::PrecompileOOG - } + let crate::precompile::Error::Reverted(gas_used) = e else { + panic!("cannot happen") + }; + if result.gas.record_cost(gas_used) { + InstructionResult::Revert + } else { + InstructionResult::PrecompileOOG } - _ => InstructionResult::PrecompileError, + } else { + InstructionResult::PrecompileError }; } + Err(PrecompileErrors::Fatal { msg }) => return Err(EVMError::Precompile(msg)), } - Some(result) + Ok(Some(result)) } /// Make call frame @@ -201,7 +208,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 { @@ -234,7 +241,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. @@ -360,7 +367,7 @@ mod tests { result.interpreter_result().result, InstructionResult::OutOfFunds ); - let checkpointed = vec![vec![JournalEntry::AccountLoaded { address: contract }]]; + let checkpointed = vec![vec![JournalEntry::AccountWarmed { address: contract }]]; assert_eq!(evm_context.journaled_state.journal, checkpointed); assert_eq!(evm_context.journaled_state.depth, 0); } diff --git a/crates/revm/src/context/inner_evm_context.rs b/crates/revm/src/context/inner_evm_context.rs index 109b16a7..200345dc 100644 --- a/crates/revm/src/context/inner_evm_context.rs +++ b/crates/revm/src/context/inner_evm_context.rs @@ -1,7 +1,7 @@ use crate::{ db::Database, interpreter::{ - analysis::to_analysed, gas, return_ok, Contract, CreateInputs, EOFCreateInput, Gas, + analysis::to_analysed, gas, return_ok, Contract, CreateInputs, EOFCreateInputs, Gas, InstructionResult, Interpreter, InterpreterResult, LoadAccountResult, SStoreResult, SelfDestructResult, MAX_CODE_SIZE, }, @@ -10,11 +10,11 @@ use crate::{ keccak256, Account, Address, AnalysisKind, Bytecode, Bytes, CreateScheme, EVMError, Env, Eof, HashSet, Spec, SpecId::{self, *}, - B256, U256, + B256, EOF_MAGIC_BYTES, EOF_MAGIC_HASH, U256, }, FrameOrResult, JournalCheckpoint, CALL_STACK_LIMIT, }; -use std::boxed::Box; +use std::{boxed::Box, sync::Arc}; /// EVM contexts contains data that EVM needs for execution. #[derive(Debug)] @@ -160,21 +160,37 @@ impl InnerEvmContext { .map(|(acc, is_cold)| (acc.info.balance, is_cold)) } - /// Return account code and if address is cold loaded. + /// Return account code bytes and if address is cold loaded. + /// + /// In case of EOF account it will return `EOF_MAGIC` (0xEF00) as code. #[inline] - pub fn code(&mut self, address: Address) -> Result<(Bytecode, bool), EVMError> { + pub fn code(&mut self, address: Address) -> Result<(Bytes, bool), EVMError> { self.journaled_state .load_code(address, &mut self.db) - .map(|(a, is_cold)| (a.info.code.clone().unwrap(), is_cold)) + .map(|(a, is_cold)| { + // SAFETY: safe to unwrap as load_code will insert code if it is empty. + let code = a.info.code.as_ref().unwrap(); + if code.is_eof() { + (EOF_MAGIC_BYTES.clone(), is_cold) + } else { + (code.original_bytes().clone(), is_cold) + } + }) } /// Get code hash of address. + /// + /// In case of EOF account it will return `EOF_MAGIC_HASH` + /// (the hash of `0xEF00`). #[inline] pub fn code_hash(&mut self, address: Address) -> Result<(B256, bool), EVMError> { let (acc, is_cold) = self.journaled_state.load_code(address, &mut self.db)?; if acc.is_empty() { return Ok((B256::ZERO, is_cold)); } + if let Some(true) = acc.info.code.as_ref().map(|code| code.is_eof()) { + return Ok((EOF_MAGIC_HASH, is_cold)); + } Ok((acc.info.code_hash, is_cold)) } @@ -229,7 +245,7 @@ impl InnerEvmContext { pub fn make_eofcreate_frame( &mut self, spec_id: SpecId, - inputs: &EOFCreateInput, + inputs: &EOFCreateInputs, ) -> Result> { let return_error = |e| { Ok(FrameOrResult::new_eofcreate_result( @@ -239,7 +255,6 @@ impl InnerEvmContext { output: Bytes::new(), }, inputs.created_address, - inputs.return_memory_range.clone(), )) }; @@ -280,9 +295,9 @@ impl InnerEvmContext { }; let contract = Contract::new( - Bytes::new(), + inputs.input.clone(), // fine to clone as it is Bytes. - Bytecode::Eof(inputs.eof_init_code.clone()), + Bytecode::Eof(Arc::new(inputs.eof_init_code.clone())), None, inputs.created_address, inputs.caller, @@ -295,7 +310,6 @@ impl InnerEvmContext { Ok(FrameOrResult::new_eofcreate_frame( inputs.created_address, - inputs.return_memory_range.clone(), checkpoint, interpreter, )) @@ -329,7 +343,7 @@ impl InnerEvmContext { // eof bytecode is going to be hashed. self.journaled_state - .set_code(address, Bytecode::Eof(bytecode)); + .set_code(address, Bytecode::Eof(Arc::new(bytecode))); } /// Make create frame. diff --git a/crates/revm/src/db.rs b/crates/revm/src/db.rs index 03dd5ac0..f7c4904a 100644 --- a/crates/revm/src/db.rs +++ b/crates/revm/src/db.rs @@ -1,16 +1,16 @@ //! [Database] implementations. -//#[cfg(feature = "alloydb")] -//pub mod alloydb; +#[cfg(feature = "alloydb")] +mod alloydb; pub mod emptydb; #[cfg(feature = "ethersdb")] -pub mod ethersdb; +mod ethersdb; pub mod in_memory_db; pub mod states; pub use crate::primitives::db::*; -//#[cfg(feature = "alloydb")] -//pub use alloydb::AlloyDB; +#[cfg(feature = "alloydb")] +pub use alloydb::AlloyDB; pub use emptydb::{EmptyDB, EmptyDBTyped}; #[cfg(feature = "ethersdb")] pub use ethersdb::EthersDB; diff --git a/crates/revm/src/db/alloydb.rs b/crates/revm/src/db/alloydb.rs index 932fc32d..40945c68 100644 --- a/crates/revm/src/db/alloydb.rs +++ b/crates/revm/src/db/alloydb.rs @@ -2,9 +2,10 @@ use crate::{ db::{Database, DatabaseRef}, primitives::{AccountInfo, Address, Bytecode, B256, KECCAK_EMPTY, U256}, }; +use alloy_eips::BlockId; use alloy_provider::{Network, Provider}; -use alloy_rpc_types::BlockId; use alloy_transport::{Transport, TransportError}; +use std::future::IntoFuture; use tokio::runtime::{Builder, Handle}; /// An alloy-powered REVM [Database]. @@ -75,10 +76,21 @@ impl> DatabaseRef for AlloyD let f = async { let nonce = self .provider - .get_transaction_count(address, self.block_number); - let balance = self.provider.get_balance(address, self.block_number); - let code = self.provider.get_code_at(address, self.block_number); - tokio::join!(nonce, balance, code) + .get_transaction_count(address) + .block_id(self.block_number); + let balance = self + .provider + .get_balance(address) + .block_id(self.block_number); + let code = self + .provider + .get_code_at(address) + .block_id(self.block_number); + tokio::join!( + nonce.into_future(), + balance.into_future(), + code.into_future() + ) }; let (nonce, balance, code) = Self::block_on(f); @@ -112,11 +124,11 @@ impl> DatabaseRef for AlloyD } fn storage_ref(&self, address: Address, index: U256) -> Result { - let slot_val = Self::block_on(self.provider.get_storage_at( - address, - index, - self.block_number, - ))?; + let f = self + .provider + .get_storage_at(address, index) + .block_id(self.block_number); + let slot_val = Self::block_on(f.into_future())?; Ok(slot_val) } } @@ -147,11 +159,11 @@ impl> Database for AlloyDB EmptyDBTyped { _phantom: PhantomData, } } - - #[doc(hidden)] - #[deprecated = "use `new` instead"] - pub fn new_keccak_block_hash() -> Self { - Self::new() - } } impl Database for EmptyDBTyped { diff --git a/crates/revm/src/db/ethersdb.rs b/crates/revm/src/db/ethersdb.rs index d7863f10..cc318577 100644 --- a/crates/revm/src/db/ethersdb.rs +++ b/crates/revm/src/db/ethersdb.rs @@ -147,8 +147,9 @@ mod tests { use super::*; use ethers_providers::{Http, Provider}; - //#[test] - fn _can_get_basic() { + #[test] + #[ignore = "flaky RPC"] + fn can_get_basic() { let client = Provider::::try_from( "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27", ) diff --git a/crates/revm/src/db/states/account_status.rs b/crates/revm/src/db/states/account_status.rs index aaf0b5b4..5902ed98 100644 --- a/crates/revm/src/db/states/account_status.rs +++ b/crates/revm/src/db/states/account_status.rs @@ -2,6 +2,7 @@ /// while we execute multiple transaction and even blocks over account that is in memory. /// This structure models all possible states that account can be in. #[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum AccountStatus { #[default] LoadedNotExisting, diff --git a/crates/revm/src/db/states/bundle_account.rs b/crates/revm/src/db/states/bundle_account.rs index 9916139d..5abf397b 100644 --- a/crates/revm/src/db/states/bundle_account.rs +++ b/crates/revm/src/db/states/bundle_account.rs @@ -15,6 +15,7 @@ use revm_precompile::HashMap; /// /// On selfdestruct storage original value is ignored. #[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BundleAccount { pub info: Option, pub original_info: Option, diff --git a/crates/revm/src/db/states/bundle_state.rs b/crates/revm/src/db/states/bundle_state.rs index 8dd4edfe..22797c87 100644 --- a/crates/revm/src/db/states/bundle_state.rs +++ b/crates/revm/src/db/states/bundle_state.rs @@ -79,30 +79,44 @@ impl BundleBuilder { } } + /// Apply a transformation to the builder. + pub fn apply(self, f: F) -> Self + where + F: FnOnce(Self) -> Self, + { + f(self) + } + + /// Apply a mutable transformation to the builder. + pub fn apply_mut(&mut self, f: F) -> &mut Self + where + F: FnOnce(&mut Self), + { + f(self); + self + } + /// Collect address info of BundleState state pub fn state_address(mut self, address: Address) -> Self { - self.states.insert(address); + self.set_state_address(address); self } /// Collect account info of BundleState state pub fn state_original_account_info(mut self, address: Address, original: AccountInfo) -> Self { - self.states.insert(address); - self.state_original.insert(address, original); + self.set_state_original_account_info(address, original); self } /// Collect account info of BundleState state pub fn state_present_account_info(mut self, address: Address, present: AccountInfo) -> Self { - self.states.insert(address); - self.state_present.insert(address, present); + self.set_state_present_account_info(address, present); self } /// Collect storage info of BundleState state pub fn state_storage(mut self, address: Address, storage: HashMap) -> Self { - self.states.insert(address); - self.state_storage.insert(address, storage); + self.set_state_storage(address, storage); self } @@ -111,7 +125,7 @@ impl BundleBuilder { /// `block_number` must respect `revert_range`, or the input /// will be ignored during the final build process pub fn revert_address(mut self, block_number: u64, address: Address) -> Self { - self.reverts.insert((block_number, address)); + self.set_revert_address(block_number, address); self } @@ -125,8 +139,7 @@ impl BundleBuilder { address: Address, account: Option>, ) -> Self { - self.reverts.insert((block_number, address)); - self.revert_account.insert((block_number, address), account); + self.set_revert_account_info(block_number, address, account); self } @@ -140,13 +153,87 @@ impl BundleBuilder { address: Address, storage: Vec<(U256, U256)>, ) -> Self { - self.reverts.insert((block_number, address)); - self.revert_storage.insert((block_number, address), storage); + self.set_revert_storage(block_number, address, storage); self } /// Collect contracts info pub fn contract(mut self, address: B256, bytecode: Bytecode) -> Self { + self.set_contract(address, bytecode); + self + } + + /// Set address info of BundleState state. + pub fn set_state_address(&mut self, address: Address) -> &mut Self { + self.states.insert(address); + self + } + + /// Set original account info of BundleState state. + pub fn set_state_original_account_info( + &mut self, + address: Address, + original: AccountInfo, + ) -> &mut Self { + self.states.insert(address); + self.state_original.insert(address, original); + self + } + + /// Set present account info of BundleState state. + pub fn set_state_present_account_info( + &mut self, + address: Address, + present: AccountInfo, + ) -> &mut Self { + self.states.insert(address); + self.state_present.insert(address, present); + self + } + + /// Set storage info of BundleState state. + pub fn set_state_storage( + &mut self, + address: Address, + storage: HashMap, + ) -> &mut Self { + self.states.insert(address); + self.state_storage.insert(address, storage); + self + } + + /// Set address info of BundleState reverts. + pub fn set_revert_address(&mut self, block_number: u64, address: Address) -> &mut Self { + self.reverts.insert((block_number, address)); + self + } + + /// Set account info of BundleState reverts. + pub fn set_revert_account_info( + &mut self, + block_number: u64, + address: Address, + account: Option>, + ) -> &mut Self { + self.reverts.insert((block_number, address)); + self.revert_account.insert((block_number, address), account); + self + } + + /// Set storage info of BundleState reverts. + pub fn set_revert_storage( + &mut self, + block_number: u64, + address: Address, + storage: Vec<(U256, U256)>, + ) -> &mut Self { + self.reverts.insert((block_number, address)); + self.revert_storage.insert((block_number, address), storage); + self + } + + /// Set contracts info. + pub fn set_contract(&mut self, address: B256, bytecode: Bytecode) -> &mut Self { self.contracts.insert(address, bytecode); self } @@ -233,6 +320,53 @@ impl BundleBuilder { pub fn get_states(&self) -> &HashSet
{ &self.states } + + /// Mutable getter for `states` field + pub fn get_states_mut(&mut self) -> &mut HashSet
{ + &mut self.states + } + + /// Mutable getter for `state_original` field + pub fn get_state_original_mut(&mut self) -> &mut HashMap { + &mut self.state_original + } + + /// Mutable getter for `state_present` field + pub fn get_state_present_mut(&mut self) -> &mut HashMap { + &mut self.state_present + } + + /// Mutable getter for `state_storage` field + pub fn get_state_storage_mut(&mut self) -> &mut HashMap> { + &mut self.state_storage + } + + /// Mutable getter for `reverts` field + pub fn get_reverts_mut(&mut self) -> &mut BTreeSet<(u64, Address)> { + &mut self.reverts + } + + /// Mutable getter for `revert_range` field + pub fn get_revert_range_mut(&mut self) -> &mut RangeInclusive { + &mut self.revert_range + } + + /// Mutable getter for `revert_account` field + pub fn get_revert_account_mut( + &mut self, + ) -> &mut HashMap<(u64, Address), Option>> { + &mut self.revert_account + } + + /// Mutable getter for `revert_storage` field + pub fn get_revert_storage_mut(&mut self) -> &mut HashMap<(u64, Address), Vec<(U256, U256)>> { + &mut self.revert_storage + } + + /// Mutable getter for `contracts` field + pub fn get_contracts_mut(&mut self) -> &mut HashMap { + &mut self.contracts + } } /// Bundle retention policy for applying substate to the bundle. @@ -259,6 +393,7 @@ impl BundleRetention { /// Reverts and created when TransitionState is applied to BundleState. /// And can be used to revert BundleState to the state before transition. #[derive(Default, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BundleState { /// Account state. pub state: HashMap, @@ -1102,4 +1237,68 @@ mod tests { // account2 got inserted assert_eq!(test.state.get(&address2).unwrap().info, Some(account2)); } + + #[test] + fn test_getters() { + let mut builder = BundleBuilder::new(0..=3); + + // Test get_states and get_states_mut + assert!(builder.get_states().is_empty()); + builder.get_states_mut().insert(account1()); + assert!(builder.get_states().contains(&account1())); + + // Test get_state_original_mut + assert!(builder.get_state_original_mut().is_empty()); + builder + .get_state_original_mut() + .insert(account1(), AccountInfo::default()); + assert!(builder.get_state_original_mut().contains_key(&account1())); + + // Test get_state_present_mut + assert!(builder.get_state_present_mut().is_empty()); + builder + .get_state_present_mut() + .insert(account1(), AccountInfo::default()); + assert!(builder.get_state_present_mut().contains_key(&account1())); + + // Test get_state_storage_mut + assert!(builder.get_state_storage_mut().is_empty()); + builder + .get_state_storage_mut() + .insert(account1(), HashMap::new()); + assert!(builder.get_state_storage_mut().contains_key(&account1())); + + // Test get_reverts_mut + assert!(builder.get_reverts_mut().is_empty()); + builder.get_reverts_mut().insert((0, account1())); + assert!(builder.get_reverts_mut().contains(&(0, account1()))); + + // Test get_revert_range_mut + assert_eq!(builder.get_revert_range_mut().clone(), 0..=3); + + // Test get_revert_account_mut + assert!(builder.get_revert_account_mut().is_empty()); + builder + .get_revert_account_mut() + .insert((0, account1()), Some(None)); + assert!(builder + .get_revert_account_mut() + .contains_key(&(0, account1()))); + + // Test get_revert_storage_mut + assert!(builder.get_revert_storage_mut().is_empty()); + builder + .get_revert_storage_mut() + .insert((0, account1()), vec![(slot1(), U256::from(0))]); + assert!(builder + .get_revert_storage_mut() + .contains_key(&(0, account1()))); + + // Test get_contracts_mut + assert!(builder.get_contracts_mut().is_empty()); + builder + .get_contracts_mut() + .insert(B256::default(), Bytecode::default()); + assert!(builder.get_contracts_mut().contains_key(&B256::default())); + } } diff --git a/crates/revm/src/db/states/reverts.rs b/crates/revm/src/db/states/reverts.rs index 5be2c932..4d8d3f40 100644 --- a/crates/revm/src/db/states/reverts.rs +++ b/crates/revm/src/db/states/reverts.rs @@ -8,6 +8,7 @@ use std::vec::Vec; /// Contains reverts of multiple account in multiple transitions (Transitions as a block). #[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Reverts(Vec>); impl Deref for Reverts { @@ -80,6 +81,7 @@ impl Reverts { /// AccountRevert is structured in this way as we need to save it inside database. /// And we need to be able to read it from database. #[derive(Clone, Default, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct AccountRevert { pub account: AccountInfoRevert, pub storage: HashMap, @@ -182,6 +184,7 @@ impl AccountRevert { /// Depending on previous state of account info this /// will tell us what to do on revert. #[derive(Clone, Default, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum AccountInfoRevert { #[default] /// Nothing changed @@ -200,6 +203,7 @@ pub enum AccountInfoRevert { /// Note: It is completely different state if Storage is Zero or Some or if Storage was /// Destroyed. Because if it is destroyed, previous values can be found in database or it can be zero. #[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum RevertToSlot { Some(U256), Destroyed, diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 2fcf9324..d5b853d2 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -2,15 +2,17 @@ use crate::{ builder::{EvmBuilder, HandlerStage, SetGenericStage}, db::{Database, DatabaseCommit, EmptyDB}, handler::Handler, - interpreter::{Host, InterpreterAction, SharedMemory}, + interpreter::{ + analysis::validate_eof, CallInputs, CreateInputs, EOFCreateInputs, EOFCreateOutcome, Gas, + Host, InstructionResult, InterpreterAction, InterpreterResult, SharedMemory, + }, primitives::{ - specification::SpecId, BlockEnv, CfgEnv, EVMError, EVMResult, EnvWithHandlerCfg, + specification::SpecId, BlockEnv, Bytes, CfgEnv, EVMError, EVMResult, EnvWithHandlerCfg, ExecutionResult, HandlerCfg, ResultAndState, TransactTo, TxEnv, }, Context, ContextWithHandlerCfg, Frame, FrameOrResult, FrameResult, }; use core::fmt; -use revm_interpreter::{CallInputs, CreateInputs}; use std::vec::Vec; /// EVM call stack limit. @@ -132,7 +134,6 @@ impl<'a, EXT, DB: Database> Evm<'a, EXT, DB> { } InterpreterAction::None => unreachable!("InterpreterAction::None is not expected"), }; - // handle result match frame_or_result { FrameOrResult::Frame(frame) => { @@ -324,6 +325,7 @@ impl Evm<'_, EXT, DB> { /// Transact pre-verified transaction. fn transact_preverified_inner(&mut self, initial_gas_spend: u64) -> EVMResult { + let spec_id = self.spec_id(); let ctx = &mut self.context; let pre_exec = self.handler.pre_execution(); @@ -346,10 +348,61 @@ impl Evm<'_, EXT, DB> { ctx, CallInputs::new_boxed(&ctx.evm.env.tx, gas_limit).unwrap(), )?, - TransactTo::Create => exec.create( - ctx, - CreateInputs::new_boxed(&ctx.evm.env.tx, gas_limit).unwrap(), - )?, + TransactTo::Create => { + // if first byte of data is magic 0xEF00, then it is EOFCreate. + if spec_id.is_enabled_in(SpecId::PRAGUE) + && ctx + .env() + .tx + .data + .get(0..=1) + .filter(|&t| t == [0xEF, 00]) + .is_some() + { + // TODO Should we just check 0xEF it seems excessive to switch to legacy only + // if it 0xEF00? + + // get nonce from tx (if set) or from account (if not). + // Nonce for call is bumped in deduct_caller while + // for CREATE it is not (it is done inside exec handlers). + let nonce = ctx.evm.env.tx.nonce.unwrap_or_else(|| { + let caller = ctx.evm.env.tx.caller; + ctx.evm + .load_account(caller) + .map(|(a, _)| a.info.nonce) + .unwrap_or_default() + }); + + // Create EOFCreateInput from transaction initdata. + let eofcreate = EOFCreateInputs::new_tx_boxed(&ctx.evm.env.tx, nonce) + .ok() + .and_then(|eofcreate| { + // validate EOF initcode + validate_eof(&eofcreate.eof_init_code).ok()?; + Some(eofcreate) + }); + + if let Some(eofcreate) = eofcreate { + exec.eofcreate(ctx, eofcreate)? + } else { + // Return result, as code is invalid. + FrameOrResult::Result(FrameResult::EOFCreate(EOFCreateOutcome::new( + InterpreterResult::new( + InstructionResult::Stop, + Bytes::new(), + Gas::new(gas_limit), + ), + ctx.env().tx.caller.create(nonce), + ))) + } + } else { + // Safe to unwrap because we are sure that it is create tx. + exec.create( + ctx, + CreateInputs::new_boxed(&ctx.evm.env.tx, gas_limit).unwrap(), + )? + } + } }; // Starts the main running loop. diff --git a/crates/revm/src/frame.rs b/crates/revm/src/frame.rs index 72576505..c36ffe07 100644 --- a/crates/revm/src/frame.rs +++ b/crates/revm/src/frame.rs @@ -33,7 +33,6 @@ pub struct CreateFrame { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct EOFCreateFrame { pub created_address: Address, - pub return_memory_range: Range, pub frame_data: FrameData, } @@ -56,6 +55,7 @@ pub enum Frame { } #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug)] pub enum FrameResult { Call(CallOutcome), Create(CreateOutcome), @@ -81,8 +81,8 @@ impl FrameResult { FrameResult::Create(outcome) => { Output::Create(outcome.result.output.clone(), outcome.address) } - FrameResult::EOFCreate(_) => { - panic!("EOFCreate can't be called from external world."); + FrameResult::EOFCreate(outcome) => { + Output::Create(outcome.result.output.clone(), Some(outcome.address)) } } } @@ -136,6 +136,7 @@ impl FrameResult { /// Contains either a frame or a result. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug)] pub enum FrameOrResult { /// Boxed call or create frame. Frame(Frame), @@ -240,13 +241,11 @@ impl FrameOrResult { pub fn new_eofcreate_frame( created_address: Address, - return_memory_range: Range, checkpoint: JournalCheckpoint, interpreter: Interpreter, ) -> Self { Self::Frame(Frame::EOFCreate(Box::new(EOFCreateFrame { created_address, - return_memory_range, frame_data: FrameData { checkpoint, interpreter, @@ -278,15 +277,10 @@ impl FrameOrResult { })) } - pub fn new_eofcreate_result( - interpreter_result: InterpreterResult, - address: Address, - return_memory_range: Range, - ) -> Self { + pub fn new_eofcreate_result(interpreter_result: InterpreterResult, address: Address) -> Self { FrameOrResult::Result(FrameResult::EOFCreate(EOFCreateOutcome { result: interpreter_result, address, - return_memory_range, })) } diff --git a/crates/revm/src/handler/handle_types/execution.rs b/crates/revm/src/handler/handle_types/execution.rs index b69354a5..9e17def0 100644 --- a/crates/revm/src/handler/handle_types/execution.rs +++ b/crates/revm/src/handler/handle_types/execution.rs @@ -6,7 +6,7 @@ use crate::{ CallFrame, Context, CreateFrame, Frame, FrameOrResult, FrameResult, }; use revm_interpreter::{ - opcode::InstructionTables, CallOutcome, CreateOutcome, EOFCreateInput, EOFCreateOutcome, + opcode::InstructionTables, CallOutcome, CreateOutcome, EOFCreateInputs, EOFCreateOutcome, InterpreterAction, InterpreterResult, }; use std::{boxed::Box, sync::Arc}; @@ -91,7 +91,7 @@ pub type InsertCreateOutcomeHandle<'a, EXT, DB> = Arc< pub type FrameEOFCreateHandle<'a, EXT, DB> = Arc< dyn Fn( &mut Context, - Box, + Box, ) -> Result::Error>> + 'a, >; @@ -192,7 +192,7 @@ impl<'a, EXT, DB: Database> ExecutionHandler<'a, EXT, DB> { context: &mut Context, inputs: Box, ) -> Result> { - (self.call)(context, inputs.clone()) + (self.call)(context, inputs) } /// Call registered handler for call return. @@ -255,7 +255,7 @@ impl<'a, EXT, DB: Database> ExecutionHandler<'a, EXT, DB> { pub fn eofcreate( &self, context: &mut Context, - inputs: Box, + inputs: Box, ) -> Result> { (self.eofcreate)(context, inputs) } diff --git a/crates/revm/src/handler/mainnet/execution.rs b/crates/revm/src/handler/mainnet/execution.rs index 9e1ff3ef..4ee557f8 100644 --- a/crates/revm/src/handler/mainnet/execution.rs +++ b/crates/revm/src/handler/mainnet/execution.rs @@ -10,7 +10,7 @@ use crate::{ }; use core::mem; use revm_interpreter::{ - opcode::InstructionTables, CallOutcome, EOFCreateInput, EOFCreateOutcome, InterpreterAction, + opcode::InstructionTables, CallOutcome, EOFCreateInputs, EOFCreateOutcome, InterpreterAction, InterpreterResult, EMPTY_SHARED_MEMORY, }; use std::boxed::Box; @@ -164,7 +164,7 @@ pub fn insert_create_outcome( #[inline] pub fn eofcreate( context: &mut Context, - inputs: Box, + inputs: Box, ) -> Result> { context.evm.make_eofcreate_frame(SPEC::SPEC_ID, &inputs) } @@ -183,7 +183,6 @@ pub fn eofcreate_return( Ok(EOFCreateOutcome::new( interpreter_result, frame.created_address, - frame.return_memory_range, )) } diff --git a/crates/revm/src/handler/mainnet/pre_execution.rs b/crates/revm/src/handler/mainnet/pre_execution.rs index c0f4fe7b..d3b9759d 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/inspector.rs b/crates/revm/src/inspector.rs index 170a42cd..26551c8d 100644 --- a/crates/revm/src/inspector.rs +++ b/crates/revm/src/inspector.rs @@ -1,5 +1,5 @@ use crate::{ - interpreter::{CallInputs, CreateInputs, EOFCreateInput, EOFCreateOutcome, Interpreter}, + interpreter::{CallInputs, CreateInputs, EOFCreateInputs, EOFCreateOutcome, Interpreter}, primitives::{db::Database, Address, Log, U256}, EvmContext, }; @@ -15,7 +15,7 @@ mod noop; // Exports. -pub use handler_register::{inspector_handle_register, inspector_instruction, GetInspector}; +pub use handler_register::{inspector_handle_register, GetInspector}; use revm_interpreter::{CallOutcome, CreateOutcome}; /// [Inspector] implementations. @@ -135,20 +135,24 @@ pub trait Inspector { outcome } + /// Called when EOF creating is called. + /// + /// This can happen from create TX or from EOFCREATE opcode. fn eofcreate( &mut self, context: &mut EvmContext, - inputs: &mut EOFCreateInput, + inputs: &mut EOFCreateInputs, ) -> Option { let _ = context; let _ = inputs; None } + /// Called when eof creating has ended. fn eofcreate_end( &mut self, context: &mut EvmContext, - inputs: &EOFCreateInput, + inputs: &EOFCreateInputs, outcome: EOFCreateOutcome, ) -> EOFCreateOutcome { let _ = context; diff --git a/crates/revm/src/inspector/handler_register.rs b/crates/revm/src/inspector/handler_register.rs index 01bc11c5..0c500588 100644 --- a/crates/revm/src/inspector/handler_register.rs +++ b/crates/revm/src/inspector/handler_register.rs @@ -1,16 +1,13 @@ use crate::{ db::Database, handler::register::EvmHandler, - interpreter::{ - opcode::{self, BoxedInstruction}, - InstructionResult, Interpreter, - }, + interpreter::{opcode, InstructionResult, Interpreter}, primitives::EVMError, Context, FrameOrResult, FrameResult, Inspector, JournalEntry, }; use core::cell::RefCell; -use revm_interpreter::opcode::InstructionTables; -use std::{boxed::Box, rc::Rc, sync::Arc, vec::Vec}; +use revm_interpreter::opcode::DynInstruction; +use std::{rc::Rc, sync::Arc, vec::Vec}; /// Provides access to an `Inspector` instance. pub trait GetInspector { @@ -40,85 +37,56 @@ impl> GetInspector for INSP { pub fn inspector_handle_register>( handler: &mut EvmHandler<'_, EXT, DB>, ) { - // Every instruction inside flat table that is going to be wrapped by inspector calls. - let table = handler.take_instruction_table(); - let mut table = match table { - InstructionTables::Plain(table) => table - .into_iter() - .map(|i| inspector_instruction(i)) - .collect::>(), - InstructionTables::Boxed(table) => table - .into_iter() - .map(|i| inspector_instruction(i)) - .collect::>(), - }; - - // Register inspector Log instruction. - let mut inspect_log = |index: u8| { - if let Some(i) = table.get_mut(index as usize) { - let old = core::mem::replace(i, Box::new(|_, _| ())); - *i = Box::new( - move |interpreter: &mut Interpreter, host: &mut Context| { - let old_log_len = host.evm.journaled_state.logs.len(); - old(interpreter, host); - // check if log was added. It is possible that revert happened - // cause of gas or stack underflow. - if host.evm.journaled_state.logs.len() == old_log_len + 1 { - // clone log. - // TODO decide if we should remove this and leave the comment - // that log can be found as journaled_state. - let last_log = host.evm.journaled_state.logs.last().unwrap().clone(); - // call Inspector - host.external.get_inspector().log(&mut host.evm, &last_log); - } - }, - ) - } - }; - - inspect_log(opcode::LOG0); - inspect_log(opcode::LOG1); - inspect_log(opcode::LOG2); - inspect_log(opcode::LOG3); - inspect_log(opcode::LOG4); - - // // register selfdestruct function. - if let Some(i) = table.get_mut(opcode::SELFDESTRUCT as usize) { - let old = core::mem::replace(i, Box::new(|_, _| ())); - *i = Box::new( - move |interpreter: &mut Interpreter, host: &mut Context| { - // execute selfdestruct - old(interpreter, host); - // check if selfdestruct was successful and if journal entry is made. - if let Some(JournalEntry::AccountDestroyed { - address, - target, - had_balance, - .. - }) = host.evm.journaled_state.journal.last().unwrap().last() - { - host.external - .get_inspector() - .selfdestruct(*address, *target, *had_balance); - } - }, - ) + let table = &mut handler.instruction_table; + + // Update all instructions to call inspector step and step_end. + table.update_all(inspector_instruction); + + // Register inspector LOG* instructions. + for opcode in opcode::LOG0..=opcode::LOG4 { + table.update_boxed(opcode, move |prev, interpreter, host| { + let prev_log_len = host.evm.journaled_state.logs.len(); + prev(interpreter, host); + // check if log was added. It is possible that revert happened + // cause of gas or stack underflow. + if host.evm.journaled_state.logs.len() == prev_log_len + 1 { + // clone log. + // TODO decide if we should remove this and leave the comment + // that log can be found as journaled_state. + let last_log = host.evm.journaled_state.logs.last().unwrap().clone(); + // call Inspector + host.external.get_inspector().log(&mut host.evm, &last_log); + } + }); } - // cast vector to array. - handler.set_instruction_table(InstructionTables::Boxed( - table.try_into().unwrap_or_else(|_| unreachable!()), - )); + // Register selfdestruct function. + table.update_boxed(opcode::SELFDESTRUCT, |prev, interpreter, host| { + // execute selfdestruct + prev(interpreter, host); + // check if selfdestruct was successful and if journal entry is made. + if let Some(JournalEntry::AccountDestroyed { + address, + target, + had_balance, + .. + }) = host.evm.journaled_state.journal.last().unwrap().last() + { + host.external + .get_inspector() + .selfdestruct(*address, *target, *had_balance); + } + }); // call and create input stack shared between handlers. They are used to share // inputs in *_end Inspector calls. - let call_input_stack = Rc::>>::new(RefCell::new(Vec::new())); - let create_input_stack = Rc::>>::new(RefCell::new(Vec::new())); - let eofcreate_input_stack = Rc::>>::new(RefCell::new(Vec::new())); + let call_input_stack = Rc::>>::default(); + let create_input_stack = Rc::>>::default(); + let eofcreate_input_stack = Rc::>>::default(); // Create handler let create_input_stack_inner = create_input_stack.clone(); - let old_handle = handler.execution.create.clone(); + let prev_handle = handler.execution.create.clone(); handler.execution.create = Arc::new( move |ctx, mut inputs| -> Result> { let inspector = ctx.external.get_inspector(); @@ -129,7 +97,7 @@ pub fn inspector_handle_register>( } create_input_stack_inner.borrow_mut().push(inputs.clone()); - let mut frame_or_result = old_handle(ctx, inputs); + let mut frame_or_result = prev_handle(ctx, inputs); if let Ok(FrameOrResult::Frame(frame)) = &mut frame_or_result { ctx.external .get_inspector() @@ -141,31 +109,67 @@ pub fn inspector_handle_register>( // Call handler let call_input_stack_inner = call_input_stack.clone(); - let old_handle = handler.execution.call.clone(); - handler.execution.call = Arc::new( - move |ctx, mut inputs| -> Result> { - // Call inspector to change input or return outcome. - let outcome = ctx.external.get_inspector().call(&mut ctx.evm, &mut inputs); - call_input_stack_inner.borrow_mut().push(inputs.clone()); - if let Some(outcome) = outcome { - return Ok(FrameOrResult::Result(FrameResult::Call(outcome))); - } + let prev_handle = handler.execution.call.clone(); + handler.execution.call = Arc::new(move |ctx, mut inputs| { + // Call inspector to change input or return outcome. + let outcome = ctx.external.get_inspector().call(&mut ctx.evm, &mut inputs); + call_input_stack_inner.borrow_mut().push(inputs.clone()); + if let Some(outcome) = outcome { + return Ok(FrameOrResult::Result(FrameResult::Call(outcome))); + } - let mut frame_or_result = old_handle(ctx, inputs); - if let Ok(FrameOrResult::Frame(frame)) = &mut frame_or_result { - ctx.external - .get_inspector() - .initialize_interp(frame.interpreter_mut(), &mut ctx.evm) - } - frame_or_result - }, - ); + let mut frame_or_result = prev_handle(ctx, inputs); + if let Ok(FrameOrResult::Frame(frame)) = &mut frame_or_result { + ctx.external + .get_inspector() + .initialize_interp(frame.interpreter_mut(), &mut ctx.evm) + } + frame_or_result + }); - // TODO(EOF) EOF create call. + // Calls inspector `eofcreate` and `initialize_interp` functions. Queues the inputs for the `eofcreate_end`` function. + // Calls the old handler, and in case of inspector returning outcome, + // returns the outcome without executing eofcreate. + let eofcreate_input_stack_inner = eofcreate_input_stack.clone(); + let prev_handle = handler.execution.eofcreate.clone(); + handler.execution.eofcreate = Arc::new(move |ctx, mut inputs| { + // Call inspector to change input or return outcome. + let outcome = ctx + .external + .get_inspector() + .eofcreate(&mut ctx.evm, &mut inputs); + eofcreate_input_stack_inner + .borrow_mut() + .push(inputs.clone()); + if let Some(outcome) = outcome { + return Ok(FrameOrResult::Result(FrameResult::EOFCreate(outcome))); + } + + let mut frame_or_result = prev_handle(ctx, inputs); + if let Ok(FrameOrResult::Frame(frame)) = &mut frame_or_result { + ctx.external + .get_inspector() + .initialize_interp(frame.interpreter_mut(), &mut ctx.evm) + } + frame_or_result + }); + + // Pops eofcreate input from the stack and calls inspector `eofcreate_end` function. + // preserve the old handler and calls it with the outcome. + let eofcreate_input_stack_inner = eofcreate_input_stack.clone(); + let prev_handle = handler.execution.insert_eofcreate_outcome.clone(); + handler.execution.insert_eofcreate_outcome = Arc::new(move |ctx, frame, mut outcome| { + let create_inputs = eofcreate_input_stack_inner.borrow_mut().pop().unwrap(); + outcome = ctx + .external + .get_inspector() + .eofcreate_end(&mut ctx.evm, &create_inputs, outcome); + prev_handle(ctx, frame, outcome) + }); // call outcome let call_input_stack_inner = call_input_stack.clone(); - let old_handle = handler.execution.insert_call_outcome.clone(); + let prev_handle = handler.execution.insert_call_outcome.clone(); handler.execution.insert_call_outcome = Arc::new(move |ctx, frame, shared_memory, mut outcome| { let call_inputs = call_input_stack_inner.borrow_mut().pop().unwrap(); @@ -173,25 +177,23 @@ pub fn inspector_handle_register>( .external .get_inspector() .call_end(&mut ctx.evm, &call_inputs, outcome); - old_handle(ctx, frame, shared_memory, outcome) + prev_handle(ctx, frame, shared_memory, outcome) }); // create outcome let create_input_stack_inner = create_input_stack.clone(); - let old_handle = handler.execution.insert_create_outcome.clone(); + let prev_handle = handler.execution.insert_create_outcome.clone(); handler.execution.insert_create_outcome = Arc::new(move |ctx, frame, mut outcome| { let create_inputs = create_input_stack_inner.borrow_mut().pop().unwrap(); outcome = ctx .external .get_inspector() .create_end(&mut ctx.evm, &create_inputs, outcome); - old_handle(ctx, frame, outcome) + prev_handle(ctx, frame, outcome) }); - // TODO(EOF) EOF create handle. - // last frame outcome - let old_handle = handler.execution.last_frame_return.clone(); + let prev_handle = handler.execution.last_frame_return.clone(); handler.execution.last_frame_return = Arc::new(move |ctx, frame_result| { let inspector = ctx.external.get_inspector(); match frame_result { @@ -209,68 +211,51 @@ pub fn inspector_handle_register>( inspector.eofcreate_end(&mut ctx.evm, &eofcreate_inputs, outcome.clone()); } } - old_handle(ctx, frame_result) + prev_handle(ctx, frame_result) }); } -/// Outer closure that calls Inspector for every instruction. -pub fn inspector_instruction< - 'a, +fn inspector_instruction( + prev: &DynInstruction<'_, Context>, + interpreter: &mut Interpreter, + host: &mut Context, +) where INSP: GetInspector, DB: Database, - Instruction: Fn(&mut Interpreter, &mut Context) + 'a, ->( - instruction: Instruction, -) -> BoxedInstruction<'a, Context> { - Box::new( - move |interpreter: &mut Interpreter, host: &mut Context| { - // SAFETY: as the PC was already incremented we need to subtract 1 to preserve the - // old Inspector behavior. - interpreter.instruction_pointer = unsafe { interpreter.instruction_pointer.sub(1) }; - - host.external - .get_inspector() - .step(interpreter, &mut host.evm); - if interpreter.instruction_result != InstructionResult::Continue { - return; - } +{ + // SAFETY: as the PC was already incremented we need to subtract 1 to preserve the + // old Inspector behavior. + interpreter.instruction_pointer = unsafe { interpreter.instruction_pointer.sub(1) }; + + // Call step. + host.external + .get_inspector() + .step(interpreter, &mut host.evm); + if interpreter.instruction_result != InstructionResult::Continue { + return; + } - // return PC to old value - interpreter.instruction_pointer = unsafe { interpreter.instruction_pointer.add(1) }; + // Reset PC to previous value. + interpreter.instruction_pointer = unsafe { interpreter.instruction_pointer.add(1) }; - // execute instruction. - instruction(interpreter, host); + // Execute instruction. + prev(interpreter, host); - host.external - .get_inspector() - .step_end(interpreter, &mut host.evm); - }, - ) + // Call step_end. + host.external + .get_inspector() + .step_end(interpreter, &mut host.evm); } #[cfg(test)] mod tests { use super::*; use crate::{ - db::EmptyDB, inspectors::NoOpInspector, - interpreter::{opcode::*, CallInputs, CallOutcome, CreateInputs, CreateOutcome}, - primitives::BerlinSpec, + interpreter::{CallInputs, CallOutcome, CreateInputs, CreateOutcome}, Evm, EvmContext, }; - // Test that this pattern builds. - #[test] - fn test_make_boxed_instruction_table() { - type MyContext = Context; - let table: InstructionTable = make_instruction_table::(); - let _boxed_table: BoxedInstructionTable<'_, MyContext> = - make_boxed_instruction_table::<'_, MyContext, BerlinSpec, _>( - table, - inspector_instruction, - ); - } - #[derive(Default, Debug)] struct StackInspector { initialize_interp_called: bool, diff --git a/crates/revm/src/journaled_state.rs b/crates/revm/src/journaled_state.rs index 51b27b32..6ed29e8b 100644 --- a/crates/revm/src/journaled_state.rs +++ b/crates/revm/src/journaled_state.rs @@ -270,15 +270,6 @@ impl JournaledState { last_journal.push(JournalEntry::AccountCreated { address }); account.info.code = None; - // Set all storages to default value. They need to be present to act as accessed slots in access list. - // it shouldn't be possible for them to have different values then zero as code is not existing for this account, - // but because tests can change that assumption we are doing it. - let empty = EvmStorageSlot::default(); - account - .storage - .iter_mut() - .for_each(|(_, slot)| *slot = empty.clone()); - // touch account. This is important as for pre SpuriousDragon account could be // saved even empty. Self::touch_account(last_journal, &address, account); @@ -321,8 +312,8 @@ impl JournaledState { ) { for entry in journal_entries.into_iter().rev() { match entry { - JournalEntry::AccountLoaded { address } => { - state.remove(&address); + JournalEntry::AccountWarmed { address } => { + state.get_mut(&address).unwrap().mark_cold(); } JournalEntry::AccountTouched { address } => { if is_spurious_dragon_enabled && address == PRECOMPILE3 { @@ -367,19 +358,33 @@ impl JournaledState { JournalEntry::AccountCreated { address } => { let account = &mut state.get_mut(&address).unwrap(); account.unmark_created(); + account + .storage + .values_mut() + .for_each(|slot| slot.mark_cold()); account.info.nonce = 0; } - JournalEntry::StorageChange { + JournalEntry::StorageWarmed { address, key } => { + state + .get_mut(&address) + .unwrap() + .storage + .get_mut(&key) + .unwrap() + .mark_cold(); + } + JournalEntry::StorageChanged { address, key, had_value, } => { - let storage = &mut state.get_mut(&address).unwrap().storage; - if let Some(had_value) = had_value { - storage.get_mut(&key).unwrap().present_value = had_value; - } else { - storage.remove(&key); - } + state + .get_mut(&address) + .unwrap() + .storage + .get_mut(&key) + .unwrap() + .present_value = had_value; } JournalEntry::TransientStorageChange { address, @@ -555,8 +560,12 @@ impl JournaledState { address: Address, db: &mut DB, ) -> Result<(&mut Account, bool), EVMError> { - Ok(match self.state.entry(address) { - Entry::Occupied(entry) => (entry.into_mut(), false), + let (value, is_cold) = match self.state.entry(address) { + Entry::Occupied(entry) => { + let account = entry.into_mut(); + let is_cold = account.mark_warm(); + (account, is_cold) + } Entry::Vacant(vac) => { let account = if let Some(account) = db.basic(address).map_err(EVMError::Database)? { @@ -565,18 +574,22 @@ impl JournaledState { Account::new_not_existing() }; - // journal loading of account. AccessList touch. - self.journal - .last_mut() - .unwrap() - .push(JournalEntry::AccountLoaded { address }); - // precompiles are warm loaded so we need to take that into account let is_cold = !self.warm_preloaded_addresses.contains(&address); (vac.insert(account), is_cold) } - }) + }; + + // journal loading of cold account. + if is_cold { + self.journal + .last_mut() + .unwrap() + .push(JournalEntry::AccountWarmed { address }); + } + + Ok((value, is_cold)) } /// Load account from database to JournaledState. @@ -641,8 +654,12 @@ impl JournaledState { let account = self.state.get_mut(&address).unwrap(); // only if account is created in this tx we can assume that storage is empty. let is_newly_created = account.is_created(); - let load = match account.storage.entry(key) { - Entry::Occupied(occ) => (occ.get().present_value, false), + let (value, is_cold) = match account.storage.entry(key) { + Entry::Occupied(occ) => { + let slot = occ.into_mut(); + let is_cold = slot.mark_warm(); + (slot.present_value, is_cold) + } Entry::Vacant(vac) => { // if storage was cleared, we don't need to ping db. let value = if is_newly_created { @@ -650,22 +667,22 @@ impl JournaledState { } else { db.storage(address, key).map_err(EVMError::Database)? }; - // add it to journal as cold loaded. - self.journal - .last_mut() - .unwrap() - .push(JournalEntry::StorageChange { - address, - key, - had_value: None, - }); vac.insert(EvmStorageSlot::new(value)); (value, true) } }; - Ok(load) + + if is_cold { + // add it to journal as cold loaded. + self.journal + .last_mut() + .unwrap() + .push(JournalEntry::StorageWarmed { address, key }); + } + + Ok((value, is_cold)) } /// Stores storage slot. @@ -702,10 +719,10 @@ impl JournaledState { self.journal .last_mut() .unwrap() - .push(JournalEntry::StorageChange { + .push(JournalEntry::StorageChanged { address, key, - had_value: Some(present), + had_value: present, }); // insert value into present state. slot.present_value = new; @@ -784,7 +801,7 @@ pub enum JournalEntry { /// Used to mark account that is warm inside EVM in regards to EIP-2929 AccessList. /// Action: We will add Account to state. /// Revert: we will remove account from state. - AccountLoaded { address: Address }, + AccountWarmed { address: Address }, /// Mark account to be destroyed and journal balance to be reverted /// Action: Mark account and transfer the balance /// Revert: Unmark the account and transfer balance back @@ -817,15 +834,18 @@ pub enum JournalEntry { /// Actions: Mark account as created /// Revert: Unmart account as created and reset nonce to zero. AccountCreated { address: Address }, - /// It is used to track both storage change and warm load of storage slot. For warm load in regard - /// to EIP-2929 AccessList had_value will be None - /// Action: Storage change or warm load - /// Revert: Revert to previous value or remove slot from storage - StorageChange { + /// Entry used to track storage changes + /// Action: Storage change + /// Revert: Revert to previous value + StorageChanged { address: Address, key: U256, - had_value: Option, //if none, storage slot was cold loaded from db and needs to be removed + had_value: U256, }, + /// Entry used to track storage warming introduced by EIP-2929. + /// Action: Storage warmed + /// Revert: Revert to cold state + StorageWarmed { address: Address, key: U256 }, /// It is used to track an EIP-1153 transient storage change. /// Action: Transient storage changed. /// Revert: Revert to previous value. diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index b63c311f..2a165063 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -40,9 +40,7 @@ pub use db::{Database, DatabaseCommit, DatabaseRef, InMemoryDB}; pub use evm::{Evm, CALL_STACK_LIMIT}; pub use frame::{CallFrame, CreateFrame, Frame, FrameData, FrameOrResult, FrameResult}; pub use handler::Handler; -pub use inspector::{ - inspector_handle_register, inspector_instruction, inspectors, GetInspector, Inspector, -}; +pub use inspector::{inspector_handle_register, inspectors, GetInspector, Inspector}; pub use journaled_state::{JournalCheckpoint, JournalEntry, JournaledState}; // export Optimism types, helpers, and constants #[cfg(feature = "optimism")] diff --git a/crates/revm/src/optimism/handler_register.rs b/crates/revm/src/optimism/handler_register.rs index d4996cca..899ba412 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