Skip to content

Commit

Permalink
most #ValidationManager tests
Browse files Browse the repository at this point in the history
  • Loading branch information
drortirosh committed Feb 3, 2025
1 parent 64e60bb commit ae02267
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 63 deletions.
4 changes: 2 additions & 2 deletions packages/bundler/contracts/tests/TestRecursionAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ contract TestRecursionAccount is TestRuleAccount {
function runRule(string memory rule) public virtual override returns (uint) {

if (eq(rule, "handleOps")) {
PackedUserOperation[] memory ops = new PackedUserOperation[](0);
ep.handleOps(ops, payable(address (1)));
//handleOps is protected by reentrancy guard. check other blocked calls to EntryPoint
ep.getDepositInfo(address(0));
return 0;
}

Expand Down
22 changes: 12 additions & 10 deletions packages/bundler/test/ValidateManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ describe('#ValidationManager', () => {
const unsafe = !await supportsDebugTraceCall(provider, false)
const preVerificationGasCalculator = new PreVerificationGasCalculator(MainnetConfig)

const senderCreator = '0xefc2c1444ebcc4db75e7613d20c6a62ff67a167c'
const senderCreator = await entryPoint.senderCreator()
const erc7562Parser = new ERC7562Parser(entryPoint.address, senderCreator)
vm = new ValidationManager(entryPoint, unsafe, preVerificationGasCalculator, erc7562Parser)

Expand Down Expand Up @@ -261,21 +261,21 @@ describe('#ValidationManager', () => {
})

it('should reject request with past validUntil', async () => {
await expect(
testTimeRangeUserOp(0, Date.now() - 1000)
).to.be.revertedWith('already expired')
expect(
await testTimeRangeUserOp(0, Date.now() - 1000).catch(e => e.message)
).match(/expired/)
})

it('should reject request with short validUntil', async () => {
await expect(
testTimeRangeUserOp(0, Date.now() + 25000)
).to.be.revertedWith('expires too soon')
expect(
await testTimeRangeUserOp(0, Date.now() + 25000).catch(e => e.message)
).to.match(/expires too soon/)
})

it('should reject request with future validAfter', async () => {
await expect(
testTimeRangeUserOp(Date.now() * 2, 0)
).to.be.revertedWith('future ')
expect(
await testTimeRangeUserOp(Date.now() * 2, 0).catch(e => e.message)
).to.match(/not due/)
})
})

Expand Down Expand Up @@ -347,6 +347,7 @@ describe('#ValidationManager', () => {
// await entryPoint.depositTo(pm.address, { value: parseEther('0.1') })
// await pm.addStake(entryPoint.address, { value: parseEther('0.1') })
const acct = await new TestRecursionAccount__factory(ethersSigner).deploy(entryPoint.address)
await acct.deployTransaction.wait()

const userOp: UserOperation = {
...cEmptyUserOp,
Expand All @@ -361,6 +362,7 @@ describe('#ValidationManager', () => {

it('should fail if validation recursively calls handleOps', async () => {
const acct = await new TestRecursionAccount__factory(ethersSigner).deploy(entryPoint.address)
await acct.deployTransaction.wait()
const op: UserOperation = {
...cEmptyUserOp,
sender: acct.address,
Expand Down
1 change: 0 additions & 1 deletion packages/utils/src/ERC4337Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,6 @@ export function decodeErrorReason (error: string | Error): DecodedError | undefi
error = (err.data ?? err.error.data) as string
}

debug('decoding', error)
if (error.startsWith(ErrorSig)) {
const [message] = defaultAbiCoder.decode(['string'], '0x' + error.substring(10))
return { message }
Expand Down
2 changes: 1 addition & 1 deletion packages/validation-manager/src/ERC7562Call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface ERC7562Call {
type: string
usedOpcodes: Record<number, number>
value?: string
calls?: ERC7562Call[]
calls: ERC7562Call[]
keccak?: string[]
}

Expand Down
138 changes: 90 additions & 48 deletions packages/validation-manager/src/ValidationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ import {
packUserOp,
requireAddressAndFields,
requireCond,
runContractScript, PackedUserOperation, sum
runContractScript,
PackedUserOperation,
sum,
IAccount__factory,
parseValidationData,
ValidationData,
IPaymaster__factory, maxUint48
} from '@account-abstraction/utils'

import { debug_traceCall } from './GethTracer'
Expand Down Expand Up @@ -81,43 +87,54 @@ export class ValidationManager implements IValidationManager {
}

// generate validation result from trace: by decoding inner calls.
async generateValidationResult (op: UserOperation, tracerResult: BundlerTracerResult): Promise<ValidationResult> {
async generateValidationResult (op: UserOperation, tracerResult: ERC7562Call): Promise<ValidationResult> {
// const validationData = tracerResult.calls[0].output
let callIndex = 0
if (op.factory != null) {
callIndex++
}
if (tracerResult.calls[callIndex] == null) {
console.log('fatal: no validateuserop')
}

const validationData = this.decodeValidateUserOp(tracerResult.calls[callIndex])
const aggregator = validationData.aggregator
let paymasterValidationData: ValidationData = { validAfter: 0, validUntil: maxUint48, aggregator: AddressZero }
let paymasterContext: string | undefined
if (op.paymaster != null) {
callIndex++
const pmRet = this.decodeValidatePaymasterUserOp(tracerResult.calls[callIndex])
paymasterContext = pmRet.context
paymasterValidationData = pmRet.validationData
}

// todo: paymasterContext should be returned to parser, to validate its length.

const paymasterAddress = op.paymaster ?? AddressZero
const factoryAddress = op.factory ?? AddressZero
const addrs = [op.sender, paymasterAddress, factoryAddress, AddressZero]
const ret = await runContractScript(this.provider, new GetStakes__factory(), [this.entryPoint.address, addrs])
const addrs = [op.sender, paymasterAddress, factoryAddress, aggregator]
const retStakes = (await runContractScript(this.provider, new GetStakes__factory(), [this.entryPoint.address, addrs]))[0]

const aggregator = AddressZero // extract from validationData
return {
const ret: ValidationResult = {
returnInfo: {
sigFailed: false, // extract from validateUserOp return value
validUntil: 0, // extract from validateUserOp return value
validAfter: 0, // extract from validateUserOp return value
sigFailed: false, // can't fail here, since handleOps didn't revert.
validUntil: Math.min(validationData.validUntil, paymasterValidationData.validUntil),
validAfter: Math.max(validationData.validAfter, paymasterValidationData.validAfter),
preOpGas: 0, // extract from innerHandleOps parameter
prefund: 0 // extract from innerHandleOps parameter
},
senderInfo: {
addr: op.sender,
stake: ret.senderStake.stake,
unstakeDelaySec: ret.senderStake.unstakeDelaySec
},
factoryInfo: {
addr: factoryAddress,
stake: ret.factoryStake.stake,
unstakeDelaySec: ret.factoryStake.unstakeDelaySec
},
paymasterInfo: {
addr: paymasterAddress,
stake: ret.paymasterStake.stake,
unstakeDelaySec: ret.paymasterStake.unstakeDelaySec
},
aggregatorInfo: {
addr: aggregator,
stake: ret.aggregatorStake.stake,
unstakeDelaySec: ret.aggregatorStake.unstakeDelaySec
}

senderInfo: retStakes[0]
}
if (op.paymaster !== null) {
ret.paymasterInfo = retStakes[1]
}
if (op.factory !== null) {
ret.factoryInfo = retStakes[2]
}
if (aggregator !== AddressZero) {
ret.aggregatorInfo = retStakes[3]
}
return ret
}

parseValidationResult (userOp: UserOperation, res: ValidationResultStructOutput): ValidationResult {
Expand Down Expand Up @@ -203,17 +220,17 @@ export class ValidationManager implements IValidationManager {

const simulationGas = BigNumber.from(userOp.preVerificationGas).add(userOp.verificationGasLimit)

const stateOverrides = {
// [this.entryPoint.address]: {
// code: EntryPointSimulationsJson.deployedBytecode
// },
...stateOverride
}

// validate against a future time: the UserOp must be valid at that time to be included
const blockOverrides = {
time: '0x' + (Math.floor(Date.now() / 1000) + VALID_UNTIL_FUTURE_SECONDS).toString(16)
}
// const stateOverrides = {
// // [this.entryPoint.address]: {
// // code: EntryPointSimulationsJson.deployedBytecode
// // },
// ...stateOverride
// }
//
// // validate against a future time: the UserOp must be valid at that time to be included
// const blockOverrides = {
// time: '0x' + (Math.floor(Date.now() / 1000) + VALID_UNTIL_FUTURE_SECONDS).toString(16)
// }

let tracer
if (!this.usingErc7562NativeTracer()) {
Expand All @@ -225,9 +242,9 @@ export class ValidationManager implements IValidationManager {
data: handleOpsData,
gasLimit: simulationGas
}, {
tracer,
stateOverrides,
blockOverrides
tracer
// stateOverrides,
// blockOverrides
},
this.providerForTracer
)
Expand All @@ -242,9 +259,9 @@ export class ValidationManager implements IValidationManager {
}
} else {
// Using Native tracer
data = tracerResult.output
if (tracerResult.error != null && (tracerResult.error as string).includes('execution reverted')) {
throw new RpcError(decodeRevertReason(data, false) as string, ValidationErrors.SimulateValidation)
const decodedErrorReason = decodeRevertReason(tracerResult.output, false) as string
if (decodedErrorReason !== 'FailedOp(1,"AA94 gas values overflow")') {
throw new RpcError(decodedErrorReason, ValidationErrors.SimulateValidation)
}
}
// // Hack to handle SELFDESTRUCT until we fix entrypoint
Expand All @@ -255,7 +272,7 @@ export class ValidationManager implements IValidationManager {
// const [decodedSimulations] = entryPointSimulations.decodeFunctionResult('simulateValidation', data)
// const validationResult = this.parseValidationResult(userOp, decodedSimulations)

const validationResult = await this.generateValidationResult(userOp, tracerResult)
const validationResult = await this.generateValidationResult(userOp, tracerResult as ERC7562Call)
debug('==dump tree=', JSON.stringify(tracerResult, null, 2)
.replace(new RegExp(userOp.sender.toLowerCase()), '{sender}')
.replace(new RegExp(getAddr(userOp.paymaster) ?? '--no-paymaster--'), '{paymaster}')
Expand Down Expand Up @@ -544,4 +561,29 @@ export class ValidationManager implements IValidationManager {
usingErc7562NativeTracer (): boolean {
return this.providerForTracer == null
}

private decodeValidateUserOp (call: ERC7562Call): ValidationData {
const iaccount = IAccount__factory.connect(call.to, this.provider)
const methodSig = iaccount.interface.getSighash('validateUserOp')
if (call.input.slice(0, 10) !== methodSig) {
throw new Error('Not a validateUserOp')
}
if (call.output == null) {
throw new Error('validateUserOp: No output')
}
return parseValidationData(call.output)
}

private decodeValidatePaymasterUserOp (call: ERC7562Call): { context: string, validationData: ValidationData } {
const iPaymaster = IPaymaster__factory.connect(call.to, this.provider)
const methodSig = iPaymaster.interface.getSighash('validatePaymasterUserOp')
if (call.input.slice(0, 10) !== methodSig) {
throw new Error('Not a validatePaymasterUserOp')
}
if (call.output == null) {
throw new Error('validatePaymasterUserOp: No output')
}
const ret = iPaymaster.interface.decodeFunctionResult('validatePaymasterUserOp', call.output)
return { context: ret.context, validationData: parseValidationData(ret.validationData) }
}
}
3 changes: 2 additions & 1 deletion packages/validation-manager/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { bundlerJSTracerName, debug_traceCall, GethNativeTracerName } from './Ge
import { ValidateUserOpResult } from './IValidationManager'
import { ValidationManager } from './ValidationManager'
import { ERC7562Parser } from './ERC7562Parser'
import { ERC7562Call } from './ERC7562Call'

export * from './ValidationManager'
export * from './ValidationManagerRIP7560'
Expand Down Expand Up @@ -42,7 +43,7 @@ export async function supportsDebugTraceCall (provider: JsonRpcProvider, rip7560
const ret = await debug_traceCall(provider,
{ from: AddressZero, to: AddressZero, data: '0x' },
{ tracer: GethNativeTracerName }).catch(e => e)
return ret.logs != null
return ret.usedOpcodes != null
}

export async function checkRulesViolations (
Expand Down

0 comments on commit ae02267

Please sign in to comment.