Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions bedrock/src/primitives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,7 @@ use std::str::FromStr;
// Re-export HTTP client types for external use
pub use http_client::{AuthenticatedHttpClient, HttpError, HttpMethod};

/// The prefix for Bedrock-generated transactions.
pub static BEDROCK_NONCE_PREFIX_CONST: &[u8; 5] = b"bdrck";

/// The prefix for PBHTX-generated transactions.
#[allow(dead_code)]
pub static PBH_NONCE_PREFIX_CONST: &[u8; 5] = b"pbhtx";

// Serde helper functions for skip_serializing_if
// ---- Serde helper functions for `skip_serializing_if` ----

/// Helper function to check if an `Address` is zero for serde `skip_serializing_if`
#[must_use]
Expand Down Expand Up @@ -59,9 +52,6 @@ pub mod http_client;
#[cfg(feature = "tooling_tests")]
pub mod tooling_tests;

/// Contract interfaces and data structures for ERC-4337 account abstraction
pub mod contracts;

/// Supported blockchain networks for Bedrock operations
#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)]
#[repr(u32)]
Expand Down
17 changes: 9 additions & 8 deletions bedrock/src/smart_account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ pub use transaction_4337::Is4337Encodable;
#[cfg(any(test, doc))]
use crate::primitives::Network;
use crate::{
bedrock_export, debug, error, primitives::HexEncodedData,
transaction::foreign::UnparsedUserOperation,
bedrock_export, debug, error,
primitives::HexEncodedData,
transaction::{
foreign::UnparsedUserOperation, EncodedSafeOpStruct, UserOperation,
GNOSIS_SAFE_4337_MODULE,
},
};

/// Enables signing of messages and EIP-712 typed data for Safe Smart Accounts.
Expand All @@ -32,13 +36,10 @@ mod transaction;
/// Reference: <https://docs.uniswap.org/contracts/permit2/overview>
mod permit2;

pub use crate::primitives::contracts::{
EncodedSafeOpStruct, ISafe4337Module, UserOperation, ENTRYPOINT_4337,
GNOSIS_SAFE_4337_MODULE,
pub use nonce::{
InstructionFlag, NonceKeyV1, TransactionTypeId, BEDROCK_NONCE_PREFIX_CONST,
};

pub use nonce::{InstructionFlag, NonceKeyV1, TransactionTypeId};

// Import the generated types from permit2 module
pub use permit2::{
UnparsedPermitTransferFrom, UnparsedTokenPermissions, PERMIT2_ADDRESS,
Expand Down Expand Up @@ -234,7 +235,7 @@ impl SafeSmartAccount {
&user_op,
valid_after,
valid_until,
)?;
);

let signature = self.sign_digest(
encoded_safe_op_struct.into_transaction_hash(),
Expand Down
9 changes: 8 additions & 1 deletion bedrock/src/smart_account/nonce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,21 @@

use ruint::aliases::U256;

use crate::primitives::BEDROCK_NONCE_PREFIX_CONST;
/// The prefix for Bedrock-generated transactions.
pub static BEDROCK_NONCE_PREFIX_CONST: &[u8; 5] = b"bdrck";

/// The prefix for PBHTX-generated transactions.
#[allow(dead_code)]
pub static PBH_NONCE_PREFIX_CONST: &[u8; 5] = b"pbhtx";

/// Stable, never-reordered identifiers for transaction classes.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TransactionTypeId {
/// ERC-20 transfer
Transfer = 1,
/// Swap Safe Owner
SwapOwner = 188,
}

impl TransactionTypeId {
Expand Down
55 changes: 34 additions & 21 deletions bedrock/src/smart_account/transaction_4337.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
//! A transaction can be initialized through a `UserOperation` struct.
//!

use crate::primitives::contracts::{EncodedSafeOpStruct, UserOperation};
use crate::primitives::{Network, PrimitiveError};
use crate::smart_account::SafeSmartAccountSigner;
use crate::transaction::rpc::{RpcError, RpcProviderName};

use alloy::primitives::{aliases::U48, Address, Bytes, FixedBytes};
use crate::smart_account::{SafeOperation, SafeSmartAccountSigner};
use crate::transaction::{
EncodedSafeOpStruct, ISafe4337Module, RpcError, RpcProviderName, UserOperation,
ENTRYPOINT_4337, GNOSIS_SAFE_4337_MODULE,
};

use alloy::primitives::{aliases::U48, Address, Bytes, FixedBytes, U256};
use alloy::sol_types::SolCall;
use chrono::{Duration, Utc};

use crate::primitives::contracts::{ENTRYPOINT_4337, GNOSIS_SAFE_4337_MODULE};

/// The default validity duration for 4337 `UserOperation` signatures.
///
/// Operations are valid for this duration from the time they are signed.
Expand All @@ -25,11 +26,29 @@ pub trait Is4337Encodable {
/// constructing a preflight `UserOperation`.
type MetadataArg;

/// Returns the target address to which the inner transaction will be executed against.
/// For example, for a token transfer, the transfer operation is executed against the token contract address.
fn target_address(&self) -> Address;

/// Returns the call data for the transaction.
fn call_data(&self) -> Bytes;

/// Converts the object into a `callData` for the `executeUserOp` method. This is the inner-most `calldata`.
///
/// This is a sensible default implementation that should work for most use cases.
///
/// # Errors
/// - Will throw a parsing error if any of the provided attributes are invalid.
fn as_execute_user_op_call_data(&self) -> Bytes;
fn as_execute_user_op_call_data(&self) -> Bytes {
ISafe4337Module::executeUserOpCall {
to: self.target_address(),
value: U256::ZERO,
data: self.call_data(),
operation: SafeOperation::Call as u8,
}
.abi_encode()
.into()
}

/// Converts the object into a preflight `UserOperation` for use with the `Safe4337Module`.
///
Expand Down Expand Up @@ -93,7 +112,7 @@ pub trait Is4337Encodable {
.await?;

// 3. Merge paymaster data
user_operation = user_operation.with_paymaster_data(sponsor_response)?;
user_operation = user_operation.with_paymaster_data(sponsor_response);

// 4. Compute validity timestamps
// validAfter = 0 (immediately valid)
Expand All @@ -114,7 +133,7 @@ pub trait Is4337Encodable {
&user_operation,
valid_after_u48,
valid_until_u48,
)?;
);

let signature = safe_account.sign_digest(
encoded_safe_op.into_transaction_hash(),
Expand Down Expand Up @@ -147,8 +166,8 @@ mod tests {

use super::*;
use crate::{
smart_account::SafeSmartAccount,
transaction::{foreign::UnparsedUserOperation, SponsorUserOperationResponse},
smart_account::SafeSmartAccount, transaction::foreign::UnparsedUserOperation,
transaction::rpc::SponsorUserOperationResponse,
};

#[test]
Expand Down Expand Up @@ -178,8 +197,7 @@ mod tests {
&user_op,
valid_after,
valid_until,
)
.unwrap();
);
let hash = encoded_safe_op.into_transaction_hash();

let smart_account = SafeSmartAccount::random();
Expand Down Expand Up @@ -265,10 +283,8 @@ mod tests {
max_fee_per_gas: U128::from(900),
};

let result = user_op.with_paymaster_data(sponsor_response);
assert!(result.is_ok());
let updated_user_op = user_op.with_paymaster_data(sponsor_response);

let updated_user_op = result.unwrap();
assert_eq!(
updated_user_op.paymaster,
address!("0x2222222222222222222222222222222222222222")
Expand Down Expand Up @@ -311,10 +327,7 @@ mod tests {
max_fee_per_gas: U128::from(900),
};

let result = user_op.with_paymaster_data(sponsor_response);
assert!(result.is_ok());

let updated_user_op = result.unwrap();
let updated_user_op = user_op.with_paymaster_data(sponsor_response);

// Paymaster fields should always be updated
assert_eq!(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use crate::primitives::{HttpError, PrimitiveError};
//! This module introduces the contract interface for:
//! - the `EntryPoint` contract, including support for ERC-4337 in the Safe Smart Account
//! - the `PBHEntryPoint` contract, which is to execute Priority Blockspace for Humans transactions

use crate::primitives::PrimitiveError;
use crate::transaction::rpc::SponsorUserOperationResponse;
use alloy::hex::FromHex;
use alloy::primitives::{aliases::U48, keccak256, Address, Bytes, FixedBytes};
Expand Down Expand Up @@ -53,6 +57,67 @@ fn serialize_u256_as_hex<S: serde::Serializer>(
}

sol! {
/// `EntryPoint` contract (0.7.0)
/// Reference: <https://github.com/eth-infinitism/account-abstraction/blob/v0.7.0/contracts/core/EntryPoint.sol>
interface IEntryPoint {
#[derive(Default, serde::Serialize, serde::Deserialize, Debug)]
#[sol(rename_all = "camelCase")]
struct PackedUserOperation {
address sender;
uint256 nonce;
bytes init_code;
bytes call_data;
bytes32 account_gas_limits;
uint256 pre_verification_gas;
bytes32 gas_fees;
bytes paymaster_and_data;
bytes signature;
}

#[derive(Default)]
struct UserOpsPerAggregator {
PackedUserOperation[] userOps;
address aggregator;
bytes signature;
}
}

/// `Multicall3` contract. Aggregates results from multiple calls in a single transaction.
///
/// Reference: <https://github.com/worldcoin/world-chain/blob/646bb294dac87bd993be9a218cc357ab1b4a8f6d/contracts/src/interfaces/IMulticall3.sol#L4C1-L4C10>
/// Reference: <https://github.com/mds1/multicall3/blob/main/src/Multicall3.sol#L13>
interface IMulticall3 {
#[derive(Default)]
struct Call3 {
address target;
bool allowFailure;
bytes callData;
}
}

// FIXME: Currently PBHEntryPoint is not in use. Depending on how it ends up being used, some of these functions might not be needed (e.g. `PackedUserOperation`).
/// `PBHEntryPoint` contract. An entry point contract that supports Priority Blockspace for Humans (PBH) transactions.
///
/// Reference: <https://github.com/worldcoin/world-chain/blob/646bb294dac87bd993be9a218cc357ab1b4a8f6d/contracts/src/interfaces/IPBHEntryPoint.sol>
interface IPBHEntryPoint {
#[derive(Default)]
struct PBHPayload {
uint256 root;
uint256 pbhExternalNullifier;
uint256 nullifierHash;
uint256[8] proof;
}

function handleAggregatedOps(
IEntryPoint.UserOpsPerAggregator[] calldata,
address payable
) external;

function pbhMulticall(
IMulticall3.Call3[] calls,
PBHPayload payload,
) external;
}

/// Interface for the `Safe4337Module` contract.
///
Expand Down Expand Up @@ -223,14 +288,15 @@ impl UserOperation {
out.into()
}

/// Merges paymaster data from sponsorship response into the `UserOperation`
/// Merges paymaster data from sponsorship response into the existing `UserOperation`
///
/// # Errors
/// Returns an error if any U128 to u128 conversion fails
/// Returns an error if any parameter conversion fails
#[must_use]
pub fn with_paymaster_data(
mut self,
sponsor_response: SponsorUserOperationResponse,
) -> Result<Self, HttpError> {
) -> Self {
self.paymaster = sponsor_response.paymaster;
self.paymaster_data = sponsor_response.paymaster_data;
self.paymaster_verification_gas_limit = sponsor_response
Expand Down Expand Up @@ -267,7 +333,7 @@ impl UserOperation {
.unwrap_or(0);
}

Ok(self)
self
}
}

Expand All @@ -282,8 +348,8 @@ impl EncodedSafeOpStruct {
user_op: &UserOperation,
valid_after: U48,
valid_until: U48,
) -> Result<Self, PrimitiveError> {
Ok(Self {
) -> Self {
Self {
type_hash: *SAFE_OP_TYPEHASH,
safe: user_op.sender,
nonce: user_op.nonce,
Expand All @@ -298,7 +364,7 @@ impl EncodedSafeOpStruct {
valid_after,
valid_until,
entry_point: *ENTRYPOINT_4337,
})
}
}

/// computes the hash of the userOp
Expand All @@ -307,56 +373,3 @@ impl EncodedSafeOpStruct {
keccak256(self.abi_encode())
}
}

sol! {
contract IMulticall3 {
#[derive(Default)]
struct Call3 {
address target;
bool allowFailure;
bytes callData;
}
}

contract IEntryPoint {
#[derive(Default, serde::Serialize, serde::Deserialize, Debug)]
struct PackedUserOperation {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
bytes32 accountGasLimits;
uint256 preVerificationGas;
bytes32 gasFees;
bytes paymasterAndData;
bytes signature;
}

#[derive(Default)]
struct UserOpsPerAggregator {
PackedUserOperation[] userOps;
address aggregator;
bytes signature;
}
}

contract IPBHEntryPoint {
#[derive(Default)]
struct PBHPayload {
uint256 root;
uint256 pbhExternalNullifier;
uint256 nullifierHash;
uint256[8] proof;
}

function handleAggregatedOps(
IEntryPoint.UserOpsPerAggregator[] calldata,
address payable
) external;

function pbhMulticall(
IMulticall3.Call3[] calls,
PBHPayload payload,
) external;
}
}
Loading