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 {
95            gas_used, output, ..
96        } => {
97            println!("  TX 1: Counter incremented (0 -> 1), gas used: {gas_used}");
98            if let revm::context_interface::result::Output::Call(bytes) = output {
99                let value = U256::from_be_slice(bytes);
100                println!("         Returned value: {value}");
101            }
102        }
103        _ => anyhow::bail!("TX 1 failed: {result1:?}"),
104    }
105
106    // Execute transaction 2: increment counter again (1 -> 2)
107    evm.db_mut().bump_bal_index(); // BAL index 2 for second tx
108    let tx2 = TxEnv::builder()
109        .caller(caller)
110        .kind(TxKind::Call(contract_address))
111        .gas_limit(100_000)
112        .nonce(1)
113        .build()
114        .unwrap();
115
116    let result2 = evm.transact_commit(tx2.clone())?;
117    match &result2 {
118        ExecutionResult::Success {
119            gas_used, output, ..
120        } => {
121            println!("  TX 2: Counter incremented (1 -> 2), gas used: {gas_used}");
122            if let revm::context_interface::result::Output::Call(bytes) = output {
123                let value = U256::from_be_slice(bytes);
124                println!("         Returned value: {value}");
125            }
126        }
127        _ => anyhow::bail!("TX 2 failed: {result2:?}"),
128    }
129
130    // Extract the built BAL
131    let bal = evm.db_mut().take_built_bal().expect("BAL should be built");
132
133    println!("\n  Built BAL with {} accounts tracked", bal.accounts.len());
134    println!();
135
136    // Print the BAL structure showing state changes
137    bal.pretty_print();
138
139    // ========================================
140    // PHASE 2: Use the BAL for re-execution
141    // ========================================
142    println!("\n--- Phase 2: Re-executing with BAL ---");
143
144    // Create a new state with the BAL for reading
145    let bal_arc = Arc::new(bal);
146    let mut state_with_bal = State::builder().with_bal(bal_arc).build();
147
148    // Re-insert initial state (simulating fresh execution context)
149    state_with_bal.insert_account(
150        caller,
151        revm::state::AccountInfo {
152            balance: U256::from(1_000_000_000_000_000_000u128),
153            nonce: 0,
154            ..Default::default()
155        },
156    );
157    let bytecode2 = revm::bytecode::Bytecode::new_raw(counter_contract.clone());
158    state_with_bal.insert_account(
159        contract_address,
160        revm::state::AccountInfo {
161            code_hash: keccak256(&counter_contract),
162            code: Some(bytecode2),
163            ..Default::default()
164        },
165    );
166
167    let ctx2 = Context::mainnet().with_db(&mut state_with_bal);
168    let mut evm2 = ctx2.build_mainnet();
169
170    // Re-execute transaction 1 using BAL
171    // The BAL provides the storage value written by TX 1 so subsequent
172    // reads in TX 2 can be resolved from the BAL instead of computing
173    evm2.db_mut().bump_bal_index(); // BAL index 1
174    let result1_replay = evm2.transact_commit(tx1)?;
175    match &result1_replay {
176        ExecutionResult::Success {
177            gas_used, output, ..
178        } => {
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 {
193            gas_used, output, ..
194        } => {
195            println!("  TX 2 replayed with BAL, gas used: {gas_used}");
196            if let revm::context_interface::result::Output::Call(bytes) = output {
197                let value = U256::from_be_slice(bytes);
198                println!("         Returned value: {value}");
199            }
200        }
201        _ => anyhow::bail!("TX 2 replay failed: {result2_replay:?}"),
202    }
203
204    println!("\n=== BAL Example Complete ===");
205
206    Ok(())
207}