Skip to content

Commit dd3bb6e

Browse files
committed
EIP-3540: EOF - EVM Object Format v1
1 parent 754785a commit dd3bb6e

File tree

8 files changed

+539
-34
lines changed

8 files changed

+539
-34
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,
@@ -376,6 +377,18 @@ proc getNonce*(ac: AccountsCache, address: EthAddress): AccountNonce {.inline.}
376377
if acc.isNil: emptyAcc.nonce
377378
else: acc.account.nonce
378379

380+
proc loadCode(acc: RefAccount, ac: AccountsCache) =
381+
if CodeLoaded in acc.flags or CodeChanged in acc.flags:
382+
return
383+
384+
when defined(geth):
385+
let data = ac.db.get(acc.account.codeHash.data)
386+
else:
387+
let data = ac.db.get(contractHashKey(acc.account.codeHash).toOpenArray)
388+
389+
acc.code = data
390+
acc.flags.incl CodeLoaded
391+
379392
proc getCode*(ac: AccountsCache, address: EthAddress): seq[byte] =
380393
let acc = ac.getAccount(address, false)
381394
if acc.isNil:
@@ -394,7 +407,18 @@ proc getCode*(ac: AccountsCache, address: EthAddress): seq[byte] =
394407
result = acc.code
395408

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

399423
proc getCommittedStorage*(ac: AccountsCache, address: EthAddress, slot: UInt256): UInt256 {.inline.} =
400424
let acc = ac.getAccount(address, false)
@@ -744,6 +768,7 @@ proc getStorage*(db: ReadOnlyStateDB, address: EthAddress, slot: UInt256): UInt2
744768
proc getNonce*(db: ReadOnlyStateDB, address: EthAddress): AccountNonce {.borrow.}
745769
proc getCode*(db: ReadOnlyStateDB, address: EthAddress): seq[byte] {.borrow.}
746770
proc getCodeSize*(db: ReadOnlyStateDB, address: EthAddress): int {.borrow.}
771+
proc hasEOFCode*(ac: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
747772
proc hasCodeOrNonce*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
748773
proc accountExists*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
749774
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: 19 additions & 5 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
@@ -313,12 +314,25 @@ proc writeContract*(c: Computation)
313314
if len == 0:
314315
return
315316

316-
# EIP-3541 constraint (https://eips.ethereum.org/EIPS/eip-3541).
317-
if fork >= FkLondon and c.output[0] == 0xEF.byte:
318-
withExtra trace, "New contract code starts with 0xEF byte, not allowed by EIP-3541"
319-
c.setError(EVMC_CONTRACT_VALIDATION_FAILURE, true)
317+
# Reject legacy contract deployment from EOF.
318+
if c.initCodeEOF and not hasEOFMagic(c.output):
319+
c.setError(ErrLegacyCode, true)
320320
return
321321

322+
# EIP-3541 constraint (https://eips.ethereum.org/EIPS/eip-3541).
323+
if hasEOFByte(c.output):
324+
if fork >= FkEOF:
325+
var con: Container
326+
let res = con.decode(c.output)
327+
if res.isErr:
328+
c.setError("EOF retcode parse error: " & res.error.toString, true)
329+
return
330+
331+
elif fork >= FkLondon:
332+
withExtra trace, "New contract code starts with 0xEF byte, not allowed by EIP-3541"
333+
c.setError(EVMC_CONTRACT_VALIDATION_FAILURE, true)
334+
return
335+
322336
# EIP-170 constraint (https://eips.ethereum.org/EIPS/eip-3541).
323337
if fork >= FkSpurious and len > EIP170_MAX_CODE_SIZE:
324338
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
@@ -145,7 +145,7 @@ const
145145
cpt.gasCosts[CodeCopy].m_handler(cpt.memory.len, memPos, len),
146146
reason = "CodeCopy fee")
147147

148-
cpt.memory.writePadded(cpt.code.bytes, memPos, copyPos, len)
148+
cpt.memory.writePadded(k.cpt.code.legacyCode, memPos, copyPos, len)
149149

150150

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

nimbus/evm/interpreter_dispatch.nim

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

1616
import
17-
std/[macros, strformat],
17+
std/[macros, sets, strformat],
1818
pkg/[chronicles, chronos, stew/byteutils],
1919
".."/[constants, db/accounts_cache],
2020
"."/[code_stream, computation],
2121
"."/[message, precompiles, state, types],
22-
./async/operations,
23-
./interpreter/[op_dispatcher, gas_costs]
22+
../utils/[utils, eof],
23+
./interpreter/[op_dispatcher, gas_costs],
24+
pkg/[chronicles, chronos, eth/keys, stew/byteutils]
2425

2526
{.push raises: [].}
2627

@@ -108,13 +109,22 @@ proc selectVM(c: Computation, fork: EVMFork, shouldPrepareTracer: bool)
108109

109110
genLowMemDispatcher(fork, c.instr, desc)
110111

111-
proc beforeExecCall(c: Computation) =
112+
proc beforeExecCall(c: Computation): bool {.gcsafe, raises: [ValueError].} =
112113
c.snapshot()
113114
if c.msg.kind == EVMC_CALL:
114115
c.vmState.mutateStateDB:
115116
db.subBalance(c.msg.sender, c.msg.value)
116117
db.addBalance(c.msg.contractAddress, c.msg.value)
117118

119+
if c.fork >= FkEOF:
120+
if c.code.hasEOFCode:
121+
# Code was already validated, so no other errors should be possible.
122+
# TODO: what should we do if there is really an error?
123+
let res = c.code.parseEOF()
124+
if res.isErr:
125+
c.setError(res.error.toString, false)
126+
return true
127+
118128
proc afterExecCall(c: Computation) =
119129
## Collect all of the accounts that *may* need to be deleted based on EIP161
120130
## https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md
@@ -145,6 +155,21 @@ proc beforeExecCreate(c: Computation): bool
145155
if c.fork >= FkBerlin:
146156
db.accessList(c.msg.contractAddress)
147157

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

150175
if c.vmState.readOnlyStateDB().hasCodeOrNonce(c.msg.contractAddress):
@@ -206,7 +231,6 @@ proc beforeExec(c: Computation): bool
206231

207232
if not c.msg.isCreate:
208233
c.beforeExecCall()
209-
false
210234
else:
211235
c.beforeExecCreate()
212236

nimbus/evm/types.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ type
9696
pendingAsyncOperation*: Future[void]
9797
continuation*: proc() {.gcsafe, raises: [CatchableError].}
9898
sysCall*: bool
99+
initcodeEOF*: bool
99100

100101
Error* = ref object
101102
statusCode*: evmc_status_code

0 commit comments

Comments
 (0)