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