re-integrated 0x5# op handlers
This commit is contained in:
parent
0f1c7cee43
commit
d373ab6460
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
# ------------------------------------------------------------------------------
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue