pub use revm; use revm::{ primitives::{hex, Address, CreateScheme, ExecutionResult, Output, TransactTo, TxEnv}, InMemoryDB, EVM, }; use std::{ fmt::{self, Debug, Formatter}, fs::{create_dir_all, File}, io::{self, Write}, process::{Command, Stdio}, str, }; // from: https://github.com/privacy-scaling-explorations/halo2-solidity-verifier/blob/85cb77b171ce3ee493628007c7a1cfae2ea878e6/examples/separately.rs#L56 pub fn save_solidity(name: impl AsRef, solidity: &str) { const DIR_GENERATED: &str = "./generated"; create_dir_all(DIR_GENERATED).unwrap(); File::create(format!("{DIR_GENERATED}/{}", name.as_ref())) .unwrap() .write_all(solidity.as_bytes()) .unwrap(); } /// Compile solidity with `--via-ir` flag, then return creation bytecode. /// /// # Panics /// Panics if executable `solc` can not be found, or compilation fails. pub fn compile_solidity(solidity: impl AsRef<[u8]>, contract_name: &str) -> Vec { let mut process = match Command::new("solc") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .arg("--bin") .arg("--optimize") .arg("-") .spawn() { Ok(process) => process, Err(err) if err.kind() == io::ErrorKind::NotFound => { panic!("Command 'solc' not found"); } Err(err) => { panic!("Failed to spawn process with command 'solc':\n{err}"); } }; process .stdin .take() .unwrap() .write_all(solidity.as_ref()) .unwrap(); let output = process.wait_with_output().unwrap(); let stdout = str::from_utf8(&output.stdout).unwrap(); if let Some(binary) = find_binary(stdout, contract_name) { binary } else { panic!( "Compilation fails:\n{}", str::from_utf8(&output.stderr).unwrap() ) } } /// Find binary from `stdout` with given `contract_name`. /// `contract_name` is provided since `solc` may compile multiple contracts or libraries. /// hence, we need to find the correct binary. fn find_binary(stdout: &str, contract_name: &str) -> Option> { let start_contract = stdout.find(contract_name)?; let stdout_contract = &stdout[start_contract..]; let start = stdout_contract.find("Binary:")? + 8; Some(hex::decode(&stdout_contract[start..stdout_contract.len() - 1]).unwrap()) } /// Evm runner. pub struct Evm { evm: EVM, } impl Debug for Evm { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut debug_struct = f.debug_struct("Evm"); debug_struct .field("env", &self.evm.env) .field("db", &self.evm.db.as_ref().unwrap()) .finish() } } impl Default for Evm { fn default() -> Self { Self { evm: EVM { env: Default::default(), db: Some(Default::default()), }, } } } impl Evm { /// Return code_size of given address. /// /// # Panics /// Panics if given address doesn't have bytecode. pub fn code_size(&mut self, address: Address) -> usize { self.evm.db.as_ref().unwrap().accounts[&address] .info .code .as_ref() .unwrap() .len() } /// Apply create transaction with given `bytecode` as creation bytecode. /// Return created `address`. /// /// # Panics /// Panics if execution reverts or halts unexpectedly. pub fn create(&mut self, bytecode: Vec) -> Address { let (_, output) = self.transact_success_or_panic(TxEnv { gas_limit: u64::MAX, transact_to: TransactTo::Create(CreateScheme::Create), data: bytecode.into(), ..Default::default() }); match output { Output::Create(_, Some(address)) => address, _ => unreachable!(), } } /// Apply call transaction to given `address` with `calldata`. /// Returns `gas_used` and `return_data`. /// /// # Panics /// Panics if execution reverts or halts unexpectedly. pub fn call(&mut self, address: Address, calldata: Vec) -> (u64, Vec) { let (gas_used, output) = self.transact_success_or_panic(TxEnv { gas_limit: u64::MAX, transact_to: TransactTo::Call(address), data: calldata.into(), ..Default::default() }); match output { Output::Call(output) => (gas_used, output.into()), _ => unreachable!(), } } fn transact_success_or_panic(&mut self, tx: TxEnv) -> (u64, Output) { self.evm.env.tx = tx; let result = self.evm.transact_commit().unwrap(); self.evm.env.tx = Default::default(); match result { ExecutionResult::Success { gas_used, output, logs, .. } => { if !logs.is_empty() { println!("--- logs from {} ---", logs[0].address); for (log_idx, log) in logs.iter().enumerate() { println!("log#{log_idx}"); for (topic_idx, topic) in log.topics.iter().enumerate() { println!(" topic{topic_idx}: {topic:?}"); } } println!("--- end ---"); } (gas_used, output) } ExecutionResult::Revert { gas_used, output } => { panic!("Transaction reverts with gas_used {gas_used} and output {output:#x}") } ExecutionResult::Halt { reason, gas_used } => panic!( "Transaction halts unexpectedly with gas_used {gas_used} and reason {reason:?}" ), } } }