Skip to content

Osaka: Mega EOF #3229

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

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions execution_chain/constants.nim
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,8 @@ const
HISTORY_STORAGE_ADDRESS* = address"0x0000F90827F1C53a10cb7A02335B175320002935"
WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS* = address"0x00000961Ef480Eb55e80D19ad83579A64c007002"
CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS* = address"0x0000BBdDc7CE488642fb579F8B00f3a590007251"

# EIP-7873 constants
MAX_INITCODE_COUNT* = 256

# End
195 changes: 195 additions & 0 deletions execution_chain/core/eof/eip4750.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# nimbus-execution-client
# Copyright (c) 2025 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.

{.push raises: [].}

import
std/[sequtils, intsets],
results,
./eof_utils

func validateEof*(code: openArray[byte]): Result[void, string] =
# Check version
if code.len < 3 or code[2] != VERSION:
return err("invalid version")

# Process section headers
var
pos = 3
codeSectionSize: seq[int]
dataSectionSize = Opt.none(int)
typeSectionSize = Opt.none(int)

while true:
# Terminator not found
if pos >= code.len:
return err("no section terminator")

let sectionId = code[pos]
inc pos
if sectionId == TERMINATOR:
break

# Disallow unknown sections
if sectionId notin [CODE, DATA, TYPE]:
return err("invalid section id")

# Data section preceding code section (i.e. code section following data section)
if sectionId == CODE and dataSectionSize.isSome:
return err("data section preceding code section")

# Code section or data section preceding type section
if sectionId == TYPE and (codeSectionSize.len > 0 or dataSectionSize.isSome):
return err("code or data section preceding type section")

# Multiple type or data sections
if sectionId == TYPE and typeSectionSize.isSome:
return err("multiple type sections")
if sectionId == DATA and dataSectionSize.isSome:
return err("multiple data sections")

# Truncated section size
if (pos + 1) >= code.len:
return err("truncated section size")

let sectionSize = code.parseUint16(pos)
if sectionId == DATA:
dataSectionSize = Opt.some(sectionSize)
elif sectionId == TYPE:
typeSectionSize = Opt.some(sectionSize)
else:
codeSectionSize.add sectionSize

inc(pos, 2)

# Empty section
if sectionSize == 0:
return err("empty section")

# Code section cannot be absent
if codeSectionSize.len == 0:
return err("no code section")

# Not more than 1024 code sections
if codeSectionSize.len > 1024:
return err("more than 1024 code sections")

# Type section can be absent only if single code section is present
if typeSectionSize.isNone and codeSectionSize.len != 1:
return err("no obligatory type section")

# Type section, if present, has size corresponding to number of code sections
if typeSectionSize.isSome and typeSectionSize.value != codeSectionSize.len * 2:
return err("invalid type section size")

# The entire container must be scanned
if code.len != (pos + typeSectionSize.get(0) + codeSectionSize.foldl(a + b) + dataSectionSize.get(0)):
return err("container size not equal to sum of section sizes")

# First type section, if present, has 0 inputs and 0 outputs
if typeSectionSize.isSome and (code[pos] != 0 or code[pos + 1] != 0):
return err("invalid type of section 0")

ok()
#[
# Raises ValidationException on invalid code
func validateCodeSection*(funcId: int,
code: openArray[byte],
types: openArray[FunctionType] = [ZeroFunctionType]):
Result[void, string] =
# Note that EOF1 already asserts this with the code section requirements
assert code.len > 0
assert funcId < types.len

var
pos = 0
opcode = 0.byte
rjumpdests = initIntSet()
immediates = initIntSet()

while pos < code.len:
# Ensure the opcode is valid
opcode = code[pos]
inc pos
if opcode notin ValidOpcodes:
return err("undefined instruction")

var pcPostInstruction = pos + ImmediateSizes[opcode].int

if opcode == OP_RJUMP or opcode == OP_RJUMPI:
if pos + 2 > code.len:
return err("truncated relative jump offset")
let offset = code.parseInt16(pos)

let rjumpdest = pos + 2 + offset
if rjumpdest < 0 or rjumpdest >= code.len:
return err("relative jump destination out of bounds")

rjumpdests.incl(rjumpdest)

elif opcode == OP_RJUMPV:
if pos + 1 > code.len:
return err("truncated jump table")
let jumpTableSize = code[pos].int
if jumpTableSize == 0:
return err("empty jump table")

pcPostInstruction = pos + 1 + 2 * jumpTableSize
if pcPostInstruction > code.len:
return err("truncated jump table")

for offsetPos in countup(pos + 1, pcPostInstruction-1, 2):
let offset = code.parseInt16(offsetPos)

let rjumpdest = pcPostInstruction + offset
if rjumpdest < 0 or rjumpdest >= code.len:
return err("relative jump destination out of bounds")

rjumpdests.incl(rjumpdest)

elif opcode == OP_CALLF:
if pos + 2 > code.len:
return err("truncated CALLF immediate")
let sectionId = code.parseUint16(pos)

if sectionId >= types.len:
return err("invalid section id")

elif opcode == OP_JUMPF:
if pos + 2 > code.len:
return err("truncated JUMPF immediate")
let sectionId = code.parseUint16(pos)

if sectionId >= types.len:
return err("invalid section id")

if types[sectionId].outputs != types[funcId].outputs:
return err("incompatible function type for JUMPF")

# Save immediate value positions
for x in pos..<pcPostInstruction:
immediates.incl(x)

# Skip immediates
pos = pcPostInstruction

# Ensure last opcode's immediate doesn't go over code end
if pos != code.len:
return err("truncated immediate")

# opcode is the *last opcode*
if opcode notin TerminatingOpcodes:
return err("no terminating instruction")

# Ensure relative jump destinations don't target immediates
if not rjumpdests.disjoint(immediates):
return err("relative jump destination targets immediate")

ok()
]#
100 changes: 100 additions & 0 deletions execution_chain/core/eof/eip5450.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# nimbus-execution-client
# Copyright (c) 2025 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.

{.push raises: [].}

import
std/[tables, deques],
results,
./eip4750,
./eip5450_table,
./eof_utils,
../../evm/interpreter/codes

func validateFunction*(funcId: int,
code: openArray[byte],
types: openArray[FunctionType] = [ZeroFunctionType]):
Result[int, string] =
assert funcId >= 0
assert funcId < types.len
assert types[funcId].inputs >= 0
assert types[funcId].outputs >= 0

?validateCodeSection(funcId, code, types)

var
stackHeights: Table[int, int]
startStackHeight = types[funcId].inputs
maxStackHeight = startStackHeight

# queue of instructions to analyze, list of (pos, stackHeight) pairs
worklist = [(0, startStackHeight)].toDeque

while worklist.len > 0:
var (pos, stackHeight) = worklist.popFirst
while true:
# Assuming code ends with a terminating instruction due to previous validation in validate_code_section()
assert pos < code.len, "code is invalid"
let
op = code[pos]
info = InstrTable[op.Op]

# Check if stack height (type arity) at given position is the same
# for all control flow paths reaching this position.
if pos in stackHeights:
if stackHeight != stackHeights[pos]:
return err("stack height mismatch for different paths")
else:
break
else:
stackHeights[pos] = stackHeight


var
stackHeightRequired = info.stackHeightRequired
stackHeightChange = info.stackHeightChange

if op == OP_CALLF:
let calledFuncId = code.parseUint16(pos + 1)
# Assuming calledFuncId is valid due to previous validation in validate_code_section()
stackHeightRequired += types[calledFuncId].inputs
stackHeightChange += types[calledFuncId].outputs - types[calledFuncId].inputs

# Detect stack underflow
if stackHeight < stackHeightRequired:
return err("stack underflow")

stackHeight += stackHeightChange
maxStackHeight = max(maxStackHeight, stackHeight)

# Handle jumps
if op == OP_RJUMP:
let offset = code.parseInt16(pos + 1)
pos += info.immediateSize + 1 + offset # pos is valid for validated code.

elif op == OP_RJUMPI:
let offset = code.parseInt16(pos + 1)
# Save true branch for later and continue to False branch.
worklist.addLast((pos + 3 + offset, stackHeight))
pos += info.immediateSize + 1

elif info.isTerminating:
let expectedHeight = if op == OP_RETF: types[funcId].outputs else: 0
if stackHeight != expectedHeight:
return err("non-empty stack on terminating instruction")
break

else:
pos += info.immediateSize + 1


if maxStackHeight >= 1023:
return err("max stack above limit")

ok(maxStackHeight)
Loading
Loading