// Copyright 2015-2019 Parity Technologies (UK) Ltd. // This file is part of Parity Ethereum. // Parity Ethereum is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity Ethereum is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity Ethereum. If not, see . //! EVM runner. use std::time::{Instant, Duration}; use common_types::transaction; use ethcore::{ client::{self, EvmTestClient, EvmTestError, TransactErr, TransactSuccess}, spec, TrieSpec, }; use ethereum_types::{H256, U256}; use ethjson; use pod::PodState; use trace; use vm::ActionParams; /// EVM execution informant. pub trait Informant: trace::VMTracer { /// Sink to use with finish type Sink; /// Display a single run init message fn before_test(&mut self, test: &str, action: &str); /// Set initial gas. fn set_gas(&mut self, _gas: U256) {} /// Clone sink. fn clone_sink(&self) -> Self::Sink; /// Display final result. fn finish(result: RunResult, _: &mut Self::Sink); } /// Execution finished correctly. #[derive(Debug)] pub struct Success { /// State root. pub state_root: H256, /// Used gas. pub gas_used: U256, /// Output as bytes. pub output: Vec, /// Time taken. pub time: Duration, /// Traces. pub traces: Option, /// Optional end state dump pub end_state: Option, } /// Execution failed. #[derive(Debug)] pub struct Failure { /// State root. pub state_root: H256, /// Used gas. pub gas_used: U256, /// Internal error. pub error: EvmTestError, /// Duration. pub time: Duration, /// Traces. pub traces: Option, /// Optional end state dump pub end_state: Option, } /// EVM execution result. pub type RunResult = Result, Failure>; /// Execute given `ActionParams` and return the result. pub fn run_action( spec: &spec::Spec, mut params: ActionParams, mut informant: T, trie_spec: TrieSpec, ) -> RunResult { informant.set_gas(params.gas); // if the code is not overwritten from CLI, use code from spec file. if params.code.is_none() { if let Some(acc) = spec.genesis_state.get().get(¶ms.code_address) { params.code = acc.code.clone().map(::std::sync::Arc::new); params.code_hash = None; } } run(spec, trie_spec, params.gas, &spec.genesis_state, |mut client| { let result = match client.call(params, &mut trace::NoopTracer, &mut informant) { Ok(r) => (Ok(r.return_data.to_vec()), Some(r.gas_left)), Err(err) => (Err(err), None), }; (result.0, H256::from_low_u64_be(0), None, result.1, informant.drain()) }) } /// Input data to run transaction. #[derive(Debug)] pub struct TxInput<'a, T> { /// State test name associated with the transaction. pub state_test_name: &'a str, /// Transaction index from list of transactions within a state root hash corresponding to a chain. pub tx_index: usize, /// Fork specification (i.e. Constantinople, EIP150, EIP158, etc). pub fork_spec_name: &'a ethjson::spec::ForkSpec, /// State of all accounts in the system that is a binary tree mapping of each account address to account data /// that is expressed as Plain Old Data containing the account balance, account nonce, account code in bytes, /// and the account storage binary tree map. pub pre_state: &'a PodState, /// State root hash associated with the transaction. pub post_root: H256, /// Client environment information associated with the transaction's chain specification. pub env_info: &'a client::EnvInfo, /// Signed transaction accompanied by a signature that may be unverified and a successfully recovered /// sender address. The unverified transaction contains a recoverable ECDSA signature that has been encoded /// as RSV components and includes replay protection for the specified chain. Verification of the signed transaction /// with a valid secret of an account's keypair and a specific chain may be used to recover the sender's public key /// and their associated address by applying the Keccak-256 hash function. pub transaction: transaction::SignedTransaction, /// JSON formatting informant. pub informant: T, /// Trie specification (i.e. Generic trie, Secure trie, Secure with fat database). pub trie_spec: TrieSpec, } /// Execute given transaction and verify resulting state root. /// Returns true if the transaction executes successfully. pub fn run_transaction( tx_input: TxInput ) -> bool { let TxInput { state_test_name, tx_index, fork_spec_name, pre_state, post_root, env_info, transaction, mut informant, trie_spec, .. } = tx_input; let fork_spec_name_formatted = format!("{:?}", fork_spec_name).to_lowercase(); let fork_spec = match EvmTestClient::fork_spec_from_json(&fork_spec_name) { Some(spec) => { informant.before_test( &format!("{}:{}:{}", &state_test_name, &fork_spec_name_formatted, tx_index), "starting"); spec }, None => { informant.before_test(&format!("{}:{}:{}", &state_test_name, fork_spec_name_formatted, &tx_index), "skipping because of missing fork specification"); return false; }, }; informant.set_gas(env_info.gas_limit); let mut sink = informant.clone_sink(); let result = run(&fork_spec, trie_spec, transaction.gas, &pre_state, |mut client| { let result = client.transact(&env_info, transaction, trace::NoopTracer, informant); match result { Ok(TransactSuccess { state_root, gas_left, output, vm_trace, end_state, .. }) => { if state_root != post_root { (Err(EvmTestError::PostCondition(format!( "State root mismatch (got: {:#x}, expected: {:#x})", state_root, post_root, ))), state_root, end_state, Some(gas_left), None) } else { (Ok(output), state_root, end_state, Some(gas_left), vm_trace) } }, Err(TransactErr { state_root, error, end_state }) => { (Err(EvmTestError::PostCondition(format!( "Unexpected execution error: {:?}", error ))), state_root, end_state, None, None) }, } }); let ok = result.is_ok(); T::finish(result, &mut sink); ok } /// Execute EVM with given `ActionParams`. pub fn run<'a, F, X>( spec: &'a spec::Spec, trie_spec: TrieSpec, initial_gas: U256, pre_state: &'a PodState, run: F, ) -> RunResult where F: FnOnce(EvmTestClient) -> (Result, EvmTestError>, H256, Option, Option, Option), { let do_dump = trie_spec == TrieSpec::Fat; let mut test_client = EvmTestClient::from_pod_state_with_trie(spec, pre_state.clone(), trie_spec) .map_err(|error| Failure { gas_used: 0.into(), error, time: Duration::from_secs(0), traces: None, state_root: H256::zero(), end_state: None, })?; if do_dump { test_client.set_dump_state(); } let start = Instant::now(); let result = run(test_client); let time = start.elapsed(); match result { (Ok(output), state_root, end_state, gas_left, traces) => Ok(Success { state_root, gas_used: gas_left.map(|gas_left| initial_gas - gas_left).unwrap_or(initial_gas), output, time, traces, end_state, }), (Err(error), state_root, end_state, gas_left, traces) => Err(Failure { gas_used: gas_left.map(|gas_left| initial_gas - gas_left).unwrap_or(initial_gas), error, time, traces, state_root, end_state, }), } } #[cfg(test)] pub mod tests { use std::sync::Arc; use rustc_hex::FromHex; use super::*; use tempdir::TempDir; use ethereum_types::Address; use ethcore::spec::{self, Spec}; pub fn run_test( informant: I, compare: F, code: &str, gas: T, expected: &str, ) where T: Into, I: Informant, F: FnOnce(Option, &str), { let mut params = ActionParams::default(); params.code = Some(Arc::new(code.from_hex().unwrap())); params.gas = gas.into(); let tempdir = TempDir::new("").unwrap(); let spec = spec::new_foundation(&tempdir.path()); let result = run_action(&spec, params, informant, TrieSpec::Secure); match result { Ok(Success { traces, .. }) => { compare(traces, expected) }, Err(Failure { traces, .. }) => { compare(traces, expected) }, } } #[test] fn should_call_account_from_spec() { use crate::display::std_json::tests::informant; let (inf, res, _) = informant(); let mut params = ActionParams::default(); params.code_address = Address::from_low_u64_be(0x20); params.gas = 0xffff.into(); let tempdir = TempDir::new("").unwrap(); let spec = Spec::load(&tempdir.path(), include_bytes!("../res/testchain.json") as &[u8]).unwrap(); let _result = run_action(&spec, params, inf, TrieSpec::Secure); assert_eq!( &String::from_utf8_lossy(&**res.0.lock().unwrap()), r#"{"pc":0,"op":98,"opName":"PUSH3","gas":"0xffff","stack":[],"storage":{},"depth":1} {"pc":4,"op":96,"opName":"PUSH1","gas":"0xfffc","stack":["0xaaaaaa"],"storage":{},"depth":1} {"pc":6,"op":96,"opName":"PUSH1","gas":"0xfff9","stack":["0xaaaaaa","0xaa"],"storage":{},"depth":1} {"pc":8,"op":80,"opName":"POP","gas":"0xfff6","stack":["0xaaaaaa","0xaa","0xaa"],"storage":{},"depth":1} {"pc":9,"op":96,"opName":"PUSH1","gas":"0xfff4","stack":["0xaaaaaa","0xaa"],"storage":{},"depth":1} {"pc":11,"op":96,"opName":"PUSH1","gas":"0xfff1","stack":["0xaaaaaa","0xaa","0xaa"],"storage":{},"depth":1} {"pc":13,"op":96,"opName":"PUSH1","gas":"0xffee","stack":["0xaaaaaa","0xaa","0xaa","0xaa"],"storage":{},"depth":1} {"pc":15,"op":96,"opName":"PUSH1","gas":"0xffeb","stack":["0xaaaaaa","0xaa","0xaa","0xaa","0xaa"],"storage":{},"depth":1} {"pc":17,"op":96,"opName":"PUSH1","gas":"0xffe8","stack":["0xaaaaaa","0xaa","0xaa","0xaa","0xaa","0xaa"],"storage":{},"depth":1} {"pc":19,"op":96,"opName":"PUSH1","gas":"0xffe5","stack":["0xaaaaaa","0xaa","0xaa","0xaa","0xaa","0xaa","0xaa"],"storage":{},"depth":1} "#); } }