upgrade evmc to v10.0.0

fixes #1172, fixes #950
This commit is contained in:
jangko 2022-09-26 19:23:54 +07:00
parent 94ec3349a4
commit 4b142ac52d
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
15 changed files with 273 additions and 515 deletions

View File

@ -18,3 +18,6 @@ type
FkIstanbul = "Istanbul"
FkBerlin = "Berlin"
FkLondon = "London"
FkParis = "Paris"
FkShanghai = "Shanghai"
FkCancun = "Cancun"

View File

@ -38,7 +38,10 @@ const
eth2, # FkPetersburg
eth2, # FkIstanbul
eth2, # FkBerlin
eth2 # FkLondon
eth2, # FkLondon
eth0, # FkParis
eth0, # FkShanghai
eth0, # FkCancun
]
{.push raises: [Defect].}

View File

@ -53,8 +53,8 @@ proc hostToComputationMessage*(msg: EvmcMessage): Message =
depth: msg.depth,
gas: msg.gas,
sender: msg.sender.fromEvmc,
contractAddress: msg.destination.fromEvmc,
codeAddress: msg.destination.fromEvmc,
contractAddress: msg.recipient.fromEvmc,
codeAddress: msg.code_address.fromEvmc,
value: msg.value.fromEvmc,
# When input size is zero, input data pointer may be null.
data: if msg.input_size <= 0: @[]
@ -122,18 +122,19 @@ proc setupHost(call: CallParams): TransactionHost =
let host = TransactionHost(
vmState: vmState,
msg: EvmcMessage(
kind: if call.isCreate: EVMC_CREATE else: EVMC_CALL,
kind: if call.isCreate: EVMC_CREATE else: EVMC_CALL,
# Default: flags: {},
# Default: depth: 0,
gas: call.gasLimit - intrinsicGas,
destination: call.to.toEvmc,
sender: call.sender.toEvmc,
value: call.value.toEvmc,
gas: call.gasLimit - intrinsicGas,
recipient: call.to.toEvmc,
code_address: call.to.toEvmc,
sender: call.sender.toEvmc,
value: call.value.toEvmc,
)
# All other defaults in `TransactionHost` are fine.
)
# Generate new contract address, prepare code, and update message `destination`
# Generate new contract address, prepare code, and update message `recipient`
# with the contract address. This differs from the previous Nimbus EVM API.
# Guarded under `evmc_enabled` for now so it doesn't break vm2.
when defined(evmc_enabled):
@ -142,14 +143,14 @@ proc setupHost(call: CallParams): TransactionHost =
let sender = call.sender
let contractAddress =
generateAddress(sender, call.vmState.readOnlyStateDB.getNonce(sender))
host.msg.destination = contractAddress.toEvmc
host.msg.recipient = contractAddress.toEvmc
host.msg.input_size = 0
host.msg.input_data = nil
code = call.input
else:
# TODO: Share the underlying data, but only after checking this does not
# cause problems with the database.
code = host.vmState.readOnlyStateDB.getCode(host.msg.destination.fromEvmc)
code = host.vmState.readOnlyStateDB.getCode(host.msg.code_address.fromEvmc)
if call.input.len > 0:
host.msg.input_size = call.input.len.csize_t
# Must copy the data so the `host.msg.input_data` pointer

View File

@ -132,8 +132,8 @@ proc evmcExecComputation*(host: TransactionHost): EvmcResult {.inline.} =
host.showCallReturn(result)
# This code assumes fields, methods and types of ABI version 9, and must be
# This code assumes fields, methods and types of ABI version 10, and must be
# checked for compatibility if the `import evmc/evmc` major version is updated.
when EVMC_ABI_VERSION != 9:
{.error: ("This code assumes EVMC_ABI_VERSION 9;" &
when EVMC_ABI_VERSION != 10:
{.error: ("This code assumes EVMC_ABI_VERSION 10;" &
" update the code to use EVMC_ABI_VERSION " & $EVMC_ABI_VERSION).}

View File

@ -64,6 +64,8 @@ proc evmcExecute(vm: ptr evmc_vm, hostInterface: ptr evmc_host_interface,
# Gas left is required to be zero when not `EVMC_SUCCESS` or `EVMC_REVERT`.
gas_left: if result.status_code notin {EVMC_SUCCESS, EVMC_REVERT}: 0'i64
else: c.gasMeter.gasRemaining.int64,
gas_refund: if result.status_code == EVMC_SUCCESS: c.gasMeter.gasRefunded.int64
else: 0'i64,
output_data: output_data,
output_size: output_size.csize_t,
release: if output_data.isNil: nil
@ -100,8 +102,8 @@ proc evmc_create_nimbus_evm(): ptr evmc_vm {.cdecl, exportc.} =
GC_ref(vm)
return cast[ptr evmc_vm](vm)
# This code assumes fields, methods and types of ABI version 9, and must be
# This code assumes fields, methods and types of ABI version 10, and must be
# checked for compatibility if the `import evmc/evmc` major version is updated.
when EVMC_ABI_VERSION != 9:
{.error: ("This code assumes EVMC_ABI_VERSION 9;" &
when EVMC_ABI_VERSION != 10:
{.error: ("This code assumes EVMC_ABI_VERSION 10;" &
" update the code to use EVMC_ABI_VERSION " & $EVMC_ABI_VERSION).}

View File

@ -56,9 +56,9 @@ proc beforeExecCallEvmcNested(host: TransactionHost,
depth: m.depth,
gas: m.gas,
sender: m.sender.fromEvmc,
codeAddress: m.destination.fromEvmc,
codeAddress: m.code_address.fromEvmc,
contractAddress: if m.kind == EVMC_CALL:
m.destination.fromEvmc
m.recipient.fromEvmc
else:
host.computation.msg.contractAddress,
value: m.value.fromEvmc,

View File

@ -11,7 +11,7 @@
import
sets, times, stint, chronicles,
eth/common/eth_types, ../db/accounts_cache, ../forks,
".."/[vm_state, vm_computation, vm_internals],
".."/[vm_state, vm_computation, vm_internals, vm_gas_costs],
./host_types, ./host_trace, ./host_call_nested
proc setupTxContext(host: TransactionHost) =
@ -45,7 +45,7 @@ proc setupTxContext(host: TransactionHost) =
# [EIP-1985](https://eips.ethereum.org/EIPS/eip-1985) although it's not
# officially accepted), and `vmState.gasLimit` is too (`GasInt`).
#
# `txContext.block_difficulty` is 256-bit, and this one can genuinely take
# `txContext.block_prev_randao` is 256-bit, and this one can genuinely take
# values over much of the 256-bit range.
let vmState = host.vmState
@ -73,7 +73,7 @@ proc setupTxContext(host: TransactionHost) =
# EIP-4399
# Transfer block randomness to difficulty OPCODE
let difficulty = vmState.difficulty.toEvmc
host.txContext.block_difficulty = flip256(difficulty)
host.txContext.block_prev_randao = flip256(difficulty)
host.cachedTxContext = true
@ -100,80 +100,66 @@ proc accountExists(host: TransactionHost, address: HostAddress): bool {.show.} =
proc getStorage(host: TransactionHost, address: HostAddress, key: HostKey): HostValue {.show.} =
host.vmState.readOnlyStateDB.getStorage(address, key)
const
# EIP-1283
SLOAD_GAS_CONSTANTINOPLE = 200
# EIP-2200
SSTORE_SET_GAS = 20000
SSTORE_RESET_GAS = 5000
SLOAD_GAS_ISTANBUL = 800
# EIP-2929
WARM_STORAGE_READ_COST = 100
COLD_SLOAD_COST = 2100
COLD_ACCOUNT_ACCESS_COST = 2600
proc setStorageStatus(host: TransactionHost, address: HostAddress,
key: HostKey, newVal: HostValue): EvmcStorageStatus {.show.} =
let
db = host.vmState.readOnlyStateDB
currentVal = db.getStorage(address, key)
SSTORE_CLEARS_SCHEDULE_EIP2200 = 15000
SSTORE_CLEARS_SCHEDULE_EIP3529 = 4800
func storageModifiedAgainRefund(originalValue, oldValue, value: HostValue,
fork: Fork): int {.inline.} =
# Calculate `SSTORE` refund according to EIP-2929 (Berlin),
# EIP-2200 (Istanbul) or EIP-1283 (Constantinople only).
result = 0
if value == originalValue:
result = if value.isZero: SSTORE_SET_GAS
elif fork >= FkBerlin: SSTORE_RESET_GAS - COLD_SLOAD_COST
else: SSTORE_RESET_GAS
let SLOAD_GAS = if fork >= FkBerlin: WARM_STORAGE_READ_COST
elif fork >= FkIstanbul: SLOAD_GAS_ISTANBUL
else: SLOAD_GAS_CONSTANTINOPLE
result -= SLOAD_GAS
let SSTORE_CLEARS_SCHEDULE = if fork >= FkLondon:
SSTORE_CLEARS_SCHEDULE_EIP3529
else:
SSTORE_CLEARS_SCHEDULE_EIP2200
if not originalValue.isZero:
if value.isZero:
result += SSTORE_CLEARS_SCHEDULE
elif oldValue.isZero:
result -= SSTORE_CLEARS_SCHEDULE
proc setStorage(host: TransactionHost, address: HostAddress,
key: HostKey, value: HostValue): EvmcStorageStatus {.show.} =
let db = host.vmState.readOnlyStateDB
let oldValue = db.getStorage(address, key)
if oldValue == value:
return EVMC_STORAGE_UNCHANGED
if currentVal == newVal:
return EVMC_STORAGE_ASSIGNED
host.vmState.mutateStateDB:
db.setStorage(address, key, value)
db.setStorage(address, key, newVal)
if host.vmState.fork >= FkIstanbul or host.vmState.fork == FkConstantinople:
let originalValue = db.getCommittedStorage(address, key)
if oldValue != originalValue:
# Gas refund for `MODIFIED_AGAIN` (EIP-1283/2200/2929 only).
let refund = storageModifiedAgainRefund(originalValue, oldValue, value,
host.vmState.fork)
# TODO: Refund depends on `Computation` at the moment.
if refund != 0:
host.computation.gasMeter.refundGas(refund)
return EVMC_STORAGE_MODIFIED_AGAIN
# https://eips.ethereum.org/EIPS/eip-1283
let originalVal = db.getCommittedStorage(address, key)
if originalVal == currentVal:
if originalVal.isZero:
return EVMC_STORAGE_ADDED
if oldValue.isZero:
return EVMC_STORAGE_ADDED
elif value.isZero:
# Gas refund for `DELETED` (all forks).
# TODO: Refund depends on `Computation` at the moment.
let SSTORE_CLEARS_SCHEDULE = if host.vmState.fork >= FkLondon:
SSTORE_CLEARS_SCHEDULE_EIP3529
else:
SSTORE_CLEARS_SCHEDULE_EIP2200
host.computation.gasMeter.refundGas(SSTORE_CLEARS_SCHEDULE)
return EVMC_STORAGE_DELETED
# !is_zero(original_val)
if newVal.isZero:
return EVMC_STORAGE_DELETED
else:
return EVMC_STORAGE_MODIFIED
# originalVal != currentVal
if originalVal.isZero.not:
if currentVal.isZero:
if originalVal == newVal:
return EVMC_STORAGE_DELETED_RESTORED
else:
return EVMC_STORAGE_DELETED_ADDED
# !is_zero(current_val)
if newVal.isZero:
return EVMC_STORAGE_MODIFIED_DELETED
# !is_zero(new_val)
if originalVal == newVal:
return EVMC_STORAGE_MODIFIED_RESTORED
else:
return EVMC_STORAGE_ASSIGNED
# is_zero(original_val)
if originalVal == newVal:
return EVMC_STORAGE_ADDED_DELETED
else:
return EVMC_STORAGE_MODIFIED
return EVMC_STORAGE_ASSIGNED
proc setStorage(host: TransactionHost, address: HostAddress,
key: HostKey, newVal: HostValue): EvmcStorageStatus {.show.} =
let
status = setStorageStatus(host, address, key, newVal)
fork = host.vmState.fork
refund = SstoreCost[fork][status].gasRefund
if refund != 0:
# TODO: Refund depends on `Computation` at the moment.
host.computation.gasMeter.refundGas(refund)
status
proc getBalance(host: TransactionHost, address: HostAddress): HostBalance {.show.} =
host.vmState.readOnlyStateDB.getBalance(address)

View File

@ -58,7 +58,8 @@ proc showEvmcMessage(msg: EvmcMessage): string =
&" gas={$msg.gas}" &
&" value={$msg.value.fromEvmc}" &
&" sender={$msg.sender.fromEvmc}" &
&" destination={$msg.destination.fromEvmc}" &
&" recipient={$msg.recipient.fromEvmc}" &
&" code_address={$msg.code_address.fromEvmc}" &
&" input_data={inputStr}"
if msg.kind == EVMC_CREATE2:
result.add &" create2_salt={$msg.create2_salt.fromEvmc}"
@ -88,7 +89,7 @@ proc showEvmcTxContext(txc: EvmcTxContext): string =
&" block_number={$txc.block_number}" &
&" block_timestamp={$txc.block_timestamp}" &
&" block_gas_limit={$txc.block_gas_limit}" &
&" block_difficulty={$txc.block_difficulty.fromEvmc}" &
&" block_prev_randao={$txc.block_prev_randao.fromEvmc}" &
&" chain_id={$txc.chain_id.fromEvmc}" &
&" block_base_fee={$txc.block_base_fee.fromEvmc}"

View File

@ -82,7 +82,7 @@ template getBlockNumber*(c: Computation): UInt256 =
template getDifficulty*(c: Computation): DifficultyInt =
when evmc_enabled:
UInt256.fromEvmc c.host.getTxContext().block_difficulty
UInt256.fromEvmc c.host.getTxContext().block_prev_randao
else:
c.vmState.difficulty
@ -192,12 +192,6 @@ proc newComputation*(vmState: BaseVMState, message: Message,
result.code = newCodeStream(
vmState.readOnlyStateDB.getCode(message.codeAddress))
when evmc_enabled:
result.host.init(
nim_host_get_interface(),
cast[evmc_host_context](result)
)
proc newComputation*(vmState: BaseVMState, message: Message, code: seq[byte]): Computation =
new result
result.vmState = vmState
@ -210,12 +204,6 @@ proc newComputation*(vmState: BaseVMState, message: Message, code: seq[byte]): C
result.selfDestructs = initHashSet[EthAddress]()
result.code = newCodeStream(code)
when evmc_enabled:
result.host.init(
nim_host_get_interface(),
cast[evmc_host_context](result)
)
template gasCosts*(c: Computation): untyped =
c.vmState.gasCosts

View File

@ -23,30 +23,32 @@ type
block_number* : int64 # The block number.
block_timestamp* : int64 # The block timestamp.
block_gas_limit* : int64 # The block gas limit.
block_difficulty*: evmc_uint256be # The block difficulty.
block_prev_randao*: evmc_uint256be # The block difficulty.
chain_id* : evmc_uint256be # The blockchain's ChainID.
block_base_fee* : evmc_uint256be # The block base fee.
nimbus_message* = object
kind*: evmc_call_kind
flags*: uint32
depth*: int32
gas*: int64
destination*: EthAddress
sender*: EthAddress
input_data*: ptr byte
input_size*: uint
value*: evmc_uint256be
kind* : evmc_call_kind
flags* : uint32
depth* : int32
gas* : int64
recipient* : EthAddress
sender* : EthAddress
input_data* : ptr byte
input_size* : uint
value* : evmc_uint256be
create2_salt*: evmc_bytes32
code_address*: EthAddress
nimbus_result* = object
status_code*: evmc_status_code
gas_left*: int64
output_data*: ptr byte
output_size*: uint
release*: proc(result: var nimbus_result) {.cdecl, gcsafe.}
status_code* : evmc_status_code
gas_left* : int64
gas_refund* : int64
output_data* : ptr byte
output_size* : uint
release* : proc(result: var nimbus_result) {.cdecl, gcsafe.}
create_address*: EthAddress
padding*: array[4, byte]
padding* : array[4, byte]
nimbus_host_interface* = object
account_exists*: proc(context: evmc_host_context, address: EthAddress): bool {.cdecl, gcsafe.}
@ -151,11 +153,3 @@ proc accessStorage*(ctx: HostContext, address: EthAddress,
key: UInt256): evmc_access_status {.inline.} =
var key = toEvmc(key)
ctx.host.access_storage(ctx.context, address, key)
#proc vmHost*(vmState: BaseVMState, gasPrice: GasInt, origin: EthAddress): HostContext =
# let host = nim_host_get_interface()
# let context = nim_host_create_context(cast[pointer](vmState), gasPrice, toEvmc(origin))
# result.init(host, context)
#
#proc destroy*(hc: HostContext) =
# nim_host_destroy_context(hc.context)

View File

@ -1,288 +0,0 @@
# Nimbus
# Copyright (c) 2019 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
proc hostReleaseResultImpl(res: var nimbus_result) {.cdecl, gcsafe.} =
dealloc(res.output_data)
proc hostGetTxContextImpl(ctx: Computation): nimbus_tx_context {.cdecl.} =
let vmstate = ctx.vmState
result.tx_gas_price = toEvmc(vmstate.txGasPrice.u256)
result.tx_origin = vmstate.txOrigin
result.block_coinbase = vmstate.coinbase
result.block_number = vmstate.blockNumber.truncate(int64)
result.block_timestamp = vmstate.timestamp.toUnix()
result.block_gas_limit = int64(vmstate.gasLimit)
# EIP-4399
# Transfer block randomness to difficulty OPCODE
let difficulty = toEvmc(vmstate.difficulty)
result.block_difficulty = difficulty
result.chain_id = toEvmc(vmstate.chaindb.config.chainId.uint.u256)
result.block_base_fee = toEvmc(vmstate.baseFee)
proc hostGetBlockHashImpl(ctx: Computation, number: int64): Hash256 {.cdecl.} =
ctx.vmState.getAncestorHash(number.u256)
proc hostAccountExistsImpl(ctx: Computation, address: EthAddress): bool {.cdecl.} =
let db = ctx.vmState.readOnlyStateDB
if ctx.fork >= FkSpurious:
not db.isDeadAccount(address)
else:
db.accountExists(address)
proc hostGetStorageImpl(ctx: Computation, address: EthAddress, key: var evmc_bytes32): evmc_bytes32 {.cdecl.} =
ctx.vmState.stateDB.getStorage(address, UInt256.fromEvmc(key)).toEvmc()
proc sstoreNetGasMetering(ctx: Computation): bool {.inline.} =
ctx.fork in {FkConstantinople, FkIstanbul, FkBerlin, FkLondon}
proc hostSetStorageImpl(ctx: Computation, address: EthAddress,
key, value: var evmc_bytes32): evmc_storage_status {.cdecl.} =
let
slot = UInt256.fromEvmc(key)
newValue = UInt256.fromEvmc(value)
statedb = ctx.vmState.readOnlyStateDB
currValue = statedb.getStorage(address, slot)
assert address == ctx.msg.contractAddress
var
status = EVMC_STORAGE_MODIFIED
gasRefund = 0.GasInt
origValue = 0.u256
if newValue == currValue:
status = EVMC_STORAGE_UNCHANGED
else:
origValue = statedb.getCommittedStorage(address, slot)
if origValue == currValue or not ctx.sstoreNetGasMetering():
if currValue == 0:
status = EVMC_STORAGE_ADDED
elif newValue == 0:
status = EVMC_STORAGE_DELETED
else:
status = EVMC_STORAGE_MODIFIED_AGAIN
ctx.vmState.mutateStateDB:
db.setStorage(address, slot, newValue)
let gasParam = GasParams(kind: Op.Sstore,
s_status: status,
s_currentValue: currValue,
s_originalValue: origValue
)
gasRefund = ctx.gasCosts[Sstore].c_handler(newValue, gasParam)[1]
if gasRefund != 0:
ctx.gasMeter.refundGas(gasRefund)
result = status
proc hostGetBalanceImpl(ctx: Computation, address: EthAddress): evmc_bytes32 {.cdecl.} =
ctx.vmState.readOnlyStateDB.getBalance(address).toEvmc()
proc hostGetCodeSizeImpl(ctx: Computation, address: EthAddress): uint {.cdecl.} =
ctx.vmState.readOnlyStateDB.getCode(address).len.uint
proc hostGetCodeHashImpl(ctx: Computation, address: EthAddress): Hash256 {.cdecl.} =
let db = ctx.vmstate.readOnlyStateDB
if not db.accountExists(address):
return
if db.isEmptyAccount(address):
return
db.getCodeHash(address)
proc hostCopyCodeImpl(ctx: Computation, address: EthAddress,
codeOffset: int, bufferData: ptr byte,
bufferSize: int): int {.cdecl.} =
var code = ctx.vmState.readOnlyStateDB.getCode(address)
# Handle "big offset" edge case.
if codeOffset > code.len:
return 0
let maxToCopy = code.len - codeOffset
let numToCopy = min(maxToCopy, bufferSize)
if numToCopy > 0:
copyMem(bufferData, code[codeOffset].addr, numToCopy)
result = numToCopy
proc hostSelfdestructImpl(ctx: Computation, address, beneficiary: EthAddress) {.cdecl.} =
assert address == ctx.msg.contractAddress
ctx.execSelfDestruct(beneficiary)
proc hostEmitLogImpl(ctx: Computation, address: EthAddress,
data: ptr byte, dataSize: int,
topics: UncheckedArray[evmc_bytes32], topicsCount: int) {.cdecl.} =
var log: Log
if topicsCount > 0:
log.topics = newSeq[Topic](topicsCount)
for i in 0 ..< topicsCount:
log.topics[i] = topics[i].bytes
log.data = @(makeOpenArray(data, dataSize))
log.address = address
ctx.addLogEntry(log)
proc hostAccessAccountImpl(ctx: Computation, address: EthAddress): evmc_access_status {.cdecl.} =
ctx.vmState.mutateStateDB:
if not db.inAccessList(address):
db.accessList(address)
return EVMC_ACCESS_COLD
else:
return EVMC_ACCESS_WARM
proc hostAccessStorageImpl(ctx: Computation, address: EthAddress,
key: var evmc_bytes32): evmc_access_status {.cdecl.} =
let slot = UInt256.fromEvmc(key)
ctx.vmState.mutateStateDB:
if not db.inAccessList(address, slot):
db.accessList(address, slot)
return EVMC_ACCESS_COLD
else:
return EVMC_ACCESS_WARM
proc enterCreateImpl(c: Computation, m: nimbus_message): Computation =
# TODO: use evmc_message to evoid copy
let childMsg = Message(
kind: CallKind(m.kind),
depth: m.depth,
gas: m.gas,
sender: m.sender,
value: UInt256.fromEvmc(m.value),
data: @(makeOpenArray(m.inputData, m.inputSize.int))
)
return newComputation(c.vmState, childMsg,
ContractSalt.fromEvmc(m.create2_salt))
template leaveCreateImpl(c, child: Computation, res: nimbus_result) =
if not child.shouldBurnGas:
res.gas_left = child.gasMeter.gasRemaining
if child.isSuccess:
c.merge(child)
res.status_code = EVMC_SUCCESS
res.create_address = child.msg.contractAddress
else:
res.status_code = if child.shouldBurnGas: EVMC_FAILURE else: EVMC_REVERT
if child.output.len > 0:
# TODO: can we move the ownership of seq to raw pointer?
res.output_size = child.output.len.uint
res.output_data = cast[ptr byte](alloc(child.output.len))
copyMem(res.output_data, child.output[0].addr, child.output.len)
res.release = hostReleaseResultImpl
template enterCallImpl(c: Computation, m: nimbus_message): Computation =
let childMsg = Message(
kind: CallKind(m.kind),
depth: m.depth,
gas: m.gas,
sender: m.sender,
codeAddress: m.destination,
contractAddress: if m.kind == EVMC_CALL: m.destination else: c.msg.contractAddress,
value: UInt256.fromEvmc(m.value),
data: @(makeOpenArray(m.inputData, m.inputSize.int)),
flags: MsgFlags(m.flags)
)
newComputation(c.vmState, childMsg)
template leaveCallImpl(c, child: Computation, res: nimbus_result) =
if not child.shouldBurnGas:
res.gas_left = child.gasMeter.gasRemaining
if child.isSuccess:
c.merge(child)
res.status_code = EVMC_SUCCESS
else:
res.status_code = if child.shouldBurnGas: EVMC_FAILURE else: EVMC_REVERT
if child.output.len > 0:
# TODO: can we move the ownership of seq to raw pointer?
res.output_size = child.output.len.uint
res.output_data = cast[ptr byte](alloc(child.output.len))
copyMem(res.output_data, child.output[0].addr, child.output.len)
res.release = hostReleaseResultImpl
proc enterHostCall(c: Computation, msg: var nimbus_message): Computation {.noinline.} =
if msg.kind == EVMC_CREATE or msg.kind == EVMC_CREATE2:
enterCreateImpl(c, msg)
else:
enterCallImpl(c, msg)
proc leaveHostCall(c, child: Computation, kind: evmc_call_kind): nimbus_result {.noinline.} =
if kind == EVMC_CREATE or kind == EVMC_CREATE2:
leaveCreateImpl(c, child, result)
else:
leaveCallImpl(c, child, result)
proc hostCallImpl(ctx: Computation, msg: var nimbus_message): nimbus_result {.cdecl.} =
let child = enterHostCall(ctx, msg)
child.execCallOrCreate()
leaveHostCall(ctx, child, msg.kind)
proc initHostInterface(): evmc_host_interface =
result.account_exists = cast[evmc_account_exists_fn](hostAccountExistsImpl)
result.get_storage = cast[evmc_get_storage_fn](hostGetStorageImpl)
result.set_storage = cast[evmc_set_storage_fn](hostSetStorageImpl)
result.get_balance = cast[evmc_get_balance_fn](hostGetBalanceImpl)
result.get_code_size = cast[evmc_get_code_size_fn](hostGetCodeSizeImpl)
result.get_code_hash = cast[evmc_get_code_hash_fn](hostGetCodeHashImpl)
result.copy_code = cast[evmc_copy_code_fn](hostCopyCodeImpl)
result.selfdestruct = cast[evmc_selfdestruct_fn](hostSelfdestructImpl)
result.call = cast[evmc_call_fn](hostCallImpl)
result.get_tx_context = cast[evmc_get_tx_context_fn](hostGetTxContextImpl)
result.get_block_hash = cast[evmc_get_block_hash_fn](hostGetBlockHashImpl)
result.emit_log = cast[evmc_emit_log_fn](hostEmitLogImpl)
result.access_account = cast[evmc_access_account_fn](hostAccessAccountImpl)
result.access_storage = cast[evmc_access_storage_fn](hostAccessStorageImpl)
proc vmSetOptionImpl(vm: ptr evmc_vm, name, value: cstring): evmc_set_option_result {.cdecl.} =
return EVMC_SET_OPTION_INVALID_NAME
proc vmExecuteImpl(vm: ptr evmc_vm, host: ptr evmc_host_interface,
ctx: Computation, rev: evmc_revision,
msg: evmc_message, code: ptr byte, code_size: uint): evmc_result {.cdecl.} =
discard
proc vmGetCapabilitiesImpl(vm: ptr evmc_vm): evmc_capabilities {.cdecl.} =
result.incl(EVMC_CAPABILITY_EVM1)
proc vmDestroyImpl(vm: ptr evmc_vm) {.cdecl.} =
dealloc(vm)
const
EVMC_HOST_NAME = "nimbus_vm"
EVMC_VM_VERSION = "0.0.1"
proc init(vm: var evmc_vm) =
vm.abi_version = EVMC_ABI_VERSION
vm.name = EVMC_HOST_NAME
vm.version = EVMC_VM_VERSION
vm.destroy = vmDestroyImpl
vm.execute = cast[evmc_execute_fn](vmExecuteImpl)
vm.get_capabilities = vmGetCapabilitiesImpl
vm.set_option = vmSetOptionImpl
let gHost = initHostInterface()
proc nim_host_get_interface(): ptr nimbus_host_interface {.exportc, cdecl.} =
result = cast[ptr nimbus_host_interface](gHost.unsafeAddr)
proc nim_host_create_context(vmstate: BaseVmState, msg: ptr evmc_message): Computation {.exportc, cdecl.} =
#result = HostContext(
# vmState: vmstate,
# gasPrice: GasInt(gasPrice),
# origin: fromEvmc(origin)
#)
GC_ref(result)
proc nim_host_destroy_context(ctx: Computation) {.exportc, cdecl.} =
GC_unref(ctx)
proc nim_create_nimbus_vm(): ptr evmc_vm {.exportc, cdecl.} =
result = create(evmc_vm)
init(result[])

View File

@ -125,6 +125,87 @@ const
ACCESS_LIST_STORAGE_KEY_COST* = 1900.GasInt
ACCESS_LIST_ADDRESS_COST* = 2400.GasInt
when defined(evmc_enabled):
type
# The gas cost specification for storage instructions.
StorageCostSpec = object
netCost : bool # Is this net gas cost metering schedule?
warmAccess: int16 # Storage warm access cost, YP: G_{warmaccess}
sset : int16 # Storage addition cost, YP: G_{sset}
reset : int16 # Storage modification cost, YP: G_{sreset}
clear : int16 # Storage deletion refund, YP: R_{sclear}
StorageStoreCost* = object
gasCost* : int16
gasRefund*: int16
# Table of gas cost specification for storage instructions per EVM revision.
func storageCostSpec(): array[Fork, StorageCostSpec] {.compileTime.} =
# Legacy cost schedule.
const revs = [
FkFrontier, FkHomestead, FkTangerine,
FkSpurious, FkByzantium, FkPetersburg]
for rev in revs:
result[rev] = StorageCostSpec(
netCost: false, warmAccess: 200, sset: 20000, reset: 5000, clear: 15000)
# Net cost schedule.
result[FkConstantinople] = StorageCostSpec(
netCost: true, warmAccess: 200, sset: 20000, reset: 5000, clear: 15000)
result[FkIstanbul] = StorageCostSpec(
netCost: true, warmAccess: 800, sset: 20000, reset: 5000, clear: 15000)
result[FkBerlin] = StorageCostSpec(
netCost: true, warmAccess: WarmStorageReadCost, sset: 20000,
reset: 5000 - ColdSloadCost, clear: 15000)
result[FkLondon] = StorageCostSpec(
netCost: true, warmAccess: WarmStorageReadCost, sset: 20000,
reset: 5000 - ColdSloadCost, clear: 4800)
result[FkParis] = result[FkLondon]
result[FkShanghai] = result[FkLondon]
result[FkCancun] = result[FkLondon]
proc legacySStoreCost(e: var array[evmc_storage_status, StorageStoreCost],
c: StorageCostSpec) {.compileTime.} =
e[EVMC_STORAGE_ADDED] = StorageStoreCost(gasCost: c.sset , gasRefund: 0)
e[EVMC_STORAGE_DELETED] = StorageStoreCost(gasCost: c.reset, gasRefund: c.clear)
e[EVMC_STORAGE_MODIFIED] = StorageStoreCost(gasCost: c.reset, gasRefund: 0)
e[EVMC_STORAGE_ASSIGNED] = e[EVMC_STORAGE_MODIFIED]
e[EVMC_STORAGE_DELETED_ADDED] = e[EVMC_STORAGE_ADDED]
e[EVMC_STORAGE_MODIFIED_DELETED] = e[EVMC_STORAGE_DELETED]
e[EVMC_STORAGE_DELETED_RESTORED] = e[EVMC_STORAGE_ADDED]
e[EVMC_STORAGE_ADDED_DELETED] = e[EVMC_STORAGE_DELETED]
e[EVMC_STORAGE_MODIFIED_RESTORED] = e[EVMC_STORAGE_MODIFIED]
proc netSStoreCost(e: var array[evmc_storage_status, StorageStoreCost],
c: StorageCostSpec) {.compileTime.} =
e[EVMC_STORAGE_ASSIGNED] = StorageStoreCost(gasCost: c.warmAccess, gasRefund: 0)
e[EVMC_STORAGE_ADDED] = StorageStoreCost(gasCost: c.sset , gasRefund: 0)
e[EVMC_STORAGE_DELETED] = StorageStoreCost(gasCost: c.reset , gasRefund: c.clear)
e[EVMC_STORAGE_MODIFIED] = StorageStoreCost(gasCost: c.reset , gasRefund: 0)
e[EVMC_STORAGE_DELETED_ADDED] = StorageStoreCost(gasCost: c.warmAccess, gasRefund: -c.clear)
e[EVMC_STORAGE_MODIFIED_DELETED] = StorageStoreCost(gasCost: c.warmAccess, gasRefund: c.clear)
e[EVMC_STORAGE_DELETED_RESTORED] = StorageStoreCost(gasCost: c.warmAccess,
gasRefund: c.reset - c.warmAccess - c.clear)
e[EVMC_STORAGE_ADDED_DELETED] = StorageStoreCost(gasCost: c.warmAccess,
gasRefund: c.sset - c.warmAccess)
e[EVMC_STORAGE_MODIFIED_RESTORED] = StorageStoreCost(gasCost: c.warmAccess,
gasRefund: c.reset - c.warm_access)
proc storageStoreCost(): array[Fork, array[evmc_storage_status, StorageStoreCost]] {.compileTime.} =
const tbl = storageCostSpec()
for rev in Fork:
let c = tbl[rev]
if not c.netCost: # legacy
legacySStoreCost(result[rev], c)
else: # net cost
netSStoreCost(result[rev], c)
const
SstoreCost* = storageStoreCost()
template gasCosts(fork: Fork, prefix, ResultGasCostsName: untyped) =
## Generate the gas cost for each forks and store them in a const
@ -224,52 +305,31 @@ template gasCosts(fork: Fork, prefix, ResultGasCostsName: untyped) =
func `prefix gasSstore`(value: UInt256, gasParams: GasParams): GasResult {.nimcall.} =
## Value is word to save
when fork >= FkBerlin:
# EIP2929
const
SLOAD_GAS = WarmStorageReadCost
SSTORE_RESET_GAS = 5000 - ColdSloadCost
else:
const
SLOAD_GAS = FeeSchedule[GasSload]
SSTORE_RESET_GAS = FeeSchedule[GasSreset]
const
NoopGas = SLOAD_GAS # if the value doesn't change.
DirtyGas = SLOAD_GAS # if a dirty value is changed.
InitGas = FeeSchedule[GasSset] # from clean zero to non-zero
InitRefund = FeeSchedule[GasSset] - SLOAD_GAS # resetting to the original zero value
CleanGas = SSTORE_RESET_GAS # from clean non-zero to something else
CleanRefund = SSTORE_RESET_GAS - SLOAD_GAS # resetting to the original non-zero value
ClearRefund = FeeSchedule[RefundsClear]# clearing an originally existing storage slot
when defined(evmc_enabled):
const
sstoreDirty = when fork < FkConstantinople or fork == FkPetersburg: CleanGas
else: DirtyGas
case gasParams.s_status
of EVMC_STORAGE_ADDED: result.gasCost = InitGas
of EVMC_STORAGE_MODIFIED: result.gasCost = CleanGas
of EVMC_STORAGE_DELETED:
result.gasCost = CleanGas
result.gasRefund += ClearRefund
of EVMC_STORAGE_UNCHANGED: result.gasCost = sstoreDirty
of EVMC_STORAGE_MODIFIED_AGAIN:
result.gasCost = sstoreDirty
if not gasParams.s_originalValue.isZero:
if gasParams.s_currentValue.isZero:
result.gasRefund -= ClearRefund
if value.isZero:
result.gasRefund += ClearRefund
if gasParams.s_originalValue == value:
if gasParams.s_originalValue.isZero:
result.gasRefund += InitRefund
else:
result.gasRefund += CleanRefund
const c = SStoreCost[fork]
let sc = c[gasParams.s_status]
result.gasCost = sc.gasCost
result.gasRefund = sc.gasRefund
else:
when fork >= FkBerlin:
# EIP2929
const
SLOAD_GAS = WarmStorageReadCost
SSTORE_RESET_GAS = 5000 - ColdSloadCost
else:
const
SLOAD_GAS = FeeSchedule[GasSload]
SSTORE_RESET_GAS = FeeSchedule[GasSreset]
const
NoopGas = SLOAD_GAS # if the value doesn't change.
DirtyGas = SLOAD_GAS # if a dirty value is changed.
InitGas = FeeSchedule[GasSset] # from clean zero to non-zero
InitRefund = FeeSchedule[GasSset] - SLOAD_GAS # resetting to the original zero value
CleanGas = SSTORE_RESET_GAS # from clean non-zero to something else
CleanRefund = SSTORE_RESET_GAS - SLOAD_GAS # resetting to the original non-zero value
ClearRefund = FeeSchedule[RefundsClear]# clearing an originally existing storage slot
when fork < FkConstantinople or fork == FkPetersburg:
let isStorageEmpty = gasParams.s_currentValue.isZero
@ -760,7 +820,10 @@ const
FkPetersburg: SpuriousGasFees,
FkIstanbul: IstanbulGasFees,
FkBerlin: BerlinGasFees,
FkLondon: LondonGasFees
FkLondon: LondonGasFees,
FkParis: LondonGasFees,
FkShanghai: LondonGasFees,
FkCancun: LondonGasFees,
]
gasCosts(FkFrontier, base, BaseGasCosts)

View File

@ -40,7 +40,7 @@ type
LocalParams = tuple
gas: UInt256
value: UInt256
destination: EthAddress
codeAddress: EthAddress
sender: EthAddress
memInPos: int
memInLen: int
@ -72,14 +72,14 @@ proc updateStackAndParams(q: var LocalParams; c: Computation) =
# and further `childGasLimit`
if FkBerlin <= c.fork:
when evmc_enabled:
if c.host.accessAccount(q.destination) == EVMC_ACCESS_COLD:
if c.host.accessAccount(q.codeAddress) == EVMC_ACCESS_COLD:
c.gasMeter.consumeGas(
ColdAccountAccessCost - WarmStorageReadCost,
reason = "EIP2929 gasCall")
else:
c.vmState.mutateStateDB:
if not db.inAccessList(q.destination):
db.accessList(q.destination)
if not db.inAccessList(q.codeAddress):
db.accessList(q.codeAddress)
# The WarmStorageReadCostEIP2929 (100) is already deducted in
# the form of a constant `gasCall`
@ -91,7 +91,7 @@ proc updateStackAndParams(q: var LocalParams; c: Computation) =
proc callParams(c: Computation): LocalParams =
## Helper for callOp()
result.gas = c.stack.popInt()
result.destination = c.stack.popAddress()
result.codeAddress = c.stack.popAddress()
result.value = c.stack.popInt()
result.memInPos = c.stack.popInt().cleanMemRef
result.memInLen = c.stack.popInt().cleanMemRef
@ -100,7 +100,7 @@ proc callParams(c: Computation): LocalParams =
result.sender = c.msg.contractAddress
result.flags = c.msg.flags
result.contractAddress = result.destination
result.contractAddress = result.codeAddress
result.updateStackAndParams(c)
@ -114,7 +114,7 @@ proc callCodeParams(c: Computation): LocalParams =
proc delegateCallParams(c: Computation): LocalParams =
## Helper for delegateCall()
result.gas = c.stack.popInt()
result.destination = c.stack.popAddress()
result.codeAddress = c.stack.popAddress()
result.memInPos = c.stack.popInt().cleanMemRef
result.memInLen = c.stack.popInt().cleanMemRef
result.memOutPos = c.stack.popInt().cleanMemRef
@ -131,7 +131,7 @@ proc delegateCallParams(c: Computation): LocalParams =
proc staticCallParams(c: Computation): LocalParams =
## Helper for staticCall()
result.gas = c.stack.popInt()
result.destination = c.stack.popAddress()
result.codeAddress = c.stack.popAddress()
result.memInPos = c.stack.popInt().cleanMemRef
result.memInLen = c.stack.popInt().cleanMemRef
result.memOutPos = c.stack.popInt().cleanMemRef
@ -140,7 +140,7 @@ proc staticCallParams(c: Computation): LocalParams =
result.value = 0.u256
result.sender = c.msg.contractAddress
result.flags = emvcStatic
result.contractAddress = result.destination
result.contractAddress = result.codeAddress
result.updateStackAndParams(c)
@ -246,15 +246,16 @@ const
msg = new(nimbus_message)
c = k.cpt
msg[] = nimbus_message(
kind : evmcCall.evmc_call_kind,
depth : (k.cpt.msg.depth + 1).int32,
gas : childGasLimit,
sender : p.sender,
destination: p.destination,
input_data : k.cpt.memory.readPtr(p.memInPos),
input_size : p.memInLen.uint,
value : toEvmc(p.value),
flags : p.flags.uint32
kind : evmcCall.evmc_call_kind,
depth : (k.cpt.msg.depth + 1).int32,
gas : childGasLimit,
sender : p.sender,
recipient : p.contractAddress,
code_address: p.codeAddress,
input_data : k.cpt.memory.readPtr(p.memInPos),
input_size : p.memInLen.uint,
value : toEvmc(p.value),
flags : p.flags.uint32
)
c.execSubCall(msg, p)
else:
@ -267,7 +268,7 @@ const
gas: childGasLimit,
sender: p.sender,
contractAddress: p.contractAddress,
codeAddress: p.destination,
codeAddress: p.codeAddress,
value: p.value,
data: k.cpt.memory.read(p.memInPos, p.memInLen),
flags: p.flags))
@ -329,15 +330,16 @@ const
msg = new(nimbus_message)
c = k.cpt
msg[] = nimbus_message(
kind : evmcCallCode.evmc_call_kind,
depth : (k.cpt.msg.depth + 1).int32,
gas : childGasLimit,
sender : p.sender,
destination: p.destination,
input_data : k.cpt.memory.readPtr(p.memInPos),
input_size : p.memInLen.uint,
value : toEvmc(p.value),
flags : p.flags.uint32
kind : evmcCallCode.evmc_call_kind,
depth : (k.cpt.msg.depth + 1).int32,
gas : childGasLimit,
sender : p.sender,
recipient : p.contractAddress,
code_address: p.codeAddress,
input_data : k.cpt.memory.readPtr(p.memInPos),
input_size : p.memInLen.uint,
value : toEvmc(p.value),
flags : p.flags.uint32
)
c.execSubCall(msg, p)
else:
@ -350,7 +352,7 @@ const
gas: childGasLimit,
sender: p.sender,
contractAddress: p.contractAddress,
codeAddress: p.destination,
codeAddress: p.codeAddress,
value: p.value,
data: k.cpt.memory.read(p.memInPos, p.memInLen),
flags: p.flags))
@ -401,15 +403,16 @@ const
msg = new(nimbus_message)
c = k.cpt
msg[] = nimbus_message(
kind : evmcDelegateCall.evmc_call_kind,
depth : (k.cpt.msg.depth + 1).int32,
gas : childGasLimit,
sender : p.sender,
destination: p.destination,
input_data : k.cpt.memory.readPtr(p.memInPos),
input_size : p.memInLen.uint,
value : toEvmc(p.value),
flags : p.flags.uint32
kind : evmcDelegateCall.evmc_call_kind,
depth : (k.cpt.msg.depth + 1).int32,
gas : childGasLimit,
sender : p.sender,
recipient : p.contractAddress,
code_address: p.codeAddress,
input_data : k.cpt.memory.readPtr(p.memInPos),
input_size : p.memInLen.uint,
value : toEvmc(p.value),
flags : p.flags.uint32
)
c.execSubCall(msg, p)
else:
@ -422,7 +425,7 @@ const
gas: childGasLimit,
sender: p.sender,
contractAddress: p.contractAddress,
codeAddress: p.destination,
codeAddress: p.codeAddress,
value: p.value,
data: k.cpt.memory.read(p.memInPos, p.memInLen),
flags: p.flags))
@ -451,7 +454,7 @@ const
# from 700 to 40
#
# when opCode == StaticCall:
# if k.cpt.fork >= FkBerlin and destination.toInt <= MaxPrecompilesAddr:
# if k.cpt.fork >= FkBerlin and codeAddress.toInt <= MaxPrecompilesAddr:
# gasCost = gasCost - 660.GasInt
if gasCost >= 0:
k.cpt.gasMeter.consumeGas(gasCost, reason = $StaticCall)
@ -478,15 +481,16 @@ const
msg = new(nimbus_message)
c = k.cpt
msg[] = nimbus_message(
kind : evmcCall.evmc_call_kind,
depth : (k.cpt.msg.depth + 1).int32,
gas : childGasLimit,
sender : p.sender,
destination: p.destination,
input_data : k.cpt.memory.readPtr(p.memInPos),
input_size : p.memInLen.uint,
value : toEvmc(p.value),
flags : p.flags.uint32
kind : evmcCall.evmc_call_kind,
depth : (k.cpt.msg.depth + 1).int32,
gas : childGasLimit,
sender : p.sender,
recipient : p.contractAddress,
code_address: p.codeAddress,
input_data : k.cpt.memory.readPtr(p.memInPos),
input_size : p.memInLen.uint,
value : toEvmc(p.value),
flags : p.flags.uint32
)
c.execSubCall(msg, p)
else:
@ -499,7 +503,7 @@ const
gas: childGasLimit,
sender: p.sender,
contractAddress: p.contractAddress,
codeAddress: p.destination,
codeAddress: p.codeAddress,
value: p.value,
data: k.cpt.memory.read(p.memInPos, p.memInLen),
flags: p.flags))

View File

@ -256,6 +256,3 @@ else:
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------
when defined(evmc_enabled):
include evmc_host

View File

@ -48,4 +48,8 @@ export
vmg.ACCESS_LIST_STORAGE_KEY_COST,
vmg.ACCESS_LIST_ADDRESS_COST
when defined(evmc_enabled):
export
vmg.SstoreCost
# End