Merge branch 'main' of github.com:getwax/wax into anon-aadhaar

This commit is contained in:
jacque006
2024-07-03 18:57:49 +02:00
5 changed files with 231 additions and 65 deletions

View 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.

View File

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

View 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);
});
});

View File

@@ -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,

View File

@@ -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) {