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#[derive(Parser, Debug)]
32pub struct Cmd {
33 #[arg(required_unless_present = "path")]
35 bytecode: Option<String>,
36 #[arg(long)]
40 path: Option<PathBuf>,
41
42 #[arg(long)]
44 bench: bool,
45
46 #[arg(long, default_value = "")]
48 input: String,
49 #[arg(long, default_value = "1000000000")]
51 gas_limit: u64,
52
53 #[arg(long)]
55 state: bool,
56 #[arg(long)]
58 trace: bool,
59 #[arg(long)]
61 json: bool,
62}
63
64impl Cmd {
65 pub fn run(&self) -> Result<(), Errors> {
67 let bytecode_str: Cow<'_, str> = if let Some(path) = &self.path {
68 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 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}