Skip to content

Commit

Permalink
Merge pull request #1 from cassc/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
cassc authored Nov 21, 2023
2 parents 848477f + c1c0ec1 commit 05b7081
Show file tree
Hide file tree
Showing 10 changed files with 791 additions and 41 deletions.
43 changes: 43 additions & 0 deletions .github/workflows/develop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Test on PR

on:
pull_request:
branches: [ main ]
workflow_dispatch:
push:
branches:
- main


env:
CARGO_TERM_COLOR: always

jobs:
Test:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3 # Git toolchain to check out code

- uses: actions-rs/toolchain@v1 # Rust toolchain
with:
toolchain: 1.73.0
components: rustfmt, clippy

- name: Get OS infomration
id: os
run: echo "KERNEL=$(uname -r)" >> $GITHUB_OUTPUT

- uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{steps.os.outputs.KERNEL}}-cargo-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('**/Cargo.toml') }}

- name: Run Rust tests
run: cargo test
49 changes: 49 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Release

on:
push:
tags:
- '*'

jobs:
release:
name: Build and publish binaries

runs-on: ubuntu-latest

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

steps:

- uses: actions/checkout@v3 # Git toolchain to check out code

- uses: actions-rs/toolchain@v1 # Rust toolchain
with:
toolchain: 1.73.0

- name: Get OS infomration
id: os
run: echo "KERNEL=$(uname -r)" >> $GITHUB_OUTPUT

- uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.tinyevm/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{steps.os.outputs.KERNEL}}-${{ hashFiles('**/Cargo.toml') }}

- name: Build with file system cache
run: |
cargo build --release
- name: Upload binaries to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: target/release/evm-interpreter
overwrite: true
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ revm = {git="https://github.com/bluealloy/revm", branch="main", features=[ "serd
revme = {git="https://github.com/bluealloy/revm", branch="main"}
serde = "1.0.190"
serde_json = "1.0.107"
tempfile = "3.8.1"
walkdir = "2.4.0"
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,36 @@ cargo install --path .

## Usage

### Execute a transaction

```bash
❯ evm-interpreter -h
Usage: evm-interpreter [OPTIONS]
❯ evm-interpreter execute -h
Usage: evm-interpreter execute [OPTIONS]

Options:
--bytecode <BYTECODE> A hex string representing a contract runtime binary code
--input <INPUT> An optional hex encoded transaction data
--pprint If provided, print stack traces to stdout
--output <OUTPUT> If provided, output as JSON to this file
--test-json <TEST_JSON> If provided, use the ethtest JSON file the input
--test-json <TEST_JSON> If provided, use the ethtest JSON file as the input
--limit <LIMIT> Maximum number of test files to run, valid when using with --test-json [default: 10]
-h, --help Print help
-V, --version Print version

```

### Compare with CuEVM

```bash
❯ evm-interpreter compare --executable path_to_cuevm_interpreter --test-json dev-resources/ethtest/GeneralStateTests/VMTests/vmArithmeticTest/arith.json
```


## Examples

### Call contract with no input (zero length transaction)

``` bash
❯ evm-interpreter --bytecode 604260005260206000F3 --pprint
❯ evm-interpreter execute --bytecode 604260005260206000F3 --pprint
Input: Bytes(0x)
Output: Bytes(0x0000000000000000000000000000000000000000000000000000000000000042)
➡️ PC: 0 OPCODE: 0x60 PUSH1
Expand Down
178 changes: 178 additions & 0 deletions src/comparator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
use eyre::Result;
use revm::primitives::{
calc_excess_blob_gas, keccak256, Bytecode, Bytes, CreateScheme, Env, TransactTo, U256,
};
use std::{path::Path, process::Command};
use tempfile::Builder;

use crate::{cuevm_test_suite::CuEvmTestSuite, inspector::TraceInspector};

/// Compare the output of CuEVM with the output of revm. Panics if there is a
/// mismatch.
pub fn execute_and_compare(
cuevm_executable: String,
test_json: String,
pprint: bool,
) -> Result<()> {
let output_json = {
let temp_dir = std::env::temp_dir();
let file = Builder::new()
.prefix("mytempfile_")
.suffix(".json")
.tempfile_in(temp_dir)?;
file.into_temp_path()
.canonicalize()?
.as_path()
.to_str()
.unwrap()
.to_string()
};

let out = Command::new(cuevm_executable)
.args(["--input", &test_json])
.args(["--output", &output_json])
.output()?;

if output_json.is_empty() {
Err(eyre::eyre!(
"Output json is empty, cuevm returns stdout: {:?}",
out.stdout
))?;
}

println!("Input: {} CuEVM Output: {}", test_json, output_json);

let path = Path::new(&output_json);
let s = std::fs::read_to_string(path)?;
let suite: CuEvmTestSuite = serde_json::from_str(&s)?;

for (_name, unit) in suite.0 {
// Create database and insert cache
let mut cache_state = revm::CacheState::new(false);
for (address, info) in unit.pre {
let acc_info = revm::primitives::AccountInfo {
balance: info.balance,
code_hash: keccak256(&info.code),
code: Some(Bytecode::new_raw(info.code)),
nonce: info.nonce,
};
cache_state.insert_account_with_storage(address, acc_info, info.storage);
}

let mut env = Env::default();
env.cfg.chain_id = 1;
env.block.number = unit.env.current_number;
env.block.coinbase = unit.env.current_coinbase;
env.block.timestamp = unit.env.current_timestamp;
env.block.gas_limit = unit.env.current_gas_limit;
env.block.basefee = unit.env.current_base_fee.unwrap_or_default();
env.block.difficulty = unit.env.current_difficulty;
env.block.prevrandao = Some(unit.env.current_difficulty.to_be_bytes().into());
if let (Some(parent_blob_gas_used), Some(parent_excess_blob_gas)) = (
unit.env.parent_blob_gas_used,
unit.env.parent_excess_blob_gas,
) {
env.block
.set_blob_excess_gas_and_price(calc_excess_blob_gas(
parent_blob_gas_used.to(),
parent_excess_blob_gas.to(),
));
}

// Run test in post generated by cuevm
for (index, test) in unit.post.into_iter().enumerate() {
env.tx.gas_limit = test.msg.gas_limit.saturating_to();
env.tx.caller = test.msg.sender;
env.tx.gas_price = test.msg.gas_price.unwrap_or_default(); // Note some ethtest has max_fee_per_gas

env.tx.data = test.msg.data.clone();
env.tx.value = test.msg.value;

let to = match test.msg.to {
Some(add) => TransactTo::Call(add),
None => TransactTo::Create(CreateScheme::Create),
};
env.tx.transact_to = to;

let cache = cache_state.clone();
let mut state = revm::db::State::builder()
.with_cached_prestate(cache)
.with_bundle_update()
.build();
let mut evm = revm::new();
evm.database(&mut state);
evm.env = env.clone();

let mut traces = vec![];
let inspector = TraceInspector {
traces: &mut traces,
};

let result = evm.inspect_commit(inspector);
let mut success = false;
let mut output = Bytes::new();
if let Ok(result) = result {
success = result.is_success();
if let Some(o) = result.output() {
output = o.to_owned();
}
}

if pprint {
traces.iter().for_each(|trace| {
trace.pprint();
});
println!(
"ID: {} {} OUTPUT: {}",
index,
if success { "✅" } else { "❌" },
output
);
}

traces.iter().enumerate().skip(1).for_each(|(idx, t)| {
let revm_stack = t.stack.data().clone();
let cuevm_stack = test.traces[idx - 1].stack.data.clone();
compare_stack(&test_json, idx, revm_stack, cuevm_stack).unwrap();
});
}
}

Ok(())
}

fn compare_stack(
test_json: &str,
idx: usize,
expected: Vec<U256>,
actual: Vec<Bytes>,
) -> Result<()> {
macro_rules! err {
() => {
eprintln!("Expected: {:?}", &expected);
eprintln!("Actual: {:?}", &actual);
Err(eyre::eyre!(
"Stack length mismatch at index {} from {}",
idx,
test_json
))?
};
}
if expected.len() != actual.len() {
err!();
}
let actual: Vec<U256> = actual
.iter()
.map(|x| {
let mut padded_array = [0u8; 32];
let xs = x.iter().cloned().collect::<Vec<u8>>();
let len = xs.len();
padded_array[32 - len..].copy_from_slice(&xs);
U256::from_be_bytes(padded_array)
})
.collect();
if actual != expected {
err!();
}
Ok(())
}
Loading

0 comments on commit 05b7081

Please sign in to comment.