Skip to content

Commit 79c2771

Browse files
committed
EIP-3540: EOF - EVM Object Format v1
1 parent aae98e1 commit 79c2771

File tree

8 files changed

+540
-46
lines changed

8 files changed

+540
-46
lines changed

nimbus/db/accounts_cache.nim

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import
22
tables, hashes, sets,
33
eth/[common, rlp], eth/trie/[hexary, db, trie_defs],
4-
../constants, ../utils/utils, storage_types,
4+
../constants, ../utils/[utils, eof], storage_types,
55
../../stateless/multi_keys,
66
./access_list as ac_access_list
77

@@ -315,25 +315,38 @@ proc getNonce*(ac: AccountsCache, address: EthAddress): AccountNonce {.inline.}
315315
if acc.isNil: emptyAcc.nonce
316316
else: acc.account.nonce
317317

318-
proc getCode*(ac: AccountsCache, address: EthAddress): seq[byte] =
319-
let acc = ac.getAccount(address, false)
320-
if acc.isNil:
318+
proc loadCode(acc: RefAccount, ac: AccountsCache) =
319+
if CodeLoaded in acc.flags or CodeChanged in acc.flags:
321320
return
322321

323-
if CodeLoaded in acc.flags or CodeChanged in acc.flags:
324-
result = acc.code
322+
when defined(geth):
323+
let data = ac.db.get(acc.account.codeHash.data)
325324
else:
326-
when defined(geth):
327-
let data = ac.db.get(acc.account.codeHash.data)
328-
else:
329-
let data = ac.db.get(contractHashKey(acc.account.codeHash).toOpenArray)
325+
let data = ac.db.get(contractHashKey(acc.account.codeHash).toOpenArray)
330326

331-
acc.code = data
332-
acc.flags.incl CodeLoaded
333-
result = acc.code
327+
acc.code = data
328+
acc.flags.incl CodeLoaded
329+
330+
proc getCode*(ac: AccountsCache, address: EthAddress): seq[byte] =
331+
let acc = ac.getAccount(address, false)
332+
if acc.isNil:
333+
return
334+
acc.loadCode(ac)
335+
acc.code
334336

335337
proc getCodeSize*(ac: AccountsCache, address: EthAddress): int {.inline.} =
336-
ac.getCode(address).len
338+
let acc = ac.getAccount(address, false)
339+
if acc.isNil:
340+
return
341+
acc.loadCode(ac)
342+
acc.code.len
343+
344+
proc hasEOFCode*(ac: AccountsCache, address: EthAddress): bool =
345+
let acc = ac.getAccount(address, false)
346+
if acc.isNil:
347+
return
348+
acc.loadCode(ac)
349+
eof.hasEOFMagic(acc.code)
337350

338351
proc getCommittedStorage*(ac: AccountsCache, address: EthAddress, slot: UInt256): UInt256 {.inline.} =
339352
let acc = ac.getAccount(address, false)
@@ -583,6 +596,7 @@ proc getStorage*(db: ReadOnlyStateDB, address: EthAddress, slot: UInt256): UInt2
583596
proc getNonce*(db: ReadOnlyStateDB, address: EthAddress): AccountNonce {.borrow.}
584597
proc getCode*(db: ReadOnlyStateDB, address: EthAddress): seq[byte] {.borrow.}
585598
proc getCodeSize*(db: ReadOnlyStateDB, address: EthAddress): int {.borrow.}
599+
proc hasEOFCode*(ac: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
586600
proc hasCodeOrNonce*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
587601
proc accountExists*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
588602
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
"."/[transaction_tracer, 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
@@ -43,6 +43,7 @@ when defined(evmc_enabled):
4343

4444
const
4545
evmc_enabled* = defined(evmc_enabled)
46+
ErrLegacyCode* = "invalid code: EOF contract must not deploy legacy code"
4647

4748
# ------------------------------------------------------------------------------
4849
# Helpers
@@ -266,13 +267,26 @@ proc writeContract*(c: Computation) =
266267
if len == 0:
267268
return
268269
269-
# EIP-3541 constraint (https://eips.ethereum.org/EIPS/eip-3541).
270-
if fork >= FkLondon and c.output[0] == 0xEF.byte:
271-
withExtra trace, "New contract code starts with 0xEF byte, not allowed by EIP-3541"
272-
# TODO: Return `EVMC_CONTRACT_VALIDATION_FAILURE` (like Silkworm).
273-
c.setError("EVMC_CONTRACT_VALIDATION_FAILURE", true)
270+
# Reject legacy contract deployment from EOF.
271+
if c.initCodeEOF and not hasEOFMagic(c.output):
272+
c.setError(ErrLegacyCode, true)
274273
return
275274
275+
# EIP-3541 constraint (https://eips.ethereum.org/EIPS/eip-3541).
276+
if hasEOFByte(c.output):
277+
if fork >= FkEOF:
278+
var con: Container
279+
let res = con.decode(c.output)
280+
if res.isErr:
281+
c.setError("EOF retcode parse error: " & res.error.toString, true)
282+
return
283+
284+
elif fork >= FkLondon:
285+
withExtra trace, "New contract code starts with 0xEF byte, not allowed by EIP-3541"
286+
# TODO: Return `EVMC_CONTRACT_VALIDATION_FAILURE` (like Silkworm).
287+
c.setError("EVMC_CONTRACT_VALIDATION_FAILURE", true)
288+
return
289+
276290
# EIP-170 constraint (https://eips.ethereum.org/EIPS/eip-3541).
277291
if fork >= FkSpurious and len > EIP170_MAX_CODE_SIZE:
278292
withExtra trace, "New contract code exceeds EIP-170 limit",

nimbus/evm/interpreter/op_handlers/oph_envinfo.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ const
158158
k.cpt.gasCosts[CodeCopy].m_handler(k.cpt.memory.len, memPos, len),
159159
reason = "CodeCopy fee")
160160

161-
k.cpt.memory.writePaddedResult(k.cpt.code.bytes, memPos, copyPos, len)
161+
k.cpt.memory.writePaddedResult(k.cpt.code.legacyCode, memPos, copyPos, len)
162162

163163

164164
gasPriceOp: Vm2OpFn = proc (k: var Vm2Ctx) =

nimbus/evm/interpreter_dispatch.nim

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ const
1515

1616
import
1717
std/[macros, sets, strformat],
18-
".."/[constants, utils/utils, db/accounts_cache],
18+
".."/[constants, db/accounts_cache],
1919
"."/[code_stream, computation],
2020
"."/[message, precompiles, state, types],
21+
../utils/[utils, eof],
2122
./interpreter/[op_dispatcher, gas_costs],
2223
pkg/[chronicles, chronos, eth/keys, stew/byteutils]
2324

@@ -103,13 +104,22 @@ proc selectVM(c: Computation, fork: EVMFork, shouldPrepareTracer: bool) {.gcsafe
103104
genLowMemDispatcher(fork, c.instr, desc)
104105

105106

106-
proc beforeExecCall(c: Computation) =
107+
proc beforeExecCall(c: Computation): bool =
107108
c.snapshot()
108109
if c.msg.kind == evmcCall:
109110
c.vmState.mutateStateDB:
110111
db.subBalance(c.msg.sender, c.msg.value)
111112
db.addBalance(c.msg.contractAddress, c.msg.value)
112113

114+
if c.fork >= FkEOF:
115+
if c.code.hasEOFCode:
116+
# Code was already validated, so no other errors should be possible.
117+
# TODO: what should we do if there is really an error?
118+
let res = c.code.parseEOF()
119+
if res.isErr:
120+
c.setError(res.error.toString, false)
121+
return true
122+
113123
proc afterExecCall(c: Computation) =
114124
## Collect all of the accounts that *may* need to be deleted based on EIP161
115125
## https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md
@@ -141,6 +151,21 @@ proc beforeExecCreate(c: Computation): bool =
141151
if c.fork >= FkBerlin:
142152
db.accessList(c.msg.contractAddress)
143153

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

146171
if c.vmState.readOnlyStateDB().hasCodeOrNonce(c.msg.contractAddress):
@@ -178,7 +203,6 @@ proc afterExecCreate(c: Computation) =
178203
proc beforeExec(c: Computation): bool =
179204
if not c.msg.isCreate:
180205
c.beforeExecCall()
181-
false
182206
else:
183207
c.beforeExecCreate()
184208

nimbus/evm/types.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ type
100100
parent*, child*: Computation
101101
pendingAsyncOperation*: Future[void]
102102
continuation*: proc() {.gcsafe.}
103+
initcodeEOF*: bool
103104

104105
Error* = ref object
105106
info*: string

0 commit comments

Comments
 (0)