nimbus-eth1/nimbus/evm/code_stream.nim

114 lines
3.0 KiB
Nim

# Nimbus
# Copyright (c) 2018-2024 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
chronicles,
eth/common,
stew/byteutils,
./interpreter/op_codes,
./code_bytes
export code_bytes
type CodeStream* = object
code: CodeBytesRef
pc*: int
func init*(T: type CodeStream, code: CodeBytesRef): T =
T(code: code)
func init*(T: type CodeStream, code: sink seq[byte]): T =
T(code: CodeBytesRef.init(move(code)))
func init*(T: type CodeStream, code: openArray[byte]): T =
T(code: CodeBytesRef.init(code))
func init*(T: type CodeStream, code: openArray[char]): T =
T(code: CodeBytesRef.init(code))
template read*(c: var CodeStream, size: int): openArray[byte] =
if c.pc + size - 1 < c.bytes.len:
let pos = c.pc
c.pc += size
c.code.bytes.toOpenArray(pos, pos + size - 1)
else:
c.pc = c.bytes.len
c.code.bytes.toOpenArray(0, -1)
func readVmWord*(c: var CodeStream, n: static int): UInt256 =
## Reads `n` bytes from the code stream and pads
## the remaining bytes with zeros.
let result_bytes = cast[ptr array[32, byte]](addr result)
let last = min(c.pc + n, c.code.bytes.len)
let toWrite = last - c.pc
for i in 0 ..< toWrite:
result_bytes[i] = c.code.bytes[last - i - 1]
c.pc = last
func len*(c: CodeStream): int =
len(c.code)
template next*(c: var CodeStream): Op =
# Retrieve the next opcode (or stop) - this is a hot spot in the interpreter
# and must be kept small for performance
let
# uint: range checked manually -> benefit from smaller codegen
pc = uint(c.pc)
bytes {.cursor.} = c.code.bytes
if pc < uint(bytes.len):
let op = Op(bytes[pc])
c.pc = cast[int](pc + 1)
op
else:
Op.Stop
iterator items*(c: var CodeStream): Op =
var nextOpcode = c.next()
while nextOpcode != Op.Stop:
yield nextOpcode
nextOpcode = c.next()
func `[]`*(c: CodeStream, offset: int): Op =
Op(c.code.bytes[offset])
func peek*(c: var CodeStream): Op =
if c.pc < c.code.bytes.len:
Op(c.code.bytes[c.pc])
else:
Op.Stop
func updatePc*(c: var CodeStream, value: int) =
c.pc = min(value, len(c))
func isValidOpcode*(c: CodeStream, position: int): bool =
c.code.isValidOpcode(position)
func bytes*(c: CodeStream): lent seq[byte] =
c.code.bytes()
func atEnd*(c: CodeStream): bool =
c.pc >= c.code.bytes.len
proc decompile*(original: CodeStream): seq[(int, Op, string)] =
# behave as https://etherscan.io/opcode-tool
var c = CodeStream.init(original.bytes)
while not c.atEnd:
var op = c.next
if op >= Push1 and op <= Push32:
result.add(
(
c.pc - 1,
op,
"0x" & c.read(op.int - 95).toHex,
)
)
elif op != Op.Stop:
result.add((c.pc - 1, op, ""))
else:
result.add((-1, Op.Stop, ""))