Skip to content
Merged
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
51 changes: 51 additions & 0 deletions op-challenger/game/fault/contracts/optimisticzkdisputegame.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package contracts
import (
"context"
"fmt"
"math/big"
"time"

"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics"
Expand All @@ -24,6 +25,23 @@ const (
ProposalStatusResolved
)

func (p ProposalStatus) String() string {
switch p {
case ProposalStatusUnchallenged:
return "Unchallenged"
case ProposalStatusChallenged:
return "Challenged"
case ProposalStatusUnchallengedAndValidProofProvided:
return "UnchallengedAndValidProofProvided"
case ProposalStatusChallengedAndValidProofProvided:
return "ChallengedAndValidProofProvided"
case ProposalStatusResolved:
return "Resolved"
default:
return fmt.Sprintf("ProposalStatus(%d)", uint8(p))
}
}

var (
methodChallenge = "challenge"
methodChallengerBond = "challengerBond"
Expand All @@ -44,6 +62,8 @@ type OptimisticZKDisputeGameContract interface {
ChallengeTx(ctx context.Context) (txmgr.TxCandidate, error)
GetProposal(ctx context.Context) (common.Hash, uint64, error)
GetChallengerMetadata(ctx context.Context, block rpcblock.Block) (ChallengerMetadata, error)
GetCredit(ctx context.Context, recipient common.Address) (*big.Int, gameTypes.GameStatus, error)
ClaimCreditTx(ctx context.Context, recipient common.Address) (txmgr.TxCandidate, error)
}

type OptimisticZKDisputeGameContractLatest struct {
Expand All @@ -52,6 +72,37 @@ type OptimisticZKDisputeGameContractLatest struct {
contract *batching.BoundContract
}

func (g *OptimisticZKDisputeGameContractLatest) GetCredit(ctx context.Context, recipient common.Address) (*big.Int, gameTypes.GameStatus, error) {
defer g.metrics.StartContractRequest("GetCredit")()
results, err := g.multiCaller.Call(ctx, rpcblock.Latest,
g.contract.Call(methodCredit, recipient),
g.contract.Call(methodStatus))
if err != nil {
return nil, gameTypes.GameStatusInProgress, err
}
if len(results) != 2 {
return nil, gameTypes.GameStatusInProgress, fmt.Errorf("expected 2 results but got %v", len(results))
}
credit := results[0].GetBigInt(0)
status, err := gameTypes.GameStatusFromUint8(results[1].GetUint8(0))
if err != nil {
return nil, gameTypes.GameStatusInProgress, fmt.Errorf("invalid game status %v: %w", status, err)
}
return credit, status, nil
}

func (g *OptimisticZKDisputeGameContractLatest) ClaimCreditTx(ctx context.Context, recipient common.Address) (txmgr.TxCandidate, error) {
defer g.metrics.StartContractRequest("ClaimCredit")()
call := g.contract.Call(methodClaimCredit, recipient)
_, err := g.multiCaller.SingleCall(ctx, rpcblock.Latest, call)
if err != nil {
return txmgr.TxCandidate{}, fmt.Errorf("%w: %w", ErrSimulationFailed, err)
}
return call.ToTxCandidate()
}

var _ OptimisticZKDisputeGameContract = (*OptimisticZKDisputeGameContractLatest)(nil)

func NewOptimisticZKDisputeGameContract(
m metrics.ContractMetricer,
addr common.Address,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package contracts

import (
"context"
"errors"
"math/big"
"testing"
"time"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -223,6 +225,52 @@ func TestZKGetProposal(t *testing.T) {
}
}

func TestZKGame_GetCredit(t *testing.T) {
for _, version := range zkVersions {
version := version
t.Run(version.String(), func(t *testing.T) {
stubRpc, game := setupZKDisputeGameTest(t, version)
addr := common.Address{0x01}
expectedCredit := big.NewInt(4284)
expectedStatus := gameTypes.GameStatusChallengerWon
stubRpc.SetResponse(zkGameAddr, methodCredit, rpcblock.Latest, []interface{}{addr}, []interface{}{expectedCredit})
stubRpc.SetResponse(zkGameAddr, methodStatus, rpcblock.Latest, nil, []interface{}{expectedStatus})

actualCredit, actualStatus, err := game.GetCredit(context.Background(), addr)
require.NoError(t, err)
require.Equal(t, expectedCredit, actualCredit)
require.Equal(t, expectedStatus, actualStatus)
})
}
}

func TestZKGame_ClaimCreditTx(t *testing.T) {
for _, version := range zkVersions {
version := version
t.Run(version.String(), func(t *testing.T) {
t.Run("Success", func(t *testing.T) {
stubRpc, game := setupZKDisputeGameTest(t, version)
addr := common.Address{0xaa}

stubRpc.SetResponse(zkGameAddr, methodClaimCredit, rpcblock.Latest, []interface{}{addr}, nil)
tx, err := game.ClaimCreditTx(context.Background(), addr)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
})

t.Run("SimulationFails", func(t *testing.T) {
stubRpc, game := setupZKDisputeGameTest(t, version)
addr := common.Address{0xaa}

stubRpc.SetError(zkGameAddr, methodClaimCredit, rpcblock.Latest, []interface{}{addr}, errors.New("still locked"))
tx, err := game.ClaimCreditTx(context.Background(), addr)
require.ErrorIs(t, err, ErrSimulationFailed)
require.Equal(t, txmgr.TxCandidate{}, tx)
})
})
}
}

func setupZKDisputeGameTest(t *testing.T, version contractVersion) (*batchingTest.AbiBasedRpc, OptimisticZKDisputeGameContract) {
fdgAbi := version.loadAbi()

Expand Down
16 changes: 14 additions & 2 deletions op-challenger/game/zk/actor.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ func (a *Actor) isValidProposal(ctx context.Context) (bool, error) {
// Output root doesn't match so can't be valid
return false, nil
}
if canonicalOutput.Status.SafeL2.Number < proposalSeqNum {
// Note this deliberately uses the simpler check of if the proposed block is currently unsafe
// The proposal is not necessarily supported by data on L1 up to the game's L1 head
// but we don't need to challenge it as long as supporting data has since become available
// and the output matches the canonical chain.
a.logger.Debug("Proposed block is not yet safe, treating as invalid", "safe", canonicalOutput.Status.SafeL2.Number, "proposed", proposalSeqNum)
return false, nil
}
Comment thread
ajsutton marked this conversation as resolved.
return true, nil
}

Expand Down Expand Up @@ -167,6 +175,10 @@ func (a *Actor) createResolveTx(ctx context.Context, gameState contracts.Challen
return txmgr.TxCandidate{}, errNoResolutionRequired
}

func (a *Actor) AdditionalStatus(_ context.Context) ([]any, error) {
return nil, nil
func (a *Actor) AdditionalStatus(ctx context.Context) ([]any, error) {
metadata, err := a.contract.GetChallengerMetadata(ctx, rpcblock.Latest)
if err != nil {
return nil, fmt.Errorf("failed to get challenger metadata: %w", err)
}
return []any{"proposalStatus", metadata.ProposalStatus}, nil
}
16 changes: 16 additions & 0 deletions op-challenger/game/zk/actor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ func TestActor(t *testing.T) {
},
challenge: true,
},
{
name: "ChallengeCurrentlyUnsafeProposal",
setup: func(t *testing.T, stubs *zkTestStubs) {
stubs.contract.proposalHash = stubs.rootProvider.root
stubs.contract.l2SequenceNumber = stubs.rootProvider.rootBlockNum
stubs.rootProvider.safeBlockNum = stubs.rootProvider.rootBlockNum - 1
},
challenge: true,
},
{
name: "ChallengeUnresolvableGameWithNoParent",
setup: func(t *testing.T, stubs *zkTestStubs) {
Expand Down Expand Up @@ -203,6 +212,7 @@ func setupActorTest(t *testing.T) (*Actor, *zkTestStubs) {
rootProvider := &stubRootProvider{
root: common.Hash{0x11},
rootBlockNum: rootBlockNum,
safeBlockNum: rootBlockNum + 10,
}
// Default to a valid proposal
contract := &stubContract{
Expand Down Expand Up @@ -231,6 +241,7 @@ type stubRootProvider struct {
outputErr error
rootBlockNum uint64
root common.Hash
safeBlockNum uint64
}

func (s *stubRootProvider) OutputAtBlock(_ context.Context, blockNum uint64) (*eth.OutputResponse, error) {
Expand All @@ -242,6 +253,11 @@ func (s *stubRootProvider) OutputAtBlock(_ context.Context, blockNum uint64) (*e
}
return &eth.OutputResponse{
OutputRoot: eth.Bytes32(s.root),
Status: &eth.SyncStatus{
SafeL2: eth.L2BlockRef{
Number: s.safeBlockNum,
},
},
}, nil
}

Expand Down
5 changes: 5 additions & 0 deletions op-challenger/game/zk/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/client"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/generic"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
Expand All @@ -22,6 +23,7 @@ type ClockReader interface {

type Registry interface {
RegisterGameType(gameType gameTypes.GameType, creator scheduler.PlayerCreator)
RegisterBondContract(gameType gameTypes.GameType, creator claims.BondContractCreator)
}

type TxSender interface {
Expand Down Expand Up @@ -60,6 +62,9 @@ func RegisterGameTypes(
ActorCreator(l1Clock, rollupClient, gameStatusProvider, contract, txSender),
)
})
registry.RegisterBondContract(gameTypes.OptimisticZKGameType, func(game gameTypes.GameMetadata) (claims.BondContract, error) {
return contracts.NewOptimisticZKDisputeGameContract(m, game.Proxy, clients.MultiCaller())
})
}
return nil
}