diff --git a/src/computation.nim b/src/computation.nim index 83dc6cf53..05bdb8030 100644 --- a/src/computation.nim +++ b/src/computation.nim @@ -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, diff --git a/src/constants.nim b/src/constants.nim index 433b6dba2..9ade467e9 100644 --- a/src/constants.nim +++ b/src/constants.nim @@ -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 diff --git a/src/errors.nim b/src/errors.nim index ae024add3..b2d423235 100644 --- a/src/errors.nim +++ b/src/errors.nim @@ -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 diff --git a/src/logic/call b/src/logic/call new file mode 100755 index 000000000..150e8bcd6 Binary files /dev/null and b/src/logic/call differ diff --git a/src/logic/call.nim b/src/logic/call.nim new file mode 100644 index 000000000..610b900fc --- /dev/null +++ b/src/logic/call.nim @@ -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") diff --git a/src/runner.nim b/src/runner.nim index 34678b58f..6bf3247b4 100644 --- a/src/runner.nim +++ b/src/runner.nim @@ -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( diff --git a/src/validation.nim b/src/validation.nim index 1cc152f83..4d005cfd3 100644 --- a/src/validation.nim +++ b/src/validation.nim @@ -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, diff --git a/src/vm/message.nim b/src/vm/message.nim index 3ec8d7eea..5c58b5cef 100644 --- a/src/vm/message.nim +++ b/src/vm/message.nim @@ -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,