// Copyright 2015-2020 Parity Technologies (UK) Ltd. // This file is part of OpenEthereum. // OpenEthereum 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. // OpenEthereum 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 OpenEthereum. If not, see . //! Simple Client used for EVM tests. use client; use db; use ethereum_types::{H160, H256, U256}; use ethtrie; use evm::{FinalizationResult, VMType}; use executive; use factory::{self, Factories}; use journaldb; use kvdb::{self, KeyValueDB}; use pod_state; use spec; use state; use state_db; use std::{fmt, sync::Arc}; use trace; use trie; use types::{log_entry, receipt, transaction}; use vm::{self, ActionParams}; /// EVM test Error. #[derive(Debug)] pub enum EvmTestError { /// Trie integrity error. Trie(Box), /// EVM error. Evm(vm::Error), /// Initialization error. ClientError(::error::Error), /// Post-condition failure, PostCondition(String), } impl> From for EvmTestError { fn from(err: E) -> Self { EvmTestError::ClientError(err.into()) } } impl fmt::Display for EvmTestError { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { use self::EvmTestError::*; match *self { Trie(ref err) => write!(fmt, "Trie: {}", err), Evm(ref err) => write!(fmt, "EVM: {}", err), ClientError(ref err) => write!(fmt, "{}", err), PostCondition(ref err) => write!(fmt, "{}", err), } } } use ethereum::{self}; use ethjson::spec::ForkSpec; /// Simplified, single-block EVM test client. pub struct EvmTestClient<'a> { state: state::State, spec: &'a spec::Spec, dump_state: fn(&state::State) -> Option, } fn no_dump_state(_: &state::State) -> Option { None } impl<'a> fmt::Debug for EvmTestClient<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("EvmTestClient") .field("state", &self.state) .field("spec", &self.spec.name) .finish() } } impl<'a> EvmTestClient<'a> { /// Converts a json spec definition into spec. pub fn spec_from_json(spec: &ForkSpec) -> Option { match *spec { ForkSpec::Frontier => Some(ethereum::new_frontier_test()), ForkSpec::Homestead => Some(ethereum::new_homestead_test()), ForkSpec::EIP150 => Some(ethereum::new_eip150_test()), ForkSpec::EIP158 => Some(ethereum::new_eip161_test()), ForkSpec::Byzantium => Some(ethereum::new_byzantium_test()), ForkSpec::Constantinople => Some(ethereum::new_constantinople_test()), ForkSpec::ConstantinopleFix => Some(ethereum::new_constantinople_fix_test()), ForkSpec::Istanbul => Some(ethereum::new_istanbul_test()), ForkSpec::EIP158ToByzantiumAt5 => Some(ethereum::new_transition_test()), ForkSpec::ByzantiumToConstantinopleFixAt5 => { Some(ethereum::new_byzantium_to_constantinoplefixat5_test()) } ForkSpec::Berlin => Some(ethereum::new_berlin_test()), ForkSpec::London => Some(ethereum::new_london_test()), ForkSpec::FrontierToHomesteadAt5 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 | ForkSpec::ByzantiumToConstantinopleAt5 => None, } } /// Change default function for dump state (default does not dump) pub fn set_dump_state_fn( &mut self, dump_state: fn(&state::State) -> Option, ) { self.dump_state = dump_state; } /// Creates new EVM test client with in-memory DB initialized with genesis of given Spec. /// Takes a `TrieSpec` to set the type of trie. pub fn new_with_trie( spec: &'a spec::Spec, trie_spec: trie::TrieSpec, ) -> Result { let factories = Self::factories(trie_spec); let state = Self::state_from_spec(spec, &factories)?; Ok(EvmTestClient { state, spec, dump_state: no_dump_state, }) } /// Creates new EVM test client with an in-memory DB initialized with genesis of given chain Spec. pub fn new(spec: &'a spec::Spec) -> Result { Self::new_with_trie(spec, trie::TrieSpec::Secure) } /// Creates new EVM test client with an in-memory DB initialized with given PodState. /// Takes a `TrieSpec` to set the type of trie. pub fn from_pod_state_with_trie( spec: &'a spec::Spec, pod_state: pod_state::PodState, trie_spec: trie::TrieSpec, ) -> Result { let factories = Self::factories(trie_spec); let state = Self::state_from_pod(spec, &factories, pod_state)?; Ok(EvmTestClient { state, spec, dump_state: no_dump_state, }) } /// Creates new EVM test client with an in-memory DB initialized with given PodState. pub fn from_pod_state( spec: &'a spec::Spec, pod_state: pod_state::PodState, ) -> Result { Self::from_pod_state_with_trie(spec, pod_state, trie::TrieSpec::Secure) } fn factories(trie_spec: trie::TrieSpec) -> Factories { Factories { vm: factory::VmFactory::new(VMType::Interpreter, 5 * 1024), trie: trie::TrieFactory::new(trie_spec), accountdb: Default::default(), } } fn state_from_spec( spec: &'a spec::Spec, factories: &Factories, ) -> Result, EvmTestError> { let db = Arc::new(ethcore_db::InMemoryWithMetrics::create( db::NUM_COLUMNS.expect("We use column-based DB; qed"), )); let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, db::COL_STATE); let mut state_db = state_db::StateDB::new(journal_db, 5 * 1024 * 1024); state_db = spec.ensure_db_good(state_db, factories)?; let genesis = spec.genesis_header(); // Write DB { let mut batch = kvdb::DBTransaction::new(); state_db.journal_under(&mut batch, 0, &genesis.hash())?; db.write(batch)?; } state::State::from_existing( state_db, *genesis.state_root(), spec.engine.account_start_nonce(0), factories.clone(), ) .map_err(EvmTestError::Trie) } fn state_from_pod( spec: &'a spec::Spec, factories: &Factories, pod_state: pod_state::PodState, ) -> Result, EvmTestError> { let db = Arc::new(ethcore_db::InMemoryWithMetrics::create( db::NUM_COLUMNS.expect("We use column-based DB; qed"), )); let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, db::COL_STATE); let state_db = state_db::StateDB::new(journal_db, 5 * 1024 * 1024); let mut state = state::State::new( state_db, spec.engine.account_start_nonce(0), factories.clone(), ); state.populate_from(pod_state); state.commit()?; Ok(state) } /// Return current state. pub fn state(&self) -> &state::State { &self.state } /// Execute the VM given ActionParams and tracer. /// Returns amount of gas left and the output. pub fn call( &mut self, params: ActionParams, tracer: &mut T, vm_tracer: &mut V, ) -> Result { let genesis = self.spec.genesis_header(); let info = client::EnvInfo { number: genesis.number(), author: *genesis.author(), timestamp: genesis.timestamp(), difficulty: *genesis.difficulty(), last_hashes: Arc::new([H256::default(); 256].to_vec()), gas_used: 0.into(), gas_limit: *genesis.gas_limit(), base_fee: genesis.base_fee(), }; self.call_envinfo(params, tracer, vm_tracer, info) } /// Execute the VM given envinfo, ActionParams and tracer. /// Returns amount of gas left and the output. pub fn call_envinfo( &mut self, params: ActionParams, tracer: &mut T, vm_tracer: &mut V, info: client::EnvInfo, ) -> Result { let mut substate = state::Substate::new(); let machine = self.spec.engine.machine(); let schedule = machine.schedule(info.number); let mut executive = executive::Executive::new(&mut self.state, &info, &machine, &schedule); executive .call(params, &mut substate, tracer, vm_tracer) .map_err(EvmTestError::Evm) } /// Executes a SignedTransaction within context of the provided state and `EnvInfo`. /// Returns the state root, gas left and the output. pub fn transact( &mut self, env_info: &client::EnvInfo, transaction: transaction::SignedTransaction, tracer: T, vm_tracer: V, ) -> std::result::Result, TransactErr> { let initial_gas = transaction.tx().gas; // Verify transaction let is_ok = transaction.verify_basic(true, None); if let Err(error) = is_ok { return Err(TransactErr { state_root: *self.state.root(), error: error.into(), end_state: (self.dump_state)(&self.state), }); } // Apply transaction let result = self.state.apply_with_tracing( &env_info, self.spec.engine.machine(), &transaction, tracer, vm_tracer, ); let scheme = self .spec .engine .machine() .create_address_scheme(env_info.number); // Touch the coinbase at the end of the test to simulate // miner reward. // Details: https://github.com/openethereum/openethereum/issues/9431 let schedule = self.spec.engine.machine().schedule(env_info.number); self.state .add_balance( &env_info.author, &0.into(), if schedule.no_empty { state::CleanupMode::NoEmpty } else { state::CleanupMode::ForceCreate }, ) .ok(); // Touching also means that we should remove the account if it's within eip161 // conditions. self.state .kill_garbage( &vec![env_info.author].into_iter().collect(), schedule.kill_empty, &None, false, ) .ok(); self.state.commit().ok(); let state_root = *self.state.root(); let end_state = (self.dump_state)(&self.state); match result { Ok(result) => Ok(TransactSuccess { state_root, gas_left: initial_gas - result.receipt.gas_used, outcome: result.receipt.outcome.clone(), output: result.output, trace: result.trace, vm_trace: result.vm_trace, logs: result.receipt.logs.clone(), contract_address: if let transaction::Action::Create = transaction.tx().action { Some( executive::contract_address( scheme, &transaction.sender(), &transaction.tx().nonce, &transaction.tx().data, ) .0, ) } else { None }, end_state, }), Err(error) => Err(TransactErr { state_root, error, end_state, }), } } } /// To be returned inside a std::result::Result::Ok after a successful /// transaction completed. #[allow(dead_code)] pub struct TransactSuccess { /// State root pub state_root: H256, /// Amount of gas left pub gas_left: U256, /// Output pub output: Vec, /// Traces pub trace: Vec, /// VM Traces pub vm_trace: Option, /// Created contract address (if any) pub contract_address: Option, /// Generated logs pub logs: Vec, /// outcome pub outcome: receipt::TransactionOutcome, /// end state if needed pub end_state: Option, } /// To be returned inside a std::result::Result::Err after a failed /// transaction. #[allow(dead_code)] pub struct TransactErr { /// State root pub state_root: H256, /// Execution error pub error: ::error::Error, /// end state if needed pub end_state: Option, }