diff --git a/src/computation.nim b/src/computation.nim index 532a4a604..f0e9286e5 100644 --- a/src/computation.nim +++ b/src/computation.nim @@ -11,6 +11,8 @@ proc memoryGasCost*(sizeInBytes: Int256): Int256 = totalCost = linearCost + quadraticCost result = totalCost +const VARIABLE_GAS_COST_OPS* = {Op.Exp} + type BaseComputation* = ref object of RootObj # The execution computation @@ -36,8 +38,11 @@ type erasesReturnData*: bool Opcode* = ref object of RootObj - kind*: Op - gasCost*: Int256 + case kind*: Op + of VARIABLE_GAS_COST_OPS: + gasCostHandler*: proc(computation: var BaseComputation): Int256 + else: + gasCostConstant*: Int256 runLogic*: proc(computation: var BaseComputation) proc newBaseComputation*(vmState: BaseVMState, message: Message): BaseComputation = diff --git a/src/constants.nim b/src/constants.nim index c94665ea0..ff83e5823 100644 --- a/src/constants.nim +++ b/src/constants.nim @@ -10,6 +10,9 @@ type proc int256*(i: int): Int256 = i.initBigInt +template i256*(i: int): Int256 = + i.int256 + # TODO # We'll have a fast fixed int256, for now this works @@ -47,6 +50,10 @@ proc `mod`*(a: Int256, b: int): Int256 = proc `div`*(a: Int256, b: int): Int256 = a div b.int256 +proc log256*(a: Int256): Int256 = + # TODO + 2.int256 + proc setXLen[T](s: var seq[T]; newlen: Natural) = if s.isNil: s = newSeq[T](newlen) diff --git a/src/errors.nim b/src/errors.nim index f6f7506f2..ae024add3 100644 --- a/src/errors.nim +++ b/src/errors.nim @@ -61,6 +61,10 @@ type ## Error raised to indicate an attempt was made to read data beyond the ## boundaries of the buffer (such as with RETURNDATACOPY) + TypeError* = object of VMError + ## Error when invalid values are found + + proc makeVMError*(): VMError = result.burnsGas = true result.erasesReturnData = true diff --git a/src/logic/arithmetic.nim b/src/logic/arithmetic.nim index e3ae6d07a..9249b025d 100644 --- a/src/logic/arithmetic.nim +++ b/src/logic/arithmetic.nim @@ -76,20 +76,20 @@ proc sdiv*(computation: var BaseComputation) = pushRes() # no curry -proc exp*(computation: var BaseComputation, gasPerByte: Int256) = +proc exp*(computation: var BaseComputation) = # Exponentiation var (base, exponent) = computation.stack.popInt(2) var bitSize = 0.int256 # TODO exponent.bitLength() var byteSize = ceil8(bitSize) div 8 var res = if base == 0: 0.int256 else: (base ^ exponent.getInt) mod constants.UINT_256_CEILING - computation.gasMeter.consumeGas( - gasPerByte * byteSize, - reason="EXP: exponent bytes" - ) + # computation.gasMeter.consumeGas( + # gasPerByte * byteSize, + # reason="EXP: exponent bytes" + # ) pushRes() -proc signextend(computation: var BaseComputation) = +proc signextend*(computation: var BaseComputation) = # Signed Extend var (bits, value) = computation.stack.popInt(2) diff --git a/src/opcode.nim b/src/opcode.nim index 82f82e1ef..1917bd693 100644 --- a/src/opcode.nim +++ b/src/opcode.nim @@ -4,18 +4,30 @@ import template run*(opcode: Opcode, computation: var BaseComputation) = # Hook for performing the actual VM execution - computation.gasMeter.consumeGas(opcode.gasCost, reason = $opcode.kind) + computation.gasMeter.consumeGas(opcode.gasCost(computation), reason = $opcode.kind) opcode.runLogic(computation) method logger*(opcode: Opcode): Logger = logging.getLogger(&"vm.opcode.{opcode.kind}") -proc newOpcode*(kind: Op, gasCost: Int256, logic: proc(computation: var BaseComputation)): Opcode = - Opcode(kind: kind, gasCost: gasCost, runLogic: logic) +method gasCost*(opcode: Opcode, computation: var BaseComputation): Int256 = + if opcode.kind in VARIABLE_GAS_COST_OPS: + opcode.gasCostHandler(computation) + else: + opcode.gasCostConstant +template newOpcode*(kind: Op, gasCost: Int256, logic: proc(computation: var BaseComputation)): Opcode = + Opcode(kind: kind, gasCostConstant: gasCost, runLogic: logic) + +template newOpcode*(kind: Op, gasHandler: proc(computation: var BaseComputation): Int256, logic: proc(computation: var BaseComputation)): Opcode = + Opcode(kind: kind, gasCostHandler: gasHandler, runLogic: logic) method `$`*(opcode: Opcode): string = - &"{opcode.kind}(0x{opcode.kind.int.toHex(2)}: {opcode.gasCost})" + let gasCost = if opcode.kind in VARIABLE_GAS_COST_OPS: + "variable" + else: + $opcode.gasCostConstant + &"{opcode.kind}(0x{opcode.kind.int.toHex(2)}: {gasCost})" macro initOpcodes*(spec: untyped): untyped = var value = ident("value") @@ -27,8 +39,12 @@ macro initOpcodes*(spec: untyped): untyped = let op = child[0] let gasCost = child[1][0][0] let handler = child[1][0][1] - var opcode = quote: - `value`[`op`] = newOpcode(`op`, `gasCost`, `handler`) + let opcode = if gasCost.repr[0].isLowerAscii(): + quote: + `value`[`op`] = Opcode(kind: `op`, gasCostHandler: `gasCost`, runLogic: `handler`) + else: + quote: + `value`[`op`] = Opcode(kind: `op`, gasCostConstant: `gasCost`, runLogic: `handler`) result[1].add(opcode) result[1].add(value) diff --git a/src/runner.nim b/src/runner.nim index 287c43414..f5a84f04a 100644 --- a/src/runner.nim +++ b/src/runner.nim @@ -1,14 +1,30 @@ import strformat, strutils, tables, macros, constants, bigints, errors, logging, vm_state, - vm / [gas_meter, stack, code_stream, memory, message, value], db / chain, computation, opcode, opcode_values, utils / [header, address], + vm / [gas_meter, stack, code_stream, memory, message, value, gas_costs], db / chain, computation, opcode, opcode_values, utils / [header, address], logic / [arithmetic, comparison] var opcodes = initOpcodes: - Op.Add: GAS_VERY_LOW add - Op.Sub: GAS_VERY_LOW sub - Op.Mul: GAS_LOW mul - Op.Div: GAS_LOW divide + # arithmetic + Op.Add: GAS_VERY_LOW add + Op.Mul: GAS_LOW mul + Op.Sub: GAS_VERY_LOW sub + Op.Div: GAS_LOW divide + Op.SDiv: GAS_LOW sdiv + Op.Mod: GAS_LOW modulo + Op.SMod: GAS_LOW smod + Op.AddMod: GAS_MID addmod + Op.MulMod: GAS_MID mulmod + Op.Exp: expGasCost arithmetic.exp + Op.SignExtend: GAS_LOW signextend + + + # comparison + Op.Lt: GAS_VERY_LOW lt + Op.Gt: GAS_VERY_LOW gt + Op.SLt: GAS_VERY_LOW slt + Op.SGt: GAS_VERY_LOW sgt + Op.Eq: GAS_VERY_LOW eq var mem = newMemory(pow(1024.int256, 2)) @@ -81,8 +97,12 @@ macro runOpcodes*(computation: untyped, program: untyped): untyped = # useful for testing simple cases runOpcodes(c): - stack: @[2.vint, 2.vint, 2.vint] + stack: @[2.vint, 2.vint, 2.vint, 2.vint, 2.vint, 2.vint, 4.vint] Op.Add Op.Mul + Op.Div + Op.Sub + Op.Mul + Op.Mul diff --git a/src/vm/gas_costs.nim b/src/vm/gas_costs.nim new file mode 100644 index 000000000..59f57ba21 --- /dev/null +++ b/src/vm/gas_costs.nim @@ -0,0 +1,8 @@ +import + strformat, bigints, + ../constants, ../opcode, ../computation, stack + +proc expGasCost*(computation: var BaseComputation): Int256 = + let arg = computation.stack.getInt(0) + result = if arg == 0: 10.i256 else: (10.i256 + 10.i256 * (1.i256 + arg.log256)) + \ No newline at end of file diff --git a/src/vm/stack.nim b/src/vm/stack.nim index a6fe763b8..753fc0ca0 100644 --- a/src/vm/stack.nim +++ b/src/vm/stack.nim @@ -168,6 +168,22 @@ proc dup*(stack: var Stack; position: int) = &"Insufficient stack items for DUP{position}") +proc getInt*(stack: Stack, position: int): Int256 = + let element = stack.values[position] + case element.kind: + of VInt: + result = element.i + else: + raise newException(TypeError, "Expected int") + +proc getBinary*(stack: Stack, position: int): cstring = + let element = stack.values[position] + case element.kind: + of VBinary: + result = element.b + else: + raise newException(TypeError, "Expected binary") + proc `$`*(stack: Stack): string = let values = stack.values.mapIt(&" {$it}").join("\n") &"Stack:\n{values}"