EIP2929 implementation

This commit is contained in:
jangko 2020-12-11 17:51:17 +07:00
parent f2b483d6ad
commit 3db535aa39
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
8 changed files with 163 additions and 11 deletions

View File

@ -39,3 +39,6 @@ proc add*(ac: var AccessList, address: EthAddress, slot: UInt256) =
val[].incl slot
do:
ac.slots[address] = toHashSet([slot])
proc clear*(ac: var AccessList) {.inline.} =
ac.slots.clear()

View File

@ -452,6 +452,10 @@ proc persist*(ac: var AccountsCache, clearCache: bool = true) =
else:
for x in cleanAccounts:
ac.savePoint.cache.del x
# EIP2929
ac.savePoint.accessList.clear()
ac.isDirty = false
iterator storage*(ac: AccountsCache, address: EthAddress): (UInt256, UInt256) =

View File

@ -3,7 +3,7 @@ import options, sets,
../db/[db_chain, accounts_cache],
../utils, ../constants, ../transaction,
../vm_state, ../vm_types, ../vm_state_transactions,
../vm/[computation, message],
../vm/[computation, message, precompiles],
../vm/interpreter/vm_forks,
./dao, ../config
@ -13,6 +13,16 @@ proc processTransaction*(tx: Transaction, sender: EthAddress, vmState: BaseVMSta
trace "Sender", sender
trace "txHash", rlpHash = tx.rlpHash
# EIP2929
if fork >= FkBerlin:
vmState.mutateStateDB:
db.accessList(sender)
if not tx.isContractCreation:
#If it's a create-tx, the destination will be added inside evm.create
db.accessList(tx.getRecipient)
for c in activePrecompiles():
db.accessList(c)
if validateTransaction(vmState, tx, sender, fork):
var c = setupComputation(vmState, tx, sender, fork)
vmState.mutateStateDB:

View File

@ -258,6 +258,12 @@ proc execCreate*(c: Computation) =
c.vmState.mutateStateDB:
db.incNonce(c.msg.sender)
# We add this to the access list _before_ taking a snapshot.
# Even if the creation fails, the access-list change should not be rolled back
# EIP2929
if c.fork >= FkBerlin:
db.accessList(c.msg.contractAddress)
c.snapshot()
defer:
c.dispose()

View File

@ -115,6 +115,11 @@ type
GasCosts* = array[Op, GasCost]
const
ColdSloadCost* = 2100
ColdAccountAccessCost* = 2600
WarmStorageReadCost* = 100
template gasCosts(fork: Fork, prefix, ResultGasCostsName: untyped) =
## Generate the gas cost for each forks and store them in a const
@ -214,13 +219,24 @@ 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 = FeeSchedule[GasSload] # if the value doesn't change.
DirtyGas = FeeSchedule[GasSload] # if a dirty value is changed.
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] - FeeSchedule[GasSload] # resetting to the original zero value
CleanGas = FeeSchedule[GasSreset]# from clean non-zero to something else
CleanRefund = FeeSchedule[GasSreset] - FeeSchedule[GasSload] # resetting to the original non-zero value
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):
@ -724,6 +740,7 @@ gasCosts(FkTangerine, tangerine, TangerineGasCosts)
gasCosts(FkSpurious, spurious, SpuriousGasCosts)
gasCosts(FkConstantinople, constantinople, ConstantinopleGasCosts)
gasCosts(FkIstanbul, istanbul, IstanbulGasCosts)
gasCosts(FkBerlin, berlin, BerlinGasCosts)
proc forkToSchedule*(fork: Fork): GasCosts =
if fork < FkHomestead:
@ -736,8 +753,10 @@ proc forkToSchedule*(fork: Fork): GasCosts =
ConstantinopleGasCosts # with EIP-1283
elif fork < FkIstanbul:
SpuriousGasCosts
else:
elif fork < FkBerlin:
IstanbulGasCosts
else:
BerlinGasCosts
const
## Precompile costs

View File

@ -23,6 +23,15 @@ logScope:
# ##################################
# Syntactic sugar
proc gasEip2929AccountCheck(c: Computation, address: EthAddress) =
c.vmState.mutateStateDB:
let gasCost = if not db.inAccessList(address):
db.accessList(address)
ColdAccountAccessCost
else:
WarmStorageReadCost
c.gasMeter.consumeGas(gasCost, reason = "gasEIP2929AccountCheck")
template push(x: typed) {.dirty.} =
## Push an expression on the computation stack
c.stack.push x
@ -763,7 +772,7 @@ template genCall(callName: untyped, opCode: Op): untyped =
(memOutPos, memOutLen)
let contractAddress = when opCode in {Call, StaticCall}: destination else: c.msg.contractAddress
var (childGasFee, childGasLimit) = c.gasCosts[opCode].c_handler(
var (gasCost, childGasLimit) = c.gasCosts[opCode].c_handler(
value,
GasParams(kind: opCode,
c_isNewAccount: not c.accountExists(contractAddress),
@ -781,8 +790,12 @@ template genCall(callName: untyped, opCode: Op): untyped =
# if c.fork >= FkBerlin and destination.toInt <= MaxPrecompilesAddr:
# childGasFee = childGasFee - 660.GasInt
if childGasFee >= 0:
c.gasMeter.consumeGas(childGasFee, reason = $opCode)
# EIP2929
if c.fork >= FkBerlin:
c.gasEip2929AccountCheck(destination)
if gasCost >= 0:
c.gasMeter.consumeGas(gasCost, reason = $opCode)
c.returnData.setLen(0)
@ -792,7 +805,7 @@ template genCall(callName: untyped, opCode: Op): untyped =
c.gasMeter.returnGas(childGasLimit)
return
if childGasFee < 0 and childGasLimit <= 0:
if gasCost < 0 and childGasLimit <= 0:
raise newException(OutOfGas, "Gas not enough to perform calculation (" & callName.astToStr & ")")
c.memory.extend(memInPos, memInLen)
@ -957,3 +970,86 @@ op sarOp, inline = true:
op extCodeHash, inline = true:
let address = c.stack.popAddress()
push: c.getCodeHash(address)
op balanceEIP2929, inline = true:
## 0x31, Get balance of the given account.
let address = c.stack.popAddress()
c.gasEip2929AccountCheck(address)
push: c.getBalance(address)
op extCodeHashEIP2929, inline = true:
let address = c.stack.popAddress()
c.gasEip2929AccountCheck(address)
push: c.getCodeHash(address)
op extCodeSizeEIP2929, inline = true:
## 0x3b, Get size of an account's code
let address = c.stack.popAddress()
c.gasEip2929AccountCheck(address)
push: c.getCodeSize(address)
op extCodeCopyEIP2929, inline = true:
## 0x3c, Copy an account's code to memory.
let address = c.stack.popAddress()
let (memStartPos, codeStartPos, size) = c.stack.popInt(3)
let (memPos, codePos, len) = (memStartPos.cleanMemRef, codeStartPos.cleanMemRef, size.cleanMemRef)
c.gasMeter.consumeGas(
c.gasCosts[ExtCodeCopy].m_handler(c.memory.len, memPos, len),
reason="ExtCodeCopy fee")
c.gasEip2929AccountCheck(address)
let codeBytes = c.getCode(address)
c.memory.writePaddedResult(codeBytes, memPos, codePos, len)
op selfDestructEIP2929, inline = false:
checkInStaticContext(c)
let
beneficiary = c.stack.popAddress()
isDead = not c.accountExists(beneficiary)
balance = c.getBalance(c.msg.contractAddress)
let gasParams = GasParams(kind: SelfDestruct,
sd_condition: isDead and not balance.isZero
)
var gasCost = c.gasCosts[SelfDestruct].c_handler(0.u256, gasParams).gasCost
c.vmState.mutateStateDB:
if not db.inAccessList(beneficiary):
db.accessList(beneficiary)
gasCost = gasCost + ColdAccountAccessCost
c.gasMeter.consumeGas(gasCost, reason = "SELFDESTRUCT EIP161")
c.selfDestruct(beneficiary)
op sloadEIP2929, inline = true, slot:
## 0x54, Load word from storage.
c.vmState.mutateStateDB:
let gasCost = if not db.inAccessList(c.msg.contractAddress, slot):
db.accessList(c.msg.contractAddress, slot)
ColdSloadCost
else:
WarmStorageReadCost
c.gasMeter.consumeGas(gasCost, reason = "sloadEIP2929")
push: c.getStorage(slot)
op sstoreEIP2929, inline = false, slot, newValue:
checkInStaticContext(c)
const SentryGasEIP2200 = 2300 # Minimum gas required to be present for an SSTORE call, not consumed
if c.gasMeter.gasRemaining <= SentryGasEIP2200:
raise newException(OutOfGas, "Gas not enough to perform EIP2200 SSTORE")
c.vmState.mutateStateDB:
if not db.inAccessList(c.msg.contractAddress, slot):
db.accessList(c.msg.contractAddress, slot)
c.gasMeter.consumeGas(ColdSloadCost, reason = "sstoreEIP2929")
when evmc_enabled:
sstoreEvmc(c, slot, newValue)
else:
sstoreNetGasMeteringImpl(c, slot, newValue)

View File

@ -235,6 +235,14 @@ proc genBerlinJumpTable(ops: array[Op, NimNode]): array[Op, NimNode] {.compileTi
result[ReturnSub] = newIdentNode "returnSub"
result[JumpSub] = newIdentNode "jumpSub"
result[Balance] = newIdentNode "balanceEIP2929"
result[ExtCodeHash] = newIdentNode "extCodeHashEIP2929"
result[ExtCodeSize] = newIdentNode "extCodeSizeEIP2929"
result[ExtCodeCopy] = newIdentNode "extCodeCopyEIP2929"
result[SelfDestruct] = newIdentNode "selfDestructEIP2929"
result[SLoad] = newIdentNode "sloadEIP2929"
result[SStore] = newIdentNode "sstoreEIP2929"
let BerlinOpDispatch {.compileTime.}: array[Op, NimNode] = genBerlinJumpTable(IstanbulOpDispatch)
proc opTableToCaseStmt(opTable: array[Op, NimNode], c: NimNode): NimNode =

View File

@ -28,6 +28,12 @@ type
paBlsMapG1
paBlsMapG2
iterator activePrecompiles*(): EthAddress =
var res: EthAddress
for c in PrecompileAddresses.low..PrecompileAddresses.high:
res[^1] = c.byte
yield res
proc getSignature(computation: Computation): (array[32, byte], Signature) =
# input is Hash, V, R, S
template data: untyped = computation.msg.data