From 5b69ea266f044d43f432001ae01313a07f0f737b Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Tue, 7 Jan 2025 20:57:00 +0200 Subject: [PATCH 1/2] fix rules --- .../src/BundlerCollectorTracer.ts | 3 ++ .../src/TracerResultParser.ts | 43 +++++++++++++++---- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/packages/validation-manager/src/BundlerCollectorTracer.ts b/packages/validation-manager/src/BundlerCollectorTracer.ts index cc8bf564..ac93d1ae 100644 --- a/packages/validation-manager/src/BundlerCollectorTracer.ts +++ b/packages/validation-manager/src/BundlerCollectorTracer.ts @@ -60,6 +60,9 @@ export interface TopLevelCallInfo { extCodeAccessInfo: { [addr: string]: string } oog?: boolean calls?: [] + type?: string + to?: string + from?: string } /** diff --git a/packages/validation-manager/src/TracerResultParser.ts b/packages/validation-manager/src/TracerResultParser.ts index 1478394a..7d157b43 100644 --- a/packages/validation-manager/src/TracerResultParser.ts +++ b/packages/validation-manager/src/TracerResultParser.ts @@ -91,7 +91,8 @@ function getEntityTitle (userOp: OperationBase, entityAddress: string): string { } // opcodes from [OP-011] -const bannedOpCodes = new Set(['GASPRICE', 'GASLIMIT', 'DIFFICULTY', 'TIMESTAMP', 'BASEFEE', 'BLOCKHASH', 'NUMBER', 'ORIGIN', 'GAS', 'CREATE', 'COINBASE', 'SELFDESTRUCT', 'RANDOM', 'PREVRANDAO', 'INVALID']) +// (CREATE, CREATE2 have special rules, OP-031, OP-032) +const bannedOpCodes = new Set(['GASPRICE', 'GASLIMIT', 'DIFFICULTY', 'TIMESTAMP', 'BASEFEE', 'BLOCKHASH', 'NUMBER', 'ORIGIN', 'GAS', 'COINBASE', 'SELFDESTRUCT', 'RANDOM', 'PREVRANDAO', 'INVALID']) // opcodes allowed in staked entities [OP-080] const opcodesOnlyInStakedEntities = new Set(['BALANCE', 'SELFBALANCE']) @@ -183,7 +184,7 @@ export function tracerResultParser ( return [addresses, storageMap] } -function processEntityCall (entityCall: TopLevelCallInfo, entityAddress: string, entityTitle: string, entStakes: StakeInfo, entitySlots: { [addr: string]: Set }, userOp: UserOperation, stakeInfoEntities: {[addr: string]: StakeInfo}, entryPointAddress: string, tracerResults: BundlerTracerResult): void { +function processEntityCall (entityCall: TopLevelCallInfo, entityAddress: string, entityTitle: string, entStakes: StakeInfo, entitySlots: { [addr: string]: Set }, userOp: UserOperation, stakeInfoEntities: {[addr: string]: StakeInfo}, entryPointAddress: string, tracerResults: BundlerTracerResult, depth = 0): void { const opcodes = entityCall.opcodes const access = entityCall.access @@ -198,12 +199,38 @@ function processEntityCall (entityCall: TopLevelCallInfo, entityAddress: string, requireCond(!opcodesOnlyInStakedEntities.has(opcode) || isStaked(entStakes), `unstaked ${entityTitle} uses banned opcode: ${opcode}`, ValidationErrors.OpcodeValidation) }) - // [OP-031] - if (entityTitle === 'factory') { - requireCond((opcodes.CREATE2 ?? 0) <= 1, `${entityTitle} with too many CREATE2`, ValidationErrors.OpcodeValidation) - } else { - requireCond(opcodes.CREATE2 == null, `${entityTitle} uses banned opcode: CREATE2`, ValidationErrors.OpcodeValidation) + + if (entityCall.type === 'CREATE') { + // CREATE is allowed only from the entity (factory, account) itself, not from inner contract + // top-level is "handleOps" (or simulateValidation) itself + requireCond(depth === 1, + `${entityTitle} uses banned opcode: CREATE from inner call depth=${depth}`, ValidationErrors.OpcodeValidation) + + // [OP-032] If there is a `factory` (even unstaked), the `sender` contract is allowed to use `CREATE` opcode + requireCond( + (entityTitle === 'account' && userOp.factory != null) || + (entityTitle === 'factory' && isStaked(entStakes)), + `${entityTitle} uses banned opcode: CREATE`, ValidationErrors.OpcodeValidation + ) } + if (entityCall.type === 'CREATE2') { + const factory = userOp.factory?.toLowerCase() ?? '' + const factoryStaked = isStaked(stakeInfoEntities[factory]) + console.log(`=== CREATE2: title:${entityTitle} factory-staked:${factoryStaked} callfromsender:${entityCall.from === userOp.sender.toLowerCase()}`) + console.log({ + stakeInfoEntities, + userOp + }) + requireCond( + (entityTitle === 'account' && factoryStaked && entityCall.from === userOp.sender.toLowerCase()) || // OP-032 + (entityTitle === 'factory' && ( + entityCall.to === userOp.sender.toLowerCase() || + (entityCall.from === userOp.factory && isStaked(entStakes)) + )), + `${entityTitle} uses banned opcode: CREATE2`, ValidationErrors.OpcodeValidation + ) + } + Object.entries(access).forEach(([addr, { reads, writes, @@ -308,7 +335,7 @@ function processEntityCall (entityCall: TopLevelCallInfo, entityAddress: string, // Recursively handling all subcalls to check validation rules if (entityCall.calls != null) { entityCall.calls.forEach((call: any) => { - processEntityCall(call, entityAddress, entityTitle, entStakes, entitySlots, userOp, stakeInfoEntities, entryPointAddress, tracerResults) + processEntityCall(call, entityAddress, entityTitle, entStakes, entitySlots, userOp, stakeInfoEntities, entryPointAddress, tracerResults, depth + 1) }) } } From f1e9a7d59a9c4d64b14cbc61c44fb29ca8477beb Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Sun, 12 Jan 2025 14:29:50 +0200 Subject: [PATCH 2/2] fix merge error (rip7560 tx type) --- packages/utils/src/RIP7560Utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/RIP7560Utils.ts b/packages/utils/src/RIP7560Utils.ts index 60b391bf..322d812e 100644 --- a/packages/utils/src/RIP7560Utils.ts +++ b/packages/utils/src/RIP7560Utils.ts @@ -7,6 +7,8 @@ import { hexlify, keccak256 } from 'ethers/lib/utils' import { OperationRIP7560 } from './interfaces/OperationRIP7560' import { AddressZero } from './ERC4337Utils' +export const RIP7560_TRANSACTION_TYPE = 5 + export function getRIP7560TransactionHash (op: OperationRIP7560, forSignature = true): string { if (!forSignature) { throw new Error('not implemented') @@ -43,7 +45,7 @@ function rlpEncodeRip7560Tx (op: OperationRIP7560, forSignature = true): string input.push(bigNumberishToUnpaddedBuffer(op.paymasterPostOpGasLimit ?? 0)) input.push(bigNumberishToUnpaddedBuffer(op.nonceKey)) let rlpEncoded: any = encode(input) - rlpEncoded = Buffer.from([4, ...rlpEncoded]) + rlpEncoded = Buffer.from([RIP7560_TRANSACTION_TYPE, ...rlpEncoded]) return hexlify(rlpEncoded) }