From 555f69011a47648bbc4de683a1b8d2b7357b10aa Mon Sep 17 00:00:00 2001 From: inphi Date: Wed, 30 Oct 2024 15:23:29 -0400 Subject: [PATCH] cannon: Add more 64-bit tests --- cannon/Makefile | 43 +- cannon/mipsevm/exec/mips_instructions.go | 4 +- cannon/mipsevm/tests/evm_common64_test.go | 244 ++------- cannon/mipsevm/tests/evm_common_test.go | 277 ++-------- .../mipsevm/tests/evm_multithreaded64_test.go | 125 +++++ .../mipsevm/tests/evm_multithreaded_test.go | 330 +++--------- .../tests/fuzz_evm_multithreaded64_test.go | 12 + .../tests/fuzz_evm_multithreaded_test.go | 5 +- cannon/mipsevm/tests/testfuncs_test.go | 502 ++++++++++++++++++ cannon/mipsevm/testutil/arch.go | 8 + 10 files changed, 822 insertions(+), 728 deletions(-) create mode 100644 cannon/mipsevm/tests/fuzz_evm_multithreaded64_test.go create mode 100644 cannon/mipsevm/tests/testfuncs_test.go diff --git a/cannon/Makefile b/cannon/Makefile index f71ffa24d200..4a9f7c68903e 100644 --- a/cannon/Makefile +++ b/cannon/Makefile @@ -18,6 +18,9 @@ endif # The MIPS64 r1 opcodes not supported by cannon. This list does not include coprocess-specific opcodes. UNSUPPORTED_OPCODES := (dclo|dclz) +CANNON32_FUZZTIME := 10s +CANNON64_FUZZTIME := 20s + cannon32-impl: env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build --tags=cannon32 -v $(LDFLAGS) -o ./bin/cannon32-impl . @@ -87,28 +90,28 @@ cannon-stf-verify: fuzz: printf "%s\n" \ - "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallBrk32 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallMmap32 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallExitGroup32 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallFcntl32 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintRead32 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateSyscallBrk32 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateSyscallMmap32 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateSyscallExitGroup32 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateSyscallFcntl32 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateHintRead32 ./mipsevm/tests" \ "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageRead32 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintWrite32 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateHintWrite32 ./mipsevm/tests" \ "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageWrite32 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallCloneST ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallCloneMT32 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateConsistencyMulOp ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateConsistencyMultOp ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateConsistencyMultuOp ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateSyscallBrk64 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateSyscallMmap64 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateSyscallExitGroup64 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateSyscallFcntl64 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateHintRead64 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageRead64 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateHintWrite64 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageWrite64 ./mipsevm/tests" \ - "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateSyscallCloneMT64 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateSyscallCloneST ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateSyscallCloneMT32 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateConsistencyMulOp ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateConsistencyMultOp ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateConsistencyMultuOp ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallBrk64 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallMmap64 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallExitGroup64 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallFcntl64 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateHintRead64 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStatePreimageRead64 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateHintWrite64 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStatePreimageWrite64 ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallCloneMT64 ./mipsevm/tests" \ | parallel -j 8 {} .PHONY: \ diff --git a/cannon/mipsevm/exec/mips_instructions.go b/cannon/mipsevm/exec/mips_instructions.go index bf1a8fdd0e7c..c8e27e63df01 100644 --- a/cannon/mipsevm/exec/mips_instructions.go +++ b/cannon/mipsevm/exec/mips_instructions.go @@ -455,10 +455,10 @@ func ExecuteMipsInstruction(insn uint32, opcode uint32, fun uint32, rs, rt, mem assertMips64(insn) return rt default: - panic("invalid instruction") + panic(fmt.Sprintf("invalid instruction: %x", insn)) } } - panic("invalid instruction") + panic(fmt.Sprintf("invalid instruction: %x", insn)) } func SignExtend(dat Word, idx Word) Word { diff --git a/cannon/mipsevm/tests/evm_common64_test.go b/cannon/mipsevm/tests/evm_common64_test.go index d83ac4fa63d1..9b1a8ee52cdb 100644 --- a/cannon/mipsevm/tests/evm_common64_test.go +++ b/cannon/mipsevm/tests/evm_common64_test.go @@ -8,22 +8,12 @@ import ( "os" "testing" - "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" "github.com/stretchr/testify/require" ) -func TestEVMSingleStep_Operators64(t *testing.T) { - cases := []struct { - name string - isImm bool - rs Word - rt Word - imm uint16 - opcode uint32 - funct uint32 - expectRes Word - }{ +func TestEVM_SingleStep_Operators64(t *testing.T) { + cases := []operatorTestCase{ {name: "dadd. both unsigned 32", funct: 0x2c, isImm: false, rs: Word(0x12), rt: Word(0x20), expectRes: Word(0x32)}, // dadd t0, s1, s2 {name: "dadd. unsigned 32 and signed", funct: 0x2c, isImm: false, rs: Word(0x12), rt: Word(^uint32(0)), expectRes: Word(0x1_00_00_00_11)}, // dadd t0, s1, s2 {name: "dadd. signed and unsigned 32", funct: 0x2c, isImm: false, rs: Word(^uint32(0)), rt: Word(0x12), expectRes: Word(0x1_00_00_00_11)}, // dadd t0, s1, s2 @@ -93,52 +83,27 @@ func TestEVMSingleStep_Operators64(t *testing.T) { {name: "dsrav max", funct: 0x17, rt: Word(0x7F_FF_00_00_00_00_00_20), rs: Word(0x3f), expectRes: Word(0x0)}, {name: "dsrav max sign-extend", funct: 0x17, rt: Word(0x80_00_00_00_00_00_00_20), rs: Word(0x3f), expectRes: Word(0xFF_FF_FF_FF_FF_FF_FF_FF)}, } + testOperators(t, cases, false) +} - v := GetMultiThreadedTestCase(t) - for i, tt := range cases { - testName := fmt.Sprintf("%v %v", v.Name, tt.name) - t.Run(testName, func(t *testing.T) { - goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(0)) - state := goVm.GetState() - var insn uint32 - var rsReg uint32 = 17 - var rtReg uint32 - var rdReg uint32 - if tt.isImm { - rtReg = 8 - insn = tt.opcode<<26 | rsReg<<21 | rtReg<<16 | uint32(tt.imm) - state.GetRegistersRef()[rtReg] = tt.rt - state.GetRegistersRef()[rsReg] = tt.rs - } else { - rtReg = 18 - rdReg = 8 - insn = rsReg<<21 | rtReg<<16 | rdReg<<11 | tt.funct - state.GetRegistersRef()[rsReg] = tt.rs - state.GetRegistersRef()[rtReg] = tt.rt - } - testutil.StoreInstruction(state.GetMemory(), 0, insn) - step := state.GetStep() - - // Setup expectations - expected := testutil.NewExpectedState(state) - expected.ExpectStep() - if tt.isImm { - expected.Registers[rtReg] = tt.expectRes - } else { - 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) - }) +func TestEVM_SingleStep_Bitwise64(t *testing.T) { + cases := []operatorTestCase{ + {name: "and", funct: 0x24, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(160)}, // and t0, s1, s2 + {name: "andi", opcode: 0xc, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(0)}, // andi t0, s1, 40 + {name: "or", funct: 0x25, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1530)}, // or t0, s1, s2 + {name: "ori", opcode: 0xd, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // ori t0, s1, 40 + {name: "xor", funct: 0x26, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1370)}, // xor t0, s1, s2 + {name: "xori", opcode: 0xe, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // xori t0, s1, 40 + {name: "nor", funct: 0x27, isImm: false, rs: Word(0x4b0), rt: Word(0x1ea), expectRes: Word(0xFF_FF_FF_FF_FF_FF_FA_05)}, // nor t0, s1, s2 + {name: "nor", funct: 0x27, isImm: false, rs: Word(0x4b0), rt: Word(0x1ea), expectRes: Word(0xFF_FF_FF_FF_FF_FF_FA_05)}, // nor t0, s1, s2 + {name: "slt", funct: 0x2a, isImm: false, rs: 0xFF_FF_FF_FE, rt: Word(5), expectRes: Word(0)}, // slt t0, s1, s2 + {name: "slt", funct: 0x2a, isImm: false, rs: 0xFF_FF_FF_FF_FF_FF_FF_FE, rt: Word(5), expectRes: Word(1)}, // slt t0, s1, s2 + {name: "sltu", funct: 0x2b, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(0)}, // sltu t0, s1, s2 } + testOperators(t, cases, false) } -func TestEVMSingleStep_Shift64(t *testing.T) { +func TestEVM_SingleStep_Shift64(t *testing.T) { cases := []struct { name string rd Word @@ -218,17 +183,10 @@ func TestEVMSingleStep_Shift64(t *testing.T) { } } -func TestEVMSingleStep_LoadStore64(t *testing.T) { - cases := []struct { - name string - rs Word - rt Word - opcode uint32 - memVal Word - expectMemVal Word - expectRes Word - imm uint16 - }{ +func TestEVM_SingleStep_LoadStore64(t *testing.T) { + t1 := Word(0xFF000000_00000108) + + cases := []loadStoreTestCase{ {name: "lb 0", opcode: uint32(0x20), memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x71)}, // lb $t0, 0($t1) {name: "lb 1", opcode: uint32(0x20), imm: 1, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x72)}, // lb $t0, 1($t1) {name: "lb 2", opcode: uint32(0x20), imm: 2, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x73)}, // lb $t0, 2($t1) @@ -421,63 +379,15 @@ func TestEVMSingleStep_LoadStore64(t *testing.T) { {name: "sd", opcode: uint32(0x3f), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x11_22_33_44_55_66_77_88)}, // sd $t0, 0($t1) {name: "sd signed", opcode: uint32(0x3f), rt: Word(0x81_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x81_22_33_44_55_66_77_88)}, // sd $t0, 4($t1) } - - v := GetMultiThreadedTestCase(t) - var t1 Word = 0xFF000000_00000108 - var baseReg uint32 = 9 - var rtReg uint32 = 8 - for i, tt := range cases { - testName := fmt.Sprintf("%v %v", v.Name, tt.name) - t.Run(testName, func(t *testing.T) { - effAddr := arch.AddressMask & t1 - - 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] = t1 - - testutil.StoreInstruction(state.GetMemory(), 0, insn) - state.GetMemory().SetWord(t1&arch.AddressMask, 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) - - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) - }) + // use a fixed base for all tests + for i := range cases { + cases[i].base = t1 } + testLoadStore(t, cases) } -func TestEVMSingleStep_DivMult64(t *testing.T) { - cases := []struct { - name string - rs Word - rt Word - funct uint32 - expectLo Word - expectHi Word - expectPanic string - }{ - // TODO(#12598): Fix 32-bit tests and remove these - {name: "mult", funct: uint32(0x18), rs: Word(0x0F_FF_00_00), rt: Word(100), expectHi: Word(0x6), expectLo: Word(0x3F_9C_00_00)}, - {name: "mult", funct: uint32(0x18), rs: Word(0xFF_FF_FF_FF), rt: Word(0xFF_FF_FF_FF), expectHi: Word(0x0), expectLo: Word(0x1)}, - {name: "mult", funct: uint32(0x18), rs: Word(0xFF_FF_FF_D3), rt: Word(0xAA_BB_CC_DD), expectHi: Word(0xE), expectLo: Word(0xFF_FF_FF_FF_FC_FC_FD_27)}, - {name: "multu", funct: uint32(0x19), rs: Word(0x0F_FF_00_00), rt: Word(100), expectHi: Word(0x6), expectLo: Word(0x3F_9C_00_00)}, - {name: "multu", funct: uint32(0x19), rs: Word(0xFF_FF_FF_FF), rt: Word(0xFF_FF_FF_FF), expectHi: Word(0xFF_FF_FF_FF_FF_FF_FF_FE), expectLo: Word(0x1)}, - {name: "multu", funct: uint32(0x19), rs: Word(0xFF_FF_FF_D3), rt: Word(0xAA_BB_CC_BE), expectHi: Word(0xFF_FF_FF_FF_AA_BB_CC_9F), expectLo: Word(0xFF_FF_FF_FF_FC_FD_02_9A)}, - +func TestEVM_SingleStep_MulDiv64(t *testing.T) { + cases := []mulDivTestCase{ // dmult s1, s2 // expected hi,lo were verified using qemu-mips {name: "dmult 0", funct: 0x1c, rs: 0, rt: 0, expectLo: 0, expectHi: 0}, @@ -514,9 +424,9 @@ func TestEVMSingleStep_DivMult64(t *testing.T) { {name: "dmultu 14", funct: 0x1d, rs: Word(0x7F_FF_FF_FF_FF_FF_FF_FF), rt: Word(0x8F_FF_FF_FF_FF_FF_FF_FF), expectLo: 0xF0_00_00_00_00_00_00_01, expectHi: 0x47_FF_FF_FF_FF_FF_FF_FE}, // ddiv rs, rt - {name: "ddiv", funct: 0x1e, rs: 0, rt: 0, expectPanic: "instruction divide by zero"}, - {name: "ddiv", funct: 0x1e, rs: 1, rt: 0, expectPanic: "instruction divide by zero"}, - {name: "ddiv", funct: 0x1e, rs: 0xFF_FF_FF_FF_FF_FF_FF_FF, rt: 0, expectPanic: "instruction divide by zero"}, + {name: "ddiv", funct: 0x1e, rs: 0, rt: 0, expectRevert: "instruction divide by zero"}, + {name: "ddiv", funct: 0x1e, rs: 1, rt: 0, expectRevert: "instruction divide by zero"}, + {name: "ddiv", funct: 0x1e, rs: 0xFF_FF_FF_FF_FF_FF_FF_FF, rt: 0, expectRevert: "instruction divide by zero"}, {name: "ddiv", funct: 0x1e, rs: 0, rt: 1, expectLo: 0, expectHi: 0}, {name: "ddiv", funct: 0x1e, rs: 1, rt: 1, expectLo: 1, expectHi: 0}, {name: "ddiv", funct: 0x1e, rs: 10, rt: 3, expectLo: 3, expectHi: 1}, @@ -527,9 +437,9 @@ func TestEVMSingleStep_DivMult64(t *testing.T) { {name: "ddiv", funct: 0x1e, rs: 0x7F_FF_FF_FF_00_00_00_00, rt: ^Word(0), expectLo: 0x80_00_00_01_00_00_00_00, expectHi: 0}, // ddivu - {name: "ddivu", funct: 0x1f, rs: 0, rt: 0, expectPanic: "instruction divide by zero"}, - {name: "ddivu", funct: 0x1f, rs: 1, rt: 0, expectPanic: "instruction divide by zero"}, - {name: "ddivu", funct: 0x1f, rs: 0xFF_FF_FF_FF_FF_FF_FF_FF, rt: 0, expectPanic: "instruction divide by zero"}, + {name: "ddivu", funct: 0x1f, rs: 0, rt: 0, expectRevert: "instruction divide by zero"}, + {name: "ddivu", funct: 0x1f, rs: 1, rt: 0, expectRevert: "instruction divide by zero"}, + {name: "ddivu", funct: 0x1f, rs: 0xFF_FF_FF_FF_FF_FF_FF_FF, rt: 0, expectRevert: "instruction divide by zero"}, {name: "ddivu", funct: 0x1f, rs: 0, rt: 1, expectLo: 0, expectHi: 0}, {name: "ddivu", funct: 0x1f, rs: 1, rt: 1, expectLo: 1, expectHi: 0}, {name: "ddivu", funct: 0x1f, rs: 10, rt: 3, expectLo: 3, expectHi: 1}, @@ -540,57 +450,16 @@ func TestEVMSingleStep_DivMult64(t *testing.T) { {name: "ddivu", funct: 0x1f, rs: 0x7F_FF_FF_FF_00_00_00_00, rt: ^Word(0), expectLo: 0, expectHi: 0x7F_FF_FF_FF_00_00_00_00}, // a couple div/divu 64-bit edge cases - {name: "div lower word zero", funct: 0x1a, rs: 1, rt: 0xFF_FF_FF_FF_00_00_00_00, expectPanic: "instruction divide by zero"}, - {name: "divu lower word zero", funct: 0x1b, rs: 1, rt: 0xFF_FF_FF_FF_00_00_00_00, expectPanic: "instruction divide by zero"}, + {name: "div lower word zero", funct: 0x1a, rs: 1, rt: 0xFF_FF_FF_FF_00_00_00_00, expectRevert: "instruction divide by zero"}, + {name: "divu lower word zero", funct: 0x1b, rs: 1, rt: 0xFF_FF_FF_FF_00_00_00_00, expectRevert: "instruction divide by zero"}, } - v := GetMultiThreadedTestCase(t) - for i, tt := range cases { - testName := fmt.Sprintf("%v %v", v.Name, tt.name) - t.Run(testName, func(t *testing.T) { - goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(0)) - state := goVm.GetState() - var rsReg uint32 = 17 - var rtReg uint32 = 18 - insn := rsReg<<21 | rtReg<<16 | tt.funct - state.GetRegistersRef()[rsReg] = tt.rs - state.GetRegistersRef()[rtReg] = tt.rt - testutil.StoreInstruction(state.GetMemory(), 0, insn) - step := state.GetStep() - - // Setup expectations - expected := testutil.NewExpectedState(state) - expected.ExpectStep() - expected.LO = tt.expectLo - expected.HI = tt.expectHi - - if tt.expectPanic != "" { - require.PanicsWithValue(t, tt.expectPanic, func() { _, _ = goVm.Step(true) }) - // TODO(#12250): Assert EVM panic for divide by zero - // testutil.AssertEVMReverts(t, state, contracts, nil, proofData, errMsg) - } else { - stepWitness, err := goVm.Step(true) - require.NoError(t, err) - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) - } - }) - } + testMulDiv(t, cases, false) } -func TestEVMSingleStep_Branch64(t *testing.T) { - versions := GetMipsVersionTestCases(t) - cases := []struct { - name string - pc Word - expectNextPC Word - opcode uint32 - regimm uint32 - expectLink bool - rs arch.SignedInteger - rt Word - offset uint16 - }{ +func TestEVM_SingleStep_Branch64(t *testing.T) { + t.Parallel() + cases := []branchTestCase{ // blez {name: "blez", pc: 0, opcode: 0x6, rs: 0x5, offset: 0x100, expectNextPC: 0x8}, {name: "blez large rs", pc: 0x10, opcode: 0x6, rs: 0x7F_FF_FF_FF_FF_FF_FF_FF, offset: 0x100, expectNextPC: 0x18}, @@ -635,34 +504,5 @@ func TestEVMSingleStep_Branch64(t *testing.T) { {name: "bgezal fill bit offset except sign", pc: 0x10, opcode: 0x1, regimm: 0x11, rs: 1, offset: 0x7F_FF, expectNextPC: 0x2_00_10, expectLink: true}, } - for _, v := range versions { - for i, tt := range cases { - testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) - t.Run(testName, func(t *testing.T) { - goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(tt.pc)) - state := goVm.GetState() - const rsReg = 8 // t0 - insn := tt.opcode<<26 | rsReg<<21 | tt.regimm<<16 | uint32(tt.offset) - testutil.StoreInstruction(state.GetMemory(), tt.pc, insn) - state.GetRegistersRef()[rsReg] = Word(tt.rs) - step := state.GetStep() - - // Setup expectations - expected := testutil.NewExpectedState(state) - expected.Step += 1 - expected.PC = state.GetCpu().NextPC - expected.NextPC = tt.expectNextPC - if tt.expectLink { - expected.Registers[31] = state.GetPC() + 8 - } - - 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) - }) - } - } + testBranch(t, cases) } diff --git a/cannon/mipsevm/tests/evm_common_test.go b/cannon/mipsevm/tests/evm_common_test.go index f61ee0850d51..5642db31717a 100644 --- a/cannon/mipsevm/tests/evm_common_test.go +++ b/cannon/mipsevm/tests/evm_common_test.go @@ -20,7 +20,9 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" ) -func TestEVM(t *testing.T) { +func TestEVM_OpenMIPS(t *testing.T) { + testutil.Cannon32OnlyTest(t, "Skipping MIPS32 assembly test vectors for cannon64") + testFiles, err := os.ReadDir("open_mips_tests/test/bin") require.NoError(t, err) @@ -36,7 +38,6 @@ func TestEVM(t *testing.T) { for _, f := range testFiles { testName := fmt.Sprintf("%v (%v)", f.Name(), c.Name) t.Run(testName, func(t *testing.T) { - testutil.TemporarilySkip64BitTests(t) for _, skipped := range skipped { if f.Name() == skipped { t.Skipf("Skipping explicitly excluded open_mips testcase: %v", f.Name()) @@ -105,7 +106,7 @@ func TestEVM(t *testing.T) { } } -func TestEVMSingleStep_Jump(t *testing.T) { +func TestEVM_SingleStep_Jump(t *testing.T) { versions := GetMipsVersionTestCases(t) cases := []struct { name string @@ -150,18 +151,8 @@ func TestEVMSingleStep_Jump(t *testing.T) { } } -func TestEVMSingleStep_Operators(t *testing.T) { - versions := GetMipsVersionTestCases(t) - cases := []struct { - name string - isImm bool - rs Word - rt Word - imm uint16 - funct uint32 - opcode uint32 - expectRes Word - }{ +func TestEVM_SingleStep_Operators(t *testing.T) { + cases := []operatorTestCase{ {name: "add", funct: 0x20, isImm: false, rs: Word(12), rt: Word(20), expectRes: Word(32)}, // add t0, s1, s2 {name: "add", funct: 0x20, isImm: false, rs: ^Word(0), rt: ^Word(0), expectRes: Word(0xFF_FF_FF_FE)}, // add t0, s1, s2 {name: "add", funct: 0x20, isImm: false, rs: Word(0x7F_FF_FF_FF), rt: Word(0x7F_FF_FF_FF), expectRes: Word(0xFF_FF_FF_FE)}, // add t0, s1, s2 @@ -191,7 +182,14 @@ func TestEVMSingleStep_Operators(t *testing.T) { {name: "subu", funct: 0x23, isImm: false, rs: Word(20), rt: Word(12), expectRes: Word(8)}, // subu t0, s1, s2 {name: "subu", funct: 0x23, isImm: false, rs: ^Word(0), rt: Word(1), expectRes: Word(0xFF_FF_FF_FE)}, // subu t0, s1, s2 {name: "subu", funct: 0x23, isImm: false, rs: Word(1), rt: ^Word(0), expectRes: Word(0x2)}, // subu t0, s1, s2 + } + testOperators(t, cases, true) +} +func TestEVM_SingleStep_Bitwise32(t *testing.T) { + testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_SingleStep_Bitwise64") + // bitwise operations that use the full word size + cases := []operatorTestCase{ {name: "and", funct: 0x24, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(160)}, // and t0, s1, s2 {name: "andi", opcode: 0xc, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(0)}, // andi t0, s1, 40 {name: "or", funct: 0x25, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1530)}, // or t0, s1, s2 @@ -202,70 +200,15 @@ func TestEVMSingleStep_Operators(t *testing.T) { {name: "slt", funct: 0x2a, isImm: false, rs: 0xFF_FF_FF_FE, rt: Word(5), expectRes: Word(1)}, // slt t0, s1, s2 {name: "sltu", funct: 0x2b, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(0)}, // sltu t0, s1, s2 } - - for _, v := range versions { - for i, tt := range cases { - testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) - t.Run(testName, func(t *testing.T) { - testutil.TemporarilySkip64BitTests(t) - goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4)) - state := goVm.GetState() - var insn uint32 - var baseReg uint32 = 17 - var rtReg uint32 - var rdReg uint32 - if tt.isImm { - rtReg = 8 - insn = tt.opcode<<26 | baseReg<<21 | rtReg<<16 | uint32(tt.imm) - state.GetRegistersRef()[rtReg] = tt.rt - state.GetRegistersRef()[baseReg] = tt.rs - } else { - rtReg = 18 - rdReg = 8 - insn = baseReg<<21 | rtReg<<16 | rdReg<<11 | tt.funct - state.GetRegistersRef()[baseReg] = tt.rs - state.GetRegistersRef()[rtReg] = tt.rt - } - testutil.StoreInstruction(state.GetMemory(), 0, insn) - step := state.GetStep() - - // Setup expectations - expected := testutil.NewExpectedState(state) - expected.Step += 1 - expected.PC = 4 - expected.NextPC = 8 - if tt.isImm { - expected.Registers[rtReg] = tt.expectRes - } else { - 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) - }) - } - } + testOperators(t, cases, false) } -func TestEVMSingleStep_LoadStore(t *testing.T) { +func TestEVM_SingleStep_LoadStore32(t *testing.T) { + testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_SingleStep_LoadStore64") loadMemVal := Word(0x11_22_33_44) loadMemValNeg := Word(0xF1_F2_F3_F4) rtVal := Word(0xaa_bb_cc_dd) - versions := GetMipsVersionTestCases(t) - cases := []struct { - name string - rt Word - base Word - imm uint32 - opcode uint32 - memVal Word - expectMemVal Word - expectRes Word - }{ + cases := []loadStoreTestCase{ {name: "lb, offset=0", opcode: uint32(0x20), base: 0x100, imm: 0x20, memVal: loadMemVal, expectRes: 0x11}, {name: "lb, offset=1", opcode: uint32(0x20), base: 0x100, imm: 0x1, memVal: loadMemVal, expectRes: 0x22}, {name: "lb, offset=2", opcode: uint32(0x20), base: 0x100, imm: 0x2, memVal: loadMemVal, expectRes: 0x33}, @@ -302,49 +245,10 @@ func TestEVMSingleStep_LoadStore(t *testing.T) { {name: "sw", opcode: uint32(0x2b), base: 0x100, imm: 0x20, rt: rtVal, expectMemVal: 0xaa_bb_cc_dd}, {name: "swr unaligned", opcode: uint32(0x2e), base: 0x100, imm: 0x1, rt: rtVal, expectMemVal: 0xcc_dd_00_00}, } - - var baseReg uint32 = 9 - var rtReg uint32 = 8 - for i, tt := range cases { - for _, v := range versions { - testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) - t.Run(testName, func(t *testing.T) { - testutil.TemporarilySkip64BitTests(t) - addr := tt.base + Word(tt.imm) - effAddr := arch.AddressMask & addr - - // Setup - goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4)) - state := goVm.GetState() - insn := tt.opcode<<26 | baseReg<<21 | rtReg<<16 | 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() - - // Setup expectations - expected := testutil.NewExpectedState(state) - expected.ExpectStep() - if tt.expectMemVal != 0 { - expected.ExpectMemoryWriteWord(effAddr, tt.expectMemVal) - } else { - expected.Registers[rtReg] = tt.expectRes - } - - // Run vm - stepWitness, err := goVm.Step(true) - require.NoError(t, err) - - // Validate - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) - }) - } - } + testLoadStore(t, cases) } -func TestEVMSingleStep_MovzMovn(t *testing.T) { +func TestEVM_SingleStep_MovzMovn(t *testing.T) { versions := GetMipsVersionTestCases(t) cases := []struct { name string @@ -406,7 +310,7 @@ func TestEVMSingleStep_MovzMovn(t *testing.T) { } -func TestEVMSingleStep_MfhiMflo(t *testing.T) { +func TestEVM_SingleStep_MfhiMflo(t *testing.T) { versions := GetMipsVersionTestCases(t) cases := []struct { name string @@ -442,23 +346,8 @@ func TestEVMSingleStep_MfhiMflo(t *testing.T) { } } -func TestEVMSingleStep_MulDiv(t *testing.T) { - var tracer *tracing.Hooks - - versions := GetMipsVersionTestCases(t) - cases := []struct { - name string - rs Word - rt Word - funct uint32 - opcode uint32 - expectHi Word - expectLo Word - expectRes Word - rdReg uint32 - expectRevert string - errMsg string - }{ +func TestEVM_SingleStep_MulDiv(t *testing.T) { + cases := []mulDivTestCase{ {name: "mul", funct: uint32(0x2), opcode: uint32(28), rs: Word(5), rt: Word(2), rdReg: uint32(0x8), expectRes: Word(10)}, // mul t0, t1, t2 {name: "mul", funct: uint32(0x2), opcode: uint32(28), rs: Word(0x1), rt: ^Word(0), rdReg: uint32(0x8), expectRes: ^Word(0)}, // mul t1, t2 {name: "mul", funct: uint32(0x2), opcode: uint32(28), rs: Word(0xFF_FF_FF_FF), rt: Word(0xFF_FF_FF_FF), rdReg: uint32(0x8), expectRes: Word(0x1)}, // mul t1, t2 @@ -473,62 +362,18 @@ func TestEVMSingleStep_MulDiv(t *testing.T) { {name: "multu", funct: uint32(0x19), rs: Word(0x1), rt: Word(0xFF_FF_FF_FF), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(0x0), expectLo: Word(0xFF_FF_FF_FF)}, // multu t1, t2 {name: "multu", funct: uint32(0x19), rs: Word(0xFF_FF_FF_FF), rt: Word(0xFF_FF_FF_FF), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(0xFF_FF_FF_FE), expectLo: Word(0x1)}, // multu t1, t2 {name: "multu", funct: uint32(0x19), rs: Word(0xFF_FF_FF_D3), rt: Word(0xAA_BB_CC_DD), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(0xAA_BB_CC_BE), expectLo: Word(0xFC_FC_FD_27)}, // multu t1, t2 + {name: "multu", funct: uint32(0x19), rs: Word(0xFF_FF_FF_D3), rt: Word(0xAA_BB_CC_BE), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(0xAA_BB_CC_9F), expectLo: Word(0xFC_FD_02_9A)}, // multu t1, t2 - {name: "div", funct: uint32(0x1a), rs: Word(5), rt: Word(2), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(1), expectLo: Word(2)}, // div t1, t2 - {name: "div by zero", funct: uint32(0x1a), rs: Word(5), rt: Word(0), rdReg: uint32(0x0), opcode: uint32(0), expectRevert: "instruction divide by zero", errMsg: "MIPS: division by zero"}, // div t1, t2 - {name: "divu", funct: uint32(0x1b), rs: Word(5), rt: Word(2), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(1), expectLo: Word(2)}, // divu t1, t2 - {name: "divu by zero", funct: uint32(0x1b), rs: Word(5), rt: Word(0), rdReg: uint32(0x0), opcode: uint32(0), expectRevert: "instruction divide by zero", errMsg: "MIPS: division by zero"}, // divu t1, t2 + {name: "div", funct: uint32(0x1a), rs: Word(5), rt: Word(2), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(1), expectLo: Word(2)}, // div t1, t2 + {name: "div by zero", funct: uint32(0x1a), rs: Word(5), rt: Word(0), rdReg: uint32(0x0), opcode: uint32(0), expectRevert: "instruction divide by zero", errMsg: "division by zero"}, // div t1, t2 + {name: "divu", funct: uint32(0x1b), rs: Word(5), rt: Word(2), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(1), expectLo: Word(2)}, // divu t1, t2 + {name: "divu by zero", funct: uint32(0x1b), rs: Word(5), rt: Word(0), rdReg: uint32(0x0), opcode: uint32(0), expectRevert: "instruction divide by zero", errMsg: "division by zero"}, // divu t1, t2 } - for _, v := range versions { - for i, tt := range cases { - testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) - t.Run(testName, func(t *testing.T) { - testutil.TemporarilySkip64BitTests(t) - goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4)) - state := goVm.GetState() - var insn uint32 - baseReg := uint32(0x9) - rtReg := uint32(0xa) - - insn = tt.opcode<<26 | baseReg<<21 | rtReg<<16 | tt.rdReg<<11 | tt.funct - state.GetRegistersRef()[rtReg] = tt.rt - state.GetRegistersRef()[baseReg] = tt.rs - testutil.StoreInstruction(state.GetMemory(), 0, insn) - - if tt.expectRevert != "" { - proofData := v.ProofGenerator(t, goVm.GetState()) - require.PanicsWithValue(t, tt.expectRevert, func() { - _, _ = goVm.Step( - false) - }) - testutil.AssertEVMReverts(t, state, v.Contracts, tracer, proofData, testutil.CreateErrorStringMatcher(tt.errMsg)) - return - } - - step := state.GetStep() - // Setup expectations - expected := testutil.NewExpectedState(state) - expected.ExpectStep() - if tt.expectRes != 0 { - expected.Registers[tt.rdReg] = tt.expectRes - } else { - expected.HI = tt.expectHi - expected.LO = tt.expectLo - } - - 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) - }) - } - } + testMulDiv(t, cases, true) } -func TestEVMSingleStep_MthiMtlo(t *testing.T) { +func TestEVM_SingleStep_MthiMtlo(t *testing.T) { versions := GetMipsVersionTestCases(t) cases := []struct { name string @@ -631,7 +476,7 @@ func TestEVM_MMap(t *testing.T) { } } -func TestEVMSysWriteHint(t *testing.T) { +func TestEVM_SysWriteHint(t *testing.T) { versions := GetMipsVersionTestCases(t) cases := []struct { name string @@ -824,7 +669,7 @@ func TestEVMSysWriteHint(t *testing.T) { } } -func TestEVMFault(t *testing.T) { +func TestEVM_Fault(t *testing.T) { var tracer *tracing.Hooks // no-tracer by default, but see test_util.MarkdownTracer versions := GetMipsVersionTestCases(t) @@ -846,7 +691,7 @@ func TestEVMFault(t *testing.T) { errMsg testutil.ErrMatcher memoryProofAddresses []Word }{ - {name: "illegal instruction", nextPC: 0, insn: 0xFF_FF_FF_FF, errMsg: testutil.CreateErrorStringMatcher("invalid instruction"), memoryProofAddresses: []Word{0xa7ef00cc}}, + {name: "illegal instruction", nextPC: 0, insn: 0b111110 << 26, errMsg: testutil.CreateErrorStringMatcher("invalid instruction"), memoryProofAddresses: []Word{0x0}}, // memoryProof for the zero address at register 0 (+ imm) {name: "branch in delay-slot", nextPC: 8, insn: 0x11_02_00_03, errMsg: testutil.CreateErrorStringMatcher("branch in delay slot")}, {name: "jump in delay-slot", nextPC: 8, insn: 0x0c_00_00_0c, errMsg: testutil.CreateErrorStringMatcher("jump in delay slot")}, {name: "misaligned instruction", pc: 1, nextPC: 4, insn: 0b110111_00001_00001 << 16, errMsg: misAlignedInstructionErr()}, @@ -859,7 +704,6 @@ func TestEVMFault(t *testing.T) { for _, tt := range cases { testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) t.Run(testName, func(t *testing.T) { - testutil.TemporarilySkip64BitTests(t) goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithPC(tt.pc), testutil.WithNextPC(tt.nextPC)) state := goVm.GetState() testutil.StoreInstruction(state.GetMemory(), 0, tt.insn) @@ -874,7 +718,7 @@ func TestEVMFault(t *testing.T) { } } -func TestHelloEVM(t *testing.T) { +func TestEVM_HelloProgram(t *testing.T) { if os.Getenv("SKIP_SLOW_TESTS") == "true" { t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled") } @@ -921,7 +765,7 @@ func TestHelloEVM(t *testing.T) { } } -func TestClaimEVM(t *testing.T) { +func TestEVM_ClaimProgram(t *testing.T) { if os.Getenv("SKIP_SLOW_TESTS") == "true" { t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled") } @@ -966,7 +810,7 @@ func TestClaimEVM(t *testing.T) { } } -func TestEntryEVM(t *testing.T) { +func TestEVM_EntryProgram(t *testing.T) { if os.Getenv("SKIP_SLOW_TESTS") == "true" { t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled") } @@ -1010,25 +854,16 @@ func TestEntryEVM(t *testing.T) { } } -func TestEVMSingleStepBranch(t *testing.T) { - versions := GetMipsVersionTestCases(t) - cases := []struct { - name string - pc Word - expectNextPC Word - opcode uint32 - regimm uint32 - expectLink bool - rs arch.SignedInteger - rt Word - offset uint16 - }{ +func TestEVM_SingleStep_Branch32(t *testing.T) { + testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_SingleStep_Branch64") + t.Parallel() + cases := []branchTestCase{ // blez {name: "blez", pc: 0, opcode: 0x6, rs: 0x5, offset: 0x100, expectNextPC: 0x8}, {name: "blez large rs", pc: 0x10, opcode: 0x6, rs: 0x7F_FF_FF_FF, offset: 0x100, expectNextPC: 0x18}, {name: "blez zero rs", pc: 0x10, opcode: 0x6, rs: 0x0, offset: 0x100, expectNextPC: 0x414}, {name: "blez sign rs", pc: 0x10, opcode: 0x6, rs: -1, offset: 0x100, expectNextPC: 0x414}, - {name: "blez rs only sign bit set", pc: 0x10, opcode: 0x6, rs: testutil.ToSignedInteger(0x80_00_00_00), offset: 0x100, expectNextPC: 0x414}, + {name: "bexpectRevertlez rs only sign bit set", pc: 0x10, opcode: 0x6, rs: testutil.ToSignedInteger(0x80_00_00_00), offset: 0x100, expectNextPC: 0x414}, {name: "blez sign-extended offset", pc: 0x10, opcode: 0x6, rs: -1, offset: 0x80_00, expectNextPC: 0xFF_FE_00_14}, // bgtz @@ -1076,35 +911,5 @@ func TestEVMSingleStepBranch(t *testing.T) { {name: "bgezal fill bit offset except sign", pc: 0x10, opcode: 0x1, regimm: 0x11, rs: 1, offset: 0x7F_FF, expectNextPC: 0x2_00_10, expectLink: true}, } - for _, v := range versions { - for i, tt := range cases { - testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) - t.Run(testName, func(t *testing.T) { - testutil.TemporarilySkip64BitTests(t) - goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(tt.pc)) - state := goVm.GetState() - const rsReg = 8 // t0 - insn := tt.opcode<<26 | rsReg<<21 | tt.regimm<<16 | uint32(tt.offset) - testutil.StoreInstruction(state.GetMemory(), tt.pc, insn) - state.GetRegistersRef()[rsReg] = Word(tt.rs) - step := state.GetStep() - - // Setup expectations - expected := testutil.NewExpectedState(state) - expected.Step += 1 - expected.PC = state.GetCpu().NextPC - expected.NextPC = tt.expectNextPC - if tt.expectLink { - expected.Registers[31] = state.GetPC() + 8 - } - - 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) - }) - } - } + testBranch(t, cases) } diff --git a/cannon/mipsevm/tests/evm_multithreaded64_test.go b/cannon/mipsevm/tests/evm_multithreaded64_test.go index 5c5963a0e027..9cc895d4a3ce 100644 --- a/cannon/mipsevm/tests/evm_multithreaded64_test.go +++ b/cannon/mipsevm/tests/evm_multithreaded64_test.go @@ -5,10 +5,13 @@ package tests import ( + "encoding/binary" "fmt" + "slices" "testing" "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" @@ -360,3 +363,125 @@ func TestEVM_MT64_SCD(t *testing.T) { } } } + +func TestEVM_MT_SysRead_Preimage64(t *testing.T) { + preimageValue := make([]byte, 0, 8) + preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x12_34_56_78) + preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x98_76_54_32) + prestateMem := Word(0xEE_EE_EE_EE_FF_FF_FF_FF) + cases := []testMTSysReadPreimageTestCase{ + {name: "Aligned addr, write 1 byte", addr: 0x00_00_FF_00, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_EE_EE_EE_FF_FF_FF_FF}, + {name: "Aligned addr, write 2 byte", addr: 0x00_00_FF_00, count: 2, writeLen: 2, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_EE_EE_FF_FF_FF_FF}, + {name: "Aligned addr, write 3 byte", addr: 0x00_00_FF_00, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_56_EE_FF_FF_FF_FF}, + {name: "Aligned addr, write 4 byte", addr: 0x00_00_FF_00, count: 4, writeLen: 4, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_56_78_FF_FF_FF_FF}, + {name: "Aligned addr, write 5 byte", addr: 0x00_00_FF_00, count: 5, writeLen: 5, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_56_78_98_FF_FF_FF}, + {name: "Aligned addr, write 6 byte", addr: 0x00_00_FF_00, count: 6, writeLen: 6, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_56_78_98_76_FF_FF}, + {name: "Aligned addr, write 7 byte", addr: 0x00_00_FF_00, count: 7, writeLen: 7, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_56_78_98_76_54_FF}, + {name: "Aligned addr, write 8 byte", addr: 0x00_00_FF_00, count: 8, writeLen: 8, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_56_78_98_76_54_32}, + + {name: "1-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_01, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_12_EE_EE_FF_FF_FF_FF}, + {name: "1-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_01, count: 2, writeLen: 2, preimageOffset: 9, prestateMem: prestateMem, postateMem: 0xEE_34_56_EE_FF_FF_FF_FF}, + {name: "1-byte misaligned addr, write 3 byte", addr: 0x00_00_FF_01, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_12_34_56_FF_FF_FF_FF}, + {name: "1-byte misaligned addr, write 4 byte", addr: 0x00_00_FF_01, count: 4, writeLen: 4, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_12_34_56_78_FF_FF_FF}, + {name: "1-byte misaligned addr, write 5 byte", addr: 0x00_00_FF_01, count: 5, writeLen: 5, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_12_34_56_78_98_FF_FF}, + {name: "1-byte misaligned addr, write 6 byte", addr: 0x00_00_FF_01, count: 6, writeLen: 6, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_12_34_56_78_98_76_FF}, + {name: "1-byte misaligned addr, write 7 byte", addr: 0x00_00_FF_01, count: 7, writeLen: 7, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_12_34_56_78_98_76_54}, + + {name: "2-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_02, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_EE_12_EE_FF_FF_FF_FF}, + {name: "2-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_02, count: 2, writeLen: 2, preimageOffset: 12, prestateMem: prestateMem, postateMem: 0xEE_EE_98_76_FF_FF_FF_FF}, + {name: "2-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_02, count: 3, writeLen: 3, preimageOffset: 12, prestateMem: prestateMem, postateMem: 0xEE_EE_98_76_54_FF_FF_FF}, + {name: "2-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_02, count: 4, writeLen: 4, preimageOffset: 12, prestateMem: prestateMem, postateMem: 0xEE_EE_98_76_54_32_FF_FF}, + + {name: "3-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_03, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_12_FF_FF_FF_FF}, + {name: "4-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_04, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_EE_12_FF_FF_FF}, + {name: "5-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_05, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_EE_FF_12_FF_FF}, + {name: "6-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_06, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_EE_FF_FF_12_FF}, + {name: "7-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_07, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_EE_FF_FF_FF_12}, + + {name: "Count of 0", addr: 0x00_00_FF_03, count: 0, writeLen: 0, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_EE_FF_FF_FF_FF}, + {name: "Count greater than 8", addr: 0x00_00_FF_00, count: 15, writeLen: 8, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_56_78_98_76_54_32}, + {name: "Count greater than 8, unaligned", addr: 0x00_00_FF_01, count: 15, writeLen: 7, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_12_34_56_78_98_76_54}, + {name: "Offset at last byte", addr: 0x00_00_FF_00, count: 8, writeLen: 1, preimageOffset: 15, prestateMem: prestateMem, postateMem: 0x32_EE_EE_EE_FF_FF_FF_FF}, + {name: "Offset just out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 16, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_EE_FF_FF_FF_FF, shouldPanic: true}, + {name: "Offset out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 17, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_EE_FF_FF_FF_FF, shouldPanic: true}, + } + testMTSysReadPreimage(t, preimageValue, cases) +} + +func TestEVM_MT_StoreOpsClearMemReservation64(t *testing.T) { + t.Parallel() + cases := []testMTStoreOpsClearMemReservationTestCase{ + {name: "Store byte", opcode: 0b10_1000, base: 0xFF_00_00_00, offset: 0x10, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0x78_FF_FF_FF_FF_FF_FF_FF}, + {name: "Store byte lower", opcode: 0b10_1000, base: 0xFF_00_00_00, offset: 0x14, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0xFF_FF_FF_FF_78_FF_FF_FF}, + {name: "Store halfword", opcode: 0b10_1001, base: 0xFF_00_00_00, offset: 0x10, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0x56_78_FF_FF_FF_FF_FF_FF}, + {name: "Store halfword lower", opcode: 0b10_1001, base: 0xFF_00_00_00, offset: 0x14, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0xFF_FF_FF_FF_56_78_FF_FF}, + {name: "Store word left", opcode: 0b10_1010, base: 0xFF_00_00_00, offset: 0x10, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0x12_34_56_78_FF_FF_FF_FF}, + {name: "Store word left lower", opcode: 0b10_1010, base: 0xFF_00_00_00, offset: 0x14, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0xFF_FF_FF_FF_12_34_56_78}, + {name: "Store word", opcode: 0b10_1011, base: 0xFF_00_00_00, offset: 0x10, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0x12_34_56_78_FF_FF_FF_FF}, + {name: "Store word lower", opcode: 0b10_1011, base: 0xFF_00_00_00, offset: 0x14, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0xFF_FF_FF_FF_12_34_56_78}, + {name: "Store word right", opcode: 0b10_1110, base: 0xFF_00_00_00, offset: 0x10, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0x78_FF_FF_FF_FF_FF_FF_FF}, + {name: "Store word right lower", opcode: 0b10_1110, base: 0xFF_00_00_00, offset: 0x14, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0xFF_FF_FF_FF_78_FF_FF_FF}, + } + preimageValue := make([]byte, 0, 8) + preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x12_34_56_78) + preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x98_76_54_32) + testMTStoreOpsClearMemReservation(t, cases) +} + +var NoopSyscalls64 = map[string]uint32{ + "SysMunmap": 5011, + "SysGetAffinity": 5196, + "SysMadvise": 5027, + "SysRtSigprocmask": 5014, + "SysSigaltstack": 5129, + "SysRtSigaction": 5013, + "SysPrlimit64": 5297, + "SysClose": 5003, + "SysPread64": 5016, + "SysStat": 5004, + "SysFstat": 5005, + //"SysFstat64": UndefinedSysNr, + "SysOpenAt": 5247, + "SysReadlink": 5087, + "SysReadlinkAt": 5257, + "SysIoctl": 5015, + "SysEpollCreate1": 5285, + "SysPipe2": 5287, + "SysEpollCtl": 5208, + "SysEpollPwait": 5272, + "SysGetRandom": 5313, + "SysUname": 5061, + //"SysStat64": UndefinedSysNr, + "SysGetuid": 5100, + "SysGetgid": 5102, + //"SysLlseek": UndefinedSysNr, + "SysMinCore": 5026, + "SysTgkill": 5225, + "SysGetRLimit": 5095, + "SysLseek": 5008, + "SysSetITimer": 5036, + "SysTimerCreate": 5216, + "SysTimerSetTime": 5217, + "SysTimerDelete": 5220, +} + +func TestEVM_NoopSyscall64(t *testing.T) { + testNoopSyscall(t, NoopSyscalls64) +} + +func TestEVM_UnsupportedSyscall64(t *testing.T) { + t.Parallel() + + var noopSyscallNums = maps.Values(NoopSyscalls64) + var SupportedSyscalls = []uint32{arch.SysMmap, arch.SysBrk, arch.SysClone, arch.SysExitGroup, arch.SysRead, arch.SysWrite, arch.SysFcntl, arch.SysExit, arch.SysSchedYield, arch.SysGetTID, arch.SysFutex, arch.SysOpen, arch.SysNanosleep, arch.SysClockGetTime, arch.SysGetpid} + unsupportedSyscalls := make([]uint32, 0, 400) + for i := 5000; i < 5400; i++ { + candidate := uint32(i) + if slices.Contains(SupportedSyscalls, candidate) || slices.Contains(noopSyscallNums, candidate) { + continue + } + unsupportedSyscalls = append(unsupportedSyscalls, candidate) + } + + testUnsupportedSyscall(t, unsupportedSyscalls) +} diff --git a/cannon/mipsevm/tests/evm_multithreaded_test.go b/cannon/mipsevm/tests/evm_multithreaded_test.go index ba858585980b..1353932dd71f 100644 --- a/cannon/mipsevm/tests/evm_multithreaded_test.go +++ b/cannon/mipsevm/tests/evm_multithreaded_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/ethereum/go-ethereum/core/tracing" - "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" @@ -19,7 +18,6 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" mttestutil "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded/testutil" "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" - preimage "github.com/ethereum-optimism/optimism/op-preimage" ) type Word = arch.Word @@ -190,37 +188,14 @@ func TestEVM_MT_SC(t *testing.T) { } } -func TestEVM_MT_SysRead_Preimage(t *testing.T) { +func TestEVM_MT_SysRead_Preimage32(t *testing.T) { + testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_MT_SysRead_Preimage64") + + t.Parallel() preimageValue := make([]byte, 0, 8) preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x12_34_56_78) preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x98_76_54_32) - - llVariations := []struct { - name string - llReservationStatus multithreaded.LLReservationStatus - matchThreadId bool - effAddrOffset Word - shouldClearReservation bool - }{ - {name: "matching reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, shouldClearReservation: true}, - {name: "matching reservation, unaligned", llReservationStatus: multithreaded.LLStatusActive32bit, effAddrOffset: 1, matchThreadId: true, shouldClearReservation: true}, - {name: "matching reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, shouldClearReservation: true}, - {name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false}, - {name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: false, effAddrOffset: 8, shouldClearReservation: false}, - {name: "no reservation, matching addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, shouldClearReservation: true}, - {name: "no reservation, mismatched addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false}, - } - - cases := []struct { - name string - addr Word - count Word - writeLen Word - preimageOffset Word - prestateMem Word - postateMem Word - shouldPanic bool - }{ + cases := []testMTSysReadPreimageTestCase{ {name: "Aligned addr, write 1 byte", addr: 0x00_00_FF_00, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_FF_FF_FF}, {name: "Aligned addr, write 2 byte", addr: 0x00_00_FF_00, count: 2, writeLen: 2, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_FF_FF}, {name: "Aligned addr, write 3 byte", addr: 0x00_00_FF_00, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_FF}, @@ -238,147 +213,22 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) { {name: "Offset just out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 16, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true}, {name: "Offset out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 17, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true}, } - for i, c := range cases { - for _, v := range llVariations { - tName := fmt.Sprintf("%v (%v)", c.name, v.name) - t.Run(tName, func(t *testing.T) { - testutil.TemporarilySkip64BitTests(t) - effAddr := arch.AddressMask & c.addr - preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageValue)).PreimageKey() - oracle := testutil.StaticOracle(t, preimageValue) - goVm, state, contracts := setup(t, i, oracle) - step := state.GetStep() - - // Define LL-related params - llAddress := effAddr + v.effAddrOffset - llOwnerThread := state.GetCurrentThread().ThreadId - if !v.matchThreadId { - llOwnerThread += 1 - } - - // Set up state - state.PreimageKey = preimageKey - state.PreimageOffset = c.preimageOffset - state.GetRegistersRef()[2] = arch.SysRead - state.GetRegistersRef()[4] = exec.FdPreimageRead - state.GetRegistersRef()[5] = c.addr - state.GetRegistersRef()[6] = c.count - testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) - state.LLReservationStatus = v.llReservationStatus - state.LLAddress = llAddress - state.LLOwnerThread = llOwnerThread - state.GetMemory().SetWord(effAddr, c.prestateMem) - // Setup expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ActiveThread().Registers[2] = c.writeLen - expected.ActiveThread().Registers[7] = 0 // no error - expected.PreimageOffset += c.writeLen - expected.ExpectMemoryWordWrite(effAddr, c.postateMem) - if v.shouldClearReservation { - expected.LLReservationStatus = multithreaded.LLStatusNone - expected.LLAddress = 0 - expected.LLOwnerThread = 0 - } - - if c.shouldPanic { - require.Panics(t, func() { _, _ = goVm.Step(true) }) - testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, contracts) - } else { - stepWitness, err := goVm.Step(true) - require.NoError(t, err) - - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - } - }) - } - } + testMTSysReadPreimage(t, preimageValue, cases) } -func TestEVM_MT_StoreOpsClearMemReservation(t *testing.T) { - llVariations := []struct { - name string - llReservationStatus multithreaded.LLReservationStatus - matchThreadId bool - effAddrOffset Word - shouldClearReservation bool - }{ - {name: "matching reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, shouldClearReservation: true}, - {name: "matching reservation, unaligned", llReservationStatus: multithreaded.LLStatusActive32bit, effAddrOffset: 1, matchThreadId: true, shouldClearReservation: true}, - {name: "matching reservation, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, shouldClearReservation: true}, - {name: "matching reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, shouldClearReservation: true}, - {name: "matching reservation, diff thread, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: false, shouldClearReservation: true}, - {name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false}, - {name: "mismatched reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, effAddrOffset: 8, shouldClearReservation: false}, - {name: "no reservation, matching addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, shouldClearReservation: true}, - {name: "no reservation, mismatched addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false}, - } - - rt := Word(0x12_34_56_78) - baseReg := 5 - rtReg := 6 - cases := []struct { - name string - opcode int - offset int - base Word - effAddr Word - preMem Word - postMem Word - }{ - {name: "Store byte", opcode: 0b10_1000, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x78_FF_FF_FF}, - {name: "Store halfword", opcode: 0b10_1001, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x56_78_FF_FF}, - {name: "Store word left", opcode: 0b10_1010, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x12_34_56_78}, - {name: "Store word", opcode: 0b10_1011, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x12_34_56_78}, - {name: "Store word right", opcode: 0b10_1110, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x78_FF_FF_FF}, - } - for i, c := range cases { - for _, v := range llVariations { - tName := fmt.Sprintf("%v (%v)", c.name, v.name) - t.Run(tName, func(t *testing.T) { - testutil.TemporarilySkip64BitTests(t) - insn := uint32((c.opcode << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) - goVm, state, contracts := setup(t, i, nil, testutil.WithPCAndNextPC(0x08)) - step := state.GetStep() - - // Define LL-related params - llAddress := c.effAddr + v.effAddrOffset - llOwnerThread := state.GetCurrentThread().ThreadId - if !v.matchThreadId { - llOwnerThread += 1 - } - - // Setup state - state.GetRegistersRef()[rtReg] = rt - state.GetRegistersRef()[baseReg] = c.base - testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) - state.GetMemory().SetWord(c.effAddr, c.preMem) - state.LLReservationStatus = v.llReservationStatus - state.LLAddress = llAddress - state.LLOwnerThread = llOwnerThread - - // Setup expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ExpectMemoryWordWrite(c.effAddr, c.postMem) - if v.shouldClearReservation { - expected.LLReservationStatus = multithreaded.LLStatusNone - expected.LLAddress = 0 - expected.LLOwnerThread = 0 - } - - stepWitness, err := goVm.Step(true) - require.NoError(t, err) - - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) - } +func TestEVM_MT_StoreOpsClearMemReservation32(t *testing.T) { + t.Parallel() + testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_MT_StoreOpsClearMemReservation64") + + cases := []testMTStoreOpsClearMemReservationTestCase{ + {name: "Store byte", opcode: 0b10_1000, base: 0xFF_00_00_04, offset: 0x08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x78_FF_FF_FF}, + {name: "Store halfword", opcode: 0b10_1001, base: 0xFF_00_00_04, offset: 0x08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x56_78_FF_FF}, + {name: "Store word left", opcode: 0b10_1010, base: 0xFF_00_00_04, offset: 0x08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x12_34_56_78}, + {name: "Store word", opcode: 0b10_1011, base: 0xFF_00_00_04, offset: 0x08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x12_34_56_78}, + {name: "Store word right", opcode: 0b10_1110, base: 0xFF_00_00_04, offset: 0x08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x78_FF_FF_FF}, } + testMTStoreOpsClearMemReservation(t, cases) } func TestEVM_SysClone_FlagHandling(t *testing.T) { @@ -633,39 +483,38 @@ func TestEVM_PopExitedThread(t *testing.T) { } func TestEVM_SysFutex_WaitPrivate(t *testing.T) { + // Note: parameters are written as 64-bit values. For 32-bit architectures, these values are downcast to 32-bit cases := []struct { name string - addressParam Word - effAddr Word - targetValue Word - actualValue Word - timeout Word + addressParam uint64 + effAddr uint64 + targetValue uint64 + actualValue uint64 + timeout uint64 shouldFail bool shouldSetTimeout bool }{ - {name: "successful wait, no timeout", addressParam: 0x1234, effAddr: 0x1234, targetValue: 0x01, actualValue: 0x01}, - {name: "successful wait, no timeout, unaligned addr", addressParam: 0x1235, effAddr: 0x1234, targetValue: 0x01, actualValue: 0x01}, - {name: "memory mismatch, no timeout", addressParam: 0x1200, effAddr: 0x1200, targetValue: 0x01, actualValue: 0x02, shouldFail: true}, - {name: "memory mismatch, no timeout, unaligned", addressParam: 0x1203, effAddr: 0x1200, targetValue: 0x01, actualValue: 0x02, shouldFail: true}, - {name: "successful wait w timeout", addressParam: 0x1234, effAddr: 0x1234, targetValue: 0x01, actualValue: 0x01, timeout: 1000000, shouldSetTimeout: true}, - {name: "successful wait w timeout, unaligned", addressParam: 0x1232, effAddr: 0x1230, targetValue: 0x01, actualValue: 0x01, timeout: 1000000, shouldSetTimeout: true}, - {name: "memory mismatch w timeout", addressParam: 0x1200, effAddr: 0x1200, targetValue: 0x01, actualValue: 0x02, timeout: 2000000, shouldFail: true}, - {name: "memory mismatch w timeout, unaligned", addressParam: 0x120F, effAddr: 0x120C, targetValue: 0x01, actualValue: 0x02, timeout: 2000000, shouldFail: true}, + {name: "successful wait, no timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_38, effAddr: 0xFF_FF_FF_FF_FF_FF_12_38, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_01}, + {name: "successful wait, no timeout, unaligned addr", addressParam: 0xFF_FF_FF_FF_FF_FF_12_39, effAddr: 0xFF_FF_FF_FF_FF_FF_12_38, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_01}, + {name: "memory mismatch, no timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_00, effAddr: 0xFF_FF_FF_FF_FF_FF_12_00, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_02, shouldFail: true}, + {name: "memory mismatch, no timeout, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_12_03, effAddr: 0xFF_FF_FF_FF_FF_FF_12_00, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_02, shouldFail: true}, + {name: "successful wait w timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_38, effAddr: 0xFF_FF_FF_FF_FF_FF_12_38, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_01, timeout: 1000000, shouldSetTimeout: true}, + {name: "successful wait w timeout, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_12_32, effAddr: 0xFF_FF_FF_FF_FF_FF_12_30, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_01, timeout: 1000000, shouldSetTimeout: true}, + {name: "memory mismatch w timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_00, effAddr: 0xFF_FF_FF_FF_FF_FF_12_00, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_02, timeout: 2000000, shouldFail: true}, + {name: "memory mismatch w timeout, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_12_0F, effAddr: 0xFF_FF_FF_FF_FF_FF_12_10, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_02, timeout: 2000000, shouldFail: true}, } - for i, c := range cases { t.Run(c.name, func(t *testing.T) { - testutil.TemporarilySkip64BitTests(t) goVm, state, contracts := setup(t, i*1234, nil) step := state.GetStep() testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.Memory.SetWord(c.effAddr, c.actualValue) + state.Memory.SetWord(Word(c.effAddr), Word(c.actualValue)) state.GetRegistersRef()[2] = arch.SysFutex // Set syscall number - state.GetRegistersRef()[4] = c.addressParam + state.GetRegistersRef()[4] = Word(c.addressParam) state.GetRegistersRef()[5] = exec.FutexWaitPrivate - state.GetRegistersRef()[6] = c.targetValue - state.GetRegistersRef()[7] = c.timeout + state.GetRegistersRef()[6] = Word(c.targetValue) + state.GetRegistersRef()[7] = Word(c.timeout) // Setup expectations expected := mttestutil.NewExpectedMTState(state) @@ -678,8 +527,8 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) { expected.ActiveThread().Registers[7] = exec.MipsEAGAIN } else { // PC and return registers should not update on success, updates happen when wait completes - expected.ActiveThread().FutexAddr = c.effAddr - expected.ActiveThread().FutexVal = c.targetValue + expected.ActiveThread().FutexAddr = Word(c.effAddr) + expected.ActiveThread().FutexVal = Word(c.targetValue) expected.ActiveThread().FutexTimeoutStep = exec.FutexNoTimeout if c.shouldSetTimeout { expected.ActiveThread().FutexTimeoutStep = step + exec.FutexTimeoutSteps + 1 @@ -687,9 +536,7 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) { } // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) + stepWitness, err := goVm.Step(true) require.NoError(t, err) // Validate post-state @@ -700,39 +547,38 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) { } func TestEVM_SysFutex_WakePrivate(t *testing.T) { + // Note: parameters are written as 64-bit values. For 32-bit architectures, these values are downcast to 32-bit cases := []struct { name string - addressParam Word - effAddr Word + addressParam uint64 + effAddr uint64 activeThreadCount int inactiveThreadCount int traverseRight bool expectTraverseRight bool }{ - {name: "Traverse right", addressParam: 0x6700, effAddr: 0x6700, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true}, - {name: "Traverse right, unaligned addr", addressParam: 0x6789, effAddr: 0x6788, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true}, - {name: "Traverse right, no left threads", addressParam: 0x6784, effAddr: 0x6784, activeThreadCount: 2, inactiveThreadCount: 0, traverseRight: true}, - {name: "Traverse right, no left threads, unaligned addr", addressParam: 0x678E, effAddr: 0x678C, activeThreadCount: 2, inactiveThreadCount: 0, traverseRight: true}, - {name: "Traverse right, single thread", addressParam: 0x6788, effAddr: 0x6788, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: true}, - {name: "Traverse right, single thread, unaligned", addressParam: 0x6789, effAddr: 0x6788, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: true}, - {name: "Traverse left", addressParam: 0x6788, effAddr: 0x6788, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false}, - {name: "Traverse left, unaliagned", addressParam: 0x6789, effAddr: 0x6788, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false}, - {name: "Traverse left, switch directions", addressParam: 0x6788, effAddr: 0x6788, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false, expectTraverseRight: true}, - {name: "Traverse left, switch directions, unaligned", addressParam: 0x6789, effAddr: 0x6788, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false, expectTraverseRight: true}, - {name: "Traverse left, single thread", addressParam: 0x6788, effAddr: 0x6788, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false, expectTraverseRight: true}, - {name: "Traverse left, single thread, unaligned", addressParam: 0x6789, effAddr: 0x6788, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false, expectTraverseRight: true}, + {name: "Traverse right", addressParam: 0xFF_FF_FF_FF_FF_FF_67_00, effAddr: 0xFF_FF_FF_FF_FF_FF_67_00, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true}, + {name: "Traverse right, unaligned addr", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true}, + {name: "Traverse right, no left threads", addressParam: 0xFF_FF_FF_FF_FF_FF_67_84, effAddr: 0xFF_FF_FF_FF_FF_FF_67_84, activeThreadCount: 2, inactiveThreadCount: 0, traverseRight: true}, + {name: "Traverse right, no left threads, unaligned addr", addressParam: 0xFF_FF_FF_FF_FF_FF_67_8E, effAddr: 0xFF_FF_FF_FF_FF_FF_67_8C, activeThreadCount: 2, inactiveThreadCount: 0, traverseRight: true}, + {name: "Traverse right, single thread", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: true}, + {name: "Traverse right, single thread, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: true}, + {name: "Traverse left", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false}, + {name: "Traverse left, unaliagned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false}, + {name: "Traverse left, switch directions", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false, expectTraverseRight: true}, + {name: "Traverse left, switch directions, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false, expectTraverseRight: true}, + {name: "Traverse left, single thread", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false, expectTraverseRight: true}, + {name: "Traverse left, single thread, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false, expectTraverseRight: true}, } - for i, c := range cases { t.Run(c.name, func(t *testing.T) { - testutil.TemporarilySkip64BitTests(t) goVm, state, contracts := setup(t, i*1122, nil) mttestutil.SetupThreads(int64(i*2244), state, c.traverseRight, c.activeThreadCount, c.inactiveThreadCount) step := state.Step testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = arch.SysFutex // Set syscall number - state.GetRegistersRef()[4] = c.addressParam + state.GetRegistersRef()[4] = Word(c.addressParam) state.GetRegistersRef()[5] = exec.FutexWakePrivate // Set up post-state expectations @@ -740,7 +586,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) { expected.ExpectStep() expected.ActiveThread().Registers[2] = 0 expected.ActiveThread().Registers[7] = 0 - expected.Wakeup = c.effAddr + expected.Wakeup = Word(c.effAddr) & arch.AddressMask // aligned for 32 and 64-bit compatibility expected.ExpectPreemption(state) expected.TraverseRight = c.expectTraverseRight if c.traverseRight != c.expectTraverseRight { @@ -750,9 +596,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) { } // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) + stepWitness, err := goVm.Step(true) require.NoError(t, err) // Validate post-state @@ -760,6 +604,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) { testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) }) } + } func TestEVM_SysFutex_UnsupportedOp(t *testing.T) { @@ -1103,70 +948,27 @@ var NoopSyscalls = map[string]uint32{ "SysTimerDelete": 4261, } -func TestEVM_NoopSyscall(t *testing.T) { - for noopName, noopVal := range NoopSyscalls { - t.Run(noopName, func(t *testing.T) { - testutil.TemporarilySkip64BitTests(t) - goVm, state, contracts := setup(t, int(noopVal), nil) - - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = Word(noopVal) // Set syscall number - step := state.Step - - // Set up post-state expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ActiveThread().Registers[2] = 0 - expected.ActiveThread().Registers[7] = 0 - - // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) - - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) - }) - - } +func TestEVM_NoopSyscall32(t *testing.T) { + testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_NoopSyscall64") + testNoopSyscall(t, NoopSyscalls) } -func TestEVM_UnsupportedSyscall(t *testing.T) { +func TestEVM_UnsupportedSyscall32(t *testing.T) { + testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_UnsupportedSyscall64") t.Parallel() - var tracer *tracing.Hooks - var NoopSyscallNums = maps.Values(NoopSyscalls) - var SupportedSyscalls = []uint32{arch.SysMmap, arch.SysBrk, arch.SysClone, arch.SysExitGroup, arch.SysRead, arch.SysWrite, arch.SysFcntl, arch.SysExit, arch.SysSchedYield, arch.SysGetTID, arch.SysFutex, arch.SysOpen, arch.SysNanosleep, arch.SysClockGetTime, arch.SysGetpid} + var noopSyscallNums = maps.Values(NoopSyscalls) + var supportedSyscalls = []uint32{arch.SysMmap, arch.SysBrk, arch.SysClone, arch.SysExitGroup, arch.SysRead, arch.SysWrite, arch.SysFcntl, arch.SysExit, arch.SysSchedYield, arch.SysGetTID, arch.SysFutex, arch.SysOpen, arch.SysNanosleep, arch.SysClockGetTime, arch.SysGetpid} unsupportedSyscalls := make([]uint32, 0, 400) for i := 4000; i < 4400; i++ { candidate := uint32(i) - if slices.Contains(SupportedSyscalls, candidate) || slices.Contains(NoopSyscallNums, candidate) { + if slices.Contains(supportedSyscalls, candidate) || slices.Contains(noopSyscallNums, candidate) { continue } unsupportedSyscalls = append(unsupportedSyscalls, candidate) } - for i, syscallNum := range unsupportedSyscalls { - testName := fmt.Sprintf("Unsupported syscallNum %v", syscallNum) - i := i - syscallNum := syscallNum - t.Run(testName, func(t *testing.T) { - testutil.TemporarilySkip64BitTests(t) - t.Parallel() - goVm, state, contracts := setup(t, i*3434, nil) - // Setup basic getThreadId syscall instruction - testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = Word(syscallNum) - proofData := multiThreadedProofGenerator(t, state) - // Set up post-state expectations - require.Panics(t, func() { _, _ = goVm.Step(true) }) - - errorMessage := "MIPS2: unimplemented syscall" - testutil.AssertEVMReverts(t, state, contracts, tracer, proofData, testutil.CreateErrorStringMatcher(errorMessage)) - }) - } + testUnsupportedSyscall(t, unsupportedSyscalls) } func TestEVM_EmptyThreadStacks(t *testing.T) { diff --git a/cannon/mipsevm/tests/fuzz_evm_multithreaded64_test.go b/cannon/mipsevm/tests/fuzz_evm_multithreaded64_test.go new file mode 100644 index 000000000000..40e2d1760dc0 --- /dev/null +++ b/cannon/mipsevm/tests/fuzz_evm_multithreaded64_test.go @@ -0,0 +1,12 @@ +//go:build cannon64 +// +build cannon64 + +package tests + +import ( + "testing" +) + +func FuzzStateSyscallCloneMT64(f *testing.F) { + doFuzzStateSyscallCloneMT(f) +} diff --git a/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go b/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go index 9d4bb46a4271..180d8cc45fae 100644 --- a/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go +++ b/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go @@ -14,10 +14,7 @@ import ( ) func FuzzStateSyscallCloneMT32(f *testing.F) { - doFuzzStateSyscallCloneMT(f) -} - -func FuzzStateSyscallCloneMT64(f *testing.F) { + testutil.Cannon32OnlyTest(f, "These tests are fully covered for 64-bits in FuzzStateSyscallCloneMT64") doFuzzStateSyscallCloneMT(f) } diff --git a/cannon/mipsevm/tests/testfuncs_test.go b/cannon/mipsevm/tests/testfuncs_test.go new file mode 100644 index 000000000000..e6036b220047 --- /dev/null +++ b/cannon/mipsevm/tests/testfuncs_test.go @@ -0,0 +1,502 @@ +package tests + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" + mttestutil "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded/testutil" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" + preimage "github.com/ethereum-optimism/optimism/op-preimage" + "github.com/ethereum/go-ethereum/crypto" +) + +type operatorTestCase struct { + name string + isImm bool + rs Word + rt Word + imm uint16 + funct uint32 + opcode uint32 + expectRes Word +} + +func testOperators(t *testing.T, cases []operatorTestCase, mips32Insn bool) { + versions := GetMipsVersionTestCases(t) + for _, v := range versions { + for i, tt := range cases { + // sign extend inputs for 64-bit compatibility + if mips32Insn { + tt.rs = randomizeUpperWord(signExtend64(tt.rs)) + tt.rt = randomizeUpperWord(signExtend64(tt.rt)) + tt.expectRes = signExtend64(tt.expectRes) + } + + testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) + t.Run(testName, func(t *testing.T) { + validator := testutil.NewEvmValidator(t, v.StateHashFn, v.Contracts) + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4)) + state := goVm.GetState() + var insn uint32 + var baseReg uint32 = 17 + var rtReg uint32 + var rdReg uint32 + if tt.isImm { + rtReg = 8 + insn = tt.opcode<<26 | baseReg<<21 | rtReg<<16 | uint32(tt.imm) + state.GetRegistersRef()[rtReg] = tt.rt + state.GetRegistersRef()[baseReg] = tt.rs + } else { + rtReg = 18 + rdReg = 8 + insn = baseReg<<21 | rtReg<<16 | rdReg<<11 | tt.funct + state.GetRegistersRef()[baseReg] = tt.rs + state.GetRegistersRef()[rtReg] = tt.rt + } + testutil.StoreInstruction(state.GetMemory(), 0, insn) + step := state.GetStep() + + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.Step += 1 + expected.PC = 4 + expected.NextPC = 8 + if tt.isImm { + expected.Registers[rtReg] = tt.expectRes + } else { + expected.Registers[rdReg] = tt.expectRes + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + validator.ValidateEVM(t, stepWitness, step, goVm) + }) + } + } +} + +type mulDivTestCase struct { + name string + rs Word + rt Word + funct uint32 + opcode uint32 + expectHi Word + expectLo Word + expectRes Word + rdReg uint32 + expectRevert string + errMsg string +} + +func testMulDiv(t *testing.T, cases []mulDivTestCase, mips32Insn bool) { + versions := GetMipsVersionTestCases(t) + for _, v := range versions { + for i, tt := range cases { + if mips32Insn { + tt.rs = randomizeUpperWord(signExtend64(tt.rs)) + tt.rt = randomizeUpperWord(signExtend64(tt.rt)) + tt.expectHi = signExtend64(tt.expectHi) + tt.expectLo = signExtend64(tt.expectLo) + tt.expectRes = signExtend64(tt.expectRes) + } + + testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) + t.Run(testName, func(t *testing.T) { + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4)) + state := goVm.GetState() + var insn uint32 + baseReg := uint32(0x9) + rtReg := uint32(0xa) + + insn = tt.opcode<<26 | baseReg<<21 | rtReg<<16 | tt.rdReg<<11 | tt.funct + state.GetRegistersRef()[rtReg] = tt.rt + state.GetRegistersRef()[baseReg] = tt.rs + testutil.StoreInstruction(state.GetMemory(), 0, insn) + + if tt.expectRevert != "" { + proofData := v.ProofGenerator(t, goVm.GetState()) + require.PanicsWithValue(t, tt.expectRevert, func() { + _, _ = goVm.Step( + false) + }) + testutil.AssertEVMReverts(t, state, v.Contracts, nil, proofData, testutil.CreateErrorStringMatcher(tt.errMsg)) + return + } + + step := state.GetStep() + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.ExpectStep() + if tt.expectRes != 0 { + expected.Registers[tt.rdReg] = tt.expectRes + } else { + expected.HI = tt.expectHi + expected.LO = tt.expectLo + } + + 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) + }) + } + } +} + +type loadStoreTestCase struct { + name string + rt Word + base Word + imm uint32 + opcode uint32 + memVal Word + expectMemVal Word + expectRes Word +} + +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 + + 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 + + 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) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + }) + } +} + +type branchTestCase struct { + name string + pc Word + expectNextPC Word + opcode uint32 + regimm uint32 + expectLink bool + rs arch.SignedInteger + offset uint16 +} + +func testBranch(t *testing.T, cases []branchTestCase) { + versions := GetMipsVersionTestCases(t) + for _, v := range versions { + for i, tt := range cases { + testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) + t.Run(testName, func(t *testing.T) { + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(tt.pc)) + state := goVm.GetState() + const rsReg = 8 // t0 + insn := tt.opcode<<26 | rsReg<<21 | tt.regimm<<16 | uint32(tt.offset) + testutil.StoreInstruction(state.GetMemory(), tt.pc, insn) + state.GetRegistersRef()[rsReg] = Word(tt.rs) + step := state.GetStep() + + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.Step += 1 + expected.PC = state.GetCpu().NextPC + expected.NextPC = tt.expectNextPC + if tt.expectLink { + expected.Registers[31] = state.GetPC() + 8 + } + + 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) + }) + } + } +} + +type testMTStoreOpsClearMemReservationTestCase struct { + // name is the test name + name string + // opcode is the instruction opcode + opcode uint32 + // offset is the immediate offset encoded in the instruction + offset uint32 + // base is the base/rs register prestate + base Word + // effAddr is the address used to set the prestate preMem value. It is also used as the base LLAddress that can be adjusted reservation assertions + effAddr Word + // premem is the prestate value of the word located at effrAddr + preMem Word + // postMem is the expected post-state value of the word located at effAddr + postMem Word +} + +func testMTStoreOpsClearMemReservation(t *testing.T, cases []testMTStoreOpsClearMemReservationTestCase) { + llVariations := []struct { + name string + llReservationStatus multithreaded.LLReservationStatus + matchThreadId bool + effAddrOffset Word + shouldClearReservation bool + }{ + {name: "matching reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, shouldClearReservation: true}, + {name: "matching reservation, unaligned", llReservationStatus: multithreaded.LLStatusActive32bit, effAddrOffset: 1, matchThreadId: true, shouldClearReservation: true}, + {name: "matching reservation, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, shouldClearReservation: true}, + {name: "matching reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, shouldClearReservation: true}, + {name: "matching reservation, diff thread, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: false, shouldClearReservation: true}, + {name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false}, + {name: "mismatched reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, effAddrOffset: 8, shouldClearReservation: false}, + {name: "no reservation, matching addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, shouldClearReservation: true}, + {name: "no reservation, mismatched addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false}, + } + + rt := Word(0x12_34_56_78) + //rt := Word(0x12_34_56_78_12_34_56_78) + baseReg := uint32(5) + rtReg := uint32(6) + for i, c := range cases { + for _, v := range llVariations { + tName := fmt.Sprintf("%v (%v)", c.name, v.name) + t.Run(tName, func(t *testing.T) { + t.Parallel() + insn := uint32((c.opcode << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) + goVm, state, contracts := setup(t, i, nil, testutil.WithPCAndNextPC(0x08)) + step := state.GetStep() + + // Define LL-related params + llAddress := c.effAddr + v.effAddrOffset + llOwnerThread := state.GetCurrentThread().ThreadId + if !v.matchThreadId { + llOwnerThread += 1 + } + + // Setup state + state.GetRegistersRef()[rtReg] = rt + state.GetRegistersRef()[baseReg] = c.base + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetMemory().SetWord(c.effAddr, c.preMem) + state.LLReservationStatus = v.llReservationStatus + state.LLAddress = llAddress + state.LLOwnerThread = llOwnerThread + + // Setup expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ExpectMemoryWordWrite(c.effAddr, c.postMem) + if v.shouldClearReservation { + expected.LLReservationStatus = multithreaded.LLStatusNone + expected.LLAddress = 0 + expected.LLOwnerThread = 0 + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) + }) + } + } +} + +type testMTSysReadPreimageTestCase struct { + name string + addr Word + count Word + writeLen Word + preimageOffset Word + prestateMem Word + postateMem Word + shouldPanic bool +} + +func testMTSysReadPreimage(t *testing.T, preimageValue []byte, cases []testMTSysReadPreimageTestCase) { + llVariations := []struct { + name string + llReservationStatus multithreaded.LLReservationStatus + matchThreadId bool + effAddrOffset Word + shouldClearReservation bool + }{ + {name: "matching reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, shouldClearReservation: true}, + {name: "matching reservation, unaligned", llReservationStatus: multithreaded.LLStatusActive32bit, effAddrOffset: 1, matchThreadId: true, shouldClearReservation: true}, + {name: "matching reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, shouldClearReservation: true}, + {name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false}, + {name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: false, effAddrOffset: 8, shouldClearReservation: false}, + {name: "no reservation, matching addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, shouldClearReservation: true}, + {name: "no reservation, mismatched addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false}, + } + + for i, c := range cases { + for _, v := range llVariations { + tName := fmt.Sprintf("%v (%v)", c.name, v.name) + t.Run(tName, func(t *testing.T) { + t.Parallel() + effAddr := arch.AddressMask & c.addr + preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageValue)).PreimageKey() + oracle := testutil.StaticOracle(t, preimageValue) + goVm, state, contracts := setup(t, i, oracle) + step := state.GetStep() + + // Define LL-related params + llAddress := effAddr + v.effAddrOffset + llOwnerThread := state.GetCurrentThread().ThreadId + if !v.matchThreadId { + llOwnerThread += 1 + } + + // Set up state + state.PreimageKey = preimageKey + state.PreimageOffset = c.preimageOffset + state.GetRegistersRef()[2] = arch.SysRead + state.GetRegistersRef()[4] = exec.FdPreimageRead + state.GetRegistersRef()[5] = c.addr + state.GetRegistersRef()[6] = c.count + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) + state.LLReservationStatus = v.llReservationStatus + state.LLAddress = llAddress + state.LLOwnerThread = llOwnerThread + state.GetMemory().SetWord(effAddr, c.prestateMem) + + // Setup expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ActiveThread().Registers[2] = c.writeLen + expected.ActiveThread().Registers[7] = 0 // no error + expected.PreimageOffset += c.writeLen + expected.ExpectMemoryWordWrite(effAddr, c.postateMem) + if v.shouldClearReservation { + expected.LLReservationStatus = multithreaded.LLStatusNone + expected.LLAddress = 0 + expected.LLOwnerThread = 0 + } + + if c.shouldPanic { + require.Panics(t, func() { _, _ = goVm.Step(true) }) + testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, contracts) + } else { + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) + } + }) + } + } +} + +func testNoopSyscall(t *testing.T, syscalls map[string]uint32) { + for noopName, noopVal := range syscalls { + t.Run(noopName, func(t *testing.T) { + t.Parallel() + goVm, state, contracts := setup(t, int(noopVal), nil) + + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = Word(noopVal) // Set syscall number + step := state.Step + + // Set up post-state expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ActiveThread().Registers[2] = 0 + expected.ActiveThread().Registers[7] = 0 + + // State transition + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) + }) + } +} + +func testUnsupportedSyscall(t *testing.T, unsupportedSyscalls []uint32) { + for i, syscallNum := range unsupportedSyscalls { + testName := fmt.Sprintf("Unsupported syscallNum %v", syscallNum) + i := i + syscallNum := syscallNum + t.Run(testName, func(t *testing.T) { + t.Parallel() + goVm, state, contracts := setup(t, i*3434, nil) + // Setup basic getThreadId syscall instruction + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = Word(syscallNum) + proofData := multiThreadedProofGenerator(t, state) + // Set up post-state expectations + require.Panics(t, func() { _, _ = goVm.Step(true) }) + + errorMessage := "unimplemented syscall" + testutil.AssertEVMReverts(t, state, contracts, nil, proofData, testutil.CreateErrorStringMatcher(errorMessage)) + }) + } +} + +// signExtend64 is used to sign-extend 32-bit words for 64-bit compatibility +func signExtend64(w Word) Word { + if arch.IsMips32 { + return w + } else { + return exec.SignExtend(w, 32) + } +} + +const seed = 0xdead + +var rand = testutil.NewRandHelper(seed) + +// randomizeUpperWord is used to assert that 32-bit operations use the lower word only +func randomizeUpperWord(w Word) Word { + if arch.IsMips32 { + return w + } else { + if w>>32 == 0x0 { // nolint:staticcheck + rnd := rand.Uint32() + mask := (uint64(rnd) << 32) | 0xFF_FF_FF_FF + return Word((uint64(w) | (0xFF_FF_FF_FF << 32)) & mask) + } else { + return w + } + } +} diff --git a/cannon/mipsevm/testutil/arch.go b/cannon/mipsevm/testutil/arch.go index a69896c041e5..b4eb50b7e3f1 100644 --- a/cannon/mipsevm/testutil/arch.go +++ b/cannon/mipsevm/testutil/arch.go @@ -4,6 +4,7 @@ package testutil import ( "bytes" + "testing" "github.com/stretchr/testify/require" @@ -46,3 +47,10 @@ func SetMemoryUint64(t require.TestingT, mem *memory.Memory, addr Word, value ui func ToSignedInteger(x Word) arch.SignedInteger { return arch.SignedInteger(x) } + +// Cannon32OnlyTest skips the test if it's not a cannon64 build +func Cannon32OnlyTest(t testing.TB, msg string, args ...any) { + if !arch.IsMips32 { + t.Skipf(msg, args...) + } +}