Skip to content

Commit

Permalink
Clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
forshtat committed Jan 20, 2025
1 parent 951758f commit 21bc4b0
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 149 deletions.
25 changes: 16 additions & 9 deletions packages/validation-manager/src/ERC7562BannedOpcodes.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
/**
* [OP-011] the opcodes banned for all entities.
*/
export const bannedOpCodes = new Set(
[
'GASPRICE',
'GASLIMIT',
'DIFFICULTY',
'TIMESTAMP',
'BASEFEE',
'BLOCKHASH',
'COINBASE',
'DIFFICULTY',
// 'GAS',
'GASLIMIT',
'GASPRICE',
'INVALID',
'NUMBER',
'ORIGIN',
'COINBASE',
'SELFDESTRUCT',
'RANDOM',
'PREVRANDAO',
'INVALID'
'RANDOM',
'SELFDESTRUCT',
'TIMESTAMP'
]
)
// opcodes allowed in staked entities [OP-080]

/**
* [OP-080] the opcodes allowed in staked entities.
*/
export const opcodesOnlyInStakedEntities = new Set(
[
'BALANCE',
Expand Down
284 changes: 144 additions & 140 deletions packages/validation-manager/src/ERC7562Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,14 @@ export class ERC7562Parser {
): ERC7562ValidationResults {
const address = tracerResults.to
this._detectEntityChange(userOp, tracerResults)
this.checkOp054(tracerResults)
this.checkOp054ExtCode(tracerResults, address, recursionDepth)
this.checkOp061(tracerResults)
this.checkOp011(tracerResults)
this.checkOp080(tracerResults)
this.checkOp020(tracerResults)
this.checkOp031(userOp, tracerResults)
this.checkOp041(userOp, tracerResults)
this.checkOp054(tracerResults)
this.checkOp054ExtCode(tracerResults, address, recursionDepth)
this.checkOp061(tracerResults)
this.checkOp080(tracerResults)
this.checkStorage(userOp, tracerResults)
for (const call of tracerResults.calls ?? []) {
this._innerStepRecursive(userOp, call, recursionDepth + 1)
Expand All @@ -250,6 +250,126 @@ export class ERC7562Parser {
}
}

/**
* OP-011: Blocked opcodes
* OP-080: `BALANCE` (0x31) and `SELFBALANCE` (0x47) are allowed only from a staked entity, else they are blocked
*/
checkOp011 (tracerResults: ERC7562Call): void {
const opcodes = tracerResults.usedOpcodes
const bannedOpCodeUsed =
Object
.keys(opcodes)
.map((opcode: string) => {
return getOpcodeName(parseInt(opcode)) ?? ''
})
.filter((opcode: string) => {
return bannedOpCodes.has(opcode)
})
bannedOpCodeUsed.forEach(
(opcode: string): void => {
this._violationDetected({
rule: ERC7562Rule.op011,
// TODO: fill in depth, entity
depth: -1,
entity: this.currentEntity,
address: tracerResults.from,
opcode,
value: '0',
errorCode: ValidationErrors.OpcodeValidation,
description: `${this.currentEntity.toString()} uses banned opcode: ${opcode.toString()}`
})
}
)
}

/**
* OP-020: Revert on "out of gas" is forbidden as it can "leak" the gas limit or the current call stack depth.
*/
checkOp020 (tracerResults: ERC7562Call): void {
if (tracerResults.outOfGas) {
this._violationDetected({
rule: ERC7562Rule.op020,
// TODO: fill in depth, entity
depth: -1,
entity: this.currentEntity,
address: tracerResults.from,
opcode: tracerResults.type,
value: '0',
errorCode: ValidationErrors.OpcodeValidation,
description: `${this.currentEntity.toString()} internally reverts on oog`
})
}
}

/**
* OP-031: CREATE2 is allowed exactly once in the deployment phase and must deploy code for the "sender" address
*/
checkOp031 (
userOp: OperationBase,
tracerResults: ERC7562Call
): void {
if (
tracerResults.type !== 'CREATE' &&
tracerResults.type !== 'CREATE2'
) {
return
}
const isFactoryStaked = this._isEntityStaked(AccountAbstractionEntity.factory)
const isAllowedCreateByOP032 =
userOp.factory != null &&
tracerResults.type === 'CREATE' &&
this.currentEntity === AccountAbstractionEntity.account &&
tracerResults.from.toLowerCase() === userOp.sender.toLowerCase()
const isAllowedCreateByEREP060 =
(
tracerResults.from.toLowerCase() === userOp.sender?.toLowerCase() ||
tracerResults.from.toLowerCase() === userOp.factory?.toLowerCase()
) &&
isFactoryStaked
const isAllowedCreateSenderByFactory =
this.currentEntity === AccountAbstractionEntity.factory &&
tracerResults.to.toLowerCase() === userOp.sender.toLowerCase()
if (!(isAllowedCreateByOP032 || isAllowedCreateByEREP060 || isAllowedCreateSenderByFactory)) {
this._violationDetected({
rule: ERC7562Rule.op011,
// TODO: fill in depth, entity
depth: -1,
entity: this.currentEntity,
address: tracerResults.from ?? 'n/a',
opcode: 'CREATE2',
value: '0',
errorCode: ValidationErrors.OpcodeValidation,
description: `${this.currentEntity.toString()} uses banned opcode: CREATE2`
})
}
}

/**
* OP-041: Access to an address without a deployed code is forbidden for EXTCODE* and *CALL opcodes
*/
checkOp041 (
userOp: OperationBase,
tracerResults: ERC7562Call
): void {
// the only contract we allow to access before its deployment is the "sender" itself, which gets created.
let illegalZeroCodeAccess: any
for (const addr of Object.keys(tracerResults.contractSize)) {
// [OP-042]
if (addr.toLowerCase() !== userOp.sender.toLowerCase() && addr.toLowerCase() !== this.entryPointAddress.toLowerCase() && tracerResults.contractSize[addr].contractSize <= 2) {
illegalZeroCodeAccess = tracerResults.contractSize[addr]
illegalZeroCodeAccess.address = addr
this._violationDetected({
address: '',
depth: 0,
entity: this.currentEntity,
rule: ERC7562Rule.op041,
errorCode: ValidationErrors.OpcodeValidation,
description: `${this.currentEntity.toString()} accesses un-deployed contract address ${illegalZeroCodeAccess?.address as string} with opcode ${illegalZeroCodeAccess?.opcode as string}`
})
}
}
}

/**
* 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.
Expand Down Expand Up @@ -277,6 +397,25 @@ export class ERC7562Parser {
}
}

checkOp054ExtCode (
tracerResults: ERC7562Call,
address: string,
recursionDepth: number
): void {
for (const addr of tracerResults.extCodeAccessInfo) {
if (addr.toLowerCase() === this.entryPointAddress.toLowerCase()) {
this._violationDetected({
address,
depth: recursionDepth,
entity: this.currentEntity,
errorCode: ValidationErrors.OpcodeValidation,
rule: ERC7562Rule.op054,
description: `${this.currentEntity} accesses EntryPoint contract address ${this.entryPointAddress} with EXTCODE* opcode`
})
}
}
}

/**
* OP-061: CALL with value is forbidden. The only exception is a call to the EntryPoint.
*/
Expand All @@ -300,56 +439,8 @@ export class ERC7562Parser {
}

/**
* OP-020: Revert on "out of gas" is forbidden as it can "leak" the gas limit or the current call stack depth.
*/
checkOp020 (tracerResults: ERC7562Call): void {
if (tracerResults.outOfGas) {
this._violationDetected({
rule: ERC7562Rule.op020,
// TODO: fill in depth, entity
depth: -1,
entity: this.currentEntity,
address: tracerResults.from,
opcode: tracerResults.type,
value: '0',
errorCode: ValidationErrors.OpcodeValidation,
description: `${this.currentEntity.toString()} 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
* OP-080: BALANCE (0x31) and SELFBALANCE (0x47) are allowed only from a staked entity, else they are blocked
*/
checkOp011 (tracerResults: ERC7562Call): void {
const opcodes = tracerResults.usedOpcodes
const bannedOpCodeUsed =
Object
.keys(opcodes)
.map((opcode: string) => {
return getOpcodeName(parseInt(opcode)) ?? ''
})
.filter((opcode: string) => {
return bannedOpCodes.has(opcode)
})
bannedOpCodeUsed.forEach(
(opcode: string): void => {
this._violationDetected({
rule: ERC7562Rule.op011,
// TODO: fill in depth, entity
depth: -1,
entity: this.currentEntity,
address: tracerResults.from,
opcode,
value: '0',
errorCode: ValidationErrors.OpcodeValidation,
description: `${this.currentEntity.toString()} uses banned opcode: ${opcode.toString()}`
})
}
)
}

checkOp080 (tracerResults: ERC7562Call): void {
const opcodes = tracerResults.usedOpcodes
const isEntityStaked = this._isEntityStaked()
Expand Down Expand Up @@ -380,48 +471,6 @@ export class ERC7562Parser {
)
}

/**
* OP-031: CREATE2 is allowed exactly once in the deployment phase and must deploy code for the "sender" address
*/
checkOp031 (
userOp: OperationBase,
tracerResults: ERC7562Call
): void {
if (
tracerResults.type !== 'CREATE' &&
tracerResults.type !== 'CREATE2'
) {
return
}
const isFactoryStaked = this._isEntityStaked(AccountAbstractionEntity.factory)
const isAllowedCreateByOP032 =
tracerResults.type === 'CREATE' &&
this.currentEntity === AccountAbstractionEntity.account &&
tracerResults.from === userOp.sender.toLowerCase()
const isAllowedCreateByEREP060 =
(
tracerResults.from.toLowerCase() === userOp.sender?.toLowerCase() ||
tracerResults.from.toLowerCase() === userOp.factory?.toLowerCase()
) &&
isFactoryStaked
const isAllowedCreateSenderByFactory =
this.currentEntity === AccountAbstractionEntity.factory &&
tracerResults.to.toLowerCase() === userOp.sender.toLowerCase()
if (!(isAllowedCreateByOP032 || isAllowedCreateByEREP060 || isAllowedCreateSenderByFactory)) {
this._violationDetected({
rule: ERC7562Rule.op011,
// TODO: fill in depth, entity
depth: -1,
entity: this.currentEntity,
address: tracerResults.from ?? 'n/a',
opcode: 'CREATE2',
value: '0',
errorCode: ValidationErrors.OpcodeValidation,
description: `${this.currentEntity.toString()} uses banned opcode: CREATE2`
})
}
}

checkStorage (
userOp: OperationBase,
tracerResults: ERC7562Call
Expand Down Expand Up @@ -481,49 +530,4 @@ export class ERC7562Parser {
}
}
}

checkOp041 (
userOp: OperationBase,
tracerResults: ERC7562Call
): void {
// the only contract we allow to access before its deployment is the "sender" itself, which gets created.
let illegalZeroCodeAccess: any
for (const addr of Object.keys(tracerResults.contractSize)) {
// [OP-042]
if (addr.toLowerCase() !== userOp.sender.toLowerCase() && addr.toLowerCase() !== this.entryPointAddress.toLowerCase() && tracerResults.contractSize[addr].contractSize <= 2) {
illegalZeroCodeAccess = tracerResults.contractSize[addr]
illegalZeroCodeAccess.address = addr
this._violationDetected({
address: '',
depth: 0,
entity: this.currentEntity,
rule: ERC7562Rule.op041,
errorCode: ValidationErrors.OpcodeValidation,
description: `${this.currentEntity.toString()} accesses un-deployed contract address ${illegalZeroCodeAccess?.address as string} with opcode ${illegalZeroCodeAccess?.opcode as string}`
})
}
}
}

checkOp054ExtCode (
tracerResults: ERC7562Call,
address: string,
recursionDepth: number
): void {
const entityTitle = 'fixme'
let illegalEntryPointCodeAccess
for (const addr of Object.keys(tracerResults.extCodeAccessInfo)) {
if (addr.toLowerCase() === this.entryPointAddress.toLowerCase()) {
illegalEntryPointCodeAccess = tracerResults.extCodeAccessInfo
this._violationDetected({
address,
depth: recursionDepth,
entity: this.currentEntity,
errorCode: ValidationErrors.OpcodeValidation,
rule: ERC7562Rule.op054,
description: `${entityTitle} accesses EntryPoint contract address ${this.entryPointAddress} with opcode $ {'todo'}`
})
}
}
}
}

0 comments on commit 21bc4b0

Please sign in to comment.