Skip to content

Commit

Permalink
Fix 0.7 Compilation Issues In Passkey Example
Browse files Browse the repository at this point in the history
  • Loading branch information
nlordell committed Mar 5, 2024
1 parent ce74cea commit fe46439
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 56 deletions.
28 changes: 18 additions & 10 deletions examples/safe-4337-passkeys/src/components/SafeCard.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { useMemo, useState } from 'react'
import { ethers } from 'ethers'
import { encodeAddModuleLibCall } from '../logic/safe'
import { encodeSafeModuleSetupCall } from '../logic/safe'
import type { SafeInitializer } from '../logic/safe'
import {
ADD_MODULES_LIB_ADDRESS,
SAFE_4337_MODULE_ADDRESS,
SAFE_MODULE_SETUP_ADDRESS,
SAFE_PROXY_FACTORY_ADDRESS,
SAFE_SINGLETON_ADDRESS,
WEBAUTHN_SIGNER_FACTORY_ADDRESS,
WEBAUTHN_VERIFIER_ADDRESS,
} from '../config'
import { PasskeyLocalStorageFormat } from '../logic/passkeys'
import { UnsignedUserOperation, getRequiredPrefund, prepareUserOperationWithInitialisation, signAndSendUserOp } from '../logic/userOp'
import {
UnsignedPackedUserOperation,
getRequiredPrefund,
packGasParameters,
prepareUserOperationWithInitialisation,
signAndSendUserOp,
} from '../logic/userOp'
import { useUserOpGasLimitEstimation } from '../hooks/useUserOpGasEstimation'
import { RequestStatus } from '../utils'
import { PrefundCard } from './OpPrefundCard'
Expand All @@ -29,8 +35,8 @@ function SafeCard({ passkey, provider }: { passkey: PasskeyLocalStorageFormat; p
['uint256', 'uint256', 'address'],
[passkey.pubkeyCoordinates.x, passkey.pubkeyCoordinates.y, WEBAUTHN_VERIFIER_ADDRESS],
),
setupTo: ADD_MODULES_LIB_ADDRESS,
setupData: encodeAddModuleLibCall([SAFE_4337_MODULE_ADDRESS]),
setupTo: SAFE_MODULE_SETUP_ADDRESS,
setupData: encodeSafeModuleSetupCall([SAFE_4337_MODULE_ADDRESS]),
}),
[passkey.pubkeyCoordinates.x, passkey.pubkeyCoordinates.y],
)
Expand Down Expand Up @@ -64,13 +70,15 @@ function SafeCard({ passkey, provider }: { passkey: PasskeyLocalStorageFormat; p
const handleDeploySafeClick = async () => {
if (!gasParametersReady) return

const userOpToSign: UnsignedUserOperation = {
const userOpToSign: UnsignedPackedUserOperation = {
...unsignedUserOperation,
verificationGasLimit: userOpGasLimitEstimation.verificationGasLimit,
...packGasParameters({
verificationGasLimit: userOpGasLimitEstimation.verificationGasLimit,
callGasLimit: userOpGasLimitEstimation.callGasLimit,
maxPriorityFeePerGas: feeData?.maxPriorityFeePerGas,
maxFeePerGas: feeData?.maxFeePerGas,
}),
preVerificationGas: userOpGasLimitEstimation.preVerificationGas,
callGasLimit: userOpGasLimitEstimation.callGasLimit,
maxFeePerGas: '0x' + feeData?.maxFeePerGas.toString(16),
maxPriorityFeePerGas: '0x' + feeData?.maxPriorityFeePerGas.toString(16),
}

const bundlerUserOpHash = await signAndSendUserOp(userOpToSign, passkey)
Expand Down
6 changes: 3 additions & 3 deletions examples/safe-4337-passkeys/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ const APP_CHAIN_ID = 80001
*/
const SAFE_SIGNER_LAUNCHPAD_ADDRESS = '0xe208DFA2A62be117142d7054BE59034c39A56dBD'

const SAFE_4337_MODULE_ADDRESS = '0x700ADDDa9D8D6fF3230016f1a0a81EA4d4615804'
const SAFE_4337_MODULE_ADDRESS = '0xfaa6F2eC82BdA7C22220522869E854a3446053A5'

const ADD_MODULES_LIB_ADDRESS = '0x2dd68b007B46fBe91B9A7c3EDa5A7a1063cB5b47'
const SAFE_MODULE_SETUP_ADDRESS = '0x2dd68b007B46fBe91B9A7c3EDa5A7a1063cB5b47'

const WEBAUTHN_SIGNER_FACTORY_ADDRESS = '0xe59e5dDd6C5D98b2b4a9D2094639dD881e305Dc5'

Expand All @@ -24,7 +24,7 @@ const ENTRYPOINT_ADDRESS = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789'
const XANDER_BLAZE_NFT_ADDRESS = '0xBb9ebb7b8Ee75CDBf64e5cE124731A89c2BC4A07'

export {
ADD_MODULES_LIB_ADDRESS,
SAFE_MODULE_SETUP_ADDRESS,
APP_CHAIN_ID,
ENTRYPOINT_ADDRESS,
SAFE_SIGNER_LAUNCHPAD_ADDRESS,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useState, useEffect } from 'react'
import { UnsignedUserOperation, UserOpGasLimitEstimation, estimateUserOpGasLimit } from '../logic/userOp'
import { UnsignedPackedUserOperation, UserOpGasLimitEstimation, estimateUserOpGasLimit } from '../logic/userOp'
import { RequestStatus } from '../utils'

/**
* Custom hook for estimating the gas limit of a user operation.
* @param userOp The unsigned user operation.
* @returns An object containing the user operation gas limit estimation and the request status.
*/
function useUserOpGasLimitEstimation(userOp: UnsignedUserOperation) {
function useUserOpGasLimitEstimation(userOp: UnsignedPackedUserOperation) {
const [userOpGasLimitEstimation, setUserOpGasLimitEstimation] = useState<UserOpGasLimitEstimation | undefined>(undefined)
const [status, setStatus] = useState<RequestStatus>(RequestStatus.NOT_REQUESTED)

Expand Down
18 changes: 9 additions & 9 deletions examples/safe-4337-passkeys/src/logic/safe.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ethers } from 'ethers'
import { abi as SafeSignerLaunchpadAbi } from '@safe-global/safe-erc4337/build/artifacts/contracts/experimental/SafeSignerLaunchpad.sol/SafeSignerLaunchpad.json'
import { abi as WebAuthnSignerFactoryAbi } from '@safe-global/safe-erc4337/build/artifacts/contracts/experimental/WebAuthnSigner.sol/WebAuthnSignerFactory.json'
import { abi as SetupModulesAbi } from '@safe-global/safe-erc4337/build/artifacts/contracts/SafeModuleSetup.sol/SafeModuleSetup.json'
import { abi as SetupModuleSetupAbi } from '@safe-global/safe-erc4337/build/artifacts/contracts/SafeModuleSetup.sol/SafeModuleSetup.json'
import {
abi as WebAuthnSignerAbi,
bytecode as WebAuthSignerBytecode,
Expand All @@ -24,7 +24,7 @@ import {
SAFE_PROXY_FACTORY_ADDRESS,
WEBAUTHN_VERIFIER_ADDRESS,
} from '../config'
import { UserOperation } from './userOp'
import { PackedUserOperation } from './userOp'

// Hardcoded because we cannot easily install @safe-global/safe-contracts because of conflicting ethers.js versions
const SafeProxyBytecode =
Expand Down Expand Up @@ -160,14 +160,14 @@ function getSafeAddress(
}

/**
* Encodes the function call to enable modules in the AddModulesLib contract.
* Encodes the function call to enable modules in the SafeModuleSetup contract.
*
* @param modules - An array of module addresses.
* @returns The encoded function call data.
*/
function encodeAddModuleLibCall(modules: string[]): string {
const addModulesLibInterface = new ethers.Interface(SetupModulesAbi) as unknown as SafeModuleSetup['interface']
return addModulesLibInterface.encodeFunctionData('enableModules', [modules])
function encodeSafeModuleSetupCall(modules: string[]): string {
const safeModuleSetupInterface = new ethers.Interface(SetupModuleSetupAbi) as unknown as SafeModuleSetup['interface']
return safeModuleSetupInterface.encodeFunctionData('enableModules', [modules])
}

/**
Expand Down Expand Up @@ -210,12 +210,12 @@ function getExecuteUserOpData(to: string, value: ethers.BigNumberish, data: stri

/**
* Encodes the user operation data for validating a user operation.
* @param userOp The user operation to be validated.
* @param userOp The packed user operation to be validated.
* @param userOpHash The hash of the user operation.
* @param missingAccountFunds The amount of missing account funds.
* @returns The encoded data for validating the user operation.
*/
function getValidateUserOpData(userOp: UserOperation, userOpHash: string, missingAccountFunds: ethers.BigNumberish): string {
function getValidateUserOpData(userOp: PackedUserOperation, userOpHash: string, missingAccountFunds: ethers.BigNumberish): string {
const safe4337ModuleInterface = new ethers.Interface(Safe4337ModuleAbi) as unknown as Safe4337Module['interface']

const validateUserOpData = safe4337ModuleInterface.encodeFunctionData('validateUserOp', [userOp, userOpHash, missingAccountFunds])
Expand All @@ -238,5 +238,5 @@ export {
getValidateUserOpData,
getInitHash,
getLaunchpadInitializer,
encodeAddModuleLibCall,
encodeSafeModuleSetupCall,
}
132 changes: 100 additions & 32 deletions examples/safe-4337-passkeys/src/logic/userOp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,38 @@ import { encodeSafeMintData } from './erc721'
import { PasskeyLocalStorageFormat } from './passkeys'
import { hexStringToUint8Array } from '../utils'

type PackedUserOperation = {
sender: string
nonce: ethers.BigNumberish
initCode: ethers.BytesLike
callData: ethers.BytesLike
accountGasLimits: ethers.BytesLike
preVerificationGas: ethers.BigNumberish
gasFees: ethers.BytesLike
paymasterAndData: ethers.BytesLike
signature: ethers.BytesLike
}

type UnsignedPackedUserOperation = Omit<PackedUserOperation, 'signature'>

type UserOperation = {
sender: string
nonce: string
initCode: string
callData: string
nonce: ethers.BigNumberish
factory?: string
factoryData?: ethers.BytesLike
callData: ethers.BytesLike
callGasLimit: ethers.BigNumberish
verificationGasLimit: ethers.BigNumberish
preVerificationGas: ethers.BigNumberish
maxFeePerGas: ethers.BigNumberish
maxPriorityFeePerGas: ethers.BigNumberish
paymasterAndData: string
signature: string
paymaster?: string
paymasterVerificationGasLimit?: ethers.BigNumberish
paymasterPostOpGasLimit?: ethers.BigNumberish
paymasterData?: ethers.BytesLike
signature: ethers.BytesLike
}

type UnsignedUserOperation = Omit<UserOperation, 'signature'>

// Dummy signature for gas estimation. We require it so the estimation doesn't revert
// if the signature is absent
const DUMMY_SIGNATURE =
Expand Down Expand Up @@ -73,7 +89,7 @@ function prepareUserOperationWithInitialisation(
initializer: SafeInitializer,
afterInitializationOpCall?: UserOpCall,
saltNonce = ethers.ZeroHash,
): UnsignedUserOperation {
): UnsignedPackedUserOperation {
const initHash = getInitHash(initializer, APP_CHAIN_ID)
const launchpadInitializer = getLaunchpadInitializer(initHash)
const predictedSafeAddress = getSafeAddress(launchpadInitializer, SAFE_PROXY_FACTORY_ADDRESS, SAFE_SIGNER_LAUNCHPAD_ADDRESS, saltNonce)
Expand All @@ -93,11 +109,13 @@ function prepareUserOperationWithInitialisation(
initializer,
getExecuteUserOpData(userOpCall.to, userOpCall.value, userOpCall.data, userOpCall.operation),
),
callGasLimit: ethers.toBeHex(2000000),
verificationGasLimit: ethers.toBeHex(2000000),
...packGasParameters({
callGasLimit: ethers.toBeHex(2000000),
verificationGasLimit: ethers.toBeHex(2000000),
maxFeePerGas: ethers.toBeHex(10000000000),
maxPriorityFeePerGas: ethers.toBeHex(10000000000),
}),
preVerificationGas: ethers.toBeHex(2000000),
maxFeePerGas: ethers.toBeHex(10000000000),
maxPriorityFeePerGas: ethers.toBeHex(10000000000),
paymasterAndData: '0x',
}

Expand Down Expand Up @@ -135,15 +153,55 @@ type UserOpGasLimitEstimation = {
* @param entryPointAddress - The entry point address. Default value is ENTRYPOINT_ADDRESS.
* @returns A promise that resolves to the estimated gas limit for the user operation.
*/
function estimateUserOpGasLimit(userOp: UnsignedUserOperation, entryPointAddress = ENTRYPOINT_ADDRESS): Promise<UserOpGasLimitEstimation> {
;(userOp as UserOperation).signature = DUMMY_SIGNATURE

function estimateUserOpGasLimit(
userOp: UnsignedPackedUserOperation,
entryPointAddress = ENTRYPOINT_ADDRESS,
): Promise<UserOpGasLimitEstimation> {
const provider = getEip4337BundlerProvider()
const estimation = provider.send('eth_estimateUserOperationGas', [userOp, entryPointAddress])
const rpcUserOp = unpackUserOperationForRpc(userOp, DUMMY_SIGNATURE)
const estimation = provider.send('eth_estimateUserOperationGas', [rpcUserOp, entryPointAddress])

return estimation
}

/**
* Unpacks a user operation for use over the bundler RPC.
* @param userOp The user operation to unpack.
* @param signature The signature bytes for the user operation.
* @returns An unpacked `UserOperation` that can be used over bunlder RPC.
*/
function unpackUserOperationForRpc(userOp: UnsignedPackedUserOperation, signature: ethers.BytesLike): UserOperation {
const initFields =
ethers.dataLength(userOp.initCode) > 0
? {
factory: ethers.getAddress(ethers.dataSlice(userOp.initCode, 0, 20)),
factoryData: ethers.dataSlice(userOp.initCode, 20),
}
: {}
const paymasterFields =
ethers.dataLength(userOp.paymasterAndData) > 0
? {
paymaster: ethers.getAddress(ethers.dataSlice(userOp.initCode, 0, 20)),
paymasterVerificationGasLimit: ethers.toBeHex(ethers.dataSlice(userOp.paymasterAndData, 20, 36)),
paymasterPostOpGasLimit: ethers.toBeHex(ethers.dataSlice(userOp.paymasterAndData, 36, 52)),
paymasterData: ethers.dataSlice(userOp.paymasterAndData, 52),
}
: {}
return {
sender: ethers.getAddress(userOp.sender),
nonce: ethers.toBeHex(userOp.nonce),
...initFields,
callData: ethers.hexlify(userOp.callData),
callGasLimit: ethers.toBeHex(ethers.dataSlice(userOp.accountGasLimits, 16, 32)),
verificationGasLimit: ethers.toBeHex(ethers.dataSlice(userOp.accountGasLimits, 0, 16)),
preVerificationGas: ethers.toBeHex(userOp.preVerificationGas),
maxFeePerGas: ethers.toBeHex(ethers.dataSlice(userOp.gasFees, 16, 32)),
maxPriorityFeePerGas: ethers.toBeHex(ethers.dataSlice(userOp.gasFees, 0, 16)),
...paymasterFields,
signature: ethers.hexlify(signature),
}
}

/**
* Calculates the required prefund amount based on the maximum fee per gas,
* user operation gas limit estimation, and a multiplier.
Expand All @@ -164,35 +222,45 @@ function getRequiredPrefund(maxFeePerGas: bigint, userOpGasLimitEstimation: User
)
}

/**
* Pasks a user operation gas parameters.
* @param op The UserOperation gas parameters to pack.
* @returns The packed UserOperation parameters.
*/
function packGasParameters(
op: Pick<UserOperation, 'verificationGasLimit' | 'callGasLimit' | 'maxPriorityFeePerGas' | 'maxFeePerGas'>,
): Pick<PackedUserOperation, 'accountGasLimits' | 'gasFees'> {
return {
accountGasLimits: ethers.solidityPacked(['uint128', 'uint128'], [op.verificationGasLimit, op.callGasLimit]),
gasFees: ethers.solidityPacked(['uint128', 'uint128'], [op.maxPriorityFeePerGas, op.maxFeePerGas]),
}
}

/**
* Packs a UserOperation object into a string using the defaultAbiCoder.
* @param op The UserOperation object to pack.
* @returns The packed UserOperation as a string.
*/
function packUserOp(op: UnsignedUserOperation): string {
function packUserOpData(op: UnsignedPackedUserOperation): string {
return ethers.AbiCoder.defaultAbiCoder().encode(
[
'address', // sender
'uint256', // nonce
'bytes32', // initCode
'bytes32', // callData
'uint256', // callGasLimit
'uint256', // verificationGasLimit
'bytes32', // accountGasLimits
'uint256', // preVerificationGas
'uint256', // maxFeePerGas
'uint256', // maxPriorityFeePerGas
'bytes32', // gasFees
'bytes32', // paymasterAndData
],
[
op.sender,
op.nonce,
ethers.keccak256(op.initCode),
ethers.keccak256(op.callData),
op.callGasLimit,
op.verificationGasLimit,
op.accountGasLimits,
op.preVerificationGas,
op.maxFeePerGas,
op.maxPriorityFeePerGas,
op.gasFees,
ethers.keccak256(op.paymasterAndData),
],
)
Expand All @@ -206,11 +274,11 @@ function packUserOp(op: UnsignedUserOperation): string {
* @returns The hash of the user operation.
*/
function getUserOpHash(
op: UnsignedUserOperation,
op: UnsignedPackedUserOperation,
entryPoint: string = ENTRYPOINT_ADDRESS,
chainId: ethers.BigNumberish = APP_CHAIN_ID,
): string {
const userOpHash = ethers.keccak256(packUserOp(op))
const userOpHash = ethers.keccak256(packUserOpData(op))
const enc = ethers.AbiCoder.defaultAbiCoder().encode(['bytes32', 'address', 'uint256'], [userOpHash, entryPoint, chainId])
return ethers.keccak256(enc)
}
Expand Down Expand Up @@ -287,7 +355,7 @@ type Assertion = {
* @throws An error if signing the user operation fails.
*/
async function signAndSendUserOp(
userOp: UnsignedUserOperation,
userOp: UnsignedPackedUserOperation,
passkey: PasskeyLocalStorageFormat,
entryPoint: string = ENTRYPOINT_ADDRESS,
chainId: ethers.BigNumberish = APP_CHAIN_ID,
Expand Down Expand Up @@ -342,15 +410,15 @@ async function signAndSendUserOp(
],
)

const userOpWithSignature = { ...userOp, signature }

return await getEip4337BundlerProvider().send('eth_sendUserOperation', [userOpWithSignature, entryPoint])
const rpcUserOp = unpackUserOperationForRpc(userOp, signature)
return await getEip4337BundlerProvider().send('eth_sendUserOperation', [rpcUserOp, entryPoint])
}

export type { UserOperation, UnsignedUserOperation, UserOpGasLimitEstimation }
export type { PackedUserOperation, UnsignedPackedUserOperation, UserOperation, UserOpGasLimitEstimation }

export {
prepareUserOperationWithInitialisation,
packGasParameters,
getEip4337BundlerProvider,
estimateUserOpGasLimit,
getRequiredPrefund,
Expand Down

0 comments on commit fe46439

Please sign in to comment.