mirror of
https://github.com/getwax/wax.git
synced 2026-01-09 15:18:02 -05:00
Merge branch 'main' of github.com:getwax/wax into anon-aadhaar
This commit is contained in:
3
packages/plugins/src/paymaster/README.md
Normal file
3
packages/plugins/src/paymaster/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Paymaster
|
||||
|
||||
These are exemplary paymaster contracts. The SponsorEverythingPaymaster contract and its test can serve as references when developing more complex paymasters for your specific use cases.
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.8.4 <0.9.0;
|
||||
|
||||
import {IEntryPoint} from 'account-abstraction/interfaces/IEntryPoint.sol';
|
||||
|
||||
import {BasePaymaster} from 'account-abstraction/core/BasePaymaster.sol';
|
||||
import {UserOperationLib} from 'account-abstraction/core/UserOperationLib.sol';
|
||||
import {PackedUserOperation} from 'account-abstraction/interfaces/PackedUserOperation.sol';
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
THIS CONTRACT IS STILL IN ACTIVE DEVELOPMENT. NOT FOR PRODUCTION USE
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/// @title This paymaster sponsors everything.
|
||||
contract SponsorEverythingPaymaster is BasePaymaster {
|
||||
using UserOperationLib for PackedUserOperation;
|
||||
|
||||
constructor(IEntryPoint _entryPoint) BasePaymaster(_entryPoint) {}
|
||||
|
||||
/**
|
||||
* Validate a user operation.
|
||||
* @param userOp - The user operation.
|
||||
* @param userOpHash - The hash of the user operation.
|
||||
* @param maxCost - The maximum cost of the user operation.
|
||||
*/
|
||||
function _validatePaymasterUserOp(
|
||||
PackedUserOperation calldata userOp,
|
||||
bytes32 userOpHash,
|
||||
uint256 maxCost
|
||||
) internal virtual override returns (bytes memory context, uint256 validationData) {
|
||||
// Validation logic comes here.
|
||||
// Approve everything.
|
||||
return ("", 0);
|
||||
}
|
||||
}
|
||||
112
packages/plugins/test/e2e/SafeSponsorEverythingPaymaster.test.ts
Normal file
112
packages/plugins/test/e2e/SafeSponsorEverythingPaymaster.test.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { expect } from "chai";
|
||||
import { ethers } from "ethers";
|
||||
import {
|
||||
SafeECDSAFactory__factory,
|
||||
SafeECDSAPlugin__factory,
|
||||
SponsorEverythingPaymaster__factory,
|
||||
EntryPoint__factory
|
||||
} from "../../typechain-types";
|
||||
import receiptOf from "./utils/receiptOf";
|
||||
import { setupTests } from "./utils/setupTests";
|
||||
import { createAndSendUserOpWithEcdsaSig } from "./utils/createUserOp";
|
||||
|
||||
const oneEther = ethers.parseEther("1");
|
||||
|
||||
describe("SafeSponsorEverythingPaymasterPlugin", () => {
|
||||
it("should pass the ERC4337 validation", async () => {
|
||||
const {
|
||||
bundlerProvider,
|
||||
provider,
|
||||
admin,
|
||||
owner,
|
||||
entryPointAddress,
|
||||
deployer,
|
||||
safeSingleton,
|
||||
} = await setupTests();
|
||||
|
||||
// Deploy paymaster.
|
||||
const paymaster = await deployer.connectOrDeploy(
|
||||
SponsorEverythingPaymaster__factory,
|
||||
[entryPointAddress],
|
||||
);
|
||||
const paymasterAddress = await paymaster.getAddress();
|
||||
|
||||
// Paymaster deposits.
|
||||
await paymaster.deposit({ value: oneEther })
|
||||
|
||||
const recipient = ethers.Wallet.createRandom();
|
||||
const transferAmount = oneEther;
|
||||
const dummySignature = await owner.signMessage("dummy sig");
|
||||
|
||||
// Deploy ecdsa plugin
|
||||
const safeECDSAFactory = await deployer.connectOrDeploy(
|
||||
SafeECDSAFactory__factory,
|
||||
[],
|
||||
);
|
||||
|
||||
const createArgs = [
|
||||
safeSingleton,
|
||||
entryPointAddress,
|
||||
await owner.getAddress(),
|
||||
0,
|
||||
] satisfies Parameters<typeof safeECDSAFactory.create.staticCall>;
|
||||
|
||||
const accountAddress = await safeECDSAFactory.create.staticCall(
|
||||
...createArgs,
|
||||
);
|
||||
|
||||
await receiptOf(safeECDSAFactory.create(...createArgs));
|
||||
|
||||
const safeEcdsaPlugin = SafeECDSAPlugin__factory.connect(
|
||||
accountAddress,
|
||||
owner,
|
||||
);
|
||||
|
||||
// Native tokens for the pre-fund
|
||||
await receiptOf(
|
||||
admin.sendTransaction({
|
||||
to: accountAddress,
|
||||
value: oneEther,
|
||||
}),
|
||||
);
|
||||
|
||||
// Construct userOp
|
||||
const userOpCallData = safeEcdsaPlugin.interface.encodeFunctionData(
|
||||
"execTransaction",
|
||||
[recipient.address, transferAmount, "0x00"],
|
||||
);
|
||||
|
||||
// Note: factoryParams is not used because we need to create both the safe
|
||||
// proxy and the plugin, and 4337 currently only allows one contract
|
||||
// creation in this step. Since we need an extra step anyway, it's simpler
|
||||
// to do the whole create outside of 4337.
|
||||
const factoryParams = {
|
||||
factory: "0x",
|
||||
factoryData: "0x",
|
||||
};
|
||||
|
||||
// Check paymaster balances before and after sending UserOp.
|
||||
const entrypoint = EntryPoint__factory.connect(entryPointAddress, provider)
|
||||
const paymasterBalanceBefore = await entrypoint.balanceOf(paymasterAddress)
|
||||
|
||||
// Send userOp
|
||||
await createAndSendUserOpWithEcdsaSig(
|
||||
provider,
|
||||
bundlerProvider,
|
||||
owner,
|
||||
accountAddress,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
paymasterAddress,
|
||||
3e5,
|
||||
"0x"
|
||||
);
|
||||
|
||||
const paymasterBalanceAfter = await entrypoint.balanceOf(paymasterAddress)
|
||||
|
||||
expect(paymasterBalanceBefore).greaterThan(paymasterBalanceAfter)
|
||||
expect(await provider.getBalance(recipient.address)).to.equal(oneEther);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable prettier/prettier */
|
||||
/* eslint-disable @typescript-eslint/comma-dangle */
|
||||
import { ethers, getBytes, NonceManager, Signer } from "ethers";
|
||||
import { BigNumberish, BytesLike, ethers, getBytes, NonceManager, Signer } from "ethers";
|
||||
import { AddressZero } from "@ethersproject/constants";
|
||||
|
||||
import { SafeProxyFactory } from "../../../typechain-types/lib/safe-contracts/contracts/proxies/SafeProxyFactory";
|
||||
@@ -79,13 +79,16 @@ export const generateFactoryParamsAndAddress = async (
|
||||
};
|
||||
|
||||
export const createUserOperation = async (
|
||||
provider: ethers.JsonRpcProvider,
|
||||
bundlerProvider: ethers.JsonRpcProvider,
|
||||
accountAddress: string,
|
||||
factoryParams: FactoryParams,
|
||||
userOpCallData: string,
|
||||
entryPointAddress: string,
|
||||
dummySignature: string
|
||||
provider: ethers.JsonRpcProvider,
|
||||
bundlerProvider: ethers.JsonRpcProvider,
|
||||
accountAddress: string,
|
||||
factoryParams: FactoryParams,
|
||||
userOpCallData: string,
|
||||
entryPointAddress: string,
|
||||
dummySignature: string,
|
||||
paymaster?: string,
|
||||
paymasterPostOpGasLimit?: BigNumberish,
|
||||
paymasterData?: BytesLike,
|
||||
) => {
|
||||
const entryPoint = EntryPoint__factory.connect(
|
||||
entryPointAddress,
|
||||
@@ -107,55 +110,66 @@ export const createUserOperation = async (
|
||||
userOp.factoryData = factoryParams.factoryData;
|
||||
}
|
||||
|
||||
const {
|
||||
callGasLimit,
|
||||
verificationGasLimit,
|
||||
preVerificationGas,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
} = await getGasEstimates(
|
||||
provider,
|
||||
bundlerProvider,
|
||||
userOp,
|
||||
entryPointAddress
|
||||
);
|
||||
const {
|
||||
callGasLimit,
|
||||
verificationGasLimit,
|
||||
preVerificationGas,
|
||||
paymasterVerificationGasLimit,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
} = await getGasEstimates(
|
||||
provider,
|
||||
bundlerProvider,
|
||||
userOp,
|
||||
entryPointAddress,
|
||||
);
|
||||
|
||||
const unsignedUserOperation = {
|
||||
sender: accountAddress,
|
||||
nonce: nonceHex,
|
||||
factory: userOp.factory,
|
||||
factoryData: userOp.factoryData,
|
||||
callData: userOpCallData,
|
||||
callGasLimit,
|
||||
verificationGasLimit,
|
||||
preVerificationGas,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
signature: dummySignature,
|
||||
} satisfies UserOperation;
|
||||
const unsignedUserOperation = {
|
||||
sender: accountAddress,
|
||||
nonce: nonceHex,
|
||||
factory: userOp.factory,
|
||||
factoryData: userOp.factoryData,
|
||||
callData: userOpCallData,
|
||||
callGasLimit,
|
||||
verificationGasLimit,
|
||||
preVerificationGas,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
paymaster: paymaster,
|
||||
paymasterVerificationGasLimit: paymaster ? paymasterVerificationGasLimit : undefined,
|
||||
paymasterPostOpGasLimit: paymasterPostOpGasLimit,
|
||||
paymasterData: paymasterData,
|
||||
signature: dummySignature,
|
||||
} satisfies UserOperation;
|
||||
|
||||
return await ethers.resolveProperties(unsignedUserOperation);
|
||||
};
|
||||
|
||||
export const createAndSendUserOpWithEcdsaSig = async (
|
||||
provider: ethers.JsonRpcProvider,
|
||||
bundlerProvider: ethers.JsonRpcProvider,
|
||||
owner: Signer,
|
||||
accountAddress: string,
|
||||
factoryParams: FactoryParams,
|
||||
userOpCallData: string,
|
||||
entryPointAddress: string,
|
||||
dummySignature: string
|
||||
provider: ethers.JsonRpcProvider,
|
||||
bundlerProvider: ethers.JsonRpcProvider,
|
||||
owner: Signer,
|
||||
accountAddress: string,
|
||||
factoryParams: FactoryParams,
|
||||
userOpCallData: string,
|
||||
entryPointAddress: string,
|
||||
dummySignature: string,
|
||||
paymaster?: string,
|
||||
paymasterPostOpGasLimit?: BigNumberish,
|
||||
paymasterData?: BytesLike,
|
||||
) => {
|
||||
const unsignedUserOperation = await createUserOperation(
|
||||
provider,
|
||||
bundlerProvider,
|
||||
accountAddress,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature
|
||||
);
|
||||
const unsignedUserOperation = await createUserOperation(
|
||||
provider,
|
||||
bundlerProvider,
|
||||
accountAddress,
|
||||
factoryParams,
|
||||
userOpCallData,
|
||||
entryPointAddress,
|
||||
dummySignature,
|
||||
paymaster,
|
||||
paymasterPostOpGasLimit,
|
||||
paymasterData,
|
||||
);
|
||||
|
||||
const userOpHash = getUserOpHash(
|
||||
unsignedUserOperation,
|
||||
|
||||
@@ -9,14 +9,15 @@ export const getGasEstimates = async (
|
||||
partialUserOperation: Partial<UserOperation>,
|
||||
entryPointAddress: string
|
||||
) => {
|
||||
const gasEstimate = (await bundlerProvider.send(
|
||||
"eth_estimateUserOperationGas",
|
||||
[partialUserOperation, entryPointAddress]
|
||||
)) as {
|
||||
verificationGasLimit: string;
|
||||
preVerificationGas: string;
|
||||
callGasLimit: string;
|
||||
};
|
||||
const gasEstimate = (await bundlerProvider.send(
|
||||
"eth_estimateUserOperationGas",
|
||||
[partialUserOperation, entryPointAddress],
|
||||
)) as {
|
||||
verificationGasLimit: string;
|
||||
preVerificationGas: string;
|
||||
paymasterVerificationGasLimit: string;
|
||||
callGasLimit: string;
|
||||
};
|
||||
|
||||
const safeVerificationGasLimit =
|
||||
BigInt(gasEstimate.verificationGasLimit) +
|
||||
@@ -28,13 +29,14 @@ export const getGasEstimates = async (
|
||||
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await getFeeData(provider);
|
||||
|
||||
return {
|
||||
callGasLimit: gasEstimate.callGasLimit,
|
||||
verificationGasLimit: ethers.toBeHex(safeVerificationGasLimit),
|
||||
preVerificationGas: ethers.toBeHex(safePreVerificationGas),
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
};
|
||||
return {
|
||||
callGasLimit: gasEstimate.callGasLimit,
|
||||
verificationGasLimit: ethers.toBeHex(safeVerificationGasLimit),
|
||||
preVerificationGas: ethers.toBeHex(safePreVerificationGas),
|
||||
paymasterVerificationGasLimit: ethers.toBeHex(safeVerificationGasLimit),
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
};
|
||||
};
|
||||
|
||||
export async function getFeeData(provider: ethers.Provider) {
|
||||
|
||||
Reference in New Issue
Block a user