use alloy_primitives::Bytes;
use alloy_sol_types::SolError;
mod compute_gas;
mod data_size;
mod frame_limit;
mod kv_update;
#[allow(clippy::module_inception)]
mod limit;
mod state_growth;
mod storage_call_stipend;
pub use data_size::*;
pub(crate) use frame_limit::{FrameLimitTracker, TxRuntimeLimit};
pub use limit::*;
use crate::MegaHaltReason;
alloy_sol_types::sol! {
#[derive(Debug, PartialEq, Eq)]
error MegaLimitExceeded(uint8 kind, uint64 limit);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LimitKind {
DataSize,
KVUpdate,
ComputeGas,
StateGrowth,
}
impl LimitKind {
pub const fn as_u8(&self) -> u8 {
match self {
Self::DataSize => 0,
Self::KVUpdate => 1,
Self::ComputeGas => 2,
Self::StateGrowth => 3,
}
}
pub const fn from_u8(kind: u8) -> Option<Self> {
match kind {
0 => Some(Self::DataSize),
1 => Some(Self::KVUpdate),
2 => Some(Self::ComputeGas),
3 => Some(Self::StateGrowth),
_ => None,
}
}
}
#[derive(Debug, Default, Clone, Copy)]
pub enum LimitCheck {
#[default]
WithinLimit,
ExceedsLimit {
kind: LimitKind,
limit: u64,
used: u64,
frame_local: bool,
},
Exempt,
}
impl LimitCheck {
#[inline]
pub const fn exceeded_limit(&self) -> bool {
matches!(self, Self::ExceedsLimit { .. })
}
#[inline]
pub const fn within_limit(&self) -> bool {
matches!(self, Self::WithinLimit)
}
#[inline]
pub const fn is_exempt(&self) -> bool {
matches!(self, Self::Exempt)
}
#[inline]
pub const fn is_frame_local(&self) -> bool {
matches!(self, Self::ExceedsLimit { frame_local: true, .. })
}
pub fn revert_data(&self) -> Bytes {
match self {
Self::ExceedsLimit { kind, limit, .. } => {
MegaLimitExceeded { kind: kind.as_u8(), limit: *limit }.abi_encode().into()
}
Self::WithinLimit | Self::Exempt => Bytes::new(),
}
}
pub fn maybe_halt_reason(&self) -> Option<MegaHaltReason> {
match self {
Self::ExceedsLimit { kind: LimitKind::DataSize, limit, used, .. } => {
Some(MegaHaltReason::DataLimitExceeded { limit: *limit, actual: *used })
}
Self::ExceedsLimit { kind: LimitKind::KVUpdate, limit, used, .. } => {
Some(MegaHaltReason::KVUpdateLimitExceeded { limit: *limit, actual: *used })
}
Self::ExceedsLimit { kind: LimitKind::ComputeGas, limit, used, .. } => {
Some(MegaHaltReason::ComputeGasLimitExceeded { limit: *limit, actual: *used })
}
Self::ExceedsLimit { kind: LimitKind::StateGrowth, limit, used, .. } => {
Some(MegaHaltReason::StateGrowthLimitExceeded { limit: *limit, actual: *used })
}
Self::WithinLimit | Self::Exempt => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_limit_check_exempt_predicate_truth_table() {
let exempt = LimitCheck::Exempt;
assert!(!exempt.exceeded_limit());
assert!(!exempt.within_limit());
assert!(exempt.is_exempt());
assert!(!exempt.is_frame_local());
assert!(exempt.revert_data().is_empty());
assert!(exempt.maybe_halt_reason().is_none());
}
#[test]
fn test_within_limit_reflects_variant() {
assert!(LimitCheck::WithinLimit.within_limit());
let exceeded = LimitCheck::ExceedsLimit {
kind: LimitKind::DataSize,
limit: 100,
used: 150,
frame_local: false,
};
assert!(!exceeded.within_limit());
}
#[test]
fn test_limit_kind_u8_roundtrip() {
for kind in [
LimitKind::DataSize,
LimitKind::KVUpdate,
LimitKind::ComputeGas,
LimitKind::StateGrowth,
] {
assert_eq!(
LimitKind::from_u8(kind.as_u8()),
Some(kind),
"round-trip failed for {kind:?}"
);
}
assert_eq!(LimitKind::from_u8(4), None);
}
}