Commit 5e23d3a7 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

e2e: Convert more fault proof tests to use the custom contract bindings. (#10716)

parent f638bfa2
......@@ -355,7 +355,7 @@ func (f *FaultDisputeGameContractLatest) getDelayedWETH(ctx context.Context, blo
func (f *FaultDisputeGameContractLatest) GetOracle(ctx context.Context) (*PreimageOracleContract, error) {
defer f.metrics.StartContractRequest("GetOracle")()
vm, err := f.vm(ctx)
vm, err := f.Vm(ctx)
if err != nil {
return nil, err
}
......@@ -458,7 +458,7 @@ func (f *FaultDisputeGameContractLatest) IsResolved(ctx context.Context, block r
return resolved, nil
}
func (f *FaultDisputeGameContractLatest) vm(ctx context.Context) (*VMContract, error) {
func (f *FaultDisputeGameContractLatest) Vm(ctx context.Context) (*VMContract, error) {
result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodVM))
if err != nil {
return nil, fmt.Errorf("failed to fetch VM addr: %w", err)
......@@ -623,4 +623,5 @@ type FaultDisputeGameContract interface {
ResolveClaimTx(claimIdx uint64) (txmgr.TxCandidate, error)
CallResolve(ctx context.Context) (gameTypes.GameStatus, error)
ResolveTx() (txmgr.TxCandidate, error)
Vm(ctx context.Context) (*VMContract, error)
}
......@@ -29,6 +29,10 @@ func NewVMContract(addr common.Address, caller *batching.MultiCaller) *VMContrac
}
}
func (c *VMContract) Addr() common.Address {
return c.contract.Addr()
}
func (c *VMContract) Oracle(ctx context.Context) (*PreimageOracleContract, error) {
results, err := c.multiCaller.SingleCall(ctx, rpcblock.Latest, c.contract.Call(methodOracle))
if err != nil {
......
......@@ -2,6 +2,7 @@ package disputegame
import (
"context"
"crypto/ecdsa"
"encoding/binary"
"math/big"
"testing"
......@@ -88,6 +89,7 @@ type FactoryHelper struct {
System DisputeSystem
Client *ethclient.Client
Opts *bind.TransactOpts
PrivKey *ecdsa.PrivateKey
FactoryAddr common.Address
Factory *bindings.DisputeGameFactory
}
......@@ -97,7 +99,8 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, system DisputeSystem) *
client := system.NodeClient("l1")
chainID, err := client.ChainID(ctx)
require.NoError(err)
opts, err := bind.NewKeyedTransactorWithChainID(TestKey, chainID)
privKey := TestKey
opts, err := bind.NewKeyedTransactorWithChainID(privKey, chainID)
require.NoError(err)
l1Deployments := system.L1Deployments()
......@@ -111,6 +114,7 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, system DisputeSystem) *
System: system,
Client: client,
Opts: opts,
PrivKey: privKey,
Factory: factory,
FactoryAddr: factoryAddr,
}
......@@ -167,8 +171,6 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string
h.Require.Len(rcpt.Logs, 2, "should have emitted a single DisputeGameCreated event")
createdEvent, err := h.Factory.ParseDisputeGameCreated(*rcpt.Logs[1])
h.Require.NoError(err)
gameBindings, err := bindings.NewFaultDisputeGame(createdEvent.DisputeProxy, h.Client)
h.Require.NoError(err)
game, err := contracts.NewFaultDisputeGameContract(ctx, metrics.NoopContractMetrics, createdEvent.DisputeProxy, batching.NewMultiCaller(h.Client.Client(), batching.DefaultBatchSize))
h.Require.NoError(err)
......@@ -182,7 +184,7 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string
provider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock)
return &OutputCannonGameHelper{
OutputGameHelper: *NewOutputGameHelper(h.T, h.Require, h.Client, h.Opts, game, gameBindings, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System),
OutputGameHelper: *NewOutputGameHelper(h.T, h.Require, h.Client, h.Opts, h.PrivKey, game, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System),
}
}
......@@ -223,8 +225,6 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri
h.Require.Len(rcpt.Logs, 2, "should have emitted a single DisputeGameCreated event")
createdEvent, err := h.Factory.ParseDisputeGameCreated(*rcpt.Logs[1])
h.Require.NoError(err)
gameBindings, err := bindings.NewFaultDisputeGame(createdEvent.DisputeProxy, h.Client)
h.Require.NoError(err)
game, err := contracts.NewFaultDisputeGameContract(ctx, metrics.NoopContractMetrics, createdEvent.DisputeProxy, batching.NewMultiCaller(h.Client.Client(), batching.DefaultBatchSize))
h.Require.NoError(err)
......@@ -238,7 +238,7 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri
provider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock)
return &OutputAlphabetGameHelper{
OutputGameHelper: *NewOutputGameHelper(h.T, h.Require, h.Client, h.Opts, game, gameBindings, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System),
OutputGameHelper: *NewOutputGameHelper(h.T, h.Require, h.Client, h.Opts, h.PrivKey, game, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System),
}
}
......
......@@ -18,12 +18,12 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-e2e/bindings"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
......@@ -233,19 +233,14 @@ func (g *OutputCannonGameHelper) VerifyPreimage(ctx context.Context, outputRootC
g.Require.NotNil(oracleData, "Should have had required preimage oracle data")
g.Require.Equal(common.Hash(preimageKey.PreimageKey()).Bytes(), oracleData.OracleKey, "Must have correct preimage key")
tx, err := g.GameBindings.AddLocalData(g.Opts,
oracleData.GetIdent(),
big.NewInt(outputRootClaim.Index),
new(big.Int).SetUint64(uint64(oracleData.OracleOffset)))
g.Require.NoError(err)
_, err = wait.ForReceiptOK(ctx, g.Client, tx.Hash())
g.Require.NoError(err)
candidate, err := g.Game.UpdateOracleTx(ctx, uint64(outputRootClaim.Index), oracleData)
g.Require.NoError(err, "failed to get oracle")
transactions.RequireSendTx(g.T, ctx, g.Client, candidate, g.PrivKey)
expectedPostState, err := provider.Get(ctx, pos)
g.Require.NoError(err, "Failed to get expected post state")
callOpts := &bind.CallOpts{Context: ctx}
vmAddr, err := g.GameBindings.Vm(callOpts)
vm, err := g.Game.Vm(ctx)
g.Require.NoError(err, "Failed to get VM address")
abi, err := bindings.MIPSMetaData.GetAbi()
......@@ -253,7 +248,7 @@ func (g *OutputCannonGameHelper) VerifyPreimage(ctx context.Context, outputRootC
caller := batching.NewMultiCaller(g.Client.Client(), batching.DefaultBatchSize)
result, err := caller.SingleCall(ctx, rpcblock.Latest, &batching.ContractCall{
Abi: abi,
Addr: vmAddr,
Addr: vm.Addr(),
Method: "step",
Args: []interface{}{
prestate, proof, localContext,
......
......@@ -3,6 +3,7 @@ package disputegame
import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"math/big"
"testing"
......@@ -15,6 +16,7 @@ import (
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-e2e/bindings"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-service/eth"
......@@ -33,23 +35,23 @@ type OutputGameHelper struct {
Require *require.Assertions
Client *ethclient.Client
Opts *bind.TransactOpts
PrivKey *ecdsa.PrivateKey
Game contracts.FaultDisputeGameContract
GameBindings *bindings.FaultDisputeGame
FactoryAddr common.Address
Addr common.Address
CorrectOutputProvider *outputs.OutputTraceProvider
System DisputeSystem
}
func NewOutputGameHelper(t *testing.T, require *require.Assertions, client *ethclient.Client, opts *bind.TransactOpts,
game contracts.FaultDisputeGameContract, gameBindings *bindings.FaultDisputeGame, factoryAddr common.Address, addr common.Address, correctOutputProvider *outputs.OutputTraceProvider, system DisputeSystem) *OutputGameHelper {
func NewOutputGameHelper(t *testing.T, require *require.Assertions, client *ethclient.Client, opts *bind.TransactOpts, privKey *ecdsa.PrivateKey,
game contracts.FaultDisputeGameContract, factoryAddr common.Address, addr common.Address, correctOutputProvider *outputs.OutputTraceProvider, system DisputeSystem) *OutputGameHelper {
return &OutputGameHelper{
T: t,
Require: require,
Client: client,
Opts: opts,
PrivKey: privKey,
Game: game,
GameBindings: gameBindings,
FactoryAddr: factoryAddr,
Addr: addr,
CorrectOutputProvider: correctOutputProvider,
......@@ -203,22 +205,13 @@ func (g *OutputGameHelper) AvailableCredit(ctx context.Context, addr common.Addr
}
func (g *OutputGameHelper) CreditUnlockDuration(ctx context.Context) time.Duration {
weth, err := g.GameBindings.Weth(&bind.CallOpts{Context: ctx})
g.Require.NoError(err, "Failed to get WETH contract")
contract, err := bindings.NewDelayedWETH(weth, g.Client)
g.Require.NoError(err)
period, err := contract.Delay(&bind.CallOpts{Context: ctx})
g.Require.NoError(err, "Failed to get WETH unlock period")
float, _ := period.Float64()
return time.Duration(float) * time.Second
_, delay, _, err := g.Game.GetBalanceAndDelay(ctx, rpcblock.Latest)
g.Require.NoError(err, "Failed to get withdrawal delay")
return delay
}
func (g *OutputGameHelper) WethBalance(ctx context.Context, addr common.Address) *big.Int {
weth, err := g.GameBindings.Weth(&bind.CallOpts{Context: ctx})
g.Require.NoError(err, "Failed to get WETH contract")
contract, err := bindings.NewDelayedWETH(weth, g.Client)
g.Require.NoError(err)
balance, err := contract.BalanceOf(&bind.CallOpts{Context: ctx}, addr)
balance, _, _, err := g.Game.GetBalanceAndDelay(ctx, rpcblock.Latest)
g.Require.NoError(err, "Failed to get WETH balance")
return balance
}
......@@ -363,10 +356,9 @@ func (g *OutputGameHelper) WaitForAllClaimsCountered(ctx context.Context) {
func (g *OutputGameHelper) Resolve(ctx context.Context) {
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
tx, err := g.GameBindings.Resolve(g.Opts)
g.Require.NoError(err)
_, err = wait.ForReceiptOK(ctx, g.Client, tx.Hash())
candidate, err := g.Game.ResolveTx()
g.Require.NoError(err)
transactions.RequireSendTx(g.T, ctx, g.Client, candidate, g.PrivKey)
}
func (g *OutputGameHelper) Status(ctx context.Context) gameTypes.GameStatus {
......@@ -545,11 +537,10 @@ func (g *OutputGameHelper) Attack(ctx context.Context, claimIdx int64, claim com
claimData, err := g.Game.GetClaim(ctx, uint64(claimIdx))
g.Require.NoError(err, "Failed to get claim data")
attackPos := claimData.Position.Attack()
transactOpts := g.makeBondedTransactOpts(ctx, claimData.Position.Attack().ToGIndex(), cfg.Opts)
err = g.sendMove(ctx, func() (*gethtypes.Transaction, error) {
return g.GameBindings.Attack(transactOpts, claimData.Value, big.NewInt(claimIdx), claim)
})
candidate, err := g.Game.AttackTx(ctx, claimData, claim)
g.Require.NoError(err, "Failed to create tx candidate")
_, _, err = transactions.SendTx(ctx, g.Client, candidate, g.PrivKey)
if err != nil {
if cfg.ignoreDupes && g.hasClaim(ctx, claimIdx, attackPos, claim) {
return
......@@ -565,11 +556,10 @@ func (g *OutputGameHelper) Defend(ctx context.Context, claimIdx int64, claim com
claimData, err := g.Game.GetClaim(ctx, uint64(claimIdx))
g.Require.NoError(err, "Failed to get claim data")
defendPos := claimData.Position.Defend()
transactOpts := g.makeBondedTransactOpts(ctx, defendPos.ToGIndex(), cfg.Opts)
err = g.sendMove(ctx, func() (*gethtypes.Transaction, error) {
return g.GameBindings.Defend(transactOpts, claimData.Value, big.NewInt(claimIdx), claim)
})
candidate, err := g.Game.DefendTx(ctx, claimData, claim)
g.Require.NoError(err, "Failed to create tx candidate")
_, _, err = transactions.SendTx(ctx, g.Client, candidate, g.PrivKey)
if err != nil {
if cfg.ignoreDupes && g.hasClaim(ctx, claimIdx, defendPos, claim) {
return
......@@ -588,45 +578,27 @@ func (g *OutputGameHelper) hasClaim(ctx context.Context, parentIdx int64, pos ty
return false
}
func (g *OutputGameHelper) sendMove(ctx context.Context, send func() (*gethtypes.Transaction, error)) error {
tx, err := send()
if err != nil {
return fmt.Errorf("transaction did not send: %w", err)
}
_, err = wait.ForReceiptOK(ctx, g.Client, tx.Hash())
if err != nil {
return fmt.Errorf("transaction was not ok: %w", err)
}
return nil
}
func (g *OutputGameHelper) makeBondedTransactOpts(ctx context.Context, pos *big.Int, Opts *bind.TransactOpts) *bind.TransactOpts {
bOpts := *Opts
bond, err := g.GameBindings.GetRequiredBond(&bind.CallOpts{Context: ctx}, pos)
g.Require.NoError(err, "Failed to get required bond")
bOpts.Value = bond
return &bOpts
}
type ErrWithData interface {
ErrorData() interface{}
}
// StepFails attempts to call step and verifies that it fails with ValidStep()
func (g *OutputGameHelper) StepFails(claimIdx int64, isAttack bool, stateData []byte, proof []byte) {
func (g *OutputGameHelper) StepFails(ctx context.Context, claimIdx int64, isAttack bool, stateData []byte, proof []byte) {
g.T.Logf("Attempting step against claim %v isAttack: %v", claimIdx, isAttack)
_, err := g.GameBindings.Step(g.Opts, big.NewInt(claimIdx), isAttack, stateData, proof)
errData, ok := err.(ErrWithData)
candidate, err := g.Game.StepTx(uint64(claimIdx), isAttack, stateData, proof)
g.Require.NoError(err, "Failed to create tx candidate")
_, _, err = transactions.SendTx(ctx, g.Client, candidate, g.PrivKey, transactions.WithReceiptFail())
var errData ErrWithData
ok := errors.As(err, &errData)
g.Require.Truef(ok, "Error should provide ErrorData method: %v", err)
g.Require.Equal("0xfb4e40dd", errData.ErrorData(), "Revert reason should be abi encoded ValidStep()")
}
// ResolveClaim resolves a single subgame
func (g *OutputGameHelper) ResolveClaim(ctx context.Context, claimIdx int64) {
tx, err := g.GameBindings.ResolveClaim(g.Opts, big.NewInt(claimIdx), common.Big0)
g.Require.NoError(err, "ResolveClaim transaction did not send")
_, err = wait.ForReceiptOK(ctx, g.Client, tx.Hash())
g.Require.NoError(err, "ResolveClaim transaction was not OK")
candidate, err := g.Game.ResolveClaimTx(uint64(claimIdx))
g.Require.NoError(err, "Failed to create resolve claim candidate tx")
transactions.RequireSendTx(g.T, ctx, g.Client, candidate, g.PrivKey)
}
// ChallengePeriod returns the challenge period fetched from the PreimageOracle contract.
......
......@@ -101,7 +101,7 @@ func (h *OutputHonestHelper) StepFails(ctx context.Context, claimIdx int64, isAt
}
prestate, proofData, _, err := h.correctTrace.GetStepData(ctx, game, claim, pos)
h.require.NoError(err, "Get step data")
h.game.StepFails(claimIdx, isAttack, prestate, proofData)
h.game.StepFails(ctx, claimIdx, isAttack, prestate, proofData)
}
func (h *OutputHonestHelper) loadState(ctx context.Context, claimIdx int64) (types.Game, types.Claim) {
......
package transactions
import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
type SendTxOpt func(cfg *sendTxCfg)
type ErrWithData interface {
ErrorData() interface{}
}
type sendTxCfg struct {
receiptStatus uint64
}
func makeSendTxCfg(opts ...SendTxOpt) *sendTxCfg {
cfg := &sendTxCfg{
receiptStatus: types.ReceiptStatusSuccessful,
}
for _, opt := range opts {
opt(cfg)
}
return cfg
}
func WithReceiptFail() SendTxOpt {
return func(cfg *sendTxCfg) {
cfg.receiptStatus = types.ReceiptStatusFailed
}
}
func RequireSendTx(t *testing.T, ctx context.Context, client *ethclient.Client, candidate txmgr.TxCandidate, privKey *ecdsa.PrivateKey, opts ...SendTxOpt) {
_, _, err := SendTx(ctx, client, candidate, privKey, opts...)
require.NoError(t, err, "Failed to send transaction")
}
func SendTx(ctx context.Context, client *ethclient.Client, candidate txmgr.TxCandidate, privKey *ecdsa.PrivateKey, opts ...SendTxOpt) (*types.Transaction, *types.Receipt, error) {
cfg := makeSendTxCfg(opts...)
from := crypto.PubkeyToAddress(privKey.PublicKey)
chainID, err := client.ChainID(ctx)
if err != nil {
return nil, nil, fmt.Errorf("failed to get chain ID: %w", err)
}
nonce, err := client.PendingNonceAt(ctx, from)
if err != nil {
return nil, nil, fmt.Errorf("failed to get next nonce: %w", err)
}
latestBlock, err := client.HeaderByNumber(ctx, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to get latest block: %w", err)
}
gasFeeCap := new(big.Int).Mul(latestBlock.BaseFee, big.NewInt(3))
gasTipCap := big.NewInt(1 * params.GWei)
if gasFeeCap.Cmp(gasTipCap) < 0 {
// gasTipCap can't be higher than gasFeeCap
// Since there's a minimum gasTipCap to be accepted, increase the gasFeeCap. Extra will be refunded anyway.
gasFeeCap = gasTipCap
}
msg := ethereum.CallMsg{
From: from,
To: candidate.To,
Value: candidate.Value,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Data: candidate.TxData,
}
gas, err := client.EstimateGas(ctx, msg)
if err != nil {
var errWithData ErrWithData
if errors.As(err, &errWithData) {
return nil, nil, fmt.Errorf("failed to estimate gas. errdata: %v err: %w", errWithData.ErrorData(), err)
}
return nil, nil, fmt.Errorf("failed to estimate gas: %w", err)
}
tx := types.MustSignNewTx(privKey, types.LatestSignerForChainID(chainID), &types.DynamicFeeTx{
ChainID: chainID,
Nonce: nonce,
To: candidate.To,
Value: candidate.Value,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Data: candidate.TxData,
Gas: gas,
})
err = client.SendTransaction(ctx, tx)
if err != nil {
return nil, nil, fmt.Errorf("failed to send transaction: %w", err)
}
receipt, err := wait.ForReceipt(ctx, client, tx.Hash(), cfg.receiptStatus)
if err != nil {
return nil, nil, fmt.Errorf("failed to find OK receipt: %w", err)
}
return tx, receipt, nil
}
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