diff --git a/ethcore/src/client/evm_test_client.rs b/ethcore/src/client/evm_test_client.rs index 8d0419e2e..74410f04c 100644 --- a/ethcore/src/client/evm_test_client.rs +++ b/ethcore/src/client/evm_test_client.rs @@ -99,7 +99,7 @@ impl<'a> fmt::Debug for EvmTestClient<'a> { impl<'a> EvmTestClient<'a> { /// Converts a json spec definition into spec. - pub fn spec_from_json(spec: &ForkSpec) -> Option { + pub fn fork_spec_from_json(spec: &ForkSpec) -> Option { match *spec { ForkSpec::Frontier => Some(ethereum::new_frontier_test()), ForkSpec::Homestead => Some(ethereum::new_homestead_test()), diff --git a/ethcore/src/json_tests/chain.rs b/ethcore/src/json_tests/chain.rs index 73cc809a4..1e9861f61 100644 --- a/ethcore/src/json_tests/chain.rs +++ b/ethcore/src/json_tests/chain.rs @@ -65,7 +65,7 @@ pub fn json_chain_test(json_data: &[u8], start_stop_ho flush!(" - {}...", name); let spec = { - let mut spec = match EvmTestClient::spec_from_json(&blockchain.network) { + let mut spec = match EvmTestClient::fork_spec_from_json(&blockchain.network) { Some(spec) => spec, None => { println!(" - {} | {:?} Ignoring tests because of missing spec", name, blockchain.network); diff --git a/ethcore/src/json_tests/state.rs b/ethcore/src/json_tests/state.rs index 524010ffe..dc2714fba 100644 --- a/ethcore/src/json_tests/state.rs +++ b/ethcore/src/json_tests/state.rs @@ -62,7 +62,7 @@ pub fn json_chain_test(json_data: &[u8], start_stop_ho for (spec_name, states) in test.post_states { let total = states.len(); - let spec = match EvmTestClient::spec_from_json(&spec_name) { + let spec = match EvmTestClient::fork_spec_from_json(&spec_name) { Some(spec) => spec, None => { println!(" - {} | {:?} Ignoring tests because of missing spec", name, spec_name); diff --git a/ethcore/src/json_tests/transaction.rs b/ethcore/src/json_tests/transaction.rs index d71ef3283..e9d2d75b9 100644 --- a/ethcore/src/json_tests/transaction.rs +++ b/ethcore/src/json_tests/transaction.rs @@ -47,7 +47,7 @@ fn do_json_test(json_data: &[u8], start_stop_hook: &mu start_stop_hook(&name, HookType::OnStart); for (spec_name, result) in test.post_state { - let spec = match EvmTestClient::spec_from_json(&spec_name) { + let spec = match EvmTestClient::fork_spec_from_json(&spec_name) { Some(spec) => spec, None => { println!(" - {} | {:?} Ignoring tests because of missing spec", name, spec_name); diff --git a/ethcore/types/src/transaction/transaction.rs b/ethcore/types/src/transaction/transaction.rs index 3b1f457ff..e1139dc1f 100644 --- a/ethcore/types/src/transaction/transaction.rs +++ b/ethcore/types/src/transaction/transaction.rs @@ -308,7 +308,7 @@ impl UnverifiedTransaction { self } - /// Checks is signature is empty. + /// Checks if the signature is empty. pub fn is_unsigned(&self) -> bool { self.r.is_zero() && self.s.is_zero() } diff --git a/evmbin/Cargo.toml b/evmbin/Cargo.toml index a835674a4..c3706b60e 100644 --- a/evmbin/Cargo.toml +++ b/evmbin/Cargo.toml @@ -9,6 +9,7 @@ name = "parity-evm" path = "./src/main.rs" [dependencies] +account-state = { path = "../ethcore/account-state" } common-types = { path = "../ethcore/types" } docopt = "1.0" env_logger = "0.5" @@ -23,7 +24,6 @@ rustc-hex = "1.0" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" -account-state = { path = "../ethcore/account-state" } trace = { path = "../ethcore/trace" } vm = { path = "../ethcore/vm" } diff --git a/evmbin/README.md b/evmbin/README.md index 936522977..1a4396ec0 100644 --- a/evmbin/README.md +++ b/evmbin/README.md @@ -30,7 +30,7 @@ Transaction options: --gas-price WEI Supplied gas price as hex (without 0x). State test options: - --only NAME Runs only a single test matching the name. + --only NAME Runs only a single state test matching the name. --chain CHAIN Run only tests from specific chain. General options: diff --git a/evmbin/res/create2callPrecompiles.json b/evmbin/res/create2callPrecompiles.json new file mode 100644 index 000000000..eb7487625 --- /dev/null +++ b/evmbin/res/create2callPrecompiles.json @@ -0,0 +1,140 @@ +{ + "create2callPrecompiles": { + "_info": { + "comment": "CALL precompiles during init code of CREATE2 contract ", + "filledwith": "testeth 1.5.0.dev2-73+commit.1bcd29e5", + "lllcversion": "Version: 0.4.26-develop.2018.9.19+commit.785cbf40.Linux.g++", + "source": "src/GeneralStateTestsFiller/stCreate2/create2callPrecompilesFiller.json", + "sourceHash": "0dcd6d7b5819f61399ecfba9a67b7518c92c426866bd5b599516c184d194e51c" + }, + "env": { + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty": "0x20000", + "currentGasLimit": "0xe8d4a51000", + "currentNumber": "0x01", + "currentTimestamp": "0x03e8", + "previousHash": "0x5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6" + }, + "post": { + "Constantinople": [ + { + "hash": "0x3dfdcd1d19badbbba8b0c953504e8b4685270ee5b86e155350b6ef1042c9ce43", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + }, + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "hash": "0x88803085d3420aec76078e215f67fc5f7b6f297fbe19d85c2236ad685d0fc7fc", + "indexes": { + "data": 1, + "gas": 0, + "value": 0 + }, + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "hash": "0x57181dda5c067cb31f084c4118791b40d5028c39071e83e60e7f7403d683527e", + "indexes": { + "data": 2, + "gas": 0, + "value": 0 + }, + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "hash": "0xf04c1039893eb6959354c3c16e9fe025d4b9dc3981362f79c56cc427dca0d544", + "indexes": { + "data": 3, + "gas": 0, + "value": 0 + }, + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "hash": "0x5d5db3d6c4377b34b74ecf8638f684acb220cc7ce286ae5f000ffa74faf38bae", + "indexes": { + "data": 4, + "gas": 0, + "value": 0 + }, + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "hash": "0xf8343b2e05ae120bf25947de840cedf1ca2c1bcda1cdb89d218427d8a84d4798", + "indexes": { + "data": 5, + "gas": 0, + "value": 0 + }, + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "hash": "0x305a8a8a7d9da97d14ed2259503d9373d803ea4b7fbf8c360f50b1b30a3d04ed", + "indexes": { + "data": 6, + "gas": 0, + "value": 0 + }, + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "hash": "0xde1d3953b508913c6e3e9bd412cd50daf60bb177517e5d1e8ccb0dab193aed03", + "indexes": { + "data": 7, + "gas": 0, + "value": 0 + }, + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + } + ] + }, + "pre": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x0de0b6b3a7640000", + "code": "", + "nonce": "0x00", + "storage": { + } + }, + "0xaddf5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x00", + "code": "0x600035600052602035602052604035604052606035606052604060c860806000600060066207a120f260005560c85160015560e851600255", + "nonce": "0x00", + "storage": { + } + }, + "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x00", + "code": "0x60003560005260203560205260403560405260603560605260803560805260a03560a05260c03560c052604061012c60806000600060066207a120f2600055604061019060606080600060076207a120f260015561012c51600a5561014c51600b55610190516014556101b051601555601454600a5414600255601554600b5414600355", + "nonce": "0x00", + "storage": { + } + } + }, + "transaction": { + "data": [ + "0x6000609a80601260003960006000f55000fe7f18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c600052601c6020527f73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f6040527feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549606052602060806080600060006001620493e0f160025560a060020a60805106600055600054321460015500", + "0x6000602380601260003960006000f55000fe64f34578907f6005526020600060256000600060026101f4f160025560005160005500", + "0x6000601a80601260003960006000f55000fe602060006000600060006003610258f160025560005160005500", + "0x6000602380601260003960006000f55000fe64f34578907f6000526020600060256000600060046101f4f160025560005160005500", + "0x6000609580601260003960006000f55000fe6001600052602060205260206040527f03fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc6060527f2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc6080527f2f0000000000000000000000000000000000000000000000000000000000000060965260206103e860976000600060055af26001556103e85160025500", + "0x6000602180601260003960006000f55000fe600160005260206000610100600060006006620927c0f160025560005160005500", + "0x600060b680601260003960006000f55000fe7f0f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd26000527f16da2f5cb6be7a0aa72c440c53c9bbdfec6c36c7d515536431b3a865468acbba6020527f1de49a4b0233273bba8146af82042d004f2085ec982397db0d97da17204cc2866040527f0217327ffc463919bef80cc166d09c6172639d8589799928761bcd9f22c903d46060526000600060806000600073addf5374fce5edbc8e2a8697c15331677e6ebf0b6207a120f25000", + "0x600060c580601260003960006000f55000fe7f1de49a4b0233273bba8146af82042d004f2085ec982397db0d97da17204cc2866000527f0217327ffc463919bef80cc166d09c6172639d8589799928761bcd9f22c903d4602052600060405260006060527f1de49a4b0233273bba8146af82042d004f2085ec982397db0d97da17204cc2866080527f0217327ffc463919bef80cc166d09c6172639d8589799928761bcd9f22c903d460a052600160c0526000600060e06000600073b94f5374fce5edbc8e2a8697c15331677e6ebf0b6207a120f25000" + ], + "gasLimit": [ + "0xe4e1c0" + ], + "gasPrice": "0x01", + "nonce": "0x00", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "to": "", + "value": [ + "0x01" + ] + } + } +} diff --git a/evmbin/res/teststate.json b/evmbin/res/teststate.json new file mode 100644 index 000000000..4e774112f --- /dev/null +++ b/evmbin/res/teststate.json @@ -0,0 +1,144 @@ +{ + "add11": { + "env": { + "currentCoinbase": "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty": "0x0100", + "currentGasLimit": "0x01c9c380", + "currentNumber": "0x00", + "currentTimestamp": "0x01", + "previousHash": "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6" + }, + "post": { + "EIP150": [ + { + "hash": "3e6dacc1575c6a8c76422255eca03529bbf4c0dda75dfc110b22d6dc4152396f", + "indexes": { "data": 0, "gas": 0, "value": 0 } + }, + { + "hash": "99a450d8ce5b987a71346d8a0a1203711f770745c7ef326912e46761f14cd764", + "indexes": { "data": 0, "gas": 0, "value": 1 } + } + ], + "EIP158": [ + { + "hash": "3e6dacc1575c6a8c76422255eca03529bbf4c0dda75dfc110b22d6dc4152396f", + "indexes": { "data": 0, "gas": 0, "value": 0 } + }, + { + "hash": "99a450d8ce5b987a71346d8a0a1203711f770745c7ef326912e46761f14cd764", + "indexes": { "data": 0, "gas": 0, "value": 1 } + } + ] + }, + "pre": { + "1000000000000000000000000000000000000000": { + "balance": "0x0de0b6b3a7640000", + "code": "0x6040600060406000600173100000000000000000000000000000000000000162055730f1600055", + "nonce": "0x00", + "storage": { + } + }, + "1000000000000000000000000000000000000001": { + "balance": "0x0de0b6b3a7640000", + "code": "0x604060006040600060027310000000000000000000000000000000000000026203d090f1600155", + "nonce": "0x00", + "storage": { + } + }, + "1000000000000000000000000000000000000002": { + "balance": "0x00", + "code": "0x600160025533600455346007553060e6553260e8553660ec553860ee553a60f055", + "nonce": "0x00", + "storage": { + } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x0de0b6b3a7640000", + "code": "0x", + "nonce": "0x00", + "storage": { + } + } + }, + "transaction": { + "data": [ "" ], + "gasLimit": [ "285000", "100000", "6000" ], + "gasPrice": "0x01", + "nonce": "0x00", + "secretKey": "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "to": "095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value": [ "10", "0" ] + } + }, + "add12": { + "env": { + "currentCoinbase": "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty": "0x0100", + "currentGasLimit": "0x01c9c380", + "currentNumber": "0x00", + "currentTimestamp": "0x01", + "previousHash": "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6" + }, + "post": { + "EIP150": [ + { + "hash": "3e6dacc1575c6a8c76422255eca03529bbf4c0dda75dfc110b22d6dc4152396f", + "indexes": { "data": 0, "gas": 0, "value": 0 } + }, + { + "hash": "99a450d8ce5b987a71346d8a0a1203711f770745c7ef326912e46761f14cd764", + "indexes": { "data": 0, "gas": 0, "value": 1 } + } + ], + "EIP158": [ + { + "hash": "3e6dacc1575c6a8c76422255eca03529bbf4c0dda75dfc110b22d6dc4152396f", + "indexes": { "data": 0, "gas": 0, "value": 0 } + }, + { + "hash": "99a450d8ce5b987a71346d8a0a1203711f770745c7ef326912e46761f14cd764", + "indexes": { "data": 0, "gas": 0, "value": 1 } + } + ] + }, + "pre": { + "1000000000000000000000000000000000000000": { + "balance": "0x0de0b6b3a7640000", + "code": "0x6040600060406000600173100000000000000000000000000000000000000162055730f1600055", + "nonce": "0x00", + "storage": { + } + }, + "1000000000000000000000000000000000000001": { + "balance": "0x0de0b6b3a7640000", + "code": "0x604060006040600060027310000000000000000000000000000000000000026203d090f1600155", + "nonce": "0x00", + "storage": { + } + }, + "1000000000000000000000000000000000000002": { + "balance": "0x00", + "code": "0x600160025533600455346007553060e6553260e8553660ec553860ee553a60f055", + "nonce": "0x00", + "storage": { + } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x0de0b6b3a7640000", + "code": "0x", + "nonce": "0x00", + "storage": { + } + } + }, + "transaction": { + "data": [ "" ], + "gasLimit": [ "285000", "100000", "6000" ], + "gasPrice": "0x01", + "nonce": "0x00", + "secretKey": "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "to": "095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value": [ "10", "0" ] + } + } +} diff --git a/evmbin/src/display/json.rs b/evmbin/src/display/json.rs index 0989f9a08..b25d2bbb2 100644 --- a/evmbin/src/display/json.rs +++ b/evmbin/src/display/json.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Ethereum. If not, see . -//! JSON VM output. +//! Log EVM instruction output data traces from a JSON formatting informant. use std::collections::HashMap; use std::mem; diff --git a/evmbin/src/display/mod.rs b/evmbin/src/display/mod.rs index 32b45c569..98a1a5b86 100644 --- a/evmbin/src/display/mod.rs +++ b/evmbin/src/display/mod.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Ethereum. If not, see . -//! VM Output display utils. +//! EVM output display utils. use std::time::Duration; diff --git a/evmbin/src/display/simple.rs b/evmbin/src/display/simple.rs index 684571835..7bc3a0e7c 100644 --- a/evmbin/src/display/simple.rs +++ b/evmbin/src/display/simple.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Ethereum. If not, see . -//! Simple VM output. +//! Log EVM instruction output data traces from a simple formatting informant. use trace; use bytes::ToPretty; diff --git a/evmbin/src/display/std_json.rs b/evmbin/src/display/std_json.rs index 478cc24a8..340cae4e1 100644 --- a/evmbin/src/display/std_json.rs +++ b/evmbin/src/display/std_json.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Ethereum. If not, see . -//! Standardized JSON VM output. +//! Log EVM instruction output data traces from a standardized JSON formatting informant. use std::collections::HashMap; use std::io; @@ -117,14 +117,14 @@ impl Default for Informant { } impl Informant { - /// std json informant using out only. + /// Standardized JSON formatting informant using stdout only. pub fn out_only() -> Self { Self::new(io::stdout(), io::stdout()) } } impl Informant { - /// std json informant using err only. + /// Standardized JSON formatting informant using stderr only. pub fn err_only() -> Self { Self::new(io::stderr(), io::stderr()) } diff --git a/evmbin/src/info.rs b/evmbin/src/info.rs index ed6d1fd0d..b95c2ec5a 100644 --- a/evmbin/src/info.rs +++ b/evmbin/src/info.rs @@ -14,19 +14,19 @@ // You should have received a copy of the GNU General Public License // along with Parity Ethereum. If not, see . -//! VM runner. +//! EVM runner. use std::time::{Instant, Duration}; -use ethereum_types::{H256, U256}; use ethcore::client::{self, EvmTestClient, EvmTestError, TransactErr, TransactSuccess}; use ethcore::{spec, TrieSpec}; -use trace; +use ethereum_types::{H256, U256}; use ethjson; use pod::PodState; +use trace; use types::transaction; use vm::ActionParams; -/// VM execution informant +/// EVM execution informant. pub trait Informant: trace::VMTracer { /// Sink to use with finish type Sink; @@ -40,41 +40,41 @@ pub trait Informant: trace::VMTracer { fn finish(result: RunResult, &mut Self::Sink); } -/// Execution finished correctly +/// Execution finished correctly. #[derive(Debug)] pub struct Success { - /// State root + /// State root. pub state_root: H256, - /// Used gas + /// Used gas. pub gas_used: U256, - /// Output as bytes + /// Output as bytes. pub output: Vec, - /// Time Taken + /// Time taken. pub time: Duration, - /// Traces + /// Traces. pub traces: Option, /// Optional end state dump pub end_state: Option, } -/// Execution failed +/// Execution failed. #[derive(Debug)] pub struct Failure { - /// State root + /// State root. pub state_root: H256, - /// Used gas + /// Used gas. pub gas_used: U256, - /// Internal error + /// Internal error. pub error: EvmTestError, - /// Duration + /// Duration. pub time: Duration, - /// Traces + /// Traces. pub traces: Option, /// Optional end state dump pub end_state: Option, } -/// EVM Execution result +/// EVM execution result. pub type RunResult = Result, Failure>; /// Execute given `ActionParams` and return the result. @@ -102,35 +102,62 @@ pub fn run_action( }) } -/// Execute given Transaction and verify resulting state root. +/// 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( - name: &str, - idx: usize, - spec: ðjson::spec::ForkSpec, - pre_state: &PodState, - post_root: H256, - env_info: &client::EnvInfo, - transaction: transaction::SignedTransaction, - mut informant: T, - trie_spec: TrieSpec, -) { - let spec_name = format!("{:?}", spec).to_lowercase(); - let spec = match EvmTestClient::spec_from_json(spec) { + 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!("{}:{}:{}", name, spec_name, idx), "starting"); + informant.before_test( + &format!("{}:{}:{}", &state_test_name, &fork_spec_name_formatted, tx_index), "starting"); spec }, None => { - informant.before_test(&format!("{}:{}:{}", name, spec_name, idx), "skipping because of missing spec"); - return; + 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(&spec, trie_spec, transaction.gas, pre_state, |mut client| { - let result = client.transact(env_info, transaction, trace::NoopTracer, informant); + 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 { @@ -151,10 +178,12 @@ pub fn run_transaction( } }); - T::finish(result, &mut sink) + let ok = result.is_ok(); + T::finish(result, &mut sink); + ok } -/// Execute VM with given `ActionParams` +/// Execute EVM with given `ActionParams`. pub fn run<'a, F, X>( spec: &'a spec::Spec, trie_spec: TrieSpec, diff --git a/evmbin/src/main.rs b/evmbin/src/main.rs index 845642c60..c6a402ac0 100644 --- a/evmbin/src/main.rs +++ b/evmbin/src/main.rs @@ -34,24 +34,24 @@ #![warn(missing_docs)] +extern crate account_state; extern crate common_types as types; +extern crate docopt; +extern crate env_logger; extern crate ethcore; +extern crate ethereum_types; extern crate ethjson; +extern crate evm; +extern crate panic_hook; +extern crate parity_bytes as bytes; +extern crate pod; extern crate rustc_hex; extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_json; -extern crate docopt; -extern crate parity_bytes as bytes; -extern crate ethereum_types; -extern crate vm; -extern crate evm; -extern crate panic_hook; -extern crate pod; -extern crate env_logger; -extern crate account_state; extern crate trace; +extern crate vm; #[cfg(test)] #[macro_use] @@ -73,24 +73,25 @@ use vm::{ActionParams, CallType}; mod info; mod display; -use info::Informant; +use info::{Informant, TxInput}; const USAGE: &'static str = r#" EVM implementation for Parity. Copyright 2015-2019 Parity Technologies (UK) Ltd. Usage: - parity-evm state-test [--json --std-json --std-dump-json --only NAME --chain CHAIN --std-out-only --std-err-only] + parity-evm state-test [--chain CHAIN --only NAME --json --std-json --std-dump-json --std-out-only --std-err-only] parity-evm stats [options] parity-evm stats-jsontests-vm parity-evm [options] parity-evm [-h | --help] Commands: - state-test Run a state test from a json file. + state-test Run a state test on a provided state test JSON file. stats Execute EVM runtime code and return the statistics. - stats-jsontests-vm Execute standard json-tests format VMTests and return - timing statistics in tsv format. + stats-jsontests-vm Execute standard json-tests on a provided state test JSON + file path, format VMTests, and return timing statistics + in tsv format. Transaction options: --code CODE Contract code as hex (without 0x). @@ -101,18 +102,20 @@ Transaction options: --gas-price WEI Supplied gas price as hex (without 0x). State test options: + --chain CHAIN Run only from specific chain name (i.e. one of EIP150, EIP158, + Frontier, Homestead, Byzantium, Constantinople, + ConstantinopleFix, EIP158ToByzantiumAt5, FrontierToHomesteadAt5, + HomesteadToDaoAt5, HomesteadToEIP150At5). --only NAME Runs only a single test matching the name. - --chain CHAIN Run only tests from specific chain. General options: + --chain PATH Path to chain spec file. --json Display verbose results in JSON. --std-json Display results in standardized JSON format. - --std-err-only With --std-json redirect to err output only. - --std-out-only With --std-json redirect to out output only. --std-dump-json Display results in standardized JSON format with additional state dump. -Display result state dump in standardized JSON format. - --chain CHAIN Chain spec file path. + --std-err-only With --std-json redirect to err output only. + --std-out-only With --std-json redirect to out output only. -h, --help Display this message and exit. "#; @@ -141,12 +144,156 @@ fn main() { } } +fn run_state_test(args: Args) { + use ethjson::state::test::Test; + + // Parse the specified state test JSON file provided to the command `state-test `. + let file = args.arg_file.expect("PATH to a state test JSON file is required"); + let mut file = match fs::File::open(&file) { + Err(err) => die(format!("Unable to open path: {:?}: {}", file, err)), + Ok(file) => file, + }; + let state_test = match Test::load(&mut file) { + Err(err) => die(format!("Unable to load the test file: {}", err)), + Ok(test) => test, + }; + // Parse the name CLI option `--only NAME`. + let only_test = args.flag_only.map(|s| s.to_lowercase()); + // Parse the chain `--chain CHAIN` + let only_chain = args.flag_chain.map(|s| s.to_lowercase()); + + // Iterate over 1st level (outer) key-value pair of the state test JSON file. + // Skip to next iteration if CLI option `--only NAME` was parsed into `only_test` and does not match + // the current key `state_test_name` (i.e. add11, create2callPrecompiles). + for (state_test_name, test) in state_test { + if let Some(false) = only_test.as_ref().map(|only_test| { + &state_test_name.to_lowercase() == only_test + }) { + continue; + } + + // Assign from 2nd level key-value pairs of the state test JSON file (i.e. env, post, pre, transaction). + let multitransaction = test.transaction; + let env_info = test.env.into(); + let pre = test.pre_state.into(); + + // Iterate over remaining "post" key of the 2nd level key-value pairs in the state test JSON file. + // Skip to next iteration if CLI option `--chain CHAIN` was parsed into `only_chain` and does not match + // the current key `fork_spec_name` (i.e. Constantinople, EIP150, EIP158). + for (fork_spec_name, states) in test.post_states { + if let Some(false) = only_chain.as_ref().map(|only_chain| { + &format!("{:?}", fork_spec_name).to_lowercase() == only_chain + }) { + continue; + } + + // Iterate over the 3rd level key-value pairs of the state test JSON file + // (i.e. list of transactions and associated state roots hashes corresponding each chain). + for (tx_index, state) in states.into_iter().enumerate() { + let post_root = state.hash.into(); + let transaction = multitransaction.select(&state.indexes).into(); + + // Determine the type of trie with state root to create in the database. + // The database is a key-value datastore implemented as a database-backend + // modified Merkle tree. + // Use a secure trie database specification when CLI option `--std-dump-json` + // is specified, otherwise use secure trie with fat trie database. + let trie_spec = if args.flag_std_dump_json { + TrieSpec::Fat + } else { + TrieSpec::Secure + }; + + // Execute the given transaction and verify resulting state root + // for CLI option `--std-dump-json` or `--std-json`. + if args.flag_std_dump_json || args.flag_std_json { + if args.flag_std_err_only { + let tx_input = TxInput { + state_test_name: &state_test_name, + tx_index, + fork_spec_name: &fork_spec_name, + pre_state: &pre, + post_root, + env_info: &env_info, + transaction, + informant: display::std_json::Informant::err_only(), + trie_spec, + }; + // Use Standard JSON informant with err only + info::run_transaction(tx_input); + } else if args.flag_std_out_only { + let tx_input = TxInput { + state_test_name: &state_test_name, + tx_index, + fork_spec_name: &fork_spec_name, + pre_state: &pre, + post_root, + env_info: &env_info, + transaction, + informant: display::std_json::Informant::out_only(), + trie_spec, + }; + // Use Standard JSON informant with out only + info::run_transaction(tx_input); + } else { + let tx_input = TxInput { + state_test_name: &state_test_name, + tx_index, + fork_spec_name: &fork_spec_name, + pre_state: &pre, + post_root, + env_info: &env_info, + transaction, + informant: display::std_json::Informant::default(), + trie_spec, + }; + // Use Standard JSON informant default + info::run_transaction(tx_input); + } + } else { + // Execute the given transaction and verify resulting state root + // for CLI option `--json`. + if args.flag_json { + let tx_input = TxInput { + state_test_name: &state_test_name, + tx_index, + fork_spec_name: &fork_spec_name, + pre_state: &pre, + post_root, + env_info: &env_info, + transaction, + informant: display::json::Informant::default(), + trie_spec, + }; + // Use JSON informant + info::run_transaction(tx_input); + } else { + let tx_input = TxInput { + state_test_name: &state_test_name, + tx_index, + fork_spec_name: &fork_spec_name, + pre_state: &pre, + post_root, + env_info: &env_info, + transaction, + informant: display::simple::Informant::default(), + trie_spec, + }; + // Use Simple informant + info::run_transaction(tx_input); + } + } + } + } + } +} + fn run_stats_jsontests_vm(args: Args) { use json_tests::HookType; use std::collections::HashMap; use std::time::{Instant, Duration}; - let file = args.arg_file.expect("FILE (or PATH) is required"); + let file = args.arg_file.expect("PATH to a state test JSON file is required"); let mut timings: HashMap)> = HashMap::new(); @@ -175,70 +322,15 @@ fn run_stats_jsontests_vm(args: Args) { } } -fn run_state_test(args: Args) { - use ethjson::state::test::Test; - - let file = args.arg_file.expect("FILE is required"); - let mut file = match fs::File::open(&file) { - Err(err) => die(format!("Unable to open: {:?}: {}", file, err)), - Ok(file) => file, - }; - let state_test = match Test::load(&mut file) { - Err(err) => die(format!("Unable to load the test file: {}", err)), - Ok(test) => test, - }; - let only_test = args.flag_only.map(|s| s.to_lowercase()); - let only_chain = args.flag_chain.map(|s| s.to_lowercase()); - - for (name, test) in state_test { - if let Some(false) = only_test.as_ref().map(|only_test| &name.to_lowercase() == only_test) { - continue; - } - - let multitransaction = test.transaction; - let env_info = test.env.into(); - let pre = test.pre_state.into(); - - for (spec, states) in test.post_states { - if let Some(false) = only_chain.as_ref().map(|only_chain| &format!("{:?}", spec).to_lowercase() == only_chain) { - continue; - } - - for (idx, state) in states.into_iter().enumerate() { - let post_root = state.hash.into(); - let transaction = multitransaction.select(&state.indexes).into(); - - let trie_spec = if args.flag_std_dump_json { - TrieSpec::Fat - } else { - TrieSpec::Secure - }; - if args.flag_json { - info::run_transaction(&name, idx, &spec, &pre, post_root, &env_info, transaction, display::json::Informant::default(), trie_spec) - } else if args.flag_std_dump_json || args.flag_std_json { - if args.flag_std_err_only { - info::run_transaction(&name, idx, &spec, &pre, post_root, &env_info, transaction, display::std_json::Informant::err_only(), trie_spec) - } else if args.flag_std_out_only { - info::run_transaction(&name, idx, &spec, &pre, post_root, &env_info, transaction, display::std_json::Informant::out_only(), trie_spec) - } else { - info::run_transaction(&name, idx, &spec, &pre, post_root, &env_info, transaction, display::std_json::Informant::default(), trie_spec) - } - } else { - info::run_transaction(&name, idx, &spec, &pre, post_root, &env_info, transaction, display::simple::Informant::default(), trie_spec) - } - } - } - } -} - +// CLI command `stats` fn run_call(args: Args, informant: T) { - let from = arg(args.from(), "--from"); - let to = arg(args.to(), "--to"); let code = arg(args.code(), "--code"); - let spec = arg(args.spec(), "--chain"); + let to = arg(args.to(), "--to"); + let from = arg(args.from(), "--from"); + let data = arg(args.data(), "--input"); let gas = arg(args.gas(), "--gas"); let gas_price = arg(args.gas_price(), "--gas-price"); - let data = arg(args.data(), "--input"); + let spec = arg(args.spec(), "--chain"); if code.is_none() && to == Address::zero() { die("Either --code or --to is required."); @@ -246,14 +338,14 @@ fn run_call(args: Args, informant: T) { let mut params = ActionParams::default(); params.call_type = if code.is_none() { CallType::Call } else { CallType::None }; + params.code = code.map(Arc::new); params.code_address = to; params.address = to; params.sender = from; params.origin = from; + params.data = data; params.gas = gas; params.gas_price = gas_price; - params.code = code.map(Arc::new); - params.data = data; let mut sink = informant.clone_sink(); let result = if args.flag_std_dump_json { @@ -270,13 +362,13 @@ struct Args { cmd_state_test: bool, cmd_stats_jsontests_vm: bool, arg_file: Option, - flag_only: Option, - flag_from: Option, - flag_to: Option, flag_code: Option, + flag_to: Option, + flag_from: Option, + flag_input: Option, flag_gas: Option, flag_gas_price: Option, - flag_input: Option, + flag_only: Option, flag_chain: Option, flag_json: bool, flag_std_json: bool, @@ -286,7 +378,44 @@ struct Args { } impl Args { - /// Set the gas limit. Defaults to max value to allow code to run for whatever time is required. + // CLI option `--code CODE` + /// Set the contract code in hex. Only send to either a contract code or a recipient address. + pub fn code(&self) -> Result, String> { + match self.flag_code { + Some(ref code) => code.from_hex().map(Some).map_err(to_string), + None => Ok(None), + } + } + + // CLI option `--to ADDRESS` + /// Set the recipient address in hex. Only send to either a contract code or a recipient address. + pub fn to(&self) -> Result { + match self.flag_to { + Some(ref to) => to.parse().map_err(to_string), + None => Ok(Address::zero()), + } + } + + // CLI option `--from ADDRESS` + /// Set the sender address. + pub fn from(&self) -> Result { + match self.flag_from { + Some(ref from) => from.parse().map_err(to_string), + None => Ok(Address::zero()), + } + } + + // CLI option `--input DATA` + /// Set the input data in hex. + pub fn data(&self) -> Result, String> { + match self.flag_input { + Some(ref input) => input.from_hex().map_err(to_string).map(Some), + None => Ok(None), + } + } + + // CLI option `--gas GAS` + /// Set the gas limit in units of gas. Defaults to max value to allow code to run for whatever time is required. pub fn gas(&self) -> Result { match self.flag_gas { Some(ref gas) => gas.parse().map_err(to_string), @@ -294,6 +423,7 @@ impl Args { } } + // CLI option `--gas-price WEI` /// Set the gas price. Defaults to zero to allow the code to run even if an account with no balance /// is used, otherwise such accounts would not have sufficient funds to pay the transaction fee. /// Defaulting to zero also makes testing easier since it is not necessary to specify a special configuration file. @@ -304,34 +434,8 @@ impl Args { } } - pub fn from(&self) -> Result { - match self.flag_from { - Some(ref from) => from.parse().map_err(to_string), - None => Ok(Address::zero()), - } - } - - pub fn to(&self) -> Result { - match self.flag_to { - Some(ref to) => to.parse().map_err(to_string), - None => Ok(Address::zero()), - } - } - - pub fn code(&self) -> Result, String> { - match self.flag_code { - Some(ref code) => code.from_hex().map(Some).map_err(to_string), - None => Ok(None), - } - } - - pub fn data(&self) -> Result, String> { - match self.flag_input { - Some(ref input) => input.from_hex().map_err(to_string).map(Some), - None => Ok(None), - } - } - + // CLI option `--chain PATH` + /// Set the path of the chain specification JSON file. pub fn spec(&self) -> Result { Ok(match self.flag_chain { Some(ref filename) => { @@ -360,8 +464,29 @@ fn die(msg: T) -> ! { #[cfg(test)] mod tests { + use std::str::FromStr; use docopt::Docopt; use super::{Args, USAGE, Address}; + use ethjson::state::test::{State}; + use ethcore::{TrieSpec}; + use ethereum_types::{H256}; + use types::transaction; + + use info; + use info::{TxInput}; + use display; + + #[derive(Debug, PartialEq, Deserialize)] + pub struct SampleStateTests { + pub add11: State, + pub add12: State, + } + + #[derive(Debug, PartialEq, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ConstantinopleStateTests { + pub create2call_precompiles: State, + } fn run>(args: &[T]) -> Args { Docopt::new(USAGE).and_then(|d| d.argv(args.into_iter()).deserialize()).unwrap() @@ -371,30 +496,32 @@ mod tests { fn should_parse_all_the_options() { let args = run(&[ "parity-evm", + "--code", "05", + "--to", "0000000000000000000000000000000000000004", + "--from", "0000000000000000000000000000000000000003", + "--input", "06", + "--gas", "1", + "--gas-price", "2", + "--chain", "./testfile.json", "--json", "--std-json", "--std-dump-json", - "--gas", "1", - "--gas-price", "2", - "--from", "0000000000000000000000000000000000000003", - "--to", "0000000000000000000000000000000000000004", - "--code", "05", - "--input", "06", - "--chain", "./testfile", "--std-err-only", "--std-out-only" + "--std-err-only", + "--std-out-only", ]); + assert_eq!(args.code(), Ok(Some(vec![05]))); + assert_eq!(args.to(), Ok(Address::from_low_u64_be(4))); + assert_eq!(args.from(), Ok(Address::from_low_u64_be(3))); + assert_eq!(args.data(), Ok(Some(vec![06]))); // input data + assert_eq!(args.gas(), Ok(1.into())); + assert_eq!(args.gas_price(), Ok(2.into())); + assert_eq!(args.flag_chain, Some("./testfile.json".to_owned())); assert_eq!(args.flag_json, true); assert_eq!(args.flag_std_json, true); assert_eq!(args.flag_std_dump_json, true); assert_eq!(args.flag_std_err_only, true); assert_eq!(args.flag_std_out_only, true); - assert_eq!(args.gas(), Ok(1.into())); - assert_eq!(args.gas_price(), Ok(2.into())); - assert_eq!(args.from(), Ok(Address::from_low_u64_be(3))); - assert_eq!(args.to(), Ok(Address::from_low_u64_be(4))); - assert_eq!(args.code(), Ok(Some(vec![05]))); - assert_eq!(args.data(), Ok(Some(vec![06]))); - assert_eq!(args.flag_chain, Some("./testfile".to_owned())); } #[test] @@ -407,15 +534,116 @@ mod tests { "--only=add11", "--json", "--std-json", - "--std-dump-json" + "--std-dump-json", + "--std-out-only", + "--std-err-only", ]); assert_eq!(args.cmd_state_test, true); assert!(args.arg_file.is_some()); + assert_eq!(args.flag_chain, Some("homestead".to_owned())); + assert_eq!(args.flag_only, Some("add11".to_owned())); assert_eq!(args.flag_json, true); assert_eq!(args.flag_std_json, true); assert_eq!(args.flag_std_dump_json, true); - assert_eq!(args.flag_chain, Some("homestead".to_owned())); - assert_eq!(args.flag_only, Some("add11".to_owned())); + assert_eq!(args.flag_std_out_only, true); + assert_eq!(args.flag_std_err_only, true); + } + + #[test] + fn should_verify_state_root_using_sample_state_test_json_file() { + let state_tests = include_str!("../res/teststate.json"); + // Parse the specified state test JSON file to simulate the CLI command `state-test `. + let deserialized_state_tests: SampleStateTests = serde_json::from_str(state_tests) + .expect("Serialization cannot fail; qed"); + + // Simulate the name CLI option `--only NAME` + let state_test_name = "add11"; + // Simulate the chain `--chain CHAIN` + let pre = deserialized_state_tests.add11.pre_state.into(); + let env_info = deserialized_state_tests.add11.env.into(); + let multitransaction = deserialized_state_tests.add11.transaction; + + let post_roots = [ + // EIP-150 + [ + H256::from_str("f4455d9332a9e171fc41b48350457147c21fc0a92364d9925913f7421e15aa95").unwrap(), + H256::from_str("a0bc824c4186c4c1543851894fbf707b5b1cf771d15e74f3517daf0a3415fe5b").unwrap(), + ], + // EIP-158 + [ + H256::from_str("f4455d9332a9e171fc41b48350457147c21fc0a92364d9925913f7421e15aa95").unwrap(), + H256::from_str("27682055e1899031c92d253ee1d22c40f70a6943724168c0b694a1a503664e0a").unwrap(), + ], + ]; + for (fork_index, (fork_spec_name, tx_states)) in deserialized_state_tests.add11.post_states.iter().enumerate() { + for (tx_index, tx_state) in tx_states.into_iter().enumerate() { + let post_root = post_roots[fork_index][tx_index]; + let informant = display::json::Informant::default(); + let trie_spec = TrieSpec::Secure; // TrieSpec::Fat for --std_dump_json + let transaction: transaction::SignedTransaction = multitransaction.select(&tx_state.indexes).into(); + let tx_input = TxInput { + state_test_name: &state_test_name, + tx_index, + fork_spec_name: &fork_spec_name, + pre_state: &pre, + post_root, + env_info: &env_info, + transaction, + informant, + trie_spec, + }; + assert!(info::run_transaction(tx_input)); + } + } + } + + #[test] + fn should_verify_state_root_using_constantinople_state_test_json_file() { + let state_tests = include_str!("../res/create2callPrecompiles.json"); + // Parse the specified state test JSON file to simulate the CLI command `state-test `. + let deserialized_state_tests: ConstantinopleStateTests = serde_json::from_str(state_tests) + .expect("Serialization cannot fail; qed"); + + // Simulate the name CLI option `--only NAME` + let state_test_name = "create2callPrecompiles"; + let post_roots = [ + // Constantinople + [ + H256::from_str("3dfdcd1d19badbbba8b0c953504e8b4685270ee5b86e155350b6ef1042c9ce43").unwrap(), + H256::from_str("88803085d3420aec76078e215f67fc5f7b6f297fbe19d85c2236ad685d0fc7fc").unwrap(), + H256::from_str("57181dda5c067cb31f084c4118791b40d5028c39071e83e60e7f7403d683527e").unwrap(), + H256::from_str("f04c1039893eb6959354c3c16e9fe025d4b9dc3981362f79c56cc427dca0d544").unwrap(), + H256::from_str("5d5db3d6c4377b34b74ecf8638f684acb220cc7ce286ae5f000ffa74faf38bae").unwrap(), + H256::from_str("f8343b2e05ae120bf25947de840cedf1ca2c1bcda1cdb89d218427d8a84d4798").unwrap(), + H256::from_str("305a8a8a7d9da97d14ed2259503d9373d803ea4b7fbf8c360f50b1b30a3d04ed").unwrap(), + H256::from_str("de1d3953b508913c6e3e9bd412cd50daf60bb177517e5d1e8ccb0dab193aed03").unwrap(), + ], + ]; + let pre = deserialized_state_tests.create2call_precompiles.pre_state.into(); + let env_info = deserialized_state_tests.create2call_precompiles.env.into(); + let multitransaction = deserialized_state_tests.create2call_precompiles.transaction; + for (fork_index, (fork_spec_name, tx_states)) in + deserialized_state_tests.create2call_precompiles.post_states.iter().enumerate() { + for (tx_index, tx_state) in tx_states.into_iter().enumerate() { + let informant = display::json::Informant::default(); + // Hash of latest transaction index in the chain + let post_root = post_roots[fork_index][tx_index]; + let trie_spec = TrieSpec::Secure; // TrieSpec::Fat for --std_dump_json + let transaction: transaction::SignedTransaction = multitransaction.select(&tx_state.indexes).into(); + let tx_input = TxInput { + state_test_name: &state_test_name, + tx_index, + fork_spec_name: &fork_spec_name, + pre_state: &pre, + post_root, + env_info: &env_info, + transaction, + informant, + trie_spec, + }; + assert!(info::run_transaction(tx_input)); + } + } } }