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,
}