Skip to content

Commit

Permalink
EVM: fix EOF fixtures (#3568)
Browse files Browse the repository at this point in the history
* evm: fix swapn stack validation

* common/evm/vm: add osaka support

* evm: guard eof gas methods

* evm: fix callf/jumpf stack validation

* evm: correctly guard eof methods

* evm: implement missing RETURNDATALOAD

* evm: fix delegatecall return buffer, eofcreate static mode

* evm: fix 7702 extcodehash on empty

* evm: remove old eof logic

* evm/eof: fix dangling bytes header

* evm/eof: throw on invalid returning sections

* evm/eof: verify rjumps per code section, not entire body

* eof: validation script update

* evm: remove unused _common args

---------

Co-authored-by: Holger Drewes <[email protected]>
  • Loading branch information
jochem-brouwer and holgerd77 authored Dec 13, 2024
1 parent 100d77d commit 662af35
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 64 deletions.
4 changes: 4 additions & 0 deletions packages/common/src/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ export const Mainnet: ChainConfig = {
name: 'prague',
block: null,
},
{
name: 'osaka',
block: null,
},
],
bootstrapNodes: [
{
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export enum Hardfork {
Shanghai = 'shanghai',
Cancun = 'cancun',
Prague = 'prague',
Osaka = 'osaka',
Verkle = 'verkle',
}

Expand Down
5 changes: 3 additions & 2 deletions packages/common/src/hardforks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,11 @@ export const hardforksDict: HardforksDict = {
* Status : Final
*/
prague: {
// TODO update this accordingly to the right devnet setup
//eips: [663, 3540, 3670, 4200, 4750, 5450, 6206, 7069, 7480, 7620, 7692, 7698], // This is EOF-only
eips: [2537, 2935, 6110, 7002, 7251, 7685, 7702], // This is current prague without EOF
},
osaka: {
eips: [663, 3540, 3670, 4200, 4750, 5450, 6206, 7069, 7480, 7620, 7692, 7698], // These are the EOF EIPs
},
/**
* Description: Next feature hardfork after prague, internally used for verkle testing/implementation (incomplete/experimental)
* URL : https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/osaka.md
Expand Down
4 changes: 4 additions & 0 deletions packages/evm/src/eof/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ class EOFBody {
}
} else {
dataSection = stream.readRemainder()

if (dataSection.length > header.dataSize) {
validationError(EOFError.DanglingBytes)
}
}

// Write all data to the object
Expand Down
1 change: 1 addition & 0 deletions packages/evm/src/eof/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export enum EOFError {
InvalidStackHeight = 'invalid stack height',
InvalidJUMPF = 'invalid jumpf target (output count)',
InvalidReturningSection = 'invalid returning code section: section is not returning',
ReturningNoReturn = 'invalid section: section should return but has no RETF/JUMP to return',
RJUMPVTableSize0 = 'invalid RJUMPV: table size 0',
UnreachableCodeSections = 'unreachable code sections',
UnreachableCode = 'unreachable code (by forward jumps)',
Expand Down
75 changes: 44 additions & 31 deletions packages/evm/src/eof/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,31 +61,9 @@ function validateOpcodes(
evm: EVM,
mode: ContainerSectionType = ContainerSectionType.RuntimeCode,
) {
// Track the intermediate bytes
const intermediateBytes = new Set<number>()
// Track the jump locations (for forward jumps it is unknown at the first pass if the byte is intermediate)
const jumpLocations = new Set<number>()

// Track the type of the container targets
// Should at the end of the analysis have all the containers
const containerTypeMap = new Map<number, ContainerSectionType>()

function addJump(location: number) {
if (intermediateBytes.has(location)) {
// When trying to JUMP into an intermediate byte: this is invalid
validationError(EOFError.InvalidRJUMP)
}
jumpLocations.add(location)
}

function addIntermediate(location: number) {
if (jumpLocations.has(location)) {
// When trying to add an intermediate to a location already JUMPed to: this is invalid
validationError(EOFError.InvalidRJUMP)
}
intermediateBytes.add(location)
}

// TODO (?) -> stackDelta currently only has active EOF opcodes, can use it directly (?)
// (so no need to generate the valid opcodeNumbers)

Expand Down Expand Up @@ -156,11 +134,42 @@ function validateOpcodes(

let codeSection = -1
for (const code of container.body.codeSections) {
// Track the intermediate bytes
const intermediateBytes = new Set<number>()
// Track the jump locations (for forward jumps it is unknown at the first pass if the byte is intermediate)
const jumpLocations = new Set<number>()

// eslint-disable-next-line no-inner-declarations
function addJump(location: number) {
if (intermediateBytes.has(location)) {
// When trying to JUMP into an intermediate byte: this is invalid
validationError(EOFError.InvalidRJUMP)
}
jumpLocations.add(location)
}

// eslint-disable-next-line no-inner-declarations
function addIntermediate(location: number) {
if (jumpLocations.has(location)) {
// When trying to add an intermediate to a location already JUMPed to: this is invalid
validationError(EOFError.InvalidRJUMP)
}
intermediateBytes.add(location)
}

codeSection++

reachableSections[codeSection] = new Set()

const returningFunction = container.body.typeSections[codeSection].outputs === 0x80
// Section is marked as "non-returning": it does never "return" to another code section
// it rather exits the current EVM call frame
const nonReturningFunction = container.body.typeSections[codeSection].outputs === 0x80

// Boolean flag to mark if this section has a returning opcode:
// RETF
// Or JUMPF into a returning section
// Each returning section should contain a returning opcode
let sectionHasReturningOpcode = false

// Tracking set of reachable opcodes
const reachableOpcodes = new Set<number>()
Expand Down Expand Up @@ -212,12 +221,12 @@ function validateOpcodes(
let minStackNext = minStackCurrent + delta
let maxStackNext = maxStackCurrent + delta

if (maxStackNext > 1023) {
if (maxStackNext > 1024) {
// TODO verify if 1023 or 1024 is the right constant
validationError(EOFError.StackOverflow)
}

if (returningFunction && opcode === 0xe4) {
if (nonReturningFunction && opcode === 0xe4) {
validationError(EOFError.InvalidReturningSection)
}

Expand Down Expand Up @@ -328,7 +337,7 @@ function validateOpcodes(
validationError(EOFError.InvalidJUMPF)
}

if (returningFunction && targetOutputs <= 0x7f) {
if (nonReturningFunction && targetOutputs <= 0x7f) {
// Current function is returning, but target is not, cannot jump into this
validationError(EOFError.InvalidReturningSection)
}
Expand All @@ -344,12 +353,12 @@ function validateOpcodes(
if (!(minStackCurrent === maxStackCurrent && maxStackCurrent === expectedStack)) {
validationError(EOFError.InvalidStackHeight)
}
sectionHasReturningOpcode = true
}
if (
maxStackCurrent + container.body.typeSections[target].maxStackHeight - targetInputs >
1024
) {
//console.log(maxStackCurrent, targetOutputs, targetInputs, targetNonReturning)
validationError(EOFError.StackOverflow)
}
}
Expand All @@ -360,6 +369,7 @@ function validateOpcodes(
if (!(minStackCurrent === maxStackCurrent && maxStackCurrent === outputs)) {
validationError(EOFError.InvalidStackHeight)
}
sectionHasReturningOpcode = true
} else if (opcode === 0xe6) {
// DUPN
const toDup = code[ptr + 1]
Expand All @@ -369,9 +379,7 @@ function validateOpcodes(
} else if (opcode === 0xe7) {
// SWAPN
const toSwap = code[ptr + 1]
// TODO: EVMONEs test wants this to be `toSwap + 2`, but that seems to be incorrect
// Will keep `toSwap + 1` for now
if (toSwap + 1 > minStackCurrent) {
if (toSwap + 2 > minStackCurrent) {
validationError(EOFError.StackUnderflow)
}
} else if (opcode === 0xe8) {
Expand Down Expand Up @@ -485,10 +493,15 @@ function validateOpcodes(
if (container.body.typeSections[codeSection].maxStackHeight !== maxStackHeight) {
validationError(EOFError.MaxStackHeightViolation)
}
if (maxStackHeight > 1023) {
if (maxStackHeight > 1024) {
// TODO verify if 1023 or 1024 is the right constant
validationError(EOFError.MaxStackHeightLimit)
}

// Validate that if the section is returning, there is a returning opcode
if (!sectionHasReturningOpcode && !nonReturningFunction) {
validationError(EOFError.ReturningNoReturn)
}
}

// Verify that each code section can be reached from code section 0
Expand Down
1 change: 1 addition & 0 deletions packages/evm/src/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export class EVM implements EVMInterface {
Hardfork.Shanghai,
Hardfork.Cancun,
Hardfork.Prague,
Hardfork.Osaka,
Hardfork.Verkle,
]
protected _tx?: {
Expand Down
Loading

0 comments on commit 662af35

Please sign in to comment.