UNPKG

@owlprotocol/contracts-account-abstraction

Version:

ERC4337 Account Abstraction support for deploying relevant smart contracts and interacting with UserOps.

477 lines (476 loc) 22.3 kB
var import_vitest = require("vitest"); var import_viem = require("viem"); var import_chains = require("viem/chains"); var import_accounts = require("viem/accounts"); var import_account_abstraction = require("viem/account-abstraction"); var import_viem_utils = require("@owlprotocol/viem-utils"); var import_envvars = require("@owlprotocol/envvars"); var import_chains2 = require("@owlprotocol/chains"); var import_estimateUserOperationGas = require("./estimateUserOperationGas.js"); var import_sendUserOperation = require("./sendUserOperation.js"); var import_getUserOperation = require("./getUserOperation.js"); var import_getUserOperationReceipt = require("./getUserOperationReceipt.js"); var import_constants = require("../../test/constants.js"); var import_SimpleAccount = require("../../SimpleAccount.js"); var import_setupERC4337Contracts = require("../../setupERC4337Contracts.js"); var import_ERC1967Proxy = require("../../artifacts/ERC1967Proxy.js"); var import_SimpleAccountFactory = require("../../artifacts/SimpleAccountFactory.js"); var import_SimpleAccount2 = require("../../artifacts/SimpleAccount.js"); var import_MyContract = require("../../artifacts/MyContract.js"); var import_VerifyingPaymaster = require("../../artifacts/VerifyingPaymaster.js"); var import_UserOperation = require("../../models/UserOperation.js"); var import_PackedUserOperation = require("../../models/PackedUserOperation.js"); var import_constants2 = require("../../constants.js"); var import_getUserOperationTotalGas = require("../../utils/getUserOperationTotalGas.js"); (0, import_vitest.describe)("actions/bundler/sendUserOperation.test.ts", function() { let chain; let account; let transport; if (import_envvars.NODE_ENV === "test") { chain = { ...import_chains.localhost, rpcUrls: { default: { http: [`http://127.0.0.1:${import_constants.port}`] } } }; account = (0, import_viem_utils.getLocalAccount)(0, { nonceManager: import_viem.nonceManager }); transport = (0, import_viem.http)(chain.rpcUrls.default.http[0]); } else { chain = import_chains2.optimismSepolia; account = (0, import_viem_utils.getUtilityAccount)({ nonceManager: import_viem.nonceManager }); transport = (0, import_viem.http)(import_chains2.optimismSepolia.rpcUrls.drpc.http[0]); } const publicClient = (0, import_viem.createPublicClient)({ chain, transport }); const bundlerClient = publicClient.extend((client) => { return { getUserOperation: (parameters) => (0, import_getUserOperation.getUserOperation)(client, parameters), getUserOperationReceipt: (parameters) => (0, import_getUserOperationReceipt.getUserOperationReceipt)(client, parameters) }; }); const walletClient = (0, import_viem.createWalletClient)({ account, chain, transport }); const entryPointSimulationsAddress = import_setupERC4337Contracts.erc4337Contracts.pimlicoEntrypointSimulations; const factoryAddress = import_setupERC4337Contracts.erc4337Contracts.simpleAccountFactory; let owner; let smartAccountAddress; let factoryData; (0, import_vitest.beforeEach)(async () => { owner = (0, import_accounts.privateKeyToAccount)((0, import_accounts.generatePrivateKey)()); smartAccountAddress = (0, import_SimpleAccount.getSimpleAccountAddress)( { owner: owner.address, salt: 0n }, { factoryAddress, proxyBytecode: import_ERC1967Proxy.ERC1967Proxy.bytecode } ); factoryData = (0, import_viem.encodeFunctionData)({ abi: [import_SimpleAccountFactory.createAccount], functionName: "createAccount", args: [owner.address, 0n] }); }); (0, import_vitest.describe)("smart account prefund", () => { (0, import_vitest.describe)("simple transaction", () => { let callData; (0, import_vitest.beforeEach)(async () => { const to = (0, import_accounts.privateKeyToAccount)((0, import_accounts.generatePrivateKey)()).address; const value = 0n; const data = "0x"; callData = (0, import_viem.encodeFunctionData)({ abi: [import_SimpleAccount2.execute], args: [to, value, data] }); }); (0, import_vitest.test)("sendUserOperation", async () => { const gasPrice = await publicClient.estimateFeesPerGas(); const userOpData = { sender: smartAccountAddress, nonce: 0n, callData, factory: factoryAddress, factoryData, maxFeePerGas: gasPrice.maxFeePerGas }; const { preVerificationGas, verificationGasLimit, callGasLimit, paymasterVerificationGasLimit, paymasterPostOpGasLimit } = await (0, import_estimateUserOperationGas.estimateUserOperationGas)({ ...publicClient, entryPointSimulationsAddress }, userOpData); (0, import_vitest.expect)(preVerificationGas).toBeGreaterThan(0n); (0, import_vitest.expect)(verificationGasLimit).toBeGreaterThan(0n); (0, import_vitest.expect)(callGasLimit).toBeGreaterThan(0n); (0, import_vitest.expect)(paymasterVerificationGasLimit).toBeUndefined(); (0, import_vitest.expect)(paymasterPostOpGasLimit).toBeUndefined(); const userOp = { ...userOpData, signature: import_UserOperation.dummySignature, callGasLimit, verificationGasLimit, preVerificationGas, maxFeePerGas: gasPrice.maxFeePerGas, maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas }; console.log({ userOp }); const userOpHash = (0, import_account_abstraction.getUserOperationHash)({ userOperation: userOp, entryPointAddress: import_account_abstraction.entryPoint07Address, entryPointVersion: "0.7", chainId: chain.id }); console.log({ userOpHash }); const signature = await owner.signMessage({ message: { raw: userOpHash } }); userOp.signature = signature; const prefundHash = await walletClient.sendTransaction({ to: smartAccountAddress, value: (0, import_getUserOperationTotalGas.getUserOperationTotalGas)(userOp) }); console.log({ prefundHash }); await publicClient.waitForTransactionReceipt({ hash: prefundHash }); const userOpHashSent = await (0, import_sendUserOperation.sendUserOperation)(walletClient, userOp); (0, import_vitest.expect)(userOpHashSent).toBe(userOpHash); console.log("waitingForUserOpReceipt"); const userOpReceipt = await (0, import_account_abstraction.waitForUserOperationReceipt)(bundlerClient, { hash: userOpHash }); (0, import_vitest.expect)(userOpReceipt.userOpHash).toBe(userOpHash); (0, import_vitest.expect)(userOpReceipt.success).toBe(true); const userOpSent = await bundlerClient.getUserOperation({ hash: userOpHash }); (0, import_vitest.expect)(userOpSent.userOperation).toStrictEqual({ ...userOp, callData: userOp.callData.toLowerCase(), paymaster: import_viem.zeroAddress, paymasterData: "0x", paymasterPostOpGasLimit: 0n, paymasterVerificationGasLimit: 0n }); }); }); (0, import_vitest.describe)("contract deploy", () => { let callData; (0, import_vitest.beforeEach)(() => { const deployParams = { // "random" salt salt: (0, import_viem.padHex)(owner.address, { size: 32 }), bytecode: import_MyContract.MyContract.bytecode }; const contractDeployTransaction = (0, import_viem_utils.getDeployDeterministicFunctionData)(deployParams); callData = (0, import_viem.encodeFunctionData)({ abi: [import_SimpleAccount2.execute], args: [contractDeployTransaction.to, 0n, contractDeployTransaction.data] }); }); (0, import_vitest.test)("sendUserOperation", async () => { const gasPrice = await publicClient.estimateFeesPerGas(); const userOpData = { sender: smartAccountAddress, nonce: 0n, callData, factory: factoryAddress, factoryData, maxFeePerGas: gasPrice.maxFeePerGas }; const { preVerificationGas, verificationGasLimit, callGasLimit, paymasterVerificationGasLimit, paymasterPostOpGasLimit } = await (0, import_estimateUserOperationGas.estimateUserOperationGas)({ ...publicClient, entryPointSimulationsAddress }, userOpData); (0, import_vitest.expect)(preVerificationGas).toBeGreaterThan(0n); (0, import_vitest.expect)(verificationGasLimit).toBeGreaterThan(0n); (0, import_vitest.expect)(callGasLimit).toBeGreaterThan(0n); (0, import_vitest.expect)(paymasterVerificationGasLimit).toBeUndefined(); (0, import_vitest.expect)(paymasterPostOpGasLimit).toBeUndefined(); const userOp = { ...userOpData, signature: import_UserOperation.dummySignature, callGasLimit, verificationGasLimit, preVerificationGas, maxFeePerGas: gasPrice.maxFeePerGas, maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas }; const userOpHash = (0, import_account_abstraction.getUserOperationHash)({ userOperation: userOp, entryPointAddress: import_account_abstraction.entryPoint07Address, entryPointVersion: "0.7", chainId: chain.id }); const signature = await owner.signMessage({ message: { raw: userOpHash } }); userOp.signature = signature; const prefundHash = await walletClient.sendTransaction({ to: smartAccountAddress, value: (0, import_getUserOperationTotalGas.getUserOperationTotalGas)(userOp) }); await publicClient.waitForTransactionReceipt({ hash: prefundHash }); const userOpHashSent = await (0, import_sendUserOperation.sendUserOperation)(walletClient, userOp); (0, import_vitest.expect)(userOpHashSent).toBe(userOpHash); const userOpReceipt = await (0, import_account_abstraction.waitForUserOperationReceipt)(bundlerClient, { hash: userOpHash }); (0, import_vitest.expect)(userOpReceipt.userOpHash).toBe(userOpHash); (0, import_vitest.expect)(userOpReceipt.success).toBe(true); const userOpSent = await bundlerClient.getUserOperation({ hash: userOpHash }); (0, import_vitest.expect)(userOpSent.userOperation).toStrictEqual({ ...userOp, callData: userOp.callData.toLowerCase(), paymaster: import_viem.zeroAddress, paymasterData: "0x", paymasterPostOpGasLimit: 0n, paymasterVerificationGasLimit: 0n }); }); }); }); (0, import_vitest.describe)("paymaster", () => { let paymasterSigner; let paymasterAddress; let validUntil; let validAfter; let paymasterDataUnsigned; let paymasterDataDummySignature; (0, import_vitest.beforeEach)(async () => { paymasterSigner = (0, import_accounts.privateKeyToAccount)((0, import_accounts.generatePrivateKey)()); paymasterAddress = (await (0, import_setupERC4337Contracts.setupVerifyingPaymaster)(walletClient, { verifyingSignerAddress: paymasterSigner.address })).address; validUntil = Date.now() + 3600; validAfter = 0; paymasterDataUnsigned = (0, import_viem.encodeAbiParameters)( [ { name: "validUntil", type: "uint48" }, { name: "validAfter", type: "uint48" } ], [validUntil, validAfter] ); paymasterDataDummySignature = (0, import_viem.concatHex)([paymasterDataUnsigned, import_UserOperation.dummySignature]); }); import_vitest.test.skipIf(import_envvars.NODE_ENV != "test")("paymaster deposit", async () => { const paymasterSlot = (0, import_estimateUserOperationGas.getPaymasterSlot)(paymasterAddress); const slotValueBefore = await publicClient.getStorageAt({ address: import_constants2.ENTRYPOINT_ADDRESS_V07, slot: paymasterSlot }); (0, import_vitest.expect)(slotValueBefore).toEqual((0, import_viem.toHex)(0, { size: 32 })); const topupAmount = (0, import_viem.parseEther)("0.0000001"); const paymasterDeposit = await publicClient.simulateContract({ account: walletClient.account, address: paymasterAddress, abi: [import_VerifyingPaymaster.deposit], functionName: "deposit", value: topupAmount, args: [] }); const paymasterDepositHash = await walletClient.writeContract(paymasterDeposit.request); await publicClient.waitForTransactionReceipt({ hash: paymasterDepositHash }); const slotValueAfter = await publicClient.getStorageAt({ address: import_constants2.ENTRYPOINT_ADDRESS_V07, slot: paymasterSlot }); (0, import_vitest.expect)(slotValueAfter).toEqual((0, import_viem.toHex)(topupAmount, { size: 32 })); }); (0, import_vitest.describe)("simple transaction", () => { let callData; (0, import_vitest.beforeEach)(() => { const to = (0, import_accounts.privateKeyToAccount)((0, import_accounts.generatePrivateKey)()).address; const value = 0n; const data = "0x"; callData = (0, import_viem.encodeFunctionData)({ abi: [import_SimpleAccount2.execute], args: [to, value, data] }); }); (0, import_vitest.test)("sendUserOperation", async () => { const gasPrice = await publicClient.estimateFeesPerGas(); const userOpData = { sender: smartAccountAddress, nonce: 0n, callData, factory: factoryAddress, factoryData, paymaster: paymasterAddress, paymasterData: paymasterDataDummySignature, maxFeePerGas: gasPrice.maxFeePerGas }; const { preVerificationGas, verificationGasLimit, callGasLimit, paymasterVerificationGasLimit, paymasterPostOpGasLimit } = await (0, import_estimateUserOperationGas.estimateUserOperationGas)({ ...publicClient, entryPointSimulationsAddress }, userOpData); (0, import_vitest.expect)(preVerificationGas).toBeGreaterThan(0n); (0, import_vitest.expect)(verificationGasLimit).toBeGreaterThan(0n); (0, import_vitest.expect)(callGasLimit).toBeGreaterThan(0n); (0, import_vitest.expect)(paymasterVerificationGasLimit).toBeGreaterThan(0n); (0, import_vitest.expect)(paymasterPostOpGasLimit).toBeGreaterThan(0n); const userOp = { ...userOpData, signature: import_UserOperation.dummySignature, callGasLimit, verificationGasLimit, preVerificationGas, paymasterVerificationGasLimit, paymasterPostOpGasLimit, maxFeePerGas: gasPrice.maxFeePerGas, maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas }; const userOpPaymasterPacked = (0, import_PackedUserOperation.toPackedUserOperation)((0, import_UserOperation.encodeUserOp)(userOp)); const userOpPaymasterHash = await publicClient.readContract({ address: paymasterAddress, abi: [import_VerifyingPaymaster.getHash], functionName: "getHash", args: [userOpPaymasterPacked, validUntil, validAfter] }); const paymasterSignature = await paymasterSigner.signMessage({ message: { raw: userOpPaymasterHash } }); const paymasterDataSigned = (0, import_viem.concatHex)([paymasterDataUnsigned, paymasterSignature]); userOp.paymasterData = paymasterDataSigned; const userOpHash = (0, import_account_abstraction.getUserOperationHash)({ userOperation: userOp, entryPointAddress: import_account_abstraction.entryPoint07Address, entryPointVersion: "0.7", chainId: chain.id }); const signature = await owner.signMessage({ message: { raw: userOpHash } }); userOp.signature = signature; const paymasterDeposit = await publicClient.simulateContract({ account: walletClient.account, address: paymasterAddress, abi: [import_VerifyingPaymaster.deposit], functionName: "deposit", value: (0, import_getUserOperationTotalGas.getUserOperationTotalGas)(userOp), args: [] }); const paymasterDepositHash = await walletClient.writeContract(paymasterDeposit.request); await publicClient.waitForTransactionReceipt({ hash: paymasterDepositHash }); const userOpHashSent = await (0, import_sendUserOperation.sendUserOperation)(walletClient, userOp); (0, import_vitest.expect)(userOpHashSent).toBe(userOpHash); const userOpReceipt = await (0, import_account_abstraction.waitForUserOperationReceipt)(bundlerClient, { hash: userOpHash }); (0, import_vitest.expect)(userOpReceipt.userOpHash).toBe(userOpHash); (0, import_vitest.expect)(userOpReceipt.success).toBe(true); const userOpSent = await bundlerClient.getUserOperation({ hash: userOpHash }); (0, import_vitest.expect)(userOpSent.userOperation).toStrictEqual({ ...userOp, callData: userOp.callData.toLowerCase() }); }); }); (0, import_vitest.describe)("contract deploy", () => { let callData; (0, import_vitest.beforeEach)(() => { const deployParams = { // "random" salt salt: (0, import_viem.padHex)(owner.address, { size: 32 }), bytecode: import_MyContract.MyContract.bytecode }; const contractDeployTransaction = (0, import_viem_utils.getDeployDeterministicFunctionData)(deployParams); callData = (0, import_viem.encodeFunctionData)({ abi: [import_SimpleAccount2.execute], args: [contractDeployTransaction.to, 0n, contractDeployTransaction.data] }); }); (0, import_vitest.test)("sendUserOperation", async () => { const gasPrice = await publicClient.estimateFeesPerGas(); const userOpData = { sender: smartAccountAddress, nonce: 0n, callData, factory: factoryAddress, factoryData, paymaster: paymasterAddress, paymasterData: paymasterDataDummySignature, maxFeePerGas: gasPrice.maxFeePerGas }; const { preVerificationGas, verificationGasLimit, callGasLimit, paymasterVerificationGasLimit, paymasterPostOpGasLimit } = await (0, import_estimateUserOperationGas.estimateUserOperationGas)({ ...publicClient, entryPointSimulationsAddress }, userOpData); (0, import_vitest.expect)(preVerificationGas).toBeGreaterThan(0n); (0, import_vitest.expect)(verificationGasLimit).toBeGreaterThan(0n); (0, import_vitest.expect)(callGasLimit).toBeGreaterThan(0n); (0, import_vitest.expect)(paymasterVerificationGasLimit).toBeGreaterThan(0n); (0, import_vitest.expect)(paymasterPostOpGasLimit).toBeGreaterThan(0n); const userOp = { ...userOpData, signature: import_UserOperation.dummySignature, callGasLimit, verificationGasLimit, preVerificationGas, paymasterVerificationGasLimit, paymasterPostOpGasLimit, maxFeePerGas: gasPrice.maxFeePerGas, maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas }; const userOpPaymasterPacked = (0, import_PackedUserOperation.toPackedUserOperation)((0, import_UserOperation.encodeUserOp)(userOp)); const userOpPaymasterHash = await publicClient.readContract({ address: paymasterAddress, abi: [import_VerifyingPaymaster.getHash], functionName: "getHash", args: [userOpPaymasterPacked, validUntil, validAfter] }); const paymasterSignature = await paymasterSigner.signMessage({ message: { raw: userOpPaymasterHash } }); const paymasterDataSigned = (0, import_viem.concatHex)([paymasterDataUnsigned, paymasterSignature]); userOp.paymasterData = paymasterDataSigned; const userOpHash = (0, import_account_abstraction.getUserOperationHash)({ userOperation: userOp, entryPointAddress: import_account_abstraction.entryPoint07Address, entryPointVersion: "0.7", chainId: chain.id }); const signature = await owner.signMessage({ message: { raw: userOpHash } }); userOp.signature = signature; const paymasterDeposit = await publicClient.simulateContract({ account: walletClient.account, address: paymasterAddress, abi: [import_VerifyingPaymaster.deposit], functionName: "deposit", value: (0, import_getUserOperationTotalGas.getUserOperationTotalGas)(userOp), args: [] }); const paymasterDepositHash = await walletClient.writeContract(paymasterDeposit.request); console.log({ paymasterDepositHash }); await publicClient.waitForTransactionReceipt({ hash: paymasterDepositHash }); const userOpHashSent = await (0, import_sendUserOperation.sendUserOperation)(walletClient, userOp); (0, import_vitest.expect)(userOpHashSent).toBe(userOpHash); console.log({ userOpHash }); const userOpReceipt = await (0, import_account_abstraction.waitForUserOperationReceipt)(bundlerClient, { hash: userOpHash }); (0, import_vitest.expect)(userOpReceipt.userOpHash).toBe(userOpHash); (0, import_vitest.expect)(userOpReceipt.success).toBe(true); console.log({ userOpTxHash: userOpReceipt.receipt.transactionHash }); const userOpSent = await bundlerClient.getUserOperation({ hash: userOpHash }); (0, import_vitest.expect)(userOpSent.userOperation).toStrictEqual({ ...userOp, callData: userOp.callData.toLowerCase() }); }); }); }); });