Transaction: Calculate EIP-1283/2200/2929 gas refund in `setStorage`

Make the host service `setStorage` calculate the gas refund itself, instead of
depending on EVM internals.

In EVMC, host `setStorage` is responsible for adding the gas refund using the
rules of EIP-1283 (Constantinople), EIP-2200 (Istanbul) or EIP-2929 (Berlin).

It is not obvious that the host must do it from EVMC documentation, but it's
how it has to be.  Note, this is very different from the gas _cost_, which the
host does not calculate.

Gas refund doesn't affect computation.  It is applied after the whole
transaction is complete, so it can be tracked on the host or EVM side.  But
`setStorage` doesn't return enough information for the EVM to calculate the
refund, so the host must do it when `setStorage` is used.

For now, this continues using Nimbus `Computation` just to hold the gas refund,
to fit around existing structures and get new EVMC working.  But the host can't
keep using `Computation`, so gas refund will be moved out of it in due course.

Signed-off-by: Jamie Lokier <jamie@shareable.org>
This commit is contained in:
Jamie Lokier 2021-05-24 09:53:53 +01:00
parent eb52fd3906
commit 97b4aa5619
No known key found for this signature in database
GPG Key ID: CBC25C68435C30A2
1 changed files with 43 additions and 13 deletions

View File

@ -87,8 +87,40 @@ proc accountExists(host: TransactionHost, address: HostAddress): bool {.show.} =
proc getStorage(host: TransactionHost, address: HostAddress, key: HostKey): HostValue {.show.} =
host.vmState.readOnlyStateDB.getStorage(address, key)
proc setStorage1(host: TransactionHost, address: HostAddress,
key: HostKey, value: HostValue): EvmcStorageStatus =
const
# EIP-1283
SLOAD_GAS_CONSTANTINOPLE = 200
# EIP-2200
SSTORE_SET_GAS = 20000
SSTORE_RESET_GAS = 5000
SSTORE_CLEARS_SCHEDULE = 15000
SLOAD_GAS_ISTANBUL = 800
# EIP-2929
WARM_STORAGE_READ_COST = 100
COLD_SLOAD_COST = 2100
COLD_ACCOUNT_ACCESS_COST = 2600
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
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)
@ -101,26 +133,24 @@ proc setStorage1(host: TransactionHost, address: HostAddress,
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
if oldValue.isZero:
return EVMC_STORAGE_ADDED
elif value.isZero:
# Gas refund for `DELETED` (all forks).
# TODO: Refund depends on `Computation` at the moment.
host.computation.gasMeter.refundGas(SSTORE_CLEARS_SCHEDULE)
return EVMC_STORAGE_DELETED
else:
return EVMC_STORAGE_MODIFIED
proc setStorage(host: TransactionHost, address: HostAddress,
key: HostKey, value: HostValue): EvmcStorageStatus {.show.} =
let status = setStorage1(host, address, key, value)
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:
host.computation.gasMeter.refundGas(gasRefund)
proc getBalance(host: TransactionHost, address: HostAddress): HostBalance {.show.} =
host.vmState.readOnlyStateDB.getBalance(address)