292 lines
10 KiB
Nim
292 lines
10 KiB
Nim
# 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)
|
|
if difficulty == default(evmc_bytes32): # or difficulty.isZero
|
|
result.block_difficulty = vmState.random.toEvmc
|
|
else:
|
|
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[])
|