1use alloy_primitives::{address, keccak256, Address, Bytes, U256};
2use revm::Database;
3use std::collections::HashMap;
4
5pub const ARBOS_STATE_ADDRESS: Address = address!("A4B05FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
7
8pub const FILTERED_TX_STATE_ADDRESS: Address = address!("a4b0500000000000000000000000000000000001");
10
11pub fn ensure_arbos_account_in_bundle<D: Database>(state: &mut revm::database::State<D>) {
15 ensure_account_in_bundle(state, ARBOS_STATE_ADDRESS);
16}
17
18pub fn ensure_account_in_bundle<D: Database>(state: &mut revm::database::State<D>, addr: Address) {
20 use revm_database::{AccountStatus, BundleAccount};
21 use revm_state::AccountInfo;
22
23 if state.bundle_state.state.contains_key(&addr) {
24 return;
25 }
26
27 let db_info = state.database.basic(addr).ok().flatten();
28
29 let info = db_info.or_else(|| {
30 Some(AccountInfo {
31 balance: U256::ZERO,
32 nonce: 1,
33 code_hash: keccak256([]),
34 code: None,
35 account_id: None,
36 })
37 });
38
39 let acc = BundleAccount {
40 info: info.clone(),
41 storage: HashMap::default(),
42 original_info: info,
43 status: AccountStatus::Loaded,
44 };
45 state.bundle_state.state.insert(addr, acc);
46}
47
48fn ensure_cache_account<D: Database>(state: &mut revm::database::State<D>, addr: Address) {
51 use revm_database::AccountStatus;
52
53 let _ = state.load_cache_account(addr);
54
55 if let Some(cached) = state.cache.accounts.get_mut(&addr) {
56 if cached.account.is_none() {
57 cached.account = Some(revm_database::PlainAccount {
58 info: revm_state::AccountInfo {
59 balance: U256::ZERO,
60 nonce: 0,
61 code_hash: keccak256([]),
62 code: None,
63 account_id: None,
64 },
65 storage: Default::default(),
66 });
67 cached.status = AccountStatus::InMemoryChange;
68 }
69 }
70}
71
72pub fn read_arbos_storage<D: Database>(state: &mut revm::database::State<D>, slot: U256) -> U256 {
74 read_storage_at(state, ARBOS_STATE_ADDRESS, slot)
75}
76
77pub fn read_storage_at<D: Database>(
79 state: &mut revm::database::State<D>,
80 account: Address,
81 slot: U256,
82) -> U256 {
83 if let Some(cached_acc) = state.cache.accounts.get(&account) {
85 if let Some(ref account) = cached_acc.account {
86 if let Some(&value) = account.storage.get(&slot) {
87 return value;
88 }
89 }
90 }
91
92 if let Some(acc) = state.bundle_state.state.get(&account) {
94 if let Some(slot_entry) = acc.storage.get(&slot) {
95 return slot_entry.present_value;
96 }
97 }
98
99 state.database.storage(account, slot).unwrap_or(U256::ZERO)
101}
102
103pub fn write_arbos_storage<D: Database>(
108 state: &mut revm::database::State<D>,
109 slot: U256,
110 value: U256,
111) {
112 write_storage_at(state, ARBOS_STATE_ADDRESS, slot, value);
113}
114
115pub fn write_storage_at<D: Database>(
117 state: &mut revm::database::State<D>,
118 account: Address,
119 slot: U256,
120 value: U256,
121) {
122 use revm_database::states::StorageSlot;
123
124 ensure_cache_account(state, account);
126
127 let current_value = {
129 state
130 .cache
131 .accounts
132 .get(&account)
133 .and_then(|ca| ca.account.as_ref())
134 .and_then(|a| a.storage.get(&slot).copied())
135 }
136 .or_else(|| {
137 state
138 .bundle_state
139 .state
140 .get(&account)
141 .and_then(|a| a.storage.get(&slot))
142 .map(|s| s.present_value)
143 });
144
145 let original_value = state.database.storage(account, slot).unwrap_or(U256::ZERO);
146
147 let prev_value = current_value.unwrap_or(original_value);
149
150 if value == prev_value {
151 return;
152 }
153
154 let (previous_info, previous_status, current_info, current_status) = {
156 let cached_acc = match state.cache.accounts.get_mut(&account) {
157 Some(acc) => acc,
158 None => return,
159 };
160
161 let previous_status = cached_acc.status;
162 let previous_info = cached_acc.account.as_ref().map(|a| a.info.clone());
163
164 if let Some(ref mut account) = cached_acc.account {
165 account.storage.insert(slot, value);
166 }
167
168 let had_no_nonce_and_code = previous_info
169 .as_ref()
170 .map(|info| info.has_no_code_and_nonce())
171 .unwrap_or_default();
172 cached_acc.status = cached_acc.status.on_changed(had_no_nonce_and_code);
173
174 let current_info = cached_acc.account.as_ref().map(|a| a.info.clone());
175 let current_status = cached_acc.status;
176 (previous_info, previous_status, current_info, current_status)
177 };
178
179 if account == ARBOS_STATE_ADDRESS {
181 tracing::debug!(
182 target: "arb::storage",
183 ?slot,
184 ?value,
185 ?prev_value,
186 ?original_value,
187 "write_storage_at applying transition"
188 );
189 }
190 let mut storage_changes: revm_database::StorageWithOriginalValues = HashMap::default();
191 storage_changes.insert(slot, StorageSlot::new_changed(original_value, value));
192
193 let transition = revm::database::TransitionAccount {
194 info: current_info,
195 status: current_status,
196 previous_info,
197 previous_status,
198 storage: storage_changes,
199 storage_was_destroyed: false,
200 };
201
202 state.apply_transition(vec![(account, transition)]);
203}
204
205pub fn get_account_balance<D: Database>(
207 state: &mut revm::database::State<D>,
208 addr: Address,
209) -> U256 {
210 if let Some(cached_acc) = state.cache.accounts.get(&addr) {
211 if let Some(ref account) = cached_acc.account {
212 return account.info.balance;
213 }
214 }
215
216 state
217 .database
218 .basic(addr)
219 .ok()
220 .flatten()
221 .map(|info| info.balance)
222 .unwrap_or(U256::ZERO)
223}
224
225pub fn set_account_nonce<D: Database>(
227 state: &mut revm::database::State<D>,
228 addr: Address,
229 nonce: u64,
230) {
231 ensure_cache_account(state, addr);
232
233 let (previous_info, previous_status, current_info, current_status) = {
234 let cached_acc = match state.cache.accounts.get_mut(&addr) {
235 Some(acc) => acc,
236 None => return,
237 };
238 let previous_status = cached_acc.status;
239 let previous_info = cached_acc.account.as_ref().map(|a| a.info.clone());
240
241 if let Some(ref mut account) = cached_acc.account {
242 account.info.nonce = nonce;
243 }
244
245 let had_no_nonce_and_code = previous_info
246 .as_ref()
247 .map(|info| info.has_no_code_and_nonce())
248 .unwrap_or_default();
249 cached_acc.status = cached_acc.status.on_changed(had_no_nonce_and_code);
250
251 let current_info = cached_acc.account.as_ref().map(|a| a.info.clone());
252 let current_status = cached_acc.status;
253 (previous_info, previous_status, current_info, current_status)
254 };
255
256 let transition = revm::database::TransitionAccount {
257 info: current_info,
258 status: current_status,
259 previous_info,
260 previous_status,
261 storage: HashMap::default(),
262 storage_was_destroyed: false,
263 };
264 state.apply_transition(vec![(addr, transition)]);
265}
266
267pub fn set_account_code<D: Database>(
269 state: &mut revm::database::State<D>,
270 addr: Address,
271 code: Bytes,
272) {
273 use revm_state::Bytecode;
274
275 ensure_cache_account(state, addr);
276 let code_hash = keccak256(&code);
277 let bytecode = Bytecode::new_raw(code);
278
279 let (previous_info, previous_status, current_info, current_status) = {
280 let cached_acc = match state.cache.accounts.get_mut(&addr) {
281 Some(acc) => acc,
282 None => return,
283 };
284 let previous_status = cached_acc.status;
285 let previous_info = cached_acc.account.as_ref().map(|a| a.info.clone());
286
287 if let Some(ref mut account) = cached_acc.account {
288 account.info.code_hash = code_hash;
289 account.info.code = Some(bytecode);
290 }
291
292 let had_no_nonce_and_code = previous_info
293 .as_ref()
294 .map(|info| info.has_no_code_and_nonce())
295 .unwrap_or_default();
296 cached_acc.status = cached_acc.status.on_changed(had_no_nonce_and_code);
297
298 let current_info = cached_acc.account.as_ref().map(|a| a.info.clone());
299 let current_status = cached_acc.status;
300 (previous_info, previous_status, current_info, current_status)
301 };
302
303 let transition = revm::database::TransitionAccount {
304 info: current_info,
305 status: current_status,
306 previous_info,
307 previous_status,
308 storage: HashMap::default(),
309 storage_was_destroyed: false,
310 };
311 state.apply_transition(vec![(addr, transition)]);
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use revm_database::{states::bundle_state::BundleRetention, StateBuilder};
318
319 #[derive(Default)]
321 struct EmptyDb;
322
323 impl Database for EmptyDb {
324 type Error = std::convert::Infallible;
325 fn basic(
326 &mut self,
327 _address: Address,
328 ) -> Result<Option<revm_state::AccountInfo>, Self::Error> {
329 Ok(None)
330 }
331 fn code_by_hash(
332 &mut self,
333 _code_hash: alloy_primitives::B256,
334 ) -> Result<revm_state::Bytecode, Self::Error> {
335 Ok(revm_state::Bytecode::default())
336 }
337 fn storage(&mut self, _address: Address, _index: U256) -> Result<U256, Self::Error> {
338 Ok(U256::ZERO)
339 }
340 fn block_hash(&mut self, _number: u64) -> Result<alloy_primitives::B256, Self::Error> {
341 Ok(alloy_primitives::B256::ZERO)
342 }
343 }
344
345 fn make_state() -> revm::database::State<EmptyDb> {
346 StateBuilder::new()
347 .with_database(EmptyDb)
348 .with_bundle_update()
349 .build()
350 }
351
352 #[test]
353 fn test_write_storage_at_creates_transition() {
354 let mut state = make_state();
355 let slot = U256::from(42);
356 let value = U256::from(12345);
357
358 write_storage_at(&mut state, ARBOS_STATE_ADDRESS, slot, value);
359
360 let cached = state.cache.accounts.get(&ARBOS_STATE_ADDRESS).unwrap();
362 let stored = cached
363 .account
364 .as_ref()
365 .unwrap()
366 .storage
367 .get(&slot)
368 .copied()
369 .unwrap();
370 assert_eq!(stored, value, "Value should be in cache");
371
372 state.merge_transitions(BundleRetention::Reverts);
374 let bundle = state.take_bundle();
375
376 let bundle_acct = bundle
378 .state
379 .get(&ARBOS_STATE_ADDRESS)
380 .expect("ArbOS account should be in bundle after merge");
381 let bundle_slot = bundle_acct
382 .storage
383 .get(&slot)
384 .expect("Slot should be in bundle storage");
385 assert_eq!(
386 bundle_slot.present_value, value,
387 "Bundle present_value should match"
388 );
389 }
390
391 #[test]
392 fn test_write_zero_value_is_noop_for_new_slot() {
393 let mut state = make_state();
394 let slot = U256::from(42);
395
396 write_storage_at(&mut state, ARBOS_STATE_ADDRESS, slot, U256::ZERO);
398
399 state.merge_transitions(BundleRetention::Reverts);
401 let bundle = state.take_bundle();
402
403 if let Some(acct) = bundle.state.get(&ARBOS_STATE_ADDRESS) {
405 assert!(
406 !acct.storage.contains_key(&slot),
407 "Slot written with zero should not appear in bundle"
408 );
409 }
410 }
411
412 #[test]
413 fn test_write_survives_multiple_transitions() {
414 let mut state = make_state();
415 let slot_a = U256::from(10);
416 let slot_b = U256::from(20);
417
418 write_storage_at(&mut state, ARBOS_STATE_ADDRESS, slot_a, U256::from(100));
420
421 write_storage_at(&mut state, ARBOS_STATE_ADDRESS, slot_b, U256::from(200));
423
424 state.merge_transitions(BundleRetention::Reverts);
426 let bundle = state.take_bundle();
427
428 let acct = bundle
429 .state
430 .get(&ARBOS_STATE_ADDRESS)
431 .expect("ArbOS account should be in bundle");
432 assert_eq!(
433 acct.storage.get(&slot_a).unwrap().present_value,
434 U256::from(100),
435 "Slot A should survive merge"
436 );
437 assert_eq!(
438 acct.storage.get(&slot_b).unwrap().present_value,
439 U256::from(200),
440 "Slot B should survive merge"
441 );
442 }
443
444 #[test]
445 fn test_read_after_write_returns_written_value() {
446 let mut state = make_state();
447 let slot = U256::from(42);
448 let value = U256::from(99999);
449
450 write_storage_at(&mut state, ARBOS_STATE_ADDRESS, slot, value);
451
452 let read_val = read_storage_at(&mut state, ARBOS_STATE_ADDRESS, slot);
454 assert_eq!(read_val, value, "Read should return written value");
455 }
456
457 #[test]
464 fn test_write_survives_evm_commit_flow() {
465 let mut state = make_state();
466 let slot_basefee = U256::from(10);
467 let slot_backlog = U256::from(20);
468
469 write_storage_at(
471 &mut state,
472 ARBOS_STATE_ADDRESS,
473 slot_basefee,
474 U256::from(100_000_000),
475 );
476
477 use revm_database::DatabaseCommit;
479 let empty_state: alloy_primitives::map::HashMap<Address, revm_state::Account> =
480 Default::default();
481 state.commit(empty_state);
482
483 let sender = address!("1111111111111111111111111111111111111111");
485 let mut user_changes: alloy_primitives::map::HashMap<Address, revm_state::Account> =
486 Default::default();
487 let _ = state.load_cache_account(sender);
489 let mut sender_acct = revm_state::Account::default();
490 sender_acct.info.balance = U256::from(1_000_000);
491 sender_acct.info.nonce = 1;
492 sender_acct.mark_touch();
493 user_changes.insert(sender, sender_acct);
494 state.commit(user_changes);
495
496 write_storage_at(
498 &mut state,
499 ARBOS_STATE_ADDRESS,
500 slot_backlog,
501 U256::from(540_000),
502 );
503
504 state.merge_transitions(BundleRetention::Reverts);
506 let bundle = state.take_bundle();
507
508 let acct = bundle
510 .state
511 .get(&ARBOS_STATE_ADDRESS)
512 .expect("ArbOS account should be in bundle");
513 assert_eq!(
514 acct.storage.get(&slot_basefee).unwrap().present_value,
515 U256::from(100_000_000),
516 "baseFee slot should survive"
517 );
518 assert_eq!(
519 acct.storage.get(&slot_backlog).unwrap().present_value,
520 U256::from(540_000),
521 "gasBacklog slot should survive"
522 );
523 }
524}