sonobe/solidity-verifiers/src/evm.rs
solidity-verifiers/src/evm.rsRaw
1pub use revm;
2use revm::{
3 primitives::{hex, Address, CreateScheme, ExecutionResult, Output, TransactTo, TxEnv},
4 InMemoryDB, EVM,
5};
6use std::{
7 fmt::{self, Debug, Formatter},
8 fs::{self, create_dir_all, File},
9 io::{self, Write},
10 path::PathBuf,
11 process::{Command, Stdio},
12 str,
13};
14
15// from: https://github.com/privacy-scaling-explorations/halo2-solidity-verifier/blob/85cb77b171ce3ee493628007c7a1cfae2ea878e6/examples/separately.rs#L56
16pub fn save_solidity(name: impl AsRef<str>, solidity: &str) {
17 let curdir = PathBuf::from(".");
18 let curdir_abs_path = fs::canonicalize(curdir).expect("Failed to get current directory");
19 let curdir_abs_path = curdir_abs_path
20 .to_str()
21 .expect("Failed to convert path to string");
22 let dir_generated = format!("{curdir_abs_path}/generated");
23 create_dir_all(dir_generated.clone()).unwrap();
24 File::create(format!("{}/{}", dir_generated, name.as_ref()))
25 .unwrap()
26 .write_all(solidity.as_bytes())
27 .unwrap();
28}
29
30/// Compile solidity with `--via-ir` flag, then return creation bytecode.
31///
32/// # Panics
33/// Panics if executable `solc` can not be found, or compilation fails.
34pub fn compile_solidity(solidity: impl AsRef<[u8]>, contract_name: &str) -> Vec<u8> {
35 let mut process = match Command::new("solc")
36 .stdin(Stdio::piped())
37 .stdout(Stdio::piped())
38 .stderr(Stdio::piped())
39 .arg("--bin")
40 .arg("--optimize")
41 .arg("-")
42 .spawn()
43 {
44 Ok(process) => process,
45 Err(err) if err.kind() == io::ErrorKind::NotFound => {
46 panic!("Command 'solc' not found");
47 }
48 Err(err) => {
49 panic!("Failed to spawn process with command 'solc':\n{err}");
50 }
51 };
52 process
53 .stdin
54 .take()
55 .unwrap()
56 .write_all(solidity.as_ref())
57 .unwrap();
58 let output = process.wait_with_output().unwrap();
59 let stdout = str::from_utf8(&output.stdout).unwrap();
60 if let Some(binary) = find_binary(stdout, contract_name) {
61 binary
62 } else {
63 panic!(
64 "Compilation fails:\n{}",
65 str::from_utf8(&output.stderr).unwrap()
66 )
67 }
68}
69
70/// Find binary from `stdout` with given `contract_name`.
71/// `contract_name` is provided since `solc` may compile multiple contracts or libraries.
72/// hence, we need to find the correct binary.
73fn find_binary(stdout: &str, contract_name: &str) -> Option<Vec<u8>> {
74 let start_contract = stdout.find(contract_name)?;
75 let stdout_contract = &stdout[start_contract..];
76 let start = stdout_contract.find("Binary:")? + 8;
77 Some(hex::decode(&stdout_contract[start..stdout_contract.len() - 1]).unwrap())
78}
79
80/// Evm runner.
81pub struct Evm {
82 evm: EVM<InMemoryDB>,
83}
84
85impl Debug for Evm {
86 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
87 let mut debug_struct = f.debug_struct("Evm");
88 debug_struct
89 .field("env", &self.evm.env)
90 .field("db", &self.evm.db.as_ref().unwrap())
91 .finish()
92 }
93}
94
95impl Default for Evm {
96 fn default() -> Self {
97 Self {
98 evm: EVM {
99 env: Default::default(),
100 db: Some(Default::default()),
101 },
102 }
103 }
104}
105
106impl Evm {
107 /// Apply create transaction with given `bytecode` as creation bytecode.
108 /// Return created `address`.
109 ///
110 /// # Panics
111 /// Panics if execution reverts or halts unexpectedly.
112 pub fn create(&mut self, bytecode: Vec<u8>) -> Address {
113 let (_, output) = self.transact_success_or_panic(TxEnv {
114 gas_limit: u64::MAX,
115 transact_to: TransactTo::Create(CreateScheme::Create),
116 data: bytecode.into(),
117 ..Default::default()
118 });
119 match output {
120 Output::Create(_, Some(address)) => address,
121 _ => unreachable!(),
122 }
123 }
124
125 /// Apply call transaction to given `address` with `calldata`.
126 /// Returns `gas_used` and `return_data`.
127 ///
128 /// # Panics
129 /// Panics if execution reverts or halts unexpectedly.
130 pub fn call(&mut self, address: Address, calldata: Vec<u8>) -> (u64, Vec<u8>) {
131 let (gas_used, output) = self.transact_success_or_panic(TxEnv {
132 gas_limit: u64::MAX,
133 transact_to: TransactTo::Call(address),
134 data: calldata.into(),
135 ..Default::default()
136 });
137 match output {
138 Output::Call(output) => (gas_used, output.into()),
139 _ => unreachable!(),
140 }
141 }
142
143 fn transact_success_or_panic(&mut self, tx: TxEnv) -> (u64, Output) {
144 self.evm.env.tx = tx;
145 let result = self.evm.transact_commit().unwrap();
146 self.evm.env.tx = Default::default();
147 match result {
148 ExecutionResult::Success {
149 gas_used,
150 output,
151 logs,
152 ..
153 } => {
154 if !logs.is_empty() {
155 println!("--- logs from {} ---", logs[0].address);
156 for (log_idx, log) in logs.iter().enumerate() {
157 println!("log#{log_idx}");
158 for (topic_idx, topic) in log.topics.iter().enumerate() {
159 println!(" topic{topic_idx}: {topic:?}");
160 }
161 }
162 println!("--- end ---");
163 }
164 (gas_used, output)
165 }
166 ExecutionResult::Revert { gas_used, output } => (gas_used, Output::Call(output)),
167 ExecutionResult::Halt { reason, gas_used } => panic!(
168 "Transaction halts unexpectedly with gas_used {gas_used} and reason {reason:?}"
169 ),
170 }
171 }
172}