Skip to content

Commit

Permalink
test AA26, AA36
Browse files Browse the repository at this point in the history
  • Loading branch information
drortirosh committed Feb 11, 2024
1 parent e9164de commit a1685fd
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 6 deletions.
2 changes: 1 addition & 1 deletion test/UserOp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function packUserOp (userOp: UserOperation): PackedUserOperation {
const accountGasLimits = packAccountGasLimits(userOp.verificationGasLimit, userOp.callGasLimit)
const gasFees = packAccountGasLimits(userOp.maxPriorityFeePerGas, userOp.maxFeePerGas)
let paymasterAndData = '0x'
if (userOp.paymaster.length >= 20 && userOp.paymaster !== AddressZero) {
if (userOp.paymaster?.length >= 20 && userOp.paymaster !== AddressZero) {
paymasterAndData = packPaymasterData(userOp.paymaster as string, userOp.paymasterVerificationGasLimit, userOp.paymasterPostOpGasLimit, userOp.paymasterData as string)
}
return {
Expand Down
92 changes: 88 additions & 4 deletions test/entrypointsimulations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
SimpleAccountFactory,
SimpleAccountFactory__factory,
SimpleAccount__factory,
TestCounter__factory
TestCounter__factory,
TestPaymasterWithPostOp,
TestPaymasterWithPostOp__factory
} from '../typechain'
import {
ONE_ETH,
Expand All @@ -17,12 +19,13 @@ import {
fund,
getAccountAddress,
getAccountInitCode,
getBalance, deployEntryPoint
getBalance, deployEntryPoint, decodeRevertReason, findSimulationUserOpWithMin, findUserOpWithMin
} from './testutils'

import { fillSignAndPack, simulateHandleOp, simulateValidation } from './UserOp'
import { fillAndSign, fillSignAndPack, packUserOp, simulateHandleOp, simulateValidation } from './UserOp'
import { BigNumber, Wallet } from 'ethers'
import { hexConcat } from 'ethers/lib/utils'
import { hexConcat, parseEther } from 'ethers/lib/utils'
import { UserOperation } from './UserOperation'

const provider = ethers.provider
describe('EntryPointSimulations', function () {
Expand Down Expand Up @@ -243,6 +246,87 @@ describe('EntryPointSimulations', function () {
})
})

describe('over-validation test', () => {
// coverage skews gas checks.
if (process.env.COVERAGE != null) {
return
}

let vgl: number
let pmVgl: number
let paymaster: TestPaymasterWithPostOp
let sender: string
let owner: Wallet
async function userOpWithGas (vgl: number, pmVgl = 0): Promise<UserOperation> {
return fillAndSign({
sender,
verificationGasLimit: vgl,
paymaster: pmVgl !== 0 ? paymaster.address : undefined,
paymasterVerificationGasLimit: pmVgl,
paymasterPostOpGasLimit: pmVgl,
maxFeePerGas: 1,
maxPriorityFeePerGas: 1
}, owner, entryPoint)
}
before(async () => {
owner = createAccountOwner()
paymaster = await new TestPaymasterWithPostOp__factory(ethersSigner).deploy(entryPoint.address)
await entryPoint.depositTo(paymaster.address, { value: parseEther('1') })
const { proxy: account } = await createAccount(ethersSigner, owner.address, entryPoint.address)
sender = account.address
await fund(account)
pmVgl = await findSimulationUserOpWithMin(async n => userOpWithGas(1e6, n), entryPoint, 1, 500000)
vgl = await findSimulationUserOpWithMin(async n => userOpWithGas(n, pmVgl), entryPoint, 3000, 500000)

const packedUserOp = packUserOp(await userOpWithGas(vgl, pmVgl))
const hash = await entryPoint.getUserOpHash(packedUserOp)
console.log('eth-call validation', await provider.call({
from: entryPoint.address,
to: account.address,
data: account.interface.encodeFunctionData('validateUserOp', [packedUserOp, hash, '0x1'])
}))
console.log('estimate validation', (await provider.estimateGas({
from: entryPoint.address,
to: account.address,
data: account.interface.encodeFunctionData('validateUserOp', [packedUserOp, hash, '0x1'])
})).sub(21000))
console.log('estimate paymaster validation', (await provider.estimateGas({
from: entryPoint.address,
to: paymaster.address,
data: paymaster.interface.encodeFunctionData('validatePaymasterUserOp', [packedUserOp, hash, '0x1'])
})).sub(21000))
await simulateValidation(packedUserOp, entryPoint.address)
.catch(e => { throw new Error(decodeRevertReason(e)!) })
})
describe('compare to execution', () => {
let execVgl: number
let execPmVgl: number
const diff = 2000
before(async () => {
execPmVgl = await findUserOpWithMin(async n => userOpWithGas(1e6, n), false, entryPoint, 1, 500000)
execVgl = await findUserOpWithMin(async n => userOpWithGas(n, execPmVgl), false, entryPoint, 1, 500000)
})
it('account verification simulation cost should be higher than execution', function () {
console.log('simulation account validation', vgl, 'above exec:', vgl - execVgl)
expect(vgl).to.be.within(execVgl + 1, execVgl + diff, `expected simulation verificationGas to be 1..${diff} above actual, but was ${vgl - execVgl}`)
})
it('paymaster verification simulation cost should be higher than execution', function () {
console.log('simulation paymaster validation', pmVgl, 'above exec:', pmVgl - execPmVgl)
expect(pmVgl).to.be.within(execPmVgl + 1, execPmVgl + diff, `expected simulation verificationGas to be 1..${diff} above actual, but was ${pmVgl - execPmVgl}`)
})
})
it('should revert with AA2x if verificationGasLimit is low', async function () {
expect(await simulateValidation(packUserOp(await userOpWithGas(vgl - 1, pmVgl)), entryPoint.address)
.catch(decodeRevertReason))
.to.match(/AA26/)
})
it('should revert with AA3x if paymasterVerificationGasLimit is low', async function () {
expect(await simulateValidation(packUserOp(await userOpWithGas(vgl, pmVgl - 1)), entryPoint.address)
.catch(decodeRevertReason))
.to.match(/AA36/)
})
})

describe('#simulateHandleOp', () => {
it('should simulate creation', async () => {
const accountOwner1 = createAccountOwner()
Expand Down
88 changes: 87 additions & 1 deletion test/testutils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { BytesLike, Hexable } from '@ethersproject/bytes'
import { expect } from 'chai'
import { Create2Factory } from '../src/Create2Factory'
import { debugTransaction } from './debugTx'
import { UserOperation } from './UserOperation'
import { packUserOp, simulateValidation } from './UserOp'

export const AddressZero = ethers.constants.AddressZero
export const HashZero = ethers.constants.HashZero
Expand Down Expand Up @@ -168,8 +170,10 @@ const decodeRevertReasonContracts = new Interface([
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
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)

Expand Down Expand Up @@ -351,3 +355,85 @@ export function packValidationData (validationData: ValidationData): BigNumber {
.add(validationData.validUntil).shl(160)
.add(validationData.aggregator)
}

// find the lowest number in the range min..max where testFunc returns true
export async function findMin (testFunc: (index: number) => Promise<boolean>, min: number, max: number, delta = 5): Promise<number> {
if (await testFunc(min)) {
throw new Error(`increase range: function already true at ${min}`)
}
if (!await testFunc(max)) {
throw new Error(`no result: function is false for max value in ${min}..${max}`)
}
while (true) {
const avg = Math.floor((max + min) / 2)
if (await testFunc(avg)) {
max = avg
} else {
min = avg
}
// console.log('== ', min, '...', max, max - min)
if (Math.abs(max - min) < delta) {
return max
}
}
}

/**
* find the lowest value that when creating a userop, still doesn't revert and
* doesn't emit UserOperationPrefundTooLow
* note: using eth_snapshot/eth_revert, since we actually submit calls to handleOps
* @param f function that return a signed userop, with parameter-under-test set to "n"
* @param min range minimum. the function is expected to return false
* @param max range maximum. the function is expected to be true
* @param entryPoint entrypoint for "fillAndSign" of userops
*/
export async function findUserOpWithMin (f: (n: number) => Promise<UserOperation>, expectExec: boolean, entryPoint: EntryPoint, min: number, max: number, delta = 2): Promise<number> {
const beneficiary = ethers.provider.getSigner().getAddress()
return await findMin(
async n => {
const snapshot = await ethers.provider.send('evm_snapshot', [])
try {
const userOp = await f(n)
// console.log('== userop=', userOp)
const rcpt = await entryPoint.handleOps([packUserOp(userOp)], beneficiary, { gasLimit: 1e6 })
.then(async r => r.wait())
if (rcpt?.events?.find(e => e.event === 'UserOperationPrefundTooLow') != null) {
// console.log('min', n, 'UserOperationPrefundTooLow')
return false
}
if (expectExec) {
const useropEvent = rcpt?.events?.find(e => e.event === 'UserOperationEvent')
if (useropEvent?.args?.success !== true) {
// console.log(rcpt?.events?.map((e: any) => ({ ev: e.event, ...objdump(e.args!) })))

// console.log('min', n, 'success=false')
return false
}
}
// console.log('min', n, 'ok')
return true
} catch (e) {
// console.log('min', n, 'ex=', decodeRevertReason(e as Error))
return false
} finally {
await ethers.provider.send('evm_revert', [snapshot])
}
}, min, max, delta
)
}

export async function findSimulationUserOpWithMin (f: (n: number) => Promise<UserOperation>, entryPoint: EntryPoint, min: number, max: number, delta = 2): Promise<number> {
return await findMin(
async n => {
try {
const userOp = await f(n)
await simulateValidation(packUserOp(userOp), entryPoint.address)
// console.log('sim', n, 'ok')
return true
} catch (e) {
// console.log('sim', n, 'ex=', decodeRevertReason(e as Error))
return false
}
}, min, max, delta
)
}

0 comments on commit a1685fd

Please sign in to comment.