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,
rex5_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),
rex5_enabled: spec.is_enabled(MegaSpecId::REX5),
frame_tracker: FrameLimitTracker::new(spec, tx_limit),
}
}
pub(crate) fn has_active_frame(&self) -> bool {
self.frame_tracker.has_active_frame()
}
fn record_discardable(&mut self, size: u64) {
self.frame_tracker.add_frame_discardable(size);
}
fn record_refund(&mut self, size: u64) {
self.frame_tracker.add_frame_refund(size);
}
pub(crate) fn record_oracle_hint_bytes(&mut self, len: u64) {
self.frame_tracker.add_tx_persistent(len);
}
pub(crate) fn record_account_write(&mut self) {
self.record_discardable(ACCOUNT_INFO_WRITE_SIZE);
}
pub(crate) fn merge_persistent_usage(&mut self, amount: u64) {
self.frame_tracker.add_tx_persistent(amount);
}
pub(crate) fn current_call_remaining(&self) -> u64 {
let tx_remaining =
self.frame_tracker.tx_limit().saturating_sub(self.frame_tracker.net_usage());
if self.rex4_enabled {
self.frame_tracker.current_frame_remaining().min(tx_remaining)
} else {
tx_remaining
}
}
}
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.rex5_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.add_tx_persistent(size);
for authorization in tx.authorization_list() {
if authorization.authority().is_some() {
self.frame_tracker.add_tx_persistent(ACCOUNT_INFO_WRITE_SIZE);
}
}
self.frame_tracker.add_tx_persistent(ACCOUNT_INFO_WRITE_SIZE);
}
#[inline]
fn push_empty_frame(&mut self) {
self.frame_tracker.push_dummy_frame();
}
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 =
self.frame_tracker.push_call_frame(call_inputs.target_address, 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.push_create_frame();
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");
self.frame_tracker.set_created_address(created_address);
self.record_discardable(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");
let is_success = result.instruction_result().is_ok();
self.frame_tracker.pop_frame_unwind_parent(is_success);
}
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);
}
}