unify evm call for both json-rpc and graphql
also fixes rpcEstimateGas for both of json-rpc and graphql
This commit is contained in:
parent
baf508f6ae
commit
960539df81
|
@ -68,4 +68,6 @@ const
|
|||
## standard secp256k1 curve.
|
||||
65
|
||||
|
||||
DEFAULT_RPC_GAS_CAP* = 50_000_000.GasInt
|
||||
|
||||
# End
|
||||
|
|
|
@ -15,8 +15,8 @@ import
|
|||
graphql, graphql/graphql as context,
|
||||
graphql/common/types, graphql/httpserver,
|
||||
graphql/instruments/query_complexity,
|
||||
../db/[db_chain, state_db], ../utils,
|
||||
../transaction, ../rpc/rpc_utils, ../vm_state, ../config,
|
||||
../db/[db_chain, state_db], ../rpc/rpc_utils,
|
||||
".."/[utils, transaction, vm_state, config, constants],
|
||||
../transaction/call_evm
|
||||
|
||||
from eth/p2p import EthereumNode
|
||||
|
@ -901,44 +901,64 @@ proc blockAccount(ud: RootRef, params: Args, parent: Node): RespResult {.apiPrag
|
|||
let address = hexToByteArray[20](params[0].val.stringVal)
|
||||
ctx.accountNode(h.header, address)
|
||||
|
||||
proc toCallData(n: Node): (RpcCallData, bool) =
|
||||
# phew, probably need to use macro here :)
|
||||
var cd: RpcCallData
|
||||
var gasLimit = false
|
||||
if n[0][1].kind != nkEmpty:
|
||||
cd.source = hextoByteArray[20](n[0][1].stringVal)
|
||||
const
|
||||
fFrom = 0
|
||||
fTo = 1
|
||||
fGasLimit = 2
|
||||
fGasPrice = 3
|
||||
fMaxFee = 4
|
||||
fMaxPriorityFee = 5
|
||||
fValue = 6
|
||||
fData = 7
|
||||
|
||||
if n[1][1].kind != nkEmpty:
|
||||
cd.to = hextoByteArray[20](n[1][1].stringVal)
|
||||
else:
|
||||
cd.contractCreation = true
|
||||
template isSome(n: Node, field: int): bool =
|
||||
# [0] is the field's name node
|
||||
# [1] is the field's value node
|
||||
n[field][1].kind != nkEmpty
|
||||
|
||||
if n[2][1].kind != nkEmpty:
|
||||
cd.gas = parseU64(n[2][1]).GasInt
|
||||
gasLimit = true
|
||||
else:
|
||||
# TODO: this is globalGasCap in geth
|
||||
cd.gas = GasInt(high(uint64) div 2)
|
||||
template fieldString(n: Node, field: int): string =
|
||||
n[field][1].stringVal
|
||||
|
||||
if n[3][1].kind != nkEmpty:
|
||||
let gasPrice = parse(n[3][1].stringVal, UInt256, radix = 16)
|
||||
cd.gasPrice = gasPrice.truncate(GasInt)
|
||||
template optionalAddress(dstField: untyped, n: Node, field: int) =
|
||||
if isSome(n, field):
|
||||
var address: EthAddress
|
||||
hexToByteArray(fieldString(n, field), address)
|
||||
dstField = some(address)
|
||||
|
||||
if n[4][1].kind != nkEmpty:
|
||||
cd.value = parse(n[4][1].stringVal, UInt256, radix = 16)
|
||||
template optionalGasInt(dstField: untyped, n: Node, field: int) =
|
||||
if isSome(n, field):
|
||||
dstField = some(parseU64(n[field][1]).GasInt)
|
||||
|
||||
if n[5][1].kind != nkEmpty:
|
||||
cd.data = hexToSeqByte(n[5][1].stringVal)
|
||||
template optionalGasHex(dstField: untyped, n: Node, field: int) =
|
||||
if isSome(n, field):
|
||||
let gas = parse(fieldString(n, field), UInt256, radix = 16)
|
||||
dstField = some(gas.truncate(GasInt))
|
||||
|
||||
(cd, gasLimit)
|
||||
template optionalHexU256(dstField: untyped, n: Node, field: int) =
|
||||
if isSome(n, field):
|
||||
dstField = some(parse(fieldString(n, field), UInt256, radix = 16))
|
||||
|
||||
template optionalBytes(dstField: untyped, n: Node, field: int) =
|
||||
if isSome(n, field):
|
||||
dstField = hexToSeqByte(fieldString(n, field))
|
||||
|
||||
proc toCallData(n: Node): RpcCallData =
|
||||
optionalAddress(result.source, n, fFrom)
|
||||
optionalAddress(result.to, n, fTo)
|
||||
optionalGasInt(result.gasLimit, n, fGasLimit)
|
||||
optionalGasHex(result.gasPrice, n, fGasPrice)
|
||||
optionalGasHex(result.maxFee, n, fMaxFee)
|
||||
optionalGasHex(result.maxPriorityFee, n, fMaxPriorityFee)
|
||||
optionalHexU256(result.value, n, fValue)
|
||||
optionalBytes(result.data, n, fData)
|
||||
|
||||
proc makeCall(ctx: GraphqlContextRef, callData: RpcCallData,
|
||||
header: BlockHeader, chainDB: BaseChainDB): RespResult =
|
||||
let (outputHex, gasUsed, isError) = rpcMakeCall(callData, header, chainDB)
|
||||
let res = rpcCallEvm(callData, header, chainDB)
|
||||
var map = respMap(ctx.ids[ethCallResult])
|
||||
map["data"] = resp("0x" & outputHex)
|
||||
map["gasUsed"] = longNode(gasUsed).get()
|
||||
map["status"] = longNode(if isError: 0 else: 1).get()
|
||||
map["data"] = resp("0x" & res.output.toHex)
|
||||
map["gasUsed"] = longNode(res.gasUsed).get()
|
||||
map["status"] = longNode(if res.isError: 0 else: 1).get()
|
||||
ok(map)
|
||||
|
||||
proc blockCall(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
|
||||
|
@ -946,7 +966,7 @@ proc blockCall(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.
|
|||
let h = HeaderNode(parent)
|
||||
let param = params[0].val
|
||||
try:
|
||||
let (callData, gasLimit) = toCallData(param)
|
||||
let callData = toCallData(param)
|
||||
ctx.makeCall(callData, h.header, ctx.chainDB)
|
||||
except Exception as em:
|
||||
err("call error: " & em.msg)
|
||||
|
@ -956,8 +976,9 @@ proc blockEstimateGas(ud: RootRef, params: Args, parent: Node): RespResult {.api
|
|||
let h = HeaderNode(parent)
|
||||
let param = params[0].val
|
||||
try:
|
||||
let (callData, gasLimit) = toCallData(param)
|
||||
let gasUsed = rpcEstimateGas(callData, h.header, ctx.chainDB, gasLimit)
|
||||
let callData = toCallData(param)
|
||||
# TODO: DEFAULT_RPC_GAS_CAP should configurable
|
||||
let gasUsed = rpcEstimateGas(callData, h.header, ctx.chainDB, DEFAULT_RPC_GAS_CAP)
|
||||
longNode(gasUsed)
|
||||
except Exception as em:
|
||||
err("estimateGas error: " & em.msg)
|
||||
|
|
|
@ -266,8 +266,9 @@ proc setupEthRpc*(node: EthereumNode, ctx: EthContext, chain: BaseChainDB , serv
|
|||
## Returns the return value of executed contract.
|
||||
let
|
||||
header = headerFromTag(chain, quantityTag)
|
||||
callData = callData(call, true, chain)
|
||||
result = rpcDoCall(callData, header, chain)
|
||||
callData = callData(call)
|
||||
res = rpcCallEvm(callData, header, chain)
|
||||
result = hexDataStr(res.output)
|
||||
|
||||
server.rpc("eth_estimateGas") do(call: EthCall, quantityTag: string) -> HexQuantityStr:
|
||||
## Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.
|
||||
|
@ -279,8 +280,9 @@ proc setupEthRpc*(node: EthereumNode, ctx: EthContext, chain: BaseChainDB , serv
|
|||
## Returns the amount of gas used.
|
||||
let
|
||||
header = chain.headerFromTag(quantityTag)
|
||||
callData = callData(call, false, chain)
|
||||
gasUsed = rpcEstimateGas(callData, header, chain, call.gas.isSome)
|
||||
callData = callData(call)
|
||||
# TODO: DEFAULT_RPC_GAS_CAP should configurable
|
||||
gasUsed = rpcEstimateGas(callData, header, chain, DEFAULT_RPC_GAS_CAP)
|
||||
result = encodeQuantity(gasUsed.uint64)
|
||||
|
||||
server.rpc("eth_getBlockByHash") do(data: EthHashStr, fullTransactions: bool) -> Option[BlockObject]:
|
||||
|
|
|
@ -33,8 +33,10 @@ type
|
|||
# Parameter from user
|
||||
source*: Option[EthAddressStr] # (optional) The address the transaction is send from.
|
||||
to*: Option[EthAddressStr] # (optional in eth_estimateGas, not in eth_call) The address the transaction is directed to.
|
||||
gas*: Option[HexQuantityStr]# (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.
|
||||
gas*: Option[HexQuantityStr] # (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.
|
||||
gasPrice*: Option[HexQuantityStr]# (optional) Integer of the gasPrice used for each paid gas.
|
||||
maxFeePerGas*: Option[HexQuantityStr] # (optional) MaxFeePerGas is the maximum fee per gas offered, in wei.
|
||||
maxPriorityFeePerGas*: Option[HexQuantityStr] # (optional) MaxPriorityFeePerGas is the maximum miner tip per gas offered, in wei.
|
||||
value*: Option[HexQuantityStr] # (optional) Integer of the value sent with this transaction.
|
||||
data*: Option[EthHashStr] # (optional) Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI.
|
||||
|
||||
|
|
|
@ -87,32 +87,31 @@ proc unsignedTx*(tx: TxSend, chain: BaseChainDB, defaultNonce: AccountNonce): Tr
|
|||
|
||||
result.payload = hexToSeqByte(tx.data.string)
|
||||
|
||||
proc callData*(call: EthCall, callMode: bool = true, chain: BaseChainDB): RpcCallData =
|
||||
if call.source.isSome:
|
||||
result.source = toAddress(call.source.get)
|
||||
template optionalAddress(src, dst: untyped) =
|
||||
if src.isSome:
|
||||
dst = some(toAddress(src.get))
|
||||
|
||||
if call.to.isSome:
|
||||
result.to = toAddress(call.to.get)
|
||||
else:
|
||||
if callMode:
|
||||
raise newException(ValueError, "call.to required for eth_call operation")
|
||||
else:
|
||||
result.contractCreation = true
|
||||
template optionalGas(src, dst: untyped) =
|
||||
if src.isSome:
|
||||
dst = some(hexToInt(src.get.string, GasInt))
|
||||
|
||||
if call.gas.isSome:
|
||||
result.gas = hexToInt(call.gas.get.string, GasInt)
|
||||
template optionalU256(src, dst: untyped) =
|
||||
if src.isSome:
|
||||
dst = some(UInt256.fromHex(src.get.string))
|
||||
|
||||
if call.gasPrice.isSome:
|
||||
result.gasPrice = hexToInt(call.gasPrice.get.string, GasInt)
|
||||
else:
|
||||
if not callMode:
|
||||
result.gasPrice = calculateMedianGasPrice(chain)
|
||||
|
||||
if call.value.isSome:
|
||||
result.value = UInt256.fromHex(call.value.get.string)
|
||||
|
||||
if call.data.isSome:
|
||||
result.data = hexToSeqByte(call.data.get.string)
|
||||
template optionalBytes(src, dst: untyped) =
|
||||
if src.isSome:
|
||||
dst = hexToSeqByte(src.get.string)
|
||||
|
||||
proc callData*(call: EthCall): RpcCallData =
|
||||
optionalAddress(call.source, result.source)
|
||||
optionalAddress(call.to, result.to)
|
||||
optionalGas(call.gas, result.gasLimit)
|
||||
optionalGas(call.gasPrice, result.gasPrice)
|
||||
optionalGas(call.maxFeePerGas, result.maxFee)
|
||||
optionalGas(call.maxPriorityFeePerGas, result.maxPriorityFee)
|
||||
optionalU256(call.value, result.value)
|
||||
optionalBytes(call.data, result.data)
|
||||
|
||||
proc populateTransactionObject*(tx: Transaction, header: BlockHeader, txIndex: int): TransactionObject =
|
||||
result.blockHash = some(header.hash)
|
||||
|
|
|
@ -62,7 +62,7 @@ proc hostToComputationMessage*(msg: EvmcMessage): Message =
|
|||
flags: if msg.isStatic: emvcStatic else: emvcNoFlags
|
||||
)
|
||||
|
||||
func intrinsicGas(call: CallParams, fork: Fork): GasInt {.inline.} =
|
||||
func intrinsicGas*(call: CallParams, fork: Fork): GasInt {.inline.} =
|
||||
# Compute the baseline gas cost for this transaction. This is the amount
|
||||
# of gas needed to send this transaction (but that is not actually used
|
||||
# for computation).
|
||||
|
|
|
@ -7,109 +7,165 @@
|
|||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
eth/common/eth_types, stint, options, stew/byteutils,
|
||||
".."/[vm_types, vm_state, vm_gas_costs, forks],
|
||||
eth/common/eth_types, stint, options, stew/byteutils, chronicles,
|
||||
".."/[vm_types, vm_state, vm_gas_costs, forks, constants],
|
||||
".."/[db/db_chain, db/accounts_cache, transaction], eth/trie/db,
|
||||
".."/[chain_config, rpc/hexstrings],
|
||||
./call_common
|
||||
|
||||
type
|
||||
RpcCallData* = object
|
||||
source*: EthAddress
|
||||
to*: EthAddress
|
||||
gas*: GasInt
|
||||
gasPrice*: GasInt
|
||||
value*: UInt256
|
||||
data*: seq[byte]
|
||||
contractCreation*: bool
|
||||
source* : Option[EthAddress]
|
||||
to* : Option[EthAddress]
|
||||
gasLimit* : Option[GasInt]
|
||||
gasPrice* : Option[GasInt]
|
||||
maxFee* : Option[GasInt]
|
||||
maxPriorityFee*: Option[GasInt]
|
||||
value* : Option[UInt256]
|
||||
data* : seq[byte]
|
||||
accessList* : AccessList
|
||||
|
||||
proc rpcRunComputation(vmState: BaseVMState, rpc: RpcCallData,
|
||||
gasLimit: GasInt, forkOverride = none(Fork),
|
||||
forEstimateGas: bool = false): CallResult =
|
||||
return runComputation(CallParams(
|
||||
proc toCallParams(vmState: BaseVMState, cd: RpcCallData,
|
||||
globalGasCap: GasInt, baseFee: Option[Uint256],
|
||||
forkOverride = none(Fork)): CallParams =
|
||||
|
||||
# Reject invalid combinations of pre- and post-1559 fee styles
|
||||
if cd.gasPrice.isSome and (cd.maxFee.isSome or cd.maxPriorityFee.isSome):
|
||||
raise newException(ValueError, "both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
||||
|
||||
# Set default gas & gas price if none were set
|
||||
var gasLimit = globalGasCap
|
||||
if gasLimit == 0:
|
||||
gasLimit = GasInt(high(uint64) div 2)
|
||||
|
||||
if cd.gasLimit.isSome:
|
||||
gasLimit = cd.gasLimit.get()
|
||||
|
||||
if globalGasCap != 0 and globalGasCap < gasLimit:
|
||||
warn "Caller gas above allowance, capping", requested = gasLimit, cap = globalGasCap
|
||||
gasLimit = globalGasCap
|
||||
|
||||
var gasPrice = cd.gasPrice.get(0.GasInt)
|
||||
if baseFee.isSome:
|
||||
# A basefee is provided, necessitating EIP-1559-type execution
|
||||
let maxPriorityFee = cd.maxPriorityFee.get(0.GasInt)
|
||||
let maxFee = cd.maxFee.get(0.GasInt)
|
||||
|
||||
# Backfill the legacy gasPrice for EVM execution, unless we're all zeroes
|
||||
if maxPriorityFee > 0 or maxFee > 0:
|
||||
let baseFee = baseFee.get().truncate(GasInt)
|
||||
let priorityFee = min(maxPriorityFee, maxFee - baseFee)
|
||||
gasPrice = priorityFee + baseFee
|
||||
|
||||
CallParams(
|
||||
vmState: vmState,
|
||||
forkOverride: forkOverride,
|
||||
gasPrice: rpc.gasPrice,
|
||||
sender: cd.source.get(ZERO_ADDRESS),
|
||||
to: cd.to.get(ZERO_ADDRESS),
|
||||
isCreate: cd.to.isNone,
|
||||
gasLimit: gasLimit,
|
||||
sender: rpc.source,
|
||||
to: rpc.to,
|
||||
isCreate: rpc.contractCreation,
|
||||
value: rpc.value,
|
||||
input: rpc.data,
|
||||
# This matches historical behaviour. It might be that not all these steps
|
||||
# should be disabled for RPC/GraphQL `call`. But until we investigate what
|
||||
# RPC/GraphQL clients are expecting, keep the same behaviour.
|
||||
noIntrinsic: not forEstimateGas, # Don't charge intrinsic gas.
|
||||
noAccessList: not forEstimateGas, # Don't initialise EIP-2929 access list.
|
||||
noGasCharge: not forEstimateGas, # Don't charge sender account for gas.
|
||||
noRefund: not forEstimateGas # Don't apply gas refund/burn rule.
|
||||
))
|
||||
gasPrice: gasPrice,
|
||||
value: cd.value.get(0.u256),
|
||||
input: cd.data,
|
||||
accessList: cd.accessList
|
||||
)
|
||||
|
||||
proc rpcDoCall*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB): HexDataStr =
|
||||
# TODO: handle revert and error
|
||||
# TODO: handle contract ABI
|
||||
# we use current header stateRoot, unlike block validation
|
||||
# which use previous block stateRoot
|
||||
# TODO: ^ Check it's correct to use current header stateRoot, not parent
|
||||
let vmState = newBaseVMState(chain.stateDB, header, chain)
|
||||
let callResult = rpcRunComputation(vmState, call, call.gas)
|
||||
return hexDataStr(callResult.output)
|
||||
proc rpcCallEvm*(call: RpcCallData, header: BlockHeader, chainDB: BaseChainDB): CallResult =
|
||||
const globalGasCap = 0 # TODO: globalGasCap should configurable by user
|
||||
let stateDB = AccountsCache.init(chainDB.db, header.stateRoot)
|
||||
let vmState = newBaseVMState(stateDB, header, chainDB)
|
||||
let params = toCallParams(vmState, call, globalGasCap, header.fee)
|
||||
|
||||
proc rpcMakeCall*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB): (string, GasInt, bool) =
|
||||
# TODO: handle revert
|
||||
let vmState = newBaseVMState(chain.stateDB, header, chain)
|
||||
let callResult = rpcRunComputation(vmState, call, call.gas)
|
||||
return (callResult.output.toHex, callResult.gasUsed, callResult.isError)
|
||||
var dbTx = chainDB.db.beginTransaction()
|
||||
defer: dbTx.dispose() # always dispose state changes
|
||||
|
||||
func rpcIntrinsicGas(call: RpcCallData, fork: Fork): GasInt =
|
||||
var intrinsicGas = call.data.intrinsicGas(fork)
|
||||
if call.contractCreation:
|
||||
intrinsicGas = intrinsicGas + gasFees[fork][GasTXCreate]
|
||||
return intrinsicGas
|
||||
runComputation(params)
|
||||
|
||||
func rpcValidateCall(call: RpcCallData, vmState: BaseVMState, gasLimit: GasInt,
|
||||
fork: Fork): bool =
|
||||
# This behaviour matches `validateTransaction`, used by `processTransaction`.
|
||||
if vmState.cumulativeGasUsed + gasLimit > vmState.blockHeader.gasLimit:
|
||||
return false
|
||||
let balance = vmState.readOnlyStateDB.getBalance(call.source)
|
||||
let gasCost = gasLimit.u256 * call.gasPrice.u256
|
||||
if gasCost > balance or call.value > balance - gasCost:
|
||||
return false
|
||||
let intrinsicGas = rpcIntrinsicGas(call, fork)
|
||||
if intrinsicGas > gasLimit:
|
||||
return false
|
||||
return true
|
||||
proc rpcEstimateGas*(cd: RpcCallData, header: BlockHeader, chainDB: BaseChainDB, gasCap: GasInt): GasInt =
|
||||
# Binary search the gas requirement, as it may be higher than the amount used
|
||||
let stateDB = AccountsCache.init(chainDB.db, header.stateRoot)
|
||||
let vmState = newBaseVMState(stateDB, header, chainDB)
|
||||
let fork = chainDB.config.toFork(header.blockNumber)
|
||||
let txGas = gasFees[fork][GasTransaction] # txGas always 21000, use constants?
|
||||
var params = toCallParams(vmState, cd, gasCap, header.fee)
|
||||
|
||||
proc rpcEstimateGas*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB, haveGasLimit: bool): GasInt =
|
||||
# TODO: handle revert and error
|
||||
var
|
||||
# we use current header stateRoot, unlike block validation
|
||||
# which use previous block stateRoot
|
||||
vmState = newBaseVMState(chain.stateDB, header, chain)
|
||||
fork = toFork(chain.config, header.blockNumber)
|
||||
gasLimit = if haveGasLimit: call.gas else: header.gasLimit - vmState.cumulativeGasUsed
|
||||
lo : GasInt = txGas - 1
|
||||
hi : GasInt = cd.gasLimit.get(0.GasInt)
|
||||
cap: GasInt
|
||||
|
||||
# Nimbus `estimateGas` has historically checked against remaining gas in the
|
||||
# current block, balance in the sender account (even if the sender is default
|
||||
# account 0x00), and other limits, and returned 0 as the gas estimate if any
|
||||
# checks failed. This behaviour came from how it used `processTransaction`
|
||||
# which calls `validateTransaction`. For now, keep this behaviour the same.
|
||||
# Compare this code with `validateTransaction`.
|
||||
#
|
||||
# TODO: This historically differs from `rpcDoCall` and `rpcMakeCall`. There
|
||||
# are other differences in rpc_utils.nim `callData` too. Are the different
|
||||
# behaviours intended, and is 0 the correct return value to mean "not enough
|
||||
# gas to start"? Probably not.
|
||||
if not rpcValidateCall(call, vmState, gasLimit, fork):
|
||||
return 0
|
||||
var dbTx = chainDB.db.beginTransaction()
|
||||
defer: dbTx.dispose() # always dispose state changes
|
||||
|
||||
# Use a db transaction to save and restore the state of the database.
|
||||
var dbTx = chain.db.beginTransaction()
|
||||
defer: dbTx.dispose()
|
||||
# Determine the highest gas limit can be used during the estimation.
|
||||
if hi < txGas:
|
||||
# block's gasLimit act as the gas ceiling
|
||||
hi = header.gasLimit
|
||||
|
||||
let callResult = rpcRunComputation(vmState, call, gasLimit, some(fork), true)
|
||||
return callResult.gasUsed
|
||||
# Normalize the max fee per gas the call is willing to spend.
|
||||
var feeCap = cd.gasPrice.get(0.GasInt)
|
||||
if cd.gasPrice.isSome and (cd.maxFee.isSome or cd.maxPriorityFee.isSome):
|
||||
raise newException(ValueError, "both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
||||
elif cd.maxFee.isSome:
|
||||
feeCap = cd.maxFee.get
|
||||
|
||||
# Recap the highest gas limit with account's available balance.
|
||||
if feeCap > 0:
|
||||
if cd.source.isNone:
|
||||
raise newException(ValueError, "`from` can't be null")
|
||||
|
||||
let balance = vmState.readOnlyStateDB.getBalance(cd.source.get)
|
||||
var available = balance
|
||||
if cd.value.isSome:
|
||||
let value = cd.value.get
|
||||
if value > available:
|
||||
raise newException(ValueError, "insufficient funds for transfer")
|
||||
available -= value
|
||||
|
||||
let allowance = available div feeCap.u256
|
||||
# If the allowance is larger than maximum GasInt, skip checking
|
||||
if allowance < high(GasInt).u256 and hi > allowance.truncate(GasInt):
|
||||
let transfer = cd.value.get(0.u256)
|
||||
warn "Gas estimation capped by limited funds", original=hi, balance,
|
||||
sent=transfer, maxFeePerGas=feeCap, fundable=allowance
|
||||
hi = allowance.truncate(GasInt)
|
||||
|
||||
# Recap the highest gas allowance with specified gasCap.
|
||||
if gasCap != 0 and hi > gasCap:
|
||||
warn "Caller gas above allowance, capping", requested=hi, cap=gasCap
|
||||
hi = gasCap
|
||||
|
||||
cap = hi
|
||||
let intrinsicGas = intrinsicGas(params, fork)
|
||||
|
||||
# Create a helper to check if a gas allowance results in an executable transaction
|
||||
proc executable(gasLimit: GasInt): bool =
|
||||
if intrinsicGas > gasLimit:
|
||||
# Special case, raise gas limit
|
||||
return true
|
||||
|
||||
params.gasLimit = gasLimit
|
||||
# TODO: bail out on consensus error similar to validateTransaction
|
||||
runComputation(params).isError
|
||||
|
||||
# Execute the binary search and hone in on an executable gas limit
|
||||
while lo+1 < hi:
|
||||
let mid = (hi + lo) div 2
|
||||
let failed = executable(mid)
|
||||
if failed:
|
||||
lo = mid
|
||||
else:
|
||||
hi = mid
|
||||
|
||||
# Reject the transaction as invalid if it still fails at the highest allowance
|
||||
if hi == cap:
|
||||
let failed = executable(hi)
|
||||
if failed:
|
||||
# TODO: provide more descriptive EVM error beside out of gas
|
||||
# e.g. revert and other EVM errors
|
||||
raise newException(ValueError, "gas required exceeds allowance " & $cap)
|
||||
|
||||
hi
|
||||
|
||||
proc txCallEvm*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork): GasInt =
|
||||
var call = CallParams(
|
||||
|
|
|
@ -457,7 +457,7 @@
|
|||
"call":{
|
||||
"__typename":"CallResult",
|
||||
"data":"0x",
|
||||
"gasUsed":0,
|
||||
"gasUsed":21000,
|
||||
"status":1
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,6 +94,9 @@ proc setupEnv(chainDB: BaseChainDB, signer, ks2: EthAddress, ctx: EthContext): T
|
|||
timeStamp = date.toTime
|
||||
difficulty = calcDifficulty(chainDB.config, timeStamp, parent)
|
||||
|
||||
# call persist() before we get the rootHash
|
||||
vmState.stateDB.persist()
|
||||
|
||||
var header = BlockHeader(
|
||||
parentHash : parentHash,
|
||||
#coinbase*: EthAddress
|
||||
|
|
Loading…
Reference in New Issue