revme/cmd/
evmrunner.rs

1use clap::Parser;
2use database::BenchmarkDB;
3use inspector::{inspectors::TracerEip3155, InspectEvm};
4use revm::{
5    bytecode::{Bytecode, BytecodeDecodeError},
6    primitives::{address, hex, Address, TxKind},
7    Context, Database, ExecuteEvm, MainBuilder, MainContext,
8};
9use std::io::Error as IoError;
10use std::path::PathBuf;
11use std::time::Duration;
12use std::{borrow::Cow, fs};
13
14#[derive(Debug, thiserror::Error)]
15pub enum Errors {
16    #[error("The specified path does not exist")]
17    PathNotExists,
18    #[error("Invalid bytecode")]
19    InvalidBytecode,
20    #[error("Invalid input")]
21    InvalidInput,
22    #[error("EVM Error")]
23    EVMError,
24    #[error(transparent)]
25    Io(#[from] IoError),
26    #[error(transparent)]
27    BytecodeDecodeError(#[from] BytecodeDecodeError),
28}
29
30/// Evm runner command allows running arbitrary evm bytecode
31///
32/// Bytecode can be provided from cli or from file with `--path` option.
33#[derive(Parser, Debug)]
34pub struct Cmd {
35    /// Hex-encoded EVM bytecode to be executed
36    #[arg(required_unless_present = "path")]
37    bytecode: Option<String>,
38    /// Path to a file containing the hex-encoded EVM bytecode to be executed
39    ///
40    /// Overrides the positional `bytecode` argument.
41    #[arg(long)]
42    path: Option<PathBuf>,
43    /// Whether to run in benchmarking mode
44    #[arg(long)]
45    bench: bool,
46    /// Hex-encoded input/calldata bytes
47    #[arg(long, default_value = "")]
48    input: String,
49    /// Whether to print the state
50    #[arg(long)]
51    state: bool,
52    /// Whether to print the trace
53    #[arg(long)]
54    trace: bool,
55}
56
57impl Cmd {
58    /// Runs evm runner command.
59    pub fn run(&self) -> Result<(), Errors> {
60        const CALLER: Address = address!("0000000000000000000000000000000000000001");
61
62        let bytecode_str: Cow<'_, str> = if let Some(path) = &self.path {
63            // Check if path exists.
64            if !path.exists() {
65                return Err(Errors::PathNotExists);
66            }
67            fs::read_to_string(path)?.into()
68        } else if let Some(bytecode) = &self.bytecode {
69            bytecode.as_str().into()
70        } else {
71            unreachable!()
72        };
73
74        let bytecode = hex::decode(bytecode_str.trim()).map_err(|_| Errors::InvalidBytecode)?;
75        let input = hex::decode(self.input.trim())
76            .map_err(|_| Errors::InvalidInput)?
77            .into();
78
79        let mut db = BenchmarkDB::new_bytecode(Bytecode::new_raw_checked(bytecode.into())?);
80
81        let nonce = db.basic(CALLER).unwrap().map_or(0, |account| account.nonce);
82
83        // BenchmarkDB is dummy state that implements Database trait.
84        // The bytecode is deployed at zero address.
85        let mut evm = Context::mainnet()
86            .with_db(db)
87            .modify_tx_chained(|tx| {
88                tx.caller = CALLER;
89                tx.kind = TxKind::Call(Address::ZERO);
90                tx.data = input;
91                tx.nonce = nonce;
92            })
93            .build_mainnet_with_inspector(TracerEip3155::new(Box::new(std::io::stdout())));
94
95        if self.bench {
96            // Microbenchmark
97            let bench_options = microbench::Options::default().time(Duration::from_secs(3));
98
99            microbench::bench(&bench_options, "Run bytecode", || {
100                let _ = evm.transact_previous().unwrap();
101            });
102
103            return Ok(());
104        }
105
106        let out = if self.trace {
107            evm.inspect_previous().map_err(|_| Errors::EVMError)?
108        } else {
109            let out = evm.transact_previous().map_err(|_| Errors::EVMError)?;
110            println!("Result: {:#?}", out.result);
111            out
112        };
113
114        if self.state {
115            println!("State: {:#?}", out.state);
116        }
117
118        Ok(())
119    }
120}