Skip to content
Open
Show file tree
Hide file tree
Changes from 18 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
41 changes: 41 additions & 0 deletions op-challenger/game/fault/contracts/gameargs/gameargs.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package gameargs

import (
"encoding/binary"
"errors"
"fmt"
"math/big"
"slices"

"github.com/ethereum-optimism/optimism/op-service/eth"
Expand All @@ -12,6 +14,7 @@ import (
const (
PermissionlessArgsLength = 124
PermissionedArgsLength = 164
ZKArgsLength = 172
)

var (
Expand Down Expand Up @@ -47,6 +50,44 @@ func (g GameArgs) PackPermissioned() []byte {
)
}

// ZKGameArgs holds the arguments for a ZK dispute game, matching the packed layout
// defined in LibGameArgs.sol (ZK_ARGS_LENGTH = 172 bytes).
type ZKGameArgs struct {
AbsolutePrestate common.Hash
Verifier common.Address
MaxChallengeDuration uint64
MaxProveDuration uint64
ChallengerBond *big.Int
AnchorStateRegistry common.Address
Weth common.Address
L2ChainID *big.Int
}

// Pack encodes the ZK game args using abi.encodePacked layout (172 bytes).
// Layout: absolutePrestate(32) + verifier(20) + maxChallengeDuration(8) +
// maxProveDuration(8) + challengerBond(32) + anchorStateRegistry(20) +
// weth(20) + l2ChainId(32)
func (z ZKGameArgs) Pack() []byte {
dur1 := make([]byte, 8)
binary.BigEndian.PutUint64(dur1, z.MaxChallengeDuration)
dur2 := make([]byte, 8)
binary.BigEndian.PutUint64(dur2, z.MaxProveDuration)
bond := make([]byte, 32)
z.ChallengerBond.FillBytes(bond)
chainID := make([]byte, 32)
z.L2ChainID.FillBytes(chainID)
return slices.Concat(
z.AbsolutePrestate[:],
z.Verifier[:],
dur1,
dur2,
bond,
z.AnchorStateRegistry[:],
z.Weth[:],
chainID,
)
}

func Parse(args []byte) (GameArgs, error) {
if len(args) != PermissionlessArgsLength && len(args) != PermissionedArgsLength {
return GameArgs{}, fmt.Errorf("%w: invalid length (%v)", ErrInvalidGameArgs, len(args))
Expand Down
32 changes: 32 additions & 0 deletions op-challenger/game/fault/contracts/gameargs/gameargs_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package gameargs

import (
"encoding/binary"
"math/big"
"math/rand"
"testing"

Expand Down Expand Up @@ -53,6 +55,36 @@ func TestParse(t *testing.T) {
})
}

func TestZKGameArgsPack(t *testing.T) {
prestate := common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
verifier := common.HexToAddress("0x1111111111111111111111111111111111111111")
asr := common.HexToAddress("0x2222222222222222222222222222222222222222")
weth := common.HexToAddress("0x3333333333333333333333333333333333333333")
bond := big.NewInt(1e18)
chainID := big.NewInt(42)

got := ZKGameArgs{
AbsolutePrestate: prestate,
Verifier: verifier,
MaxChallengeDuration: 3600,
MaxProveDuration: 7200,
ChallengerBond: bond,
AnchorStateRegistry: asr,
Weth: weth,
L2ChainID: chainID,
}.Pack()

require.Len(t, got, ZKArgsLength)
require.Equal(t, prestate[:], got[0:32])
require.Equal(t, verifier[:], got[32:52])
require.Equal(t, uint64(3600), binary.BigEndian.Uint64(got[52:60]))
require.Equal(t, uint64(7200), binary.BigEndian.Uint64(got[60:68]))
require.Equal(t, bond, new(big.Int).SetBytes(got[68:100]))
require.Equal(t, asr[:], got[100:120])
require.Equal(t, weth[:], got[120:140])
require.Equal(t, chainID, new(big.Int).SetBytes(got[140:172]))
}

func fullGameArgs() GameArgs {
rng := rand.New(rand.NewSource(0))
return GameArgs{
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
45 changes: 45 additions & 0 deletions op-deployer/pkg/deployer/pipeline/dispute_games.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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"
)
Expand Down Expand Up @@ -121,6 +122,50 @@ 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")
}
gameArgs := gameargs.ZKGameArgs{
AbsolutePrestate: zk.AbsolutePrestate,
Verifier: zk.Verifier,
MaxChallengeDuration: zk.MaxChallengeDuration,
MaxProveDuration: zk.MaxProveDuration,
ChallengerBond: zk.ChallengerBond.ToInt(),
AnchorStateRegistry: thisState.OpChainContracts.AnchorStateRegistryProxy,
Weth: thisState.OpChainContracts.DelayedWethPermissionlessGameProxy,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit/question: Here we use thisState.OpChainContracts.DelayedWethPermissionlessGameProxy (makes sense, ZK is permissionless), but in line 202 below, we use thisState.OpChainContracts.DelayedWethPermissionedGameProxy for both permissioned and permissionless cannon games in the deploy.

This question is not about a change in this PR (this behavior was already in op-deployer previously), and I think it is correct to use the permissionless proxy for ZK games, but I'm raising this inconsistency/difference so someone with more context around these contracts can double check (cc @stevennevins)

L2ChainID: new(big.Int).SetBytes(thisIntent.ID[:]),
}.Pack()
zkInput := opcm.SetDisputeGameImplInput{
Factory: thisState.OpChainContracts.DisputeGameFactoryProxy,
Impl: zkImpl,
AnchorStateRegistry: common.Address{},
GameType: game.DisputeGameType,
GameArgs: gameArgs,
}
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
Loading
Loading