Skip to content

Commit

Permalink
Reimplementation of jsontests in Apache-2 license (#209)
Browse files Browse the repository at this point in the history
* Basic state test decoding

* Basic run structure

* Init in-memory backend

* Implement Backend and FullBackend for InMemoryBackend

* Init cloned backend

* Finish implementation of state root check

* Fix cargo fmt
  • Loading branch information
sorpaas committed Nov 10, 2023
1 parent b15f4b7 commit 4520fa6
Show file tree
Hide file tree
Showing 18 changed files with 876 additions and 60 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,5 @@ with-serde = [
[workspace]
members = [
"interpreter",
"jsontests",
]
2 changes: 1 addition & 1 deletion interpreter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ edition = "2018"

[dependencies]
log = { version = "0.4", optional = true }
primitive-types = { version = "0.12", default-features = false }
primitive-types = { version = "0.12", default-features = false, features = ["rlp"] }
scale-codec = { package = "parity-scale-codec", version = "3.2", default-features = false, features = ["derive", "full"], optional = true }
scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true }
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
Expand Down
4 changes: 2 additions & 2 deletions interpreter/src/call_create.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::utils::{h256_to_u256, u256_to_usize};
use crate::{
Context, ExitError, ExitException, ExitResult, Machine, Memory, Opcode, RuntimeFullBackend,
Context, ExitError, ExitException, ExitResult, Machine, Memory, Opcode, RuntimeBackend,
RuntimeState, Transfer,
};
use core::cmp::{max, min};
Expand All @@ -27,7 +27,7 @@ pub enum CreateScheme {
}

impl CreateScheme {
pub fn address<H: RuntimeFullBackend>(&self, handler: &H) -> H160 {
pub fn address<H: RuntimeBackend>(&self, handler: &H) -> H160 {
match self {
CreateScheme::Create2 {
caller,
Expand Down
18 changes: 18 additions & 0 deletions interpreter/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,24 @@ impl From<ExitError> for ExitResult {
}
}

#[cfg(feature = "std")]
impl std::error::Error for ExitError {
fn description(&self) -> &str {
match self {
Self::Exception(_) => "EVM exit exception",
Self::Reverted => "EVM internal revert",
Self::Fatal(_) => "EVM fatal error",
}
}
}

#[cfg(feature = "std")]
impl std::fmt::Display for ExitError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}

/// Exit succeed reason.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(
Expand Down
8 changes: 6 additions & 2 deletions interpreter/src/eval/system.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::Control;
use crate::{ExitException, ExitFatal, ExitSucceed, Machine, RuntimeBackend, RuntimeState};
use crate::{ExitException, ExitFatal, ExitSucceed, Log, Machine, RuntimeBackend, RuntimeState};
use alloc::vec::Vec;
use primitive_types::{H256, U256};
use sha3::{Digest, Keccak256};
Expand Down Expand Up @@ -313,7 +313,11 @@ pub fn log<S: AsRef<RuntimeState>, H: RuntimeBackend, Tr>(
}
}

match handler.log(machine.state.as_ref().context.address, topics, data) {
match handler.log(Log {
address: machine.state.as_ref().context.address,
topics,
data,
}) {
Ok(()) => Control::Continue,
Err(e) => Control::Exit(e.into()),
}
Expand Down
2 changes: 1 addition & 1 deletion interpreter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub use crate::eval::{Control, Efn, Etable};
pub use crate::memory::Memory;
pub use crate::opcode::Opcode;
pub use crate::runtime::{
CallCreateTrap, Context, RuntimeBackend, RuntimeFullBackend, RuntimeState, Transfer,
CallCreateTrap, Context, Log, RuntimeBackend, RuntimeBaseBackend, RuntimeState, Transfer,
};
pub use crate::stack::Stack;
pub use crate::valids::Valids;
Expand Down
39 changes: 27 additions & 12 deletions interpreter/src/runtime.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{ExitError, Opcode};
use primitive_types::{H160, H256, U256};
use sha3::{Digest, Keccak256};

/// Runtime state.
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -46,6 +47,14 @@ pub struct Transfer {
pub value: U256,
}

/// Log
#[derive(Clone, Debug)]
pub struct Log {
pub address: H160,
pub topics: Vec<H256>,
pub data: Vec<u8>,
}

pub trait CallCreateTrap: Sized {
fn call_create_trap(opcode: Opcode) -> Self;
}
Expand All @@ -56,7 +65,7 @@ impl CallCreateTrap for Opcode {
}
}

pub trait RuntimeBackend {
pub trait RuntimeBaseBackend {
/// Get environmental block hash.
fn block_hash(&self, number: U256) -> H256;
/// Get environmental block number.
Expand All @@ -83,18 +92,30 @@ pub trait RuntimeBackend {
/// Get balance of address.
fn balance(&self, address: H160) -> U256;
/// Get code size of address.
fn code_size(&self, address: H160) -> U256;
fn code_size(&self, address: H160) -> U256 {
U256::from(self.code(address).len())
}
/// Get code hash of address.
fn code_hash(&self, address: H160) -> H256;
fn code_hash(&self, address: H160) -> H256 {
H256::from_slice(&Keccak256::digest(&self.code(address)[..]))
}
/// Get code of address.
fn code(&self, address: H160) -> Vec<u8>;
/// Get storage value of address at index.
fn storage(&self, address: H160, index: H256) -> H256;
/// Get original storage value of address at index.
fn original_storage(&self, address: H160, index: H256) -> H256;

/// Check whether an address exists.
fn exists(&self, address: H160) -> bool;

/// Get the current nonce of an account.
fn nonce(&self, address: H160) -> U256;
}

/// The distinguish between `RuntimeBaseBackend` and `RuntimeBackend` is for the implementation of
/// overlays.
pub trait RuntimeBackend: RuntimeBaseBackend {
/// Get original storage value of address at index.
fn original_storage(&self, address: H160, index: H256) -> H256;
/// Check whether an address has already been deleted.
fn deleted(&self, address: H160) -> bool;
/// Checks if the address or (address, index) pair has been previously accessed.
Expand All @@ -103,16 +124,10 @@ pub trait RuntimeBackend {
fn mark_hot(&mut self, address: H160, index: Option<H256>) -> Result<(), ExitError>;
/// Set storage value of address at index.
fn set_storage(&mut self, address: H160, index: H256, value: H256) -> Result<(), ExitError>;

/// Create a log owned by address with given topics and data.
fn log(&mut self, address: H160, topics: Vec<H256>, data: Vec<u8>) -> Result<(), ExitError>;
fn log(&mut self, log: Log) -> Result<(), ExitError>;
/// Mark an address to be deleted, with funds transferred to target.
fn mark_delete(&mut self, address: H160, target: H160) -> Result<(), ExitError>;
}

pub trait RuntimeFullBackend: RuntimeBackend {
/// Get the current nonce of an account.
fn nonce(&self, address: H160) -> U256;
/// Fully delete storages of an account.
fn reset_storage(&mut self, address: H160);
/// Set code of an account.
Expand Down
43 changes: 36 additions & 7 deletions interpreter/tests/usability.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use evm_interpreter::{
Capture, Context, Control, Etable, ExitError, ExitSucceed, Machine, Opcode, RuntimeBackend,
RuntimeState,
Capture, Context, Control, Etable, ExitError, ExitSucceed, Log, Machine, Opcode,
RuntimeBackend, RuntimeBaseBackend, RuntimeState, Transfer,
};
use primitive_types::{H160, H256, U256};
use std::rc::Rc;
Expand Down Expand Up @@ -57,7 +57,7 @@ fn etable_wrap2() {

pub struct UnimplementedHandler;

impl<'a> RuntimeBackend for UnimplementedHandler {
impl<'a> RuntimeBaseBackend for UnimplementedHandler {
fn balance(&self, _address: H160) -> U256 {
unimplemented!()
}
Expand All @@ -73,9 +73,6 @@ impl<'a> RuntimeBackend for UnimplementedHandler {
fn storage(&self, _address: H160, _index: H256) -> H256 {
unimplemented!()
}
fn original_storage(&self, _address: H160, _index: H256) -> H256 {
unimplemented!()
}

fn gas_price(&self) -> U256 {
unimplemented!()
Expand Down Expand Up @@ -114,25 +111,57 @@ impl<'a> RuntimeBackend for UnimplementedHandler {
fn exists(&self, _address: H160) -> bool {
unimplemented!()
}

fn nonce(&self, _address: H160) -> U256 {
unimplemented!()
}
}

impl<'a> RuntimeBackend for UnimplementedHandler {
fn original_storage(&self, _address: H160, _index: H256) -> H256 {
unimplemented!()
}

fn deleted(&self, _address: H160) -> bool {
unimplemented!()
}
fn is_cold(&self, _address: H160, _index: Option<H256>) -> bool {
unimplemented!()
}

fn mark_hot(&mut self, _address: H160, _index: Option<H256>) -> Result<(), ExitError> {
unimplemented!()
}

fn set_storage(&mut self, _address: H160, _index: H256, _value: H256) -> Result<(), ExitError> {
unimplemented!()
}
fn log(&mut self, _address: H160, _topics: Vec<H256>, _data: Vec<u8>) -> Result<(), ExitError> {
fn log(&mut self, _log: Log) -> Result<(), ExitError> {
unimplemented!()
}
fn mark_delete(&mut self, _address: H160, _target: H160) -> Result<(), ExitError> {
unimplemented!()
}

fn reset_storage(&mut self, _address: H160) {
unimplemented!()
}

fn set_code(&mut self, _address: H160, _code: Vec<u8>) {
unimplemented!()
}

fn reset_balance(&mut self, _address: H160) {
unimplemented!()
}

fn transfer(&mut self, _transfer: Transfer) -> Result<(), ExitError> {
unimplemented!()
}

fn inc_nonce(&mut self, _address: H160) -> Result<(), ExitError> {
unimplemented!()
}
}

static RUNTIME_ETABLE: Etable<RuntimeState, UnimplementedHandler, Opcode> = Etable::runtime();
Expand Down
18 changes: 18 additions & 0 deletions jsontests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "jsontests"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
evm = { path = ".." }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
primitive-types = { version = "0.12", features = ["rlp", "serde"] }
clap = { version = "4", features = ["derive"] }
thiserror = "1"
hex = { version = "0.4", features = ["serde"] }
ethereum = "0.15.0"
rlp = "0.5"
sha3 = "0.10"
21 changes: 21 additions & 0 deletions jsontests/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use thiserror::Error;

#[derive(Error, Debug)]
pub enum TestError {
#[error("state root is different")]
StateMismatch,
}

#[derive(Error, Debug)]
pub enum Error {
#[error("io error")]
IO(#[from] std::io::Error),
#[error("json error")]
JSON(#[from] serde_json::Error),
#[error("evm error")]
EVM(#[from] evm::ExitError),
#[error("unsupported fork")]
UnsupportedFork,
#[error("test error")]
Test(#[from] TestError),
}
94 changes: 94 additions & 0 deletions jsontests/src/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use evm::backend::in_memory::InMemoryBackend;
use evm::utils::h256_to_u256;
use primitive_types::{H256, U256};
use sha3::{Digest, Keccak256};

/// Basic account type.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TrieAccount {
/// Nonce of the account.
pub nonce: U256,
/// Balance of the account.
pub balance: U256,
/// Storage root of the account.
pub storage_root: H256,
/// Code hash of the account.
pub code_hash: H256,
/// Code version of the account.
pub code_version: U256,
}

impl rlp::Encodable for TrieAccount {
fn rlp_append(&self, stream: &mut rlp::RlpStream) {
let use_short_version = self.code_version == U256::zero();

match use_short_version {
true => {
stream.begin_list(4);
}
false => {
stream.begin_list(5);
}
}

stream.append(&self.nonce);
stream.append(&self.balance);
stream.append(&self.storage_root);
stream.append(&self.code_hash);

if !use_short_version {
stream.append(&self.code_version);
}
}
}

impl rlp::Decodable for TrieAccount {
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
let use_short_version = match rlp.item_count()? {
4 => true,
5 => false,
_ => return Err(rlp::DecoderError::RlpIncorrectListLen),
};

Ok(TrieAccount {
nonce: rlp.val_at(0)?,
balance: rlp.val_at(1)?,
storage_root: rlp.val_at(2)?,
code_hash: rlp.val_at(3)?,
code_version: if use_short_version {
U256::zero()
} else {
rlp.val_at(4)?
},
})
}
}

pub fn state_root(backend: &InMemoryBackend) -> H256 {
let tree = backend
.current_layer()
.state
.iter()
.map(|(address, account)| {
let storage_root = ethereum::util::sec_trie_root(
account
.storage
.iter()
.map(|(k, v)| (k, rlp::encode(&h256_to_u256(*v)))),
);

let code_hash = H256::from_slice(&Keccak256::digest(&account.code));
let account = TrieAccount {
nonce: account.nonce,
balance: account.balance,
storage_root,
code_hash,
code_version: U256::zero(),
};

(address, rlp::encode(&account))
})
.collect::<Vec<_>>();

ethereum::util::sec_trie_root(tree)
}
Loading

0 comments on commit 4520fa6

Please sign in to comment.