Commit d4051e33 authored by protolambda's avatar protolambda Committed by clabby

cannon,op-challenger,dispute-game: VM Status prefix byte in claim hash

parent cf040a8c
......@@ -9,7 +9,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
......@@ -331,12 +330,12 @@ func Run(ctx *cli.Context) error {
}
if proofAt(state) {
preStateHash := crypto.Keccak256Hash(state.EncodeWitness())
preStateHash := state.EncodeWitness().StateHash()
witness, err := stepFn(true)
if err != nil {
return fmt.Errorf("failed at proof-gen step %d (PC: %08x): %w", step, state.PC, err)
}
postStateHash := crypto.Keccak256Hash(state.EncodeWitness())
postStateHash := state.EncodeWitness().StateHash()
proof := &Proof{
Step: step,
Pre: preStateHash,
......
......@@ -5,7 +5,6 @@ import (
"os"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/urfave/cli/v2"
)
......@@ -31,7 +30,7 @@ func Witness(ctx *cli.Context) error {
return fmt.Errorf("invalid input state (%v): %w", input, err)
}
witness := state.EncodeWitness()
h := crypto.Keccak256Hash(witness)
h := witness.StateHash()
if output != "" {
if err := os.WriteFile(output, witness, 0755); err != nil {
return fmt.Errorf("writing output to %v: %w", output, err)
......
......@@ -5,6 +5,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
)
type State struct {
......@@ -37,7 +38,11 @@ type State struct {
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
}
func (s *State) EncodeWitness() []byte {
func (s *State) VMStatus() uint8 {
return vmStatus(s.Exited, s.ExitCode)
}
func (s *State) EncodeWitness() StateWitness {
out := make([]byte, 0)
memRoot := s.Memory.MerkleRoot()
out = append(out, memRoot[:]...)
......@@ -60,3 +65,37 @@ func (s *State) EncodeWitness() []byte {
}
return out
}
type StateWitness []byte
const (
VMStatusValid = 0
VMStatusInvalid = 1
VMStatusPanic = 2
VMStatusUnfinished = 3
)
func (sw StateWitness) StateHash() common.Hash {
hash := crypto.Keccak256Hash(sw)
offset := 32*2 + 4*6
exited := sw[offset]
exitCode := sw[offset+1]
status := vmStatus(exited == 1, exitCode)
hash[0] = status
return hash
}
func vmStatus(exited bool, exitCode uint8) uint8 {
if exited {
switch exitCode {
case 0:
return VMStatusValid
case 1:
return VMStatusInvalid
default:
return VMStatusPanic
}
} else {
return VMStatusUnfinished
}
}
package fault
import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"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/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
// Service provides a clean interface for the challenger to interact
// with the fault package.
type Service interface {
// MonitorGame monitors the fault dispute game and attempts to progress it.
MonitorGame(context.Context) error
}
type service struct {
agent *Agent
agreeWithProposedOutput bool
caller *FaultCaller
logger log.Logger
}
// NewService creates a new Service.
func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*service, error) {
txMgr, err := txmgr.NewSimpleTxManager("challenger", logger, &metrics.NoopTxMetrics{}, cfg.TxMgrConfig)
if err != nil {
return nil, fmt.Errorf("failed to create the transaction manager: %w", err)
}
client, err := client.DialEthClientWithTimeout(client.DefaultDialTimeout, logger, cfg.L1EthRpc)
if err != nil {
return nil, fmt.Errorf("failed to dial L1: %w", err)
}
contract, err := bindings.NewFaultDisputeGameCaller(cfg.GameAddress, client)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault dispute game contract: %w", err)
}
loader := NewLoader(contract)
gameDepth, err := loader.FetchGameDepth(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch the game depth: %w", err)
}
gameDepth = uint64(gameDepth)
var trace types.TraceProvider
var updater types.OracleUpdater
switch cfg.TraceType {
case config.TraceTypeCannon:
trace, err = cannon.NewTraceProvider(ctx, logger, cfg, client)
if err != nil {
return nil, fmt.Errorf("create cannon trace provider: %w", err)
}
updater, err = cannon.NewOracleUpdater(ctx, logger, txMgr, cfg.GameAddress, client)
if err != nil {
return nil, fmt.Errorf("failed to create the cannon updater: %w", err)
}
case config.TraceTypeAlphabet:
trace = alphabet.NewTraceProvider(cfg.AlphabetTrace, gameDepth)
updater = alphabet.NewOracleUpdater(logger)
default:
return nil, fmt.Errorf("unsupported trace type: %v", cfg.TraceType)
}
return newTypedService(ctx, logger, cfg, loader, gameDepth, client, trace, updater, txMgr)
}
// newTypedService creates a new Service from a provided trace provider.
func newTypedService(ctx context.Context, logger log.Logger, cfg *config.Config, loader Loader, gameDepth uint64, client *ethclient.Client, provider types.TraceProvider, updater types.OracleUpdater, txMgr txmgr.TxManager) (*service, error) {
if err := ValidateAbsolutePrestate(ctx, provider, loader); err != nil {
return nil, fmt.Errorf("failed to validate absolute prestate: %w", err)
}
gameLogger := logger.New("game", cfg.GameAddress)
responder, err := NewFaultResponder(gameLogger, txMgr, cfg.GameAddress)
if err != nil {
return nil, fmt.Errorf("failed to create the responder: %w", err)
}
caller, err := NewFaultCallerFromBindings(cfg.GameAddress, client, gameLogger)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault contract: %w", err)
}
return &service{
agent: NewAgent(loader, int(gameDepth), provider, responder, updater, cfg.AgreeWithProposedOutput, gameLogger),
agreeWithProposedOutput: cfg.AgreeWithProposedOutput,
caller: caller,
logger: gameLogger,
}, nil
}
// ValidateAbsolutePrestate validates the absolute prestate of the fault game.
func ValidateAbsolutePrestate(ctx context.Context, trace types.TraceProvider, loader Loader) error {
providerPrestate, err := trace.AbsolutePreState(ctx)
if err != nil {
return fmt.Errorf("failed to get the trace provider's absolute prestate: %w", err)
}
providerPrestateHash, err := trace.StateHash(ctx, providerPrestate)
if err != nil {
return fmt.Errorf("failed to compute state-hash: %w", err)
}
onchainPrestateHash, err := loader.FetchAbsolutePrestateHash(ctx)
if err != nil {
return fmt.Errorf("failed to get the onchain absolute prestate: %w", err)
}
if providerPrestateHash != onchainPrestateHash {
return fmt.Errorf("trace provider's absolute prestate does not match onchain absolute prestate")
}
return nil
}
// MonitorGame monitors the fault dispute game and attempts to progress it.
func (s *service) MonitorGame(ctx context.Context) error {
return MonitorGame(ctx, s.logger, s.agreeWithProposedOutput, s.agent, s.caller)
}
package fault
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
)
var (
mockTraceProviderError = fmt.Errorf("mock trace provider error")
mockLoaderError = fmt.Errorf("mock loader error")
)
// TestValidateAbsolutePrestate tests that the absolute prestate is validated
// correctly by the service component.
func TestValidateAbsolutePrestate(t *testing.T) {
t.Run("ValidPrestates", func(t *testing.T) {
prestate := []byte{0x00, 0x01, 0x02, 0x03}
prestateHash := mockStateHash(prestate)
mockTraceProvider := newMockTraceProvider(false, prestate)
mockLoader := newMockLoader(false, prestateHash)
err := ValidateAbsolutePrestate(context.Background(), mockTraceProvider, mockLoader)
require.NoError(t, err)
})
t.Run("TraceProviderErrors", func(t *testing.T) {
prestate := []byte{0x00, 0x01, 0x02, 0x03}
mockTraceProvider := newMockTraceProvider(true, prestate)
mockLoader := newMockLoader(false, mockStateHash(prestate))
err := ValidateAbsolutePrestate(context.Background(), mockTraceProvider, mockLoader)
require.ErrorIs(t, err, mockTraceProviderError)
})
t.Run("LoaderErrors", func(t *testing.T) {
prestate := []byte{0x00, 0x01, 0x02, 0x03}
mockTraceProvider := newMockTraceProvider(false, prestate)
mockLoader := newMockLoader(true, mockStateHash(prestate))
err := ValidateAbsolutePrestate(context.Background(), mockTraceProvider, mockLoader)
require.ErrorIs(t, err, mockLoaderError)
})
t.Run("PrestateMismatch", func(t *testing.T) {
mockTraceProvider := newMockTraceProvider(false, []byte{0x00, 0x01, 0x02, 0x03})
mockLoader := newMockLoader(false, common.Hash{0x00})
err := ValidateAbsolutePrestate(context.Background(), mockTraceProvider, mockLoader)
require.Error(t, err)
})
}
type mockTraceProvider struct {
prestateErrors bool
prestate []byte
}
func newMockTraceProvider(prestateErrors bool, prestate []byte) *mockTraceProvider {
return &mockTraceProvider{
prestateErrors: prestateErrors,
prestate: prestate,
}
}
func (m *mockTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, error) {
panic("not implemented")
}
func (m *mockTraceProvider) GetStepData(ctx context.Context, i uint64) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) {
panic("not implemented")
}
func (m *mockTraceProvider) AbsolutePreState(ctx context.Context) ([]byte, error) {
if m.prestateErrors {
return nil, mockTraceProviderError
}
return m.prestate, nil
}
func mockStateHash(state []byte) common.Hash {
h := crypto.Keccak256Hash(state)
h[0] = types.VMStatusValid
return h
}
func (m *mockTraceProvider) StateHash(ctx context.Context, state []byte) (common.Hash, error) {
return mockStateHash(state), nil
}
type mockLoader struct {
prestateError bool
prestateHash common.Hash
}
func newMockLoader(prestateError bool, prestateHash common.Hash) *mockLoader {
return &mockLoader{
prestateError: prestateError,
prestateHash: prestateHash,
}
}
func (m *mockLoader) FetchClaims(ctx context.Context) ([]types.Claim, error) {
panic("not implemented")
}
func (m *mockLoader) FetchGameDepth(ctx context.Context) (uint64, error) {
panic("not implemented")
}
func (m *mockLoader) FetchAbsolutePrestateHash(ctx context.Context) (common.Hash, error) {
if m.prestateError {
return common.Hash{}, mockLoaderError
}
return m.prestateHash, nil
}
......@@ -138,16 +138,15 @@ func (l *loader) FetchClaims(ctx context.Context) ([]types.Claim, error) {
}
// FetchAbsolutePrestateHash fetches the hashed absolute prestate from the fault dispute game.
func (l *loader) FetchAbsolutePrestateHash(ctx context.Context) ([]byte, error) {
func (l *loader) FetchAbsolutePrestateHash(ctx context.Context) (common.Hash, error) {
callOpts := bind.CallOpts{
Context: ctx,
}
absolutePrestate, err := l.caller.ABSOLUTEPRESTATE(&callOpts)
if err != nil {
return nil, err
return common.Hash{}, err
}
returnValue := absolutePrestate[:]
return returnValue, nil
return absolutePrestate, nil
}
......@@ -9,6 +9,8 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
)
var (
......@@ -58,7 +60,7 @@ func (ap *AlphabetTraceProvider) Get(ctx context.Context, i uint64) (common.Hash
if err != nil {
return common.Hash{}, err
}
return crypto.Keccak256Hash(claimBytes), nil
return alphabetStateHash(claimBytes), nil
}
// AbsolutePreState returns the absolute pre-state for the alphabet trace.
......@@ -66,11 +68,31 @@ func (ap *AlphabetTraceProvider) AbsolutePreState(ctx context.Context) ([]byte,
return common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000060"), nil
}
func (ap *AlphabetTraceProvider) StateHash(ctx context.Context, state []byte) (common.Hash, error) {
return alphabetStateHash(state), nil
}
// BuildAlphabetPreimage constructs the claim bytes for the index and state item.
func BuildAlphabetPreimage(i uint64, letter string) []byte {
return append(IndexToBytes(i), LetterToBytes(letter)...)
}
const maxAlphabet = 26
func alphabetStateHash(state []byte) common.Hash {
h := crypto.Keccak256Hash(state)
// instead of the state containing an "exited" boolean, we just check if the index reached the end
i := new(big.Int).SetBytes(state[:32])
if !i.IsUint64() || i.Uint64() > maxAlphabet {
h[0] = types.VMStatusPanic // this state should never be reached, if we increment by 1 per step
} else if i.Uint64() == maxAlphabet {
h[0] = types.VMStatusValid
} else {
h[0] = types.VMStatusUnfinished
}
return h
}
// IndexToBytes converts an index to a byte slice big endian
func IndexToBytes(i uint64) []byte {
big := new(big.Int)
......
......@@ -6,12 +6,11 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
)
func alphabetClaim(index uint64, letter string) common.Hash {
return crypto.Keccak256Hash(BuildAlphabetPreimage(index, letter))
return alphabetStateHash(BuildAlphabetPreimage(index, letter))
}
// TestAlphabetProvider_Get_ClaimsByTraceIndex tests the [fault.AlphabetProvider] Get function.
......@@ -60,7 +59,7 @@ func FuzzIndexToBytes(f *testing.F) {
// returns the correct pre-image for a index.
func TestGetStepData_Succeeds(t *testing.T) {
ap := NewTraceProvider("abc", 2)
expected := BuildAlphabetPreimage(0, "a'")
expected := BuildAlphabetPreimage(0, "a")
retrieved, proof, data, err := ap.GetStepData(context.Background(), uint64(1))
require.NoError(t, err)
require.Equal(t, expected, retrieved)
......
......@@ -15,9 +15,13 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
)
const (
......@@ -25,7 +29,7 @@ const (
)
type proofData struct {
ClaimValue hexutil.Bytes `json:"post"`
ClaimValue common.Hash `json:"post"`
StateData hexutil.Bytes `json:"state-data"`
ProofData hexutil.Bytes `json:"proof-data"`
OracleKey hexutil.Bytes `json:"oracle-key,omitempty"`
......@@ -86,7 +90,7 @@ func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, e
if err != nil {
return common.Hash{}, err
}
value := common.BytesToHash(proof.ClaimValue)
value := proof.ClaimValue
if value == (common.Hash{}) {
return common.Hash{}, errors.New("proof missing post hash")
......@@ -152,8 +156,8 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa
// No execution is done, so no proof-data or oracle values are required.
witness := state.EncodeWitness()
proof := &proofData{
ClaimValue: crypto.Keccak256(witness),
StateData: witness,
ClaimValue: witness.StateHash(),
StateData: hexutil.Bytes(witness),
ProofData: []byte{},
OracleKey: nil,
OracleValue: nil,
......@@ -177,3 +181,7 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa
}
return &proof, nil
}
func (p *CannonTraceProvider) StateHash(ctx context.Context, state []byte) (common.Hash, error) {
return mipsevm.StateWitness(state).StateHash(), nil
}
......@@ -15,7 +15,6 @@ import (
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
......@@ -43,7 +42,9 @@ func TestGet(t *testing.T) {
value, err := provider.Get(context.Background(), 7000)
require.NoError(t, err)
require.Contains(t, generator.generated, 7000, "should have tried to generate the proof")
require.Equal(t, crypto.Keccak256Hash(generator.finalState.EncodeWitness()), value)
stateHash, err := provider.StateHash(context.Background(), generator.finalState.EncodeWitness())
require.NoError(t, err)
require.Equal(t, stateHash, value)
})
t.Run("MissingPostHash", func(t *testing.T) {
......@@ -86,7 +87,7 @@ func TestGetStepData(t *testing.T) {
Exited: true,
}
generator.proof = &proofData{
ClaimValue: common.Hash{0xaa}.Bytes(),
ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
OracleKey: common.Hash{0xdd}.Bytes(),
......@@ -111,7 +112,7 @@ func TestGetStepData(t *testing.T) {
Exited: true,
}
generator.proof = &proofData{
ClaimValue: common.Hash{0xaa}.Bytes(),
ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
OracleKey: common.Hash{0xdd}.Bytes(),
......@@ -185,7 +186,7 @@ func TestAbsolutePreState(t *testing.T) {
Step: 0,
Registers: [32]uint32{},
}
require.Equal(t, state.EncodeWitness(), preState)
require.Equal(t, []byte(state.EncodeWitness()), preState)
})
}
......
......@@ -105,8 +105,18 @@ type TraceProvider interface {
// AbsolutePreState is the pre-image value of the trace that transitions to the trace value at index 0
AbsolutePreState(ctx context.Context) (preimage []byte, err error)
// StateHash computes the state-hash of the given state, or returns an error if the state is invalid.
StateHash(ctx context.Context, state []byte) (common.Hash, error)
}
const (
VMStatusValid = 0
VMStatusInvalid = 1
VMStatusPanic = 2
VMStatusUnfinished = 3
)
// ClaimData is the core of a claim. It must be unique inside a specific game.
type ClaimData struct {
Value common.Hash
......
......@@ -103,7 +103,9 @@ contract MIPS {
from, to := copyMem(from, to, 4) // lo
from, to := copyMem(from, to, 4) // hi
from, to := copyMem(from, to, 4) // heap
let exitCode := shr(248, mload(from))
from, to := copyMem(from, to, 1) // exitCode
let exited := shr(248, mload(from))
from, to := copyMem(from, to, 1) // exited
from, to := copyMem(from, to, 8) // step
from := add(from, 32) // offset to registers
......@@ -117,8 +119,20 @@ contract MIPS {
// Log the resulting MIPS state, for debugging
log0(start, sub(to, start))
function vmStatus(_exited, _exitCode) -> status_ {
switch _exited
case 1 {
switch _exitCode
case 0 { status_ := 0 } // VMStatusValid
case 1 { status_ := 1 } // VMStatusInvalid
default { status_ := 2 } // VMStatusPanic
} default { status_ := 3 } // VMStatusUnfinished
}
let status := vmStatus(exited, exitCode)
// Compute the hash of the resulting MIPS state
out_ := keccak256(start, sub(to, start))
out_ := or(shl(248, status), and(not(shl(248, 0xff)), out_))
}
}
......
......@@ -91,6 +91,10 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
// If there is no implementation to clone for the given `GameType`, revert.
if (address(impl) == address(0)) revert NoImplementation(_gameType);
// The VMStatus must indicate (1) 'invalid', to argue that disputed thing is invalid.
// Games that agree with the existing outcome are not allowed.
if (uint8(Claim.unwrap(_rootClaim)[0]) != 1) revert UnexpectedRootClaim(_rootClaim);
// Clone the implementation contract and initialize it with the given parameters.
proxy_ = IDisputeGame(address(impl).clone(abi.encodePacked(_rootClaim, _extraData)));
proxy_.initialize();
......
......@@ -15,6 +15,11 @@ error NoImplementation(GameType gameType);
/// @param uuid The UUID of the dispute game that already exists.
error GameAlreadyExists(Hash uuid);
/// @notice Thrown when the root claim has an unexpected VM status.
/// Some games can only start with a root-claim with a specific status.
/// @param rootClaim is the claim that was unexpected.
error UnexpectedRootClaim(Claim rootClaim);
////////////////////////////////////////////////////////////////
// `FaultDisputeGame` Errors //
////////////////////////////////////////////////////////////////
......
......@@ -36,11 +36,19 @@ contract DisputeGameFactory_Init is L2OutputOracle_Initializer {
}
contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init {
function changeClaimStatus(Claim claim, uint8 status) public pure returns (Claim _out) {
bytes32 hash = Claim.unwrap(claim);
hash = bytes32((uint256(hash) & (~(uint256(0xff) << 248))) | (uint256(status) << 248));
return Claim.wrap(hash);
}
/// @dev Tests that the `create` function succeeds when creating a new dispute game
/// with a `GameType` that has an implementation set.
function testFuzz_create_succeeds(uint8 gameType, Claim rootClaim, bytes calldata extraData) public {
// Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values.
GameType gt = GameType.wrap(uint8(bound(gameType, 0, 2)));
// Ensure the rootClaim has a VMStatus that disagrees with the validity.
rootClaim = changeClaimStatus(rootClaim, 1);
// Set all three implementations to the same `FakeClone` contract.
for (uint8 i; i < 3; i++) {
......@@ -68,15 +76,30 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init {
function testFuzz_create_noImpl_reverts(uint8 gameType, Claim rootClaim, bytes calldata extraData) public {
// Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values.
GameType gt = GameType.wrap(uint8(bound(gameType, 0, 2)));
// Ensure the rootClaim has a VMStatus that disagrees with the validity.
rootClaim = changeClaimStatus(rootClaim, 1);
vm.expectRevert(abi.encodeWithSelector(NoImplementation.selector, gt));
factory.create(gt, rootClaim, extraData);
}
/// @dev Tests that the `create` function reverts when the rootClaim does not disagree with the outcome.
function testFuzz_create_badRootStatus_reverts(uint8 gameType, Claim rootClaim, bytes calldata extraData) public {
// Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values.
GameType gt = GameType.wrap(uint8(bound(gameType, 0, 2)));
// Ensure the root claim does not have the correct VM status
if (uint8(Claim.unwrap(rootClaim)[0]) == 1) rootClaim = changeClaimStatus(rootClaim, 0);
vm.expectRevert(abi.encodeWithSelector(UnexpectedRootClaim.selector, rootClaim));
factory.create(gt, rootClaim, extraData);
}
/// @dev Tests that the `create` function reverts when there exists a dispute game with the same UUID.
function testFuzz_create_sameUUID_reverts(uint8 gameType, Claim rootClaim, bytes calldata extraData) public {
// Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values.
GameType gt = GameType.wrap(uint8(bound(gameType, 0, 2)));
// Ensure the rootClaim has a VMStatus that disagrees with the validity.
rootClaim = changeClaimStatus(rootClaim, 1);
// Set all three implementations to the same `FakeClone` contract.
for (uint8 i; i < 3; i++) {
......
......@@ -77,7 +77,7 @@ contract FaultDisputeGame_Init is DisputeGameFactory_Init {
contract FaultDisputeGame_Test is FaultDisputeGame_Init {
/// @dev The root claim of the game.
Claim internal constant ROOT_CLAIM = Claim.wrap(bytes32(uint256(10)));
Claim internal constant ROOT_CLAIM = Claim.wrap(bytes32((uint256(1) << 248) + uint256(10)));
/// @dev The absolute prestate of the trace.
Claim internal constant ABSOLUTE_PRESTATE = Claim.wrap(bytes32(uint256(0)));
......
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