Skip to content

Commit 05b7081

Browse files
authored
Merge pull request #1 from cassc/dev
Dev
2 parents 848477f + c1c0ec1 commit 05b7081

File tree

10 files changed

+791
-41
lines changed

10 files changed

+791
-41
lines changed

.github/workflows/develop.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Test on PR
2+
3+
on:
4+
pull_request:
5+
branches: [ main ]
6+
workflow_dispatch:
7+
push:
8+
branches:
9+
- main
10+
11+
12+
env:
13+
CARGO_TERM_COLOR: always
14+
15+
jobs:
16+
Test:
17+
18+
runs-on: ubuntu-latest
19+
20+
steps:
21+
- uses: actions/checkout@v3 # Git toolchain to check out code
22+
23+
- uses: actions-rs/toolchain@v1 # Rust toolchain
24+
with:
25+
toolchain: 1.73.0
26+
components: rustfmt, clippy
27+
28+
- name: Get OS infomration
29+
id: os
30+
run: echo "KERNEL=$(uname -r)" >> $GITHUB_OUTPUT
31+
32+
- uses: actions/cache@v3
33+
with:
34+
path: |
35+
~/.cargo/bin/
36+
~/.cargo/registry/index/
37+
~/.cargo/registry/cache/
38+
~/.cargo/git/db/
39+
target/
40+
key: ${{ runner.os }}-${{steps.os.outputs.KERNEL}}-cargo-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('**/Cargo.toml') }}
41+
42+
- name: Run Rust tests
43+
run: cargo test

.github/workflows/release.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- '*'
7+
8+
jobs:
9+
release:
10+
name: Build and publish binaries
11+
12+
runs-on: ubuntu-latest
13+
14+
env:
15+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16+
17+
steps:
18+
19+
- uses: actions/checkout@v3 # Git toolchain to check out code
20+
21+
- uses: actions-rs/toolchain@v1 # Rust toolchain
22+
with:
23+
toolchain: 1.73.0
24+
25+
- name: Get OS infomration
26+
id: os
27+
run: echo "KERNEL=$(uname -r)" >> $GITHUB_OUTPUT
28+
29+
- uses: actions/cache@v3
30+
with:
31+
path: |
32+
~/.cargo/bin/
33+
~/.cargo/registry/index/
34+
~/.cargo/registry/cache/
35+
~/.tinyevm/
36+
~/.cargo/git/db/
37+
target/
38+
key: ${{ runner.os }}-${{steps.os.outputs.KERNEL}}-${{ hashFiles('**/Cargo.toml') }}
39+
40+
- name: Build with file system cache
41+
run: |
42+
cargo build --release
43+
44+
- name: Upload binaries to release
45+
uses: svenstaro/upload-release-action@v2
46+
with:
47+
repo_token: ${{ secrets.GITHUB_TOKEN }}
48+
file: target/release/evm-interpreter
49+
overwrite: true

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ revm = {git="https://github.com/bluealloy/revm", branch="main", features=[ "serd
1515
revme = {git="https://github.com/bluealloy/revm", branch="main"}
1616
serde = "1.0.190"
1717
serde_json = "1.0.107"
18+
tempfile = "3.8.1"
1819
walkdir = "2.4.0"

README.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,36 @@ cargo install --path .
1414

1515
## Usage
1616

17+
### Execute a transaction
18+
1719
```bash
18-
❯ evm-interpreter -h
19-
Usage: evm-interpreter [OPTIONS]
20+
❯ evm-interpreter execute -h
21+
Usage: evm-interpreter execute [OPTIONS]
2022

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

34+
### Compare with CuEVM
35+
36+
```bash
37+
❯ evm-interpreter compare --executable path_to_cuevm_interpreter --test-json dev-resources/ethtest/GeneralStateTests/VMTests/vmArithmeticTest/arith.json
38+
```
39+
40+
3141
## Examples
3242

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

3545
``` bash
36-
❯ evm-interpreter --bytecode 604260005260206000F3 --pprint
46+
❯ evm-interpreter execute --bytecode 604260005260206000F3 --pprint
3747
Input: Bytes(0x)
3848
Output: Bytes(0x0000000000000000000000000000000000000000000000000000000000000042)
3949
➡️ PC: 0 OPCODE: 0x60 PUSH1

src/comparator.rs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
use eyre::Result;
2+
use revm::primitives::{
3+
calc_excess_blob_gas, keccak256, Bytecode, Bytes, CreateScheme, Env, TransactTo, U256,
4+
};
5+
use std::{path::Path, process::Command};
6+
use tempfile::Builder;
7+
8+
use crate::{cuevm_test_suite::CuEvmTestSuite, inspector::TraceInspector};
9+
10+
/// Compare the output of CuEVM with the output of revm. Panics if there is a
11+
/// mismatch.
12+
pub fn execute_and_compare(
13+
cuevm_executable: String,
14+
test_json: String,
15+
pprint: bool,
16+
) -> Result<()> {
17+
let output_json = {
18+
let temp_dir = std::env::temp_dir();
19+
let file = Builder::new()
20+
.prefix("mytempfile_")
21+
.suffix(".json")
22+
.tempfile_in(temp_dir)?;
23+
file.into_temp_path()
24+
.canonicalize()?
25+
.as_path()
26+
.to_str()
27+
.unwrap()
28+
.to_string()
29+
};
30+
31+
let out = Command::new(cuevm_executable)
32+
.args(["--input", &test_json])
33+
.args(["--output", &output_json])
34+
.output()?;
35+
36+
if output_json.is_empty() {
37+
Err(eyre::eyre!(
38+
"Output json is empty, cuevm returns stdout: {:?}",
39+
out.stdout
40+
))?;
41+
}
42+
43+
println!("Input: {} CuEVM Output: {}", test_json, output_json);
44+
45+
let path = Path::new(&output_json);
46+
let s = std::fs::read_to_string(path)?;
47+
let suite: CuEvmTestSuite = serde_json::from_str(&s)?;
48+
49+
for (_name, unit) in suite.0 {
50+
// Create database and insert cache
51+
let mut cache_state = revm::CacheState::new(false);
52+
for (address, info) in unit.pre {
53+
let acc_info = revm::primitives::AccountInfo {
54+
balance: info.balance,
55+
code_hash: keccak256(&info.code),
56+
code: Some(Bytecode::new_raw(info.code)),
57+
nonce: info.nonce,
58+
};
59+
cache_state.insert_account_with_storage(address, acc_info, info.storage);
60+
}
61+
62+
let mut env = Env::default();
63+
env.cfg.chain_id = 1;
64+
env.block.number = unit.env.current_number;
65+
env.block.coinbase = unit.env.current_coinbase;
66+
env.block.timestamp = unit.env.current_timestamp;
67+
env.block.gas_limit = unit.env.current_gas_limit;
68+
env.block.basefee = unit.env.current_base_fee.unwrap_or_default();
69+
env.block.difficulty = unit.env.current_difficulty;
70+
env.block.prevrandao = Some(unit.env.current_difficulty.to_be_bytes().into());
71+
if let (Some(parent_blob_gas_used), Some(parent_excess_blob_gas)) = (
72+
unit.env.parent_blob_gas_used,
73+
unit.env.parent_excess_blob_gas,
74+
) {
75+
env.block
76+
.set_blob_excess_gas_and_price(calc_excess_blob_gas(
77+
parent_blob_gas_used.to(),
78+
parent_excess_blob_gas.to(),
79+
));
80+
}
81+
82+
// Run test in post generated by cuevm
83+
for (index, test) in unit.post.into_iter().enumerate() {
84+
env.tx.gas_limit = test.msg.gas_limit.saturating_to();
85+
env.tx.caller = test.msg.sender;
86+
env.tx.gas_price = test.msg.gas_price.unwrap_or_default(); // Note some ethtest has max_fee_per_gas
87+
88+
env.tx.data = test.msg.data.clone();
89+
env.tx.value = test.msg.value;
90+
91+
let to = match test.msg.to {
92+
Some(add) => TransactTo::Call(add),
93+
None => TransactTo::Create(CreateScheme::Create),
94+
};
95+
env.tx.transact_to = to;
96+
97+
let cache = cache_state.clone();
98+
let mut state = revm::db::State::builder()
99+
.with_cached_prestate(cache)
100+
.with_bundle_update()
101+
.build();
102+
let mut evm = revm::new();
103+
evm.database(&mut state);
104+
evm.env = env.clone();
105+
106+
let mut traces = vec![];
107+
let inspector = TraceInspector {
108+
traces: &mut traces,
109+
};
110+
111+
let result = evm.inspect_commit(inspector);
112+
let mut success = false;
113+
let mut output = Bytes::new();
114+
if let Ok(result) = result {
115+
success = result.is_success();
116+
if let Some(o) = result.output() {
117+
output = o.to_owned();
118+
}
119+
}
120+
121+
if pprint {
122+
traces.iter().for_each(|trace| {
123+
trace.pprint();
124+
});
125+
println!(
126+
"ID: {} {} OUTPUT: {}",
127+
index,
128+
if success { "✅" } else { "❌" },
129+
output
130+
);
131+
}
132+
133+
traces.iter().enumerate().skip(1).for_each(|(idx, t)| {
134+
let revm_stack = t.stack.data().clone();
135+
let cuevm_stack = test.traces[idx - 1].stack.data.clone();
136+
compare_stack(&test_json, idx, revm_stack, cuevm_stack).unwrap();
137+
});
138+
}
139+
}
140+
141+
Ok(())
142+
}
143+
144+
fn compare_stack(
145+
test_json: &str,
146+
idx: usize,
147+
expected: Vec<U256>,
148+
actual: Vec<Bytes>,
149+
) -> Result<()> {
150+
macro_rules! err {
151+
() => {
152+
eprintln!("Expected: {:?}", &expected);
153+
eprintln!("Actual: {:?}", &actual);
154+
Err(eyre::eyre!(
155+
"Stack length mismatch at index {} from {}",
156+
idx,
157+
test_json
158+
))?
159+
};
160+
}
161+
if expected.len() != actual.len() {
162+
err!();
163+
}
164+
let actual: Vec<U256> = actual
165+
.iter()
166+
.map(|x| {
167+
let mut padded_array = [0u8; 32];
168+
let xs = x.iter().cloned().collect::<Vec<u8>>();
169+
let len = xs.len();
170+
padded_array[32 - len..].copy_from_slice(&xs);
171+
U256::from_be_bytes(padded_array)
172+
})
173+
.collect();
174+
if actual != expected {
175+
err!();
176+
}
177+
Ok(())
178+
}

0 commit comments

Comments
 (0)