revme/cmd/
evmrunner.rs

1use clap::Parser;
2use context::TxEnv;
3use database::{BenchmarkDB, BENCH_CALLER, BENCH_TARGET};
4use inspector::{inspectors::TracerEip3155, InspectEvm};
5use revm::{
6    bytecode::{Bytecode, BytecodeDecodeError},
7    primitives::{hex, TxKind},
8    Context, Database, ExecuteEvm, MainBuilder, MainContext,
9};
10use std::path::PathBuf;
11use std::{borrow::Cow, fs};
12use std::{io::Error as IoError, time::Instant};
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
44    /// Whether to run in benchmarking mode
45    #[arg(long)]
46    bench: bool,
47
48    /// Hex-encoded input/calldata bytes
49    #[arg(long, default_value = "")]
50    input: String,
51    /// Gas limit
52    #[arg(long, default_value = "1000000000")]
53    gas_limit: u64,
54
55    /// Whether to print the state
56    #[arg(long)]
57    state: bool,
58    /// Whether to print the trace
59    #[arg(long)]
60    trace: bool,
61}
62
63impl Cmd {
64    /// Runs evm runner command.
65    pub fn run(&self) -> Result<(), Errors> {
66        let bytecode_str: Cow<'_, str> = if let Some(path) = &self.path {
67            // Check if path exists.
68            if !path.exists() {
69                return Err(Errors::PathNotExists);
70            }
71            fs::read_to_string(path)?.into()
72        } else if let Some(bytecode) = &self.bytecode {
73            bytecode.as_str().into()
74        } else {
75            unreachable!()
76        };
77
78        let bytecode = hex::decode(bytecode_str.trim().trim_start_matches("0x"))
79            .map_err(|_| Errors::InvalidBytecode)?;
80        let input = hex::decode(self.input.trim().trim_start_matches("0x"))
81            .map_err(|_| Errors::InvalidInput)?
82            .into();
83
84        let mut db = BenchmarkDB::new_bytecode(Bytecode::new_raw_checked(bytecode.into())?);
85
86        let nonce = db
87            .basic(BENCH_CALLER)
88            .unwrap()
89            .map_or(0, |account| account.nonce);
90
91        // BenchmarkDB is dummy state that implements Database trait.
92        // The bytecode is deployed at zero address.
93        let mut evm = Context::mainnet()
94            .with_db(db)
95            .build_mainnet_with_inspector(TracerEip3155::new(Box::new(std::io::stdout())));
96
97        let tx = TxEnv::builder()
98            .caller(BENCH_CALLER)
99            .kind(TxKind::Call(BENCH_TARGET))
100            .data(input)
101            .nonce(nonce)
102            .gas_limit(self.gas_limit)
103            .build()
104            .unwrap();
105
106        if self.bench {
107            let mut criterion = criterion::Criterion::default()
108                .warm_up_time(std::time::Duration::from_millis(300))
109                .measurement_time(std::time::Duration::from_secs(2))
110                .without_plots();
111            let mut criterion_group = criterion.benchmark_group("revme");
112            criterion_group.bench_function("evm", |b| {
113                b.iter_batched(
114                    || tx.clone(),
115                    |input| evm.transact(input).unwrap(),
116                    criterion::BatchSize::SmallInput,
117                );
118            });
119            criterion_group.finish();
120
121            return Ok(());
122        }
123
124        let time = Instant::now();
125        let r = if self.trace {
126            evm.inspect_tx(tx)
127        } else {
128            evm.transact(tx)
129        }
130        .map_err(|_| Errors::EVMError)?;
131        let time = time.elapsed();
132
133        println!("Result: {:#?}", r.result);
134        if self.state {
135            println!("State: {:#?}", r.state);
136        }
137
138        println!("Elapsed: {time:?}");
139        Ok(())
140    }
141}