Commit ce2ce43b authored by smartcontracts's avatar smartcontracts Committed by GitHub

feat: have AnchorStateRegistry use a single root (#13700)

* feat: have AnchorStateRegistry use a single root

Updates the AnchorStateRegistry to use a single unified anchor
root by checking with the OptimismPortal for the currently
respected game type. Additionally makes the AnchorStateRegistry
MCP ready.

Users MUST deploy this contract as a new proxy and cannot upgrade
their existing proxy.

* Update snapshots post-merge

* op-deployer: Support single ASR

* Update semver

* updates after rebase

* rename method

* make isGameProper comment more obvious

* update semver

* semver bump

* simpler api for boolean functions

* comment updates

* address wildmolasses comments

* add test for not guardian

* a few more tests

---------
Co-authored-by: default avatarMatthew Slipper <me@matthewslipper.com>
parent 544c42b3
......@@ -196,7 +196,7 @@ func DeployL2ToL1(l1Host *script.Host, superCfg *SuperchainConfig, superDeployme
l1Host.SetTxOrigin(cfg.Deployer)
output, err := opcm.DeployOPChainV160(l1Host, opcm.DeployOPChainInputV160{
output, err := opcm.DeployOPChain(l1Host, opcm.DeployOPChainInput{
OpChainProxyAdminOwner: cfg.ProxyAdminOwner,
SystemConfigOwner: cfg.SystemConfigOwner,
Batcher: cfg.BatchSenderAddress,
......@@ -215,7 +215,6 @@ func DeployL2ToL1(l1Host *script.Host, superCfg *SuperchainConfig, superDeployme
DisputeSplitDepth: cfg.DisputeSplitDepth,
DisputeClockExtension: cfg.DisputeClockExtension,
DisputeMaxClockDuration: cfg.DisputeMaxClockDuration,
StartingAnchorRoots: opcm.PermissionedGameStartingAnchorRoots,
})
if err != nil {
return nil, fmt.Errorf("failed to deploy L2 OP chain: %w", err)
......
......@@ -20,6 +20,7 @@ type Implementations struct {
L1StandardBridgeImpl common.Address `json:"L1StandardBridgeImpl"`
OptimismMintableERC20FactoryImpl common.Address `json:"OptimismMintableERC20FactoryImpl"`
DisputeGameFactoryImpl common.Address `json:"DisputeGameFactoryImpl"`
AnchorStateRegistryImpl common.Address `json:"AnchorStateRegistryImpl"`
}
type SuperchainDeployment struct {
......
package integration_test
import (
"bufio"
"bytes"
"compress/gzip"
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"log/slog"
"maps"
"math/big"
"os"
"strings"
"testing"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/rpc"
"github.com/lmittmann/w3"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/env"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/retryproxy"
altda "github.com/ethereum-optimism/optimism/op-alt-da"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/inspect"
"github.com/ethereum-optimism/optimism/op-node/rollup"
......@@ -38,7 +25,6 @@ import (
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/testutil"
"github.com/ethereum-optimism/optimism/op-service/testutils/anvil"
"github.com/ethereum/go-ethereum/crypto"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
......@@ -82,8 +68,6 @@ network_params:
const defaultL1ChainID uint64 = 77799777
var versionFunc = w3.MustNewFunc("version()", "string")
type deployerKey struct{}
func (d *deployerKey) HDPath() string {
......@@ -202,406 +186,6 @@ func TestEndToEndApply(t *testing.T) {
})
}
type existingOPCMTest struct {
name string
network string
l1Release string
l2Release string
l2AllocsFile string
l1Semvers *expectedL1Semvers
l2Semvers *inspect.L2PredeploySemvers
}
type expectedL1Semvers struct {
SystemConfig string
PermissionedDisputeGame string
MIPS string
OptimismPortal string
AnchorStateRegistry string
DelayedWETH string
DisputeGameFactory string
PreimageOracle string
L1CrossDomainMessenger string
L1ERC721Bridge string
L1StandardBridge string
OptimismMintableERC20 string
}
func TestApplyExistingOPCM(t *testing.T) {
expectedL2SemversV160 := &inspect.L2PredeploySemvers{
L2ToL1MessagePasser: "1.1.1-beta.1",
DeployerWhitelist: "1.1.1-beta.1",
WETH: "1.0.0-beta.1",
L2CrossDomainMessenger: "2.1.1-beta.1",
L2StandardBridge: "1.11.1-beta.1",
SequencerFeeVault: "1.5.0-beta.2",
OptimismMintableERC20Factory: "1.10.1-beta.2",
L1BlockNumber: "1.1.1-beta.1",
GasPriceOracle: "1.3.1-beta.1",
L1Block: "1.5.1-beta.1",
LegacyMessagePasser: "1.1.1-beta.1",
L2ERC721Bridge: "1.7.1-beta.2",
OptimismMintableERC721Factory: "1.4.1-beta.1",
BaseFeeVault: "1.5.0-beta.2",
L1FeeVault: "1.5.0-beta.2",
SchemaRegistry: "1.3.1-beta.1",
EAS: "1.4.1-beta.1",
CrossL2Inbox: "",
L2toL2CrossDomainMessenger: "",
SuperchainWETH: "",
ETHLiquidity: "",
SuperchainTokenBridge: "",
OptimismMintableERC20: "1.4.0-beta.1",
OptimismMintableERC721: "1.3.1-beta.1",
}
expectedL1SemversV160 := &expectedL1Semvers{
SystemConfig: "2.2.0",
PermissionedDisputeGame: "1.3.1-beta.3", // Deployment bug
MIPS: "1.1.0",
OptimismPortal: "3.10.0",
AnchorStateRegistry: "2.0.1-beta.3", // Deployment bug
DelayedWETH: "1.1.0",
DisputeGameFactory: "1.0.0",
PreimageOracle: "1.1.2",
L1CrossDomainMessenger: "2.3.0",
L1ERC721Bridge: "2.1.0",
L1StandardBridge: "2.1.0",
OptimismMintableERC20: "1.9.0",
}
expectedL1SemversV180 := &expectedL1Semvers{
SystemConfig: "2.3.0",
PermissionedDisputeGame: "1.3.1",
MIPS: "1.2.1",
OptimismPortal: "3.10.0",
AnchorStateRegistry: "2.0.1-beta.3", // Deployment bug persisting across releases
DelayedWETH: "1.1.0",
DisputeGameFactory: "1.0.0",
PreimageOracle: "1.1.2",
L1CrossDomainMessenger: "2.3.0",
L1ERC721Bridge: "2.1.0",
L1StandardBridge: "2.1.0",
OptimismMintableERC20: "1.9.0",
}
tests := []existingOPCMTest{
{
"mainnet v1.6.0",
"mainnet",
"op-contracts/v1.6.0",
"op-contracts/v1.7.0-beta.1+l2-contracts",
"allocs-l2-v160-1.json.gz",
expectedL1SemversV160,
expectedL2SemversV160,
},
{
"sepolia v1.6.0",
"sepolia",
"op-contracts/v1.6.0",
"op-contracts/v1.7.0-beta.1+l2-contracts",
"allocs-l2-v160-11155111.json.gz",
expectedL1SemversV160,
expectedL2SemversV160,
},
{
"sepolia v1.8.0-rc.4",
"sepolia",
"op-contracts/v1.8.0-rc.4",
// The L2 predeploys need to still be the v1.7.0 beta contracts.
"op-contracts/v1.7.0-beta.1+l2-contracts",
// The L2 predeploys do not change in version 1.8.0.
"allocs-l2-v160-11155111.json.gz",
expectedL1SemversV180,
expectedL2SemversV160,
},
{
"mainnet v1.8.0-rc.4",
"mainnet",
"op-contracts/v1.8.0-rc.4",
// The L2 predeploys need to still be the v1.7.0 beta contracts.
"op-contracts/v1.7.0-beta.1+l2-contracts",
// The L2 predeploys do not change in version 1.8.0.
"allocs-l2-v160-1.json.gz",
expectedL1SemversV180,
expectedL2SemversV160,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
testApplyExistingOPCM(t, tt)
})
}
}
func testApplyExistingOPCM(t *testing.T, testInfo existingOPCMTest) {
op_e2e.InitParallel(t)
var forkRPCUrl string
var l1Versions standard.L1Versions
var l1ChainID uint64
if testInfo.network == "mainnet" {
forkRPCUrl = os.Getenv("MAINNET_RPC_URL")
l1Versions = standard.L1VersionsMainnet
l1ChainID = 1
} else if testInfo.network == "sepolia" {
forkRPCUrl = os.Getenv("SEPOLIA_RPC_URL")
l1Versions = standard.L1VersionsSepolia
l1ChainID = 11155111
} else {
t.Fatalf("invalid network: %s", testInfo.network)
}
require.NotEmpty(t, forkRPCUrl, "no fork RPC URL provided")
lgr := testlog.Logger(t, slog.LevelDebug)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
retryProxy := retryproxy.New(lgr, forkRPCUrl)
require.NoError(t, retryProxy.Start())
t.Cleanup(func() {
require.NoError(t, retryProxy.Stop())
})
runner, err := anvil.New(
retryProxy.Endpoint(),
lgr,
)
require.NoError(t, err)
require.NoError(t, runner.Start(ctx))
t.Cleanup(func() {
require.NoError(t, runner.Stop())
})
l1RPC, err := rpc.Dial(runner.RPCUrl())
require.NoError(t, err)
l1Client := ethclient.NewClient(l1RPC)
require.NoError(t, err)
l1ChainIDBig := new(big.Int).SetUint64(l1ChainID)
dk, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic)
require.NoError(t, err)
// index 0 from Anvil's test set
pk, err := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80")
require.NoError(t, err)
l2ChainID := uint256.NewInt(777)
// Hardcode the below tags to ensure the test is validating the correct
// version even if the underlying tag changes
intent, st := newIntent(
t,
l1ChainIDBig,
dk,
l2ChainID,
artifacts.MustNewLocatorFromTag(testInfo.l1Release),
artifacts.MustNewLocatorFromTag(testInfo.l2Release),
)
// NOTE: the reference allocs for version 1.6 contain the gov token, so we need to enable it
// via override here.
intent.GlobalDeployOverrides = map[string]any{
"enableGovernance": true,
}
// Define a new create2 salt to avoid contract address collisions
_, err = rand.Read(st.Create2Salt[:])
require.NoError(t, err)
require.NoError(t, deployer.ApplyPipeline(
ctx,
deployer.ApplyPipelineOpts{
DeploymentTarget: deployer.DeploymentTargetLive,
L1RPCUrl: runner.RPCUrl(),
DeployerPrivateKey: pk,
Intent: intent,
State: st,
Logger: lgr,
StateWriter: pipeline.NoopStateWriter(),
},
))
validateOPChainDeployment(t, ethClientCodeGetter(ctx, l1Client), st, intent, true)
releases := l1Versions[testInfo.l1Release]
implTests := []struct {
name string
expAddr common.Address
actAddr common.Address
}{
{"OptimismPortal", releases.OptimismPortal.ImplementationAddress, st.ImplementationsDeployment.OptimismPortalImplAddress},
{"SystemConfig,", releases.SystemConfig.ImplementationAddress, st.ImplementationsDeployment.SystemConfigImplAddress},
{"L1CrossDomainMessenger", releases.L1CrossDomainMessenger.ImplementationAddress, st.ImplementationsDeployment.L1CrossDomainMessengerImplAddress},
{"L1ERC721Bridge", releases.L1ERC721Bridge.ImplementationAddress, st.ImplementationsDeployment.L1ERC721BridgeImplAddress},
{"L1StandardBridge", releases.L1StandardBridge.ImplementationAddress, st.ImplementationsDeployment.L1StandardBridgeImplAddress},
{"OptimismMintableERC20Factory", releases.OptimismMintableERC20Factory.ImplementationAddress, st.ImplementationsDeployment.OptimismMintableERC20FactoryImplAddress},
{"DisputeGameFactory", releases.DisputeGameFactory.ImplementationAddress, st.ImplementationsDeployment.DisputeGameFactoryImplAddress},
{"MIPS", releases.MIPS.Address, st.ImplementationsDeployment.MipsSingletonAddress},
{"PreimageOracle", releases.PreimageOracle.Address, st.ImplementationsDeployment.PreimageOracleSingletonAddress},
{"DelayedWETH", releases.DelayedWETH.ImplementationAddress, st.ImplementationsDeployment.DelayedWETHImplAddress},
}
for _, tt := range implTests {
require.Equal(t, tt.expAddr, tt.actAddr, "unexpected address for %s", tt.name)
}
chainState := st.Chains[0]
versionTests := []struct {
name string
expVersion string
addr common.Address
}{
{"SystemConfig", testInfo.l1Semvers.SystemConfig, chainState.SystemConfigProxyAddress},
{"PermissionedDisputeGame", testInfo.l1Semvers.PermissionedDisputeGame, chainState.PermissionedDisputeGameAddress},
{"MIPS", testInfo.l1Semvers.MIPS, st.ImplementationsDeployment.MipsSingletonAddress},
{"OptimismPortal", testInfo.l1Semvers.OptimismPortal, chainState.OptimismPortalProxyAddress},
{"AnchorStateRegistry", testInfo.l1Semvers.AnchorStateRegistry, chainState.AnchorStateRegistryProxyAddress},
{"DelayedWETH", testInfo.l1Semvers.DelayedWETH, chainState.DelayedWETHPermissionedGameProxyAddress},
{"DisputeGameFactory", testInfo.l1Semvers.DisputeGameFactory, chainState.DisputeGameFactoryProxyAddress},
{"PreimageOracle", testInfo.l1Semvers.PreimageOracle, st.ImplementationsDeployment.PreimageOracleSingletonAddress},
{"L1CrossDomainMessenger", testInfo.l1Semvers.L1CrossDomainMessenger, chainState.L1CrossDomainMessengerProxyAddress},
{"L1ERC721Bridge", testInfo.l1Semvers.L1ERC721Bridge, chainState.L1ERC721BridgeProxyAddress},
{"L1StandardBridge", testInfo.l1Semvers.L1StandardBridge, chainState.L1StandardBridgeProxyAddress},
{"OptimismMintableERC20", testInfo.l1Semvers.OptimismMintableERC20, chainState.OptimismMintableERC20FactoryProxyAddress},
}
versionArgs, err := versionFunc.EncodeArgs()
require.NoError(t, err)
for _, tt := range versionTests {
ret, err := l1Client.CallContract(ctx, ethereum.CallMsg{
To: &tt.addr,
Data: versionArgs,
}, nil)
require.NoError(t, err)
var actVersion string
require.NoError(t, versionFunc.DecodeReturns(ret, &actVersion))
require.Equal(t, tt.expVersion, actVersion, "unexpected version for %s", tt.name)
}
superchain, err := standard.SuperchainFor(l1ChainIDBig.Uint64())
require.NoError(t, err)
managerOwner, err := standard.SuperchainProxyAdminAddrFor(l1ChainIDBig.Uint64())
require.NoError(t, err)
superchainTests := []struct {
name string
expAddr common.Address
actAddr common.Address
}{
{"ProxyAdmin", managerOwner, st.SuperchainDeployment.ProxyAdminAddress},
{"SuperchainConfig", common.Address(*superchain.Config.SuperchainConfigAddr), st.SuperchainDeployment.SuperchainConfigProxyAddress},
{"ProtocolVersions", common.Address(*superchain.Config.ProtocolVersionsAddr), st.SuperchainDeployment.ProtocolVersionsProxyAddress},
}
for _, tt := range superchainTests {
require.Equal(t, tt.expAddr, tt.actAddr, "unexpected address for %s", tt.name)
}
artifactsFSL2, cleanupL2, err := artifacts.Download(
ctx,
intent.L2ContractsLocator,
artifacts.LogProgressor(lgr),
)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, cleanupL2())
})
chainIntent := intent.Chains[0]
semvers, err := inspect.L2Semvers(inspect.L2SemversConfig{
Lgr: lgr,
Artifacts: artifactsFSL2,
ChainState: chainState,
})
require.NoError(t, err)
require.EqualValues(t, testInfo.l2Semvers, semvers)
f, err := os.Open(fmt.Sprintf("./testdata/%s", testInfo.l2AllocsFile))
require.NoError(t, err)
defer f.Close()
gzr, err := gzip.NewReader(f)
require.NoError(t, err)
defer gzr.Close()
dec := json.NewDecoder(bufio.NewReader(gzr))
var expAllocs types.GenesisAlloc
require.NoError(t, dec.Decode(&expAllocs))
type storageCheckerFunc func(addr common.Address, actStorage map[common.Hash]common.Hash)
storageDiff := func(addr common.Address, expStorage, actStorage map[common.Hash]common.Hash) {
require.EqualValues(t, expStorage, actStorage, "storage for %s differs", addr)
}
defaultStorageChecker := func(addr common.Address, actStorage map[common.Hash]common.Hash) {
storageDiff(addr, expAllocs[addr].Storage, actStorage)
}
overrideStorageChecker := func(addr common.Address, actStorage, overrides map[common.Hash]common.Hash) {
expStorage := make(map[common.Hash]common.Hash)
maps.Copy(expStorage, expAllocs[addr].Storage)
maps.Copy(expStorage, overrides)
storageDiff(addr, expStorage, actStorage)
}
storageCheckers := map[common.Address]storageCheckerFunc{
predeploys.L2CrossDomainMessengerAddr: func(addr common.Address, actStorage map[common.Hash]common.Hash) {
overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{
{31: 0xcf}: common.BytesToHash(chainState.L1CrossDomainMessengerProxyAddress.Bytes()),
})
},
predeploys.L2StandardBridgeAddr: func(addr common.Address, actStorage map[common.Hash]common.Hash) {
overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{
{31: 0x04}: common.BytesToHash(chainState.L1StandardBridgeProxyAddress.Bytes()),
})
},
predeploys.L2ERC721BridgeAddr: func(addr common.Address, actStorage map[common.Hash]common.Hash) {
overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{
{31: 0x02}: common.BytesToHash(chainState.L1ERC721BridgeProxyAddress.Bytes()),
})
},
predeploys.ProxyAdminAddr: func(addr common.Address, actStorage map[common.Hash]common.Hash) {
overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{
{}: common.BytesToHash(intent.Chains[0].Roles.L2ProxyAdminOwner.Bytes()),
})
},
// The ProxyAdmin owner is also set on the ProxyAdmin contract's implementation address, see
// L2Genesis.s.sol line 292.
common.HexToAddress("0xc0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30018"): func(addr common.Address, actStorage map[common.Hash]common.Hash) {
overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{
{}: common.BytesToHash(chainIntent.Roles.L2ProxyAdminOwner.Bytes()),
})
},
}
//Use a custom equality function to compare the genesis allocs
//because the reflect-based one is really slow
actAllocs := st.Chains[0].Allocs.Data.Accounts
for addr, expAcc := range expAllocs {
actAcc, ok := actAllocs[addr]
require.True(t, ok)
require.True(t, expAcc.Balance.Cmp(actAcc.Balance) == 0, "balance for %s differs", addr)
require.Equal(t, expAcc.Nonce, actAcc.Nonce, "nonce for %s differs", addr)
require.Equal(t, hex.EncodeToString(expAllocs[addr].Code), hex.EncodeToString(actAcc.Code), "code for %s differs", addr)
storageChecker, ok := storageCheckers[addr]
if !ok {
storageChecker = defaultStorageChecker
}
storageChecker(addr, actAcc.Storage)
}
for addr := range actAllocs {
if _, ok := expAllocs[addr]; ok {
continue
}
t.Logf("unexpected account: %s", addr.Hex())
}
}
func TestGlobalOverrides(t *testing.T) {
op_e2e.InitParallel(t)
......
......@@ -9,46 +9,23 @@ import (
)
var anchorRootFunc = w3.MustNewFunc(`
dummy((uint32 gameType, (bytes32 root, uint256 l2BlockNumber) outputRoot)[] roots)
dummy((bytes32 root, uint256 l2BlockNumber) outputRoot)
`, "")
type StartingAnchorRoot struct {
GameType uint32
Root common.Hash
L2BlockNumber *big.Int
}
var DefaultStartingAnchorRoot = StartingAnchorRoot{
GameType: 1,
Root: common.Hash{0xde, 0xad},
L2BlockNumber: common.Big0,
}
type encodingStartingAnchorRoot struct {
GameType uint32
OutputRoot struct {
Root common.Hash
L2BlockNumber *big.Int
}
}
func EncodeStartingAnchorRoots(roots []StartingAnchorRoot) ([]byte, error) {
args := make([]encodingStartingAnchorRoot, len(roots))
for i, root := range roots {
args[i] = encodingStartingAnchorRoot{
GameType: root.GameType,
OutputRoot: struct {
Root common.Hash
L2BlockNumber *big.Int
}{
Root: root.Root,
L2BlockNumber: root.L2BlockNumber,
},
}
}
encoded, err := anchorRootFunc.EncodeArgs(args)
func EncodeStartingAnchorRoot(root StartingAnchorRoot) ([]byte, error) {
encoded, err := anchorRootFunc.EncodeArgs(root)
if err != nil {
return nil, fmt.Errorf("error encoding anchor roots: %w", err)
return nil, fmt.Errorf("error encoding anchor root: %w", err)
}
// Chop off the function selector since w3 can't serialize structs directly
return encoded[4:], nil
......
......@@ -9,34 +9,22 @@ import (
)
func TestEncodeStartingAnchorRoots(t *testing.T) {
encoded, err := EncodeStartingAnchorRoots([]StartingAnchorRoot{
DefaultStartingAnchorRoot,
})
encoded, err := EncodeStartingAnchorRoot(DefaultStartingAnchorRoot)
require.NoError(t, err)
require.EqualValues(t, PermissionedGameStartingAnchorRoots, encoded)
require.EqualValues(t, PermissionedGameStartingAnchorRoot, encoded)
encoded, err = EncodeStartingAnchorRoots([]StartingAnchorRoot{
{
GameType: 0,
L2BlockNumber: common.Big0,
},
{
GameType: 1,
Root: common.Hash{0xde, 0xad},
L2BlockNumber: big.NewInt(0),
},
encoded, err = EncodeStartingAnchorRoot(StartingAnchorRoot{
Root: common.Hash{0xde, 0xad, 0xbe, 0xef},
L2BlockNumber: big.NewInt(9),
})
require.NoError(t, err)
require.EqualValues(t,
common.Hex2Bytes(
"0000000000000000000000000000000000000000000000000000000000000020"+
"0000000000000000000000000000000000000000000000000000000000000002"+
"0000000000000000000000000000000000000000000000000000000000000000"+
"0000000000000000000000000000000000000000000000000000000000000000"+
"0000000000000000000000000000000000000000000000000000000000000000"+
"0000000000000000000000000000000000000000000000000000000000000001"+
"dead000000000000000000000000000000000000000000000000000000000000"+
"0000000000000000000000000000000000000000000000000000000000000000"),
[]byte{
0xde, 0xad, 0xbe, 0xef, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x09,
},
encoded,
)
}
......@@ -39,6 +39,7 @@ type DeployImplementationsOutput struct {
L1StandardBridgeImpl common.Address
OptimismMintableERC20FactoryImpl common.Address
DisputeGameFactoryImpl common.Address
AnchorStateRegistryImpl common.Address
}
func (output *DeployImplementationsOutput) CheckOutput(input common.Address) error {
......
......@@ -9,13 +9,16 @@ import (
"github.com/ethereum/go-ethereum/common"
)
// PermissionedGameStartingAnchorRoots is a root of bytes32(hex"dead") for the permissioned game at block 0,
// PermissionedGameStartingAnchorRoot is a root of bytes32(hex"dead") for the permissioned game at block 0,
// and no root for the permissionless game.
var PermissionedGameStartingAnchorRoots = []byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xde, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
var PermissionedGameStartingAnchorRoot = []byte{
0xde, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
type DeployOPChainInputV160 struct {
type DeployOPChainInput struct {
OpChainProxyAdminOwner common.Address
SystemConfigOwner common.Address
Batcher common.Address
......@@ -36,14 +39,17 @@ type DeployOPChainInputV160 struct {
DisputeSplitDepth uint64
DisputeClockExtension uint64
DisputeMaxClockDuration uint64
StartingAnchorRoots []byte
AllowCustomDisputeParameters bool
}
func (input *DeployOPChainInputV160) InputSet() bool {
func (input *DeployOPChainInput) InputSet() bool {
return true
}
func (input *DeployOPChainInput) StartingAnchorRoot() []byte {
return PermissionedGameStartingAnchorRoot
}
type DeployOPChainOutput struct {
OpChainProxyAdmin common.Address
AddressManager common.Address
......@@ -71,8 +77,8 @@ type DeployOPChainScript struct {
Run func(input, output common.Address) error
}
func DeployOPChainV160(host *script.Host, input DeployOPChainInputV160) (DeployOPChainOutput, error) {
return RunScriptSingle[DeployOPChainInputV160, DeployOPChainOutput](host, input, "DeployOPChain.s.sol", "DeployOPChain")
func DeployOPChain(host *script.Host, input DeployOPChainInput) (DeployOPChainOutput, error) {
return RunScriptSingle[DeployOPChainInput, DeployOPChainOutput](host, input, "DeployOPChain.s.sol", "DeployOPChain")
}
type ReadImplementationAddressesInput struct {
......
......@@ -17,7 +17,6 @@ type DeployOPCMInput struct {
ProxyAdminBlueprint common.Address
L1ChugSplashProxyBlueprint common.Address
ResolvedDelegateProxyBlueprint common.Address
AnchorStateRegistryBlueprint common.Address
PermissionedDisputeGame1Blueprint common.Address
PermissionedDisputeGame2Blueprint common.Address
......@@ -28,6 +27,7 @@ type DeployOPCMInput struct {
L1CrossDomainMessengerImpl common.Address
L1StandardBridgeImpl common.Address
DisputeGameFactoryImpl common.Address
AnchorStateRegistryImpl common.Address
DelayedWETHImpl common.Address
MipsImpl common.Address
}
......
......@@ -75,6 +75,7 @@ func DeployImplementations(env *Env, intent *state.Intent, st *state.State) erro
L1StandardBridgeImplAddress: dio.L1StandardBridgeImpl,
OptimismMintableERC20FactoryImplAddress: dio.OptimismMintableERC20FactoryImpl,
DisputeGameFactoryImplAddress: dio.DisputeGameFactoryImpl,
AnchorStateRegistryImplAddress: dio.AnchorStateRegistryImpl,
}
return nil
......
......@@ -27,12 +27,12 @@ func DeployOPChain(env *Env, intent *state.Intent, st *state.State, chainID comm
var dco opcm.DeployOPChainOutput
lgr.Info("deploying OP chain using local allocs", "id", chainID.Hex())
dci, err := makeDCIV160(intent, thisIntent, chainID, st)
dci, err := makeDCI(intent, thisIntent, chainID, st)
if err != nil {
return fmt.Errorf("error making deploy OP chain input: %w", err)
}
dco, err = opcm.DeployOPChainV160(env.L1ScriptHost, dci)
dco, err = opcm.DeployOPChain(env.L1ScriptHost, dci)
if err != nil {
return fmt.Errorf("error deploying OP chain: %w", err)
}
......@@ -70,7 +70,7 @@ func DeployOPChain(env *Env, intent *state.Intent, st *state.State, chainID comm
return nil
}
func makeDCIV160(intent *state.Intent, thisIntent *state.ChainIntent, chainID common.Hash, st *state.State) (opcm.DeployOPChainInputV160, error) {
func makeDCI(intent *state.Intent, thisIntent *state.ChainIntent, chainID common.Hash, st *state.State) (opcm.DeployOPChainInput, error) {
proofParams, err := jsonutil.MergeJSON(
state.ChainProofParams{
DisputeGameType: standard.DisputeGameType,
......@@ -84,31 +84,10 @@ func makeDCIV160(intent *state.Intent, thisIntent *state.ChainIntent, chainID co
thisIntent.DeployOverrides,
)
if err != nil {
return opcm.DeployOPChainInputV160{}, fmt.Errorf("error merging proof params from overrides: %w", err)
return opcm.DeployOPChainInput{}, fmt.Errorf("error merging proof params from overrides: %w", err)
}
startingAnchorRoots := opcm.PermissionedGameStartingAnchorRoots
if len(thisIntent.AdditionalDisputeGames) > 0 {
anchorRoots := []opcm.StartingAnchorRoot{
opcm.DefaultStartingAnchorRoot,
}
for _, game := range thisIntent.AdditionalDisputeGames {
anchorRoots = append(anchorRoots, opcm.StartingAnchorRoot{
GameType: game.DisputeGameType,
Root: game.StartingAnchorRoot,
L2BlockNumber: common.Big0,
})
}
encoded, err := opcm.EncodeStartingAnchorRoots(anchorRoots)
if err != nil {
return opcm.DeployOPChainInputV160{}, fmt.Errorf("error encoding starting anchor roots: %w", err)
}
startingAnchorRoots = encoded
}
return opcm.DeployOPChainInputV160{
return opcm.DeployOPChainInput{
OpChainProxyAdminOwner: thisIntent.Roles.L1ProxyAdminOwner,
SystemConfigOwner: thisIntent.Roles.SystemConfigOwner,
Batcher: thisIntent.Roles.Batcher,
......@@ -128,7 +107,6 @@ func makeDCIV160(intent *state.Intent, thisIntent *state.ChainIntent, chainID co
DisputeClockExtension: proofParams.DisputeClockExtension, // 3 hours (input in seconds)
DisputeMaxClockDuration: proofParams.DisputeMaxClockDuration, // 3.5 days (input in seconds)
AllowCustomDisputeParameters: proofParams.DangerouslyAllowCustomDisputeParameters,
StartingAnchorRoots: startingAnchorRoots,
}, nil
}
......
......@@ -33,7 +33,6 @@ type AdditionalDisputeGame struct {
OracleMinProposalSize uint64
OracleChallengePeriodSeconds uint64
MakeRespected bool
StartingAnchorRoot common.Hash
}
type ChainIntent struct {
......
......@@ -81,6 +81,7 @@ type ImplementationsDeployment struct {
L1StandardBridgeImplAddress common.Address `json:"l1StandardBridgeImplAddress"`
OptimismMintableERC20FactoryImplAddress common.Address `json:"optimismMintableERC20FactoryImplAddress"`
DisputeGameFactoryImplAddress common.Address `json:"disputeGameFactoryImplAddress"`
AnchorStateRegistryImplAddress common.Address `json:"anchorStateRegistryImplAddress"`
}
type AdditionalDisputeGameState struct {
......
......@@ -443,7 +443,6 @@ func defaultIntent(root string, loc *artifacts.Locator, deployer common.Address,
OracleMinProposalSize: 10000,
OracleChallengePeriodSeconds: 0,
MakeRespected: true,
StartingAnchorRoot: genesisOutputRoot,
},
{
ChainProofParams: state.ChainProofParams{
......@@ -456,7 +455,6 @@ func defaultIntent(root string, loc *artifacts.Locator, deployer common.Address,
DisputeMaxClockDuration: 1200,
},
VMType: state.VMTypeAlphabet,
StartingAnchorRoot: genesisOutputRoot,
},
{
ChainProofParams: state.ChainProofParams{
......@@ -468,7 +466,6 @@ func defaultIntent(root string, loc *artifacts.Locator, deployer common.Address,
DisputeMaxClockDuration: 1200,
},
VMType: cannonVMType(allocType),
StartingAnchorRoot: genesisOutputRoot,
},
},
},
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol";
import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol";
import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
import { GameType, Hash, OutputRoot } from "src/dispute/lib/Types.sol";
interface IAnchorStateRegistry {
struct StartingAnchorRoot {
GameType gameType;
OutputRoot outputRoot;
}
error InvalidGameStatus();
error Unauthorized();
error UnregisteredGame();
error AnchorStateRegistry_Unauthorized();
error AnchorStateRegistry_ImproperAnchorGame();
error AnchorStateRegistry_InvalidAnchorGame();
event AnchorNotUpdated(IFaultDisputeGame indexed game);
event AnchorUpdated(IFaultDisputeGame indexed game);
event Initialized(uint8 version);
function anchors(GameType) external view returns (Hash root, uint256 l2BlockNumber); // nosemgrep
function anchorGame() external view returns (IFaultDisputeGame);
function anchors(GameType) external view returns (Hash, uint256);
function getAnchorRoot() external view returns (Hash, uint256);
function disputeGameFactory() external view returns (IDisputeGameFactory);
function initialize(
StartingAnchorRoot[] memory _startingAnchorRoots,
ISuperchainConfig _superchainConfig
)
external;
function initialize(ISuperchainConfig _superchainConfig, IDisputeGameFactory _disputeGameFactory, IOptimismPortal2 _portal, OutputRoot memory _startingAnchorRoot) external;
function isGameRegistered(IDisputeGame _game) external view returns (bool);
function isGameBlacklisted(IDisputeGame _game) external view returns (bool);
function isGameRespected(IDisputeGame _game) external view returns (bool);
function isGameRetired(IDisputeGame _game) external view returns (bool);
function isGameProper(IDisputeGame _game) external view returns (bool);
function portal() external view returns (IOptimismPortal2);
function setAnchorState(IFaultDisputeGame _game) external;
function superchainConfig() external view returns (ISuperchainConfig);
function tryUpdateAnchorState() external;
function version() external view returns (string memory);
function __constructor__(IDisputeGameFactory _disputeGameFactory) external;
function __constructor__() external;
}
......@@ -73,11 +73,13 @@ test-upgrade *ARGS: build-go-ffi
#!/bin/bash
echo "Running upgrade tests at block $pinnedBlockNumber"
export FORK_BLOCK_NUMBER=$pinnedBlockNumber
export NO_MATCH_CONTRACTS="OptimismPortal2WithMockERC20_Test|OptimismPortal2_FinalizeWithdrawal_Test|AnchorStateRegistry_Initialize_Test|AnchorStateRegistry_TryUpdateAnchorState_Test|FaultDisputeGame_Test|FaultDispute_1v1_Actors_Test"
export NO_MATCH_CONTRACTS="OptimismPortal2WithMockERC20_Test|OptimismPortal2_FinalizeWithdrawal_Test|'AnchorStateRegistry_*'|FaultDisputeGame_Test|FaultDispute_1v1_Actors_Test"
export NO_MATCH_PATHS="test/dispute/AnchorStateRegistry.t.sol"
FORK_RPC_URL=$ETH_RPC_URL \
FORK_TEST=true \
forge test --match-path "test/{L1,dispute}/**" \
--no-match-contract "$NO_MATCH_CONTRACTS" \
--no-match-path "$NO_MATCH_PATHS" \
{{ARGS}}
test-upgrade-rerun *ARGS: build-go-ffi
......
......@@ -529,10 +529,6 @@ library ChainAssertions {
Blueprint.parseBlueprintPreamble(address(blueprints.resolvedDelegateProxy).code);
require(keccak256(rdProxyPreamble.initcode) == keccak256(vm.getCode("ResolvedDelegateProxy")), "CHECK-OPCM-180");
Blueprint.Preamble memory asrPreamble =
Blueprint.parseBlueprintPreamble(address(blueprints.anchorStateRegistry).code);
require(keccak256(asrPreamble.initcode) == keccak256(vm.getCode("AnchorStateRegistry")), "CHECK-OPCM-190");
Blueprint.Preamble memory pdg1Preamble =
Blueprint.parseBlueprintPreamble(address(blueprints.permissionedDisputeGame1).code);
Blueprint.Preamble memory pdg2Preamble =
......
......@@ -380,7 +380,6 @@ contract Deploy is Deployer {
artifacts.save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactoryProxy));
artifacts.save("PermissionedDelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy));
artifacts.save("AnchorStateRegistryProxy", address(deployOutput.anchorStateRegistryProxy));
artifacts.save("AnchorStateRegistryImpl", address(deployOutput.anchorStateRegistryImpl));
artifacts.save("PermissionedDisputeGame", address(deployOutput.permissionedDisputeGame));
artifacts.save("OptimismPortalProxy", address(deployOutput.optimismPortalProxy));
artifacts.save("OptimismPortal2Proxy", address(deployOutput.optimismPortalProxy));
......@@ -854,24 +853,6 @@ contract Deploy is Deployer {
/// @notice Get the DeployInput struct to use for testing
function getDeployInput() public view returns (OPContractsManager.DeployInput memory) {
OutputRoot memory testOutputRoot = OutputRoot({
root: Hash.wrap(cfg.faultGameGenesisOutputRoot()),
l2BlockNumber: cfg.faultGameGenesisBlock()
});
IAnchorStateRegistry.StartingAnchorRoot[] memory startingAnchorRoots =
new IAnchorStateRegistry.StartingAnchorRoot[](5);
startingAnchorRoots[0] =
IAnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.CANNON, outputRoot: testOutputRoot });
startingAnchorRoots[1] = IAnchorStateRegistry.StartingAnchorRoot({
gameType: GameTypes.PERMISSIONED_CANNON,
outputRoot: testOutputRoot
});
startingAnchorRoots[2] =
IAnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.ASTERISC, outputRoot: testOutputRoot });
startingAnchorRoots[3] =
IAnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.FAST, outputRoot: testOutputRoot });
startingAnchorRoots[4] =
IAnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.ALPHABET, outputRoot: testOutputRoot });
string memory saltMixer = "salt mixer";
return OPContractsManager.DeployInput({
roles: OPContractsManager.Roles({
......@@ -885,7 +866,9 @@ contract Deploy is Deployer {
basefeeScalar: cfg.basefeeScalar(),
blobBasefeeScalar: cfg.blobbasefeeScalar(),
l2ChainId: cfg.l2ChainID(),
startingAnchorRoots: abi.encode(startingAnchorRoots),
startingAnchorRoot: abi.encode(
OutputRoot({ root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), l2BlockNumber: cfg.faultGameGenesisBlock() })
),
saltMixer: saltMixer,
gasLimit: uint64(cfg.l2GenesisBlockGasLimit()),
disputeGameType: GameTypes.PERMISSIONED_CANNON,
......
......@@ -13,7 +13,7 @@ import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol";
import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol";
import { IMIPS } from "interfaces/cannon/IMIPS.sol";
import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol";
import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol";
import { OPContractsManager } from "src/L1/OPContractsManager.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
......@@ -142,6 +142,7 @@ contract DeployImplementationsOutput is BaseDeployIO {
IL1StandardBridge internal _l1StandardBridgeImpl;
IOptimismMintableERC20Factory internal _optimismMintableERC20FactoryImpl;
IDisputeGameFactory internal _disputeGameFactoryImpl;
IAnchorStateRegistry internal _anchorStateRegistryImpl;
function set(bytes4 _sel, address _addr) public {
require(_addr != address(0), "DeployImplementationsOutput: cannot set zero address");
......@@ -158,6 +159,7 @@ contract DeployImplementationsOutput is BaseDeployIO {
else if (_sel == this.l1StandardBridgeImpl.selector) _l1StandardBridgeImpl = IL1StandardBridge(payable(_addr));
else if (_sel == this.optimismMintableERC20FactoryImpl.selector) _optimismMintableERC20FactoryImpl = IOptimismMintableERC20Factory(_addr);
else if (_sel == this.disputeGameFactoryImpl.selector) _disputeGameFactoryImpl = IDisputeGameFactory(_addr);
else if (_sel == this.anchorStateRegistryImpl.selector) _anchorStateRegistryImpl = IAnchorStateRegistry(_addr);
else revert("DeployImplementationsOutput: unknown selector");
// forgefmt: disable-end
}
......@@ -179,7 +181,8 @@ contract DeployImplementationsOutput is BaseDeployIO {
address(this.l1ERC721BridgeImpl()),
address(this.l1StandardBridgeImpl()),
address(this.optimismMintableERC20FactoryImpl()),
address(this.disputeGameFactoryImpl())
address(this.disputeGameFactoryImpl()),
address(this.anchorStateRegistryImpl())
);
DeployUtils.assertValidContractAddresses(Solarray.extend(addrs1, addrs2));
......@@ -242,10 +245,16 @@ contract DeployImplementationsOutput is BaseDeployIO {
return _disputeGameFactoryImpl;
}
function anchorStateRegistryImpl() public view returns (IAnchorStateRegistry) {
DeployUtils.assertValidContractAddress(address(_anchorStateRegistryImpl));
return _anchorStateRegistryImpl;
}
// -------- Deployment Assertions --------
function assertValidDeploy(DeployImplementationsInput _dii) public view {
assertValidDelayedWETHImpl(_dii);
assertValidDisputeGameFactoryImpl(_dii);
assertValidAnchorStateRegistryImpl(_dii);
assertValidL1CrossDomainMessengerImpl(_dii);
assertValidL1ERC721BridgeImpl(_dii);
assertValidL1StandardBridgeImpl(_dii);
......@@ -387,6 +396,12 @@ contract DeployImplementationsOutput is BaseDeployIO {
require(address(factory.owner()) == address(0), "DG-10");
}
function assertValidAnchorStateRegistryImpl(DeployImplementationsInput) internal view {
IAnchorStateRegistry registry = anchorStateRegistryImpl();
DeployUtils.assertInitialized({ _contractAddress: address(registry), _isProxy: false, _slot: 0, _offset: 0 });
}
}
contract DeployImplementations is Script {
......@@ -406,6 +421,7 @@ contract DeployImplementations is Script {
deployPreimageOracleSingleton(_dii, _dio);
deployMipsSingleton(_dii, _dio);
deployDisputeGameFactoryImpl(_dio);
deployAnchorStateRegistryImpl(_dio);
// Deploy the OP Contracts Manager with the new implementations set.
deployOPContractsManager(_dii, _dio);
......@@ -438,6 +454,7 @@ contract DeployImplementations is Script {
l1CrossDomainMessengerImpl: address(_dio.l1CrossDomainMessengerImpl()),
l1StandardBridgeImpl: address(_dio.l1StandardBridgeImpl()),
disputeGameFactoryImpl: address(_dio.disputeGameFactoryImpl()),
anchorStateRegistryImpl: address(_dio.anchorStateRegistryImpl()),
delayedWETHImpl: address(_dio.delayedWETHImpl()),
mipsImpl: address(_dio.mipsSingleton())
});
......@@ -481,8 +498,6 @@ contract DeployImplementations is Script {
require(checkAddress == address(0), "OPCM-40");
(blueprints.resolvedDelegateProxy, checkAddress) = DeployUtils.createDeterministicBlueprint(vm.getCode("ResolvedDelegateProxy"), _salt);
require(checkAddress == address(0), "OPCM-50");
(blueprints.anchorStateRegistry, checkAddress) = DeployUtils.createDeterministicBlueprint(vm.getCode("AnchorStateRegistry"), _salt);
require(checkAddress == address(0), "OPCM-60");
// The max initcode/runtimecode size is 48KB/24KB.
// But for Blueprint, the initcode is stored as runtime code, that's why it's necessary to split into 2 parts.
(blueprints.permissionedDisputeGame1, blueprints.permissionedDisputeGame2) = DeployUtils.createDeterministicBlueprint(vm.getCode("PermissionedDisputeGame"), _salt);
......@@ -569,7 +584,7 @@ contract DeployImplementations is Script {
// | Contract | Proxied | Deployment | MCP Ready |
// |-------------------------|---------|-----------------------------------|------------|
// | DisputeGameFactory | Yes | Bespoke | Yes |
// | AnchorStateRegistry | Yes | Bespoke | No |
// | AnchorStateRegistry | Yes | Bespoke | Yes |
// | FaultDisputeGame | No | Bespoke | No | Not yet supported by OPCM
// | PermissionedDisputeGame | No | Bespoke | No |
// | DelayedWETH | Yes | Two bespoke (one per DisputeGame) | Yes *️⃣ |
......@@ -586,6 +601,7 @@ contract DeployImplementations is Script {
// here we deploy:
//
// - DisputeGameFactory (implementation)
// - AnchorStateRegistry (implementation)
// - OptimismPortal2 (implementation)
// - DelayedWETH (implementation)
// - PreimageOracle (singleton)
......@@ -594,7 +610,6 @@ contract DeployImplementations is Script {
// For contracts which are not MCP ready neither the Proxy nor the implementation can be shared, therefore they
// are deployed by `DeployOpChain.s.sol`.
// These are:
// - AnchorStateRegistry (proxy and implementation)
// - FaultDisputeGame (not proxied)
// - PermissionedDisputeGame (not proxied)
// - DelayedWeth (proxies only)
......@@ -690,6 +705,19 @@ contract DeployImplementations is Script {
_dio.set(_dio.disputeGameFactoryImpl.selector, address(impl));
}
function deployAnchorStateRegistryImpl(DeployImplementationsOutput _dio) public virtual {
vm.broadcast(msg.sender);
IAnchorStateRegistry impl = IAnchorStateRegistry(
DeployUtils.createDeterministic({
_name: "AnchorStateRegistry",
_args: DeployUtils.encodeConstructor(abi.encodeCall(IAnchorStateRegistry.__constructor__, ())),
_salt: _salt
})
);
vm.label(address(impl), "AnchorStateRegistryImpl");
_dio.set(_dio.anchorStateRegistryImpl.selector, address(impl));
}
// -------- Utilities --------
function etchIOContracts() public returns (DeployImplementationsInput dii_, DeployImplementationsOutput dio_) {
......@@ -769,6 +797,7 @@ contract DeployImplementationsInterop is DeployImplementations {
l1CrossDomainMessengerImpl: address(_dio.l1CrossDomainMessengerImpl()),
l1StandardBridgeImpl: address(_dio.l1StandardBridgeImpl()),
disputeGameFactoryImpl: address(_dio.disputeGameFactoryImpl()),
anchorStateRegistryImpl: address(_dio.anchorStateRegistryImpl()),
delayedWETHImpl: address(_dio.delayedWETHImpl()),
mipsImpl: address(_dio.mipsSingleton())
});
......
......@@ -22,7 +22,6 @@ contract DeployOPCMInput is BaseDeployIO {
address internal _proxyAdminBlueprint;
address internal _l1ChugSplashProxyBlueprint;
address internal _resolvedDelegateProxyBlueprint;
address internal _anchorStateRegistryBlueprint;
address internal _permissionedDisputeGame1Blueprint;
address internal _permissionedDisputeGame2Blueprint;
......@@ -33,6 +32,7 @@ contract DeployOPCMInput is BaseDeployIO {
address internal _l1CrossDomainMessengerImpl;
address internal _l1StandardBridgeImpl;
address internal _disputeGameFactoryImpl;
address internal _anchorStateRegistryImpl;
address internal _delayedWETHImpl;
address internal _mipsImpl;
......@@ -47,7 +47,6 @@ contract DeployOPCMInput is BaseDeployIO {
else if (_sel == this.proxyAdminBlueprint.selector) _proxyAdminBlueprint = _addr;
else if (_sel == this.l1ChugSplashProxyBlueprint.selector) _l1ChugSplashProxyBlueprint = _addr;
else if (_sel == this.resolvedDelegateProxyBlueprint.selector) _resolvedDelegateProxyBlueprint = _addr;
else if (_sel == this.anchorStateRegistryBlueprint.selector) _anchorStateRegistryBlueprint = _addr;
else if (_sel == this.permissionedDisputeGame1Blueprint.selector) _permissionedDisputeGame1Blueprint = _addr;
else if (_sel == this.permissionedDisputeGame2Blueprint.selector) _permissionedDisputeGame2Blueprint = _addr;
else if (_sel == this.l1ERC721BridgeImpl.selector) _l1ERC721BridgeImpl = _addr;
......@@ -57,6 +56,7 @@ contract DeployOPCMInput is BaseDeployIO {
else if (_sel == this.l1CrossDomainMessengerImpl.selector) _l1CrossDomainMessengerImpl = _addr;
else if (_sel == this.l1StandardBridgeImpl.selector) _l1StandardBridgeImpl = _addr;
else if (_sel == this.disputeGameFactoryImpl.selector) _disputeGameFactoryImpl = _addr;
else if (_sel == this.anchorStateRegistryImpl.selector) _anchorStateRegistryImpl = _addr;
else if (_sel == this.delayedWETHImpl.selector) _delayedWETHImpl = _addr;
else if (_sel == this.mipsImpl.selector) _mipsImpl = _addr;
else revert("DeployOPCMInput: unknown selector");
......@@ -110,11 +110,6 @@ contract DeployOPCMInput is BaseDeployIO {
return _resolvedDelegateProxyBlueprint;
}
function anchorStateRegistryBlueprint() public view returns (address) {
require(_anchorStateRegistryBlueprint != address(0), "DeployOPCMInput: not set");
return _anchorStateRegistryBlueprint;
}
function permissionedDisputeGame1Blueprint() public view returns (address) {
require(_permissionedDisputeGame1Blueprint != address(0), "DeployOPCMInput: not set");
return _permissionedDisputeGame1Blueprint;
......@@ -160,6 +155,11 @@ contract DeployOPCMInput is BaseDeployIO {
return _disputeGameFactoryImpl;
}
function anchorStateRegistryImpl() public view returns (address) {
require(_anchorStateRegistryImpl != address(0), "DeployOPCMInput: not set");
return _anchorStateRegistryImpl;
}
function delayedWETHImpl() public view returns (address) {
require(_delayedWETHImpl != address(0), "DeployOPCMInput: not set");
return _delayedWETHImpl;
......@@ -196,7 +196,6 @@ contract DeployOPCM is Script {
proxyAdmin: _doi.proxyAdminBlueprint(),
l1ChugSplashProxy: _doi.l1ChugSplashProxyBlueprint(),
resolvedDelegateProxy: _doi.resolvedDelegateProxyBlueprint(),
anchorStateRegistry: _doi.anchorStateRegistryBlueprint(),
permissionedDisputeGame1: _doi.permissionedDisputeGame1Blueprint(),
permissionedDisputeGame2: _doi.permissionedDisputeGame2Blueprint(),
permissionlessDisputeGame1: address(0),
......@@ -210,6 +209,7 @@ contract DeployOPCM is Script {
l1CrossDomainMessengerImpl: address(_doi.l1CrossDomainMessengerImpl()),
l1StandardBridgeImpl: address(_doi.l1StandardBridgeImpl()),
disputeGameFactoryImpl: address(_doi.disputeGameFactoryImpl()),
anchorStateRegistryImpl: address(_doi.anchorStateRegistryImpl()),
delayedWETHImpl: address(_doi.delayedWETHImpl()),
mipsImpl: address(_doi.mipsImpl())
});
......@@ -251,7 +251,6 @@ contract DeployOPCM is Script {
require(blueprints.proxyAdmin == _doi.proxyAdminBlueprint(), "OPCMI-60");
require(blueprints.l1ChugSplashProxy == _doi.l1ChugSplashProxyBlueprint(), "OPCMI-70");
require(blueprints.resolvedDelegateProxy == _doi.resolvedDelegateProxyBlueprint(), "OPCMI-80");
require(blueprints.anchorStateRegistry == _doi.anchorStateRegistryBlueprint(), "OPCMI-90");
require(blueprints.permissionedDisputeGame1 == _doi.permissionedDisputeGame1Blueprint(), "OPCMI-100");
require(blueprints.permissionedDisputeGame2 == _doi.permissionedDisputeGame2Blueprint(), "OPCMI-110");
......@@ -265,8 +264,9 @@ contract DeployOPCM is Script {
require(implementations.l1CrossDomainMessengerImpl == _doi.l1CrossDomainMessengerImpl(), "OPCMI-160");
require(implementations.l1StandardBridgeImpl == _doi.l1StandardBridgeImpl(), "OPCMI-170");
require(implementations.disputeGameFactoryImpl == _doi.disputeGameFactoryImpl(), "OPCMI-180");
require(implementations.delayedWETHImpl == _doi.delayedWETHImpl(), "OPCMI-190");
require(implementations.mipsImpl == _doi.mipsImpl(), "OPCMI-200");
require(implementations.anchorStateRegistryImpl == _doi.anchorStateRegistryImpl(), "OPCMI-190");
require(implementations.delayedWETHImpl == _doi.delayedWETHImpl(), "OPCMI-200");
require(implementations.mipsImpl == _doi.mipsImpl(), "OPCMI-210");
}
function etchIOContracts() public returns (DeployOPCMInput doi_, DeployOPCMOutput doo_) {
......
......@@ -159,7 +159,7 @@ contract DeployOPChainInput is BaseDeployIO {
return _l2ChainId;
}
function startingAnchorRoots() public pure returns (bytes memory) {
function startingAnchorRoot() public pure returns (bytes memory) {
// WARNING: For now always hardcode the starting permissioned game anchor root to 0xdead,
// and we do not set anything for the permissioned game. This is because we currently only
// support deploying straight to permissioned games, and the starting root does not
......@@ -168,10 +168,10 @@ contract DeployOPChainInput is BaseDeployIO {
// because to to update to the permissionless game, we will need to update its starting
// anchor root and deploy a new permissioned dispute game contract anyway.
//
// You can `console.logBytes(abi.encode(ScriptConstants.DEFAULT_STARTING_ANCHOR_ROOTS()))` to get the bytes that
// You can `console.logBytes(abi.encode(ScriptConstants.DEFAULT_OUTPUT_ROOT()))` to get the bytes that
// are hardcoded into `op-chain-ops/deployer/opcm/opchain.go`
return abi.encode(ScriptConstants.DEFAULT_STARTING_ANCHOR_ROOTS());
return abi.encode(ScriptConstants.DEFAULT_OUTPUT_ROOT());
}
function opcm() public view returns (OPContractsManager) {
......@@ -228,7 +228,6 @@ contract DeployOPChainOutput is BaseDeployIO {
IOptimismPortal2 internal _optimismPortalProxy;
IDisputeGameFactory internal _disputeGameFactoryProxy;
IAnchorStateRegistry internal _anchorStateRegistryProxy;
IAnchorStateRegistry internal _anchorStateRegistryImpl;
IFaultDisputeGame internal _faultDisputeGame;
IPermissionedDisputeGame internal _permissionedDisputeGame;
IDelayedWETH internal _delayedWETHPermissionedGameProxy;
......@@ -247,7 +246,6 @@ contract DeployOPChainOutput is BaseDeployIO {
else if (_sel == this.optimismPortalProxy.selector) _optimismPortalProxy = IOptimismPortal2(payable(_addr)) ;
else if (_sel == this.disputeGameFactoryProxy.selector) _disputeGameFactoryProxy = IDisputeGameFactory(_addr) ;
else if (_sel == this.anchorStateRegistryProxy.selector) _anchorStateRegistryProxy = IAnchorStateRegistry(_addr) ;
else if (_sel == this.anchorStateRegistryImpl.selector) _anchorStateRegistryImpl = IAnchorStateRegistry(_addr) ;
else if (_sel == this.faultDisputeGame.selector) _faultDisputeGame = IFaultDisputeGame(_addr) ;
else if (_sel == this.permissionedDisputeGame.selector) _permissionedDisputeGame = IPermissionedDisputeGame(_addr) ;
else if (_sel == this.delayedWETHPermissionedGameProxy.selector) _delayedWETHPermissionedGameProxy = IDelayedWETH(payable(_addr)) ;
......@@ -314,11 +312,6 @@ contract DeployOPChainOutput is BaseDeployIO {
return _anchorStateRegistryProxy;
}
function anchorStateRegistryImpl() public view returns (IAnchorStateRegistry) {
DeployUtils.assertValidContractAddress(address(_anchorStateRegistryImpl));
return _anchorStateRegistryImpl;
}
function faultDisputeGame() public view returns (IFaultDisputeGame) {
DeployUtils.assertValidContractAddress(address(_faultDisputeGame));
return _faultDisputeGame;
......@@ -361,7 +354,7 @@ contract DeployOPChain is Script {
basefeeScalar: _doi.basefeeScalar(),
blobBasefeeScalar: _doi.blobBaseFeeScalar(),
l2ChainId: _doi.l2ChainId(),
startingAnchorRoots: _doi.startingAnchorRoots(),
startingAnchorRoot: _doi.startingAnchorRoot(),
saltMixer: _doi.saltMixer(),
gasLimit: _doi.gasLimit(),
disputeGameType: _doi.disputeGameType(),
......@@ -385,7 +378,6 @@ contract DeployOPChain is Script {
vm.label(address(deployOutput.optimismPortalProxy), "optimismPortalProxy");
vm.label(address(deployOutput.disputeGameFactoryProxy), "disputeGameFactoryProxy");
vm.label(address(deployOutput.anchorStateRegistryProxy), "anchorStateRegistryProxy");
vm.label(address(deployOutput.anchorStateRegistryImpl), "anchorStateRegistryImpl");
// vm.label(address(deployOutput.faultDisputeGame), "faultDisputeGame");
vm.label(address(deployOutput.permissionedDisputeGame), "permissionedDisputeGame");
vm.label(address(deployOutput.delayedWETHPermissionedGameProxy), "delayedWETHPermissionedGameProxy");
......@@ -404,7 +396,6 @@ contract DeployOPChain is Script {
_doo.set(_doo.optimismPortalProxy.selector, address(deployOutput.optimismPortalProxy));
_doo.set(_doo.disputeGameFactoryProxy.selector, address(deployOutput.disputeGameFactoryProxy));
_doo.set(_doo.anchorStateRegistryProxy.selector, address(deployOutput.anchorStateRegistryProxy));
_doo.set(_doo.anchorStateRegistryImpl.selector, address(deployOutput.anchorStateRegistryImpl));
// _doo.set(_doo.faultDisputeGame.selector, address(deployOutput.faultDisputeGame));
_doo.set(_doo.permissionedDisputeGame.selector, address(deployOutput.permissionedDisputeGame));
_doo.set(_doo.delayedWETHPermissionedGameProxy.selector, address(deployOutput.delayedWETHPermissionedGameProxy));
......@@ -433,7 +424,6 @@ contract DeployOPChain is Script {
address(_doo.optimismPortalProxy()),
address(_doo.disputeGameFactoryProxy()),
address(_doo.anchorStateRegistryProxy()),
address(_doo.anchorStateRegistryImpl()),
address(_doo.permissionedDisputeGame()),
// address(_doo.faultDisputeGame()),
address(_doo.delayedWETHPermissionedGameProxy())
......@@ -447,7 +437,6 @@ contract DeployOPChain is Script {
// -------- Deployment Assertions --------
function assertValidDeploy(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal {
assertValidAnchorStateRegistryImpl(_doi, _doo);
assertValidAnchorStateRegistryProxy(_doi, _doo);
assertValidDelayedWETH(_doi, _doo);
assertValidDisputeGameFactory(_doi, _doo);
......@@ -508,9 +497,6 @@ contract DeployOPChain is Script {
_offset: 0
});
vm.prank(address(0));
address impl = proxy.implementation();
require(impl == address(_doo.anchorStateRegistryImpl()), "ANCHORP-20");
require(
address(_doo.anchorStateRegistryProxy().disputeGameFactory()) == address(_doo.disputeGameFactoryProxy()),
"ANCHORP-30"
......@@ -521,14 +507,6 @@ contract DeployOPChain is Script {
require(Hash.unwrap(actualRoot) == expectedRoot, "ANCHORP-40");
}
function assertValidAnchorStateRegistryImpl(DeployOPChainInput, DeployOPChainOutput _doo) internal {
IAnchorStateRegistry registry = _doo.anchorStateRegistryImpl();
DeployUtils.assertInitialized({ _contractAddress: address(registry), _isProxy: false, _slot: 0, _offset: 0 });
require(address(registry.disputeGameFactory()) == address(_doo.disputeGameFactoryProxy()), "ANCHORI-10");
}
function assertValidSystemConfig(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal {
ISystemConfig systemConfig = _doo.systemConfigProxy();
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol";
import { GameTypes, OutputRoot, Hash } from "src/dispute/lib/Types.sol";
import { OutputRoot, Hash } from "src/dispute/lib/Types.sol";
/// @title Constants
/// @notice Constants is a library for storing constants. Simple! Don't put everything in here, just
......@@ -13,14 +12,4 @@ library Constants {
function DEFAULT_OUTPUT_ROOT() internal pure returns (OutputRoot memory) {
return OutputRoot({ root: Hash.wrap(bytes32(hex"dead")), l2BlockNumber: 0 });
}
function DEFAULT_STARTING_ANCHOR_ROOTS() internal pure returns (IAnchorStateRegistry.StartingAnchorRoot[] memory) {
IAnchorStateRegistry.StartingAnchorRoot[] memory defaultStartingAnchorRoots =
new IAnchorStateRegistry.StartingAnchorRoot[](1);
defaultStartingAnchorRoots[0] = IAnchorStateRegistry.StartingAnchorRoot({
gameType: GameTypes.PERMISSIONED_CANNON,
outputRoot: DEFAULT_OUTPUT_ROOT()
});
return defaultStartingAnchorRoots;
}
}
[
{
"inputs": [
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"internalType": "contract IDisputeGameFactory",
"name": "_disputeGameFactory",
"inputs": [],
"name": "anchorGame",
"outputs": [
{
"internalType": "contract IFaultDisputeGame",
"name": "",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
......@@ -22,12 +29,12 @@
"outputs": [
{
"internalType": "Hash",
"name": "root",
"name": "",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "l2BlockNumber",
"name": "",
"type": "uint256"
}
],
......@@ -47,14 +54,40 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getAnchorRoot",
"outputs": [
{
"internalType": "Hash",
"name": "",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
"internalType": "contract ISuperchainConfig",
"name": "_superchainConfig",
"type": "address"
},
{
"internalType": "GameType",
"name": "gameType",
"type": "uint32"
"internalType": "contract IDisputeGameFactory",
"name": "_disputeGameFactory",
"type": "address"
},
{
"internalType": "contract IOptimismPortal2",
"name": "_portal",
"type": "address"
},
{
"components": [
......@@ -70,23 +103,121 @@
}
],
"internalType": "struct OutputRoot",
"name": "outputRoot",
"name": "_startingAnchorRoot",
"type": "tuple"
}
],
"internalType": "struct AnchorStateRegistry.StartingAnchorRoot[]",
"name": "_startingAnchorRoots",
"type": "tuple[]"
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"internalType": "contract ISuperchainConfig",
"name": "_superchainConfig",
"inputs": [
{
"internalType": "contract IDisputeGame",
"name": "_game",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"name": "isGameBlacklisted",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IDisputeGame",
"name": "_game",
"type": "address"
}
],
"name": "isGameProper",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IDisputeGame",
"name": "_game",
"type": "address"
}
],
"name": "isGameRegistered",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IDisputeGame",
"name": "_game",
"type": "address"
}
],
"name": "isGameRespected",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IDisputeGame",
"name": "_game",
"type": "address"
}
],
"name": "isGameRetired",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "portal",
"outputs": [
{
"internalType": "contract IOptimismPortal2",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
......@@ -135,6 +266,32 @@
"stateMutability": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "contract IFaultDisputeGame",
"name": "game",
"type": "address"
}
],
"name": "AnchorNotUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "contract IFaultDisputeGame",
"name": "game",
"type": "address"
}
],
"name": "AnchorUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
......@@ -150,17 +307,17 @@
},
{
"inputs": [],
"name": "InvalidGameStatus",
"name": "AnchorStateRegistry_ImproperAnchorGame",
"type": "error"
},
{
"inputs": [],
"name": "Unauthorized",
"name": "AnchorStateRegistry_InvalidAnchorGame",
"type": "error"
},
{
"inputs": [],
"name": "UnregisteredGame",
"name": "AnchorStateRegistry_Unauthorized",
"type": "error"
}
]
\ No newline at end of file
......@@ -43,11 +43,6 @@
"name": "resolvedDelegateProxy",
"type": "address"
},
{
"internalType": "address",
"name": "anchorStateRegistry",
"type": "address"
},
{
"internalType": "address",
"name": "permissionedDisputeGame1",
......@@ -110,6 +105,11 @@
"name": "disputeGameFactoryImpl",
"type": "address"
},
{
"internalType": "address",
"name": "anchorStateRegistryImpl",
"type": "address"
},
{
"internalType": "address",
"name": "delayedWETHImpl",
......@@ -271,11 +271,6 @@
"name": "resolvedDelegateProxy",
"type": "address"
},
{
"internalType": "address",
"name": "anchorStateRegistry",
"type": "address"
},
{
"internalType": "address",
"name": "permissionedDisputeGame1",
......@@ -382,7 +377,7 @@
},
{
"internalType": "bytes",
"name": "startingAnchorRoots",
"name": "startingAnchorRoot",
"type": "bytes"
},
{
......@@ -485,11 +480,6 @@
"name": "anchorStateRegistryProxy",
"type": "address"
},
{
"internalType": "contract IAnchorStateRegistry",
"name": "anchorStateRegistryImpl",
"type": "address"
},
{
"internalType": "contract IFaultDisputeGame",
"name": "faultDisputeGame",
......@@ -560,6 +550,11 @@
"name": "disputeGameFactoryImpl",
"type": "address"
},
{
"internalType": "address",
"name": "anchorStateRegistryImpl",
"type": "address"
},
{
"internalType": "address",
"name": "delayedWETHImpl",
......@@ -732,7 +727,7 @@
},
{
"inputs": [],
"name": "InvalidStartingAnchorRoots",
"name": "InvalidStartingAnchorRoot",
"type": "error"
},
{
......
......@@ -43,11 +43,6 @@
"name": "resolvedDelegateProxy",
"type": "address"
},
{
"internalType": "address",
"name": "anchorStateRegistry",
"type": "address"
},
{
"internalType": "address",
"name": "permissionedDisputeGame1",
......@@ -110,6 +105,11 @@
"name": "disputeGameFactoryImpl",
"type": "address"
},
{
"internalType": "address",
"name": "anchorStateRegistryImpl",
"type": "address"
},
{
"internalType": "address",
"name": "delayedWETHImpl",
......@@ -271,11 +271,6 @@
"name": "resolvedDelegateProxy",
"type": "address"
},
{
"internalType": "address",
"name": "anchorStateRegistry",
"type": "address"
},
{
"internalType": "address",
"name": "permissionedDisputeGame1",
......@@ -382,7 +377,7 @@
},
{
"internalType": "bytes",
"name": "startingAnchorRoots",
"name": "startingAnchorRoot",
"type": "bytes"
},
{
......@@ -485,11 +480,6 @@
"name": "anchorStateRegistryProxy",
"type": "address"
},
{
"internalType": "contract IAnchorStateRegistry",
"name": "anchorStateRegistryImpl",
"type": "address"
},
{
"internalType": "contract IFaultDisputeGame",
"name": "faultDisputeGame",
......@@ -560,6 +550,11 @@
"name": "disputeGameFactoryImpl",
"type": "address"
},
{
"internalType": "address",
"name": "anchorStateRegistryImpl",
"type": "address"
},
{
"internalType": "address",
"name": "delayedWETHImpl",
......@@ -732,7 +727,7 @@
},
{
"inputs": [],
"name": "InvalidStartingAnchorRoots",
"name": "InvalidStartingAnchorRoot",
"type": "error"
},
{
......
......@@ -16,8 +16,8 @@
"sourceCodeHash": "0x51f0876ab8410ce32838483f8f59ad6d1c5b4a368e47415b30e44baf291a394b"
},
"src/L1/OPContractsManager.sol": {
"initCodeHash": "0x8bef0b53e7102491957d3ea12ff4857d735dada6af3ef122376bcf3f5489c9b9",
"sourceCodeHash": "0x819e5e9867e09d16346daf81cfc9c129bf9026308055d1fbb6623b4f8818e613"
"initCodeHash": "0x88a6d99e668340e3af5c728c29e94e7229d89da0762b4bbf93bc10e596795c9f",
"sourceCodeHash": "0x2d21506cc51ebe0b60bcf89883aff5e9b1269567ce44ee779de3d3940e23fb65"
},
"src/L1/OptimismPortal2.sol": {
"initCodeHash": "0x2121a97875875150106a54a71c6c4c03afe90b3364e416be047f55fdeab57204",
......@@ -152,8 +152,8 @@
"sourceCodeHash": "0xb7b0a06cd971c4647247dc19ce997d0c64a73e87c81d30731da9cf9efa1b952a"
},
"src/dispute/AnchorStateRegistry.sol": {
"initCodeHash": "0x2d831d6afc62df024eb2df22eaca3c7378171e63d87c608bb53c0020d30c3dee",
"sourceCodeHash": "0xf1ce12bed377624e43697ae316646493e8df73a064bc4e250936964448326a70"
"initCodeHash": "0xfbeeac40d86d13e71c7add66eef6357576a93b6a175c9cff6ec6ef587fe3acc4",
"sourceCodeHash": "0xbb2e08da74d470fc30dd35dc39834e19f676a45974aa2403eb97e84bc5bed0a8"
},
"src/dispute/DelayedWETH.sol": {
"initCodeHash": "0x759d7f9c52b7c13ce4502f39dae3a75d130c6278240cde0b60ae84616aa2bd48",
......
......@@ -14,17 +14,38 @@
"type": "bool"
},
{
"bytes": "32",
"label": "anchors",
"bytes": "20",
"label": "superchainConfig",
"offset": 2,
"slot": "0",
"type": "contract ISuperchainConfig"
},
{
"bytes": "20",
"label": "disputeGameFactory",
"offset": 0,
"slot": "1",
"type": "mapping(GameType => struct OutputRoot)"
"type": "contract IDisputeGameFactory"
},
{
"bytes": "20",
"label": "superchainConfig",
"label": "portal",
"offset": 0,
"slot": "2",
"type": "contract ISuperchainConfig"
"type": "contract IOptimismPortal2"
},
{
"bytes": "20",
"label": "anchorGame",
"offset": 0,
"slot": "3",
"type": "contract IFaultDisputeGame"
},
{
"bytes": "64",
"label": "startingAnchorRoot",
"offset": 0,
"slot": "4",
"type": "struct OutputRoot"
}
]
\ No newline at end of file
......@@ -7,17 +7,17 @@
"type": "string"
},
{
"bytes": "320",
"bytes": "288",
"label": "blueprint",
"offset": 0,
"slot": "1",
"type": "struct OPContractsManager.Blueprints"
},
{
"bytes": "288",
"bytes": "320",
"label": "implementation",
"offset": 0,
"slot": "11",
"slot": "10",
"type": "struct OPContractsManager.Implementations"
}
]
\ No newline at end of file
......@@ -7,17 +7,17 @@
"type": "string"
},
{
"bytes": "320",
"bytes": "288",
"label": "blueprint",
"offset": 0,
"slot": "1",
"type": "struct OPContractsManager.Blueprints"
},
{
"bytes": "288",
"bytes": "320",
"label": "implementation",
"offset": 0,
"slot": "11",
"slot": "10",
"type": "struct OPContractsManager.Implementations"
}
]
\ No newline at end of file
......@@ -5,7 +5,7 @@ pragma solidity 0.8.15;
import { Blueprint } from "src/libraries/Blueprint.sol";
import { Constants } from "src/libraries/Constants.sol";
import { Bytes } from "src/libraries/Bytes.sol";
import { Claim, Duration, GameType, GameTypes } from "src/dispute/lib/Types.sol";
import { Claim, Duration, GameType, GameTypes, OutputRoot } from "src/dispute/lib/Types.sol";
// Interfaces
import { ISemver } from "interfaces/universal/ISemver.sol";
......@@ -18,7 +18,6 @@ import { IAddressManager } from "interfaces/legacy/IAddressManager.sol";
import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol";
import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol";
import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol";
import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol";
import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol";
import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
......@@ -49,9 +48,8 @@ contract OPContractsManager is ISemver {
uint32 basefeeScalar;
uint32 blobBasefeeScalar;
uint256 l2ChainId;
// The correct type is AnchorStateRegistry.StartingAnchorRoot[] memory,
// but OP Deployer does not yet support structs.
bytes startingAnchorRoots;
// The correct type is OutputRoot memory but OP Deployer does not yet support structs.
bytes startingAnchorRoot;
// The salt mixer is used as part of making the resulting salt unique.
string saltMixer;
uint64 gasLimit;
......@@ -77,7 +75,6 @@ contract OPContractsManager is ISemver {
IOptimismPortal2 optimismPortalProxy;
IDisputeGameFactory disputeGameFactoryProxy;
IAnchorStateRegistry anchorStateRegistryProxy;
IAnchorStateRegistry anchorStateRegistryImpl;
IFaultDisputeGame faultDisputeGame;
IPermissionedDisputeGame permissionedDisputeGame;
IDelayedWETH delayedWETHPermissionedGameProxy;
......@@ -95,7 +92,6 @@ contract OPContractsManager is ISemver {
address proxyAdmin;
address l1ChugSplashProxy;
address resolvedDelegateProxy;
address anchorStateRegistry;
address permissionedDisputeGame1;
address permissionedDisputeGame2;
address permissionlessDisputeGame1;
......@@ -111,6 +107,7 @@ contract OPContractsManager is ISemver {
address l1CrossDomainMessengerImpl;
address l1StandardBridgeImpl;
address disputeGameFactoryImpl;
address anchorStateRegistryImpl;
address delayedWETHImpl;
address mipsImpl;
}
......@@ -138,8 +135,8 @@ contract OPContractsManager is ISemver {
// -------- Constants and Variables --------
/// @custom:semver 1.0.0-beta.29
string public constant version = "1.0.0-beta.29";
/// @custom:semver 1.0.0-beta.30
string public constant version = "1.0.0-beta.30";
/// @notice Represents the interface version so consumers know how to decode the DeployOutput struct
/// that's emitted in the `Deployed` event. Whenever that struct changes, a new version should be used.
......@@ -198,8 +195,8 @@ contract OPContractsManager is ISemver {
/// @notice Thrown when the latest release is not set upon initialization.
error LatestReleaseNotSet();
/// @notice Thrown when the starting anchor roots are not provided.
error InvalidStartingAnchorRoots();
/// @notice Thrown when the starting anchor root is not provided.
error InvalidStartingAnchorRoot();
/// @notice Thrown when certain methods are called outside of a DELEGATECALL.
error OnlyDelegatecall();
......@@ -293,15 +290,6 @@ contract OPContractsManager is ISemver {
output.opChainProxyAdmin.setImplementationName(address(output.l1CrossDomainMessengerProxy), contractName);
// Now that all proxies are deployed, we can transfer ownership of the AddressManager to the ProxyAdmin.
output.addressManager.transferOwnership(address(output.opChainProxyAdmin));
// The AnchorStateRegistry Implementation is not MCP Ready, and therefore requires an implementation per chain.
// It must be deployed after the DisputeGameFactoryProxy so that it can be provided as a constructor argument.
output.anchorStateRegistryImpl = IAnchorStateRegistry(
Blueprint.deployFrom(
blueprint.anchorStateRegistry,
computeSalt(l2ChainId, saltMixer, "AnchorStateRegistry"),
abi.encode(output.disputeGameFactoryProxy)
)
);
// Eventually we will switch from DelayedWETHPermissionedGameProxy to DelayedWETHPermissionlessGameProxy.
output.delayedWETHPermissionedGameProxy = IDelayedWETH(
......@@ -397,11 +385,11 @@ contract OPContractsManager is ISemver {
);
output.disputeGameFactoryProxy.transferOwnership(address(_input.roles.opChainProxyAdminOwner));
data = encodeAnchorStateRegistryInitializer(_input);
data = encodeAnchorStateRegistryInitializer(_input, output);
upgradeAndCall(
output.opChainProxyAdmin,
address(output.anchorStateRegistryProxy),
address(output.anchorStateRegistryImpl),
implementation.anchorStateRegistryImpl,
data
);
......@@ -539,7 +527,7 @@ contract OPContractsManager is ISemver {
if (_input.roles.proposer == address(0)) revert InvalidRoleAddress("proposer");
if (_input.roles.challenger == address(0)) revert InvalidRoleAddress("challenger");
if (_input.startingAnchorRoots.length == 0) revert InvalidStartingAnchorRoots();
if (_input.startingAnchorRoot.length == 0) revert InvalidStartingAnchorRoot();
}
/// @notice Maps an L2 chain ID to an L1 batch inbox address as defined by the standard
......@@ -686,15 +674,20 @@ contract OPContractsManager is ISemver {
return abi.encodeCall(IDisputeGameFactory.initialize, (address(this)));
}
function encodeAnchorStateRegistryInitializer(DeployInput memory _input)
function encodeAnchorStateRegistryInitializer(
DeployInput memory _input,
DeployOutput memory _output
)
internal
view
virtual
returns (bytes memory)
{
IAnchorStateRegistry.StartingAnchorRoot[] memory startingAnchorRoots =
abi.decode(_input.startingAnchorRoots, (IAnchorStateRegistry.StartingAnchorRoot[]));
return abi.encodeCall(IAnchorStateRegistry.initialize, (startingAnchorRoots, superchainConfig));
OutputRoot memory startingAnchorRoot = abi.decode(_input.startingAnchorRoot, (OutputRoot));
return abi.encodeCall(
IAnchorStateRegistry.initialize,
(superchainConfig, _output.disputeGameFactoryProxy, _output.optimismPortalProxy, startingAnchorRoot)
);
}
function encodeDelayedWETHInitializer(DeployInput memory _input) internal view virtual returns (bytes memory) {
......
......@@ -6,8 +6,6 @@ import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable
// Libraries
import { GameType, OutputRoot, Claim, GameStatus, Hash } from "src/dispute/lib/Types.sol";
import { Unauthorized } from "src/libraries/errors/CommonErrors.sol";
import { UnregisteredGame, InvalidGameStatus } from "src/dispute/lib/Errors.sol";
// Interfaces
import { ISemver } from "interfaces/universal/ISemver.sol";
......@@ -15,6 +13,7 @@ import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol";
import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol";
import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
/// @custom:proxied true
/// @title AnchorStateRegistry
......@@ -23,105 +22,218 @@ import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
/// challenged within the challenge period. By using stored anchor states, new FaultDisputeGame instances can
/// be initialized with a more recent starting state which reduces the amount of required offchain computation.
contract AnchorStateRegistry is Initializable, ISemver {
/// @notice Describes an initial anchor state for a game type.
struct StartingAnchorRoot {
GameType gameType;
OutputRoot outputRoot;
}
/// @notice Semantic version.
/// @custom:semver 2.0.1-beta.6
string public constant version = "2.0.1-beta.6";
/// @notice DisputeGameFactory address.
IDisputeGameFactory internal immutable DISPUTE_GAME_FACTORY;
/// @notice Returns the anchor state for the given game type.
mapping(GameType => OutputRoot) public anchors;
/// @custom:semver 2.1.0-beta.1
string public constant version = "2.1.0-beta.1";
/// @notice Address of the SuperchainConfig contract.
ISuperchainConfig public superchainConfig;
/// @param _disputeGameFactory DisputeGameFactory address.
constructor(IDisputeGameFactory _disputeGameFactory) {
DISPUTE_GAME_FACTORY = _disputeGameFactory;
/// @notice Address of the DisputeGameFactory contract.
IDisputeGameFactory public disputeGameFactory;
/// @notice Address of the OptimismPortal contract.
IOptimismPortal2 public portal;
/// @notice The game whose claim is currently being used as the anchor state.
IFaultDisputeGame public anchorGame;
/// @notice The starting anchor root.
OutputRoot internal startingAnchorRoot;
/// @notice Emitted when an anchor state is not updated.
/// @param game Game that was not used as the new anchor game.
event AnchorNotUpdated(IFaultDisputeGame indexed game);
/// @notice Emitted when an anchor state is updated.
/// @param game Game that was used as the new anchor game.
event AnchorUpdated(IFaultDisputeGame indexed game);
/// @notice Thrown when an unauthorized caller attempts to set the anchor state.
error AnchorStateRegistry_Unauthorized();
/// @notice Thrown when an improper anchor game is provided.
error AnchorStateRegistry_ImproperAnchorGame();
/// @notice Thrown when an invalid anchor game is provided.
error AnchorStateRegistry_InvalidAnchorGame();
/// @notice Constructor to disable initializers.
constructor() {
_disableInitializers();
}
/// @notice Initializes the contract.
/// @param _startingAnchorRoots An array of starting anchor roots.
/// @param _superchainConfig The address of the SuperchainConfig contract.
/// @param _disputeGameFactory The address of the DisputeGameFactory contract.
/// @param _portal The address of the OptimismPortal contract.
/// @param _startingAnchorRoot The starting anchor root.
function initialize(
StartingAnchorRoot[] memory _startingAnchorRoots,
ISuperchainConfig _superchainConfig
ISuperchainConfig _superchainConfig,
IDisputeGameFactory _disputeGameFactory,
IOptimismPortal2 _portal,
OutputRoot memory _startingAnchorRoot
)
external
initializer
{
for (uint256 i = 0; i < _startingAnchorRoots.length; i++) {
StartingAnchorRoot memory startingAnchorRoot = _startingAnchorRoots[i];
anchors[startingAnchorRoot.gameType] = startingAnchorRoot.outputRoot;
}
superchainConfig = _superchainConfig;
disputeGameFactory = _disputeGameFactory;
portal = _portal;
startingAnchorRoot = _startingAnchorRoot;
}
/// @notice Returns the DisputeGameFactory address.
/// @return DisputeGameFactory address.
function disputeGameFactory() external view returns (IDisputeGameFactory) {
return DISPUTE_GAME_FACTORY;
/// @custom:legacy
/// @notice Returns the anchor root. Note that this is a legacy deprecated function and will
/// be removed in a future release. Use getAnchorRoot() instead. Anchor roots are no
/// longer stored per game type, so this function will return the same root for all
/// game types.
function anchors(GameType /* unused */ ) external view returns (Hash, uint256) {
return getAnchorRoot();
}
/// @notice Callable by FaultDisputeGame contracts to update the anchor state. Pulls the anchor state directly from
/// the FaultDisputeGame contract and stores it in the registry if the new anchor state is valid and the
/// state is newer than the current anchor state.
function tryUpdateAnchorState() external {
/// @notice Returns the current anchor root.
/// @return The anchor root.
function getAnchorRoot() public view returns (Hash, uint256) {
// Return the starting anchor root if there is no anchor game.
if (address(anchorGame) == address(0)) {
return (startingAnchorRoot.root, startingAnchorRoot.l2BlockNumber);
}
// Otherwise, return the anchor root.
return (Hash.wrap(anchorGame.rootClaim().raw()), anchorGame.l2BlockNumber());
}
/// @notice Determines whether a game is registered in the DisputeGameFactory.
/// @param _game The game to check.
/// @return Whether the game is factory registered.
function isGameRegistered(IDisputeGame _game) public view returns (bool) {
// Grab the game and game data.
IFaultDisputeGame game = IFaultDisputeGame(msg.sender);
(GameType gameType, Claim rootClaim, bytes memory extraData) = game.gameData();
(GameType gameType, Claim rootClaim, bytes memory extraData) = _game.gameData();
// Grab the verified address of the game based on the game data.
// slither-disable-next-line unused-return
(IDisputeGame factoryRegisteredGame,) =
DISPUTE_GAME_FACTORY.games({ _gameType: gameType, _rootClaim: rootClaim, _extraData: extraData });
(IDisputeGame _factoryRegisteredGame,) =
disputeGameFactory.games({ _gameType: gameType, _rootClaim: rootClaim, _extraData: extraData });
// Return whether the game is factory registered.
return address(_factoryRegisteredGame) == address(_game);
}
// Must be a valid game.
if (address(factoryRegisteredGame) != address(game)) revert UnregisteredGame();
/// @notice Determines whether a game is of a respected game type.
/// @param _game The game to check.
/// @return Whether the game is of a respected game type.
function isGameRespected(IDisputeGame _game) public view returns (bool) {
return _game.gameType().raw() == portal.respectedGameType().raw();
}
/// @notice Determines whether a game is blacklisted.
/// @param _game The game to check.
/// @return Whether the game is blacklisted.
function isGameBlacklisted(IDisputeGame _game) public view returns (bool) {
return portal.disputeGameBlacklist(_game);
}
// No need to update anything if the anchor state is already newer.
if (game.l2BlockNumber() <= anchors[gameType].l2BlockNumber) {
/// @notice Determines whether a game is retired.
/// @param _game The game to check.
/// @return Whether the game is retired.
function isGameRetired(IDisputeGame _game) public view returns (bool) {
// Must be created at or after the respectedGameTypeUpdatedAt timestamp. Note that the
// strict inequality exactly mirrors the logic in the OptimismPortal contract.
return _game.createdAt().raw() < portal.respectedGameTypeUpdatedAt();
}
/// @notice **READ THIS FUNCTION DOCUMENTATION CAREFULLY.**
/// Determines whether a game resolved properly and the game was not subject to any
/// invalidation conditions. The root claim of a proper game IS NOT guaranteed to be
/// valid. The root claim of a proper game CAN BE incorrect and still be a proper game.
/// DO NOT USE THIS FUNCTION ALONE TO DETERMINE IF A ROOT CLAIM IS VALID.
/// @dev Note that it is possible for games to be created when their game type is not the
/// respected game type. We do not consider these games to be Proper Games. isGameProper()
/// can currently guarantee this because the OptimismPortal contract will always set the
/// retirement timestamp whenever the respected game type is updated such that any games
/// created before any update of the respected game type are automatically retired. If
/// this coupling is broken, then we must instead check that the game type *was* the
/// respected game type at the time of the game's creation.
/// @param _game The game to check.
/// @return Whether the game is a proper game.
function isGameProper(IDisputeGame _game) public view returns (bool) {
// Must be registered in the DisputeGameFactory.
if (!isGameRegistered(_game)) {
return false;
}
// Must be respected game type.
if (!isGameRespected(_game)) {
return false;
}
// Must not be blacklisted.
if (isGameBlacklisted(_game)) {
return false;
}
// Must be created at or after the respectedGameTypeUpdatedAt timestamp.
if (isGameRetired(_game)) {
return false;
}
return true;
}
/// @notice Allows FaultDisputeGame contracts to attempt to become the new anchor game. A game
/// can only become the new anchor game if it is not invalid (it is a Proper Game), it
/// resolved in favor of the root claim, and it is newer than the current anchor game.
function tryUpdateAnchorState() external {
// Grab the game.
IFaultDisputeGame game = IFaultDisputeGame(msg.sender);
// Check if the game is a proper game.
if (!isGameProper(game)) {
emit AnchorNotUpdated(game);
return;
}
// Must be a game that resolved in favor of the state.
if (game.status() != GameStatus.DEFENDER_WINS) {
emit AnchorNotUpdated(game);
return;
}
// Must be newer than the current anchor game.
(, uint256 anchorL2BlockNumber) = getAnchorRoot();
if (game.l2BlockNumber() <= anchorL2BlockNumber) {
emit AnchorNotUpdated(game);
return;
}
// Actually update the anchor state.
anchors[gameType] = OutputRoot({ l2BlockNumber: game.l2BlockNumber(), root: Hash.wrap(game.rootClaim().raw()) });
// Update the anchor game.
anchorGame = game;
emit AnchorUpdated(game);
}
/// @notice Sets the anchor state given the game.
/// @notice Sets the anchor state given the game. Can only be triggered by the Guardian
/// address. Unlike tryUpdateAnchorState(), this function does not check if the
/// provided is newer than the existing anchor game. This allows the Guardian to
/// recover from situations in which the current anchor game is invalid.
/// @param _game The game to set the anchor state for.
function setAnchorState(IFaultDisputeGame _game) external {
if (msg.sender != superchainConfig.guardian()) revert Unauthorized();
// Get the metadata of the game.
(GameType gameType, Claim rootClaim, bytes memory extraData) = _game.gameData();
// Grab the verified address of the game based on the game data.
// slither-disable-next-line unused-return
(IDisputeGame factoryRegisteredGame,) =
DISPUTE_GAME_FACTORY.games({ _gameType: gameType, _rootClaim: rootClaim, _extraData: extraData });
// Function can only be triggered by the guardian.
if (msg.sender != superchainConfig.guardian()) {
revert AnchorStateRegistry_Unauthorized();
}
// Must be a valid game.
if (address(factoryRegisteredGame) != address(_game)) revert UnregisteredGame();
// Check if the game is a proper game.
if (!isGameProper(_game)) {
revert AnchorStateRegistry_ImproperAnchorGame();
}
// The game must have resolved in favor of the root claim.
if (_game.status() != GameStatus.DEFENDER_WINS) revert InvalidGameStatus();
if (_game.status() != GameStatus.DEFENDER_WINS) {
revert AnchorStateRegistry_InvalidAnchorGame();
}
// Update the anchor.
anchors[gameType] =
OutputRoot({ l2BlockNumber: _game.l2BlockNumber(), root: Hash.wrap(_game.rootClaim().raw()) });
// Update the anchor game.
anchorGame = _game;
emit AnchorUpdated(_game);
}
}
......@@ -127,13 +127,3 @@ error L2BlockNumberChallenged();
/// @notice Thrown when an unauthorized address attempts to interact with the game.
error BadAuth();
////////////////////////////////////////////////////////////////
// `AnchorStateRegistry` Errors //
////////////////////////////////////////////////////////////////
/// @notice Thrown when attempting to set an anchor state using an unregistered game.
error UnregisteredGame();
/// @notice Thrown when attempting to set an anchor state using an invalid game result.
error InvalidGameStatus();
......@@ -98,7 +98,7 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase {
basefeeScalar: _doi.basefeeScalar(),
blobBasefeeScalar: _doi.blobBaseFeeScalar(),
l2ChainId: _doi.l2ChainId(),
startingAnchorRoots: _doi.startingAnchorRoots(),
startingAnchorRoot: _doi.startingAnchorRoot(),
saltMixer: _doi.saltMixer(),
gasLimit: _doi.gasLimit(),
disputeGameType: _doi.disputeGameType(),
......@@ -184,7 +184,6 @@ contract OPContractsManager_AddGameType_Test is Test {
(blueprints.proxyAdmin,) = Blueprint.create(vm.getCode("ProxyAdmin"), salt);
(blueprints.l1ChugSplashProxy,) = Blueprint.create(vm.getCode("L1ChugSplashProxy"), salt);
(blueprints.resolvedDelegateProxy,) = Blueprint.create(vm.getCode("ResolvedDelegateProxy"), salt);
(blueprints.anchorStateRegistry,) = Blueprint.create(vm.getCode("AnchorStateRegistry"), salt);
(blueprints.permissionedDisputeGame1, blueprints.permissionedDisputeGame2) =
Blueprint.create(vm.getCode("PermissionedDisputeGame"), salt);
(blueprints.permissionlessDisputeGame1, blueprints.permissionlessDisputeGame2) =
......@@ -200,6 +199,7 @@ contract OPContractsManager_AddGameType_Test is Test {
l1CrossDomainMessengerImpl: address(new L1CrossDomainMessenger()),
l1StandardBridgeImpl: address(new L1StandardBridge()),
disputeGameFactoryImpl: address(new DisputeGameFactory()),
anchorStateRegistryImpl: address(new AnchorStateRegistry()),
delayedWETHImpl: address(new DelayedWETH(3)),
mipsImpl: address(new MIPS(oracle))
});
......@@ -209,12 +209,6 @@ contract OPContractsManager_AddGameType_Test is Test {
opcm = new OPContractsManager(superchainConfigProxy, protocolVersionsProxy, "dev", blueprints, impls);
AnchorStateRegistry.StartingAnchorRoot[] memory roots = new AnchorStateRegistry.StartingAnchorRoot[](1);
roots[0] = AnchorStateRegistry.StartingAnchorRoot({
outputRoot: OutputRoot({ root: Hash.wrap(hex"dead"), l2BlockNumber: 0 }),
gameType: GameType.wrap(1)
});
chainDeployOutput = opcm.deploy(
OPContractsManager.DeployInput({
roles: OPContractsManager.Roles({
......@@ -227,7 +221,7 @@ contract OPContractsManager_AddGameType_Test is Test {
}),
basefeeScalar: 1,
blobBasefeeScalar: 1,
startingAnchorRoots: abi.encode(roots),
startingAnchorRoot: abi.encode(OutputRoot({ root: Hash.wrap(hex"dead"), l2BlockNumber: 0 })),
l2ChainId: 100,
saltMixer: "hello",
gasLimit: 30_000_000,
......
......@@ -5,12 +5,16 @@ pragma solidity ^0.8.15;
import { FaultDisputeGame_Init, _changeClaimStatus } from "test/dispute/FaultDisputeGame.t.sol";
// Libraries
import "src/dispute/lib/Types.sol";
import "src/dispute/lib/Errors.sol";
import { GameType, GameStatus, Hash, Claim, VMStatuses, OutputRoot } from "src/dispute/lib/Types.sol";
import { Hash } from "src/dispute/lib/Types.sol";
// Interfaces
import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol";
import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol";
contract AnchorStateRegistry_Init is FaultDisputeGame_Init {
event AnchorNotUpdated(IFaultDisputeGame indexed game);
event AnchorUpdated(IFaultDisputeGame indexed game);
function setUp() public virtual override {
// Duplicating the initialization/setup logic of FaultDisputeGame_Test.
// See that test for more information, actual values here not really important.
......@@ -26,89 +30,608 @@ contract AnchorStateRegistry_Init is FaultDisputeGame_Init {
contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_Init {
/// @dev Tests that initialization is successful.
function test_initialize_succeeds() public view {
(Hash cannonRoot, uint256 cannonL2BlockNumber) = anchorStateRegistry.anchors(GameTypes.CANNON);
(Hash permissionedCannonRoot, uint256 permissionedCannonL2BlockNumber) =
anchorStateRegistry.anchors(GameTypes.PERMISSIONED_CANNON);
(Hash asteriscRoot, uint256 asteriscL2BlockNumber) = anchorStateRegistry.anchors(GameTypes.ASTERISC);
(Hash alphabetRoot, uint256 alphabetL2BlockNumber) = anchorStateRegistry.anchors(GameTypes.ALPHABET);
assertEq(cannonRoot.raw(), 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF);
assertEq(cannonL2BlockNumber, 0);
assertEq(permissionedCannonRoot.raw(), 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF);
assertEq(permissionedCannonL2BlockNumber, 0);
assertEq(asteriscRoot.raw(), 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF);
assertEq(asteriscL2BlockNumber, 0);
assertEq(alphabetRoot.raw(), 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF);
assertEq(alphabetL2BlockNumber, 0);
// Verify starting anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot();
assertEq(root.raw(), 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF);
assertEq(l2BlockNumber, 0);
// Verify contract addresses.
assert(anchorStateRegistry.superchainConfig() == superchainConfig);
assert(anchorStateRegistry.disputeGameFactory() == disputeGameFactory);
assert(anchorStateRegistry.portal() == optimismPortal2);
}
}
contract AnchorStateRegistry_Initialize_TestFail is AnchorStateRegistry_Init {
/// @notice Tests that initialization cannot be done twice
function test_initialize_twice_reverts() public {
vm.expectRevert("Initializable: contract is already initialized");
anchorStateRegistry.initialize(
superchainConfig,
disputeGameFactory,
optimismPortal2,
OutputRoot({
root: Hash.wrap(0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF),
l2BlockNumber: 0
})
);
}
}
contract AnchorStateRegistry_Version_Test is AnchorStateRegistry_Init {
/// @notice Tests that the version function returns a string.
function test_version_succeeds() public view {
assert(bytes(anchorStateRegistry.version()).length > 0);
}
}
contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_Init {
/// @notice Tests that getAnchorRoot will return the value of the starting anchor root when no
/// anchor game exists yet.
function test_getAnchorRoot_noAnchorGame_succeeds() public view {
// Assert that we nave no anchor game yet.
assert(address(anchorStateRegistry.anchorGame()) == address(0));
// We should get the starting anchor root back.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot();
assertEq(root.raw(), 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF);
assertEq(l2BlockNumber, 0);
}
}
contract AnchorStateRegistry_Anchors_Test is AnchorStateRegistry_Init {
/// @notice Tests that the anchors() function always matches the result of the getAnchorRoot()
/// function regardless of the game type used.
/// @param _gameType Game type to use as input to anchors().
function testFuzz_anchors_matchesGetAnchorRoot_succeeds(GameType _gameType) public view {
// Get the anchor root according to getAnchorRoot().
(Hash root1, uint256 l2BlockNumber1) = anchorStateRegistry.getAnchorRoot();
// Get the anchor root according to anchors().
(Hash root2, uint256 l2BlockNumber2) = anchorStateRegistry.anchors(_gameType);
// Assert that the two roots are the same.
assertEq(root1.raw(), root2.raw());
assertEq(l2BlockNumber1, l2BlockNumber2);
}
}
contract AnchorStateRegistry_IsGameRegistered_Test is AnchorStateRegistry_Init {
/// @notice Tests that isGameRegistered will return true if the game is registered.
function test_isGameRegistered_isActuallyFactoryRegistered_succeeds() public view {
assertTrue(anchorStateRegistry.isGameRegistered(gameProxy));
}
/// @notice Tests that isGameRegistered will return false if the game is not registered.
function test_isGameRegistered_isNotFactoryRegistered_succeeds() public {
// Mock the DisputeGameFactory to make it seem that the game was not registered.
vm.mockCall(
address(disputeGameFactory),
abi.encodeCall(
disputeGameFactory.games, (gameProxy.gameType(), gameProxy.rootClaim(), gameProxy.extraData())
),
abi.encode(address(0), 0)
);
assertFalse(anchorStateRegistry.isGameRegistered(gameProxy));
}
}
contract AnchorStateRegistry_IsGameBlacklisted_Test is AnchorStateRegistry_Init {
/// @notice Tests that isGameBlacklisted will return true if the game is blacklisted.
function test_isGameBlacklisted_isActuallyBlacklisted_succeeds() public {
// Mock the disputeGameBlacklist call to return true.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)),
abi.encode(true)
);
assertTrue(anchorStateRegistry.isGameBlacklisted(gameProxy));
}
/// @notice Tests that isGameBlacklisted will return false if the game is not blacklisted.
function test_isGameBlacklisted_isNotBlacklisted_succeeds() public {
// Mock the disputeGameBlacklist call to return false.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)),
abi.encode(false)
);
assertFalse(anchorStateRegistry.isGameBlacklisted(gameProxy));
}
}
contract AnchorStateRegistry_IsGameRespected_Test is AnchorStateRegistry_Init {
/// @notice Tests that isGameRespected will return true if the game is of the respected game type.
function test_isGameRespected_isRespected_succeeds() public {
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
assertTrue(anchorStateRegistry.isGameRespected(gameProxy));
}
/// @notice Tests that isGameRespected will return false if the game is not of the respected game
/// type.
/// @param _gameType The game type to use for the test.
function testFuzz_isGameRespected_isNotRespected_succeeds(GameType _gameType) public {
if (_gameType.raw() == gameProxy.gameType().raw()) {
_gameType = GameType.wrap(_gameType.raw() + 1);
}
// Make our game type NOT the respected game type.
vm.mockCall(
address(optimismPortal2), abi.encodeCall(optimismPortal2.respectedGameType, ()), abi.encode(_gameType)
);
assertFalse(anchorStateRegistry.isGameRespected(gameProxy));
}
}
contract AnchorStateRegistry_IsGameRetired_Test is AnchorStateRegistry_Init {
/// @notice Tests that isGameRetired will return true if the game is retired.
/// @param _retirementTimestamp The retirement timestamp to use for the test.
function testFuzz_isGameRetired_isRetired_succeeds(uint64 _retirementTimestamp) public {
// Make sure retirement timestamp is later than the game's creation time.
_retirementTimestamp = uint64(bound(_retirementTimestamp, gameProxy.createdAt().raw() + 1, type(uint64).max));
// Mock the respectedGameTypeUpdatedAt call to be later than the game's creation time.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()),
abi.encode(_retirementTimestamp)
);
assertTrue(anchorStateRegistry.isGameRetired(gameProxy));
}
/// @notice Tests that isGameRetired will return false if the game is not retired.
/// @param _retirementTimestamp The retirement timestamp to use for the test.
function testFuzz_isGameRetired_isNotRetired_succeeds(uint64 _retirementTimestamp) public {
// Make sure retirement timestamp is earlier than the game's creation time.
_retirementTimestamp = uint64(bound(_retirementTimestamp, 0, gameProxy.createdAt().raw()));
// Mock the respectedGameTypeUpdatedAt call to be earlier than the game's creation time.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()),
abi.encode(_retirementTimestamp)
);
assertFalse(anchorStateRegistry.isGameRetired(gameProxy));
}
}
contract AnchorStateRegistry_IsGameProper_Test is AnchorStateRegistry_Init {
/// @notice Tests that isGameProper will return true if the game meets all conditions.
function test_isGameProper_meetsAllConditions_succeeds() public {
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
assertTrue(anchorStateRegistry.isGameProper(gameProxy));
}
/// @notice Tests that isGameProper will return false if the game is not registered.
function test_isGameProper_isNotFactoryRegistered_succeeds() public {
// Mock the DisputeGameFactory to make it seem that the game was not registered.
vm.mockCall(
address(disputeGameFactory),
abi.encodeCall(
disputeGameFactory.games, (gameProxy.gameType(), gameProxy.rootClaim(), gameProxy.extraData())
),
abi.encode(address(0), 0)
);
assertFalse(anchorStateRegistry.isGameProper(gameProxy));
}
/// @notice Tests that isGameProper will return false if the game is not the respected game type.
function testFuzz_isGameProper_isNotRespected_succeeds(GameType _gameType) public {
if (_gameType.raw() == gameProxy.gameType().raw()) {
_gameType = GameType.wrap(_gameType.raw() + 1);
}
// Make our game type NOT the respected game type.
vm.mockCall(
address(optimismPortal2), abi.encodeCall(optimismPortal2.respectedGameType, ()), abi.encode(_gameType)
);
assertFalse(anchorStateRegistry.isGameProper(gameProxy));
}
/// @notice Tests that isGameProper will return false if the game is blacklisted.
function test_isGameProper_isBlacklisted_succeeds() public {
// Mock the disputeGameBlacklist call to return true.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)),
abi.encode(true)
);
assertFalse(anchorStateRegistry.isGameProper(gameProxy));
}
/// @notice Tests that isGameProper will return false if the game is retired.
function testFuzz_isGameProper_isRetired_succeeds(uint64 _retirementTimestamp) public {
// Make sure retirement timestamp is later than the game's creation time.
_retirementTimestamp = uint64(bound(_retirementTimestamp, gameProxy.createdAt().raw() + 1, type(uint64).max));
// Mock the respectedGameTypeUpdatedAt call to be later than the game's creation time.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()),
abi.encode(_retirementTimestamp)
);
assertFalse(anchorStateRegistry.isGameProper(gameProxy));
}
}
contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_Init {
/// @dev Tests that updating the anchor state succeeds when the game state is valid and newer.
function test_tryUpdateAnchorState_validNewerState_succeeds() public {
// Confirm that the anchor state is older than the game state.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
assert(l2BlockNumber < gameProxy.l2BlockNumber());
/// @notice Tests that tryUpdateAnchorState will succeed if the game is valid, the game block
/// number is greater than the current anchor root block number, and the game is the
/// currently respected game type.
/// @param _l2BlockNumber The L2 block number to use for the game.
function testFuzz_tryUpdateAnchorState_validNewerState_succeeds(uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot();
// Bound the new block number.
_l2BlockNumber = bound(_l2BlockNumber, l2BlockNumber + 1, type(uint256).max);
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the state that we want.
// Mock the DEFENDER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS));
// Try to update the anchor state.
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
// Update the anchor state.
vm.prank(address(gameProxy));
vm.expectEmit(address(anchorStateRegistry));
emit AnchorUpdated(gameProxy);
anchorStateRegistry.tryUpdateAnchorState();
// Confirm that the anchor state is now the same as the game state.
(root, l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
(root, l2BlockNumber) = anchorStateRegistry.getAnchorRoot();
assertEq(l2BlockNumber, gameProxy.l2BlockNumber());
assertEq(root.raw(), gameProxy.rootClaim().raw());
// Confirm that the anchor game is now set.
IFaultDisputeGame anchorGame = anchorStateRegistry.anchorGame();
assertEq(address(anchorGame), address(gameProxy));
}
/// @dev Tests that updating the anchor state fails when the game state is valid but older.
function test_tryUpdateAnchorState_validOlderState_fails() public {
// Confirm that the anchor state is newer than the game state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(0));
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
assert(l2BlockNumber >= gameProxy.l2BlockNumber());
/// @notice Tests that tryUpdateAnchorState will not update the anchor state if the game block
/// number is less than or equal to the current anchor root block number.
/// @param _l2BlockNumber The L2 block number to use for the game.
function testFuzz_tryUpdateAnchorState_validOlderStateNoUpdate_succeeds(uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot();
// Bound the new block number.
_l2BlockNumber = bound(_l2BlockNumber, 0, l2BlockNumber);
// Mock the state that we want.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(0));
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the DEFENDER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS));
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
// Try to update the anchor state.
vm.prank(address(gameProxy));
vm.expectEmit(address(anchorStateRegistry));
emit AnchorNotUpdated(gameProxy);
anchorStateRegistry.tryUpdateAnchorState();
// Confirm that the anchor state has not updated.
(Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
(Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.getAnchorRoot();
assertEq(updatedL2BlockNumber, l2BlockNumber);
assertEq(updatedRoot.raw(), root.raw());
}
/// @dev Tests that updating the anchor state fails when the game state is invalid.
function test_tryUpdateAnchorState_invalidNewerState_fails() public {
// Confirm that the anchor state is older than the game state.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
assert(l2BlockNumber < gameProxy.l2BlockNumber());
/// @notice Tests that tryUpdateAnchorState will not update the anchor state if the game is not
/// registered.
/// @param _l2BlockNumber The L2 block number to use for the game.
function testFuzz_tryUpdateAnchorState_notFactoryRegisteredGameNoUpdate_succeeds(uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot();
// Bound the new block number.
_l2BlockNumber = bound(_l2BlockNumber, l2BlockNumber, type(uint256).max);
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the DEFENDER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS));
// Mock the state that we want.
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
// Mock the DisputeGameFactory to make it seem that the game was not registered.
vm.mockCall(
address(disputeGameFactory),
abi.encodeCall(
disputeGameFactory.games, (gameProxy.gameType(), gameProxy.rootClaim(), gameProxy.extraData())
),
abi.encode(address(0), 0)
);
// Try to update the anchor state.
vm.prank(address(gameProxy));
vm.expectEmit(address(anchorStateRegistry));
emit AnchorNotUpdated(gameProxy);
anchorStateRegistry.tryUpdateAnchorState();
// Confirm that the anchor state has not updated.
(Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.getAnchorRoot();
assertEq(updatedL2BlockNumber, l2BlockNumber);
assertEq(updatedRoot.raw(), root.raw());
}
/// @notice Tests that tryUpdateAnchorState will not update the anchor state if the game status
/// is CHALLENGER_WINS.
/// @param _l2BlockNumber The L2 block number to use for the game.
function testFuzz_tryUpdateAnchorState_challengerWinsNoUpdate_succeeds(uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot();
// Bound the new block number.
_l2BlockNumber = bound(_l2BlockNumber, l2BlockNumber, type(uint256).max);
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the CHALLENGER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.CHALLENGER_WINS));
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
// Try to update the anchor state.
vm.prank(address(gameProxy));
vm.expectEmit(address(anchorStateRegistry));
emit AnchorNotUpdated(gameProxy);
anchorStateRegistry.tryUpdateAnchorState();
// Confirm that the anchor state has not updated.
(Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
(Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.getAnchorRoot();
assertEq(updatedL2BlockNumber, l2BlockNumber);
assertEq(updatedRoot.raw(), root.raw());
}
/// @notice Tests that tryUpdateAnchorState will not update the anchor state if the game status
/// is IN_PROGRESS.
/// @param _l2BlockNumber The L2 block number to use for the game.
function testFuzz_tryUpdateAnchorState_inProgressNoUpdate_succeeds(uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot();
// Bound the new block number.
_l2BlockNumber = bound(_l2BlockNumber, l2BlockNumber, type(uint256).max);
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the CHALLENGER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.IN_PROGRESS));
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
// Try to update the anchor state.
vm.prank(address(gameProxy));
vm.expectEmit(address(anchorStateRegistry));
emit AnchorNotUpdated(gameProxy);
anchorStateRegistry.tryUpdateAnchorState();
// Confirm that the anchor state has not updated.
(Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.getAnchorRoot();
assertEq(updatedL2BlockNumber, l2BlockNumber);
assertEq(updatedRoot.raw(), root.raw());
}
/// @dev Tests that updating the anchor state fails when the game is not registered with the factory.
function test_tryUpdateAnchorState_invalidGame_fails() public {
// Confirm that the anchor state is older than the game state.
/// @notice Tests that tryUpdateAnchorState will not update the anchor state if the game type
/// is not the respected game type.
/// @param _l2BlockNumber The L2 block number to use for the game.
function testFuzz_tryUpdateAnchorState_notRespectedGameTypeNoUpdate_succeeds(uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
assert(l2BlockNumber < gameProxy.l2BlockNumber());
// Mock the state that we want.
// Bound the new block number.
_l2BlockNumber = bound(_l2BlockNumber, l2BlockNumber, type(uint256).max);
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the DEFENDER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS));
// Mock the respectedGameType call so that it does NOT match our game type.
vm.mockCall(address(optimismPortal2), abi.encodeCall(optimismPortal2.respectedGameType, ()), abi.encode(999));
// Try to update the anchor state.
vm.prank(address(gameProxy));
vm.expectEmit(address(anchorStateRegistry));
emit AnchorNotUpdated(gameProxy);
anchorStateRegistry.tryUpdateAnchorState();
// Confirm that the anchor state has not updated.
(Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
assertEq(updatedL2BlockNumber, l2BlockNumber);
assertEq(updatedRoot.raw(), root.raw());
}
/// @notice Tests that tryUpdateAnchorState will not update the anchor state if the game is
/// blacklisted.
/// @param _l2BlockNumber The L2 block number to use for the game.
function testFuzz_tryUpdateAnchorState_blacklistedGameNoUpdate_succeeds(uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot();
// Bound the new block number.
_l2BlockNumber = bound(_l2BlockNumber, l2BlockNumber + 1, type(uint256).max);
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the DEFENDER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS));
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
// Mock the disputeGameBlacklist call to return true.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)),
abi.encode(true)
);
// Update the anchor state.
vm.prank(address(gameProxy));
vm.expectEmit(address(anchorStateRegistry));
emit AnchorNotUpdated(gameProxy);
anchorStateRegistry.tryUpdateAnchorState();
// Confirm that the anchor state has not updated.
(Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
assertEq(updatedL2BlockNumber, l2BlockNumber);
assertEq(updatedRoot.raw(), root.raw());
}
/// @notice Tests that tryUpdateAnchorState will not update the anchor state if the game is
/// retired.
/// @param _l2BlockNumber The L2 block number to use for the game.
function testFuzz_tryUpdateAnchorState_retiredGameNoUpdate_succeeds(uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot();
// Bound the new block number.
_l2BlockNumber = bound(_l2BlockNumber, l2BlockNumber + 1, type(uint256).max);
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the DEFENDER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS));
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
// Mock the respectedGameTypeUpdatedAt call to be later than the game's creation time.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()),
abi.encode(gameProxy.createdAt().raw() + 1)
);
// Update the anchor state.
vm.prank(address(gameProxy));
vm.expectEmit(address(anchorStateRegistry));
emit AnchorNotUpdated(gameProxy);
anchorStateRegistry.tryUpdateAnchorState();
// Confirm that the anchor state has not updated.
(Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
assertEq(updatedL2BlockNumber, l2BlockNumber);
assertEq(updatedRoot.raw(), root.raw());
}
}
contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_Init {
/// @notice Tests that setAnchorState will succeed with a game with any L2 block number as long
/// as the game is valid and is the currently respected game type.
/// @param _l2BlockNumber The L2 block number to use for the game.
function testFuzz_setAnchorState_anyL2BlockNumber_succeeds(uint256 _l2BlockNumber) public {
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the DEFENDER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS));
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
// Set the anchor state.
vm.prank(superchainConfig.guardian());
vm.expectEmit(address(anchorStateRegistry));
emit AnchorUpdated(gameProxy);
anchorStateRegistry.setAnchorState(gameProxy);
// Confirm that the anchor state has updated.
(Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
assertEq(updatedL2BlockNumber, gameProxy.l2BlockNumber());
assertEq(updatedRoot.raw(), gameProxy.rootClaim().raw());
// Confirm that the anchor game is now set.
IFaultDisputeGame anchorGame = anchorStateRegistry.anchorGame();
assertEq(address(anchorGame), address(gameProxy));
}
}
contract AnchorStateRegistry_SetAnchorState_TestFail is AnchorStateRegistry_Init {
/// @notice Tests that setAnchorState will revert if the sender is not the guardian.
/// @param _sender The address of the sender.
/// @param _l2BlockNumber The L2 block number to use for the game.
function testFuzz_setAnchorState_notGuardian_fails(address _sender, uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot();
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the DEFENDER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS));
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
// Mock the DisputeGameFactory to make it seem that the game was not registered.
vm.mockCall(
address(disputeGameFactory),
abi.encodeCall(
......@@ -118,9 +641,9 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In
);
// Try to update the anchor state.
vm.prank(address(gameProxy));
vm.expectRevert(UnregisteredGame.selector);
anchorStateRegistry.tryUpdateAnchorState();
vm.prank(_sender);
vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_Unauthorized.selector);
anchorStateRegistry.setAnchorState(gameProxy);
// Confirm that the anchor state has not updated.
(Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
......@@ -128,15 +651,26 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In
assertEq(updatedRoot.raw(), root.raw());
}
function test_setAnchorState_invalidGame_fails() public {
// Confirm that the anchor state is older than the game state.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
require(
l2BlockNumber < gameProxy.l2BlockNumber(),
"AnchorStateRegistry_TryUpdateAnchorState_Test: l2BlockNumber < gameProxy.l2BlockNumber()"
/// @notice Tests that setAnchorState will revert if the game is not registered.
/// @param _l2BlockNumber The L2 block number to use for the game.
function testFuzz_setAnchorState_notFactoryRegisteredGame_fails(uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot();
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the DEFENDER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS));
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
// Mock the state that we want.
// Mock the DisputeGameFactory to make it seem that the game was not registered.
vm.mockCall(
address(disputeGameFactory),
abi.encodeCall(
......@@ -147,7 +681,7 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In
// Try to update the anchor state.
vm.prank(superchainConfig.guardian());
vm.expectRevert(UnregisteredGame.selector);
vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_ImproperAnchorGame.selector);
anchorStateRegistry.setAnchorState(gameProxy);
// Confirm that the anchor state has not updated.
......@@ -156,16 +690,90 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In
assertEq(updatedRoot.raw(), root.raw());
}
/// @dev Tests that setting the anchor state fails if the challenger wins.
function test_setAnchorState_challengerWins_fails() public {
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
/// @notice Tests that setAnchorState will revert if the game is valid and the game status is
/// CHALLENGER_WINS.
/// @param _l2BlockNumber The L2 block number to use for the game.
function testFuzz_setAnchorState_challengerWins_fails(uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot();
// Mock the state that we want.
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the CHALLENGER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.CHALLENGER_WINS));
// Set the anchor state.
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
// Try to update the anchor state.
vm.prank(superchainConfig.guardian());
vm.expectRevert(InvalidGameStatus.selector);
vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector);
anchorStateRegistry.setAnchorState(gameProxy);
// Confirm that the anchor state has not updated.
(Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.getAnchorRoot();
assertEq(updatedL2BlockNumber, l2BlockNumber);
assertEq(updatedRoot.raw(), root.raw());
}
/// @notice Tests that setAnchorState will revert if the game is valid and the game status is
/// IN_PROGRESS.
/// @param _l2BlockNumber The L2 block number to use for the game.
function testFuzz_setAnchorState_inProgress_fails(uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot();
// Bound the new block number.
_l2BlockNumber = bound(_l2BlockNumber, l2BlockNumber, type(uint256).max);
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the IN_PROGRESS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.IN_PROGRESS));
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
// Try to update the anchor state.
vm.prank(superchainConfig.guardian());
vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector);
anchorStateRegistry.setAnchorState(gameProxy);
// Confirm that the anchor state has not updated.
(Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.getAnchorRoot();
assertEq(updatedL2BlockNumber, l2BlockNumber);
assertEq(updatedRoot.raw(), root.raw());
}
/// @notice Tests that setAnchorState will revert if the game is valid and the game type is not
/// the respected game type.
/// @param _l2BlockNumber The L2 block number to use for the game.
function testFuzz_setAnchorState_notRespectedGameType_fails(uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the DEFENDER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS));
// Mock the respectedGameType call so that it does NOT match our game type.
vm.mockCall(address(optimismPortal2), abi.encodeCall(optimismPortal2.respectedGameType, ()), abi.encode(999));
// Try to update the anchor state.
vm.prank(superchainConfig.guardian());
vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_ImproperAnchorGame.selector);
anchorStateRegistry.setAnchorState(gameProxy);
// Confirm that the anchor state has not updated.
......@@ -174,16 +782,35 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In
assertEq(updatedRoot.raw(), root.raw());
}
/// @dev Tests that setting the anchor state fails if the game is in progress.
function test_setAnchorState_gameInProgress_fails() public {
/// @notice Tests that setAnchorState will revert if the game is valid and the game is blacklisted.
/// @param _l2BlockNumber The L2 block number to use for the game.
function test_setAnchorState_blacklistedGame_fails(uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
// Mock the state that we want.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.IN_PROGRESS));
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the DEFENDER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS));
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
// Mock the disputeGameBlacklist call to return true.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)),
abi.encode(true)
);
// Set the anchor state.
vm.prank(superchainConfig.guardian());
vm.expectRevert(InvalidGameStatus.selector);
vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_ImproperAnchorGame.selector);
anchorStateRegistry.setAnchorState(gameProxy);
// Confirm that the anchor state has not updated.
......@@ -192,18 +819,40 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In
assertEq(updatedRoot.raw(), root.raw());
}
/// @dev Tests that setting the anchor state succeeds.
function test_setAnchorState_succeeds() public {
// Mock the state that we want.
/// @notice Tests that setAnchorState will revert if the game is valid and the game is retired.
/// @param _l2BlockNumber The L2 block number to use for the game.
function test_setAnchorState_retiredGame_fails(uint256 _l2BlockNumber) public {
// Grab block number of the existing anchor root.
(Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
// Mock the l2BlockNumber call.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(_l2BlockNumber));
// Mock the DEFENDER_WINS state.
vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS));
// Make our game type the respected game type.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameType, ()),
abi.encode(gameProxy.gameType())
);
// Mock the respectedGameTypeUpdatedAt call to be later than the game's creation time.
vm.mockCall(
address(optimismPortal2),
abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()),
abi.encode(gameProxy.createdAt().raw() + 1)
);
// Set the anchor state.
vm.prank(superchainConfig.guardian());
vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_ImproperAnchorGame.selector);
anchorStateRegistry.setAnchorState(gameProxy);
// Confirm that the anchor state has updated.
// Confirm that the anchor state has not updated.
(Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType());
assertEq(updatedL2BlockNumber, gameProxy.l2BlockNumber());
assertEq(updatedRoot.raw(), gameProxy.rootClaim().raw());
assertEq(updatedL2BlockNumber, l2BlockNumber);
assertEq(updatedRoot.raw(), root.raw());
}
}
......@@ -8,6 +8,7 @@ import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol";
import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol";
import { IMIPS } from "interfaces/cannon/IMIPS.sol";
import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol";
import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol";
......@@ -92,6 +93,7 @@ contract DeployImplementationsOutput_Test is Test {
IOptimismMintableERC20Factory optimismMintableERC20FactoryImpl =
IOptimismMintableERC20Factory(makeAddr("optimismMintableERC20FactoryImpl"));
IDisputeGameFactory disputeGameFactoryImpl = IDisputeGameFactory(makeAddr("disputeGameFactoryImpl"));
IAnchorStateRegistry anchorStateRegistryImpl = IAnchorStateRegistry(makeAddr("anchorStateRegistryImpl"));
vm.etch(address(opcm), hex"01");
vm.etch(address(optimismPortalImpl), hex"01");
......@@ -104,6 +106,7 @@ contract DeployImplementationsOutput_Test is Test {
vm.etch(address(l1StandardBridgeImpl), hex"01");
vm.etch(address(optimismMintableERC20FactoryImpl), hex"01");
vm.etch(address(disputeGameFactoryImpl), hex"01");
vm.etch(address(anchorStateRegistryImpl), hex"01");
dio.set(dio.opcm.selector, address(opcm));
dio.set(dio.optimismPortalImpl.selector, address(optimismPortalImpl));
dio.set(dio.delayedWETHImpl.selector, address(delayedWETHImpl));
......@@ -115,6 +118,7 @@ contract DeployImplementationsOutput_Test is Test {
dio.set(dio.l1StandardBridgeImpl.selector, address(l1StandardBridgeImpl));
dio.set(dio.optimismMintableERC20FactoryImpl.selector, address(optimismMintableERC20FactoryImpl));
dio.set(dio.disputeGameFactoryImpl.selector, address(disputeGameFactoryImpl));
dio.set(dio.anchorStateRegistryImpl.selector, address(anchorStateRegistryImpl));
assertEq(address(opcm), address(dio.opcm()), "50");
assertEq(address(optimismPortalImpl), address(dio.optimismPortalImpl()), "100");
......@@ -127,6 +131,7 @@ contract DeployImplementationsOutput_Test is Test {
assertEq(address(l1StandardBridgeImpl), address(dio.l1StandardBridgeImpl()), "800");
assertEq(address(optimismMintableERC20FactoryImpl), address(dio.optimismMintableERC20FactoryImpl()), "900");
assertEq(address(disputeGameFactoryImpl), address(dio.disputeGameFactoryImpl()), "950");
assertEq(address(anchorStateRegistryImpl), address(dio.anchorStateRegistryImpl()), "960");
}
function test_getters_whenNotSet_reverts() public {
......@@ -161,6 +166,9 @@ contract DeployImplementationsOutput_Test is Test {
vm.expectRevert(expectedErr);
dio.disputeGameFactoryImpl();
vm.expectRevert(expectedErr);
dio.anchorStateRegistryImpl();
}
function test_getters_whenAddrHasNoCode_reverts() public {
......@@ -267,6 +275,7 @@ contract DeployImplementations_Test is Test {
deployImplementations.deployPreimageOracleSingleton(dii, dio);
deployImplementations.deployMipsSingleton(dii, dio);
deployImplementations.deployDisputeGameFactoryImpl(dio);
deployImplementations.deployAnchorStateRegistryImpl(dio);
deployImplementations.deployOPContractsManager(dii, dio);
// Store the original addresses.
......@@ -280,6 +289,7 @@ contract DeployImplementations_Test is Test {
address preimageOracleSingleton = address(dio.preimageOracleSingleton());
address mipsSingleton = address(dio.mipsSingleton());
address disputeGameFactoryImpl = address(dio.disputeGameFactoryImpl());
address anchorStateRegistryImpl = address(dio.anchorStateRegistryImpl());
address opcm = address(dio.opcm());
// Do the deployments again. Thi should be a noop.
......@@ -293,6 +303,7 @@ contract DeployImplementations_Test is Test {
deployImplementations.deployPreimageOracleSingleton(dii, dio);
deployImplementations.deployMipsSingleton(dii, dio);
deployImplementations.deployDisputeGameFactoryImpl(dio);
deployImplementations.deployAnchorStateRegistryImpl(dio);
deployImplementations.deployOPContractsManager(dii, dio);
// Assert that the addresses did not change.
......@@ -306,7 +317,8 @@ contract DeployImplementations_Test is Test {
assertEq(preimageOracleSingleton, address(dio.preimageOracleSingleton()), "800");
assertEq(mipsSingleton, address(dio.mipsSingleton()), "900");
assertEq(disputeGameFactoryImpl, address(dio.disputeGameFactoryImpl()), "1000");
assertEq(opcm, address(dio.opcm()), "1100");
assertEq(anchorStateRegistryImpl, address(dio.anchorStateRegistryImpl()), "1100");
assertEq(opcm, address(dio.opcm()), "1200");
}
function testFuzz_run_memory_succeeds(bytes32 _seed) public {
......
......@@ -40,9 +40,6 @@ contract DeployOPCMInput_Test is Test {
vm.expectRevert("DeployOPCMInput: not set");
dii.resolvedDelegateProxyBlueprint();
vm.expectRevert("DeployOPCMInput: not set");
dii.anchorStateRegistryBlueprint();
vm.expectRevert("DeployOPCMInput: not set");
dii.permissionedDisputeGame1Blueprint();
......@@ -70,6 +67,9 @@ contract DeployOPCMInput_Test is Test {
vm.expectRevert("DeployOPCMInput: not set");
dii.disputeGameFactoryImpl();
vm.expectRevert("DeployOPCMInput: not set");
dii.anchorStateRegistryImpl();
vm.expectRevert("DeployOPCMInput: not set");
dii.delayedWETHImpl();
......@@ -87,7 +87,6 @@ contract DeployOPCMInput_Test is Test {
address proxyAdminBlueprint = makeAddr("proxyAdminBlueprint");
address l1ChugSplashProxyBlueprint = makeAddr("l1ChugSplashProxyBlueprint");
address resolvedDelegateProxyBlueprint = makeAddr("resolvedDelegateProxyBlueprint");
address anchorStateRegistryBlueprint = makeAddr("anchorStateRegistryBlueprint");
address permissionedDisputeGame1Blueprint = makeAddr("permissionedDisputeGame1Blueprint");
address permissionedDisputeGame2Blueprint = makeAddr("permissionedDisputeGame2Blueprint");
......@@ -99,7 +98,6 @@ contract DeployOPCMInput_Test is Test {
dii.set(dii.proxyAdminBlueprint.selector, proxyAdminBlueprint);
dii.set(dii.l1ChugSplashProxyBlueprint.selector, l1ChugSplashProxyBlueprint);
dii.set(dii.resolvedDelegateProxyBlueprint.selector, resolvedDelegateProxyBlueprint);
dii.set(dii.anchorStateRegistryBlueprint.selector, anchorStateRegistryBlueprint);
dii.set(dii.permissionedDisputeGame1Blueprint.selector, permissionedDisputeGame1Blueprint);
dii.set(dii.permissionedDisputeGame2Blueprint.selector, permissionedDisputeGame2Blueprint);
......@@ -111,7 +109,6 @@ contract DeployOPCMInput_Test is Test {
assertEq(dii.proxyAdminBlueprint(), proxyAdminBlueprint, "300");
assertEq(dii.l1ChugSplashProxyBlueprint(), l1ChugSplashProxyBlueprint, "350");
assertEq(dii.resolvedDelegateProxyBlueprint(), resolvedDelegateProxyBlueprint, "400");
assertEq(dii.anchorStateRegistryBlueprint(), anchorStateRegistryBlueprint, "450");
assertEq(dii.permissionedDisputeGame1Blueprint(), permissionedDisputeGame1Blueprint, "500");
assertEq(dii.permissionedDisputeGame2Blueprint(), permissionedDisputeGame2Blueprint, "550");
}
......@@ -124,6 +121,7 @@ contract DeployOPCMInput_Test is Test {
address l1CrossDomainMessengerImpl = makeAddr("l1CrossDomainMessengerImpl");
address l1StandardBridgeImpl = makeAddr("l1StandardBridgeImpl");
address disputeGameFactoryImpl = makeAddr("disputeGameFactoryImpl");
address anchorStateRegistryImpl = makeAddr("anchorStateRegistryImpl");
address delayedWETHImpl = makeAddr("delayedWETHImpl");
address mipsImpl = makeAddr("mipsImpl");
......@@ -134,6 +132,7 @@ contract DeployOPCMInput_Test is Test {
dii.set(dii.l1CrossDomainMessengerImpl.selector, l1CrossDomainMessengerImpl);
dii.set(dii.l1StandardBridgeImpl.selector, l1StandardBridgeImpl);
dii.set(dii.disputeGameFactoryImpl.selector, disputeGameFactoryImpl);
dii.set(dii.anchorStateRegistryImpl.selector, anchorStateRegistryImpl);
dii.set(dii.delayedWETHImpl.selector, delayedWETHImpl);
dii.set(dii.mipsImpl.selector, mipsImpl);
......@@ -225,7 +224,6 @@ contract DeployOPCMTest is Test {
doi.set(doi.proxyAdminBlueprint.selector, makeAddr("proxyAdminBlueprint"));
doi.set(doi.l1ChugSplashProxyBlueprint.selector, makeAddr("l1ChugSplashProxyBlueprint"));
doi.set(doi.resolvedDelegateProxyBlueprint.selector, makeAddr("resolvedDelegateProxyBlueprint"));
doi.set(doi.anchorStateRegistryBlueprint.selector, makeAddr("anchorStateRegistryBlueprint"));
doi.set(doi.permissionedDisputeGame1Blueprint.selector, makeAddr("permissionedDisputeGame1Blueprint"));
doi.set(doi.permissionedDisputeGame2Blueprint.selector, makeAddr("permissionedDisputeGame2Blueprint"));
......@@ -237,6 +235,7 @@ contract DeployOPCMTest is Test {
doi.set(doi.l1CrossDomainMessengerImpl.selector, makeAddr("l1CrossDomainMessengerImpl"));
doi.set(doi.l1StandardBridgeImpl.selector, makeAddr("l1StandardBridgeImpl"));
doi.set(doi.disputeGameFactoryImpl.selector, makeAddr("disputeGameFactoryImpl"));
doi.set(doi.anchorStateRegistryImpl.selector, makeAddr("anchorStateRegistryImpl"));
doi.set(doi.delayedWETHImpl.selector, makeAddr("delayedWETHImpl"));
doi.set(doi.mipsImpl.selector, makeAddr("mipsImpl"));
......@@ -249,7 +248,6 @@ contract DeployOPCMTest is Test {
vm.etch(doi.proxyAdminBlueprint(), hex"01");
vm.etch(doi.l1ChugSplashProxyBlueprint(), hex"01");
vm.etch(doi.resolvedDelegateProxyBlueprint(), hex"01");
vm.etch(doi.anchorStateRegistryBlueprint(), hex"01");
vm.etch(doi.permissionedDisputeGame1Blueprint(), hex"01");
vm.etch(doi.permissionedDisputeGame2Blueprint(), hex"01");
......
......@@ -155,7 +155,6 @@ contract DeployOPChainOutput_Test is Test {
doo.set(doo.optimismPortalProxy.selector, address(optimismPortalProxy));
doo.set(doo.disputeGameFactoryProxy.selector, address(disputeGameFactoryProxy));
doo.set(doo.anchorStateRegistryProxy.selector, address(anchorStateRegistryProxy));
doo.set(doo.anchorStateRegistryImpl.selector, address(anchorStateRegistryImpl));
doo.set(doo.faultDisputeGame.selector, address(faultDisputeGame));
doo.set(doo.permissionedDisputeGame.selector, address(permissionedDisputeGame));
doo.set(doo.delayedWETHPermissionedGameProxy.selector, address(delayedWETHPermissionedGameProxy));
......@@ -172,7 +171,6 @@ contract DeployOPChainOutput_Test is Test {
assertEq(address(optimismPortalProxy), address(doo.optimismPortalProxy()), "800");
assertEq(address(disputeGameFactoryProxy), address(doo.disputeGameFactoryProxy()), "900");
assertEq(address(anchorStateRegistryProxy), address(doo.anchorStateRegistryProxy()), "1100");
assertEq(address(anchorStateRegistryImpl), address(doo.anchorStateRegistryImpl()), "1200");
assertEq(address(faultDisputeGame), address(doo.faultDisputeGame()), "1300");
assertEq(address(permissionedDisputeGame), address(doo.permissionedDisputeGame()), "1400");
assertEq(address(delayedWETHPermissionedGameProxy), address(doo.delayedWETHPermissionedGameProxy()), "1500");
......@@ -214,9 +212,6 @@ contract DeployOPChainOutput_Test is Test {
vm.expectRevert(expectedErr);
doo.anchorStateRegistryProxy();
vm.expectRevert(expectedErr);
doo.anchorStateRegistryImpl();
vm.expectRevert(expectedErr);
doo.faultDisputeGame();
......@@ -275,10 +270,6 @@ contract DeployOPChainOutput_Test is Test {
vm.expectRevert(expectedErr);
doo.anchorStateRegistryProxy();
doo.set(doo.anchorStateRegistryImpl.selector, emptyAddr);
vm.expectRevert(expectedErr);
doo.anchorStateRegistryImpl();
doo.set(doo.faultDisputeGame.selector, emptyAddr);
vm.expectRevert(expectedErr);
doo.faultDisputeGame();
......@@ -336,7 +327,7 @@ contract DeployOPChain_TestBase is Test {
uint32 basefeeScalar = 100;
uint32 blobBaseFeeScalar = 200;
uint256 l2ChainId = 300;
IAnchorStateRegistry.StartingAnchorRoot[] startingAnchorRoots;
OutputRoot startingAnchorRoot = OutputRoot({ root: Hash.wrap(keccak256("defaultOutputRoot")), l2BlockNumber: 400 });
OPContractsManager opcm = OPContractsManager(address(0));
string saltMixer = "defaultSaltMixer";
uint64 gasLimit = 60_000_000;
......@@ -349,25 +340,6 @@ contract DeployOPChain_TestBase is Test {
uint64 disputeMaxClockDuration = Duration.unwrap(Duration.wrap(3.5 days));
function setUp() public virtual {
// Set defaults for reference types
uint256 cannonBlock = 400;
uint256 permissionedBlock = 500;
startingAnchorRoots.push(
IAnchorStateRegistry.StartingAnchorRoot({
gameType: GameTypes.CANNON,
outputRoot: OutputRoot({ root: Hash.wrap(keccak256("defaultOutputRootCannon")), l2BlockNumber: cannonBlock })
})
);
startingAnchorRoots.push(
IAnchorStateRegistry.StartingAnchorRoot({
gameType: GameTypes.PERMISSIONED_CANNON,
outputRoot: OutputRoot({
root: Hash.wrap(keccak256("defaultOutputRootPermissioned")),
l2BlockNumber: permissionedBlock
})
})
);
// Configure and deploy Superchain contracts
DeploySuperchain deploySuperchain = new DeploySuperchain();
(DeploySuperchainInput dsi, DeploySuperchainOutput dso) = deploySuperchain.etchIOContracts();
......@@ -432,25 +404,6 @@ contract DeployOPChain_Test is DeployOPChain_TestBase {
blobBaseFeeScalar = uint32(uint256(hash(_seed, 7)));
l2ChainId = uint256(hash(_seed, 8));
// Set the initial anchor states. The typical usage we expect is to pass in one root per game type.
uint256 cannonBlock = uint256(hash(_seed, 9));
uint256 permissionedBlock = uint256(hash(_seed, 10));
startingAnchorRoots.push(
IAnchorStateRegistry.StartingAnchorRoot({
gameType: GameTypes.CANNON,
outputRoot: OutputRoot({ root: Hash.wrap(keccak256(abi.encode(_seed, 11))), l2BlockNumber: cannonBlock })
})
);
startingAnchorRoots.push(
IAnchorStateRegistry.StartingAnchorRoot({
gameType: GameTypes.PERMISSIONED_CANNON,
outputRoot: OutputRoot({
root: Hash.wrap(keccak256(abi.encode(_seed, 12))),
l2BlockNumber: permissionedBlock
})
})
);
doi.set(doi.opChainProxyAdminOwner.selector, opChainProxyAdminOwner);
doi.set(doi.systemConfigOwner.selector, systemConfigOwner);
doi.set(doi.batcher.selector, batcher);
......
......@@ -554,11 +554,19 @@ contract Specification_Test is CommonTest {
// AnchorStateRegistry
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("anchors(uint32)") });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("getAnchorRoot()") });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("disputeGameFactory()") });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("initialize((uint32,(bytes32,uint256))[],address)") });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("portal()") });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("anchorGame()") });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("initialize(address,address,address,(bytes32,uint256))") });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("tryUpdateAnchorState()") });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("setAnchorState(address)"), _auth: Role.GUARDIAN });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("version()") });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameRegistered(address)") });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameRespected(address)") });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameBlacklisted(address)") });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameRetired(address)") });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameProper(address)") });
_addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("superchainConfig()") });
// PermissionedDisputeGame
......
......@@ -10,15 +10,16 @@ import { Process } from "scripts/libraries/Process.sol";
// Libraries
import { LibString } from "@solady/utils/LibString.sol";
import { GameType } from "src/dispute/lib/Types.sol";
import { GameType, Hash, OutputRoot } from "src/dispute/lib/Types.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
// Interfaces
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
import { ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol";
import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol";
/// @title Initializer_Test
/// @dev Ensures that the `initialize()` function on contracts cannot be called more than
......@@ -318,7 +319,12 @@ contract Initializer_Test is CommonTest {
target: EIP1967Helper.getImplementation(address(anchorStateRegistry)),
initCalldata: abi.encodeCall(
anchorStateRegistry.initialize,
(new IAnchorStateRegistry.StartingAnchorRoot[](1), ISuperchainConfig(address(0)))
(
ISuperchainConfig(address(0)),
IDisputeGameFactory(address(0)),
IOptimismPortal2(payable(0)),
OutputRoot({ root: Hash.wrap(bytes32(0)), l2BlockNumber: 0 })
)
)
})
);
......@@ -329,7 +335,12 @@ contract Initializer_Test is CommonTest {
target: address(anchorStateRegistry),
initCalldata: abi.encodeCall(
anchorStateRegistry.initialize,
(new IAnchorStateRegistry.StartingAnchorRoot[](1), ISuperchainConfig(address(0)))
(
ISuperchainConfig(address(0)),
IDisputeGameFactory(address(0)),
IOptimismPortal2(payable(0)),
OutputRoot({ root: Hash.wrap(bytes32(0)), l2BlockNumber: 0 })
)
)
})
);
......
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