Skip to content
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

implement EOF suite: EIP-3540, 3670, 4200, 4750, and 5450 #1413

Draft
wants to merge 7 commits 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions nimbus/common/evmforks.nim
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ const
FkParis* = EVMC_PARIS
FkShanghai* = EVMC_SHANGHAI
FkCancun* = EVMC_CANCUN


# Meta forks related to specific EIP
FkEOF* = FkCancun
10 changes: 7 additions & 3 deletions nimbus/common/hardforks.nim
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,14 @@ type
Shanghai
Cancun

const lastPurelyBlockNumberBasedFork* = GrayGlacier
# MergeFork is special because of TTD.
const firstTimeBasedFork* = Shanghai
const
lastPurelyBlockNumberBasedFork* = GrayGlacier

# MergeFork is special because of TTD.
firstTimeBasedFork* = Shanghai

# Meta Fork
EOFFork* = Cancun

type
CliqueOptions* = object
Expand Down
29 changes: 26 additions & 3 deletions nimbus/db/accounts_cache.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
import
std/[tables, hashes, sets],
eth/[common, rlp],
../constants, ../utils/[utils, eof],
../../stateless/multi_keys,
../constants,
../utils/utils,
./access_list as ac_access_list,
"."/[core_db, distinct_tries, storage_types, transient_storage]

Expand Down Expand Up @@ -376,6 +375,18 @@ proc getNonce*(ac: AccountsCache, address: EthAddress): AccountNonce {.inline.}
if acc.isNil: emptyAcc.nonce
else: acc.account.nonce

proc loadCode(acc: RefAccount, ac: AccountsCache) =
if CodeLoaded in acc.flags or CodeChanged in acc.flags:
return

when defined(geth):
let data = ac.kvt.get(acc.account.codeHash.data)
else:
let data = ac.kvt.get(contractHashKey(acc.account.codeHash).toOpenArray)

acc.code = data
acc.flags.incl CodeLoaded

proc getCode*(ac: AccountsCache, address: EthAddress): seq[byte] =
let acc = ac.getAccount(address, false)
if acc.isNil:
Expand All @@ -394,7 +405,18 @@ proc getCode*(ac: AccountsCache, address: EthAddress): seq[byte] =
result = acc.code

proc getCodeSize*(ac: AccountsCache, address: EthAddress): int {.inline.} =
ac.getCode(address).len
let acc = ac.getAccount(address, false)
if acc.isNil:
return
acc.loadCode(ac)
acc.code.len

proc hasEOFCode*(ac: AccountsCache, address: EthAddress): bool =
let acc = ac.getAccount(address, false)
if acc.isNil:
return
acc.loadCode(ac)
eof.hasEOFMagic(acc.code)

proc getCommittedStorage*(ac: AccountsCache, address: EthAddress, slot: UInt256): UInt256 {.inline.} =
let acc = ac.getAccount(address, false)
Expand Down Expand Up @@ -744,6 +766,7 @@ proc getStorage*(db: ReadOnlyStateDB, address: EthAddress, slot: UInt256): UInt2
proc getNonce*(db: ReadOnlyStateDB, address: EthAddress): AccountNonce {.borrow.}
proc getCode*(db: ReadOnlyStateDB, address: EthAddress): seq[byte] {.borrow.}
proc getCodeSize*(db: ReadOnlyStateDB, address: EthAddress): int {.borrow.}
proc hasEOFCode*(ac: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
proc hasCodeOrNonce*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
proc accountExists*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
proc isDeadAccount*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
Expand Down
167 changes: 167 additions & 0 deletions nimbus/evm/analysis.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Nimbus
# Copyright (c) 2023 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or
# distributed except according to those terms.

import
interpreter/op_codes

const
set2BitsMask = uint16(0b11)
set3BitsMask = uint16(0b111)
set4BitsMask = uint16(0b1111)
set5BitsMask = uint16(0b1_1111)
set6BitsMask = uint16(0b11_1111)
set7BitsMask = uint16(0b111_1111)

# bitvec is a bit vector which maps bytes in a program.
# An unset bit means the byte is an opcode, a set bit means
# it's data (i.e. argument of PUSHxx).
type
Bitvec* = seq[byte]

proc set1(bits: var Bitvec, pos: int) =
let x = bits[pos div 8]
bits[pos div 8] = x or byte(1 shl (pos mod 8))

proc setN(bits: var Bitvec, flag: uint16, pos: int) =
let z = pos div 8
let a = flag shl (pos mod 8)
let x = bits[z]
bits[z] = x or byte(a)
let b = byte(a shr 8)
if b != 0:
bits[z+1] = b

proc set8(bits: var Bitvec, pos: int) =
let z = pos div 8
let a = byte(0xFF shl (pos mod 8))
bits[z] = bits[z] or a
bits[z+1] = not a

proc set16(bits: var Bitvec, pos: int) =
let z = pos div 8
let a = byte(0xFF shl (pos mod 8))
bits[z] = bits[z] or a
bits[z+1] = 0xFF
bits[z+2] = not a

# codeSegment checks if the position is in a code segment.
proc codeSegment*(bits: Bitvec, pos: int): bool =
((bits[pos div 8] shr (pos mod 8)) and 1) == 0

# codeBitmapInternal is the internal implementation of codeBitmap.
# It exists for the purpose of being able to run benchmark tests
# without dynamic allocations affecting the results.
proc codeBitmapInternal(bits: var Bitvec; code: openArray[byte]) =
var pc = 0
while pc < code.len:
let op = Op(code[pc])
inc pc

if op < PUSH1:
continue

var numbits = op.int - PUSH1.int + 1
if numbits >= 8:
while numbits >= 16:
bits.set16(pc)
pc += 16
numbits -= 16

while numbits >= 8:
bits.set8(pc)
pc += 8
numbits -= 8

case numbits
of 1: bits.set1(pc)
of 2: bits.setN(set2BitsMask, pc)
of 3: bits.setN(set3BitsMask, pc)
of 4: bits.setN(set4BitsMask, pc)
of 5: bits.setN(set5BitsMask, pc)
of 6: bits.setN(set6BitsMask, pc)
of 7: bits.setN(set7BitsMask, pc)
else: discard
pc += numbits

# codeBitmap collects data locations in code.
proc codeBitmap*(code: openArray[byte]): Bitvec =
# The bitmap is 4 bytes longer than necessary, in case the code
# ends with a PUSH32, the algorithm will push zeroes onto the
# bitvector outside the bounds of the actual code.
let len = (code.len div 8)+1+4
result = newSeq[byte](len)
result.codeBitmapInternal(code)

# eofCodeBitmapInternal is the internal implementation of codeBitmap for EOF
# code validation.
proc eofCodeBitmapInternal(bits: var Bitvec; code: openArray[byte]) =
var pc = 0
while pc < code.len:
let op = Op(code[pc])
inc pc

# RJUMP and RJUMPI always have 2 byte operand.
if op == RJUMP or op == RJUMPI:
bits.setN(set2BitsMask, pc)
pc += 2
continue

var numbits = 0
if op >= PUSH1 and op <= PUSH32:
numbits = op.int - PUSH1.int + 1
elif op == RJUMPV:
# RJUMPV is unique as it has a variable sized operand.
# The total size is determined by the count byte which
# immediate proceeds RJUMPV. Truncation will be caught
# in other validation steps -- for now, just return a
# valid bitmap for as much of the code as is
# available.
if pc >= code.len:
# Count missing, no more bits to mark.
return
numbits = code[pc].int*2 + 1
if pc+numbits > code.len:
# Jump table is truncated, mark as many bits
# as possible.
numbits = code.len - pc
else:
# If not PUSH (the int8(op) > int(PUSH32) is always false).
continue

if numbits >= 8:
while numbits >= 16:
bits.set16(pc)
pc += 16
numbits -= 16

while numbits >= 8:
bits.set8(pc)
pc += 8
numbits -= 8

case numbits
of 1: bits.set1(pc)
of 2: bits.setN(set2BitsMask, pc)
of 3: bits.setN(set3BitsMask, pc)
of 4: bits.setN(set4BitsMask, pc)
of 5: bits.setN(set5BitsMask, pc)
of 6: bits.setN(set6BitsMask, pc)
of 7: bits.setN(set7BitsMask, pc)
else: discard
pc += numbits

# eofCodeBitmap collects data locations in code.
proc eofCodeBitmap*(code: openArray[byte]): Bitvec =
# The bitmap is 4 bytes longer than necessary, in case the code
# ends with a PUSH32, the algorithm will push zeroes onto the
# bitvector outside the bounds of the actual code.
let len = (code.len div 8)+1+4
result = newSeq[byte](len)
result.eofCodeBitmapInternal(code)