Fixes for Call gas usage, Calls use precompiles, pass down their opCode

This commit is contained in:
coffeepots 2018-10-02 16:07:16 +01:00
parent e2087f0922
commit 36270ff4d5
2 changed files with 86 additions and 59 deletions

View File

@ -11,7 +11,7 @@ import
../constants, ../errors, ../validation, ../vm_state, ../vm_types,
./interpreter/[opcode_values, gas_meter, gas_costs, vm_forks],
./code_stream, ./memory, ./message, ./stack, ../db/[state_db, db_chain],
../utils/header, byteutils, ranges
../utils/header, byteutils, ranges, eth_keys, precompiles
logScope:
topics = "vm computation"
@ -85,7 +85,7 @@ proc prepareChildMessage*(
code,
childOptions)
proc applyMessage(computation: var BaseComputation) =
proc applyMessage(computation: var BaseComputation, opCode: static[Op]) =
var transaction = computation.vmState.beginTransaction()
defer: transaction.dispose()
@ -97,28 +97,66 @@ proc applyMessage(computation: var BaseComputation) =
computation.vmState.chainDb.getStateDb(
computation.vmState.blockHeader.hash, false).
getBalance(computation.msg.sender)
var newBalance = senderBalance
if sender_balance < computation.msg.value:
raise newException(InsufficientFunds,
&"Insufficient funds: {senderBalance} < {computation.msg.value}"
)
when opCode in {Call, CallCode}:
let
insufficientFunds = senderBalance < computation.msg.value
stackTooDeep = computation.msg.depth >= MaxCallDepth
computation.vmState.mutateStateDb:
db.setBalance(computation.msg.sender, db.getBalance(computation.msg.sender) - computation.msg.value)
db.addBalance(computation.msg.storage_address, computation.msg.value)
if insufficientFunds or stackTooDeep:
computation.returnData = @[]
var errMessage: string
if insufficientFunds:
errMessage = &"Insufficient Funds: have: {$senderBalance} need: {$computation.msg.value}"
elif stackTooDeep:
errMessage = "Stack Limit Reached"
else:
raise newException(VMError, "Invariant: Unreachable code path")
debug "Computation failure", msg = errMessage
computation.gasMeter.returnGas(computation.msg.gas)
push: 0
return
newBalance = senderBalance - computation.msg.value
computation.vmState.mutateStateDb:
db.setBalance(computation.msg.sender, newBalance)
db.addBalance(computation.msg.storage_address, computation.msg.value)
debug "Value transferred",
source = computation.msg.sender,
dest = computation.msg.storage_address,
value = computation.msg.value,
oldSenderBalance = senderBalance,
newSenderBalance = newBalance,
gasPrice = computation.msg.gasPrice,
gas = computation.msg.gas
debug "Apply message",
value = computation.msg.value,
senderBalance = newBalance,
sender = computation.msg.sender.toHex,
address = computation.msg.storage_address.toHex
address = computation.msg.storage_address.toHex,
gasPrice = computation.msg.gasPrice,
gas = computation.msg.gas
computation.opcodeExec(computation)
# Run code
if not computation.execPrecompiles:
computation.opcodeExec(computation)
if not computation.isError:
debug "Computation committed"
transaction.commit()
else:
debug "Computation rolled back due to error"
proc applyCreateMessage(fork: Fork, computation: var BaseComputation) =
computation.applyMessage()
proc applyCreateMessage(fork: Fork, computation: var BaseComputation, opCode: static[Op]) =
computation.applyMessage(opCode)
var transaction: DbTransaction
defer: transaction.safeDispose()
@ -157,13 +195,13 @@ proc applyCreateMessage(fork: Fork, computation: var BaseComputation) =
else:
# Different from Frontier:
# Reverts state on gas failure while writing contract code.
# TODO: Revert snapshot
# Transaction are reverted automatically by safeDispose.
discard
else:
if transaction != nil:
transaction.commit()
proc generateChildComputation*(fork: Fork, computation: BaseComputation, childMsg: Message): BaseComputation =
proc generateChildComputation*(fork: Fork, computation: BaseComputation, childMsg: Message, opCode: static[Op]): BaseComputation =
var childComp = newBaseComputation(
computation.vmState,
computation.vmState.blockHeader.blockNumber,
@ -173,9 +211,9 @@ proc generateChildComputation*(fork: Fork, computation: BaseComputation, childMs
childComp.opCodeExec = computation.opCodeExec
if childMsg.isCreate:
fork.applyCreateMessage(childComp)
fork.applyCreateMessage(childComp, opCode)
else:
applyMessage(childComp)
applyMessage(childComp, opCode)
return childComp
proc addChildComputation(fork: Fork, computation: BaseComputation, child: BaseComputation) =
@ -193,10 +231,10 @@ proc addChildComputation(fork: Fork, computation: BaseComputation, child: BaseCo
computation.returnData = child.output
computation.children.add(child)
proc applyChildComputation*(computation: BaseComputation, childMsg: Message): BaseComputation =
proc applyChildComputation*(computation: BaseComputation, childMsg: Message, opCode: static[Op]): BaseComputation =
## Apply the vm message childMsg as a child computation.
let fork = computation.vmState.blockHeader.blockNumber.toFork
result = fork.generateChildComputation(computation, childMsg)
result = fork.generateChildComputation(computation, childMsg, opCode)
fork.addChildComputation(computation, result)
proc registerAccountForDeletion*(c: var BaseComputation, beneficiary: EthAddress) =

View File

@ -14,6 +14,9 @@ import
../../vm_state, ../../errors, ../../constants, ../../vm_types,
../../db/[db_chain, state_db], ../../utils/addresses
logScope:
topics = "opcode impl"
# ##################################
# Syntactic sugar
@ -545,6 +548,18 @@ op create, inline = false, value, startPosition, size:
contractAddress = generateAddress(computation.msg.storageAddress, creationNonce)
isCollision = db.hasCodeOrNonce(contractAddress)
let (gasCost, childMsgGas) = computation.gasCosts[Op.Create].c_handler(
value,
GasParams(kind: Call,
c_isNewAccount: true, # TODO stub
c_gasBalance: 0,
c_contractGas: 0,
c_currentMemSize: computation.memory.len,
c_memOffset: 0, # TODO make sure if we pass the largest mem requested
c_memLength: 0 # or an addition of mem requested
))
if isCollision:
debug("Address collision while creating contract", address = contractAddress.toHex)
push: 0
@ -552,7 +567,7 @@ op create, inline = false, value, startPosition, size:
let childMsg = prepareChildMessage(
computation,
gas = 0, # TODO refactor gas
gas = childMsgGas,
to = CREATE_CONTRACT_ADDRESS,
value = value,
data = @[],
@ -560,10 +575,7 @@ op create, inline = false, value, startPosition, size:
options = MessageOptions(createAddress: contractAddress)
)
# let childComputation = applyChildBaseComputation(computation, childMsg)
var childComputation: BaseComputation # TODO - stub
new childComputation
childComputation.gasMeter.init(0)
let childComputation = computation.applyChildComputation(childMsg, Create)
if childComputation.isError:
push: 0
@ -652,13 +664,12 @@ proc staticCallParams(computation: var BaseComputation): (UInt256, UInt256, EthA
memoryOutputSize,
emvcStatic) # is_static
template genCall(callName: untyped): untyped =
template genCall(callName: untyped, opCode: Op): untyped =
op callName, inline = false:
## CALL, 0xf1, Message-Call into an account
## CALLCODE, 0xf2, Message-call into this account with an alternative account's code.
## DELEGATECALL, 0xf4, Message-call into this account with an alternative account's code, but persisting the current values for sender and value.
## STATICCALL, 0xfa, Static message-call into an account.
# TODO: forked calls for Homestead
let (gas, value, to, sender,
codeAddress,
@ -668,7 +679,7 @@ template genCall(callName: untyped): untyped =
let (memInPos, memInLen, memOutPos, memOutLen) = (memoryInputStartPosition.cleanMemRef, memoryInputSize.cleanMemRef, memoryOutputStartPosition.cleanMemRef, memoryOutputSize.cleanMemRef)
let (gasCost, childMsgGas) = computation.gasCosts[Op.Call].c_handler(
let (gasCost, childMsgGas) = computation.gasCosts[opCode].c_handler(
value,
GasParams(kind: Call,
c_isNewAccount: true, # TODO stub
@ -678,41 +689,19 @@ template genCall(callName: untyped): untyped =
c_memOffset: 0, # TODO make sure if we pass the largest mem requested
c_memLength: 0 # or an addition of mem requested
))
debug "Call", gasCost = gasCost, childCost = childMsgGas
computation.gasMeter.consumeGas(gasCost, reason = $opCode)
computation.memory.extend(memInPos, memInLen)
computation.memory.extend(memOutPos, memOutLen)
let
callData = computation.memory.read(memInPos, memInLen)
senderBalance = computation.vmState.readOnlyStateDb.getBalance(computation.msg.storageAddress)
# TODO check gas balance rollover
# TODO: shouldTransferValue in py-evm is:
# True for call and callCode
# False for callDelegate and callStatic
insufficientFunds = senderBalance < value # TODO: and shouldTransferValue
stackTooDeep = computation.msg.depth >= MaxCallDepth
if insufficientFunds or stackTooDeep:
computation.returnData = @[]
var errMessage: string
if insufficientFunds:
# Note: for some reason we can't use strformat here, we get undeclared identifiers
errMessage = &"Insufficient Funds: have: " & $senderBalance & "need: " & $value
elif stackTooDeep:
errMessage = "Stack Limit Reached"
else:
raise newException(VMError, "Invariant: Unreachable code path")
# computation.logger.debug(&"failure: {errMessage}") # TODO: Error: expression 'logger' has no type (or is ambiguous)
computation.gasMeter.returnGas(childMsgGas)
push: 0
return
let code =
if codeAddress != ZERO_ADDRESS:
computation.vmState.readOnlyStateDb.getCode(codeAddress)
else:
computation.vmState.readOnlyStateDb.getCode(to)
code =
if codeAddress != ZERO_ADDRESS:
computation.vmState.readOnlyStateDb.getCode(codeAddress)
else:
computation.vmState.readOnlyStateDb.getCode(to)
var childMsg = prepareChildMessage(
computation,
@ -727,7 +716,7 @@ template genCall(callName: untyped): untyped =
if sender != ZERO_ADDRESS:
childMsg.sender = sender
var childComputation = applyChildComputation(computation, childMsg)
var childComputation = applyChildComputation(computation, childMsg, opCode)
if childComputation.isError:
push: 0
@ -742,10 +731,10 @@ template genCall(callName: untyped): untyped =
if not childComputation.shouldBurnGas:
computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining)
genCall(call)
genCall(callCode)
genCall(delegateCall)
genCall(staticCall)
genCall(call, Call)
genCall(callCode, CallCode)
genCall(delegateCall, DelegateCall)
genCall(staticCall, StaticCall)
op returnOp, inline = false, startPos, size:
## 0xf3, Halt execution returning output data.