Skip to main content

revme/cmd/
evmrunner.rs

1use clap::Parser;
2use revm::{
3    bytecode::{Bytecode, BytecodeDecodeError},
4    context::TxEnv,
5    database::{BenchmarkDB, BENCH_CALLER, BENCH_TARGET},
6    inspector::{inspectors::TracerEip3155, InspectEvm},
7    primitives::{hex, TxKind},
8    Context, Database, ExecuteEvm, MainBuilder, MainContext,
9};
10use std::{borrow::Cow, fs, io::Error as IoError, path::PathBuf, time::Instant};
11
12#[derive(Debug, thiserror::Error)]
13pub enum Errors {
14    #[error("The specified path does not exist")]
15    PathNotExists,
16    #[error("Invalid bytecode")]
17    InvalidBytecode,
18    #[error("Invalid input")]
19    InvalidInput,
20    #[error("EVM Error")]
21    EVMError,
22    #[error(transparent)]
23    Io(#[from] IoError),
24    #[error(transparent)]
25    BytecodeDecodeError(#[from] BytecodeDecodeError),
26}
27
28/// Evm runner command allows running arbitrary evm bytecode
29///
30/// Bytecode can be provided from cli or from file with `--path` option.
31#[derive(Parser, Debug)]
32pub struct Cmd {
33    /// Hex-encoded EVM bytecode to be executed
34    #[arg(required_unless_present = "path")]
35    bytecode: Option<String>,
36    /// Path to a file containing the hex-encoded EVM bytecode to be executed
37    ///
38    /// Overrides the positional `bytecode` argument.
39    #[arg(long)]
40    path: Option<PathBuf>,
41
42    /// Whether to run in benchmarking mode
43    #[arg(long)]
44    bench: bool,
45
46    /// Hex-encoded input/calldata bytes
47    #[arg(long, default_value = "")]
48    input: String,
49    /// Gas limit
50    #[arg(long, default_value = "1000000000")]
51    gas_limit: u64,
52
53    /// Whether to print the state
54    #[arg(long)]
55    state: bool,
56    /// Whether to print the trace
57    #[arg(long)]
58    trace: bool,
59    /// Output results in JSON format
60    #[arg(long)]
61    json: bool,
62}
63
64impl Cmd {
65    /// Runs evm runner command.
66    pub fn run(&self) -> Result<(), Errors> {
67        let bytecode_str: Cow<'_, str> = if let Some(path) = &self.path {
68            // Check if path exists.
69            if !path.exists() {
70                return Err(Errors::PathNotExists);
71            }
72            fs::read_to_string(path)?.into()
73        } else if let Some(bytecode) = &self.bytecode {
74            bytecode.as_str().into()
75        } else {
76            unreachable!()
77        };
78
79        let bytecode = hex::decode(bytecode_str.trim().trim_start_matches("0x"))
80            .map_err(|_| Errors::InvalidBytecode)?;
81        let input = hex::decode(self.input.trim().trim_start_matches("0x"))
82            .map_err(|_| Errors::InvalidInput)?
83            .into();
84
85        let mut db = BenchmarkDB::new_bytecode(Bytecode::new_raw_checked(bytecode.into())?);
86
87        let nonce = db
88            .basic(BENCH_CALLER)
89            .unwrap()
90            .map_or(0, |account| account.nonce);
91
92        // BenchmarkDB is dummy state that implements Database trait.
93        // The bytecode is deployed at zero address.
94        let mut evm = Context::mainnet()
95            .with_db(db)
96            .build_mainnet_with_inspector(TracerEip3155::new(Box::new(std::io::stdout())));
97
98        let tx = TxEnv::builder()
99            .caller(BENCH_CALLER)
100            .kind(TxKind::Call(BENCH_TARGET))
101            .data(input)
102            .nonce(nonce)
103            .gas_limit(self.gas_limit)
104            .build()
105            .unwrap();
106
107        if self.bench {
108            let mut criterion = criterion::Criterion::default()
109                .warm_up_time(std::time::Duration::from_millis(300))
110                .measurement_time(std::time::Duration::from_secs(2))
111                .without_plots();
112            let mut criterion_group = criterion.benchmark_group("revme");
113            criterion_group.bench_function("evm", |b| {
114                b.iter_batched(
115                    || tx.clone(),
116                    |input| evm.transact(input).unwrap(),
117                    criterion::BatchSize::SmallInput,
118                );
119            });
120            criterion_group.finish();
121
122            return Ok(());
123        }
124
125        let time = Instant::now();
126        let r = if self.trace {
127            evm.inspect_tx(tx)
128        } else {
129            evm.transact(tx)
130        }
131        .map_err(|_| Errors::EVMError)?;
132        let time = time.elapsed();
133
134        if self.json {
135            let json = if self.state {
136                serde_json::json!({
137                    "result": r.result,
138                    "state": r.state,
139                    "elapsed": time.as_secs_f64(),
140                })
141            } else {
142                serde_json::json!({
143                    "result": r.result,
144                    "elapsed": time.as_secs_f64(),
145                })
146            };
147            println!("{}", serde_json::to_string_pretty(&json).unwrap());
148        } else {
149            println!("Result: {:#?}", r.result);
150            if self.state {
151                println!("State: {:#?}", r.state);
152            }
153            println!("Elapsed: {time:?}");
154        }
155        Ok(())
156    }
157}