2021-04-08 14:52:10 +00:00
|
|
|
# Nimbus
|
|
|
|
# Copyright (c) 2018 Status Research & Development GmbH
|
|
|
|
# Licensed under either of
|
2021-04-26 16:00:46 +00:00
|
|
|
# * 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.
|
2021-04-08 14:52:10 +00:00
|
|
|
|
|
|
|
import
|
2021-06-01 11:27:05 +00:00
|
|
|
../constants, ../forks,
|
2021-04-26 16:00:46 +00:00
|
|
|
../db/accounts_cache,
|
|
|
|
../utils,
|
|
|
|
./code_stream,
|
2021-06-01 11:27:05 +00:00
|
|
|
./interpreter/[gas_meter, gas_costs, op_codes],
|
2021-04-26 16:00:46 +00:00
|
|
|
./memory,
|
|
|
|
./message,
|
|
|
|
./stack,
|
|
|
|
./state,
|
|
|
|
./transaction_tracer,
|
|
|
|
./types,
|
|
|
|
chronicles,
|
|
|
|
eth/[common, keys],
|
|
|
|
options,
|
|
|
|
sets
|
2021-04-21 07:18:59 +00:00
|
|
|
|
2021-04-26 16:00:46 +00:00
|
|
|
logScope:
|
|
|
|
topics = "vm computation"
|
|
|
|
|
|
|
|
when defined(chronicles_log_level):
|
|
|
|
import stew/byteutils
|
|
|
|
|
2022-09-26 04:56:54 +00:00
|
|
|
when defined(evmc_enabled):
|
|
|
|
import
|
|
|
|
evmc/evmc,
|
|
|
|
evmc_helpers,
|
|
|
|
evmc_api,
|
|
|
|
stew/ranges/ptr_arith
|
|
|
|
|
|
|
|
export
|
|
|
|
evmc,
|
|
|
|
evmc_helpers,
|
|
|
|
evmc_api,
|
|
|
|
ptr_arith
|
|
|
|
|
|
|
|
const
|
|
|
|
evmc_enabled* = defined(evmc_enabled)
|
|
|
|
|
2021-04-26 16:00:46 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Helpers
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2021-06-11 11:46:26 +00:00
|
|
|
proc generateContractAddress(c: Computation, salt: ContractSalt): EthAddress =
|
2021-04-26 16:00:46 +00:00
|
|
|
if c.msg.kind == evmcCreate:
|
2022-04-08 04:54:11 +00:00
|
|
|
let creationNonce = c.vmState.readOnlyStateDB().getNonce(c.msg.sender)
|
2021-04-26 16:00:46 +00:00
|
|
|
result = generateAddress(c.msg.sender, creationNonce)
|
|
|
|
else:
|
|
|
|
result = generateSafeAddress(c.msg.sender, salt, c.msg.data)
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
template getCoinbase*(c: Computation): EthAddress =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
c.host.getTxContext().block_coinbase
|
|
|
|
else:
|
|
|
|
c.vmState.coinbase
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
template getTimestamp*(c: Computation): int64 =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
c.host.getTxContext().block_timestamp
|
|
|
|
else:
|
|
|
|
c.vmState.timestamp.toUnix
|
2021-04-26 16:00:46 +00:00
|
|
|
|
2022-04-08 04:54:11 +00:00
|
|
|
template getBlockNumber*(c: Computation): UInt256 =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
c.host.getTxContext().block_number.u256
|
|
|
|
else:
|
|
|
|
c.vmState.blockNumber.blockNumberToVmWord
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
template getDifficulty*(c: Computation): DifficultyInt =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
UInt256.fromEvmc c.host.getTxContext().block_difficulty
|
|
|
|
else:
|
|
|
|
c.vmState.difficulty
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
template getGasLimit*(c: Computation): GasInt =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
c.host.getTxContext().block_gas_limit.GasInt
|
|
|
|
else:
|
|
|
|
c.vmState.gasLimit
|
2021-04-26 16:00:46 +00:00
|
|
|
|
2022-04-08 04:54:11 +00:00
|
|
|
template getBaseFee*(c: Computation): UInt256 =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
UInt256.fromEvmc c.host.getTxContext().block_base_fee
|
|
|
|
else:
|
|
|
|
c.vmState.baseFee
|
2021-06-27 13:19:22 +00:00
|
|
|
|
2021-04-26 16:00:46 +00:00
|
|
|
template getChainId*(c: Computation): uint =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
UInt256.fromEvmc(c.host.getTxContext().chain_id).truncate(uint)
|
|
|
|
else:
|
|
|
|
c.vmState.chainDB.config.chainId.uint
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
template getOrigin*(c: Computation): EthAddress =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
c.host.getTxContext().tx_origin
|
|
|
|
else:
|
|
|
|
c.vmState.txOrigin
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
template getGasPrice*(c: Computation): GasInt =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
UInt256.fromEvmc(c.host.getTxContext().tx_gas_price).truncate(GasInt)
|
|
|
|
else:
|
|
|
|
c.vmState.txGasPrice
|
2021-04-26 16:00:46 +00:00
|
|
|
|
2022-04-08 04:54:11 +00:00
|
|
|
template getBlockHash*(c: Computation, blockNumber: UInt256): Hash256 =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
c.host.getBlockHash(blockNumber)
|
|
|
|
else:
|
|
|
|
c.vmState.getAncestorHash(blockNumber.vmWordToBlockNumber)
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
template accountExists*(c: Computation, address: EthAddress): bool =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
c.host.accountExists(address)
|
2021-04-26 16:00:46 +00:00
|
|
|
else:
|
2022-09-26 04:56:54 +00:00
|
|
|
if c.fork >= FkSpurious:
|
|
|
|
not c.vmState.readOnlyStateDB.isDeadAccount(address)
|
|
|
|
else:
|
|
|
|
c.vmState.readOnlyStateDB.accountExists(address)
|
2021-04-26 16:00:46 +00:00
|
|
|
|
2022-04-08 04:54:11 +00:00
|
|
|
template getStorage*(c: Computation, slot: UInt256): UInt256 =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
c.host.getStorage(c.msg.contractAddress, slot)
|
|
|
|
else:
|
|
|
|
c.vmState.readOnlyStateDB.getStorage(c.msg.contractAddress, slot)
|
2021-04-26 16:00:46 +00:00
|
|
|
|
2022-04-08 04:54:11 +00:00
|
|
|
template getBalance*(c: Computation, address: EthAddress): UInt256 =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
c.host.getBalance(address)
|
|
|
|
else:
|
|
|
|
c.vmState.readOnlyStateDB.getBalance(address)
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
template getCodeSize*(c: Computation, address: EthAddress): uint =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
c.host.getCodeSize(address)
|
|
|
|
else:
|
|
|
|
uint(c.vmState.readOnlyStateDB.getCodeSize(address))
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
template getCodeHash*(c: Computation, address: EthAddress): Hash256 =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
c.host.getCodeHash(address)
|
2021-04-26 16:00:46 +00:00
|
|
|
else:
|
2022-09-26 04:56:54 +00:00
|
|
|
let
|
|
|
|
db = c.vmState.readOnlyStateDB
|
|
|
|
if not db.accountExists(address) or db.isEmptyAccount(address):
|
|
|
|
default(Hash256)
|
|
|
|
else:
|
|
|
|
db.getCodeHash(address)
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
template selfDestruct*(c: Computation, address: EthAddress) =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
c.host.selfDestruct(c.msg.contractAddress, address)
|
|
|
|
else:
|
|
|
|
c.execSelfDestruct(address)
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
template getCode*(c: Computation, address: EthAddress): seq[byte] =
|
2022-09-26 04:56:54 +00:00
|
|
|
when evmc_enabled:
|
|
|
|
c.host.copyCode(address)
|
|
|
|
else:
|
|
|
|
c.vmState.readOnlyStateDB.getCode(address)
|
2021-04-26 16:00:46 +00:00
|
|
|
|
2021-06-11 11:46:26 +00:00
|
|
|
proc newComputation*(vmState: BaseVMState, message: Message,
|
|
|
|
salt: ContractSalt = ZERO_CONTRACTSALT): Computation =
|
2021-04-26 16:00:46 +00:00
|
|
|
new result
|
|
|
|
result.vmState = vmState
|
|
|
|
result.msg = message
|
|
|
|
result.memory = Memory()
|
|
|
|
result.stack = newStack()
|
|
|
|
result.returnStack = @[]
|
|
|
|
result.gasMeter.init(message.gas)
|
|
|
|
result.touchedAccounts = initHashSet[EthAddress]()
|
2021-05-11 09:05:58 +00:00
|
|
|
result.selfDestructs = initHashSet[EthAddress]()
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
if result.msg.isCreate():
|
|
|
|
result.msg.contractAddress = result.generateContractAddress(salt)
|
|
|
|
result.code = newCodeStream(message.data)
|
|
|
|
message.data = @[]
|
|
|
|
else:
|
|
|
|
result.code = newCodeStream(
|
2022-04-08 04:54:11 +00:00
|
|
|
vmState.readOnlyStateDB.getCode(message.codeAddress))
|
2021-04-26 16:00:46 +00:00
|
|
|
|
2022-09-26 04:56:54 +00:00
|
|
|
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
|
|
|
|
result.msg = message
|
|
|
|
result.memory = Memory()
|
|
|
|
result.stack = newStack()
|
|
|
|
result.returnStack = @[]
|
|
|
|
result.gasMeter.init(message.gas)
|
|
|
|
result.touchedAccounts = initHashSet[EthAddress]()
|
|
|
|
result.selfDestructs = initHashSet[EthAddress]()
|
|
|
|
result.code = newCodeStream(code)
|
|
|
|
|
|
|
|
when evmc_enabled:
|
|
|
|
result.host.init(
|
|
|
|
nim_host_get_interface(),
|
|
|
|
cast[evmc_host_context](result)
|
|
|
|
)
|
|
|
|
|
2021-04-26 16:00:46 +00:00
|
|
|
template gasCosts*(c: Computation): untyped =
|
|
|
|
c.vmState.gasCosts
|
|
|
|
|
|
|
|
template fork*(c: Computation): untyped =
|
|
|
|
c.vmState.fork
|
|
|
|
|
|
|
|
proc isOriginComputation*(c: Computation): bool =
|
|
|
|
# Is this computation the computation initiated by a transaction
|
|
|
|
c.msg.sender == c.vmState.txOrigin
|
|
|
|
|
|
|
|
template isSuccess*(c: Computation): bool =
|
|
|
|
c.error.isNil
|
|
|
|
|
|
|
|
template isError*(c: Computation): bool =
|
|
|
|
not c.isSuccess
|
|
|
|
|
|
|
|
func shouldBurnGas*(c: Computation): bool =
|
|
|
|
c.isError and c.error.burnsGas
|
|
|
|
|
2021-05-11 09:05:58 +00:00
|
|
|
proc isSelfDestructed*(c: Computation, address: EthAddress): bool =
|
|
|
|
result = address in c.selfDestructs
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
proc snapshot*(c: Computation) =
|
2022-04-08 04:54:11 +00:00
|
|
|
c.savePoint = c.vmState.stateDB.beginSavepoint()
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
proc commit*(c: Computation) =
|
2021-10-28 09:42:39 +00:00
|
|
|
c.vmState.stateDB.commit(c.savePoint)
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
proc dispose*(c: Computation) {.inline.} =
|
2021-10-28 09:42:39 +00:00
|
|
|
c.vmState.stateDB.safeDispose(c.savePoint)
|
2021-04-26 16:00:46 +00:00
|
|
|
c.savePoint = nil
|
|
|
|
|
|
|
|
proc rollback*(c: Computation) =
|
2021-10-28 09:42:39 +00:00
|
|
|
c.vmState.stateDB.rollback(c.savePoint)
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
proc setError*(c: Computation, msg: string, burnsGas = false) {.inline.} =
|
|
|
|
c.error = Error(info: msg, burnsGas: burnsGas)
|
|
|
|
|
EVM: `writeContract` fixes, never return contract code as `RETURNDATA`
This fixes #867 "EIP-170 related consensus error at Goerli block 5080941", and
equivalent on other networks.
This combines a change on the EVM-caller side with an EVM-side change from
@jangko 6548ff98 "fixes CREATE/CREATE2's `returndata` bug", making the caller
EVM ignore any data except from `REVERT`.
Either change works by itself. The reason for both is to ensure we definitely
comply with ambiguous EVMC expectations from either side of that boundary, and
it makes the internal API clearer.
As well as fixing a specific consensus issue, there are some other EVM logic
changes too: Refactored `writeContract`, how `RETURNDATA` is handled inside the
EVM, and changed behaviour with quirks before EIP-2 (Homestead).
The fix allows sync to pass block 5080941 on Goerli, and probably equivalent on
other networks. Here's a trace at batch 5080897..5081088:
```
TRC 2021-10-01 21:18:12.883+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=5080897 toBlock=5081088
...
DBG 2021-10-01 21:18:13.270+01:00 Contract code size exceeds EIP170 topics="vm computation" file=computation.nim:236 limit=24577 actual=31411
DBG 2021-10-01 21:18:13.271+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 block=5080941/0A3537BC5BDFC637349E1C77D9648F2F65E2BF973ABF7956618F854B769DF626 gasUsed=3129669 cumulativeGasUsed=3132615
TRC 2021-10-01 21:18:13.271+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<IP:PORT>
```
Although it says "Contract code size" and "gasUsed", this bug is more general
than either contract size or gas. It's due to incorrect behaviour of EVM
instructions `RETURNDATA` and `RETURNDATASIZE`.
Sometimes when `writeContract` decides to reject writing the contract for any
of several reasons (for example just insufficient gas), the unwritten contract
code was being used as the "return data", and given to the caller. If the
caller used `RETURNDATA` or `RETURNDATASIZE` ops, those incorrectly reported
the contract code that didn't get written.
EIP-211 (https://eips.ethereum.org/EIPS/eip-211) describes `RETURNDATA`:
> "`CREATE` and `CREATE2` are considered to return the empty buffer in the
> success case and the failure data in the failure case".
The language is ambiguous. In fact "failure case" means when the contract uses
`REVERT` to finish. It doesn't mean other failures like out of gas, EIP-170
limit, EIP-3541, etc.
To be thorough, and to ensure we always do the right thing with real EVMC when
that's finalised, this patch fixes the `RETURNDATA` issue in two places, either
of which make Goerli block 5080941 pass.
`writeContract` has been refactored to be caller, and so has where it's called.
It sets an error in the usual way if contract writing is rejected -- that's
anticipating EVMC, where we'll use different error codes later.
Overall four behaviour changes:
1. On the callee side, it doesn't set `c.outputData` except for `REVERT`.
2. On the caller side, it doesn't read `child.outputData` except for `REVERT`.
3. There was a bug in processing before Homestead fork (EIP-2). We did not
match the spec or other implementations; now we do. When there's
insufficient gas, before Homestead it's treated as success but with an empty
contract.
https://github.com/ethereum/pyethereum/blob/d117c8f3fd93359fc641fd850fa799436f7c43b5/ethereum/processblock.py#L304
https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586
4. The Byzantium check has been removed, as it's unnecessary.
Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-12-02 19:44:51 +00:00
|
|
|
proc writeContract*(c: Computation) =
|
|
|
|
template withExtra(tracer: untyped, args: varargs[untyped]) =
|
|
|
|
tracer args, newContract=($c.msg.contractAddress),
|
2022-01-18 16:19:32 +00:00
|
|
|
blockNumber=c.vmState.blockNumber,
|
|
|
|
parentHash=($c.vmState.parent.blockHash)
|
EVM: `writeContract` fixes, never return contract code as `RETURNDATA`
This fixes #867 "EIP-170 related consensus error at Goerli block 5080941", and
equivalent on other networks.
This combines a change on the EVM-caller side with an EVM-side change from
@jangko 6548ff98 "fixes CREATE/CREATE2's `returndata` bug", making the caller
EVM ignore any data except from `REVERT`.
Either change works by itself. The reason for both is to ensure we definitely
comply with ambiguous EVMC expectations from either side of that boundary, and
it makes the internal API clearer.
As well as fixing a specific consensus issue, there are some other EVM logic
changes too: Refactored `writeContract`, how `RETURNDATA` is handled inside the
EVM, and changed behaviour with quirks before EIP-2 (Homestead).
The fix allows sync to pass block 5080941 on Goerli, and probably equivalent on
other networks. Here's a trace at batch 5080897..5081088:
```
TRC 2021-10-01 21:18:12.883+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=5080897 toBlock=5081088
...
DBG 2021-10-01 21:18:13.270+01:00 Contract code size exceeds EIP170 topics="vm computation" file=computation.nim:236 limit=24577 actual=31411
DBG 2021-10-01 21:18:13.271+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 block=5080941/0A3537BC5BDFC637349E1C77D9648F2F65E2BF973ABF7956618F854B769DF626 gasUsed=3129669 cumulativeGasUsed=3132615
TRC 2021-10-01 21:18:13.271+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<IP:PORT>
```
Although it says "Contract code size" and "gasUsed", this bug is more general
than either contract size or gas. It's due to incorrect behaviour of EVM
instructions `RETURNDATA` and `RETURNDATASIZE`.
Sometimes when `writeContract` decides to reject writing the contract for any
of several reasons (for example just insufficient gas), the unwritten contract
code was being used as the "return data", and given to the caller. If the
caller used `RETURNDATA` or `RETURNDATASIZE` ops, those incorrectly reported
the contract code that didn't get written.
EIP-211 (https://eips.ethereum.org/EIPS/eip-211) describes `RETURNDATA`:
> "`CREATE` and `CREATE2` are considered to return the empty buffer in the
> success case and the failure data in the failure case".
The language is ambiguous. In fact "failure case" means when the contract uses
`REVERT` to finish. It doesn't mean other failures like out of gas, EIP-170
limit, EIP-3541, etc.
To be thorough, and to ensure we always do the right thing with real EVMC when
that's finalised, this patch fixes the `RETURNDATA` issue in two places, either
of which make Goerli block 5080941 pass.
`writeContract` has been refactored to be caller, and so has where it's called.
It sets an error in the usual way if contract writing is rejected -- that's
anticipating EVMC, where we'll use different error codes later.
Overall four behaviour changes:
1. On the callee side, it doesn't set `c.outputData` except for `REVERT`.
2. On the caller side, it doesn't read `child.outputData` except for `REVERT`.
3. There was a bug in processing before Homestead fork (EIP-2). We did not
match the spec or other implementations; now we do. When there's
insufficient gas, before Homestead it's treated as success but with an empty
contract.
https://github.com/ethereum/pyethereum/blob/d117c8f3fd93359fc641fd850fa799436f7c43b5/ethereum/processblock.py#L304
https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586
4. The Byzantium check has been removed, as it's unnecessary.
Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-12-02 19:44:51 +00:00
|
|
|
|
|
|
|
# In each check below, they are guarded by `len > 0`. This includes writing
|
|
|
|
# out the code, because the account already has zero-length code to handle
|
|
|
|
# nested calls (this is specified). May as well simplify other branches.
|
|
|
|
let (len, fork) = (c.output.len, c.fork)
|
|
|
|
if len == 0:
|
|
|
|
return
|
|
|
|
|
|
|
|
# EIP-3541 constraint (https://eips.ethereum.org/EIPS/eip-3541).
|
|
|
|
if fork >= FkLondon and c.output[0] == 0xEF.byte:
|
|
|
|
withExtra trace, "New contract code starts with 0xEF byte, not allowed by EIP-3541"
|
|
|
|
# TODO: Return `EVMC_CONTRACT_VALIDATION_FAILURE` (like Silkworm).
|
|
|
|
c.setError("EVMC_CONTRACT_VALIDATION_FAILURE", true)
|
|
|
|
return
|
|
|
|
|
|
|
|
# EIP-170 constraint (https://eips.ethereum.org/EIPS/eip-3541).
|
|
|
|
if fork >= FkSpurious and len > EIP170_MAX_CODE_SIZE:
|
|
|
|
withExtra trace, "New contract code exceeds EIP-170 limit",
|
|
|
|
codeSize=len, maxSize=EIP170_MAX_CODE_SIZE
|
|
|
|
# TODO: Return `EVMC_OUT_OF_GAS` (like Silkworm).
|
|
|
|
c.setError("EVMC_OUT_OF_GAS", true)
|
|
|
|
return
|
2021-06-28 10:14:08 +00:00
|
|
|
|
2021-10-19 11:52:01 +00:00
|
|
|
# Charge gas and write the code even if the code address is self-destructed.
|
|
|
|
# Non-empty code in a newly created, self-destructed account is possible if
|
|
|
|
# the init code calls `DELEGATECALL` or `CALLCODE` to other code which uses
|
|
|
|
# `SELFDESTRUCT`. This shows on Mainnet blocks 6001128..6001204, where the
|
|
|
|
# gas difference matters. The new code can be called later in the
|
|
|
|
# transaction too, before self-destruction wipes the account at the end.
|
2021-04-26 16:00:46 +00:00
|
|
|
|
EVM: `writeContract` fixes, never return contract code as `RETURNDATA`
This fixes #867 "EIP-170 related consensus error at Goerli block 5080941", and
equivalent on other networks.
This combines a change on the EVM-caller side with an EVM-side change from
@jangko 6548ff98 "fixes CREATE/CREATE2's `returndata` bug", making the caller
EVM ignore any data except from `REVERT`.
Either change works by itself. The reason for both is to ensure we definitely
comply with ambiguous EVMC expectations from either side of that boundary, and
it makes the internal API clearer.
As well as fixing a specific consensus issue, there are some other EVM logic
changes too: Refactored `writeContract`, how `RETURNDATA` is handled inside the
EVM, and changed behaviour with quirks before EIP-2 (Homestead).
The fix allows sync to pass block 5080941 on Goerli, and probably equivalent on
other networks. Here's a trace at batch 5080897..5081088:
```
TRC 2021-10-01 21:18:12.883+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=5080897 toBlock=5081088
...
DBG 2021-10-01 21:18:13.270+01:00 Contract code size exceeds EIP170 topics="vm computation" file=computation.nim:236 limit=24577 actual=31411
DBG 2021-10-01 21:18:13.271+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 block=5080941/0A3537BC5BDFC637349E1C77D9648F2F65E2BF973ABF7956618F854B769DF626 gasUsed=3129669 cumulativeGasUsed=3132615
TRC 2021-10-01 21:18:13.271+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<IP:PORT>
```
Although it says "Contract code size" and "gasUsed", this bug is more general
than either contract size or gas. It's due to incorrect behaviour of EVM
instructions `RETURNDATA` and `RETURNDATASIZE`.
Sometimes when `writeContract` decides to reject writing the contract for any
of several reasons (for example just insufficient gas), the unwritten contract
code was being used as the "return data", and given to the caller. If the
caller used `RETURNDATA` or `RETURNDATASIZE` ops, those incorrectly reported
the contract code that didn't get written.
EIP-211 (https://eips.ethereum.org/EIPS/eip-211) describes `RETURNDATA`:
> "`CREATE` and `CREATE2` are considered to return the empty buffer in the
> success case and the failure data in the failure case".
The language is ambiguous. In fact "failure case" means when the contract uses
`REVERT` to finish. It doesn't mean other failures like out of gas, EIP-170
limit, EIP-3541, etc.
To be thorough, and to ensure we always do the right thing with real EVMC when
that's finalised, this patch fixes the `RETURNDATA` issue in two places, either
of which make Goerli block 5080941 pass.
`writeContract` has been refactored to be caller, and so has where it's called.
It sets an error in the usual way if contract writing is rejected -- that's
anticipating EVMC, where we'll use different error codes later.
Overall four behaviour changes:
1. On the callee side, it doesn't set `c.outputData` except for `REVERT`.
2. On the caller side, it doesn't read `child.outputData` except for `REVERT`.
3. There was a bug in processing before Homestead fork (EIP-2). We did not
match the spec or other implementations; now we do. When there's
insufficient gas, before Homestead it's treated as success but with an empty
contract.
https://github.com/ethereum/pyethereum/blob/d117c8f3fd93359fc641fd850fa799436f7c43b5/ethereum/processblock.py#L304
https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586
4. The Byzantium check has been removed, as it's unnecessary.
Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-12-02 19:44:51 +00:00
|
|
|
let gasParams = GasParams(kind: Create, cr_memLength: len)
|
2021-04-26 16:00:46 +00:00
|
|
|
let codeCost = c.gasCosts[Create].c_handler(0.u256, gasParams).gasCost
|
EVM: `writeContract` fixes, never return contract code as `RETURNDATA`
This fixes #867 "EIP-170 related consensus error at Goerli block 5080941", and
equivalent on other networks.
This combines a change on the EVM-caller side with an EVM-side change from
@jangko 6548ff98 "fixes CREATE/CREATE2's `returndata` bug", making the caller
EVM ignore any data except from `REVERT`.
Either change works by itself. The reason for both is to ensure we definitely
comply with ambiguous EVMC expectations from either side of that boundary, and
it makes the internal API clearer.
As well as fixing a specific consensus issue, there are some other EVM logic
changes too: Refactored `writeContract`, how `RETURNDATA` is handled inside the
EVM, and changed behaviour with quirks before EIP-2 (Homestead).
The fix allows sync to pass block 5080941 on Goerli, and probably equivalent on
other networks. Here's a trace at batch 5080897..5081088:
```
TRC 2021-10-01 21:18:12.883+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=5080897 toBlock=5081088
...
DBG 2021-10-01 21:18:13.270+01:00 Contract code size exceeds EIP170 topics="vm computation" file=computation.nim:236 limit=24577 actual=31411
DBG 2021-10-01 21:18:13.271+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 block=5080941/0A3537BC5BDFC637349E1C77D9648F2F65E2BF973ABF7956618F854B769DF626 gasUsed=3129669 cumulativeGasUsed=3132615
TRC 2021-10-01 21:18:13.271+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<IP:PORT>
```
Although it says "Contract code size" and "gasUsed", this bug is more general
than either contract size or gas. It's due to incorrect behaviour of EVM
instructions `RETURNDATA` and `RETURNDATASIZE`.
Sometimes when `writeContract` decides to reject writing the contract for any
of several reasons (for example just insufficient gas), the unwritten contract
code was being used as the "return data", and given to the caller. If the
caller used `RETURNDATA` or `RETURNDATASIZE` ops, those incorrectly reported
the contract code that didn't get written.
EIP-211 (https://eips.ethereum.org/EIPS/eip-211) describes `RETURNDATA`:
> "`CREATE` and `CREATE2` are considered to return the empty buffer in the
> success case and the failure data in the failure case".
The language is ambiguous. In fact "failure case" means when the contract uses
`REVERT` to finish. It doesn't mean other failures like out of gas, EIP-170
limit, EIP-3541, etc.
To be thorough, and to ensure we always do the right thing with real EVMC when
that's finalised, this patch fixes the `RETURNDATA` issue in two places, either
of which make Goerli block 5080941 pass.
`writeContract` has been refactored to be caller, and so has where it's called.
It sets an error in the usual way if contract writing is rejected -- that's
anticipating EVMC, where we'll use different error codes later.
Overall four behaviour changes:
1. On the callee side, it doesn't set `c.outputData` except for `REVERT`.
2. On the caller side, it doesn't read `child.outputData` except for `REVERT`.
3. There was a bug in processing before Homestead fork (EIP-2). We did not
match the spec or other implementations; now we do. When there's
insufficient gas, before Homestead it's treated as success but with an empty
contract.
https://github.com/ethereum/pyethereum/blob/d117c8f3fd93359fc641fd850fa799436f7c43b5/ethereum/processblock.py#L304
https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586
4. The Byzantium check has been removed, as it's unnecessary.
Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-12-02 19:44:51 +00:00
|
|
|
if codeCost <= c.gasMeter.gasRemaining:
|
|
|
|
c.gasMeter.consumeGas(codeCost, reason = "Write new contract code")
|
2022-04-08 04:54:11 +00:00
|
|
|
c.vmState.mutateStateDB:
|
EVM: `writeContract` fixes, never return contract code as `RETURNDATA`
This fixes #867 "EIP-170 related consensus error at Goerli block 5080941", and
equivalent on other networks.
This combines a change on the EVM-caller side with an EVM-side change from
@jangko 6548ff98 "fixes CREATE/CREATE2's `returndata` bug", making the caller
EVM ignore any data except from `REVERT`.
Either change works by itself. The reason for both is to ensure we definitely
comply with ambiguous EVMC expectations from either side of that boundary, and
it makes the internal API clearer.
As well as fixing a specific consensus issue, there are some other EVM logic
changes too: Refactored `writeContract`, how `RETURNDATA` is handled inside the
EVM, and changed behaviour with quirks before EIP-2 (Homestead).
The fix allows sync to pass block 5080941 on Goerli, and probably equivalent on
other networks. Here's a trace at batch 5080897..5081088:
```
TRC 2021-10-01 21:18:12.883+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=5080897 toBlock=5081088
...
DBG 2021-10-01 21:18:13.270+01:00 Contract code size exceeds EIP170 topics="vm computation" file=computation.nim:236 limit=24577 actual=31411
DBG 2021-10-01 21:18:13.271+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 block=5080941/0A3537BC5BDFC637349E1C77D9648F2F65E2BF973ABF7956618F854B769DF626 gasUsed=3129669 cumulativeGasUsed=3132615
TRC 2021-10-01 21:18:13.271+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<IP:PORT>
```
Although it says "Contract code size" and "gasUsed", this bug is more general
than either contract size or gas. It's due to incorrect behaviour of EVM
instructions `RETURNDATA` and `RETURNDATASIZE`.
Sometimes when `writeContract` decides to reject writing the contract for any
of several reasons (for example just insufficient gas), the unwritten contract
code was being used as the "return data", and given to the caller. If the
caller used `RETURNDATA` or `RETURNDATASIZE` ops, those incorrectly reported
the contract code that didn't get written.
EIP-211 (https://eips.ethereum.org/EIPS/eip-211) describes `RETURNDATA`:
> "`CREATE` and `CREATE2` are considered to return the empty buffer in the
> success case and the failure data in the failure case".
The language is ambiguous. In fact "failure case" means when the contract uses
`REVERT` to finish. It doesn't mean other failures like out of gas, EIP-170
limit, EIP-3541, etc.
To be thorough, and to ensure we always do the right thing with real EVMC when
that's finalised, this patch fixes the `RETURNDATA` issue in two places, either
of which make Goerli block 5080941 pass.
`writeContract` has been refactored to be caller, and so has where it's called.
It sets an error in the usual way if contract writing is rejected -- that's
anticipating EVMC, where we'll use different error codes later.
Overall four behaviour changes:
1. On the callee side, it doesn't set `c.outputData` except for `REVERT`.
2. On the caller side, it doesn't read `child.outputData` except for `REVERT`.
3. There was a bug in processing before Homestead fork (EIP-2). We did not
match the spec or other implementations; now we do. When there's
insufficient gas, before Homestead it's treated as success but with an empty
contract.
https://github.com/ethereum/pyethereum/blob/d117c8f3fd93359fc641fd850fa799436f7c43b5/ethereum/processblock.py#L304
https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586
4. The Byzantium check has been removed, as it's unnecessary.
Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-12-02 19:44:51 +00:00
|
|
|
db.setCode(c.msg.contractAddress, c.output)
|
|
|
|
withExtra trace, "Writing new contract code"
|
|
|
|
return
|
|
|
|
|
|
|
|
if fork >= FkHomestead:
|
|
|
|
# EIP-2 (https://eips.ethereum.org/EIPS/eip-2).
|
|
|
|
# TODO: Return `EVMC_OUT_OF_GAS` (like Silkworm).
|
|
|
|
c.setError("EVMC_OUT_OF_GAS", true)
|
2021-04-26 16:00:46 +00:00
|
|
|
else:
|
EVM: `writeContract` fixes, never return contract code as `RETURNDATA`
This fixes #867 "EIP-170 related consensus error at Goerli block 5080941", and
equivalent on other networks.
This combines a change on the EVM-caller side with an EVM-side change from
@jangko 6548ff98 "fixes CREATE/CREATE2's `returndata` bug", making the caller
EVM ignore any data except from `REVERT`.
Either change works by itself. The reason for both is to ensure we definitely
comply with ambiguous EVMC expectations from either side of that boundary, and
it makes the internal API clearer.
As well as fixing a specific consensus issue, there are some other EVM logic
changes too: Refactored `writeContract`, how `RETURNDATA` is handled inside the
EVM, and changed behaviour with quirks before EIP-2 (Homestead).
The fix allows sync to pass block 5080941 on Goerli, and probably equivalent on
other networks. Here's a trace at batch 5080897..5081088:
```
TRC 2021-10-01 21:18:12.883+01:00 Persisting blocks file=persist_blocks.nim:43 fromBlock=5080897 toBlock=5081088
...
DBG 2021-10-01 21:18:13.270+01:00 Contract code size exceeds EIP170 topics="vm computation" file=computation.nim:236 limit=24577 actual=31411
DBG 2021-10-01 21:18:13.271+01:00 gasUsed neq cumulativeGasUsed file=process_block.nim:68 block=5080941/0A3537BC5BDFC637349E1C77D9648F2F65E2BF973ABF7956618F854B769DF626 gasUsed=3129669 cumulativeGasUsed=3132615
TRC 2021-10-01 21:18:13.271+01:00 peer disconnected file=blockchain_sync.nim:407 peer=<IP:PORT>
```
Although it says "Contract code size" and "gasUsed", this bug is more general
than either contract size or gas. It's due to incorrect behaviour of EVM
instructions `RETURNDATA` and `RETURNDATASIZE`.
Sometimes when `writeContract` decides to reject writing the contract for any
of several reasons (for example just insufficient gas), the unwritten contract
code was being used as the "return data", and given to the caller. If the
caller used `RETURNDATA` or `RETURNDATASIZE` ops, those incorrectly reported
the contract code that didn't get written.
EIP-211 (https://eips.ethereum.org/EIPS/eip-211) describes `RETURNDATA`:
> "`CREATE` and `CREATE2` are considered to return the empty buffer in the
> success case and the failure data in the failure case".
The language is ambiguous. In fact "failure case" means when the contract uses
`REVERT` to finish. It doesn't mean other failures like out of gas, EIP-170
limit, EIP-3541, etc.
To be thorough, and to ensure we always do the right thing with real EVMC when
that's finalised, this patch fixes the `RETURNDATA` issue in two places, either
of which make Goerli block 5080941 pass.
`writeContract` has been refactored to be caller, and so has where it's called.
It sets an error in the usual way if contract writing is rejected -- that's
anticipating EVMC, where we'll use different error codes later.
Overall four behaviour changes:
1. On the callee side, it doesn't set `c.outputData` except for `REVERT`.
2. On the caller side, it doesn't read `child.outputData` except for `REVERT`.
3. There was a bug in processing before Homestead fork (EIP-2). We did not
match the spec or other implementations; now we do. When there's
insufficient gas, before Homestead it's treated as success but with an empty
contract.
https://github.com/ethereum/pyethereum/blob/d117c8f3fd93359fc641fd850fa799436f7c43b5/ethereum/processblock.py#L304
https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586
4. The Byzantium check has been removed, as it's unnecessary.
Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-12-02 19:44:51 +00:00
|
|
|
# Before EIP-2, when out of gas for code storage, the account ends up with
|
|
|
|
# zero-length code and no error. No gas is charged. Code cited in EIP-2:
|
|
|
|
# https://github.com/ethereum/pyethereum/blob/d117c8f3fd93/ethereum/processblock.py#L304
|
|
|
|
# https://github.com/ethereum/go-ethereum/blob/401354976bb4/core/vm/instructions.go#L586
|
|
|
|
# The account already has zero-length code to handle nested calls.
|
|
|
|
withExtra trace, "New contract given empty code by pre-Homestead rules"
|
2021-04-26 16:00:46 +00:00
|
|
|
|
2022-09-26 04:56:54 +00:00
|
|
|
template chainTo*(c: Computation, toChild: typeof(c.child), after: untyped) =
|
2021-04-26 16:00:46 +00:00
|
|
|
c.child = toChild
|
|
|
|
c.continuation = proc() =
|
2022-09-26 04:56:54 +00:00
|
|
|
c.continuation = nil
|
2021-04-26 16:00:46 +00:00
|
|
|
after
|
|
|
|
|
|
|
|
proc merge*(c, child: Computation) =
|
|
|
|
c.logEntries.add child.logEntries
|
|
|
|
c.gasMeter.refundGas(child.gasMeter.gasRefunded)
|
2021-05-11 09:05:58 +00:00
|
|
|
c.selfDestructs.incl child.selfDestructs
|
2021-04-26 16:00:46 +00:00
|
|
|
c.touchedAccounts.incl child.touchedAccounts
|
|
|
|
|
|
|
|
proc execSelfDestruct*(c: Computation, beneficiary: EthAddress) =
|
|
|
|
c.vmState.mutateStateDB:
|
|
|
|
let
|
|
|
|
localBalance = c.getBalance(c.msg.contractAddress)
|
|
|
|
beneficiaryBalance = c.getBalance(beneficiary)
|
|
|
|
|
|
|
|
# Transfer to beneficiary
|
|
|
|
db.setBalance(beneficiary, localBalance + beneficiaryBalance)
|
|
|
|
|
|
|
|
# Zero the balance of the address being deleted.
|
|
|
|
# This must come after sending to beneficiary in case the
|
|
|
|
# contract named itself as the beneficiary.
|
|
|
|
db.setBalance(c.msg.contractAddress, 0.u256)
|
|
|
|
|
|
|
|
trace "SELFDESTRUCT",
|
|
|
|
contractAddress = c.msg.contractAddress.toHex,
|
|
|
|
localBalance = localBalance.toString,
|
|
|
|
beneficiary = beneficiary.toHex
|
|
|
|
|
|
|
|
c.touchedAccounts.incl beneficiary
|
|
|
|
# Register the account to be deleted
|
2021-05-11 09:05:58 +00:00
|
|
|
c.selfDestructs.incl(c.msg.contractAddress)
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
proc addLogEntry*(c: Computation, log: Log) {.inline.} =
|
|
|
|
c.logEntries.add(log)
|
|
|
|
|
|
|
|
proc getGasRefund*(c: Computation): GasInt =
|
|
|
|
if c.isSuccess:
|
|
|
|
result = c.gasMeter.gasRefunded
|
|
|
|
|
|
|
|
proc refundSelfDestruct*(c: Computation) =
|
|
|
|
let cost = gasFees[c.fork][RefundSelfDestruct]
|
2021-05-11 09:05:58 +00:00
|
|
|
c.gasMeter.refundGas(cost * c.selfDestructs.len)
|
2021-04-26 16:00:46 +00:00
|
|
|
|
|
|
|
proc tracingEnabled*(c: Computation): bool {.inline.} =
|
|
|
|
TracerFlags.EnableTracing in c.vmState.tracer.flags
|
|
|
|
|
|
|
|
proc traceOpCodeStarted*(c: Computation, op: Op): int {.inline.} =
|
|
|
|
c.vmState.tracer.traceOpCodeStarted(c, op)
|
|
|
|
|
|
|
|
proc traceOpCodeEnded*(c: Computation, op: Op, lastIndex: int) {.inline.} =
|
|
|
|
c.vmState.tracer.traceOpCodeEnded(c, op, lastIndex)
|
|
|
|
|
|
|
|
proc traceError*(c: Computation) {.inline.} =
|
|
|
|
c.vmState.tracer.traceError(c)
|
|
|
|
|
|
|
|
proc prepareTracer*(c: Computation) {.inline.} =
|
|
|
|
c.vmState.tracer.prepare(c.msg.depth)
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# End
|
|
|
|
# ------------------------------------------------------------------------------
|