Skip to content

Commit

Permalink
WIP: Rewrite the ERC-7562 parser in a modular configurable way
Browse files Browse the repository at this point in the history
  • Loading branch information
forshtat committed Jan 12, 2025
1 parent 9c56201 commit cfa7fc9
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 0 deletions.
11 changes: 11 additions & 0 deletions packages/validation-manager/src/AccountAbstractionEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const enum AccountAbstractionEntity {
sender = 'Sender',
paymaster = 'Paymaster',
factory = 'Factory',
aggregator = 'Aggregator',
senderCreator = 'SenderCreator',
entryPoint = 'EntryPoint',
// TODO: leaving 'fixme' entity for future refactor
// (some rules are checked in a way that makes it hard to find entity)
fixme = 'fixme'
}
37 changes: 37 additions & 0 deletions packages/validation-manager/src/ERC7562Rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export const enum ERC7562Rule {
op011 = 'OP-011',
op012 = 'OP-012',
op013 = 'OP-013',
op020 = 'OP-020',
op031 = 'OP-031',
op041 = 'OP-041',
op042 = 'OP-042',
op051 = 'OP-051',
op052 = 'OP-052',
op053 = 'OP-053',
op054 = 'OP-054',
op061 = 'OP-061',
op062 = 'OP-062',
op070 = 'OP-070',
op080 = 'OP-080',
cod010 = 'COD-010',
sto010 = 'STO-010',
sto021 = 'STO-021',
sto022 = 'STO-022',
sto031 = 'STO-031',
sto032 = 'STO-032',
sto033 = 'STO-033',
sto040 = 'STO-040',
sto041 = 'STO-041',
grep010 = 'GREP-010',
grep020 = 'GREP-020',
grep040 = 'GREP-040',
grep050 = 'GREP-050',
srep010 = 'SREP-010',
srep040 = 'SREP-040',
erep010 = 'EREP-010',
erep015 = 'EREP-015',
erep020 = 'EREP-020',
erep030 = 'EREP-030',
erep040 = 'EREP-040'
}
17 changes: 17 additions & 0 deletions packages/validation-manager/src/ERC7562RuleViolation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ValidationErrors } from '@account-abstraction/utils'

import { ERC7562Rule } from './ERC7562Rule'
import { AccountAbstractionEntity } from './AccountAbstractionEntity'

export interface ERC7562RuleViolation {
rule: ERC7562Rule
depth: number
entity: AccountAbstractionEntity
address: string
errorCode: ValidationErrors
description: string
conflict?: string
opcode?: string
value?: string
slot?: string
}
102 changes: 102 additions & 0 deletions packages/validation-manager/src/ERC7562TracerParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { ERC7562RuleViolation } from './ERC7562RuleViolation'
import { OperationBase, ValidationErrors } from '@account-abstraction/utils'
import { BundlerTracerResult, MethodInfo } from './BundlerCollectorTracer'
import { ERC7562Rule } from './ERC7562Rule'
import { AltMempoolConfig } from '@account-abstraction/utils/dist/src/altmempool/AltMempoolConfig'
import { AccountAbstractionEntity } from './AccountAbstractionEntity'
import { BigNumber } from 'ethers'

export class ERC7562TracerParser {
constructor (
readonly mempoolConfig: AltMempoolConfig,
readonly entryPointAddress: string
) {

}

private _isCallToEntryPoint (call: MethodInfo): boolean {
return call.to?.toLowerCase() === this.entryPointAddress?.toLowerCase() &&
call.from?.toLowerCase() !== this.entryPointAddress?.toLowerCase()
}

/**
* Validates the UserOperation and throws an exception in case current mempool configuration rules were violated.
*/
requireCompliance (
userOp: OperationBase,
tracerResults: BundlerTracerResult
): void {
const violations = this.parseResults(userOp, tracerResults)
if (violations.length > 0) {
// TODO: human-readable description of which rules were violated.
throw new Error('Rules Violated')
}
}

parseResults (
userOp: OperationBase,
tracerResults: BundlerTracerResult
): ERC7562RuleViolation[] {
return []
}

checkSanity (tracerResults: BundlerTracerResult): void {
if (Object.values(tracerResults.callsFromEntryPoint).length < 1) {
throw new Error('Unexpected traceCall result: no calls from entrypoint.')
}
}

/**
* OP-052: May call `depositTo(sender)` with any value from either the `sender` or `factory`.
* OP-053: May call the fallback function from the `sender` with any value.
* OP-054: Any other access to the EntryPoint is forbidden.
*/
checkOp054 (tracerResults: BundlerTracerResult): ERC7562RuleViolation[] {
const callStack = tracerResults.calls.filter((call: any) => call.topLevelTargetAddress == null) as MethodInfo[]
const callInfoEntryPoint = callStack.filter(call => {
const isCallToEntryPoint = this._isCallToEntryPoint(call)
const isEntryPointCallAllowedOP052 = call.method === 'depositTo'
const isEntryPointCallAllowedOP053 = call.method === '0x'
const isEntryPointCallAllowed = isEntryPointCallAllowedOP052 || isEntryPointCallAllowedOP053
return isCallToEntryPoint && !isEntryPointCallAllowed
})
return callInfoEntryPoint.map((it: MethodInfo): ERC7562RuleViolation => {
return {
rule: ERC7562Rule.op054,
// TODO: fill in depth, entity
depth: -1,
entity: AccountAbstractionEntity.fixme,
address: it.from,
opcode: it.type,
value: it.value,
errorCode: ValidationErrors.OpcodeValidation,
description: `illegal call into EntryPoint during validation ${it?.method}`
}
})
}

/**
* OP-061: CALL with value is forbidden. The only exception is a call to the EntryPoint.
*/
checkOp061 (tracerResults: BundlerTracerResult): ERC7562RuleViolation[] {
const callStack = tracerResults.calls.filter((call: any) => call.topLevelTargetAddress == null) as MethodInfo[]
const illegalNonZeroValueCall = callStack.filter(
call =>
!this._isCallToEntryPoint(call) &&
!BigNumber.from(call.value ?? 0).eq(0)
)
return illegalNonZeroValueCall.map((it: MethodInfo): ERC7562RuleViolation => {
return {
rule: ERC7562Rule.op061,
// TODO: fill in depth, entity
depth: -1,
entity: AccountAbstractionEntity.fixme,
address: it.from,
opcode: it.type,
value: it.value,
errorCode: ValidationErrors.OpcodeValidation,
description: 'May not may CALL with value'
}
})
}
}

0 comments on commit cfa7fc9

Please sign in to comment.