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

AA-227: Define JSON schema for the alt-mempool configuration #238

Merged
merged 30 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
851cbd6
AA-227: Define JSON schema for the alt-mempool configuration
forshtat Jan 7, 2025
9c56201
Merge branch 'master' of github.com:eth-infinitism/bundler into AA-22…
forshtat Jan 12, 2025
cfa7fc9
WIP: Rewrite the ERC-7562 parser in a modular configurable way
forshtat Jan 12, 2025
449e29a
WIP: Add checks for OP-011 and OP-080
forshtat Jan 12, 2025
c43b1b9
WIP: Check OP-031 rules violations
forshtat Jan 12, 2025
23a64c3
WIP: Express some STO rules in readable format
forshtat Jan 13, 2025
266018f
More rules
forshtat Jan 13, 2025
a7ce5bb
Remove the old 'TracerResultsParser'
forshtat Jan 13, 2025
a9c6fca
Define and validate parser results schema
forshtat Jan 14, 2025
217e5b1
Remove new parser's dependency on the old tracer
forshtat Jan 14, 2025
f5804bd
Remove conversion function and make new tracer recursive as well
forshtat Jan 14, 2025
c496ee2
Clean up parser code
forshtat Jan 14, 2025
1697e56
Passes banned opcodes spec test
forshtat Jan 14, 2025
ec7b480
Fix some error reporting logic
forshtat Jan 19, 2025
0185e2e
Fix incorrect stake check
forshtat Jan 19, 2025
3d8f316
Fix EREP-060 and some other bugs
forshtat Jan 20, 2025
df3ec83
Fix associated storage attribution, struct access
forshtat Jan 20, 2025
951758f
Fix
forshtat Jan 20, 2025
21bc4b0
Clean up
forshtat Jan 20, 2025
bb0fadb
Undo removing the old parser
forshtat Jan 20, 2025
2310bdc
Skip opcode rules check for the code within EntryPoint
forshtat Jan 20, 2025
f295fe5
Fix typo
forshtat Jan 20, 2025
5f2c358
Fix not returning 'storageMap' and 'contractAddress' results
forshtat Jan 20, 2025
646c686
WIP: Add support for RIP-7560 transaction type
forshtat Jan 21, 2025
6f61c92
Configure AA_SENDER_CREATOR for ERC7562Parser
forshtat Jan 21, 2025
da7f986
Fix some issues with RIP-7560
forshtat Jan 21, 2025
a85db52
Enable legacy tracer and parser for the EIP-7702 tests
forshtat Jan 21, 2025
dd554cd
Handle the 'depth' field and clean up code for merging
forshtat Jan 21, 2025
44e16fb
Fix lint and depcheck
forshtat Jan 21, 2025
dd53eee
Fix lint
forshtat Jan 21, 2025
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
50 changes: 50 additions & 0 deletions packages/utils/src/altmempool/AltMempoolConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
type Role = 'sender' | 'paymaster' | 'factory'

type EnterOpcode = 'CALL' | 'DELEGATECALL' | 'CALLCODE' | 'STATICCALL' | 'CREATE' | 'CREATE2'

export interface AltMempoolRuleExceptionBase {
role?: Role
address?: string
depths?: number[]
enterOpcode?: EnterOpcode[]
enterMethodSelector?: `0x${string}`
}

export interface AltMempoolRuleExceptionBannedOpcode extends AltMempoolRuleExceptionBase {
opcodes: string[]
slots: Array<`0x${string}`>
}

type RuleException = `0x${string}` | Role | AltMempoolRuleExceptionBase | AltMempoolRuleExceptionBannedOpcode

export interface BaseAltMempoolRule {
enabled?: boolean
exceptions?: RuleException[]
}

// TODO: define all current rules
type RuleERC7562 = 'erep010' | 'erep020'

export interface AltMempoolConfig {
[mempoolId: number]: { [rule in RuleERC7562]?: BaseAltMempoolRule }
}

const config: AltMempoolConfig = {
1: {
erep010: {
enabled: true,
exceptions: [
'sender',
'0xdeadbeef',
{
depths: [3],
enterOpcode: ['CALL'],
opcodes: ['SSTORE', 'SLOAD'],
slots: ['0xdeadbeef']
}
]
}
}
}

console.log(config)
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
}
228 changes: 228 additions & 0 deletions packages/validation-manager/src/ERC7562TracerParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { ERC7562RuleViolation } from './ERC7562RuleViolation'
import { OperationBase, requireCond, ValidationErrors } from '@account-abstraction/utils'
import { BundlerTracerResult, MethodInfo, TopLevelCallInfo } from './BundlerCollectorTracer'
import { ERC7562Rule } from './ERC7562Rule'
import { AltMempoolConfig } from '@account-abstraction/utils/dist/src/altmempool/AltMempoolConfig'
import { AccountAbstractionEntity } from './AccountAbstractionEntity'
import { BigNumber } from 'ethers'
import { bannedOpCodes, opcodesOnlyInStakedEntities } from './TracerResultParser'

export class ERC7562TracerParser {
forshtat marked this conversation as resolved.
Show resolved Hide resolved
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[] {
this.checkSanity(tracerResults)
this.checkOp054(tracerResults)
this.checkOp061(tracerResults)
this.checkOp011(tracerResults)
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'
}
})
}

/**
* OP-020: Revert on "out of gas" is forbidden as it can "leak" the gas limit or the current call stack depth.
*/
checkOp020 (tracerResults: BundlerTracerResult): ERC7562RuleViolation[] {
const entityCallsFromEntryPoint = tracerResults.callsFromEntryPoint.filter((call: any) => call.topLevelTargetAddress != null)
const entityCallsWithOOG = entityCallsFromEntryPoint.filter((it: TopLevelCallInfo) => it.oog)
return entityCallsWithOOG.map((it: TopLevelCallInfo) => {
const entityTitle = 'fixme'
return {
rule: ERC7562Rule.op020,
// TODO: fill in depth, entity
depth: -1,
entity: AccountAbstractionEntity.fixme,
address: it.from ?? 'n/a',
opcode: it.type ?? 'n/a',
value: '0',
errorCode: ValidationErrors.OpcodeValidation,
description: `${entityTitle} internally reverts on oog`
}
})
}

/**
* OP-011: Blocked opcodes
* OP-080: `BALANCE` (0x31) and `SELFBALANCE` (0x47) are allowed only from a staked entity, else they are blocked
*/
checkOp011 (tracerResults: BundlerTracerResult): ERC7562RuleViolation[] {
const entityCallsFromEntryPoint = tracerResults.callsFromEntryPoint.filter((call: any) => call.topLevelTargetAddress != null)
const violations: ERC7562RuleViolation[] = []
for (const topLevelCallInfo of entityCallsFromEntryPoint) {
const opcodes = topLevelCallInfo.opcodes
const bannedOpCodeUsed = Object.keys(opcodes).filter((opcode: string) => {
return bannedOpCodes.has(opcode)
})
// TODO: TBD: Creating an object for each violation may be wasteful but makes it easier to choose a right mempool.
const bannedOpcodesViolations: ERC7562RuleViolation[] =
bannedOpCodeUsed
.map(
(opcode: string): ERC7562RuleViolation => {
const entityTitle = 'fixme'
return {
rule: ERC7562Rule.op011,
// TODO: fill in depth, entity
depth: -1,
entity: AccountAbstractionEntity.fixme,
address: topLevelCallInfo.from ?? 'n/a',
opcode,
value: '0',
errorCode: ValidationErrors.OpcodeValidation,
description: `${entityTitle} uses banned opcode: ${opcode}`
}
}
)
violations.push(...bannedOpcodesViolations)

// TODO: Deduplicate code in an elegant way
// TODO: Extract OP-080 into a separate function
const onlyStakedOpCodeUsed = Object.keys(opcodes).filter((opcode: string) => {
return opcodesOnlyInStakedEntities.has(opcode) && !this._isEntityStaked(topLevelCallInfo)
})
const onlyStakedOpcodesViolations: ERC7562RuleViolation[] =
onlyStakedOpCodeUsed
.map(
(opcode: string): ERC7562RuleViolation => {
const entityTitle = 'fixme'
return {
rule: ERC7562Rule.op011,
// TODO: fill in depth, entity
depth: -1,
entity: AccountAbstractionEntity.fixme,
address: topLevelCallInfo.from ?? 'n/a',
opcode,
value: '0',
errorCode: ValidationErrors.OpcodeValidation,
description: `unstaked ${entityTitle} uses banned opcode: ${opcode}`
}
}
)
violations.push(...onlyStakedOpcodesViolations)
}
return violations
}

/**
* OP-031: CREATE2 is allowed exactly once in the deployment phase and must deploy code for the "sender" address
*/
checkOp031 (
userOp: OperationBase,
tracerResults: BundlerTracerResult
): ERC7562RuleViolation[] {
const entityCallsFromEntryPoint = tracerResults.callsFromEntryPoint.filter((call: any) => call.topLevelTargetAddress != null)
const violations: ERC7562RuleViolation[] = []
for (const topLevelCallInfo of entityCallsFromEntryPoint) {
if (topLevelCallInfo.type !== 'CREATE2') {
continue
}
const entityTitle = 'fixme' as string
const factoryStaked = false
const isAllowedCreateByOP032 = entityTitle === 'account' && factoryStaked && topLevelCallInfo.from === userOp.sender.toLowerCase()
const isAllowedCreateByEREP060 = entityTitle === 'factory' && topLevelCallInfo.from === userOp.factory && factoryStaked
const isAllowedCreateSenderByFactory = entityTitle === 'factory' && topLevelCallInfo.to === userOp.sender.toLowerCase()
if (!(isAllowedCreateByOP032 || isAllowedCreateByEREP060 || isAllowedCreateSenderByFactory)) {
violations.push({
rule: ERC7562Rule.op011,
// TODO: fill in depth, entity
depth: -1,
entity: AccountAbstractionEntity.fixme,
address: topLevelCallInfo.from ?? 'n/a',
opcode: 'CREATE2',
value: '0',
errorCode: ValidationErrors.OpcodeValidation,
description: `${entityTitle} uses banned opcode: CREATE2`
})
}
}
return violations
}
private _isEntityStaked (topLevelCallInfo: TopLevelCallInfo): boolean {
throw new Error('Method not implemented.')
}
}
Loading