123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- const { ethers, config, entrypoint, senderCreator } = require('hardhat');
- const SIG_VALIDATION_SUCCESS = '0x0000000000000000000000000000000000000000';
- const SIG_VALIDATION_FAILURE = '0x0000000000000000000000000000000000000001';
- function getAddress(account) {
- return account.target ?? account.address ?? account;
- }
- function pack(left, right) {
- return ethers.solidityPacked(['uint128', 'uint128'], [left, right]);
- }
- function packValidationData(validAfter, validUntil, authorizer) {
- return ethers.solidityPacked(
- ['uint48', 'uint48', 'address'],
- [
- validAfter,
- validUntil,
- typeof authorizer == 'boolean'
- ? authorizer
- ? SIG_VALIDATION_SUCCESS
- : SIG_VALIDATION_FAILURE
- : getAddress(authorizer),
- ],
- );
- }
- function packInitCode(factory, factoryData) {
- return ethers.solidityPacked(['address', 'bytes'], [getAddress(factory), factoryData]);
- }
- function packPaymasterAndData(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData) {
- return ethers.solidityPacked(
- ['address', 'uint128', 'uint128', 'bytes'],
- [getAddress(paymaster), paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData],
- );
- }
- /// Represent one user operation
- class UserOperation {
- constructor(params) {
- this.sender = getAddress(params.sender);
- this.nonce = params.nonce;
- this.factory = params.factory ?? undefined;
- this.factoryData = params.factoryData ?? '0x';
- this.callData = params.callData ?? '0x';
- this.verificationGas = params.verificationGas ?? 10_000_000n;
- this.callGas = params.callGas ?? 100_000n;
- this.preVerificationGas = params.preVerificationGas ?? 100_000n;
- this.maxPriorityFee = params.maxPriorityFee ?? 100_000n;
- this.maxFeePerGas = params.maxFeePerGas ?? 100_000n;
- this.paymaster = params.paymaster ?? undefined;
- this.paymasterVerificationGasLimit = params.paymasterVerificationGasLimit ?? 0n;
- this.paymasterPostOpGasLimit = params.paymasterPostOpGasLimit ?? 0n;
- this.paymasterData = params.paymasterData ?? '0x';
- this.signature = params.signature ?? '0x';
- }
- get packed() {
- return {
- sender: this.sender,
- nonce: this.nonce,
- initCode: this.factory ? packInitCode(this.factory, this.factoryData) : '0x',
- callData: this.callData,
- accountGasLimits: pack(this.verificationGas, this.callGas),
- preVerificationGas: this.preVerificationGas,
- gasFees: pack(this.maxPriorityFee, this.maxFeePerGas),
- paymasterAndData: this.paymaster
- ? packPaymasterAndData(
- this.paymaster,
- this.paymasterVerificationGasLimit,
- this.paymasterPostOpGasLimit,
- this.paymasterData,
- )
- : '0x',
- signature: this.signature,
- };
- }
- hash(entrypoint) {
- return entrypoint.getUserOpHash(this.packed);
- }
- }
- const parseInitCode = initCode => ({
- factory: '0x' + initCode.replace(/0x/, '').slice(0, 40),
- factoryData: '0x' + initCode.replace(/0x/, '').slice(40),
- });
- /// Global ERC-4337 environment helper.
- class ERC4337Helper {
- constructor() {
- this.factoryAsPromise = ethers.deployContract('$Create2');
- }
- async wait() {
- this.factory = await this.factoryAsPromise;
- return this;
- }
- async newAccount(name, extraArgs = [], params = {}) {
- const env = {
- entrypoint: params.entrypoint ?? entrypoint.v08,
- senderCreator: params.senderCreator ?? senderCreator.v08,
- };
- const { factory } = await this.wait();
- const accountFactory = await ethers.getContractFactory(name);
- if (params.erc7702signer) {
- const delegate = await accountFactory.deploy(...extraArgs);
- const instance = await params.erc7702signer.getAddress().then(address => accountFactory.attach(address));
- const authorization = await params.erc7702signer.authorize({ address: delegate.target });
- return new ERC7702SmartAccount(instance, authorization, env);
- } else {
- const initCode = await accountFactory
- .getDeployTransaction(...extraArgs)
- .then(tx =>
- factory.interface.encodeFunctionData('$deploy', [0, params.salt ?? ethers.randomBytes(32), tx.data]),
- )
- .then(deployCode => ethers.concat([factory.target, deployCode]));
- const instance = await ethers.provider
- .call({
- from: env.entrypoint,
- to: env.senderCreator,
- data: env.senderCreator.interface.encodeFunctionData('createSender', [initCode]),
- })
- .then(result => ethers.getAddress(ethers.hexlify(ethers.getBytes(result).slice(-20))))
- .then(address => accountFactory.attach(address));
- return new SmartAccount(instance, initCode, env);
- }
- }
- }
- /// Represent one ERC-4337 account contract.
- class SmartAccount extends ethers.BaseContract {
- constructor(instance, initCode, env) {
- super(instance.target, instance.interface, instance.runner, instance.deployTx);
- this.address = instance.target;
- this.initCode = initCode;
- this._env = env;
- }
- async deploy(account = this.runner) {
- const { factory: to, factoryData: data } = parseInitCode(this.initCode);
- this.deployTx = await account.sendTransaction({ to, data });
- return this;
- }
- async createUserOp(userOp = {}) {
- userOp.sender ??= this;
- userOp.nonce ??= await this._env.entrypoint.getNonce(userOp.sender, 0);
- if (ethers.isAddressable(userOp.paymaster)) {
- userOp.paymaster = await ethers.resolveAddress(userOp.paymaster);
- userOp.paymasterVerificationGasLimit ??= 100_000n;
- userOp.paymasterPostOpGasLimit ??= 100_000n;
- }
- return new UserOperationWithContext(userOp, this._env);
- }
- }
- class ERC7702SmartAccount extends SmartAccount {
- constructor(instance, authorization, env) {
- super(instance, undefined, env);
- this.authorization = authorization;
- }
- async deploy() {
- // hardhat signers from @nomicfoundation/hardhat-ethers do not support type 4 txs.
- // so we rebuild it using "native" ethers
- await ethers.Wallet.fromPhrase(config.networks.hardhat.accounts.mnemonic, ethers.provider).sendTransaction({
- to: ethers.ZeroAddress,
- authorizationList: [this.authorization],
- gasLimit: 46_000n, // 21,000 base + PER_EMPTY_ACCOUNT_COST
- });
- return this;
- }
- }
- class UserOperationWithContext extends UserOperation {
- constructor(userOp, env) {
- super(userOp);
- this._sender = userOp.sender;
- this._env = env;
- }
- addInitCode() {
- if (this._sender?.initCode) {
- return Object.assign(this, parseInitCode(this._sender.initCode));
- } else throw new Error('No init code available for the sender of this user operation');
- }
- getAuthorization() {
- if (this._sender?.authorization) {
- return this._sender.authorization;
- } else throw new Error('No EIP-7702 authorization available for the sender of this user operation');
- }
- hash() {
- return super.hash(this._env.entrypoint);
- }
- }
- module.exports = {
- SIG_VALIDATION_SUCCESS,
- SIG_VALIDATION_FAILURE,
- packValidationData,
- packInitCode,
- packPaymasterAndData,
- UserOperation,
- ERC4337Helper,
- };
|