1use alloy_primitives::{Address, Bytes, U256};
2use alloy_rlp::{RlpDecodable, RlpEncodable};
3
4use crate::FrameAddress;
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
8#[repr(u8)]
9#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
10#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
11#[cfg_attr(feature = "borsh", borsh(use_discriminant = true))]
12pub enum FrameMode {
13 #[default]
15 Default = 0,
16 Verify = 1,
18 Sender = 2,
20}
21
22impl FrameMode {
23 pub const fn is_default(self) -> bool {
25 matches!(self, Self::Default)
26 }
27
28 pub const fn is_verify(self) -> bool {
30 matches!(self, Self::Verify)
31 }
32
33 pub const fn is_sender(self) -> bool {
35 matches!(self, Self::Sender)
36 }
37
38 pub const fn try_from_u8(value: u8) -> Option<Self> {
40 match value {
41 0 => Some(Self::Default),
42 1 => Some(Self::Verify),
43 2 => Some(Self::Sender),
44 _ => None,
45 }
46 }
47}
48
49impl_u8_discriminant!(FrameMode, InvalidMode, "invalid EIP-8141 frame mode");
50
51#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
53#[repr(u8)]
54#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
55#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
56#[cfg_attr(feature = "borsh", borsh(use_discriminant = true))]
57pub enum ApprovalScope {
58 #[default]
60 None = 0x00,
61 Payment = 0x01,
63 Execution = 0x02,
65 ExecutionAndPayment = 0x03,
67}
68
69impl ApprovalScope {
70 pub const fn is_none(self) -> bool {
72 matches!(self, Self::None)
73 }
74
75 pub const fn is_payment(self) -> bool {
77 matches!(self, Self::Payment)
78 }
79
80 pub const fn is_execution(self) -> bool {
82 matches!(self, Self::Execution)
83 }
84
85 pub const fn is_execution_and_payment(self) -> bool {
87 matches!(self, Self::ExecutionAndPayment)
88 }
89
90 pub const fn allows_execution(self) -> bool {
93 matches!(self, Self::Execution | Self::ExecutionAndPayment)
94 }
95
96 pub const fn allows_payment(self) -> bool {
99 matches!(self, Self::Payment | Self::ExecutionAndPayment)
100 }
101
102 pub const fn try_from_u8(value: u8) -> Option<Self> {
104 match value {
105 0x00 => Some(Self::None),
106 0x01 => Some(Self::Payment),
107 0x02 => Some(Self::Execution),
108 0x03 => Some(Self::ExecutionAndPayment),
109 _ => None,
110 }
111 }
112}
113
114impl_u8_discriminant!(ApprovalScope, InvalidScope);
115
116#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable)]
118#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
119#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
120#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
121#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
122pub struct FrameLimits {
123 #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::quantity"))]
125 pub execution: u64,
126 #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::quantity"))]
128 pub state: u64,
129}
130
131#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable)]
133#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
134#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
135#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
136#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
137pub struct Frame {
138 pub mode: FrameMode,
140 #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::quantity_u8"))]
142 pub flags: u8,
143 pub target: FrameAddress,
145 pub limits: FrameLimits,
147 pub value: U256,
149 pub data: Bytes,
151}
152
153impl Frame {
154 pub const fn new(
156 mode: FrameMode,
157 flags: u8,
158 target: FrameAddress,
159 limits: FrameLimits,
160 value: U256,
161 data: Bytes,
162 ) -> Self {
163 Self { mode, flags, target, limits, value, data }
164 }
165
166 pub const fn target_address(&self) -> Option<Address> {
168 self.target.address()
169 }
170
171 pub const fn resolved_target(&self, sender: Address) -> Address {
173 self.target.resolve(sender)
174 }
175
176 pub const fn allowed_scope(&self) -> ApprovalScope {
178 match self.flags & crate::APPROVE_SCOPE_MASK {
179 0 => ApprovalScope::None,
180 1 => ApprovalScope::Payment,
181 2 => ApprovalScope::Execution,
182 _ => ApprovalScope::ExecutionAndPayment,
183 }
184 }
185
186 pub const fn is_atomic_batch(&self) -> bool {
188 self.flags & crate::ATOMIC_BATCH_FLAG != 0
189 }
190
191 pub const fn has_reserved_flags(&self) -> bool {
193 self.flags & !crate::FRAME_FLAGS_MASK != 0
194 }
195
196 pub fn is_expiry_verifier(&self) -> bool {
201 self.mode == FrameMode::Verify && self.target_address() == Some(crate::EXPIRY_VERIFIER)
202 }
203
204 pub fn has_valid_expiry_verifier_fields(&self) -> bool {
206 self.flags == 0
207 && self.limits.state == 0
208 && self.value.is_zero()
209 && self.data.len() == crate::EXPIRY_DATA_LENGTH
210 }
211}
212
213#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable)]
215#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
216#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
217#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
218#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
219pub struct TransactionFees {
220 pub max_priority_fee_per_gas: U256,
222 pub max_fee_per_gas: U256,
224 pub max_fee_per_blob_gas: U256,
226}
227
228#[cfg(test)]
229mod tests {
230 use super::{Frame, FrameMode};
231 use alloy_primitives::{Bytes, U256};
232
233 #[test]
234 fn approval_permissions_from_frame_flags() {
235 for (scope, execution, payment) in
236 [(0, false, false), (1, false, true), (2, true, false), (3, true, true)]
237 {
238 for atomic in [0, crate::ATOMIC_BATCH_FLAG] {
239 let frame = Frame { flags: scope | atomic, ..Default::default() };
240 assert_eq!(frame.allowed_scope().allows_execution(), execution);
241 assert_eq!(frame.allowed_scope().allows_payment(), payment);
242 }
243 }
244 }
245
246 fn expiry_frame() -> Frame {
247 Frame {
248 mode: FrameMode::Verify,
249 target: crate::EXPIRY_VERIFIER.into(),
250 data: Bytes::from(vec![0; crate::EXPIRY_DATA_LENGTH]),
251 ..Default::default()
252 }
253 }
254
255 #[test]
256 fn expiry_verifier_classification_is_separate_from_field_validation() {
257 let mut frame = expiry_frame();
258 assert!(frame.is_expiry_verifier());
259 assert!(frame.has_valid_expiry_verifier_fields());
260
261 frame.flags = 1;
262 frame.value = U256::from(1);
263 frame.data = Bytes::new();
264
265 assert!(frame.is_expiry_verifier());
266 assert!(!frame.has_valid_expiry_verifier_fields());
267 }
268}