Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions op-chain-ops/addresses/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type ImplementationsContracts struct {
AnchorStateRegistryImpl common.Address
FaultDisputeGameImpl common.Address
PermissionedDisputeGameImpl common.Address
ZkDisputeGameImpl common.Address
StorageSetterImpl common.Address
}

Expand Down
1 change: 1 addition & 0 deletions op-deployer/pkg/deployer/opcm/opchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ type ReadImplementationAddressesOutput struct {
PermissionedDisputeGame common.Address
SuperFaultDisputeGame common.Address
SuperPermissionedDisputeGame common.Address
ZkDisputeGame common.Address
OpcmStandardValidator common.Address
OpcmInteropMigrator common.Address
}
Expand Down
51 changes: 51 additions & 0 deletions op-deployer/pkg/deployer/pipeline/dispute_games.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import (
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/upgrade/embedded"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/lmittmann/w3"
)

func DeployAdditionalDisputeGames(
Expand Down Expand Up @@ -121,6 +123,51 @@ func deployDisputeGame(
}
vmAddr = out.MipsSingleton
}
case state.VMTypeZK:
zkImpl := st.ImplementationsDeployment.ZkDisputeGameImpl
if zkImpl == (common.Address{}) {
return fmt.Errorf("ZkDisputeGameImpl is not deployed; ensure ZKDisputeGameFlag is set in devFeatureBitmap")
}
if game.ZKDisputeGame == nil {
return fmt.Errorf("ZKDisputeGame params must be set when VMType is ZK")
}
if game.DisputeGameType != uint32(embedded.GameTypeZKDisputeGame) {
return fmt.Errorf("DisputeGameType must be %d for ZK dispute game, got %d", embedded.GameTypeZKDisputeGame, game.DisputeGameType)
}
zk := game.ZKDisputeGame
if zk.ChallengerBond == nil || zk.ChallengerBond.ToInt().Sign() <= 0 {
return fmt.Errorf("ZKDisputeGame.ChallengerBond must be set to a positive value")
}
challengerBond := zk.ChallengerBond.ToInt()
encoded, err := zkGameArgEncoder.EncodeArgs(&embedded.ZKDisputeGameConfig{
AbsolutePrestate: zk.AbsolutePrestate,
Verifier: zk.Verifier,
MaxChallengeDuration: zk.MaxChallengeDuration,
MaxProveDuration: zk.MaxProveDuration,
ChallengerBond: challengerBond,
})
if err != nil {
return fmt.Errorf("failed to encode ZK game args: %w", err)
}
zkInput := opcm.SetDisputeGameImplInput{
Factory: thisState.OpChainContracts.DisputeGameFactoryProxy,
Impl: zkImpl,
AnchorStateRegistry: common.Address{},
GameType: game.DisputeGameType,
GameArgs: encoded[4:],
}
if game.MakeRespected {
zkInput.AnchorStateRegistry = thisState.OpChainContracts.AnchorStateRegistryProxy
}
if err := opcm.SetDisputeGameImpl(env.L1ScriptHost, zkInput); err != nil {
return fmt.Errorf("failed to set ZK dispute game impl: %w", err)
}
thisState.AdditionalDisputeGames = append(thisState.AdditionalDisputeGames, state.AdditionalDisputeGameState{
GameType: game.DisputeGameType,
VMType: game.VMType,
GameAddress: zkImpl,
})
return nil
default:
return fmt.Errorf("unsupported VM type: %v", game.VMType)
}
Expand Down Expand Up @@ -213,6 +260,10 @@ func deployDisputeGame(
return nil
}

// zkGameArgEncoder encodes the ZK dispute game args for SetDisputeGameImpl.
// Mirrors the zkEncoder in upgrade/embedded/upgrade.go (same ABI signature).
var zkGameArgEncoder = w3.MustNewFunc("dummy((bytes32 absolutePrestate,address verifier,uint64 maxChallengeDuration,uint64 maxProveDuration,uint256 challengerBond))", "")

func shouldDeployAdditionalDisputeGames(thisIntent *state.ChainIntent, thisState *state.ChainState) bool {
if len(thisIntent.AdditionalDisputeGames) == 0 {
return false
Expand Down
245 changes: 245 additions & 0 deletions op-deployer/pkg/deployer/pipeline/dispute_games_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
package pipeline

import (
"log/slog"
"math/big"
"testing"

"github.com/ethereum-optimism/optimism/op-chain-ops/addresses"
"github.com/ethereum-optimism/optimism/op-core/devfeatures"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
)

func TestShouldDeployAdditionalDisputeGames(t *testing.T) {
dummyGame := state.AdditionalDisputeGame{VMType: state.VMTypeCannon}

tests := []struct {
name string
intent *state.ChainIntent
st *state.ChainState
expected bool
}{
{
name: "no_games_in_intent",
intent: &state.ChainIntent{},
st: &state.ChainState{},
expected: false,
},
{
name: "games_in_intent_empty_state",
intent: &state.ChainIntent{AdditionalDisputeGames: []state.AdditionalDisputeGame{dummyGame}},
st: &state.ChainState{},
expected: true,
},
{
name: "games_in_intent_already_deployed",
intent: &state.ChainIntent{AdditionalDisputeGames: []state.AdditionalDisputeGame{dummyGame}},
st: &state.ChainState{
AdditionalDisputeGames: []state.AdditionalDisputeGameState{
{GameType: 1, VMType: state.VMTypeCannon},
},
},
expected: false,
},
{
name: "zk_game_in_intent_empty_state",
intent: &state.ChainIntent{AdditionalDisputeGames: []state.AdditionalDisputeGame{{VMType: state.VMTypeZK}}},
st: &state.ChainState{},
expected: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := shouldDeployAdditionalDisputeGames(tt.intent, tt.st)
require.Equal(t, tt.expected, got)
})
}
}

func TestDeployDisputeGame_ZK_ZeroImpl(t *testing.T) {
lgr := testlog.Logger(t, slog.LevelInfo)

env := &Env{Logger: lgr}
st := &state.State{
ImplementationsDeployment: &addresses.ImplementationsContracts{
ZkDisputeGameImpl: common.Address{}, // zero — flag was not active
},
}
game := state.AdditionalDisputeGame{
VMType: state.VMTypeZK,
ZKDisputeGame: &state.ZKDisputeGameParams{
Verifier: common.HexToAddress("0x1111111111111111111111111111111111111111"),
AbsolutePrestate: common.HexToHash("0xdeadbeef"),
ChallengerBond: (*hexutil.Big)(big.NewInt(1e18)),
},
}

err := deployDisputeGame(env, st, &state.ChainIntent{}, &state.ChainState{}, game)
require.ErrorContains(t, err, "ZkDisputeGameImpl is not deployed")
}

func TestDeployDisputeGame_ZK_NilParams(t *testing.T) {
lgr := testlog.Logger(t, slog.LevelInfo)

env := &Env{Logger: lgr}
st := &state.State{
ImplementationsDeployment: &addresses.ImplementationsContracts{
ZkDisputeGameImpl: common.HexToAddress("0x2222222222222222222222222222222222222222"),
},
}
game := state.AdditionalDisputeGame{
VMType: state.VMTypeZK,
ZKDisputeGame: nil, // params not set
}

err := deployDisputeGame(env, st, &state.ChainIntent{}, &state.ChainState{}, game)
require.ErrorContains(t, err, "ZKDisputeGame params must be set")
}

func TestDeployDisputeGame_ZK_WrongDisputeGameType(t *testing.T) {
lgr := testlog.Logger(t, slog.LevelInfo)

env := &Env{Logger: lgr}
st := &state.State{
ImplementationsDeployment: &addresses.ImplementationsContracts{
ZkDisputeGameImpl: common.HexToAddress("0x2222222222222222222222222222222222222222"),
},
}
game := state.AdditionalDisputeGame{
VMType: state.VMTypeZK,
ZKDisputeGame: &state.ZKDisputeGameParams{},
ChainProofParams: state.ChainProofParams{DisputeGameType: 0}, // wrong — must be GameTypeZKDisputeGame (10)
}

err := deployDisputeGame(env, st, &state.ChainIntent{}, &state.ChainState{}, game)
require.ErrorContains(t, err, "DisputeGameType must be")
}

func TestDeployDisputeGame_ZK_NilChallengerBond(t *testing.T) {
lgr := testlog.Logger(t, slog.LevelInfo)

env := &Env{Logger: lgr}
st := &state.State{
ImplementationsDeployment: &addresses.ImplementationsContracts{
ZkDisputeGameImpl: common.HexToAddress("0x2222222222222222222222222222222222222222"),
},
}
game := state.AdditionalDisputeGame{
VMType: state.VMTypeZK,
ZKDisputeGame: &state.ZKDisputeGameParams{
ChallengerBond: nil,
},
ChainProofParams: state.ChainProofParams{DisputeGameType: 10},
}

err := deployDisputeGame(env, st, &state.ChainIntent{}, &state.ChainState{}, game)
require.ErrorContains(t, err, "ChallengerBond must be set")
}

func TestDeployDisputeGame_ZK_ZeroChallengerBond(t *testing.T) {
lgr := testlog.Logger(t, slog.LevelInfo)

env := &Env{Logger: lgr}
st := &state.State{
ImplementationsDeployment: &addresses.ImplementationsContracts{
ZkDisputeGameImpl: common.HexToAddress("0x2222222222222222222222222222222222222222"),
},
}
game := state.AdditionalDisputeGame{
VMType: state.VMTypeZK,
ZKDisputeGame: &state.ZKDisputeGameParams{
ChallengerBond: (*hexutil.Big)(big.NewInt(0)),
},
ChainProofParams: state.ChainProofParams{DisputeGameType: 10},
}

err := deployDisputeGame(env, st, &state.ChainIntent{}, &state.ChainState{}, game)
require.ErrorContains(t, err, "ChallengerBond must be set")
}

func TestDeployDisputeGame_UnsupportedVMType(t *testing.T) {
lgr := testlog.Logger(t, slog.LevelInfo)

env := &Env{Logger: lgr}
st := &state.State{
ImplementationsDeployment: &addresses.ImplementationsContracts{},
}
game := state.AdditionalDisputeGame{
VMType: state.VMType("UNSUPPORTED"),
}

err := deployDisputeGame(env, st, &state.ChainIntent{}, &state.ChainState{}, game)
require.ErrorContains(t, err, "unsupported VM type")
}

// TestZKDisputeGameFlag validates that devfeatures.ZKDisputeGameFlag matches the expected value.
func TestZKDisputeGameFlag(t *testing.T) {
expected := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000001000000")
require.Equal(t, expected, devfeatures.ZKDisputeGameFlag,
"devfeatures.ZKDisputeGameFlag must match the expected value")
}

// TestZKDevFlagAutoEnable validates that DeployImplementations auto-enables
// the ZK dev flag when a chain has VMTypeZK in AdditionalDisputeGames.
func TestZKDevFlagAutoEnable(t *testing.T) {
chainID := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001")

intentWithZK := &state.Intent{
GlobalDeployOverrides: map[string]any{},
Chains: []*state.ChainIntent{
{
ID: chainID,
AdditionalDisputeGames: []state.AdditionalDisputeGame{
{VMType: state.VMTypeZK},
},
},
},
}

intentWithoutZK := &state.Intent{
GlobalDeployOverrides: map[string]any{},
Chains: []*state.ChainIntent{
{
ID: chainID,
AdditionalDisputeGames: []state.AdditionalDisputeGame{},
},
},
}

t.Run("zk_game_enables_flag", func(t *testing.T) {
bitmap := computeDevFeatureBitmap(intentWithZK)
// ZK flag bit (0x01000000) should be set
require.NotEqual(t, common.Hash{}, bitmap)
for i := range devfeatures.ZKDisputeGameFlag {
require.Equal(t, devfeatures.ZKDisputeGameFlag[i], bitmap[i]&devfeatures.ZKDisputeGameFlag[i],
"ZK dev flag bit %d should be set in bitmap", i)
}
})

t.Run("no_zk_game_does_not_enable_flag", func(t *testing.T) {
bitmap := computeDevFeatureBitmap(intentWithoutZK)
require.Equal(t, common.Hash{}, bitmap)
})
}

// computeDevFeatureBitmap extracts the ZK flag auto-enable logic from DeployImplementations for testing.
func computeDevFeatureBitmap(intent *state.Intent) common.Hash {
var bitmap common.Hash
outer:
for _, chain := range intent.Chains {
for _, game := range chain.AdditionalDisputeGames {
if game.VMType == state.VMTypeZK {
for i := range bitmap {
bitmap[i] |= devfeatures.ZKDisputeGameFlag[i]
}
break outer
}
}
}
return bitmap
}
17 changes: 15 additions & 2 deletions op-deployer/pkg/deployer/pipeline/implementations.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import (
"fmt"
"math/big"

"github.com/ethereum-optimism/optimism/op-chain-ops/addresses"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
"github.com/ethereum/go-ethereum/common"

"github.com/ethereum-optimism/optimism/op-chain-ops/addresses"
"github.com/ethereum-optimism/optimism/op-core/devfeatures"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
"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-service/jsonutil"
)

func DeployImplementations(env *Env, intent *state.Intent, st *state.State) error {
Expand Down Expand Up @@ -43,6 +44,17 @@ func DeployImplementations(env *Env, intent *state.Intent, st *state.State) erro
return fmt.Errorf("error merging proof params from overrides: %w", err)
}

// Auto-enable ZKDisputeGameFlag if any chain has a ZK dispute game in AdditionalDisputeGames.
outer:
for _, chain := range intent.Chains {
for _, game := range chain.AdditionalDisputeGames {
if game.VMType == state.VMTypeZK {
proofParams.DevFeatureBitmap = devfeatures.EnableDevFeature(proofParams.DevFeatureBitmap, devfeatures.ZKDisputeGameFlag)
break outer
}
}
}
Comment thread
ajsutton marked this conversation as resolved.
Outdated

var dio opcm.DeployImplementationsOutput
input := opcm.DeployImplementationsInput{
WithdrawalDelaySeconds: new(big.Int).SetUint64(proofParams.WithdrawalDelaySeconds),
Expand Down Expand Up @@ -100,6 +112,7 @@ func DeployImplementations(env *Env, intent *state.Intent, st *state.State) erro
AnchorStateRegistryImpl: dio.AnchorStateRegistryImpl,
FaultDisputeGameImpl: dio.FaultDisputeGameImpl,
PermissionedDisputeGameImpl: dio.PermissionedDisputeGameImpl,
ZkDisputeGameImpl: dio.ZkDisputeGameImpl,
StorageSetterImpl: dio.StorageSetterImpl,
}

Expand Down
1 change: 1 addition & 0 deletions op-deployer/pkg/deployer/pipeline/opchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func DeployOPChain(env *Env, intent *state.Intent, st *state.State, chainID comm
st.ImplementationsDeployment.PreimageOracleImpl = impls.PreimageOracleSingleton
st.ImplementationsDeployment.FaultDisputeGameImpl = impls.FaultDisputeGame
st.ImplementationsDeployment.PermissionedDisputeGameImpl = impls.PermissionedDisputeGame
st.ImplementationsDeployment.ZkDisputeGameImpl = impls.ZkDisputeGame
st.ImplementationsDeployment.OpcmStandardValidatorImpl = impls.OpcmStandardValidator

return nil
Expand Down
Loading
Loading