use crate::{VolatileDataAccess, VolatileDataAccessType, ORACLE_CONTRACT_ADDRESS};
use alloy_primitives::Address;
#[derive(Debug, Clone)]
pub struct VolatileDataAccessTracker {
volatile_data_accessed: VolatileDataAccess,
compute_gas_limit: Option<u64>,
block_env_access_limit: u64,
oracle_access_limit: u64,
disable_depth: Option<usize>,
}
impl VolatileDataAccessTracker {
pub fn new(block_env_access_limit: u64, oracle_access_limit: u64) -> Self {
Self {
volatile_data_accessed: VolatileDataAccess::empty(),
compute_gas_limit: None,
block_env_access_limit,
oracle_access_limit,
disable_depth: None,
}
}
pub fn accessed(&self) -> bool {
!self.volatile_data_accessed.is_empty()
}
pub fn get_volatile_data_info(&self) -> Option<VolatileDataAccess> {
self.accessed().then_some(self.volatile_data_accessed)
}
pub fn get_compute_gas_limit(&self) -> Option<u64> {
self.compute_gas_limit
}
pub fn get_block_env_accesses(&self) -> VolatileDataAccess {
self.volatile_data_accessed.block_env_only()
}
pub fn get_volatile_data_accessed(&self) -> VolatileDataAccess {
self.volatile_data_accessed
}
pub fn mark_block_env_accessed(&mut self, access_type: VolatileDataAccessType) {
self.volatile_data_accessed.insert(access_type.into());
self.apply_or_create_limit(self.block_env_access_limit);
}
pub fn has_accessed_beneficiary_balance(&self) -> bool {
self.volatile_data_accessed.has_beneficiary_balance_access()
}
pub fn mark_beneficiary_balance_accessed(&mut self) {
self.volatile_data_accessed.insert(VolatileDataAccess::BENEFICIARY_BALANCE);
self.apply_or_create_limit(self.block_env_access_limit);
}
pub fn has_accessed_oracle(&self) -> bool {
self.volatile_data_accessed.has_oracle_access()
}
pub fn check_and_mark_oracle_access(&mut self, address: &Address) -> bool {
if address == &ORACLE_CONTRACT_ADDRESS {
self.volatile_data_accessed.insert(VolatileDataAccess::ORACLE);
self.apply_or_create_limit(self.oracle_access_limit);
true
} else {
false
}
}
fn apply_or_create_limit(&mut self, limit: u64) {
if let Some(current_limit) = self.compute_gas_limit {
self.compute_gas_limit = Some(current_limit.min(limit));
} else {
self.compute_gas_limit = Some(limit);
}
}
pub fn disable_access(&mut self, depth: usize) {
match self.disable_depth {
Some(current) if depth >= current => {}
_ => self.disable_depth = Some(depth),
}
}
pub fn volatile_access_disabled(&self, current_depth: usize) -> bool {
self.disable_depth.is_some_and(|d| current_depth >= d)
}
pub fn enable_access(&mut self, caller_depth: usize) -> bool {
match self.disable_depth {
None => true,
Some(d) if caller_depth <= d => {
self.disable_depth = None;
true
}
Some(_) => false,
}
}
pub fn enable_access_if_returning(&mut self, current_depth: usize) {
if self.disable_depth.is_some_and(|d| current_depth < d) {
self.disable_depth = None;
}
}
pub fn reset(&mut self) {
self.volatile_data_accessed = VolatileDataAccess::empty();
self.compute_gas_limit = None;
self.disable_depth = None;
}
pub fn merge_accesses_from_bitmap(&mut self, other_bitmap: VolatileDataAccess) {
self.volatile_data_accessed |= other_bitmap;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::VolatileDataAccessType;
#[test]
fn test_merge_accesses_from_bitmap_unions_bitmap_and_preserves_other_state() {
let mut parent = VolatileDataAccessTracker::new(20_000_000, 20_000_000);
parent.mark_block_env_accessed(VolatileDataAccessType::Timestamp);
let parent_cap_before = parent.get_compute_gas_limit();
assert_eq!(parent_cap_before, Some(20_000_000));
assert!(parent.get_volatile_data_accessed().contains(VolatileDataAccess::TIMESTAMP));
let snapshot = VolatileDataAccess::BLOCK_NUMBER | VolatileDataAccess::ORACLE;
parent.merge_accesses_from_bitmap(snapshot);
let merged = parent.get_volatile_data_accessed();
assert!(merged.contains(VolatileDataAccess::TIMESTAMP));
assert!(merged.contains(VolatileDataAccess::BLOCK_NUMBER));
assert!(merged.contains(VolatileDataAccess::ORACLE));
assert!(!merged.contains(VolatileDataAccess::BENEFICIARY_BALANCE));
assert_eq!(parent.get_compute_gas_limit(), parent_cap_before);
assert!(!parent.volatile_access_disabled(0));
assert!(!parent.volatile_access_disabled(10));
}
#[test]
fn test_merge_accesses_from_bitmap_empty_snapshot_is_noop() {
let mut parent = VolatileDataAccessTracker::new(20_000_000, 20_000_000);
parent.mark_beneficiary_balance_accessed();
let before_bitmap = parent.get_volatile_data_accessed();
let before_cap = parent.get_compute_gas_limit();
parent.merge_accesses_from_bitmap(VolatileDataAccess::empty());
assert_eq!(parent.get_volatile_data_accessed(), before_bitmap);
assert_eq!(parent.get_compute_gas_limit(), before_cap);
}
#[test]
fn test_merge_accesses_from_bitmap_into_empty_parent_copies_bitmap_only() {
let mut parent = VolatileDataAccessTracker::new(20_000_000, 20_000_000);
let snapshot = VolatileDataAccess::COINBASE | VolatileDataAccess::ORACLE;
parent.merge_accesses_from_bitmap(snapshot);
let merged = parent.get_volatile_data_accessed();
assert!(merged.contains(VolatileDataAccess::COINBASE));
assert!(merged.contains(VolatileDataAccess::ORACLE));
assert_eq!(parent.get_compute_gas_limit(), None);
assert!(!parent.volatile_access_disabled(0));
assert!(!parent.volatile_access_disabled(3));
}
#[test]
fn test_merge_accesses_from_bitmap_is_idempotent() {
let mut parent = VolatileDataAccessTracker::new(20_000_000, 20_000_000);
let snapshot = VolatileDataAccess::TIMESTAMP | VolatileDataAccess::ORACLE;
parent.merge_accesses_from_bitmap(snapshot);
let after_first = parent.get_volatile_data_accessed();
let cap_after_first = parent.get_compute_gas_limit();
parent.merge_accesses_from_bitmap(snapshot);
assert_eq!(parent.get_volatile_data_accessed(), after_first);
assert_eq!(parent.get_compute_gas_limit(), cap_after_first);
}
}