Commit 96ef7bb4 authored by Inphi's avatar Inphi Committed by GitHub

cannon: Implement 64-bit Solidity VM (#12665)

* cannon: Implement MIPS64Memory.sol

* cannon: Implement 64-bit Solidity VM

- Implements 64-bit Cannon (with multithreading) in MIPS64.sol
- Re-enable differential testing for 64-bit VMs

* review comments

* check pc for 4-byte alignment

* gofmt

* update snapshot

* address nits; add more add/sub/mult overflow tests

* diff test misaligned instruction

* fix mul[t] MIPS64.sol emulation

* diff fuzz mul operations

* fix addiu test case

* fix GetInstruction return value type
parent d6bda033
......@@ -56,9 +56,12 @@ sanitize-program:
contract:
cd ../packages/contracts-bedrock && forge build
test: elf contract
test: elf contract test64
go test -v ./...
test64: elf contract
go test -tags=cannon64 -run '(TestEVM.*64|TestHelloEVM|TestClaimEVM)' ./mipsevm/tests
diff-%-cannon: cannon elf
$$OTHER_CANNON load-elf --type $* --path ./testdata/example/bin/hello.elf --out ./bin/prestate-other.bin.gz --meta ""
./bin/cannon load-elf --type $* --path ./testdata/example/bin/hello.elf --out ./bin/prestate.bin.gz --meta ""
......@@ -96,6 +99,10 @@ fuzz:
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallCloneST ./mipsevm/tests
# Multi-threaded tests
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallCloneMT ./mipsevm/tests
# 64-bit tests - increased fuzztime for a larger input space
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
.PHONY: \
cannon32-impl \
......
......@@ -3,6 +3,7 @@ package exec
import (
"fmt"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
......@@ -37,7 +38,7 @@ func (m *MemoryTrackerImpl) TrackMemAccess(effAddr Word) {
// TrackMemAccess2 creates a proof for a memory access following a call to TrackMemAccess
// This is used to generate proofs for contiguous memory accesses within the same step
func (m *MemoryTrackerImpl) TrackMemAccess2(effAddr Word) {
if m.memProofEnabled && m.lastMemAccess+4 != effAddr {
if m.memProofEnabled && m.lastMemAccess+arch.WordSizeBytes != effAddr {
panic(fmt.Errorf("unexpected disjointed mem access at %08x, last memory access is at %08x buffered", effAddr, m.lastMemAccess))
}
m.lastMemAccess = effAddr
......
......@@ -378,7 +378,7 @@ func ExecuteMipsInstruction(insn uint32, opcode uint32, fun uint32, rs, rt, mem
w := uint32(SelectSubWord(rs, mem, 4, false))
val := w >> (24 - (rs&3)*8)
mask := uint32(0xFFFFFFFF) >> (24 - (rs&3)*8)
lwrResult := ((uint32(rt) & ^mask) | val) & 0xFFFFFFFF
lwrResult := (uint32(rt) & ^mask) | val
if rs&3 == 3 { // loaded bit 31
return SignExtend(Word(lwrResult), 32)
} else {
......@@ -539,13 +539,13 @@ func HandleHiLo(cpu *mipsevm.CpuScalars, registers *[32]Word, fun uint32, rs Wor
cpu.HI = SignExtend(Word(acc>>32), 32)
cpu.LO = SignExtend(Word(uint32(acc)), 32)
case 0x1a: // div
if rt == 0 {
if uint32(rt) == 0 {
panic("instruction divide by zero")
}
cpu.HI = SignExtend(Word(int32(rs)%int32(rt)), 32)
cpu.LO = SignExtend(Word(int32(rs)/int32(rt)), 32)
case 0x1b: // divu
if rt == 0 {
if uint32(rt) == 0 {
panic("instruction divide by zero")
}
cpu.HI = SignExtend(Word(uint32(rs)%uint32(rt)), 32)
......
......@@ -10,7 +10,6 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/stretchr/testify/require"
)
......@@ -134,12 +133,12 @@ func TestEVMSingleStep_Operators64(t *testing.T) {
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
}
func TestEVMSingleStep_Shift(t *testing.T) {
func TestEVMSingleStep_Shift64(t *testing.T) {
cases := []struct {
name string
rd Word
......@@ -190,7 +189,8 @@ func TestEVMSingleStep_Shift(t *testing.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))
pc := Word(0x0)
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(pc))
state := goVm.GetState()
var insn uint32
var rtReg uint32
......@@ -200,7 +200,7 @@ func TestEVMSingleStep_Shift(t *testing.T) {
insn = rtReg<<16 | rdReg<<11 | tt.sa<<6 | tt.funct
state.GetRegistersRef()[rdReg] = tt.rd
state.GetRegistersRef()[rtReg] = tt.rt
testutil.StoreInstruction(state.GetMemory(), 0, insn)
testutil.StoreInstruction(state.GetMemory(), pc, insn)
step := state.GetStep()
// Setup expectations
......@@ -213,7 +213,7 @@ func TestEVMSingleStep_Shift(t *testing.T) {
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
}
......@@ -455,12 +455,12 @@ func TestEVMSingleStep_LoadStore64(t *testing.T) {
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
}
func TestEVMSingleStep_DivMult(t *testing.T) {
func TestEVMSingleStep_DivMult64(t *testing.T) {
cases := []struct {
name string
rs Word
......@@ -470,6 +470,14 @@ func TestEVMSingleStep_DivMult(t *testing.T) {
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)},
// dmult s1, s2
// expected hi,lo were verified using qemu-mips
{name: "dmult 0", funct: 0x1c, rs: 0, rt: 0, expectLo: 0, expectHi: 0},
......@@ -530,6 +538,10 @@ func TestEVMSingleStep_DivMult(t *testing.T) {
{name: "ddivu", funct: 0x1f, rs: ^Word(0), rt: ^Word(0), expectLo: 1, expectHi: 0},
{name: "ddivu", funct: 0x1f, rs: ^Word(0), rt: 2, expectLo: 0x7F_FF_FF_FF_FF_FF_FF_FF, expectHi: 1},
{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"},
}
v := GetMultiThreadedTestCase(t)
......@@ -560,15 +572,13 @@ func TestEVMSingleStep_DivMult(t *testing.T) {
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
}
})
}
}
func TestEVMSingleStepBranch64(t *testing.T) {
var tracer *tracing.Hooks
func TestEVMSingleStep_Branch64(t *testing.T) {
versions := GetMipsVersionTestCases(t)
cases := []struct {
name string
......@@ -651,7 +661,7 @@ func TestEVMSingleStepBranch64(t *testing.T) {
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
}
......
This diff is collapsed.
......@@ -8,7 +8,6 @@ import (
"fmt"
"testing"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
......@@ -18,8 +17,6 @@ import (
)
func TestEVM_MT64_LL(t *testing.T) {
var tracer *tracing.Hooks
memVal := Word(0x11223344_55667788)
memValNeg := Word(0xF1223344_F5667788)
cases := []struct {
......@@ -84,15 +81,13 @@ func TestEVM_MT64_LL(t *testing.T) {
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts)
})
}
}
}
func TestEVM_MT64_SC(t *testing.T) {
var tracer *tracing.Hooks
llVariations := []struct {
name string
llReservationStatus multithreaded.LLReservationStatus
......@@ -187,15 +182,13 @@ func TestEVM_MT64_SC(t *testing.T) {
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts)
})
}
}
}
func TestEVM_MT64_LLD(t *testing.T) {
var tracer *tracing.Hooks
memVal := Word(0x11223344_55667788)
memValNeg := Word(0xF1223344_F5667788)
cases := []struct {
......@@ -260,15 +253,13 @@ func TestEVM_MT64_LLD(t *testing.T) {
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts)
})
}
}
}
func TestEVM_MT64_SCD(t *testing.T) {
var tracer *tracing.Hooks
value := Word(0x11223344_55667788)
llVariations := []struct {
name string
......@@ -364,7 +355,7 @@ func TestEVM_MT64_SCD(t *testing.T) {
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts)
})
}
}
......
......@@ -5,7 +5,6 @@ import (
"os"
"testing"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
......@@ -17,8 +16,6 @@ import (
)
func TestEVM_LL(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
base Word
......@@ -61,14 +58,12 @@ func TestEVM_LL(t *testing.T) {
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
}
func TestEVM_SC(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
base Word
......@@ -115,14 +110,12 @@ func TestEVM_SC(t *testing.T) {
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
}
func TestEVM_SysRead_Preimage(t *testing.T) {
var tracer *tracing.Hooks
preimageValue := make([]byte, 0, 8)
preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x12_34_56_78)
preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x98_76_54_32)
......@@ -183,14 +176,14 @@ func TestEVM_SysRead_Preimage(t *testing.T) {
if c.shouldPanic {
require.Panics(t, func() { _, _ = goVm.Step(true) })
testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, v.Contracts, tracer)
testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, v.Contracts)
} else {
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, tracer)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
}
})
}
......
//go:build cannon64
// +build cannon64
package tests
import (
"os"
"testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
"github.com/stretchr/testify/require"
)
func FuzzStateConsistencyMulOp(f *testing.F) {
f.Add(int64(0x80_00_00_00), int64(0x80_00_00_00), int64(1))
f.Add(
testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_11_22_33_44)),
testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_11_22_33_44)),
int64(1),
)
f.Add(
testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_80_00_00_00)),
testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_80_00_00_00)),
int64(1),
)
f.Add(
testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_FF_FF_FF_FF)),
testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_FF_FF_FF_FF)),
int64(1),
)
const opcode uint32 = 28
const mulFunct uint32 = 0x2
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, rs int64, rt int64, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
mulOpConsistencyCheck(t, versions, opcode, true, mulFunct, Word(rs), Word(rt), seed)
})
}
})
}
func FuzzStateConsistencyMultOp(f *testing.F) {
f.Add(int64(0x80_00_00_00), int64(0x80_00_00_00), int64(1))
f.Add(
testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_11_22_33_44)),
testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_11_22_33_44)),
int64(1),
)
f.Add(
testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_80_00_00_00)),
testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_80_00_00_00)),
int64(1),
)
f.Add(
testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_FF_FF_FF_FF)),
testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_FF_FF_FF_FF)),
int64(1),
)
const multFunct uint32 = 0x18
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, rs int64, rt int64, seed int64) {
mulOpConsistencyCheck(t, versions, 0, false, multFunct, Word(rs), Word(rt), seed)
})
}
func FuzzStateConsistencyMultuOp(f *testing.F) {
f.Add(uint64(0x80_00_00_00), uint64(0x80_00_00_00), int64(1))
f.Add(
uint64(0xFF_FF_FF_FF_11_22_33_44),
uint64(0xFF_FF_FF_FF_11_22_33_44),
int64(1),
)
f.Add(
uint64(0xFF_FF_FF_FF_80_00_00_00),
uint64(0xFF_FF_FF_FF_80_00_00_00),
int64(1),
)
f.Add(
uint64(0xFF_FF_FF_FF_FF_FF_FF_FF),
uint64(0xFF_FF_FF_FF_FF_FF_FF_FF),
int64(1),
)
const multuFunct uint32 = 0x19
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, rs uint64, rt uint64, seed int64) {
mulOpConsistencyCheck(t, versions, 0, false, multuFunct, rs, rt, seed)
})
}
type insn struct {
opcode uint32
expectRdReg bool
funct uint32
}
func mulOpConsistencyCheck(
t *testing.T, versions []VersionedVMTestCase,
opcode uint32, expectRdReg bool, funct uint32,
rs Word, rt Word, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
rsReg := uint32(17)
rtReg := uint32(18)
rdReg := uint32(0)
if expectRdReg {
rdReg = 19
}
insn := opcode<<26 | rsReg<<21 | rtReg<<16 | rdReg<<11 | funct
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(seed), testutil.WithPCAndNextPC(0))
state := goVm.GetState()
state.GetRegistersRef()[rsReg] = rs
state.GetRegistersRef()[rtReg] = rt
testutil.StoreInstruction(state.GetMemory(), 0, insn)
step := state.GetStep()
// mere sanity checks
expected := testutil.NewExpectedState(state)
expected.ExpectStep()
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// use the post-state rdReg or LO and HI just so we can run sanity checks
if expectRdReg {
expected.Registers[rdReg] = state.GetRegistersRef()[rdReg]
} else {
expected.LO = state.GetCpu().LO
expected.HI = state.GetCpu().HI
}
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
}
......@@ -42,7 +42,7 @@ func FuzzStateSyscallBrk(f *testing.F) {
require.False(t, stepWitness.HasPreimage())
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
})
......@@ -97,7 +97,7 @@ func FuzzStateSyscallMmap(f *testing.F) {
require.False(t, stepWitness.HasPreimage())
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
})
......@@ -126,7 +126,7 @@ func FuzzStateSyscallExitGroup(f *testing.F) {
require.False(t, stepWitness.HasPreimage())
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
})
......@@ -182,7 +182,7 @@ func FuzzStateSyscallFcntl(f *testing.F) {
require.False(t, stepWitness.HasPreimage())
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
})
......@@ -219,7 +219,7 @@ func FuzzStateHintRead(f *testing.F) {
require.False(t, stepWitness.HasPreimage())
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
})
......@@ -282,7 +282,7 @@ func FuzzStatePreimageRead(f *testing.F) {
require.True(t, stepWitness.HasPreimage())
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
})
......@@ -364,7 +364,7 @@ func FuzzStateHintWrite(f *testing.F) {
// Validate
require.Equal(t, expectedHints, oracle.Hints())
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
})
......@@ -424,7 +424,7 @@ func FuzzStatePreimageWrite(f *testing.F) {
require.False(t, stepWitness.HasPreimage())
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
})
......
......@@ -62,6 +62,6 @@ func FuzzStateSyscallCloneMT(f *testing.F) {
require.False(t, stepWitness.HasPreimage())
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), v.Contracts)
})
}
......@@ -7,6 +7,7 @@ import (
"math/big"
"os"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/op-chain-ops/srcmap"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
......@@ -66,7 +67,11 @@ func loadArtifacts(version MipsVersion) (*Artifacts, error) {
case MipsSingleThreaded:
mips, err = artifactFS.ReadArtifact("MIPS.sol", "MIPS")
case MipsMultithreaded:
mips, err = artifactFS.ReadArtifact("MIPS2.sol", "MIPS2")
if arch.IsMips32 {
mips, err = artifactFS.ReadArtifact("MIPS2.sol", "MIPS2")
} else {
mips, err = artifactFS.ReadArtifact("MIPS64.sol", "MIPS64")
}
default:
return nil, fmt.Errorf("Unknown MipsVersion supplied: %v", version)
}
......@@ -167,7 +172,11 @@ func SourceMapTracer(t require.TestingT, version MipsVersion, mips *foundry.Arti
case MipsSingleThreaded:
mipsSrcMap, err = srcFS.SourceMap(mips, "MIPS")
case MipsMultithreaded:
mipsSrcMap, err = srcFS.SourceMap(mips, "MIPS2")
if arch.IsMips32 {
mipsSrcMap, err = srcFS.SourceMap(mips, "MIPS2")
} else {
mipsSrcMap, err = srcFS.SourceMap(mips, "MIPS64")
}
default:
require.Fail(t, "invalid mips version")
}
......
......@@ -34,5 +34,5 @@ func GetInstruction(mem *memory.Memory, pc Word) uint32 {
if pc&0x3 != 0 {
panic(fmt.Errorf("unaligned memory access: %x", pc))
}
return exec.LoadSubWord(mem, pc, 4, false, new(exec.NoopMemoryTracker))
return uint32(exec.LoadSubWord(mem, pc, 4, false, new(exec.NoopMemoryTracker)))
}
......@@ -14,6 +14,7 @@ import (
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
......@@ -22,6 +23,9 @@ import (
preimage "github.com/ethereum-optimism/optimism/op-preimage"
)
// maxStepGas should be less than the L1 gas limit
const maxStepGas = 20_000_000
type MIPSEVM struct {
sender vm.AccountRef
startingGas uint64
......@@ -36,11 +40,29 @@ type MIPSEVM struct {
lastPreimageOracleInput []byte
}
func NewMIPSEVM(contracts *ContractMetadata) *MIPSEVM {
func NewMIPSEVM(contracts *ContractMetadata, opts ...evmOption) *MIPSEVM {
env, evmState := NewEVMEnv(contracts)
sender := vm.AccountRef{0x13, 0x37}
startingGas := uint64(30_000_000)
return &MIPSEVM{sender, startingGas, env, evmState, contracts.Addresses, nil, contracts.Artifacts, math.MaxUint64, nil, nil}
startingGas := uint64(maxStepGas)
evm := &MIPSEVM{sender, startingGas, env, evmState, contracts.Addresses, nil, contracts.Artifacts, math.MaxUint64, nil, nil}
for _, opt := range opts {
opt(evm)
}
return evm
}
type evmOption func(c *MIPSEVM)
func WithSourceMapTracer(t *testing.T, ver MipsVersion) evmOption {
return func(evm *MIPSEVM) {
evm.SetSourceMapTracer(t, ver)
}
}
func WithTracingHooks(tracer *tracing.Hooks) evmOption {
return func(evm *MIPSEVM) {
evm.SetTracer(tracer)
}
}
func (m *MIPSEVM) SetTracer(tracer *tracing.Hooks) {
......@@ -171,15 +193,8 @@ func LogStepFailureAtCleanup(t *testing.T, mipsEvm *MIPSEVM) {
}
// ValidateEVM runs a single evm step and validates against an FPVM poststate
func ValidateEVM(t *testing.T, stepWitness *mipsevm.StepWitness, step uint64, goVm mipsevm.FPVM, hashFn mipsevm.HashFn, contracts *ContractMetadata, tracer *tracing.Hooks) {
if !arch.IsMips32 {
// TODO(#12250) Re-enable EVM validation once 64-bit MIPS contracts are completed
t.Logf("WARNING: Skipping EVM validation for 64-bit MIPS")
return
}
evm := NewMIPSEVM(contracts)
evm.SetTracer(tracer)
func ValidateEVM(t *testing.T, stepWitness *mipsevm.StepWitness, step uint64, goVm mipsevm.FPVM, hashFn mipsevm.HashFn, contracts *ContractMetadata, opts ...evmOption) {
evm := NewMIPSEVM(contracts, opts...)
LogStepFailureAtCleanup(t, evm)
evmPost := evm.Step(t, stepWitness, step, hashFn)
......@@ -188,15 +203,39 @@ func ValidateEVM(t *testing.T, stepWitness *mipsevm.StepWitness, step uint64, go
"mipsevm produced different state than EVM")
}
type ErrMatcher func(*testing.T, []byte)
func CreateNoopErrorMatcher() ErrMatcher {
return func(t *testing.T, ret []byte) {}
}
// CreateErrorStringMatcher matches an Error(string)
func CreateErrorStringMatcher(expect string) ErrMatcher {
return func(t *testing.T, ret []byte) {
require.Greaterf(t, len(ret), 4, "Return data length should be greater than 4 bytes: %x", ret)
unpacked, decodeErr := abi.UnpackRevert(ret)
require.NoError(t, decodeErr, "Failed to unpack revert reason")
require.Contains(t, unpacked, expect, "Revert reason mismatch")
}
}
// CreateCustomErrorMatcher matches a custom error given the error signature
func CreateCustomErrorMatcher(sig string) ErrMatcher {
return func(t *testing.T, ret []byte) {
expect := crypto.Keccak256([]byte(sig))[:4]
require.EqualValuesf(t, expect, ret, "return value is %x", ret)
}
}
// AssertEVMReverts runs a single evm step from an FPVM prestate and asserts that the VM panics
func AssertEVMReverts(t *testing.T, state mipsevm.FPVMState, contracts *ContractMetadata, tracer *tracing.Hooks, ProofData []byte, expectedReason string) {
func AssertEVMReverts(t *testing.T, state mipsevm.FPVMState, contracts *ContractMetadata, tracer *tracing.Hooks, ProofData []byte, matcher ErrMatcher) {
encodedWitness, _ := state.EncodeWitness()
stepWitness := &mipsevm.StepWitness{
State: encodedWitness,
ProofData: ProofData,
}
input := EncodeStepInput(t, stepWitness, mipsevm.LocalContext{}, contracts.Artifacts.MIPS)
startingGas := uint64(30_000_000)
startingGas := uint64(maxStepGas)
env, evmState := NewEVMEnv(contracts)
env.Config.Tracer = tracer
......@@ -204,18 +243,14 @@ func AssertEVMReverts(t *testing.T, state mipsevm.FPVMState, contracts *Contract
ret, _, err := env.Call(vm.AccountRef(sender), contracts.Addresses.MIPS, input, startingGas, common.U2560)
require.EqualValues(t, err, vm.ErrExecutionReverted)
require.Greater(t, len(ret), 4, "Return data length should be greater than 4 bytes")
unpacked, decodeErr := abi.UnpackRevert(ret)
require.NoError(t, decodeErr, "Failed to unpack revert reason")
require.Equal(t, expectedReason, unpacked, "Revert reason mismatch")
matcher(t, ret)
logs := evmState.Logs()
require.Equal(t, 0, len(logs))
}
func AssertPreimageOracleReverts(t *testing.T, preimageKey [32]byte, preimageValue []byte, preimageOffset arch.Word, contracts *ContractMetadata, tracer *tracing.Hooks) {
evm := NewMIPSEVM(contracts)
evm.SetTracer(tracer)
func AssertPreimageOracleReverts(t *testing.T, preimageKey [32]byte, preimageValue []byte, preimageOffset arch.Word, contracts *ContractMetadata, opts ...evmOption) {
evm := NewMIPSEVM(contracts, opts...)
LogStepFailureAtCleanup(t, evm)
evm.assertPreimageOracleReverts(t, preimageKey, preimageValue, preimageOffset)
......
......@@ -238,6 +238,16 @@ func (s *SourceMapTracer) info(codeAddr common.Address, pc uint64) string {
func (s *SourceMapTracer) OnOpCode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
op := vm.OpCode(opcode)
var top []string
stk := scope.StackData()
for i := len(stk) - 1; i >= 0; i-- {
top = append(top, stk[i].Hex())
if len(top) == 4 {
break
}
}
stkInfo := fmt.Sprintf("[%s]", strings.Join(top, ", "))
if op.IsPush() {
var val []byte
sc, ok := scope.(*vm.ScopeContext)
......@@ -248,10 +258,10 @@ func (s *SourceMapTracer) OnOpCode(pc uint64, opcode byte, gas, cost uint64, sco
} else {
val = []byte("N/A")
}
fmt.Fprintf(s.out, "%-40s : pc %x opcode %s (%x)\n", s.info(scope.Address(), pc), pc, op.String(), val)
fmt.Fprintf(s.out, "%-40s : pc %x opcode %s (%x) \t| stk[:%d] %s\n", s.info(scope.Address(), pc), pc, op.String(), val, len(top), stkInfo)
return
}
fmt.Fprintf(s.out, "%-40s : pc %x opcode %s\n", s.info(scope.Address(), pc), pc, op.String())
fmt.Fprintf(s.out, "%-40s : pc %x opcode %s \t\t| stk[:%d] %s\n", s.info(scope.Address(), pc), pc, op.String(), len(top), stkInfo)
}
func (s *SourceMapTracer) OnFault(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) {
......
......@@ -147,6 +147,10 @@
"initCodeHash": "0xaedf0d0b0e94a0c5e7d987331d2fdba84230f5704a6ca33677e70cde7051b17e",
"sourceCodeHash": "0x9fa2d1297ad1e93b4d3c5c0fed08bedcd8f746807589f0fd3369e79347c6a027"
},
"src/cannon/MIPS64.sol": {
"initCodeHash": "0x47c2bdd1e6fbb4941caa20a2ba5c2a66de198a8c7b540a9d4a0d84dbe8d3bfea",
"sourceCodeHash": "0xdb7f8a92ed552a2720f5fe3c0a32e4069026f0b23933145493ead88403206814"
},
"src/cannon/PreimageOracle.sol": {
"initCodeHash": "0x5d7e8ae64f802bd9d760e3d52c0a620bd02405dc2c8795818db9183792ffe81c",
"sourceCodeHash": "0x979d8595d925c70a123e72c062fa58c9ef94777c2e93b6bc3231d6679e2e9055"
......
[
{
"inputs": [
{
"internalType": "contract IPreimageOracle",
"name": "_oracle",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "oracle",
"outputs": [
{
"internalType": "contract IPreimageOracle",
"name": "oracle_",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_stateData",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "_proof",
"type": "bytes"
},
{
"internalType": "bytes32",
"name": "_localContext",
"type": "bytes32"
}
],
"name": "step",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "InvalidExitedValue",
"type": "error"
},
{
"inputs": [],
"name": "InvalidMemoryProof",
"type": "error"
},
{
"inputs": [],
"name": "InvalidPC",
"type": "error"
},
{
"inputs": [],
"name": "InvalidRMWInstruction",
"type": "error"
},
{
"inputs": [],
"name": "InvalidSecondMemoryProof",
"type": "error"
}
]
\ No newline at end of file
This diff is collapsed.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
library MIPS64Arch {
uint64 internal constant WORD_SIZE = 64;
uint64 internal constant WORD_SIZE_BYTES = 8;
uint64 internal constant EXT_MASK = 0x7;
uint64 internal constant ADDRESS_MASK = 0xFFFFFFFFFFFFFFF8;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { InvalidExitedValue } from "src/cannon/libraries/CannonErrors.sol";
library MIPS64State {
struct CpuScalars {
uint64 pc;
uint64 nextPC;
uint64 lo;
uint64 hi;
}
function assertExitedIsValid(uint32 _exited) internal pure {
if (_exited > 1) {
revert InvalidExitedValue();
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment