diff --git a/nimbus/vm2/interpreter/op_handlers.nim b/nimbus/vm2/interpreter/op_handlers.nim index 77a352263..db46491f5 100644 --- a/nimbus/vm2/interpreter/op_handlers.nim +++ b/nimbus/vm2/interpreter/op_handlers.nim @@ -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 diff --git a/nimbus/vm2/interpreter/op_handlers/oph_defs.nim b/nimbus/vm2/interpreter/op_handlers/oph_defs.nim index 1c41b9005..6a8750fa0 100644 --- a/nimbus/vm2/interpreter/op_handlers/oph_defs.nim +++ b/nimbus/vm2/interpreter/op_handlers/oph_defs.nim @@ -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 diff --git a/nimbus/vm2/interpreter/op_handlers/oph_memory.nim b/nimbus/vm2/interpreter/op_handlers/oph_memory.nim new file mode 100644 index 000000000..8ee1acb33 --- /dev/null +++ b/nimbus/vm2/interpreter/op_handlers/oph_memory.nim @@ -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 +# ------------------------------------------------------------------------------ diff --git a/nimbus/vm2/interpreter/opcodes_impl.nim b/nimbus/vm2/interpreter/opcodes_impl.nim index f467f19fc..46e20acf3 100644 --- a/nimbus/vm2/interpreter/opcodes_impl.nim +++ b/nimbus/vm2/interpreter/opcodes_impl.nim @@ -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.