Commit 17bfe98e authored by Adrian Sutton's avatar Adrian Sutton

op-e2e: Use pre-deployed dispute game contracts

parent 58a2d894
...@@ -470,6 +470,7 @@ func NewDeployConfigWithNetwork(network, path string) (*DeployConfig, error) { ...@@ -470,6 +470,7 @@ func NewDeployConfigWithNetwork(network, path string) (*DeployConfig, error) {
// L1Deployments represents a set of L1 contracts that are deployed. // L1Deployments represents a set of L1 contracts that are deployed.
type L1Deployments struct { type L1Deployments struct {
AddressManager common.Address `json:"AddressManager"` AddressManager common.Address `json:"AddressManager"`
BlockOracle common.Address `json:"BlockOracle"`
DisputeGameFactory common.Address `json:"DisputeGameFactory"` DisputeGameFactory common.Address `json:"DisputeGameFactory"`
DisputeGameFactoryProxy common.Address `json:"DisputeGameFactoryProxy"` DisputeGameFactoryProxy common.Address `json:"DisputeGameFactoryProxy"`
L1CrossDomainMessenger common.Address `json:"L1CrossDomainMessenger"` L1CrossDomainMessenger common.Address `json:"L1CrossDomainMessenger"`
...@@ -512,7 +513,7 @@ func (d *L1Deployments) Check() error { ...@@ -512,7 +513,7 @@ func (d *L1Deployments) Check() error {
for i := 0; i < val.NumField(); i++ { for i := 0; i < val.NumField(); i++ {
name := val.Type().Field(i).Name name := val.Type().Field(i).Name
// Skip the non production ready contracts // Skip the non production ready contracts
if name == "DisputeGameFactory" || name == "DisputeGameFactoryProxy" { if name == "DisputeGameFactory" || name == "DisputeGameFactoryProxy" || name == "BlockOracle" {
continue continue
} }
if val.Field(i).Interface().(common.Address) == (common.Address{}) { if val.Field(i).Interface().(common.Address) == (common.Address{}) {
......
package disputegame
import (
"context"
"math/big"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
"github.com/ethereum-optimism/optimism/op-e2e/config"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require"
)
// deployDisputeGameContracts deploys the DisputeGameFactory, AlphabetVM and FaultDisputeGame contracts
// It configures the alphabet fault game as game type 0 (faultGameType)
// If/when the dispute game factory becomes a predeployed contract this can be removed and just use the
// predeployed version
func deployDisputeGameContracts(require *require.Assertions, ctx context.Context, clock *clock.AdvancingClock, client *ethclient.Client, opts *bind.TransactOpts, gameDuration uint64) (*bindings.DisputeGameFactory, *big.Int) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
defer cancel()
// Deploy the proxy
_, tx, proxy, err := bindings.DeployProxy(opts, client, deployer.TestAddress)
require.NoError(err)
proxyAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
// Deploy the dispute game factory implementation
_, tx, _, err = bindings.DeployDisputeGameFactory(opts, client)
require.NoError(err)
factoryAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
// Point the proxy at the implementation and create bindings going via the proxy
disputeGameFactoryAbi, err := bindings.DisputeGameFactoryMetaData.GetAbi()
require.NoError(err)
data, err := disputeGameFactoryAbi.Pack("initialize", deployer.TestAddress)
require.NoError(err)
tx, err = proxy.UpgradeToAndCall(opts, factoryAddr, data)
require.NoError(err)
_, err = utils.WaitReceiptOK(ctx, client, tx.Hash())
require.NoError(err)
factory, err := bindings.NewDisputeGameFactory(proxyAddr, client)
require.NoError(err)
// Now setup the fault dispute game type
// Start by deploying the AlphabetVM
_, tx, _, err = bindings.DeployAlphabetVM(opts, client, alphabetVMAbsolutePrestateClaim)
require.NoError(err)
alphaVMAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
l2OutputOracle, err := bindings.NewL2OutputOracle(config.L1Deployments.L2OutputOracleProxy, client)
require.NoError(err)
// Deploy the block hash oracle
_, tx, _, err = bindings.DeployBlockOracle(opts, client)
require.NoError(err)
blockHashOracleAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
blockHashOracle, err := bindings.NewBlockOracle(blockHashOracleAddr, client)
require.NoError(err)
// Deploy the fault dispute game implementation
_, tx, _, err = bindings.DeployFaultDisputeGame(
opts,
client,
uint8(0),
alphabetVMAbsolutePrestateClaim,
big.NewInt(alphabetGameDepth),
gameDuration,
alphaVMAddr,
config.L1Deployments.L2OutputOracleProxy,
blockHashOracleAddr,
)
require.NoError(err)
faultDisputeGameAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
// Create a proposer transactor
secrets, err := e2eutils.DefaultMnemonicConfig.Secrets()
require.NoError(err)
chainId, err := client.ChainID(ctx)
require.NoError(err)
proposerOpts, err := bind.NewKeyedTransactorWithChainID(secrets.Proposer, chainId)
require.NoError(err)
// Propose 2 outputs
for i := uint8(0); i < 2; i++ {
nextBlockNumber, err := l2OutputOracle.NextBlockNumber(&bind.CallOpts{Pending: true, Context: ctx})
require.NoError(err)
block, err := client.BlockByNumber(ctx, big.NewInt(int64(i)))
require.NoError(err)
tx, err = l2OutputOracle.ProposeL2Output(proposerOpts, [32]byte{i + 1}, nextBlockNumber, block.Hash(), block.Number())
require.NoError(err)
_, err = utils.WaitReceiptOK(ctx, client, tx.Hash())
require.NoError(err)
}
// Set the fault game type implementation
tx, err = factory.SetImplementation(opts, faultGameType, faultDisputeGameAddr)
require.NoError(err)
_, err = utils.WaitReceiptOK(ctx, client, tx.Hash())
require.NoError(err, "wait for final transaction to be included and OK")
// Warp 15 seconds ahead for a diff in the timestamp.
clock.AdvanceTime(15 * time.Second)
// Store the current block in the oracle
tx, err = blockHashOracle.Checkpoint(opts)
require.NoError(err)
r, err := utils.WaitReceiptOK(ctx, client, tx.Hash())
require.NoError(err, "failed to store block in blockoracle")
return factory, new(big.Int).Sub(r.BlockNumber, big.NewInt(1))
}
...@@ -10,15 +10,14 @@ import ( ...@@ -10,15 +10,14 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer" "github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/fault/alphabet" "github.com/ethereum-optimism/optimism/op-challenger/fault/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-service/client/utils" "github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -35,48 +34,90 @@ const ( ...@@ -35,48 +34,90 @@ const (
StatusDefenderWins StatusDefenderWins
) )
var alphabetVMAbsolutePrestate = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000060") func (s Status) String() string {
var alphabetVMAbsolutePrestateClaim = crypto.Keccak256Hash(alphabetVMAbsolutePrestate) switch s {
case StatusInProgress:
return "In Progress"
case StatusChallengerWins:
return "Challenger Wins"
case StatusDefenderWins:
return "Defender Wins"
default:
return fmt.Sprintf("Unknown status: %v", int(s))
}
}
var CorrectAlphabet = "abcdefghijklmnop" var CorrectAlphabet = "abcdefghijklmnop"
type FactoryHelper struct { type FactoryHelper struct {
t *testing.T t *testing.T
require *require.Assertions require *require.Assertions
client *ethclient.Client client *ethclient.Client
opts *bind.TransactOpts opts *bind.TransactOpts
factory *bindings.DisputeGameFactory factory *bindings.DisputeGameFactory
l1Head *big.Int blockOracle *bindings.BlockOracle
l2oo *bindings.L2OutputOracleCaller
} }
func NewFactoryHelper(t *testing.T, ctx context.Context, clock *clock.AdvancingClock, client *ethclient.Client, gameDuration uint64) *FactoryHelper { func NewFactoryHelper(t *testing.T, ctx context.Context, deployments *genesis.L1Deployments, client *ethclient.Client) *FactoryHelper {
require := require.New(t) require := require.New(t)
chainID, err := client.ChainID(ctx) chainID, err := client.ChainID(ctx)
require.NoError(err) require.NoError(err)
opts, err := bind.NewKeyedTransactorWithChainID(deployer.TestKey, chainID) opts, err := bind.NewKeyedTransactorWithChainID(deployer.TestKey, chainID)
require.NoError(err) require.NoError(err)
factory, l1Head := deployDisputeGameContracts(require, ctx, clock, client, opts, gameDuration) require.NotNil(deployments, "No deployments")
factory, err := bindings.NewDisputeGameFactory(deployments.DisputeGameFactoryProxy, client)
require.NoError(err)
blockOracle, err := bindings.NewBlockOracle(deployments.BlockOracle, client)
require.NoError(err)
l2oo, err := bindings.NewL2OutputOracleCaller(deployments.L2OutputOracleProxy, client)
require.NoError(err, "Error creating l2oo caller")
//factory, l1Head := deployDisputeGameContracts(require, ctx, clock, client, opts, gameDuration)
return &FactoryHelper{ return &FactoryHelper{
t: t, t: t,
require: require, require: require,
client: client, client: client,
opts: opts, opts: opts,
factory: factory, factory: factory,
l1Head: l1Head, blockOracle: blockOracle,
l2oo: l2oo,
} }
} }
func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet string) *FaultGameHelper { func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet string) *FaultGameHelper {
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) // Wait for two output proposals to be published
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel() defer cancel()
trace := alphabet.NewTraceProvider(claimedAlphabet, 4) err := utils.WaitFor(ctx, time.Second, func() (bool, error) {
index, err := h.l2oo.LatestOutputIndex(&bind.CallOpts{Context: ctx})
if err != nil {
h.t.Logf("Could not get latest output index: %v", err.Error())
return false, nil
}
h.t.Logf("Latest output index: %v", index)
return index.Cmp(big.NewInt(1)) >= 0, nil
})
h.require.NoError(err, "Did not get two output roots")
ctx, cancel = context.WithTimeout(ctx, 1*time.Minute)
defer cancel()
// Store the current block in the oracle
tx, err := h.blockOracle.Checkpoint(h.opts)
h.require.NoError(err)
r, err := utils.WaitReceiptOK(ctx, h.client, tx.Hash())
h.require.NoError(err, "failed to store block in blockoracle")
l1Head := new(big.Int).Sub(r.BlockNumber, big.NewInt(1))
trace := alphabet.NewTraceProvider(claimedAlphabet, alphabetGameDepth)
rootClaim, err := trace.Get(ctx, lastAlphabetTraceIndex) rootClaim, err := trace.Get(ctx, lastAlphabetTraceIndex)
h.require.NoError(err, "get root claim") h.require.NoError(err, "get root claim")
extraData := make([]byte, 64) extraData := make([]byte, 64)
binary.BigEndian.PutUint64(extraData[24:], uint64(8)) binary.BigEndian.PutUint64(extraData[24:], uint64(8))
binary.BigEndian.PutUint64(extraData[56:], h.l1Head.Uint64()) binary.BigEndian.PutUint64(extraData[56:], l1Head.Uint64())
tx, err := h.factory.Create(h.opts, faultGameType, rootClaim, extraData) tx, err = h.factory.Create(h.opts, faultGameType, rootClaim, extraData)
h.require.NoError(err, "create fault dispute game") h.require.NoError(err, "create fault dispute game")
rcpt, err := utils.WaitReceiptOK(ctx, h.client, tx.Hash()) rcpt, err := utils.WaitReceiptOK(ctx, h.client, tx.Hash())
h.require.NoError(err, "wait for create fault dispute game receipt to be OK") h.require.NoError(err, "wait for create fault dispute game receipt to be OK")
...@@ -128,6 +169,12 @@ func (g *FaultGameHelper) StartChallenger(ctx context.Context, l1Endpoint string ...@@ -128,6 +169,12 @@ func (g *FaultGameHelper) StartChallenger(ctx context.Context, l1Endpoint string
return c return c
} }
func (g *FaultGameHelper) GameDuration(ctx context.Context) time.Duration {
duration, err := g.game.GAMEDURATION(&bind.CallOpts{Context: ctx})
g.require.NoError(err, "failed to get game duration")
return time.Duration(duration) * time.Second
}
func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) { func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) {
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel() defer cancel()
...@@ -190,6 +237,7 @@ func (g *FaultGameHelper) Resolve(ctx context.Context) { ...@@ -190,6 +237,7 @@ func (g *FaultGameHelper) Resolve(ctx context.Context) {
} }
func (g *FaultGameHelper) WaitForGameStatus(ctx context.Context, expected Status) { func (g *FaultGameHelper) WaitForGameStatus(ctx context.Context, expected Status) {
g.t.Logf("Waiting for game %v to have status %v", g.addr, expected)
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel() defer cancel()
err := utils.WaitFor(ctx, 1*time.Second, func() (bool, error) { err := utils.WaitFor(ctx, 1*time.Second, func() (bool, error) {
...@@ -199,7 +247,7 @@ func (g *FaultGameHelper) WaitForGameStatus(ctx context.Context, expected Status ...@@ -199,7 +247,7 @@ func (g *FaultGameHelper) WaitForGameStatus(ctx context.Context, expected Status
if err != nil { if err != nil {
return false, fmt.Errorf("game status unavailable: %w", err) return false, fmt.Errorf("game status unavailable: %w", err)
} }
g.t.Logf("Game %v has state %v, waiting for state %v", g.addr, Status(status), expected)
return expected == Status(status), nil return expected == Status(status), nil
}) })
g.require.NoError(err, "wait for game status") g.require.NoError(err, "wait for game status")
......
...@@ -3,7 +3,6 @@ package op_e2e ...@@ -3,7 +3,6 @@ package op_e2e
import ( import (
"context" "context"
"testing" "testing"
"time"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
...@@ -17,13 +16,13 @@ func TestResolveDisputeGame(t *testing.T) { ...@@ -17,13 +16,13 @@ func TestResolveDisputeGame(t *testing.T) {
InitParallel(t) InitParallel(t)
ctx := context.Background() ctx := context.Background()
sys, l1Client := startL1OnlySystem(t) sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close) t.Cleanup(sys.Close)
gameDuration := 24 * time.Hour disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys.cfg.L1Deployments, l1Client)
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys.TimeTravelClock, l1Client, uint64(gameDuration.Seconds()))
game := disputeGameFactory.StartAlphabetGame(ctx, "zyxwvut") game := disputeGameFactory.StartAlphabetGame(ctx, "zyxwvut")
require.NotNil(t, game) require.NotNil(t, game)
gameDuration := game.GameDuration(ctx)
game.WaitForGameStatus(ctx, disputegame.StatusInProgress) game.WaitForGameStatus(ctx, disputegame.StatusInProgress)
...@@ -115,13 +114,13 @@ func TestChallengerCompleteDisputeGame(t *testing.T) { ...@@ -115,13 +114,13 @@ func TestChallengerCompleteDisputeGame(t *testing.T) {
InitParallel(t) InitParallel(t)
ctx := context.Background() ctx := context.Background()
sys, l1Client := startL1OnlySystem(t) sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close) t.Cleanup(sys.Close)
gameDuration := 24 * time.Hour disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys.cfg.L1Deployments, l1Client)
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys.TimeTravelClock, l1Client, uint64(gameDuration.Seconds()))
game := disputeGameFactory.StartAlphabetGame(ctx, test.rootClaimAlphabet) game := disputeGameFactory.StartAlphabetGame(ctx, test.rootClaimAlphabet)
require.NotNil(t, game) require.NotNil(t, game)
gameDuration := game.GameDuration(ctx)
game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "Defender", func(c *config.Config) { game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "Defender", func(c *config.Config) {
c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Mallory) c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Mallory)
...@@ -144,13 +143,13 @@ func TestChallengerCompleteDisputeGame(t *testing.T) { ...@@ -144,13 +143,13 @@ func TestChallengerCompleteDisputeGame(t *testing.T) {
} }
} }
func startL1OnlySystem(t *testing.T) (*System, *ethclient.Client) { func startFaultDisputeSystem(t *testing.T) (*System, *ethclient.Client) {
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L1BlockTime = 1
delete(cfg.Nodes, "verifier") delete(cfg.Nodes, "verifier")
delete(cfg.Nodes, "sequencer")
cfg.SupportL1TimeTravel = true cfg.SupportL1TimeTravel = true
cfg.DeployConfig.L2OutputOracleSubmissionInterval = 2
cfg.NonFinalizedProposals = true // Submit output proposals asap
sys, err := cfg.Start() sys, err := cfg.Start()
require.Nil(t, err, "Error starting up system") require.NoError(t, err, "Error starting up system")
return sys, sys.Clients["l1"] return sys, sys.Clients["l1"]
} }
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
"eip1559Elasticity": 6, "eip1559Elasticity": 6,
"l1GenesisBlockTimestamp": "0x64c811bf", "l1GenesisBlockTimestamp": "0x64c811bf",
"l2GenesisRegolithTimeOffset": "0x0", "l2GenesisRegolithTimeOffset": "0x0",
"faultGameAbsolutePrestate": 96, "faultGameAbsolutePrestate": "0x41c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98",
"faultGameMaxDepth": 4, "faultGameMaxDepth": 4,
"faultGameMaxDuration": 120 "faultGameMaxDuration": 300
} }
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