From b0e2e5b55673d6610b7ce94bac7745292e378265 Mon Sep 17 00:00:00 2001 From: Alex Forshtat Date: Sat, 6 Apr 2024 21:54:24 +0200 Subject: [PATCH 1/6] Write contract names and transaction hashes to a JSON file; skip irrelevant --- gascalc/1-simple-wallet.gas.ts | 9 +++++-- gascalc/2-paymaster.gas.ts | 2 +- gascalc/3-huge-tx-gas.ts | 2 +- gascalc/4-paymaster-postop.gas.ts | 2 +- gascalc/5-token-paymaster.gas.ts | 2 +- gascalc/GasChecker.ts | 43 ++++++++++++++++++++++++++----- 6 files changed, 48 insertions(+), 12 deletions(-) diff --git a/gascalc/1-simple-wallet.gas.ts b/gascalc/1-simple-wallet.gas.ts index 0943d1f0d..0d80d9330 100644 --- a/gascalc/1-simple-wallet.gas.ts +++ b/gascalc/1-simple-wallet.gas.ts @@ -1,15 +1,20 @@ -import { GasChecker } from './GasChecker' +import { GasCheckCollector, GasChecker } from './GasChecker' context('simple account', function () { this.timeout(60000) const g = new GasChecker() + before(async function () { + await GasCheckCollector.init() + GasCheckCollector.inst.createJsonResult = true + }) + it('simple 1', async function () { await g.addTestRow({ title: 'simple', count: 1, diffLastGas: false }) await g.addTestRow({ title: 'simple - diff from previous', count: 2, diffLastGas: true }) }) - it('simple 10', async function () { + it.skip('simple 10', async function () { if (g.skipLong()) this.skip() await g.addTestRow({ title: 'simple', count: 10, diffLastGas: false }) await g.addTestRow({ title: 'simple - diff from previous', count: 11, diffLastGas: true }) diff --git a/gascalc/2-paymaster.gas.ts b/gascalc/2-paymaster.gas.ts index 89a5d3dc9..7f10eaaf1 100644 --- a/gascalc/2-paymaster.gas.ts +++ b/gascalc/2-paymaster.gas.ts @@ -7,7 +7,7 @@ import { hexValue } from '@ethersproject/bytes' const ethersSigner = ethers.provider.getSigner() -context('Minimal Paymaster', function () { +context.skip('Minimal Paymaster', function () { this.timeout(60000) const g = new GasChecker() diff --git a/gascalc/3-huge-tx-gas.ts b/gascalc/3-huge-tx-gas.ts index ae762478e..f1c3bde08 100644 --- a/gascalc/3-huge-tx-gas.ts +++ b/gascalc/3-huge-tx-gas.ts @@ -1,6 +1,6 @@ import { DefaultGasTestInfo, GasChecker } from './GasChecker' -context('huge tx - 5k', function () { +context.skip('huge tx - 5k', function () { this.timeout(60000) const huge = DefaultGasTestInfo.destCallData!.padEnd(10240, 'f') const g = new GasChecker() diff --git a/gascalc/4-paymaster-postop.gas.ts b/gascalc/4-paymaster-postop.gas.ts index c7495dc72..6eb93a683 100644 --- a/gascalc/4-paymaster-postop.gas.ts +++ b/gascalc/4-paymaster-postop.gas.ts @@ -7,7 +7,7 @@ import { hexValue } from '@ethersproject/bytes' const ethersSigner = ethers.provider.getSigner() -context('Paymaster with PostOp', function () { +context.skip('Paymaster with PostOp', function () { this.timeout(60000) const g = new GasChecker() diff --git a/gascalc/5-token-paymaster.gas.ts b/gascalc/5-token-paymaster.gas.ts index b9505882c..6d44bb3c5 100644 --- a/gascalc/5-token-paymaster.gas.ts +++ b/gascalc/5-token-paymaster.gas.ts @@ -17,7 +17,7 @@ import { BigNumber } from 'ethers' import { createAccountOwner } from '../test/testutils' // const ethersSigner = ethers.provider.getSigner() -context('Token Paymaster', function () { +context.skip('Token Paymaster', function () { this.timeout(60000) const g = new GasChecker() diff --git a/gascalc/GasChecker.ts b/gascalc/GasChecker.ts index 45a21ff4e..9ef3b9a56 100644 --- a/gascalc/GasChecker.ts +++ b/gascalc/GasChecker.ts @@ -129,7 +129,11 @@ export class GasChecker { defaultAbiCoder.encode(['address'], [this.entryPoint().address]) ]), 0, 2885201) console.log('factaddr', factoryAddress) + GasCheckCollector.inst.setContractName(factoryAddress, 'SimpleAccountFactory') const fact = SimpleAccountFactory__factory.connect(factoryAddress, ethersSigner) + + const implAddress = await fact.accountImplementation() + GasCheckCollector.inst.setContractName(implAddress, 'SimpleAccount') // create accounts const creationOps: PackedUserOperation[] = [] for (const n of range(count)) { @@ -157,6 +161,7 @@ export class GasChecker { this.accounts[addr] = this.accountOwner // deploy if not already deployed. await fact.createAccount(this.accountOwner.address, salt) + GasCheckCollector.inst.setContractName(addr, 'ERC1967Proxy') const accountBalance = await GasCheckCollector.inst.entryPoint.balanceOf(addr) if (accountBalance.lte(minDepositOrBalance)) { await GasCheckCollector.inst.entryPoint.depositTo(addr, { value: minDepositOrBalance.mul(5) }) @@ -200,6 +205,7 @@ export class GasChecker { dest = account } else if (dest === 'random') { dest = createAddress() + GasCheckCollector.inst.setContractName(dest, '!EOA!') const destBalance = await getBalance(dest) if (destBalance.eq(0)) { console.log('dest replenish', dest) @@ -276,8 +282,8 @@ export class GasChecker { count: info.count, gasUsed, accountEst, - title: info.title - // receipt: rcpt + title: info.title, + receipt: rcpt } if (info.diffLastGas) { ret1.gasDiff = gasDiff @@ -305,6 +311,13 @@ export class GasCheckCollector { static initPromise?: Promise entryPoint: EntryPoint + createJsonResult: boolean = false + readonly contracts = new Map() + readonly txHashes: string[] = [] + + setContractName (address: string, name: string): void { + this.contracts.set(address.toLowerCase(), name) + } static async init (): Promise { if (this.inst == null) { @@ -318,6 +331,7 @@ export class GasCheckCollector { async _init (entryPointAddressOrTest: string = 'test'): Promise { console.log('signer=', await ethersSigner.getAddress()) DefaultGasTestInfo.beneficiary = createAddress() + this.setContractName(DefaultGasTestInfo.beneficiary, '!EOA! (beneficiary)') const bal = await getBalance(ethersSigner.getAddress()) if (bal.gt(parseEther('100000000'))) { @@ -331,14 +345,16 @@ export class GasCheckCollector { } else { this.entryPoint = EntryPoint__factory.connect(entryPointAddressOrTest, ethersSigner) } + this.setContractName(this.entryPoint.address, 'EntryPoint') const tableHeaders = [ 'handleOps description ', 'count', 'total gasUsed', - 'per UserOp gas\n(delta for\none UserOp)', + // 'per UserOp gas\n(delta for\none UserOp)', // 'account.exec()\nestimateGas', - 'per UserOp overhead\n(compared to\naccount.exec())' + // 'per UserOp overhead\n(compared to\naccount.exec())', + 'transaction hash' ] this.initTable(tableHeaders) @@ -390,9 +406,21 @@ export class GasCheckCollector { const tableOutput = table(this.tabRows, this.tableConfig) write(tableOutput) + if (this.createJsonResult) { + this.writeResultInJson() + } // process.exit(0) } + writeResultInJson (): void { + const res = { + contracts: Object.fromEntries(this.contracts.entries()), + transactions: this.txHashes + } + + fs.writeFileSync(`gas-checker-result-${Date.now()}.json`, JSON.stringify(res)) + } + addRow (res: GasTestResult): void { const gasUsed = res.gasDiff != null ? '' : res.gasUsed // hide "total gasUsed" if there is a diff const perOp = res.gasDiff != null ? res.gasDiff - res.accountEst : '' @@ -401,9 +429,12 @@ export class GasCheckCollector { res.title, res.count, gasUsed, - res.gasDiff ?? '', + // res.gasDiff ?? '', // res.accountEst, - perOp]) + // perOp, + res.receipt?.transactionHash]) + + this.txHashes.push(res.receipt!.transactionHash) } } From 8e4c11898febe3746896f3fcb27e156872518f74 Mon Sep 17 00:00:00 2001 From: Alex Forshtat Date: Sat, 6 Apr 2024 22:12:20 +0200 Subject: [PATCH 2/6] Print "gas used" for all rows --- gascalc/GasChecker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gascalc/GasChecker.ts b/gascalc/GasChecker.ts index 9ef3b9a56..34a65cc56 100644 --- a/gascalc/GasChecker.ts +++ b/gascalc/GasChecker.ts @@ -422,7 +422,8 @@ export class GasCheckCollector { } addRow (res: GasTestResult): void { - const gasUsed = res.gasDiff != null ? '' : res.gasUsed // hide "total gasUsed" if there is a diff + // const gasUsed = res.gasDiff != null ? '' : res.gasUsed // hide "total gasUsed" if there is a diff + const gasUsed = res.gasUsed const perOp = res.gasDiff != null ? res.gasDiff - res.accountEst : '' this.tabRows.push([ From 525b8aebc0bc79e2103c353cf6353128b033079d Mon Sep 17 00:00:00 2001 From: Alex Forshtat Date: Sat, 6 Apr 2024 23:57:43 +0200 Subject: [PATCH 3/6] Switch "2-paymaster.gas.ts" test to Verifying Paymaster instead of Aceept-All --- gascalc/2-paymaster.gas.ts | 38 ++++++++++++++++++++++++-------------- gascalc/GasChecker.ts | 36 +++++++++++++++++++++++++----------- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/gascalc/2-paymaster.gas.ts b/gascalc/2-paymaster.gas.ts index 7f10eaaf1..1efd08faf 100644 --- a/gascalc/2-paymaster.gas.ts +++ b/gascalc/2-paymaster.gas.ts @@ -1,35 +1,45 @@ import { parseEther } from 'ethers/lib/utils' -import { TestPaymasterAcceptAll__factory } from '../typechain' +import { VerifyingPaymaster, VerifyingPaymaster__factory } from '../typechain' import { ethers } from 'hardhat' -import { GasChecker } from './GasChecker' -import { Create2Factory } from '../src/Create2Factory' -import { hexValue } from '@ethersproject/bytes' +import { GasCheckCollector, GasChecker } from './GasChecker' const ethersSigner = ethers.provider.getSigner() -context.skip('Minimal Paymaster', function () { +context('Verifying Paymaster', function () { this.timeout(60000) const g = new GasChecker() - let paymasterAddress: string + let paymaster: VerifyingPaymaster + before(async () => { - const paymasterInit = hexValue(new TestPaymasterAcceptAll__factory(ethersSigner).getDeployTransaction(g.entryPoint().address).data!) - paymasterAddress = await new Create2Factory(ethers.provider, ethersSigner).deploy(paymasterInit, 0) - const paymaster = TestPaymasterAcceptAll__factory.connect(paymasterAddress, ethersSigner) + // const paymasterInit = hexValue(new TestPaymasterAcceptAll__factory(ethersSigner).getDeployTransaction(g.entryPoint().address).data!) + const entryPointAddress = g.entryPoint().address + paymaster = await new VerifyingPaymaster__factory(ethersSigner).deploy(entryPointAddress, g.accountOwner.address) + GasCheckCollector.inst.setContractName(paymaster.address, 'VerifyingPaymaster') + await paymaster.addStake(1, { value: 1 }) await g.entryPoint().depositTo(paymaster.address, { value: parseEther('10') }) }) - it('simple paymaster', async function () { - await g.addTestRow({ title: 'simple paymaster', count: 1, paymaster: paymasterAddress, diffLastGas: false }) + + it('verifying paymaster', async function () { + await g.createAccounts1(2) await g.addTestRow({ - title: 'simple paymaster with diff', + title: 'verifying paymaster', + count: 1, + paymaster: paymaster.address, + verifyingPaymaster: true, + diffLastGas: false + }) + await g.addTestRow({ + title: 'verifying paymaster with diff', count: 2, - paymaster: paymasterAddress, + paymaster: paymaster.address, + verifyingPaymaster: true, diffLastGas: true }) }) - it('simple paymaster 10', async function () { + it.skip('simple paymaster 10', async function () { if (g.skipLong()) this.skip() await g.addTestRow({ title: 'simple paymaster', count: 10, paymaster: paymasterAddress, diffLastGas: false }) diff --git a/gascalc/GasChecker.ts b/gascalc/GasChecker.ts index 34a65cc56..6cd3ddc6e 100644 --- a/gascalc/GasChecker.ts +++ b/gascalc/GasChecker.ts @@ -1,16 +1,21 @@ // calculate gas usage of different bundle sizes import '../test/aa.init' -import { defaultAbiCoder, hexConcat, parseEther } from 'ethers/lib/utils' +import { arrayify, defaultAbiCoder, hexConcat, parseEther } from 'ethers/lib/utils' import { AddressZero, checkForGeth, - createAddress, createAccountOwner, - deployEntryPoint, decodeRevertReason + createAddress, + decodeRevertReason, + deployEntryPoint } from '../test/testutils' import { - EntryPoint, EntryPoint__factory, SimpleAccountFactory, - SimpleAccountFactory__factory, SimpleAccount__factory + EntryPoint, + EntryPoint__factory, + SimpleAccount__factory, + SimpleAccountFactory, + SimpleAccountFactory__factory, + VerifyingPaymaster__factory } from '../typechain' import { BigNumberish, Wallet } from 'ethers' import hre from 'hardhat' @@ -20,7 +25,7 @@ import { table, TableUserConfig } from 'table' import { Create2Factory } from '../src/Create2Factory' import * as fs from 'fs' import { SimpleAccountInterface } from '../typechain/contracts/samples/SimpleAccount' -import { PackedUserOperation } from '../test/UserOperation' +import { PackedUserOperation, UserOperation } from '../test/UserOperation' import { expect } from 'chai' const gasCheckerLogFile = './reports/gas-checker.txt' @@ -42,6 +47,7 @@ interface GasTestInfo { title: string diffLastGas: boolean paymaster: string + verifyingPaymaster: boolean count: number // address, or 'random' or 'self' (for account itself) dest: string @@ -198,8 +204,6 @@ export class GasChecker { const userOps = await Promise.all(range(info.count) .map(index => Object.entries(this.accounts)[index]) .map(async ([account, accountOwner]) => { - const paymaster = info.paymaster - let { dest, destValue, destCallData } = info if (dest === 'self') { dest = account @@ -230,18 +234,28 @@ export class GasChecker { } // console.debug('== account est=', accountEst.toString()) accountEst = est.accountEst - const op = await fillSignAndPack({ + const userOpInput: Partial = { sender: account, callData: accountExecFromEntryPoint, maxPriorityFeePerGas: info.gasPrice, maxFeePerGas: info.gasPrice, callGasLimit: accountEst, verificationGasLimit: 1000000, - paymaster: paymaster, + paymaster: info.paymaster, paymasterVerificationGasLimit: 50000, paymasterPostOpGasLimit: 50000, preVerificationGas: 1 - }, accountOwner, GasCheckCollector.inst.entryPoint) + } + if (info.verifyingPaymaster) { + const MOCK_VALID_UNTIL = '0x00000000deadbeef' + const MOCK_VALID_AFTER = '0x0000000000001234' + const userOp1 = await fillUserOp(userOpInput, this.entryPoint()) + const paymaster = VerifyingPaymaster__factory.connect(info.paymaster, ethersSigner) + const hash = await paymaster.getHash(packUserOp(userOp1), MOCK_VALID_UNTIL, MOCK_VALID_AFTER) + const sig = await this.accountOwner.signMessage(arrayify(hash)) + userOpInput.paymasterData = hexConcat([defaultAbiCoder.encode(['uint48', 'uint48'], [MOCK_VALID_UNTIL, MOCK_VALID_AFTER]), sig]) + } + const op = await fillSignAndPack(userOpInput, accountOwner, GasCheckCollector.inst.entryPoint) // const packed = packUserOp(op, false) // console.log('== packed cost=', callDataCost(packed), packed) return op From b880f673c316660708638ae5884f2ee8308946b5 Mon Sep 17 00:00:00 2001 From: Alex Forshtat Date: Sun, 7 Apr 2024 09:26:21 +0200 Subject: [PATCH 4/6] Add Zerodev Kernel's submodules --- .gitmodules | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..e9f07b0e0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,15 @@ +[submodule "contracts/test/zerodev-kernel-lite/forge-std"] + path = contracts/test/zerodev-kernel-lite/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "contracts/test/zerodev-kernel-lite/I4337"] + path = contracts/test/zerodev-kernel-lite/I4337 + url = https://github.com/leekt/I4337 +[submodule "contracts/test/zerodev-kernel-lite/FreshCryptoLib"] + path = contracts/test/zerodev-kernel-lite/FreshCryptoLib + url = https://github.com/rdubois-crypto/FreshCryptoLib +[submodule "contracts/test/zerodev-kernel-lite/p256-verifier"] + path = contracts/test/zerodev-kernel-lite/p256-verifier + url = https://github.com/daimo-eth/p256-verifier +[submodule "contracts/test/zerodev-kernel-lite/solady"] + path = contracts/test/zerodev-kernel-lite/solady + url = https://github.com/vectorized/solady From d8a705572a5f01a37682f2cb3c969b12eae2447a Mon Sep 17 00:00:00 2001 From: Alex Forshtat Date: Sun, 7 Apr 2024 21:38:49 +0200 Subject: [PATCH 5/6] Remove git modules actually --- .gitmodules | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e9f07b0e0..000000000 --- a/.gitmodules +++ /dev/null @@ -1,15 +0,0 @@ -[submodule "contracts/test/zerodev-kernel-lite/forge-std"] - path = contracts/test/zerodev-kernel-lite/forge-std - url = https://github.com/foundry-rs/forge-std -[submodule "contracts/test/zerodev-kernel-lite/I4337"] - path = contracts/test/zerodev-kernel-lite/I4337 - url = https://github.com/leekt/I4337 -[submodule "contracts/test/zerodev-kernel-lite/FreshCryptoLib"] - path = contracts/test/zerodev-kernel-lite/FreshCryptoLib - url = https://github.com/rdubois-crypto/FreshCryptoLib -[submodule "contracts/test/zerodev-kernel-lite/p256-verifier"] - path = contracts/test/zerodev-kernel-lite/p256-verifier - url = https://github.com/daimo-eth/p256-verifier -[submodule "contracts/test/zerodev-kernel-lite/solady"] - path = contracts/test/zerodev-kernel-lite/solady - url = https://github.com/vectorized/solady From 4e581cf6b4b8f7e6da2877f217d8f4a0632b0a83 Mon Sep 17 00:00:00 2001 From: Alex Forshtat Date: Sun, 7 Apr 2024 23:08:51 +0200 Subject: [PATCH 6/6] WIP: run Zerodev Kernel in "gas calc" (! Wrong branch = EP is 0.7, ZD is 0.6) --- gascalc/7-zerodev-kernel-lite.gas.ts | 33 ++++++++++++++++++++++++++++ gascalc/GasChecker.ts | 12 ++++++++-- test/testutils.ts | 4 ++-- 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 gascalc/7-zerodev-kernel-lite.gas.ts diff --git a/gascalc/7-zerodev-kernel-lite.gas.ts b/gascalc/7-zerodev-kernel-lite.gas.ts new file mode 100644 index 000000000..c36158dda --- /dev/null +++ b/gascalc/7-zerodev-kernel-lite.gas.ts @@ -0,0 +1,33 @@ +import { GasCheckCollector, GasChecker } from './GasChecker' +import { createAccountOwner } from '../test/testutils' +import { ethers } from 'hardhat' + +// TODO: NOTE: Must be executed separately as otherwise test will reuse SimpleAccount +context.only('simple account', function () { + this.timeout(60000) + const g = new GasChecker() + + // deployed by 'hardhat deploy' command in Zerodev repo fork + const zkLite = '0xe040e67D6cE5e39C5270Da5E9DCe25e082CEe70D' + + before(async function () { + await GasCheckCollector.init() + GasCheckCollector.inst.createJsonResult = true + const zerodevKernelOwner = createAccountOwner(1000) + console.log('zerodevKernelOwner= ', zerodevKernelOwner.address) + await g.insertAccount(zkLite, zerodevKernelOwner) + + const code = await ethers.provider.getCode(zkLite) + console.log('code= ', code) + }) + + it('simple 1', async function () { + await g.addTestRow({ + title: 'zd-kernel-lite', + count: 1, + skipAccountCreation: true, + diffLastGas: false + }) + await g.addTestRow({ title: 'zd-kernel-lite - diff from previous', count: 2, diffLastGas: true }) + }) +}) diff --git a/gascalc/GasChecker.ts b/gascalc/GasChecker.ts index 6cd3ddc6e..43ae79735 100644 --- a/gascalc/GasChecker.ts +++ b/gascalc/GasChecker.ts @@ -48,6 +48,7 @@ interface GasTestInfo { diffLastGas: boolean paymaster: string verifyingPaymaster: boolean + skipAccountCreation: boolean count: number // address, or 'random' or 'self' (for account itself) dest: string @@ -176,6 +177,11 @@ export class GasChecker { await this.entryPoint().handleOps(creationOps, ethersSigner.getAddress()) } + async insertAccount (address: string, owner: Wallet): Promise { + this.createdAccounts.add(address) + this.accounts[address] = owner + } + /** * helper: run a test scenario, and add a table row * @param params - test parameters. missing values filled in from DefaultGasTestInfo @@ -197,8 +203,10 @@ export class GasChecker { console.debug('== running test count=', info.count) - // fill accounts up to this code. - await this.createAccounts1(info.count) + if (!info.skipAccountCreation) { + // fill accounts up to this code. + await this.createAccounts1(info.count) + } let accountEst: number = 0 const userOps = await Promise.all(range(info.count) diff --git a/test/testutils.ts b/test/testutils.ts index 3e18f7848..0c4d1e621 100644 --- a/test/testutils.ts +++ b/test/testutils.ts @@ -67,8 +67,8 @@ export async function getTokenBalance (token: IERC20, address: string): Promise< let counter = 0 // create non-random account, so gas calculations are deterministic -export function createAccountOwner (): Wallet { - const privateKey = keccak256(Buffer.from(arrayify(BigNumber.from(++counter)))) +export function createAccountOwner (index?: number): Wallet { + const privateKey = keccak256(Buffer.from(arrayify(BigNumber.from(index ?? ++counter)))) return new ethers.Wallet(privateKey, ethers.provider) // return new ethers.Wallet('0x'.padEnd(66, privkeyBase), ethers.provider); }