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