Skip to content

Commit 3da8861

Browse files
committed
EIP-3670: EOF - Code Validation, EIP-5450: EOF - Stack Validation
1 parent 49f43d2 commit 3da8861

File tree

10 files changed

+898
-12
lines changed

10 files changed

+898
-12
lines changed

nimbus/evm/analysis.nim

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Nimbus
2+
# Copyright (c) 2023 Status Research & Development GmbH
3+
# Licensed under either of
4+
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
5+
# http://www.apache.org/licenses/LICENSE-2.0)
6+
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
7+
# http://opensource.org/licenses/MIT)
8+
# at your option. This file may not be copied, modified, or
9+
# distributed except according to those terms.
10+
11+
import
12+
interpreter/op_codes
13+
14+
const
15+
set2BitsMask = uint16(0b11)
16+
set3BitsMask = uint16(0b111)
17+
set4BitsMask = uint16(0b1111)
18+
set5BitsMask = uint16(0b1_1111)
19+
set6BitsMask = uint16(0b11_1111)
20+
set7BitsMask = uint16(0b111_1111)
21+
22+
# bitvec is a bit vector which maps bytes in a program.
23+
# An unset bit means the byte is an opcode, a set bit means
24+
# it's data (i.e. argument of PUSHxx).
25+
type
26+
Bitvec* = seq[byte]
27+
28+
proc set1(bits: var Bitvec, pos: int) =
29+
let x = bits[pos div 8]
30+
bits[pos div 8] = x or byte(1 shl (pos mod 8))
31+
32+
proc setN(bits: var Bitvec, flag: uint16, pos: int) =
33+
let z = pos div 8
34+
let a = flag shl (pos mod 8)
35+
let x = bits[z]
36+
bits[z] = x or byte(a)
37+
let b = byte(a shr 8)
38+
if b != 0:
39+
bits[z+1] = b
40+
41+
proc set8(bits: var Bitvec, pos: int) =
42+
let z = pos div 8
43+
let a = byte(0xFF shl (pos mod 8))
44+
bits[z] = bits[z] or a
45+
bits[z+1] = not a
46+
47+
proc set16(bits: var Bitvec, pos: int) =
48+
let z = pos div 8
49+
let a = byte(0xFF shl (pos mod 8))
50+
bits[z] = bits[z] or a
51+
bits[z+1] = 0xFF
52+
bits[z+2] = not a
53+
54+
# codeSegment checks if the position is in a code segment.
55+
proc codeSegment*(bits: Bitvec, pos: int): bool =
56+
((bits[pos div 8] shr (pos mod 8)) and 1) == 0
57+
58+
# codeBitmapInternal is the internal implementation of codeBitmap.
59+
# It exists for the purpose of being able to run benchmark tests
60+
# without dynamic allocations affecting the results.
61+
proc codeBitmapInternal(bits: var Bitvec; code: openArray[byte]) =
62+
var pc = 0
63+
while pc < code.len:
64+
let op = Op(code[pc])
65+
inc pc
66+
67+
if op < PUSH1:
68+
continue
69+
70+
var numbits = op.int - PUSH1.int + 1
71+
if numbits >= 8:
72+
while numbits >= 16:
73+
bits.set16(pc)
74+
pc += 16
75+
numbits -= 16
76+
77+
while numbits >= 8:
78+
bits.set8(pc)
79+
pc += 8
80+
numbits -= 8
81+
82+
case numbits
83+
of 1: bits.set1(pc)
84+
of 2: bits.setN(set2BitsMask, pc)
85+
of 3: bits.setN(set3BitsMask, pc)
86+
of 4: bits.setN(set4BitsMask, pc)
87+
of 5: bits.setN(set5BitsMask, pc)
88+
of 6: bits.setN(set6BitsMask, pc)
89+
of 7: bits.setN(set7BitsMask, pc)
90+
else: discard
91+
pc += numbits
92+
93+
# codeBitmap collects data locations in code.
94+
proc codeBitmap*(code: openArray[byte]): Bitvec =
95+
# The bitmap is 4 bytes longer than necessary, in case the code
96+
# ends with a PUSH32, the algorithm will push zeroes onto the
97+
# bitvector outside the bounds of the actual code.
98+
let len = (code.len div 8)+1+4
99+
result = newSeq[byte](len)
100+
result.codeBitmapInternal(code)
101+
102+
# eofCodeBitmapInternal is the internal implementation of codeBitmap for EOF
103+
# code validation.
104+
proc eofCodeBitmapInternal(bits: var Bitvec; code: openArray[byte]) =
105+
var pc = 0
106+
while pc < code.len:
107+
let op = Op(code[pc])
108+
inc pc
109+
110+
# RJUMP and RJUMPI always have 2 byte operand.
111+
if op == RJUMP or op == RJUMPI:
112+
bits.setN(set2BitsMask, pc)
113+
pc += 2
114+
continue
115+
116+
var numbits = 0
117+
if op >= PUSH1 and op <= PUSH32:
118+
numbits = op.int - PUSH1.int + 1
119+
elif op == RJUMPV:
120+
# RJUMPV is unique as it has a variable sized operand.
121+
# The total size is determined by the count byte which
122+
# immediate proceeds RJUMPV. Truncation will be caught
123+
# in other validation steps -- for now, just return a
124+
# valid bitmap for as much of the code as is
125+
# available.
126+
if pc >= code.len:
127+
# Count missing, no more bits to mark.
128+
return
129+
numbits = code[pc].int*2 + 1
130+
if pc+numbits > code.len:
131+
# Jump table is truncated, mark as many bits
132+
# as possible.
133+
numbits = code.len - pc
134+
else:
135+
# If not PUSH (the int8(op) > int(PUSH32) is always false).
136+
continue
137+
138+
if numbits >= 8:
139+
while numbits >= 16:
140+
bits.set16(pc)
141+
pc += 16
142+
numbits -= 16
143+
144+
while numbits >= 8:
145+
bits.set8(pc)
146+
pc += 8
147+
numbits -= 8
148+
149+
case numbits
150+
of 1: bits.set1(pc)
151+
of 2: bits.setN(set2BitsMask, pc)
152+
of 3: bits.setN(set3BitsMask, pc)
153+
of 4: bits.setN(set4BitsMask, pc)
154+
of 5: bits.setN(set5BitsMask, pc)
155+
of 6: bits.setN(set6BitsMask, pc)
156+
of 7: bits.setN(set7BitsMask, pc)
157+
else: discard
158+
pc += numbits
159+
160+
# eofCodeBitmap collects data locations in code.
161+
proc eofCodeBitmap*(code: openArray[byte]): Bitvec =
162+
# The bitmap is 4 bytes longer than necessary, in case the code
163+
# ends with a PUSH32, the algorithm will push zeroes onto the
164+
# bitvector outside the bounds of the actual code.
165+
let len = (code.len div 8)+1+4
166+
result = newSeq[byte](len)
167+
result.eofCodeBitmapInternal(code)

nimbus/evm/computation.nim

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import
1212
".."/[db/accounts_cache, constants],
1313
"."/[code_stream, memory, message, stack, state],
14-
"."/[types],
14+
"."/[types, validate],
1515
./interpreter/[gas_meter, gas_costs, op_codes],
1616
../common/[common, evmforks],
1717
../utils/[utils, eof],
@@ -318,6 +318,11 @@ proc writeContract*(c: Computation)
318318
c.setError("EOF retcode parse error: " & res.error.toString, true)
319319
return
320320

321+
let vres = con.validateCode()
322+
if vres.isErr:
323+
c.setError("EOF retcode validate error: " & vres.error.toString, true)
324+
return
325+
321326
elif fork >= FkLondon:
322327
withExtra trace, "New contract code starts with 0xEF byte, not allowed by EIP-3541"
323328
# TODO: Return `EVMC_CONTRACT_VALIDATION_FAILURE` (like Silkworm).

nimbus/evm/interpreter/op_handlers.nim

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,6 @@ proc mkOpTable(selected: EVMFork): array[Op,Vm2OpExec] {.compileTime.} =
7575
# Public functions
7676
# ------------------------------------------------------------------------------
7777

78-
#const
79-
# vm2OpHandlers* = block:
80-
# var rc: array[Fork, array[Op, Vm2OpExec]]
81-
# for w in Fork:
82-
# rc[w] = w.mkOpTable
83-
# rc
84-
8578
type
8679
vmOpHandlersRec* = tuple
8780
name: string ## Name (or ID) of op handler

nimbus/evm/interpreter/op_handlers/oph_call.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ const
541541
post: vm2OpIgnore)),
542542

543543
(opCode: CallCode, ## 0xf2, Message-Call with alternative code
544-
forks: Vm2OpAllForks,
544+
forks: Vm2OpAllForks - Vm2OpEOFAndLater,
545545
name: "callCode",
546546
info: "Message-call into this account with alternative account's code",
547547
exec: (prep: vm2OpIgnore,

nimbus/evm/interpreter/op_handlers/oph_sysops.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ const
210210
post: vm2OpIgnore)),
211211

212212
(opCode: SelfDestruct, ## 0xff, EIP2929: self destruct, Berlin and later
213-
forks: Vm2OpBerlinAndLater,
213+
forks: Vm2OpBerlinAndLater - Vm2OpEOFAndLater,
214214
name: "selfDestructEIP2929",
215215
info: "EIP2929: Halt execution and register account for later deletion",
216216
exec: (prep: vm2OpIgnore,

nimbus/evm/interpreter_dispatch.nim

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const
1616
import
1717
std/[macros, sets, strformat],
1818
".."/[constants, db/accounts_cache],
19-
"."/[code_stream, computation],
19+
"."/[code_stream, computation, validate],
2020
"."/[message, precompiles, state, types],
2121
../utils/[utils, eof],
2222
./interpreter/[op_dispatcher, gas_costs],
@@ -161,6 +161,11 @@ proc beforeExecCreate(c: Computation): bool
161161
c.setError("EOF initcode parse error: " & res.error.toString, false)
162162
return true
163163

164+
let vres = c.code.container.validateCode()
165+
if vres.isErr:
166+
c.setError("EOF initcode validation error: " & vres.error.toString, false)
167+
return true
168+
164169
c.snapshot()
165170

166171
if c.vmState.readOnlyStateDB().hasCodeOrNonce(c.msg.contractAddress):

0 commit comments

Comments
 (0)