use alloy_primitives::{Address, U256};
use revm::{
context::{transaction::AuthorizationTr, Transaction},
handler::{EthFrame, FrameResult},
interpreter::{
interpreter::EthInterpreter, interpreter_action::FrameInit, FrameInput, SStoreResult,
},
};
use super::frame_limit::{CallFrameInfo, FrameLimitTracker, TxRuntimeLimit};
use crate::{MegaSpecId, MegaTransaction};
#[derive(Debug, Clone)]
pub(crate) struct KVUpdateTracker {
rex4_enabled: bool,
frame_tracker: FrameLimitTracker<CallFrameInfo>,
}
impl KVUpdateTracker {
pub(crate) fn new(spec: MegaSpecId, tx_limit: u64) -> Self {
Self {
rex4_enabled: spec.is_enabled(MegaSpecId::REX4),
frame_tracker: FrameLimitTracker::new(spec, tx_limit),
}
}
fn record_discardable(&mut self, n: u64) {
self.frame_tracker.add_frame_discardable(n);
}
fn record_refund(&mut self, n: u64) {
self.frame_tracker.add_frame_refund(n);
}
pub(crate) fn record_account_update(&mut self) {
self.record_discardable(1);
}
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 KVUpdateTracker {
#[inline]
fn tx_limit(&self) -> u64 {
self.frame_tracker.tx_limit()
}
#[inline]
fn tx_usage(&self) -> u64 {
self.frame_tracker.net_usage()
}
#[inline]
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::KVUpdate);
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(),
"KVUpdate TX-level exceeded with active frame — budget invariant violated"
);
super::LimitCheck::ExceedsLimit {
kind: super::LimitKind::KVUpdate,
limit,
used,
frame_local: false,
}
} else {
super::LimitCheck::WithinLimit
}
}
fn before_tx_start(&mut self, tx: &MegaTransaction) {
for authorization in tx.authorization_list() {
if authorization.authority().is_some() {
self.frame_tracker.add_tx_persistent(1);
}
}
self.frame_tracker.add_tx_persistent(1);
}
#[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(1);
}
self.record_discardable(1);
}
}
FrameInput::Create(_) => {
let parent_needs_update = self.frame_tracker.push_create_frame();
if parent_needs_update {
self.record_discardable(1);
}
}
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(1);
}
}
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(1);
}
} else if store_result.is_original_eq_new() {
self.record_refund(1);
}
}
}