arnaucube
arnaucube/sonobePublic Self-hosted repository
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}