Skip to main content

example_bal/
main.rs

1//! Block Access List (BAL) example demonstrating how to:
2//! 1. Build a BAL by executing transactions and capturing state changes
3//! 2. Use the BAL to re-execute the same transactions with pre-computed state
4//!
5//! BAL (EIP-7928) optimizes block execution by pre-computing state access patterns,
6//! allowing parallel or optimized re-execution using the captured state versions.
7#![cfg_attr(not(test), warn(unused_crate_dependencies))]
8
9use std::sync::Arc;
10
11use revm::{
12    bytecode::opcode,
13    context::{Context, ContextTr, TxEnv},
14    context_interface::result::ExecutionResult,
15    database::State,
16    primitives::{address, keccak256, Bytes, TxKind, U256},
17    ExecuteCommitEvm, MainBuilder, MainContext,
18};
19
20fn main() -> anyhow::Result<()> {
21    println!("=== Block Access List (BAL) Example ===\n");
22
23    // Simple counter contract bytecode:
24    // - Reads current value from slot 0
25    // - Adds 1 to it
26    // - Stores back to slot 0
27    // - Returns the new value
28    //
29    // Bytecode: PUSH0 SLOAD PUSH1 1 ADD DUP1 PUSH0 SSTORE PUSH0 MSTORE PUSH1 32 PUSH0 RETURN
30    let counter_contract: Bytes = [
31        opcode::PUSH0, // Push slot 0
32        opcode::SLOAD, // Load current value from slot 0
33        opcode::PUSH1,
34        0x01,           // Push 1
35        opcode::ADD,    // Add 1 to current value
36        opcode::DUP1,   // Duplicate for return
37        opcode::PUSH0,  // Push slot 0
38        opcode::SSTORE, // Store incremented value
39        opcode::PUSH0,  // Push memory offset 0
40        opcode::MSTORE, // Store result in memory
41        opcode::PUSH1,
42        0x20,           // Push 32 (return size)
43        opcode::PUSH0,  // Push 0 (return offset)
44        opcode::RETURN, // Return 32 bytes from memory
45    ]
46    .into();
47
48    let caller = address!("0x1000000000000000000000000000000000000001");
49    let contract_address = address!("0x2000000000000000000000000000000000000002");
50
51    // ========================================
52    // PHASE 1: Build the BAL
53    // ========================================
54    println!("--- Phase 1: Building BAL ---");
55
56    let mut state_for_building = State::builder().with_bal_builder().build();
57
58    // Give caller some balance
59    state_for_building.insert_account(
60        caller,
61        revm::state::AccountInfo {
62            balance: U256::from(1_000_000_000_000_000_000u128),
63            nonce: 0,
64            ..Default::default()
65        },
66    );
67
68    // Deploy counter contract
69    let bytecode = revm::bytecode::Bytecode::new_raw(counter_contract.clone());
70    state_for_building.insert_account(
71        contract_address,
72        revm::state::AccountInfo {
73            code_hash: keccak256(&counter_contract),
74            code: Some(bytecode),
75            ..Default::default()
76        },
77    );
78
79    let ctx = Context::mainnet().with_db(&mut state_for_building);
80    let mut evm = ctx.build_mainnet();
81
82    // Execute transaction 1: increment counter (0 -> 1)
83    evm.db_mut().bump_bal_index(); // BAL index 1 for first tx
84    let tx1 = TxEnv::builder()
85        .caller(caller)
86        .kind(TxKind::Call(contract_address))
87        .gas_limit(100_000)
88        .nonce(0)
89        .build()
90        .unwrap();
91
92    let result1 = evm.transact_commit(tx1.clone())?;
93    match &result1 {
94        ExecutionResult::Success { gas, output, .. } => {
95            println!(
96                "  TX 1: Counter incremented (0 -> 1), gas used: {}",
97                gas.used()
98            );
99            if let revm::context_interface::result::Output::Call(bytes) = output {
100                let value = U256::from_be_slice(bytes);
101                println!("         Returned value: {value}");
102            }
103        }
104        _ => anyhow::bail!("TX 1 failed: {result1:?}"),
105    }
106
107    // Execute transaction 2: increment counter again (1 -> 2)
108    evm.db_mut().bump_bal_index(); // BAL index 2 for second tx
109    let tx2 = TxEnv::builder()
110        .caller(caller)
111        .kind(TxKind::Call(contract_address))
112        .gas_limit(100_000)
113        .nonce(1)
114        .build()
115        .unwrap();
116
117    let result2 = evm.transact_commit(tx2.clone())?;
118    match &result2 {
119        ExecutionResult::Success { gas, output, .. } => {
120            println!(
121                "  TX 2: Counter incremented (1 -> 2), gas used: {}",
122                gas.used()
123            );
124            if let revm::context_interface::result::Output::Call(bytes) = output {
125                let value = U256::from_be_slice(bytes);
126                println!("         Returned value: {value}");
127            }
128        }
129        _ => anyhow::bail!("TX 2 failed: {result2:?}"),
130    }
131
132    // Extract the built BAL
133    let bal = evm.db_mut().take_built_bal().expect("BAL should be built");
134
135    println!("\n  Built BAL with {} accounts tracked", bal.accounts.len());
136    println!();
137
138    // Print the BAL structure showing state changes
139    bal.pretty_print();
140
141    // ========================================
142    // PHASE 2: Use the BAL for re-execution
143    // ========================================
144    println!("\n--- Phase 2: Re-executing with BAL ---");
145
146    // Create a new state with the BAL for reading
147    let bal_arc = Arc::new(bal);
148    let mut state_with_bal = State::builder().with_bal(bal_arc).build();
149
150    // Re-insert initial state (simulating fresh execution context)
151    state_with_bal.insert_account(
152        caller,
153        revm::state::AccountInfo {
154            balance: U256::from(1_000_000_000_000_000_000u128),
155            nonce: 0,
156            ..Default::default()
157        },
158    );
159    let bytecode2 = revm::bytecode::Bytecode::new_raw(counter_contract.clone());
160    state_with_bal.insert_account(
161        contract_address,
162        revm::state::AccountInfo {
163            code_hash: keccak256(&counter_contract),
164            code: Some(bytecode2),
165            ..Default::default()
166        },
167    );
168
169    let ctx2 = Context::mainnet().with_db(&mut state_with_bal);
170    let mut evm2 = ctx2.build_mainnet();
171
172    // Re-execute transaction 1 using BAL
173    // The BAL provides the storage value written by TX 1 so subsequent
174    // reads in TX 2 can be resolved from the BAL instead of computing
175    evm2.db_mut().bump_bal_index(); // BAL index 1
176    let result1_replay = evm2.transact_commit(tx1)?;
177    match &result1_replay {
178        ExecutionResult::Success { gas, output, .. } => {
179            println!("  TX 1 replayed with BAL, gas used: {}", gas.used());
180            if let revm::context_interface::result::Output::Call(bytes) = output {
181                let value = U256::from_be_slice(bytes);
182                println!("         Returned value: {value}");
183            }
184        }
185        _ => anyhow::bail!("TX 1 replay failed: {result1_replay:?}"),
186    }
187
188    // Re-execute transaction 2 using BAL
189    evm2.db_mut().bump_bal_index(); // BAL index 2
190    let result2_replay = evm2.transact_commit(tx2)?;
191    match &result2_replay {
192        ExecutionResult::Success { gas, output, .. } => {
193            println!("  TX 2 replayed with BAL, gas used: {}", gas.used());
194            if let revm::context_interface::result::Output::Call(bytes) = output {
195                let value = U256::from_be_slice(bytes);
196                println!("         Returned value: {value}");
197            }
198        }
199        _ => anyhow::bail!("TX 2 replay failed: {result2_replay:?}"),
200    }
201
202    println!("\n=== BAL Example Complete ===");
203
204    Ok(())
205}