WIP call (different call variants depending on fork)

This commit is contained in:
Alexander Ivanov 2018-01-30 20:12:05 +02:00
parent 69dccb1520
commit b9eb74fa26
8 changed files with 287 additions and 14 deletions

View File

@ -94,7 +94,7 @@ method prepareChildMessage*(
options: MessageOptions = newMessageOptions()): Message =
var childOptions = options
childOptions.depth = c.msg.depth + 1.i256
childOptions.depth = c.msg.depth + 1
result = newMessage(
gas,
c.msg.gasPrice,

View File

@ -92,7 +92,7 @@ let
CREATE_CONTRACT_ADDRESS* = cstring""
ZERO_ADDRESS* = repeat(cstring"\x00", 20)
ZERO_HASH32* = repeat(cstring"\x00", 20)
STACKDEPTHLIMIT* = 1024
STACK_DEPTH_LIMIT* = 1024
GAS_NULL* = 0.i256
GAS_ZERO* = 0.i256
@ -118,7 +118,7 @@ let
GAS_SELF_DESTRUCT_NEW_ACCOUNT* = 25_000.i256
GAS_CREATE* = 32_000.i256
GAS_CALL* = 40.i256
GASCALLVALUE = 9_000.i256
GAS_CALL_VALUE* = 9_000.i256
GAS_CALL_STIPEND* = 2_300.i256
GAS_NEW_ACCOUNT* = 25_000.i256

View File

@ -64,6 +64,8 @@ type
TypeError* = object of VMError
## Error when invalid values are found
NotImplementedError* = object of VMError
## Not implemented error
proc makeVMError*(): VMError =
result.burnsGas = true

BIN
src/logic/call Executable file

Binary file not shown.

262
src/logic/call.nim Normal file
View File

@ -0,0 +1,262 @@
import
strformat,
../constants, ../errors, ../computation, ../opcode, ../opcode_values, ../logging,
.. / vm / [stack, memory, gas_meter, message],
.. / utils / [address, bytes],
bigints
type
BaseCall* = ref object of Opcode
Call* = ref object of BaseCall
CallCode* = ref object of BaseCall
DelegateCall* = ref object of BaseCall
CallEIP150* = ref object of Call
CallCodeEIP150* = ref object of CallCode
DelegateCallEIP150* = ref object of DelegateCall
CallEIP161* = ref object of CallEIP150
# Byzantium
StaticCall* = ref object of CallEIP161
CallByzantium* = ref object of CallEIP161
using
computation: var BaseComputation
method msgExtraGas*(call: BaseCall, computation; gas: Int256, to: cstring, value: Int256): Int256 {.base.} =
raise newException(NotImplementedError, "Must be implemented by subclasses")
method msgGas*(call: BaseCall, computation; gas: Int256, to: cstring, value: Int256): (Int256, Int256) {.base.} =
let extraGas = call.msgExtraGas(computation, gas, to, value)
let totalFee = gas + extraGas
let childMsgGas = gas + (if value != 0: GAS_CALL_STIPEND else: 0.i256)
(childMsgGas, totalFee)
method callParams*(call: BaseCall, computation): (Int256, Int256, cstring, cstring, cstring, Int256, Int256, Int256, Int256, bool, bool) {.base.} =
raise newException(NotImplementedError, "Must be implemented subclasses")
method runLogic*(call: BaseCall, computation) =
computation.gasMeter.consumeGas(call.gasCost(computation), reason = $call.kind)
let (gas, value, to, sender,
codeAddress,
memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize,
shouldTransferValue,
isStatic) = call.callParams(computation)
computation.extendMemory(memoryInputStartPosition, memoryInputSize)
computation.extendMemory(memoryOutputStartPosition, memoryOutputSize)
let callData = computation.memory.read(memoryInputStartPosition, memoryInputSize)
let (childMsgGas, childMsgGasFee) = call.msgGas(computation, gas, to, value)
computation.gasMeter.consumeGas(childMsgGasFee, reason = $call.kind)
# TODO: Pre-call checks
# with computation.vm_state.state_db(read_only=True) as state_db:
# sender_balance = state_db.get_balance(computation.msg.storage_address)
let senderBalance = 0.i256
let insufficientFunds = shouldTransferValue and senderBalance < value
let stackTooDeep = computation.msg.depth + 1 > constants.STACK_DEPTH_LIMIT
if insufficientFunds or stackTooDeep:
computation.returnData = cstring""
var errMessage: string
if insufficientFunds:
errMessage = &"Insufficient Funds: have: {senderBalance} | need: {value}"
elif stackTooDeep:
errMessage = "Stack Limit Reached"
else:
raise newException(VMError, "Invariant: Unreachable code path")
call.logger.debug(&"{call.kind} failure: {errMessage}")
computation.gasMeter.returnGas(childMsgGas)
computation.stack.push(0.i256)
else:
# TODO: with
# with computation.vm_state.state_db(read_only=True) as state_db:
# if code_address:
# code = state_db.get_code(code_address)
# else:
# code = state_db.get_code(to)
let code = cstring""
let childMsg = computation.prepareChildMessage(
childMsgGas,
to,
value,
callData,
code,
MessageOptions(
shouldTransferValue: shouldTransferValue,
isStatic: isStatic))
if not sender.isNil:
childMsg.sender = sender
# let childComputation = computation.applyChildComputation(childMsg)
# TODO
var childComputation: BaseComputation
if childComputation.isError:
computation.stack.push(0.i256)
else:
computation.stack.push(1.i256)
if not childComputation.shouldEraseReturnData:
let actualOutputSize = min(memoryOutputSize, childComputation.output.len.i256)
computation.memory.write(
memoryOutputStartPosition,
actualOutputSize,
childComputation.output.toBytes[0 ..< actualOutputSize.getInt])
if not childComputation.shouldBurnGas:
computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining)
method msgExtraGas(call: Call, computation; gas: Int256, to: cstring, value: Int256): Int256 =
# TODO: db
# with computation.vm_state.state_db(read_only=True) as state_db:
# let accountExists = db.accountExists(to)
let accountExists = false
let transferGasFee = if value != 0: GAS_CALL_VALUE else: 0.i256
let createGasFee = if not accountExists: GAS_NEW_ACCOUNT else: 0.i256
transferGasFee + createGasFee
method callParams(call: CallCode, computation): (Int256, Int256, cstring, cstring, cstring, Int256, Int256, Int256, Int256, bool, bool) =
let gas = computation.stack.popInt()
let to = cstring(forceBytesToAddress(computation.stack.popBinary))
let (value,
memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(5)
result = (gas,
value,
to,
nil, # sender
nil, # code_address
memoryInputStartPosition,
memoryInputSize,
memoryOutputStartPosition,
memoryOutputSize,
true, # should_transfer_value,
computation.msg.isStatic)
method msgExtraGas(call: CallCode, computation; gas: Int256, to: cstring, value: Int256): Int256 =
if value != 0: GAS_CALL_VALUE else: 0.i256
method callParams(call: Call, computation): (Int256, Int256, cstring, cstring, cstring, Int256, Int256, Int256, Int256, bool, bool) =
let gas = computation.stack.popInt()
let codeAddress = cstring(forceBytesToAddress(computation.stack.popBinary))
let (value,
memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(5)
let to = computation.msg.storageAddress
let sender = computation.msg.storageAddress
result = (gas,
value,
to,
sender,
codeAddress,
memoryInputStartPosition,
memoryInputSize,
memoryOutputStartPosition,
memoryOutputSize,
true, # should_transfer_value,
computation.msg.isStatic)
method msgGas(call: DelegateCall, computation; gas: Int256, to: cstring, value: Int256): (Int256, Int256) =
(gas, gas)
method msgExtraGas(call: DelegateCall, computation; gas: Int256, to: cstring, value: Int256): Int256 =
0.i256
method callParams(call: DelegateCall, computation): (Int256, Int256, cstring, cstring, cstring, Int256, Int256, Int256, Int256, bool, bool) =
let gas = computation.stack.popInt()
let codeAddress = cstring(forceBytesToAddress(computation.stack.popBinary))
let (memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(4)
let to = computation.msg.storageAddress
let sender = computation.msg.storageAddress
let value = computation.msg.value
result = (gas,
value,
to,
sender,
codeAddress,
memoryInputStartPosition,
memoryInputSize,
memoryOutputStartPosition,
memoryOutputSize,
false, # should_transfer_value,
computation.msg.isStatic)
proc maxChildGasEIP150(gas: Int256): Int256 =
gas - gas div 64
proc computeEIP150MsgGas(computation; gas: Int256, extraGas: Int256, value: Int256, name: string, callStipend: Int256): (Int256, Int256) =
if computation.gasMeter.gasRemaining < extraGas:
raise newException(OutOfGas, &"Out of gas: Needed {extraGas} - Remaining {computation.gasMeter.gasRemaining} - Reason: {name}")
let gas = min(gas, maxChildGasEIP150(computation.gasMeter.gasRemaining - extraGas))
let totalFee = gas + extraGas
let childMsgGas = gas + (if value != 0: callStipend else: 0.i256)
(childMsgGas, totalFee)
method msgGas(call: CallEIP150, computation; gas: Int256, to: cstring, value: Int256): (Int256, Int256) =
let extraGas = call.msgExtraGas(computation, gas, to, value)
computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, GAS_CALL_STIPEND)
method msgGas(call: CallCodeEIP150, computation; gas: Int256, to: cstring, value: Int256): (Int256, Int256) =
let extraGas = call.msgExtraGas(computation, gas, to, value)
computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, GAS_CALL_STIPEND)
method msgGas(call: DelegateCallEIP150, computation; gas: Int256, to: cstring, value: Int256): (Int256, Int256) =
let extraGas = call.msgExtraGas(computation, gas, to, value)
computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, 0.i256)
proc msgExtraGas*(call: CallEIP161, computation; gas: Int256, to: cstring, value: Int256): Int256 =
# TODO: with
# with computation.vm_state.state_db(read_only=True) as state_db:
# account_is_dead = (
# not state_db.account_exists(to) or
# state_db.account_is_empty(to))
let accountIsDead = true
let transferGasFee = if value != 0: GAS_CALL_VALUE else: 0.i256
let createGasFee = if accountIsDead and value != 0: GAS_NEW_ACCOUNT else: 0.i256
transferGasFee + createGasFee
method callParams(call: StaticCall, computation): (Int256, Int256, cstring, cstring, cstring, Int256, Int256, Int256, Int256, bool, bool) =
let gas = computation.stack.popInt()
let to = cstring(forceBytesToAddress(computation.stack.popBinary))
let (memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(4)
result = (gas,
0.i256, # value
to,
nil, # sender
nil, # codeAddress
memoryInputStartPosition,
memoryInputSize,
memoryOutputStartPosition,
memoryOutputSize,
false, # should_transfer_value,
true) # is_static
method callParams(call: CallByzantium, computation): (Int256, Int256, cstring, cstring, cstring, Int256, Int256, Int256, Int256, bool, bool) =
result = procCall callParams(call, computation)
if computation.msg.isStatic and result[1] != 0:
raise newException(WriteProtection, "Cannot modify state while inside of a STATICCALL context")

View File

@ -2,7 +2,7 @@ import
strformat, strutils, tables, macros,
constants, bigints, errors, logging, vm_state,
vm / [gas_meter, stack, code_stream, memory, message, value, gas_costs], db / chain, computation, opcode, opcode_values, utils / [header, address],
logic / [arithmetic, comparison, sha3, context, block_ops, stack_ops, duplication, swap, memory_ops, storage, flow, logging_ops, invalid]
logic / [arithmetic, comparison, sha3, context, block_ops, stack_ops, duplication, swap, memory_ops, storage, flow, logging_ops, invalid, call]
var opcodes = initOpcodes:
# arithmetic
@ -97,12 +97,16 @@ var opcodes = initOpcodes:
# Op.Create: GAS_CREATE create
# Op.Call: 0.i256 callOp
# Op.CallCode: 0.i256 callCodeOp
# Op.Return: 0.i256 returnOp
# Op.DelegateCall: 0.i256 delegateCall
# Op.SelfDestruct: GAS_SELF_DESTRUCT_COST selfDestruct
# call
opcodes[Op.Call] = Call(kind: Op.Call)
opcodes[Op.CallCode] = CallCode(kind: Op.CallCode)
opcodes[Op.DelegateCall] = DelegateCall(kind: Op.DelegateCall)
# system
# Op.Create: GAS_CREATE create
# Op.Return: 0.i256 returnOp
# Op.SelfDestruct: GAS_SELF_DESTRUCT_COST selfDestruct
var mem = newMemory(pow(1024.int256, 2))
@ -120,7 +124,7 @@ var msg = newMessage(
0.int256,
data,
code,
MessageOptions(depth: 1.int256))
MessageOptions(depth: 1))
var c = BaseComputation(
vmState: BaseVMState(

View File

@ -18,6 +18,11 @@ proc validateGte*(value: Int256, minimum: int, title: string = "Value") =
raise newException(ValidationError,
fmt"{title} {value} is not greater than or equal to {minimum}")
proc validateGte*(value: int, minimum: int, title: string = "Value") =
if value <= minimum:
raise newException(ValidationError,
fmt"{title} {value} is not greater than or equal to {minimum}")
proc validateGt*(value: Int256, minimum: int, title: string = "Value") =
if value < minimum.int256:
raise newException(ValidationError,

View File

@ -26,7 +26,7 @@ type
code*: cstring
internalOrigin: cstring
internalCodeAddress: cstring
depth*: Int256
depth*: int
internalStorageAddress: cstring
shouldTransferValue*: bool
isStatic*: bool
@ -34,7 +34,7 @@ type
MessageOptions* = ref object
origin*: cstring
depth*: Int256
depth*: int
createAddress*: cstring
codeAddress*: cstring
shouldTransferValue*: bool
@ -51,7 +51,7 @@ proc `storageAddress=`*(message: var Message, value: cstring) =
proc newMessageOptions*(
origin: cstring = nil,
depth: Int256 = 0.int256,
depth: int = 0,
createAddress: cstring = nil,
codeAddress: cstring = nil,
shouldTransferValue: bool = true,