Commit e808e58b authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into aj/flaky-multipeer

parents c446b09f 37ed84de
---
'@eth-optimism/chain-mon': minor
'@eth-optimism/core-utils': patch
---
Added a new service wallet-mon to identify unexpected transfers from key accounts
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -5,7 +5,6 @@ import "github.com/ethereum/go-ethereum/common"
const (
L2ToL1MessagePasser = "0x4200000000000000000000000000000000000016"
DeployerWhitelist = "0x4200000000000000000000000000000000000002"
LegacyERC20ETH = "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000"
WETH9 = "0x4200000000000000000000000000000000000006"
L2CrossDomainMessenger = "0x4200000000000000000000000000000000000007"
L2StandardBridge = "0x4200000000000000000000000000000000000010"
......@@ -26,7 +25,6 @@ const (
var (
L2ToL1MessagePasserAddr = common.HexToAddress(L2ToL1MessagePasser)
DeployerWhitelistAddr = common.HexToAddress(DeployerWhitelist)
LegacyERC20ETHAddr = common.HexToAddress(LegacyERC20ETH)
WETH9Addr = common.HexToAddress(WETH9)
L2CrossDomainMessengerAddr = common.HexToAddress(L2CrossDomainMessenger)
L2StandardBridgeAddr = common.HexToAddress(L2StandardBridge)
......@@ -49,27 +47,17 @@ var (
// IsProxied returns true for predeploys that will sit behind a proxy contract
func IsProxied(predeployAddr common.Address) bool {
switch predeployAddr {
case LegacyERC20ETHAddr:
case WETH9Addr:
case GovernanceTokenAddr:
case ProxyAdminAddr:
default:
return true
}
return false
}
// IsDeprecated returns true for predeploys we should skip in post-bedrock genesis generation
func IsDeprecated(predeployAddr common.Address) bool {
// TODO: confirm if we can safely add the remaining deprecated predeploys here
// (see https://github.com/ethereum-optimism/optimism/blob/develop/specs/predeploys.md#overview)
return predeployAddr == LegacyERC20ETHAddr
}
func init() {
Predeploys["L2ToL1MessagePasser"] = &L2ToL1MessagePasserAddr
Predeploys["DeployerWhitelist"] = &DeployerWhitelistAddr
Predeploys["LegacyERC20ETH"] = &LegacyERC20ETHAddr
Predeploys["WETH9"] = &WETH9Addr
Predeploys["L2CrossDomainMessenger"] = &L2CrossDomainMessengerAddr
Predeploys["L2StandardBridge"] = &L2StandardBridgeAddr
......
package genesis
import (
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/immutables"
"github.com/ethereum-optimism/optimism/op-chain-ops/state"
)
// BuildL2DeveloperGenesis will build the developer Optimism Genesis
// Block. Suitable for devnets.
func BuildL2DeveloperGenesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Genesis, error) {
// BuildL2DeveloperGenesis will build the L2 genesis block.
func BuildL2Genesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Genesis, error) {
genspec, err := NewL2Genesis(config, l1StartBlock)
if err != nil {
return nil, err
}
db := state.NewMemoryStateDB(genspec)
if config.FundDevAccounts {
FundDevAccounts(db)
}
SetPrecompileBalances(db)
storage, err := NewL2StorageConfig(config, l1StartBlock)
if err != nil {
return nil, err
}
immutable, err := NewL2ImmutableConfig(config, l1StartBlock)
if err != nil {
return nil, err
}
if err := SetL2Proxies(db); err != nil {
return nil, err
}
if err := SetImplementations(db, storage, immutable); err != nil {
return nil, err
}
if err := SetDevOnlyL2Implementations(db, storage, immutable); err != nil {
return nil, err
}
return db.Genesis(), nil
}
// BuildL2MainnetGenesis will build an L2 Genesis suitable for a Superchain mainnet that does not
// require a pre-bedrock migration & supports optional governance token predeploy. Details:
//
// - Creates proxies for predeploys in the address space:
// [0x4200000000000000000000000000000000000000, 0x4200000000000000000000000000000000000800)
//
// - All predeploy proxies owned by the ProxyAdmin
//
// - Predeploys as per the spec except for no LegacyERC20ETH predeploy at
// 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000
//
// - optional governance token at 0x4200000000000000000000000000000000000042 if
// config.EnableGovernance is true (& otherwise a no-impl proxy remains at this address)
//
// - no accounts are pre-funded
func BuildL2MainnetGenesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Genesis, error) {
genspec, err := NewL2Genesis(config, l1StartBlock)
if err != nil {
return nil, err
}
db := state.NewMemoryStateDB(genspec)
storage, err := NewL2StorageConfig(config, l1StartBlock)
if err != nil {
return nil, err
......@@ -90,20 +36,10 @@ func BuildL2MainnetGenesis(config *DeployConfig, l1StartBlock *types.Block) (*co
}
// Set up the proxies
depBytecode, err := bindings.GetDeployedBytecode("Proxy")
err = setProxies(db, predeploys.ProxyAdminAddr, bigL2PredeployNamespace, 2048)
if err != nil {
return nil, err
}
if len(depBytecode) == 0 {
return nil, errors.New("Proxy has empty bytecode")
}
for i := uint64(0); i <= 2048; i++ {
bigAddr := new(big.Int).Or(bigL2PredeployNamespace, new(big.Int).SetUint64(i))
addr := common.BigToAddress(bigAddr)
db.CreateAccount(addr)
db.SetCode(addr, depBytecode)
db.SetState(addr, AdminSlot, predeploys.ProxyAdminAddr.Hash())
}
// Set up the implementations
deployResults, err := immutables.BuildOptimism(immutable)
......@@ -112,9 +48,6 @@ func BuildL2MainnetGenesis(config *DeployConfig, l1StartBlock *types.Block) (*co
}
for name, predeploy := range predeploys.Predeploys {
addr := *predeploy
if predeploys.IsDeprecated(addr) {
continue
}
if addr == predeploys.GovernanceTokenAddr && !config.EnableGovernance {
// there is no governance token configured, so skip the governance token predeploy
log.Warn("Governance is not enabled, skipping governance token predeploy.")
......
......@@ -28,58 +28,8 @@ func init() {
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
func TestBuildL2DeveloperGenesis(t *testing.T) {
config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json")
require.Nil(t, err)
backend := backends.NewSimulatedBackend(
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
},
15000000,
)
block, err := backend.BlockByNumber(context.Background(), common.Big0)
require.NoError(t, err)
gen, err := genesis.BuildL2DeveloperGenesis(config, block)
require.Nil(t, err)
require.NotNil(t, gen)
proxyBytecode, err := bindings.GetDeployedBytecode("Proxy")
require.NoError(t, err)
for name, address := range predeploys.Predeploys {
addr := *address
account, ok := gen.Alloc[addr]
require.Equal(t, true, ok)
require.Greater(t, len(account.Code), 0)
if name == "GovernanceToken" || name == "LegacyERC20ETH" || name == "ProxyAdmin" || name == "WETH9" {
continue
}
adminSlot, ok := account.Storage[genesis.AdminSlot]
require.Equal(t, true, ok, name)
require.Equal(t, predeploys.ProxyAdminAddr.Hash(), adminSlot)
require.Equal(t, proxyBytecode, account.Code)
}
require.Equal(t, 2343, len(gen.Alloc))
if writeFile {
file, _ := json.MarshalIndent(gen, "", " ")
_ = os.WriteFile("genesis.json", file, 0644)
}
}
func TestBuildL2DeveloperGenesisDevAccountsFunding(t *testing.T) {
config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json")
require.Nil(t, err)
config.FundDevAccounts = false
err = config.InitDeveloperDeployedAddresses()
require.NoError(t, err)
// Tests the BuildL2MainnetGenesis factory with the provided config.
func testBuildL2Genesis(t *testing.T, config *genesis.DeployConfig) *core.Genesis {
backend := backends.NewSimulatedBackend(
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
......@@ -89,29 +39,7 @@ func TestBuildL2DeveloperGenesisDevAccountsFunding(t *testing.T) {
block, err := backend.BlockByNumber(context.Background(), common.Big0)
require.NoError(t, err)
gen, err := genesis.BuildL2DeveloperGenesis(config, block)
require.NoError(t, err)
require.Equal(t, 2321, len(gen.Alloc))
}
// Tests the BuildL2MainnetGenesis factory. enableGovernance is used to override enableGovernance
// config option. When false, the test confirms the governance token predeploy address instead
// holds a proxy contract.
func testBuildL2Genesis(t *testing.T, enableGovernance bool) {
config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json")
require.Nil(t, err)
config.EnableGovernance = enableGovernance
backend := backends.NewSimulatedBackend(
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
},
15000000,
)
block, err := backend.BlockByNumber(context.Background(), common.Big0)
require.NoError(t, err)
gen, err := genesis.BuildL2MainnetGenesis(config, block)
gen, err := genesis.BuildL2Genesis(config, block)
require.Nil(t, err)
require.NotNil(t, gen)
......@@ -122,17 +50,12 @@ func testBuildL2Genesis(t *testing.T, enableGovernance bool) {
addr := *predeploy
account, ok := gen.Alloc[addr]
if predeploys.IsDeprecated(addr) && !predeploys.IsProxied(addr) {
// deprecated, non-proxied predeploys should have no account
require.Equal(t, false, ok, name)
continue
}
require.Equal(t, true, ok, name)
require.Greater(t, len(account.Code), 0)
adminSlot, ok := account.Storage[genesis.AdminSlot]
isProxy := predeploys.IsProxied(addr) ||
(!enableGovernance && addr == predeploys.GovernanceTokenAddr)
(!config.EnableGovernance && addr == predeploys.GovernanceTokenAddr)
if isProxy {
require.Equal(t, true, ok, name)
require.Equal(t, predeploys.ProxyAdminAddr.Hash(), adminSlot)
......@@ -142,18 +65,39 @@ func testBuildL2Genesis(t *testing.T, enableGovernance bool) {
require.NotEqual(t, proxyBytecode, account.Code, name)
}
}
require.Equal(t, 2063, len(gen.Alloc))
if writeFile {
file, _ := json.MarshalIndent(gen, "", " ")
_ = os.WriteFile("genesis.json", file, 0644)
}
return gen
}
func TestBuildL2DeveloperGenesis(t *testing.T) {
config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json")
require.Nil(t, err)
config.EnableGovernance = false
config.FundDevAccounts = true
err = config.InitDeveloperDeployedAddresses()
require.NoError(t, err)
gen := testBuildL2Genesis(t, config)
require.Equal(t, 2342, len(gen.Alloc))
}
func TestBuildL2MainnetGenesis(t *testing.T) {
testBuildL2Genesis(t, true)
config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json")
require.Nil(t, err)
config.EnableGovernance = true
config.FundDevAccounts = false
gen := testBuildL2Genesis(t, config)
require.Equal(t, 2064, len(gen.Alloc))
}
func TestBuildL2MainnetNoGovernanceGenesis(t *testing.T) {
testBuildL2Genesis(t, false)
config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json")
require.Nil(t, err)
config.EnableGovernance = false
config.FundDevAccounts = false
gen := testBuildL2Genesis(t, config)
require.Equal(t, 2064, len(gen.Alloc))
}
......@@ -2,12 +2,10 @@ package genesis
import (
"errors"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/immutables"
"github.com/ethereum-optimism/optimism/op-chain-ops/state"
"github.com/ethereum/go-ethereum/common"
......@@ -15,19 +13,6 @@ import (
"github.com/ethereum/go-ethereum/log"
)
// UntouchableCodeHashes contains code hashes of all the contracts
// that should not be touched by the migration process.
type ChainHashMap map[uint64]common.Hash
var (
// UntouchablePredeploys are addresses in the predeploy namespace
// that should not be touched by the migration process.
UntouchablePredeploys = map[common.Address]bool{
predeploys.GovernanceTokenAddr: true,
predeploys.WETH9Addr: true,
}
)
// FundDevAccounts will fund each of the development accounts.
func FundDevAccounts(db vm.StateDB) {
for _, account := range DevAccounts {
......@@ -36,14 +21,6 @@ func FundDevAccounts(db vm.StateDB) {
}
}
// SetL2Proxies will set each of the proxies in the state. It requires
// a Proxy and ProxyAdmin deployment present so that the Proxy bytecode
// can be set in state and the ProxyAdmin can be set as the admin of the
// Proxy.
func SetL2Proxies(db vm.StateDB) error {
return setProxies(db, predeploys.ProxyAdminAddr, bigL2PredeployNamespace, 2048)
}
// SetL1Proxies will set each of the proxies in the state. It requires
// a Proxy and ProxyAdmin deployment present so that the Proxy bytecode
// can be set in state and the ProxyAdmin can be set as the admin of the
......@@ -65,11 +42,6 @@ func setProxies(db vm.StateDB, proxyAdminAddr common.Address, namespace *big.Int
bigAddr := new(big.Int).Or(namespace, new(big.Int).SetUint64(i))
addr := common.BigToAddress(bigAddr)
if UntouchablePredeploys[addr] {
log.Info("Skipping setting proxy", "address", addr)
continue
}
if !db.Exist(addr) {
db.CreateAccount(addr)
}
......@@ -82,87 +54,6 @@ func setProxies(db vm.StateDB, proxyAdminAddr common.Address, namespace *big.Int
return nil
}
func SetLegacyETH(db vm.StateDB, storage state.StorageConfig, immutable immutables.ImmutableConfig) error {
deployResults, err := immutables.BuildOptimism(immutable)
if err != nil {
return err
}
return setupPredeploy(db, deployResults, storage, "LegacyERC20ETH", predeploys.LegacyERC20ETHAddr, predeploys.LegacyERC20ETHAddr)
}
// SetImplementations will set the implementations of the contracts in the state
// and configure the proxies to point to the implementations. It also sets
// the appropriate storage values for each contract at the proxy address.
func SetImplementations(db vm.StateDB, storage state.StorageConfig, immutable immutables.ImmutableConfig) error {
deployResults, err := immutables.BuildOptimism(immutable)
if err != nil {
return err
}
for name, address := range predeploys.Predeploys {
if UntouchablePredeploys[*address] {
continue
}
if *address == predeploys.LegacyERC20ETHAddr {
continue
}
codeAddr, err := AddressToCodeNamespace(*address)
if err != nil {
return fmt.Errorf("error converting to code namespace: %w", err)
}
if !db.Exist(codeAddr) {
db.CreateAccount(codeAddr)
}
db.SetState(*address, ImplementationSlot, codeAddr.Hash())
if err := setupPredeploy(db, deployResults, storage, name, *address, codeAddr); err != nil {
return err
}
code := db.GetCode(codeAddr)
if len(code) == 0 {
return fmt.Errorf("code not set for %s", name)
}
}
return nil
}
func SetDevOnlyL2Implementations(db vm.StateDB, storage state.StorageConfig, immutable immutables.ImmutableConfig) error {
deployResults, err := immutables.BuildOptimism(immutable)
if err != nil {
return err
}
for name, address := range predeploys.Predeploys {
if !UntouchablePredeploys[*address] {
continue
}
db.CreateAccount(*address)
if err := setupPredeploy(db, deployResults, storage, name, *address, *address); err != nil {
return err
}
code := db.GetCode(*address)
if len(code) == 0 {
return fmt.Errorf("code not set for %s", name)
}
}
db.CreateAccount(predeploys.LegacyERC20ETHAddr)
if err := setupPredeploy(db, deployResults, storage, "LegacyERC20ETH", predeploys.LegacyERC20ETHAddr, predeploys.LegacyERC20ETHAddr); err != nil {
return fmt.Errorf("error setting up legacy eth: %w", err)
}
return nil
}
// SetPrecompileBalances will set a single wei at each precompile address.
// This is an optimization to make calling them cheaper. This should only
// be used for devnets.
......
......@@ -185,7 +185,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *
l1Block := l1Genesis.ToBlock()
l2Genesis, err := genesis.BuildL2DeveloperGenesis(deployConf, l1Block)
l2Genesis, err := genesis.BuildL2Genesis(deployConf, l1Block)
require.NoError(t, err, "failed to create l2 genesis")
if alloc.PrefundTestUsers {
for _, addr := range deployParams.Addresses.All() {
......
......@@ -53,7 +53,7 @@ func NewOpGeth(t *testing.T, ctx context.Context, cfg *SystemConfig) (*OpGeth, e
require.Nil(t, err)
l1Block := l1Genesis.ToBlock()
l2Genesis, err := genesis.BuildL2DeveloperGenesis(cfg.DeployConfig, l1Block)
l2Genesis, err := genesis.BuildL2Genesis(cfg.DeployConfig, l1Block)
require.Nil(t, err)
l2GenesisBlock := l2Genesis.ToBlock()
......
......@@ -359,7 +359,7 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) {
}
l1Block := l1Genesis.ToBlock()
l2Genesis, err := genesis.BuildL2DeveloperGenesis(cfg.DeployConfig, l1Block)
l2Genesis, err := genesis.BuildL2Genesis(cfg.DeployConfig, l1Block)
if err != nil {
return nil, err
}
......
......@@ -61,7 +61,7 @@ var Subcommands = cli.Commands{
}
l1StartBlock := l1Genesis.ToBlock()
l2Genesis, err := genesis.BuildL2DeveloperGenesis(config, l1StartBlock)
l2Genesis, err := genesis.BuildL2Genesis(config, l1StartBlock)
if err != nil {
return err
}
......@@ -144,7 +144,7 @@ var Subcommands = cli.Commands{
}
// Build the developer L2 genesis block
l2Genesis, err := genesis.BuildL2DeveloperGenesis(config, l1StartBlock)
l2Genesis, err := genesis.BuildL2Genesis(config, l1StartBlock)
if err != nil {
return fmt.Errorf("error creating l2 developer genesis: %w", err)
}
......
......@@ -89,3 +89,7 @@ ENTRYPOINT ["npm", "run", "start:drippie-mon"]
FROM base as wd-mon
WORKDIR /opt/optimism/packages/chain-mon
ENTRYPOINT ["yarn", "run", "start:wd-mon"]
FROM base as wallet-mon
WORKDIR /opt/optimism/packages/chain-mon
ENTRYPOINT ["yarn", "run", "start:wallet-mon"]
......@@ -8,6 +8,17 @@ BALANCE_MON__RPC=
# JSON array in the format [{ "address": <address>, "nickname": <nickname> }, ... ]
BALANCE_MON__ACCOUNTS=
###############################################################################
# ↓ wallet-mon ↓ #
###############################################################################
# RPC pointing to network to monitor
WALLET_MON__RPC=
# The block number to start monitoring from
# Defaults to the first bedrock block if unset.
WALLET_MON__START_BLOCK_NUMBER=
###############################################################################
# ↓ drippie-mon ↓ #
###############################################################################
......
......@@ -10,6 +10,7 @@
],
"scripts": {
"start:balance-mon": "ts-node ./src/balance-mon/service.ts",
"start:wallet-mon": "ts-node ./src/wallet-mon/service.ts",
"start:drippie-mon": "ts-node ./src/drippie-mon/service.ts",
"start:wd-mon": "ts-node ./src/wd-mon/service.ts",
"test:coverage": "echo 'No tests defined.'",
......@@ -35,6 +36,7 @@
"dependencies": {
"@eth-optimism/common-ts": "0.8.1",
"@eth-optimism/contracts-periphery": "1.0.8",
"@eth-optimism/contracts-bedrock": "0.14.0",
"@eth-optimism/core-utils": "0.12.0",
"@eth-optimism/sdk": "2.1.0",
"ethers": "^5.7.0",
......
export * from './balance-mon/service'
export * from './drippie-mon/service'
export * from './wd-mon/service'
export * from './wallet-mon/service'
import {
BaseServiceV2,
StandardOptions,
Gauge,
Counter,
validators,
waitForProvider,
} from '@eth-optimism/common-ts'
import { getChainId, compareAddrs } from '@eth-optimism/core-utils'
import { Provider } from '@ethersproject/abstract-provider'
import mainnetConfig from '@eth-optimism/contracts-bedrock/deploy-config/mainnet.json'
import goerliConfig from '@eth-optimism/contracts-bedrock/deploy-config/goerli.json'
import l2OutputOracleArtifactsMainnet from '@eth-optimism/contracts-bedrock/deployments/mainnet/L2OutputOracleProxy.json'
import l2OutputOracleArtifactsGoerli from '@eth-optimism/contracts-bedrock/deployments/goerli/L2OutputOracleProxy.json'
import { version } from '../../package.json'
const networks = {
1: {
name: 'mainnet',
l1StartingBlockTag: mainnetConfig.l1StartingBlockTag,
accounts: [
{
label: 'Proposer',
wallet: mainnetConfig.l2OutputOracleProposer,
target: l2OutputOracleArtifactsMainnet.address,
},
{
label: 'Batcher',
wallet: mainnetConfig.batchSenderAddress,
target: mainnetConfig.batchInboxAddress,
},
],
},
10: {
name: 'goerli',
l1StartingBlockTag: goerliConfig.l1StartingBlockTag,
accounts: [
{
label: 'Proposer',
wallet: goerliConfig.l2OutputOracleProposer,
target: l2OutputOracleArtifactsGoerli.address,
},
{
label: 'Batcher',
wallet: goerliConfig.batchSenderAddress,
target: goerliConfig.batchInboxAddress,
},
],
},
}
type WalletMonOptions = {
rpc: Provider
startBlockNumber: number
}
type WalletMonMetrics = {
validatedCalls: Counter
unexpectedCalls: Counter
unexpectedRpcErrors: Counter
}
type WalletMonState = {
chainId: number
highestUncheckedBlockNumber: number
}
export class WalletMonService extends BaseServiceV2<
WalletMonOptions,
WalletMonMetrics,
WalletMonState
> {
constructor(options?: Partial<WalletMonOptions & StandardOptions>) {
super({
version,
name: 'wallet-mon',
loop: true,
options: {
loopIntervalMs: 1000,
...options,
},
optionsSpec: {
rpc: {
validator: validators.provider,
desc: 'Provider for network to monitor balances on',
},
startBlockNumber: {
validator: validators.num,
default: -1,
desc: 'L1 block number to start checking from',
public: true,
},
},
metricsSpec: {
validatedCalls: {
type: Gauge,
desc: 'Transactions from the account checked',
labels: ['wallet', 'target', 'nickname'],
},
unexpectedCalls: {
type: Counter,
desc: 'Number of unexpected wallets',
labels: ['wallet', 'target', 'nickname'],
},
unexpectedRpcErrors: {
type: Counter,
desc: 'Number of unexpected RPC errors',
labels: ['section', 'name'],
},
},
})
}
protected async init(): Promise<void> {
// Connect to L1.
await waitForProvider(this.options.rpc, {
logger: this.logger,
name: 'L1',
})
this.state.chainId = await getChainId(this.options.rpc)
const l1StartingBlockTag = networks[this.state.chainId].l1StartingBlockTag
if (this.options.startBlockNumber === -1) {
const block = await this.options.rpc.getBlock(l1StartingBlockTag)
this.state.highestUncheckedBlockNumber = block.number
} else {
this.state.highestUncheckedBlockNumber = this.options.startBlockNumber
}
}
protected async main(): Promise<void> {
if (
(await this.options.rpc.getBlockNumber()) <
this.state.highestUncheckedBlockNumber
) {
this.logger.info('Waiting for new blocks')
return
}
const network = networks[this.state.chainId]
const accounts = network.accounts
const block = await this.options.rpc.getBlock(
this.state.highestUncheckedBlockNumber
)
this.logger.info('Checking block', {
number: block.number,
})
const transactions = []
for (const txHash of block.transactions) {
const t = await this.options.rpc.getTransaction(txHash)
transactions.push(t)
}
for (const transaction of transactions) {
for (const account of accounts) {
if (compareAddrs(account.wallet, transaction.from)) {
if (compareAddrs(account.target, transaction.to)) {
this.metrics.validatedCalls.inc({
nickname: account.label,
wallet: account.address,
target: account.target,
})
this.logger.info('validated call', {
nickname: account.label,
wallet: account.address,
target: account.target,
})
} else {
this.metrics.unexpectedCalls.inc({
nickname: account.label,
wallet: account.address,
target: transaction.to,
})
this.logger.error('Unexpected call detected', {
nickname: account.label,
address: account.address,
target: transaction.to,
})
}
}
}
}
this.logger.info('Checked block', {
number: this.state.highestUncheckedBlockNumber,
})
this.state.highestUncheckedBlockNumber++
}
}
if (require.main === module) {
const service = new WalletMonService()
service.run()
}
This diff is collapsed.
......@@ -255,15 +255,11 @@
| Name | Type | Slot | Offset | Bytes | Contract |
|-----------------|--------------------------------------------|------|--------|-------|-------------------------------------------------------------|
| _owner | address | 0 | 0 | 20 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| gameImpls | mapping(GameType => contract IDisputeGame) | 1 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| disputeGames | mapping(Hash => contract IDisputeGame) | 2 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| disputeGameList | contract IDisputeGame[] | 3 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
=======================
➡ contracts/dispute/BondManager.sol:BondManager
=======================
| Name | Type | Slot | Offset | Bytes | Contract |
|-------|---------------------------------------------|------|--------|-------|-----------------------------------------------|
| bonds | mapping(bytes32 => struct BondManager.Bond) | 0 | 0 | 32 | contracts/dispute/BondManager.sol:BondManager |
| _initialized | uint8 | 0 | 0 | 1 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| _initializing | bool | 0 | 1 | 1 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| __gap | uint256[50] | 1 | 0 | 1600 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| _owner | address | 51 | 0 | 20 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| __gap | uint256[49] | 52 | 0 | 1568 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| gameImpls | mapping(GameType => contract IDisputeGame) | 101 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| disputeGames | mapping(Hash => contract IDisputeGame) | 102 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
| disputeGameList | contract IDisputeGame[] | 103 | 0 | 32 | contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory |
......@@ -140,7 +140,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
}
/**
* @custom:semver 1.6.0
* @custom:semver 1.7.0
*
* @param _l2Oracle Address of the L2OutputOracle contract.
* @param _guardian Address that can pause deposits and withdrawals.
......@@ -152,7 +152,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
address _guardian,
bool _paused,
SystemConfig _config
) Semver(1, 6, 0) {
) Semver(1, 7, 0) {
L2_ORACLE = _l2Oracle;
GUARDIAN = _guardian;
SYSTEM_CONFIG = _config;
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../libraries/DisputeTypes.sol";
import { SafeCall } from "../libraries/SafeCall.sol";
import { IDisputeGame } from "./interfaces/IDisputeGame.sol";
import { IDisputeGameFactory } from "./interfaces/IDisputeGameFactory.sol";
import { IBondManager } from "./interfaces/IBondManager.sol";
/**
* @title BondManager
* @notice The Bond Manager serves as an escrow for permissionless output proposal bonds.
*/
contract BondManager is IBondManager {
/**
* @notice The Bond Type
*/
struct Bond {
address owner;
bytes32 id;
uint128 expiration;
uint128 amount;
}
/**
* @notice The permissioned dispute game factory.
* @dev Used to verify the status of bonds.
*/
IDisputeGameFactory public immutable DISPUTE_GAME_FACTORY;
/**
* @notice Amount of gas used to transfer ether when splitting the bond.
* This is a reasonable amount of gas for a transfer, even to a smart contract.
* The number of participants is bound of by the block gas limit.
*/
uint256 private constant TRANSFER_GAS = 30_000;
/**
* @notice Mapping from bondId to bond.
*/
mapping(bytes32 => Bond) public bonds;
/**
* @notice Instantiates the bond maanger with the registered dispute game factory.
* @param _disputeGameFactory is the dispute game factory.
*/
constructor(IDisputeGameFactory _disputeGameFactory) {
DISPUTE_GAME_FACTORY = _disputeGameFactory;
}
/**
* @inheritdoc IBondManager
*/
function post(
bytes32 _bondId,
address _bondOwner,
uint128 _minClaimHold
) external payable {
require(bonds[_bondId].owner == address(0), "BondManager: BondId already posted.");
require(_bondOwner != address(0), "BondManager: Owner cannot be the zero address.");
require(msg.value > 0, "BondManager: Value must be non-zero.");
uint128 expiration = uint128(_minClaimHold + block.timestamp);
bonds[_bondId] = Bond({
owner: _bondOwner,
id: _bondId,
expiration: expiration,
amount: uint128(msg.value)
});
emit BondPosted(_bondId, _bondOwner, expiration, msg.value);
}
/**
* @inheritdoc IBondManager
*/
function seize(bytes32 _bondId) external {
Bond memory b = bonds[_bondId];
require(b.owner != address(0), "BondManager: The bond does not exist.");
require(b.expiration >= block.timestamp, "BondManager: Bond expired.");
IDisputeGame caller = IDisputeGame(msg.sender);
IDisputeGame game = DISPUTE_GAME_FACTORY.games(
GameTypes.ATTESTATION,
caller.rootClaim(),
caller.extraData()
);
require(msg.sender == address(game), "BondManager: Unauthorized seizure.");
require(game.status() == GameStatus.CHALLENGER_WINS, "BondManager: Game incomplete.");
delete bonds[_bondId];
emit BondSeized(_bondId, b.owner, msg.sender, b.amount);
bool success = SafeCall.send(payable(msg.sender), gasleft(), b.amount);
require(success, "BondManager: Failed to send Ether.");
}
/**
* @inheritdoc IBondManager
*/
function seizeAndSplit(bytes32 _bondId, address[] calldata _claimRecipients) external {
Bond memory b = bonds[_bondId];
require(b.owner != address(0), "BondManager: The bond does not exist.");
require(b.expiration >= block.timestamp, "BondManager: Bond expired.");
IDisputeGame caller = IDisputeGame(msg.sender);
IDisputeGame game = DISPUTE_GAME_FACTORY.games(
GameTypes.ATTESTATION,
caller.rootClaim(),
caller.extraData()
);
require(msg.sender == address(game), "BondManager: Unauthorized seizure.");
require(game.status() == GameStatus.CHALLENGER_WINS, "BondManager: Game incomplete.");
delete bonds[_bondId];
emit BondSeized(_bondId, b.owner, msg.sender, b.amount);
uint256 len = _claimRecipients.length;
uint256 proportionalAmount = b.amount / len;
// Send the proportional amount to each recipient. Do not revert if a send fails as that
// will prevent other recipients from receiving their share.
for (uint256 i; i < len; i++) {
SafeCall.send({
_target: payable(_claimRecipients[i]),
_gas: TRANSFER_GAS,
_value: proportionalAmount
});
}
}
/**
* @inheritdoc IBondManager
*/
function reclaim(bytes32 _bondId) external {
Bond memory b = bonds[_bondId];
require(b.owner == msg.sender, "BondManager: Unauthorized claimant.");
require(b.expiration <= block.timestamp, "BondManager: Bond isn't claimable yet.");
delete bonds[_bondId];
emit BondReclaimed(_bondId, msg.sender, b.amount);
bool success = SafeCall.send(payable(msg.sender), gasleft(), b.amount);
require(success, "BondManager: Failed to send Ether.");
}
}
......@@ -4,17 +4,20 @@ pragma solidity ^0.8.15;
import "../libraries/DisputeTypes.sol";
import "../libraries/DisputeErrors.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ClonesWithImmutableArgs } from "@cwia/ClonesWithImmutableArgs.sol";
import {
OwnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { IDisputeGame } from "./interfaces/IDisputeGame.sol";
import { IDisputeGameFactory } from "./interfaces/IDisputeGameFactory.sol";
import { IVersioned } from "./interfaces/IVersioned.sol";
/**
* @title DisputeGameFactory
* @notice A factory contract for creating `IDisputeGame` contracts.
*/
contract DisputeGameFactory is Ownable, IDisputeGameFactory {
contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersioned {
/**
* @dev Allows for the creation of clone proxies with immutable arguments.
*/
......@@ -40,11 +43,27 @@ contract DisputeGameFactory is Ownable, IDisputeGameFactory {
IDisputeGame[] public disputeGameList;
/**
* @notice Constructs a new DisputeGameFactory contract.
* @notice Constructs a new DisputeGameFactory contract. Set the owner
* to `address(0)` to prevent accidental usage of the implementation.
*/
constructor() OwnableUpgradeable() {
_transferOwnership(address(0));
}
/**
* @notice Initializes the contract.
* @param _owner The owner of the contract.
*/
constructor(address _owner) Ownable() {
transferOwnership(_owner);
function initialize(address _owner) external initializer {
__Ownable_init();
_transferOwnership(_owner);
}
/**
* @inheritdoc IVersioned
*/
function version() external pure returns (string memory) {
return "0.0.1";
}
/**
......
......@@ -112,20 +112,50 @@ library Bytes {
* @return Resulting nibble array.
*/
function toNibbles(bytes memory _bytes) internal pure returns (bytes memory) {
uint256 bytesLength = _bytes.length;
bytes memory nibbles = new bytes(bytesLength * 2);
bytes1 b;
for (uint256 i = 0; i < bytesLength; ) {
b = _bytes[i];
nibbles[i * 2] = b >> 4;
nibbles[i * 2 + 1] = b & 0x0f;
unchecked {
++i;
bytes memory _nibbles;
assembly {
// Grab a free memory offset for the new array
_nibbles := mload(0x40)
// Load the length of the passed bytes array from memory
let bytesLength := mload(_bytes)
// Calculate the length of the new nibble array
// This is the length of the input array times 2
let nibblesLength := shl(0x01, bytesLength)
// Update the free memory pointer to allocate memory for the new array.
// To do this, we add the length of the new array + 32 bytes for the array length
// rounded up to the nearest 32 byte boundary to the current free memory pointer.
mstore(0x40, add(_nibbles, and(not(0x1F), add(nibblesLength, 0x3F))))
// Store the length of the new array in memory
mstore(_nibbles, nibblesLength)
// Store the memory offset of the _bytes array's contents on the stack
let bytesStart := add(_bytes, 0x20)
// Store the memory offset of the nibbles array's contents on the stack
let nibblesStart := add(_nibbles, 0x20)
// Loop through each byte in the input array
for {
let i := 0x00
} lt(i, bytesLength) {
i := add(i, 0x01)
} {
// Get the starting offset of the next 2 bytes in the nibbles array
let offset := add(nibblesStart, shl(0x01, i))
// Load the byte at the current index within the `_bytes` array
let b := byte(0x00, mload(add(bytesStart, i)))
// Pull out the first nibble and store it in the new array
mstore8(offset, shr(0x04, b))
// Pull out the second nibble and store it in the new array
mstore8(add(offset, 0x01), and(b, 0x0F))
}
}
return nibbles;
return _nibbles;
}
/**
......
......@@ -182,49 +182,6 @@ contract Bytes_slice_Test is Test {
}
contract Bytes_toNibbles_Test is Test {
/**
* @notice Diffs the test Solidity version of `toNibbles` against the Yul version.
*
* @param _bytes The `bytes` array to convert to nibbles.
*
* @return Yul version of `toNibbles` applied to `_bytes`.
*/
function _toNibblesYul(bytes memory _bytes) internal pure returns (bytes memory) {
// Allocate memory for the `nibbles` array.
bytes memory nibbles = new bytes(_bytes.length << 1);
assembly {
// Load the length of the passed bytes array from memory
let bytesLength := mload(_bytes)
// Store the memory offset of the _bytes array's contents on the stack
let bytesStart := add(_bytes, 0x20)
// Store the memory offset of the nibbles array's contents on the stack
let nibblesStart := add(nibbles, 0x20)
// Loop through each byte in the input array
for {
let i := 0x00
} lt(i, bytesLength) {
i := add(i, 0x01)
} {
// Get the starting offset of the next 2 bytes in the nibbles array
let offset := add(nibblesStart, shl(0x01, i))
// Load the byte at the current index within the `_bytes` array
let b := byte(0x00, mload(add(bytesStart, i)))
// Pull out the first nibble and store it in the new array
mstore8(offset, shr(0x04, b))
// Pull out the second nibble and store it in the new array
mstore8(add(offset, 0x01), and(b, 0x0F))
}
}
return nibbles;
}
/**
* @notice Tests that, given an input of 5 bytes, the `toNibbles` function returns an array of
* 10 nibbles corresponding to the input data.
......@@ -272,11 +229,49 @@ contract Bytes_toNibbles_Test is Test {
}
/**
* @notice Test that the `toNibbles` function in the `Bytes` library is equivalent to the Yul
* implementation.
* @notice Tests that the `toNibbles` function correctly updates the free memory pointer depending
* on the length of the resulting array.
*/
function testDiff_toNibbles_succeeds(bytes memory _input) public {
assertEq(Bytes.toNibbles(_input), _toNibblesYul(_input));
function testFuzz_toNibbles_memorySafety_succeeds(bytes memory _input) public {
// Grab the free memory pointer before the `toNibbles` operation
uint64 initPtr;
assembly {
initPtr := mload(0x40)
}
uint64 expectedPtr = uint64(initPtr + 0x20 + ((_input.length * 2 + 0x1F) & ~uint256(0x1F)));
// Ensure that all memory outside of the expected range is safe.
vm.expectSafeMemory(initPtr, expectedPtr);
// Pull out each individual nibble from the input bytes array
bytes memory nibbles = Bytes.toNibbles(_input);
// Grab the free memory pointer after the `toNibbles` operation
uint64 finalPtr;
assembly {
finalPtr := mload(0x40)
}
// The free memory pointer should have been updated properly
if (_input.length == 0) {
// If the input length is zero, only 32 bytes of memory should have been allocated.
assertEq(finalPtr, initPtr + 0x20);
} else {
// If the input length is greater than zero, the memory allocated should be the
// length of the input * 2 + 32 bytes for the length field.
//
// Note that we use a slightly less efficient, but equivalent method of rounding
// up `_length` to the next multiple of 32 than is used in the `toNibbles` function.
// This is to diff test the method used in `toNibbles`.
uint64 _expectedPtr = uint64(initPtr + 0x20 + (((_input.length * 2 + 0x1F) >> 5) << 5));
assertEq(finalPtr, _expectedPtr);
// Sanity check for equivalence of the rounding methods.
assertEq(_expectedPtr, expectedPtr);
}
// The nibbles length should be equal to `_length * 2`
assertEq(nibbles.length, _input.length << 1);
}
}
......
......@@ -7,10 +7,10 @@ import "../libraries/DisputeErrors.sol";
import { Test } from "forge-std/Test.sol";
import { DisputeGameFactory } from "../dispute/DisputeGameFactory.sol";
import { IDisputeGame } from "../dispute/interfaces/IDisputeGame.sol";
import { Proxy } from "../universal/Proxy.sol";
contract DisputeGameFactory_Test is Test {
contract DisputeGameFactory_Initializer is Test {
DisputeGameFactory factory;
FakeClone fakeClone;
event DisputeGameCreated(
address indexed disputeProxy,
......@@ -20,8 +20,23 @@ contract DisputeGameFactory_Test is Test {
event ImplementationSet(address indexed impl, GameType indexed gameType);
function setUp() public {
factory = new DisputeGameFactory(address(this));
function setUp() public virtual {
Proxy proxy = new Proxy(address(this));
DisputeGameFactory impl = new DisputeGameFactory();
proxy.upgradeToAndCall({
_implementation: address(impl),
_data: abi.encodeCall(impl.initialize, (address(this)))
});
factory = DisputeGameFactory(address(proxy));
}
}
contract DisputeGameFactory_Test is DisputeGameFactory_Initializer {
FakeClone fakeClone;
function setUp() public override {
super.setUp();
fakeClone = new FakeClone();
}
......
# `ResourceMetering` Invariants
## The base fee should increase if the last block used more than the target amount of gas
**Test:** [`ResourceMetering.t.sol#L180`](../contracts/test/invariants/ResourceMetering.t.sol#L180)
If the last block used more than the target amount of gas (and there were no empty blocks in between), ensure this block's baseFee increased, but not by more than the max amount per block.
## The base fee should decrease if the last block used less than the target amount of gas
**Test:** [`ResourceMetering.t.sol#L191`](../contracts/test/invariants/ResourceMetering.t.sol#L191)
If the previous block used less than the target amount of gas, the base fee should decrease, but not more than the max amount.
## A block's base fee should never be below `MINIMUM_BASE_FEE`
**Test:** [`ResourceMetering.t.sol#L201`](../contracts/test/invariants/ResourceMetering.t.sol#L201)
This test asserts that a block's base fee can never drop below the `MINIMUM_BASE_FEE` threshold.
## A block can never consume more than `MAX_RESOURCE_LIMIT` gas.
**Test:** [`ResourceMetering.t.sol#L211`](../contracts/test/invariants/ResourceMetering.t.sol#L211)
This test asserts that a block can never consume more than the `MAX_RESOURCE_LIMIT` gas threshold.
## The base fee can never be raised more than the max base fee change.
**Test:** [`ResourceMetering.t.sol#L222`](../contracts/test/invariants/ResourceMetering.t.sol#L222)
After a block consumes more gas than the target gas, the base fee cannot be raised more than the maximum amount allowed. The max base fee change (per-block) is derived as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR`
## The base fee can never be lowered more than the max base fee change.
**Test:** [`ResourceMetering.t.sol#L233`](../contracts/test/invariants/ResourceMetering.t.sol#L233)
After a block consumes less than the target gas, the base fee cannot be lowered more than the maximum amount allowed. The max base fee change (per-block) is derived as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR`
## The `maxBaseFeeChange` calculation over multiple blocks can never underflow.
**Test:** [`ResourceMetering.t.sol#L244`](../contracts/test/invariants/ResourceMetering.t.sol#L244)
When calculating the `maxBaseFeeChange` after multiple empty blocks, the calculation should never be allowed to underflow.
## The base fee should increase if the last block used more than the target amount of gas
**Test:** [`FuzzResourceMetering.sol#L158`](../contracts/echidna/FuzzResourceMetering.sol#L158)
......
#!/bin/sh
# RPC endpoint for the migration rehearsal network
export ETH_RPC_URL="https://mainnet-l1-rehearsal.optimism.io/"
# export ETH_RPC_URL="localhost:8545"
# Default HH key
HH_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
# Default HH addr
HH_ADDR="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
# SystemDictator contract (deployed by Maurelian on 2023-05-09)
MSD="0x49149a233de6E4cD6835971506F47EE5862289c1"
# ProxyAdmin contract
PROXY_ADMIN="0x43cA9bAe8dF108684E5EAaA720C25e1b32B0A075"
# AddressManager contract
ADDRESS_MANAGER="0xdE1FCfB0851916CA5101820A69b13a4E276bd81F"
# ResolvedDelegateProxy contract
RESOLVED_DELEGATE_PROXY="0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1"
# L1ChugSplashProxy contract
# use `setOwner(address)` for this one
L1_CHUG_SPLASH_PROXY="0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1"
# Proxy contract
# use `changeAdmin(address)` for this one
PROXY="0x5a7749f83b81B301cAb5f48EB8516B986DAef23D"
# OptimismPortal proxy
PORTAL_PROXY="0x59C4e2c6a6dC27c259D6d067a039c831e1ff4947"
# Check existing owners (should all be $HH_ADDR)
# cast call $PROXY_ADMIN "owner()(address)"
# cast call $ADDRESS_MANAGER "owner()(address)"
# cast admin $L1_CHUG_SPLASH_PROXY
# cast admin $PROXY
# ---
# Transfer ownership
# ---
# cast send --private-key $HH_KEY $PROXY_ADMIN "transferOwnership(address)" $MSD
# cast send --private-key $HH_KEY $ADDRESS_MANAGER "transferOwnership(address)" $MSD
# cast send --private-key $HH_KEY $L1_CHUG_SPLASH_PROXY "setOwner(address)" $MSD
# cast send --private-key $HH_KEY $PROXY "changeAdmin(address)" $MSD
# ---
# Execute Phase 1
# ---
# cast send --private-key $HH_KEY $MSD "phase1()"
# updateDynamicConfig signature
SIG="updateDynamicConfig((uint256,uint256),bool)"
# Encode calldata
CALLDATA=$(cast abi-encode $SIG "(17377105,1685641931)" true)
# Grab the selector
SELECTOR=$(cast sig $SIG)
# Prepare full payload
PAYLOAD=$(cast --concat-hex $SELECTOR $CALLDATA)
# Sanity check calldata
# cast pretty-calldata $PAYLOAD
# ---
# Update dynamic config
# ---
# cast send --private-key $HH_KEY $MSD $PAYLOAD
# ---
# !!!POINT OF NO RETURN!!!
# Execute phase 2
# ---
# cast send --private-key $HH_KEY $MSD "phase2()"
# ---
# Unpause the portal
# ---
# cast send --private-key $HH_KEY $PORTAL_PROXY "unpause()"
# ---
# Unpause Portal with CallForwarder
# ---
# Fetch portal guardian
# cast call $PORTAL_PROXY "GUARDIAN()(address)"
SIG="forward(address,bytes)"
FORWARD_SIG=$(cast sig $SIG)
CALLDATA=$(cast abi-encode $SIG $PORTAL_PROXY $(cast sig "unpause()"))
PAYLOAD=$(cast --concat-hex $FORWARD_SIG $CALLDATA)
# Sanity check calldata
# cast pretty-calldata $PAYLOAD
# Send unpause tx from multisig
# cast send "0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A" $PAYLOAD --private-key $HH_KEY
# Check if paused
# cast call $PORTAL_PROXY "paused()(bool)"
# Check bytecode of multisig
# cast code "0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A" --rpc-url https://mainnet-l1-rehearsal.optimism.io
......@@ -32,7 +32,6 @@ contracts=(
contracts/universal/OptimismMintableERC20.sol:OptimismMintableERC20
contracts/universal/OptimismMintableERC20Factory.sol:OptimismMintableERC20Factory
contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory
contracts/dispute/BondManager.sol:BondManager
)
dir=$(dirname "$0")
......
......@@ -465,46 +465,6 @@ const check = {
await checkProxy(hre, 'L1Block', signer.provider)
await assertProxy(hre, 'L1Block', signer.provider)
},
// LegacyERC20ETH
// - not behind a proxy
// - check name
// - check symbol
// - check decimals
// - check BRIDGE
// - check REMOTE_TOKEN
// - totalSupply should be set to 0
LegacyERC20ETH: async (hre: HardhatRuntimeEnvironment, signer: Signer) => {
const LegacyERC20ETH = await hre.ethers.getContractAt(
'LegacyERC20ETH',
predeploys.LegacyERC20ETH,
signer
)
const name = await LegacyERC20ETH.name()
assert(name === 'Ether')
console.log(` - name: ${name}`)
const symbol = await LegacyERC20ETH.symbol()
assert(symbol === 'ETH')
console.log(` - symbol: ${symbol}`)
const decimals = await LegacyERC20ETH.decimals()
assert(decimals === 18)
console.log(` - decimals: ${decimals}`)
const BRIDGE = await LegacyERC20ETH.BRIDGE()
assert(BRIDGE === predeploys.L2StandardBridge)
const REMOTE_TOKEN = await LegacyERC20ETH.REMOTE_TOKEN()
assert(REMOTE_TOKEN === hre.ethers.constants.AddressZero)
const totalSupply = await LegacyERC20ETH.totalSupply()
assert(totalSupply.eq(0))
console.log(` - totalSupply: ${totalSupply}`)
await checkProxy(hre, 'LegacyERC20ETH', signer.provider)
// No proxy at this address, don't call assertProxy
},
// WETH9
// - check name
// - check symbol
......
......@@ -48,3 +48,14 @@ export const reqenv = (name: string): string => {
export const getenv = (name: string, fallback?: string): string | undefined => {
return process.env[name] || fallback
}
/**
* Returns true if the given string is a valid address.
*
* @param a First address to check.
* @param b Second address to check.
* @returns True if the given addresses match.
*/
export const compareAddrs = (a: string, b: string): boolean => {
return a.toLowerCase() === b.toLowerCase()
}
......@@ -62,7 +62,7 @@ or `Bedrock`. Deprecated contracts should not be used.
| L2ToL1MessagePasser | 0x4200000000000000000000000000000000000016 | Bedrock | No | Yes |
| L2ERC721Bridge | 0x4200000000000000000000000000000000000014 | Legacy | No | Yes |
| OptimismMintableERC721Factory | 0x4200000000000000000000000000000000000017 | Bedrock | No | Yes |
| ProxyAdmin | 0x4200000000000000000000000000000000000018 | Bedrock | No | No |
| ProxyAdmin | 0x4200000000000000000000000000000000000018 | Bedrock | No | Yes |
| BaseFeeVault | 0x4200000000000000000000000000000000000019 | Bedrock | No | Yes |
| L1FeeVault | 0x420000000000000000000000000000000000001a | Bedrock | No | Yes |
......
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