#[cfg(not(feature = "std"))]
use alloc as std;
use std::vec::Vec;
use revm::{
handler::FrameResult,
interpreter::{interpreter_action::FrameInit, CallScheme, FrameInput},
};
use super::compute_gas;
use crate::{constants, MegaSpecId};
#[derive(Debug, Clone)]
pub(crate) struct StorageCallStipendTracker {
stipend_amount: u64,
stack: Vec<u64>,
}
#[derive(Clone, Copy, Debug)]
struct StorageCallStipendGrant {
stipend: u64,
compute_gas_cap: u64,
}
impl StorageCallStipendTracker {
pub(crate) fn new(spec: MegaSpecId) -> Self {
Self { stipend_amount: Self::stipend_for_spec(spec), 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(0);
}
pub(crate) fn before_frame_init(
&mut self,
frame_init: &mut FrameInit,
compute_gas: &mut compute_gas::ComputeGasTracker,
) {
if let Some(grant) = self.detect_and_apply(frame_init) {
self.stack.push(grant.stipend);
compute_gas.cap_current_frame_limit(grant.compute_gas_cap);
} else {
self.stack.push(0);
}
}
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 stipend = self.stack.pop().unwrap_or(0);
if stipend > 0 {
let gas = result.gas_mut();
let original_limit = gas.limit().saturating_sub(stipend);
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().copied().unwrap_or(0)
}
fn detect_and_apply(&self, frame_init: &mut FrameInit) -> Option<StorageCallStipendGrant> {
if self.stipend_amount == 0 {
return None;
}
let FrameInput::Call(call_inputs) = &mut frame_init.frame_input else {
return None;
};
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);
if !(is_internal_call && is_value_transfer && is_call_or_callcode) {
return None;
}
let stipend = self.stipend_amount;
let compute_gas_cap = call_inputs.gas_limit;
call_inputs.gas_limit = call_inputs.gas_limit.saturating_add(stipend);
Some(StorageCallStipendGrant { stipend, compute_gas_cap })
}
}