From e631c39cb4abca63bbbce52052df1c5fc85f5c75 Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Wed, 19 Jan 2022 10:37:34 -0800 Subject: [PATCH] l2geth: Add berlin hardfork --- .changeset/early-baboons-look.md | 5 + .changeset/many-cougars-scream.md | 5 + .changeset/smooth-points-appear.md | 5 + integration-tests/contracts/Precompiles.sol | 23 ++ .../contracts/SelfDestruction.sol | 15 ++ integration-tests/hardhat.config.ts | 1 + integration-tests/tasks/check-block-hashes.ts | 58 +++++ .../test/env-specific/nightly.spec.ts | 109 +++++++++ integration-tests/test/hardfork.spec.ts | 209 ++++++++++++++++++ integration-tests/test/rpc.spec.ts | 45 ++-- integration-tests/test/shared/utils.ts | 12 + .../accounts/abi/bind/backends/simulated.go | 17 +- l2geth/core/evm.go | 1 - l2geth/core/state/access_list.go | 136 ++++++++++++ l2geth/core/state/journal.go | 34 +++ l2geth/core/state/statedb.go | 73 ++++++ l2geth/core/state/statedb_test.go | 174 +++++++++++++++ l2geth/core/state_transition.go | 6 + l2geth/core/types/access_list_tx.go | 41 ++++ l2geth/core/types/transaction.go | 19 +- l2geth/core/vm/contracts.go | 123 +++++++++-- l2geth/core/vm/eips.go | 51 ++++- l2geth/core/vm/errors.go | 1 + l2geth/core/vm/evm.go | 12 +- l2geth/core/vm/interface.go | 10 + l2geth/core/vm/interpreter.go | 9 +- l2geth/core/vm/jump_table.go | 10 + l2geth/core/vm/logger.go | 75 +++++++ l2geth/core/vm/operations_acl.go | 182 +++++++++++++-- l2geth/core/vm/operations_acl_optimism.go | 103 +++++++++ l2geth/core/vm/runtime/runtime.go | 12 +- l2geth/core/vm/runtime/runtime_test.go | 115 ++++++++++ l2geth/eth/api_backend.go | 26 +-- l2geth/eth/api_tracer.go | 54 ++++- l2geth/interfaces.go | 2 + l2geth/internal/ethapi/api.go | 14 +- l2geth/internal/ethapi/backend.go | 2 +- l2geth/les/api_backend.go | 2 +- l2geth/params/config.go | 24 +- l2geth/params/protocol_params.go | 42 ++-- l2geth/scripts/init.sh | 47 ++++ l2geth/scripts/start.sh | 19 +- l2geth/tests/state_test_util.go | 10 + packages/contracts/bin/take-dump.ts | 4 + packages/contracts/src/make-genesis.ts | 3 + 45 files changed, 1800 insertions(+), 140 deletions(-) create mode 100644 .changeset/early-baboons-look.md create mode 100644 .changeset/many-cougars-scream.md create mode 100644 .changeset/smooth-points-appear.md create mode 100644 integration-tests/contracts/Precompiles.sol create mode 100644 integration-tests/contracts/SelfDestruction.sol create mode 100644 integration-tests/tasks/check-block-hashes.ts create mode 100644 integration-tests/test/env-specific/nightly.spec.ts create mode 100644 integration-tests/test/hardfork.spec.ts create mode 100644 l2geth/core/state/access_list.go create mode 100644 l2geth/core/types/access_list_tx.go create mode 100644 l2geth/core/vm/operations_acl_optimism.go create mode 100755 l2geth/scripts/init.sh diff --git a/.changeset/early-baboons-look.md b/.changeset/early-baboons-look.md new file mode 100644 index 000000000000..41f29ac2f4be --- /dev/null +++ b/.changeset/early-baboons-look.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/integration-tests': patch +--- + +Add in berlin hardfork tests diff --git a/.changeset/many-cougars-scream.md b/.changeset/many-cougars-scream.md new file mode 100644 index 000000000000..2f042ca8cdf8 --- /dev/null +++ b/.changeset/many-cougars-scream.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/l2geth': patch +--- + +Implement berlin hardfork diff --git a/.changeset/smooth-points-appear.md b/.changeset/smooth-points-appear.md new file mode 100644 index 000000000000..93f50e490579 --- /dev/null +++ b/.changeset/smooth-points-appear.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/contracts': patch +--- + +Add berlin hardfork config to genesis creation diff --git a/integration-tests/contracts/Precompiles.sol b/integration-tests/contracts/Precompiles.sol new file mode 100644 index 000000000000..52fccc0f9559 --- /dev/null +++ b/integration-tests/contracts/Precompiles.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +contract Precompiles { + function expmod(uint256 base, uint256 e, uint256 m) public returns (uint256 o) { + assembly { + // define pointer + let p := mload(0x40) + // store data assembly-favouring ways + mstore(p, 0x20) // Length of Base + mstore(add(p, 0x20), 0x20) // Length of Exponent + mstore(add(p, 0x40), 0x20) // Length of Modulus + mstore(add(p, 0x60), base) // Base + mstore(add(p, 0x80), e) // Exponent + mstore(add(p, 0xa0), m) // Modulus + if iszero(staticcall(sub(gas(), 2000), 0x05, p, 0xc0, p, 0x20)) { + revert(0, 0) + } + // data + o := mload(p) + } + } +} diff --git a/integration-tests/contracts/SelfDestruction.sol b/integration-tests/contracts/SelfDestruction.sol new file mode 100644 index 000000000000..2a9666a48565 --- /dev/null +++ b/integration-tests/contracts/SelfDestruction.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +contract SelfDestruction { + bytes32 public data = 0x0000000000000000000000000000000000000000000000000000000061626364; + + function setData(bytes32 _data) public { + data = _data; + } + + function destruct() public { + address payable self = payable(address(this)); + selfdestruct(self); + } +} diff --git a/integration-tests/hardhat.config.ts b/integration-tests/hardhat.config.ts index 436206583e7d..6153a8ffc8b1 100644 --- a/integration-tests/hardhat.config.ts +++ b/integration-tests/hardhat.config.ts @@ -4,6 +4,7 @@ import { HardhatUserConfig } from 'hardhat/types' import '@nomiclabs/hardhat-ethers' import '@nomiclabs/hardhat-waffle' import 'hardhat-gas-reporter' +import './tasks/check-block-hashes' import { envConfig } from './test/shared/utils' const enableGasReport = !!process.env.ENABLE_GAS_REPORT diff --git a/integration-tests/tasks/check-block-hashes.ts b/integration-tests/tasks/check-block-hashes.ts new file mode 100644 index 000000000000..c30d67cfdcc2 --- /dev/null +++ b/integration-tests/tasks/check-block-hashes.ts @@ -0,0 +1,58 @@ +import { task } from 'hardhat/config' +import { providers } from 'ethers' + +import { die, logStderr } from '../test/shared/utils' + +task( + 'check-block-hashes', + 'Compares the block hashes of two different replicas.' +) + .addPositionalParam('replicaA', 'The first replica') + .addPositionalParam('replicaB', 'The second replica') + .setAction(async ({ replicaA, replicaB }) => { + const providerA = new providers.JsonRpcProvider(replicaA) + const providerB = new providers.JsonRpcProvider(replicaB) + + let netA + let netB + try { + netA = await providerA.getNetwork() + } catch (e) { + console.error(`Error getting network from ${replicaA}:`) + die(e) + } + try { + netB = await providerA.getNetwork() + } catch (e) { + console.error(`Error getting network from ${replicaB}:`) + die(e) + } + + if (netA.chainId !== netB.chainId) { + die('Chain IDs do not match') + return + } + + logStderr('Getting block height.') + const heightA = await providerA.getBlockNumber() + const heightB = await providerB.getBlockNumber() + const endHeight = Math.min(heightA, heightB) + logStderr(`Chose block height: ${endHeight}`) + + for (let n = endHeight; n >= 1; n--) { + const blocks = await Promise.all([ + providerA.getBlock(n), + providerB.getBlock(n), + ]) + + const hashA = blocks[0].hash + const hashB = blocks[1].hash + if (hashA !== hashB) { + console.log(`HASH MISMATCH! block=${n} a=${hashA} b=${hashB}`) + continue + } + + console.log(`HASHES OK! block=${n} hash=${hashA}`) + return + } + }) diff --git a/integration-tests/test/env-specific/nightly.spec.ts b/integration-tests/test/env-specific/nightly.spec.ts new file mode 100644 index 000000000000..0ee5e15a344f --- /dev/null +++ b/integration-tests/test/env-specific/nightly.spec.ts @@ -0,0 +1,109 @@ +import { Contract } from 'ethers' +import { ethers } from 'hardhat' + +import { OptimismEnv } from '../shared/env' +import { expect } from '../shared/setup' +import { traceToGasByOpcode } from '../hardfork.spec' +import { envConfig } from '../shared/utils' + +describe('Nightly', () => { + before(async function () { + if (!envConfig.RUN_NIGHTLY_TESTS) { + this.skip() + } + }) + + describe('Berlin Hardfork', () => { + let env: OptimismEnv + let SimpleStorage: Contract + let Precompiles: Contract + + before(async () => { + env = await OptimismEnv.new() + SimpleStorage = await ethers.getContractAt( + 'SimpleStorage', + '0xE08fFE40748367ddc29B5A154331C73B7FCC13bD', + env.l2Wallet + ) + + Precompiles = await ethers.getContractAt( + 'Precompiles', + '0x32E8Fbfd0C0bd1117112b249e997C27b0EC7cba2', + env.l2Wallet + ) + }) + + describe('EIP-2929', () => { + it('should update the gas schedule', async () => { + const tx = await SimpleStorage.setValueNotXDomain( + `0x${'77'.repeat(32)}` + ) + await tx.wait() + + const berlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash] + ) + const preBerlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + ['0x2bb346f53544c5711502fbcbd1d78dc4fb61ca5f9390b9d6d67f1a3a77de7c39'] + ) + + const berlinSstoreCosts = traceToGasByOpcode( + berlinTrace.structLogs, + 'SSTORE' + ) + const preBerlinSstoreCosts = traceToGasByOpcode( + preBerlinTrace.structLogs, + 'SSTORE' + ) + expect(preBerlinSstoreCosts).to.eq(80000) + expect(berlinSstoreCosts).to.eq(5300) + }) + }) + + describe('EIP-2565', () => { + it('should become cheaper', async () => { + const tx = await Precompiles.expmod(64, 1, 64, { gasLimit: 5_000_000 }) + await tx.wait() + + const berlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash] + ) + const preBerlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + ['0x7ba7d273449b0062448fe5e7426bb169a032ce189d0e3781eb21079e85c2d7d5'] + ) + expect(berlinTrace.gas).to.be.lt(preBerlinTrace.gas) + }) + }) + + describe('Berlin Additional (L1 London)', () => { + describe('EIP-3529', () => { + it('should remove the refund for selfdestruct', async () => { + const Factory__SelfDestruction = await ethers.getContractFactory( + 'SelfDestruction', + env.l2Wallet + ) + + const SelfDestruction = await Factory__SelfDestruction.deploy() + const tx = await SelfDestruction.destruct({ gasLimit: 5_000_000 }) + await tx.wait() + + const berlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash] + ) + const preBerlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [ + '0x948667349f00e996d9267e5c30d72fe7202a0ecdb88bab191e9a022bba6e4cb3', + ] + ) + expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas) + }) + }) + }) + }) +}) diff --git a/integration-tests/test/hardfork.spec.ts b/integration-tests/test/hardfork.spec.ts new file mode 100644 index 000000000000..43f1d89ef440 --- /dev/null +++ b/integration-tests/test/hardfork.spec.ts @@ -0,0 +1,209 @@ +import { Contract, BigNumber } from 'ethers' +import { ethers } from 'hardhat' + +import { expect } from './shared/setup' +import { OptimismEnv } from './shared/env' + +export const traceToGasByOpcode = (structLogs, opcode) => { + let gas = 0 + const opcodes = [] + for (const log of structLogs) { + if (log.op === opcode) { + opcodes.push(opcode) + gas += log.gasCost + } + } + return gas +} + +describe('Hard forks', () => { + let env: OptimismEnv + let SimpleStorage: Contract + let SelfDestruction: Contract + let Precompiles: Contract + + before(async () => { + env = await OptimismEnv.new() + const Factory__SimpleStorage = await ethers.getContractFactory( + 'SimpleStorage', + env.l2Wallet + ) + SimpleStorage = await Factory__SimpleStorage.deploy() + + const Factory__SelfDestruction = await ethers.getContractFactory( + 'SelfDestruction', + env.l2Wallet + ) + SelfDestruction = await Factory__SelfDestruction.deploy() + + const Factory__Precompiles = await ethers.getContractFactory( + 'Precompiles', + env.l2Wallet + ) + Precompiles = await Factory__Precompiles.deploy() + }) + + describe('Berlin', () => { + // https://eips.ethereum.org/EIPS/eip-2929 + describe('EIP-2929', () => { + it('should update the gas schedule', async () => { + // Get the tip height + const tip = await env.l2Provider.getBlock('latest') + + // send a transaction to be able to trace + const tx = await SimpleStorage.setValueNotXDomain( + `0x${'77'.repeat(32)}` + ) + await tx.wait() + + // Collect the traces + const berlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash] + ) + const preBerlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash, { overrides: { berlinBlock: tip.number * 2 } }] + ) + expect(berlinTrace.gas).to.not.eq(preBerlinTrace.gas) + + const berlinSstoreCosts = traceToGasByOpcode( + berlinTrace.structLogs, + 'SSTORE' + ) + const preBerlinSstoreCosts = traceToGasByOpcode( + preBerlinTrace.structLogs, + 'SSTORE' + ) + expect(berlinSstoreCosts).to.not.eq(preBerlinSstoreCosts) + }) + }) + + // https://eips.ethereum.org/EIPS/eip-2565 + describe('EIP-2565', async () => { + it('should become cheaper', async () => { + const tip = await env.l2Provider.getBlock('latest') + + const tx = await Precompiles.expmod(64, 1, 64, { gasLimit: 5_000_000 }) + await tx.wait() + + const berlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash] + ) + const preBerlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash, { overrides: { berlinBlock: tip.number * 2 } }] + ) + expect(berlinTrace.gas).to.be.lt(preBerlinTrace.gas) + }) + }) + }) + + // Optimism includes EIP-3529 as part of its Berlin hardfork. It is part + // of the London hardfork on L1. Since it is coupled to the Berlin + // hardfork, some of its functionality cannot be directly tests via + // integration tests since we can currently only turn on all of the Berlin + // EIPs or none of the Berlin EIPs + describe('Berlin Additional (L1 London)', () => { + // https://eips.ethereum.org/EIPS/eip-3529 + describe('EIP-3529', async () => { + const bytes32Zero = '0x' + '00'.repeat(32) + const bytes32NonZero = '0x' + 'ff'.repeat(32) + + it('should lower the refund for storage clear', async () => { + const tip = await env.l2Provider.getBlock('latest') + + const value = await SelfDestruction.callStatic.data() + // It should be non zero + expect(BigNumber.from(value).toNumber()).to.not.eq(0) + + { + // Set the value to another non zero value + // Going from non zero to non zero + const tx = await SelfDestruction.setData(bytes32NonZero, { + gasLimit: 5_000_000, + }) + await tx.wait() + + const berlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash] + ) + const preBerlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash, { overrides: { berlinBlock: tip.number * 2 } }] + ) + // Updating a non zero value to another non zero value should not change + expect(berlinTrace.gas).to.deep.eq(preBerlinTrace.gas) + } + + { + // Set the value to the zero value + // Going from non zero to zero + const tx = await SelfDestruction.setData(bytes32Zero, { + gasLimit: 5_000_000, + }) + await tx.wait() + + const berlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash] + ) + const preBerlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash, { overrides: { berlinBlock: tip.number * 2 } }] + ) + + // Updating to a zero value from a non zero value should becomes + // more expensive due to this change being coupled with EIP-2929 + expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas) + } + + { + // Set the value to a non zero value + // Going from zero to non zero + const tx = await SelfDestruction.setData(bytes32NonZero, { + gasLimit: 5_000_000, + }) + await tx.wait() + + const berlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash] + ) + const preBerlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash, { overrides: { berlinBlock: tip.number * 2 } }] + ) + + // Updating to a zero value from a non zero value should becomes + // more expensive due to this change being coupled with EIP-2929 + expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas) + } + }) + + it('should remove the refund for selfdestruct', async () => { + const tip = await env.l2Provider.getBlock('latest') + + // Send transaction with a large gas limit + const tx = await SelfDestruction.destruct({ gasLimit: 5_000_000 }) + await tx.wait() + + const berlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash] + ) + const preBerlinTrace = await env.l2Provider.send( + 'debug_traceTransaction', + [tx.hash, { overrides: { berlinBlock: tip.number * 2 } }] + ) + + // The berlin execution should use more gas than the pre Berlin + // execution because there is no longer a selfdestruct gas + // refund + expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas) + }) + }) + }) +}) diff --git a/integration-tests/test/rpc.spec.ts b/integration-tests/test/rpc.spec.ts index b31edf6fb3d6..56d46cc83162 100644 --- a/integration-tests/test/rpc.spec.ts +++ b/integration-tests/test/rpc.spec.ts @@ -28,6 +28,7 @@ describe('Basic RPC tests', () => { const provider = injectL2Context(l2Provider) let Reverter: Contract + let ValueContext: Contract let revertMessage: string let revertingTx: TransactionRequest let revertingDeployTx: TransactionRequest @@ -53,6 +54,12 @@ describe('Basic RPC tests', () => { revertingDeployTx = { data: Factory__ConstructorReverter.bytecode, } + + // Deploy a contract to check msg.value of the call + const Factory__ValueContext: ContractFactory = + await ethers.getContractFactory('ValueContext', wallet) + ValueContext = await Factory__ValueContext.deploy() + await ValueContext.deployTransaction.wait() }) describe('eth_sendRawTransaction', () => { @@ -209,12 +216,6 @@ describe('Basic RPC tests', () => { }) it('should allow eth_calls with nonzero value', async () => { - // Deploy a contract to check msg.value of the call - const Factory__ValueContext: ContractFactory = - await ethers.getContractFactory('ValueContext', wallet) - const ValueContext: Contract = await Factory__ValueContext.deploy() - await ValueContext.deployTransaction.wait() - // Fund account to call from const from = wallet.address const value = 15 @@ -234,12 +235,6 @@ describe('Basic RPC tests', () => { // https://github.com/ethereum-optimism/optimism/issues/1998 it('should use address(0) as the default "from" value', async () => { - // Deploy a contract to check msg.caller - const Factory__ValueContext: ContractFactory = - await ethers.getContractFactory('ValueContext', wallet) - const ValueContext: Contract = await Factory__ValueContext.deploy() - await ValueContext.deployTransaction.wait() - // Do the call and check msg.sender const data = ValueContext.interface.encodeFunctionData('getCaller') const res = await provider.call({ @@ -256,12 +251,6 @@ describe('Basic RPC tests', () => { }) it('should correctly use the "from" value', async () => { - // Deploy a contract to check msg.caller - const Factory__ValueContext: ContractFactory = - await ethers.getContractFactory('ValueContext', wallet) - const ValueContext: Contract = await Factory__ValueContext.deploy() - await ValueContext.deployTransaction.wait() - const from = wallet.address // Do the call and check msg.sender @@ -278,6 +267,15 @@ describe('Basic RPC tests', () => { ) expect(paddedRes).to.eq(from) }) + + it('should be deterministic', async () => { + let res = await ValueContext.callStatic.getSelfBalance() + for (let i = 0; i < 10; i++) { + const next = await ValueContext.callStatic.getSelfBalance() + expect(res.toNumber()).to.deep.eq(next.toNumber()) + res = next + } + }) }) describe('eth_getTransactionReceipt', () => { @@ -450,7 +448,7 @@ describe('Basic RPC tests', () => { }) describe('eth_estimateGas', () => { - it('gas estimation is deterministic', async () => { + it('simple send gas estimation is deterministic', async () => { let lastEstimate: BigNumber for (let i = 0; i < 10; i++) { const estimate = await l2Provider.estimateGas({ @@ -466,6 +464,15 @@ describe('Basic RPC tests', () => { } }) + it('deterministic gas estimation for evm execution', async () => { + let res = await ValueContext.estimateGas.getSelfBalance() + for (let i = 0; i < 10; i++) { + const next = await ValueContext.estimateGas.getSelfBalance() + expect(res.toNumber()).to.deep.eq(next.toNumber()) + res = next + } + }) + it('should return a gas estimate for txs with empty data', async () => { const estimate = await l2Provider.estimateGas({ to: defaultTransactionFactory().to, diff --git a/integration-tests/test/shared/utils.ts b/integration-tests/test/shared/utils.ts index baaebf9492f8..0a10c4adba0d 100644 --- a/integration-tests/test/shared/utils.ts +++ b/integration-tests/test/shared/utils.ts @@ -93,6 +93,9 @@ const procEnv = cleanEnv(process.env, { RUN_STRESS_TESTS: bool({ default: true, }), + RUN_NIGHTLY_TESTS: bool({ + default: false, + }), MOCHA_TIMEOUT: num({ default: 120_000, @@ -264,3 +267,12 @@ export const isHardhat = async () => { const chainId = await l1Wallet.getChainId() return chainId === HARDHAT_CHAIN_ID } + +export const die = (...args) => { + console.log(...args) + process.exit(1) +} + +export const logStderr = (msg: string) => { + process.stderr.write(`${msg}\n`) +} diff --git a/l2geth/accounts/abi/bind/backends/simulated.go b/l2geth/accounts/abi/bind/backends/simulated.go index c79a292d4e13..2d81e36fa642 100644 --- a/l2geth/accounts/abi/bind/backends/simulated.go +++ b/l2geth/accounts/abi/bind/backends/simulated.go @@ -592,14 +592,15 @@ type callmsg struct { ethereum.CallMsg } -func (m callmsg) From() common.Address { return m.CallMsg.From } -func (m callmsg) Nonce() uint64 { return 0 } -func (m callmsg) CheckNonce() bool { return false } -func (m callmsg) To() *common.Address { return m.CallMsg.To } -func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } -func (m callmsg) Gas() uint64 { return m.CallMsg.Gas } -func (m callmsg) Value() *big.Int { return m.CallMsg.Value } -func (m callmsg) Data() []byte { return m.CallMsg.Data } +func (m callmsg) From() common.Address { return m.CallMsg.From } +func (m callmsg) Nonce() uint64 { return 0 } +func (m callmsg) CheckNonce() bool { return false } +func (m callmsg) To() *common.Address { return m.CallMsg.To } +func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } +func (m callmsg) Gas() uint64 { return m.CallMsg.Gas } +func (m callmsg) Value() *big.Int { return m.CallMsg.Value } +func (m callmsg) Data() []byte { return m.CallMsg.Data } +func (m callmsg) AccessList() types.AccessList { return m.CallMsg.AccessList } // UsingOVM // These getters return OVM specific fields diff --git a/l2geth/core/evm.go b/l2geth/core/evm.go index 65c021c728f0..1121809607dd 100644 --- a/l2geth/core/evm.go +++ b/l2geth/core/evm.go @@ -48,7 +48,6 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author } if rcfg.UsingOVM { // When using the OVM, we must: - // - Set the BlockNumber to be the msg.L1BlockNumber // - Set the Time to be the msg.L1Timestamp return vm.Context{ CanTransfer: CanTransfer, diff --git a/l2geth/core/state/access_list.go b/l2geth/core/state/access_list.go new file mode 100644 index 000000000000..78f0799fa2ff --- /dev/null +++ b/l2geth/core/state/access_list.go @@ -0,0 +1,136 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package state + +import ( + "github.com/ethereum-optimism/optimism/l2geth/common" +) + +type accessList struct { + addresses map[common.Address]int + slots []map[common.Hash]struct{} +} + +// ContainsAddress returns true if the address is in the access list. +func (al *accessList) ContainsAddress(address common.Address) bool { + _, ok := al.addresses[address] + return ok +} + +// Contains checks if a slot within an account is present in the access list, returning +// separate flags for the presence of the account and the slot respectively. +func (al *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { + idx, ok := al.addresses[address] + if !ok { + // no such address (and hence zero slots) + return false, false + } + if idx == -1 { + // address yes, but no slots + return true, false + } + _, slotPresent = al.slots[idx][slot] + return true, slotPresent +} + +// newAccessList creates a new accessList. +func newAccessList() *accessList { + return &accessList{ + addresses: make(map[common.Address]int), + } +} + +// Copy creates an independent copy of an accessList. +func (a *accessList) Copy() *accessList { + cp := newAccessList() + for k, v := range a.addresses { + cp.addresses[k] = v + } + cp.slots = make([]map[common.Hash]struct{}, len(a.slots)) + for i, slotMap := range a.slots { + newSlotmap := make(map[common.Hash]struct{}, len(slotMap)) + for k := range slotMap { + newSlotmap[k] = struct{}{} + } + cp.slots[i] = newSlotmap + } + return cp +} + +// AddAddress adds an address to the access list, and returns 'true' if the operation +// caused a change (addr was not previously in the list). +func (al *accessList) AddAddress(address common.Address) bool { + if _, present := al.addresses[address]; present { + return false + } + al.addresses[address] = -1 + return true +} + +// AddSlot adds the specified (addr, slot) combo to the access list. +// Return values are: +// - address added +// - slot added +// For any 'true' value returned, a corresponding journal entry must be made. +func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) { + idx, addrPresent := al.addresses[address] + if !addrPresent || idx == -1 { + // Address not present, or addr present but no slots there + al.addresses[address] = len(al.slots) + slotmap := map[common.Hash]struct{}{slot: {}} + al.slots = append(al.slots, slotmap) + return !addrPresent, true + } + // There is already an (address,slot) mapping + slotmap := al.slots[idx] + if _, ok := slotmap[slot]; !ok { + slotmap[slot] = struct{}{} + // Journal add slot change + return false, true + } + // No changes required + return false, false +} + +// DeleteSlot removes an (address, slot)-tuple from the access list. +// This operation needs to be performed in the same order as the addition happened. +// This method is meant to be used by the journal, which maintains ordering of +// operations. +func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) { + idx, addrOk := al.addresses[address] + // There are two ways this can fail + if !addrOk { + panic("reverting slot change, address not present in list") + } + slotmap := al.slots[idx] + delete(slotmap, slot) + // If that was the last (first) slot, remove it + // Since additions and rollbacks are always performed in order, + // we can delete the item without worrying about screwing up later indices + if len(slotmap) == 0 { + al.slots = al.slots[:idx] + al.addresses[address] = -1 + } +} + +// DeleteAddress removes an address from the access list. This operation +// needs to be performed in the same order as the addition happened. +// This method is meant to be used by the journal, which maintains ordering of +// operations. +func (al *accessList) DeleteAddress(address common.Address) { + delete(al.addresses, address) +} diff --git a/l2geth/core/state/journal.go b/l2geth/core/state/journal.go index b542bfdbb21a..7319881d39cc 100644 --- a/l2geth/core/state/journal.go +++ b/l2geth/core/state/journal.go @@ -129,6 +129,15 @@ type ( touchChange struct { account *common.Address } + + // Changes to the access list + accessListAddAccountChange struct { + address *common.Address + } + accessListAddSlotChange struct { + address *common.Address + slot *common.Hash + } ) func (ch createObjectChange) revert(s *StateDB) { @@ -230,3 +239,28 @@ func (ch addPreimageChange) revert(s *StateDB) { func (ch addPreimageChange) dirtied() *common.Address { return nil } + +func (ch accessListAddAccountChange) revert(s *StateDB) { + /* + One important invariant here, is that whenever a (addr, slot) is added, if the + addr is not already present, the add causes two journal entries: + - one for the address, + - one for the (address,slot) + Therefore, when unrolling the change, we can always blindly delete the + (addr) at this point, since no storage adds can remain when come upon + a single (addr) change. + */ + s.accessList.DeleteAddress(*ch.address) +} + +func (ch accessListAddAccountChange) dirtied() *common.Address { + return nil +} + +func (ch accessListAddSlotChange) revert(s *StateDB) { + s.accessList.DeleteSlot(*ch.address, *ch.slot) +} + +func (ch accessListAddSlotChange) dirtied() *common.Address { + return nil +} diff --git a/l2geth/core/state/statedb.go b/l2geth/core/state/statedb.go index 0e50ca8392ff..a15fee1141ae 100644 --- a/l2geth/core/state/statedb.go +++ b/l2geth/core/state/statedb.go @@ -100,6 +100,9 @@ type StateDB struct { preimages map[common.Hash][]byte + // Per-transaction access list + accessList *accessList + // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. journal *journal @@ -132,6 +135,7 @@ func New(root common.Hash, db Database) (*StateDB, error) { logs: make(map[common.Hash][]*types.Log), preimages: make(map[common.Hash][]byte), journal: newJournal(), + accessList: newAccessList(), }, nil } @@ -163,6 +167,7 @@ func (s *StateDB) Reset(root common.Hash) error { s.logs = make(map[common.Hash][]*types.Log) s.logSize = 0 s.preimages = make(map[common.Hash][]byte) + s.accessList = newAccessList() s.clearJournalAndRefund() return nil } @@ -673,6 +678,13 @@ func (s *StateDB) Copy() *StateDB { for hash, preimage := range s.preimages { state.preimages[hash] = preimage } + // Do we need to copy the access list? In practice: No. At the start of a + // transaction, the access list is empty. In practice, we only ever copy state + // _between_ transactions/blocks, never in the middle of a transaction. + // However, it doesn't cost us much to copy an empty list, so we do it anyway + // to not blow up if we ever decide copy it in the middle of a transaction + state.accessList = s.accessList.Copy() + return state } @@ -764,6 +776,7 @@ func (s *StateDB) Prepare(thash, bhash common.Hash, ti int) { s.thash = thash s.bhash = bhash s.txIndex = ti + s.accessList = newAccessList() } func (s *StateDB) clearJournalAndRefund() { @@ -815,3 +828,63 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { return nil }) } + +// PrepareAccessList handles the preparatory steps for executing a state transition with +// regards to both EIP-2929 and EIP-2930: +// +// - Add sender to access list (2929) +// - Add destination to access list (2929) +// - Add precompiles to access list (2929) +// - Add the contents of the optional tx access list (2930) +// +// This method should only be called if Berlin/2929+2930 is applicable at the current number. +func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { + s.AddAddressToAccessList(sender) + if dst != nil { + s.AddAddressToAccessList(*dst) + } + for _, addr := range precompiles { + s.AddAddressToAccessList(addr) + } + for _, el := range list { + s.AddAddressToAccessList(el.Address) + for _, key := range el.StorageKeys { + s.AddSlotToAccessList(el.Address, key) + } + } +} + +// AddAddressToAccessList adds the given address to the access list +func (s *StateDB) AddAddressToAccessList(addr common.Address) { + if s.accessList.AddAddress(addr) { + s.journal.append(accessListAddAccountChange{&addr}) + } +} + +// AddSlotToAccessList adds the given (address, slot)-tuple to the access list +func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { + addrMod, slotMod := s.accessList.AddSlot(addr, slot) + if addrMod { + // In practice, this should not happen, since there is no way to enter the + // scope of 'address' without having the 'address' become already added + // to the access list (via call-variant, create, etc). + // Better safe than sorry, though + s.journal.append(accessListAddAccountChange{&addr}) + } + if slotMod { + s.journal.append(accessListAddSlotChange{ + address: &addr, + slot: &slot, + }) + } +} + +// AddressInAccessList returns true if the given address is in the access list. +func (s *StateDB) AddressInAccessList(addr common.Address) bool { + return s.accessList.ContainsAddress(addr) +} + +// SlotInAccessList returns true if the given (address, slot)-tuple is in the access list. +func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { + return s.accessList.Contains(addr, slot) +} diff --git a/l2geth/core/state/statedb_test.go b/l2geth/core/state/statedb_test.go index 70542c634b8a..638b304b5247 100644 --- a/l2geth/core/state/statedb_test.go +++ b/l2geth/core/state/statedb_test.go @@ -680,3 +680,177 @@ func TestDeleteCreateRevert(t *testing.T) { t.Fatalf("self-destructed contract came alive") } } + +func TestStateDBAccessList(t *testing.T) { + // Some helpers + addr := func(a string) common.Address { + return common.HexToAddress(a) + } + slot := func(a string) common.Hash { + return common.HexToHash(a) + } + + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(common.Hash{}, db) + state.accessList = newAccessList() + + verifyAddrs := func(astrings ...string) { + t.Helper() + // convert to common.Address form + var addresses []common.Address + var addressMap = make(map[common.Address]struct{}) + for _, astring := range astrings { + address := addr(astring) + addresses = append(addresses, address) + addressMap[address] = struct{}{} + } + // Check that the given addresses are in the access list + for _, address := range addresses { + if !state.AddressInAccessList(address) { + t.Fatalf("expected %x to be in access list", address) + } + } + // Check that only the expected addresses are present in the acesslist + for address := range state.accessList.addresses { + if _, exist := addressMap[address]; !exist { + t.Fatalf("extra address %x in access list", address) + } + } + } + verifySlots := func(addrString string, slotStrings ...string) { + if !state.AddressInAccessList(addr(addrString)) { + t.Fatalf("scope missing address/slots %v", addrString) + } + var address = addr(addrString) + // convert to common.Hash form + var slots []common.Hash + var slotMap = make(map[common.Hash]struct{}) + for _, slotString := range slotStrings { + s := slot(slotString) + slots = append(slots, s) + slotMap[s] = struct{}{} + } + // Check that the expected items are in the access list + for i, s := range slots { + if _, slotPresent := state.SlotInAccessList(address, s); !slotPresent { + t.Fatalf("input %d: scope missing slot %v (address %v)", i, s, addrString) + } + } + // Check that no extra elements are in the access list + index := state.accessList.addresses[address] + if index >= 0 { + stateSlots := state.accessList.slots[index] + for s := range stateSlots { + if _, slotPresent := slotMap[s]; !slotPresent { + t.Fatalf("scope has extra slot %v (address %v)", s, addrString) + } + } + } + } + + state.AddAddressToAccessList(addr("aa")) // 1 + state.AddSlotToAccessList(addr("bb"), slot("01")) // 2,3 + state.AddSlotToAccessList(addr("bb"), slot("02")) // 4 + verifyAddrs("aa", "bb") + verifySlots("bb", "01", "02") + + // Make a copy + stateCopy1 := state.Copy() + if exp, got := 4, state.journal.length(); exp != got { + t.Fatalf("journal length mismatch: have %d, want %d", got, exp) + } + + // same again, should cause no journal entries + state.AddSlotToAccessList(addr("bb"), slot("01")) + state.AddSlotToAccessList(addr("bb"), slot("02")) + state.AddAddressToAccessList(addr("aa")) + if exp, got := 4, state.journal.length(); exp != got { + t.Fatalf("journal length mismatch: have %d, want %d", got, exp) + } + // some new ones + state.AddSlotToAccessList(addr("bb"), slot("03")) // 5 + state.AddSlotToAccessList(addr("aa"), slot("01")) // 6 + state.AddSlotToAccessList(addr("cc"), slot("01")) // 7,8 + state.AddAddressToAccessList(addr("cc")) + if exp, got := 8, state.journal.length(); exp != got { + t.Fatalf("journal length mismatch: have %d, want %d", got, exp) + } + + verifyAddrs("aa", "bb", "cc") + verifySlots("aa", "01") + verifySlots("bb", "01", "02", "03") + verifySlots("cc", "01") + + // now start rolling back changes + state.journal.revert(state, 7) + if _, ok := state.SlotInAccessList(addr("cc"), slot("01")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb", "cc") + verifySlots("aa", "01") + verifySlots("bb", "01", "02", "03") + + state.journal.revert(state, 6) + if state.AddressInAccessList(addr("cc")) { + t.Fatalf("addr present, expected missing") + } + verifyAddrs("aa", "bb") + verifySlots("aa", "01") + verifySlots("bb", "01", "02", "03") + + state.journal.revert(state, 5) + if _, ok := state.SlotInAccessList(addr("aa"), slot("01")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb") + verifySlots("bb", "01", "02", "03") + + state.journal.revert(state, 4) + if _, ok := state.SlotInAccessList(addr("bb"), slot("03")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb") + verifySlots("bb", "01", "02") + + state.journal.revert(state, 3) + if _, ok := state.SlotInAccessList(addr("bb"), slot("02")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb") + verifySlots("bb", "01") + + state.journal.revert(state, 2) + if _, ok := state.SlotInAccessList(addr("bb"), slot("01")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb") + + state.journal.revert(state, 1) + if state.AddressInAccessList(addr("bb")) { + t.Fatalf("addr present, expected missing") + } + verifyAddrs("aa") + + state.journal.revert(state, 0) + if state.AddressInAccessList(addr("aa")) { + t.Fatalf("addr present, expected missing") + } + if got, exp := len(state.accessList.addresses), 0; got != exp { + t.Fatalf("expected empty, got %d", got) + } + if got, exp := len(state.accessList.slots), 0; got != exp { + t.Fatalf("expected empty, got %d", got) + } + // Check the copy + // Make a copy + state = stateCopy1 + verifyAddrs("aa", "bb") + verifySlots("bb", "01", "02") + if got, exp := len(state.accessList.addresses), 2; got != exp { + t.Fatalf("expected empty, got %d", got) + } + if got, exp := len(state.accessList.slots), 1; got != exp { + t.Fatalf("expected empty, got %d", got) + } +} diff --git a/l2geth/core/state_transition.go b/l2geth/core/state_transition.go index 2ed1a450b4a6..c6c7ae0c9779 100644 --- a/l2geth/core/state_transition.go +++ b/l2geth/core/state_transition.go @@ -79,6 +79,7 @@ type Message interface { Nonce() uint64 CheckNonce() bool Data() []byte + AccessList() types.AccessList L1Timestamp() uint64 L1BlockNumber() *big.Int @@ -253,6 +254,11 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo vmerr error ) + // The access list gets created here + if rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber); rules.IsBerlin { + st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) + } + if contractCreation { ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value) } else { diff --git a/l2geth/core/types/access_list_tx.go b/l2geth/core/types/access_list_tx.go new file mode 100644 index 000000000000..aa29748fab2a --- /dev/null +++ b/l2geth/core/types/access_list_tx.go @@ -0,0 +1,41 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "github.com/ethereum-optimism/optimism/l2geth/common" +) + +//go:generate gencodec -type AccessTuple -out gen_access_tuple.go + +// AccessList is an EIP-2930 access list. +type AccessList []AccessTuple + +// AccessTuple is the element type of an access list. +type AccessTuple struct { + Address common.Address `json:"address" gencodec:"required"` + StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` +} + +// StorageKeys returns the total number of storage keys in the access list. +func (al AccessList) StorageKeys() int { + sum := 0 + for _, tuple := range al { + sum += len(tuple.StorageKeys) + } + return sum +} diff --git a/l2geth/core/types/transaction.go b/l2geth/core/types/transaction.go index 6d23a5c3409e..53a03836dd4d 100644 --- a/l2geth/core/types/transaction.go +++ b/l2geth/core/types/transaction.go @@ -479,6 +479,7 @@ type Message struct { gasPrice *big.Int data []byte checkNonce bool + accessList AccessList l1Timestamp uint64 l1BlockNumber *big.Int @@ -495,6 +496,7 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b gasPrice: gasPrice, data: data, checkNonce: checkNonce, + accessList: AccessList{}, l1Timestamp: l1Timestamp, l1BlockNumber: l1BlockNumber, @@ -502,14 +504,15 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b } } -func (m Message) From() common.Address { return m.from } -func (m Message) To() *common.Address { return m.to } -func (m Message) GasPrice() *big.Int { return m.gasPrice } -func (m Message) Value() *big.Int { return m.amount } -func (m Message) Gas() uint64 { return m.gasLimit } -func (m Message) Nonce() uint64 { return m.nonce } -func (m Message) Data() []byte { return m.data } -func (m Message) CheckNonce() bool { return m.checkNonce } +func (m Message) From() common.Address { return m.from } +func (m Message) To() *common.Address { return m.to } +func (m Message) GasPrice() *big.Int { return m.gasPrice } +func (m Message) Value() *big.Int { return m.amount } +func (m Message) Gas() uint64 { return m.gasLimit } +func (m Message) Nonce() uint64 { return m.nonce } +func (m Message) Data() []byte { return m.data } +func (m Message) CheckNonce() bool { return m.checkNonce } +func (m Message) AccessList() AccessList { return m.accessList } func (m Message) L1Timestamp() uint64 { return m.l1Timestamp } func (m Message) L1BlockNumber() *big.Int { return m.l1BlockNumber } diff --git a/l2geth/core/vm/contracts.go b/l2geth/core/vm/contracts.go index c9737a701636..398ce36367de 100644 --- a/l2geth/core/vm/contracts.go +++ b/l2geth/core/vm/contracts.go @@ -77,6 +77,55 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{9}): &blake2F{}, } +// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum +// contracts used in the Berlin release. +var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hash{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2F{}, +} + +var ( + PrecompiledAddressesBerlin []common.Address + PrecompiledAddressesIstanbul []common.Address + PrecompiledAddressesByzantium []common.Address + PrecompiledAddressesHomestead []common.Address +) + +func init() { + for k := range PrecompiledContractsHomestead { + PrecompiledAddressesHomestead = append(PrecompiledAddressesHomestead, k) + } + for k := range PrecompiledContractsByzantium { + PrecompiledAddressesByzantium = append(PrecompiledAddressesByzantium, k) + } + for k := range PrecompiledContractsIstanbul { + PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k) + } + for k := range PrecompiledContractsBerlin { + PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k) + } +} + +func ActivePrecompiles(rules params.Rules) []common.Address { + switch { + case rules.IsBerlin: + return PrecompiledAddressesBerlin + case rules.IsIstanbul: + return PrecompiledAddressesIstanbul + case rules.IsByzantium: + return PrecompiledAddressesByzantium + default: + return PrecompiledAddressesHomestead + } +} + // RunPrecompiledContract runs and evaluates the output of a precompiled contract. func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) { gas := p.RequiredGas(input) @@ -170,13 +219,18 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) { } // bigModExp implements a native big integer exponential modular operation. -type bigModExp struct{} +type bigModExp struct { + eip2565 bool +} var ( big1 = big.NewInt(1) + big3 = big.NewInt(3) big4 = big.NewInt(4) + big7 = big.NewInt(7) big8 = big.NewInt(8) big16 = big.NewInt(16) + big20 = big.NewInt(20) big32 = big.NewInt(32) big64 = big.NewInt(64) big96 = big.NewInt(96) @@ -186,6 +240,34 @@ var ( big199680 = big.NewInt(199680) ) +// modexpMultComplexity implements bigModexp multComplexity formula, as defined in EIP-198 +// +// def mult_complexity(x): +// if x <= 64: return x ** 2 +// elif x <= 1024: return x ** 2 // 4 + 96 * x - 3072 +// else: return x ** 2 // 16 + 480 * x - 199680 +// +// where is x is max(length_of_MODULUS, length_of_BASE) +func modexpMultComplexity(x *big.Int) *big.Int { + switch { + case x.Cmp(big64) <= 0: + x.Mul(x, x) // x ** 2 + case x.Cmp(big1024) <= 0: + // (x ** 2 // 4 ) + ( 96 * x - 3072) + x = new(big.Int).Add( + new(big.Int).Div(new(big.Int).Mul(x, x), big4), + new(big.Int).Sub(new(big.Int).Mul(big96, x), big3072), + ) + default: + // (x ** 2 // 16) + (480 * x - 199680) + x = new(big.Int).Add( + new(big.Int).Div(new(big.Int).Mul(x, x), big16), + new(big.Int).Sub(new(big.Int).Mul(big480, x), big199680), + ) + } + return x +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bigModExp) RequiredGas(input []byte) uint64 { var ( @@ -220,25 +302,36 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 { adjExpLen.Mul(big8, adjExpLen) } adjExpLen.Add(adjExpLen, big.NewInt(int64(msb))) - // Calculate the gas cost of the operation gas := new(big.Int).Set(math.BigMax(modLen, baseLen)) - switch { - case gas.Cmp(big64) <= 0: + if c.eip2565 { + // EIP-2565 has three changes + // 1. Different multComplexity (inlined here) + // in EIP-2565 (https://eips.ethereum.org/EIPS/eip-2565): + // + // def mult_complexity(x): + // ceiling(x/8)^2 + // + //where is x is max(length_of_MODULUS, length_of_BASE) + gas = gas.Add(gas, big7) + gas = gas.Div(gas, big8) gas.Mul(gas, gas) - case gas.Cmp(big1024) <= 0: - gas = new(big.Int).Add( - new(big.Int).Div(new(big.Int).Mul(gas, gas), big4), - new(big.Int).Sub(new(big.Int).Mul(big96, gas), big3072), - ) - default: - gas = new(big.Int).Add( - new(big.Int).Div(new(big.Int).Mul(gas, gas), big16), - new(big.Int).Sub(new(big.Int).Mul(big480, gas), big199680), - ) + + gas.Mul(gas, math.BigMax(adjExpLen, big1)) + // 2. Different divisor (`GQUADDIVISOR`) (3) + gas.Div(gas, big3) + if gas.BitLen() > 64 { + return math.MaxUint64 + } + // 3. Minimum price of 200 gas + if gas.Uint64() < 200 { + return 200 + } + return gas.Uint64() } + gas = modexpMultComplexity(gas) gas.Mul(gas, math.BigMax(adjExpLen, big1)) - gas.Div(gas, new(big.Int).SetUint64(params.ModExpQuadCoeffDiv)) + gas.Div(gas, big20) if gas.BitLen() > 64 { return math.MaxUint64 diff --git a/l2geth/core/vm/eips.go b/l2geth/core/vm/eips.go index 2a653b314c68..84ff5965f920 100644 --- a/l2geth/core/vm/eips.go +++ b/l2geth/core/vm/eips.go @@ -91,7 +91,11 @@ func enable2200(jt *JumpTable) { jt[SSTORE].dynamicGas = gasSStoreEIP2200 } -func enableMinimal2929(jt *JumpTable) { +// enable2929 enables "EIP-2929: Gas cost increases for state access opcodes" +// https://eips.ethereum.org/EIPS/eip-2929 +func enable2929(jt *JumpTable) { + jt[SSTORE].dynamicGas = gasSStoreEIP2929 + jt[SLOAD].constantGas = 0 jt[SLOAD].dynamicGas = gasSLoadEIP2929 @@ -124,3 +128,48 @@ func enableMinimal2929(jt *JumpTable) { jt[SELFDESTRUCT].constantGas = params.SelfdestructGasEIP150 jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929 } + +// enable3529 enabled "EIP-3529: Reduction in refunds": +// - Removes refunds for selfdestructs +// - Reduces refunds for SSTORE +// - Reduces max refunds to 20% gas +func enable3529(jt *JumpTable) { + jt[SSTORE].dynamicGas = gasSStoreEIP3529 + jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP3529 +} + +// UsingOVM +// Optimism specific changes +func enableMinimal2929(jt *JumpTable) { + jt[SLOAD].constantGas = 0 + jt[SLOAD].dynamicGas = gasSLoadEIP2929Optimism + + jt[EXTCODECOPY].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929Optimism + + jt[EXTCODESIZE].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheckOptimism + + jt[EXTCODEHASH].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheckOptimism + + jt[BALANCE].constantGas = params.WarmStorageReadCostEIP2929 + jt[BALANCE].dynamicGas = gasEip2929AccountCheckOptimism + + jt[CALL].constantGas = params.WarmStorageReadCostEIP2929 + jt[CALL].dynamicGas = gasCallEIP2929Optimism + + jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929 + jt[CALLCODE].dynamicGas = gasCallCodeEIP2929Optimism + + jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929 + jt[STATICCALL].dynamicGas = gasStaticCallEIP2929Optimism + + jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929 + jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929Optimism + + // This was previously part of the dynamic cost, but we're using it as a constantGas + // factor here + jt[SELFDESTRUCT].constantGas = params.SelfdestructGasEIP150 + jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929Optimism +} diff --git a/l2geth/core/vm/errors.go b/l2geth/core/vm/errors.go index 7f88f324ea13..2c7dc27ae5db 100644 --- a/l2geth/core/vm/errors.go +++ b/l2geth/core/vm/errors.go @@ -27,4 +27,5 @@ var ( ErrInsufficientBalance = errors.New("insufficient balance for transfer") ErrContractAddressCollision = errors.New("contract address collision") ErrNoCompatibleInterpreter = errors.New("no compatible interpreter") + ErrGasUintOverflow = errors.New("gas uint64 overflow") ) diff --git a/l2geth/core/vm/evm.go b/l2geth/core/vm/evm.go index 03a2f768670a..177e7c4f4c5f 100644 --- a/l2geth/core/vm/evm.go +++ b/l2geth/core/vm/evm.go @@ -55,6 +55,9 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err if evm.chainRules.IsIstanbul { precompiles = PrecompiledContractsIstanbul } + if evm.chainRules.IsBerlin { + precompiles = PrecompiledContractsBerlin + } if p := precompiles[*contract.CodeAddr]; p != nil { return RunPrecompiledContract(p, input, contract) } @@ -220,6 +223,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if evm.chainRules.IsIstanbul { precompiles = PrecompiledContractsIstanbul } + if evm.chainRules.IsBerlin { + precompiles = PrecompiledContractsBerlin + } if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 { // Calling a non existing account, don't do anything, but ping the tracer if evm.vmConfig.Debug && evm.depth == 0 { @@ -413,7 +419,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } nonce := evm.StateDB.GetNonce(caller.Address()) evm.StateDB.SetNonce(caller.Address(), nonce+1) - + // 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 + if evm.chainRules.IsBerlin { + evm.StateDB.AddAddressToAccessList(address) + } // Ensure there's no existing contract already at the designated address contractHash := evm.StateDB.GetCodeHash(address) if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { diff --git a/l2geth/core/vm/interface.go b/l2geth/core/vm/interface.go index 16d8e8ee7435..83582cd02875 100644 --- a/l2geth/core/vm/interface.go +++ b/l2geth/core/vm/interface.go @@ -57,6 +57,16 @@ type StateDB interface { // is defined according to EIP161 (balance = nonce = code = 0). Empty(common.Address) bool + PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) + AddressInAccessList(addr common.Address) bool + SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) + // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddAddressToAccessList(addr common.Address) + // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddSlotToAccessList(addr common.Address, slot common.Hash) + RevertToSnapshot(int) Snapshot() int diff --git a/l2geth/core/vm/interpreter.go b/l2geth/core/vm/interpreter.go index 596e49a5282a..fcfd032bf2f6 100644 --- a/l2geth/core/vm/interpreter.go +++ b/l2geth/core/vm/interpreter.go @@ -94,8 +94,13 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { if !cfg.JumpTable[STOP].valid { var jt JumpTable switch { + case evm.chainRules.IsBerlin: + jt = berlinInstructionSet case evm.chainRules.IsIstanbul: jt = istanbulInstructionSet + if rcfg.UsingOVM { + enableMinimal2929(&jt) + } case evm.chainRules.IsConstantinople: jt = constantinopleInstructionSet case evm.chainRules.IsByzantium: @@ -116,10 +121,6 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { log.Error("EIP activation failed", "eip", eip, "error", err) } } - // Enable minimal eip 2929 - if rcfg.UsingOVM { - enableMinimal2929(&jt) - } cfg.JumpTable = jt } diff --git a/l2geth/core/vm/jump_table.go b/l2geth/core/vm/jump_table.go index 3d8cd6f859ea..a58c6327a3b8 100644 --- a/l2geth/core/vm/jump_table.go +++ b/l2geth/core/vm/jump_table.go @@ -61,11 +61,21 @@ var ( byzantiumInstructionSet = newByzantiumInstructionSet() constantinopleInstructionSet = newConstantinopleInstructionSet() istanbulInstructionSet = newIstanbulInstructionSet() + berlinInstructionSet = newBerlinInstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. type JumpTable [256]operation +// newBerlinInstructionSet returns the frontier, homestead, byzantium, +// contantinople, istanbul, petersburg and berlin instructions. +func newBerlinInstructionSet() JumpTable { + instructionSet := newIstanbulInstructionSet() + enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929 + enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529 + return instructionSet +} + // newIstanbulInstructionSet returns the frontier, homestead // byzantium, contantinople and petersburg instructions. func newIstanbulInstructionSet() JumpTable { diff --git a/l2geth/core/vm/logger.go b/l2geth/core/vm/logger.go index bfb0f0968f9e..9e23e04cfe58 100644 --- a/l2geth/core/vm/logger.go +++ b/l2geth/core/vm/logger.go @@ -21,12 +21,14 @@ import ( "fmt" "io" "math/big" + "strings" "time" "github.com/ethereum-optimism/optimism/l2geth/common" "github.com/ethereum-optimism/optimism/l2geth/common/hexutil" "github.com/ethereum-optimism/optimism/l2geth/common/math" "github.com/ethereum-optimism/optimism/l2geth/core/types" + "github.com/ethereum-optimism/optimism/l2geth/params" ) // Storage represents a contract's storage. @@ -49,6 +51,8 @@ type LogConfig struct { DisableStorage bool // disable storage capture Debug bool // print output during capture end Limit int // maximum length of output, but zero means unlimited + // Chain overrides, can be used to execute a trace using future fork rules + Overrides *params.ChainConfig `json:"overrides,omitempty"` } //go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go @@ -254,3 +258,74 @@ func WriteLogs(writer io.Writer, logs []*types.Log) { fmt.Fprintln(writer) } } + +type mdLogger struct { + out io.Writer + cfg *LogConfig +} + +// NewMarkdownLogger creates a logger which outputs information in a format adapted +// for human readability, and is also a valid markdown table +func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger { + l := &mdLogger{writer, cfg} + if l.cfg == nil { + l.cfg = &LogConfig{} + } + return l +} + +func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { + if !create { + fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n", + from.String(), to.String(), + input, gas, value) + } else { + fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n", + from.String(), to.String(), + input, gas, value) + } + + fmt.Fprintf(t.out, ` +| Pc | Op | Cost | Stack | RStack | Refund | +|-------|-------------|------|-----------|-----------|---------| +`) + return nil +} + +func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { + fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) + + if !t.cfg.DisableStack { + // format stack + var a []string + for _, elem := range stack.data { + a = append(a, fmt.Sprintf("%v", elem.String())) + } + b := fmt.Sprintf("[%v]", strings.Join(a, ",")) + fmt.Fprintf(t.out, "%10v |", b) + + // format return stack + a = a[:0] + b = fmt.Sprintf("[%v]", strings.Join(a, ",")) + fmt.Fprintf(t.out, "%10v |", b) + } + fmt.Fprintf(t.out, "%10v |", env.StateDB.GetRefund()) + fmt.Fprintln(t.out, "") + if err != nil { + fmt.Fprintf(t.out, "Error: %v\n", err) + } + return nil +} + +func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { + + fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) + + return nil +} + +func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) error { + fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n", + output, gasUsed, err) + return nil +} diff --git a/l2geth/core/vm/operations_acl.go b/l2geth/core/vm/operations_acl.go index f6f120212c2b..0238d567601c 100644 --- a/l2geth/core/vm/operations_acl.go +++ b/l2geth/core/vm/operations_acl.go @@ -1,4 +1,4 @@ -// Copyright 2019 The go-ethereum Authors +// Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -24,39 +24,163 @@ import ( "github.com/ethereum-optimism/optimism/l2geth/params" ) -// These functions are modified to work without the access list logic. -// Access lists will be added in the future and these functions can -// be reverted to their upstream implementations. +func makeGasSStoreFunc(clearingRefund uint64) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // If we fail the minimum gas availability invariant, fail (0) + if contract.Gas <= params.SstoreSentryGasEIP2200 { + return 0, errors.New("not enough gas for reentrancy sentry") + } + // Gas sentry honoured, do the actual gas calculation based on the stored value + var ( + y, x = stack.Back(1), stack.peek() + slot = common.BigToHash(x) + current = evm.StateDB.GetState(contract.Address(), slot) + cost = uint64(0) + ) + // Check slot presence in the access list + if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { + cost = params.ColdSloadCostEIP2929 + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + if !addrPresent { + // Once we're done with YOLOv2 and schedule this for mainnet, might + // be good to remove this panic here, which is just really a + // canary to have during testing + panic("impossible case: address was not present in access list during sstore op") + } + } + value := common.BigToHash(y) -// Modified dynamic gas cost to always return the cold cost + if current == value { // noop (1) + // EIP 2200 original clause: + // return params.SloadGasEIP2200, nil + return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS + } + original := evm.StateDB.GetCommittedState(contract.Address(), common.BigToHash(x)) + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return cost + params.SstoreSetGasEIP2200, nil + } + if value == (common.Hash{}) { // delete slot (2.1.2b) + evm.StateDB.AddRefund(clearingRefund) + } + // EIP-2200 original clause: + // return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) + return cost + (params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929), nil // write existing slot (2.1.2) + } + if original != (common.Hash{}) { + if current == (common.Hash{}) { // recreate slot (2.2.1.1) + evm.StateDB.SubRefund(clearingRefund) + } else if value == (common.Hash{}) { // delete slot (2.2.1.2) + evm.StateDB.AddRefund(clearingRefund) + } + } + if original == value { + if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) + // EIP 2200 Original clause: + //evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) + evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.WarmStorageReadCostEIP2929) + } else { // reset to original existing slot (2.2.2.2) + // EIP 2200 Original clause: + // evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) + // - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST) + // - SLOAD_GAS redefined as WARM_STORAGE_READ_COST + // Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST + evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929) + } + } + // EIP-2200 original clause: + //return params.SloadGasEIP2200, nil // dirty update (2.2) + return cost + params.WarmStorageReadCostEIP2929, nil // dirty update (2.2) + } +} + +// gasSLoadEIP2929 calculates dynamic gas for SLOAD according to EIP-2929 +// For SLOAD, if the (address, storage_key) pair (where address is the address of the contract +// whose storage is being read) is not yet in accessed_storage_keys, +// charge 2100 gas and add the pair to accessed_storage_keys. +// If the pair is already in accessed_storage_keys, charge 100 gas. func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - return params.ColdSloadCostEIP2929, nil + loc := stack.peek() + slot := common.BigToHash(loc) + // Check slot presence in the access list + if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { + // When it fails, this returns false + // When it succeeds, this returns true + + // If the caller cannot afford the cost, this change will be rolled back + // If he does afford it, we can skip checking the same thing later on, during execution + evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + + // This is what happens during actual execution + return params.ColdSloadCostEIP2929, nil + } + + // Every other time, during gas estimation, we hit the bottom code path + // Which causes the gas estimation to be too small, and the tx runs out + // of gas + return params.WarmStorageReadCostEIP2929, nil } +// gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929 +// EIP spec: +// > If the target is not in accessed_addresses, +// > charge COLD_ACCOUNT_ACCESS_COST gas, and add the address to accessed_addresses. +// > Otherwise, charge WARM_STORAGE_READ_COST gas. func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // memory expansion first (dynamic part of pre-2929 implementation) gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) if err != nil { return 0, err } - var overflow bool - if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { - return 0, errors.New("gas uint64 overflow") + addr := common.BigToAddress(stack.peek()) + // Check slot presence in the access list + if !evm.StateDB.AddressInAccessList(addr) { + evm.StateDB.AddAddressToAccessList(addr) + var overflow bool + // We charge (cold-warm), since 'warm' is already charged as constantGas + if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil } return gas, nil } +// gasEip2929AccountCheck checks whether the first stack item (as address) is present in the access list. +// If it is, this method returns '0', otherwise 'cold-warm' gas, presuming that the opcode using it +// is also using 'warm' as constant factor. +// This method is used by: +// - extcodehash, +// - extcodesize, +// - (ext) balance func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - return params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929, nil + addr := common.BigToAddress(stack.peek()) + // Check slot presence in the access list + if !evm.StateDB.AddressInAccessList(addr) { + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddAddressToAccessList(addr) + // The warm storage read cost is already charged as constantGas + return params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929, nil + } + return 0, nil } func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + addr := common.BigToAddress(stack.Back(1)) + // Check slot presence in the access list + warmAccess := evm.StateDB.AddressInAccessList(addr) // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so // the cost to charge for cold access, if any, is Cold - Warm coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 - if !contract.UseGas(coldCost) { - return 0, ErrOutOfGas + if !warmAccess { + evm.StateDB.AddAddressToAccessList(addr) + // Charge the remaining difference here already, to correctly calculate available + // gas for call + if !contract.UseGas(coldCost) { + return 0, ErrOutOfGas + } } // Now call the old calculator, which takes into account // - create new account @@ -64,7 +188,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { // - memory expansion // - 63/64ths rule gas, err := oldCalculator(evm, contract, stack, mem, memorySize) - if err != nil { + if warmAccess || err != nil { return gas, err } // In case of a cold access, we temporarily add the cold charge back, and also @@ -82,14 +206,40 @@ var ( gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall) gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode) gasSelfdestructEIP2929 = makeSelfdestructGasFn(true) + // gasSelfdestructEIP3529 implements the changes in EIP-2539 (no refunds) + gasSelfdestructEIP3529 = makeSelfdestructGasFn(false) + + // gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929 + // + // When calling SSTORE, check if the (address, storage_key) pair is in accessed_storage_keys. + // If it is not, charge an additional COLD_SLOAD_COST gas, and add the pair to accessed_storage_keys. + // Additionally, modify the parameters defined in EIP 2200 as follows: + // + // Parameter Old value New value + // SLOAD_GAS 800 = WARM_STORAGE_READ_COST + // SSTORE_RESET_GAS 5000 5000 - COLD_SLOAD_COST + // + //The other parameters defined in EIP 2200 are unchanged. + // see gasSStoreEIP2200(...) in core/vm/gas_table.go for more info about how EIP 2200 is specified + gasSStoreEIP2929 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP2200) + + // gasSStoreEIP2539 implements gas cost for SSTORE according to EPI-2539 + // Replace `SSTORE_CLEARS_SCHEDULE` with `SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST` (4,800) + gasSStoreEIP3529 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529) ) // makeSelfdestructGasFn can create the selfdestruct dynamic gas function for EIP-2929 and EIP-2539 func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - address := common.BigToAddress(stack.peek()) - gas := params.ColdAccountAccessCostEIP2929 - + var ( + gas uint64 + address = common.BigToAddress(stack.peek()) + ) + if !evm.StateDB.AddressInAccessList(address) { + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddAddressToAccessList(address) + gas = params.ColdAccountAccessCostEIP2929 + } // if empty and transfers value if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { gas += params.CreateBySelfdestructGas diff --git a/l2geth/core/vm/operations_acl_optimism.go b/l2geth/core/vm/operations_acl_optimism.go new file mode 100644 index 000000000000..3c0f7b15d1b3 --- /dev/null +++ b/l2geth/core/vm/operations_acl_optimism.go @@ -0,0 +1,103 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import ( + "errors" + + "github.com/ethereum-optimism/optimism/l2geth/common" + "github.com/ethereum-optimism/optimism/l2geth/common/math" + "github.com/ethereum-optimism/optimism/l2geth/params" +) + +// These functions are modified to work without the access list logic. +// Access lists will be added in the future and these functions can +// be reverted to their upstream implementations. + +// Modified dynamic gas cost to always return the cold cost +func gasSLoadEIP2929Optimism(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return params.ColdSloadCostEIP2929, nil +} + +func gasExtCodeCopyEIP2929Optimism(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // memory expansion first (dynamic part of pre-2929 implementation) + gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + var overflow bool + if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { + return 0, errors.New("gas uint64 overflow") + } + return gas, nil +} + +func gasEip2929AccountCheckOptimism(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929, nil +} + +func makeCallVariantGasCallEIP2929Optimism(oldCalculator gasFunc) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so + // the cost to charge for cold access, if any, is Cold - Warm + coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 + if !contract.UseGas(coldCost) { + return 0, ErrOutOfGas + } + // Now call the old calculator, which takes into account + // - create new account + // - transfer value + // - memory expansion + // - 63/64ths rule + gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + if err != nil { + return gas, err + } + // In case of a cold access, we temporarily add the cold charge back, and also + // add it to the returned gas. By adding it to the return, it will be charged + // outside of this function, as part of the dynamic gas, and that will make it + // also become correctly reported to tracers. + contract.Gas += coldCost + return gas + coldCost, nil + } +} + +var ( + gasCallEIP2929Optimism = makeCallVariantGasCallEIP2929Optimism(gasCall) + gasDelegateCallEIP2929Optimism = makeCallVariantGasCallEIP2929Optimism(gasDelegateCall) + gasStaticCallEIP2929Optimism = makeCallVariantGasCallEIP2929Optimism(gasStaticCall) + gasCallCodeEIP2929Optimism = makeCallVariantGasCallEIP2929Optimism(gasCallCode) + gasSelfdestructEIP2929Optimism = makeSelfdestructGasFnOptimism(true) +) + +// makeSelfdestructGasFn can create the selfdestruct dynamic gas function for EIP-2929 and EIP-2539 +func makeSelfdestructGasFnOptimism(refundsEnabled bool) gasFunc { + gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := common.BigToAddress(stack.peek()) + gas := params.ColdAccountAccessCostEIP2929 + + // if empty and transfers value + if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { + gas += params.CreateBySelfdestructGas + } + if refundsEnabled && !evm.StateDB.HasSuicided(contract.Address()) { + evm.StateDB.AddRefund(params.SelfdestructRefundGas) + } + return gas, nil + } + return gasFunc +} diff --git a/l2geth/core/vm/runtime/runtime.go b/l2geth/core/vm/runtime/runtime.go index fe942c0a104e..c195a7ca4b68 100644 --- a/l2geth/core/vm/runtime/runtime.go +++ b/l2geth/core/vm/runtime/runtime.go @@ -106,6 +106,9 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) + if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin { + cfg.State.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules), nil) + } cfg.State.CreateAccount(address) // set the receiver's (the executing contract) code for execution. cfg.State.SetCode(address, code) @@ -135,7 +138,9 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - + if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin { + cfg.State.PrepareAccessList(cfg.Origin, nil, vm.ActivePrecompiles(rules), nil) + } // Call the code with the given configuration. code, address, leftOverGas, err := vmenv.Create( sender, @@ -157,6 +162,11 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er vmenv := NewEnv(cfg) sender := cfg.State.GetOrNewStateObject(cfg.Origin) + statedb := cfg.State + + if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin { + statedb.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules), nil) + } // Call the code with the given configuration. ret, leftOverGas, err := vmenv.Call( sender, diff --git a/l2geth/core/vm/runtime/runtime_test.go b/l2geth/core/vm/runtime/runtime_test.go index 41b6aa4e1f93..c2ca4568ae58 100644 --- a/l2geth/core/vm/runtime/runtime_test.go +++ b/l2geth/core/vm/runtime/runtime_test.go @@ -17,12 +17,15 @@ package runtime import ( + "fmt" "math/big" + "os" "strings" "testing" "github.com/ethereum-optimism/optimism/l2geth/accounts/abi" "github.com/ethereum-optimism/optimism/l2geth/common" + "github.com/ethereum-optimism/optimism/l2geth/core/asm" "github.com/ethereum-optimism/optimism/l2geth/core/rawdb" "github.com/ethereum-optimism/optimism/l2geth/core/state" "github.com/ethereum-optimism/optimism/l2geth/core/vm" @@ -203,3 +206,115 @@ func BenchmarkEVM_CREATE2_1200(bench *testing.B) { // initcode size 1200K, repeatedly calls CREATE2 and then modifies the mem contents benchmarkEVM_Create(bench, "5b5862124f80600080f5600152600056") } + +// TestEip2929Cases contains various testcases that are used for +// EIP-2929 about gas repricings +func TestEip2929Cases(t *testing.T) { + + id := 1 + prettyPrint := func(comment string, code []byte) { + + instrs := make([]string, 0) + it := asm.NewInstructionIterator(code) + for it.Next() { + if it.Arg() != nil && 0 < len(it.Arg()) { + instrs = append(instrs, fmt.Sprintf("%v 0x%x", it.Op(), it.Arg())) + } else { + instrs = append(instrs, fmt.Sprintf("%v", it.Op())) + } + } + ops := strings.Join(instrs, ", ") + fmt.Printf("### Case %d\n\n", id) + id++ + fmt.Printf("%v\n\nBytecode: \n```\n0x%x\n```\nOperations: \n```\n%v\n```\n\n", + comment, + code, ops) + Execute(code, nil, &Config{ + EVMConfig: vm.Config{ + Debug: true, + Tracer: vm.NewMarkdownLogger(nil, os.Stdout), + ExtraEips: []int{2929}, + }, + }) + } + + { // First eip testcase + code := []byte{ + // Three checks against a precompile + byte(vm.PUSH1), 1, byte(vm.EXTCODEHASH), byte(vm.POP), + byte(vm.PUSH1), 2, byte(vm.EXTCODESIZE), byte(vm.POP), + byte(vm.PUSH1), 3, byte(vm.BALANCE), byte(vm.POP), + // Three checks against a non-precompile + byte(vm.PUSH1), 0xf1, byte(vm.EXTCODEHASH), byte(vm.POP), + byte(vm.PUSH1), 0xf2, byte(vm.EXTCODESIZE), byte(vm.POP), + byte(vm.PUSH1), 0xf3, byte(vm.BALANCE), byte(vm.POP), + // Same three checks (should be cheaper) + byte(vm.PUSH1), 0xf2, byte(vm.EXTCODEHASH), byte(vm.POP), + byte(vm.PUSH1), 0xf3, byte(vm.EXTCODESIZE), byte(vm.POP), + byte(vm.PUSH1), 0xf1, byte(vm.BALANCE), byte(vm.POP), + // Check the origin, and the 'this' + byte(vm.ORIGIN), byte(vm.BALANCE), byte(vm.POP), + byte(vm.ADDRESS), byte(vm.BALANCE), byte(vm.POP), + + byte(vm.STOP), + } + prettyPrint("This checks `EXT`(codehash,codesize,balance) of precompiles, which should be `100`, "+ + "and later checks the same operations twice against some non-precompiles. "+ + "Those are cheaper second time they are accessed. Lastly, it checks the `BALANCE` of `origin` and `this`.", code) + } + + { // EXTCODECOPY + code := []byte{ + // extcodecopy( 0xff,0,0,0,0) + byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset + byte(vm.PUSH1), 0xff, byte(vm.EXTCODECOPY), + // extcodecopy( 0xff,0,0,0,0) + byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset + byte(vm.PUSH1), 0xff, byte(vm.EXTCODECOPY), + // extcodecopy( this,0,0,0,0) + byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset + byte(vm.ADDRESS), byte(vm.EXTCODECOPY), + + byte(vm.STOP), + } + prettyPrint("This checks `extcodecopy( 0xff,0,0,0,0)` twice, (should be expensive first time), "+ + "and then does `extcodecopy( this,0,0,0,0)`.", code) + } + + { // SLOAD + SSTORE + code := []byte{ + + // Add slot `0x1` to access list + byte(vm.PUSH1), 0x01, byte(vm.SLOAD), byte(vm.POP), // SLOAD( 0x1) (add to access list) + // Write to `0x1` which is already in access list + byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x01, byte(vm.SSTORE), // SSTORE( loc: 0x01, val: 0x11) + // Write to `0x2` which is not in access list + byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x02, byte(vm.SSTORE), // SSTORE( loc: 0x02, val: 0x11) + // Write again to `0x2` + byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x02, byte(vm.SSTORE), // SSTORE( loc: 0x02, val: 0x11) + // Read slot in access list (0x2) + byte(vm.PUSH1), 0x02, byte(vm.SLOAD), // SLOAD( 0x2) + // Read slot in access list (0x1) + byte(vm.PUSH1), 0x01, byte(vm.SLOAD), // SLOAD( 0x1) + } + prettyPrint("This checks `sload( 0x1)` followed by `sstore(loc: 0x01, val:0x11)`, then 'naked' sstore:"+ + "`sstore(loc: 0x02, val:0x11)` twice, and `sload(0x2)`, `sload(0x1)`. ", code) + } + { // Call variants + code := []byte{ + // identity precompile + byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0x04, byte(vm.PUSH1), 0x0, byte(vm.CALL), byte(vm.POP), + + // random account - call 1 + byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.PUSH1), 0x0, byte(vm.CALL), byte(vm.POP), + + // random account - call 2 + byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.PUSH1), 0x0, byte(vm.STATICCALL), byte(vm.POP), + } + prettyPrint("This calls the `identity`-precompile (cheap), then calls an account (expensive) and `staticcall`s the same"+ + "account (cheap)", code) + } +} diff --git a/l2geth/eth/api_backend.go b/l2geth/eth/api_backend.go index 342163c48f59..36f685e8213f 100644 --- a/l2geth/eth/api_backend.go +++ b/l2geth/eth/api_backend.go @@ -141,13 +141,7 @@ func (b *EthAPIBackend) SequencerClientHttp() string { } func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { - // Pending block is only known by the miner - if number == rpc.PendingBlockNumber { - block := b.eth.miner.PendingBlock() - return block.Header(), nil - } - // Otherwise resolve and return the block - if number == rpc.LatestBlockNumber { + if number == rpc.LatestBlockNumber || number == rpc.PendingBlockNumber { return b.eth.blockchain.CurrentBlock().Header(), nil } return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil @@ -175,13 +169,7 @@ func (b *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*ty } func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { - // Pending block is only known by the miner - if number == rpc.PendingBlockNumber { - block := b.eth.miner.PendingBlock() - return block, nil - } - // Otherwise resolve and return the block - if number == rpc.LatestBlockNumber { + if number == rpc.LatestBlockNumber || number == rpc.PendingBlockNumber { return b.eth.blockchain.CurrentBlock(), nil } return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil @@ -213,12 +201,6 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r } func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { - // Pending state is only known by the miner - if number == rpc.PendingBlockNumber { - block, state := b.eth.miner.Pending() - return state, block.Header(), nil - } - // Otherwise resolve the block number and return its state header, err := b.HeaderByNumber(ctx, number) if err != nil { return nil, nil, err @@ -271,7 +253,7 @@ func (b *EthAPIBackend) GetTd(blockHash common.Hash) *big.Int { return b.eth.blockchain.GetTdByHash(blockHash) } -func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { +func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmCfg vm.Config) (*vm.EVM, func() error, error) { // This was removed upstream: // https://github.com/ethereum-optimism/optimism/l2geth/commit/39f502329fac4640cfb71959c3496f19ea88bc85#diff-9886da3412b43831145f62cec6e895eb3613a175b945e5b026543b7463454603 // We're throwing this behind a UsingOVM flag for now as to not break @@ -282,7 +264,7 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *sta vmError := func() error { return nil } context := core.NewEVMContext(msg, header, b.eth.BlockChain(), nil) - return vm.NewEVM(context, state, b.eth.blockchain.Config(), *b.eth.blockchain.GetVMConfig()), vmError, nil + return vm.NewEVM(context, state, b.eth.blockchain.Config(), vmCfg), vmError, nil } func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { diff --git a/l2geth/eth/api_tracer.go b/l2geth/eth/api_tracer.go index 828ace90f569..d3c3030c7705 100644 --- a/l2geth/eth/api_tracer.go +++ b/l2geth/eth/api_tracer.go @@ -38,6 +38,7 @@ import ( "github.com/ethereum-optimism/optimism/l2geth/eth/tracers" "github.com/ethereum-optimism/optimism/l2geth/internal/ethapi" "github.com/ethereum-optimism/optimism/l2geth/log" + "github.com/ethereum-optimism/optimism/l2geth/params" "github.com/ethereum-optimism/optimism/l2geth/rlp" "github.com/ethereum-optimism/optimism/l2geth/rpc" "github.com/ethereum-optimism/optimism/l2geth/trie" @@ -106,17 +107,15 @@ func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.Block var from, to *types.Block switch start { - case rpc.PendingBlockNumber: - from = api.eth.miner.PendingBlock() case rpc.LatestBlockNumber: + case rpc.PendingBlockNumber: from = api.eth.blockchain.CurrentBlock() default: from = api.eth.blockchain.GetBlockByNumber(uint64(start)) } switch end { - case rpc.PendingBlockNumber: - to = api.eth.miner.PendingBlock() case rpc.LatestBlockNumber: + case rpc.PendingBlockNumber: to = api.eth.blockchain.CurrentBlock() default: to = api.eth.blockchain.GetBlockByNumber(uint64(end)) @@ -357,9 +356,8 @@ func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.B var block *types.Block switch number { - case rpc.PendingBlockNumber: - block = api.eth.miner.PendingBlock() case rpc.LatestBlockNumber: + case rpc.PendingBlockNumber: block = api.eth.blockchain.CurrentBlock() default: block = api.eth.blockchain.GetBlockByNumber(uint64(number)) @@ -561,9 +559,30 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block // Execute transaction, either tracing all or just the requested one var ( - signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number()) - dumps []string + dumps []string + signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number()) + chainConfig = api.eth.blockchain.Config() + canon = true ) + + // Check if there are any overrides: the caller may wish to enable a future + // fork when executing this block. Note, such overrides are only applicable to the + // actual specified block, not any preceding blocks that we have to go through + // in order to obtain the state. + // Therefore, it's perfectly valid to specify `"futureForkBlock": 0`, to enable `futureFork` + + if config != nil && config.Overrides != nil { + // Copy the config, to not screw up the main config + // Note: the Clique-part is _not_ deep copied + chainConfigCopy := new(params.ChainConfig) + *chainConfigCopy = *chainConfig + chainConfig = chainConfigCopy + if berlin := config.LogConfig.Overrides.BerlinBlock; berlin != nil { + chainConfig.BerlinBlock = berlin + canon = false + } + } + for i, tx := range block.Transactions() { // Prepare the trasaction for un-traced execution var ( @@ -579,7 +598,9 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block if tx.Hash() == txHash || txHash == (common.Hash{}) { // Generate a unique temporary file to dump it into prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4]) - + if !canon { + prefix = fmt.Sprintf("%valt-", prefix) + } dump, err = ioutil.TempFile(os.TempDir(), prefix) if err != nil { return nil, err @@ -595,7 +616,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block } } // Execute the transaction and flush any traces to disk - vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vmConf) + vmenv := vm.NewEVM(vmctx, statedb, chainConfig, vmConf) _, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) if writer != nil { writer.Flush() @@ -755,8 +776,19 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v default: tracer = vm.NewStructLogger(config.LogConfig) } + + chainConfig := api.eth.blockchain.Config() + if config != nil && config.LogConfig != nil && config.LogConfig.Overrides != nil { + chainConfigCopy := new(params.ChainConfig) + *chainConfigCopy = *chainConfig + chainConfig = chainConfigCopy + if berlin := config.LogConfig.Overrides.BerlinBlock; berlin != nil { + chainConfig.BerlinBlock = berlin + } + } + // Run the transaction with tracing enabled. - vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) + vmenv := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{Debug: true, Tracer: tracer}) ret, gas, failed, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) if err != nil { diff --git a/l2geth/interfaces.go b/l2geth/interfaces.go index fd6aec3bfe7a..cc05b0a7bf3c 100644 --- a/l2geth/interfaces.go +++ b/l2geth/interfaces.go @@ -120,6 +120,8 @@ type CallMsg struct { Value *big.Int // amount of wei sent along with the call Data []byte // input data, usually an ABI-encoded contract method invocation + AccessList types.AccessList // EIP-2930 access list. + L1Timestamp uint64 L1BlockNumber *big.Int QueueOrigin types.QueueOrigin diff --git a/l2geth/internal/ethapi/api.go b/l2geth/internal/ethapi/api.go index 2561588d1e2f..a7c3a806db2f 100644 --- a/l2geth/internal/ethapi/api.go +++ b/l2geth/internal/ethapi/api.go @@ -910,7 +910,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo defer cancel() // Get a new instance of the EVM. - evm, vmError, err := b.GetEVM(ctx, msg, state, header) + evm, vmError, err := b.GetEVM(ctx, msg, state, header, vmCfg) if err != nil { return nil, 0, false, err } @@ -989,11 +989,13 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash } cap = hi - // Set sender address or use a default if none specified - if args.From == nil { - if wallets := b.AccountManager().Wallets(); len(wallets) > 0 { - if accounts := wallets[0].Accounts(); len(accounts) > 0 { - args.From = &accounts[0].Address + if !rcfg.UsingOVM { + // Set sender address or use a default if none specified + if args.From == nil { + if wallets := b.AccountManager().Wallets(); len(wallets) > 0 { + if accounts := wallets[0].Accounts(); len(accounts) > 0 { + args.From = &accounts[0].Address + } } } } diff --git a/l2geth/internal/ethapi/backend.go b/l2geth/internal/ethapi/backend.go index a6a21f43339a..db7f8623bbcf 100644 --- a/l2geth/internal/ethapi/backend.go +++ b/l2geth/internal/ethapi/backend.go @@ -59,7 +59,7 @@ type Backend interface { StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) GetTd(hash common.Hash) *big.Int - GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) + GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmCfg vm.Config) (*vm.EVM, func() error, error) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription diff --git a/l2geth/les/api_backend.go b/l2geth/les/api_backend.go index 24623f6eb5cc..9433cc3eaae6 100644 --- a/l2geth/les/api_backend.go +++ b/l2geth/les/api_backend.go @@ -199,7 +199,7 @@ func (b *LesApiBackend) GetTd(hash common.Hash) *big.Int { return b.eth.blockchain.GetTdByHash(hash) } -func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { +func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmCfg vm.Config) (*vm.EVM, func() error, error) { state.SetBalance(msg.From(), math.MaxBig256) context := core.NewEVMContext(msg, header, b.eth.blockchain, nil) return vm.NewEVM(context, state, b.eth.chainConfig, vm.Config{}), state.Error, nil diff --git a/l2geth/params/config.go b/l2geth/params/config.go index 42fa0a30c1b0..8a91cfe5bea6 100644 --- a/l2geth/params/config.go +++ b/l2geth/params/config.go @@ -215,16 +215,16 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(108), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(108), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(420), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(420), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} TestRules = TestChainConfig.Rules(new(big.Int)) ) @@ -295,7 +295,9 @@ type ChainConfig struct { PetersburgBlock *big.Int `json:"petersburgBlock,omitempty"` // Petersburg switch block (nil = same as Constantinople) IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated) - EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) + BerlinBlock *big.Int `json:"berlinBlock,omitempty"` // Berlin switch block (nil = no fork, 0 = already on berlin) + + EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) // Various consensus engines Ethash *EthashConfig `json:"ethash,omitempty"` @@ -332,7 +334,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -345,6 +347,7 @@ func (c *ChainConfig) String() string { c.PetersburgBlock, c.IstanbulBlock, c.MuirGlacierBlock, + c.BerlinBlock, engine, ) } @@ -401,6 +404,11 @@ func (c *ChainConfig) IsIstanbul(num *big.Int) bool { return isForked(c.IstanbulBlock, num) } +// IsBerlin returns whether num is either equal to the Berlin fork block or greater. +func (c *ChainConfig) IsBerlin(num *big.Int) bool { + return isForked(c.BerlinBlock, num) +} + // IsEWASM returns whether num represents a block number after the EWASM fork func (c *ChainConfig) IsEWASM(num *big.Int) bool { return isForked(c.EWASMBlock, num) @@ -442,6 +450,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {"petersburgBlock", c.PetersburgBlock}, {"istanbulBlock", c.IstanbulBlock}, {"muirGlacierBlock", c.MuirGlacierBlock}, + {name: "berlinBlock", block: c.BerlinBlock}, } { if lastFork.name != "" { // Next one must be higher number @@ -498,6 +507,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, head) { return newCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock) } + if isForkIncompatible(c.BerlinBlock, newcfg.BerlinBlock, head) { + return newCompatError("Berlin fork block", c.BerlinBlock, newcfg.BerlinBlock) + } if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) { return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock) } @@ -568,6 +580,7 @@ type Rules struct { ChainID *big.Int IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool + IsBerlin bool } // Rules ensures c's ChainID is not nil. @@ -586,5 +599,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { IsConstantinople: c.IsConstantinople(num), IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), + IsBerlin: c.IsBerlin(num), } } diff --git a/l2geth/params/protocol_params.go b/l2geth/params/protocol_params.go index 8fbd4af621c7..d1d10ff76bf7 100644 --- a/l2geth/params/protocol_params.go +++ b/l2geth/params/protocol_params.go @@ -52,7 +52,11 @@ const ( NetSstoreResetRefund uint64 = 4800 // Once per SSTORE operation for resetting to the original non-zero value NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original zero value - SstoreSentryGasEIP2200 uint64 = 2300 // Minimum gas required to be present for an SSTORE call, not consumed + SstoreSentryGasEIP2200 uint64 = 2300 // Minimum gas required to be present for an SSTORE call, not consumed + SstoreSetGasEIP2200 uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero + SstoreResetGasEIP2200 uint64 = 5000 // Once per SSTORE operation from clean non-zero to something else + SstoreClearsScheduleRefundEIP2200 uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot + SstoreNoopGasEIP2200 uint64 = 800 // Once per SSTORE operation if the value doesn't change. SstoreDirtyGasEIP2200 uint64 = 800 // Once per SSTORE operation if a dirty value is changed. SstoreInitGasEIP2200 uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero @@ -65,23 +69,31 @@ const ( ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST + // In EIP-2200: SstoreResetGas was 5000. + // In EIP-2929: SstoreResetGas was changed to '5000 - COLD_SLOAD_COST'. + // In EIP-3529: SSTORE_CLEARS_SCHEDULE is defined as SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST + // Which becomes: 5000 - 2100 + 1900 = 4800 + SstoreClearsScheduleRefundEIP3529 uint64 = SstoreResetGasEIP2200 - ColdSloadCostEIP2929 + TxAccessListStorageKeyGas + JumpdestGas uint64 = 1 // Once per JUMPDEST operation. EpochDuration uint64 = 30000 // Duration between proof-of-work epochs. - CreateDataGas uint64 = 200 // - CallCreateDepth uint64 = 1024 // Maximum depth of call/create stack. - ExpGas uint64 = 10 // Once per EXP instruction - LogGas uint64 = 375 // Per LOG* operation. - CopyGas uint64 = 3 // - StackLimit uint64 = 1024 // Maximum size of VM stack allowed. - TierStepGas uint64 = 0 // Once per operation, for a selection of them. - LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas. - CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction. - Create2Gas uint64 = 32000 // Once per CREATE2 operation - SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. - MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. - TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. - TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) + CreateDataGas uint64 = 200 // + CallCreateDepth uint64 = 1024 // Maximum depth of call/create stack. + ExpGas uint64 = 10 // Once per EXP instruction + LogGas uint64 = 375 // Per LOG* operation. + CopyGas uint64 = 3 // + StackLimit uint64 = 1024 // Maximum size of VM stack allowed. + TierStepGas uint64 = 0 // Once per operation, for a selection of them. + LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas. + CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction. + Create2Gas uint64 = 32000 // Once per CREATE2 operation + SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. + MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. + TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. + TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) + TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list + TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list // These have been changed during the course of the chain CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. diff --git a/l2geth/scripts/init.sh b/l2geth/scripts/init.sh new file mode 100755 index 000000000000..335042274111 --- /dev/null +++ b/l2geth/scripts/init.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# script to help simplify l2geth initialization +# it needs a path on the filesystem to the state +# dump + +set -eou pipefail + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )" +REPO=$DIR/.. +STATE_DUMP=${STATE_DUMP:-$REPO/../packages/contracts/dist/dumps/state-dump.latest.json} +DATADIR=${DATADIR:-$HOME/.ethereum} + +# These are the initial key and address that must be used for the clique +# signer on the optimism network. All nodes must be initialized with this +# key before they are able to join the network and sync correctly. +# The signer address needs to be in the genesis block's extradata. +SIGNER_KEY=6587ae678cf4fc9a33000cdbf9f35226b71dcc6a4684a31203241f9bcfd55d27 +SIGNER=0x00000398232e2064f896018496b4b44b3d62751f + +mkdir -p $DATADIR + +if [[ ! -f $STATE_DUMP ]]; then + echo "Cannot find $STATE_DUMP" + exit 1 +fi + +# Add funds to the signer account so that it can be used to send transactions +if [[ ! -z "$DEVELOPMENT" ]]; then + echo "Setting up development genesis" + echo "Assuming that the initial clique signer is $SIGNER, this is configured in genesis extradata" + DUMP=$(cat $STATE_DUMP | jq '.alloc += {"0x00000398232e2064f896018496b4b44b3d62751f": {balance: "10000000000000000000"}}') + TEMP=$(mktemp) + echo "$DUMP" | jq . > $TEMP + STATE_DUMP=$TEMP +fi + +geth="$REPO/build/bin/geth" +USING_OVM=true $geth init --datadir $DATADIR $STATE_DUMP + +echo "6587ae678cf4fc9a33000cdbf9f35226b71dcc6a4684a31203241f9bcfd55d27" \ + > $DATADIR/keyfile + +echo "password" > $DATADIR/password + +USING_OVM=true $geth account import \ + --datadir $DATADIR --password $DATADIR/password $DATADIR/keyfile diff --git a/l2geth/scripts/start.sh b/l2geth/scripts/start.sh index d48c353a1173..fc73c5aea76e 100755 --- a/l2geth/scripts/start.sh +++ b/l2geth/scripts/start.sh @@ -6,16 +6,18 @@ REPO=$DIR/.. IS_VERIFIER= ROLLUP_SYNC_SERVICE_ENABLE=true DATADIR=$HOME/.ethereum -TARGET_GAS_LIMIT=11000000 +TARGET_GAS_LIMIT=15000000 ETH1_CTC_DEPLOYMENT_HEIGHT=12686738 ROLLUP_CLIENT_HTTP=http://localhost:7878 ROLLUP_POLL_INTERVAL=15s -ROLLUP_TIMESTAMP_REFRESH=3m +ROLLUP_TIMESTAMP_REFRESH=15s CACHE=1024 RPC_PORT=8545 WS_PORT=8546 VERBOSITY=3 ROLLUP_BACKEND=l2 +CHAIN_ID=69 +BLOCK_SIGNER_ADDRESS=0x00000398232E2064F896018496b4b44b3D62751F USAGE=" Start the Sequencer or Verifier with most configuration pre-set. @@ -174,15 +176,22 @@ cmd="$cmd --ws" cmd="$cmd --wsaddr 0.0.0.0" cmd="$cmd --wsport $WS_PORT" cmd="$cmd --wsorigins '*'" -cmd="$cmd --rpcapi 'eth,net,rollup,web3,debug'" +cmd="$cmd --rpcapi eth,net,rollup,web3,debug,personal" cmd="$cmd --gasprice 0" cmd="$cmd --nousb" cmd="$cmd --gcmode=archive" -cmd="$cmd --ipcdisable" +cmd="$cmd --nodiscover" +cmd="$cmd --mine" +cmd="$cmd --password=$DATADIR/password" +cmd="$cmd --allow-insecure-unlock" +cmd="$cmd --unlock=$BLOCK_SIGNER_ADDRESS" +cmd="$cmd --miner.etherbase=$BLOCK_SIGNER_ADDRESS" +cmd="$cmd --txpool.pricelimit 0" + if [[ ! -z "$IS_VERIFIER" ]]; then cmd="$cmd --rollup.verifier" fi cmd="$cmd --verbosity=$VERBOSITY" echo -e "Running:\nTARGET_GAS_LIMIT=$TARGET_GAS_LIMIT USING_OVM=true $cmd" -eval env TARGET_GAS_LIMIT=$TARGET_GAS_LIMIT USING_OVM=true $cmd +TARGET_GAS_LIMIT=$TARGET_GAS_LIMIT USING_OVM=true $cmd diff --git a/l2geth/tests/state_test_util.go b/l2geth/tests/state_test_util.go index 4347e67942e8..789e7c12a565 100644 --- a/l2geth/tests/state_test_util.go +++ b/l2geth/tests/state_test_util.go @@ -181,6 +181,16 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config) (*stat context.GetHash = vmTestBlockHash evm := vm.NewEVM(context, statedb, config, vmconfig) + if config.IsBerlin(context.BlockNumber) { + statedb.AddAddressToAccessList(msg.From()) + if dst := msg.To(); dst != nil { + statedb.AddAddressToAccessList(*dst) + // If it's a create-tx, the destination will be added inside evm.create + } + for _, addr := range vm.ActivePrecompiles(config.Rules(context.BlockNumber)) { + statedb.AddAddressToAccessList(addr) + } + } gaspool := new(core.GasPool) gaspool.AddGas(block.GasLimit()) snapshot := statedb.Snapshot() diff --git a/packages/contracts/bin/take-dump.ts b/packages/contracts/bin/take-dump.ts index ab4cb3db4e8d..9dc3bbe60741 100644 --- a/packages/contracts/bin/take-dump.ts +++ b/packages/contracts/bin/take-dump.ts @@ -65,6 +65,8 @@ import { makeL2GenesisFile } from '../src/make-genesis' const l1FeeWalletAddress = env.L1_FEE_WALLET_ADDRESS // The L1 cross domain messenger address, used for cross domain messaging const l1CrossDomainMessengerAddress = env.L1_CROSS_DOMAIN_MESSENGER_ADDRESS + // The block height at which the berlin hardfork activates + const berlinBlock = parseInt(env.BERLIN_BLOCK, 10) || 0 ensure(whitelistOwner, 'WHITELIST_OWNER') ensure(gasPriceOracleOwner, 'GAS_PRICE_ORACLE_OWNER') @@ -74,6 +76,7 @@ import { makeL2GenesisFile } from '../src/make-genesis' ensure(l1StandardBridgeAddress, 'L1_STANDARD_BRIDGE_ADDRESS') ensure(l1FeeWalletAddress, 'L1_FEE_WALLET_ADDRESS') ensure(l1CrossDomainMessengerAddress, 'L1_CROSS_DOMAIN_MESSENGER_ADDRESS') + ensure(berlinBlock, 'BERLIN_BLOCK') // Basic warning so users know that the whitelist will be disabled if the owner is the zero address. if (env.WHITELIST_OWNER === '0x' + '00'.repeat(20)) { @@ -96,6 +99,7 @@ import { makeL2GenesisFile } from '../src/make-genesis' l1StandardBridgeAddress, l1FeeWalletAddress, l1CrossDomainMessengerAddress, + berlinBlock, }) fs.writeFileSync(outfile, JSON.stringify(genesis, null, 4)) diff --git a/packages/contracts/src/make-genesis.ts b/packages/contracts/src/make-genesis.ts index 34526145ceab..7b3419849c3a 100644 --- a/packages/contracts/src/make-genesis.ts +++ b/packages/contracts/src/make-genesis.ts @@ -40,6 +40,8 @@ export interface RollupDeployConfig { l1FeeWalletAddress: string // Address of the L1CrossDomainMessenger contract. l1CrossDomainMessengerAddress: string + // Block height to activate berlin hardfork + berlinBlock: number } /** @@ -150,6 +152,7 @@ export const makeL2GenesisFile = async ( petersburgBlock: 0, istanbulBlock: 0, muirGlacierBlock: 0, + berlinBlock: cfg.berlinBlock, clique: { period: 0, epoch: 30000,