@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
JavaScript
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()
});
});
});
});
});