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