Skip to main content

alloy_rpc_types_eth/
erc4337.rs

1use crate::{Log, TransactionReceipt};
2use alloc::vec::Vec;
3use alloy_consensus::conditional::BlockConditionalAttributes;
4use alloy_primitives::{
5    map::{AddressHashMap, HashMap},
6    Address, BlockNumber, Bytes, B256, U256,
7};
8
9/// Options for conditional raw transaction submissions.
10///
11/// TransactionConditional represents the preconditions that determine the inclusion of the
12/// transaction, enforced out-of-protocol by the sequencer.
13///
14/// See also <https://github.com/ethereum-optimism/op-geth/blob/928070c7fc097362ed2d40a4f72889ba91544931/core/types/transaction_conditional.go#L74-L76>.
15#[derive(Debug, Clone, Default, Eq, PartialEq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
18pub struct TransactionConditional {
19    /// A map of account addresses to their expected storage states.
20    /// Each account can have a specified storage root or explicit slot-value pairs.
21    #[cfg_attr(feature = "serde", serde(default))]
22    pub known_accounts: AddressHashMap<AccountStorage>,
23    /// The minimal block number at which the transaction can be included.
24    /// `None` indicates no minimum block number constraint.
25    #[cfg_attr(
26        feature = "serde",
27        serde(
28            default,
29            with = "alloy_serde::quantity::opt",
30            skip_serializing_if = "Option::is_none"
31        )
32    )]
33    pub block_number_min: Option<BlockNumber>,
34    /// The maximal block number at which the transaction can be included.
35    /// `None` indicates no maximum block number constraint.
36    #[cfg_attr(
37        feature = "serde",
38        serde(
39            default,
40            with = "alloy_serde::quantity::opt",
41            skip_serializing_if = "Option::is_none"
42        )
43    )]
44    pub block_number_max: Option<BlockNumber>,
45    /// The minimal timestamp at which the transaction can be included.
46    /// `None` indicates no minimum timestamp constraint.
47    #[cfg_attr(
48        feature = "serde",
49        serde(
50            default,
51            with = "alloy_serde::quantity::opt",
52            skip_serializing_if = "Option::is_none"
53        )
54    )]
55    pub timestamp_min: Option<u64>,
56    /// The maximal timestamp at which the transaction can be included.
57    /// `None` indicates no maximum timestamp constraint.
58    #[cfg_attr(
59        feature = "serde",
60        serde(
61            default,
62            with = "alloy_serde::quantity::opt",
63            skip_serializing_if = "Option::is_none"
64        )
65    )]
66    pub timestamp_max: Option<u64>,
67}
68
69impl TransactionConditional {
70    /// Returns true if any configured block parameter (`timestamp_max`, `block_number_max`) are
71    /// exceeded by the given block parameter.
72    ///
73    /// E.g. the block parameter's timestamp is higher than the configured `block_number_max`
74    pub const fn has_exceeded_block_attributes(&self, block: &BlockConditionalAttributes) -> bool {
75        self.has_exceeded_block_number(block.number) || self.has_exceeded_timestamp(block.timestamp)
76    }
77
78    /// Returns true if the configured max block number is strictly lower than the given
79    /// `block_number`
80    pub const fn has_exceeded_block_number(&self, block_number: BlockNumber) -> bool {
81        let Some(max_num) = self.block_number_max else { return false };
82        block_number > max_num
83    }
84
85    /// Returns true if the configured max timestamp is strictly lower than the given `timestamp`
86    pub const fn has_exceeded_timestamp(&self, timestamp: u64) -> bool {
87        let Some(max_timestamp) = self.timestamp_max else { return false };
88        timestamp > max_timestamp
89    }
90
91    /// Returns `true` if the transaction matches the given block attributes.
92    pub const fn matches_block_attributes(&self, block: &BlockConditionalAttributes) -> bool {
93        self.matches_block_number(block.number) && self.matches_timestamp(block.timestamp)
94    }
95
96    /// Returns `true` if the transaction matches the given block number.
97    pub const fn matches_block_number(&self, block_number: BlockNumber) -> bool {
98        if let Some(min) = self.block_number_min {
99            if block_number < min {
100                return false;
101            }
102        }
103        if let Some(max) = self.block_number_max {
104            if block_number > max {
105                return false;
106            }
107        }
108        true
109    }
110
111    /// Returns `true` if the transaction matches the given timestamp.
112    pub const fn matches_timestamp(&self, timestamp: u64) -> bool {
113        if let Some(min) = self.timestamp_min {
114            if timestamp < min {
115                return false;
116            }
117        }
118        if let Some(max) = self.timestamp_max {
119            if timestamp > max {
120                return false;
121            }
122        }
123        true
124    }
125
126    /// Computes the aggregate cost of the preconditions; total number of storage lookups required
127    pub fn cost(&self) -> u64 {
128        let mut cost = 0;
129        for account in self.known_accounts.values() {
130            // default cost to handle empty accounts
131            cost += 1;
132            match account {
133                AccountStorage::RootHash(_) => {
134                    cost += 1;
135                }
136                AccountStorage::Slots(slots) => {
137                    cost += slots.len() as u64;
138                }
139            }
140        }
141
142        if self.block_number_min.is_some() || self.block_number_max.is_some() {
143            cost += 1;
144        }
145        if self.timestamp_min.is_some() || self.timestamp_max.is_some() {
146            cost += 1;
147        }
148
149        cost
150    }
151}
152
153/// Represents the expected state of an account for a transaction to be conditionally accepted.
154///
155/// Allows for a user to express their preference of a known prestate at a particular account. Only
156/// one of the storage root or storage slots is allowed to be set. If the storage root is set, then
157/// the user prefers their transaction to only be included in a block if the account's storage root
158/// matches. If the storage slots are set, then the user prefers their transaction to only be
159/// included if the particular storage slot values from state match.
160#[derive(Debug, Clone, Eq, PartialEq)]
161#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
162#[cfg_attr(feature = "serde", serde(untagged))]
163pub enum AccountStorage {
164    /// Expected storage root hash of the account.
165    RootHash(B256),
166    /// Explicit storage slots and their expected values.
167    Slots(HashMap<U256, B256>),
168}
169
170impl AccountStorage {
171    /// Returns `true` if the account storage is a root hash.
172    pub const fn is_root(&self) -> bool {
173        matches!(self, Self::RootHash(_))
174    }
175
176    /// Returns the slot values if the account storage is a slot map.
177    pub const fn as_slots(&self) -> Option<&HashMap<U256, B256>> {
178        match self {
179            Self::Slots(slots) => Some(slots),
180            _ => None,
181        }
182    }
183}
184
185/// [`UserOperation`] in the spec: Entry Point V0.6
186#[derive(Debug, Clone, PartialEq, Eq)]
187#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
188#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
189pub struct UserOperation {
190    /// The address of the smart contract account
191    pub sender: Address,
192    /// Anti-replay protection; also used as the salt for first-time account creation
193    pub nonce: U256,
194    /// Code used to deploy the account if not yet on-chain
195    pub init_code: Bytes,
196    /// Data that's passed to the sender for execution
197    pub call_data: Bytes,
198    /// Gas limit for execution phase
199    pub call_gas_limit: U256,
200    /// Gas limit for verification phase
201    pub verification_gas_limit: U256,
202    /// Gas to compensate the bundler
203    pub pre_verification_gas: U256,
204    /// Maximum fee per gas
205    pub max_fee_per_gas: U256,
206    /// Maximum priority fee per gas
207    pub max_priority_fee_per_gas: U256,
208    /// Paymaster Contract address and any extra data required for verification and execution
209    /// (empty for self-sponsored transaction)
210    pub paymaster_and_data: Bytes,
211    /// Used to validate a UserOperation along with the nonce during verification
212    pub signature: Bytes,
213}
214
215/// [`PackedUserOperation`] in the spec: Entry Point V0.7
216#[derive(Debug, Clone, PartialEq, Eq)]
217#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
218#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
219pub struct PackedUserOperation {
220    /// The account making the operation.
221    pub sender: Address,
222    /// Prevents message replay attacks and serves as a randomizing element for initial user
223    /// registration.
224    pub nonce: U256,
225    /// Deployer contract address: Required exclusively for deploying new accounts that don't yet
226    /// exist on the blockchain.
227    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
228    pub factory: Option<Address>,
229    /// Factory data for the account creation process, applicable only when using a deployer
230    /// contract.
231    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
232    pub factory_data: Option<Bytes>,
233    /// The call data.
234    pub call_data: Bytes,
235    /// The gas limit for the call.
236    pub call_gas_limit: U256,
237    /// The gas limit for the verification.
238    pub verification_gas_limit: U256,
239    /// Prepaid gas fee: Covers the bundler's costs for initial transaction validation and data
240    /// transmission.
241    pub pre_verification_gas: U256,
242    /// The maximum fee per gas.
243    pub max_fee_per_gas: U256,
244    /// The maximum priority fee per gas.
245    pub max_priority_fee_per_gas: U256,
246    /// Paymaster contract address: Needed if a third party is covering transaction costs; left
247    /// blank for self-funded accounts.
248    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
249    pub paymaster: Option<Address>,
250    /// The gas limit for the paymaster verification.
251    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
252    pub paymaster_verification_gas_limit: Option<U256>,
253    /// The gas limit for the paymaster post-operation.
254    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
255    pub paymaster_post_op_gas_limit: Option<U256>,
256    /// The paymaster data.
257    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
258    pub paymaster_data: Option<Bytes>,
259    /// The signature of the transaction.
260    pub signature: Bytes,
261}
262
263/// Send User Operation
264#[derive(Debug, Clone, PartialEq, Eq)]
265#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
266pub enum SendUserOperation {
267    /// User Operation
268    EntryPointV06(UserOperation),
269    /// Packed User Operation
270    EntryPointV07(PackedUserOperation),
271}
272
273/// Response to sending a user operation.
274#[derive(Debug, Clone, PartialEq, Eq)]
275#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
276#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
277pub struct SendUserOperationResponse {
278    /// The hash of the user operation.
279    pub user_op_hash: Bytes,
280}
281
282/// Represents the receipt of a user operation.
283#[derive(Debug, Clone, PartialEq, Eq)]
284#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
285#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
286pub struct UserOperationReceipt {
287    /// The hash of the user operation.
288    pub user_op_hash: Bytes,
289    /// The entry point address for the user operation.
290    pub entry_point: Address,
291    /// The address of the sender of the user operation.
292    pub sender: Address,
293    /// The nonce of the user operation.
294    pub nonce: U256,
295    /// The address of the paymaster, if any.
296    pub paymaster: Address,
297    /// The actual gas cost incurred by the user operation.
298    pub actual_gas_cost: U256,
299    /// The actual gas used by the user operation.
300    pub actual_gas_used: U256,
301    /// Indicates whether the user operation was successful.
302    pub success: bool,
303    /// The reason for failure, if any.
304    pub reason: Bytes,
305    /// The logs generated by the user operation.
306    pub logs: Vec<Log>,
307    /// The transaction receipt of the user operation.
308    pub receipt: TransactionReceipt,
309}
310
311/// Represents the gas estimation for a user operation.
312#[derive(Debug, Clone, PartialEq, Eq)]
313#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
314#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
315pub struct UserOperationGasEstimation {
316    /// The gas limit for the pre-verification.
317    pub pre_verification_gas: U256,
318    /// The gas limit for the verification.
319    pub verification_gas: U256,
320    /// The gas limit for the paymaster verification.
321    pub paymaster_verification_gas: U256,
322    /// The gas limit for the call.
323    pub call_gas_limit: U256,
324}