Skip to content

Commit 38ed079

Browse files
committed
v0.8.0 structs...
1 parent 08a6dd3 commit 38ed079

File tree

1 file changed

+126
-30
lines changed

1 file changed

+126
-30
lines changed

src/wallet.rs

Lines changed: 126 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,20 @@ sol! {
4040
bytes paymasterAndData;
4141
bytes signature;
4242
}
43+
44+
// v0.8 UserOperation struct with packed fields
45+
#[derive(Debug, Default, PartialEq, Eq)]
46+
struct PackedUserOperation {
47+
address sender;
48+
uint256 nonce;
49+
bytes initCode;
50+
bytes callData;
51+
bytes32 accountGasLimits; // packed verificationGasLimit (16 bytes) and callGasLimit (16 bytes)
52+
uint256 preVerificationGas;
53+
bytes32 gasFees; // packed maxPriorityFeePerGas (16 bytes) and maxFeePerGas (16 bytes)
54+
bytes paymasterAndData;
55+
bytes signature;
56+
}
4357
}
4458

4559
// Define contract interfaces
@@ -2305,42 +2319,20 @@ impl UserOperationBuilder {
23052319
self,
23062320
entry_point: EthAddress,
23072321
signer: &S,
2308-
) -> Result<UserOperation, WalletError> {
2309-
// Create the UserOperation struct
2310-
let user_op = UserOperation {
2311-
sender: self.sender,
2312-
nonce: self.nonce,
2313-
initCode: Bytes::from(self.init_code.clone()),
2314-
callData: Bytes::from(self.call_data.clone()),
2315-
callGasLimit: self.call_gas_limit,
2316-
verificationGasLimit: self.verification_gas_limit,
2317-
preVerificationGas: self.pre_verification_gas,
2318-
maxFeePerGas: self.max_fee_per_gas,
2319-
maxPriorityFeePerGas: self.max_priority_fee_per_gas,
2320-
paymasterAndData: Bytes::from(self.paymaster_and_data.clone()),
2321-
signature: Bytes::default(), // Will be filled after signing
2322-
};
2322+
) -> Result<PackedUserOperation, WalletError> {
2323+
// Create the v0.8 PackedUserOperation struct
2324+
let mut packed_op = build_packed_user_operation(&self);
23232325

23242326
// Get the UserOp hash for signing
2325-
let user_op_hash = self.get_user_op_hash(&user_op, entry_point, self.chain_id);
2327+
let user_op_hash = self.get_user_op_hash_v08(&packed_op, entry_point, self.chain_id);
23262328

23272329
// Sign the hash
23282330
let signature = signer.sign_message(&user_op_hash)?;
23292331

2330-
// Create final UserOp with signature
2331-
Ok(UserOperation {
2332-
sender: self.sender,
2333-
nonce: self.nonce,
2334-
initCode: Bytes::from(self.init_code),
2335-
callData: Bytes::from(self.call_data),
2336-
callGasLimit: self.call_gas_limit,
2337-
verificationGasLimit: self.verification_gas_limit,
2338-
preVerificationGas: self.pre_verification_gas,
2339-
maxFeePerGas: self.max_fee_per_gas,
2340-
maxPriorityFeePerGas: self.max_priority_fee_per_gas,
2341-
paymasterAndData: Bytes::from(self.paymaster_and_data),
2342-
signature: Bytes::from(signature),
2343-
})
2332+
// Set the signature
2333+
packed_op.signature = Bytes::from(signature);
2334+
2335+
Ok(packed_op)
23442336
}
23452337

23462338
/// Calculate the UserOp hash according to ERC-4337 spec
@@ -2365,6 +2357,28 @@ impl UserOperationBuilder {
23652357
hasher.finalize().to_vec()
23662358
}
23672359

2360+
/// Calculate the UserOp hash for v0.8 according to ERC-4337 spec
2361+
fn get_user_op_hash_v08(
2362+
&self,
2363+
user_op: &PackedUserOperation,
2364+
entry_point: EthAddress,
2365+
chain_id: u64,
2366+
) -> Vec<u8> {
2367+
use sha3::{Digest, Keccak256};
2368+
2369+
// Pack the UserOp for hashing (without signature)
2370+
let packed = self.pack_user_op_for_hash_v08(user_op);
2371+
let user_op_hash = Keccak256::digest(&packed);
2372+
2373+
// Create the final hash with entry point and chain ID
2374+
let mut hasher = Keccak256::new();
2375+
hasher.update(user_op_hash);
2376+
hasher.update(entry_point.as_slice());
2377+
hasher.update(&chain_id.to_be_bytes());
2378+
2379+
hasher.finalize().to_vec()
2380+
}
2381+
23682382
/// Pack UserOp fields for hashing (ERC-4337 specification)
23692383
fn pack_user_op_for_hash(&self, user_op: &UserOperation) -> Vec<u8> {
23702384
use sha3::{Digest, Keccak256};
@@ -2405,6 +2419,46 @@ impl UserOperationBuilder {
24052419

24062420
packed
24072421
}
2422+
2423+
/// Pack UserOp fields for hashing v0.8 (ERC-4337 specification)
2424+
fn pack_user_op_for_hash_v08(&self, user_op: &PackedUserOperation) -> Vec<u8> {
2425+
use sha3::{Digest, Keccak256};
2426+
2427+
let mut packed = Vec::new();
2428+
2429+
// Pack all fields except signature
2430+
packed.extend_from_slice(user_op.sender.as_slice());
2431+
packed.extend_from_slice(&user_op.nonce.to_be_bytes::<32>());
2432+
2433+
// For initCode and paymasterAndData, we hash them if non-empty
2434+
if !user_op.initCode.is_empty() {
2435+
let hash = Keccak256::digest(&user_op.initCode);
2436+
packed.extend_from_slice(&hash);
2437+
} else {
2438+
packed.extend_from_slice(&[0u8; 32]);
2439+
}
2440+
2441+
if !user_op.callData.is_empty() {
2442+
let hash = Keccak256::digest(&user_op.callData);
2443+
packed.extend_from_slice(&hash);
2444+
} else {
2445+
packed.extend_from_slice(&[0u8; 32]);
2446+
}
2447+
2448+
// Pack the packed fields directly (accountGasLimits and gasFees)
2449+
packed.extend_from_slice(&user_op.accountGasLimits.0);
2450+
packed.extend_from_slice(&user_op.preVerificationGas.to_be_bytes::<32>());
2451+
packed.extend_from_slice(&user_op.gasFees.0);
2452+
2453+
if !user_op.paymasterAndData.is_empty() {
2454+
let hash = Keccak256::digest(&user_op.paymasterAndData);
2455+
packed.extend_from_slice(&hash);
2456+
} else {
2457+
packed.extend_from_slice(&[0u8; 32]);
2458+
}
2459+
2460+
packed
2461+
}
24082462
}
24092463

24102464
/// Helper to create calldata for TBA execute through UserOp
@@ -2424,6 +2478,48 @@ pub fn create_tba_userop_calldata(
24242478
call.abi_encode()
24252479
}
24262480

2481+
/// Pack two 16-byte values into a single bytes32 for v0.8 UserOperation
2482+
fn pack_gas_values(high: U256, low: U256) -> B256 {
2483+
let mut packed = [0u8; 32];
2484+
// Take the lower 16 bytes of each value
2485+
let high_bytes = high.to_be_bytes::<32>();
2486+
let low_bytes = low.to_be_bytes::<32>();
2487+
2488+
// Pack high value in first 16 bytes
2489+
packed[0..16].copy_from_slice(&high_bytes[16..32]);
2490+
// Pack low value in last 16 bytes
2491+
packed[16..32].copy_from_slice(&low_bytes[16..32]);
2492+
2493+
B256::from(packed)
2494+
}
2495+
2496+
/// Build a v0.8 PackedUserOperation from the builder values
2497+
pub fn build_packed_user_operation(builder: &UserOperationBuilder) -> PackedUserOperation {
2498+
// Pack gas limits: verificationGasLimit (high) and callGasLimit (low)
2499+
let account_gas_limits = pack_gas_values(
2500+
builder.verification_gas_limit,
2501+
builder.call_gas_limit
2502+
);
2503+
2504+
// Pack gas fees: maxPriorityFeePerGas (high) and maxFeePerGas (low)
2505+
let gas_fees = pack_gas_values(
2506+
builder.max_priority_fee_per_gas,
2507+
builder.max_fee_per_gas
2508+
);
2509+
2510+
PackedUserOperation {
2511+
sender: builder.sender,
2512+
nonce: builder.nonce,
2513+
initCode: Bytes::from(builder.init_code.clone()),
2514+
callData: Bytes::from(builder.call_data.clone()),
2515+
accountGasLimits: account_gas_limits,
2516+
preVerificationGas: builder.pre_verification_gas,
2517+
gasFees: gas_fees,
2518+
paymasterAndData: Bytes::from(builder.paymaster_and_data.clone()),
2519+
signature: Bytes::default(),
2520+
}
2521+
}
2522+
24272523
/// Get the ERC-4337 EntryPoint address for a given chain
24282524
pub fn get_entry_point_address(chain_id: u64) -> Option<EthAddress> {
24292525
match chain_id {

0 commit comments

Comments
 (0)