├── .github └── workflows │ ├── grevm-ethereum.yml │ ├── grevm-fmt.yml │ └── grevm-test.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── benches ├── continuous_blocks.rs ├── gigagas.rs └── mainnet.rs ├── build.rs ├── docs ├── use-with-reth.md ├── v1 │ ├── README.md │ └── images │ │ ├── glassnode-studio_eth-ethereum-transaction-type-breakdown-relative.png │ │ ├── grevm-process.svg │ │ ├── image 1.png │ │ ├── image 2.png │ │ ├── image 3.png │ │ ├── image 4.png │ │ └── image.png └── v2 │ ├── grevm2.1.md │ ├── grevm2.md │ └── images │ ├── dependent_distance_plot.svg │ ├── g2design.png │ └── parallel_state.png ├── rust-toolchain.toml ├── rustfmt.toml ├── src ├── async_commit.rs ├── hint.rs ├── lib.rs ├── parallel_state.rs ├── scheduler.rs ├── storage.rs ├── tx_dependency.rs └── utils.rs └── tests ├── common ├── execute.rs ├── mod.rs └── storage.rs ├── erc20 ├── contracts │ └── ERC20Token.hex ├── erc20_contract.rs ├── main.rs └── mod.rs ├── ethereum └── main.rs ├── mainnet.rs ├── native_transfers.rs └── uniswap ├── assets ├── SingleSwap.hex ├── SwapRouter.hex ├── UniswapV3Factory.hex ├── UniswapV3Pool.hex └── WETH9.hex ├── contract.rs ├── main.rs └── mod.rs /.github/workflows/grevm-ethereum.yml: -------------------------------------------------------------------------------- 1 | name: Grevm Ethereum 2 | 3 | on: 4 | pull_request: 5 | branches: [ "main" ] 6 | merge_group: 7 | types: [checks_requested] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | # check out 16 | - name: Checkout repository 17 | uses: actions/checkout@v3 18 | 19 | # install rust tools 20 | - name: Set up Rust 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: stable 24 | override: true 25 | 26 | - name: Run tests 27 | run: cargo test --features update-submodule-ethereum-tests --test ethereum 28 | -------------------------------------------------------------------------------- /.github/workflows/grevm-fmt.yml: -------------------------------------------------------------------------------- 1 | name: Grevm Format Check 2 | 3 | on: 4 | pull_request: 5 | branches: [ "main" ] 6 | merge_group: 7 | types: [checks_requested] 8 | 9 | jobs: 10 | fmt_check: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Rust 19 | uses: dsherret/rust-toolchain-file@v1 20 | 21 | # install nightly toolchain at the same time. 22 | - name: Setup rust toolchain 23 | shell: bash 24 | run: rustup toolchain install nightly 25 | 26 | - name: Install rustfmt 27 | run: rustup component add rustfmt --toolchain nightly 28 | 29 | - name: Run cargo fmt 30 | run: cargo +nightly fmt --check 31 | -------------------------------------------------------------------------------- /.github/workflows/grevm-test.yml: -------------------------------------------------------------------------------- 1 | name: Grevm Tests 2 | 3 | on: 4 | pull_request: 5 | branches: [ "main" ] 6 | merge_group: 7 | types: [checks_requested] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | # check out 16 | - name: Checkout repository 17 | uses: actions/checkout@v3 18 | 19 | # install rust tools 20 | - name: Set up Rust 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: stable 24 | override: true 25 | 26 | - name: Run tests 27 | run: cargo test --features update-submodule-test-data -- --skip ethereum 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | ./debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | # Generated by Intellij-based IDEs. 13 | .idea 14 | 15 | # Generated by MacOS 16 | .DS_Store 17 | 18 | # Prometheus data dir 19 | data/ 20 | 21 | # Release artifacts 22 | dist/ 23 | 24 | # VSCode 25 | .vscode 26 | 27 | # Coverage report 28 | lcov.info 29 | 30 | # Cache directory for CCLS, if using it with MDBX sources 31 | .ccls-cache/ 32 | 33 | # Rust bug report 34 | rustc-ice-* 35 | 36 | # Rust lock file 37 | Cargo.lock 38 | 39 | # test submodules 40 | test_data 41 | tests/ethereum/tests 42 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grevm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "Create Parallel EVM" 6 | build = "build.rs" 7 | 8 | [dependencies] 9 | revm = { package = "revm", git = "https://github.com/Galxe/revm", branch = "v19.5.0-gravity" } 10 | revm-primitives = { package = "revm-primitives", git = "https://github.com/Galxe/revm", branch = "v19.5.0-gravity" } 11 | ahash = { version = "0.8.11", features = ["serde"] } 12 | rayon = "1.10.0" 13 | atomic = "0.6.0" 14 | parking_lot = "0.12" 15 | 16 | # Alloy 17 | alloy-chains = "0.1.32" 18 | alloy-primitives = { version = "0.8.20", default-features = false, features = ["map-foldhash"] } 19 | 20 | # async 21 | futures = "0.3" 22 | rand = "0.8.5" 23 | auto_impl = "1" 24 | lazy_static = "1.5.0" 25 | dashmap = "6.0" 26 | 27 | # metrics 28 | metrics = "0.24" 29 | metrics-derive = "0.1" 30 | 31 | [dev-dependencies] 32 | criterion = "0.5.1" 33 | metrics-util = "0.19.0" 34 | walkdir = "2.5.0" 35 | rayon = "1.10.0" 36 | revme = { package = "revme", git = "https://github.com/Galxe/revm", branch = "v19.5.0-gravity" } 37 | tikv-jemallocator = "0.6" 38 | bincode = "1.3.3" 39 | serde_json = "1.0.94" 40 | 41 | [lints] 42 | rust.missing_debug_implementations = "warn" 43 | rust.missing_docs = "warn" 44 | rust.unreachable_pub = "warn" 45 | 46 | [[bench]] 47 | name = "gigagas" 48 | harness = false 49 | 50 | [[bench]] 51 | name = "mainnet" 52 | harness = false 53 | 54 | [[bench]] 55 | name = "continuous_blocks" 56 | harness = false 57 | 58 | [profile.release] 59 | codegen-units = 1 60 | panic = "abort" 61 | lto = "fat" 62 | 63 | [profile.bench] 64 | debug = 2 65 | opt-level = 3 66 | lto = "off" 67 | 68 | [features] 69 | update-test-submodule = ["update-submodule-test-data", "update-submodule-ethereum-tests"] 70 | update-submodule-test-data = [] 71 | update-submodule-ethereum-tests = [] 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grevm 2 | 3 | > Grevm (both 1.0 and 2.0/2.1) is reth-ready, please see [use-with-reth.md](docs/use-with-reth.md) for more details. 4 | 5 | Grevm is a Block-STM inspired optimistic parallel EVM execution engine that leverages DAG-based task scheduling, dynamic 6 | dependency management, and parallel state storage to significantly boost throughput of 7 | [revm](https://github.com/bluealloy/revm), while reducing CPU overhead in high-conflict scenarios. 8 | 9 | ![Design Diagram](docs/v2/images/g2design.png) 10 | 11 | ## **TL;DR – Highlights of Grevm 2.1** 12 | 13 | - **Grevm 2.1 achieves near-optimal performance in low-contention scenarios**, matching Block-STM with **16.57 14 | gigagas/s** for Uniswap workloads and outperforming it with **95% less CPU usage** in inherently non-parallelizable 15 | cases by **20–30%**, achieving performance close to sequential execution. 16 | - **Breaks Grevm 1.0’s limitations in handling highly dependent transactions**, delivering a **5.5× throughput 17 | increase** to **2.96 gigagas/s** in **30%-hot-ratio hybrid workloads** by minimizing re-executions through **DAG-based 18 | scheduling** and **Task Groups**. 19 | - **Introduces Parallel State Store**, leveraging **asynchronous execution result bundling** to **overlap and amortize 20 | 30-60ms of post-execution overhead within parallel execution**, effectively hiding these costs within execution time. 21 | It also seamlessly handles **miner rewards and the self-destruct opcode** without the performance penalties of 22 | sequential fallbacks. 23 | - **In-depth analysis of optimistic parallel execution** reveals the **underestimated efficiency of Block-STM** and the 24 | strength of **optimistic parallelism**, providing new insights into parallel execution. 25 | - **Lock-free DAG** replaces global locks with fine-grained node-level synchronization. Improved scheduling performance 26 | by 60% and overall performance by over 30% in low conflict situations. 27 | 28 | ## Architecture Overview 29 | 30 | Grevm 2.1 is composed of three main modules: 31 | 32 | - **Dependency Manager (DAG Manager):** 33 | Constructs a directed acyclic graph (DAG) of transaction dependencies based on speculative read/write hints. 34 | 35 | - **Execution Scheduler:** 36 | Selects transactions with no dependencies (out-degree of 0) for parallel execution, groups adjacent dependent 37 | transactions into **task groups**, and dynamically updates dependencies to minimize re-execution. 38 | 39 | - **Parallel State Storage:** 40 | Provides an asynchronous commit mechanism with multi-version memory to reduce latency and manage miner rewards and 41 | self-destruct opcodes efficiently. 42 | 43 | ## Running the Benchmark 44 | 45 | To reproduce the benchmark: 46 | 47 | ```bash 48 | JEMALLOC_SYS_WITH_MALLOC_CONF="thp:always,metadata_thp:always" \ 49 | NUM_EOA= HOT_RATIO= DB_LATENCY_US= \ 50 | cargo bench --bench gigagas 51 | ``` 52 | 53 | Replace ``, ``, and `` with your desired parameters. 54 | 55 | ## Further Details 56 | 57 | For a comprehensive explanation of the design, algorithmic choices, and in-depth benchmark analysis, please refer to the 58 | full technical report. 59 | 60 | - [Grevm 2.1 Tech Report](docs/v2/grevm2.1.md) 61 | - [Grevm 2.0 Tech Report](docs/v2/grevm2.md) 62 | - [Grevm 1.0 Tech Report](docs/v1/README.md) 63 | -------------------------------------------------------------------------------- /benches/continuous_blocks.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | #[path = "../tests/common/mod.rs"] 4 | pub mod common; 5 | 6 | use criterion::{criterion_group, criterion_main, Criterion}; 7 | use grevm::{ParallelState, ParallelTakeBundle, Scheduler}; 8 | use metrics_util::debugging::{DebugValue, DebuggingRecorder}; 9 | use rand::Rng; 10 | use revm::{db::states::bundle_state::BundleRetention, EvmBuilder, StateBuilder}; 11 | use revm_primitives::db::DatabaseCommit; 12 | use std::{cmp::max, fs, sync::Arc, time::Instant}; 13 | 14 | const TEST_DATA_DIR: &str = "test_data"; 15 | const GIGA_GAS: u64 = 1_000_000_000; 16 | 17 | fn bench_1gigagas(c: &mut Criterion) { 18 | let db_latency_us = std::env::var("DB_LATENCY_US").map(|s| s.parse().unwrap()).unwrap_or(0); 19 | let with_hints = std::env::var("WITH_HINTS").map_or(false, |s| s.parse().unwrap()); 20 | 21 | let continuous_blocks = String::from("19126588_19127588"); 22 | if !common::continuous_blocks_exist(continuous_blocks.clone()) { 23 | panic!("No test data"); 24 | } 25 | 26 | let (block_env, mut db) = common::load_continuous_blocks(continuous_blocks, Some(100)); 27 | let bytecodes = common::load_bytecodes_from_disk(); 28 | if db.bytecodes.is_empty() { 29 | db.bytecodes = bytecodes; 30 | } 31 | db.latency_us = db_latency_us; 32 | let db = Arc::new(db); 33 | 34 | let mut smallest_base_fee = 0; 35 | for i in 0..block_env.len() { 36 | if block_env[i].0.block.basefee < block_env[smallest_base_fee].0.block.basefee { 37 | smallest_base_fee = i; 38 | } 39 | } 40 | let env = block_env[smallest_base_fee].0.clone(); 41 | 42 | let mut passed_txs = vec![]; 43 | let mut total_gas = 0; 44 | let mut sequential_state = 45 | StateBuilder::new().with_bundle_update().with_database_ref(db.clone()).build(); 46 | let mut evm = EvmBuilder::default() 47 | .with_db(&mut sequential_state) 48 | .with_spec_id(env.spec_id()) 49 | .with_env(Box::new(env.env.as_ref().clone())) 50 | .build(); 51 | for (_, txs, _) in block_env.clone() { 52 | for tx in txs { 53 | *evm.tx_mut() = tx.clone(); 54 | match evm.transact() { 55 | Ok(result_and_state) => { 56 | evm.db_mut().commit(result_and_state.state); 57 | passed_txs.push(tx); 58 | total_gas += result_and_state.result.gas_used(); 59 | if total_gas >= GIGA_GAS { 60 | break; 61 | } 62 | } 63 | Err(_) => {} 64 | } 65 | } 66 | } 67 | 68 | let mut group = 69 | c.benchmark_group(format!("Large Block({} txs) with 1Gigagas", passed_txs.len())); 70 | let mut iter_loop = 0; 71 | let report_metrics = rand::thread_rng().gen_range(0..10); 72 | let txs = Arc::new(passed_txs); 73 | group.bench_function("Grevm Parallel", |b| { 74 | b.iter(|| { 75 | let recorder = DebuggingRecorder::new(); 76 | let state = ParallelState::new(db.clone(), true, false); 77 | metrics::with_local_recorder(&recorder, || { 78 | let env = env.clone(); 79 | let mut executor = 80 | Scheduler::new(env.spec_id(), *env.env, txs.clone(), state, with_hints); 81 | executor.parallel_execute(None).unwrap(); 82 | let (_, mut inner_state) = executor.take_result_and_state(); 83 | let _ = inner_state.parallel_take_bundle(BundleRetention::Reverts); 84 | }); 85 | if iter_loop == report_metrics { 86 | let snapshot = recorder.snapshotter().snapshot(); 87 | println!("\n>>>> Large Block metrics: <<<<"); 88 | for (key, _, _, value) in snapshot.into_vec() { 89 | let value = match value { 90 | DebugValue::Counter(v) => v as usize, 91 | DebugValue::Gauge(v) => v.0 as usize, 92 | DebugValue::Histogram(v) => { 93 | let mut s: Vec = 94 | v.clone().into_iter().map(|ov| ov.0 as usize).collect(); 95 | s.sort(); 96 | s.get(s.len() / 2).cloned().unwrap_or(0) 97 | } 98 | }; 99 | println!("{} => {:?}", key.key().name(), value); 100 | } 101 | } 102 | iter_loop += 1; 103 | }) 104 | }); 105 | 106 | group.bench_function("Origin Sequential", |b| { 107 | b.iter(|| { 108 | common::execute_revm_sequential( 109 | db.clone(), 110 | env.spec_id(), 111 | env.env.as_ref().clone(), 112 | &*txs, 113 | ) 114 | .unwrap(); 115 | }) 116 | }); 117 | } 118 | 119 | fn bench_continuous_mainnet(c: &mut Criterion) { 120 | let db_latency_us = std::env::var("DB_LATENCY_US").map(|s| s.parse().unwrap()).unwrap_or(0); 121 | let with_hints = std::env::var("WITH_HINTS").map_or(false, |s| s.parse().unwrap()); 122 | 123 | let bytecodes = common::load_bytecodes_from_disk(); 124 | for block_path in fs::read_dir(format!("{TEST_DATA_DIR}/con_eth_blocks")).unwrap() { 125 | let block_path = block_path.unwrap().path(); 126 | let block_range = block_path.file_name().unwrap().to_str().unwrap(); 127 | let mut group = c.benchmark_group(format!("Block Range {block_range}")); 128 | let start = Instant::now(); 129 | println!("Start to load dump env for block range {}", block_range); 130 | let (block_env, mut db) = common::load_continuous_blocks(block_range.to_string(), None); 131 | println!( 132 | "Load dump env for block range {} with {}s", 133 | block_range, 134 | start.elapsed().as_secs() 135 | ); 136 | if db.bytecodes.is_empty() { 137 | db.bytecodes = bytecodes.clone(); 138 | } 139 | db.latency_us = db_latency_us; 140 | let db = Arc::new(db); 141 | 142 | let mut iter_loop = 0; 143 | let report_metrics = rand::thread_rng().gen_range(0..10); 144 | group.bench_function("Grevm Parallel", |b| { 145 | b.iter(|| { 146 | let recorder = DebuggingRecorder::new(); 147 | let mut state = ParallelState::new(db.clone(), true, false); 148 | metrics::with_local_recorder(&recorder, || { 149 | for (env, txs, post) in block_env.clone() { 150 | let mut executor = Scheduler::new( 151 | env.spec_id(), 152 | *env.env, 153 | Arc::new(txs), 154 | state, 155 | with_hints, 156 | ); 157 | executor.parallel_execute(None).unwrap(); 158 | let (_, mut inner_state) = executor.take_result_and_state(); 159 | inner_state.increment_balances(post).unwrap(); 160 | state = inner_state; 161 | } 162 | let _ = state.parallel_take_bundle(BundleRetention::Reverts); 163 | }); 164 | if iter_loop == report_metrics { 165 | let snapshot = recorder.snapshotter().snapshot(); 166 | println!("\n>>>> Block Range {} P50 metrics: <<<<", block_range); 167 | for (key, _, _, value) in snapshot.into_vec() { 168 | let value = match value { 169 | DebugValue::Counter(v) => v as usize, 170 | DebugValue::Gauge(v) => v.0 as usize, 171 | DebugValue::Histogram(v) => { 172 | let mut s: Vec = 173 | v.clone().into_iter().map(|ov| ov.0 as usize).collect(); 174 | s.sort(); 175 | s.get(s.len() / 2).cloned().unwrap_or(0) 176 | } 177 | }; 178 | println!("{} => {:?}", key.key().name(), value); 179 | } 180 | } 181 | iter_loop += 1; 182 | }) 183 | }); 184 | 185 | group.bench_function("Origin Sequential", |b| { 186 | b.iter(|| { 187 | let mut sequential_state = 188 | StateBuilder::new().with_bundle_update().with_database_ref(db.clone()).build(); 189 | for (env, txs, post) in block_env.clone() { 190 | { 191 | let mut evm = EvmBuilder::default() 192 | .with_db(&mut sequential_state) 193 | .with_spec_id(env.spec_id()) 194 | .with_env(Box::new(env.env.as_ref().clone())) 195 | .build(); 196 | for tx in txs { 197 | *evm.tx_mut() = tx.clone(); 198 | let result_and_state = evm.transact().unwrap(); 199 | evm.db_mut().commit(result_and_state.state); 200 | } 201 | } 202 | sequential_state.increment_balances(post).unwrap(); 203 | sequential_state.merge_transitions(BundleRetention::Reverts); 204 | let _ = sequential_state.take_bundle(); 205 | } 206 | }) 207 | }); 208 | } 209 | } 210 | 211 | fn bench_blocks(c: &mut Criterion) { 212 | bench_1gigagas(c); 213 | bench_continuous_mainnet(c); 214 | } 215 | 216 | criterion_group!(benches, bench_blocks); 217 | criterion_main!(benches); 218 | -------------------------------------------------------------------------------- /benches/mainnet.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | #[path = "../tests/common/mod.rs"] 4 | pub mod common; 5 | 6 | use std::sync::Arc; 7 | 8 | use crate::common::execute_revm_sequential; 9 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 10 | use grevm::{ParallelState, Scheduler}; 11 | use metrics_util::debugging::{DebugValue, DebuggingRecorder}; 12 | use rand::Rng; 13 | 14 | fn benchmark_mainnet(c: &mut Criterion) { 15 | let db_latency_us = std::env::var("DB_LATENCY_US").map(|s| s.parse().unwrap()).unwrap_or(0); 16 | let with_hints = std::env::var("WITH_HINTS").map_or(false, |s| s.parse().unwrap()); 17 | 18 | common::for_each_block_from_disk(|env, txs, mut db| { 19 | db.latency_us = db_latency_us; 20 | let number = env.env.block.number; 21 | let num_txs = txs.len(); 22 | let mut group = c.benchmark_group(format!("Block {number}({num_txs} txs)")); 23 | 24 | let txs = Arc::new(txs); 25 | let db = Arc::new(db); 26 | 27 | group.bench_function("Origin Sequential", |b| { 28 | b.iter(|| { 29 | execute_revm_sequential(db.clone(), env.spec_id(), env.env.as_ref().clone(), &*txs) 30 | .unwrap(); 31 | }) 32 | }); 33 | 34 | let mut iter_loop = 0; 35 | let report_metrics = rand::thread_rng().gen_range(0..10); 36 | group.bench_function("Grevm Parallel", |b| { 37 | b.iter(|| { 38 | let recorder = DebuggingRecorder::new(); 39 | let state = ParallelState::new(db.clone(), true, false); 40 | metrics::with_local_recorder(&recorder, || { 41 | let mut executor = Scheduler::new( 42 | black_box(env.spec_id()), 43 | black_box(env.env.as_ref().clone()), 44 | black_box(txs.clone()), 45 | black_box(state), 46 | with_hints, 47 | ); 48 | executor.parallel_execute(None).unwrap(); 49 | }); 50 | if iter_loop == report_metrics { 51 | let snapshot = recorder.snapshotter().snapshot(); 52 | println!("\n>>>> Block {}({} txs) metrics: <<<<", number, num_txs); 53 | for (key, _, _, value) in snapshot.into_vec() { 54 | let value = match value { 55 | DebugValue::Counter(v) => v as usize, 56 | DebugValue::Gauge(v) => v.0 as usize, 57 | DebugValue::Histogram(v) => { 58 | v.last().cloned().map_or(0, |ov| ov.0 as usize) 59 | } 60 | }; 61 | println!("{} => {:?}", key.key().name(), value); 62 | } 63 | } 64 | iter_loop += 1; 65 | }) 66 | }); 67 | }); 68 | } 69 | 70 | criterion_group!(benches, benchmark_mainnet); 71 | criterion_main!(benches); 72 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains the build script for the grevm project. 2 | 3 | use std::{path::Path, process::Command}; 4 | 5 | struct TestSubmodule { 6 | path: &'static str, 7 | url: &'static str, 8 | branch: &'static str, 9 | commit: Option<&'static str>, 10 | } 11 | 12 | fn git_clone(submodule: &TestSubmodule) -> Command { 13 | let mut command = Command::new("git"); 14 | command.args(&["clone", "-b", submodule.branch, submodule.url, submodule.path]); 15 | command 16 | } 17 | 18 | fn git_fetch(submodule: &TestSubmodule) -> Command { 19 | let mut command = Command::new("git"); 20 | command.args(&["-C", submodule.path, "fetch", "origin", submodule.branch]); 21 | command 22 | } 23 | 24 | fn git_checkout(submodule: &TestSubmodule) -> Command { 25 | let mut command = Command::new("git"); 26 | if let Some(commit) = submodule.commit { 27 | command.args(&["-C", submodule.path, "checkout", commit]); 28 | } else { 29 | command.args(&["-C", submodule.path, "checkout", &format!("origin/{}", submodule.branch)]); 30 | } 31 | command 32 | } 33 | 34 | fn update_submodule(submodule: &TestSubmodule) { 35 | let test_data = Path::new(submodule.path); 36 | if !test_data.exists() && !git_clone(submodule).status().unwrap().success() { 37 | panic!("Failed to clone submodule {}", submodule.path); 38 | } 39 | 40 | if submodule.commit.is_some() { 41 | if git_checkout(submodule).status().unwrap().success() { 42 | return; 43 | } 44 | } 45 | 46 | if !git_fetch(submodule).status().unwrap().success() { 47 | panic!("Failed to fetch branch {} in submodule {}", submodule.branch, submodule.path); 48 | } 49 | 50 | if !git_checkout(submodule).status().unwrap().success() { 51 | panic!("Failed to checkout {} in submodule {}", submodule.branch, submodule.path); 52 | } 53 | } 54 | 55 | fn update_submodules(submodules: &[TestSubmodule]) { 56 | std::thread::scope(|s| { 57 | for submodule in submodules { 58 | s.spawn(|| { 59 | println!("Updating submodule {}", submodule.path); 60 | update_submodule(submodule); 61 | println!("Updating submodule {} done", submodule.path); 62 | }); 63 | } 64 | }); 65 | } 66 | 67 | fn main() { 68 | #[allow(unused_mut)] 69 | let mut submodules = vec![]; 70 | 71 | #[cfg(feature = "update-submodule-test-data")] 72 | submodules.push(TestSubmodule { 73 | path: "test_data", 74 | url: "https://github.com/Galxe/grevm-test-data", 75 | branch: "main", 76 | commit: Some("4264bdf"), 77 | }); 78 | 79 | #[cfg(feature = "update-submodule-ethereum-tests")] 80 | submodules.push(TestSubmodule { 81 | path: "tests/ethereum/tests", 82 | url: "https://github.com/ethereum/tests", 83 | branch: "develop", 84 | commit: Some("4f65a0a"), 85 | }); 86 | 87 | update_submodules(&submodules); 88 | } 89 | -------------------------------------------------------------------------------- /docs/use-with-reth.md: -------------------------------------------------------------------------------- 1 | # Use Grevm with reth 2 | 3 | ## Import 4 | 5 | Add the following line to your Cargo.toml 6 | 7 | ```rust 8 | [dependencies] 9 | grevm = { git = "https://github.com/Galxe/grevm.git", branch = "main" } 10 | ``` 11 | 12 | ## Usage 13 | 14 | Using Grevm is straightforward and efficient. You just need to create a `GrevmScheduler` and then call `GrevmScheduler::parallel_execute` to get the results of parallel transaction execution. That’s all there is to it—simple and streamlined, making the process remarkably easy. 15 | 16 | ```rust 17 | grevm::scheduler 18 | impl GrevmScheduler 19 | where 20 | DB: DatabaseRef + Send + Sync + 'static, 21 | DB::Error: Send + Sync, 22 | pub fn new(spec_id: SpecId, env: Env, db: DB, txs: Vec) -> Self 23 | 24 | pub fn parallel_execute(mut self) -> Result 25 | ``` 26 | 27 | In the code above, the generic constraint for `DB` is relatively relaxed; `DB` only needs to implement `DatabaseRef`, a read-only database trait provided by `revm` for Ethereum Virtual Machine interactions. Because this is a read-only interface, it naturally satisfies the `Send` and `Sync` traits. However, the `'static` constraint, which prevents `DB` from containing reference types (`&`), may not be met by all `DB` implementations. This `'static` requirement is introduced by `tokio` in Grevm, as `tokio` requires that objects have lifetimes independent of any references. 28 | 29 | Grevm addresses this limitation by ensuring that internal object lifetimes do not exceed its own, offering an `unsafe` alternative method, `new_grevm_scheduler`, for creating `GrevmScheduler`. This method allows developers to bypass the `'static` constraint if they are confident in managing lifetimes safely. 30 | 31 | ```rust 32 | pub fn new_grevm_scheduler( 33 | spec_id: SpecId, 34 | env: Env, 35 | db: DB, 36 | txs: Arc>, 37 | state: Option>, 38 | ) -> GrevmScheduler> 39 | where 40 | DB: DatabaseRef + Send + Sync, 41 | DB::Error: Clone + Send + Sync + 'static, 42 | ``` 43 | 44 | ### Introduce Grevm to Reth 45 | 46 | Reth provides the `BlockExecutorProvider` trait for creating `Executor` for executing a single block in live sync and `BatchExecutor` for executing a batch of blocks in historical sync. However, the associated type constraints in `BlockExecutorProvider` do not meet the `DB` constraints required for parallel block execution. To avoid imposing constraints on the `DB` satisfying `ParallelDatabase` on all structs that implement the `BlockExecutorProvider` trait in Reth, we have designed the `ParallelExecutorProvider` trait for creating `Executor` and `BatchExecutor` that satisfy the `ParallelDatabase` constraint. We have also extended the `BlockExecutorProvider` trait with a `try_into_parallel_provider` method. If a `BlockExecutorProvider` needs to support parallel execution, the implementation of `try_into_parallel_provider` is required to return a struct that implements the `ParallelExecutorProvider` trait; otherwise, it defaults to return `None`. Through this design, without imposing stronger constraints on the `DB` in the `BlockExecutorProvider` trait, developers are provided with the optional ability to extend `BlockExecutorProvider` to produce parallel block executors. For `EthExecutorProvider`, which provides executors to execute regular ethereum blocks, we have implemented `GrevmExecutorProvider` to extend its capability for parallel execution of ethereum blocks. `GrevmExecutorProvider` can create `GrevmBlockExecutor` and `GrevmBatchExecutor` that execute blocks using Grevm parallel executor. 47 | 48 | ```rust 49 | // crates/evm/src/execute.rs 50 | 51 | pub trait BlockExecutorProvider: Send + Sync + Clone + Unpin + 'static { 52 | ... 53 | type ParallelProvider<'a>: ParallelExecutorProvider; 54 | 55 | /// Try to create a parallel provider from this provider. 56 | /// Return None if the block provider does not support parallel execution. 57 | fn try_into_parallel_provider(&self) -> Option> { 58 | None 59 | } 60 | } 61 | 62 | /// A type that can create a new parallel executor for block execution. 63 | pub trait ParallelExecutorProvider { 64 | type Executor: for<'a> Executor< 65 | DB, 66 | Input<'a> = BlockExecutionInput<'a, BlockWithSenders>, 67 | Output = BlockExecutionOutput, 68 | Error = BlockExecutionError, 69 | >; 70 | 71 | type BatchExecutor: for<'a> BatchExecutor< 72 | DB, 73 | Input<'a> = BlockExecutionInput<'a, BlockWithSenders>, 74 | Output = ExecutionOutcome, 75 | Error = BlockExecutionError, 76 | >; 77 | 78 | fn executor(&self, db: DB) -> Self::Executor 79 | where 80 | DB: ParallelDatabase; 81 | 82 | fn batch_executor(&self, db: DB) -> Self::BatchExecutor 83 | where 84 | DB: ParallelDatabase; 85 | } 86 | ``` 87 | 88 | When there is a need to execute a block, we expect developers to call `BlockExecutorProvider::try_into_parallel_provider` and decide whether to use the parallel execution solution provided by `BlockExecutorProvider` based on the returned value, or resort to sequential block execution if there is no corresponding parallel execution implementation in `BlockExecutorProvider`. By making such calls, we can also control the return value of `try_into_parallel_provider` by setting the environment variable `EVM_DISABLE_GREVM`, reverting to Reth's native executor based on revm for block execution. This feature is handy for conducting comparative experiments. 89 | 90 | ```rust 91 | // crates/blockchain-tree/src/chain.rs 92 | // AppendableChain::validate_and_execute 93 | let state = if let Some(parallel_provider) = 94 | externals.executor_factory.try_into_parallel_provider() 95 | { 96 | parallel_provider.executor(db).execute((&block, U256::MAX).into())? 97 | } else { 98 | externals.executor_factory.executor(db).execute((&block, U256::MAX).into())? 99 | }; 100 | ``` 101 | 102 | ```rust 103 | // crates/blockchain-tree/src/chain.rs 104 | // ExecutionStage::execute 105 | let mut executor = 106 | if let Some(parallel_provider) = self.executor_provider.try_into_parallel_provider() { 107 | EitherBatchExecutor::Parallel(parallel_provider.batch_executor(Arc::new(db))) 108 | } else { 109 | EitherBatchExecutor::Sequential(self.executor_provider.batch_executor(db)) 110 | }; 111 | ``` 112 | 113 | The integration code for connecting Grevm to Reth is already complete. You can check it out here: [Introduce Grevm to Reth](https://github.com/Galxe/grevm-reth/commit/9473670ab3c02e3699af1413f9c7bffc4bbbee45) 114 | 115 | ## Metrics 116 | 117 | To monitor and observe execution performance, Grevm uses the `metrics` crate from [crates.io](https://crates.io/crates/metrics). Users can integrate the [Prometheus exporter](https://crates.io/crates/metrics-exporter-prometheus) within their code to expose metrics data, which can then be configured in Grafana for real-time monitoring and visualization. Below is a table that provides the names and descriptions of various metric indicators: 118 | 119 | | Metric Name | Description | 120 | | --- | --- | 121 | | grevm.parallel_round_calls | Number of times parallel execution is called. | 122 | | grevm.sequential_execute_calls | Number of times sequential execution is called. | 123 | | grevm.total_tx_cnt | Total number of transactions. | 124 | | grevm.parallel_tx_cnt | Number of transactions executed in parallel. | 125 | | grevm.sequential_tx_cnt | Number of transactions executed sequentially. | 126 | | grevm.finality_tx_cnt | Number of transactions that encountered conflicts. | 127 | | grevm.conflict_tx_cnt | Number of transactions that reached finality. | 128 | | grevm.unconfirmed_tx_cnt | Number of transactions that are unconfirmed. | 129 | | grevm.reusable_tx_cnt | Number of reusable transactions. | 130 | | grevm.skip_validation_cnt | Number of transactions that skip validation | 131 | | grevm.concurrent_partition_num | Number of concurrent partitions. | 132 | | grevm.partition_execution_time_diff | Execution time difference between partitions(in nanoseconds). | 133 | | grevm.partition_num_tx_diff | Number of transactions difference between partitions. | 134 | | grevm.parse_hints_time | Time taken to parse execution hints(in nanoseconds). | 135 | | grevm.partition_tx_time | Time taken to partition transactions(in nanoseconds). | 136 | | grevm.parallel_execute_time | Time taken to validate transactions(in nanoseconds). | 137 | | grevm.validate_time | Time taken to execute(in nanoseconds). | 138 | | grevm.merge_write_set_time | Time taken to merge write set(in nanoseconds). | 139 | | grevm.commit_transition_time | Time taken to commit transition(in nanoseconds). | 140 | | grevm.build_output_time | Time taken to build output(in nanoseconds). | -------------------------------------------------------------------------------- /docs/v1/images/glassnode-studio_eth-ethereum-transaction-type-breakdown-relative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Galxe/grevm/eae1571d7d3451a6a69fb0be9277c7f6513063ec/docs/v1/images/glassnode-studio_eth-ethereum-transaction-type-breakdown-relative.png -------------------------------------------------------------------------------- /docs/v1/images/image 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Galxe/grevm/eae1571d7d3451a6a69fb0be9277c7f6513063ec/docs/v1/images/image 1.png -------------------------------------------------------------------------------- /docs/v1/images/image 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Galxe/grevm/eae1571d7d3451a6a69fb0be9277c7f6513063ec/docs/v1/images/image 2.png -------------------------------------------------------------------------------- /docs/v1/images/image 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Galxe/grevm/eae1571d7d3451a6a69fb0be9277c7f6513063ec/docs/v1/images/image 3.png -------------------------------------------------------------------------------- /docs/v1/images/image 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Galxe/grevm/eae1571d7d3451a6a69fb0be9277c7f6513063ec/docs/v1/images/image 4.png -------------------------------------------------------------------------------- /docs/v1/images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Galxe/grevm/eae1571d7d3451a6a69fb0be9277c7f6513063ec/docs/v1/images/image.png -------------------------------------------------------------------------------- /docs/v2/images/g2design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Galxe/grevm/eae1571d7d3451a6a69fb0be9277c7f6513063ec/docs/v2/images/g2design.png -------------------------------------------------------------------------------- /docs/v2/images/parallel_state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Galxe/grevm/eae1571d7d3451a6a69fb0be9277c7f6513063ec/docs/v2/images/parallel_state.png -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.81.0" 3 | 4 | # Note: we don't specify cargofmt in our toolchain because we rely on 5 | # the nightly version of cargofmt and verify formatting in CI/CD. 6 | components = ["cargo", "clippy", "rustc", "rust-docs", "rust-std", "rust-analyzer"] 7 | 8 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | imports_granularity = "Crate" 3 | use_small_heuristics = "Max" 4 | comment_width = 100 5 | wrap_comments = true 6 | binop_separator = "Back" 7 | trailing_comma = "Vertical" 8 | trailing_semicolon = false 9 | use_field_init_shorthand = true 10 | format_code_in_doc_comments = true 11 | doc_comment_code_block_width = 100 12 | -------------------------------------------------------------------------------- /src/async_commit.rs: -------------------------------------------------------------------------------- 1 | use crate::{GrevmError, ParallelState, TxId}; 2 | use revm_primitives::{ 3 | db::{Database, DatabaseCommit, DatabaseRef}, 4 | Address, EVMError, ExecutionResult, InvalidTransaction, ResultAndState, TxEnv, 5 | }; 6 | use std::cmp::Ordering; 7 | 8 | /// `StateAsyncCommit` asynchronously finalizes transaction states, 9 | /// serving two critical purposes: 10 | /// ensuring Ethereum-compatible execution results and resolving edge cases like miner rewards and 11 | /// self-destructed accounts. Though state commits strictly follow transaction confirmation order 12 | /// for correctness, the asynchronous pipeline eliminates any additional block execution latency by 13 | /// decoupling finalization from the critical path. 14 | pub(crate) struct StateAsyncCommit<'a, DB> 15 | where 16 | DB: DatabaseRef, 17 | { 18 | coinbase: Address, 19 | results: Vec, 20 | state: &'a ParallelState, 21 | commit_result: Result<(), GrevmError>, 22 | } 23 | 24 | impl<'a, DB> StateAsyncCommit<'a, DB> 25 | where 26 | DB: DatabaseRef, 27 | { 28 | pub(crate) fn new(coinbase: Address, state: &'a ParallelState) -> Self { 29 | Self { coinbase, results: vec![], state, commit_result: Ok(()) } 30 | } 31 | 32 | fn state_mut(&self) -> &mut ParallelState { 33 | #[allow(invalid_reference_casting)] 34 | unsafe { 35 | &mut *(self.state as *const ParallelState as *mut ParallelState) 36 | } 37 | } 38 | 39 | pub(crate) fn init(&mut self) -> Result<(), DB::Error> { 40 | // Accesses the coinbase account to ensure proper handling of miner rewards (via 41 | // increment_balances) within ParallelState. This preemptive access guarantees correct state 42 | // synchronization when applying miner rewards during the final commitment phase. 43 | match self.state_mut().basic(self.coinbase) { 44 | Ok(_) => Ok(()), 45 | Err(e) => Err(e), 46 | } 47 | } 48 | 49 | pub(crate) fn take_result(&mut self) -> Vec { 50 | std::mem::take(&mut self.results) 51 | } 52 | 53 | pub(crate) fn commit_result(&self) -> &Result<(), GrevmError> { 54 | &self.commit_result 55 | } 56 | 57 | pub(crate) fn commit(&mut self, txid: TxId, tx_env: &TxEnv, result_and_state: ResultAndState) { 58 | // During Grevm's execution, transaction nonces are temporarily set to `None` to bypass the 59 | // EVM's strict sequential nonce verification. This design enables concurrent transaction 60 | // processing without immediate validation failures. However, during the final commitment 61 | // phase, the system enforces strict nonce monotonicity checks to guarantee transaction 62 | // integrity and prevent double-spending attacks. 63 | if let Some(tx) = tx_env.nonce { 64 | match self.state.basic_ref(tx_env.caller) { 65 | Ok(info) => { 66 | if let Some(info) = info { 67 | let state = info.nonce; 68 | match tx.cmp(&state) { 69 | Ordering::Greater => { 70 | self.commit_result = Err(GrevmError { 71 | txid, 72 | error: EVMError::Transaction( 73 | InvalidTransaction::NonceTooHigh { tx, state }, 74 | ), 75 | }); 76 | } 77 | Ordering::Less => { 78 | self.commit_result = Err(GrevmError { 79 | txid, 80 | error: EVMError::Transaction(InvalidTransaction::NonceTooLow { 81 | tx, 82 | state, 83 | }), 84 | }); 85 | } 86 | _ => {} 87 | } 88 | } 89 | } 90 | Err(e) => { 91 | self.commit_result = Err(GrevmError { txid, error: EVMError::Database(e) }) 92 | } 93 | } 94 | } 95 | let ResultAndState { result, state, rewards } = result_and_state; 96 | self.results.push(result); 97 | self.state_mut().commit(state); 98 | // In Ethereum, each transaction includes a miner reward, which would introduce write 99 | // conflicts in the read-write set if implemented naively, preventing parallel transaction 100 | // execution. Grevm adopts an optimized approach: it defers miner reward distribution until 101 | // the transaction commitment phase rather than during execution. This design ensures 102 | // correct concurrency - even if subsequent transactions access the miner's account, they 103 | // will read the proper miner state from ParallelState (verified via commit_idx) without 104 | // creating artificial dependencies. 105 | assert!(self.state_mut().increment_balances(vec![(self.coinbase, rewards)]).is_ok()); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/hint.rs: -------------------------------------------------------------------------------- 1 | use crate::{fork_join_util, tx_dependency::TxDependency, LocationAndType, TxId}; 2 | use ahash::{AHashMap as HashMap, AHashSet as HashSet}; 3 | use revm::primitives::{ 4 | alloy_primitives::U160, keccak256, ruint::UintTryFrom, Address, Bytes, TxEnv, TxKind, B256, 5 | U256, 6 | }; 7 | use std::{cmp::max, sync::Arc}; 8 | 9 | /// This module provides functionality for parsing and handling execution hints 10 | /// for parallel transaction execution in the context of Ethereum-like blockchains. 11 | /// It includes definitions for contract types, ERC20 functions, read-write types, 12 | /// and methods for updating transaction states based on contract interactions. 13 | #[allow(dead_code)] 14 | enum ContractType { 15 | UNKNOWN, 16 | ERC20, 17 | } 18 | 19 | /// Represents different types of contracts. Currently, only ERC20 is supported. 20 | enum ERC20Function { 21 | UNKNOWN, 22 | Allowance, 23 | Approve, 24 | BalanceOf, 25 | Decimals, 26 | DecreaseAllowance, 27 | IncreaseAllowance, 28 | Name, 29 | Symbol, 30 | TotalSupply, 31 | Transfer, 32 | TransferFrom, 33 | } 34 | 35 | impl From for ERC20Function { 36 | fn from(func_id: u32) -> Self { 37 | match func_id { 38 | 0xdd62ed3e => ERC20Function::Allowance, 39 | 0x095ea7b3 => ERC20Function::Approve, 40 | 0x70a08231 => ERC20Function::BalanceOf, 41 | 0x313ce567 => ERC20Function::Decimals, 42 | 0xa457c2d7 => ERC20Function::DecreaseAllowance, 43 | 0x39509351 => ERC20Function::IncreaseAllowance, 44 | 0x06fdde03 => ERC20Function::Name, 45 | 0x95d89b41 => ERC20Function::Symbol, 46 | 0x18160ddd => ERC20Function::TotalSupply, 47 | 0xa9059cbb => ERC20Function::Transfer, 48 | 0x23b872dd => ERC20Function::TransferFrom, 49 | _ => ERC20Function::UNKNOWN, 50 | } 51 | } 52 | } 53 | 54 | enum RWType { 55 | ReadOnly, 56 | WriteOnly, 57 | ReadWrite, 58 | } 59 | 60 | #[derive(Clone)] 61 | struct RWSet { 62 | pub read_set: HashSet, 63 | pub write_set: HashSet, 64 | } 65 | 66 | impl RWSet { 67 | fn new() -> Self { 68 | Self { read_set: HashSet::new(), write_set: HashSet::new() } 69 | } 70 | 71 | fn insert_location(&mut self, location: LocationAndType, rw_type: RWType) { 72 | match rw_type { 73 | RWType::ReadOnly => { 74 | self.read_set.insert(location); 75 | } 76 | RWType::WriteOnly => { 77 | self.write_set.insert(location); 78 | } 79 | RWType::ReadWrite => { 80 | self.read_set.insert(location.clone()); 81 | self.write_set.insert(location); 82 | } 83 | } 84 | } 85 | } 86 | 87 | /// Struct to hold shared transaction states and provide methods for parsing 88 | /// and handling execution hints for parallel transaction execution. 89 | pub(crate) struct ParallelExecutionHints { 90 | rw_set: Vec, 91 | txs: Arc>, 92 | } 93 | 94 | impl ParallelExecutionHints { 95 | pub(crate) fn new(txs: Arc>) -> Self { 96 | Self { rw_set: vec![RWSet::new(); txs.len()], txs } 97 | } 98 | 99 | fn generate_dependency(&self) -> TxDependency { 100 | let num_txs = self.txs.len(); 101 | let mut last_write_tx: HashMap = HashMap::new(); 102 | let mut dependent_tx: Vec> = vec![None; num_txs]; 103 | let mut affect_txs = vec![HashSet::new(); num_txs]; 104 | for (txid, rw_set) in self.rw_set.iter().enumerate() { 105 | for location in rw_set.read_set.iter() { 106 | if let Some(&previous) = last_write_tx.get(location) { 107 | let new_dep = dependent_tx[txid].map_or(previous, |dep| max(dep, previous)); 108 | dependent_tx[txid] = Some(new_dep); 109 | affect_txs[previous].insert(txid); 110 | } 111 | } 112 | for location in rw_set.write_set.iter() { 113 | last_write_tx.insert(location.clone(), txid); 114 | } 115 | } 116 | TxDependency::create(dependent_tx, affect_txs) 117 | } 118 | 119 | pub(crate) fn parse_hints(&self) -> TxDependency { 120 | let txs = self.txs.clone(); 121 | // Utilize fork-join utility to process transactions in parallel 122 | fork_join_util(txs.len(), None, |start_tx, end_tx, _| { 123 | #[allow(invalid_reference_casting)] 124 | let hints = unsafe { &mut *(&self.rw_set as *const Vec as *mut Vec) }; 125 | for index in start_tx..end_tx { 126 | let tx_env = &txs[index]; 127 | let rw_set = &mut hints[index]; 128 | // Insert caller's basic location into read-write set 129 | rw_set.insert_location(LocationAndType::Basic(tx_env.caller), RWType::ReadWrite); 130 | if let TxKind::Call(to_address) = tx_env.transact_to { 131 | if !tx_env.data.is_empty() { 132 | rw_set 133 | .insert_location(LocationAndType::Basic(to_address), RWType::ReadOnly); 134 | // Update hints with contract data based on the transaction details 135 | if !ParallelExecutionHints::update_hints_with_contract_data( 136 | tx_env.caller, 137 | to_address, 138 | None, 139 | &tx_env.data, 140 | rw_set, 141 | ) { 142 | rw_set.insert_location( 143 | LocationAndType::Basic(to_address), 144 | RWType::WriteOnly, 145 | ); 146 | } 147 | } else if to_address != tx_env.caller { 148 | rw_set 149 | .insert_location(LocationAndType::Basic(to_address), RWType::ReadWrite); 150 | } 151 | } 152 | } 153 | }); 154 | self.generate_dependency() 155 | } 156 | 157 | /// This function computes the storage slot using the provided slot number and a vector of 158 | /// indices. It utilizes the keccak256 hash function to derive the final storage slot value. 159 | /// 160 | /// # Type Parameters 161 | /// 162 | /// * `K` - The type of the slot number. 163 | /// * `V` - The type of the indices. 164 | /// 165 | /// # Parameters 166 | /// 167 | /// * `slot` - The initial slot number. 168 | /// * `indices` - A vector of indices used to compute the final storage slot. 169 | /// 170 | /// # Returns 171 | /// 172 | /// * `U256` - The computed storage slot. 173 | /// 174 | /// # ABI Standards 175 | /// 176 | /// This function adheres to the ABI (Application Binary Interface) standards for Ethereum 177 | /// Virtual Machine (EVM). For more information on ABI standards, you can refer to the 178 | /// following resources: 179 | /// 180 | /// * [Ethereum Contract ABI Specification](https://docs.soliditylang.org/en/latest/abi-spec.html) 181 | /// * [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf) 182 | /// * [EIP-20: ERC-20 Token Standard](https://eips.ethereum.org/EIPS/eip-20) 183 | /// 184 | /// These resources provide detailed information on how data is encoded and decoded in the EVM, 185 | /// which is essential for understanding how storage slots are calculated. 186 | fn slot_from_indices(slot: K, indices: Vec) -> U256 187 | where 188 | U256: UintTryFrom, 189 | U256: UintTryFrom, 190 | { 191 | let mut result = B256::from(U256::from(slot)); 192 | for index in indices { 193 | let to_prepend = B256::from(U256::from(index)); 194 | result = keccak256([to_prepend.as_slice(), result.as_slice()].concat()) 195 | } 196 | result.into() 197 | } 198 | 199 | fn slot_from_address(slot: u32, addresses: Vec
) -> U256 { 200 | let indices: Vec = addresses 201 | .into_iter() 202 | .map(|address| { 203 | let encoded_as_u160: U160 = address.into(); 204 | U256::from(encoded_as_u160) 205 | }) 206 | .collect(); 207 | Self::slot_from_indices(slot, indices) 208 | } 209 | 210 | fn update_hints_with_contract_data( 211 | caller: Address, 212 | contract_address: Address, 213 | code: Option, 214 | data: &Bytes, 215 | tx_rw_set: &mut RWSet, 216 | ) -> bool { 217 | if code.is_none() && data.is_empty() { 218 | return false; 219 | } 220 | if data.len() < 4 || (data.len() - 4) % 32 != 0 { 221 | // Invalid tx, or tx that triggers fallback CALL 222 | return false; 223 | } 224 | let (func_id, parameters) = Self::decode_contract_parameters(data); 225 | match Self::get_contract_type(contract_address, data) { 226 | ContractType::ERC20 => match ERC20Function::from(func_id) { 227 | ERC20Function::Transfer => { 228 | if parameters.len() != 2 { 229 | return false; 230 | } 231 | let to_address: [u8; 20] = 232 | parameters[0].as_slice()[12..].try_into().expect("try into failed"); 233 | let to_slot = Self::slot_from_address(0, vec![Address::new(to_address)]); 234 | let from_slot = Self::slot_from_address(0, vec![caller]); 235 | tx_rw_set.insert_location( 236 | LocationAndType::Storage(contract_address, from_slot), 237 | RWType::ReadWrite, 238 | ); 239 | if to_slot != from_slot { 240 | tx_rw_set.insert_location( 241 | LocationAndType::Storage(contract_address, to_slot), 242 | RWType::ReadWrite, 243 | ); 244 | } 245 | } 246 | ERC20Function::TransferFrom => { 247 | if parameters.len() != 3 { 248 | return false; 249 | } 250 | let from_address: [u8; 20] = 251 | parameters[0].as_slice()[12..].try_into().expect("try into failed"); 252 | let from_address = Address::new(from_address); 253 | let to_address: [u8; 20] = 254 | parameters[1].as_slice()[12..].try_into().expect("try into failed"); 255 | let to_address = Address::new(to_address); 256 | let from_slot = Self::slot_from_address(1, vec![from_address]); 257 | let to_slot = Self::slot_from_address(1, vec![to_address]); 258 | let allowance_slot = Self::slot_from_address(1, vec![from_address, caller]); 259 | tx_rw_set.insert_location( 260 | LocationAndType::Storage(contract_address, from_slot), 261 | RWType::ReadWrite, 262 | ); 263 | tx_rw_set.insert_location( 264 | LocationAndType::Storage(contract_address, to_slot), 265 | RWType::ReadWrite, 266 | ); 267 | // TODO(gaoxin): if from_slot == to_slot, what happened? 268 | tx_rw_set.insert_location( 269 | LocationAndType::Storage(contract_address, allowance_slot), 270 | RWType::ReadWrite, 271 | ); 272 | } 273 | _ => { 274 | return false; 275 | } 276 | }, 277 | ContractType::UNKNOWN => { 278 | return false; 279 | } 280 | } 281 | true 282 | } 283 | 284 | fn get_contract_type(_contract_address: Address, _data: &Bytes) -> ContractType { 285 | // TODO(gaoxin): Parse the correct contract type to determined how to handle call data 286 | ContractType::ERC20 287 | } 288 | 289 | fn decode_contract_parameters(data: &Bytes) -> (u32, Vec) { 290 | let func_id: u32 = 0; 291 | let mut parameters: Vec = Vec::new(); 292 | if data.len() <= 4 { 293 | return (func_id, parameters); 294 | } 295 | 296 | let func_id = u32::from_be_bytes([data[0], data[1], data[2], data[3]]); 297 | 298 | for chunk in data[4..].chunks(32) { 299 | let array: [u8; 32] = chunk.try_into().expect("Slice has wrong length!"); 300 | parameters.push(B256::from(array)); 301 | } 302 | 303 | (func_id, parameters) 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Grevm 2 | //! 3 | //! Grevm is a high-performance, parallelized Ethereum Virtual Machine (EVM) inspired by BlockSTM 4 | //! designed to handle concurrent transaction execution and validation. It provides utilities for 5 | //! managing transaction states, dependencies, and memory, while leveraging multi-threading to 6 | //! maximize throughput. 7 | //! 8 | //! ## Concurrency 9 | //! 10 | //! Grevm automatically determines the optimal level of concurrency based on the available CPU 11 | //! cores, but this can be customized as needed. The `CONCURRENT_LEVEL` static variable provides the 12 | //! default concurrency level. 13 | //! 14 | //! ## Error Handling 15 | //! 16 | //! Errors during execution are encapsulated in the `GrevmError` type, which includes the 17 | //! transaction ID and the underlying EVM error. This allows for precise debugging and error 18 | //! reporting. 19 | mod async_commit; 20 | mod hint; 21 | mod parallel_state; 22 | mod scheduler; 23 | mod storage; 24 | mod tx_dependency; 25 | mod utils; 26 | 27 | use ahash::{AHashMap as HashMap, AHashSet as HashSet}; 28 | use lazy_static::lazy_static; 29 | use rayon::prelude::{IntoParallelIterator, ParallelIterator}; 30 | use revm_primitives::{AccountInfo, Address, Bytecode, EVMError, EVMResult, B256, U256}; 31 | use std::{cmp::min, thread}; 32 | 33 | lazy_static! { 34 | static ref CONCURRENT_LEVEL: usize = 35 | thread::available_parallelism().map(|n| n.get()).unwrap_or(8); 36 | } 37 | 38 | type TxId = usize; 39 | 40 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 41 | enum TransactionStatus { 42 | #[default] 43 | Initial, 44 | Executing, 45 | Executed, 46 | Validating, 47 | Unconfirmed, 48 | Conflict, 49 | Finality, 50 | } 51 | 52 | #[derive(Debug, Default)] 53 | struct TxState { 54 | pub status: TransactionStatus, 55 | pub incarnation: usize, 56 | pub dependency: Option, 57 | } 58 | 59 | #[derive(Clone, Debug, PartialEq)] 60 | struct TxVersion { 61 | pub txid: TxId, 62 | pub incarnation: usize, 63 | } 64 | 65 | impl TxVersion { 66 | pub(crate) fn new(txid: TxId, incarnation: usize) -> Self { 67 | Self { txid, incarnation } 68 | } 69 | } 70 | 71 | #[derive(Debug, PartialEq)] 72 | enum ReadVersion { 73 | MvMemory(TxVersion), 74 | Storage, 75 | } 76 | 77 | #[derive(Debug, Clone, PartialEq, Eq)] 78 | struct AccountBasic { 79 | /// The balance of the account. 80 | pub balance: U256, 81 | /// The nonce of the account. 82 | pub nonce: u64, 83 | pub code_hash: Option, 84 | } 85 | 86 | #[derive(Debug, Clone)] 87 | enum MemoryValue { 88 | Basic(AccountInfo), 89 | Code(Bytecode), 90 | Storage(U256), 91 | SelfDestructed, 92 | } 93 | 94 | #[derive(Debug, Clone)] 95 | struct MemoryEntry { 96 | incarnation: usize, 97 | data: MemoryValue, 98 | estimate: bool, 99 | } 100 | 101 | impl MemoryEntry { 102 | pub(crate) fn new(incarnation: usize, data: MemoryValue, estimate: bool) -> Self { 103 | Self { incarnation, data, estimate } 104 | } 105 | } 106 | 107 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 108 | enum LocationAndType { 109 | Basic(Address), 110 | 111 | Storage(Address, U256), 112 | 113 | Code(Address), 114 | } 115 | 116 | struct TransactionResult { 117 | pub read_set: HashMap, 118 | pub write_set: HashSet, 119 | pub execute_result: EVMResult, 120 | } 121 | 122 | #[derive(Clone, Debug)] 123 | enum Task { 124 | Execution(TxVersion), 125 | Validation(TxVersion), 126 | } 127 | 128 | impl Default for Task { 129 | fn default() -> Self { 130 | Task::Execution(TxVersion::new(0, 0)) 131 | } 132 | } 133 | 134 | enum AbortReason { 135 | EvmError, 136 | #[allow(dead_code)] 137 | SelfDestructed, 138 | #[allow(dead_code)] 139 | FallbackSequential, 140 | } 141 | 142 | /// Grevm error type. 143 | #[derive(Debug, Clone)] 144 | pub struct GrevmError { 145 | /// The transaction id that caused the error. 146 | pub txid: TxId, 147 | /// The error that occurred. 148 | pub error: EVMError, 149 | } 150 | 151 | /// Utility function for parallel execution using fork-join pattern. 152 | /// 153 | /// This function divides the work into partitions and executes the provided closure `f` 154 | /// in parallel across multiple threads. The number of partitions can be specified, or it 155 | /// will default to twice the number of CPU cores plus one. 156 | /// 157 | /// # Arguments 158 | /// 159 | /// * `num_elements` - The total number of elements to process. 160 | /// * `num_partitions` - Optional number of partitions to divide the work into. 161 | /// * `f` - A closure that takes three arguments: the start index, the end index, and the partition 162 | /// index. 163 | /// 164 | /// # Example 165 | /// 166 | /// ``` 167 | /// use grevm::fork_join_util; 168 | /// fork_join_util(100, Some(4), |start, end, index| { 169 | /// println!("Partition {}: processing elements {} to {}", index, start, end); 170 | /// }); 171 | /// ``` 172 | pub fn fork_join_util<'scope, F>(num_elements: usize, num_partitions: Option, f: F) 173 | where 174 | F: Fn(usize, usize, usize) + Send + Sync + 'scope, 175 | { 176 | let parallel_cnt = num_partitions.unwrap_or(*CONCURRENT_LEVEL); 177 | let remaining = num_elements % parallel_cnt; 178 | let chunk_size = num_elements / parallel_cnt; 179 | (0..parallel_cnt).into_par_iter().for_each(|index| { 180 | let start_pos = chunk_size * index + min(index, remaining); 181 | let mut end_pos = start_pos + chunk_size; 182 | if index < remaining { 183 | end_pos += 1; 184 | } 185 | f(start_pos, end_pos, index); 186 | }); 187 | } 188 | 189 | pub use parallel_state::{ParallelCacheState, ParallelState}; 190 | pub use scheduler::Scheduler; 191 | pub use storage::{ParallelBundleState, ParallelTakeBundle}; 192 | -------------------------------------------------------------------------------- /src/storage.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | fork_join_util, scheduler::MVMemory, AccountBasic, LocationAndType, MemoryEntry, MemoryValue, 3 | ParallelState, ReadVersion, TxId, TxVersion, 4 | }; 5 | use ahash::{AHashMap as HashMap, AHashSet as HashSet}; 6 | use parking_lot::Mutex; 7 | use revm::{ 8 | db::{states::bundle_state::BundleRetention, AccountRevert, BundleAccount, BundleState}, 9 | interpreter::analysis::to_analysed, 10 | TransitionState, 11 | }; 12 | use revm_primitives::{ 13 | db::{Database, DatabaseRef}, 14 | AccountInfo, Address, Bytecode, EvmState, B256, U256, 15 | }; 16 | use std::sync::atomic::{AtomicUsize, Ordering}; 17 | 18 | /// A trait that provides functionality for applying state transitions in parallel 19 | /// and creating reverts for a `BundleState`. 20 | /// 21 | /// This trait is designed to optimize the process of applying transitions and 22 | /// generating reverts by leveraging parallelism, especially when dealing with 23 | /// large sets of transitions. 24 | pub trait ParallelBundleState { 25 | /// apply transitions to create reverts for BundleState in parallel 26 | fn parallel_apply_transitions_and_create_reverts( 27 | &mut self, 28 | transitions: TransitionState, 29 | retention: BundleRetention, 30 | ); 31 | } 32 | 33 | impl ParallelBundleState for BundleState { 34 | fn parallel_apply_transitions_and_create_reverts( 35 | &mut self, 36 | transitions: TransitionState, 37 | retention: BundleRetention, 38 | ) { 39 | if !self.state.is_empty() { 40 | self.apply_transitions_and_create_reverts(transitions, retention); 41 | return; 42 | } 43 | 44 | let include_reverts = retention.includes_reverts(); 45 | // pessimistically pre-allocate assuming _all_ accounts changed. 46 | let reverts_capacity = if include_reverts { transitions.transitions.len() } else { 0 }; 47 | let transitions = transitions.transitions; 48 | let addresses: Vec
= transitions.keys().cloned().collect(); 49 | let reverts: Vec> = vec![None; reverts_capacity]; 50 | let bundle_state: Vec> = vec![None; transitions.len()]; 51 | let state_size = AtomicUsize::new(0); 52 | let contracts = Mutex::new(revm_primitives::HashMap::default()); 53 | 54 | fork_join_util(transitions.len(), None, |start_pos, end_pos, _| { 55 | #[allow(invalid_reference_casting)] 56 | let reverts = unsafe { 57 | &mut *(&reverts as *const Vec> 58 | as *mut Vec>) 59 | }; 60 | #[allow(invalid_reference_casting)] 61 | let addresses = 62 | unsafe { &mut *(&addresses as *const Vec
as *mut Vec
) }; 63 | #[allow(invalid_reference_casting)] 64 | let bundle_state = unsafe { 65 | &mut *(&bundle_state as *const Vec> 66 | as *mut Vec>) 67 | }; 68 | 69 | for pos in start_pos..end_pos { 70 | let address = addresses[pos]; 71 | let transition = transitions.get(&address).cloned().unwrap(); 72 | // add new contract if it was created/changed. 73 | if let Some((hash, new_bytecode)) = transition.has_new_contract() { 74 | contracts.lock().insert(hash, new_bytecode.clone()); 75 | } 76 | let present_bundle = transition.present_bundle_account(); 77 | let revert = transition.create_revert(); 78 | if let Some(revert) = revert { 79 | state_size.fetch_add(present_bundle.size_hint(), Ordering::Relaxed); 80 | bundle_state[pos] = Some((address, present_bundle)); 81 | if include_reverts { 82 | reverts[pos] = Some((address, revert)); 83 | } 84 | } 85 | } 86 | }); 87 | self.state_size = state_size.load(Ordering::Acquire); 88 | 89 | // much faster than bundle_state.into_iter().filter_map(|r| r).collect() 90 | self.state.reserve(transitions.len()); 91 | for bundle in bundle_state { 92 | if let Some((address, state)) = bundle { 93 | self.state.insert(address, state); 94 | } 95 | } 96 | let mut final_reverts = Vec::with_capacity(reverts_capacity); 97 | for revert in reverts { 98 | if let Some(r) = revert { 99 | final_reverts.push(r); 100 | } 101 | } 102 | self.reverts.push(final_reverts); 103 | self.contracts = contracts.into_inner(); 104 | } 105 | } 106 | 107 | /// Provides functionality for extracting a `BundleState` from a `ParallelState` 108 | /// while applying state transitions in parallel. 109 | /// 110 | /// This trait is designed to optimize the process of taking a `BundleState` by leveraging 111 | /// parallelism to apply transitions and generate reverts efficiently. It ensures that the 112 | /// resulting `BundleState` reflects the latest state changes based on the provided retention 113 | /// policy. 114 | pub trait ParallelTakeBundle { 115 | /// take bundle in parallel 116 | fn parallel_take_bundle(&mut self, retention: BundleRetention) -> BundleState; 117 | } 118 | 119 | impl ParallelTakeBundle for ParallelState { 120 | fn parallel_take_bundle(&mut self, retention: BundleRetention) -> BundleState { 121 | if let Some(transition_state) = self.transition_state.as_mut().map(TransitionState::take) { 122 | self.bundle_state 123 | .parallel_apply_transitions_and_create_reverts(transition_state, retention); 124 | } 125 | self.take_bundle() 126 | } 127 | } 128 | 129 | pub(crate) struct CacheDB<'a, DB> 130 | where 131 | DB: DatabaseRef, 132 | { 133 | coinbase: Address, 134 | db: &'a DB, 135 | mv_memory: &'a MVMemory, 136 | commit_idx: &'a AtomicUsize, 137 | 138 | read_set: HashMap, 139 | read_accounts: HashMap, 140 | current_tx: TxVersion, 141 | accurate_origin: bool, 142 | estimate_txs: HashSet, 143 | } 144 | 145 | impl<'a, DB> CacheDB<'a, DB> 146 | where 147 | DB: DatabaseRef, 148 | { 149 | pub(crate) fn new( 150 | coinbase: Address, 151 | db: &'a DB, 152 | mv_memory: &'a MVMemory, 153 | commit_idx: &'a AtomicUsize, 154 | ) -> Self { 155 | Self { 156 | coinbase, 157 | db, 158 | mv_memory, 159 | commit_idx, 160 | read_set: HashMap::new(), 161 | read_accounts: HashMap::new(), 162 | current_tx: TxVersion::new(0, 0), 163 | accurate_origin: true, 164 | estimate_txs: HashSet::new(), 165 | } 166 | } 167 | 168 | pub(crate) fn reset_state(&mut self, tx_version: TxVersion) { 169 | self.current_tx = tx_version; 170 | self.read_set.clear(); 171 | self.read_accounts.clear(); 172 | self.accurate_origin = true; 173 | self.estimate_txs.clear(); 174 | } 175 | 176 | pub(crate) fn read_accurate_origin(&self) -> bool { 177 | self.accurate_origin 178 | } 179 | 180 | pub(crate) fn take_estimate_txs(&mut self) -> HashSet { 181 | std::mem::take(&mut self.estimate_txs) 182 | } 183 | 184 | pub(crate) fn take_read_set(&mut self) -> HashMap { 185 | std::mem::take(&mut self.read_set) 186 | } 187 | 188 | pub(crate) fn update_mv_memory( 189 | &self, 190 | changes: &EvmState, 191 | estimate: bool, 192 | ) -> HashSet { 193 | let mut write_set = HashSet::new(); 194 | for (address, account) in changes { 195 | if *address == self.coinbase { 196 | continue; 197 | } 198 | if account.is_selfdestructed() { 199 | let memory_entry = MemoryEntry::new( 200 | self.current_tx.incarnation, 201 | MemoryValue::SelfDestructed, 202 | estimate, 203 | ); 204 | write_set.insert(LocationAndType::Basic(address.clone())); 205 | self.mv_memory 206 | .entry(LocationAndType::Basic(address.clone())) 207 | .or_default() 208 | .insert(self.current_tx.txid, memory_entry); 209 | continue; 210 | } 211 | 212 | // If the account is touched, it means that the account's state has been modified 213 | // during the transaction. This includes changes to the account's balance, nonce, 214 | // or code. We need to track these changes to ensure the correct state is committed 215 | // after the transaction. 216 | if account.is_touched() { 217 | let read_account = self.read_accounts.get(address); 218 | let has_code = !account.info.is_empty_code_hash(); 219 | // is newly created contract 220 | let new_contract = has_code && 221 | account.info.code.is_some() && 222 | read_account.map_or(true, |account| account.code_hash.is_none()); 223 | if new_contract { 224 | let location = LocationAndType::Code(address.clone()); 225 | write_set.insert(location.clone()); 226 | self.mv_memory.entry(location).or_default().insert( 227 | self.current_tx.txid, 228 | MemoryEntry::new( 229 | self.current_tx.incarnation, 230 | MemoryValue::Code(to_analysed(account.info.code.clone().unwrap())), 231 | estimate, 232 | ), 233 | ); 234 | } 235 | 236 | if new_contract || 237 | read_account.is_none() || 238 | read_account.is_some_and(|basic| { 239 | basic.nonce != account.info.nonce || basic.balance != account.info.balance 240 | }) 241 | { 242 | let location = LocationAndType::Basic(address.clone()); 243 | write_set.insert(location.clone()); 244 | self.mv_memory.entry(location).or_default().insert( 245 | self.current_tx.txid, 246 | MemoryEntry::new( 247 | self.current_tx.incarnation, 248 | MemoryValue::Basic(AccountInfo { code: None, ..account.info }), 249 | estimate, 250 | ), 251 | ); 252 | } 253 | } 254 | 255 | for (slot, value) in account.changed_storage_slots() { 256 | let location = LocationAndType::Storage(*address, *slot); 257 | write_set.insert(location.clone()); 258 | self.mv_memory.entry(location).or_default().insert( 259 | self.current_tx.txid, 260 | MemoryEntry::new( 261 | self.current_tx.incarnation, 262 | MemoryValue::Storage(value.present_value), 263 | estimate, 264 | ), 265 | ); 266 | } 267 | } 268 | 269 | write_set 270 | } 271 | 272 | fn code_by_address( 273 | &mut self, 274 | address: Address, 275 | code_hash: B256, 276 | ) -> Result { 277 | let mut result = None; 278 | let mut read_version = ReadVersion::Storage; 279 | let location = LocationAndType::Code(address); 280 | // 1. read from multi-version memory 281 | if let Some(written_transactions) = self.mv_memory.get(&location) { 282 | if let Some((&txid, entry)) = 283 | written_transactions.range(..self.current_tx.txid).next_back() 284 | { 285 | match &entry.data { 286 | MemoryValue::Code(code) => { 287 | result = Some(code.clone()); 288 | if entry.estimate { 289 | self.estimate_txs.insert(txid); 290 | } 291 | read_version = 292 | ReadVersion::MvMemory(TxVersion::new(txid, entry.incarnation)); 293 | } 294 | _ => {} 295 | } 296 | } 297 | } 298 | // 2. read from database 299 | if result.is_none() { 300 | let byte_code = self.db.code_by_hash_ref(code_hash)?; 301 | result = Some(byte_code); 302 | } 303 | 304 | self.read_set.insert(location, read_version); 305 | Ok(result.expect("No bytecode")) 306 | } 307 | 308 | fn clear_destructed_entry(&self, account: Address) { 309 | let current_tx = self.current_tx.txid; 310 | for mut entry in self.mv_memory.iter_mut() { 311 | let destructed = match entry.key() { 312 | LocationAndType::Basic(address) => *address == account, 313 | LocationAndType::Storage(address, _) => *address == account, 314 | LocationAndType::Code(address) => *address == account, 315 | }; 316 | if destructed { 317 | *entry.value_mut() = entry.value_mut().split_off(¤t_tx); 318 | } 319 | } 320 | } 321 | } 322 | 323 | impl<'a, DB> Database for CacheDB<'a, DB> 324 | where 325 | DB: DatabaseRef, 326 | { 327 | type Error = DB::Error; 328 | 329 | fn basic(&mut self, address: Address) -> Result, Self::Error> { 330 | let mut result = None; 331 | if address == self.coinbase { 332 | self.accurate_origin = self.commit_idx.load(Ordering::Acquire) == self.current_tx.txid; 333 | result = self.db.basic_ref(address.clone())?; 334 | } else { 335 | let mut read_version = ReadVersion::Storage; 336 | let mut read_account = AccountBasic { balance: U256::ZERO, nonce: 0, code_hash: None }; 337 | let location = LocationAndType::Basic(address.clone()); 338 | // 1. read from multi-version memory 339 | let mut clear_destructed_entry = false; 340 | if let Some(written_transactions) = self.mv_memory.get(&location) { 341 | if let Some((&txid, entry)) = 342 | written_transactions.range(..self.current_tx.txid).next_back() 343 | { 344 | match &entry.data { 345 | MemoryValue::Basic(info) => { 346 | result = Some(info.clone()); 347 | read_account = AccountBasic { 348 | balance: info.balance, 349 | nonce: info.nonce, 350 | code_hash: if info.is_empty_code_hash() { 351 | None 352 | } else { 353 | Some(info.code_hash) 354 | }, 355 | }; 356 | if entry.estimate { 357 | self.estimate_txs.insert(txid); 358 | } 359 | read_version = 360 | ReadVersion::MvMemory(TxVersion::new(txid, entry.incarnation)); 361 | } 362 | MemoryValue::SelfDestructed => { 363 | if self.commit_idx.load(Ordering::Acquire) == self.current_tx.txid { 364 | // make sure read after the latest self-destructed 365 | clear_destructed_entry = true; 366 | } else { 367 | self.accurate_origin = false; 368 | result = Some(AccountInfo::default()); 369 | } 370 | } 371 | _ => {} 372 | } 373 | } 374 | } 375 | if clear_destructed_entry { 376 | self.clear_destructed_entry(address); 377 | } 378 | // 2. read from database 379 | if result.is_none() { 380 | let info = self.db.basic_ref(address.clone())?; 381 | if let Some(info) = info { 382 | read_account = AccountBasic { 383 | balance: info.balance, 384 | nonce: info.nonce, 385 | code_hash: if info.is_empty_code_hash() { 386 | None 387 | } else { 388 | Some(info.code_hash) 389 | }, 390 | }; 391 | result = Some(info.clone()); 392 | } 393 | } 394 | self.read_accounts.insert(address, read_account); 395 | self.read_set.insert(location, read_version); 396 | } 397 | 398 | if let Some(info) = &mut result { 399 | if !info.is_empty_code_hash() && info.code.is_none() { 400 | info.code = Some(self.code_by_address(address.clone(), info.code_hash)?); 401 | } 402 | } 403 | Ok(result) 404 | } 405 | 406 | fn code_by_hash(&mut self, code_hash: B256) -> Result { 407 | self.db.code_by_hash_ref(code_hash) 408 | } 409 | 410 | fn storage(&mut self, address: Address, index: U256) -> Result { 411 | let mut result = None; 412 | let mut read_version = ReadVersion::Storage; 413 | let location = LocationAndType::Storage(address.clone(), index.clone()); 414 | // 1. read from multi-version memory 415 | if let Some(written_transactions) = self.mv_memory.get(&location) { 416 | if let Some((&txid, entry)) = 417 | written_transactions.range(..self.current_tx.txid).next_back() 418 | { 419 | if let MemoryValue::Storage(slot) = &entry.data { 420 | result = Some(slot.clone()); 421 | if entry.estimate { 422 | self.estimate_txs.insert(txid); 423 | } 424 | read_version = ReadVersion::MvMemory(TxVersion::new(txid, entry.incarnation)); 425 | } 426 | } 427 | } 428 | // 2. read from database 429 | if result.is_none() { 430 | let mut new_ca = false; 431 | if let Some(ReadVersion::MvMemory(_)) = 432 | self.read_set.get(&LocationAndType::Code(address.clone())) 433 | { 434 | new_ca = true; 435 | } 436 | let slot = 437 | if new_ca { U256::default() } else { self.db.storage_ref(address, index)? }; 438 | result = Some(slot.clone()); 439 | } 440 | 441 | self.read_set.insert(location, read_version); 442 | Ok(result.expect("No storage slot")) 443 | } 444 | 445 | fn block_hash(&mut self, number: u64) -> Result { 446 | self.db.block_hash_ref(number) 447 | } 448 | } 449 | -------------------------------------------------------------------------------- /src/tx_dependency.rs: -------------------------------------------------------------------------------- 1 | use crate::TxId; 2 | use ahash::AHashSet as HashSet; 3 | use parking_lot::Mutex; 4 | use std::sync::atomic::{AtomicUsize, Ordering}; 5 | 6 | #[derive(Debug, Clone)] 7 | struct DependentState { 8 | onboard: bool, 9 | dependency: Option, 10 | } 11 | 12 | impl Default for DependentState { 13 | fn default() -> Self { 14 | Self { onboard: true, dependency: None } 15 | } 16 | } 17 | 18 | pub(crate) struct TxDependency { 19 | num_txs: usize, 20 | dependent_state: Vec>, 21 | affect_txs: Vec>>, 22 | index: AtomicUsize, 23 | } 24 | 25 | impl TxDependency { 26 | pub(crate) fn new(num_txs: usize) -> Self { 27 | Self { 28 | num_txs, 29 | dependent_state: (0..num_txs).map(|_| Default::default()).collect(), 30 | affect_txs: (0..num_txs).map(|_| Default::default()).collect(), 31 | index: AtomicUsize::new(0), 32 | } 33 | } 34 | 35 | pub(crate) fn create(dependent_tx: Vec>, affect_txs: Vec>) -> Self { 36 | assert_eq!(dependent_tx.len(), affect_txs.len()); 37 | let num_txs = dependent_tx.len(); 38 | Self { 39 | num_txs, 40 | dependent_state: dependent_tx 41 | .into_iter() 42 | .map(|dep| Mutex::new(DependentState { onboard: true, dependency: dep })) 43 | .collect(), 44 | affect_txs: affect_txs.into_iter().map(|affects| Mutex::new(affects)).collect(), 45 | index: AtomicUsize::new(0), 46 | } 47 | } 48 | 49 | pub(crate) fn next(&self) -> Option { 50 | if self.index.load(Ordering::Relaxed) < self.num_txs { 51 | let index = self.index.fetch_add(1, Ordering::Relaxed); 52 | if index < self.num_txs { 53 | let mut state = self.dependent_state[index].lock(); 54 | if state.onboard && state.dependency.is_none() { 55 | state.onboard = false; 56 | return Some(index) 57 | } 58 | } 59 | } 60 | None 61 | } 62 | 63 | pub(crate) fn index(&self) -> usize { 64 | self.index.load(Ordering::Relaxed) 65 | } 66 | 67 | /// The benchmark `bench_dependency_distance` tests how transaction dependency distance affects 68 | /// actual conflicts. When dependency_distance ≤ 4, transactions exhibit significantly higher 69 | /// conflict probability, with the most pronounced effect occurring at distance = 1. 70 | /// Accordingly, Grevm specifically checks dependencies with distance = 1 when updating the DAG. 71 | /// This optimization balances conflict detection accuracy with computational efficiency. 72 | pub(crate) fn remove(&self, txid: TxId, pop_next: bool) -> Option { 73 | let mut next = None; 74 | let mut affects = self.affect_txs[txid].lock(); 75 | if affects.is_empty() { 76 | return next; 77 | } 78 | for &tx in affects.iter() { 79 | let mut dependent = self.dependent_state[tx].lock(); 80 | if dependent.dependency == Some(txid) { 81 | dependent.dependency = None; 82 | if dependent.onboard { 83 | if pop_next && tx == txid + 1 && self.index.load(Ordering::Relaxed) > tx { 84 | dependent.onboard = false; 85 | next = Some(tx); 86 | } else { 87 | self.index.fetch_min(tx, Ordering::Relaxed); 88 | } 89 | } 90 | } 91 | } 92 | affects.clear(); 93 | next 94 | } 95 | 96 | pub(crate) fn commit(&self, txid: TxId) { 97 | let next = txid + 1; 98 | if next < self.num_txs { 99 | let mut state = self.dependent_state[next].lock(); 100 | if state.onboard { 101 | state.dependency = None; 102 | self.index.fetch_min(next, Ordering::Relaxed); 103 | } 104 | } 105 | } 106 | 107 | /// When transactions fail due to EVM execution errors or access incorrect miner/self-destructed 108 | /// states, Grevm assigns them a special self-referential dependency (where dependency equals 109 | /// the transaction's own ID). This dependency is only cleared when commit_idx matches txid, 110 | /// ensuring the transaction can only proceed after obtaining the correct account state. This 111 | /// mechanism guarantees state consistency while maintaining parallel execution capabilities. 112 | pub(crate) fn key_tx(&self, txid: TxId, commit_idx: &AtomicUsize) { 113 | let mut state = self.dependent_state[txid].lock(); 114 | if txid > commit_idx.load(Ordering::Acquire) { 115 | state.dependency = Some(txid); 116 | } 117 | if !state.onboard { 118 | state.onboard = true; 119 | } 120 | if state.dependency.is_none() { 121 | self.index.fetch_min(txid, Ordering::Relaxed); 122 | } 123 | } 124 | 125 | pub(crate) fn add(&self, txid: TxId, dep_id: Option) { 126 | if let Some(dep_id) = dep_id { 127 | let mut dep = self.affect_txs[dep_id].lock(); 128 | let mut dep_state = self.dependent_state[dep_id].lock(); 129 | let mut state = self.dependent_state[txid].lock(); 130 | state.dependency = Some(dep_id); 131 | if !state.onboard { 132 | state.onboard = true; 133 | } 134 | 135 | dep.insert(txid); 136 | if !dep_state.onboard { 137 | dep_state.onboard = true; 138 | } 139 | if dep_state.dependency.is_none() { 140 | self.index.fetch_min(dep_id, Ordering::Relaxed); 141 | } 142 | } else { 143 | let mut state = self.dependent_state[txid].lock(); 144 | if !state.onboard { 145 | state.onboard = true; 146 | state.dependency = None; 147 | self.index.fetch_min(txid, Ordering::Relaxed); 148 | } 149 | } 150 | } 151 | 152 | pub(crate) fn print(&self) { 153 | let dependent_tx: Vec<(TxId, DependentState)> = 154 | self.dependent_state.iter().map(|dep| dep.lock().clone()).enumerate().collect(); 155 | let affect_txs: Vec<(TxId, HashSet)> = 156 | self.affect_txs.iter().map(|affects| affects.lock().clone()).enumerate().collect(); 157 | println!("tx_states: {:?}", dependent_tx); 158 | println!("affect_txs: {:?}", affect_txs); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicUsize, Ordering}; 2 | 3 | #[derive(Debug)] 4 | pub(crate) struct ContinuousDetectSet { 5 | num_flag: usize, 6 | index_flag: Vec, 7 | num_index: AtomicUsize, 8 | continuous_idx: AtomicUsize, 9 | } 10 | 11 | impl ContinuousDetectSet { 12 | pub(crate) fn new(num_flag: usize) -> Self { 13 | Self { 14 | num_flag, 15 | index_flag: vec![false; num_flag], 16 | num_index: AtomicUsize::new(0), 17 | continuous_idx: AtomicUsize::new(0), 18 | } 19 | } 20 | 21 | fn check_continuous(&self) { 22 | let mut continuous_idx = self.continuous_idx.load(Ordering::Relaxed); 23 | while continuous_idx < self.num_flag && self.index_flag[continuous_idx] { 24 | if self 25 | .continuous_idx 26 | .compare_exchange( 27 | continuous_idx, 28 | continuous_idx + 1, 29 | Ordering::Relaxed, 30 | Ordering::Relaxed, 31 | ) 32 | .is_err() 33 | { 34 | break; 35 | } 36 | continuous_idx = self.continuous_idx.load(Ordering::Relaxed); 37 | } 38 | } 39 | 40 | pub(crate) fn add(&self, index: usize) { 41 | if !self.index_flag[index] { 42 | #[allow(invalid_reference_casting)] 43 | let index_flag = 44 | unsafe { &mut *(&self.index_flag as *const Vec as *mut Vec) }; 45 | index_flag[index] = true; 46 | self.num_index.fetch_add(1, Ordering::Relaxed); 47 | self.check_continuous(); 48 | } 49 | } 50 | 51 | pub(crate) fn continuous_idx(&self) -> usize { 52 | if self.num_index.load(Ordering::Relaxed) >= self.num_flag && 53 | self.continuous_idx.load(Ordering::Relaxed) < self.num_flag 54 | { 55 | self.check_continuous(); 56 | } 57 | self.continuous_idx.load(Ordering::Relaxed) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/common/execute.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{storage::InMemoryDB, MINER_ADDRESS}; 2 | use metrics_util::debugging::{DebugValue, DebuggingRecorder}; 3 | 4 | use alloy_chains::NamedChain; 5 | use grevm::{ParallelState, ParallelTakeBundle, Scheduler}; 6 | use revm::{ 7 | db::{ 8 | states::{bundle_state::BundleRetention, StorageSlot}, 9 | AccountRevert, BundleAccount, BundleState, PlainAccount, 10 | }, 11 | primitives::{ 12 | alloy_primitives::U160, uint, AccountInfo, Address, Bytecode, EVMError, Env, 13 | ExecutionResult, SpecId, TxEnv, B256, KECCAK_EMPTY, U256, 14 | }, 15 | CacheState, DatabaseCommit, DatabaseRef, EvmBuilder, StateBuilder, TransitionState, 16 | }; 17 | use revm_primitives::{EnvWithHandlerCfg, ResultAndState}; 18 | use std::{ 19 | cmp::min, 20 | collections::{BTreeMap, HashMap}, 21 | fmt::{Debug, Display}, 22 | fs::{self, File}, 23 | io::{BufReader, BufWriter}, 24 | sync::Arc, 25 | time::Instant, 26 | }; 27 | 28 | pub(crate) fn compare_result_and_state(left: &Vec, right: &Vec) { 29 | assert_eq!(left.len(), right.len()); 30 | for (l, r) in left.iter().zip(right.iter()) {} 31 | for txid in 0..left.len() { 32 | assert_eq!(left[txid].rewards, right[txid].rewards, "Tx {}", txid); 33 | assert_eq!(left[txid].result, right[txid].result, "Tx {}", txid); 34 | let l = &left[txid].state; 35 | let r = &right[txid].state; 36 | for (address, l) in l { 37 | let r = 38 | r.get(address).expect(format!("no account {:?} in Tx {}", address, txid).as_str()); 39 | assert_eq!(l.info, r.info, "account {:?} info in Tx {}", address, txid); 40 | assert_eq!(l.storage, r.storage, "account {:?} storage in Tx {}", address, txid); 41 | assert_eq!(l.status, r.status, "account {:?} status in Tx {}", address, txid); 42 | } 43 | } 44 | } 45 | 46 | pub(crate) fn compare_bundle_state(left: &BundleState, right: &BundleState) { 47 | assert!( 48 | left.contracts.keys().all(|k| right.contracts.contains_key(k)), 49 | "Left contracts: {:?}, Right contracts: {:?}", 50 | left.contracts.keys(), 51 | right.contracts.keys() 52 | ); 53 | assert_eq!( 54 | left.contracts.len(), 55 | right.contracts.len(), 56 | "Left contracts: {:?}, Right contracts: {:?}", 57 | left.contracts.keys(), 58 | right.contracts.keys() 59 | ); 60 | 61 | let left_state: BTreeMap<&Address, &BundleAccount> = left.state.iter().collect(); 62 | let right_state: BTreeMap<&Address, &BundleAccount> = right.state.iter().collect(); 63 | assert_eq!(left_state.len(), right_state.len()); 64 | 65 | for ((addr1, account1), (addr2, account2)) in 66 | left_state.into_iter().zip(right_state.into_iter()) 67 | { 68 | assert_eq!(addr1, addr2); 69 | assert_eq!(account1.info, account2.info, "Address: {:?}", addr1); 70 | assert_eq!(account1.original_info, account2.original_info, "Address: {:?}", addr1); 71 | assert_eq!(account1.status, account2.status, "Address: {:?}", addr1); 72 | assert_eq!(account1.storage.len(), account2.storage.len()); 73 | let left_storage: BTreeMap<&U256, &StorageSlot> = account1.storage.iter().collect(); 74 | let right_storage: BTreeMap<&U256, &StorageSlot> = account2.storage.iter().collect(); 75 | for (s1, s2) in left_storage.into_iter().zip(right_storage.into_iter()) { 76 | assert_eq!(s1, s2, "Address: {:?}", addr1); 77 | } 78 | } 79 | 80 | assert_eq!(left.reverts.len(), right.reverts.len()); 81 | for (left, right) in left.reverts.iter().zip(right.reverts.iter()) { 82 | assert_eq!(left.len(), right.len()); 83 | let right: HashMap<&Address, &AccountRevert> = right.iter().map(|(k, v)| (k, v)).collect(); 84 | for (addr, revert) in left.iter() { 85 | assert_eq!(revert, *right.get(addr).unwrap(), "Address: {:?}", addr); 86 | } 87 | } 88 | } 89 | 90 | pub(crate) fn compare_execution_result(left: &Vec, right: &Vec) { 91 | for (i, (left_res, right_res)) in left.iter().zip(right.iter()).enumerate() { 92 | assert_eq!(left_res, right_res, "Tx {}", i); 93 | } 94 | assert_eq!(left.len(), right.len()); 95 | } 96 | 97 | pub(crate) fn mock_miner_account() -> (Address, PlainAccount) { 98 | let address = Address::from(U160::from(MINER_ADDRESS)); 99 | let account = PlainAccount { 100 | info: AccountInfo { balance: U256::from(0), nonce: 1, code_hash: KECCAK_EMPTY, code: None }, 101 | storage: Default::default(), 102 | }; 103 | (address, account) 104 | } 105 | 106 | pub(crate) fn mock_eoa_account(idx: usize) -> (Address, PlainAccount) { 107 | let address = Address::from(U160::from(idx)); 108 | let account = PlainAccount { 109 | info: AccountInfo { 110 | balance: uint!(1_000_000_000_000_000_000_U256), 111 | nonce: 1, 112 | code_hash: KECCAK_EMPTY, 113 | code: None, 114 | }, 115 | storage: Default::default(), 116 | }; 117 | (address, account) 118 | } 119 | 120 | pub(crate) fn mock_block_accounts(from: usize, size: usize) -> HashMap { 121 | let mut accounts: HashMap = 122 | (from..(from + size)).map(mock_eoa_account).collect(); 123 | let miner = mock_miner_account(); 124 | accounts.insert(miner.0, miner.1); 125 | accounts 126 | } 127 | 128 | pub(crate) fn compare_evm_execute( 129 | db: DB, 130 | txs: Vec, 131 | with_hints: bool, 132 | parallel_metrics: HashMap<&str, usize>, 133 | ) where 134 | DB: DatabaseRef + Send + Sync, 135 | DB::Error: Send + Sync + Clone + Debug, 136 | { 137 | // create registry for metrics 138 | let recorder = DebuggingRecorder::new(); 139 | 140 | let mut env = Env::default(); 141 | env.cfg.chain_id = NamedChain::Mainnet.into(); 142 | env.block.coinbase = Address::from(U160::from(MINER_ADDRESS)); 143 | let db = Arc::new(db); 144 | let txs = Arc::new(txs); 145 | 146 | let parallel_result = metrics::with_local_recorder(&recorder, || { 147 | let start = Instant::now(); 148 | let state = ParallelState::new(db.clone(), true, true); 149 | let mut executor = 150 | Scheduler::new(SpecId::LATEST, env.clone(), txs.clone(), state, with_hints); 151 | // set determined partitions 152 | executor.parallel_execute(Some(23)).expect("parallel execute failed"); 153 | println!("Grevm parallel execute time: {}ms", start.elapsed().as_millis()); 154 | 155 | let snapshot = recorder.snapshotter().snapshot(); 156 | for (key, _, _, value) in snapshot.into_vec() { 157 | let value = match value { 158 | DebugValue::Counter(v) => v as usize, 159 | DebugValue::Gauge(v) => v.0 as usize, 160 | DebugValue::Histogram(v) => v.last().cloned().map_or(0, |ov| ov.0 as usize), 161 | }; 162 | println!("metrics: {} => value: {:?}", key.key().name(), value); 163 | if let Some(metric) = parallel_metrics.get(key.key().name()) { 164 | assert_eq!(*metric, value); 165 | } 166 | } 167 | let (results, mut state) = executor.take_result_and_state(); 168 | (results, state.parallel_take_bundle(BundleRetention::Reverts)) 169 | }); 170 | 171 | let start = Instant::now(); 172 | let reth_result = 173 | execute_revm_sequential(db.clone(), SpecId::LATEST, env.clone(), &*txs).unwrap(); 174 | println!("Origin sequential execute time: {}ms", start.elapsed().as_millis()); 175 | 176 | let mut max_gas_spent = 0; 177 | let mut max_gas_used = 0; 178 | for result in reth_result.0.iter() { 179 | match result { 180 | ExecutionResult::Success { gas_used, gas_refunded, .. } => { 181 | max_gas_spent = max_gas_spent.max(gas_used + gas_refunded); 182 | max_gas_used = max_gas_used.max(*gas_used); 183 | } 184 | _ => panic!("result is not success"), 185 | } 186 | } 187 | println!("max_gas_spent: {}, max_gas_used: {}", max_gas_spent, max_gas_used); 188 | compare_execution_result(&reth_result.0, ¶llel_result.0); 189 | compare_bundle_state(&reth_result.1, ¶llel_result.1); 190 | } 191 | 192 | /// Simulate the sequential execution of transactions in reth 193 | pub(crate) fn execute_revm_sequential( 194 | db: DB, 195 | spec_id: SpecId, 196 | env: Env, 197 | txs: &[TxEnv], 198 | ) -> Result<(Vec, BundleState), EVMError> 199 | where 200 | DB: DatabaseRef, 201 | DB::Error: Debug, 202 | { 203 | let db = StateBuilder::new().with_bundle_update().with_database_ref(db).build(); 204 | let mut evm = 205 | EvmBuilder::default().with_db(db).with_spec_id(spec_id).with_env(Box::new(env)).build(); 206 | 207 | let mut results = Vec::with_capacity(txs.len()); 208 | for tx in txs { 209 | *evm.tx_mut() = tx.clone(); 210 | let result_and_state = evm.transact()?; 211 | evm.db_mut().commit(result_and_state.state); 212 | results.push(result_and_state.result); 213 | } 214 | evm.db_mut().merge_transitions(BundleRetention::Reverts); 215 | 216 | Ok((results, evm.db_mut().take_bundle())) 217 | } 218 | 219 | const TEST_DATA_DIR: &str = "test_data"; 220 | 221 | pub(crate) fn load_bytecodes_from_disk() -> HashMap { 222 | // Parse bytecodes 223 | bincode::deserialize_from(BufReader::new( 224 | File::open(format!("{TEST_DATA_DIR}/bytecodes.bincode")).unwrap(), 225 | )) 226 | .unwrap() 227 | } 228 | 229 | pub(crate) fn continuous_blocks_exist(blocks: String) -> bool { 230 | if let Ok(exist) = fs::exists(format!("{TEST_DATA_DIR}/con_eth_blocks/{blocks}")) { 231 | exist 232 | } else { 233 | false 234 | } 235 | } 236 | 237 | pub(crate) fn load_continuous_blocks( 238 | blocks: String, 239 | num_blocks: Option, 240 | ) -> (Vec<(EnvWithHandlerCfg, Vec, HashMap)>, InMemoryDB) { 241 | let splits: Vec<&str> = blocks.split('_').collect(); 242 | let start_block: usize = splits[0].parse().unwrap(); 243 | let mut end_block: usize = splits[1].parse::().unwrap() + 1; 244 | if let Some(num_blocks) = num_blocks { 245 | end_block = min(end_block, start_block + num_blocks); 246 | } 247 | let mut block_txs = Vec::with_capacity(end_block - start_block); 248 | for block_number in start_block..end_block { 249 | let env: EnvWithHandlerCfg = serde_json::from_reader(BufReader::new( 250 | File::open(format!("{TEST_DATA_DIR}/con_eth_blocks/{blocks}/{block_number}/env.json")) 251 | .unwrap(), 252 | )) 253 | .unwrap(); 254 | let txs: Vec = serde_json::from_reader(BufReader::new( 255 | File::open(format!("{TEST_DATA_DIR}/con_eth_blocks/{blocks}/{block_number}/txs.json")) 256 | .unwrap(), 257 | )) 258 | .unwrap(); 259 | let post: HashMap = serde_json::from_reader(BufReader::new( 260 | File::open(format!("{TEST_DATA_DIR}/con_eth_blocks/{blocks}/{block_number}/post.json")) 261 | .unwrap(), 262 | )) 263 | .unwrap(); 264 | block_txs.push((env, txs, post)); 265 | } 266 | 267 | // Parse state 268 | let accounts: HashMap = serde_json::from_reader(BufReader::new( 269 | File::open(format!("{TEST_DATA_DIR}/con_eth_blocks/{blocks}/pre_state.json")).unwrap(), 270 | )) 271 | .unwrap(); 272 | 273 | // Parse block hashes 274 | let block_hashes: HashMap = 275 | File::open(format!("{TEST_DATA_DIR}/con_eth_blocks/{blocks}/block_hashes.json")) 276 | .map(|file| serde_json::from_reader(BufReader::new(file)).unwrap()) 277 | .unwrap_or_default(); 278 | 279 | // Parse bytecodes 280 | let bytecodes: HashMap = 281 | File::open(format!("{TEST_DATA_DIR}/con_eth_blocks/{blocks}/bytecodes.bincode")) 282 | .map(|file| bincode::deserialize_from(BufReader::new(file)).unwrap()) 283 | .unwrap_or_default(); 284 | 285 | (block_txs, InMemoryDB::new(accounts, bytecodes, block_hashes)) 286 | } 287 | 288 | pub(crate) fn load_block_from_disk( 289 | block_number: u64, 290 | ) -> (EnvWithHandlerCfg, Vec, InMemoryDB) { 291 | // Parse block 292 | let env: EnvWithHandlerCfg = serde_json::from_reader(BufReader::new( 293 | File::open(format!("{TEST_DATA_DIR}/blocks/{block_number}/env.json")).unwrap(), 294 | )) 295 | .unwrap(); 296 | let txs: Vec = serde_json::from_reader(BufReader::new( 297 | File::open(format!("{TEST_DATA_DIR}/blocks/{block_number}/txs.json")).unwrap(), 298 | )) 299 | .unwrap(); 300 | 301 | // Parse state 302 | let accounts: HashMap = serde_json::from_reader(BufReader::new( 303 | File::open(format!("{TEST_DATA_DIR}/blocks/{block_number}/pre_state.json")).unwrap(), 304 | )) 305 | .unwrap(); 306 | 307 | // Parse block hashes 308 | let block_hashes: HashMap = 309 | File::open(format!("{TEST_DATA_DIR}/blocks/{block_number}/block_hashes.json")) 310 | .map(|file| serde_json::from_reader(BufReader::new(file)).unwrap()) 311 | .unwrap_or_default(); 312 | 313 | // Parse bytecodes 314 | let bytecodes: HashMap = 315 | File::open(format!("{TEST_DATA_DIR}/blocks/{block_number}/bytecodes.bincode")) 316 | .map(|file| bincode::deserialize_from(BufReader::new(file)).unwrap()) 317 | .unwrap_or_default(); 318 | 319 | (env, txs, InMemoryDB::new(accounts, bytecodes, block_hashes)) 320 | } 321 | 322 | pub(crate) fn for_each_block_from_disk( 323 | mut handler: impl FnMut(EnvWithHandlerCfg, Vec, InMemoryDB), 324 | ) { 325 | // Parse bytecodes 326 | let bytecodes = load_bytecodes_from_disk(); 327 | 328 | for block_path in fs::read_dir(format!("{TEST_DATA_DIR}/blocks")).unwrap() { 329 | let block_path = block_path.unwrap().path(); 330 | let block_number = block_path.file_name().unwrap().to_str().unwrap(); 331 | 332 | let (env, txs, mut db) = load_block_from_disk(block_number.parse().unwrap()); 333 | if db.bytecodes.is_empty() { 334 | // Use the global bytecodes if the block doesn't have its own 335 | db.bytecodes = bytecodes.clone(); 336 | } 337 | handler(env, txs, db); 338 | } 339 | } 340 | 341 | pub(crate) fn dump_block_env( 342 | env: &EnvWithHandlerCfg, 343 | txs: &[TxEnv], 344 | cache_state: &CacheState, 345 | transition_state: &TransitionState, 346 | block_hashes: &BTreeMap, 347 | ) { 348 | let path = format!("{}/large_block", TEST_DATA_DIR); 349 | 350 | // Write env data to file 351 | serde_json::to_writer(BufWriter::new(File::create(format!("{path}/env.json")).unwrap()), env) 352 | .unwrap(); 353 | 354 | // Write txs data to file 355 | serde_json::to_writer(BufWriter::new(File::create(format!("{path}/txs.json")).unwrap()), txs) 356 | .unwrap(); 357 | 358 | // Write pre-state and bytecodes data to file 359 | let mut pre_state: HashMap = 360 | HashMap::with_capacity(transition_state.transitions.len()); 361 | let mut bytecodes = cache_state.contracts.clone(); 362 | for (addr, account) in cache_state.accounts.iter() { 363 | if let Some(transition_account) = transition_state.transitions.get(addr) { 364 | // account has been modified by execution, use previous info 365 | if let Some(info) = transition_account.previous_info.as_ref() { 366 | let mut storage = if let Some(account) = account.account.as_ref() { 367 | account.storage.clone() 368 | } else { 369 | HashMap::default() 370 | }; 371 | storage.extend( 372 | transition_account.storage.iter().map(|(k, v)| (*k, v.original_value())), 373 | ); 374 | 375 | let mut info = info.clone(); 376 | if let Some(code) = info.code.take() { 377 | bytecodes.entry(info.code_hash).or_insert_with(|| code); 378 | } 379 | pre_state.insert(*addr, PlainAccount { info, storage }); 380 | } 381 | } else if let Some(account) = account.account.as_ref() { 382 | // account has not been modified, use current info in cache 383 | let mut account = account.clone(); 384 | if let Some(code) = account.info.code.take() { 385 | bytecodes.entry(account.info.code_hash).or_insert_with(|| code); 386 | } 387 | pre_state.insert(*addr, account.clone()); 388 | } 389 | } 390 | 391 | serde_json::to_writer( 392 | BufWriter::new(File::create(format!("{path}/pre_state.json")).unwrap()), 393 | &pre_state, 394 | ) 395 | .unwrap(); 396 | bincode::serialize_into( 397 | BufWriter::new(File::create(format!("{path}/bytecodes.bincode")).unwrap()), 398 | &bytecodes, 399 | ) 400 | .unwrap(); 401 | 402 | // Write block hashes to file 403 | serde_json::to_writer( 404 | BufWriter::new(File::create(format!("{path}/block_hashes.json")).unwrap()), 405 | block_hashes, 406 | ) 407 | .unwrap(); 408 | } 409 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | mod execute; 2 | pub mod storage; 3 | 4 | pub use execute::*; 5 | 6 | pub const TRANSFER_GAS_LIMIT: u64 = 21_000; 7 | // skip precompile address 8 | pub const MINER_ADDRESS: usize = 999; 9 | pub const START_ADDRESS: usize = 1000; 10 | -------------------------------------------------------------------------------- /tests/common/storage.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use revm::{ 4 | db::PlainAccount, 5 | primitives::{ 6 | alloy_primitives::U160, keccak256, ruint::UintTryFrom, AccountInfo, Address, Bytecode, 7 | B256, I256, U256, 8 | }, 9 | DatabaseRef, 10 | }; 11 | 12 | /// A DatabaseRef that stores chain data in memory. 13 | #[derive(Debug, Default, Clone)] 14 | pub struct InMemoryDB { 15 | pub accounts: HashMap, 16 | pub bytecodes: HashMap, 17 | pub block_hashes: HashMap, 18 | /// Simulated query latency in microseconds 19 | pub latency_us: u64, 20 | } 21 | 22 | impl InMemoryDB { 23 | pub fn new( 24 | accounts: HashMap, 25 | bytecodes: HashMap, 26 | block_hashes: HashMap, 27 | ) -> Self { 28 | Self { accounts, bytecodes, block_hashes, latency_us: 0 } 29 | } 30 | } 31 | 32 | impl DatabaseRef for InMemoryDB { 33 | type Error = String; 34 | 35 | fn basic_ref(&self, address: Address) -> Result, Self::Error> { 36 | if self.latency_us > 0 { 37 | std::thread::sleep(std::time::Duration::from_micros(self.latency_us)); 38 | } 39 | Ok(self.accounts.get(&address).map(|account| account.info.clone())) 40 | } 41 | 42 | fn code_by_hash_ref(&self, code_hash: B256) -> Result { 43 | if self.latency_us > 0 { 44 | std::thread::sleep(std::time::Duration::from_micros(self.latency_us)); 45 | } 46 | self.bytecodes 47 | .get(&code_hash) 48 | .cloned() 49 | .ok_or(String::from(format!("can't find code by hash {code_hash}"))) 50 | } 51 | 52 | fn storage_ref(&self, address: Address, index: U256) -> Result { 53 | if self.latency_us > 0 { 54 | std::thread::sleep(std::time::Duration::from_micros(self.latency_us)); 55 | } 56 | let storage = self.accounts.get(&address).ok_or(format!("can't find account {address}"))?; 57 | Ok(storage.storage.get(&index).cloned().unwrap_or_default()) 58 | } 59 | 60 | fn block_hash_ref(&self, number: u64) -> Result { 61 | if self.latency_us > 0 { 62 | std::thread::sleep(std::time::Duration::from_micros(self.latency_us)); 63 | } 64 | Ok(self 65 | .block_hashes 66 | .get(&number) 67 | .cloned() 68 | // Matching REVM's [EmptyDB] for now 69 | .unwrap_or_else(|| keccak256(number.to_string().as_bytes()))) 70 | } 71 | } 72 | 73 | #[derive(Debug, Default)] 74 | pub struct StorageBuilder { 75 | dict: revm_primitives::HashMap, 76 | } 77 | 78 | impl StorageBuilder { 79 | pub fn new() -> Self { 80 | StorageBuilder { dict: HashMap::default() } 81 | } 82 | 83 | pub fn set(&mut self, slot: K, value: V) 84 | where 85 | U256: UintTryFrom, 86 | U256: UintTryFrom, 87 | { 88 | self.dict.insert(U256::from(slot), U256::from(value)); 89 | } 90 | 91 | pub fn set_many(&mut self, starting_slot: K, value: &[U256; L]) 92 | where 93 | U256: UintTryFrom, 94 | U256: UintTryFrom, 95 | { 96 | for (index, item) in value.iter().enumerate() { 97 | let slot = U256::from(starting_slot).wrapping_add(U256::from(index)); 98 | self.dict.insert(slot, *item); 99 | } 100 | } 101 | 102 | pub fn set_with_offset(&mut self, key: K, offset: usize, length: usize, value: V) 103 | where 104 | U256: UintTryFrom, 105 | U256: UintTryFrom, 106 | { 107 | let entry = self.dict.entry(U256::from(key)).or_default(); 108 | let mut buffer = B256::from(*entry); 109 | let value_buffer = B256::from(U256::from(value)); 110 | buffer[(32 - offset - length)..(32 - offset)] 111 | .copy_from_slice(&value_buffer[(32 - length)..32]); 112 | *entry = buffer.into(); 113 | } 114 | 115 | pub fn build(self) -> revm_primitives::HashMap { 116 | self.dict 117 | } 118 | } 119 | 120 | pub fn from_address(address: Address) -> U256 { 121 | let encoded_as_u160: U160 = address.into(); 122 | U256::from(encoded_as_u160) 123 | } 124 | 125 | pub fn from_short_string(text: &str) -> U256 { 126 | assert!(text.len() < 32); 127 | let encoded_as_b256 = B256::bit_or( 128 | B256::right_padding_from(text.as_bytes()), 129 | B256::left_padding_from(&[(text.len() * 2) as u8]), 130 | ); 131 | encoded_as_b256.into() 132 | } 133 | 134 | pub fn from_indices(slot: K, indices: &[V]) -> U256 135 | where 136 | U256: UintTryFrom, 137 | U256: UintTryFrom, 138 | { 139 | let mut result = B256::from(U256::from(slot)); 140 | for index in indices { 141 | let to_prepend = B256::from(U256::from(*index)); 142 | result = keccak256([to_prepend.as_slice(), result.as_slice()].concat()) 143 | } 144 | result.into() 145 | } 146 | 147 | pub fn from_tick(tick: i32) -> U256 { 148 | let encoded_as_i256 = I256::try_from(tick).unwrap(); 149 | encoded_as_i256.into_raw() 150 | } 151 | -------------------------------------------------------------------------------- /tests/erc20/contracts/ERC20Token.hex: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b50600436106100a95760003560e01c8063395093511161007157806339509351146101d957806370a082311461020557806395d89b411461022b578063a457c2d714610233578063a9059cbb1461025f578063dd62ed3e1461028b576100a9565b806306fdde03146100ae578063095ea7b31461012b57806318160ddd1461016b57806323b872dd14610185578063313ce567146101bb575b600080fd5b6100b66102b9565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100f05781810151838201526020016100d8565b50505050905090810190601f16801561011d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101576004803603604081101561014157600080fd5b506001600160a01b03813516906020013561034f565b604080519115158252519081900360200190f35b61017361036c565b60408051918252519081900360200190f35b6101576004803603606081101561019b57600080fd5b506001600160a01b03813581169160208101359091169060400135610372565b6101c36103f9565b6040805160ff9092168252519081900360200190f35b610157600480360360408110156101ef57600080fd5b506001600160a01b038135169060200135610402565b6101736004803603602081101561021b57600080fd5b50356001600160a01b0316610450565b6100b661046b565b6101576004803603604081101561024957600080fd5b506001600160a01b0381351690602001356104cc565b6101576004803603604081101561027557600080fd5b506001600160a01b038135169060200135610534565b610173600480360360408110156102a157600080fd5b506001600160a01b0381358116916020013516610548565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103455780601f1061031a57610100808354040283529160200191610345565b820191906000526020600020905b81548152906001019060200180831161032857829003601f168201915b5050505050905090565b600061036361035c6105d4565b84846105d8565b50600192915050565b60025490565b600061037f8484846106c4565b6103ef8461038b6105d4565b6103ea85604051806060016040528060288152602001610927602891396001600160a01b038a166000908152600160205260408120906103c96105d4565b6001600160a01b03168152602081019190915260400160002054919061081f565b6105d8565b5060019392505050565b60055460ff1690565b600061036361040f6105d4565b846103ea85600160006104206105d4565b6001600160a01b03908116825260208083019390935260409182016000908120918c168152925290205490610573565b6001600160a01b031660009081526020819052604090205490565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103455780601f1061031a57610100808354040283529160200191610345565b60006103636104d96105d4565b846103ea8560405180606001604052806025815260200161099860259139600160006105036105d4565b6001600160a01b03908116825260208083019390935260409182016000908120918d1681529252902054919061081f565b60006103636105416105d4565b84846106c4565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6000828201838110156105cd576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b03831661061d5760405162461bcd60e51b81526004018080602001828103825260248152602001806109746024913960400191505060405180910390fd5b6001600160a01b0382166106625760405162461bcd60e51b81526004018080602001828103825260228152602001806108df6022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166107095760405162461bcd60e51b815260040180806020018281038252602581526020018061094f6025913960400191505060405180910390fd5b6001600160a01b03821661074e5760405162461bcd60e51b81526004018080602001828103825260238152602001806108bc6023913960400191505060405180910390fd5b6107598383836108b6565b61079681604051806060016040528060268152602001610901602691396001600160a01b038616600090815260208190526040902054919061081f565b6001600160a01b0380851660009081526020819052604080822093909355908416815220546107c59082610573565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156108ae5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561087357818101518382015260200161085b565b50505050905090810190601f1680156108a05780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b50505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa2646970667358221220af410612545251aac98e209cf5be29983b4a961bded32de26d3322fdc6305ef864736f6c63430007060033 2 | -------------------------------------------------------------------------------- /tests/erc20/erc20_contract.rs: -------------------------------------------------------------------------------- 1 | use crate::common::storage::{from_address, from_indices, from_short_string, StorageBuilder}; 2 | use lazy_static::lazy_static; 3 | use revm::{ 4 | db::PlainAccount, 5 | interpreter::analysis::to_analysed, 6 | primitives::{ 7 | fixed_bytes, hex::FromHex, ruint::UintTryFrom, AccountInfo, Address, Bytecode, Bytes, B256, 8 | U256, 9 | }, 10 | }; 11 | use std::collections::HashMap; 12 | 13 | const ERC20_TOKEN: &str = include_str!("./contracts/ERC20Token.hex"); 14 | 15 | // $ forge inspect ERC20Token methods 16 | 17 | lazy_static! { 18 | pub static ref ERC20_ALLLOWANCE: U256 = U256::from(0xdd62ed3e_i64); 19 | pub static ref ERC20_APPROVE: U256 = U256::from(0x095ea7b3_i64); 20 | pub static ref ERC20_BALANCE_OF: U256 = U256::from(0x70a08231_i64); 21 | pub static ref ERC20_DECIMALS: U256 = U256::from(0x313ce567_i64); 22 | pub static ref ERC20_DECREASE_ALLOWANCE: U256 = U256::from(0xa457c2d7_i64); 23 | pub static ref ERC20_INCREASE_ALLOWANCE: U256 = U256::from(0x39509351_i64); 24 | pub static ref ERC20_NAME: U256 = U256::from(0x06fdde03_i64); 25 | pub static ref ERC20_SYMBOL: U256 = U256::from(0x95d89b41_i64); 26 | pub static ref ERC20_TOTAL_SUPPLY: U256 = U256::from(0x18160ddd_i64); 27 | pub static ref ERC20_TRANSFER: U256 = U256::from(0xa9059cbb_i64); 28 | pub static ref ERC20_TRANSFER_FROM: U256 = U256::from(0x23b872dd_i64); 29 | } 30 | 31 | // @risechain/op-test-bench/foundry/src/ERC20Token.sol 32 | #[derive(Debug, Default)] 33 | pub struct ERC20Token { 34 | name: String, 35 | symbol: String, 36 | decimals: U256, 37 | initial_supply: U256, 38 | balances: HashMap, 39 | allowances: HashMap<(Address, Address), U256>, 40 | } 41 | 42 | impl ERC20Token { 43 | pub fn new(name: &str, symbol: &str, decimals: U, initial_supply: V) -> Self 44 | where 45 | U256: UintTryFrom, 46 | U256: UintTryFrom, 47 | { 48 | Self { 49 | name: String::from(name), 50 | symbol: String::from(symbol), 51 | decimals: U256::from(decimals), 52 | initial_supply: U256::from(initial_supply), 53 | balances: HashMap::new(), 54 | allowances: HashMap::new(), 55 | } 56 | } 57 | 58 | pub fn add_balances(&mut self, addresses: &[Address], amount: U256) -> &mut Self { 59 | for address in addresses { 60 | self.balances.insert(*address, amount); 61 | } 62 | self 63 | } 64 | 65 | pub fn add_allowances( 66 | &mut self, 67 | addresses: &[Address], 68 | spender: Address, 69 | amount: U256, 70 | ) -> &mut Self { 71 | for address in addresses { 72 | self.allowances.insert((*address, spender), amount); 73 | } 74 | self 75 | } 76 | 77 | // | Name | Type | Slot | Offset | Bytes | 78 | // |--------------|-------------------------------------------------|------|--------|-------| 79 | // | _balances | mapping(address => uint256) | 0 | 0 | 32 | 80 | // | _allowances | mapping(address => mapping(address => uint256)) | 1 | 0 | 32 | 81 | // | _totalSupply | uint256 | 2 | 0 | 32 | 82 | // | _name | string | 3 | 0 | 32 | 83 | // | _symbol | string | 4 | 0 | 32 | 84 | // | _decimals | uint8 | 5 | 0 | 1 | 85 | pub fn build(&self) -> PlainAccount { 86 | let hex = ERC20_TOKEN.trim(); 87 | let bytecode = Bytecode::new_raw(Bytes::from_hex(hex).unwrap()); 88 | let bytecode = to_analysed(bytecode); 89 | 90 | let mut store = StorageBuilder::new(); 91 | store.set(0, 0); // mapping 92 | store.set(1, 0); // mapping 93 | store.set(2, self.initial_supply); 94 | store.set(3, from_short_string(&self.name)); 95 | store.set(4, from_short_string(&self.symbol)); 96 | store.set(5, self.decimals); 97 | 98 | for (address, amount) in self.balances.iter() { 99 | store.set(from_indices(0, &[from_address(*address)]), *amount); 100 | } 101 | 102 | for ((address, spender), amount) in self.allowances.iter() { 103 | store.set(from_indices(1, &[from_address(*address), from_address(*spender)]), *amount); 104 | } 105 | 106 | PlainAccount { 107 | info: AccountInfo { 108 | balance: U256::ZERO, 109 | nonce: 1, 110 | code_hash: bytecode.hash_slow(), 111 | code: Some(bytecode), 112 | }, 113 | storage: store.build(), 114 | } 115 | } 116 | 117 | pub fn transfer(recipient: Address, amount: U256) -> Bytes { 118 | Bytes::from( 119 | [ 120 | &fixed_bytes!("a9059cbb")[..], 121 | // ERC20_TRANSFER.to(), 122 | &B256::from(from_address(recipient))[..], 123 | &B256::from(amount)[..], 124 | ] 125 | .concat(), 126 | ) 127 | } 128 | 129 | pub fn transfer_from(sender: Address, recipient: Address, amount: U256) -> Bytes { 130 | Bytes::from( 131 | [ 132 | &fixed_bytes!("23b872dd")[..], 133 | &B256::from(from_address(sender))[..], 134 | &B256::from(from_address(recipient))[..], 135 | &B256::from(amount)[..], 136 | ] 137 | .concat(), 138 | ) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /tests/erc20/main.rs: -------------------------------------------------------------------------------- 1 | // Each cluster has one ERC20 contract and X families. 2 | // Each family has Y people. 3 | // Each person performs Z transfers to random people within the family. 4 | 5 | use crate::{ 6 | common::START_ADDRESS, 7 | erc20::{ 8 | erc20_contract::ERC20Token, generate_cluster, generate_cluster_and_txs, 9 | TransactionModeType, TxnBatchConfig, GAS_LIMIT, 10 | }, 11 | }; 12 | use common::storage::InMemoryDB; 13 | use revm::primitives::{alloy_primitives::U160, uint, Address, TransactTo, TxEnv, U256}; 14 | use std::collections::HashMap; 15 | 16 | #[path = "../common/mod.rs"] 17 | pub mod common; 18 | 19 | #[path = "./mod.rs"] 20 | pub mod erc20; 21 | 22 | const GIGA_GAS: u64 = 1_000_000_000; 23 | 24 | #[test] 25 | fn erc20_gigagas() { 26 | const PEVM_GAS_LIMIT: u64 = 26_938; 27 | let block_size = (GIGA_GAS as f64 / PEVM_GAS_LIMIT as f64).ceil() as usize; 28 | let (mut state, bytecodes, eoa, sca) = generate_cluster(block_size, 1); 29 | let miner = common::mock_miner_account(); 30 | state.insert(miner.0, miner.1); 31 | let mut txs = Vec::with_capacity(block_size); 32 | let sca = sca[0]; 33 | for addr in eoa { 34 | let tx = TxEnv { 35 | caller: addr, 36 | transact_to: TransactTo::Call(sca), 37 | value: U256::from(0), 38 | gas_limit: GAS_LIMIT, 39 | gas_price: U256::from(1), 40 | nonce: Some(0), 41 | data: ERC20Token::transfer(addr, U256::from(900)), 42 | ..TxEnv::default() 43 | }; 44 | txs.push(tx); 45 | } 46 | let db = InMemoryDB::new(state, bytecodes, Default::default()); 47 | common::compare_evm_execute( 48 | db, 49 | txs, 50 | true, 51 | [ 52 | ("grevm.parallel_round_calls", 1), 53 | ("grevm.sequential_execute_calls", 0), 54 | ("grevm.parallel_tx_cnt", block_size), 55 | ("grevm.conflict_tx_cnt", 0), 56 | ("grevm.skip_validation_cnt", block_size), 57 | ] 58 | .into_iter() 59 | .collect(), 60 | ); 61 | } 62 | 63 | #[test] 64 | fn erc20_hints_test() { 65 | let account1 = Address::from(U160::from(START_ADDRESS + 1)); 66 | let account2 = Address::from(U160::from(START_ADDRESS + 2)); 67 | let account3 = Address::from(U160::from(START_ADDRESS + 3)); 68 | let account4 = Address::from(U160::from(START_ADDRESS + 4)); 69 | let mut accounts = common::mock_block_accounts(START_ADDRESS + 1, 4); 70 | let mut bytecodes = HashMap::new(); 71 | // START_ADDRESS as contract address 72 | let contract_address = Address::from(U160::from(START_ADDRESS)); 73 | let galxe_account = 74 | ERC20Token::new("Galxe Token", "G", 18, 222_222_000_000_000_000_000_000u128) 75 | .add_balances( 76 | &[account1, account2, account3, account4], 77 | uint!(1_000_000_000_000_000_000_U256), 78 | ) 79 | .add_allowances(&[account1], account2, uint!(50_000_000_000_000_000_U256)) 80 | .build(); 81 | bytecodes.insert(galxe_account.info.code_hash, galxe_account.info.code.clone().unwrap()); 82 | accounts.insert(contract_address, galxe_account); 83 | // tx0: account1 --(erc20)--> account4 84 | // tx1: account2 --(erc20)--> account4 85 | // tx2: account3 --(raw)--> account4 86 | // so, (tx0, tx1) are independent with (tx2) 87 | let mut txs: Vec = vec![ 88 | TxEnv { 89 | caller: account1, 90 | transact_to: TransactTo::Call(contract_address), 91 | value: U256::from(0), 92 | gas_limit: GAS_LIMIT, 93 | gas_price: U256::from(1), 94 | nonce: Some(1), 95 | ..TxEnv::default() 96 | }, 97 | TxEnv { 98 | caller: account2, 99 | transact_to: TransactTo::Call(contract_address), 100 | value: U256::from(0), 101 | gas_limit: GAS_LIMIT, 102 | gas_price: U256::from(1), 103 | nonce: Some(1), 104 | ..TxEnv::default() 105 | }, 106 | TxEnv { 107 | caller: account3, 108 | transact_to: TransactTo::Call(account4), 109 | value: U256::from(100), 110 | gas_limit: GAS_LIMIT, 111 | gas_price: U256::from(1), 112 | nonce: Some(1), 113 | ..TxEnv::default() 114 | }, 115 | ]; 116 | let call_data = ERC20Token::transfer(account4, U256::from(900)); 117 | txs[0].data = call_data.clone(); 118 | txs[1].data = call_data.clone(); 119 | let db = InMemoryDB::new(accounts, bytecodes, Default::default()); 120 | common::compare_evm_execute(db, txs, true, Default::default()); 121 | } 122 | 123 | #[test] 124 | fn erc20_independent() { 125 | const NUM_SCA: usize = 1; 126 | const NUM_EOA: usize = 100; 127 | const NUM_TXNS_PER_ADDRESS: usize = 1; 128 | let batch_txn_config = TxnBatchConfig::new( 129 | NUM_EOA, 130 | NUM_SCA, 131 | NUM_TXNS_PER_ADDRESS, 132 | erc20::TransactionCallDataType::Transfer, 133 | TransactionModeType::SameCaller, 134 | ); 135 | let (mut state, bytecodes, txs) = generate_cluster_and_txs(&batch_txn_config); 136 | let miner = common::mock_miner_account(); 137 | state.insert(miner.0, miner.1); 138 | let db = InMemoryDB::new(state, bytecodes, Default::default()); 139 | common::compare_evm_execute(db, txs, true, Default::default()); 140 | } 141 | 142 | #[test] 143 | fn erc20_batch_transfer() { 144 | const NUM_SCA: usize = 3; 145 | const NUM_EOA: usize = 10; 146 | const NUM_TXNS_PER_ADDRESS: usize = 20; 147 | 148 | let batch_txn_config = TxnBatchConfig::new( 149 | NUM_EOA, 150 | NUM_SCA, 151 | NUM_TXNS_PER_ADDRESS, 152 | erc20::TransactionCallDataType::Transfer, 153 | TransactionModeType::Random, 154 | ); 155 | 156 | let mut final_state = HashMap::from([common::mock_miner_account()]); 157 | let mut final_bytecodes = HashMap::default(); 158 | let mut final_txs = Vec::::new(); 159 | for _ in 0..1 { 160 | let (state, bytecodes, txs) = generate_cluster_and_txs(&batch_txn_config); 161 | final_state.extend(state); 162 | final_bytecodes.extend(bytecodes); 163 | final_txs.extend(txs); 164 | } 165 | 166 | let db = InMemoryDB::new(final_state, final_bytecodes, Default::default()); 167 | common::compare_evm_execute(db, final_txs, true, Default::default()); 168 | } 169 | -------------------------------------------------------------------------------- /tests/erc20/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use revm::{ 4 | db::{DbAccount, PlainAccount}, 5 | interpreter::analysis::to_analysed, 6 | primitives::{uint, AccountInfo, Address, Bytecode, TransactTo, TxEnv, B256, U256}, 7 | }; 8 | 9 | use crate::erc20::erc20_contract::ERC20Token; 10 | 11 | pub mod erc20_contract; 12 | 13 | pub const GAS_LIMIT: u64 = 35_000; 14 | pub const ESTIMATED_GAS_USED: u64 = 29_738; 15 | 16 | /// Mapping from address to [EvmAccount] 17 | pub type ChainState = HashMap; 18 | 19 | /// Mapping from code hashes to [EvmCode]s 20 | pub type Bytecodes = HashMap; 21 | 22 | /// Mapping from block numbers to block hashes 23 | pub type BlockHashes = HashMap; 24 | 25 | // TODO: Better randomness control. Sometimes we want duplicates to test 26 | // dependent transactions, sometimes we want to guarantee non-duplicates 27 | // for independent benchmarks. 28 | fn generate_addresses(length: usize) -> Vec
{ 29 | (0..length).map(|_| Address::new(rand::random())).collect() 30 | } 31 | 32 | #[derive(Clone, Debug, Copy)] 33 | pub enum TransactionCallDataType { 34 | Empty, 35 | Mix, 36 | Allowance, 37 | Approve, 38 | BalanceOf, 39 | Decimals, 40 | DecreaseAllowance, 41 | IncreaseAllowance, 42 | Name, 43 | Symbol, 44 | TotalSupply, 45 | Transfer, 46 | TransferFrom, 47 | } 48 | 49 | #[derive(Clone, Debug, Copy)] 50 | pub enum TransactionModeType { 51 | Random, 52 | SameCaller, 53 | Empty, 54 | } 55 | 56 | #[derive(Clone, Debug)] 57 | pub struct TxnBatchConfig { 58 | num_eoa: usize, 59 | num_sca: usize, 60 | num_txns_per_address: usize, 61 | transaction_call_data_type: TransactionCallDataType, 62 | transaction_mode_type: TransactionModeType, 63 | } 64 | 65 | impl TxnBatchConfig { 66 | pub fn new( 67 | num_eoa: usize, 68 | num_sca: usize, 69 | num_txns_per_address: usize, 70 | transaction_call_data_type: TransactionCallDataType, 71 | transaction_mode_type: TransactionModeType, 72 | ) -> Self { 73 | TxnBatchConfig { 74 | num_eoa, 75 | num_sca, 76 | num_txns_per_address, 77 | transaction_call_data_type, 78 | transaction_mode_type, 79 | } 80 | } 81 | } 82 | 83 | pub fn generate_erc20_batch( 84 | eoa_addresses: Vec
, 85 | sca_addresses: Vec
, 86 | num_transfers_per_eoa: usize, 87 | transaction_call_data_type: TransactionCallDataType, 88 | transaction_mode_type: TransactionModeType, 89 | ) -> Vec { 90 | let mut txns = Vec::new(); 91 | 92 | for nonce in 0..num_transfers_per_eoa { 93 | for caller_index in 0..eoa_addresses.len() { 94 | let sender: Address = eoa_addresses[caller_index]; 95 | 96 | let recipient: Address = match transaction_mode_type { 97 | TransactionModeType::SameCaller => sender, 98 | TransactionModeType::Random => { 99 | eoa_addresses[rand::random::() % eoa_addresses.len()] 100 | } 101 | TransactionModeType::Empty => Address::new([0; 20]), 102 | }; 103 | let to_address: Address = sca_addresses[rand::random::() % sca_addresses.len()]; 104 | 105 | let mut tx_env = TxEnv { 106 | caller: sender, 107 | gas_limit: GAS_LIMIT, 108 | gas_price: U256::from(0xb2d05e07u64), 109 | transact_to: TransactTo::Call(to_address), 110 | value: U256::from(0), 111 | nonce: Some(nonce as u64), 112 | ..TxEnv::default() 113 | }; 114 | 115 | match transaction_call_data_type { 116 | TransactionCallDataType::Transfer => { 117 | tx_env.data = ERC20Token::transfer(recipient, U256::from(rand::random::())); 118 | } 119 | TransactionCallDataType::TransferFrom => { 120 | tx_env.data = ERC20Token::transfer_from( 121 | sender, 122 | recipient, 123 | U256::from(rand::random::()), 124 | ); 125 | } 126 | _ => {} 127 | } 128 | txns.push(tx_env); 129 | } 130 | } 131 | txns 132 | } 133 | 134 | /// Return a tuple of (state, bytecodes, eoa_addresses, sca_addresses) 135 | pub fn generate_cluster( 136 | num_eoa: usize, 137 | num_sca: usize, 138 | ) -> (HashMap, HashMap, Vec
, Vec
) { 139 | let mut state = HashMap::new(); 140 | let eoa_addresses: Vec
= generate_addresses(num_eoa); 141 | 142 | for person in eoa_addresses.iter() { 143 | state.insert( 144 | *person, 145 | PlainAccount { 146 | info: AccountInfo { 147 | balance: uint!(1_000_000_000_000_000_000_U256), 148 | ..AccountInfo::default() 149 | }, 150 | ..PlainAccount::default() 151 | }, 152 | ); 153 | } 154 | 155 | let (contract_accounts, bytecodes) = generate_contract_accounts(num_sca, &eoa_addresses); 156 | let mut erc20_sca_addresses = Vec::with_capacity(num_sca); 157 | for (addr, sca) in contract_accounts { 158 | state.insert(addr, sca); 159 | erc20_sca_addresses.push(addr); 160 | } 161 | 162 | (state, bytecodes, eoa_addresses, erc20_sca_addresses) 163 | } 164 | 165 | pub fn generate_cluster_and_txs( 166 | txn_batch_config: &TxnBatchConfig, 167 | ) -> (HashMap, HashMap, Vec) { 168 | let (state, bytecodes, eoa_addresses, erc20_sca_address) = 169 | generate_cluster(txn_batch_config.num_eoa, txn_batch_config.num_sca); 170 | let txs = generate_erc20_batch( 171 | eoa_addresses, 172 | erc20_sca_address, 173 | txn_batch_config.num_txns_per_address, 174 | txn_batch_config.transaction_call_data_type, 175 | txn_batch_config.transaction_mode_type, 176 | ); 177 | (state, bytecodes, txs) 178 | } 179 | 180 | /// Return a tuple of (contract_accounts, bytecodes) 181 | pub(crate) fn generate_contract_accounts( 182 | num_sca: usize, 183 | eoa_addresses: &[Address], 184 | ) -> (Vec<(Address, PlainAccount)>, HashMap) { 185 | let mut accounts = Vec::with_capacity(num_sca); 186 | let mut bytecodes = HashMap::new(); 187 | for _ in 0..num_sca { 188 | let gld_address = Address::new(rand::random()); 189 | let mut gld_account = 190 | ERC20Token::new("Gold Token", "GLD", 18, 222_222_000_000_000_000_000_000u128) 191 | .add_balances(&eoa_addresses, uint!(1_000_000_000_000_000_000_U256)) 192 | .build(); 193 | bytecodes 194 | .insert(gld_account.info.code_hash, to_analysed(gld_account.info.code.take().unwrap())); 195 | accounts.push((gld_address, gld_account)); 196 | } 197 | (accounts, bytecodes) 198 | } 199 | -------------------------------------------------------------------------------- /tests/ethereum/main.rs: -------------------------------------------------------------------------------- 1 | // Basing on https://github.com/bluealloy/revm/blob/main/bins/revme/src/cmd/statetest/runner.rs. 2 | // These tests may seem useless: 3 | // - They only have one transaction. 4 | // - REVM already tests them. 5 | // Nevertheless, they are important: 6 | // - REVM doesn't test very tightly (not matching on expected failures, skipping tests, etc.). 7 | // - We must use a REVM fork (for distinguishing explicit & implicit reads, etc.). 8 | // - We use custom handlers (for lazy-updating the beneficiary account, etc.) that require 9 | // "re-testing". 10 | // - Help outline the minimal state commitment logic for pevm. 11 | 12 | #![allow(missing_docs)] 13 | 14 | use crate::common::storage::InMemoryDB; 15 | use alloy_chains::NamedChain; 16 | use grevm::{ParallelState, ParallelTakeBundle, Scheduler}; 17 | use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; 18 | use revm::{ 19 | db::{states::bundle_state::BundleRetention, PlainAccount}, 20 | primitives::{ 21 | calc_excess_blob_gas, ruint::ParseError, AccountInfo, BlobExcessGasAndPrice, BlockEnv, 22 | Bytecode, CfgEnv, Env as RevmEnv, TransactTo, TxEnv, KECCAK_EMPTY, 23 | }, 24 | }; 25 | use revme::cmd::statetest::{ 26 | merkle_trie::{log_rlp_hash, state_merkle_trie_root}, 27 | models::{Env, SpecName, TestSuite, TestUnit, TransactionParts, TxPartIndices}, 28 | utils::recover_address, 29 | }; 30 | use std::{collections::HashMap, fs, path::Path, sync::Arc}; 31 | use walkdir::{DirEntry, WalkDir}; 32 | 33 | #[path = "../common/mod.rs"] 34 | pub mod common; 35 | 36 | fn build_block_env(env: &Env) -> BlockEnv { 37 | BlockEnv { 38 | number: env.current_number, 39 | coinbase: env.current_coinbase, 40 | timestamp: env.current_timestamp, 41 | gas_limit: env.current_gas_limit, 42 | basefee: env.current_base_fee.unwrap_or_default(), 43 | difficulty: env.current_difficulty, 44 | prevrandao: env.current_random, 45 | blob_excess_gas_and_price: if let Some(current_excess_blob_gas) = 46 | env.current_excess_blob_gas 47 | { 48 | Some(BlobExcessGasAndPrice::new(current_excess_blob_gas.to(), false)) 49 | } else if let ( 50 | Some(parent_blob_gas_used), 51 | Some(parent_excess_blob_gas), 52 | Some(parent_target_blobs_per_block), 53 | ) = ( 54 | env.parent_blob_gas_used, 55 | env.parent_excess_blob_gas, 56 | env.parent_target_blobs_per_block, 57 | ) { 58 | Some(BlobExcessGasAndPrice::new( 59 | calc_excess_blob_gas( 60 | parent_blob_gas_used.to(), 61 | parent_excess_blob_gas.to(), 62 | parent_target_blobs_per_block.to(), 63 | ), 64 | false, 65 | )) 66 | } else { 67 | None 68 | }, 69 | } 70 | } 71 | 72 | fn build_tx_env(tx: &TransactionParts, indexes: &TxPartIndices) -> Result { 73 | Ok(TxEnv { 74 | caller: if let Some(address) = tx.sender { 75 | address 76 | } else if let Some(address) = recover_address(tx.secret_key.as_slice()) { 77 | address 78 | } else { 79 | panic!("Failed to parse caller") // TODO: Report test name 80 | }, 81 | gas_limit: tx.gas_limit[indexes.gas].saturating_to(), 82 | gas_price: tx.gas_price.or(tx.max_fee_per_gas).unwrap_or_default(), 83 | transact_to: match tx.to { 84 | Some(address) => TransactTo::Call(address), 85 | None => TransactTo::Create, 86 | }, 87 | value: tx.value[indexes.value], 88 | data: tx.data[indexes.data].clone(), 89 | nonce: Some(tx.nonce.saturating_to()), 90 | chain_id: Some(1), // Ethereum mainnet 91 | access_list: tx 92 | .access_lists 93 | .get(indexes.data) 94 | .and_then(Option::as_deref) 95 | .cloned() 96 | .unwrap_or_default(), 97 | gas_priority_fee: tx.max_priority_fee_per_gas, 98 | blob_hashes: tx.blob_versioned_hashes.clone(), 99 | max_fee_per_blob_gas: tx.max_fee_per_blob_gas, 100 | authorization_list: None, // TODO: Support in the upcoming hardfork 101 | #[cfg(feature = "optimism")] 102 | optimism: revm::primitives::OptimismFields::default(), 103 | }) 104 | } 105 | 106 | fn run_test_unit(path: &Path, unit: TestUnit) { 107 | unit.post.into_par_iter().for_each(|(spec_name, tests)| { 108 | // Constantinople was immediately extended by Petersburg. 109 | // There was technically never a Constantinople transaction on mainnet 110 | // so REVM understandably doesn't support it (without Petersburg). 111 | if spec_name == SpecName::Constantinople { 112 | return; 113 | } 114 | 115 | tests.into_par_iter().for_each(|test| { 116 | let tx_env = build_tx_env(&unit.transaction, &test.indexes); 117 | if test.expect_exception.as_deref() == Some("TR_RLP_WRONGVALUE") && tx_env.is_err() { 118 | return; 119 | } 120 | 121 | let mut accounts = HashMap::new(); 122 | let mut bytecodes = HashMap::new(); 123 | for (address, raw_info) in unit.pre.iter() { 124 | let code = Bytecode::new_raw(raw_info.code.clone()); 125 | let code_hash = if code.is_empty() { 126 | KECCAK_EMPTY 127 | } else { 128 | let code_hash = code.hash_slow(); 129 | bytecodes.insert(code_hash, code); 130 | code_hash 131 | }; 132 | let info = AccountInfo { 133 | balance: raw_info.balance, 134 | nonce: raw_info.nonce, 135 | code_hash, 136 | code: None, 137 | }; 138 | accounts.insert( 139 | *address, 140 | PlainAccount { info, storage: raw_info.storage.clone().into_iter().collect() }, 141 | ); 142 | } 143 | let env = RevmEnv { 144 | cfg: CfgEnv::default().with_chain_id(NamedChain::Mainnet.into()), 145 | block: build_block_env(&unit.env), 146 | tx: Default::default(), 147 | }; 148 | let db = InMemoryDB::new(accounts.clone(), bytecodes, Default::default()); 149 | let mut executor = Scheduler::new(spec_name.to_spec_id(), env, Arc::new(vec![tx_env.unwrap()]), ParallelState::new(db, true, true), true); 150 | 151 | match ( 152 | test.expect_exception.as_deref(), 153 | executor.parallel_execute(None), 154 | ) { 155 | // EIP-2681 156 | (Some("TransactionException.NONCE_IS_MAX"), Ok(exec_results)) => { 157 | let (results, mut state) = executor.take_result_and_state(); 158 | let state = state.parallel_take_bundle(BundleRetention::Reverts); 159 | assert_eq!(results.len(), 1); 160 | // This is overly strict as we only need the newly created account's code to be empty. 161 | // Extracting such account is unjustified complexity so let's live with this for now. 162 | assert!(state.state.values().all(|account| { 163 | match &account.info { 164 | Some(account) => account.is_empty_code_hash(), 165 | None => true, 166 | } 167 | })); 168 | } 169 | // Remaining tests that expect execution to fail -> match error 170 | (Some(exception), Err(error)) => { 171 | println!("Error-Error: {}: {:?}", exception, error); 172 | let error = error.error.to_string(); 173 | assert!(match exception { 174 | "TransactionException.INSUFFICIENT_ACCOUNT_FUNDS|TransactionException.INTRINSIC_GAS_TOO_LOW" => error == "transaction validation error: call gas cost exceeds the gas limit", 175 | "TransactionException.INSUFFICIENT_ACCOUNT_FUNDS" => error.contains("lack of funds"), 176 | "TransactionException.INTRINSIC_GAS_TOO_LOW" => error == "transaction validation error: call gas cost exceeds the gas limit", 177 | "TransactionException.SENDER_NOT_EOA" => error == "transaction validation error: reject transactions from senders with deployed code", 178 | "TransactionException.PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS" => error == "transaction validation error: priority fee is greater than max fee", 179 | "TransactionException.GAS_ALLOWANCE_EXCEEDED" => error == "transaction validation error: caller gas limit exceeds the block gas limit", 180 | "TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS" => error == "transaction validation error: gas price is less than basefee", 181 | "TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED" => error.contains("too many blobs"), 182 | "TransactionException.INITCODE_SIZE_EXCEEDED" => error == "transaction validation error: create initcode size limit", 183 | "TransactionException.TYPE_3_TX_ZERO_BLOBS" => error == "transaction validation error: empty blobs", 184 | "TransactionException.TYPE_3_TX_CONTRACT_CREATION" => error == "transaction validation error: blob create transaction", 185 | "TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH" => error == "transaction validation error: blob version not supported", 186 | "TransactionException.INSUFFICIENT_ACCOUNT_FUNDS|TransactionException.GASLIMIT_PRICE_PRODUCT_OVERFLOW" => error == "transaction validation error: overflow payment in transaction", 187 | "TransactionException.TYPE_3_TX_PRE_FORK|TransactionException.TYPE_3_TX_ZERO_BLOBS" => error == "transaction validation error: blob versioned hashes not supported", 188 | "TransactionException.TYPE_3_TX_PRE_FORK" => error == "transaction validation error: blob versioned hashes not supported", 189 | "TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS" => error == "transaction validation error: blob gas price is greater than max fee per blob gas", 190 | other => panic!("Mismatched error!\nPath: {path:?}\nExpected: {other:?}\nGot: {error:?}"), 191 | }); 192 | } 193 | // Tests that exepect execution to succeed -> match post state root 194 | (None, Ok(_)) => { 195 | let (results, mut state) = executor.take_result_and_state(); 196 | let state = state.parallel_take_bundle(BundleRetention::Reverts); 197 | assert_eq!(results.len(), 1); 198 | let logs = results[0].clone().into_logs(); 199 | let logs_root = log_rlp_hash(&logs); 200 | assert_eq!(logs_root, test.logs, "Mismatched logs root for {path:?}"); 201 | // This is a good reference for a minimal state/DB commitment logic for 202 | // pevm/revm to meet the Ethereum specs throughout the eras. 203 | for (address, bundle) in state.state { 204 | if bundle.info.is_some() { 205 | let chain_state_account = accounts.entry(address).or_default(); 206 | for (index, slot) in bundle.storage.iter() { 207 | chain_state_account.storage.insert(*index, slot.present_value); 208 | } 209 | } 210 | if let Some(account) = bundle.info { 211 | let chain_state_account = accounts.entry(address).or_default(); 212 | chain_state_account.info.balance = account.balance; 213 | chain_state_account.info.nonce = account.nonce; 214 | chain_state_account.info.code_hash = account.code_hash; 215 | chain_state_account.info.code = account.code; 216 | } else { 217 | accounts.remove(&address); 218 | } 219 | } 220 | // TODO: Implement our own state root calculation function to remove 221 | // this conversion to [PlainAccount] 222 | let plain_chain_state = accounts.into_iter().collect::>(); 223 | state_merkle_trie_root( 224 | plain_chain_state.iter().map(|(address, account)| (*address, account)), 225 | ); 226 | } 227 | unexpected_res => { 228 | panic!("grevm doesn't match the test's expectation for {path:?}: {:?}", unexpected_res.0) 229 | } 230 | } 231 | }); 232 | }); 233 | } 234 | 235 | #[test] 236 | fn ethereum_state_tests() { 237 | WalkDir::new("tests/ethereum/tests/GeneralStateTests") 238 | .into_iter() 239 | .filter_map(Result::ok) 240 | .map(DirEntry::into_path) 241 | .filter(|path| path.extension() == Some("json".as_ref())) 242 | // For development, we can further filter to run a small set of tests, 243 | // or filter out time-consuming tests like: 244 | // - stTimeConsuming/** 245 | // - vmPerformance/loopMul.json 246 | // - stQuadraticComplexityTest/Call50000_sha256.json 247 | .collect::>() 248 | .par_iter() 249 | .for_each(|path| { 250 | let raw_content = fs::read_to_string(path) 251 | .unwrap_or_else(|e| panic!("Cannot read suite {path:?}: {e:?}")); 252 | match serde_json::from_str(&raw_content) { 253 | Ok(TestSuite(suite)) => { 254 | suite.into_par_iter().for_each(|(_, unit)| run_test_unit(path, unit)); 255 | } 256 | Err(e) => { 257 | println!("Cannot parse suite {path:?}: {e:?}") 258 | } 259 | } 260 | }); 261 | } 262 | -------------------------------------------------------------------------------- /tests/mainnet.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | mod common; 4 | 5 | use std::sync::Arc; 6 | 7 | use common::storage::InMemoryDB; 8 | use grevm::{ParallelState, ParallelTakeBundle, Scheduler}; 9 | use metrics_util::debugging::{DebugValue, DebuggingRecorder}; 10 | use revm::{db::states::bundle_state::BundleRetention, EvmBuilder, StateBuilder}; 11 | use revm_primitives::{db::DatabaseCommit, EnvWithHandlerCfg, TxEnv}; 12 | 13 | const GIGA_GAS: u64 = 1_000_000_000; 14 | 15 | /// Return gas used 16 | fn test_execute( 17 | env: EnvWithHandlerCfg, 18 | txs: Vec, 19 | db: InMemoryDB, 20 | dump_transition: bool, 21 | ) -> u64 { 22 | let txs = Arc::new(txs); 23 | let db = Arc::new(db); 24 | 25 | let reth_result = 26 | common::execute_revm_sequential(db.clone(), env.spec_id(), env.env.as_ref().clone(), &*txs) 27 | .unwrap(); 28 | 29 | // create registry for metrics 30 | let recorder = DebuggingRecorder::new(); 31 | let with_hints = std::env::var("WITH_HINTS").map_or(false, |s| s.parse().unwrap()); 32 | let parallel_result = metrics::with_local_recorder(&recorder, || { 33 | let state = ParallelState::new(db.clone(), true, true); 34 | let mut executor = Scheduler::new(env.spec_id(), *env.env, txs, state, with_hints); 35 | executor.parallel_execute(None).unwrap(); 36 | 37 | let snapshot = recorder.snapshotter().snapshot(); 38 | for (key, _, _, value) in snapshot.into_vec() { 39 | let value = match value { 40 | DebugValue::Counter(v) => v as usize, 41 | DebugValue::Gauge(v) => v.0 as usize, 42 | DebugValue::Histogram(v) => v.last().cloned().map_or(0, |ov| ov.0 as usize), 43 | }; 44 | println!("metrics: {} => value: {:?}", key.key().name(), value); 45 | } 46 | let (results, mut state) = executor.take_result_and_state(); 47 | (results, state.parallel_take_bundle(BundleRetention::Reverts)) 48 | }); 49 | 50 | common::compare_execution_result(&reth_result.0, ¶llel_result.0); 51 | common::compare_bundle_state(&reth_result.1, ¶llel_result.1); 52 | reth_result.0.iter().map(|r| r.gas_used()).sum() 53 | } 54 | 55 | #[test] 56 | fn mainnet() { 57 | let dump_transition = std::env::var("DUMP_TRANSITION").is_ok(); 58 | if let Ok(block_number) = std::env::var("BLOCK_NUMBER").map(|s| s.parse().unwrap()) { 59 | // Test a specific block 60 | let bytecodes = common::load_bytecodes_from_disk(); 61 | let (env, txs, mut db) = common::load_block_from_disk(block_number); 62 | if db.bytecodes.is_empty() { 63 | // Use the global bytecodes if the block doesn't have its own 64 | db.bytecodes = bytecodes.clone(); 65 | } 66 | test_execute(env, txs, db, dump_transition); 67 | return; 68 | } 69 | 70 | common::for_each_block_from_disk(|env, txs, db| { 71 | let number = env.env.block.number; 72 | let num_txs = txs.len(); 73 | println!("Test Block {number}"); 74 | let gas_used = test_execute(env, txs, db, dump_transition); 75 | println!("Test Block {number} done({num_txs} txs, {gas_used} gas)"); 76 | }); 77 | } 78 | 79 | fn one_gigagas() { 80 | let continuous_blocks = String::from("19126588_19127588"); 81 | if !common::continuous_blocks_exist(continuous_blocks.clone()) { 82 | panic!("No test data"); 83 | } 84 | 85 | let (block_env, mut db) = common::load_continuous_blocks(continuous_blocks, Some(100)); 86 | let bytecodes = common::load_bytecodes_from_disk(); 87 | if db.bytecodes.is_empty() { 88 | db.bytecodes = bytecodes; 89 | } 90 | 91 | let mut smallest_base_fee = 0; 92 | for i in 0..block_env.len() { 93 | if block_env[i].0.block.basefee < block_env[smallest_base_fee].0.block.basefee { 94 | smallest_base_fee = i; 95 | } 96 | } 97 | let env = block_env[smallest_base_fee].0.clone(); 98 | 99 | let mut passed_txs = vec![]; 100 | let mut total_gas = 0; 101 | let mut num_blocks = 0; 102 | let mut sequential_state = 103 | StateBuilder::new().with_bundle_update().with_database_ref(db).build(); 104 | { 105 | let mut evm = EvmBuilder::default() 106 | .with_db(&mut sequential_state) 107 | .with_spec_id(env.spec_id()) 108 | .with_env(Box::new(env.env.as_ref().clone())) 109 | .build(); 110 | for (_, txs, _) in block_env.clone() { 111 | num_blocks += 1; 112 | for tx in txs { 113 | *evm.tx_mut() = tx.clone(); 114 | match evm.transact() { 115 | Ok(result_and_state) => { 116 | evm.db_mut().commit(result_and_state.state); 117 | passed_txs.push(tx); 118 | total_gas += result_and_state.result.gas_used(); 119 | if total_gas >= GIGA_GAS { 120 | break; 121 | } 122 | } 123 | Err(_) => {} 124 | } 125 | } 126 | } 127 | } 128 | 129 | common::dump_block_env( 130 | &env, 131 | &passed_txs, 132 | &sequential_state.cache, 133 | sequential_state.transition_state.as_ref().unwrap(), 134 | &sequential_state.block_hashes, 135 | ); 136 | } 137 | 138 | #[test] 139 | fn continuous_blocks() { 140 | let continuous_blocks = 141 | std::env::var("CONTINUOUS_BLOCKS").unwrap_or(String::from("19126588_19127588")); 142 | if !common::continuous_blocks_exist(continuous_blocks.clone()) { 143 | return; 144 | } 145 | 146 | let (block_env, mut db) = common::load_continuous_blocks(continuous_blocks, Some(70)); 147 | let bytecodes = common::load_bytecodes_from_disk(); 148 | if db.bytecodes.is_empty() { 149 | db.bytecodes = bytecodes; 150 | } 151 | let with_hints = std::env::var("WITH_HINTS").map_or(false, |s| s.parse().unwrap()); 152 | let db = Arc::new(db); 153 | 154 | // parallel execute 155 | let mut total_gas = 0; 156 | let mut num_blocks = 0; 157 | let mut state = ParallelState::new(db.clone(), true, false); 158 | let mut parallel_results = vec![]; 159 | for (env, txs, post) in block_env.clone() { 160 | let mut executor = 161 | Scheduler::new(env.spec_id(), *env.env, Arc::new(txs), state, with_hints); 162 | executor.parallel_execute(None).unwrap(); 163 | let (results, mut inner_state) = executor.take_result_and_state(); 164 | total_gas += results.iter().map(|r| r.gas_used()).sum::(); 165 | inner_state.increment_balances(post).unwrap(); 166 | state = inner_state; 167 | parallel_results.extend(results); 168 | num_blocks += 1; 169 | if total_gas > GIGA_GAS { 170 | println!( 171 | "Execute {} blocks({} txs) with {} gas", 172 | num_blocks, 173 | parallel_results.len(), 174 | total_gas 175 | ); 176 | break; 177 | } 178 | } 179 | let parallel_bundle = state.parallel_take_bundle(BundleRetention::Reverts); 180 | 181 | // sequential execute 182 | let mut sequential_state = 183 | StateBuilder::new().with_bundle_update().with_database_ref(db).build(); 184 | let mut executed_blocks = 0; 185 | let mut sequential_results = vec![]; 186 | for (env, txs, post) in block_env { 187 | { 188 | let mut evm = EvmBuilder::default() 189 | .with_db(&mut sequential_state) 190 | .with_spec_id(env.spec_id()) 191 | .with_env(Box::new(env.env.as_ref().clone())) 192 | .build(); 193 | for tx in txs { 194 | *evm.tx_mut() = tx.clone(); 195 | let result_and_state = evm.transact().unwrap(); 196 | evm.db_mut().commit(result_and_state.state); 197 | sequential_results.push(result_and_state.result); 198 | } 199 | } 200 | sequential_state.increment_balances(post).unwrap(); 201 | executed_blocks += 1; 202 | if executed_blocks >= num_blocks { 203 | break; 204 | } 205 | } 206 | sequential_state.merge_transitions(BundleRetention::Reverts); 207 | let sequential_bundle = sequential_state.take_bundle(); 208 | 209 | common::compare_execution_result(&sequential_results, ¶llel_results); 210 | common::compare_bundle_state(&sequential_bundle, ¶llel_bundle); 211 | } 212 | -------------------------------------------------------------------------------- /tests/native_transfers.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | pub mod common; 4 | 5 | use crate::common::{MINER_ADDRESS, START_ADDRESS}; 6 | use common::storage::InMemoryDB; 7 | 8 | use revm::primitives::{alloy_primitives::U160, Address, TransactTo, TxEnv, U256}; 9 | use std::collections::HashMap; 10 | 11 | const GIGA_GAS: u64 = 1_000_000_000; 12 | 13 | #[test] 14 | fn native_gigagas() { 15 | let block_size = (GIGA_GAS as f64 / common::TRANSFER_GAS_LIMIT as f64).ceil() as usize; 16 | let accounts = common::mock_block_accounts(START_ADDRESS, block_size); 17 | let db = InMemoryDB::new(accounts, Default::default(), Default::default()); 18 | // START_ADDRESS + block_size 19 | // let txs: Vec = (1..=block_size) 20 | let txs: Vec = (0..block_size) 21 | .map(|i| { 22 | let address = Address::from(U160::from(START_ADDRESS + i)); 23 | TxEnv { 24 | caller: address, 25 | transact_to: TransactTo::Call(address), 26 | value: U256::from(1), 27 | gas_limit: common::TRANSFER_GAS_LIMIT, 28 | gas_price: U256::from(1), 29 | nonce: None, 30 | ..TxEnv::default() 31 | } 32 | }) 33 | .collect(); 34 | common::compare_evm_execute(db, txs, true, Default::default()); 35 | } 36 | 37 | #[test] 38 | fn native_transfers_independent() { 39 | let block_size = 10_000; // number of transactions 40 | let accounts = common::mock_block_accounts(START_ADDRESS, block_size); 41 | let db = InMemoryDB::new(accounts, Default::default(), Default::default()); 42 | let txs: Vec = (0..block_size) 43 | .map(|i| { 44 | let address = Address::from(U160::from(START_ADDRESS + i)); 45 | TxEnv { 46 | caller: address, 47 | transact_to: TransactTo::Call(address), 48 | value: U256::from(1), 49 | gas_limit: common::TRANSFER_GAS_LIMIT, 50 | gas_price: U256::from(1), 51 | nonce: Some(1), 52 | ..TxEnv::default() 53 | } 54 | }) 55 | .collect(); 56 | common::compare_evm_execute(db, txs, true, Default::default()); 57 | } 58 | 59 | #[test] 60 | fn native_with_same_sender() { 61 | let block_size = 100; 62 | let accounts = common::mock_block_accounts(START_ADDRESS, block_size + 1); 63 | let db = InMemoryDB::new(accounts, Default::default(), Default::default()); 64 | 65 | let sender_address = Address::from(U160::from(START_ADDRESS)); 66 | let receiver_address = Address::from(U160::from(START_ADDRESS + 1)); 67 | let mut sender_nonce = 0; 68 | let txs: Vec = (0..block_size) 69 | .map(|i| { 70 | let (address, to, _) = if i % 4 != 1 { 71 | ( 72 | Address::from(U160::from(START_ADDRESS + i)), 73 | Address::from(U160::from(START_ADDRESS + i)), 74 | 1, 75 | ) 76 | } else { 77 | sender_nonce += 1; 78 | (sender_address, receiver_address, sender_nonce) 79 | }; 80 | 81 | TxEnv { 82 | caller: address, 83 | transact_to: TransactTo::Call(to), 84 | value: U256::from(i), 85 | gas_limit: common::TRANSFER_GAS_LIMIT, 86 | gas_price: U256::from(1), 87 | // If setting nonce, then nonce validation against the account's nonce, 88 | // the parallel execution will fail for the nonce validation. 89 | // However, the failed evm.transact() doesn't generate write set, 90 | // then there's no dependency can be detected even two txs are related. 91 | // TODO(gaoxin): lazily update nonce 92 | nonce: None, 93 | ..TxEnv::default() 94 | } 95 | }) 96 | .collect(); 97 | common::compare_evm_execute(db, txs, false, Default::default()); 98 | } 99 | 100 | #[test] 101 | fn native_with_all_related() { 102 | let block_size = 47620; 103 | let accounts = common::mock_block_accounts(START_ADDRESS, block_size); 104 | let db = InMemoryDB::new(accounts, Default::default(), Default::default()); 105 | let txs: Vec = (0..block_size) 106 | .map(|i| { 107 | // tx(i) => tx(i+1), all transactions should execute sequentially. 108 | let from = Address::from(U160::from(START_ADDRESS + i)); 109 | let to = Address::from(U160::from(START_ADDRESS + i + 1)); 110 | 111 | TxEnv { 112 | caller: from, 113 | transact_to: TransactTo::Call(to), 114 | value: U256::from(1000), 115 | gas_limit: common::TRANSFER_GAS_LIMIT, 116 | gas_price: U256::from(1), 117 | nonce: None, 118 | ..TxEnv::default() 119 | } 120 | }) 121 | .collect(); 122 | common::compare_evm_execute(db, txs, false, Default::default()); 123 | } 124 | 125 | #[test] 126 | fn native_with_unconfirmed_reuse() { 127 | let block_size = 100; 128 | let accounts = common::mock_block_accounts(START_ADDRESS, block_size); 129 | let db = InMemoryDB::new(accounts, Default::default(), Default::default()); 130 | let txs: Vec = (0..block_size) 131 | .map(|i| { 132 | let (from, to) = if i % 10 == 0 { 133 | ( 134 | Address::from(U160::from(START_ADDRESS + i)), 135 | Address::from(U160::from(START_ADDRESS + i + 1)), 136 | ) 137 | } else { 138 | ( 139 | Address::from(U160::from(START_ADDRESS + i)), 140 | Address::from(U160::from(START_ADDRESS + i)), 141 | ) 142 | }; 143 | // tx0 tx10, tx20, tx30 ... tx90 will produce dependency for the next tx, 144 | // so tx1, tx11, tx21, tx31, tx91 maybe redo on next round. 145 | // However, tx2 ~ tx9, tx12 ~ tx19 can reuse the result from the pre-round context. 146 | TxEnv { 147 | caller: from, 148 | transact_to: TransactTo::Call(to), 149 | value: U256::from(100), 150 | gas_limit: common::TRANSFER_GAS_LIMIT, 151 | gas_price: U256::from(1), 152 | nonce: None, 153 | ..TxEnv::default() 154 | } 155 | }) 156 | .collect(); 157 | common::compare_evm_execute(db, txs, false, HashMap::new()); 158 | } 159 | 160 | #[test] 161 | fn native_zero_or_one_tx() { 162 | let accounts = common::mock_block_accounts(START_ADDRESS, 0); 163 | let db = InMemoryDB::new(accounts, Default::default(), Default::default()); 164 | let txs: Vec = vec![]; 165 | // empty block 166 | common::compare_evm_execute(db, txs, false, HashMap::new()); 167 | 168 | // one tx 169 | let txs = vec![TxEnv { 170 | caller: Address::from(U160::from(START_ADDRESS)), 171 | transact_to: TransactTo::Call(Address::from(U160::from(START_ADDRESS))), 172 | value: U256::from(1000), 173 | gas_limit: common::TRANSFER_GAS_LIMIT, 174 | gas_price: U256::from(1), 175 | nonce: None, 176 | ..TxEnv::default() 177 | }]; 178 | let accounts = common::mock_block_accounts(START_ADDRESS, 1); 179 | let db = InMemoryDB::new(accounts, Default::default(), Default::default()); 180 | common::compare_evm_execute(db, txs, false, HashMap::new()); 181 | } 182 | 183 | #[test] 184 | fn native_loaded_not_existing_account() { 185 | let block_size = 100; // number of transactions 186 | let mut accounts = common::mock_block_accounts(START_ADDRESS, block_size); 187 | // remove miner address 188 | let miner_address = Address::from(U160::from(MINER_ADDRESS)); 189 | accounts.remove(&miner_address); 190 | let db = InMemoryDB::new(accounts, Default::default(), Default::default()); 191 | let txs: Vec = (START_ADDRESS..START_ADDRESS + block_size) 192 | .map(|i| { 193 | let address = Address::from(U160::from(i)); 194 | // transfer to not existing account 195 | let to = Address::from(U160::from(i + block_size)); 196 | TxEnv { 197 | caller: address, 198 | transact_to: TransactTo::Call(to), 199 | value: U256::from(999), 200 | gas_limit: common::TRANSFER_GAS_LIMIT, 201 | gas_price: U256::from(1), 202 | nonce: Some(1), 203 | ..TxEnv::default() 204 | } 205 | }) 206 | .collect(); 207 | common::compare_evm_execute(db, txs, true, HashMap::new()); 208 | } 209 | 210 | #[test] 211 | fn native_transfer_with_beneficiary() { 212 | let block_size = 20; // number of transactions 213 | let accounts = common::mock_block_accounts(START_ADDRESS, block_size); 214 | let db = InMemoryDB::new(accounts, Default::default(), Default::default()); 215 | let mut txs: Vec = (START_ADDRESS..START_ADDRESS + block_size) 216 | .map(|i| { 217 | let address = Address::from(U160::from(i)); 218 | TxEnv { 219 | caller: address, 220 | transact_to: TransactTo::Call(address), 221 | value: U256::from(100), 222 | gas_limit: common::TRANSFER_GAS_LIMIT, 223 | gas_price: U256::from(1), 224 | nonce: None, 225 | ..TxEnv::default() 226 | } 227 | }) 228 | .collect(); 229 | let start_address = Address::from(U160::from(START_ADDRESS)); 230 | let miner_address = Address::from(U160::from(MINER_ADDRESS)); 231 | // miner => start 232 | txs.push(TxEnv { 233 | caller: miner_address, 234 | transact_to: TransactTo::Call(start_address), 235 | value: U256::from(1), 236 | gas_limit: common::TRANSFER_GAS_LIMIT, 237 | gas_price: U256::from(1), 238 | nonce: Some(1), 239 | ..TxEnv::default() 240 | }); 241 | // miner => start 242 | txs.push(TxEnv { 243 | caller: miner_address, 244 | transact_to: TransactTo::Call(start_address), 245 | value: U256::from(1), 246 | gas_limit: common::TRANSFER_GAS_LIMIT, 247 | gas_price: U256::from(1), 248 | nonce: Some(2), 249 | ..TxEnv::default() 250 | }); 251 | // start => miner 252 | txs.push(TxEnv { 253 | caller: start_address, 254 | transact_to: TransactTo::Call(miner_address), 255 | value: U256::from(1), 256 | gas_limit: common::TRANSFER_GAS_LIMIT, 257 | gas_price: U256::from(1), 258 | nonce: Some(2), 259 | ..TxEnv::default() 260 | }); 261 | // miner => miner 262 | txs.push(TxEnv { 263 | caller: miner_address, 264 | transact_to: TransactTo::Call(miner_address), 265 | value: U256::from(1), 266 | gas_limit: common::TRANSFER_GAS_LIMIT, 267 | gas_price: U256::from(1), 268 | nonce: Some(3), 269 | ..TxEnv::default() 270 | }); 271 | common::compare_evm_execute(db, txs, true, Default::default()); 272 | } 273 | 274 | #[test] 275 | fn native_transfer_with_beneficiary_enough() { 276 | let block_size = 20; // number of transactions 277 | let accounts = common::mock_block_accounts(START_ADDRESS, block_size); 278 | let db = InMemoryDB::new(accounts, Default::default(), Default::default()); 279 | let mut txs: Vec = (START_ADDRESS..START_ADDRESS + block_size) 280 | .map(|i| { 281 | let address = Address::from(U160::from(i)); 282 | TxEnv { 283 | caller: address, 284 | transact_to: TransactTo::Call(address), 285 | value: U256::from(100), 286 | gas_limit: common::TRANSFER_GAS_LIMIT, 287 | gas_price: U256::from(1), 288 | nonce: None, 289 | ..TxEnv::default() 290 | } 291 | }) 292 | .collect(); 293 | let start_address = Address::from(U160::from(START_ADDRESS)); 294 | let miner_address = Address::from(U160::from(MINER_ADDRESS)); 295 | // start => miner 296 | txs.push(TxEnv { 297 | caller: start_address, 298 | transact_to: TransactTo::Call(miner_address), 299 | value: U256::from(100000), 300 | gas_limit: common::TRANSFER_GAS_LIMIT, 301 | gas_price: U256::from(1), 302 | nonce: Some(2), 303 | ..TxEnv::default() 304 | }); 305 | // miner => start 306 | txs.push(TxEnv { 307 | caller: miner_address, 308 | transact_to: TransactTo::Call(start_address), 309 | value: U256::from(1), 310 | gas_limit: common::TRANSFER_GAS_LIMIT, 311 | gas_price: U256::from(1), 312 | nonce: Some(1), 313 | ..TxEnv::default() 314 | }); 315 | // miner => start 316 | txs.push(TxEnv { 317 | caller: miner_address, 318 | transact_to: TransactTo::Call(start_address), 319 | value: U256::from(1), 320 | gas_limit: common::TRANSFER_GAS_LIMIT, 321 | gas_price: U256::from(1), 322 | nonce: Some(2), 323 | ..TxEnv::default() 324 | }); 325 | // miner => miner 326 | txs.push(TxEnv { 327 | caller: miner_address, 328 | transact_to: TransactTo::Call(miner_address), 329 | value: U256::from(1), 330 | gas_limit: common::TRANSFER_GAS_LIMIT, 331 | gas_price: U256::from(1), 332 | nonce: Some(3), 333 | ..TxEnv::default() 334 | }); 335 | common::compare_evm_execute(db, txs, true, Default::default()); 336 | } 337 | -------------------------------------------------------------------------------- /tests/uniswap/assets/SingleSwap.hex: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b50600436106100575760003560e01c80636b0552601461005c5780638dc33f8214610085578063b2db18a214610098578063c31c9c07146100ab578063c92b0891146100c0575b600080fd5b61006f61006a366004610a5d565b6100d3565b60405161007c9190610b37565b60405180910390f35b61006f610093366004610a8d565b61021c565b61006f6100a6366004610a8d565b6103c0565b6100b361055e565b60405161007c9190610b0e565b61006f6100ce366004610a5d565b610582565b6001546000906100ee906001600160a01b0316333085610670565b600154610125906001600160a01b03167f000000000000000000000000e7cfcccb38ce07ba9d8d13431afe8cf6172de031846107c8565b60408051610100810182526001546001600160a01b0380821683526000805482166020850152600160a01b90920462ffffff168385015233606084015242608084015260a0830186905260c0830182905260e0830191909152915163414bf38960e01b815290917f000000000000000000000000e7cfcccb38ce07ba9d8d13431afe8cf6172de031169063414bf389906101c3908490600401610b22565b602060405180830381600087803b1580156101dd57600080fd5b505af11580156101f1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102159190610a75565b9392505050565b600154600090610237906001600160a01b0316333085610670565b60015461026e906001600160a01b03167f000000000000000000000000e7cfcccb38ce07ba9d8d13431afe8cf6172de031846107c8565b60408051610100810182526001546001600160a01b0380821683526000805482166020850152600160a01b90920462ffffff168385015233606084015242608084015260a0830187905260c0830186905260e08301919091529151631b67c43360e31b815290917f000000000000000000000000e7cfcccb38ce07ba9d8d13431afe8cf6172de031169063db3e21989061030c908490600401610b22565b602060405180830381600087803b15801561032657600080fd5b505af115801561033a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061035e9190610a75565b9150828210156103b9576001546103a0906001600160a01b03167f000000000000000000000000e7cfcccb38ce07ba9d8d13431afe8cf6172de03160006107c8565b6001546103b9906001600160a01b031633848603610916565b5092915050565b600080546103d9906001600160a01b0316333085610670565b600054610410906001600160a01b03167f000000000000000000000000e7cfcccb38ce07ba9d8d13431afe8cf6172de031846107c8565b6040805161010081018252600080546001600160a01b0390811683526001548082166020850152600160a01b900462ffffff168385015233606084015242608084015260a0830187905260c0830186905260e08301919091529151631b67c43360e31b815290917f000000000000000000000000e7cfcccb38ce07ba9d8d13431afe8cf6172de031169063db3e2198906104ae908490600401610b22565b602060405180830381600087803b1580156104c857600080fd5b505af11580156104dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105009190610a75565b9150828210156103b95760008054610545916001600160a01b03909116907f000000000000000000000000e7cfcccb38ce07ba9d8d13431afe8cf6172de031906107c8565b6000546103b9906001600160a01b031633848603610916565b7f000000000000000000000000e7cfcccb38ce07ba9d8d13431afe8cf6172de03181565b6000805461059b906001600160a01b0316333085610670565b6000546105d2906001600160a01b03167f000000000000000000000000e7cfcccb38ce07ba9d8d13431afe8cf6172de031846107c8565b6040805161010081018252600080546001600160a01b0390811683526001548082166020850152600160a01b900462ffffff168385015233606084015242608084015260a0830186905260c0830182905260e0830191909152915163414bf38960e01b815290917f000000000000000000000000e7cfcccb38ce07ba9d8d13431afe8cf6172de031169063414bf389906101c3908490600401610b22565b604080516001600160a01b0385811660248301528481166044830152606480830185905283518084039091018152608490920183526020820180516001600160e01b03166323b872dd60e01b178152925182516000948594938a169392918291908083835b602083106106f45780518252601f1990920191602091820191016106d5565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610756576040519150601f19603f3d011682016040523d82523d6000602084013e61075b565b606091505b5091509150818015610789575080511580610789575080806020019051602081101561078657600080fd5b50515b6107c0576040805162461bcd60e51b815260206004820152600360248201526229aa2360e91b604482015290519081900360640190fd5b505050505050565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663095ea7b360e01b1781529251825160009485949389169392918291908083835b602083106108445780518252601f199092019160209182019101610825565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146108a6576040519150601f19603f3d011682016040523d82523d6000602084013e6108ab565b606091505b50915091508180156108d95750805115806108d957508080602001905160208110156108d657600080fd5b50515b61090f576040805162461bcd60e51b8152602060048201526002602482015261534160f01b604482015290519081900360640190fd5b5050505050565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663a9059cbb60e01b1781529251825160009485949389169392918291908083835b602083106109925780518252601f199092019160209182019101610973565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146109f4576040519150601f19603f3d011682016040523d82523d6000602084013e6109f9565b606091505b5091509150818015610a27575080511580610a275750808060200190516020811015610a2457600080fd5b50515b61090f576040805162461bcd60e51b815260206004820152600260248201526114d560f21b604482015290519081900360640190fd5b600060208284031215610a6e578081fd5b5035919050565b600060208284031215610a86578081fd5b5051919050565b60008060408385031215610a9f578081fd5b50508035926020909101359150565b80516001600160a01b03908116835260208083015182169084015260408083015162ffffff16908401526060808301518216908401526080808301519084015260a0828101519084015260c0808301519084015260e09182015116910152565b6001600160a01b0391909116815260200190565b6101008101610b318284610aae565b92915050565b9081526020019056fea2646970667358221220bd048c9922f1c5ebeebe53d343ea31f5d385d5e76d3927b622cdd588b06a0ada64736f6c63430007060033 2 | -------------------------------------------------------------------------------- /tests/uniswap/assets/SwapRouter.hex: -------------------------------------------------------------------------------- 1 | 6080604052600436106101025760003560e01c8063c04b8d5911610095578063df2ab5bb11610064578063df2ab5bb14610284578063e0e189a014610297578063f28c0498146102aa578063f3995c67146102bd578063fa461e33146102d057610172565b8063c04b8d5914610236578063c2e3140a14610249578063c45a01551461025c578063db3e21981461027157610172565b80634aa4a4fc116100d15780634aa4a4fc146101ce5780639b2c0a37146101f0578063a4a78f0c14610203578063ac9650d81461021657610172565b806312210e8a14610177578063414bf3891461017f5780634659a494146101a857806349404b7c146101bb57610172565b3661017257336001600160a01b037f0000000000000000000000006509f2a854ba7441039fce3b959d5badd2ffcfcd1614610170576040805162461bcd60e51b81526020600482015260096024820152684e6f7420574554483960b81b604482015290519081900360640190fd5b005b600080fd5b6101706102f0565b61019261018d36600461222a565b610302565b60405161019f9190612583565b60405180910390f35b6101706101b6366004611fa8565b610429565b6101706101c9366004612331565b6104c3565b3480156101da57600080fd5b506101e361063d565b60405161019f9190612420565b6101706101fe366004612360565b610661565b610170610211366004611fa8565b61082d565b610229610224366004612008565b6108be565b60405161019f919061247a565b61019261024436600461217f565b6109fe565b610170610257366004611fa8565b610b12565b34801561026857600080fd5b506101e3610ba1565b61019261027f36600461222a565b610bc5565b610170610292366004611f09565b610cec565b6101706102a5366004611f4a565b610dca565b6101926102b8366004612246565b610ef1565b6101706102cb366004611fa8565b610fe7565b3480156102dc57600080fd5b506101706102eb36600461209a565b611059565b471561030057610300334761116c565b565b600081608001358061031261125b565b111561035b576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb881d1bdbc81bdb19606a1b604482015290519081900360640190fd5b6103f460a08401356103736080860160608701611ee6565b610384610100870160e08801611ee6565b604080518082019091528061039c60208a018a611ee6565b6103ac60608b0160408c0161230e565b6103bc60408c0160208d01611ee6565b6040516020016103ce939291906123d5565b6040516020818303038152906040528152602001336001600160a01b031681525061125f565b91508260c001358210156104235760405162461bcd60e51b815260040161041a90612519565b60405180910390fd5b50919050565b604080516323f2ebc360e21b815233600482015230602482015260448101879052606481018690526001608482015260ff851660a482015260c4810184905260e4810183905290516001600160a01b03881691638fcbaf0c9161010480830192600092919082900301818387803b1580156104a357600080fd5b505af11580156104b7573d6000803e3d6000fd5b50505050505050505050565b60007f0000000000000000000000006509f2a854ba7441039fce3b959d5badd2ffcfcd6001600160a01b03166370a08231306040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b15801561053257600080fd5b505afa158015610546573d6000803e3d6000fd5b505050506040513d602081101561055c57600080fd5b50519050828110156105aa576040805162461bcd60e51b8152602060048201526012602482015271496e73756666696369656e7420574554483960701b604482015290519081900360640190fd5b8015610638577f0000000000000000000000006509f2a854ba7441039fce3b959d5badd2ffcfcd6001600160a01b0316632e1a7d4d826040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b15801561061657600080fd5b505af115801561062a573d6000803e3d6000fd5b50505050610638828261116c565b505050565b7f0000000000000000000000006509f2a854ba7441039fce3b959d5badd2ffcfcd81565b600082118015610672575060648211155b61067b57600080fd5b60007f0000000000000000000000006509f2a854ba7441039fce3b959d5badd2ffcfcd6001600160a01b03166370a08231306040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156106ea57600080fd5b505afa1580156106fe573d6000803e3d6000fd5b505050506040513d602081101561071457600080fd5b5051905084811015610762576040805162461bcd60e51b8152602060048201526012602482015271496e73756666696369656e7420574554483960701b604482015290519081900360640190fd5b8015610826577f0000000000000000000000006509f2a854ba7441039fce3b959d5badd2ffcfcd6001600160a01b0316632e1a7d4d826040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b1580156107ce57600080fd5b505af11580156107e2573d6000803e3d6000fd5b5050505060006127106107fe85846113b190919063ffffffff16565b8161080557fe5b049050801561081857610818838261116c565b6108248582840361116c565b505b5050505050565b60408051636eb1769f60e11b81523360048201523060248201529051600019916001600160a01b0389169163dd62ed3e91604480820192602092909190829003018186803b15801561087e57600080fd5b505afa158015610892573d6000803e3d6000fd5b505050506040513d60208110156108a857600080fd5b5051101561082457610824868686868686610429565b60608167ffffffffffffffff811180156108d757600080fd5b5060405190808252806020026020018201604052801561090b57816020015b60608152602001906001900390816108f65790505b50905060005b828110156109f7576000803086868581811061092957fe5b905060200281019061093b919061258c565b604051610949929190612410565b600060405180830381855af49150503d8060008114610984576040519150601f19603f3d011682016040523d82523d6000602084013e610989565b606091505b5091509150816109d5576044815110156109a257600080fd5b600481019050808060200190518101906109bc9190612115565b60405162461bcd60e51b815260040161041a91906124da565b808484815181106109e257fe5b60209081029190910101525050600101610911565b5092915050565b6000816040015180610a0e61125b565b1115610a57576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb881d1bdbc81bdb19606a1b604482015290519081900360640190fd5b335b6000610a6885600001516113db565b9050610ab4856060015182610a81578660200151610a83565b305b60006040518060400160405280610a9d8b600001516113e7565b8152602001876001600160a01b031681525061125f565b60608601528015610ad4578451309250610acd906113f6565b8552610ae1565b8460600151935050610ae7565b50610a59565b8360800151831015610b0b5760405162461bcd60e51b815260040161041a90612519565b5050919050565b60408051636eb1769f60e11b8152336004820152306024820152905186916001600160a01b0389169163dd62ed3e91604480820192602092909190829003018186803b158015610b6157600080fd5b505afa158015610b75573d6000803e3d6000fd5b505050506040513d6020811015610b8b57600080fd5b5051101561082457610824868686868686610fe7565b7f000000000000000000000000d787a42ee3ac477c46dd6c912e7af795d44453d581565b6000816080013580610bd561125b565b1115610c1e576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb881d1bdbc81bdb19606a1b604482015290519081900360640190fd5b610cba60a0840135610c366080860160608701611ee6565b610c47610100870160e08801611ee6565b6040518060400160405280886020016020810190610c659190611ee6565b610c7560608b0160408c0161230e565b610c8260208c018c611ee6565b604051602001610c94939291906123d5565b6040516020818303038152906040528152602001336001600160a01b031681525061140d565b91508260c00135821115610ce05760405162461bcd60e51b815260040161041a906124ed565b50600019600055919050565b6000836001600160a01b03166370a08231306040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015610d3b57600080fd5b505afa158015610d4f573d6000803e3d6000fd5b505050506040513d6020811015610d6557600080fd5b5051905082811015610db3576040805162461bcd60e51b815260206004820152601260248201527124b739bab33334b1b4b2b73a103a37b5b2b760711b604482015290519081900360640190fd5b8015610dc457610dc4848383611588565b50505050565b600082118015610ddb575060648211155b610de457600080fd5b6000856001600160a01b03166370a08231306040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b158015610e3357600080fd5b505afa158015610e47573d6000803e3d6000fd5b505050506040513d6020811015610e5d57600080fd5b5051905084811015610eab576040805162461bcd60e51b815260206004820152601260248201527124b739bab33334b1b4b2b73a103a37b5b2b760711b604482015290519081900360640190fd5b8015610824576000612710610ec083866113b1565b81610ec757fe5b0490508015610edb57610edb878483611588565b610ee88786838503611588565b50505050505050565b6000816040013580610f0161125b565b1115610f4a576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb881d1bdbc81bdb19606a1b604482015290519081900360640190fd5b610fbd6060840135610f626040860160208701611ee6565b6040805180820190915260009080610f7a898061258c565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505050908252503360209091015261140d565b5060005491508260800135821115610ce05760405162461bcd60e51b815260040161041a906124ed565b6040805163d505accf60e01b8152336004820152306024820152604481018790526064810186905260ff8516608482015260a4810184905260c4810183905290516001600160a01b0388169163d505accf9160e480830192600092919082900301818387803b1580156104a357600080fd5b60008413806110685750600083135b61107157600080fd5b600061107f8284018461227e565b9050600080600061109384600001516116cf565b9250925092506110c57f000000000000000000000000d787a42ee3ac477c46dd6c912e7af795d44453d5848484611700565b5060008060008a136110ec57846001600160a01b0316846001600160a01b03161089611103565b836001600160a01b0316856001600160a01b0316108a5b9150915081156111225761111d858760200151338461171f565b6104b7565b855161112d906113db565b1561115257855161113d906113f6565b865261114c813360008961140d565b506104b7565b806000819055508394506104b7858760200151338461171f565b604080516000808252602082019092526001600160a01b0384169083906040518082805190602001908083835b602083106111b85780518252601f199092019160209182019101611199565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d806000811461121a576040519150601f19603f3d011682016040523d82523d6000602084013e61121f565b606091505b5050905080610638576040805162461bcd60e51b815260206004820152600360248201526253544560e81b604482015290519081900360640190fd5b4290565b60006001600160a01b038416611273573093505b600080600061128585600001516116cf565b919450925090506001600160a01b03808316908416106000806112a98686866118af565b6001600160a01b031663128acb088b856112c28f6118ed565b6001600160a01b038e16156112d7578d6112fd565b876112f65773fffd8963efd1fc6a506488495d951d5263988d256112fd565b6401000276a45b8d60405160200161130e9190612546565b6040516020818303038152906040526040518663ffffffff1660e01b815260040161133d959493929190612434565b6040805180830381600087803b15801561135657600080fd5b505af115801561136a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061138e9190612077565b915091508261139d578161139f565b805b6000039b9a5050505050505050505050565b60008215806113cc575050818102818382816113c957fe5b04145b6113d557600080fd5b92915050565b8051604211155b919050565b60606113d5826000602b611903565b80516060906113d590839060179060161901611903565b60006001600160a01b038416611421573093505b600080600061143385600001516116cf565b919450925090506001600160a01b03808416908316106000806114578587866118af565b6001600160a01b031663128acb088b856114708f6118ed565b6000036001600160a01b038e1615611488578d6114ae565b876114a75773fffd8963efd1fc6a506488495d951d5263988d256114ae565b6401000276a45b8d6040516020016114bf9190612546565b6040516020818303038152906040526040518663ffffffff1660e01b81526004016114ee959493929190612434565b6040805180830381600087803b15801561150757600080fd5b505af115801561151b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061153f9190612077565b9150915060008361155457818360000361155a565b82826000035b90985090506001600160a01b038a16611579578b811461157957600080fd5b50505050505050949350505050565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663a9059cbb60e01b1781529251825160009485949389169392918291908083835b602083106116045780518252601f1990920191602091820191016115e5565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114611666576040519150601f19603f3d011682016040523d82523d6000602084013e61166b565b606091505b5091509150818015611699575080511580611699575080806020019051602081101561169657600080fd5b50515b610826576040805162461bcd60e51b815260206004820152600260248201526114d560f21b604482015290519081900360640190fd5b600080806116dd8482611a54565b92506116ea846014611b04565b90506116f7846017611a54565b91509193909250565b600061171685611711868686611bab565b611c01565b95945050505050565b7f0000000000000000000000006509f2a854ba7441039fce3b959d5badd2ffcfcd6001600160a01b0316846001600160a01b03161480156117605750804710155b15611882577f0000000000000000000000006509f2a854ba7441039fce3b959d5badd2ffcfcd6001600160a01b031663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b1580156117c057600080fd5b505af11580156117d4573d6000803e3d6000fd5b50505050507f0000000000000000000000006509f2a854ba7441039fce3b959d5badd2ffcfcd6001600160a01b031663a9059cbb83836040518363ffffffff1660e01b815260040180836001600160a01b0316815260200182815260200192505050602060405180830381600087803b15801561185057600080fd5b505af1158015611864573d6000803e3d6000fd5b505050506040513d602081101561187a57600080fd5b50610dc49050565b6001600160a01b0383163014156118a35761189e848383611588565b610dc4565b610dc484848484611c24565b60006118e57f000000000000000000000000d787a42ee3ac477c46dd6c912e7af795d44453d56118e0868686611bab565b611d74565b949350505050565b6000600160ff1b82106118ff57600080fd5b5090565b60608182601f01101561194e576040805162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b604482015290519081900360640190fd5b828284011015611996576040805162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b604482015290519081900360640190fd5b818301845110156119e2576040805162461bcd60e51b8152602060048201526011602482015270736c6963655f6f75744f66426f756e647360781b604482015290519081900360640190fd5b606082158015611a015760405191506000825260208201604052611a4b565b6040519150601f8416801560200281840101858101878315602002848b0101015b81831015611a3a578051835260209283019201611a22565b5050858452601f01601f1916604052505b50949350505050565b600081826014011015611aa3576040805162461bcd60e51b8152602060048201526012602482015271746f416464726573735f6f766572666c6f7760701b604482015290519081900360640190fd5b8160140183511015611af4576040805162461bcd60e51b8152602060048201526015602482015274746f416464726573735f6f75744f66426f756e647360581b604482015290519081900360640190fd5b500160200151600160601b900490565b600081826003011015611b52576040805162461bcd60e51b8152602060048201526011602482015270746f55696e7432345f6f766572666c6f7760781b604482015290519081900360640190fd5b8160030183511015611ba2576040805162461bcd60e51b8152602060048201526014602482015273746f55696e7432345f6f75744f66426f756e647360601b604482015290519081900360640190fd5b50016003015190565b611bb3611e58565b826001600160a01b0316846001600160a01b03161115611bd1579192915b50604080516060810182526001600160a01b03948516815292909316602083015262ffffff169181019190915290565b6000611c0d8383611d74565b9050336001600160a01b038216146113d557600080fd5b604080516001600160a01b0385811660248301528481166044830152606480830185905283518084039091018152608490920183526020820180516001600160e01b03166323b872dd60e01b178152925182516000948594938a169392918291908083835b60208310611ca85780518252601f199092019160209182019101611c89565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114611d0a576040519150601f19603f3d011682016040523d82523d6000602084013e611d0f565b606091505b5091509150818015611d3d575080511580611d3d5750808060200190516020811015611d3a57600080fd5b50515b610824576040805162461bcd60e51b815260206004820152600360248201526229aa2360e91b604482015290519081900360640190fd5b600081602001516001600160a01b031682600001516001600160a01b031610611d9c57600080fd5b50805160208083015160409384015184516001600160a01b0394851681850152939091168385015262ffffff166060808401919091528351808403820181526080840185528051908301206001600160f81b031960a085015294901b6bffffffffffffffffffffffff191660a183015260b58201939093527f636fcf59d7fdf3a03833dba1c6d936c9d7c6730057c8c4c8e5059feaeab60e0460d5808301919091528251808303909101815260f5909101909152805191012090565b604080516060810182526000808252602082018190529181019190915290565b80356113e28161264a565b600082601f830112611e93578081fd5b8135611ea6611ea1826125fc565b6125d8565b818152846020838601011115611eba578283fd5b816020850160208301379081016020019190915292915050565b60006101008284031215610423578081fd5b600060208284031215611ef7578081fd5b8135611f028161264a565b9392505050565b600080600060608486031215611f1d578182fd5b8335611f288161264a565b9250602084013591506040840135611f3f8161264a565b809150509250925092565b600080600080600060a08688031215611f61578081fd5b8535611f6c8161264a565b9450602086013593506040860135611f838161264a565b9250606086013591506080860135611f9a8161264a565b809150509295509295909350565b60008060008060008060c08789031215611fc0578081fd5b8635611fcb8161264a565b95506020870135945060408701359350606087013560ff81168114611fee578182fd5b9598949750929560808101359460a0909101359350915050565b6000806020838503121561201a578182fd5b823567ffffffffffffffff80821115612031578384fd5b818501915085601f830112612044578384fd5b813581811115612052578485fd5b8660208083028501011115612065578485fd5b60209290920196919550909350505050565b60008060408385031215612089578182fd5b505080516020909101519092909150565b600080600080606085870312156120af578182fd5b8435935060208501359250604085013567ffffffffffffffff808211156120d4578384fd5b818701915087601f8301126120e7578384fd5b8135818111156120f5578485fd5b886020828501011115612106578485fd5b95989497505060200194505050565b600060208284031215612126578081fd5b815167ffffffffffffffff81111561213c578182fd5b8201601f8101841361214c578182fd5b805161215a611ea1826125fc565b81815285602083850101111561216e578384fd5b61171682602083016020860161261e565b600060208284031215612190578081fd5b813567ffffffffffffffff808211156121a7578283fd5b9083019060a082860312156121ba578283fd5b60405160a0810181811083821117156121cf57fe5b6040528235828111156121e0578485fd5b6121ec87828601611e83565b8252506121fb60208401611e78565b602082015260408301356040820152606083013560608201526080830135608082015280935050505092915050565b6000610100828403121561223c578081fd5b611f028383611ed4565b600060208284031215612257578081fd5b813567ffffffffffffffff81111561226d578182fd5b820160a08185031215611f02578182fd5b60006020828403121561228f578081fd5b813567ffffffffffffffff808211156122a6578283fd5b90830190604082860312156122b9578283fd5b6040516040810181811083821117156122ce57fe5b6040528235828111156122df578485fd5b6122eb87828601611e83565b825250602083013592506122fe8361264a565b6020810192909252509392505050565b60006020828403121561231f578081fd5b813562ffffff81168114611f02578182fd5b60008060408385031215612343578182fd5b8235915060208301356123558161264a565b809150509250929050565b60008060008060808587031215612375578182fd5b8435935060208501356123878161264a565b925060408501359150606085013561239e8161264a565b939692955090935050565b600081518084526123c181602086016020860161261e565b601f01601f19169290920160200192915050565b606093841b6bffffffffffffffffffffffff19908116825260e89390931b6001600160e81b0319166014820152921b166017820152602b0190565b6000828483379101908152919050565b6001600160a01b0391909116815260200190565b6001600160a01b0386811682528515156020830152604082018590528316606082015260a06080820181905260009061246f908301846123a9565b979650505050505050565b6000602080830181845280855180835260408601915060408482028701019250838701855b828110156124cd57603f198886030184526124bb8583516123a9565b9450928501929085019060010161249f565b5092979650505050505050565b600060208252611f0260208301846123a9565b602080825260129082015271151bdbc81b5d58da081c995c5d595cdd195960721b604082015260600190565b602080825260139082015272151bdbc81b1a5d1d1b19481c9958d95a5d9959606a1b604082015260600190565b60006020825282516040602084015261256260608401826123a9565b602094909401516001600160a01b0316604093909301929092525090919050565b90815260200190565b6000808335601e198436030181126125a2578283fd5b83018035915067ffffffffffffffff8211156125bc578283fd5b6020019150368190038213156125d157600080fd5b9250929050565b60405181810167ffffffffffffffff811182821017156125f457fe5b604052919050565b600067ffffffffffffffff82111561261057fe5b50601f01601f191660200190565b60005b83811015612639578181015183820152602001612621565b83811115610dc45750506000910152565b6001600160a01b038116811461265f57600080fd5b5056fea2646970667358221220df884b27961ec494f07fb30a6eb9212a043b248dcf11803944e001062be0ec9a64736f6c63430007060033 2 | -------------------------------------------------------------------------------- /tests/uniswap/assets/WETH9.hex: -------------------------------------------------------------------------------- 1 | 60806040526004361061009c5760003560e01c8063313ce56711610064578063313ce5671461021157806370a082311461023c57806395d89b411461026f578063a9059cbb14610284578063d0e30db01461009c578063dd62ed3e146102bd5761009c565b806306fdde03146100a6578063095ea7b31461013057806318160ddd1461017d57806323b872dd146101a45780632e1a7d4d146101e7575b6100a46102f8565b005b3480156100b257600080fd5b506100bb610347565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100f55781810151838201526020016100dd565b50505050905090810190601f1680156101225780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561013c57600080fd5b506101696004803603604081101561015357600080fd5b506001600160a01b0381351690602001356103d5565b604080519115158252519081900360200190f35b34801561018957600080fd5b5061019261043b565b60408051918252519081900360200190f35b3480156101b057600080fd5b50610169600480360360608110156101c757600080fd5b506001600160a01b0381358116916020810135909116906040013561043f565b3480156101f357600080fd5b506100a46004803603602081101561020a57600080fd5b5035610573565b34801561021d57600080fd5b50610226610608565b6040805160ff9092168252519081900360200190f35b34801561024857600080fd5b506101926004803603602081101561025f57600080fd5b50356001600160a01b0316610611565b34801561027b57600080fd5b506100bb610623565b34801561029057600080fd5b50610169600480360360408110156102a757600080fd5b506001600160a01b03813516906020013561067d565b3480156102c957600080fd5b50610192600480360360408110156102e057600080fd5b506001600160a01b0381358116916020013516610691565b33600081815260036020908152604091829020805434908101909155825190815291517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9281900390910190a2565b6000805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156103cd5780601f106103a2576101008083540402835291602001916103cd565b820191906000526020600020905b8154815290600101906020018083116103b057829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a350600192915050565b4790565b6001600160a01b03831660009081526003602052604081205482111561046457600080fd5b6001600160a01b03841633148015906104a257506001600160a01b038416600090815260046020908152604080832033845290915290205460001914155b15610502576001600160a01b03841660009081526004602090815260408083203384529091529020548211156104d757600080fd5b6001600160a01b03841660009081526004602090815260408083203384529091529020805483900390555b6001600160a01b03808516600081815260036020908152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060019392505050565b3360009081526003602052604090205481111561058f57600080fd5b33600081815260036020526040808220805485900390555183156108fc0291849190818181858888f193505050501580156105ce573d6000803e3d6000fd5b5060408051828152905133917f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65919081900360200190a250565b60025460ff1681565b60036020526000908152604090205481565b60018054604080516020600284861615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156103cd5780601f106103a2576101008083540402835291602001916103cd565b600061068a33848461043f565b9392505050565b60046020908152600092835260408084209091529082529020548156fea265627a7a723158206b1a8bf63bbe31f0023b933510d886c7447799ce7ec9e6a09f07d1415e3ff74664736f6c63430005110032 2 | -------------------------------------------------------------------------------- /tests/uniswap/contract.rs: -------------------------------------------------------------------------------- 1 | use crate::common::storage::{ 2 | from_address, from_indices, from_short_string, from_tick, StorageBuilder, 3 | }; 4 | use revm::{ 5 | db::PlainAccount, 6 | primitives::{ 7 | fixed_bytes, 8 | hex::{FromHex, ToHexExt}, 9 | keccak256, uint, AccountInfo, Address, Bytecode, Bytes, FixedBytes, B256, U256, 10 | }, 11 | }; 12 | use std::collections::HashMap; 13 | 14 | const POOL_FEE: u32 = 3000; 15 | const TICK_SPACING: i32 = 60; 16 | 17 | const SINGLE_SWAP: &str = include_str!("./assets/SingleSwap.hex"); 18 | const UNISWAP_V3_FACTORY: &str = include_str!("./assets/UniswapV3Factory.hex"); 19 | const WETH9: &str = include_str!("./assets/WETH9.hex"); 20 | const UNISWAP_V3_POOL: &str = include_str!("./assets/UniswapV3Pool.hex"); 21 | const SWAP_ROUTER: &str = include_str!("./assets/SwapRouter.hex"); 22 | 23 | #[inline] 24 | fn keccak256_all(chunks: &[&[u8]]) -> B256 { 25 | keccak256(chunks.concat()) 26 | } 27 | 28 | // @gnosis/canonical-weth/contracts/WETH9.sol 29 | #[derive(Debug, Default)] 30 | pub struct WETH9 {} 31 | 32 | impl WETH9 { 33 | pub fn new() -> Self { 34 | Self {} 35 | } 36 | // | Name | Type | Slot | Offset | Bytes | 37 | // |-----------|-------------------------------------------------|------|--------|-------| 38 | // | name | string | 0 | 0 | 32 | 39 | // | symbol | string | 1 | 0 | 32 | 40 | // | decimals | uint8 | 2 | 0 | 1 | 41 | // | balanceOf | mapping(address => uint256) | 3 | 0 | 32 | 42 | // | allowance | mapping(address => mapping(address => uint256)) | 4 | 0 | 32 | 43 | pub fn build(&self) -> PlainAccount { 44 | let hex = WETH9.trim(); 45 | let bytecode = Bytecode::new_raw(Bytes::from_hex(hex).unwrap()); 46 | 47 | let mut store = StorageBuilder::new(); 48 | store.set(0, from_short_string("Wrapped Ether")); 49 | store.set(1, from_short_string("WETH")); 50 | store.set(3, 18); 51 | store.set(4, 0); // mapping 52 | store.set(5, 0); // mapping 53 | 54 | PlainAccount { 55 | info: AccountInfo { 56 | balance: U256::ZERO, 57 | nonce: 1u64, 58 | code_hash: bytecode.hash_slow(), 59 | code: Some(bytecode), 60 | }, 61 | storage: store.build(), 62 | } 63 | } 64 | } 65 | 66 | // @uniswap/v3-core/contracts/UniswapV3Factory.sol 67 | #[derive(Debug, Default)] 68 | pub struct UniswapV3Factory { 69 | owner: Address, 70 | pools: HashMap<(Address, Address, U256), Address>, 71 | } 72 | 73 | impl UniswapV3Factory { 74 | pub fn new(owner: Address) -> Self { 75 | Self { owner, pools: HashMap::new() } 76 | } 77 | 78 | pub fn add_pool( 79 | &mut self, 80 | token_0: Address, 81 | token_1: Address, 82 | pool_address: Address, 83 | ) -> &mut Self { 84 | self.pools.insert((token_0, token_1, U256::from(POOL_FEE)), pool_address); 85 | self 86 | } 87 | 88 | // | Name | Type | Slot | Offset | Bytes | 89 | // |----------------------|--------------------------------------------------------------------|------|--------|-------| 90 | // | parameters | struct UniswapV3PoolDeployer.Parameters | 0 | 0 | 96 | 91 | // | owner | address | 3 | 0 | 20 | 92 | // | feeAmountTickSpacing | mapping(uint24 => int24) | 4 | 0 | 32 | 93 | // | getPool | mapping(address => mapping(address => mapping(uint24 => address))) | 5 | 0 | 32 | 94 | pub fn build(&self, address: Address) -> PlainAccount { 95 | let hex = UNISWAP_V3_FACTORY 96 | .trim() 97 | .replace("0b748751e6f8b1a38c9386a19d9f8966b3593a9e", &address.encode_hex()); 98 | let bytecode = Bytecode::new_raw(Bytes::from_hex(hex).unwrap()); 99 | 100 | let mut store = StorageBuilder::new(); 101 | store.set(0, 0); 102 | store.set(1, 0); 103 | store.set(2, 0); 104 | store.set(3, from_address(self.owner)); 105 | store.set(4, 0); // mapping 106 | store.set(5, 0); // mapping 107 | 108 | store.set(from_indices(4, &[500]), 10); 109 | store.set(from_indices(4, &[3000]), 60); 110 | store.set(from_indices(4, &[10000]), 200); 111 | 112 | for ((token_0, token_1, pool_fee), pool_address) in self.pools.iter() { 113 | store.set( 114 | from_indices(5, &[from_address(*token_0), from_address(*token_1), *pool_fee]), 115 | from_address(*pool_address), 116 | ); 117 | store.set( 118 | from_indices(5, &[from_address(*token_1), from_address(*token_0), *pool_fee]), 119 | from_address(*pool_address), 120 | ); 121 | } 122 | 123 | PlainAccount { 124 | info: AccountInfo { 125 | balance: U256::ZERO, 126 | nonce: 1u64, 127 | code_hash: bytecode.hash_slow(), 128 | code: Some(bytecode), 129 | }, 130 | storage: store.build(), 131 | } 132 | } 133 | } 134 | 135 | // @uniswap/v3-core/contracts/UniswapV3Pool.sol 136 | #[derive(Debug, Default)] 137 | pub struct UniswapV3Pool { 138 | token_0: Address, 139 | token_1: Address, 140 | factory: Address, 141 | positions: HashMap, 142 | ticks: HashMap, 143 | tick_bitmap: HashMap, 144 | } 145 | 146 | impl UniswapV3Pool { 147 | pub fn new(token_0: Address, token_1: Address, factory: Address) -> Self { 148 | Self { 149 | token_0, 150 | token_1, 151 | factory, 152 | positions: HashMap::new(), 153 | ticks: HashMap::new(), 154 | tick_bitmap: HashMap::new(), 155 | } 156 | } 157 | 158 | pub fn add_position( 159 | &mut self, 160 | owner: Address, 161 | tick_lower: i32, 162 | tick_upper: i32, 163 | value: [U256; 4], 164 | ) -> &mut Self { 165 | let t0 = &FixedBytes::<20>::from(owner)[..]; // 20 bytes 166 | let t1 = &FixedBytes::<32>::from(from_tick(tick_lower))[29..32]; // 3 bytes 167 | let t2 = &FixedBytes::<32>::from(from_tick(tick_upper))[29..32]; // 3 bytes 168 | let key: U256 = keccak256([t0, t1, t2].concat()).into(); 169 | self.positions.insert(key, value); 170 | self 171 | } 172 | 173 | pub fn add_tick(&mut self, tick: i32, value: [U256; 4]) -> &mut Self { 174 | self.ticks.insert(from_tick(tick), value); 175 | 176 | let index: i32 = tick / TICK_SPACING; 177 | self.tick_bitmap 178 | .entry(from_tick(index >> 8)) 179 | .or_default() 180 | .set_bit((index & 0xff).try_into().unwrap(), true); 181 | self 182 | } 183 | 184 | // | Name | Type | Slot | Offset | Bytes | 185 | // |----------------------|------------------------------------------|------|--------|---------| 186 | // | slot0 | struct UniswapV3Pool.Slot0 | 0 | 0 | 32 | 187 | // | feeGrowthGlobal0X128 | uint256 | 1 | 0 | 32 | 188 | // | feeGrowthGlobal1X128 | uint256 | 2 | 0 | 32 | 189 | // | protocolFees | struct UniswapV3Pool.ProtocolFees | 3 | 0 | 32 | 190 | // | liquidity | uint128 | 4 | 0 | 16 | 191 | // | ticks | mapping(int24 => struct Tick.Info) | 5 | 0 | 32 | 192 | // | tickBitmap | mapping(int16 => uint256) | 6 | 0 | 32 | 193 | // | positions | mapping(bytes32 => struct Position.Info) | 7 | 0 | 32 | 194 | // | observations | struct Oracle.Observation[65535] | 8 | 0 | 2097120 | 195 | pub fn build(&self, address: Address) -> PlainAccount { 196 | let hex = UNISWAP_V3_POOL 197 | .trim() 198 | .replace("261d8c5e9742e6f7f1076fa1f560894524e19cad", &self.token_0.encode_hex()) 199 | .replace("ce3478a9e0167a6bc5716dc39dbbbfac38f27623", &self.token_1.encode_hex()) 200 | .replace("cba6b9a951749b8735c603e7ffc5151849248772", &self.factory.encode_hex()) 201 | .replace("d495d5e5cab2567777fff988c4fcd71328b17c9d", &address.encode_hex()); 202 | let bytecode = Bytecode::new_raw(Bytes::from_hex(hex).unwrap()); 203 | 204 | let mut store = StorageBuilder::new(); 205 | store 206 | .set(0, uint!(0x0001000001000100000000000000000000000001000000000000000000000000_U256)); 207 | store.set(1, 0); 208 | store.set(2, 0); 209 | store.set(3, 0); 210 | store.set(4, 111_111_000_000_010_412_955_141u128); 211 | store.set(5, 0); // mapping 212 | store.set(6, 0); // mapping 213 | store.set(7, 0); // mapping 214 | store 215 | .set(8, uint!(0x0100000000000000000000000000000000000000000000000000000000000001_U256)); 216 | 217 | for (key, value) in self.ticks.iter() { 218 | store.set_many(from_indices(5, &[*key]), value); 219 | } 220 | 221 | for (key, value) in self.tick_bitmap.iter() { 222 | store.set(from_indices(6, &[*key]), *value); 223 | } 224 | 225 | for (key, value) in self.positions.iter() { 226 | store.set_many(from_indices(7, &[*key]), value); 227 | } 228 | 229 | PlainAccount { 230 | info: AccountInfo { 231 | balance: U256::ZERO, 232 | nonce: 1u64, 233 | code_hash: bytecode.hash_slow(), 234 | code: Some(bytecode), 235 | }, 236 | storage: store.build(), 237 | } 238 | } 239 | 240 | // @uniswap/v3-periphery/contracts/libraries/PoolAddress.sol 241 | pub fn get_address(&self, factory_address: Address, pool_init_code_hash: B256) -> Address { 242 | let hash = keccak256_all(&[ 243 | fixed_bytes!("ff").as_slice(), 244 | factory_address.as_slice(), 245 | keccak256_all(&[ 246 | B256::left_padding_from(self.token_0.as_slice()).as_slice(), 247 | B256::left_padding_from(self.token_1.as_slice()).as_slice(), 248 | B256::from(U256::from(POOL_FEE)).as_slice(), 249 | ]) 250 | .as_slice(), 251 | pool_init_code_hash.as_slice(), 252 | ]); 253 | Address::from_slice(&hash[12..32]) 254 | } 255 | } 256 | 257 | // @uniswap/v3-periphery/contracts/SwapRouter.sol 258 | #[derive(Debug, Default)] 259 | pub struct SwapRouter { 260 | weth9: Address, 261 | factory: Address, 262 | pool_init_code_hash: B256, 263 | } 264 | 265 | impl SwapRouter { 266 | pub fn new(weth9: Address, factory: Address, pool_init_code_hash: B256) -> Self { 267 | Self { weth9, factory, pool_init_code_hash } 268 | } 269 | 270 | // | Name | Type | Slot | Offset | Bytes | 271 | // |----------------|---------|------|--------|-------| 272 | // | amountInCached | uint256 | 0 | 0 | 32 | 273 | pub fn build(&self) -> PlainAccount { 274 | let hex = SWAP_ROUTER 275 | .trim() 276 | .replace("6509f2a854ba7441039fce3b959d5badd2ffcfcd", &self.weth9.encode_hex()) 277 | .replace("d787a42ee3ac477c46dd6c912e7af795d44453d5", &self.factory.encode_hex()) 278 | .replace( 279 | "636fcf59d7fdf3a03833dba1c6d936c9d7c6730057c8c4c8e5059feaeab60e04", 280 | &self.pool_init_code_hash.encode_hex(), 281 | ); 282 | 283 | let bytecode = Bytecode::new_raw(Bytes::from_hex(hex).unwrap()); 284 | 285 | let mut store = StorageBuilder::new(); 286 | store 287 | .set(0, uint!(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256)); 288 | 289 | PlainAccount { 290 | info: AccountInfo { 291 | balance: U256::ZERO, 292 | nonce: 1u64, 293 | code_hash: bytecode.hash_slow(), 294 | code: Some(bytecode), 295 | }, 296 | storage: store.build(), 297 | } 298 | } 299 | } 300 | 301 | /// `@risechain/op-test-bench/foundry/src/SingleSwap.sol` 302 | #[derive(Debug, Default)] 303 | pub struct SingleSwap { 304 | swap_router: Address, 305 | token_0: Address, 306 | token_1: Address, 307 | } 308 | 309 | impl SingleSwap { 310 | pub fn new(swap_router: Address, token_0: Address, token_1: Address) -> Self { 311 | Self { swap_router, token_0, token_1 } 312 | } 313 | 314 | // | Name | Type | Slot | Offset | Bytes | 315 | // |--------|---------|------|--------|-------| 316 | // | token0 | address | 0 | 0 | 20 | 317 | // | token1 | address | 1 | 0 | 20 | 318 | // | fee | uint24 | 1 | 20 | 3 | 319 | pub fn build(&self) -> PlainAccount { 320 | let hex = SINGLE_SWAP 321 | .trim() 322 | .replace("e7cfcccb38ce07ba9d8d13431afe8cf6172de031", &self.swap_router.encode_hex()); 323 | let bytecode = Bytecode::new_raw(Bytes::from_hex(hex).unwrap()); 324 | 325 | let mut store = StorageBuilder::new(); 326 | store.set(0, from_address(self.token_0)); 327 | store.set(1, from_address(self.token_1)); 328 | store.set_with_offset(1, 20, 3, POOL_FEE); 329 | 330 | PlainAccount { 331 | info: AccountInfo { 332 | balance: U256::ZERO, 333 | nonce: 1u64, 334 | code_hash: bytecode.hash_slow(), 335 | code: Some(bytecode), 336 | }, 337 | storage: store.build(), 338 | } 339 | } 340 | 341 | pub fn sell_token0(amount: U256) -> Bytes { 342 | Bytes::from([&fixed_bytes!("c92b0891")[..], &B256::from(amount)[..]].concat()) 343 | } 344 | 345 | pub fn sell_token1(amount: U256) -> Bytes { 346 | Bytes::from([&fixed_bytes!("6b055260")[..], &B256::from(amount)[..]].concat()) 347 | } 348 | 349 | pub fn buy_token0(arg1: U256, arg2: U256) -> Bytes { 350 | Bytes::from( 351 | [&fixed_bytes!("8dc33f82")[..], &B256::from(arg1)[..], &B256::from(arg2)[..]].concat(), 352 | ) 353 | } 354 | 355 | pub fn buy_token1(arg1: U256, arg2: U256) -> Bytes { 356 | Bytes::from( 357 | [&fixed_bytes!("b2db18a2")[..], &B256::from(arg1)[..], &B256::from(arg2)[..]].concat(), 358 | ) 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /tests/uniswap/main.rs: -------------------------------------------------------------------------------- 1 | //! Launch K clusters. 2 | //! Each cluster has M people. 3 | //! Each person makes N swaps. 4 | 5 | #[path = "../common/mod.rs"] 6 | pub mod common; 7 | 8 | #[path = "../erc20/mod.rs"] 9 | pub mod erc20; 10 | 11 | #[path = "./mod.rs"] 12 | pub mod uniswap; 13 | 14 | use crate::uniswap::generate_cluster; 15 | use common::storage::InMemoryDB; 16 | use revm::primitives::TxEnv; 17 | use std::collections::HashMap; 18 | 19 | #[test] 20 | fn uniswap_clusters() { 21 | const NUM_CLUSTERS: usize = 20; 22 | const NUM_PEOPLE_PER_CLUSTER: usize = 20; 23 | const NUM_SWAPS_PER_PERSON: usize = 20; 24 | 25 | let mut final_state = HashMap::from([common::mock_miner_account()]); 26 | let mut final_bytecodes = HashMap::default(); 27 | let mut final_txs = Vec::::new(); 28 | for _ in 0..NUM_CLUSTERS { 29 | let (state, bytecodes, txs) = 30 | generate_cluster(NUM_PEOPLE_PER_CLUSTER, NUM_SWAPS_PER_PERSON); 31 | final_state.extend(state); 32 | final_bytecodes.extend(bytecodes); 33 | final_txs.extend(txs); 34 | } 35 | 36 | let db = InMemoryDB::new(final_state, final_bytecodes, Default::default()); 37 | common::compare_evm_execute(db, final_txs, false, HashMap::new()); 38 | } 39 | -------------------------------------------------------------------------------- /tests/uniswap/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | 3 | use crate::erc20::erc20_contract::ERC20Token; 4 | use contract::{SingleSwap, SwapRouter, UniswapV3Factory, UniswapV3Pool, WETH9}; 5 | use revm::{ 6 | db::PlainAccount, 7 | interpreter::analysis::to_analysed, 8 | primitives::{ 9 | uint, AccountInfo, Address, Bytecode, Bytes, HashMap, TransactTo, TxEnv, B256, U256, 10 | }, 11 | }; 12 | 13 | pub const GAS_LIMIT: u64 = 200_000; 14 | pub const ESTIMATED_GAS_USED: u64 = 155_934; 15 | 16 | /// Return a tuple of (contract_accounts, bytecodes, single_swap_address) 17 | /// `single_swap_address` is the entrance of the contract. 18 | pub(crate) fn generate_contract_accounts( 19 | eoa_addresses: &[Address], 20 | ) -> (Vec<(Address, PlainAccount)>, HashMap, Address) { 21 | let (dai_address, usdc_address) = { 22 | let x = Address::new(rand::random()); 23 | let y = Address::new(rand::random()); 24 | (std::cmp::min(x, y), std::cmp::max(x, y)) 25 | }; 26 | let pool_init_code_hash = B256::new(rand::random()); 27 | let swap_router_address = Address::new(rand::random()); 28 | let single_swap_address = Address::new(rand::random()); 29 | let weth9_address = Address::new(rand::random()); 30 | let owner = Address::new(rand::random()); 31 | let factory_address = Address::new(rand::random()); 32 | let nonfungible_position_manager_address = Address::new(rand::random()); 33 | let pool_address = UniswapV3Pool::new(dai_address, usdc_address, factory_address) 34 | .get_address(factory_address, pool_init_code_hash); 35 | 36 | let weth9_account = WETH9::new().build(); 37 | 38 | let dai_account = ERC20Token::new("DAI", "DAI", 18, 222_222_000_000_000_000_000_000u128) 39 | .add_balances(&[pool_address], uint!(111_111_000_000_000_000_000_000_U256)) 40 | .add_balances(&eoa_addresses, uint!(1_000_000_000_000_000_000_U256)) 41 | .add_allowances(&eoa_addresses, single_swap_address, uint!(1_000_000_000_000_000_000_U256)) 42 | .build(); 43 | 44 | let usdc_account = ERC20Token::new("USDC", "USDC", 18, 222_222_000_000_000_000_000_000u128) 45 | .add_balances(&[pool_address], uint!(111_111_000_000_000_000_000_000_U256)) 46 | .add_balances(&eoa_addresses, uint!(1_000_000_000_000_000_000_U256)) 47 | .add_allowances(&eoa_addresses, single_swap_address, uint!(1_000_000_000_000_000_000_U256)) 48 | .build(); 49 | 50 | let factory_account = UniswapV3Factory::new(owner) 51 | .add_pool(dai_address, usdc_address, pool_address) 52 | .build(factory_address); 53 | 54 | let pool_account = UniswapV3Pool::new(dai_address, usdc_address, factory_address) 55 | .add_position( 56 | nonfungible_position_manager_address, 57 | -600000, 58 | 600000, 59 | [ 60 | uint!(0x00000000000000000000000000000000000000000000178756e190b388651605_U256), 61 | uint!(0x0000000000000000000000000000000000000000000000000000000000000000_U256), 62 | uint!(0x0000000000000000000000000000000000000000000000000000000000000000_U256), 63 | uint!(0x0000000000000000000000000000000000000000000000000000000000000000_U256), 64 | ], 65 | ) 66 | .add_tick( 67 | -600000, 68 | [ 69 | uint!(0x000000000000178756e190b388651605000000000000178756e190b388651605_U256), 70 | uint!(0x0000000000000000000000000000000000000000000000000000000000000000_U256), 71 | uint!(0x0000000000000000000000000000000000000000000000000000000000000000_U256), 72 | uint!(0x0100000001000000000000000000000000000000000000000000000000000000_U256), 73 | ], 74 | ) 75 | .add_tick( 76 | 600000, 77 | [ 78 | uint!(0xffffffffffffe878a91e6f4c779ae9fb000000000000178756e190b388651605_U256), 79 | uint!(0x0000000000000000000000000000000000000000000000000000000000000000_U256), 80 | uint!(0x0000000000000000000000000000000000000000000000000000000000000000_U256), 81 | uint!(0x0100000000000000000000000000000000000000000000000000000000000000_U256), 82 | ], 83 | ) 84 | .build(pool_address); 85 | 86 | let swap_router_account = 87 | SwapRouter::new(weth9_address, factory_address, pool_init_code_hash).build(); 88 | 89 | let single_swap_account = 90 | SingleSwap::new(swap_router_address, dai_address, usdc_address).build(); 91 | 92 | let mut accounts = Vec::new(); 93 | accounts.push((weth9_address, weth9_account)); 94 | accounts.push((dai_address, dai_account)); 95 | accounts.push((usdc_address, usdc_account)); 96 | accounts.push((factory_address, factory_account)); 97 | accounts.push((pool_address, pool_account)); 98 | accounts.push((swap_router_address, swap_router_account)); 99 | accounts.push((single_swap_address, single_swap_account)); 100 | 101 | let mut bytecodes = revm_primitives::HashMap::default(); 102 | for (_, account) in accounts.iter_mut() { 103 | let code = account.info.code.take(); 104 | if let Some(code) = code { 105 | let code = to_analysed(code); 106 | bytecodes.insert(account.info.code_hash, code); 107 | } 108 | } 109 | 110 | (accounts, bytecodes, single_swap_address) 111 | } 112 | 113 | pub fn generate_cluster( 114 | num_people: usize, 115 | num_swaps_per_person: usize, 116 | ) -> (HashMap, HashMap, Vec) { 117 | // TODO: Better randomness control. Sometimes we want duplicates to test 118 | // dependent transactions, sometimes we want to guarantee non-duplicates 119 | // for independent benchmarks. 120 | let people_addresses: Vec
= 121 | (0..num_people).map(|_| Address::new(rand::random())).collect(); 122 | 123 | let (contract_accounts, bytecodes, single_swap_address) = 124 | generate_contract_accounts(&people_addresses); 125 | let mut state: HashMap = contract_accounts.into_iter().collect(); 126 | 127 | for person in people_addresses.iter() { 128 | state.insert( 129 | *person, 130 | PlainAccount { 131 | info: AccountInfo { 132 | balance: uint!(4_567_000_000_000_000_000_000_U256), 133 | ..AccountInfo::default() 134 | }, 135 | ..PlainAccount::default() 136 | }, 137 | ); 138 | } 139 | 140 | let mut txs = Vec::new(); 141 | 142 | for nonce in 0..num_swaps_per_person { 143 | for person in people_addresses.iter() { 144 | let data_bytes = match nonce % 4 { 145 | 0 => SingleSwap::sell_token0(U256::from(2000)), 146 | 1 => SingleSwap::sell_token1(U256::from(2000)), 147 | 2 => SingleSwap::buy_token0(U256::from(1000), U256::from(2000)), 148 | 3 => SingleSwap::buy_token1(U256::from(1000), U256::from(2000)), 149 | _ => Default::default(), 150 | }; 151 | 152 | txs.push(TxEnv { 153 | caller: *person, 154 | gas_limit: GAS_LIMIT, 155 | gas_price: U256::from(0xb2d05e07u64), 156 | transact_to: TransactTo::Call(single_swap_address), 157 | data: Bytes::from(data_bytes), 158 | nonce: Some(nonce as u64), 159 | ..TxEnv::default() 160 | }) 161 | } 162 | } 163 | 164 | (state, bytecodes, txs) 165 | } 166 | --------------------------------------------------------------------------------