use alloy_primitives::{Address, U256};
use revm::{
context::{transaction::AuthorizationTr, Transaction},
handler::{EthFrame, FrameResult},
interpreter::{
interpreter::EthInterpreter, interpreter_action::FrameInit, FrameInput, InterpreterAction,
SStoreResult,
},
};
use super::frame_limit::CallFrameInfo;
use crate::{FrameLimitTracker, MegaSpecId, TxRuntimeLimit};
pub const BASE_TX_SIZE: u64 = 110;
pub const AUTHORIZATION_SIZE: u64 = 101;
pub const LOG_TOPIC_SIZE: u64 = 32;
pub const SALT_KEY_SIZE: u64 = 8;
pub const SALT_VALUE_DELTA_ACCOUNT_INFO_SIZE: u64 = 32;
pub const SALT_VALUE_DELTA_STORAGE_SLOT_SIZE: u64 = 32;
pub const ACCOUNT_INFO_WRITE_SIZE: u64 = SALT_KEY_SIZE + SALT_VALUE_DELTA_ACCOUNT_INFO_SIZE;
pub const STORAGE_SLOT_WRITE_SIZE: u64 = SALT_KEY_SIZE + SALT_VALUE_DELTA_STORAGE_SLOT_SIZE;
#[derive(Debug, Clone)]
pub(crate) struct DataSizeTracker {
rex4_enabled: bool,
frame_tracker: FrameLimitTracker<CallFrameInfo>,
}
impl DataSizeTracker {
pub(crate) fn new(spec: MegaSpecId, tx_limit: u64) -> Self {
Self {
rex4_enabled: spec.is_enabled(MegaSpecId::REX4),
frame_tracker: FrameLimitTracker::new(tx_limit),
}
}
fn push_frame(&mut self, info: CallFrameInfo) {
if self.rex4_enabled {
self.frame_tracker.push_frame(info);
} else {
self.frame_tracker.push_frame_with_limit(u64::MAX, info);
}
}
pub(crate) fn has_active_frame(&self) -> bool {
self.frame_tracker.has_active_frame()
}
fn record_discardable(&mut self, size: u64) {
if let Some(entry) = self.frame_tracker.frame_mut() {
entry.discardable_usage += size;
}
}
fn record_refund(&mut self, size: u64) {
if let Some(entry) = self.frame_tracker.frame_mut() {
entry.refund += size;
}
}
}
impl TxRuntimeLimit for DataSizeTracker {
#[inline]
fn tx_limit(&self) -> u64 {
self.frame_tracker.tx_limit()
}
#[inline]
fn tx_usage(&self) -> u64 {
self.frame_tracker.net_usage()
}
fn reset(&mut self) {
self.frame_tracker.reset();
}
fn check_limit(&self) -> super::LimitCheck {
if self.rex4_enabled {
let frame_check =
self.frame_tracker.exceeds_current_frame_limit(super::LimitKind::DataSize);
if frame_check.exceeded_limit() {
return frame_check;
}
}
let used = self.tx_usage();
let limit = self.frame_tracker.tx_limit();
if used > limit {
debug_assert!(
!self.rex4_enabled || !self.frame_tracker.has_active_frame(),
"DataSize TX-level exceeded with active frame — budget invariant violated"
);
super::LimitCheck::ExceedsLimit {
kind: super::LimitKind::DataSize,
limit,
used,
frame_local: false,
}
} else {
super::LimitCheck::WithinLimit
}
}
fn before_tx_start(&mut self, tx: &crate::MegaTransaction) {
let mut size = BASE_TX_SIZE;
size += tx.input().len() as u64;
size += tx
.access_list()
.map(|item| item.map(|access| access.size() as u64).sum::<u64>())
.unwrap_or_default();
size += tx.authorization_list_len() as u64 * AUTHORIZATION_SIZE;
self.frame_tracker.tx_mut().persistent_usage += size;
for authorization in tx.authorization_list() {
if authorization.authority().is_some() {
self.frame_tracker.tx_mut().persistent_usage += ACCOUNT_INFO_WRITE_SIZE;
}
}
self.frame_tracker.tx_mut().persistent_usage += ACCOUNT_INFO_WRITE_SIZE;
}
#[inline]
fn push_empty_frame(&mut self) {
self.push_frame(CallFrameInfo { target_address: None, target_updated: false });
}
fn before_frame_init<JOURNAL: crate::JournalInspectTr<DBError: core::fmt::Debug>>(
&mut self,
frame_init: &FrameInit,
_journal: &mut JOURNAL,
) -> Result<(), JOURNAL::DBError> {
match &frame_init.frame_input {
FrameInput::Call(call_inputs) => {
let has_transfer = call_inputs.transfers_value();
let parent_needs_update = has_transfer &&
self.frame_tracker
.frame_mut()
.is_some_and(|entry| !entry.info.target_updated);
self.push_frame(CallFrameInfo {
target_address: Some(call_inputs.target_address),
target_updated: has_transfer,
});
if has_transfer {
if parent_needs_update {
self.record_discardable(ACCOUNT_INFO_WRITE_SIZE);
}
self.record_discardable(ACCOUNT_INFO_WRITE_SIZE);
}
}
FrameInput::Create(_) => {
let parent_needs_update =
self.frame_tracker.frame_mut().is_some_and(|entry| !entry.info.target_updated);
self.push_frame(CallFrameInfo { target_address: None, target_updated: true });
if parent_needs_update {
self.record_discardable(ACCOUNT_INFO_WRITE_SIZE);
}
}
FrameInput::Empty => unreachable!(),
}
Ok(())
}
fn after_frame_init_on_frame(&mut self, frame: &EthFrame<EthInterpreter>) {
if frame.data.is_create() {
let created_address =
frame.data.created_address().expect("created address is none for create frame");
if let Some(entry) = self.frame_tracker.frame_mut() {
assert!(entry.info.target_address.is_none(), "created account already recorded");
entry.info.target_address = Some(created_address);
entry.discardable_usage += ACCOUNT_INFO_WRITE_SIZE;
}
}
}
fn after_frame_run<'a>(
&mut self,
frame: &'a EthFrame<EthInterpreter>,
action: &'a mut InterpreterAction,
) {
if let InterpreterAction::Return(interpreter_result) = action {
if frame.data.is_create() {
let code_size = interpreter_result.output.len() as u64;
self.record_discardable(code_size);
}
}
}
fn before_frame_return_result<const LAST_FRAME: bool>(&mut self, result: &FrameResult) {
assert!(LAST_FRAME || self.frame_tracker.has_active_frame(), "frame stack is empty");
self.frame_tracker.pop_frame(result.instruction_result().is_ok());
}
fn after_sstore(&mut self, _target_address: Address, _slot: U256, store_result: &SStoreResult) {
if store_result.is_original_eq_present() {
if !store_result.is_original_eq_new() {
self.record_discardable(STORAGE_SLOT_WRITE_SIZE);
}
} else if store_result.is_original_eq_new() {
self.record_refund(STORAGE_SLOT_WRITE_SIZE);
}
}
fn after_log(&mut self, num_topics: u64, data_size: u64) {
let size = num_topics * LOG_TOPIC_SIZE + data_size;
self.record_discardable(size);
}
}