re-integrated 0x5# op handlers

This commit is contained in:
Jordan Hrycaj 2021-04-14 17:34:24 +01:00 committed by zah
parent 0f1c7cee43
commit d373ab6460
4 changed files with 559 additions and 235 deletions

View File

@ -21,60 +21,38 @@ import
./op_codes,
./op_handlers/[oph_defs,
oph_arithmetic, oph_hash, oph_envinfo, oph_blockdata,
oph_memory,
oph_sysops]
# ------------------------------------------------------------------------------
# Helper
# ------------------------------------------------------------------------------
const
allForksTable = block:
var rc: array[Op, Vm2OpExec]
proc importList(rc: var array[Op,Vm2OpExec];
sel: Fork; list: seq[Vm2OpExec]; s: string) {.compileTime.} =
for w in list:
if sel notin w.forks:
continue
var oInf = rc[w.opCode].info
if oInf != "" or 0 < rc[w.opCode].forks.card:
echo &"*** {s}: duplicate <{w.opCode}> entry: \"{oInf}\" vs. \"{w.info}\""
doAssert rc[w.opCode].info == ""
doAssert rc[w.opCode].forks.card == 0
rc[w.opCode] = w
proc complain(rec: Vm2OpExec; s: string): bool =
var
op = rec.opCode
oInfo = rc[op].info
nInfo = rec.info
if oInfo != "":
echo &"*** {s}: duplicate <{op}> entry: \"{oInfo}\" vs. \"{nInfo}\""
return true
for w in vm2OpExecArithmetic:
if w.complain("Arithmetic"):
doAssert rc[w.opCode].info == ""
rc[w.opCode] = w
proc mkOpTable(select: Fork): array[Op,Vm2OpExec] {.compileTime.} =
result.importList(select, vm2OpExecArithmetic, "Arithmetic")
result.importList(select, vm2OpExecHash, "Hash")
result.importList(select, vm2OpExecEnvInfo, "EnvInfo")
result.importList(select, vm2OpExecBlockData, "BlockData")
result.importList(select, vm2OpExecMemory, "Memory")
result.importList(select, vm2OpExecSysOP, "SysOp")
for w in vm2OpExecHash:
if w.complain("Hash"):
doAssert rc[w.opCode].info == ""
rc[w.opCode] = w
for w in vm2OpExecEnvInfo:
if w.complain("EnvInfo"):
doAssert rc[w.opCode].info == ""
rc[w.opCode] = w
for w in vm2OpExecBlockData:
if w.complain("BlockData"):
doAssert rc[w.opCode].info == ""
rc[w.opCode] = w
for w in vm2OpExecSysOP:
if w.complain("SysOp"):
doAssert rc[w.opCode].info == ""
rc[w.opCode] = w
rc
proc mkOpTable(select: Fork): array[Op, Vm2OpExec] {.compileTime.} =
for op in Op:
var w = allForksTable[op]
if select in w.forks:
result[op] = w
else:
result[op] = allForksTable[Invalid]
result[op].opCode = op
if select notin result[op].forks:
result[op] = result[Invalid]
result[op].opCode = op
# ------------------------------------------------------------------------------
# Public handler tables
@ -87,15 +65,15 @@ const
rc[w] = w.mkOpTable
rc
vm2OpTabFrontier* = vm2OpHandlers[FkFrontier]
vm2OpTabHomestead* = vm2OpHandlers[FkHomestead]
vm2OpTabTangerine* = vm2OpHandlers[FkTangerine]
vm2OpTabSpurious* = vm2OpHandlers[FkSpurious]
vm2OpTabByzantium* = vm2OpHandlers[FkByzantium]
vm2OpTabConstantinople* = vm2OpHandlers[FkConstantinople]
vm2OpTabPetersburg* = vm2OpHandlers[FkPetersburg]
vm2OpTabIstanbul* = vm2OpHandlers[FkIstanbul]
vm2OpTabBerlin* = vm2OpHandlers[FkBerlin]
# vm2OpTabFrontier* = vm2OpHandlers[FkFrontier]
# vm2OpTabHomestead* = vm2OpHandlers[FkHomestead]
# vm2OpTabTangerine* = vm2OpHandlers[FkTangerine]
# vm2OpTabSpurious* = vm2OpHandlers[FkSpurious]
# vm2OpTabByzantium* = vm2OpHandlers[FkByzantium]
# vm2OpTabConstantinople* = vm2OpHandlers[FkConstantinople]
# vm2OpTabPetersburg* = vm2OpHandlers[FkPetersburg]
# vm2OpTabIstanbul* = vm2OpHandlers[FkIstanbul]
# vm2OpTabBerlin* = vm2OpHandlers[FkBerlin]
# ------------------------------------------------------------------------------
# Debugging ...
@ -106,12 +84,18 @@ when isMainModule and isNoisy:
proc gdbBPSink() =
dummy.inc
const
a = vm2OpTabBerlin
b = a[Shl].info
gdbBPSink()
echo ">>> ", b
echo ">>> berlin[shl]: ",
vm2OpHandlers[FkBerlin][Shl].info
echo ">>> frontier[sstore]: ",
vm2OpHandlers[FkFrontier][Sstore].info
echo ">>> constantinople[sstore]: ",
vm2OpHandlers[FkConstantinople][Sstore].info
echo ">>> berlin[sstore]: ",
vm2OpHandlers[FkBerlin][Sstore].info
# ------------------------------------------------------------------------------
# End

View File

@ -36,19 +36,29 @@ else:
{.fatal: "Flag \"vm2_enabled\" must be unset "&
"while circular dependency breaker kludge is activated".}
type
ReadOnlyStateDB* =
seq[byte]
GasMeter* = object
whatever: int
gasRemaining*: int
CodeStream* = ref object
bytes*: seq[byte]
pc*: int
BaseVMState* = ref object
accountDb*: ReadOnlyStateDB
Message* = ref object
contractAddress*: UInt256
sender*: UInt256
value*: UInt256
data*: seq[byte]
flags*: int
Computation* = ref object
returnStack*: seq[int]
vmState*: BaseVMState
gasMeter*: GasMeter
stack*: Stack
memory*: Memory

View File

@ -0,0 +1,466 @@
# Nimbus
# Copyright (c) 2018 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.
## EVM Opcode Handlers: Stack, Memory, Storage And Flow Operations
## ===============================================================
##
const
kludge {.intdefine.}: int = 0
breakCircularDependency {.used.} = kludge > 0
import
../../../errors,
./oph_defs,
strformat,
stint
# ------------------------------------------------------------------------------
# Kludge BEGIN
# ------------------------------------------------------------------------------
when not breakCircularDependency:
import
../../../db/accounts_cache,
../../code_stream,
../../stack,
../../v2computation,
../../v2memory,
../../v2state,
../../v2types,
../gas_meter,
../utils/v2utils_numeric,
../v2gas_costs,
eth/common,
times
else:
import macros
const emvcStatic = 1
var blindGasCosts: array[Op,int]
# copied from stack.nim
macro genTupleType(len: static[int], elemType: untyped): untyped =
result = nnkTupleConstr.newNimNode()
for i in 0 ..< len: result.add(elemType)
# function stubs from stack.nim (to satisfy compiler logic)
proc push[T](x: Stack; n: T) = discard
proc popInt(x: var Stack): UInt256 = discard
proc popInt(x: var Stack, n: static[int]): auto =
var rc: genTupleType(n, UInt256)
return rc
# function stubs from v2computation.nim (to satisfy compiler logic)
proc getStorage(c: Computation, slot: Uint256): Uint256 = 0.u256
proc gasCosts(c: Computation): array[Op,int] = blindGasCosts
# function stubs from v2utils_numeric.nim
func cleanMemRef(x: UInt256): int = 0
# function stubs from v2memory.nim
proc len(mem: Memory): int = 0
proc extend(mem: var Memory; startPos: Natural; size: Natural) = discard
proc write(mem: var Memory, startPos: Natural, val: openarray[byte]) = discard
proc read(mem: var Memory, startPos: Natural, size: Natural): seq[byte] = @[]
# function stubs from code_stream.nim
proc len(c: CodeStream): int = len(c.bytes)
proc peek(c: var CodeStream): Op = Stop
proc isValidOpcode(c: CodeStream, position: int): bool = false
# function stubs from v2state.nim
proc readOnlyStateDB(x: BaseVMState): ReadOnlyStateDB = x.accountDb
template mutateStateDB(vmState: BaseVMState, body: untyped) = discard
# function stubs from gas_meter.nim
proc refundGas(gasMeter: var GasMeter; amount: int) = discard
proc consumeGas(gasMeter: var GasMeter; amount: int; reason: string) = discard
# stubs from v2gas_costs.nim
type GasParams = object
case kind*: Op
of Sstore:
s_currentValue: Uint256
s_originalValue: Uint256
else:
discard
proc c_handler(x: int; y: Uint256, z: GasParams): (int,int) = (0,0)
proc m_handler(x: int; curMemSize, memOffset, memLen: int64): int = 0
# function stubs from state_db.nim
proc getCommittedStorage(x: ReadOnlyStateDB; y,z: Uint256): Uint256 = 0.u256
# ------------------------------------------------------------------------------
# Kludge END
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
template checkInStaticContext(c: Computation) =
# TODO: if possible, this check only appear
# when fork >= FkByzantium
if emvcStatic == c.msg.flags:
raise newException(
StaticContextError,
"Cannot modify state while inside of STATICCALL context")
proc sstoreNetGasMeteringImpl(c: Computation; slot, newValue: Uint256) =
let
stateDB = c.vmState.readOnlyStateDB
currentValue = c.getStorage(slot)
gasParam = GasParams(
kind: Op.Sstore,
s_currentValue: currentValue,
s_originalValue: stateDB.getCommittedStorage(c.msg.contractAddress, slot))
(gasCost, gasRefund) = c.gasCosts[Sstore].c_handler(newValue, gasParam)
c.gasMeter.consumeGas(
gasCost, &"SSTORE EIP2200: {c.msg.contractAddress}[{slot}]" &
&" -> {newValue} ({currentValue})")
if gasRefund != 0:
c.gasMeter.refundGas(gasRefund)
c.vmState.mutateStateDB:
db.setStorage(c.msg.contractAddress, slot, newValue)
proc jumpImpl(c: Computation; jumpTarget: UInt256) =
if jumpTarget >= c.code.len.u256:
raise newException(
InvalidJumpDestination, "Invalid Jump Destination")
let jt = jumpTarget.truncate(int)
c.code.pc = jt
let nextOpcode = c.code.peek
if nextOpcode != JUMPDEST:
raise newException(InvalidJumpDestination, "Invalid Jump Destination")
# TODO: next check seems redundant
if not c.code.isValidOpcode(jt):
raise newException(
InvalidInstruction, "Jump resulted in invalid instruction")
# ------------------------------------------------------------------------------
# Private, op handlers implementation
# ------------------------------------------------------------------------------
const
popOp: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x50, Remove item from stack.
discard k.cpt.stack.popInt
mloadOp: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x51, Load word from memory
let (memStartPos) = k.cpt.stack.popInt(1)
let memPos = memStartPos.cleanMemRef
k.cpt.gasMeter.consumeGas(
k.cpt.gasCosts[MLoad].m_handler(k.cpt.memory.len, memPos, 32),
reason = "MLOAD: GasVeryLow + memory expansion")
k.cpt.memory.extend(memPos, 32)
k.cpt.stack.push:
k.cpt.memory.read(memPos, 32)
mstoreOp: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x52, Save word to memory
let (memStartPos, value) = k.cpt.stack.popInt(2)
let memPos = memStartPos.cleanMemRef
k.cpt.gasMeter.consumeGas(
k.cpt.gasCosts[MStore].m_handler(k.cpt.memory.len, memPos, 32),
reason = "MSTORE: GasVeryLow + memory expansion")
k.cpt.memory.extend(memPos, 32)
k.cpt.memory.write(memPos, value.toByteArrayBE)
mstore8Op: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x53, Save byte to memory
let (memStartPos, value) = k.cpt.stack.popInt(2)
let memPos = memStartPos.cleanMemRef
k.cpt.gasMeter.consumeGas(
k.cpt.gasCosts[MStore].m_handler(k.cpt.memory.len, memPos, 1),
reason = "MSTORE8: GasVeryLow + memory expansion")
k.cpt.memory.extend(memPos, 1)
k.cpt.memory.write(memPos, [value.toByteArrayBE[31]])
sloadOp: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x54, Load word from storage.
let (slot) = k.cpt.stack.popInt(1)
k.cpt.stack.push:
k.cpt.getStorage(slot)
sstoreOp: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x55, Save word to storage.
let (slot, newValue) = k.cpt.stack.popInt(2)
checkInStaticContext(k.cpt)
# sstoreImpl(k.cpt, slot, newValue)
# template sstoreImpl(c: Computation, slot, newValue: Uint256) =
let
currentValue = k.cpt.getStorage(slot)
gasParam = GasParams(
kind: Op.Sstore,
s_currentValue: currentValue)
(gasCost, gasRefund) =
k.cpt.gasCosts[Sstore].c_handler(newValue, gasParam)
k.cpt.gasMeter.consumeGas(
gasCost, &"SSTORE: {k.cpt.msg.contractAddress}[{slot}] " &
&"-> {newValue} ({currentValue})")
if gasRefund > 0:
k.cpt.gasMeter.refundGas(gasRefund)
k.cpt.vmState.mutateStateDB:
db.setStorage(k.cpt.msg.contractAddress, slot, newValue)
sstoreEIP1283Op: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x55, EIP1283: sstore for Constantinople and later
let (slot, newValue) = k.cpt.stack.popInt(2)
checkInStaticContext(k.cpt)
sstoreNetGasMeteringImpl(k.cpt, slot, newValue)
sstoreEIP2200Op: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x55, EIP2200: sstore for Istanbul and later
let (slot, newValue) = k.cpt.stack.popInt(2)
checkInStaticContext(k.cpt)
const SentryGasEIP2200 = 2300
if k.cpt.gasMeter.gasRemaining <= SentryGasEIP2200:
raise newException(
OutOfGas,
"Gas not enough to perform EIP2200 SSTORE")
sstoreNetGasMeteringImpl(k.cpt, slot, newValue)
jumpOp: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x56, Alter the program counter
let (jumpTarget) = k.cpt.stack.popInt(1)
jumpImpl(k.cpt, jumpTarget)
jumpIOp: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x57, Conditionally alter the program counter.
let (jumpTarget, testedValue) = k.cpt.stack.popInt(2)
if testedValue != 0:
jumpImpl(k.cpt, jumpTarget)
pcOp: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x58, Get the value of the program counter prior to the increment
## corresponding to this instruction.
k.cpt.stack.push:
max(k.cpt.code.pc - 1, 0)
msizeOp: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x59, Get the size of active memory in bytes.
k.cpt.stack.push:
k.cpt.memory.len
gasOp: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x5a, Get the amount of available gas, including the corresponding
## reduction for the cost of this instruction.
k.cpt.stack.push:
k.cpt.gasMeter.gasRemaining
jumpDestOp: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x5b, Mark a valid destination for jumps. This operation has no effect
## on machine state during execution.
discard
beginSubOp: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x5c, Marks the entry point to a subroutine
raise newException(
OutOfGas,
"Abort: Attempt to execute BeginSub opcode")
returnSubOp: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x5d, Returns control to the caller of a subroutine.
if k.cpt.returnStack.len == 0:
raise newException(
OutOfGas,
"Abort: invalid returnStack during ReturnSub")
k.cpt.code.pc = k.cpt.returnStack.pop()
jumpSubOp: Vm2OpFn = proc (k: Vm2Ctx) =
## 0x5e, Transfers control to a subroutine.
let (jumpTarget) = k.cpt.stack.popInt(1)
if jumpTarget >= k.cpt.code.len.u256:
raise newException(
InvalidJumpDestination, "JumpSub destination exceeds code len")
let returnPC = k.cpt.code.pc
let jt = jumpTarget.truncate(int)
k.cpt.code.pc = jt
let nextOpcode = k.cpt.code.peek
if nextOpcode != BeginSub:
raise newException(
InvalidJumpDestination, "Invalid JumpSub destination")
if k.cpt.returnStack.len == 1023:
raise newException(
FullStack, "Out of returnStack")
k.cpt.returnStack.add returnPC
inc k.cpt.code.pc
# ------------------------------------------------------------------------------
# Public, op exec table entries
# ------------------------------------------------------------------------------
const
vm2OpExecMemory*: seq[Vm2OpExec] = @[
(opCode: Pop, ## x50, Remove item from stack
forks: Vm2OpAllForks,
info: "Remove item from stack",
exec: (prep: vm2OpIgnore,
run: popOp,
post: vm2OpIgnore)),
(opCode: Mload, ## 0x51, Load word from memory
forks: Vm2OpAllForks,
info: "Load word from memory",
exec: (prep: vm2OpIgnore,
run: mloadOp,
post: vm2OpIgnore)),
(opCode: Mstore, ## 0x52, Save word to memory
forks: Vm2OpAllForks,
info: "Save word to memory",
exec: (prep: vm2OpIgnore,
run: mstoreOp,
post: vm2OpIgnore)),
(opCode: Mstore8, ## 0x53, Save byte to memory
forks: Vm2OpAllForks,
info: "Save byte to memory",
exec: (prep: vm2OpIgnore,
run: mstore8Op,
post: vm2OpIgnore)),
(opCode: Sload, ## 0x54, Load word from storage
forks: Vm2OpAllForks,
info: "Load word from storage",
exec: (prep: vm2OpIgnore,
run: sloadOp,
post: vm2OpIgnore)),
(opCode: Sstore, ## 0x55, Save word
forks: Vm2OpAllForks - Vm2OpConstantinopleAndLater,
info: "Save word to storage",
exec: (prep: vm2OpIgnore,
run: sstoreOp,
post: vm2OpIgnore)),
(opCode: Sstore, ## 0x55, sstore for Constantinople and later
forks: Vm2OpConstantinopleAndLater - Vm2OpIstanbulAndLater,
info: "EIP1283: sstore for Constantinople and later",
exec: (prep: vm2OpIgnore,
run: sstoreEIP1283Op,
post: vm2OpIgnore)),
(opCode: Sstore, ## 0x55, sstore for Istanbul and later
forks: Vm2OpIstanbulAndLater,
info: "EIP2200: sstore for Istanbul and later",
exec: (prep: vm2OpIgnore,
run: sstoreEIP2200Op,
post: vm2OpIgnore)),
(opCode: Jump, ## 0x56, Jump
forks: Vm2OpIstanbulAndLater,
info: "Alter the program counter",
exec: (prep: vm2OpIgnore,
run: jumpOp,
post: vm2OpIgnore)),
(opCode: JumpI, ## 0x57, Conditional jump
forks: Vm2OpAllForks,
info: "Conditionally alter the program counter",
exec: (prep: vm2OpIgnore,
run: jumpIOp,
post: vm2OpIgnore)),
(opCode: Pc, ## 0x58, Program counter prior to instruction
forks: Vm2OpAllForks,
info: "Get the value of the program counter prior to the increment "&
"corresponding to this instruction",
exec: (prep: vm2OpIgnore,
run: pcOp,
post: vm2OpIgnore)),
(opCode: Msize, ## 0x59, Memory size
forks: Vm2OpAllForks,
info: "Get the size of active memory in bytes",
exec: (prep: vm2OpIgnore,
run: msizeOp,
post: vm2OpIgnore)),
(opCode: Gas, ## 0x5a, Get available gas
forks: Vm2OpAllForks,
info: "Get the amount of available gas, including the corresponding "&
"reduction for the cost of this instruction",
exec: (prep: vm2OpIgnore,
run: gasOp,
post: vm2OpIgnore)),
(opCode: JumpDest, ## 0x5b, Mark jump target. This operation has no effect
## on machine state during execution
forks: Vm2OpAllForks,
info: "Mark a valid destination for jumps",
exec: (prep: vm2OpIgnore,
run: jumpDestOp,
post: vm2OpIgnore)),
(opCode: BeginSub, ## 0x5c, Begin subroutine
forks: Vm2OpAllForks,
info: " Marks the entry point to a subroutine",
exec: (prep: vm2OpIgnore,
run: beginSubOp,
post: vm2OpIgnore)),
(opCode: ReturnSub, ## 0x5d, Return
forks: Vm2OpAllForks,
info: "Returns control to the caller of a subroutine",
exec: (prep: vm2OpIgnore,
run: returnSubOp,
post: vm2OpIgnore)),
(opCode: JumpSub, ## 0x5e, Call subroutine
forks: Vm2OpAllForks,
info: "Transfers control to a subroutine",
exec: (prep: vm2OpIgnore,
run: jumpSubOp,
post: vm2OpIgnore))]
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -55,6 +55,25 @@ proc writePaddedResult(mem: var Memory,
# TODO: avoid unnecessary memory allocation
mem.write(paddingOffset, repeat(paddingValue, numPaddingBytes))
template sstoreNetGasMeteringImpl(c: Computation, slot, newValue: Uint256) =
let stateDB = c.vmState.readOnlyStateDB
let currentValue {.inject.} = c.getStorage(slot)
let
gasParam = GasParams(
kind: Op.Sstore,
s_currentValue: currentValue,
s_originalValue: stateDB.getCommittedStorage(c.msg.contractAddress, slot))
(gasCost, gasRefund) = c.gasCosts[Sstore].c_handler(newValue, gasParam)
c.gasMeter.consumeGas(gasCost, &"SSTORE EIP2200: {c.msg.contractAddress}[{slot}] -> {newValue} ({currentValue})")
if gasRefund != 0:
c.gasMeter.refundGas(gasRefund)
c.vmState.mutateStateDB:
db.setStorage(c.msg.contractAddress, slot, newValue)
# ##################################
# re-implemented OP handlers
@ -64,18 +83,18 @@ proc gdbBPHook*() =
stderr.write &"*** Hello {gdbBPHook_counter}\n"
stderr.flushFile
template opHandlerX(callName: untyped; opCode: Op) =
template opHandlerX(callName: untyped; opCode: Op; fork = FkBerlin) =
proc callName*(c: Computation) =
gdbBPHook()
var desc: Vm2Ctx
desc.cpt = c
vm2OpTabBerlin[opCode].exec.run(desc)
vm2OpHandlers[fork][opCode].exec.run(desc)
template opHandler(callName: untyped; opCode: Op) =
template opHandler(callName: untyped; opCode: Op; fork = FkBerlin) =
proc callName*(c: Computation) =
var desc: Vm2Ctx
desc.cpt = c
vm2OpTabBerlin[opCode].exec.run(desc)
vm2OpHandlers[fork][opCode].exec.run(desc)
opHandler add, Op.Add
opHandler mul, Op.Mul
@ -123,178 +142,23 @@ opHandler difficulty, Op.Difficulty
opHandler gasLimit, Op.GasLimit
opHandler chainId, Op.ChainId
opHandler selfBalance, Op.SelfBalance
# ##########################################
# 50s: Stack, Memory, Storage and Flow Operations
op pop, inline = true:
## 0x50, Remove item from stack.
discard c.stack.popInt()
op mload, inline = true, memStartPos:
## 0x51, Load word from memory
let memPos = memStartPos.cleanMemRef
c.gasMeter.consumeGas(
c.gasCosts[MLoad].m_handler(c.memory.len, memPos, 32),
reason="MLOAD: GasVeryLow + memory expansion"
)
c.memory.extend(memPos, 32)
push: c.memory.read(memPos, 32) # TODO, should we convert to native endianness?
op mstore, inline = true, memStartPos, value:
## 0x52, Save word to memory
let memPos = memStartPos.cleanMemRef
c.gasMeter.consumeGas(
c.gasCosts[MStore].m_handler(c.memory.len, memPos, 32),
reason="MSTORE: GasVeryLow + memory expansion"
)
c.memory.extend(memPos, 32)
c.memory.write(memPos, value.toByteArrayBE) # is big-endian correct? Parity/Geth do convert
op mstore8, inline = true, memStartPos, value:
## 0x53, Save byte to memory
let memPos = memStartPos.cleanMemRef
c.gasMeter.consumeGas(
c.gasCosts[MStore].m_handler(c.memory.len, memPos, 1),
reason="MSTORE8: GasVeryLow + memory expansion"
)
c.memory.extend(memPos, 1)
c.memory.write(memPos, [value.toByteArrayBE[31]])
op sload, inline = true, slot:
## 0x54, Load word from storage.
push: c.getStorage(slot)
template sstoreImpl(c: Computation, slot, newValue: Uint256) =
let currentValue {.inject.} = c.getStorage(slot)
let
gasParam = GasParams(kind: Op.Sstore, s_currentValue: currentValue)
(gasCost, gasRefund) = c.gasCosts[Sstore].c_handler(newValue, gasParam)
c.gasMeter.consumeGas(gasCost, &"SSTORE: {c.msg.contractAddress}[{slot}] -> {newValue} ({currentValue})")
if gasRefund > 0:
c.gasMeter.refundGas(gasRefund)
c.vmState.mutateStateDB:
db.setStorage(c.msg.contractAddress, slot, newValue)
op sstore, inline = false, slot, newValue:
## 0x55, Save word to storage.
checkInStaticContext(c)
block:
sstoreImpl(c, slot, newValue)
template sstoreNetGasMeteringImpl(c: Computation, slot, newValue: Uint256) =
let stateDB = c.vmState.readOnlyStateDB
let currentValue {.inject.} = c.getStorage(slot)
let
gasParam = GasParams(kind: Op.Sstore,
s_currentValue: currentValue,
s_originalValue: stateDB.getCommittedStorage(c.msg.contractAddress, slot)
)
(gasCost, gasRefund) = c.gasCosts[Sstore].c_handler(newValue, gasParam)
c.gasMeter.consumeGas(gasCost, &"SSTORE EIP2200: {c.msg.contractAddress}[{slot}] -> {newValue} ({currentValue})")
if gasRefund != 0:
c.gasMeter.refundGas(gasRefund)
c.vmState.mutateStateDB:
db.setStorage(c.msg.contractAddress, slot, newValue)
op sstoreEIP2200, inline = false, slot, newValue:
checkInStaticContext(c)
const SentryGasEIP2200 = 2300 # Minimum gas required to be present for an SSTORE call, not consumed
if c.gasMeter.gasRemaining <= SentryGasEIP2200:
raise newException(OutOfGas, "Gas not enough to perform EIP2200 SSTORE")
block:
sstoreNetGasMeteringImpl(c, slot, newValue)
op sstoreEIP1283, inline = false, slot, newValue:
checkInStaticContext(c)
block:
sstoreNetGasMeteringImpl(c, slot, newValue)
proc jumpImpl(c: Computation, jumpTarget: UInt256) =
if jumpTarget >= c.code.len.u256:
raise newException(InvalidJumpDestination, "Invalid Jump Destination")
let jt = jumpTarget.truncate(int)
c.code.pc = jt
let nextOpcode = c.code.peek
if nextOpcode != JUMPDEST:
raise newException(InvalidJumpDestination, "Invalid Jump Destination")
# TODO: next check seems redundant
if not c.code.isValidOpcode(jt):
raise newException(InvalidInstruction, "Jump resulted in invalid instruction")
op jump, inline = true, jumpTarget:
## 0x56, Alter the program counter
jumpImpl(c, jumpTarget)
op jumpI, inline = true, jumpTarget, testedValue:
## 0x57, Conditionally alter the program counter.
if testedValue != 0:
jumpImpl(c, jumpTarget)
op pc, inline = true:
## 0x58, Get the value of the program counter prior to the increment corresponding to this instruction.
push: max(c.code.pc - 1, 0)
op msize, inline = true:
## 0x59, Get the size of active memory in bytes.
push: c.memory.len
op gas, inline = true:
## 0x5a, Get the amount of available gas, including the corresponding reduction for the cost of this instruction.
push: c.gasMeter.gasRemaining
op jumpDest, inline = true:
## 0x5b, Mark a valid destination for jumps. This operation has no effect on machine state during execution.
discard
op beginSub, inline = true:
## 0x5c, Marks the entry point to a subroutine
raise newException(OutOfGas, "Abort: Attempt to execute BeginSub opcode")
op returnSub, inline = true:
## 0x5d, Returns control to the caller of a subroutine.
if c.returnStack.len == 0:
raise newException(OutOfGas, "Abort: invalid returnStack during ReturnSub")
# Other than the check that the return stack is not empty, there is no
# need to validate the pc from 'returns', since we only ever push valid
# values onto it via jumpsub.
c.code.pc = c.returnStack.pop()
op jumpSub, inline = true, jumpTarget:
## 0x5e, Transfers control to a subroutine.
if jumpTarget >= c.code.len.u256:
raise newException(InvalidJumpDestination, "JumpSub destination exceeds code len")
let returnPC = c.code.pc
let jt = jumpTarget.truncate(int)
c.code.pc = jt
let nextOpcode = c.code.peek
if nextOpcode != BeginSub:
raise newException(InvalidJumpDestination, "Invalid JumpSub destination")
if c.returnStack.len == 1023:
raise newException(FullStack, "Out of returnStack")
c.returnStack.add returnPC
inc c.code.pc
opHandler pop, Op.Pop
opHandler mload, Op.Mload
opHandler mstore, Op.Mstore
opHandler mstore8, Op.Mstore8
opHandler sload, Op.Sload
opHandler sstore, Op.Sstore, FkFrontier
opHandler sstoreEIP1283, Op.Sstore, FkConstantinople
opHandler sstoreEIP2200, Op.Sstore
opHandler jump, Op.Jump
opHandler jumpI, Op.JumpI
opHandler pc, Op.Pc
opHandler msize, Op.Msize
opHandler gas, Op.Gas
opHandler jumpDest, Op.JumpDest
opHandler beginSub, Op.BeginSub
opHandler returnSub, Op.ReturnSub
opHandler jumpSub, Op.JumpSub
# ##########################################
# 60s & 70s: Push Operations.