use alloy_primitives::{Address, U256};
use revm::{
handler::FrameResult,
interpreter::{interpreter_action::FrameInit, FrameInput, SStoreResult},
primitives::hardfork::SpecId,
};
use crate::{FrameLimitTracker, JournalInspectTr, MegaSpecId, TxRuntimeLimit};
#[derive(Debug, Clone)]
pub(crate) struct StateGrowthTracker {
spec: MegaSpecId,
frame_tracker: FrameLimitTracker<()>,
}
impl StateGrowthTracker {
pub(crate) fn new(spec: MegaSpecId, tx_limit: u64) -> Self {
Self { spec, frame_tracker: FrameLimitTracker::new(tx_limit) }
}
fn push_frame(&mut self) {
if self.spec.is_enabled(MegaSpecId::REX4) {
self.frame_tracker.push_frame(());
} else {
self.frame_tracker.push_frame_with_limit(u64::MAX, ());
}
}
fn record_growth(&mut self, n: u64) {
if let Some(entry) = self.frame_tracker.frame_mut() {
entry.discardable_usage += n;
}
}
fn record_refund(&mut self, n: u64) {
if let Some(entry) = self.frame_tracker.frame_mut() {
entry.refund += n;
}
}
}
impl TxRuntimeLimit for StateGrowthTracker {
#[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.spec.is_enabled(MegaSpecId::REX4) {
let frame_check =
self.frame_tracker.exceeds_current_frame_limit(super::LimitKind::StateGrowth);
if frame_check.exceeded_limit() {
return frame_check;
}
}
let used = self.tx_usage();
let limit = self.frame_tracker.tx_limit();
if used > limit {
super::LimitCheck::ExceedsLimit {
kind: super::LimitKind::StateGrowth,
limit,
used,
frame_local: false,
}
} else {
super::LimitCheck::WithinLimit
}
}
#[inline]
fn push_empty_frame(&mut self) {
self.push_frame();
}
fn before_frame_init<JOURNAL: JournalInspectTr<DBError: core::fmt::Debug>>(
&mut self,
frame_init: &FrameInit,
journal: &mut JOURNAL,
) -> Result<(), JOURNAL::DBError> {
self.push_frame();
match &frame_init.frame_input {
FrameInput::Call(call_inputs) => {
if call_inputs.transfers_value() {
let to_account =
journal.inspect_account_delegated(self.spec, call_inputs.target_address)?;
let is_empty = to_account.state_clear_aware_is_empty(SpecId::PRAGUE);
if is_empty {
self.record_growth(1);
}
}
}
FrameInput::Create(_) => {
self.record_growth(1);
}
FrameInput::Empty => unreachable!(),
}
Ok(())
}
fn after_sstore(&mut self, _target_address: Address, _slot: U256, store_result: &SStoreResult) {
match (
store_result.original_value.is_zero(),
store_result.present_value.is_zero(),
store_result.new_value.is_zero(),
) {
(true, true, false) => {
self.record_growth(1);
}
(true, false, true) => {
self.record_refund(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");
self.frame_tracker.pop_frame(result.instruction_result().is_ok());
}
fn after_selfdestruct(&mut self, refund: u64) {
self.record_refund(refund);
}
}