From fa787dcbacc3bff4bee271363932d392a0ce2f7c Mon Sep 17 00:00:00 2001 From: livingrockrises <90545960+livingrockrises@users.noreply.github.com> Date: Fri, 12 Jul 2024 01:14:16 +0530 Subject: [PATCH] remove hardhat stuff --- .github/workflows/ci.yml | 2 +- .github/workflows/coverage.yml | 8 +- .gitignore | 3 - CHANGELOG.md | 8 +- README.md | 18 +- hardhat.config.ts | 26 - package.json | 36 +- .../biconomy-sponsorship-paymaster-specs.ts | 210 -------- test/hardhat/utils/deployment.ts | 156 ------ test/hardhat/utils/general.ts | 60 --- test/hardhat/utils/testUtils.ts | 257 --------- test/hardhat/utils/types.ts | 30 -- test/hardhat/utils/userOpHelpers.ts | 486 ------------------ 13 files changed, 22 insertions(+), 1278 deletions(-) delete mode 100644 hardhat.config.ts delete mode 100644 test/hardhat/biconomy-sponsorship-paymaster-specs.ts delete mode 100644 test/hardhat/utils/deployment.ts delete mode 100644 test/hardhat/utils/general.ts delete mode 100644 test/hardhat/utils/testUtils.ts delete mode 100644 test/hardhat/utils/types.ts delete mode 100644 test/hardhat/utils/userOpHelpers.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d52cb6..9d1dccb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,5 +46,5 @@ jobs: - name: Build Typechain and Foundry run: yarn build - - name: Run Forge and Hardhat Tests + - name: Run Forge Tests run: yarn test diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d5f509d..d3846a4 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -26,7 +26,7 @@ jobs: - name: Install Foundry Dependencies run: forge install - - name: Generate Hardhat & Foundry Coverage Report + - name: Generate Foundry Coverage Report run: yarn coverage:report - name: Upload Foundry Coverage Report to Codecov @@ -36,9 +36,3 @@ jobs: file: ./coverage/foundry/lcov.info flags: foundry - - name: Upload Hardhat Coverage Report to Codecov - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: ./coverage/lcov.info - flags: hardhat diff --git a/.gitignore b/.gitignore index 9691a6f..52b85d9 100644 --- a/.gitignore +++ b/.gitignore @@ -24,9 +24,6 @@ broadcast/*/31337/ node_modules .env -# Hardhat files -/cache -/artifacts /docs # TypeChain files diff --git a/CHANGELOG.md b/CHANGELOG.md index ab54d1a..e026822 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] -- Initial setup and configurations for both Foundry and Hardhat environments. +- Initial setup and configurations for Foundry. - Integration of GitHub Actions for CI/CD pipelines. - Addition of linter configurations for Solidity and TypeScript. @@ -15,8 +15,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - `Foo.sol` and `Lock.sol` smart contracts under the `contracts` directory. -- Foundry and Hardhat configurations for building and testing smart contracts. -- Comprehensive testing scripts for Foundry and Hardhat environments in `test/foundry` and `test/hardhat`. +- Foundry configuration for building and testing smart contracts. +- Comprehensive testing scripts for Foundry environment in `test/foundry`. - GitHub Actions workflows for automated testing, linting, and security checks. - Documentation for getting started, usage, and contribution guidelines. @@ -39,4 +39,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - Initial commit with basic project structure. -- Setup of development environments for Solidity smart contract development using Foundry and Hardhat. +- Setup of development environments for Solidity smart contract development using Foundry. diff --git a/README.md b/README.md index 3bf95a3..0014a1f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![Biconomy](https://img.shields.io/badge/Made_with_%F0%9F%8D%8A_by-Biconomy-ff4e17?style=flat)](https://biconomy.io) [![License MIT](https://img.shields.io/badge/License-MIT-blue?&style=flat)](./LICENSE) [![Hardhat](https://img.shields.io/badge/Built%20with-Hardhat-FFDB1C.svg)](https://hardhat.org/) [![Foundry](https://img.shields.io/badge/Built%20with-Foundry-FFBD10.svg)](https://getfoundry.sh/) +[![Biconomy](https://img.shields.io/badge/Made_with_%F0%9F%8D%8A_by-Biconomy-ff4e17?style=flat)](https://biconomy.io) [![License MIT](https://img.shields.io/badge/License-MIT-blue?&style=flat)](./LICENSE) [![Foundry](https://img.shields.io/badge/Built%20with-Foundry-FFBD10.svg)](https://getfoundry.sh/) + -![Codecov Hardhat Coverage](https://img.shields.io/codecov/c/gh/bcnmy/sc-template?token=2BYDIFQ56W&flag=hardhat&label=Hardhat-coverage&logo=codecov) ![Codecov Foundry Coverage](https://img.shields.io/codecov/c/gh/bcnmy/sc-template?token=2BYDIFQ56W&flag=foundry&label=Foundry-coverage&logo=codecov) # Smart Contract Template Base ๐Ÿš€ @@ -23,14 +23,14 @@ This repository serves as a comprehensive foundation for smart contract projects ## Features - **Smart Contract Template Base**: A robust foundation for future smart contract projects. -- **Hardhat & Foundry Support**: Equipped with both Hardhat and Foundry tools and an adapted folder structure for seamless development. +- **Foundry Support**: Equipped with Foundry tools and an adapted folder structure for seamless development. - **Best Practices**: Adheres to industry best practices in smart contract programming to ensure code quality and security. - **Continuous Integration & Deployment**: Utilizes GitHub Actions for automated testing and deployment, ensuring code reliability. - **Strict Linting**: Implements Solhint based on the Solidity style guide, enhancing code quality and consistency. -- **Comprehensive Testing**: Includes a wide range of tests (unit, fuzz, fork) for both Foundry and Hardhat environments. +- **Comprehensive Testing**: Includes a wide range of tests (unit, fuzz, fork) for both Foundry environment. - **Environment Configuration**: Comes with `.env.example` for easy setup of API keys and environmental variables. - **Code Formatting**: Uses Prettier to maintain a consistent code style across the project. -- **Configurations for Foundry & Hardhat**: Provides essential settings and scripts for building, testing, and deployment, tailored for both development environments. +- **Configurations for Foundry**: Provides essential settings and scripts for building, testing, and deployment, tailored for both development environments. ## Getting Started @@ -63,7 +63,7 @@ Copy `.env.example` to `.env` and fill in your details. ## ๐Ÿ› ๏ธ Essential Scripts -Execute key operations for Foundry and Hardhat with these scripts. Append `:forge` or `:hardhat` to run them in the respective environment. +Execute key operations for Foundry with these scripts. Append `:forge` to run them in the respective environment. ### ๐Ÿ—๏ธ Build Contracts @@ -71,7 +71,7 @@ Execute key operations for Foundry and Hardhat with these scripts. Append `:forg yarn build ``` -Compiles contracts for both Foundry and Hardhat. +Compiles contracts for both Foundry. ### ๐Ÿงช Run Tests @@ -135,9 +135,9 @@ Automatically fixes linting problems found. yarn check ``` -To generate reports of the storage layout for potential upgrades safety using `hardhat-storage-layout`. -๐Ÿ”„ Add `:forge` or `:hardhat` to any script above to target only Foundry or Hardhat environment, respectively. + +๐Ÿ”„ Add `:forge` to any script above to target only Foundry ## ๐Ÿ”’ Security Audits diff --git a/hardhat.config.ts b/hardhat.config.ts deleted file mode 100644 index e139ab6..0000000 --- a/hardhat.config.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-toolbox"; -import "hardhat-storage-layout"; -import "@bonadocs/docgen"; - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.26", - settings: { - optimizer: { - enabled: true, - runs: 1000000, - details: { - yul: true, - }, - }, - viaIR: true, - }, - }, - docgen: { - projectName: "Biconomy Paymasters", - projectDescription: "Account Abstraction (v0.7.0) Paymasters", - }, -}; - -export default config; diff --git a/package.json b/package.json index a59cac8..5d8622f 100644 --- a/package.json +++ b/package.json @@ -8,22 +8,13 @@ }, "dependencies": { "@biconomy-devx/erc7579-msa": "^0.0.4", - "@openzeppelin/contracts": "^5.0.1", - "hardhat": "^2.20.1" + "@openzeppelin/contracts": "^5.0.1" }, "devDependencies": { "@bonadocs/docgen": "^1.0.1-alpha.1", "@ethersproject/abstract-provider": "^5.7.0", - "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", - "@nomicfoundation/hardhat-ethers": "^3.0.5", - "@nomicfoundation/hardhat-foundry": "^1.1.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.10", - "@nomicfoundation/hardhat-toolbox": "^4.0.0", - "@nomicfoundation/hardhat-verify": "^2.0.4", - "@nomiclabs/hardhat-ethers": "^2.2.3", "@prb/test": "^0.6.4", "@typechain/ethers-v6": "^0.5.1", - "@typechain/hardhat": "^9.1.0", "@types/chai": "^4.3.11", "@types/mocha": ">=10.0.6", "@types/node": ">=20.11.19", @@ -31,10 +22,6 @@ "chai": "^4.3.7", "codecov": "^3.8.3", "ethers": "^6.11.1", - "hardhat-deploy": "^0.11.45", - "hardhat-deploy-ethers": "^0.4.1", - "hardhat-gas-reporter": "^1.0.10", - "hardhat-storage-layout": "^0.1.7", "modulekit": "github:rhinestonewtf/modulekit", "prettier": "^3.2.5", "prettier-plugin-solidity": "^1.3.1", @@ -52,31 +39,22 @@ "ethereum", "forge", "foundry", - "hardhat", "smart-contracts", "solidity" ], "private": true, "scripts": { "clean:forge": "forge clean", - "clean:hardhat": "yarn hardhat clean", - "clean": "yarn run clean:forge && yarn run clean:hardhat && rm -rf cache docs coverage storageLayout coverage.json", + "clean": "yarn run clean:forge && rm -rf cache docs coverage storageLayout coverage.json", "build:forge": "forge build", - "build:hardhat": "yarn hardhat compile", - "build": "yarn run build:forge && yarn run build:hardhat", + "build": "yarn run build:forge", "test:forge": "forge test", - "test:hardhat": "yarn hardhat test", - "test": "yarn run test:hardhat && yarn run test:forge", + "test": "yarn run test:forge", "test:gas:forge": "forge test --gas-report", - "test:gas:hardhat": "REPORT_GAS=true hardhat test", - "test:gas": "yarn test:gas:hardhat && yarn test:gas:forge", + "test:gas": "yarn test:gas:forge", "coverage:forge": "forge coverage", - "coverage:hardhat": "yarn hardhat coverage", - "coverage": "yarn run coverage:forge && yarn run coverage:hardhat", - "coverage:report": "forge coverage --report lcov && genhtml lcov.info --branch-coverage --output-dir coverage/foundry && mv lcov.info coverage/foundry && yarn run coverage:hardhat", - "docs": "yarn hardhat docgen", - "check-storage": "yarn hardhat check", - "deploy:hardhat": "yarn hardhat run --network localhost scripts/typescript/deploy.ts", + "coverage": "yarn run coverage:forge", + "coverage:report": "forge coverage --report lcov && genhtml lcov.info --branch-coverage --output-dir coverage/foundry && mv lcov.info coverage/foundry", "deploy:forge": "forge script scripts/solidity/Deploy.s.sol --broadcast --rpc-url http://localhost:8545", "lint:sol": "yarn solhint 'contracts/**/*.sol' && forge fmt --check", "lint:sol-fix": "yarn prettier --write 'contracts/**/*.sol' && yarn solhint 'contracts/**/*.sol' --fix --noPrompt && forge fmt", diff --git a/test/hardhat/biconomy-sponsorship-paymaster-specs.ts b/test/hardhat/biconomy-sponsorship-paymaster-specs.ts deleted file mode 100644 index c3a48d4..0000000 --- a/test/hardhat/biconomy-sponsorship-paymaster-specs.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { ethers } from "hardhat"; -import { expect } from "chai"; -import { - AbiCoder, - AddressLike, - BytesLike, - Signer, - parseEther, - toBeHex, -} from "ethers"; -import { - EntryPoint, - EntryPoint__factory, - MockValidator, - MockValidator__factory, - SmartAccount, - SmartAccount__factory, - AccountFactory, - AccountFactory__factory, - BiconomySponsorshipPaymaster, - BiconomySponsorshipPaymaster__factory, -} from "../../typechain-types"; - -import { - DefaultsForUserOp, - fillAndSign, - fillSignAndPack, - packUserOp, - simulateValidation, -} from "./utils/userOpHelpers"; -import { parseValidationData } from "./utils/testUtils"; - -export const AddressZero = ethers.ZeroAddress; - -const MOCK_VALID_UNTIL = "0x00000000deadbeef"; -const MOCK_VALID_AFTER = "0x0000000000001234"; -const MARKUP = 1100000; -export const ENTRY_POINT_V7 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032"; - -const coder = AbiCoder.defaultAbiCoder(); - -export async function deployEntryPoint( - provider = ethers.provider, -): Promise { - const epf = await (await ethers.getContractFactory("EntryPoint")).deploy(); - // Retrieve the deployed contract bytecode - const deployedCode = await ethers.provider.getCode(await epf.getAddress()); - - // Use hardhat_setCode to set the contract code at the specified address - await ethers.provider.send("hardhat_setCode", [ENTRY_POINT_V7, deployedCode]); - - return epf.attach(ENTRY_POINT_V7) as EntryPoint; -} - -describe("EntryPoint with Biconomy Sponsorship Paymaster", function () { - let entryPoint: EntryPoint; - let depositorSigner: Signer; - let walletOwner: Signer; - let walletAddress: string, paymasterAddress: string; - let paymasterDepositorId: string; - let ethersSigner: Signer[]; - let offchainSigner: Signer, deployer: Signer, feeCollector: Signer; - let paymaster: BiconomySponsorshipPaymaster; - let smartWalletImp: SmartAccount; - let ecdsaModule: MockValidator; - let walletFactory: AccountFactory; - - beforeEach(async function () { - ethersSigner = await ethers.getSigners(); - entryPoint = await deployEntryPoint(); - - deployer = ethersSigner[0]; - offchainSigner = ethersSigner[1]; - depositorSigner = ethersSigner[2]; - feeCollector = ethersSigner[3]; - walletOwner = deployer; - - paymasterDepositorId = await depositorSigner.getAddress(); - - const offchainSignerAddress = await offchainSigner.getAddress(); - const walletOwnerAddress = await walletOwner.getAddress(); - const feeCollectorAddess = await feeCollector.getAddress(); - - ecdsaModule = await new MockValidator__factory(deployer).deploy(); - - paymaster = await new BiconomySponsorshipPaymaster__factory( - deployer, - ).deploy( - await deployer.getAddress(), - await entryPoint.getAddress(), - offchainSignerAddress, - feeCollectorAddess, - ); - - smartWalletImp = await new SmartAccount__factory(deployer).deploy(); - - walletFactory = await new AccountFactory__factory(deployer).deploy( - await smartWalletImp.getAddress(), - ); - - await walletFactory - .connect(deployer) - .addStake(86400, { value: parseEther("2") }); - - const smartAccountDeploymentIndex = 0; - - // Module initialization data, encoded - const moduleInstallData = ethers.solidityPacked( - ["address"], - [walletOwnerAddress], - ); - - await walletFactory.createAccount( - await ecdsaModule.getAddress(), - moduleInstallData, - smartAccountDeploymentIndex, - ); - - const expected = await walletFactory.getCounterFactualAddress( - await ecdsaModule.getAddress(), - moduleInstallData, - smartAccountDeploymentIndex, - ); - - walletAddress = expected; - - paymasterAddress = await paymaster.getAddress(); - - await paymaster - .connect(deployer) - .addStake(86400, { value: parseEther("2") }); - - await paymaster.depositFor(paymasterDepositorId, { - value: parseEther("1"), - }); - - await entryPoint.depositTo(paymasterAddress, { value: parseEther("1") }); - - await deployer.sendTransaction({ - to: expected, - value: parseEther("1"), - data: "0x", - }); - }); - - describe("Deployed Account : #validatePaymasterUserOp and #sendEmptySponsoredTx", () => { - it("succeed with valid signature", async () => { - const nonceKey = ethers.zeroPadBytes(await ecdsaModule.getAddress(), 24); - const userOp1 = await fillAndSign( - { - sender: walletAddress, - paymaster: paymasterAddress, - paymasterData: ethers.concat([ - ethers.zeroPadValue(paymasterDepositorId, 20), - ethers.zeroPadValue(toBeHex(MOCK_VALID_UNTIL), 6), - ethers.zeroPadValue(toBeHex(MOCK_VALID_AFTER), 6), - ethers.zeroPadValue(toBeHex(MARKUP), 4), - "0x" + "00".repeat(65), - ]), - paymasterPostOpGasLimit: 40_000, - }, - walletOwner, - entryPoint, - "getNonce", - nonceKey, - ); - const hash = await paymaster.getHash( - packUserOp(userOp1), - paymasterDepositorId, - MOCK_VALID_UNTIL, - MOCK_VALID_AFTER, - MARKUP, - ); - const sig = await offchainSigner.signMessage(ethers.getBytes(hash)); - const userOp = await fillSignAndPack( - { - ...userOp1, - paymaster: paymasterAddress, - paymasterData: ethers.concat([ - ethers.zeroPadValue(paymasterDepositorId, 20), - ethers.zeroPadValue(toBeHex(MOCK_VALID_UNTIL), 6), - ethers.zeroPadValue(toBeHex(MOCK_VALID_AFTER), 6), - ethers.zeroPadValue(toBeHex(MARKUP), 4), - sig, - ]), - paymasterPostOpGasLimit: 40_000, - }, - walletOwner, - entryPoint, - "getNonce", - nonceKey, - ); - // const parsedPnD = await paymaster.parsePaymasterAndData(userOp.paymasterAndData) - const res = await simulateValidation( - userOp, - await entryPoint.getAddress(), - ); - const validationData = parseValidationData( - res.returnInfo.paymasterValidationData, - ); - expect(validationData).to.eql({ - aggregator: AddressZero, - validAfter: parseInt(MOCK_VALID_AFTER), - validUntil: parseInt(MOCK_VALID_UNTIL), - }); - - await entryPoint.handleOps([userOp], await deployer.getAddress()); - }); - }); -}); diff --git a/test/hardhat/utils/deployment.ts b/test/hardhat/utils/deployment.ts deleted file mode 100644 index 18ebef0..0000000 --- a/test/hardhat/utils/deployment.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { BytesLike, HDNodeWallet, Signer } from "ethers"; -import { deployments, ethers } from "hardhat"; -import { - AccountFactory, - BiconomySponsorshipPaymaster, - EntryPoint, - MockValidator, - SmartAccount, -} from "../../../typechain-types"; -import { TASK_DEPLOY } from "hardhat-deploy"; -import { DeployResult } from "hardhat-deploy/dist/types"; - -export const ENTRY_POINT_V7 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032"; - -/** - * Generic function to deploy a contract using ethers.js. - * - * @param contractName The name of the contract to deploy. - * @param deployer The Signer object representing the deployer account. - * @returns A promise that resolves to the deployed contract instance. - */ -export async function deployContract( - contractName: string, - deployer: Signer, -): Promise { - const ContractFactory = await ethers.getContractFactory( - contractName, - deployer, - ); - const contract = await ContractFactory.deploy(); - await contract.waitForDeployment(); - return contract as T; -} - -/** - * Deploys the EntryPoint contract with a deterministic deployment. - * @returns A promise that resolves to the deployed EntryPoint contract instance. - */ -export async function getDeployedEntrypoint(): Promise { - const [deployer] = await ethers.getSigners(); - - // Deploy the contract normally to get its bytecode - const EntryPoint = await ethers.getContractFactory("EntryPoint"); - const entryPoint = await EntryPoint.deploy(); - await entryPoint.waitForDeployment(); - - // Retrieve the deployed contract bytecode - const deployedCode = await ethers.provider.getCode( - await entryPoint.getAddress(), - ); - - // Use hardhat_setCode to set the contract code at the specified address - await ethers.provider.send("hardhat_setCode", [ENTRY_POINT_V7, deployedCode]); - - return EntryPoint.attach(ENTRY_POINT_V7) as EntryPoint; -} - -/** - * Deploys the (MSA) Smart Account implementation contract with a deterministic deployment. - * @returns A promise that resolves to the deployed SA implementation contract instance. - */ -export async function getDeployedMSAImplementation(): Promise { - const accounts: Signer[] = await ethers.getSigners(); - const addresses = await Promise.all( - accounts.map((account) => account.getAddress()), - ); - - const SmartAccount = await ethers.getContractFactory("SmartAccount"); - const deterministicMSAImpl = await deployments.deploy("SmartAccount", { - from: addresses[0], - deterministicDeployment: true, - }); - - return SmartAccount.attach(deterministicMSAImpl.address) as SmartAccount; -} - -/** - * Deploys the AccountFactory contract with a deterministic deployment. - * @returns A promise that resolves to the deployed EntryPoint contract instance. - */ -export async function getDeployedAccountFactory( - implementationAddress: string, - // Note: this could be converted to dto so that additional args can easily be passed -): Promise { - const accounts: Signer[] = await ethers.getSigners(); - const addresses = await Promise.all( - accounts.map((account) => account.getAddress()), - ); - - const AccountFactory = await ethers.getContractFactory("AccountFactory"); - const deterministicAccountFactory = await deployments.deploy( - "AccountFactory", - { - from: addresses[0], - deterministicDeployment: true, - args: [implementationAddress], - }, - ); - - return AccountFactory.attach( - deterministicAccountFactory.address, - ) as AccountFactory; -} - -/** - * Deploys the MockValidator contract with a deterministic deployment. - * @returns A promise that resolves to the deployed MockValidator contract instance. - */ -export async function getDeployedMockValidator(): Promise { - const accounts: Signer[] = await ethers.getSigners(); - const addresses = await Promise.all( - accounts.map((account) => account.getAddress()), - ); - - const MockValidator = await ethers.getContractFactory("MockValidator"); - const deterministicMockValidator = await deployments.deploy("MockValidator", { - from: addresses[0], - deterministicDeployment: true, - }); - - return MockValidator.attach( - deterministicMockValidator.address, - ) as MockValidator; -} - -/** - * Deploys the MockValidator contract with a deterministic deployment. - * @returns A promise that resolves to the deployed MockValidator contract instance. - */ -export async function getDeployedSponsorshipPaymaster( - owner: string, - entryPoint: string, - verifyingSigner: string, - feeCollector: string, -): Promise { - const accounts: Signer[] = await ethers.getSigners(); - const addresses = await Promise.all( - accounts.map((account) => account.getAddress()), - ); - - const BiconomySponsorshipPaymaster = await ethers.getContractFactory( - "BiconomySponsorshipPaymaster", - ); - const deterministicSponsorshipPaymaster = await deployments.deploy( - "BiconomySponsorshipPaymaster", - { - from: addresses[0], - deterministicDeployment: true, - args: [owner, entryPoint, verifyingSigner, feeCollector], - }, - ); - - return BiconomySponsorshipPaymaster.attach( - deterministicSponsorshipPaymaster.address, - ) as BiconomySponsorshipPaymaster; -} diff --git a/test/hardhat/utils/general.ts b/test/hardhat/utils/general.ts deleted file mode 100644 index 7e9e596..0000000 --- a/test/hardhat/utils/general.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { BigNumberish } from "ethers"; -import { ethers } from "hardhat"; - -/** - * Encodes data using the defaultAbiCoder from ethers.AbiCoder. - * @param types The types of the values being encoded. - * @param values The values to encode. - * @returns The encoded data. - */ -export function encodeData(types: string[], values: any[]): string { - return ethers.AbiCoder.defaultAbiCoder().encode(types, values); -} - -/** - * Converts a regular string to a bytes32 string. - * - * @param text The regular string to convert. - * @returns The converted bytes32 string. - */ -export const toBytes32 = (text: string): string => { - return ethers.encodeBytes32String(text); -}; - -/** - * Converts a bytes32 string to a regular string. - * - * @param bytes32 The bytes32 string to convert. - * @returns The converted regular string. - */ -export const fromBytes32 = (bytes32: string): string => { - return ethers.decodeBytes32String(bytes32); -}; - -/** - * Converts a numeric value to its equivalent in 18 decimal places. - * @param value The numeric value to convert. - * @returns The equivalent value in 18 decimal places as a bigint. - */ -export const to18 = (value: BigNumberish): bigint => { - return ethers.parseUnits(value.toString(), 18); -}; - -/** - * Converts a value from 18 decimal places to a string representation. - * - * @param value The value to convert. - * @returns The string representation of the converted value. - */ -export const from18 = (value: bigint): string => { - return ethers.formatUnits(value, 18); -}; - -/** - * Converts the given amount to Gwei. - * @param amount - The amount to convert. - * @returns The converted amount in Gwei. - */ -export function toGwei(amount: BigNumberish): BigNumberish { - return ethers.parseUnits(amount.toString(), "gwei"); -} diff --git a/test/hardhat/utils/testUtils.ts b/test/hardhat/utils/testUtils.ts deleted file mode 100644 index abe1776..0000000 --- a/test/hardhat/utils/testUtils.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { - AbiCoder, - AddressLike, - BigNumberish, - Contract, - Interface, - dataSlice, - parseEther, - toBeHex, -} from "ethers"; -import { ethers } from "hardhat"; -import { EntryPoint__factory, IERC20 } from "../../../typechain-types"; - -// define mode and exec type enums -export const CALLTYPE_SINGLE = "0x00"; // 1 byte -export const CALLTYPE_BATCH = "0x01"; // 1 byte -export const EXECTYPE_DEFAULT = "0x00"; // 1 byte -export const EXECTYPE_TRY = "0x01"; // 1 byte -export const EXECTYPE_DELEGATE = "0xFF"; // 1 byte -export const MODE_DEFAULT = "0x00000000"; // 4 bytes -export const UNUSED = "0x00000000"; // 4 bytes -export const MODE_PAYLOAD = "0x00000000000000000000000000000000000000000000"; // 22 bytes - -export const AddressZero = ethers.ZeroAddress; -export const HashZero = ethers.ZeroHash; -export const ONE_ETH = parseEther("1"); -export const TWO_ETH = parseEther("2"); -export const FIVE_ETH = parseEther("5"); -export const maxUint48 = 2 ** 48 - 1; - -export const tostr = (x: any): string => (x != null ? x.toString() : "null"); - -const coder = AbiCoder.defaultAbiCoder(); - -export interface ValidationData { - aggregator: string; - validAfter: number; - validUntil: number; -} - -export const panicCodes: { [key: number]: string } = { - // from https://docs.soliditylang.org/en/v0.8.0/control-structures.html - 0x01: "assert(false)", - 0x11: "arithmetic overflow/underflow", - 0x12: "divide by zero", - 0x21: "invalid enum value", - 0x22: "storage byte array that is incorrectly encoded", - 0x31: ".pop() on an empty array.", - 0x32: "array sout-of-bounds or negative index", - 0x41: "memory overflow", - 0x51: "zero-initialized variable of internal function type", -}; -export const Erc20 = [ - "function transfer(address _receiver, uint256 _value) public returns (bool success)", - "function transferFrom(address, address, uint256) public returns (bool)", - "function approve(address _spender, uint256 _value) public returns (bool success)", - "function allowance(address _owner, address _spender) public view returns (uint256 remaining)", - "function balanceOf(address _owner) public view returns (uint256 balance)", - "event Approval(address indexed _owner, address indexed _spender, uint256 _value)", -]; - -export const Erc20Interface = new ethers.Interface(Erc20); - -export const encodeTransfer = ( - target: string, - amount: string | number, -): string => { - return Erc20Interface.encodeFunctionData("transfer", [target, amount]); -}; - -export const encodeTransferFrom = ( - from: string, - target: string, - amount: string | number, -): string => { - return Erc20Interface.encodeFunctionData("transferFrom", [ - from, - target, - amount, - ]); -}; - -// rethrow "cleaned up" exception. -// - stack trace goes back to method (or catch) line, not inner provider -// - attempt to parse revert data (needed for geth) -// use with ".catch(rethrow())", so that current source file/line is meaningful. -export function rethrow(): (e: Error) => void { - const callerStack = new Error() - .stack!.replace(/Error.*\n.*at.*\n/, "") - .replace(/.*at.* \(internal[\s\S]*/, ""); - - if (arguments[0] != null) { - throw new Error("must use .catch(rethrow()), and NOT .catch(rethrow)"); - } - return function (e: Error) { - const solstack = e.stack!.match(/((?:.* at .*\.sol.*\n)+)/); - const stack = (solstack != null ? solstack[1] : "") + callerStack; - // const regex = new RegExp('error=.*"data":"(.*?)"').compile() - const found = /error=.*?"data":"(.*?)"/.exec(e.message); - let message: string; - if (found != null) { - const data = found[1]; - message = - decodeRevertReason(data) ?? e.message + " - " + data.slice(0, 100); - } else { - message = e.message; - } - const err = new Error(message); - err.stack = "Error: " + message + "\n" + stack; - throw err; - }; -} - -const decodeRevertReasonContracts = new Interface([ - ...EntryPoint__factory.createInterface().fragments, - "error ECDSAInvalidSignature()", -]); // .filter(f => f.type === 'error')) - -export function decodeRevertReason( - data: string | Error, - nullIfNoMatch = true, -): string | null { - if (typeof data !== "string") { - const err = data as any; - data = (err.data ?? err.error?.data) as string; - if (typeof data !== "string") throw err; - } - - const methodSig = data.slice(0, 10); - const dataParams = "0x" + data.slice(10); - - // can't add Error(string) to xface... - if (methodSig === "0x08c379a0") { - const [err] = coder.decode(["string"], dataParams); - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - return `Error(${err})`; - } else if (methodSig === "0x4e487b71") { - const [code] = coder.decode(["uint256"], dataParams); - return `Panic(${panicCodes[code] ?? code} + ')`; - } - - try { - const err = decodeRevertReasonContracts.parseError(data); - // treat any error "bytes" argument as possible error to decode (e.g. FailedOpWithRevert, PostOpReverted) - const args = err!.args.map((arg: any, index) => { - switch (err?.fragment.inputs[index].type) { - case "bytes": - return decodeRevertReason(arg); - case "string": - return `"${arg as string}"`; - default: - return arg; - } - }); - return `${err!.name}(${args.join(",")})`; - } catch (e) { - // throw new Error('unsupported errorSig ' + data) - if (!nullIfNoMatch) { - return data; - } - return null; - } -} - -export function tonumber(x: any): number { - try { - return parseFloat(x.toString()); - } catch (e: any) { - console.log("=== failed to parseFloat:", x, e.message); - return NaN; - } -} - -// just throw 1eth from account[0] to the given address (or contract instance) -export async function fund( - contractOrAddress: string | Contract, - amountEth = "1", -): Promise { - let address: string; - if (typeof contractOrAddress === "string") { - address = contractOrAddress; - } else { - address = await contractOrAddress.getAddress(); - } - const [firstSigner] = await ethers.getSigners(); - await firstSigner.sendTransaction({ - to: address, - value: parseEther(amountEth), - }); -} - -export async function getBalance(address: string): Promise { - const balance = await ethers.provider.getBalance(address); - return parseInt(balance.toString()); -} - -export async function getTokenBalance( - token: IERC20, - address: string, -): Promise { - const balance = await token.balanceOf(address); - return parseInt(balance.toString()); -} - -export async function isDeployed(addr: string): Promise { - const code = await ethers.provider.getCode(addr); - return code.length > 2; -} - -// Getting initcode for AccountFactory which accepts one validator (with ECDSA owner required for installation) -export async function getInitCode( - ownerAddress: AddressLike, - factoryAddress: AddressLike, - validatorAddress: AddressLike, - saDeploymentIndex: number = 0, -): Promise { - const AccountFactory = await ethers.getContractFactory("AccountFactory"); - const moduleInstallData = ethers.solidityPacked(["address"], [ownerAddress]); - - // Encode the createAccount function call with the provided parameters - const factoryDeploymentData = AccountFactory.interface - .encodeFunctionData("createAccount", [ - validatorAddress, - moduleInstallData, - saDeploymentIndex, - ]) - .slice(2); - - return factoryAddress + factoryDeploymentData; -} - -export function callDataCost(data: string): number { - return ethers - .getBytes(data) - .map((x) => (x === 0 ? 4 : 16)) - .reduce((sum, x) => sum + x); -} - -export function parseValidationData( - validationData: BigNumberish, -): ValidationData { - const data = ethers.zeroPadValue(toBeHex(validationData), 32); - - // string offsets start from left (msb) - const aggregator = dataSlice(data, 32 - 20); - let validUntil = parseInt(dataSlice(data, 32 - 26, 32 - 20)); - if (validUntil === 0) { - validUntil = maxUint48; - } - const validAfter = parseInt(dataSlice(data, 0, 6)); - - return { - aggregator, - validAfter, - validUntil, - }; -} diff --git a/test/hardhat/utils/types.ts b/test/hardhat/utils/types.ts deleted file mode 100644 index 7dd52fa..0000000 --- a/test/hardhat/utils/types.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { AddressLike, BigNumberish, BytesLike } from "ethers"; - -export interface UserOperation { - sender: AddressLike; // Or string - nonce?: BigNumberish; - initCode?: BytesLike; - callData?: BytesLike; - callGasLimit?: BigNumberish; - verificationGasLimit?: BigNumberish; - preVerificationGas?: BigNumberish; - maxFeePerGas?: BigNumberish; - maxPriorityFeePerGas?: BigNumberish; - paymaster?: AddressLike; // Or string - paymasterVerificationGasLimit?: BigNumberish; - paymasterPostOpGasLimit?: BigNumberish; - paymasterData?: BytesLike; - signature?: BytesLike; -} - -export interface PackedUserOperation { - sender: AddressLike; // Or string - nonce: BigNumberish; - initCode: BytesLike; - callData: BytesLike; - accountGasLimits: BytesLike; - preVerificationGas: BigNumberish; - gasFees: BytesLike; - paymasterAndData: BytesLike; - signature: BytesLike; -} diff --git a/test/hardhat/utils/userOpHelpers.ts b/test/hardhat/utils/userOpHelpers.ts deleted file mode 100644 index 50fccd5..0000000 --- a/test/hardhat/utils/userOpHelpers.ts +++ /dev/null @@ -1,486 +0,0 @@ -import { ethers } from "hardhat"; -import { - EntryPoint, - EntryPointSimulations__factory, - IEntryPointSimulations, -} from "../../../typechain-types"; -import { PackedUserOperation, UserOperation } from "./types"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { TransactionRequest } from "@ethersproject/abstract-provider"; -import { - AbiCoder, - BigNumberish, - BytesLike, - Contract, - Signer, - dataSlice, - keccak256, - toBeHex, -} from "ethers"; -import { toGwei } from "./general"; -import { callDataCost, decodeRevertReason, rethrow } from "./testUtils"; -import EntryPointSimulationsJson from "../../../artifacts/account-abstraction/contracts/core/EntryPointSimulations.sol/EntryPointSimulations.json"; - -const AddressZero = ethers.ZeroAddress; -const coder = AbiCoder.defaultAbiCoder(); - -export function packUserOp(userOp: UserOperation): PackedUserOperation { - const { - sender, - nonce, - initCode = "0x", - callData = "0x", - callGasLimit = 1_500_000, - verificationGasLimit = 1_500_000, - preVerificationGas = 2_000_000, - maxFeePerGas = toGwei("20"), - maxPriorityFeePerGas = toGwei("10"), - paymaster = ethers.ZeroAddress, - paymasterData = "0x", - paymasterVerificationGasLimit = 3_00_000, - paymasterPostOpGasLimit = 0, - signature = "0x", - } = userOp; - - const accountGasLimits = packAccountGasLimits( - verificationGasLimit, - callGasLimit, - ); - const gasFees = packAccountGasLimits(maxPriorityFeePerGas, maxFeePerGas); - let paymasterAndData = "0x"; - if (paymaster.toString().length >= 20 && paymaster !== ethers.ZeroAddress) { - paymasterAndData = packPaymasterData( - userOp.paymaster as string, - paymasterVerificationGasLimit, - paymasterPostOpGasLimit, - paymasterData as string, - ) as string; - } - return { - sender: userOp.sender, - nonce: userOp.nonce || 0, - callData: userOp.callData || "0x", - accountGasLimits, - initCode: userOp.initCode || "0x", - preVerificationGas: userOp.preVerificationGas || 50000, - gasFees, - paymasterAndData, - signature: userOp.signature || "0x", - }; -} - -export function encodeUserOp( - userOp: UserOperation, - forSignature = true, -): string { - const packedUserOp = packUserOp(userOp); - if (forSignature) { - return coder.encode( - [ - "address", - "uint256", - "bytes32", - "bytes32", - "bytes32", - "uint256", - "bytes32", - "bytes32", - ], - [ - packedUserOp.sender, - packedUserOp.nonce, - keccak256(packedUserOp.initCode), - keccak256(packedUserOp.callData), - packedUserOp.accountGasLimits, - packedUserOp.preVerificationGas, - packedUserOp.gasFees, - keccak256(packedUserOp.paymasterAndData), - ], - ); - } else { - // for the purpose of calculating gas cost encode also signature (and no keccak of bytes) - return coder.encode( - [ - "address", - "uint256", - "bytes", - "bytes", - "bytes32", - "uint256", - "bytes32", - "bytes", - "bytes", - ], - [ - packedUserOp.sender, - packedUserOp.nonce, - packedUserOp.initCode, - packedUserOp.callData, - packedUserOp.accountGasLimits, - packedUserOp.preVerificationGas, - packedUserOp.gasFees, - packedUserOp.paymasterAndData, - packedUserOp.signature, - ], - ); - } -} - -// Can be moved to testUtils -export function packPaymasterData( - paymaster: string, - paymasterVerificationGasLimit: BigNumberish, - postOpGasLimit: BigNumberish, - paymasterData: BytesLike, -): BytesLike { - return ethers.concat([ - paymaster, - ethers.zeroPadValue(toBeHex(Number(paymasterVerificationGasLimit)), 16), - ethers.zeroPadValue(toBeHex(Number(postOpGasLimit)), 16), - paymasterData, - ]); -} - -// Can be moved to testUtils -export function packAccountGasLimits( - verificationGasLimit: BigNumberish, - callGasLimit: BigNumberish, -): string { - return ethers.concat([ - ethers.zeroPadValue(toBeHex(Number(verificationGasLimit)), 16), - ethers.zeroPadValue(toBeHex(Number(callGasLimit)), 16), - ]); -} - -// Can be moved to testUtils -export function unpackAccountGasLimits(accountGasLimits: string): { - verificationGasLimit: number; - callGasLimit: number; -} { - return { - verificationGasLimit: parseInt(accountGasLimits.slice(2, 34), 16), - callGasLimit: parseInt(accountGasLimits.slice(34), 16), - }; -} - -export function getUserOpHash( - op: UserOperation, - entryPoint: string, - chainId: number, -): string { - const userOpHash = keccak256(encodeUserOp(op, true)); - const enc = coder.encode( - ["bytes32", "address", "uint256"], - [userOpHash, entryPoint, chainId], - ); - return keccak256(enc); -} - -export const DefaultsForUserOp: UserOperation = { - sender: AddressZero, - nonce: 0, - initCode: "0x", - callData: "0x", - callGasLimit: 0, - verificationGasLimit: 150000, // default verification gas. will add create2 cost (3200+200*length) if initCode exists - preVerificationGas: 21000, // should also cover calldata cost. - maxFeePerGas: 0, - maxPriorityFeePerGas: 1e9, - paymaster: AddressZero, - paymasterData: "0x", - paymasterVerificationGasLimit: 3e5, - paymasterPostOpGasLimit: 0, - signature: "0x", -}; - -// Different compared to infinitism utils -export async function signUserOp( - op: UserOperation, - signer: Signer, - entryPoint: string, - chainId: number, -): Promise { - const message = getUserOpHash(op, entryPoint, chainId); - - const signature = await signer.signMessage(ethers.getBytes(message)); - - return { - ...op, - signature: signature, - }; -} - -export function fillUserOpDefaults( - op: Partial, - defaults = DefaultsForUserOp, -): UserOperation { - const partial: any = { ...op }; - // we want "item:undefined" to be used from defaults, and not override defaults, so we must explicitly - // remove those so "merge" will succeed. - for (const key in partial) { - if (partial[key] == null) { - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete partial[key]; - } - } - const filled = { ...defaults, ...partial }; - return filled; -} - -// helper to fill structure: -// - default callGasLimit to estimate call from entryPoint to account (TODO: add overhead) -// if there is initCode: -// - calculate sender by eth_call the deployment code -// - default verificationGasLimit estimateGas of deployment code plus default 100000 -// no initCode: -// - update nonce from account.getNonce() -// entryPoint param is only required to fill in "sender address when specifying "initCode" -// nonce: assume contract as "getNonce()" function, and fill in. -// sender - only in case of construction: fill sender from initCode. -// callGasLimit: VERY crude estimation (by estimating call to account, and add rough entryPoint overhead -// verificationGasLimit: hard-code default at 100k. should add "create2" cost -export async function fillUserOp( - op: Partial, - entryPoint?: EntryPoint, - getNonceFunction = "getNonce", - nonceKey = "0", -): Promise { - const op1 = { ...op }; - const provider = ethers.provider; - if (op.initCode != null && op.initCode !== "0x") { - const initAddr = dataSlice(op1.initCode!, 0, 20); - const initCallData = dataSlice(op1.initCode!, 20); - if (op1.nonce == null) op1.nonce = 0; - if (op1.sender == null) { - if (provider == null) throw new Error("no entrypoint/provider"); - op1.sender = await entryPoint! - .getSenderAddress(op1.initCode!) - .catch((e) => e.errorArgs.sender); - } - if (op1.verificationGasLimit == null) { - if (provider == null) throw new Error("no entrypoint/provider"); - const initEstimate = await provider.estimateGas({ - from: await entryPoint?.getAddress(), - to: initAddr, - data: initCallData, - gasLimit: 10e6, - }); - op1.verificationGasLimit = - Number(DefaultsForUserOp.verificationGasLimit!) + Number(initEstimate); - } - } - if (op1.nonce == null) { - // TODO: nonce should be fetched from entrypoint based on key - // if (provider == null) throw new Error('must have entryPoint to autofill nonce') - // const c = new Contract(op.sender! as string, [`function ${getNonceFunction}() view returns(uint256)`], provider) - // op1.nonce = await c[getNonceFunction]().catch(rethrow()) - const nonce = await entryPoint?.getNonce(op1.sender!, nonceKey); - op1.nonce = nonce ?? 0n; - } - if (op1.callGasLimit == null && op.callData != null) { - if (provider == null) - throw new Error("must have entryPoint for callGasLimit estimate"); - const gasEtimated = await provider.estimateGas({ - from: await entryPoint?.getAddress(), - to: op1.sender, - data: op1.callData as string, - }); - - // console.log('estim', op1.sender,'len=', op1.callData!.length, 'res=', gasEtimated) - // estimateGas assumes direct call from entryPoint. add wrapper cost. - op1.callGasLimit = gasEtimated; // .add(55000) - } - if (op1.paymaster != null) { - if (op1.paymasterVerificationGasLimit == null) { - op1.paymasterVerificationGasLimit = - DefaultsForUserOp.paymasterVerificationGasLimit; - } - if (op1.paymasterPostOpGasLimit == null) { - op1.paymasterPostOpGasLimit = DefaultsForUserOp.paymasterPostOpGasLimit; - } - } - if (op1.maxFeePerGas == null) { - if (provider == null) - throw new Error("must have entryPoint to autofill maxFeePerGas"); - const block = await provider.getBlock("latest"); - op1.maxFeePerGas = - Number(block!.baseFeePerGas!) + - Number( - op1.maxPriorityFeePerGas ?? DefaultsForUserOp.maxPriorityFeePerGas, - ); - } - // TODO: this is exactly what fillUserOp below should do - but it doesn't. - // adding this manually - if (op1.maxPriorityFeePerGas == null) { - op1.maxPriorityFeePerGas = DefaultsForUserOp.maxPriorityFeePerGas; - } - const op2 = fillUserOpDefaults(op1); - // if(op2 === undefined || op2 === null) { - // throw new Error('op2 is undefined or null') - // } - // eslint-disable-next-line @typescript-eslint/no-base-to-string - if (op2?.preVerificationGas?.toString() === "0") { - // TODO: we don't add overhead, which is ~21000 for a single TX, but much lower in a batch. - op2.preVerificationGas = callDataCost(encodeUserOp(op2, false)); - } - return op2; -} - -export async function fillAndPack( - op: Partial, - entryPoint?: EntryPoint, - getNonceFunction = "getNonce", -): Promise { - const userOp = await fillUserOp(op, entryPoint, getNonceFunction); - if (userOp === undefined) { - throw new Error("userOp is undefined"); - } - return packUserOp(userOp); -} - -export async function fillAndSign( - op: Partial, - signer: Signer | Signer, - entryPoint?: EntryPoint, - getNonceFunction = "getNonce", - nonceKey = "0", -): Promise { - const provider = ethers.provider; - const op2 = await fillUserOp(op, entryPoint, getNonceFunction, nonceKey); - if (op2 === undefined) { - throw new Error("op2 is undefined"); - } - - const chainId = await provider!.getNetwork().then((net) => net.chainId); - const message = ethers.getBytes( - getUserOpHash(op2, await entryPoint!.getAddress(), Number(chainId)), - ); - - let signature; - try { - signature = await signer.signMessage(message); - } catch (err: any) { - // attempt to use 'eth_sign' instead of 'personal_sign' which is not supported by Foundry Anvil - signature = await (signer as any)._legacySignMessage(message); - } - return { - ...op2, - signature, - }; -} - -export async function fillSignAndPack( - op: Partial, - signer: Signer | Signer, - entryPoint?: EntryPoint, - getNonceFunction = "getNonce", - nonceKey = "0", -): Promise { - const filledAndSignedOp = await fillAndSign( - op, - signer, - entryPoint, - getNonceFunction, - nonceKey, - ); - return packUserOp(filledAndSignedOp); -} - -/** - * This function relies on a "state override" functionality of the 'eth_call' RPC method - * in order to provide the details of a simulated validation call to the bundler - * @param userOp - * @param entryPointAddress - * @param txOverrides - */ -export async function simulateValidation( - userOp: PackedUserOperation, - entryPointAddress: string, - txOverrides?: any, -): Promise { - const entryPointSimulations = - EntryPointSimulations__factory.createInterface(); - const data = entryPointSimulations.encodeFunctionData("simulateValidation", [ - userOp, - ]); - const tx: TransactionRequest = { - to: entryPointAddress, - data, - ...txOverrides, - }; - const stateOverride = { - [entryPointAddress]: { - code: EntryPointSimulationsJson.deployedBytecode, - }, - }; - try { - const simulationResult = await ethers.provider.send("eth_call", [ - tx, - "latest", - stateOverride, - ]); - const res = entryPointSimulations.decodeFunctionResult( - "simulateValidation", - simulationResult, - ); - // note: here collapsing the returned "tuple of one" into a single value - will break for returning actual tuples - return res[0]; - } catch (error: any) { - const revertData = error?.data; - if (revertData != null) { - // note: this line throws the revert reason instead of returning it - entryPointSimulations.decodeFunctionResult( - "simulateValidation", - revertData, - ); - } - throw error; - } -} - -// TODO: this code is very much duplicated but "encodeFunctionData" is based on 20 overloads -// TypeScript is not able to resolve overloads with variables: https://github.com/microsoft/TypeScript/issues/14107 -export async function simulateHandleOp( - userOp: PackedUserOperation, - target: string, - targetCallData: string, - entryPointAddress: string, - txOverrides?: any, -): Promise { - const entryPointSimulations = - EntryPointSimulations__factory.createInterface(); - const data = entryPointSimulations.encodeFunctionData("simulateHandleOp", [ - userOp, - target, - targetCallData, - ]); - const tx: TransactionRequest = { - to: entryPointAddress, - data, - ...txOverrides, - }; - const stateOverride = { - [entryPointAddress]: { - code: EntryPointSimulationsJson.deployedBytecode, - }, - }; - try { - const simulationResult = await ethers.provider.send("eth_call", [ - tx, - "latest", - stateOverride, - ]); - const res = entryPointSimulations.decodeFunctionResult( - "simulateHandleOp", - simulationResult, - ); - // note: here collapsing the returned "tuple of one" into a single value - will break for returning actual tuples - return res[0]; - } catch (error: any) { - const err = decodeRevertReason(error); - if (err != null) { - throw new Error(err); - } - throw error; - } -}