use core::ops::Range;
use alloy_primitives::{Address, Bytes, U256};
use op_revm::OpHaltReason;
use revm::{
context::result::{HaltReason, OutOfGasError},
handler::{EthFrame, FrameResult, ItemOrResult},
interpreter::{
interpreter::EthInterpreter, interpreter_action::FrameInit, CallOutcome, CreateOutcome,
FrameInput, Gas, InstructionResult, InterpreterAction, InterpreterResult, SStoreResult,
},
};
use super::{
compute_gas, data_size, frame_limit::TxRuntimeLimit, kv_update, state_growth,
storage_call_stipend,
};
use crate::{
EvmTxRuntimeLimits, JournalInspectTr, MegaHaltReason, MegaSpecId, MegaTransaction,
VolatileDataAccess,
};
use super::LimitCheck;
#[derive(Debug)]
pub struct AdditionalLimit {
pub has_exceeded_limit: LimitCheck,
pub rescued_gas: u64,
pub limits: EvmTxRuntimeLimits,
pub(crate) state_growth: state_growth::StateGrowthTracker,
pub(crate) data_size: data_size::DataSizeTracker,
pub(crate) kv_update: kv_update::KVUpdateTracker,
pub(crate) compute_gas: compute_gas::ComputeGasTracker,
pub(crate) storage_call_stipend: storage_call_stipend::StorageCallStipendTracker,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct LimitUsage {
pub data_size: u64,
pub kv_updates: u64,
pub compute_gas: u64,
pub state_growth: u64,
}
impl AdditionalLimit {
pub fn new(spec: MegaSpecId, limits: EvmTxRuntimeLimits) -> Self {
Self {
has_exceeded_limit: LimitCheck::WithinLimit,
rescued_gas: 0,
limits,
state_growth: state_growth::StateGrowthTracker::new(spec, limits.tx_state_growth_limit),
data_size: data_size::DataSizeTracker::new(spec, limits.tx_data_size_limit),
kv_update: kv_update::KVUpdateTracker::new(spec, limits.tx_kv_updates_limit),
compute_gas: compute_gas::ComputeGasTracker::new(spec, limits.tx_compute_gas_limit),
storage_call_stipend: storage_call_stipend::StorageCallStipendTracker::new(spec),
}
}
}
impl AdditionalLimit {
pub const EXCEEDING_LIMIT_INSTRUCTION_RESULT: InstructionResult = InstructionResult::OutOfGas;
#[inline]
pub(crate) fn exceeding_instruction_result(&self) -> InstructionResult {
if self.has_exceeded_limit.is_frame_local() {
InstructionResult::Revert
} else {
Self::EXCEEDING_LIMIT_INSTRUCTION_RESULT
}
}
pub fn reset(&mut self) {
self.has_exceeded_limit = LimitCheck::WithinLimit;
self.rescued_gas = 0;
self.compute_gas.reset();
self.state_growth.reset();
self.data_size.reset();
self.kv_update.reset();
self.storage_call_stipend.reset();
}
#[inline]
pub fn get_usage(&self) -> LimitUsage {
LimitUsage {
data_size: self.data_size.tx_usage(),
kv_updates: self.kv_update.tx_usage(),
compute_gas: self.compute_gas.tx_usage(),
state_growth: self.state_growth.tx_usage(),
}
}
#[inline]
pub(crate) fn push_empty_frame(&mut self) {
self.state_growth.push_empty_frame();
self.data_size.push_empty_frame();
self.kv_update.push_empty_frame();
self.compute_gas.push_empty_frame();
self.storage_call_stipend.push_empty_frame();
}
#[inline]
pub fn compute_gas_limit(&self) -> u64 {
self.compute_gas.tx_limit()
}
#[inline]
pub fn current_call_remaining_compute_gas(&self) -> u64 {
self.compute_gas.current_call_remaining()
}
#[inline]
pub fn detained_compute_gas_limit(&self) -> u64 {
self.compute_gas.detained_limit()
}
#[inline]
pub(crate) fn detained_compute_gas_halt_reason(
&self,
access_type: VolatileDataAccess,
) -> Option<MegaHaltReason> {
self.compute_gas.is_detained_exceed().then(|| MegaHaltReason::VolatileDataAccessOutOfGas {
access_type,
limit: self.compute_gas.detained_limit(),
actual: self.compute_gas.tx_usage(),
})
}
#[inline]
pub fn set_compute_gas_limit(&mut self, new_limit: u64) {
self.compute_gas.set_detained_limit(new_limit);
}
#[inline]
pub fn check_limit(&mut self) -> LimitCheck {
if self.has_exceeded_limit.exceeded_limit() {
return self.has_exceeded_limit;
}
let data_size_check = self.data_size.check_limit();
if data_size_check.exceeded_limit() {
self.has_exceeded_limit = data_size_check;
return self.has_exceeded_limit;
}
let kv_update_check = self.kv_update.check_limit();
if kv_update_check.exceeded_limit() {
self.has_exceeded_limit = kv_update_check;
return self.has_exceeded_limit;
}
let compute_gas_check = self.compute_gas.check_limit();
if compute_gas_check.exceeded_limit() {
self.has_exceeded_limit = compute_gas_check;
return self.has_exceeded_limit;
}
let state_growth_check = self.state_growth.check_limit();
if state_growth_check.exceeded_limit() {
self.has_exceeded_limit = state_growth_check;
return self.has_exceeded_limit;
}
self.has_exceeded_limit
}
pub fn is_exceeding_limit_halt(&mut self, halt_reason: &OpHaltReason) -> bool {
matches!(halt_reason, &OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Basic))) &&
self.check_limit().exceeded_limit()
}
}
impl AdditionalLimit {
pub(crate) fn record_compute_gas(&mut self, compute_gas_used: u64) -> bool {
self.compute_gas.record_gas_used(compute_gas_used);
!self.check_limit().exceeded_limit()
}
pub(crate) fn rescue_gas(&mut self, gas: &Gas) {
let stipend = self.storage_call_stipend.current_frame_stipend();
debug_assert_eq!(
stipend, 0,
"rescue_gas called with active stipend {stipend} — inflated gas would leak"
);
let effective_remaining = if stipend > 0 {
let original_limit = gas.limit().saturating_sub(stipend);
gas.remaining().min(original_limit)
} else {
gas.remaining()
};
self.rescued_gas += effective_remaining;
}
pub(crate) fn try_rescue_gas(&mut self, gas: &Gas) {
let limit_check = self.check_limit();
if limit_check.exceeded_limit() && !limit_check.is_frame_local() {
self.rescue_gas(gas);
}
}
pub(crate) fn before_tx_start(&mut self, tx: &MegaTransaction) {
self.state_growth.before_tx_start(tx);
self.data_size.before_tx_start(tx);
self.kv_update.before_tx_start(tx);
self.check_limit();
}
pub(crate) fn before_frame_init<JOURNAL: JournalInspectTr<DBError: core::fmt::Debug>>(
&mut self,
frame_init: &mut FrameInit,
journal: &mut JOURNAL,
) -> Result<Option<FrameResult>, JOURNAL::DBError> {
self.state_growth.before_frame_init(frame_init, journal)?;
self.data_size.before_frame_init(frame_init, journal)?;
self.kv_update.before_frame_init(frame_init, journal)?;
self.compute_gas.before_frame_init(frame_init, journal)?;
self.storage_call_stipend.before_frame_init(frame_init, &mut self.compute_gas);
if self.check_limit().exceeded_limit() {
return Ok(self.create_exceeded_limit_result(&frame_init.frame_input));
}
Ok(None)
}
pub(crate) fn frame_result_if_exceeding_limit(
&mut self,
frame_input: &FrameInput,
) -> Option<FrameResult> {
if !self.has_exceeded_limit.exceeded_limit() {
return None;
}
self.create_exceeded_limit_result(frame_input)
}
fn create_exceeded_limit_result(&mut self, frame_input: &FrameInput) -> Option<FrameResult> {
let (gas_limit, return_memory_offset) = match frame_input {
FrameInput::Call(inputs) => {
(inputs.gas_limit, Some(inputs.return_memory_offset.clone()))
}
FrameInput::Create(inputs) => (inputs.gas_limit, None),
FrameInput::Empty => unreachable!(),
};
let output = self.has_exceeded_limit.revert_data();
let result = create_exceeding_limit_frame_result(
self.exceeding_instruction_result(),
Gas::new(gas_limit),
return_memory_offset,
output,
);
self.try_rescue_gas(result.gas());
Some(result)
}
pub(crate) fn after_frame_init(
&mut self,
init_result: &ItemOrResult<&mut EthFrame<EthInterpreter>, FrameResult>,
) {
if let ItemOrResult::Item(frame) = &init_result {
self.state_growth.after_frame_init_on_frame(frame);
self.data_size.after_frame_init_on_frame(frame);
self.kv_update.after_frame_init_on_frame(frame);
self.compute_gas.after_frame_init_on_frame(frame);
} else if let ItemOrResult::Result(result) = init_result {
self.try_rescue_gas(result.gas());
}
}
pub(crate) fn before_frame_run(
&mut self,
frame: &EthFrame<EthInterpreter>,
) -> Option<InterpreterResult> {
self.state_growth.before_frame_run(frame);
self.data_size.before_frame_run(frame);
self.kv_update.before_frame_run(frame);
self.compute_gas.before_frame_run(frame);
if self.check_limit().exceeded_limit() {
let output = self.has_exceeded_limit.revert_data();
return Some(create_exceeding_interpreter_result(
self.exceeding_instruction_result(),
frame.interpreter.gas,
output,
));
}
None
}
pub(crate) fn after_frame_run(
&mut self,
result: &mut FrameResult,
gas_remaining_before_process_action: Option<u64>,
) {
if let Some(gas_remaining_before) = gas_remaining_before_process_action {
let compute_gas_cost = gas_remaining_before.saturating_sub(result.gas().remaining());
if !self.record_compute_gas(compute_gas_cost) {
mark_frame_result_as_exceeding_limit(
result,
self.exceeding_instruction_result(),
Default::default(),
);
}
}
self.try_rescue_gas(result.gas());
}
pub(crate) fn after_frame_run_instructions<'a>(
&mut self,
frame: &'a EthFrame<EthInterpreter>,
action: &'a mut InterpreterAction,
) {
self.state_growth.after_frame_run(frame, action);
self.data_size.after_frame_run(frame, action);
self.kv_update.after_frame_run(frame, action);
self.compute_gas.after_frame_run(frame, action);
if let InterpreterAction::Return(interpreter_result) = action {
if frame.data.is_create() {
if self.has_exceeded_limit.exceeded_limit() {
let output = self.has_exceeded_limit.revert_data();
mark_interpreter_result_as_exceeding_limit(
interpreter_result,
self.exceeding_instruction_result(),
output,
);
return;
}
if self.check_limit().exceeded_limit() {
let output = self.has_exceeded_limit.revert_data();
mark_interpreter_result_as_exceeding_limit(
interpreter_result,
self.exceeding_instruction_result(),
output,
);
}
}
}
}
pub(crate) fn before_frame_return_result<const LAST_FRAME: bool>(
&mut self,
result: &mut FrameResult,
) {
let duplicate_return_frame_result = LAST_FRAME && !self.data_size.has_active_frame();
self.state_growth.before_frame_return_result::<LAST_FRAME>(result);
self.data_size.before_frame_return_result::<LAST_FRAME>(result);
self.kv_update.before_frame_return_result::<LAST_FRAME>(result);
self.compute_gas.before_frame_return_result::<LAST_FRAME>(result);
self.storage_call_stipend.before_frame_return_result::<LAST_FRAME>(result);
let limit_check = self.check_limit();
if limit_check.exceeded_limit() && !duplicate_return_frame_result {
if limit_check.is_frame_local() {
let output = limit_check.revert_data();
self.has_exceeded_limit = LimitCheck::WithinLimit;
match result {
FrameResult::Call(o) => {
o.result.result = InstructionResult::Revert;
o.result.output = output;
}
FrameResult::Create(o) => {
o.result.result = InstructionResult::Revert;
o.result.output = output;
}
}
} else {
mark_frame_result_as_exceeding_limit(
result,
Self::EXCEEDING_LIMIT_INSTRUCTION_RESULT,
Default::default(),
);
}
}
}
pub(crate) fn on_sstore(
&mut self,
target_address: Address,
slot: U256,
store_result: &SStoreResult,
) -> bool {
self.state_growth.after_sstore(target_address, slot, store_result);
self.data_size.after_sstore(target_address, slot, store_result);
self.kv_update.after_sstore(target_address, slot, store_result);
!self.check_limit().exceeded_limit()
}
pub(crate) fn on_log(&mut self, num_topics: u64, data_size: u64) -> bool {
self.state_growth.after_log(num_topics, data_size);
self.data_size.after_log(num_topics, data_size);
!self.check_limit().exceeded_limit()
}
pub(crate) fn on_selfdestruct(&mut self, refund: u64) {
self.state_growth.after_selfdestruct(refund);
}
}
fn create_exceeding_limit_frame_result(
instruction_result: InstructionResult,
gas: Gas,
return_memory_offset: Option<Range<usize>>,
output: Bytes,
) -> FrameResult {
match return_memory_offset {
None => FrameResult::Create(CreateOutcome::new(
create_exceeding_interpreter_result(instruction_result, gas, output),
None,
)),
Some(return_memory_offset) => FrameResult::Call(CallOutcome::new(
create_exceeding_interpreter_result(instruction_result, gas, output),
return_memory_offset,
)),
}
}
fn create_exceeding_interpreter_result(
instruction_result: InstructionResult,
gas: Gas,
output: Bytes,
) -> InterpreterResult {
InterpreterResult::new(instruction_result, output, gas)
}
fn mark_interpreter_result_as_exceeding_limit(
result: &mut InterpreterResult,
instruction_result: InstructionResult,
output: Bytes,
) {
result.result = instruction_result;
result.output = output;
}
pub(crate) fn mark_frame_result_as_exceeding_limit(
result: &mut FrameResult,
instruction_result: InstructionResult,
output: Bytes,
) {
match result {
FrameResult::Call(call_outcome) => {
mark_interpreter_result_as_exceeding_limit(
&mut call_outcome.result,
instruction_result,
output,
);
}
FrameResult::Create(create_outcome) => {
mark_interpreter_result_as_exceeding_limit(
&mut create_outcome.result,
instruction_result,
output,
);
}
}
}