Commit e631c39c authored by Mark Tyneway's avatar Mark Tyneway Committed by Matthew Slipper

l2geth: Add berlin hardfork

parent cdc4ddd8
---
'@eth-optimism/integration-tests': patch
---
Add in berlin hardfork tests
---
'@eth-optimism/l2geth': patch
---
Implement berlin hardfork
---
'@eth-optimism/contracts': patch
---
Add berlin hardfork config to genesis creation
// 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)
}
}
}
// 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);
}
}
...@@ -4,6 +4,7 @@ import { HardhatUserConfig } from 'hardhat/types' ...@@ -4,6 +4,7 @@ import { HardhatUserConfig } from 'hardhat/types'
import '@nomiclabs/hardhat-ethers' import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle' import '@nomiclabs/hardhat-waffle'
import 'hardhat-gas-reporter' import 'hardhat-gas-reporter'
import './tasks/check-block-hashes'
import { envConfig } from './test/shared/utils' import { envConfig } from './test/shared/utils'
const enableGasReport = !!process.env.ENABLE_GAS_REPORT const enableGasReport = !!process.env.ENABLE_GAS_REPORT
......
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
}
})
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)
})
})
})
})
})
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)
})
})
})
})
...@@ -28,6 +28,7 @@ describe('Basic RPC tests', () => { ...@@ -28,6 +28,7 @@ describe('Basic RPC tests', () => {
const provider = injectL2Context(l2Provider) const provider = injectL2Context(l2Provider)
let Reverter: Contract let Reverter: Contract
let ValueContext: Contract
let revertMessage: string let revertMessage: string
let revertingTx: TransactionRequest let revertingTx: TransactionRequest
let revertingDeployTx: TransactionRequest let revertingDeployTx: TransactionRequest
...@@ -53,6 +54,12 @@ describe('Basic RPC tests', () => { ...@@ -53,6 +54,12 @@ describe('Basic RPC tests', () => {
revertingDeployTx = { revertingDeployTx = {
data: Factory__ConstructorReverter.bytecode, 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', () => { describe('eth_sendRawTransaction', () => {
...@@ -209,12 +216,6 @@ describe('Basic RPC tests', () => { ...@@ -209,12 +216,6 @@ describe('Basic RPC tests', () => {
}) })
it('should allow eth_calls with nonzero value', async () => { 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 // Fund account to call from
const from = wallet.address const from = wallet.address
const value = 15 const value = 15
...@@ -234,12 +235,6 @@ describe('Basic RPC tests', () => { ...@@ -234,12 +235,6 @@ describe('Basic RPC tests', () => {
// https://github.com/ethereum-optimism/optimism/issues/1998 // https://github.com/ethereum-optimism/optimism/issues/1998
it('should use address(0) as the default "from" value', async () => { 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 // Do the call and check msg.sender
const data = ValueContext.interface.encodeFunctionData('getCaller') const data = ValueContext.interface.encodeFunctionData('getCaller')
const res = await provider.call({ const res = await provider.call({
...@@ -256,12 +251,6 @@ describe('Basic RPC tests', () => { ...@@ -256,12 +251,6 @@ describe('Basic RPC tests', () => {
}) })
it('should correctly use the "from" value', async () => { 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 const from = wallet.address
// Do the call and check msg.sender // Do the call and check msg.sender
...@@ -278,6 +267,15 @@ describe('Basic RPC tests', () => { ...@@ -278,6 +267,15 @@ describe('Basic RPC tests', () => {
) )
expect(paddedRes).to.eq(from) 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', () => { describe('eth_getTransactionReceipt', () => {
...@@ -450,7 +448,7 @@ describe('Basic RPC tests', () => { ...@@ -450,7 +448,7 @@ describe('Basic RPC tests', () => {
}) })
describe('eth_estimateGas', () => { describe('eth_estimateGas', () => {
it('gas estimation is deterministic', async () => { it('simple send gas estimation is deterministic', async () => {
let lastEstimate: BigNumber let lastEstimate: BigNumber
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const estimate = await l2Provider.estimateGas({ const estimate = await l2Provider.estimateGas({
...@@ -466,6 +464,15 @@ describe('Basic RPC tests', () => { ...@@ -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 () => { it('should return a gas estimate for txs with empty data', async () => {
const estimate = await l2Provider.estimateGas({ const estimate = await l2Provider.estimateGas({
to: defaultTransactionFactory().to, to: defaultTransactionFactory().to,
......
...@@ -93,6 +93,9 @@ const procEnv = cleanEnv(process.env, { ...@@ -93,6 +93,9 @@ const procEnv = cleanEnv(process.env, {
RUN_STRESS_TESTS: bool({ RUN_STRESS_TESTS: bool({
default: true, default: true,
}), }),
RUN_NIGHTLY_TESTS: bool({
default: false,
}),
MOCHA_TIMEOUT: num({ MOCHA_TIMEOUT: num({
default: 120_000, default: 120_000,
...@@ -264,3 +267,12 @@ export const isHardhat = async () => { ...@@ -264,3 +267,12 @@ export const isHardhat = async () => {
const chainId = await l1Wallet.getChainId() const chainId = await l1Wallet.getChainId()
return chainId === HARDHAT_CHAIN_ID 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`)
}
...@@ -600,6 +600,7 @@ func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } ...@@ -600,6 +600,7 @@ func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
func (m callmsg) Gas() uint64 { return m.CallMsg.Gas } func (m callmsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callmsg) Value() *big.Int { return m.CallMsg.Value } func (m callmsg) Value() *big.Int { return m.CallMsg.Value }
func (m callmsg) Data() []byte { return m.CallMsg.Data } func (m callmsg) Data() []byte { return m.CallMsg.Data }
func (m callmsg) AccessList() types.AccessList { return m.CallMsg.AccessList }
// UsingOVM // UsingOVM
// These getters return OVM specific fields // These getters return OVM specific fields
......
...@@ -48,7 +48,6 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author ...@@ -48,7 +48,6 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
} }
if rcfg.UsingOVM { if rcfg.UsingOVM {
// When using the OVM, we must: // When using the OVM, we must:
// - Set the BlockNumber to be the msg.L1BlockNumber
// - Set the Time to be the msg.L1Timestamp // - Set the Time to be the msg.L1Timestamp
return vm.Context{ return vm.Context{
CanTransfer: CanTransfer, CanTransfer: CanTransfer,
......
// 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 <http://www.gnu.org/licenses/>.
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)
}
...@@ -129,6 +129,15 @@ type ( ...@@ -129,6 +129,15 @@ type (
touchChange struct { touchChange struct {
account *common.Address 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) { func (ch createObjectChange) revert(s *StateDB) {
...@@ -230,3 +239,28 @@ func (ch addPreimageChange) revert(s *StateDB) { ...@@ -230,3 +239,28 @@ func (ch addPreimageChange) revert(s *StateDB) {
func (ch addPreimageChange) dirtied() *common.Address { func (ch addPreimageChange) dirtied() *common.Address {
return nil 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
}
...@@ -100,6 +100,9 @@ type StateDB struct { ...@@ -100,6 +100,9 @@ type StateDB struct {
preimages map[common.Hash][]byte preimages map[common.Hash][]byte
// Per-transaction access list
accessList *accessList
// Journal of state modifications. This is the backbone of // Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot. // Snapshot and RevertToSnapshot.
journal *journal journal *journal
...@@ -132,6 +135,7 @@ func New(root common.Hash, db Database) (*StateDB, error) { ...@@ -132,6 +135,7 @@ func New(root common.Hash, db Database) (*StateDB, error) {
logs: make(map[common.Hash][]*types.Log), logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte), preimages: make(map[common.Hash][]byte),
journal: newJournal(), journal: newJournal(),
accessList: newAccessList(),
}, nil }, nil
} }
...@@ -163,6 +167,7 @@ func (s *StateDB) Reset(root common.Hash) error { ...@@ -163,6 +167,7 @@ func (s *StateDB) Reset(root common.Hash) error {
s.logs = make(map[common.Hash][]*types.Log) s.logs = make(map[common.Hash][]*types.Log)
s.logSize = 0 s.logSize = 0
s.preimages = make(map[common.Hash][]byte) s.preimages = make(map[common.Hash][]byte)
s.accessList = newAccessList()
s.clearJournalAndRefund() s.clearJournalAndRefund()
return nil return nil
} }
...@@ -673,6 +678,13 @@ func (s *StateDB) Copy() *StateDB { ...@@ -673,6 +678,13 @@ func (s *StateDB) Copy() *StateDB {
for hash, preimage := range s.preimages { for hash, preimage := range s.preimages {
state.preimages[hash] = preimage 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 return state
} }
...@@ -764,6 +776,7 @@ func (s *StateDB) Prepare(thash, bhash common.Hash, ti int) { ...@@ -764,6 +776,7 @@ func (s *StateDB) Prepare(thash, bhash common.Hash, ti int) {
s.thash = thash s.thash = thash
s.bhash = bhash s.bhash = bhash
s.txIndex = ti s.txIndex = ti
s.accessList = newAccessList()
} }
func (s *StateDB) clearJournalAndRefund() { func (s *StateDB) clearJournalAndRefund() {
...@@ -815,3 +828,63 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { ...@@ -815,3 +828,63 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
return nil 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)
}
...@@ -680,3 +680,177 @@ func TestDeleteCreateRevert(t *testing.T) { ...@@ -680,3 +680,177 @@ func TestDeleteCreateRevert(t *testing.T) {
t.Fatalf("self-destructed contract came alive") 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)
}
}
...@@ -79,6 +79,7 @@ type Message interface { ...@@ -79,6 +79,7 @@ type Message interface {
Nonce() uint64 Nonce() uint64
CheckNonce() bool CheckNonce() bool
Data() []byte Data() []byte
AccessList() types.AccessList
L1Timestamp() uint64 L1Timestamp() uint64
L1BlockNumber() *big.Int L1BlockNumber() *big.Int
...@@ -253,6 +254,11 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo ...@@ -253,6 +254,11 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
vmerr error 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 { if contractCreation {
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value) ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
} else { } else {
......
// 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 <http://www.gnu.org/licenses/>.
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
}
...@@ -479,6 +479,7 @@ type Message struct { ...@@ -479,6 +479,7 @@ type Message struct {
gasPrice *big.Int gasPrice *big.Int
data []byte data []byte
checkNonce bool checkNonce bool
accessList AccessList
l1Timestamp uint64 l1Timestamp uint64
l1BlockNumber *big.Int l1BlockNumber *big.Int
...@@ -495,6 +496,7 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b ...@@ -495,6 +496,7 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b
gasPrice: gasPrice, gasPrice: gasPrice,
data: data, data: data,
checkNonce: checkNonce, checkNonce: checkNonce,
accessList: AccessList{},
l1Timestamp: l1Timestamp, l1Timestamp: l1Timestamp,
l1BlockNumber: l1BlockNumber, l1BlockNumber: l1BlockNumber,
...@@ -510,6 +512,7 @@ func (m Message) Gas() uint64 { return m.gasLimit } ...@@ -510,6 +512,7 @@ func (m Message) Gas() uint64 { return m.gasLimit }
func (m Message) Nonce() uint64 { return m.nonce } func (m Message) Nonce() uint64 { return m.nonce }
func (m Message) Data() []byte { return m.data } func (m Message) Data() []byte { return m.data }
func (m Message) CheckNonce() bool { return m.checkNonce } 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) L1Timestamp() uint64 { return m.l1Timestamp }
func (m Message) L1BlockNumber() *big.Int { return m.l1BlockNumber } func (m Message) L1BlockNumber() *big.Int { return m.l1BlockNumber }
......
...@@ -77,6 +77,55 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ ...@@ -77,6 +77,55 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{9}): &blake2F{}, 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. // RunPrecompiledContract runs and evaluates the output of a precompiled contract.
func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) { func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {
gas := p.RequiredGas(input) gas := p.RequiredGas(input)
...@@ -170,13 +219,18 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) { ...@@ -170,13 +219,18 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) {
} }
// bigModExp implements a native big integer exponential modular operation. // bigModExp implements a native big integer exponential modular operation.
type bigModExp struct{} type bigModExp struct {
eip2565 bool
}
var ( var (
big1 = big.NewInt(1) big1 = big.NewInt(1)
big3 = big.NewInt(3)
big4 = big.NewInt(4) big4 = big.NewInt(4)
big7 = big.NewInt(7)
big8 = big.NewInt(8) big8 = big.NewInt(8)
big16 = big.NewInt(16) big16 = big.NewInt(16)
big20 = big.NewInt(20)
big32 = big.NewInt(32) big32 = big.NewInt(32)
big64 = big.NewInt(64) big64 = big.NewInt(64)
big96 = big.NewInt(96) big96 = big.NewInt(96)
...@@ -186,6 +240,34 @@ var ( ...@@ -186,6 +240,34 @@ var (
big199680 = big.NewInt(199680) 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. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bigModExp) RequiredGas(input []byte) uint64 { func (c *bigModExp) RequiredGas(input []byte) uint64 {
var ( var (
...@@ -220,25 +302,36 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 { ...@@ -220,25 +302,36 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 {
adjExpLen.Mul(big8, adjExpLen) adjExpLen.Mul(big8, adjExpLen)
} }
adjExpLen.Add(adjExpLen, big.NewInt(int64(msb))) adjExpLen.Add(adjExpLen, big.NewInt(int64(msb)))
// Calculate the gas cost of the operation // Calculate the gas cost of the operation
gas := new(big.Int).Set(math.BigMax(modLen, baseLen)) gas := new(big.Int).Set(math.BigMax(modLen, baseLen))
switch { if c.eip2565 {
case gas.Cmp(big64) <= 0: // 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) gas.Mul(gas, gas)
case gas.Cmp(big1024) <= 0:
gas = new(big.Int).Add( gas.Mul(gas, math.BigMax(adjExpLen, big1))
new(big.Int).Div(new(big.Int).Mul(gas, gas), big4), // 2. Different divisor (`GQUADDIVISOR`) (3)
new(big.Int).Sub(new(big.Int).Mul(big96, gas), big3072), gas.Div(gas, big3)
) if gas.BitLen() > 64 {
default: return math.MaxUint64
gas = new(big.Int).Add( }
new(big.Int).Div(new(big.Int).Mul(gas, gas), big16), // 3. Minimum price of 200 gas
new(big.Int).Sub(new(big.Int).Mul(big480, gas), big199680), if gas.Uint64() < 200 {
) return 200
}
return gas.Uint64()
} }
gas = modexpMultComplexity(gas)
gas.Mul(gas, math.BigMax(adjExpLen, big1)) gas.Mul(gas, math.BigMax(adjExpLen, big1))
gas.Div(gas, new(big.Int).SetUint64(params.ModExpQuadCoeffDiv)) gas.Div(gas, big20)
if gas.BitLen() > 64 { if gas.BitLen() > 64 {
return math.MaxUint64 return math.MaxUint64
......
...@@ -91,7 +91,11 @@ func enable2200(jt *JumpTable) { ...@@ -91,7 +91,11 @@ func enable2200(jt *JumpTable) {
jt[SSTORE].dynamicGas = gasSStoreEIP2200 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].constantGas = 0
jt[SLOAD].dynamicGas = gasSLoadEIP2929 jt[SLOAD].dynamicGas = gasSLoadEIP2929
...@@ -124,3 +128,48 @@ func enableMinimal2929(jt *JumpTable) { ...@@ -124,3 +128,48 @@ func enableMinimal2929(jt *JumpTable) {
jt[SELFDESTRUCT].constantGas = params.SelfdestructGasEIP150 jt[SELFDESTRUCT].constantGas = params.SelfdestructGasEIP150
jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929 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
}
...@@ -27,4 +27,5 @@ var ( ...@@ -27,4 +27,5 @@ var (
ErrInsufficientBalance = errors.New("insufficient balance for transfer") ErrInsufficientBalance = errors.New("insufficient balance for transfer")
ErrContractAddressCollision = errors.New("contract address collision") ErrContractAddressCollision = errors.New("contract address collision")
ErrNoCompatibleInterpreter = errors.New("no compatible interpreter") ErrNoCompatibleInterpreter = errors.New("no compatible interpreter")
ErrGasUintOverflow = errors.New("gas uint64 overflow")
) )
...@@ -55,6 +55,9 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err ...@@ -55,6 +55,9 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err
if evm.chainRules.IsIstanbul { if evm.chainRules.IsIstanbul {
precompiles = PrecompiledContractsIstanbul precompiles = PrecompiledContractsIstanbul
} }
if evm.chainRules.IsBerlin {
precompiles = PrecompiledContractsBerlin
}
if p := precompiles[*contract.CodeAddr]; p != nil { if p := precompiles[*contract.CodeAddr]; p != nil {
return RunPrecompiledContract(p, input, contract) return RunPrecompiledContract(p, input, contract)
} }
...@@ -220,6 +223,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas ...@@ -220,6 +223,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if evm.chainRules.IsIstanbul { if evm.chainRules.IsIstanbul {
precompiles = PrecompiledContractsIstanbul precompiles = PrecompiledContractsIstanbul
} }
if evm.chainRules.IsBerlin {
precompiles = PrecompiledContractsBerlin
}
if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 { if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer // Calling a non existing account, don't do anything, but ping the tracer
if evm.vmConfig.Debug && evm.depth == 0 { if evm.vmConfig.Debug && evm.depth == 0 {
...@@ -413,7 +419,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, ...@@ -413,7 +419,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
} }
nonce := evm.StateDB.GetNonce(caller.Address()) nonce := evm.StateDB.GetNonce(caller.Address())
evm.StateDB.SetNonce(caller.Address(), nonce+1) 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 // Ensure there's no existing contract already at the designated address
contractHash := evm.StateDB.GetCodeHash(address) contractHash := evm.StateDB.GetCodeHash(address)
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) {
......
...@@ -57,6 +57,16 @@ type StateDB interface { ...@@ -57,6 +57,16 @@ type StateDB interface {
// is defined according to EIP161 (balance = nonce = code = 0). // is defined according to EIP161 (balance = nonce = code = 0).
Empty(common.Address) bool 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) RevertToSnapshot(int)
Snapshot() int Snapshot() int
......
...@@ -94,8 +94,13 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { ...@@ -94,8 +94,13 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
if !cfg.JumpTable[STOP].valid { if !cfg.JumpTable[STOP].valid {
var jt JumpTable var jt JumpTable
switch { switch {
case evm.chainRules.IsBerlin:
jt = berlinInstructionSet
case evm.chainRules.IsIstanbul: case evm.chainRules.IsIstanbul:
jt = istanbulInstructionSet jt = istanbulInstructionSet
if rcfg.UsingOVM {
enableMinimal2929(&jt)
}
case evm.chainRules.IsConstantinople: case evm.chainRules.IsConstantinople:
jt = constantinopleInstructionSet jt = constantinopleInstructionSet
case evm.chainRules.IsByzantium: case evm.chainRules.IsByzantium:
...@@ -116,10 +121,6 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { ...@@ -116,10 +121,6 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
log.Error("EIP activation failed", "eip", eip, "error", err) log.Error("EIP activation failed", "eip", eip, "error", err)
} }
} }
// Enable minimal eip 2929
if rcfg.UsingOVM {
enableMinimal2929(&jt)
}
cfg.JumpTable = jt cfg.JumpTable = jt
} }
......
...@@ -61,11 +61,21 @@ var ( ...@@ -61,11 +61,21 @@ var (
byzantiumInstructionSet = newByzantiumInstructionSet() byzantiumInstructionSet = newByzantiumInstructionSet()
constantinopleInstructionSet = newConstantinopleInstructionSet() constantinopleInstructionSet = newConstantinopleInstructionSet()
istanbulInstructionSet = newIstanbulInstructionSet() istanbulInstructionSet = newIstanbulInstructionSet()
berlinInstructionSet = newBerlinInstructionSet()
) )
// JumpTable contains the EVM opcodes supported at a given fork. // JumpTable contains the EVM opcodes supported at a given fork.
type JumpTable [256]operation 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 // newIstanbulInstructionSet returns the frontier, homestead
// byzantium, contantinople and petersburg instructions. // byzantium, contantinople and petersburg instructions.
func newIstanbulInstructionSet() JumpTable { func newIstanbulInstructionSet() JumpTable {
......
...@@ -21,12 +21,14 @@ import ( ...@@ -21,12 +21,14 @@ import (
"fmt" "fmt"
"io" "io"
"math/big" "math/big"
"strings"
"time" "time"
"github.com/ethereum-optimism/optimism/l2geth/common" "github.com/ethereum-optimism/optimism/l2geth/common"
"github.com/ethereum-optimism/optimism/l2geth/common/hexutil" "github.com/ethereum-optimism/optimism/l2geth/common/hexutil"
"github.com/ethereum-optimism/optimism/l2geth/common/math" "github.com/ethereum-optimism/optimism/l2geth/common/math"
"github.com/ethereum-optimism/optimism/l2geth/core/types" "github.com/ethereum-optimism/optimism/l2geth/core/types"
"github.com/ethereum-optimism/optimism/l2geth/params"
) )
// Storage represents a contract's storage. // Storage represents a contract's storage.
...@@ -49,6 +51,8 @@ type LogConfig struct { ...@@ -49,6 +51,8 @@ type LogConfig struct {
DisableStorage bool // disable storage capture DisableStorage bool // disable storage capture
Debug bool // print output during capture end Debug bool // print output during capture end
Limit int // maximum length of output, but zero means unlimited 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 //go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
...@@ -254,3 +258,74 @@ func WriteLogs(writer io.Writer, logs []*types.Log) { ...@@ -254,3 +258,74 @@ func WriteLogs(writer io.Writer, logs []*types.Log) {
fmt.Fprintln(writer) 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
}
This diff is collapsed.
// 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 <http://www.gnu.org/licenses/>.
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
}
...@@ -106,6 +106,9 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { ...@@ -106,6 +106,9 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
vmenv = NewEnv(cfg) vmenv = NewEnv(cfg)
sender = vm.AccountRef(cfg.Origin) 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) cfg.State.CreateAccount(address)
// set the receiver's (the executing contract) code for execution. // set the receiver's (the executing contract) code for execution.
cfg.State.SetCode(address, code) cfg.State.SetCode(address, code)
...@@ -135,7 +138,9 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { ...@@ -135,7 +138,9 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
vmenv = NewEnv(cfg) vmenv = NewEnv(cfg)
sender = vm.AccountRef(cfg.Origin) 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. // Call the code with the given configuration.
code, address, leftOverGas, err := vmenv.Create( code, address, leftOverGas, err := vmenv.Create(
sender, sender,
...@@ -157,6 +162,11 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er ...@@ -157,6 +162,11 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
vmenv := NewEnv(cfg) vmenv := NewEnv(cfg)
sender := cfg.State.GetOrNewStateObject(cfg.Origin) 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. // Call the code with the given configuration.
ret, leftOverGas, err := vmenv.Call( ret, leftOverGas, err := vmenv.Call(
sender, sender,
......
...@@ -17,12 +17,15 @@ ...@@ -17,12 +17,15 @@
package runtime package runtime
import ( import (
"fmt"
"math/big" "math/big"
"os"
"strings" "strings"
"testing" "testing"
"github.com/ethereum-optimism/optimism/l2geth/accounts/abi" "github.com/ethereum-optimism/optimism/l2geth/accounts/abi"
"github.com/ethereum-optimism/optimism/l2geth/common" "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/rawdb"
"github.com/ethereum-optimism/optimism/l2geth/core/state" "github.com/ethereum-optimism/optimism/l2geth/core/state"
"github.com/ethereum-optimism/optimism/l2geth/core/vm" "github.com/ethereum-optimism/optimism/l2geth/core/vm"
...@@ -203,3 +206,115 @@ func BenchmarkEVM_CREATE2_1200(bench *testing.B) { ...@@ -203,3 +206,115 @@ func BenchmarkEVM_CREATE2_1200(bench *testing.B) {
// initcode size 1200K, repeatedly calls CREATE2 and then modifies the mem contents // initcode size 1200K, repeatedly calls CREATE2 and then modifies the mem contents
benchmarkEVM_Create(bench, "5b5862124f80600080f5600152600056") 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)
}
}
...@@ -141,13 +141,7 @@ func (b *EthAPIBackend) SequencerClientHttp() string { ...@@ -141,13 +141,7 @@ func (b *EthAPIBackend) SequencerClientHttp() string {
} }
func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
// Pending block is only known by the miner if number == rpc.LatestBlockNumber || number == rpc.PendingBlockNumber {
if number == rpc.PendingBlockNumber {
block := b.eth.miner.PendingBlock()
return block.Header(), nil
}
// Otherwise resolve and return the block
if number == rpc.LatestBlockNumber {
return b.eth.blockchain.CurrentBlock().Header(), nil return b.eth.blockchain.CurrentBlock().Header(), nil
} }
return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil
...@@ -175,13 +169,7 @@ func (b *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*ty ...@@ -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) { func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
// Pending block is only known by the miner if number == rpc.LatestBlockNumber || number == rpc.PendingBlockNumber {
if number == rpc.PendingBlockNumber {
block := b.eth.miner.PendingBlock()
return block, nil
}
// Otherwise resolve and return the block
if number == rpc.LatestBlockNumber {
return b.eth.blockchain.CurrentBlock(), nil return b.eth.blockchain.CurrentBlock(), nil
} }
return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil
...@@ -213,12 +201,6 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r ...@@ -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) { 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) header, err := b.HeaderByNumber(ctx, number)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
...@@ -271,7 +253,7 @@ func (b *EthAPIBackend) GetTd(blockHash common.Hash) *big.Int { ...@@ -271,7 +253,7 @@ func (b *EthAPIBackend) GetTd(blockHash common.Hash) *big.Int {
return b.eth.blockchain.GetTdByHash(blockHash) 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: // This was removed upstream:
// https://github.com/ethereum-optimism/optimism/l2geth/commit/39f502329fac4640cfb71959c3496f19ea88bc85#diff-9886da3412b43831145f62cec6e895eb3613a175b945e5b026543b7463454603 // 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 // 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 ...@@ -282,7 +264,7 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *sta
vmError := func() error { return nil } vmError := func() error { return nil }
context := core.NewEVMContext(msg, header, b.eth.BlockChain(), 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 { func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
......
...@@ -38,6 +38,7 @@ import ( ...@@ -38,6 +38,7 @@ import (
"github.com/ethereum-optimism/optimism/l2geth/eth/tracers" "github.com/ethereum-optimism/optimism/l2geth/eth/tracers"
"github.com/ethereum-optimism/optimism/l2geth/internal/ethapi" "github.com/ethereum-optimism/optimism/l2geth/internal/ethapi"
"github.com/ethereum-optimism/optimism/l2geth/log" "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/rlp"
"github.com/ethereum-optimism/optimism/l2geth/rpc" "github.com/ethereum-optimism/optimism/l2geth/rpc"
"github.com/ethereum-optimism/optimism/l2geth/trie" "github.com/ethereum-optimism/optimism/l2geth/trie"
...@@ -106,17 +107,15 @@ func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.Block ...@@ -106,17 +107,15 @@ func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.Block
var from, to *types.Block var from, to *types.Block
switch start { switch start {
case rpc.PendingBlockNumber:
from = api.eth.miner.PendingBlock()
case rpc.LatestBlockNumber: case rpc.LatestBlockNumber:
case rpc.PendingBlockNumber:
from = api.eth.blockchain.CurrentBlock() from = api.eth.blockchain.CurrentBlock()
default: default:
from = api.eth.blockchain.GetBlockByNumber(uint64(start)) from = api.eth.blockchain.GetBlockByNumber(uint64(start))
} }
switch end { switch end {
case rpc.PendingBlockNumber:
to = api.eth.miner.PendingBlock()
case rpc.LatestBlockNumber: case rpc.LatestBlockNumber:
case rpc.PendingBlockNumber:
to = api.eth.blockchain.CurrentBlock() to = api.eth.blockchain.CurrentBlock()
default: default:
to = api.eth.blockchain.GetBlockByNumber(uint64(end)) to = api.eth.blockchain.GetBlockByNumber(uint64(end))
...@@ -357,9 +356,8 @@ func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.B ...@@ -357,9 +356,8 @@ func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.B
var block *types.Block var block *types.Block
switch number { switch number {
case rpc.PendingBlockNumber:
block = api.eth.miner.PendingBlock()
case rpc.LatestBlockNumber: case rpc.LatestBlockNumber:
case rpc.PendingBlockNumber:
block = api.eth.blockchain.CurrentBlock() block = api.eth.blockchain.CurrentBlock()
default: default:
block = api.eth.blockchain.GetBlockByNumber(uint64(number)) block = api.eth.blockchain.GetBlockByNumber(uint64(number))
...@@ -561,9 +559,30 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block ...@@ -561,9 +559,30 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
// Execute transaction, either tracing all or just the requested one // Execute transaction, either tracing all or just the requested one
var ( 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() { for i, tx := range block.Transactions() {
// Prepare the trasaction for un-traced execution // Prepare the trasaction for un-traced execution
var ( var (
...@@ -579,7 +598,9 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block ...@@ -579,7 +598,9 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
if tx.Hash() == txHash || txHash == (common.Hash{}) { if tx.Hash() == txHash || txHash == (common.Hash{}) {
// Generate a unique temporary file to dump it into // 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]) 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) dump, err = ioutil.TempFile(os.TempDir(), prefix)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -595,7 +616,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block ...@@ -595,7 +616,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
} }
} }
// Execute the transaction and flush any traces to disk // 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())) _, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
if writer != nil { if writer != nil {
writer.Flush() writer.Flush()
...@@ -755,8 +776,19 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v ...@@ -755,8 +776,19 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v
default: default:
tracer = vm.NewStructLogger(config.LogConfig) 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. // 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())) ret, gas, failed, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
if err != nil { if err != nil {
......
...@@ -120,6 +120,8 @@ type CallMsg struct { ...@@ -120,6 +120,8 @@ type CallMsg struct {
Value *big.Int // amount of wei sent along with the call Value *big.Int // amount of wei sent along with the call
Data []byte // input data, usually an ABI-encoded contract method invocation Data []byte // input data, usually an ABI-encoded contract method invocation
AccessList types.AccessList // EIP-2930 access list.
L1Timestamp uint64 L1Timestamp uint64
L1BlockNumber *big.Int L1BlockNumber *big.Int
QueueOrigin types.QueueOrigin QueueOrigin types.QueueOrigin
......
...@@ -910,7 +910,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo ...@@ -910,7 +910,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
defer cancel() defer cancel()
// Get a new instance of the EVM. // 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 { if err != nil {
return nil, 0, false, err return nil, 0, false, err
} }
...@@ -989,6 +989,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash ...@@ -989,6 +989,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
} }
cap = hi cap = hi
if !rcfg.UsingOVM {
// Set sender address or use a default if none specified // Set sender address or use a default if none specified
if args.From == nil { if args.From == nil {
if wallets := b.AccountManager().Wallets(); len(wallets) > 0 { if wallets := b.AccountManager().Wallets(); len(wallets) > 0 {
...@@ -997,6 +998,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash ...@@ -997,6 +998,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
} }
} }
} }
}
// Use zero-address if none other is available // Use zero-address if none other is available
if args.From == nil { if args.From == nil {
args.From = &common.Address{} args.From = &common.Address{}
......
...@@ -59,7 +59,7 @@ type Backend interface { ...@@ -59,7 +59,7 @@ type Backend interface {
StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
GetTd(hash common.Hash) *big.Int 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 SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
......
...@@ -199,7 +199,7 @@ func (b *LesApiBackend) GetTd(hash common.Hash) *big.Int { ...@@ -199,7 +199,7 @@ func (b *LesApiBackend) GetTd(hash common.Hash) *big.Int {
return b.eth.blockchain.GetTdByHash(hash) 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) state.SetBalance(msg.From(), math.MaxBig256)
context := core.NewEVMContext(msg, header, b.eth.blockchain, nil) context := core.NewEVMContext(msg, header, b.eth.blockchain, nil)
return vm.NewEVM(context, state, b.eth.chainConfig, vm.Config{}), state.Error, nil return vm.NewEVM(context, state, b.eth.chainConfig, vm.Config{}), state.Error, nil
......
...@@ -215,16 +215,16 @@ var ( ...@@ -215,16 +215,16 @@ var (
// //
// This configuration is intentionally not using keyed fields to force anyone // This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields. // 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 // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
// and accepted by the Ethereum core developers into the Clique consensus. // and accepted by the Ethereum core developers into the Clique consensus.
// //
// This configuration is intentionally not using keyed fields to force anyone // This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields. // 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)) TestRules = TestChainConfig.Rules(new(big.Int))
) )
...@@ -295,6 +295,8 @@ type ChainConfig struct { ...@@ -295,6 +295,8 @@ type ChainConfig struct {
PetersburgBlock *big.Int `json:"petersburgBlock,omitempty"` // Petersburg switch block (nil = same as Constantinople) 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) 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) MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) 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) EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated)
// Various consensus engines // Various consensus engines
...@@ -332,7 +334,7 @@ func (c *ChainConfig) String() string { ...@@ -332,7 +334,7 @@ func (c *ChainConfig) String() string {
default: default:
engine = "unknown" 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.ChainID,
c.HomesteadBlock, c.HomesteadBlock,
c.DAOForkBlock, c.DAOForkBlock,
...@@ -345,6 +347,7 @@ func (c *ChainConfig) String() string { ...@@ -345,6 +347,7 @@ func (c *ChainConfig) String() string {
c.PetersburgBlock, c.PetersburgBlock,
c.IstanbulBlock, c.IstanbulBlock,
c.MuirGlacierBlock, c.MuirGlacierBlock,
c.BerlinBlock,
engine, engine,
) )
} }
...@@ -401,6 +404,11 @@ func (c *ChainConfig) IsIstanbul(num *big.Int) bool { ...@@ -401,6 +404,11 @@ func (c *ChainConfig) IsIstanbul(num *big.Int) bool {
return isForked(c.IstanbulBlock, num) 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 // IsEWASM returns whether num represents a block number after the EWASM fork
func (c *ChainConfig) IsEWASM(num *big.Int) bool { func (c *ChainConfig) IsEWASM(num *big.Int) bool {
return isForked(c.EWASMBlock, num) return isForked(c.EWASMBlock, num)
...@@ -442,6 +450,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { ...@@ -442,6 +450,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
{"petersburgBlock", c.PetersburgBlock}, {"petersburgBlock", c.PetersburgBlock},
{"istanbulBlock", c.IstanbulBlock}, {"istanbulBlock", c.IstanbulBlock},
{"muirGlacierBlock", c.MuirGlacierBlock}, {"muirGlacierBlock", c.MuirGlacierBlock},
{name: "berlinBlock", block: c.BerlinBlock},
} { } {
if lastFork.name != "" { if lastFork.name != "" {
// Next one must be higher number // Next one must be higher number
...@@ -498,6 +507,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi ...@@ -498,6 +507,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi
if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, head) { if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, head) {
return newCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock) 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) { if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) {
return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock) return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock)
} }
...@@ -568,6 +580,7 @@ type Rules struct { ...@@ -568,6 +580,7 @@ type Rules struct {
ChainID *big.Int ChainID *big.Int
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
IsBerlin bool
} }
// Rules ensures c's ChainID is not nil. // Rules ensures c's ChainID is not nil.
...@@ -586,5 +599,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { ...@@ -586,5 +599,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules {
IsConstantinople: c.IsConstantinople(num), IsConstantinople: c.IsConstantinople(num),
IsPetersburg: c.IsPetersburg(num), IsPetersburg: c.IsPetersburg(num),
IsIstanbul: c.IsIstanbul(num), IsIstanbul: c.IsIstanbul(num),
IsBerlin: c.IsBerlin(num),
} }
} }
...@@ -53,6 +53,10 @@ const ( ...@@ -53,6 +53,10 @@ const (
NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original 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. 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. 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 SstoreInitGasEIP2200 uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero
...@@ -65,6 +69,12 @@ const ( ...@@ -65,6 +69,12 @@ const (
ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST
WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_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. JumpdestGas uint64 = 1 // Once per JUMPDEST operation.
EpochDuration uint64 = 30000 // Duration between proof-of-work epochs. EpochDuration uint64 = 30000 // Duration between proof-of-work epochs.
...@@ -82,6 +92,8 @@ const ( ...@@ -82,6 +92,8 @@ const (
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. 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. 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) 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 // These have been changed during the course of the chain
CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction.
......
#!/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
...@@ -6,16 +6,18 @@ REPO=$DIR/.. ...@@ -6,16 +6,18 @@ REPO=$DIR/..
IS_VERIFIER= IS_VERIFIER=
ROLLUP_SYNC_SERVICE_ENABLE=true ROLLUP_SYNC_SERVICE_ENABLE=true
DATADIR=$HOME/.ethereum DATADIR=$HOME/.ethereum
TARGET_GAS_LIMIT=11000000 TARGET_GAS_LIMIT=15000000
ETH1_CTC_DEPLOYMENT_HEIGHT=12686738 ETH1_CTC_DEPLOYMENT_HEIGHT=12686738
ROLLUP_CLIENT_HTTP=http://localhost:7878 ROLLUP_CLIENT_HTTP=http://localhost:7878
ROLLUP_POLL_INTERVAL=15s ROLLUP_POLL_INTERVAL=15s
ROLLUP_TIMESTAMP_REFRESH=3m ROLLUP_TIMESTAMP_REFRESH=15s
CACHE=1024 CACHE=1024
RPC_PORT=8545 RPC_PORT=8545
WS_PORT=8546 WS_PORT=8546
VERBOSITY=3 VERBOSITY=3
ROLLUP_BACKEND=l2 ROLLUP_BACKEND=l2
CHAIN_ID=69
BLOCK_SIGNER_ADDRESS=0x00000398232E2064F896018496b4b44b3D62751F
USAGE=" USAGE="
Start the Sequencer or Verifier with most configuration pre-set. Start the Sequencer or Verifier with most configuration pre-set.
...@@ -174,15 +176,22 @@ cmd="$cmd --ws" ...@@ -174,15 +176,22 @@ cmd="$cmd --ws"
cmd="$cmd --wsaddr 0.0.0.0" cmd="$cmd --wsaddr 0.0.0.0"
cmd="$cmd --wsport $WS_PORT" cmd="$cmd --wsport $WS_PORT"
cmd="$cmd --wsorigins '*'" 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 --gasprice 0"
cmd="$cmd --nousb" cmd="$cmd --nousb"
cmd="$cmd --gcmode=archive" 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 if [[ ! -z "$IS_VERIFIER" ]]; then
cmd="$cmd --rollup.verifier" cmd="$cmd --rollup.verifier"
fi fi
cmd="$cmd --verbosity=$VERBOSITY" cmd="$cmd --verbosity=$VERBOSITY"
echo -e "Running:\nTARGET_GAS_LIMIT=$TARGET_GAS_LIMIT USING_OVM=true $cmd" 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
...@@ -181,6 +181,16 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config) (*stat ...@@ -181,6 +181,16 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config) (*stat
context.GetHash = vmTestBlockHash context.GetHash = vmTestBlockHash
evm := vm.NewEVM(context, statedb, config, vmconfig) 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 := new(core.GasPool)
gaspool.AddGas(block.GasLimit()) gaspool.AddGas(block.GasLimit())
snapshot := statedb.Snapshot() snapshot := statedb.Snapshot()
......
...@@ -65,6 +65,8 @@ import { makeL2GenesisFile } from '../src/make-genesis' ...@@ -65,6 +65,8 @@ import { makeL2GenesisFile } from '../src/make-genesis'
const l1FeeWalletAddress = env.L1_FEE_WALLET_ADDRESS const l1FeeWalletAddress = env.L1_FEE_WALLET_ADDRESS
// The L1 cross domain messenger address, used for cross domain messaging // The L1 cross domain messenger address, used for cross domain messaging
const l1CrossDomainMessengerAddress = env.L1_CROSS_DOMAIN_MESSENGER_ADDRESS 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(whitelistOwner, 'WHITELIST_OWNER')
ensure(gasPriceOracleOwner, 'GAS_PRICE_ORACLE_OWNER') ensure(gasPriceOracleOwner, 'GAS_PRICE_ORACLE_OWNER')
...@@ -74,6 +76,7 @@ import { makeL2GenesisFile } from '../src/make-genesis' ...@@ -74,6 +76,7 @@ import { makeL2GenesisFile } from '../src/make-genesis'
ensure(l1StandardBridgeAddress, 'L1_STANDARD_BRIDGE_ADDRESS') ensure(l1StandardBridgeAddress, 'L1_STANDARD_BRIDGE_ADDRESS')
ensure(l1FeeWalletAddress, 'L1_FEE_WALLET_ADDRESS') ensure(l1FeeWalletAddress, 'L1_FEE_WALLET_ADDRESS')
ensure(l1CrossDomainMessengerAddress, 'L1_CROSS_DOMAIN_MESSENGER_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. // 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)) { if (env.WHITELIST_OWNER === '0x' + '00'.repeat(20)) {
...@@ -96,6 +99,7 @@ import { makeL2GenesisFile } from '../src/make-genesis' ...@@ -96,6 +99,7 @@ import { makeL2GenesisFile } from '../src/make-genesis'
l1StandardBridgeAddress, l1StandardBridgeAddress,
l1FeeWalletAddress, l1FeeWalletAddress,
l1CrossDomainMessengerAddress, l1CrossDomainMessengerAddress,
berlinBlock,
}) })
fs.writeFileSync(outfile, JSON.stringify(genesis, null, 4)) fs.writeFileSync(outfile, JSON.stringify(genesis, null, 4))
......
...@@ -40,6 +40,8 @@ export interface RollupDeployConfig { ...@@ -40,6 +40,8 @@ export interface RollupDeployConfig {
l1FeeWalletAddress: string l1FeeWalletAddress: string
// Address of the L1CrossDomainMessenger contract. // Address of the L1CrossDomainMessenger contract.
l1CrossDomainMessengerAddress: string l1CrossDomainMessengerAddress: string
// Block height to activate berlin hardfork
berlinBlock: number
} }
/** /**
...@@ -150,6 +152,7 @@ export const makeL2GenesisFile = async ( ...@@ -150,6 +152,7 @@ export const makeL2GenesisFile = async (
petersburgBlock: 0, petersburgBlock: 0,
istanbulBlock: 0, istanbulBlock: 0,
muirGlacierBlock: 0, muirGlacierBlock: 0,
berlinBlock: cfg.berlinBlock,
clique: { clique: {
period: 0, period: 0,
epoch: 30000, epoch: 30000,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment