diff --git a/Cargo.lock b/Cargo.lock index ec7ba04563..344bba0a49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2394,8 +2394,10 @@ dependencies = [ "alloy-rlp", "hash-db", "hashbrown", + "hex", "indicatif", "k256", + "microbench", "plain_hasher", "revm", "serde", diff --git a/bins/revme/Cargo.toml b/bins/revme/Cargo.toml index 59118f5fae..6792e8c30a 100644 --- a/bins/revme/Cargo.toml +++ b/bins/revme/Cargo.toml @@ -10,8 +10,10 @@ version = "0.2.2" [dependencies] hash-db = "0.15" +hex = "0.4" hashbrown = "0.14" indicatif = "0.17" +microbench = "0.5" plain_hasher = "0.2" revm = { path = "../../crates/revm", version = "6.1.0", default-features = false, features = [ "ethersdb", diff --git a/bins/revme/src/cmd.rs b/bins/revme/src/cmd.rs index 821e39ee71..3734b69bd9 100644 --- a/bins/revme/src/cmd.rs +++ b/bins/revme/src/cmd.rs @@ -1,3 +1,4 @@ +pub mod evmrunner; pub mod format_kzg_setup; pub mod statetest; @@ -13,6 +14,10 @@ pub enum MainCmd { about = "Format kzg settings from a trusted setup file (.txt) into binary format (.bin)" )] FormatKzgSetup(format_kzg_setup::Cmd), + #[structopt( + about = "Evm runner command allows running arbitrary evm bytecode.\nBytecode can be provided from cli or from file with --path option." + )] + Evm(evmrunner::Cmd), } #[derive(Debug, thiserror::Error)] @@ -21,6 +26,8 @@ pub enum Error { Statetest(#[from] statetest::Error), #[error(transparent)] KzgErrors(#[from] format_kzg_setup::KzgErrors), + #[error(transparent)] + EvmRunnerErrors(#[from] evmrunner::Errors), } impl MainCmd { @@ -28,6 +35,7 @@ impl MainCmd { match self { Self::Statetest(cmd) => cmd.run().map_err(Into::into), Self::FormatKzgSetup(cmd) => cmd.run().map_err(Into::into), + Self::Evm(cmd) => cmd.run().map_err(Into::into), } } } diff --git a/bins/revme/src/cmd/evmrunner.rs b/bins/revme/src/cmd/evmrunner.rs new file mode 100644 index 0000000000..1414a97054 --- /dev/null +++ b/bins/revme/src/cmd/evmrunner.rs @@ -0,0 +1,105 @@ +use revm::{ + db::BenchmarkDB, + primitives::{Address, Bytecode, TransactTo}, + Evm, +}; +use std::io::Error as IoError; +use std::path::PathBuf; +use std::time::Duration; +use std::{borrow::Cow, fs}; +use structopt::StructOpt; + +extern crate alloc; + +#[derive(Debug, thiserror::Error)] +pub enum Errors { + #[error("The specified path does not exist")] + PathNotExists, + #[error("Invalid bytecode")] + InvalidBytecode, + #[error("Invalid input")] + InvalidInput, + #[error("EVM Error")] + EVMError, + #[error(transparent)] + Io(IoError), +} + +impl From for Errors { + fn from(e: IoError) -> Self { + Errors::Io(e) + } +} + +/// Evm runner command allows running arbitrary evm bytecode. +/// Bytecode can be provided from cli or from file with --path option. +#[derive(StructOpt, Debug)] +pub struct Cmd { + /// Bytecode to be executed. + #[structopt(default_value = "")] + bytecode: String, + /// Path to file containing the evm bytecode. + /// Overrides the bytecode option. + #[structopt(long)] + path: Option, + /// Run in benchmarking mode. + #[structopt(long)] + bench: bool, + /// Input bytes. + #[structopt(long, default_value = "")] + input: String, + /// Print the state. + #[structopt(long)] + state: bool, +} + +impl Cmd { + /// Run statetest command. + pub fn run(&self) -> Result<(), Errors> { + let bytecode_str: Cow<'_, str> = if let Some(path) = &self.path { + // check if path exists. + if !path.exists() { + return Err(Errors::PathNotExists); + } + fs::read_to_string(path)?.to_owned().into() + } else { + self.bytecode.as_str().into() + }; + + let bytecode = hex::decode(bytecode_str.trim()).map_err(|_| Errors::InvalidBytecode)?; + let input = hex::decode(self.input.trim()) + .map_err(|_| Errors::InvalidInput)? + .into(); + // BenchmarkDB is dummy state that implements Database trait. + // the bytecode is deployed at zero address. + let mut evm = Evm::builder() + .with_db(BenchmarkDB::new_bytecode(Bytecode::new_raw( + bytecode.into(), + ))) + .modify_tx_env(|tx| { + // execution globals block hash/gas_limit/coinbase/timestamp.. + tx.caller = "0x0000000000000000000000000000000000000001" + .parse() + .unwrap(); + tx.transact_to = TransactTo::Call(Address::ZERO); + tx.data = input; + }) + .build(); + + if self.bench { + // Microbenchmark + let bench_options = microbench::Options::default().time(Duration::from_secs(3)); + + microbench::bench(&bench_options, "Run bytecode", || { + let _ = evm.transact().unwrap(); + }); + } else { + let out = evm.transact().map_err(|_| Errors::EVMError)?; + println!("Result: {:#?}", out.result); + if self.state { + println!("State: {:#?}", out.state); + } + } + Ok(()) + } +}