1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
use crate::{Log, TransactionReceipt};
use alloc::vec::Vec;
use alloy_consensus::conditional::BlockConditionalAttributes;
use alloy_primitives::{
    map::{AddressHashMap, HashMap},
    Address, BlockNumber, Bytes, B256, U256,
};

/// Alias for backwards compat
#[deprecated(
    since = "0.8.4",
    note = "Please use `TransactionConditional` instead of `ConditionalOptions`."
)]
pub type ConditionalOptions = TransactionConditional;

/// Options for conditional raw transaction submissions.
///
/// TransactionConditional represents the preconditions that determine the inclusion of the
/// transaction, enforced out-of-protocol by the sequencer.
///
/// See also <https://github.com/ethereum-optimism/op-geth/blob/928070c7fc097362ed2d40a4f72889ba91544931/core/types/transaction_conditional.go#L74-L76>.
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct TransactionConditional {
    /// A map of account addresses to their expected storage states.
    /// Each account can have a specified storage root or explicit slot-value pairs.
    #[cfg_attr(feature = "serde", serde(default))]
    pub known_accounts: AddressHashMap<AccountStorage>,
    /// The minimal block number at which the transaction can be included.
    /// `None` indicates no minimum block number constraint.
    #[cfg_attr(
        feature = "serde",
        serde(
            default,
            with = "alloy_serde::quantity::opt",
            skip_serializing_if = "Option::is_none"
        )
    )]
    pub block_number_min: Option<BlockNumber>,
    /// The maximal block number at which the transaction can be included.
    /// `None` indicates no maximum block number constraint.
    #[cfg_attr(
        feature = "serde",
        serde(
            default,
            with = "alloy_serde::quantity::opt",
            skip_serializing_if = "Option::is_none"
        )
    )]
    pub block_number_max: Option<BlockNumber>,
    /// The minimal timestamp at which the transaction can be included.
    /// `None` indicates no minimum timestamp constraint.
    #[cfg_attr(
        feature = "serde",
        serde(
            default,
            with = "alloy_serde::quantity::opt",
            skip_serializing_if = "Option::is_none"
        )
    )]
    pub timestamp_min: Option<u64>,
    /// The maximal timestamp at which the transaction can be included.
    /// `None` indicates no maximum timestamp constraint.
    #[cfg_attr(
        feature = "serde",
        serde(
            default,
            with = "alloy_serde::quantity::opt",
            skip_serializing_if = "Option::is_none"
        )
    )]
    pub timestamp_max: Option<u64>,
}

impl TransactionConditional {
    /// Returns `true` if the transaction matches the given block attributes.
    pub const fn matches_block_attributes(&self, block: &BlockConditionalAttributes) -> bool {
        self.matches_block_number(block.number) && self.matches_timestamp(block.timestamp)
    }

    /// Returns `true` if the transaction matches the given block number.
    pub const fn matches_block_number(&self, block_number: BlockNumber) -> bool {
        if let Some(min) = self.block_number_min {
            if block_number < min {
                return false;
            }
        }
        if let Some(max) = self.block_number_max {
            if block_number > max {
                return false;
            }
        }
        true
    }

    /// Returns `true` if the transaction matches the given timestamp.
    pub const fn matches_timestamp(&self, timestamp: u64) -> bool {
        if let Some(min) = self.timestamp_min {
            if timestamp < min {
                return false;
            }
        }
        if let Some(max) = self.timestamp_max {
            if timestamp > max {
                return false;
            }
        }
        true
    }

    /// Computes the aggregate cost of the preconditions; total number of storage lookups required
    pub fn cost(&self) -> u64 {
        let mut cost = 0;
        for account in self.known_accounts.values() {
            // default cost to handle empty accounts
            cost += 1;
            match account {
                AccountStorage::RootHash(_) => {
                    cost += 1;
                }
                AccountStorage::Slots(slots) => {
                    cost += slots.len() as u64;
                }
            }
        }

        if self.block_number_min.is_some() || self.block_number_max.is_some() {
            cost += 1;
        }
        if self.timestamp_min.is_some() || self.timestamp_max.is_some() {
            cost += 1;
        }

        cost
    }
}

/// Represents the expected state of an account for a transaction to be conditionally accepted.
///
/// Allows for a user to express their preference of a known prestate at a particular account. Only
/// one of the storage root or storage slots is allowed to be set. If the storage root is set, then
/// the user prefers their transaction to only be included in a block if the account's storage root
/// matches. If the storage slots are set, then the user prefers their transaction to only be
/// included if the particular storage slot values from state match.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum AccountStorage {
    /// Expected storage root hash of the account.
    RootHash(B256),
    /// Explicit storage slots and their expected values.
    Slots(HashMap<U256, B256>),
}

impl AccountStorage {
    /// Returns `true` if the account storage is a root hash.
    pub const fn is_root(&self) -> bool {
        matches!(self, Self::RootHash(_))
    }

    /// Returns the slot values if the account storage is a slot map.
    pub const fn as_slots(&self) -> Option<&HashMap<U256, B256>> {
        match self {
            Self::Slots(slots) => Some(slots),
            _ => None,
        }
    }
}

/// [`UserOperation`] in the spec: Entry Point V0.6
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct UserOperation {
    /// The address of the smart contract account
    pub sender: Address,
    /// Anti-replay protection; also used as the salt for first-time account creation
    pub nonce: U256,
    /// Code used to deploy the account if not yet on-chain
    pub init_code: Bytes,
    /// Data that's passed to the sender for execution
    pub call_data: Bytes,
    /// Gas limit for execution phase
    pub call_gas_limit: U256,
    /// Gas limit for verification phase
    pub verification_gas_limit: U256,
    /// Gas to compensate the bundler
    pub pre_verification_gas: U256,
    /// Maximum fee per gas
    pub max_fee_per_gas: U256,
    /// Maximum priority fee per gas
    pub max_priority_fee_per_gas: U256,
    /// Paymaster Contract address and any extra data required for verification and execution
    /// (empty for self-sponsored transaction)
    pub paymaster_and_data: Bytes,
    /// Used to validate a UserOperation along with the nonce during verification
    pub signature: Bytes,
}

/// [`PackedUserOperation`] in the spec: Entry Point V0.7
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct PackedUserOperation {
    /// The account making the operation.
    pub sender: Address,
    /// Prevents message replay attacks and serves as a randomizing element for initial user
    /// registration.
    pub nonce: U256,
    /// Deployer contract address: Required exclusively for deploying new accounts that don't yet
    /// exist on the blockchain.
    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
    pub factory: Option<Address>,
    /// Factory data for the account creation process, applicable only when using a deployer
    /// contract.
    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
    pub factory_data: Option<Bytes>,
    /// The call data.
    pub call_data: Bytes,
    /// The gas limit for the call.
    pub call_gas_limit: U256,
    /// The gas limit for the verification.
    pub verification_gas_limit: U256,
    /// Prepaid gas fee: Covers the bundler's costs for initial transaction validation and data
    /// transmission.
    pub pre_verification_gas: U256,
    /// The maximum fee per gas.
    pub max_fee_per_gas: U256,
    /// The maximum priority fee per gas.
    pub max_priority_fee_per_gas: U256,
    /// Paymaster contract address: Needed if a third party is covering transaction costs; left
    /// blank for self-funded accounts.
    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
    pub paymaster: Option<Address>,
    /// The gas limit for the paymaster verification.
    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
    pub paymaster_verification_gas_limit: Option<U256>,
    /// The gas limit for the paymaster post-operation.
    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
    pub paymaster_post_op_gas_limit: Option<U256>,
    /// The paymaster data.
    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
    pub paymaster_data: Option<Bytes>,
    /// The signature of the transaction.
    pub signature: Bytes,
}

/// Send User Operation
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SendUserOperation {
    /// User Operation
    EntryPointV06(UserOperation),
    /// Packed User Operation
    EntryPointV07(PackedUserOperation),
}

/// Response to sending a user operation.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct SendUserOperationResponse {
    /// The hash of the user operation.
    pub user_op_hash: Bytes,
}

/// Represents the receipt of a user operation.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct UserOperationReceipt {
    /// The hash of the user operation.
    pub user_op_hash: Bytes,
    /// The entry point address for the user operation.
    pub entry_point: Address,
    /// The address of the sender of the user operation.
    pub sender: Address,
    /// The nonce of the user operation.
    pub nonce: U256,
    /// The address of the paymaster, if any.
    pub paymaster: Address,
    /// The actual gas cost incurred by the user operation.
    pub actual_gas_cost: U256,
    /// The actual gas used by the user operation.
    pub actual_gas_used: U256,
    /// Indicates whether the user operation was successful.
    pub success: bool,
    /// The reason for failure, if any.
    pub reason: Bytes,
    /// The logs generated by the user operation.
    pub logs: Vec<Log>,
    /// The transaction receipt of the user operation.
    pub receipt: TransactionReceipt,
}

/// Represents the gas estimation for a user operation.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct UserOperationGasEstimation {
    /// The gas limit for the pre-verification.
    pub pre_verification_gas: U256,
    /// The gas limit for the verification.
    pub verification_gas: U256,
    /// The gas limit for the paymaster verification.
    pub paymaster_verification_gas: U256,
    /// The gas limit for the call.
    pub call_gas_limit: U256,
}