use alloy_primitives::{Address, Bytes, U256};
use alloy_rlp::{RlpDecodable, RlpEncodable};
use crate::FrameAddress;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[repr(u8)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
#[cfg_attr(feature = "borsh", borsh(use_discriminant = true))]
pub enum FrameMode {
#[default]
Default = 0,
Verify = 1,
Sender = 2,
}
impl FrameMode {
pub const fn try_from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(Self::Default),
1 => Some(Self::Verify),
2 => Some(Self::Sender),
_ => None,
}
}
}
impl_u8_discriminant!(FrameMode, InvalidMode, "invalid EIP-8141 frame mode");
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[repr(u8)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
#[cfg_attr(feature = "borsh", borsh(use_discriminant = true))]
pub enum ApprovalScope {
#[default]
None = 0x00,
Payment = 0x01,
Execution = 0x02,
ExecutionAndPayment = 0x03,
}
impl ApprovalScope {
pub const fn try_from_u8(value: u8) -> Option<Self> {
match value {
0x00 => Some(Self::None),
0x01 => Some(Self::Payment),
0x02 => Some(Self::Execution),
0x03 => Some(Self::ExecutionAndPayment),
_ => None,
}
}
}
impl_u8_discriminant!(ApprovalScope, InvalidScope);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
pub struct FrameLimits {
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::quantity"))]
pub execution: u64,
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::quantity"))]
pub state: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
pub struct Frame {
pub mode: FrameMode,
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::quantity_u8"))]
pub flags: u8,
pub target: FrameAddress,
pub limits: FrameLimits,
pub value: U256,
pub data: Bytes,
}
impl Frame {
pub const fn new(
mode: FrameMode,
flags: u8,
target: FrameAddress,
limits: FrameLimits,
value: U256,
data: Bytes,
) -> Self {
Self { mode, flags, target, limits, value, data }
}
pub const fn target_address(&self) -> Option<Address> {
self.target.address()
}
pub const fn resolved_target(&self, sender: Address) -> Address {
self.target.resolve(sender)
}
pub const fn allowed_scope(&self) -> ApprovalScope {
match self.flags & crate::APPROVE_SCOPE_MASK {
0 => ApprovalScope::None,
1 => ApprovalScope::Payment,
2 => ApprovalScope::Execution,
_ => ApprovalScope::ExecutionAndPayment,
}
}
pub const fn is_atomic_batch(&self) -> bool {
self.flags & crate::ATOMIC_BATCH_FLAG != 0
}
pub const fn has_reserved_flags(&self) -> bool {
self.flags & !crate::FRAME_FLAGS_MASK != 0
}
pub fn is_expiry_verifier(&self) -> bool {
self.mode == FrameMode::Verify && self.target_address() == Some(crate::EXPIRY_VERIFIER)
}
pub fn has_valid_expiry_verifier_fields(&self) -> bool {
self.flags == 0
&& self.limits.state == 0
&& self.value.is_zero()
&& self.data.len() == crate::EXPIRY_DATA_LENGTH
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
pub struct TransactionFees {
pub max_priority_fee_per_gas: U256,
pub max_fee_per_gas: U256,
pub max_fee_per_blob_gas: U256,
}
#[cfg(test)]
mod tests {
use super::{Frame, FrameMode};
use alloy_primitives::{Bytes, U256};
fn expiry_frame() -> Frame {
Frame {
mode: FrameMode::Verify,
target: crate::EXPIRY_VERIFIER.into(),
data: Bytes::from(vec![0; crate::EXPIRY_DATA_LENGTH]),
..Default::default()
}
}
#[test]
fn expiry_verifier_classification_is_separate_from_field_validation() {
let mut frame = expiry_frame();
assert!(frame.is_expiry_verifier());
assert!(frame.has_valid_expiry_verifier_fields());
frame.flags = 1;
frame.value = U256::from(1);
frame.data = Bytes::new();
assert!(frame.is_expiry_verifier());
assert!(!frame.has_valid_expiry_verifier_fields());
}
}