#[cfg(not(feature = "std"))]
use alloc as std;
use std::vec::Vec;
use revm::{
handler::FrameResult,
interpreter::{interpreter_action::FrameInit, CallScheme, FrameInput, Gas},
};
use super::compute_gas;
use crate::{constants, MegaSpecId};
#[derive(Debug, Clone)]
pub(crate) struct StorageCallStipendTracker {
stipend_amount: u64,
rex5_enabled: bool,
stack: Vec<StipendFrame>,
}
#[derive(Debug, Clone, Copy, Default)]
struct StipendFrame {
remaining: u64,
legacy_inflated: bool,
}
impl StorageCallStipendTracker {
pub(crate) fn new(spec: MegaSpecId) -> Self {
Self {
stipend_amount: Self::stipend_for_spec(spec),
rex5_enabled: spec.is_enabled(MegaSpecId::REX5),
stack: Vec::new(),
}
}
fn stipend_for_spec(spec: MegaSpecId) -> u64 {
if spec.is_enabled(MegaSpecId::REX4) {
constants::rex4::STORAGE_CALL_STIPEND
} else {
0
}
}
pub(crate) fn reset(&mut self) {
self.stack.clear();
}
pub(crate) fn push_empty_frame(&mut self) {
self.stack.push(StipendFrame::default());
}
pub(crate) fn try_consume(&mut self, amount: u64) -> u64 {
if !self.rex5_enabled {
return 0;
}
let Some(frame) = self.stack.last_mut() else {
return 0;
};
let drained = frame.remaining.min(amount);
frame.remaining -= drained;
drained
}
pub(crate) fn before_frame_init(
&mut self,
frame_init: &mut FrameInit,
compute_gas: &mut compute_gas::ComputeGasTracker,
) {
if !self.detect_grant(frame_init) {
self.stack.push(StipendFrame::default());
return;
}
if self.rex5_enabled {
self.stack
.push(StipendFrame { remaining: self.stipend_amount, legacy_inflated: false });
return;
}
let FrameInput::Call(call_inputs) = &mut frame_init.frame_input else {
self.stack.push(StipendFrame::default());
return;
};
let pre_inflation_limit = call_inputs.gas_limit;
call_inputs.gas_limit = call_inputs.gas_limit.saturating_add(self.stipend_amount);
compute_gas.cap_current_frame_limit(pre_inflation_limit);
self.stack.push(StipendFrame { remaining: self.stipend_amount, legacy_inflated: true });
}
pub(crate) fn before_frame_return_result<const LAST_FRAME: bool>(
&mut self,
result: &mut FrameResult,
) {
assert!(LAST_FRAME || !self.stack.is_empty(), "frame stack is empty");
let frame = self.stack.pop().unwrap_or_default();
if self.rex5_enabled {
return;
}
if frame.legacy_inflated {
let gas = result.gas_mut();
let original_limit = gas.limit().saturating_sub(self.stipend_amount);
let burn = gas.remaining().saturating_sub(original_limit);
if burn > 0 {
let _ = gas.record_cost(burn);
}
}
}
pub(crate) fn current_frame_stipend(&self) -> u64 {
self.stack.last().map(|frame| frame.remaining).unwrap_or(0)
}
pub(crate) fn effective_remaining_for_rescue(&self, gas: &Gas) -> u64 {
if self.rex5_enabled {
return gas.remaining();
}
let stipend = self.current_frame_stipend();
if stipend > 0 {
let original_limit = gas.limit().saturating_sub(stipend);
gas.remaining().min(original_limit)
} else {
gas.remaining()
}
}
fn detect_grant(&self, frame_init: &FrameInit) -> bool {
if self.stipend_amount == 0 {
return false;
}
let FrameInput::Call(call_inputs) = &frame_init.frame_input else {
return false;
};
let is_internal_call = frame_init.depth != 0;
let is_value_transfer = call_inputs.transfers_value();
let is_call_or_callcode =
matches!(call_inputs.scheme, CallScheme::Call | CallScheme::CallCode);
is_internal_call && is_value_transfer && is_call_or_callcode
}
}