sonobe/folding-schemes-solidity/src/evm.rs
folding-schemes-solidity/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 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 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 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 /// Return code_size of given address.
102 ///
103 /// # Panics
104 /// Panics if given address doesn't have bytecode.
105 pub fn code_size(&mut self, address: Address) -> usize {
106 self.evm.db.as_ref().unwrap().accounts[&address]
107 .info
108 .code
109 .as_ref()
110 .unwrap()
111 .len()
112 }
113
114 /// Apply create transaction with given `bytecode` as creation bytecode.
115 /// Return created `address`.
116 ///
117 /// # Panics
118 /// Panics if execution reverts or halts unexpectedly.
119 pub fn create(&mut self, bytecode: Vec<u8>) -> Address {
120 let (_, output) = self.transact_success_or_panic(TxEnv {
121 gas_limit: u64::MAX,
122 transact_to: TransactTo::Create(CreateScheme::Create),
123 data: bytecode.into(),
124 ..Default::default()
125 });
126 match output {
127 Output::Create(_, Some(address)) => address,
128 _ => unreachable!(),
129 }
130 }
131
132 /// Apply call transaction to given `address` with `calldata`.
133 /// Returns `gas_used` and `return_data`.
134 ///
135 /// # Panics
136 /// Panics if execution reverts or halts unexpectedly.
137 pub fn call(&mut self, address: Address, calldata: Vec<u8>) -> (u64, Vec<u8>) {
138 let (gas_used, output) = self.transact_success_or_panic(TxEnv {
139 gas_limit: u64::MAX,
140 transact_to: TransactTo::Call(address),
141 data: calldata.into(),
142 ..Default::default()
143 });
144 match output {
145 Output::Call(output) => (gas_used, output.into()),
146 _ => unreachable!(),
147 }
148 }
149
150 fn transact_success_or_panic(&mut self, tx: TxEnv) -> (u64, Output) {
151 self.evm.env.tx = tx;
152 let result = self.evm.transact_commit().unwrap();
153 self.evm.env.tx = Default::default();
154 match result {
155 ExecutionResult::Success {
156 gas_used,
157 output,
158 logs,
159 ..
160 } => {
161 if !logs.is_empty() {
162 println!("--- logs from {} ---", logs[0].address);
163 for (log_idx, log) in logs.iter().enumerate() {
164 println!("log#{log_idx}");
165 for (topic_idx, topic) in log.topics.iter().enumerate() {
166 println!(" topic{topic_idx}: {topic:?}");
167 }
168 }
169 println!("--- end ---");
170 }
171 (gas_used, output)
172 }
173 ExecutionResult::Revert { gas_used, output } => {
174 panic!("Transaction reverts with gas_used {gas_used} and output {output:#x}")
175 }
176 ExecutionResult::Halt { reason, gas_used } => panic!(
177 "Transaction halts unexpectedly with gas_used {gas_used} and reason {reason:?}"
178 ),
179 }
180 }
181}