refactor: Refactor evmbin CLI (#10742)

* docs: Add comments to run_transaction arguments

* docs: Add general state test example from github.com/ethereum/test

* docs: Add state test file used in ethjson

* refactor: Reorder CLI options. Modify CLI descriptions. See commit comments

* Reorder parity-evm CLI options
* Update descriptions for CLI options
* Change to `--chain PATH` (general) and `--chain CHAIN` (state test)
* Remove unncessary 'Display result state dump in standardized JSON format.

* refactor: Move  function to be ordered after

* refactor: Refactor run_state_test

* refactor: Modify run_stats_jsontests_vm comment to be more specific

* refactor: Refactor run_call

* refactor: Update Args struct including rustdocs

* refactor: Reorder functions in Args struct to match other orders

* tests: Update tests for evmbin

* revert unintentional changes

* comply with style guide

* docs: Info and Display Modules made public so appear in rustdocs

* docs: Rename VM to EVM

* docs: Update rustdocs

* docs: Update state-test cli command comments

Co-Authored-By: David <dvdplm@gmail.com>

* docs: Update chain path cli command description

Co-Authored-By: David <dvdplm@gmail.com>

* docs: Prefix to specify only one chain type to be provided

Co-Authored-By: David <dvdplm@gmail.com>

* docs: Update to be lowercase fat

Co-Authored-By: David <dvdplm@gmail.com>

* rename err to stderr, out to stdout

* revert to wei for gas price

* review-fix: Do not expose private modules but still show docs

View docs with:
```
cargo doc -p evmbin --document-private-items --open
```

* test: Read from file. Add initial tests for state-test CLI command

* review-fix: Change to single TODO that links to new issue to create integration tests

* refactor: Move run_transaction params into fields of a TxInput struct and make doc comments of its fields (#10769)

* Question

* refactor: Further changes for doc comments to be part of public struct

* refactor: Rename InputData to TxInput for clarity in docs

* docs: Change String to fixed length str

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* refactor: Update evmbin/src/info.rs moving mut into fn declaration

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* refactor: Update evmbin/src/info.rs moving mut into fn declaration part 2

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* review-fix: Add missing docs to TxInput transaction and trie_spec

* docs: Improve grammar

* review-fix: Destructure tx_input

* WIP

* review-fix: Rename variables of InputTx

* rename `spec_from_json` to `fork_spec_from_json`
* rename `name` to `state_test_name`
* rename `spec` to `fork_spec_name`
* rename `spec_checked` to `fork_spec`

* review-fix: Rename idx to tx_index

* fix indentation

* review-fix: Add missing part of tests. Yet to fix tests and add assertions

* [evmbin] remove state-db dependency

* [evmbin] run_transaction returns bool

* [evmbin] more cleanup
This commit is contained in:
Luke Schoen 2019-08-07 16:51:08 +02:00 committed by David
parent 46954527e7
commit c689495826
15 changed files with 723 additions and 182 deletions

View File

@ -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<spec::Spec> {
pub fn fork_spec_from_json(spec: &ForkSpec) -> Option<spec::Spec> {
match *spec {
ForkSpec::Frontier => Some(ethereum::new_frontier_test()),
ForkSpec::Homestead => Some(ethereum::new_homestead_test()),

View File

@ -65,7 +65,7 @@ pub fn json_chain_test<H: FnMut(&str, HookType)>(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);

View File

@ -62,7 +62,7 @@ pub fn json_chain_test<H: FnMut(&str, HookType)>(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);

View File

@ -47,7 +47,7 @@ fn do_json_test<H: FnMut(&str, HookType)>(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);

View File

@ -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()
}

View File

@ -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" }

View File

@ -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:

View File

@ -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"
]
}
}
}

144
evmbin/res/teststate.json Normal file
View File

@ -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" ]
}
}
}

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
//! JSON VM output.
//! Log EVM instruction output data traces from a JSON formatting informant.
use std::collections::HashMap;
use std::mem;

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
//! VM Output display utils.
//! EVM output display utils.
use std::time::Duration;

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
//! Simple VM output.
//! Log EVM instruction output data traces from a simple formatting informant.
use trace;
use bytes::ToPretty;

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
//! 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<io::Stderr, io::Stdout> {
}
impl Informant<io::Stdout, io::Stdout> {
/// 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<io::Stderr, io::Stderr> {
/// std json informant using err only.
/// Standardized JSON formatting informant using stderr only.
pub fn err_only() -> Self {
Self::new(io::stderr(), io::stderr())
}

View File

@ -14,19 +14,19 @@
// You should have received a copy of the GNU General Public License
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
//! 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<Self::Output>, &mut Self::Sink);
}
/// Execution finished correctly
/// Execution finished correctly.
#[derive(Debug)]
pub struct Success<T> {
/// 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<u8>,
/// Time Taken
/// Time taken.
pub time: Duration,
/// Traces
/// Traces.
pub traces: Option<T>,
/// Optional end state dump
pub end_state: Option<PodState>,
}
/// Execution failed
/// Execution failed.
#[derive(Debug)]
pub struct Failure<T> {
/// 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<T>,
/// Optional end state dump
pub end_state: Option<PodState>,
}
/// EVM Execution result
/// EVM execution result.
pub type RunResult<T> = Result<Success<T>, Failure<T>>;
/// Execute given `ActionParams` and return the result.
@ -102,35 +102,62 @@ pub fn run_action<T: Informant>(
})
}
/// 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<T: Informant>(
name: &str,
idx: usize,
spec: &ethjson::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<T>
) -> 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: Informant>(
}
});
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,

View File

@ -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 <file> [--json --std-json --std-dump-json --only NAME --chain CHAIN --std-out-only --std-err-only]
parity-evm state-test <file> [--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 <file>
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 <file>`.
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<String, (Instant, Option<Duration>)> = 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<T: Informant>(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<T: Informant>(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<PathBuf>,
flag_only: Option<String>,
flag_from: Option<String>,
flag_to: Option<String>,
flag_code: Option<String>,
flag_to: Option<String>,
flag_from: Option<String>,
flag_input: Option<String>,
flag_gas: Option<String>,
flag_gas_price: Option<String>,
flag_input: Option<String>,
flag_only: Option<String>,
flag_chain: Option<String>,
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<Option<Bytes>, 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<Address, String> {
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<Address, String> {
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<Option<Bytes>, 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<U256, String> {
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<Address, String> {
match self.flag_from {
Some(ref from) => from.parse().map_err(to_string),
None => Ok(Address::zero()),
}
}
pub fn to(&self) -> Result<Address, String> {
match self.flag_to {
Some(ref to) => to.parse().map_err(to_string),
None => Ok(Address::zero()),
}
}
pub fn code(&self) -> Result<Option<Bytes>, 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<Option<Bytes>, 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<spec::Spec, String> {
Ok(match self.flag_chain {
Some(ref filename) => {
@ -360,8 +464,29 @@ fn die<T: fmt::Display>(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<T: AsRef<str>>(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 <file>`.
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 <file>`.
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));
}
}
}
}