Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
21 changes: 21 additions & 0 deletions op-acceptance-tests/tests/proofs/zk/setup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package zk

import (
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-core/devfeatures"
"github.com/ethereum-optimism/optimism/op-devstack/devtest"
"github.com/ethereum-optimism/optimism/op-devstack/presets"
"github.com/ethereum-optimism/optimism/op-devstack/sysgo"
)

func zkOpts() []presets.Option {
return []presets.Option{
presets.WithGameTypeAdded(gameTypes.ZKDisputeGameType),
presets.WithDeployerOptions(sysgo.WithDevFeatureEnabled(devfeatures.ZKDisputeGameFlag)),
presets.WithDeployerOptions(sysgo.WithJovianAtGenesis),
}
}

func newSystem(t devtest.T) *presets.Minimal {
return presets.NewMinimal(t, zkOpts()...)
}
23 changes: 23 additions & 0 deletions op-acceptance-tests/tests/proofs/zk/smoke_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package zk

import (
"testing"

"github.com/ethereum-optimism/optimism/op-devstack/devtest"
)

// TestSmoke verifies that op-deployer correctly deploys and registers the ZK
// dispute game when ZKDisputeGameFlag is enabled.
func TestSmoke(gt *testing.T) {
t := devtest.ParallelT(gt)
sys := newSystem(t)
require := t.Require()

zk := sys.DisputeGameFactory().ZKGameImpl()

require.NotEmpty(zk.Address, "ZK dispute game impl must be registered in DisputeGameFactory")
require.NotZero(zk.Args.MaxChallengeDuration, "maxChallengeDuration must be set")
require.NotZero(zk.Args.MaxProveDuration, "maxProveDuration must be set")
require.Positive(zk.Args.ChallengerBond.Sign(), "challengerBond must be non-zero")
require.Equal(sys.L2Chain.ChainID().ToBig(), zk.Args.L2ChainID, "l2ChainId must match deployed chain")
}
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
185 changes: 185 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,185 @@
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")
}
5 changes: 3 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,13 @@ 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-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 @@ -100,6 +100,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