Skip to content

Commit ac0ddf9

Browse files
committed
EIP-3540: EOF - EVM Object Format v1
1 parent b940439 commit ac0ddf9

File tree

8 files changed

+541
-38
lines changed

8 files changed

+541
-38
lines changed

nimbus/db/accounts_cache.nim

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import
1212
std/[tables, hashes, sets],
1313
eth/[common, rlp],
14+
../constants, ../utils/[utils, eof], storage_types,
1415
../../stateless/multi_keys,
1516
../constants,
1617
../utils/utils,
@@ -378,6 +379,18 @@ proc getNonce*(ac: AccountsCache, address: EthAddress): AccountNonce {.inline.}
378379
if acc.isNil: emptyAcc.nonce
379380
else: acc.account.nonce
380381

382+
proc loadCode(acc: RefAccount, ac: AccountsCache) =
383+
if CodeLoaded in acc.flags or CodeChanged in acc.flags:
384+
return
385+
386+
when defined(geth):
387+
let data = ac.db.get(acc.account.codeHash.data)
388+
else:
389+
let data = ac.db.get(contractHashKey(acc.account.codeHash).toOpenArray)
390+
391+
acc.code = data
392+
acc.flags.incl CodeLoaded
393+
381394
proc getCode*(ac: AccountsCache, address: EthAddress): seq[byte] =
382395
let acc = ac.getAccount(address, false)
383396
if acc.isNil:
@@ -396,7 +409,18 @@ proc getCode*(ac: AccountsCache, address: EthAddress): seq[byte] =
396409
result = acc.code
397410

398411
proc getCodeSize*(ac: AccountsCache, address: EthAddress): int {.inline.} =
399-
ac.getCode(address).len
412+
let acc = ac.getAccount(address, false)
413+
if acc.isNil:
414+
return
415+
acc.loadCode(ac)
416+
acc.code.len
417+
418+
proc hasEOFCode*(ac: AccountsCache, address: EthAddress): bool =
419+
let acc = ac.getAccount(address, false)
420+
if acc.isNil:
421+
return
422+
acc.loadCode(ac)
423+
eof.hasEOFMagic(acc.code)
400424

401425
proc getCommittedStorage*(ac: AccountsCache, address: EthAddress, slot: UInt256): UInt256 {.inline.} =
402426
let acc = ac.getAccount(address, false)
@@ -728,6 +752,7 @@ proc getStorage*(db: ReadOnlyStateDB, address: EthAddress, slot: UInt256): UInt2
728752
proc getNonce*(db: ReadOnlyStateDB, address: EthAddress): AccountNonce {.borrow.}
729753
proc getCode*(db: ReadOnlyStateDB, address: EthAddress): seq[byte] {.borrow.}
730754
proc getCodeSize*(db: ReadOnlyStateDB, address: EthAddress): int {.borrow.}
755+
proc hasEOFCode*(ac: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
731756
proc hasCodeOrNonce*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
732757
proc accountExists*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
733758
proc isDeadAccount*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}

nimbus/evm/code_stream.nim

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,56 @@
66
# at your option. This file may not be copied, modified, or distributed except according to those terms.
77

88
import
9-
chronicles, strformat, strutils, sequtils, parseutils, sets, macros,
9+
std/[strformat, strutils, sequtils, parseutils, sets],
10+
chronicles,
1011
eth/common,
12+
stew/[results, endians2],
13+
stew/ranges/ptr_arith,
14+
../utils/eof,
1115
./interpreter/op_codes
1216

1317
logScope:
1418
topics = "vm code_stream"
1519

1620
type
21+
CodeView = ptr UncheckedArray[byte]
22+
1723
CodeStream* = ref object
18-
bytes*: seq[byte]
24+
# pre EOF byte code
25+
legacyCode*: seq[byte]
26+
27+
# view into legacyCode or
28+
# into one of EOF code section
29+
codeView: CodeView
30+
31+
# length of legacy code or
32+
# one of EOF code section
33+
codeLen: int
34+
1935
depthProcessed: int
2036
invalidPositions: HashSet[int]
2137
pc*: int
2238
cached: seq[(int, Op, string)]
2339

40+
# EOF container
41+
container*: Container
42+
43+
# EOF code section index
44+
section: int
45+
2446
proc `$`*(b: byte): string =
2547
$(b.int)
2648

2749
proc newCodeStream*(codeBytes: seq[byte]): CodeStream =
2850
new(result)
29-
shallowCopy(result.bytes, codeBytes)
51+
shallowCopy(result.legacyCode, codeBytes)
3052
result.pc = 0
3153
result.invalidPositions = initHashSet[int]()
3254
result.depthProcessed = 0
3355
result.cached = @[]
56+
result.codeLen = result.legacyCode.len
57+
if result.codeLen > 0:
58+
result.codeView = cast[CodeView](addr result.legacyCode[0])
3459

3560
proc newCodeStream*(codeBytes: string): CodeStream =
3661
newCodeStream(codeBytes.mapIt(it.byte))
@@ -47,37 +72,56 @@ proc newCodeStreamFromUnescaped*(code: string): CodeStream =
4772

4873
proc read*(c: var CodeStream, size: int): seq[byte] =
4974
# TODO: use openArray[bytes]
50-
if c.pc + size - 1 < c.bytes.len:
51-
result = c.bytes[c.pc .. c.pc + size - 1]
75+
if c.pc + size - 1 < c.codeLen:
76+
result = @(makeOpenArray(addr c.codeView[c.pc], byte, size))
5277
c.pc += size
5378
else:
5479
result = @[]
55-
c.pc = c.bytes.len
80+
c.pc = c.codeLen
5681

5782
proc readVmWord*(c: var CodeStream, n: int): UInt256 =
5883
## Reads `n` bytes from the code stream and pads
5984
## the remaining bytes with zeros.
60-
let result_bytes = cast[ptr array[32, byte]](addr result)
85+
let resultBytes = cast[ptr array[32, byte]](addr result)
6186

62-
let last = min(c.pc + n, c.bytes.len)
87+
let last = min(c.pc + n, c.codeLen)
6388
let toWrite = last - c.pc
64-
for i in 0 ..< toWrite : result_bytes[i] = c.bytes[last - i - 1]
89+
for i in 0 ..< toWrite : resultBytes[i] = c.codeView[last - i - 1]
6590
c.pc = last
6691

6792
proc readInt16*(c: var CodeStream): int =
68-
result = (c.bytes[c.pc].int shl 8) or c.bytes[c.pc+1].int
93+
let x = uint16.fromBytesBE(makeOpenArray(addr c.codeView[c.pc], byte, 2))
94+
result = cast[int16](x).int
6995
c.pc += 2
7096

7197
proc readByte*(c: var CodeStream): byte =
72-
result = c.bytes[c.pc]
98+
result = c.codeView[c.pc]
7399
inc c.pc
74100

75101
proc len*(c: CodeStream): int =
76-
len(c.bytes)
102+
if c.container.code.len > 0:
103+
c.container.size
104+
else:
105+
c.legacyCode.len
106+
107+
proc setSection*(c: CodeStream, sec: int) =
108+
if sec < c.container.code.len:
109+
c.codeLen = c.container.code[sec].len
110+
if c.codeLen > 0:
111+
c.codeView = cast[CodeView](addr c.container.code[sec][0])
112+
c.section = sec
113+
114+
proc parseEOF*(c: CodeStream): Result[void, EOFV1Error] =
115+
result = decode(c.container, c.legacyCode)
116+
if result.isOk:
117+
c.setSection(0)
118+
119+
func hasEOFCode*(c: CodeStream): bool =
120+
hasEOFMagic(c.legacyCode)
77121

78122
proc next*(c: var CodeStream): Op =
79-
if c.pc != c.bytes.len:
80-
result = Op(c.bytes[c.pc])
123+
if c.pc != c.codeLen:
124+
result = Op(c.codeView[c.pc])
81125
inc c.pc
82126
else:
83127
result = Stop
@@ -89,11 +133,11 @@ iterator items*(c: var CodeStream): Op =
89133
nextOpcode = c.next()
90134

91135
proc `[]`*(c: CodeStream, offset: int): Op =
92-
Op(c.bytes[offset])
136+
Op(c.codeView[offset])
93137

94138
proc peek*(c: var CodeStream): Op =
95-
if c.pc < c.bytes.len:
96-
result = Op(c.bytes[c.pc])
139+
if c.pc < c.codeLen:
140+
result = Op(c.codeView[c.pc])
97141
else:
98142
result = Stop
99143

@@ -111,7 +155,7 @@ when false:
111155
cs.pc = anchorPc
112156

113157
proc isValidOpcode*(c: CodeStream, position: int): bool =
114-
if position >= len(c):
158+
if position >= c.codeLen:
115159
return false
116160
if position in c.invalidPositions:
117161
return false
@@ -141,7 +185,12 @@ proc decompile*(original: var CodeStream): seq[(int, Op, string)] =
141185
if original.cached.len > 0:
142186
return original.cached
143187
result = @[]
144-
var c = newCodeStream(original.bytes)
188+
var c = newCodeStream(original.legacyCode)
189+
if c.hasEOFCode:
190+
let res = c.parseEOF
191+
if res.isErr:
192+
return
193+
145194
while true:
146195
var op = c.next
147196
if op >= Push1 and op <= Push32:
@@ -165,4 +214,4 @@ proc hasSStore*(c: var CodeStream): bool =
165214
result = opcodes.anyIt(it[1] == Sstore)
166215

167216
proc atEnd*(c: CodeStream): bool =
168-
result = c.pc >= c.bytes.len
217+
result = c.pc >= c.codeLen

nimbus/evm/computation.nim

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import
1414
"."/[types],
1515
./interpreter/[gas_meter, gas_costs, op_codes],
1616
../common/[common, evmforks],
17-
../utils/utils,
17+
../utils/[utils, eof],
1818
chronicles, chronos,
1919
eth/[keys],
2020
sets
@@ -45,6 +45,7 @@ when defined(evmc_enabled):
4545

4646
const
4747
evmc_enabled* = defined(evmc_enabled)
48+
ErrLegacyCode* = "invalid code: EOF contract must not deploy legacy code"
4849

4950
# ------------------------------------------------------------------------------
5051
# Helpers
@@ -291,13 +292,26 @@ proc writeContract*(c: Computation)
291292
if len == 0:
292293
return
293294

294-
# EIP-3541 constraint (https://eips.ethereum.org/EIPS/eip-3541).
295-
if fork >= FkLondon and c.output[0] == 0xEF.byte:
296-
withExtra trace, "New contract code starts with 0xEF byte, not allowed by EIP-3541"
297-
# TODO: Return `EVMC_CONTRACT_VALIDATION_FAILURE` (like Silkworm).
298-
c.setError("EVMC_CONTRACT_VALIDATION_FAILURE", true)
295+
# Reject legacy contract deployment from EOF.
296+
if c.initCodeEOF and not hasEOFMagic(c.output):
297+
c.setError(ErrLegacyCode, true)
299298
return
300299

300+
# EIP-3541 constraint (https://eips.ethereum.org/EIPS/eip-3541).
301+
if hasEOFByte(c.output):
302+
if fork >= FkEOF:
303+
var con: Container
304+
let res = con.decode(c.output)
305+
if res.isErr:
306+
c.setError("EOF retcode parse error: " & res.error.toString, true)
307+
return
308+
309+
elif fork >= FkLondon:
310+
withExtra trace, "New contract code starts with 0xEF byte, not allowed by EIP-3541"
311+
# TODO: Return `EVMC_CONTRACT_VALIDATION_FAILURE` (like Silkworm).
312+
c.setError("EVMC_CONTRACT_VALIDATION_FAILURE", true)
313+
return
314+
301315
# EIP-170 constraint (https://eips.ethereum.org/EIPS/eip-3541).
302316
if fork >= FkSpurious and len > EIP170_MAX_CODE_SIZE:
303317
withExtra trace, "New contract code exceeds EIP-170 limit",

nimbus/evm/interpreter/op_handlers/oph_envinfo.nim

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,7 @@ const
169169
cpt.gasCosts[CodeCopy].m_handler(cpt.memory.len, memPos, len),
170170
reason = "CodeCopy fee")
171171

172-
cpt.memory.writePaddedResult(cpt.code.bytes, memPos, copyPos, len)
173-
172+
cpt.memory.writePaddedResult(k.cpt.code.legacyCode, memPos, copyPos, len)
174173

175174
gasPriceOp: Vm2OpFn = proc (k: var Vm2Ctx) =
176175
## 0x3A, Get price of gas in current environment.

nimbus/evm/interpreter_dispatch.nim

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ const
1414
lowMemoryCompileTime {.used.} = lowmem > 0
1515

1616
import
17-
std/[macros, strformat],
18-
pkg/[chronicles, chronos, stew/byteutils],
19-
".."/[constants, utils/utils, db/accounts_cache],
17+
std/[macros, sets, strformat],
18+
".."/[constants, db/accounts_cache],
2019
"."/[code_stream, computation],
2120
"."/[message, precompiles, state, types],
22-
./async/operations,
23-
./interpreter/[op_dispatcher, gas_costs]
21+
../utils/[utils, eof],
22+
./interpreter/[op_dispatcher, gas_costs],
23+
pkg/[chronicles, chronos, eth/keys, stew/byteutils]
2424

2525
{.push raises: [].}
2626

@@ -100,13 +100,22 @@ proc selectVM(c: Computation, fork: EVMFork, shouldPrepareTracer: bool)
100100

101101
genLowMemDispatcher(fork, c.instr, desc)
102102

103-
proc beforeExecCall(c: Computation) =
103+
proc beforeExecCall(c: Computation): bool {.gcsafe, raises: [ValueError].} =
104104
c.snapshot()
105105
if c.msg.kind == evmcCall:
106106
c.vmState.mutateStateDB:
107107
db.subBalance(c.msg.sender, c.msg.value)
108108
db.addBalance(c.msg.contractAddress, c.msg.value)
109109

110+
if c.fork >= FkEOF:
111+
if c.code.hasEOFCode:
112+
# Code was already validated, so no other errors should be possible.
113+
# TODO: what should we do if there is really an error?
114+
let res = c.code.parseEOF()
115+
if res.isErr:
116+
c.setError(res.error.toString, false)
117+
return true
118+
110119
proc afterExecCall(c: Computation) =
111120
## Collect all of the accounts that *may* need to be deleted based on EIP161
112121
## https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md
@@ -137,6 +146,21 @@ proc beforeExecCreate(c: Computation): bool
137146
if c.fork >= FkBerlin:
138147
db.accessList(c.msg.contractAddress)
139148

149+
let isCallerEOF = c.vmState.readOnlyStateDB.hasEOFCode(c.msg.sender)
150+
c.initCodeEOF = c.code.hasEOFCode
151+
152+
if c.fork >= FkEOF:
153+
if isCallerEOF and not c.initcodeEOF:
154+
# Don't allow EOF contract to run legacy initcode.
155+
c.setError(ErrLegacyCode, false)
156+
return true
157+
elif c.initcodeEOF:
158+
# If the initcode is EOF, verify it is well-formed.
159+
let res = c.code.parseEOF()
160+
if res.isErr:
161+
c.setError("EOF initcode parse error: " & res.error.toString, false)
162+
return true
163+
140164
c.snapshot()
141165

142166
if c.vmState.readOnlyStateDB().hasCodeOrNonce(c.msg.contractAddress):
@@ -197,7 +221,6 @@ proc beforeExec(c: Computation): bool
197221

198222
if not c.msg.isCreate:
199223
c.beforeExecCall()
200-
false
201224
else:
202225
c.beforeExecCreate()
203226

nimbus/evm/types.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ type
8080
parent*, child*: Computation
8181
pendingAsyncOperation*: Future[void]
8282
continuation*: proc() {.gcsafe, raises: [CatchableError].}
83+
initcodeEOF*: bool
8384

8485
Error* = ref object
8586
info*: string

0 commit comments

Comments
 (0)