diff --git a/nimbus/db/access_list.nim b/nimbus/db/access_list.nim index ee6659351..82ebaa18f 100644 --- a/nimbus/db/access_list.nim +++ b/nimbus/db/access_list.nim @@ -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() diff --git a/nimbus/db/accounts_cache.nim b/nimbus/db/accounts_cache.nim index 46b9e4fc9..48256aded 100644 --- a/nimbus/db/accounts_cache.nim +++ b/nimbus/db/accounts_cache.nim @@ -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) = diff --git a/nimbus/p2p/executor.nim b/nimbus/p2p/executor.nim index a7d318982..d34cfc55f 100644 --- a/nimbus/p2p/executor.nim +++ b/nimbus/p2p/executor.nim @@ -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: diff --git a/nimbus/vm/computation.nim b/nimbus/vm/computation.nim index ba680ea6f..65f9e3e83 100644 --- a/nimbus/vm/computation.nim +++ b/nimbus/vm/computation.nim @@ -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() diff --git a/nimbus/vm/interpreter/gas_costs.nim b/nimbus/vm/interpreter/gas_costs.nim index 0d23eb16d..2029ca3e3 100644 --- a/nimbus/vm/interpreter/gas_costs.nim +++ b/nimbus/vm/interpreter/gas_costs.nim @@ -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 diff --git a/nimbus/vm/interpreter/opcodes_impl.nim b/nimbus/vm/interpreter/opcodes_impl.nim index 14dcbba5d..d9f0f8086 100644 --- a/nimbus/vm/interpreter/opcodes_impl.nim +++ b/nimbus/vm/interpreter/opcodes_impl.nim @@ -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) diff --git a/nimbus/vm/interpreter_dispatch.nim b/nimbus/vm/interpreter_dispatch.nim index 8439c4c68..d7d0bd97e 100644 --- a/nimbus/vm/interpreter_dispatch.nim +++ b/nimbus/vm/interpreter_dispatch.nim @@ -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 = diff --git a/nimbus/vm/precompiles.nim b/nimbus/vm/precompiles.nim index 9db0377e4..d8abca624 100644 --- a/nimbus/vm/precompiles.nim +++ b/nimbus/vm/precompiles.nim @@ -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