Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Benchmarks post verkle #468

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions gascalc/1-simple-wallet.gas.ts
Original file line number Diff line number Diff line change
@@ -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 })
Expand Down
38 changes: 24 additions & 14 deletions gascalc/2-paymaster.gas.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,45 @@
import { parseEther } from 'ethers/lib/utils'

Check failure on line 1 in gascalc/2-paymaster.gas.ts

View workflow job for this annotation

GitHub Actions / test

Cannot find name 'paymasterAddress'.

Check failure on line 1 in gascalc/2-paymaster.gas.ts

View workflow job for this annotation

GitHub Actions / test

Cannot find name 'paymasterAddress'.

Check failure on line 1 in gascalc/2-paymaster.gas.ts

View workflow job for this annotation

GitHub Actions / test

Cannot find name 'paymasterAddress'.

Check failure on line 1 in gascalc/2-paymaster.gas.ts

View workflow job for this annotation

GitHub Actions / test

Cannot find name 'paymasterAddress'.
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('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 })
Expand Down
2 changes: 1 addition & 1 deletion gascalc/3-huge-tx-gas.ts
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
2 changes: 1 addition & 1 deletion gascalc/4-paymaster-postop.gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
2 changes: 1 addition & 1 deletion gascalc/5-token-paymaster.gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
33 changes: 33 additions & 0 deletions gascalc/7-zerodev-kernel-lite.gas.ts
Original file line number Diff line number Diff line change
@@ -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 })
})
})
94 changes: 74 additions & 20 deletions gascalc/GasChecker.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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'
Expand All @@ -42,6 +47,8 @@ interface GasTestInfo {
title: string
diffLastGas: boolean
paymaster: string
verifyingPaymaster: boolean
skipAccountCreation: boolean
count: number
// address, or 'random' or 'self' (for account itself)
dest: string
Expand Down Expand Up @@ -129,7 +136,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)) {
Expand Down Expand Up @@ -157,6 +168,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) })
Expand All @@ -165,6 +177,11 @@ export class GasChecker {
await this.entryPoint().handleOps(creationOps, ethersSigner.getAddress())
}

async insertAccount (address: string, owner: Wallet): Promise<void> {
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
Expand All @@ -186,20 +203,21 @@ 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)
.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
} 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)
Expand All @@ -224,18 +242,28 @@ export class GasChecker {
}
// console.debug('== account est=', accountEst.toString())
accountEst = est.accountEst
const op = await fillSignAndPack({
const userOpInput: Partial<UserOperation> = {
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
Expand Down Expand Up @@ -276,8 +304,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
Expand Down Expand Up @@ -305,6 +333,13 @@ export class GasCheckCollector {
static initPromise?: Promise<GasCheckCollector>

entryPoint: EntryPoint
createJsonResult: boolean = false
readonly contracts = new Map<string, string>()
readonly txHashes: string[] = []

setContractName (address: string, name: string): void {
this.contracts.set(address.toLowerCase(), name)
}

static async init (): Promise<void> {
if (this.inst == null) {
Expand All @@ -318,6 +353,7 @@ export class GasCheckCollector {
async _init (entryPointAddressOrTest: string = 'test'): Promise<this> {
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'))) {
Expand All @@ -331,14 +367,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)
Expand Down Expand Up @@ -390,20 +428,36 @@ 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 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([
res.title,
res.count,
gasUsed,
res.gasDiff ?? '',
// res.gasDiff ?? '',
// res.accountEst,
perOp])
// perOp,
res.receipt?.transactionHash])

this.txHashes.push(res.receipt!.transactionHash)
}
}

Expand Down
4 changes: 2 additions & 2 deletions test/testutils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Loading