diff --git a/cannon/cmd/load_elf.go b/cannon/cmd/load_elf.go index a5ee43d13986e..7188449983c49 100644 --- a/cannon/cmd/load_elf.go +++ b/cannon/cmd/load_elf.go @@ -60,8 +60,7 @@ func LoadELF(ctx *cli.Context) error { if err != nil { return err } - switch ver { - case versions.GetCurrentSingleThreaded(): + if versions.IsSupportedSingleThreaded(ver) { createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) { return program.LoadELF(f, singlethreaded.CreateInitialState) } @@ -72,11 +71,11 @@ func LoadELF(ctx *cli.Context) error { } return program.PatchStack(state) } - case versions.GetCurrentMultiThreaded(), versions.GetCurrentMultiThreaded64(): + } else if versions.IsSupportedMultiThreaded(ver) || versions.IsSupportedMultiThreaded64(ver) { createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) { return program.LoadELF(f, multithreaded.CreateInitialState) } - default: + } else { return fmt.Errorf("unsupported state version: %d (%s)", ver, ver.String()) } @@ -97,7 +96,7 @@ func LoadELF(ctx *cli.Context) error { } // Ensure the state is written with appropriate version information - versionedState, err := versions.NewFromState(state) + versionedState, err := versions.NewFromState(ver, state) if err != nil { return fmt.Errorf("failed to create versioned state: %w", err) } diff --git a/cannon/mipsevm/iface.go b/cannon/mipsevm/iface.go index 4b1ab091976bd..ed65960e570be 100644 --- a/cannon/mipsevm/iface.go +++ b/cannon/mipsevm/iface.go @@ -58,7 +58,7 @@ type FPVMState interface { EncodeWitness() (witness []byte, hash common.Hash) // CreateVM creates a FPVM that can operate on this state. - CreateVM(logger log.Logger, po PreimageOracle, stdOut, stdErr io.Writer, meta Metadata) FPVM + CreateVM(logger log.Logger, po PreimageOracle, stdOut, stdErr io.Writer, meta Metadata, features FeatureToggles) FPVM } type SymbolMatcher func(addr arch.Word) bool @@ -68,6 +68,14 @@ type Metadata interface { CreateSymbolMatcher(name string) SymbolMatcher } +// FeatureToggles defines the set of features which are enabled only on some of the supported state versions. +// This allows supporting multiple state versions concurrently without needing to create completely separate +// FPVM implementations and duplicate a lot of code. +// Toggles here are temporary and should be removed once the newer state version is deployed widely. The older +// version can then be supported via multicannon pulling in a specific build and support for it dropped in latest code. +type FeatureToggles struct { +} + type FPVM interface { // GetState returns the current state of the VM. The FPVMState is updated by successive calls to Step GetState() FPVMState diff --git a/cannon/mipsevm/multithreaded/instrumented.go b/cannon/mipsevm/multithreaded/instrumented.go index 5295d74811616..73138925569ea 100644 --- a/cannon/mipsevm/multithreaded/instrumented.go +++ b/cannon/mipsevm/multithreaded/instrumented.go @@ -24,11 +24,12 @@ type InstrumentedState struct { preimageOracle *exec.TrackingPreimageOracleReader meta mipsevm.Metadata + features mipsevm.FeatureToggles } var _ mipsevm.FPVM = (*InstrumentedState)(nil) -func NewInstrumentedState(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, meta mipsevm.Metadata) *InstrumentedState { +func NewInstrumentedState(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, meta mipsevm.Metadata, features mipsevm.FeatureToggles) *InstrumentedState { return &InstrumentedState{ state: state, log: log, @@ -39,6 +40,7 @@ func NewInstrumentedState(state *State, po mipsevm.PreimageOracle, stdOut, stdEr statsTracker: NoopStatsTracker(), preimageOracle: exec.NewTrackingPreimageOracleReader(po), meta: meta, + features: features, } } diff --git a/cannon/mipsevm/multithreaded/instrumented_test.go b/cannon/mipsevm/multithreaded/instrumented_test.go index 659a5b7a5c290..0e650ec250203 100644 --- a/cannon/mipsevm/multithreaded/instrumented_test.go +++ b/cannon/mipsevm/multithreaded/instrumented_test.go @@ -4,6 +4,7 @@ import ( "bytes" "io" "os" + "reflect" "testing" "github.com/ethereum/go-ethereum/log" @@ -16,7 +17,7 @@ import ( ) func vmFactory(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, meta *program.Metadata) mipsevm.FPVM { - return NewInstrumentedState(state, po, stdOut, stdErr, log, meta) + return NewInstrumentedState(state, po, stdOut, stdErr, log, meta, allFeaturesEnabled()) } func TestInstrumentedState_OpenMips(t *testing.T) { @@ -53,7 +54,7 @@ func TestInstrumentedState_UtilsCheck(t *testing.T) { oracle := testutil.StaticOracle(t, []byte{}) var stdOutBuf, stdErrBuf bytes.Buffer - us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), meta) + us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), meta, allFeaturesEnabled()) for i := 0; i < 1_000_000; i++ { if us.GetState().GetExited() { @@ -186,7 +187,7 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) { oracle := testutil.StaticOracle(t, []byte{}) var stdOutBuf, stdErrBuf bytes.Buffer - us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), meta) + us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), meta, allFeaturesEnabled()) for i := 0; i < test.steps; i++ { if us.GetState().GetExited() { @@ -231,7 +232,7 @@ func TestInstrumentedState_Alloc(t *testing.T) { state, meta := testutil.LoadELFProgram(t, testutil.ProgramPath("alloc"), CreateInitialState, false) oracle := testutil.AllocOracle(t, test.numAllocs, test.allocSize) - us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, testutil.CreateLogger(), meta) + us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, testutil.CreateLogger(), meta, allFeaturesEnabled()) require.NoError(t, us.InitDebug()) // emulation shouldn't take more than 20 B steps for i := 0; i < 20_000_000_000; i++ { @@ -252,3 +253,15 @@ func TestInstrumentedState_Alloc(t *testing.T) { }) } } + +// allFeaturesEnabled returns a FeatureToggles with all toggles enabled. +// We can't use versions.FeaturesForVersion to test the specific toggles for each version because it creates a +// dependency loop, so we just cover the everything enabled case as that should be the upcoming version. +func allFeaturesEnabled() mipsevm.FeatureToggles { + toggles := mipsevm.FeatureToggles{} + tRef := reflect.ValueOf(toggles) + for i := 0; i < tRef.NumField(); i++ { + tRef.Field(i).Set(reflect.ValueOf(true)) + } + return toggles +} diff --git a/cannon/mipsevm/multithreaded/state.go b/cannon/mipsevm/multithreaded/state.go index f2b4e8278fc10..35ca34d1bc077 100644 --- a/cannon/mipsevm/multithreaded/state.go +++ b/cannon/mipsevm/multithreaded/state.go @@ -103,9 +103,9 @@ func CreateInitialState(pc, heapStart Word) *State { return state } -func (s *State) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata) mipsevm.FPVM { +func (s *State) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata, features mipsevm.FeatureToggles) mipsevm.FPVM { logger.Info("Using cannon multithreaded VM", "is32", arch.IsMips32) - return NewInstrumentedState(s, po, stdOut, stdErr, logger, meta) + return NewInstrumentedState(s, po, stdOut, stdErr, logger, meta, features) } func (s *State) GetCurrentThread() *ThreadState { diff --git a/cannon/mipsevm/program/testutil/mocks.go b/cannon/mipsevm/program/testutil/mocks.go index 484527710aa52..6a74288884007 100644 --- a/cannon/mipsevm/program/testutil/mocks.go +++ b/cannon/mipsevm/program/testutil/mocks.go @@ -125,6 +125,6 @@ func (m MockFPVMState) EncodeWitness() (witness []byte, hash common.Hash) { panic("not implemented") } -func (m MockFPVMState) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata) mipsevm.FPVM { +func (m MockFPVMState) CreateVM(_ log.Logger, _ mipsevm.PreimageOracle, _, _ io.Writer, _ mipsevm.Metadata, _ mipsevm.FeatureToggles) mipsevm.FPVM { panic("not implemented") } diff --git a/cannon/mipsevm/singlethreaded/state.go b/cannon/mipsevm/singlethreaded/state.go index 71e664cbbc163..c32f7a3a4f9eb 100644 --- a/cannon/mipsevm/singlethreaded/state.go +++ b/cannon/mipsevm/singlethreaded/state.go @@ -70,7 +70,7 @@ func CreateInitialState(pc, heapStart Word) *State { return state } -func (s *State) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata) mipsevm.FPVM { +func (s *State) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata, _ mipsevm.FeatureToggles) mipsevm.FPVM { return NewInstrumentedState(s, po, stdOut, stdErr, meta) } diff --git a/cannon/mipsevm/tests/evm_common64_test.go b/cannon/mipsevm/tests/evm_common64_test.go index de994c6ff00f6..86ba78a063059 100644 --- a/cannon/mipsevm/tests/evm_common64_test.go +++ b/cannon/mipsevm/tests/evm_common64_test.go @@ -144,36 +144,38 @@ func TestEVM_SingleStep_Shift64(t *testing.T) { {name: "dsra32", funct: 0x3f, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x7F_FF_FF_FF_FF_FF_FF_FF), sa: 31, expectRes: Word(0x0)}, // dsra32 t8, s2, 1 } - v := GetMultiThreadedTestCase(t) for i, tt := range cases { - testName := fmt.Sprintf("%v %v", v.Name, tt.name) - t.Run(testName, func(t *testing.T) { - pc := Word(0x0) - goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(pc)) - state := goVm.GetState() - var insn uint32 - var rtReg uint32 - var rdReg uint32 - rtReg = 18 - rdReg = 8 - insn = rtReg<<16 | rdReg<<11 | tt.sa<<6 | tt.funct - state.GetRegistersRef()[rdReg] = tt.rd - state.GetRegistersRef()[rtReg] = tt.rt - testutil.StoreInstruction(state.GetMemory(), pc, insn) - step := state.GetStep() - - // Setup expectations - expected := testutil.NewExpectedState(state) - expected.ExpectStep() - expected.Registers[rdReg] = tt.expectRes - - stepWitness, err := goVm.Step(true) - require.NoError(t, err) - - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) - }) + for _, v := range GetMultiThreadedTestCases(t) { + v := v + testName := fmt.Sprintf("%v %v", v.Name, tt.name) + t.Run(testName, func(t *testing.T) { + pc := Word(0x0) + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(pc)) + state := goVm.GetState() + var insn uint32 + var rtReg uint32 + var rdReg uint32 + rtReg = 18 + rdReg = 8 + insn = rtReg<<16 | rdReg<<11 | tt.sa<<6 | tt.funct + state.GetRegistersRef()[rdReg] = tt.rd + state.GetRegistersRef()[rtReg] = tt.rt + testutil.StoreInstruction(state.GetMemory(), pc, insn) + step := state.GetStep() + + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.ExpectStep() + expected.Registers[rdReg] = tt.expectRes + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + }) + } } } diff --git a/cannon/mipsevm/tests/evm_common_test.go b/cannon/mipsevm/tests/evm_common_test.go index a8a06f9acc7ce..af0aa192409de 100644 --- a/cannon/mipsevm/tests/evm_common_test.go +++ b/cannon/mipsevm/tests/evm_common_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" "github.com/ethereum/go-ethereum/core/tracing" "github.com/stretchr/testify/require" @@ -27,21 +28,13 @@ func TestEVM_OpenMIPS(t *testing.T) { require.NoError(t, err) cases := GetMipsVersionTestCases(t) - skippedTests := map[string][]string{ - "multi-threaded": {"clone.bin"}, - "single-threaded": {}, - } for _, c := range cases { - skipped, exists := skippedTests[c.Name] - require.True(t, exists) for _, f := range testFiles { testName := fmt.Sprintf("%v (%v)", f.Name(), c.Name) t.Run(testName, func(t *testing.T) { - for _, skipped := range skipped { - if f.Name() == skipped { - t.Skipf("Skipping explicitly excluded open_mips testcase: %v", f.Name()) - } + if !versions.IsSupportedSingleThreaded(c.Version) && f.Name() == "clone.bin" { + t.Skipf("%v only supported for singlethreaded VMs", f.Name()) } oracle := testutil.SelectOracleFixture(t, f.Name()) diff --git a/cannon/mipsevm/tests/evm_multithreaded64_test.go b/cannon/mipsevm/tests/evm_multithreaded64_test.go index 4f39c79b7ade7..713393f1aac33 100644 --- a/cannon/mipsevm/tests/evm_multithreaded64_test.go +++ b/cannon/mipsevm/tests/evm_multithreaded64_test.go @@ -463,7 +463,11 @@ var NoopSyscalls64 = map[string]uint32{ } func TestEVM_NoopSyscall64(t *testing.T) { - testNoopSyscall(t, NoopSyscalls64) + t.Parallel() + for _, version := range GetMultiThreadedTestCases(t) { + noOpCalls := maps.Clone(NoopSyscalls64) + testNoopSyscall(t, version, noOpCalls) + } } func TestEVM_UnsupportedSyscall64(t *testing.T) { @@ -480,7 +484,10 @@ func TestEVM_UnsupportedSyscall64(t *testing.T) { unsupportedSyscalls = append(unsupportedSyscalls, candidate) } - testUnsupportedSyscall(t, unsupportedSyscalls) + for _, version := range GetMultiThreadedTestCases(t) { + unsupported := unsupportedSyscalls + testUnsupportedSyscall(t, version, unsupported) + } } // Asserts the undefined syscall handling on cannon64 triggers a VM panic @@ -492,16 +499,18 @@ func TestEVM_UndefinedSyscall(t *testing.T) { "SysLlseek": arch.SysLlseek, } for name, val := range undefinedSyscalls { - t.Run(name, func(t *testing.T) { - t.Parallel() - goVm, state, contracts := setup(t, int(val), nil) - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = Word(val) // Set syscall number - proofData := multiThreadedProofGenerator(t, state) - - require.Panics(t, func() { _, _ = goVm.Step(true) }) - errorMessage := "unimplemented syscall" - testutil.AssertEVMReverts(t, state, contracts, nil, proofData, testutil.CreateErrorStringMatcher(errorMessage)) - }) + for _, version := range GetMultiThreadedTestCases(t) { + t.Run(fmt.Sprintf("%v-%v", version.Name, name), func(t *testing.T) { + t.Parallel() + goVm, state, contracts := setupWithTestCase(t, version, int(val), nil) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = Word(val) // Set syscall number + proofData := multiThreadedProofGenerator(t, state) + + require.Panics(t, func() { _, _ = goVm.Step(true) }) + errorMessage := "unimplemented syscall" + testutil.AssertEVMReverts(t, state, contracts, nil, proofData, testutil.CreateErrorStringMatcher(errorMessage)) + }) + } } } diff --git a/cannon/mipsevm/tests/evm_multithreaded_test.go b/cannon/mipsevm/tests/evm_multithreaded_test.go index 220de42a91740..a3c0999d35c37 100644 --- a/cannon/mipsevm/tests/evm_multithreaded_test.go +++ b/cannon/mipsevm/tests/evm_multithreaded_test.go @@ -8,6 +8,7 @@ import ( "slices" "testing" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" "github.com/ethereum/go-ethereum/core/tracing" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" @@ -252,35 +253,39 @@ func TestEVM_SysClone_FlagHandling(t *testing.T) { } for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - state := multithreaded.CreateEmptyState() - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = arch.SysClone // Set syscall number - state.GetRegistersRef()[4] = c.flags // Set first argument - curStep := state.Step + c := c + for _, version := range GetMultiThreadedTestCases(t) { + version := version + t.Run(fmt.Sprintf("%v-%v", version.Name, c.name), func(t *testing.T) { + state := multithreaded.CreateEmptyState() + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = arch.SysClone // Set syscall number + state.GetRegistersRef()[4] = c.flags // Set first argument + curStep := state.Step - var err error - var stepWitness *mipsevm.StepWitness - goVm := multithreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil, nil) - if !c.valid { - // The VM should exit - stepWitness, err = goVm.Step(true) - require.NoError(t, err) - require.Equal(t, curStep+1, state.GetStep()) - require.Equal(t, true, goVm.GetState().GetExited()) - require.Equal(t, uint8(mipsevm.VMStatusPanic), goVm.GetState().GetExitCode()) - require.Equal(t, 1, state.ThreadCount()) - } else { - stepWitness, err = goVm.Step(true) - require.NoError(t, err) - require.Equal(t, curStep+1, state.GetStep()) - require.Equal(t, false, goVm.GetState().GetExited()) - require.Equal(t, uint8(0), goVm.GetState().GetExitCode()) - require.Equal(t, 2, state.ThreadCount()) - } + var err error + var stepWitness *mipsevm.StepWitness + goVm := multithreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil, nil, versions.FeaturesForVersion(version.Version)) + if !c.valid { + // The VM should exit + stepWitness, err = goVm.Step(true) + require.NoError(t, err) + require.Equal(t, curStep+1, state.GetStep()) + require.Equal(t, true, goVm.GetState().GetExited()) + require.Equal(t, uint8(mipsevm.VMStatusPanic), goVm.GetState().GetExitCode()) + require.Equal(t, 1, state.ThreadCount()) + } else { + stepWitness, err = goVm.Step(true) + require.NoError(t, err) + require.Equal(t, curStep+1, state.GetStep()) + require.Equal(t, false, goVm.GetState().GetExited()) + require.Equal(t, uint8(0), goVm.GetState().GetExitCode()) + require.Equal(t, 2, state.ThreadCount()) + } - testutil.ValidateEVM(t, stepWitness, curStep, goVm, multithreaded.GetStateHashFn(), contracts) - }) + testutil.ValidateEVM(t, stepWitness, curStep, goVm, multithreaded.GetStateHashFn(), contracts) + }) + } } } @@ -947,7 +952,7 @@ var NoopSyscalls = map[string]uint32{ func TestEVM_NoopSyscall32(t *testing.T) { testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_NoopSyscall64") - testNoopSyscall(t, NoopSyscalls) + testNoopSyscall(t, GetMultiThreadedTestCase(t, versions.VersionMultiThreaded), NoopSyscalls) } func TestEVM_UnsupportedSyscall32(t *testing.T) { @@ -965,7 +970,7 @@ func TestEVM_UnsupportedSyscall32(t *testing.T) { unsupportedSyscalls = append(unsupportedSyscalls, candidate) } - testUnsupportedSyscall(t, unsupportedSyscalls) + testUnsupportedSyscall(t, GetMultiThreadedTestCase(t, versions.VersionMultiThreaded), unsupportedSyscalls) } func TestEVM_EmptyThreadStacks(t *testing.T) { @@ -1095,11 +1100,15 @@ func TestEVM_SchedQuantumThreshold(t *testing.T) { } func setup(t require.TestingT, randomSeed int, preimageOracle mipsevm.PreimageOracle, opts ...testutil.StateOption) (mipsevm.FPVM, *multithreaded.State, *testutil.ContractMetadata) { - v := GetMultiThreadedTestCase(t) + cases := GetMultiThreadedTestCases(t) + require.NotZero(t, len(cases), "must have at least one supported multithreaded test case") + // Use the most recent supported version + return setupWithTestCase(t, cases[len(cases)-1], randomSeed, preimageOracle, opts...) +} + +func setupWithTestCase(t require.TestingT, v VersionedVMTestCase, randomSeed int, preimageOracle mipsevm.PreimageOracle, opts ...testutil.StateOption) (mipsevm.FPVM, *multithreaded.State, *testutil.ContractMetadata) { allOpts := append([]testutil.StateOption{testutil.WithRandomization(int64(randomSeed))}, opts...) vm := v.VMFactory(preimageOracle, os.Stdout, os.Stderr, testutil.CreateLogger(), allOpts...) state := mttestutil.GetMtState(t, vm) - return vm, state, v.Contracts - } diff --git a/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go b/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go index 20a628f4e34ec..7b3d9d2756bb7 100644 --- a/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go +++ b/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go @@ -15,8 +15,10 @@ import ( ) func FuzzStateSyscallCloneMT(f *testing.F) { - v := GetMultiThreadedTestCase(f) - f.Fuzz(func(t *testing.T, nextThreadId, stackPtr Word, seed int64) { + versions := GetMultiThreadedTestCases(f) + require.NotZero(f, len(versions), "must have at least one multithreaded version supported") + f.Fuzz(func(t *testing.T, nextThreadId, stackPtr Word, seed int64, version int) { + v := versions[version%len(versions)] goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(seed)) state := mttestutil.GetMtState(t, goVm) // Update existing threads to avoid collision with nextThreadId diff --git a/cannon/mipsevm/tests/helpers.go b/cannon/mipsevm/tests/helpers.go index c4f857f34b832..7c81a70f76834 100644 --- a/cannon/mipsevm/tests/helpers.go +++ b/cannon/mipsevm/tests/helpers.go @@ -3,6 +3,7 @@ package tests import ( "io" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" @@ -26,13 +27,13 @@ func singleThreadedVmFactory(po mipsevm.PreimageOracle, stdOut, stdErr io.Writer return singlethreaded.NewInstrumentedState(state, po, stdOut, stdErr, nil) } -func multiThreadedVmFactory(po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, opts ...testutil.StateOption) mipsevm.FPVM { +func multiThreadedVmFactory(po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, features mipsevm.FeatureToggles, opts ...testutil.StateOption) mipsevm.FPVM { state := multithreaded.CreateEmptyState() mutator := mttestutil.NewStateMutatorMultiThreaded(state) for _, opt := range opts { opt(mutator) } - return multithreaded.NewInstrumentedState(state, po, stdOut, stdErr, log, nil) + return multithreaded.NewInstrumentedState(state, po, stdOut, stdErr, log, nil, features) } type ElfVMFactory func(t require.TestingT, elfFile string, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger) mipsevm.FPVM @@ -44,9 +45,9 @@ func singleThreadElfVmFactory(t require.TestingT, elfFile string, po mipsevm.Pre return fpvm } -func multiThreadElfVmFactory(t require.TestingT, elfFile string, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger) mipsevm.FPVM { +func multiThreadElfVmFactory(t require.TestingT, elfFile string, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, features mipsevm.FeatureToggles) mipsevm.FPVM { state, meta := testutil.LoadELFProgram(t, elfFile, multithreaded.CreateInitialState, false) - fpvm := multithreaded.NewInstrumentedState(state, po, stdOut, stdErr, log, meta) + fpvm := multithreaded.NewInstrumentedState(state, po, stdOut, stdErr, log, meta, features) require.NoError(t, fpvm.InitDebug()) return fpvm } @@ -92,6 +93,7 @@ type VersionedVMTestCase struct { VMFactory VMFactory ElfVMFactory ElfVMFactory ProofGenerator ProofGenerator + Version versions.StateVersion } func GetSingleThreadedTestCase(t require.TestingT) VersionedVMTestCase { @@ -102,32 +104,46 @@ func GetSingleThreadedTestCase(t require.TestingT) VersionedVMTestCase { VMFactory: singleThreadedVmFactory, ElfVMFactory: singleThreadElfVmFactory, ProofGenerator: singleThreadedProofGenerator, + Version: versions.VersionSingleThreaded2, } } -func GetMultiThreadedTestCase(t require.TestingT) VersionedVMTestCase { +func GetMultiThreadedTestCase(t require.TestingT, version versions.StateVersion) VersionedVMTestCase { + features := versions.FeaturesForVersion(version) return VersionedVMTestCase{ - Name: "multi-threaded", - Contracts: testutil.TestContractsSetup(t, testutil.MipsMultithreaded), - StateHashFn: multithreaded.GetStateHashFn(), - VMFactory: multiThreadedVmFactory, - ElfVMFactory: multiThreadElfVmFactory, + Name: version.String(), + Contracts: testutil.TestContractsSetup(t, testutil.MipsMultithreaded), + StateHashFn: multithreaded.GetStateHashFn(), + VMFactory: func(po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, opts ...testutil.StateOption) mipsevm.FPVM { + return multiThreadedVmFactory(po, stdOut, stdErr, log, features, opts...) + }, + ElfVMFactory: func(t require.TestingT, elfFile string, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger) mipsevm.FPVM { + return multiThreadElfVmFactory(t, elfFile, po, stdOut, stdErr, log, features) + }, ProofGenerator: multiThreadedProofGenerator, + Version: version, } } -func GetMipsVersionTestCases(t require.TestingT) []VersionedVMTestCase { - if arch.IsMips32 { - return []VersionedVMTestCase{ - GetSingleThreadedTestCase(t), - GetMultiThreadedTestCase(t), +func GetMultiThreadedTestCases(t require.TestingT) []VersionedVMTestCase { + var cases []VersionedVMTestCase + for _, version := range versions.StateVersionTypes { + if arch.IsMips32 && versions.IsSupportedMultiThreaded(version) { + cases = append(cases, GetMultiThreadedTestCase(t, version)) } - } else { - // 64-bit only supports MTCannon - return []VersionedVMTestCase{ - GetMultiThreadedTestCase(t), + if !arch.IsMips32 && versions.IsSupportedMultiThreaded64(version) { + cases = append(cases, GetMultiThreadedTestCase(t, version)) } } + return cases +} + +func GetMipsVersionTestCases(t require.TestingT) []VersionedVMTestCase { + cases := GetMultiThreadedTestCases(t) + if arch.IsMips32 { + cases = append(cases, GetSingleThreadedTestCase(t)) + } + return cases } type threadProofTestcase struct { diff --git a/cannon/mipsevm/tests/testfuncs_test.go b/cannon/mipsevm/tests/testfuncs_test.go index 0087ab445d737..c7d86240b4a4d 100644 --- a/cannon/mipsevm/tests/testfuncs_test.go +++ b/cannon/mipsevm/tests/testfuncs_test.go @@ -170,39 +170,40 @@ func testLoadStore(t *testing.T, cases []loadStoreTestCase) { baseReg := uint32(9) rtReg := uint32(8) - v := GetMultiThreadedTestCase(t) - for i, tt := range cases { - testName := fmt.Sprintf("%v %v", v.Name, tt.name) - t.Run(testName, func(t *testing.T) { - addr := tt.base + Word(tt.imm) - effAddr := arch.AddressMask & addr + for _, v := range GetMultiThreadedTestCases(t) { + for i, tt := range cases { + testName := fmt.Sprintf("%v %v", v.Name, tt.name) + t.Run(testName, func(t *testing.T) { + addr := tt.base + Word(tt.imm) + effAddr := arch.AddressMask & addr - goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(0)) - state := goVm.GetState() + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(0)) + state := goVm.GetState() - insn := tt.opcode<<26 | baseReg<<21 | rtReg<<16 | uint32(tt.imm) - state.GetRegistersRef()[rtReg] = tt.rt - state.GetRegistersRef()[baseReg] = tt.base + insn := tt.opcode<<26 | baseReg<<21 | rtReg<<16 | uint32(tt.imm) + state.GetRegistersRef()[rtReg] = tt.rt + state.GetRegistersRef()[baseReg] = tt.base - testutil.StoreInstruction(state.GetMemory(), 0, insn) - state.GetMemory().SetWord(effAddr, tt.memVal) - step := state.GetStep() + testutil.StoreInstruction(state.GetMemory(), 0, insn) + state.GetMemory().SetWord(effAddr, tt.memVal) + step := state.GetStep() - // Setup expectations - expected := testutil.NewExpectedState(state) - expected.ExpectStep() - if tt.expectMemVal != 0 { - expected.ExpectMemoryWriteWord(effAddr, tt.expectMemVal) - } else { - expected.Registers[rtReg] = tt.expectRes - } - stepWitness, err := goVm.Step(true) - require.NoError(t, err) + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.ExpectStep() + if tt.expectMemVal != 0 { + expected.ExpectMemoryWriteWord(effAddr, tt.expectMemVal) + } else { + expected.Registers[rtReg] = tt.expectRes + } + stepWitness, err := goVm.Step(true) + require.NoError(t, err) - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) - }) + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + }) + } } } @@ -425,11 +426,11 @@ func testMTSysReadPreimage(t *testing.T, preimageValue []byte, cases []testMTSys } } -func testNoopSyscall(t *testing.T, syscalls map[string]uint32) { +func testNoopSyscall(t *testing.T, version VersionedVMTestCase, syscalls map[string]uint32) { for noopName, noopVal := range syscalls { - t.Run(noopName, func(t *testing.T) { + t.Run(fmt.Sprintf("%v-%v", version.Name, noopName), func(t *testing.T) { t.Parallel() - goVm, state, contracts := setup(t, int(noopVal), nil) + goVm, state, contracts := setupWithTestCase(t, version, int(noopVal), nil) testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = Word(noopVal) // Set syscall number @@ -452,14 +453,14 @@ func testNoopSyscall(t *testing.T, syscalls map[string]uint32) { } } -func testUnsupportedSyscall(t *testing.T, unsupportedSyscalls []uint32) { +func testUnsupportedSyscall(t *testing.T, version VersionedVMTestCase, unsupportedSyscalls []uint32) { for i, syscallNum := range unsupportedSyscalls { - testName := fmt.Sprintf("Unsupported syscallNum %v", syscallNum) + testName := fmt.Sprintf("%v Unsupported syscallNum %v", version.Name, syscallNum) i := i syscallNum := syscallNum t.Run(testName, func(t *testing.T) { t.Parallel() - goVm, state, contracts := setup(t, i*3434, nil) + goVm, state, contracts := setupWithTestCase(t, version, i*3434, nil) // Setup basic getThreadId syscall instruction testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = Word(syscallNum) diff --git a/cannon/mipsevm/versions/detect_test.go b/cannon/mipsevm/versions/detect_test.go index efdc650e9f02b..b3bd18bf3a664 100644 --- a/cannon/mipsevm/versions/detect_test.go +++ b/cannon/mipsevm/versions/detect_test.go @@ -50,31 +50,42 @@ func TestDetectVersion_fromFile(t *testing.T) { // Check that the latest supported versions write new states in a way that is detected correctly func TestDetectVersion_singleThreadedBinary(t *testing.T) { - targetVersion := GetCurrentSingleThreaded() if !arch.IsMips32 { t.Skip("Single-threaded states are not supported for 64-bit VMs") } - - state, err := NewFromState(singlethreaded.CreateEmptyState()) - require.NoError(t, err) - path := writeToFile(t, "state.bin.gz", state) - version, err := DetectVersion(path) - require.NoError(t, err) - require.Equal(t, targetVersion, version) + for _, version := range StateVersionTypes { + version := version + if !IsSupportedSingleThreaded(version) { + continue + } + t.Run(version.String(), func(t *testing.T) { + state, err := NewFromState(version, singlethreaded.CreateEmptyState()) + require.NoError(t, err) + path := writeToFile(t, "state.bin.gz", state) + version, err := DetectVersion(path) + require.NoError(t, err) + require.Equal(t, version, version) + }) + } } func TestDetectVersion_multiThreadedBinary(t *testing.T) { - targetVersion := GetCurrentMultiThreaded() - if !arch.IsMips32 { - targetVersion = GetCurrentMultiThreaded64() + for _, version := range StateVersionTypes { + version := version + if arch.IsMips32 && !IsSupportedMultiThreaded(version) { + continue + } else if !arch.IsMips32 && !IsSupportedMultiThreaded64(version) { + continue + } + t.Run(version.String(), func(t *testing.T) { + state, err := NewFromState(version, multithreaded.CreateEmptyState()) + require.NoError(t, err) + path := writeToFile(t, "state.bin.gz", state) + version, err := DetectVersion(path) + require.NoError(t, err) + require.Equal(t, version, version) + }) } - - state, err := NewFromState(multithreaded.CreateEmptyState()) - require.NoError(t, err) - path := writeToFile(t, "state.bin.gz", state) - version, err := DetectVersion(path) - require.NoError(t, err) - require.Equal(t, targetVersion, version) } func TestDetectVersion_invalid(t *testing.T) { diff --git a/cannon/mipsevm/versions/state.go b/cannon/mipsevm/versions/state.go index bba831bfc3595..2ef3e1396fbd5 100644 --- a/cannon/mipsevm/versions/state.go +++ b/cannon/mipsevm/versions/state.go @@ -10,47 +10,53 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" "github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded" - "github.com/ethereum-optimism/optimism/op-service/jsonutil" "github.com/ethereum-optimism/optimism/op-service/serialize" + "github.com/ethereum/go-ethereum/log" ) var ( ErrUnknownVersion = errors.New("unknown version") + ErrUnsupportedVersion = errors.New("unsupported version") ErrJsonNotSupported = errors.New("json not supported") ErrUnsupportedMipsArch = errors.New("mips architecture is not supported") ) func LoadStateFromFile(path string) (*VersionedState, error) { if !serialize.IsBinaryFile(path) { - // Always use singlethreaded for JSON states - state, err := jsonutil.LoadJSON[singlethreaded.State](path) - if err != nil { - return nil, err - } - return NewFromState(state) + // JSON states are always singlethreaded v1 which is no longer supported + return nil, fmt.Errorf("%w: %s", ErrUnsupportedVersion, VersionSingleThreaded) } return serialize.LoadSerializedBinary[VersionedState](path) } -func NewFromState(state mipsevm.FPVMState) (*VersionedState, error) { +func NewFromState(vers StateVersion, state mipsevm.FPVMState) (*VersionedState, error) { switch state := state.(type) { case *singlethreaded.State: + if !IsSupportedSingleThreaded(vers) { + return nil, fmt.Errorf("%w: %v", ErrUnsupportedVersion, vers) + } if !arch.IsMips32 { return nil, ErrUnsupportedMipsArch } return &VersionedState{ - Version: GetCurrentSingleThreaded(), + Version: vers, FPVMState: state, }, nil case *multithreaded.State: if arch.IsMips32 { + if !IsSupportedMultiThreaded(vers) { + return nil, fmt.Errorf("%w: %v", ErrUnsupportedVersion, vers) + } return &VersionedState{ - Version: GetCurrentMultiThreaded(), + Version: vers, FPVMState: state, }, nil } else { + if !IsSupportedMultiThreaded64(vers) { + return nil, fmt.Errorf("%w: %v", ErrUnsupportedVersion, vers) + } return &VersionedState{ - Version: GetCurrentMultiThreaded64(), + Version: vers, FPVMState: state, }, nil } @@ -66,6 +72,17 @@ type VersionedState struct { mipsevm.FPVMState } +func (s *VersionedState) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata) mipsevm.FPVM { + features := FeaturesForVersion(s.Version) + return s.FPVMState.CreateVM(logger, po, stdOut, stdErr, meta, features) +} + +func FeaturesForVersion(version StateVersion) mipsevm.FeatureToggles { + features := mipsevm.FeatureToggles{} + // Set any required feature toggles based on the state version here. + return features +} + func (s *VersionedState) Serialize(w io.Writer) error { bout := serialize.NewBinaryWriter(w) if err := bout.WriteUInt(s.Version); err != nil { @@ -80,8 +97,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error { return err } - switch s.Version { - case GetCurrentSingleThreaded(): + if IsSupportedSingleThreaded(s.Version) { if !arch.IsMips32 { return ErrUnsupportedMipsArch } @@ -91,7 +107,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error { } s.FPVMState = state return nil - case GetCurrentMultiThreaded(): + } else if IsSupportedMultiThreaded(s.Version) { if !arch.IsMips32 { return ErrUnsupportedMipsArch } @@ -101,7 +117,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error { } s.FPVMState = state return nil - case GetCurrentMultiThreaded64(): + } else if IsSupportedMultiThreaded64(s.Version) { if arch.IsMips32 { return ErrUnsupportedMipsArch } @@ -111,7 +127,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error { } s.FPVMState = state return nil - default: + } else { return fmt.Errorf("%w: %d", ErrUnknownVersion, s.Version) } } diff --git a/cannon/mipsevm/versions/state64_test.go b/cannon/mipsevm/versions/state64_test.go index b8f74d1db0385..afc7e18aa7ea2 100644 --- a/cannon/mipsevm/versions/state64_test.go +++ b/cannon/mipsevm/versions/state64_test.go @@ -7,6 +7,7 @@ import ( "path/filepath" "testing" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded" "github.com/stretchr/testify/require" "github.com/ethereum-optimism/optimism/cannon/mipsevm" @@ -15,37 +16,64 @@ import ( ) func TestNewFromState(t *testing.T) { - t.Run("multithreaded64-latestVersion", func(t *testing.T) { - actual, err := NewFromState(multithreaded.CreateEmptyState()) - require.NoError(t, err) - require.IsType(t, &multithreaded.State{}, actual.FPVMState) - require.Equal(t, GetCurrentMultiThreaded64(), actual.Version) - }) + for _, version := range StateVersionTypes { + if !IsSupportedMultiThreaded64(version) { + t.Run(version.String()+"-unsupported", func(t *testing.T) { + _, err := NewFromState(version, multithreaded.CreateEmptyState()) + require.ErrorIs(t, err, ErrUnsupportedVersion) + }) + } else { + t.Run(version.String(), func(t *testing.T) { + actual, err := NewFromState(version, multithreaded.CreateEmptyState()) + require.NoError(t, err) + require.IsType(t, &multithreaded.State{}, actual.FPVMState) + require.Equal(t, version, actual.Version) + }) + t.Run(version.String()+"-st-unsupported", func(t *testing.T) { + _, err := NewFromState(version, singlethreaded.CreateEmptyState()) + require.ErrorIs(t, err, ErrUnsupportedVersion) + }) + } + } } func TestLoadStateFromFile(t *testing.T) { - t.Run("Multithreaded64_latestVersion_FromBinary", func(t *testing.T) { - expected, err := NewFromState(multithreaded.CreateEmptyState()) - require.NoError(t, err) + for _, version := range StateVersionTypes { + if !IsSupportedMultiThreaded64(version) { + continue + } + t.Run(version.String(), func(t *testing.T) { + expected, err := NewFromState(version, multithreaded.CreateEmptyState()) + require.NoError(t, err) + + path := writeToFile(t, "state.bin.gz", expected) + actual, err := LoadStateFromFile(path) + require.NoError(t, err) + require.Equal(t, expected, actual) + }) + } +} - path := writeToFile(t, "state.bin.gz", expected) - actual, err := LoadStateFromFile(path) - require.NoError(t, err) - require.Equal(t, expected, actual) - }) +type versionAndStateCreator struct { + version StateVersion + createState func() mipsevm.FPVMState } func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) { - tests := []struct { + var tests []struct { version StateVersion createState func() mipsevm.FPVMState - }{ - {GetCurrentMultiThreaded64(), func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }}, + } + for _, version := range StateVersionTypes { + if !IsSupportedMultiThreaded64(version) { + continue + } + tests = append(tests, versionAndStateCreator{version: version, createState: func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }}) } for _, test := range tests { test := test t.Run(test.version.String(), func(t *testing.T) { - state, err := NewFromState(test.createState()) + state, err := NewFromState(test.version, test.createState()) require.NoError(t, err) dir := t.TempDir() diff --git a/cannon/mipsevm/versions/state_test.go b/cannon/mipsevm/versions/state_test.go index 930906b6fbb01..13a4bbd99fa15 100644 --- a/cannon/mipsevm/versions/state_test.go +++ b/cannon/mipsevm/versions/state_test.go @@ -4,7 +4,9 @@ package versions import ( + "os" "path/filepath" + "strconv" "testing" "github.com/stretchr/testify/require" @@ -16,55 +18,103 @@ import ( ) func TestNewFromState(t *testing.T) { - t.Run("singlethreaded-latestVersion", func(t *testing.T) { - actual, err := NewFromState(singlethreaded.CreateEmptyState()) - require.NoError(t, err) - require.IsType(t, &singlethreaded.State{}, actual.FPVMState) - require.Equal(t, GetCurrentSingleThreaded(), actual.Version) - }) - - t.Run("multithreaded-latestVersion", func(t *testing.T) { - actual, err := NewFromState(multithreaded.CreateEmptyState()) - require.NoError(t, err) - require.IsType(t, &multithreaded.State{}, actual.FPVMState) - require.Equal(t, GetCurrentMultiThreaded(), actual.Version) - }) + for _, version := range StateVersionTypes { + if IsSupportedSingleThreaded(version) { + t.Run(version.String(), func(t *testing.T) { + actual, err := NewFromState(version, singlethreaded.CreateEmptyState()) + require.NoError(t, err) + require.IsType(t, &singlethreaded.State{}, actual.FPVMState) + require.Equal(t, version, actual.Version) + }) + t.Run(version.String()+"-mt-unsupported", func(t *testing.T) { + _, err := NewFromState(version, multithreaded.CreateEmptyState()) + require.ErrorIs(t, err, ErrUnsupportedVersion) + }) + } else if IsSupportedMultiThreaded(version) { + t.Run(version.String(), func(t *testing.T) { + actual, err := NewFromState(version, multithreaded.CreateEmptyState()) + require.NoError(t, err) + require.IsType(t, &multithreaded.State{}, actual.FPVMState) + require.Equal(t, version, actual.Version) + }) + t.Run(version.String()+"-st-unsupported", func(t *testing.T) { + _, err := NewFromState(version, singlethreaded.CreateEmptyState()) + require.ErrorIs(t, err, ErrUnsupportedVersion) + }) + } else { + t.Run(version.String()+"-unsupported", func(t *testing.T) { + _, err := NewFromState(version, multithreaded.CreateEmptyState()) + require.ErrorIs(t, err, ErrUnsupportedVersion) + }) + } + } } func TestLoadStateFromFile(t *testing.T) { - t.Run("SinglethreadedFromBinary", func(t *testing.T) { - expected, err := NewFromState(singlethreaded.CreateEmptyState()) - require.NoError(t, err) + for _, version := range StateVersionTypes { + if IsSupportedSingleThreaded(version) { + t.Run(version.String(), func(t *testing.T) { + expected, err := NewFromState(version, singlethreaded.CreateEmptyState()) + require.NoError(t, err) - path := writeToFile(t, "state.bin.gz", expected) - actual, err := LoadStateFromFile(path) - require.NoError(t, err) - require.Equal(t, expected, actual) - }) + path := writeToFile(t, "state.bin.gz", expected) + actual, err := LoadStateFromFile(path) + require.NoError(t, err) + require.Equal(t, expected, actual) + }) + } + if IsSupportedMultiThreaded(version) { + t.Run(version.String(), func(t *testing.T) { + expected, err := NewFromState(version, multithreaded.CreateEmptyState()) + require.NoError(t, err) - t.Run("MultithreadedFromBinary", func(t *testing.T) { - expected, err := NewFromState(multithreaded.CreateEmptyState()) - require.NoError(t, err) + path := writeToFile(t, "state.bin.gz", expected) + actual, err := LoadStateFromFile(path) + require.NoError(t, err) + require.Equal(t, expected, actual) + }) + } + } - path := writeToFile(t, "state.bin.gz", expected) - actual, err := LoadStateFromFile(path) + t.Run("JSONUnsupported", func(t *testing.T) { + filename := strconv.Itoa(int(VersionSingleThreaded)) + ".json" + dir := t.TempDir() + path := filepath.Join(dir, filename) + in, err := historicStates.ReadFile(filepath.Join(statesPath, filename)) require.NoError(t, err) - require.Equal(t, expected, actual) + require.NoError(t, os.WriteFile(path, in, 0o644)) + + _, err = LoadStateFromFile(path) + require.ErrorIs(t, err, ErrUnsupportedVersion) }) } +type versionAndStateCreator struct { + version StateVersion + createState func() mipsevm.FPVMState +} + func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) { - tests := []struct { + var tests []struct { version StateVersion createState func() mipsevm.FPVMState - }{ - {GetCurrentSingleThreaded(), func() mipsevm.FPVMState { return singlethreaded.CreateEmptyState() }}, - {GetCurrentMultiThreaded(), func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }}, + } + for _, version := range StateVersionTypes { + if !IsSupportedSingleThreaded(version) { + continue + } + tests = append(tests, versionAndStateCreator{version: version, createState: func() mipsevm.FPVMState { return singlethreaded.CreateEmptyState() }}) + } + for _, version := range StateVersionTypes { + if !IsSupportedMultiThreaded(version) { + continue + } + tests = append(tests, versionAndStateCreator{version: version, createState: func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }}) } for _, test := range tests { test := test t.Run(test.version.String(), func(t *testing.T) { - state, err := NewFromState(test.createState()) + state, err := NewFromState(test.version, test.createState()) require.NoError(t, err) dir := t.TempDir() diff --git a/cannon/mipsevm/versions/version.go b/cannon/mipsevm/versions/version.go index 0c33144e130a9..ab459f7a24dfc 100644 --- a/cannon/mipsevm/versions/version.go +++ b/cannon/mipsevm/versions/version.go @@ -89,17 +89,17 @@ func GetStateVersionStrings() []string { return vers } -// GetCurrentMultiThreaded64 returns the 64-bit multithreaded VM version that is currently supported -func GetCurrentMultiThreaded64() StateVersion { - return VersionMultiThreaded64_v3 +// IsSupportedMultiThreaded64 returns true if the state version is a 64-bit multithreaded VM that is currently supported +func IsSupportedMultiThreaded64(ver StateVersion) bool { + return ver == VersionMultiThreaded64_v3 } -// GetCurrentMultiThreaded returns the 32-bit multithreaded VM version that is currently supported -func GetCurrentMultiThreaded() StateVersion { - return VersionMultiThreaded_v2 +// IsSupportedMultiThreaded returns true if the state version is a 32-bit multithreaded VM that is currently supported +func IsSupportedMultiThreaded(ver StateVersion) bool { + return ver == VersionMultiThreaded_v2 } -// GetCurrentSingleThreaded returns the 32-bit single-threaded VM version that is currently supported -func GetCurrentSingleThreaded() StateVersion { - return VersionSingleThreaded2 +// IsSupportedSingleThreaded returns true if the state version is a 32-bit single-threaded VM that is currently supported +func IsSupportedSingleThreaded(ver StateVersion) bool { + return ver == VersionSingleThreaded2 }