implement evmc create/create2
This commit is contained in:
parent
270854a5aa
commit
fff35ab01d
|
@ -126,14 +126,14 @@ template getCode*(c: Computation, address: EthAddress): ByteRange =
|
|||
else:
|
||||
c.vmState.readOnlyStateDB.getCode(address)
|
||||
|
||||
proc generateContractAddress(c: Computation, salt: Option[Uint256]): EthAddress =
|
||||
proc generateContractAddress(c: Computation, salt: Uint256): EthAddress =
|
||||
if c.msg.kind == evmcCreate:
|
||||
let creationNonce = c.vmState.readOnlyStateDb().getNonce(c.msg.sender)
|
||||
result = generateAddress(c.msg.sender, creationNonce)
|
||||
else:
|
||||
result = generateSafeAddress(c.msg.sender, salt.get(), c.msg.data)
|
||||
result = generateSafeAddress(c.msg.sender, salt, c.msg.data)
|
||||
|
||||
proc newComputation*(vmState: BaseVMState, message: Message, salt=none(Uint256)): Computation =
|
||||
proc newComputation*(vmState: BaseVMState, message: Message, salt= 0.u256): Computation =
|
||||
new result
|
||||
result.vmState = vmState
|
||||
result.msg = message
|
||||
|
@ -306,6 +306,46 @@ proc addChildComputation*(c, child: Computation) =
|
|||
if not child.shouldBurnGas:
|
||||
c.gasMeter.returnGas(child.gasMeter.gasRemaining)
|
||||
|
||||
proc execCreate*(c: Computation) =
|
||||
c.vmState.mutateStateDB:
|
||||
db.incNonce(c.msg.sender)
|
||||
|
||||
c.snapshot()
|
||||
defer:
|
||||
c.dispose()
|
||||
|
||||
if c.vmState.readOnlyStateDb().hasCodeOrNonce(c.msg.contractAddress):
|
||||
c.setError("Address collision when creating contract address={c.msg.contractAddress.toHex}", true)
|
||||
c.rollback()
|
||||
return
|
||||
|
||||
c.vmState.mutateStateDb:
|
||||
db.subBalance(c.msg.sender, c.msg.value)
|
||||
db.addBalance(c.msg.contractAddress, c.msg.value)
|
||||
db.clearStorage(c.msg.contractAddress)
|
||||
if c.fork >= FkSpurious:
|
||||
# EIP161 nonce incrementation
|
||||
db.incNonce(c.msg.contractAddress)
|
||||
|
||||
executeOpcodes(c)
|
||||
|
||||
if c.isSuccess:
|
||||
let fork = c.fork
|
||||
let contractFailed = not c.writeContract(fork)
|
||||
if contractFailed and fork >= FkHomestead:
|
||||
c.setError(&"writeContract failed, depth={c.msg.depth}", true)
|
||||
|
||||
if c.isSuccess:
|
||||
c.commit()
|
||||
else:
|
||||
c.rollback()
|
||||
|
||||
proc merge*(c, child: Computation) =
|
||||
c.logEntries.add child.logEntries
|
||||
c.gasMeter.refundGas(child.gasMeter.gasRefunded)
|
||||
c.suicides.incl child.suicides
|
||||
c.touchedAccounts.incl child.touchedAccounts
|
||||
|
||||
proc execSelfDestruct*(c: Computation, beneficiary: EthAddress) =
|
||||
c.vmState.mutateStateDB:
|
||||
let
|
||||
|
|
|
@ -26,6 +26,27 @@ type
|
|||
block_difficulty*: evmc_uint256be # The block difficulty.
|
||||
chain_id* : evmc_uint256be # The blockchain's ChainID.
|
||||
|
||||
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
|
||||
create2_salt*: evmc_bytes32
|
||||
|
||||
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.}
|
||||
create_address*: EthAddress
|
||||
padding*: array[4, byte]
|
||||
|
||||
nimbus_host_interface* = object
|
||||
account_exists*: proc(context: evmc_host_context, address: EthAddress): bool {.cdecl, gcsafe.}
|
||||
get_storage*: proc(context: evmc_host_context, address: EthAddress, key: ptr evmc_uint256be): evmc_uint256be {.cdecl, gcsafe.}
|
||||
|
@ -38,7 +59,7 @@ type
|
|||
code_offset: int, buffer_data: ptr byte,
|
||||
buffer_size: int): int {.cdecl, gcsafe.}
|
||||
selfdestruct*: proc(context: evmc_host_context, address, beneficiary: EthAddress) {.cdecl, gcsafe.}
|
||||
call*: proc(context: evmc_host_context, msg: ptr evmc_message): evmc_result {.cdecl, gcsafe.}
|
||||
call*: proc(context: evmc_host_context, msg: ptr nimbus_message): nimbus_result {.cdecl, gcsafe.}
|
||||
get_tx_context*: proc(context: evmc_host_context): nimbus_tx_context {.cdecl, gcsafe.}
|
||||
get_block_hash*: proc(context: evmc_host_context, number: int64): Hash256 {.cdecl, gcsafe.}
|
||||
emit_log*: proc(context: evmc_host_context, address: EthAddress,
|
||||
|
@ -114,7 +135,7 @@ proc emitLog*(ctx: HostContext, address: EthAddress, data: openArray[byte],
|
|||
ctx.host.emit_log(ctx.context, address, if data.len > 0: data[0].unsafeAddr else: nil,
|
||||
data.len.uint, topics, topicsCount.uint)
|
||||
|
||||
proc call*(ctx: HostContext, msg: evmc_message): evmc_result {.inline.} =
|
||||
proc call*(ctx: HostContext, msg: nimbus_message): nimbus_result {.inline.} =
|
||||
ctx.host.call(ctx.context, msg.unsafeAddr)
|
||||
|
||||
#proc vmHost*(vmState: BaseVMState, gasPrice: GasInt, origin: EthAddress): HostContext =
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
# * 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(result: var evmc_result) {.cdecl.} =
|
||||
discard
|
||||
proc hostReleaseResultImpl(res: var nimbus_result) {.cdecl, gcsafe.} =
|
||||
dealloc(res.output_data)
|
||||
|
||||
proc hostGetTxContextImpl(ctx: Computation): nimbus_tx_context {.cdecl.} =
|
||||
let vmstate = ctx.vmState
|
||||
|
@ -123,9 +123,46 @@ proc hostEmitLogImpl(ctx: Computation, address: EthAddress,
|
|||
log.address = address
|
||||
ctx.addLogEntry(log)
|
||||
|
||||
proc hostCallImpl(ctx: Computation, msg: var evmc_message): evmc_result {.cdecl.} =
|
||||
template createImpl(c: Computation, m: nimbus_message, res: nimbus_result) =
|
||||
# TODO: use evmc_message to evoid copy
|
||||
var childMsg = Message(
|
||||
kind: CallKind(m.kind.ord),
|
||||
depth: m.depth,
|
||||
gas: m.gas,
|
||||
sender: m.sender,
|
||||
value: Uint256.fromEvmc(m.value)
|
||||
)
|
||||
if m.input_size.int > 0:
|
||||
childMsg.data = newSeq[byte](m.input_size.int)
|
||||
copyMem(childMsg.data[0].addr, m.input_data, m.input_size.int)
|
||||
|
||||
let child = newComputation(c.vmState, childMsg, Uint256.fromEvmc(m.create2_salt))
|
||||
child.execCreate()
|
||||
|
||||
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:
|
||||
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 callImpl(c: Computation, msg: nimbus_message, res: nimbus_result) =
|
||||
discard
|
||||
|
||||
proc hostCallImpl(ctx: Computation, msg: var nimbus_message): nimbus_result {.cdecl.} =
|
||||
if msg.kind == EVMC_CREATE or msg.kind == EVMC_CREATE2:
|
||||
createImpl(ctx, msg, result)
|
||||
else:
|
||||
callImpl(ctx, msg, result)
|
||||
|
||||
proc initHostInterface(): evmc_host_interface =
|
||||
result.account_exists = cast[evmc_account_exists_fn](hostAccountExistsImpl)
|
||||
result.get_storage = cast[evmc_get_storage_fn](hostGetStorageImpl)
|
||||
|
|
|
@ -15,7 +15,7 @@ import
|
|||
../../db/[db_chain, state_db]
|
||||
|
||||
when defined(evmc_enabled):
|
||||
import ../evmc_api, ../evmc_helpers
|
||||
import ../evmc_api, ../evmc_helpers, evmc/evmc
|
||||
|
||||
logScope:
|
||||
topics = "opcode impl"
|
||||
|
@ -546,87 +546,97 @@ genLog()
|
|||
|
||||
# ##########################################
|
||||
# f0s: System operations.
|
||||
|
||||
proc canTransfer(c: Computation, memPos, memLen: int, value: Uint256, opCode: static[Op]): bool =
|
||||
let gasParams = GasParams(kind: Create,
|
||||
cr_currentMemSize: c.memory.len,
|
||||
cr_memOffset: memPos,
|
||||
cr_memLength: memLen
|
||||
)
|
||||
var gasCost = c.gasCosts[Create].c_handler(1.u256, gasParams).gasCost
|
||||
let reason = &"CREATE: GasCreate + {memLen} * memory expansion"
|
||||
|
||||
when opCode == Create2:
|
||||
gasCost = gasCost + c.gasCosts[Create2].m_handler(0, 0, memLen)
|
||||
|
||||
c.gasMeter.consumeGas(gasCost, reason = reason)
|
||||
c.memory.extend(memPos, memLen)
|
||||
c.returnData.setLen(0)
|
||||
|
||||
# the sender is childmsg sender, not parent msg sender
|
||||
# perhaps we need to move this code somewhere else
|
||||
# to avoid confusion
|
||||
let senderBalance = c.getBalance(c.msg.contractAddress)
|
||||
|
||||
if senderBalance < value:
|
||||
debug "Computation Failure", reason = "Insufficient funds available to transfer", required = c.msg.value, balance = senderBalance
|
||||
return false
|
||||
|
||||
if c.msg.depth >= MaxCallDepth:
|
||||
debug "Computation Failure", reason = "Stack too deep", maximumDepth = MaxCallDepth, depth = c.msg.depth
|
||||
return false
|
||||
|
||||
result = true
|
||||
|
||||
proc setupCreate(c: Computation, memPos, len: int, value: Uint256, opCode: static[Op]): Computation =
|
||||
var createMsgGas = c.gasMeter.gasRemaining
|
||||
if c.fork >= FkTangerine:
|
||||
createMsgGas -= createMsgGas div 64
|
||||
|
||||
# Consume gas here that will be passed to child
|
||||
c.gasMeter.consumeGas(createMsgGas, reason="CREATE")
|
||||
|
||||
when opCode == Create:
|
||||
const callKind = evmcCreate
|
||||
else:
|
||||
const callKind = evmcCreate2
|
||||
|
||||
let childMsg = Message(
|
||||
kind: callKind,
|
||||
depth: c.msg.depth + 1,
|
||||
gas: createMsgGas,
|
||||
sender: c.msg.contractAddress,
|
||||
value: value,
|
||||
data: c.memory.read(memPos, len)
|
||||
)
|
||||
|
||||
when opCode == Create:
|
||||
result = newComputation(c.vmState, childMsg)
|
||||
else:
|
||||
result = newComputation(c.vmState, childMsg, c.stack.popInt().some)
|
||||
|
||||
template genCreate(callName: untyped, opCode: Op): untyped =
|
||||
op callName, inline = false, val, startPosition, size:
|
||||
## 0xf0, Create a new account with associated code.
|
||||
op callName, inline = false:
|
||||
checkInStaticContext(c)
|
||||
let
|
||||
endowment = c.stack.popInt()
|
||||
memPos = c.stack.popInt().safeInt
|
||||
|
||||
let (memPos, len) = (startPosition.safeInt, size.safeInt)
|
||||
if not c.canTransfer(memPos, len, val, opCode):
|
||||
push: 0
|
||||
when opCode == Create:
|
||||
const callKind = evmcCreate
|
||||
let memLen {.inject.} = c.stack.peekInt().safeInt
|
||||
let salt = 0.u256
|
||||
else:
|
||||
const callKind = evmcCreate2
|
||||
let memLen {.inject.} = c.stack.popInt().safeInt
|
||||
let salt = c.stack.peekInt()
|
||||
|
||||
c.stack.top(0)
|
||||
|
||||
let gasParams = GasParams(kind: Create,
|
||||
cr_currentMemSize: c.memory.len,
|
||||
cr_memOffset: memPos,
|
||||
cr_memLength: memLen
|
||||
)
|
||||
var gasCost = c.gasCosts[Create].c_handler(1.u256, gasParams).gasCost
|
||||
when opCode == Create2:
|
||||
gasCost = gasCost + c.gasCosts[Create2].m_handler(0, 0, memLen)
|
||||
|
||||
let reason = &"CREATE: GasCreate + {memLen} * memory expansion"
|
||||
c.gasMeter.consumeGas(gasCost, reason = reason)
|
||||
c.memory.extend(memPos, memLen)
|
||||
c.returnData.setLen(0)
|
||||
|
||||
if c.msg.depth >= MaxCallDepth:
|
||||
debug "Computation Failure", reason = "Stack too deep", maxDepth = MaxCallDepth, depth = c.msg.depth
|
||||
return
|
||||
|
||||
var child = setupCreate(c, memPos, len, val, opCode)
|
||||
if child.isNil: return
|
||||
if endowment != 0:
|
||||
let senderBalance = c.getBalance(c.msg.contractAddress)
|
||||
if senderBalance < endowment:
|
||||
debug "Computation Failure", reason = "Insufficient funds available to transfer", required = endowment, balance = senderBalance
|
||||
return
|
||||
|
||||
continuation(child):
|
||||
addChildComputation(c, child)
|
||||
var createMsgGas = c.gasMeter.gasRemaining
|
||||
if c.fork >= FkTangerine:
|
||||
createMsgGas -= createMsgGas div 64
|
||||
c.gasMeter.consumeGas(createMsgGas, reason="CREATE")
|
||||
|
||||
if child.isError:
|
||||
push: 0
|
||||
when evmc_enabled:
|
||||
let msg = nimbus_message(
|
||||
kind: callKind.evmc_call_kind,
|
||||
depth: (c.msg.depth + 1).int32,
|
||||
gas: createMsgGas,
|
||||
sender: c.msg.contractAddress,
|
||||
input_data: c.memory.readPtr(memPos),
|
||||
input_size: memLen.uint,
|
||||
value: toEvmc(endowment),
|
||||
create2_salt: toEvmc(salt)
|
||||
)
|
||||
|
||||
var res = c.host.call(msg)
|
||||
if res.output_size.int > 0:
|
||||
c.returnData = newSeq[byte](res.output_size.int)
|
||||
copyMem(c.returnData[0].addr, res.output_data, c.returnData.len)
|
||||
|
||||
c.gasMeter.returnGas(res.gas_left)
|
||||
|
||||
if res.status_code == EVMC_SUCCESS:
|
||||
c.stack.top(res.create_address)
|
||||
|
||||
if not res.release.isNil:
|
||||
res.release(res)
|
||||
else:
|
||||
let childMsg = Message(
|
||||
kind: callKind,
|
||||
depth: c.msg.depth + 1,
|
||||
gas: createMsgGas,
|
||||
sender: c.msg.contractAddress,
|
||||
value: endowment,
|
||||
data: c.memory.read(memPos, memLen)
|
||||
)
|
||||
|
||||
var child = newComputation(c.vmState, childMsg, salt)
|
||||
child.execCreate()
|
||||
if not child.shouldBurnGas:
|
||||
c.gasMeter.returnGas(child.gasMeter.gasRemaining)
|
||||
|
||||
if child.isSuccess:
|
||||
c.merge(child)
|
||||
c.stack.top child.msg.contractAddress
|
||||
else:
|
||||
push: child.msg.contractAddress
|
||||
|
||||
child.applyMessage(Create)
|
||||
c.returnData = child.output
|
||||
|
||||
genCreate(create, Create)
|
||||
genCreate(create2, Create2)
|
||||
|
|
|
@ -42,6 +42,11 @@ proc read*(memory: var Memory, startPos: Natural, size: Natural): seq[byte] =
|
|||
# TODO: use an openarray[byte]
|
||||
result = memory.bytes[startPos ..< (startPos + size)]
|
||||
|
||||
when defined(evmc_enabled):
|
||||
proc readPtr*(memory: var Memory, startPos: Natural): ptr byte =
|
||||
if memory.bytes.len == 0 or startPos > memory.bytes.len: return
|
||||
result = memory.bytes[startPos].addr
|
||||
|
||||
proc write*(memory: var Memory, startPos: Natural, value: openarray[byte]) =
|
||||
let size = value.len
|
||||
if size == 0:
|
||||
|
|
|
@ -126,3 +126,10 @@ proc `$`*(stack: Stack): string =
|
|||
proc `[]`*(stack: Stack, i: BackwardsIndex, T: typedesc): T =
|
||||
# This should be used only for tracer/test/debugging
|
||||
fromStackElement(stack.values[i], result)
|
||||
|
||||
proc peekInt*(stack: Stack): UInt256 =
|
||||
ensurePop(stack, 1)
|
||||
fromStackElement(stack.values[^1], result)
|
||||
|
||||
proc top*(stack: Stack, value: uint | int | GasInt | UInt256 | EthAddress | Hash256) {.inline.} =
|
||||
toStackElement(value, stack.values[^1])
|
||||
|
|
Loading…
Reference in New Issue