Skip to content

Commit

Permalink
Merge branch 'master' into allowed-precompiles
Browse files Browse the repository at this point in the history
  • Loading branch information
shahafn committed Feb 17, 2025
2 parents 0c0ea2e + fa411b7 commit 73449d5
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 59 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/setup-node@v1
- uses: actions/setup-node@v4
with:
node-version: '18'
- uses: actions/checkout@v1
- uses: actions/cache@v2
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}
Expand All @@ -31,11 +31,11 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
- uses: actions/setup-node@v4
with:
node-version: '18'
- uses: actions/checkout@v1
- uses: actions/cache@v2
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}
Expand Down
1 change: 1 addition & 0 deletions packages/utils/src/deployStakeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const stakeManagerSalt = '0x90d8084deab30c2a37c45e8d47f49f2f7965183cb6990

export async function deployStakeManager (provider: JsonRpcProvider, signer = provider.getSigner()): Promise<Rip7560StakeManager> {
const addr = await new DeterministicDeployer(provider, signer).deterministicDeploy(stakeManagerByteCode, stakeManagerSalt)
console.log(`Deployed StakeManager contract at: ${addr}`)
return Rip7560StakeManager__factory.connect(addr, signer)
}

Expand Down
3 changes: 2 additions & 1 deletion packages/validation-manager/src/ERC7562Call.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ow from 'ow'
import { CallFrameType } from './altmempool/AltMempoolConfig'

export interface ContractSize {
contractSize: number
Expand All @@ -24,7 +25,7 @@ export interface ERC7562Call {
outOfGas: boolean
output?: string
to: string
type: string
type: CallFrameType
usedOpcodes: Record<number, number>
value?: string
calls?: ERC7562Call[]
Expand Down
129 changes: 87 additions & 42 deletions packages/validation-manager/src/ERC7562Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
SlotMap,
StakeInfo,
StorageMap,
ValidationErrors,
toBytes32
toBytes32,
ValidationErrors
} from '@account-abstraction/utils'

import { ERC7562Violation, toError } from './ERC7562Violation'
Expand Down Expand Up @@ -299,7 +299,7 @@ export class ERC7562Parser {
throw new Error('Unexpected traceCall result: no calls from entrypoint.')
}
this.stakeValidationResult = validationResult
this._innerStepRecursive(userOp, erc7562Call, 0)
this._innerStepRecursive(userOp, erc7562Call, 0, erc7562Call.to)
return {
contractAddresses: this.contractAddresses,
ruleViolations: this.ruleViolations,
Expand Down Expand Up @@ -380,6 +380,7 @@ export class ERC7562Parser {

private _violationDetected (violation: ERC7562Violation): void {
this.ruleViolations.push(violation)
console.error('violationDetected:', JSON.stringify(violation))
if (this.bailOnViolation) {
throw toError(violation)
}
Expand Down Expand Up @@ -474,31 +475,40 @@ export class ERC7562Parser {
private _innerStepRecursive (
userOp: OperationBase,
erc7562Call: ERC7562Call,
recursionDepth: number
recursionDepth: number,
delegatecallStorageAddress: string
): void {
const address = erc7562Call.to
const address: string = erc7562Call.to
this.contractAddresses.push(address)
this._detectEntityChange(userOp, erc7562Call)
this._checkOp011(erc7562Call, recursionDepth)
this._checkOp020(erc7562Call, recursionDepth)
this._checkOp031(userOp, erc7562Call, recursionDepth)
this._checkOp041(userOp, erc7562Call, recursionDepth)
this._checkOp054(erc7562Call, recursionDepth)
this._checkOp054ExtCode(erc7562Call, address, recursionDepth)
this._checkOp061(erc7562Call, recursionDepth)
this._checkOp062AllowedPrecompiles(erc7562Call, recursionDepth)
this._checkOp080(erc7562Call, recursionDepth)
this._checkStorage(userOp, erc7562Call, recursionDepth)
this._checkOp011(erc7562Call, recursionDepth, delegatecallStorageAddress)
this._checkOp020(erc7562Call, recursionDepth, delegatecallStorageAddress)
this._checkOp031(userOp, erc7562Call, recursionDepth, delegatecallStorageAddress)
this._checkOp041(userOp, erc7562Call, recursionDepth, delegatecallStorageAddress)
this._checkOp054(erc7562Call, recursionDepth, delegatecallStorageAddress)
this._checkOp054ExtCode(erc7562Call, address, recursionDepth, delegatecallStorageAddress)
this._checkOp061(erc7562Call, recursionDepth, delegatecallStorageAddress)
this._checkOp062AllowedPrecompiles(erc7562Call, recursionDepth, delegatecallStorageAddress)
this._checkOp080(erc7562Call, recursionDepth, delegatecallStorageAddress)
this._checkStorage(userOp, erc7562Call, recursionDepth, delegatecallStorageAddress)
for (const call of erc7562Call.calls ?? []) {
this._innerStepRecursive(userOp, call, recursionDepth + 1)
let newContext: string = call.to
if (call.type === 'DELEGATECALL') {
newContext = delegatecallStorageAddress
}
this._innerStepRecursive(userOp, call, recursionDepth + 1, newContext)
}
}

/**
* OP-011: Blocked opcodes
* OP-080: `BALANCE` (0x31) and `SELFBALANCE` (0x47) are allowed only from a staked entity, else they are blocked
*/
private _checkOp011 (erc7562Call: ERC7562Call, recursionDepth: number): void {
private _checkOp011 (
erc7562Call: ERC7562Call,
recursionDepth: number,
delegatecallStorageAddress: string
): void {
if (erc7562Call.to.toLowerCase() === this.entryPointAddress.toLowerCase()) {
// Currently inside the EntryPoint deposit code, no access control applies here
return
Expand All @@ -519,7 +529,9 @@ export class ERC7562Parser {
rule: ERC7562Rule.op011,
depth: recursionDepth,
entity: this.currentEntity,
address: erc7562Call.from,
address: erc7562Call.to,
delegatecallStorageAddress,
callFrameType: erc7562Call.type,
opcode,
value: '0',
errorCode: ValidationErrors.OpcodeValidation,
Expand All @@ -532,15 +544,21 @@ 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.
*/
private _checkOp020 (erc7562Call: ERC7562Call, recursionDepth: number): void {
private _checkOp020 (
erc7562Call: ERC7562Call,
recursionDepth: number,
delegatecallStorageAddress: string
): void {
if (erc7562Call.outOfGas) {
this._violationDetected({
rule: ERC7562Rule.op020,
// TODO: fill in depth, entity
depth: recursionDepth,
entity: this.currentEntity,
address: erc7562Call.from,
delegatecallStorageAddress,
opcode: erc7562Call.type,
callFrameType: erc7562Call.type,
value: '0',
errorCode: ValidationErrors.OpcodeValidation,
description: `${this.currentEntity.toString()} internally reverts on oog`
Expand All @@ -554,7 +572,8 @@ export class ERC7562Parser {
private _checkOp031 (
userOp: OperationBase,
erc7562Call: ERC7562Call,
recursionDepth: number
recursionDepth: number,
delegatecallStorageAddress: string
): void {
if (
erc7562Call.type !== 'CREATE' &&
Expand All @@ -566,7 +585,10 @@ export class ERC7562Parser {
const isAllowedCreateByOP032 =
userOp.factory != null &&
erc7562Call.type === 'CREATE' &&
this.currentEntity === AccountAbstractionEntity.account &&
(
this.currentEntity === AccountAbstractionEntity.account ||
this.currentEntity === AccountAbstractionEntity.factory
) &&
erc7562Call.from.toLowerCase() === userOp.sender.toLowerCase()
const isAllowedCreateByEREP060 =
(
Expand All @@ -583,6 +605,8 @@ export class ERC7562Parser {
depth: recursionDepth,
entity: this.currentEntity,
address: erc7562Call.from ?? 'n/a',
delegatecallStorageAddress,
callFrameType: erc7562Call.type,
opcode: 'CREATE2',
value: '0',
errorCode: ValidationErrors.OpcodeValidation,
Expand All @@ -597,27 +621,30 @@ export class ERC7562Parser {
private _checkOp041 (
userOp: OperationBase,
erc7562Call: ERC7562Call,
recursionDepth: number
recursionDepth: number,
delegatecallStorageAddress: string
): 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(erc7562Call.contractSize)) {
for (const address of Object.keys(erc7562Call.contractSize)) {
// skip precompiles
if (this._isPrecompiled(addr)) {
if (this._isPrecompiled(address)) {
continue
}
// [OP-042]
if (
addr.toLowerCase() !== userOp.sender.toLowerCase() &&
// addr.toLowerCase() !== AA_ENTRY_POINT &&
addr.toLowerCase() !== this.entryPointAddress.toLowerCase() &&
erc7562Call.contractSize[addr].contractSize <= 2) {
illegalZeroCodeAccess = erc7562Call.contractSize[addr]
illegalZeroCodeAccess.address = addr
address.toLowerCase() !== userOp.sender.toLowerCase() &&
// address.toLowerCase() !== AA_ENTRY_POINT &&
address.toLowerCase() !== this.entryPointAddress.toLowerCase() &&
erc7562Call.contractSize[address].contractSize <= 2) {
illegalZeroCodeAccess = erc7562Call.contractSize[address]
illegalZeroCodeAccess.address = address
this._violationDetected({
address: '',
address,
delegatecallStorageAddress,
depth: recursionDepth,
entity: this.currentEntity,
callFrameType: erc7562Call.type,
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}`
Expand All @@ -633,7 +660,8 @@ export class ERC7562Parser {
*/
private _checkOp054 (
erc7562Call: ERC7562Call,
recursionDepth: number
recursionDepth: number,
delegatecallStorageAddress: string
): void {
const isCallToEntryPoint = this._isCallToEntryPoint(erc7562Call)
const knownMethod = this._tryDetectKnownMethod(erc7562Call)
Expand All @@ -653,8 +681,10 @@ export class ERC7562Parser {
depth: recursionDepth,
entity: this.currentEntity,
address: erc7562Call.from,
delegatecallStorageAddress,
opcode: erc7562Call.type,
value: erc7562Call.value,
callFrameType: erc7562Call.type,
errorCode: ValidationErrors.OpcodeValidation,
description: `illegal call into EntryPoint during validation ${knownMethod}`
})
Expand All @@ -664,15 +694,18 @@ export class ERC7562Parser {
private _checkOp054ExtCode (
erc7562Call: ERC7562Call,
address: string,
recursionDepth: number
recursionDepth: number,
delegatecallStorageAddress: string
): void {
for (const addr of erc7562Call.extCodeAccessInfo) {
if (addr.toLowerCase() === this.entryPointAddress.toLowerCase()) {
this._violationDetected({
address,
delegatecallStorageAddress,
depth: recursionDepth,
entity: this.currentEntity,
errorCode: ValidationErrors.OpcodeValidation,
callFrameType: erc7562Call.type,
rule: ERC7562Rule.op054,
description: `${this.currentEntity} accesses EntryPoint contract address ${this.entryPointAddress} with EXTCODE* opcode`
})
Expand All @@ -685,7 +718,8 @@ export class ERC7562Parser {
*/
private _checkOp061 (
erc7562Call: ERC7562Call,
recursionDepth: number
recursionDepth: number,
delegatecallStorageAddress: string
): void {
const isIllegalNonZeroValueCall =
!this._isCallToEntryPoint(erc7562Call) &&
Expand All @@ -696,10 +730,12 @@ export class ERC7562Parser {
depth: recursionDepth,
entity: this.currentEntity,
address: erc7562Call.from,
delegatecallStorageAddress,
opcode: erc7562Call.type,
value: erc7562Call.value,
callFrameType: erc7562Call.type,
errorCode: ValidationErrors.OpcodeValidation,
description: 'May not may CALL with value'
description: 'May not make a CALL with value'
})
}
}
Expand All @@ -713,10 +749,11 @@ export class ERC7562Parser {
*/
private _checkOp062AllowedPrecompiles (
erc7562Call: ERC7562Call,
recursionDepth: number
recursionDepth: number,
delegatecallStorageAddress: string
): void {
for (const addr of Object.keys(erc7562Call.contractSize)) {
if (this._isForbiddenPrecompiled(addr)) {
for (const address of Object.keys(erc7562Call.contractSize)) {
if (this._isForbiddenPrecompiled(address)) {
this._violationDetected({
rule: ERC7562Rule.op062,
depth: recursionDepth,
Expand All @@ -725,7 +762,9 @@ export class ERC7562Parser {
opcode: erc7562Call.type,
value: erc7562Call.value,
errorCode: ValidationErrors.OpcodeValidation,
description: 'Illegal call to forbidden precompile ' + addr
description: 'Illegal call to forbidden precompile ' + address,
callFrameType: erc7562Call.type,
delegatecallStorageAddress
})
}
}
Expand All @@ -736,7 +775,8 @@ export class ERC7562Parser {
*/
private _checkOp080 (
erc7562Call: ERC7562Call,
recursionDepth: number
recursionDepth: number,
delegatecallStorageAddress: string
): void {
const opcodes = erc7562Call.usedOpcodes
const isEntityStaked = this._isEntityStaked()
Expand All @@ -758,7 +798,9 @@ export class ERC7562Parser {
depth: recursionDepth,
entity: this.currentEntity,
address: erc7562Call.from ?? 'n/a',
delegatecallStorageAddress,
opcode,
callFrameType: erc7562Call.type,
value: '0',
errorCode: ValidationErrors.OpcodeValidation,
description: `unstaked ${this.currentEntity.toString()} uses banned opcode: ${opcode}`
Expand All @@ -770,7 +812,8 @@ export class ERC7562Parser {
private _checkStorage (
userOp: OperationBase,
erc7562Call: ERC7562Call,
recursionDepth: number
recursionDepth: number,
delegatecallStorageAddress: string
): void {
if (
erc7562Call.to.toLowerCase() === this.entryPointAddress.toLowerCase() ||
Expand Down Expand Up @@ -824,11 +867,13 @@ export class ERC7562Parser {
description = `${this.currentEntity.toString()} has forbidden ${readWrite} ${transientStr}${addressName} slot ${slot}`
}
this._violationDetected({
address: '',
address,
delegatecallStorageAddress,
depth: recursionDepth,
entity: this.currentEntity,
errorCode: ValidationErrors.OpcodeValidation,
rule: ERC7562Rule.sto010,
callFrameType: erc7562Call.type,
description
})
}
Expand Down
5 changes: 4 additions & 1 deletion packages/validation-manager/src/ERC7562Violation.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { RpcError, ValidationErrors } from '@account-abstraction/utils'

import { ERC7562Rule } from './enum/ERC7562Rule'
import { AccountAbstractionEntity } from './AccountAbstractionEntity'
import { ERC7562Rule } from './enum/ERC7562Rule'
import { CallFrameType } from './altmempool/AltMempoolConfig'

export interface ERC7562Violation {
rule: ERC7562Rule
depth: number
entity: AccountAbstractionEntity
address: string
delegatecallStorageAddress: string
errorCode: ValidationErrors
description: string
callFrameType: CallFrameType
conflict?: string
opcode?: string
value?: string
Expand Down
Loading

0 comments on commit 73449d5

Please sign in to comment.