Commit 5212c4db authored by mbaxter's avatar mbaxter Committed by GitHub

cannon: Run common evm tests across all implementations (#11333)

* cannon: Prep test utils to handle Mips2.sol

* cannon: Add metadata struct to hold all contract-related metadata

* cannon: Add forge debug test for mips2

* cannon: Fix path to mips2 artifacts in testutil

* cannon: Rework evm tests to run across both cannon impls

* cannon: Skip failing test for now, add todo

* cannon: Rename FPVMState.GetRegisters to GetRegistersMutable

* cannon: Run linter

* cannon: Fix skipped claim test

* cannon: Rename FPVMState registers getter to follow convention

* cannon: Rename cpu getter to match naming convention

* cannon: Fix bad merge - elf paths, versioned references
parent 7c83398b
......@@ -29,8 +29,8 @@ test: elf contract
go test -v ./...
fuzz:
# Common vm tests
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallBrk ./mipsevm/tests
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallClone ./mipsevm/tests
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallMmap ./mipsevm/tests
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallExitGroup ./mipsevm/tests
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallFcntl ./mipsevm/tests
......@@ -38,6 +38,10 @@ fuzz:
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageRead ./mipsevm/tests
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintWrite ./mipsevm/tests
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageWrite ./mipsevm/tests
# Single-threaded tests
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
.PHONY: \
cannon \
......
......@@ -2,6 +2,7 @@ package mipsevm
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
......@@ -9,11 +10,23 @@ import (
type FPVMState interface {
GetMemory() *memory.Memory
// GetHeap returns the current memory address at the top of the heap
GetHeap() uint32
// GetPreimageKey returns the most recently accessed preimage key
GetPreimageKey() common.Hash
// GetPreimageOffset returns the current offset into the current preimage
GetPreimageOffset() uint32
// GetPC returns the currently executing program counter
GetPC() uint32
// GetRegisters returns the currently active registers
GetRegisters() *[32]uint32
// GetCpu returns the currently active cpu scalars, including the program counter
GetCpu() CpuScalars
// GetRegistersRef returns a pointer to the currently active registers
GetRegistersRef() *[32]uint32
// GetStep returns the current VM step
GetStep() uint64
......@@ -24,6 +37,16 @@ type FPVMState interface {
// GetExitCode returns the exit code
GetExitCode() uint8
// GetLastHint returns optional metadata which is not part of the VM state itself.
// It is used to remember the last pre-image hint,
// so a VM can start from any state without fetching prior pre-images,
// and instead just repeat the last hint on setup,
// to make sure pre-image requests can be served.
// The first 4 bytes are a uin32 length prefix.
// Warning: the hint MAY NOT BE COMPLETE. I.e. this is buffered,
// and should only be read when len(LastHint) > 4 && uint32(LastHint[:4]) <= len(LastHint[4:])
GetLastHint() hexutil.Bytes
// EncodeWitness returns the witness for the current state and the state hash
EncodeWitness() (witness []byte, hash common.Hash)
}
......
......@@ -14,9 +14,9 @@ import (
)
func (m *InstrumentedState) handleSyscall() error {
thread := m.state.getCurrentThread()
thread := m.state.GetCurrentThread()
syscallNum, a0, a1, a2, a3 := exec.GetSyscallArgs(m.state.GetRegisters())
syscallNum, a0, a1, a2, a3 := exec.GetSyscallArgs(m.state.GetRegistersRef())
v0 := uint32(0)
v1 := uint32(0)
......@@ -188,7 +188,7 @@ func (m *InstrumentedState) mipsStep() error {
return nil
}
m.state.Step += 1
thread := m.state.getCurrentThread()
thread := m.state.GetCurrentThread()
// During wakeup traversal, search for the first thread blocked on the wakeup address.
// Don't allow regular execution until we have found such a thread or else we have visited all threads.
......@@ -264,7 +264,7 @@ func (m *InstrumentedState) mipsStep() error {
}
// Exec the rest of the step logic
return exec.ExecMipsCoreStepLogic(m.state.getCpu(), m.state.GetRegisters(), m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker)
return exec.ExecMipsCoreStepLogic(m.state.getCpuRef(), m.state.GetRegistersRef(), m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker)
}
func (m *InstrumentedState) onWaitComplete(thread *ThreadState, isTimedOut bool) {
......
......@@ -53,7 +53,7 @@ func (t *ThreadedStackTrackerImpl) Traceback() {
}
func (t *ThreadedStackTrackerImpl) getCurrentTracker() exec.TraceableStackTracker {
thread := t.state.getCurrentThread()
thread := t.state.GetCurrentThread()
tracker, exists := t.trackersByThreadId[thread.ThreadId]
if !exists {
tracker = exec.NewStackTrackerUnsafe(t.state, t.meta)
......
......@@ -52,13 +52,6 @@ type State struct {
NextThreadId uint32 `json:"nextThreadId"`
// LastHint is optional metadata, and not part of the VM state itself.
// It is used to remember the last pre-image hint,
// so a VM can start from any state without fetching prior pre-images,
// and instead just repeat the last hint on setup,
// to make sure pre-image requests can be served.
// The first 4 bytes are a uin32 length prefix.
// Warning: the hint MAY NOT BE COMPLETE. I.e. this is buffered,
// and should only be read when len(LastHint) > 4 && uint32(LastHint[:4]) <= len(LastHint[4:])
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
}
......@@ -83,7 +76,7 @@ func CreateEmptyState() *State {
func CreateInitialState(pc, heapStart uint32) *State {
state := CreateEmptyState()
currentThread := state.getCurrentThread()
currentThread := state.GetCurrentThread()
currentThread.Cpu.PC = pc
currentThread.Cpu.NextPC = pc + 4
state.Heap = heapStart
......@@ -91,7 +84,7 @@ func CreateInitialState(pc, heapStart uint32) *State {
return state
}
func (s *State) getCurrentThread() *ThreadState {
func (s *State) GetCurrentThread() *ThreadState {
activeStack := s.getActiveThreadStack()
activeStackSize := len(activeStack)
......@@ -131,17 +124,22 @@ func (s *State) calculateThreadStackRoot(stack []*ThreadState) common.Hash {
}
func (s *State) GetPC() uint32 {
activeThread := s.getCurrentThread()
activeThread := s.GetCurrentThread()
return activeThread.Cpu.PC
}
func (s *State) GetRegisters() *[32]uint32 {
activeThread := s.getCurrentThread()
return &activeThread.Registers
func (s *State) GetCpu() mipsevm.CpuScalars {
activeThread := s.GetCurrentThread()
return activeThread.Cpu
}
func (s *State) getCpuRef() *mipsevm.CpuScalars {
return &s.GetCurrentThread().Cpu
}
func (s *State) getCpu() *mipsevm.CpuScalars {
return &s.getCurrentThread().Cpu
func (s *State) GetRegistersRef() *[32]uint32 {
activeThread := s.GetCurrentThread()
return &activeThread.Registers
}
func (s *State) GetExitCode() uint8 { return s.ExitCode }
......@@ -150,6 +148,10 @@ func (s *State) GetExited() bool { return s.Exited }
func (s *State) GetStep() uint64 { return s.Step }
func (s *State) GetLastHint() hexutil.Bytes {
return s.LastHint
}
func (s *State) VMStatus() uint8 {
return mipsevm.VmStatus(s.Exited, s.ExitCode)
}
......@@ -158,6 +160,18 @@ func (s *State) GetMemory() *memory.Memory {
return s.Memory
}
func (s *State) GetHeap() uint32 {
return s.Heap
}
func (s *State) GetPreimageKey() common.Hash {
return s.PreimageKey
}
func (s *State) GetPreimageOffset() uint32 {
return s.PreimageOffset
}
func (s *State) EncodeWitness() ([]byte, common.Hash) {
out := make([]byte, 0, STATE_WITNESS_SIZE)
memRoot := s.Memory.MerkleRoot()
......@@ -214,6 +228,12 @@ func (sw StateWitness) StateHash() (common.Hash, error) {
return stateHashFromWitness(sw), nil
}
func GetStateHashFn() mipsevm.HashFn {
return func(sw []byte) (common.Hash, error) {
return StateWitness(sw).StateHash()
}
}
func stateHashFromWitness(sw []byte) common.Hash {
if len(sw) != STATE_WITNESS_SIZE {
panic("Invalid witness length")
......
......@@ -133,7 +133,7 @@ func TestState_EmptyThreadsRoot(t *testing.T) {
func TestState_EncodeThreadProof_SingleThread(t *testing.T) {
state := CreateEmptyState()
// Set some fields on the active thread
activeThread := state.getCurrentThread()
activeThread := state.GetCurrentThread()
activeThread.Cpu.PC = 4
activeThread.Cpu.NextPC = 8
activeThread.Cpu.HI = 11
......@@ -181,7 +181,7 @@ func TestState_EncodeThreadProof_MultipleThreads(t *testing.T) {
expectedRoot = crypto.Keccak256Hash(hashData)
}
expectedProof := append([]byte{}, state.getCurrentThread().serializeThread()[:]...)
expectedProof := append([]byte{}, state.GetCurrentThread().serializeThread()[:]...)
expectedProof = append(expectedProof, expectedRoot[:]...)
actualProof := state.EncodeThreadProof()
......
......@@ -64,7 +64,7 @@ func PatchStack(st mipsevm.FPVMState) error {
if err := st.GetMemory().SetMemoryRange(sp-4*memory.PageSize, bytes.NewReader(make([]byte, 5*memory.PageSize))); err != nil {
return fmt.Errorf("failed to allocate page for stack content")
}
st.GetRegisters()[29] = sp
st.GetRegistersRef()[29] = sp
storeMem := func(addr uint32, v uint32) {
var dat [4]byte
......
......@@ -34,13 +34,6 @@ type State struct {
Registers [32]uint32 `json:"registers"`
// LastHint is optional metadata, and not part of the VM state itself.
// It is used to remember the last pre-image hint,
// so a VM can start from any state without fetching prior pre-images,
// and instead just repeat the last hint on setup,
// to make sure pre-image requests can be served.
// The first 4 bytes are a uin32 length prefix.
// Warning: the hint MAY NOT BE COMPLETE. I.e. this is buffered,
// and should only be read when len(LastHint) > 4 && uint32(LastHint[:4]) <= len(LastHint[4:])
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
}
......@@ -130,7 +123,9 @@ func (s *State) UnmarshalJSON(data []byte) error {
func (s *State) GetPC() uint32 { return s.Cpu.PC }
func (s *State) GetRegisters() *[32]uint32 { return &s.Registers }
func (s *State) GetCpu() mipsevm.CpuScalars { return s.Cpu }
func (s *State) GetRegistersRef() *[32]uint32 { return &s.Registers }
func (s *State) GetExitCode() uint8 { return s.ExitCode }
......@@ -138,6 +133,10 @@ func (s *State) GetExited() bool { return s.Exited }
func (s *State) GetStep() uint64 { return s.Step }
func (s *State) GetLastHint() hexutil.Bytes {
return s.LastHint
}
func (s *State) VMStatus() uint8 {
return mipsevm.VmStatus(s.Exited, s.ExitCode)
}
......@@ -146,6 +145,18 @@ func (s *State) GetMemory() *memory.Memory {
return s.Memory
}
func (s *State) GetHeap() uint32 {
return s.Heap
}
func (s *State) GetPreimageKey() common.Hash {
return s.PreimageKey
}
func (s *State) GetPreimageOffset() uint32 {
return s.PreimageOffset
}
func (s *State) EncodeWitness() ([]byte, common.Hash) {
out := make([]byte, 0, STATE_WITNESS_SIZE)
memRoot := s.Memory.MerkleRoot()
......
......@@ -2,6 +2,7 @@ package tests
import (
"bytes"
"fmt"
"io"
"os"
"path"
......@@ -18,41 +19,40 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
func testContractsSetup(t require.TestingT) (*testutil.Artifacts, *testutil.Addresses) {
artifacts, err := testutil.LoadArtifacts()
require.NoError(t, err)
addrs := &testutil.Addresses{
MIPS: common.Address{0: 0xff, 19: 1},
Oracle: common.Address{0: 0xff, 19: 2},
Sender: common.Address{0x13, 0x37},
FeeRecipient: common.Address{0xaa},
}
return artifacts, addrs
}
func TestEVM(t *testing.T) {
testFiles, err := os.ReadDir("open_mips_tests/test/bin")
require.NoError(t, err)
contracts, addrs := testContractsSetup(t)
var tracer *tracing.Hooks // no-tracer by default, but test_util.MarkdownTracer
cases := GetMipsVersionTestCases(t)
skippedTests := map[string][]string{
"multi-threaded": []string{"clone.bin"},
"single-threaded": []string{},
}
for _, c := range cases {
skipped, exists := skippedTests[c.Name]
require.True(t, exists)
for _, f := range testFiles {
t.Run(f.Name(), func(t *testing.T) {
testName := fmt.Sprintf("%v (%v)", f.Name(), c.Name)
t.Run(testName, func(t *testing.T) {
for _, skipped := range skipped {
if f.Name() == skipped {
t.Skipf("Skipping explicitly excluded open_mips testcase: %v", f.Name())
}
}
oracle := testutil.SelectOracleFixture(t, f.Name())
// Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin"
expectPanic := strings.HasSuffix(f.Name(), "panic.bin")
evm := testutil.NewMIPSEVM(contracts, addrs)
evm := testutil.NewMIPSEVM(c.Contracts)
evm.SetTracer(tracer)
evm.SetLocalOracle(oracle)
testutil.LogStepFailureAtCleanup(t, evm)
......@@ -60,14 +60,14 @@ func TestEVM(t *testing.T) {
fn := path.Join("open_mips_tests/test/bin", f.Name())
programMem, err := os.ReadFile(fn)
require.NoError(t, err)
state := &singlethreaded.State{Cpu: mipsevm.CpuScalars{PC: 0, NextPC: 4}, Memory: memory.NewMemory()}
err = state.Memory.SetMemoryRange(0, bytes.NewReader(programMem))
goVm := c.VMFactory(oracle, os.Stdout, os.Stderr, testutil.CreateLogger())
state := goVm.GetState()
err = state.GetMemory().SetMemoryRange(0, bytes.NewReader(programMem))
require.NoError(t, err, "load program into state")
// set the return address ($ra) to jump into when test completes
state.Registers[31] = testutil.EndAddr
goState := singlethreaded.NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, nil)
state.GetRegistersRef()[31] = testutil.EndAddr
// Catch panics and check if they are expected
defer func() {
......@@ -81,100 +81,47 @@ func TestEVM(t *testing.T) {
}()
for i := 0; i < 1000; i++ {
curStep := goState.GetState().GetStep()
if goState.GetState().GetPC() == testutil.EndAddr {
curStep := goVm.GetState().GetStep()
if goVm.GetState().GetPC() == testutil.EndAddr {
break
}
if exitGroup && goState.GetState().GetExited() {
if exitGroup && goVm.GetState().GetExited() {
break
}
insn := state.Memory.GetMemory(state.Cpu.PC)
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.Cpu.PC, insn)
insn := state.GetMemory().GetMemory(state.GetPC())
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.GetStep(), state.GetPC(), insn)
stepWitness, err := goState.Step(true)
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness, curStep, singlethreaded.GetStateHashFn())
evmPost := evm.Step(t, stepWitness, curStep, c.StateHashFn)
// verify the post-state matches.
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
goPost, _ := goState.GetState().EncodeWitness()
goPost, _ := goVm.GetState().EncodeWitness()
require.Equalf(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM at step %d", state.Step)
"mipsevm produced different state than EVM at step %d", state.GetStep())
}
if exitGroup {
require.NotEqual(t, uint32(testutil.EndAddr), goState.GetState().GetPC(), "must not reach end")
require.True(t, goState.GetState().GetExited(), "must set exited state")
require.Equal(t, uint8(1), goState.GetState().GetExitCode(), "must exit with 1")
require.NotEqual(t, uint32(testutil.EndAddr), goVm.GetState().GetPC(), "must not reach end")
require.True(t, goVm.GetState().GetExited(), "must set exited state")
require.Equal(t, uint8(1), goVm.GetState().GetExitCode(), "must exit with 1")
} else if expectPanic {
require.NotEqual(t, uint32(testutil.EndAddr), goState.GetState().GetPC(), "must not reach end")
require.NotEqual(t, uint32(testutil.EndAddr), state.GetPC(), "must not reach end")
} else {
require.Equal(t, uint32(testutil.EndAddr), state.Cpu.PC, "must reach end")
require.Equal(t, uint32(testutil.EndAddr), state.GetPC(), "must reach end")
// inspect test result
done, result := state.Memory.GetMemory(testutil.BaseAddrEnd+4), state.Memory.GetMemory(testutil.BaseAddrEnd+8)
done, result := state.GetMemory().GetMemory(testutil.BaseAddrEnd+4), state.GetMemory().GetMemory(testutil.BaseAddrEnd+8)
require.Equal(t, done, uint32(1), "must be done")
require.Equal(t, result, uint32(1), "must have success result")
}
})
}
}
func TestEVM_CloneFlags(t *testing.T) {
//contracts, addrs := testContractsSetup(t)
//var tracer *tracing.Hooks
cases := []struct {
name string
flags uint32
valid bool
}{
{"the supported flags bitmask", exec.ValidCloneFlags, true},
{"no flags", 0, false},
{"all flags", ^uint32(0), false},
{"all unsupported flags", ^uint32(exec.ValidCloneFlags), false},
{"a few supported flags", exec.CloneFs | exec.CloneSysvsem, false},
{"one supported flag", exec.CloneFs, false},
{"mixed supported and unsupported flags", exec.CloneFs | exec.CloneParentSettid, false},
{"a single unsupported flag", exec.CloneUntraced, false},
{"multiple unsupported flags", exec.CloneUntraced | exec.CloneParentSettid, false},
}
const insn = uint32(0x00_00_00_0C) // syscall instruction
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
state := multithreaded.CreateEmptyState()
state.Memory.SetMemory(state.GetPC(), insn)
state.GetRegisters()[2] = exec.SysClone // Set syscall number
state.GetRegisters()[4] = tt.flags // Set first argument
//curStep := state.Step
us := multithreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil)
if !tt.valid {
// The VM should exit
_, err := us.Step(true)
require.NoError(t, err)
require.Equal(t, true, us.GetState().GetExited())
require.Equal(t, uint8(mipsevm.VMStatusPanic), us.GetState().GetExitCode())
} else {
/*stepWitness*/ _, err := us.Step(true)
require.NoError(t, err)
}
// TODO: Validate EVM execution once onchain implementation is ready
//evm := testutil.NewMIPSEVM(contracts, addrs)
//evm.SetTracer(tracer)
//testutil.LogStepFailureAtCleanup(t, evm)
//
//evmPost := evm.Step(t, stepWitness, curStep, singlethreaded.GetStateHashFn())
//goPost, _ := us.GetState().EncodeWitness()
//require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
// "mipsevm produced different state than EVM")
})
}
}
func TestEVMSingleStep(t *testing.T) {
contracts, addrs := testContractsSetup(t)
var tracer *tracing.Hooks
versions := GetMipsVersionTestCases(t)
cases := []struct {
name string
pc uint32
......@@ -187,32 +134,35 @@ func TestEVMSingleStep(t *testing.T) {
{"jal non-zero PC region", 0x10000000, 0x10000004, 0x0C_00_00_02}, // jal 0x2
}
for _, v := range versions {
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
state := &singlethreaded.State{Cpu: mipsevm.CpuScalars{PC: tt.pc, NextPC: tt.nextPC}, Memory: memory.NewMemory()}
state.Memory.SetMemory(tt.pc, tt.insn)
curStep := state.Step
us := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil)
stepWitness, err := us.Step(true)
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(), WithPC(tt.pc), WithNextPC(tt.nextPC))
state := goVm.GetState()
state.GetMemory().SetMemory(tt.pc, tt.insn)
curStep := state.GetStep()
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
evm := testutil.NewMIPSEVM(contracts, addrs)
evm := testutil.NewMIPSEVM(v.Contracts)
evm.SetTracer(tracer)
testutil.LogStepFailureAtCleanup(t, evm)
evmPost := evm.Step(t, stepWitness, curStep, singlethreaded.GetStateHashFn())
goPost, _ := us.GetState().EncodeWitness()
evmPost := evm.Step(t, stepWitness, curStep, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
}
}
func TestEVM_MMap(t *testing.T) {
contracts, addrs := testContractsSetup(t)
var tracer *tracing.Hooks
versions := GetMipsVersionTestCases(t)
cases := []struct {
name string
heap uint32
......@@ -232,27 +182,30 @@ func TestEVM_MMap(t *testing.T) {
{name: "Request specific address", heap: program.HEAP_START, address: 0x50_00_00_00, size: 0, shouldFail: false, expectedHeap: program.HEAP_START},
}
for _, v := range versions {
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
state := singlethreaded.CreateEmptyState()
state.Heap = c.heap
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.Registers = testutil.RandomRegisters(77)
state.Registers[2] = exec.SysMmap
state.Registers[4] = c.address
state.Registers[5] = c.size
step := state.Step
expectedRegisters := state.Registers
expectedHeap := state.Heap
expectedMemoryRoot := state.Memory.MerkleRoot()
testName := fmt.Sprintf("%v (%v)", c.name, v.Name)
t.Run(testName, func(t *testing.T) {
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), WithHeap(c.heap))
state := goVm.GetState()
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
*state.GetRegistersRef() = testutil.RandomRegisters(77)
state.GetRegistersRef()[2] = exec.SysMmap
state.GetRegistersRef()[4] = c.address
state.GetRegistersRef()[5] = c.size
step := state.GetStep()
expectedRegisters := testutil.CopyRegisters(state)
expectedHeap := state.GetHeap()
expectedMemoryRoot := state.GetMemory().MerkleRoot()
if c.shouldFail {
expectedRegisters[2] = exec.SysErrorSignal
expectedRegisters[7] = exec.MipsEINVAL
} else {
expectedHeap = c.expectedHeap
if c.address == 0 {
expectedRegisters[2] = state.Heap
expectedRegisters[2] = state.GetHeap()
expectedRegisters[7] = 0
} else {
expectedRegisters[2] = c.address
......@@ -260,41 +213,41 @@ func TestEVM_MMap(t *testing.T) {
}
}
us := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil)
stepWitness, err := us.Step(true)
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
require.Equal(t, step+1, state.Step)
require.Equal(t, expectedHeap, state.Heap)
require.Equal(t, expectedRegisters, state.Registers)
require.Equal(t, expectedMemoryRoot, state.Memory.MerkleRoot())
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, uint32(0), state.PreimageOffset)
require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, false, state.Exited)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, hexutil.Bytes(nil), state.LastHint)
evm := testutil.NewMIPSEVM(contracts, addrs)
require.Equal(t, step+1, state.GetStep())
require.Equal(t, expectedHeap, state.GetHeap())
require.Equal(t, expectedRegisters, state.GetRegistersRef())
require.Equal(t, expectedMemoryRoot, state.GetMemory().MerkleRoot())
require.Equal(t, common.Hash{}, state.GetPreimageKey())
require.Equal(t, uint32(0), state.GetPreimageOffset())
require.Equal(t, uint32(4), state.GetCpu().PC)
require.Equal(t, uint32(8), state.GetCpu().NextPC)
require.Equal(t, uint32(0), state.GetCpu().HI)
require.Equal(t, uint32(0), state.GetCpu().LO)
require.Equal(t, false, state.GetExited())
require.Equal(t, uint8(0), state.GetExitCode())
require.Equal(t, hexutil.Bytes(nil), state.GetLastHint())
evm := testutil.NewMIPSEVM(v.Contracts)
evm.SetTracer(tracer)
testutil.LogStepFailureAtCleanup(t, evm)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := us.GetState().EncodeWitness()
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
}
}
func TestEVMSysWriteHint(t *testing.T) {
contracts, addrs := testContractsSetup(t)
var tracer *tracing.Hooks
versions := GetMipsVersionTestCases(t)
cases := []struct {
name string
memOffset int // Where the hint data is stored in memory
......@@ -439,47 +392,45 @@ func TestEVMSysWriteHint(t *testing.T) {
insn = uint32(0x00_00_00_0C) // syscall instruction
)
for _, v := range versions {
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
testName := fmt.Sprintf("%v (%v)", tt.name, v.Name)
t.Run(testName, func(t *testing.T) {
oracle := hintTrackingOracle{}
state := &singlethreaded.State{Cpu: mipsevm.CpuScalars{PC: 0, NextPC: 4}, Memory: memory.NewMemory()}
state.LastHint = tt.lastHint
state.Registers[2] = exec.SysWrite
state.Registers[4] = exec.FdHintWrite
state.Registers[5] = uint32(tt.memOffset)
state.Registers[6] = uint32(tt.bytesToWrite)
err := state.Memory.SetMemoryRange(uint32(tt.memOffset), bytes.NewReader(tt.hintData))
goVm := v.VMFactory(&oracle, os.Stdout, os.Stderr, testutil.CreateLogger(), WithLastHint(tt.lastHint))
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysWrite
state.GetRegistersRef()[4] = exec.FdHintWrite
state.GetRegistersRef()[5] = uint32(tt.memOffset)
state.GetRegistersRef()[6] = uint32(tt.bytesToWrite)
err := state.GetMemory().SetMemoryRange(uint32(tt.memOffset), bytes.NewReader(tt.hintData))
require.NoError(t, err)
state.Memory.SetMemory(0, insn)
curStep := state.Step
state.GetMemory().SetMemory(0, insn)
curStep := state.GetStep()
us := singlethreaded.NewInstrumentedState(state, &oracle, os.Stdout, os.Stderr, nil)
stepWitness, err := us.Step(true)
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
require.Equal(t, tt.expectedHints, oracle.hints)
evm := testutil.NewMIPSEVM(contracts, addrs)
evm := testutil.NewMIPSEVM(v.Contracts)
evm.SetTracer(tracer)
testutil.LogStepFailureAtCleanup(t, evm)
evmPost := evm.Step(t, stepWitness, curStep, singlethreaded.GetStateHashFn())
goPost, _ := us.GetState().EncodeWitness()
evmPost := evm.Step(t, stepWitness, curStep, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
}
}
func TestEVMFault(t *testing.T) {
contracts, addrs := testContractsSetup(t)
var tracer *tracing.Hooks // no-tracer by default, but see test_util.MarkdownTracer
sender := common.Address{0x13, 0x37}
env, evmState := testutil.NewEVMEnv(contracts, addrs)
env.Config.Tracer = tracer
versions := GetMipsVersionTestCases(t)
cases := []struct {
name string
nextPC uint32
......@@ -490,116 +441,132 @@ func TestEVMFault(t *testing.T) {
{"jump in delay-slot", 8, 0x0c_00_00_0c},
}
for _, v := range versions {
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
state := &singlethreaded.State{Cpu: mipsevm.CpuScalars{PC: 0, NextPC: tt.nextPC}, Memory: memory.NewMemory()}
initialState := &singlethreaded.State{Cpu: mipsevm.CpuScalars{PC: 0, NextPC: tt.nextPC}, Memory: state.Memory}
state.Memory.SetMemory(0, tt.insn)
testName := fmt.Sprintf("%v (%v)", tt.name, v.Name)
t.Run(testName, func(t *testing.T) {
env, evmState := testutil.NewEVMEnv(v.Contracts)
env.Config.Tracer = tracer
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), WithNextPC(tt.nextPC))
state := goVm.GetState()
state.GetMemory().SetMemory(0, tt.insn)
// set the return address ($ra) to jump into when test completes
state.Registers[31] = testutil.EndAddr
state.GetRegistersRef()[31] = testutil.EndAddr
us := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil)
require.Panics(t, func() { _, _ = us.Step(true) })
require.Panics(t, func() { _, _ = goVm.Step(true) })
insnProof := initialState.Memory.MerkleProof(0)
encodedWitness, _ := initialState.EncodeWitness()
insnProof := state.GetMemory().MerkleProof(0)
encodedWitness, _ := state.EncodeWitness()
stepWitness := &mipsevm.StepWitness{
State: encodedWitness,
ProofData: insnProof[:],
}
input := testutil.EncodeStepInput(t, stepWitness, mipsevm.LocalContext{}, contracts.MIPS)
input := testutil.EncodeStepInput(t, stepWitness, mipsevm.LocalContext{}, v.Contracts.Artifacts.MIPS)
startingGas := uint64(30_000_000)
_, _, err := env.Call(vm.AccountRef(sender), addrs.MIPS, input, startingGas, common.U2560)
_, _, err := env.Call(vm.AccountRef(sender), v.Contracts.Addresses.MIPS, input, startingGas, common.U2560)
require.EqualValues(t, err, vm.ErrExecutionReverted)
logs := evmState.Logs()
require.Equal(t, 0, len(logs))
})
}
}
}
func TestHelloEVM(t *testing.T) {
contracts, addrs := testContractsSetup(t)
var tracer *tracing.Hooks // no-tracer by default, but see test_util.MarkdownTracer
evm := testutil.NewMIPSEVM(contracts, addrs)
versions := GetMipsVersionTestCases(t)
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
evm := testutil.NewMIPSEVM(v.Contracts)
evm.SetTracer(tracer)
testutil.LogStepFailureAtCleanup(t, evm)
state := testutil.LoadELFProgram(t, "../../testdata/example/bin/hello.elf", singlethreaded.CreateInitialState, true)
var stdOutBuf, stdErrBuf bytes.Buffer
goState := singlethreaded.NewInstrumentedState(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), nil)
elfFile := "../../testdata/example/bin/hello.elf"
goVm := v.ElfVMFactory(t, elfFile, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger())
state := goVm.GetState()
start := time.Now()
for i := 0; i < 400_000; i++ {
curStep := goState.GetState().GetStep()
if goState.GetState().GetExited() {
curStep := goVm.GetState().GetStep()
if goVm.GetState().GetExited() {
break
}
insn := state.Memory.GetMemory(state.Cpu.PC)
insn := state.GetMemory().GetMemory(state.GetPC())
if i%1000 == 0 { // avoid spamming test logs, we are executing many steps
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.Cpu.PC, insn)
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.GetStep(), state.GetPC(), insn)
}
stepWitness, err := goState.Step(true)
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness, curStep, singlethreaded.GetStateHashFn())
evmPost := evm.Step(t, stepWitness, curStep, v.StateHashFn)
// verify the post-state matches.
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
goPost, _ := goState.GetState().EncodeWitness()
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
}
end := time.Now()
delta := end.Sub(start)
t.Logf("test took %s, %d instructions, %s per instruction", delta, state.Step, delta/time.Duration(state.Step))
t.Logf("test took %s, %d instructions, %s per instruction", delta, state.GetStep(), delta/time.Duration(state.GetStep()))
require.True(t, state.Exited, "must complete program")
require.Equal(t, uint8(0), state.ExitCode, "exit with 0")
require.True(t, state.GetExited(), "must complete program")
require.Equal(t, uint8(0), state.GetExitCode(), "exit with 0")
require.Equal(t, "hello world!\n", stdOutBuf.String(), "stdout says hello")
require.Equal(t, "", stdErrBuf.String(), "stderr silent")
})
}
}
func TestClaimEVM(t *testing.T) {
contracts, addrs := testContractsSetup(t)
var tracer *tracing.Hooks // no-tracer by default, but see test_util.MarkdownTracer
evm := testutil.NewMIPSEVM(contracts, addrs)
versions := GetMipsVersionTestCases(t)
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
evm := testutil.NewMIPSEVM(v.Contracts)
evm.SetTracer(tracer)
testutil.LogStepFailureAtCleanup(t, evm)
state := testutil.LoadELFProgram(t, "../../testdata/example/bin/claim.elf", singlethreaded.CreateInitialState, true)
oracle, expectedStdOut, expectedStdErr := testutil.ClaimTestOracle(t)
var stdOutBuf, stdErrBuf bytes.Buffer
goState := singlethreaded.NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), nil)
elfFile := "../../testdata/example/bin/claim.elf"
goVm := v.ElfVMFactory(t, elfFile, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger())
state := goVm.GetState()
for i := 0; i < 2000_000; i++ {
curStep := goState.GetState().GetStep()
if goState.GetState().GetExited() {
curStep := goVm.GetState().GetStep()
if goVm.GetState().GetExited() {
break
}
insn := state.Memory.GetMemory(state.Cpu.PC)
insn := state.GetMemory().GetMemory(state.GetPC())
if i%1000 == 0 { // avoid spamming test logs, we are executing many steps
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.Cpu.PC, insn)
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.GetStep(), state.GetPC(), insn)
}
stepWitness, err := goState.Step(true)
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness, curStep, singlethreaded.GetStateHashFn())
evmPost := evm.Step(t, stepWitness, curStep, v.StateHashFn)
goPost, _ := goState.GetState().EncodeWitness()
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
}
require.True(t, state.Exited, "must complete program")
require.Equal(t, uint8(0), state.ExitCode, "exit with 0")
require.True(t, state.GetExited(), "must complete program")
require.Equal(t, uint8(0), state.GetExitCode(), "exit with 0")
require.Equal(t, expectedStdOut, stdOutBuf.String(), "stdout")
require.Equal(t, expectedStdErr, stdErrBuf.String(), "stderr")
})
}
}
type hintTrackingOracle struct {
......
package tests
import (
"os"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
func TestEVM_CloneFlags(t *testing.T) {
contracts := testutil.TestContractsSetup(t, testutil.MipsMultithreaded)
var tracer *tracing.Hooks
cases := []struct {
name string
flags uint32
valid bool
}{
{"the supported flags bitmask", exec.ValidCloneFlags, true},
{"no flags", 0, false},
{"all flags", ^uint32(0), false},
{"all unsupported flags", ^uint32(exec.ValidCloneFlags), false},
{"a few supported flags", exec.CloneFs | exec.CloneSysvsem, false},
{"one supported flag", exec.CloneFs, false},
{"mixed supported and unsupported flags", exec.CloneFs | exec.CloneParentSettid, false},
{"a single unsupported flag", exec.CloneUntraced, false},
{"multiple unsupported flags", exec.CloneUntraced | exec.CloneParentSettid, false},
}
const insn = uint32(0x00_00_00_0C) // syscall instruction
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
state := multithreaded.CreateEmptyState()
state.Memory.SetMemory(state.GetPC(), insn)
state.GetRegistersRef()[2] = exec.SysClone // Set syscall number
state.GetRegistersRef()[4] = tt.flags // Set first argument
curStep := state.Step
var err error
var stepWitness *mipsevm.StepWitness
us := multithreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil)
if !tt.valid {
// The VM should exit
stepWitness, err = us.Step(true)
require.NoError(t, err)
require.Equal(t, true, us.GetState().GetExited())
require.Equal(t, uint8(mipsevm.VMStatusPanic), us.GetState().GetExitCode())
} else {
stepWitness, err = us.Step(true)
require.NoError(t, err)
}
evm := testutil.NewMIPSEVM(contracts)
evm.SetTracer(tracer)
testutil.LogStepFailureAtCleanup(t, evm)
evmPost := evm.Step(t, stepWitness, curStep, multithreaded.GetStateHashFn())
goPost, _ := us.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
}
package tests
import (
"bytes"
"math/rand"
"os"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
)
const syscallInsn = uint32(0x00_00_00_0c)
func FuzzStateSyscallBrk(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, pc uint32, step uint64, preimageOffset uint32) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(),
WithPC(pc), WithNextPC(nextPC), WithStep(step), WithPreimageOffset(preimageOffset))
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysBrk
state.GetMemory().SetMemory(pc, syscallInsn)
preStateRoot := state.GetMemory().MerkleRoot()
expectedRegisters := testutil.CopyRegisters(state)
expectedRegisters[2] = program.PROGRAM_BREAK
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc+4, state.GetPC())
require.Equal(t, nextPC+4, state.GetCpu().NextPC)
require.Equal(t, uint32(0), state.GetCpu().LO)
require.Equal(t, uint32(0), state.GetCpu().HI)
require.Equal(t, uint32(0), state.GetHeap())
require.Equal(t, uint8(0), state.GetExitCode())
require.Equal(t, false, state.GetExited())
require.Equal(t, preStateRoot, state.GetMemory().MerkleRoot())
require.Equal(t, expectedRegisters, state.GetRegistersRef())
require.Equal(t, step+1, state.GetStep())
require.Equal(t, common.Hash{}, state.GetPreimageKey())
require.Equal(t, preimageOffset, state.GetPreimageOffset())
evm := testutil.NewMIPSEVM(v.Contracts)
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
})
}
func FuzzStateSyscallMmap(f *testing.F) {
// Add special cases for large memory allocation
f.Add(uint32(0), uint32(0x1000), uint32(program.HEAP_END), int64(1))
f.Add(uint32(0), uint32(1<<31), uint32(program.HEAP_START), int64(2))
// Check edge case - just within bounds
f.Add(uint32(0), uint32(0x1000), uint32(program.HEAP_END-4096), int64(3))
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, addr uint32, siz uint32, heap uint32, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
step := uint64(0)
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(),
WithStep(step), WithHeap(heap))
state := goVm.GetState()
*state.GetRegistersRef() = testutil.RandomRegisters(seed)
state.GetRegistersRef()[2] = exec.SysMmap
state.GetRegistersRef()[4] = addr
state.GetRegistersRef()[5] = siz
state.GetMemory().SetMemory(0, syscallInsn)
preStateRoot := state.GetMemory().MerkleRoot()
preStateRegisters := testutil.CopyRegisters(state)
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
var expectedHeap uint32
expectedRegisters := preStateRegisters
if addr == 0 {
sizAlign := siz
if sizAlign&memory.PageAddrMask != 0 { // adjust size to align with page size
sizAlign = siz + memory.PageSize - (siz & memory.PageAddrMask)
}
newHeap := heap + sizAlign
if newHeap > program.HEAP_END || newHeap < heap || sizAlign < siz {
expectedHeap = heap
expectedRegisters[2] = exec.SysErrorSignal
expectedRegisters[7] = exec.MipsEINVAL
} else {
expectedRegisters[2] = heap
expectedRegisters[7] = 0 // no error
expectedHeap = heap + sizAlign
}
} else {
expectedRegisters[2] = addr
expectedRegisters[7] = 0 // no error
expectedHeap = heap
}
require.Equal(t, uint32(4), state.GetCpu().PC)
require.Equal(t, uint32(8), state.GetCpu().NextPC)
require.Equal(t, uint32(0), state.GetCpu().LO)
require.Equal(t, uint32(0), state.GetCpu().HI)
require.Equal(t, preStateRoot, state.GetMemory().MerkleRoot())
require.Equal(t, uint64(1), state.GetStep())
require.Equal(t, common.Hash{}, state.GetPreimageKey())
require.Equal(t, uint32(0), state.GetPreimageOffset())
require.Equal(t, expectedHeap, state.GetHeap())
require.Equal(t, uint8(0), state.GetExitCode())
require.Equal(t, false, state.GetExited())
require.Equal(t, expectedRegisters, state.GetRegistersRef())
evm := testutil.NewMIPSEVM(v.Contracts)
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
})
}
func FuzzStateSyscallExitGroup(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, exitCode uint8, pc uint32, step uint64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(),
WithPC(pc), WithNextPC(nextPC), WithStep(step))
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysExitGroup
state.GetRegistersRef()[4] = uint32(exitCode)
state.GetMemory().SetMemory(pc, syscallInsn)
preStateRoot := state.GetMemory().MerkleRoot()
preStateRegisters := testutil.CopyRegisters(state)
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc, state.GetCpu().PC)
require.Equal(t, nextPC, state.GetCpu().NextPC)
require.Equal(t, uint32(0), state.GetCpu().LO)
require.Equal(t, uint32(0), state.GetCpu().HI)
require.Equal(t, uint32(0), state.GetHeap())
require.Equal(t, uint8(exitCode), state.GetExitCode())
require.Equal(t, true, state.GetExited())
require.Equal(t, preStateRoot, state.GetMemory().MerkleRoot())
require.Equal(t, preStateRegisters, state.GetRegistersRef())
require.Equal(t, step+1, state.GetStep())
require.Equal(t, common.Hash{}, state.GetPreimageKey())
require.Equal(t, uint32(0), state.GetPreimageOffset())
evm := testutil.NewMIPSEVM(v.Contracts)
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
})
}
func FuzzStateSyscallFcntl(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, fd uint32, cmd uint32) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
step := uint64(0)
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(),
WithStep(step))
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysFcntl
state.GetRegistersRef()[4] = fd
state.GetRegistersRef()[5] = cmd
state.GetMemory().SetMemory(0, syscallInsn)
preStateRoot := state.GetMemory().MerkleRoot()
preStateRegisters := testutil.CopyRegisters(state)
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.GetCpu().PC)
require.Equal(t, uint32(8), state.GetCpu().NextPC)
require.Equal(t, uint32(0), state.GetCpu().LO)
require.Equal(t, uint32(0), state.GetCpu().HI)
require.Equal(t, uint32(0), state.GetHeap())
require.Equal(t, uint8(0), state.GetExitCode())
require.Equal(t, false, state.GetExited())
require.Equal(t, preStateRoot, state.GetMemory().MerkleRoot())
require.Equal(t, uint64(1), state.GetStep())
require.Equal(t, common.Hash{}, state.GetPreimageKey())
require.Equal(t, uint32(0), state.GetPreimageOffset())
if cmd == 3 {
expectedRegisters := preStateRegisters
switch fd {
case exec.FdStdin, exec.FdPreimageRead, exec.FdHintRead:
expectedRegisters[2] = 0
case exec.FdStdout, exec.FdStderr, exec.FdPreimageWrite, exec.FdHintWrite:
expectedRegisters[2] = 1
default:
expectedRegisters[2] = 0xFF_FF_FF_FF
expectedRegisters[7] = exec.MipsEBADF
}
require.Equal(t, expectedRegisters, state.GetRegistersRef())
} else {
expectedRegisters := preStateRegisters
expectedRegisters[2] = 0xFF_FF_FF_FF
expectedRegisters[7] = exec.MipsEINVAL
require.Equal(t, expectedRegisters, state.GetRegistersRef())
}
evm := testutil.NewMIPSEVM(v.Contracts)
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
})
}
func FuzzStateHintRead(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
step := uint64(0)
preimageData := []byte("hello world")
preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey()
oracle := testutil.StaticOracle(t, preimageData) // only used for hinting
goVm := v.VMFactory(oracle, os.Stdout, os.Stderr, testutil.CreateLogger(),
WithStep(step), WithPreimageKey(preimageKey))
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysRead
state.GetRegistersRef()[4] = exec.FdHintRead
state.GetRegistersRef()[5] = addr
state.GetRegistersRef()[6] = count
state.GetMemory().SetMemory(0, syscallInsn)
preStatePreimageKey := state.GetPreimageKey()
preStateRoot := state.GetMemory().MerkleRoot()
expectedRegisters := testutil.CopyRegisters(state)
expectedRegisters[2] = count
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.GetCpu().PC)
require.Equal(t, uint32(8), state.GetCpu().NextPC)
require.Equal(t, uint32(0), state.GetCpu().LO)
require.Equal(t, uint32(0), state.GetCpu().HI)
require.Equal(t, uint32(0), state.GetHeap())
require.Equal(t, uint8(0), state.GetExitCode())
require.Equal(t, false, state.GetExited())
require.Equal(t, preStateRoot, state.GetMemory().MerkleRoot())
require.Equal(t, uint64(1), state.GetStep())
require.Equal(t, preStatePreimageKey, state.GetPreimageKey())
require.Equal(t, expectedRegisters, state.GetRegistersRef())
evm := testutil.NewMIPSEVM(v.Contracts)
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
})
}
func FuzzStatePreimageRead(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32, preimageOffset uint32) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
step := uint64(0)
preimageData := []byte("hello world")
if preimageOffset >= uint32(len(preimageData)) {
t.SkipNow()
}
preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey()
oracle := testutil.StaticOracle(t, preimageData)
goVm := v.VMFactory(oracle, os.Stdout, os.Stderr, testutil.CreateLogger(),
WithStep(step), WithPreimageKey(preimageKey), WithPreimageOffset(preimageOffset))
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysRead
state.GetRegistersRef()[4] = exec.FdPreimageRead
state.GetRegistersRef()[5] = addr
state.GetRegistersRef()[6] = count
state.GetMemory().SetMemory(0, syscallInsn)
preStatePreimageKey := state.GetPreimageKey()
preStateRoot := state.GetMemory().MerkleRoot()
writeLen := count
if writeLen > 4 {
writeLen = 4
}
if preimageOffset+writeLen > uint32(8+len(preimageData)) {
writeLen = uint32(8+len(preimageData)) - preimageOffset
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
require.True(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.GetCpu().PC)
require.Equal(t, uint32(8), state.GetCpu().NextPC)
require.Equal(t, uint32(0), state.GetCpu().LO)
require.Equal(t, uint32(0), state.GetCpu().HI)
require.Equal(t, uint32(0), state.GetHeap())
require.Equal(t, uint8(0), state.GetExitCode())
require.Equal(t, false, state.GetExited())
if writeLen > 0 {
// Memory may be unchanged if we're writing the first zero-valued 7 bytes of the pre-image.
//require.NotEqual(t, preStateRoot, state.GetMemory().MerkleRoot())
require.Greater(t, state.GetPreimageOffset(), preimageOffset)
} else {
require.Equal(t, preStateRoot, state.GetMemory().MerkleRoot())
require.Equal(t, state.GetPreimageOffset(), preimageOffset)
}
require.Equal(t, uint64(1), state.GetStep())
require.Equal(t, preStatePreimageKey, state.GetPreimageKey())
evm := testutil.NewMIPSEVM(v.Contracts)
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
})
}
func FuzzStateHintWrite(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32, randSeed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
step := uint64(0)
preimageData := []byte("hello world")
preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey()
oracle := testutil.StaticOracle(t, preimageData) // only used for hinting
goVm := v.VMFactory(oracle, os.Stdout, os.Stderr, testutil.CreateLogger(),
WithStep(step), WithPreimageKey(preimageKey))
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysWrite
state.GetRegistersRef()[4] = exec.FdHintWrite
state.GetRegistersRef()[5] = addr
state.GetRegistersRef()[6] = count
// Set random data at the target memory range
randBytes, err := randomBytes(randSeed, count)
require.NoError(t, err)
err = state.GetMemory().SetMemoryRange(addr, bytes.NewReader(randBytes))
require.NoError(t, err)
// Set syscall instruction
state.GetMemory().SetMemory(0, syscallInsn)
preStatePreimageKey := state.GetPreimageKey()
preStateRoot := state.GetMemory().MerkleRoot()
expectedRegisters := testutil.CopyRegisters(state)
expectedRegisters[2] = count
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.GetCpu().PC)
require.Equal(t, uint32(8), state.GetCpu().NextPC)
require.Equal(t, uint32(0), state.GetCpu().LO)
require.Equal(t, uint32(0), state.GetCpu().HI)
require.Equal(t, uint32(0), state.GetHeap())
require.Equal(t, uint8(0), state.GetExitCode())
require.Equal(t, false, state.GetExited())
require.Equal(t, preStateRoot, state.GetMemory().MerkleRoot())
require.Equal(t, uint64(1), state.GetStep())
require.Equal(t, preStatePreimageKey, state.GetPreimageKey())
require.Equal(t, expectedRegisters, state.GetRegistersRef())
evm := testutil.NewMIPSEVM(v.Contracts)
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
})
}
func FuzzStatePreimageWrite(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
step := uint64(0)
preimageData := []byte("hello world")
preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey()
oracle := testutil.StaticOracle(t, preimageData)
goVm := v.VMFactory(oracle, os.Stdout, os.Stderr, testutil.CreateLogger(),
WithStep(step), WithPreimageKey(preimageKey), WithPreimageOffset(128))
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysWrite
state.GetRegistersRef()[4] = exec.FdPreimageWrite
state.GetRegistersRef()[5] = addr
state.GetRegistersRef()[6] = count
state.GetMemory().SetMemory(0, syscallInsn)
preStateRoot := state.GetMemory().MerkleRoot()
expectedRegisters := testutil.CopyRegisters(state)
sz := 4 - (addr & 0x3)
if sz < count {
count = sz
}
expectedRegisters[2] = count
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.GetCpu().PC)
require.Equal(t, uint32(8), state.GetCpu().NextPC)
require.Equal(t, uint32(0), state.GetCpu().LO)
require.Equal(t, uint32(0), state.GetCpu().HI)
require.Equal(t, uint32(0), state.GetHeap())
require.Equal(t, uint8(0), state.GetExitCode())
require.Equal(t, false, state.GetExited())
require.Equal(t, preStateRoot, state.GetMemory().MerkleRoot())
require.Equal(t, uint64(1), state.GetStep())
require.Equal(t, uint32(0), state.GetPreimageOffset())
require.Equal(t, expectedRegisters, state.GetRegistersRef())
evm := testutil.NewMIPSEVM(v.Contracts)
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
})
}
func randomBytes(seed int64, length uint32) ([]byte, error) {
r := rand.New(rand.NewSource(seed))
randBytes := make([]byte, length)
if _, err := r.Read(randBytes); err != nil {
return nil, err
}
return randBytes, nil
}
package tests
import (
"os"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
// TODO
func FuzzStateSyscallCloneMT(f *testing.F) {
v := GetMultiThreadedTestCase(f)
// t.Skip is causing linting check to fail, disable for now
//nolint:staticcheck
f.Fuzz(func(t *testing.T, pc uint32, step uint64, preimageOffset uint32) {
// TODO(cp-903) Customize test for multi-threaded vm
t.Skip("TODO - customize this test for MTCannon")
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(),
WithPC(pc), WithNextPC(nextPC), WithStep(step), WithPreimageOffset(preimageOffset))
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysClone
state.GetMemory().SetMemory(pc, syscallInsn)
preStateRoot := state.GetMemory().MerkleRoot()
expectedRegisters := testutil.CopyRegisters(state)
expectedRegisters[2] = 0x1
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc+4, state.GetCpu().PC)
require.Equal(t, nextPC+4, state.GetCpu().NextPC)
require.Equal(t, uint32(0), state.GetCpu().LO)
require.Equal(t, uint32(0), state.GetCpu().HI)
require.Equal(t, uint32(0), state.GetHeap())
require.Equal(t, uint8(0), state.GetExitCode())
require.Equal(t, false, state.GetExited())
require.Equal(t, preStateRoot, state.GetMemory().MerkleRoot())
require.Equal(t, expectedRegisters, state.GetRegistersRef())
require.Equal(t, step+1, state.GetStep())
require.Equal(t, common.Hash{}, state.GetPreimageKey())
require.Equal(t, preimageOffset, state.GetPreimageOffset())
evm := testutil.NewMIPSEVM(v.Contracts)
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
package tests
import (
"os"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
func FuzzStateSyscallCloneST(f *testing.F) {
v := GetSingleThreadedTestCase(f)
f.Fuzz(func(t *testing.T, pc uint32, step uint64, preimageOffset uint32) {
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(),
WithPC(pc), WithNextPC(nextPC), WithStep(step), WithPreimageOffset(preimageOffset))
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysClone
state.GetMemory().SetMemory(pc, syscallInsn)
preStateRoot := state.GetMemory().MerkleRoot()
expectedRegisters := testutil.CopyRegisters(state)
expectedRegisters[2] = 0x1
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc+4, state.GetCpu().PC)
require.Equal(t, nextPC+4, state.GetCpu().NextPC)
require.Equal(t, uint32(0), state.GetCpu().LO)
require.Equal(t, uint32(0), state.GetCpu().HI)
require.Equal(t, uint32(0), state.GetHeap())
require.Equal(t, uint8(0), state.GetExitCode())
require.Equal(t, false, state.GetExited())
require.Equal(t, preStateRoot, state.GetMemory().MerkleRoot())
require.Equal(t, expectedRegisters, state.GetRegistersRef())
require.Equal(t, step+1, state.GetStep())
require.Equal(t, common.Hash{}, state.GetPreimageKey())
require.Equal(t, preimageOffset, state.GetPreimageOffset())
evm := testutil.NewMIPSEVM(v.Contracts)
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
package tests
import (
"bytes"
"math/rand"
"os"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
)
const syscallInsn = uint32(0x00_00_00_0c)
func FuzzStateSyscallBrk(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, pc uint32, step uint64, preimageOffset uint32) {
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: pc,
NextPC: nextPC,
LO: 0,
HI: 0,
},
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysBrk},
Step: step,
PreimageKey: common.Hash{},
PreimageOffset: preimageOffset,
}
state.Memory.SetMemory(pc, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
expectedRegisters[2] = program.PROGRAM_BREAK
goState := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc+4, state.Cpu.PC)
require.Equal(t, nextPC+4, state.Cpu.NextPC)
require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, expectedRegisters, state.Registers)
require.Equal(t, step+1, state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, preimageOffset, state.PreimageOffset)
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateSyscallClone(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, pc uint32, step uint64, preimageOffset uint32) {
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: pc,
NextPC: nextPC,
LO: 0,
HI: 0,
},
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysClone},
Step: step,
PreimageOffset: preimageOffset,
}
state.Memory.SetMemory(pc, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
expectedRegisters[2] = 0x1
goState := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc+4, state.Cpu.PC)
require.Equal(t, nextPC+4, state.Cpu.NextPC)
require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, expectedRegisters, state.Registers)
require.Equal(t, step+1, state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, preimageOffset, state.PreimageOffset)
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateSyscallMmap(f *testing.F) {
contracts, addrs := testContractsSetup(f)
step := uint64(0)
// Add special cases for large memory allocation
f.Add(uint32(0), uint32(0x1000), uint32(program.HEAP_END), int64(1))
f.Add(uint32(0), uint32(1<<31), uint32(program.HEAP_START), int64(2))
// Check edge case - just within bounds
f.Add(uint32(0), uint32(0x1000), uint32(program.HEAP_END-4096), int64(3))
f.Fuzz(func(t *testing.T, addr uint32, siz uint32, heap uint32, seed int64) {
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
},
Heap: heap,
ExitCode: 0,
Exited: false,
Memory: memory.NewMemory(),
Registers: testutil.RandomRegisters(seed),
Step: step,
PreimageOffset: 0,
}
state.Memory.SetMemory(0, syscallInsn)
state.Registers[2] = exec.SysMmap
state.Registers[4] = addr
state.Registers[5] = siz
preStateRoot := state.Memory.MerkleRoot()
preStateRegisters := state.Registers
goState := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
var expectedHeap uint32
expectedRegisters := preStateRegisters
if addr == 0 {
sizAlign := siz
if sizAlign&memory.PageAddrMask != 0 { // adjust size to align with page size
sizAlign = siz + memory.PageSize - (siz & memory.PageAddrMask)
}
newHeap := heap + sizAlign
if newHeap > program.HEAP_END || newHeap < heap || sizAlign < siz {
expectedHeap = heap
expectedRegisters[2] = exec.SysErrorSignal
expectedRegisters[7] = exec.MipsEINVAL
} else {
expectedRegisters[2] = heap
expectedRegisters[7] = 0 // no error
expectedHeap = heap + sizAlign
}
} else {
expectedRegisters[2] = addr
expectedRegisters[7] = 0 // no error
expectedHeap = heap
}
require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, uint32(0), state.PreimageOffset)
require.Equal(t, expectedHeap, state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, expectedRegisters, state.Registers)
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateSyscallExitGroup(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, exitCode uint8, pc uint32, step uint64) {
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: pc,
NextPC: nextPC,
LO: 0,
HI: 0,
},
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysExitGroup, 4: uint32(exitCode)},
Step: step,
PreimageOffset: 0,
}
state.Memory.SetMemory(pc, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
preStateRegisters := state.Registers
goState := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc, state.Cpu.PC)
require.Equal(t, nextPC, state.Cpu.NextPC)
require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(exitCode), state.ExitCode)
require.Equal(t, true, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, preStateRegisters, state.Registers)
require.Equal(t, step+1, state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, uint32(0), state.PreimageOffset)
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateSyscallFcntl(f *testing.F) {
contracts, addrs := testContractsSetup(f)
step := uint64(0)
f.Fuzz(func(t *testing.T, fd uint32, cmd uint32) {
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
},
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysFcntl, 4: fd, 5: cmd},
Step: step,
PreimageOffset: 0,
}
state.Memory.SetMemory(0, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
preStateRegisters := state.Registers
goState := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, uint32(0), state.PreimageOffset)
if cmd == 3 {
expectedRegisters := preStateRegisters
switch fd {
case exec.FdStdin, exec.FdPreimageRead, exec.FdHintRead:
expectedRegisters[2] = 0
case exec.FdStdout, exec.FdStderr, exec.FdPreimageWrite, exec.FdHintWrite:
expectedRegisters[2] = 1
default:
expectedRegisters[2] = 0xFF_FF_FF_FF
expectedRegisters[7] = exec.MipsEBADF
}
require.Equal(t, expectedRegisters, state.Registers)
} else {
expectedRegisters := preStateRegisters
expectedRegisters[2] = 0xFF_FF_FF_FF
expectedRegisters[7] = exec.MipsEINVAL
require.Equal(t, expectedRegisters, state.Registers)
}
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateHintRead(f *testing.F) {
contracts, addrs := testContractsSetup(f)
step := uint64(0)
f.Fuzz(func(t *testing.T, addr uint32, count uint32) {
preimageData := []byte("hello world")
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
},
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysRead, 4: exec.FdHintRead, 5: addr, 6: count},
Step: step,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: 0,
}
state.Memory.SetMemory(0, syscallInsn)
preStatePreimageKey := state.PreimageKey
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
expectedRegisters[2] = count
oracle := testutil.StaticOracle(t, preimageData) // only used for hinting
goState := singlethreaded.NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, nil)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, preStatePreimageKey, state.PreimageKey)
require.Equal(t, expectedRegisters, state.Registers)
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStatePreimageRead(f *testing.F) {
contracts, addrs := testContractsSetup(f)
step := uint64(0)
f.Fuzz(func(t *testing.T, addr uint32, count uint32, preimageOffset uint32) {
preimageData := []byte("hello world")
if preimageOffset >= uint32(len(preimageData)) {
t.SkipNow()
}
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
},
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysRead, 4: exec.FdPreimageRead, 5: addr, 6: count},
Step: step,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: preimageOffset,
}
state.Memory.SetMemory(0, syscallInsn)
preStatePreimageKey := state.PreimageKey
preStateRoot := state.Memory.MerkleRoot()
writeLen := count
if writeLen > 4 {
writeLen = 4
}
if preimageOffset+writeLen > uint32(8+len(preimageData)) {
writeLen = uint32(8+len(preimageData)) - preimageOffset
}
oracle := testutil.StaticOracle(t, preimageData)
goState := singlethreaded.NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, nil)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.True(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
if writeLen > 0 {
// Memory may be unchanged if we're writing the first zero-valued 7 bytes of the pre-image.
//require.NotEqual(t, preStateRoot, state.Memory.MerkleRoot())
require.Greater(t, state.PreimageOffset, preimageOffset)
} else {
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, state.PreimageOffset, preimageOffset)
}
require.Equal(t, uint64(1), state.Step)
require.Equal(t, preStatePreimageKey, state.PreimageKey)
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateHintWrite(f *testing.F) {
contracts, addrs := testContractsSetup(f)
step := uint64(0)
f.Fuzz(func(t *testing.T, addr uint32, count uint32, randSeed int64) {
preimageData := []byte("hello world")
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
},
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysWrite, 4: exec.FdHintWrite, 5: addr, 6: count},
Step: step,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: 0,
LastHint: nil,
}
// Set random data at the target memory range
randBytes, err := randomBytes(randSeed, count)
require.NoError(t, err)
err = state.Memory.SetMemoryRange(addr, bytes.NewReader(randBytes))
require.NoError(t, err)
// Set syscall instruction
state.Memory.SetMemory(0, syscallInsn)
preStatePreimageKey := state.PreimageKey
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
expectedRegisters[2] = count
oracle := testutil.StaticOracle(t, preimageData) // only used for hinting
goState := singlethreaded.NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, nil)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, preStatePreimageKey, state.PreimageKey)
require.Equal(t, expectedRegisters, state.Registers)
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStatePreimageWrite(f *testing.F) {
contracts, addrs := testContractsSetup(f)
step := uint64(0)
f.Fuzz(func(t *testing.T, addr uint32, count uint32) {
preimageData := []byte("hello world")
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
},
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysWrite, 4: exec.FdPreimageWrite, 5: addr, 6: count},
Step: 0,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: 128,
}
state.Memory.SetMemory(0, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
sz := 4 - (addr & 0x3)
if sz < count {
count = sz
}
expectedRegisters[2] = count
oracle := testutil.StaticOracle(t, preimageData)
goState := singlethreaded.NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, nil)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, uint32(0), state.PreimageOffset)
require.Equal(t, expectedRegisters, state.Registers)
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func randomBytes(seed int64, length uint32) ([]byte, error) {
r := rand.New(rand.NewSource(seed))
randBytes := make([]byte, length)
if _, err := r.Read(randBytes); err != nil {
return nil, err
}
return randBytes, nil
}
package tests
import (
"io"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
type StateMutator interface {
SetPC(pc uint32)
SetNextPC(nextPC uint32)
SetHeap(addr uint32)
SetLastHint(lastHint hexutil.Bytes)
SetPreimageKey(key common.Hash)
SetPreimageOffset(offset uint32)
SetStep(step uint64)
}
type singlethreadedMutator struct {
state *singlethreaded.State
}
var _ StateMutator = (*singlethreadedMutator)(nil)
func (m *singlethreadedMutator) SetPC(pc uint32) {
m.state.Cpu.PC = pc
}
func (m *singlethreadedMutator) SetNextPC(nextPC uint32) {
m.state.Cpu.NextPC = nextPC
}
func (m *singlethreadedMutator) SetHeap(addr uint32) {
m.state.Heap = addr
}
func (m *singlethreadedMutator) SetLastHint(lastHint hexutil.Bytes) {
m.state.LastHint = lastHint
}
func (m *singlethreadedMutator) SetPreimageKey(key common.Hash) {
m.state.PreimageKey = key
}
func (m *singlethreadedMutator) SetPreimageOffset(offset uint32) {
m.state.PreimageOffset = offset
}
func (m *singlethreadedMutator) SetStep(step uint64) {
m.state.Step = step
}
type multithreadedMutator struct {
state *multithreaded.State
}
var _ StateMutator = (*multithreadedMutator)(nil)
func (m *multithreadedMutator) SetPC(pc uint32) {
thread := m.state.GetCurrentThread()
thread.Cpu.PC = pc
}
func (m *multithreadedMutator) SetHeap(addr uint32) {
m.state.Heap = addr
}
func (m *multithreadedMutator) SetNextPC(nextPC uint32) {
thread := m.state.GetCurrentThread()
thread.Cpu.NextPC = nextPC
}
func (m *multithreadedMutator) SetLastHint(lastHint hexutil.Bytes) {
m.state.LastHint = lastHint
}
func (m *multithreadedMutator) SetPreimageKey(key common.Hash) {
m.state.PreimageKey = key
}
func (m *multithreadedMutator) SetPreimageOffset(offset uint32) {
m.state.PreimageOffset = offset
}
func (m *multithreadedMutator) SetStep(step uint64) {
m.state.Step = step
}
type VMOption func(vm StateMutator)
func WithPC(pc uint32) VMOption {
return func(state StateMutator) {
state.SetPC(pc)
}
}
func WithNextPC(nextPC uint32) VMOption {
return func(state StateMutator) {
state.SetNextPC(nextPC)
}
}
func WithHeap(addr uint32) VMOption {
return func(state StateMutator) {
state.SetHeap(addr)
}
}
func WithLastHint(lastHint hexutil.Bytes) VMOption {
return func(state StateMutator) {
state.SetLastHint(lastHint)
}
}
func WithPreimageKey(key common.Hash) VMOption {
return func(state StateMutator) {
state.SetPreimageKey(key)
}
}
func WithPreimageOffset(offset uint32) VMOption {
return func(state StateMutator) {
state.SetPreimageOffset(offset)
}
}
func WithStep(step uint64) VMOption {
return func(state StateMutator) {
state.SetStep(step)
}
}
type VMFactory func(po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, opts ...VMOption) mipsevm.FPVM
func singleThreadedVmFactory(po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, opts ...VMOption) mipsevm.FPVM {
state := singlethreaded.CreateEmptyState()
mutator := &singlethreadedMutator{state: state}
for _, opt := range opts {
opt(mutator)
}
return singlethreaded.NewInstrumentedState(state, po, stdOut, stdErr, nil)
}
func multiThreadedVmFactory(po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, opts ...VMOption) mipsevm.FPVM {
state := multithreaded.CreateEmptyState()
mutator := &multithreadedMutator{state: state}
for _, opt := range opts {
opt(mutator)
}
return multithreaded.NewInstrumentedState(state, po, stdOut, stdErr, log)
}
type ElfVMFactory func(t require.TestingT, elfFile string, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger) mipsevm.FPVM
func singleThreadElfVmFactory(t require.TestingT, elfFile string, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger) mipsevm.FPVM {
state := testutil.LoadELFProgram(t, elfFile, singlethreaded.CreateInitialState, true)
return singlethreaded.NewInstrumentedState(state, po, stdOut, stdErr, nil)
}
func multiThreadElfVmFactory(t require.TestingT, elfFile string, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger) mipsevm.FPVM {
state := testutil.LoadELFProgram(t, elfFile, multithreaded.CreateInitialState, false)
return multithreaded.NewInstrumentedState(state, po, stdOut, stdErr, log)
}
type VersionedVMTestCase struct {
Name string
Contracts *testutil.ContractMetadata
StateHashFn mipsevm.HashFn
VMFactory VMFactory
ElfVMFactory ElfVMFactory
}
func GetSingleThreadedTestCase(t require.TestingT) VersionedVMTestCase {
return VersionedVMTestCase{
Name: "single-threaded",
Contracts: testutil.TestContractsSetup(t, testutil.MipsSingleThreaded),
StateHashFn: singlethreaded.GetStateHashFn(),
VMFactory: singleThreadedVmFactory,
ElfVMFactory: singleThreadElfVmFactory,
}
}
func GetMultiThreadedTestCase(t require.TestingT) VersionedVMTestCase {
return VersionedVMTestCase{
Name: "multi-threaded",
Contracts: testutil.TestContractsSetup(t, testutil.MipsMultithreaded),
StateHashFn: multithreaded.GetStateHashFn(),
VMFactory: multiThreadedVmFactory,
ElfVMFactory: multiThreadElfVmFactory,
}
}
func GetMipsVersionTestCases(t require.TestingT) []VersionedVMTestCase {
return []VersionedVMTestCase{
GetSingleThreadedTestCase(t),
GetMultiThreadedTestCase(t),
}
}
......@@ -5,3 +5,10 @@ const BaseAddrEnd = 0xbf_ff_ff_f0
// EndAddr is used as return-address for tests
const EndAddr = 0xa7ef00d0
type MipsVersion int
const (
MipsSingleThreaded MipsVersion = iota
MipsMultithreaded
)
......@@ -2,7 +2,6 @@ package testutil
import (
"debug/elf"
"testing"
"github.com/stretchr/testify/require"
......@@ -10,7 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
)
func LoadELFProgram[T mipsevm.FPVMState](t *testing.T, name string, initState program.CreateInitialFPVMState[T], doPatchGo bool) T {
func LoadELFProgram[T mipsevm.FPVMState](t require.TestingT, name string, initState program.CreateInitialFPVMState[T], doPatchGo bool) T {
elfProgram, err := elf.Open(name)
require.NoError(t, err, "open ELF file")
......
......@@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
......@@ -23,9 +24,50 @@ import (
"github.com/ethereum/go-ethereum/params"
)
// LoadArtifacts loads the Cannon contracts, from the contracts package.
func LoadArtifacts() (*Artifacts, error) {
mips, err := foundry.ReadArtifact("../../../packages/contracts-bedrock/forge-artifacts/MIPS.sol/MIPS.json")
type Artifacts struct {
MIPS *foundry.Artifact
Oracle *foundry.Artifact
}
type Addresses struct {
MIPS common.Address
Oracle common.Address
Sender common.Address
FeeRecipient common.Address
}
type ContractMetadata struct {
Artifacts *Artifacts
Addresses *Addresses
}
func TestContractsSetup(t require.TestingT, version MipsVersion) *ContractMetadata {
artifacts, err := loadArtifacts(version)
require.NoError(t, err)
addrs := &Addresses{
MIPS: common.Address{0: 0xff, 19: 1},
Oracle: common.Address{0: 0xff, 19: 2},
Sender: common.Address{0x13, 0x37},
FeeRecipient: common.Address{0xaa},
}
return &ContractMetadata{Artifacts: artifacts, Addresses: addrs}
}
// loadArtifacts loads the Cannon contracts, from the contracts package.
func loadArtifacts(version MipsVersion) (*Artifacts, error) {
var mipsMetadata string
switch version {
case MipsSingleThreaded:
mipsMetadata = "../../../packages/contracts-bedrock/forge-artifacts/MIPS.sol/MIPS.json"
case MipsMultithreaded:
mipsMetadata = "../../../packages/contracts-bedrock/forge-artifacts/MIPS2.sol/MIPS2.json"
default:
return nil, fmt.Errorf("Unknown MipsVersion supplied: %v", version)
}
mips, err := foundry.ReadArtifact(mipsMetadata)
if err != nil {
return nil, fmt.Errorf("failed to load MIPS contract: %w", err)
}
......@@ -41,19 +83,7 @@ func LoadArtifacts() (*Artifacts, error) {
}, nil
}
type Artifacts struct {
MIPS *foundry.Artifact
Oracle *foundry.Artifact
}
type Addresses struct {
MIPS common.Address
Oracle common.Address
Sender common.Address
FeeRecipient common.Address
}
func NewEVMEnv(artifacts *Artifacts, addrs *Addresses) (*vm.EVM, *state.StateDB) {
func NewEVMEnv(contracts *ContractMetadata) (*vm.EVM, *state.StateDB) {
// Temporary hack until Cancun is activated on mainnet
cpy := *params.MainnetChainConfig
chainCfg := &cpy // don't modify the global chain config
......@@ -74,20 +104,20 @@ func NewEVMEnv(artifacts *Artifacts, addrs *Addresses) (*vm.EVM, *state.StateDB)
env := vm.NewEVM(blockContext, vm.TxContext{}, state, chainCfg, vmCfg)
// pre-deploy the contracts
env.StateDB.SetCode(addrs.Oracle, artifacts.Oracle.DeployedBytecode.Object)
env.StateDB.SetCode(contracts.Addresses.Oracle, contracts.Artifacts.Oracle.DeployedBytecode.Object)
var mipsCtorArgs [32]byte
copy(mipsCtorArgs[12:], addrs.Oracle[:])
mipsDeploy := append(bytes.Clone(artifacts.MIPS.Bytecode.Object), mipsCtorArgs[:]...)
copy(mipsCtorArgs[12:], contracts.Addresses.Oracle[:])
mipsDeploy := append(bytes.Clone(contracts.Artifacts.MIPS.Bytecode.Object), mipsCtorArgs[:]...)
startingGas := uint64(30_000_000)
_, deployedMipsAddr, leftOverGas, err := env.Create(vm.AccountRef(addrs.Sender), mipsDeploy, startingGas, common.U2560)
_, deployedMipsAddr, leftOverGas, err := env.Create(vm.AccountRef(contracts.Addresses.Sender), mipsDeploy, startingGas, common.U2560)
if err != nil {
panic(fmt.Errorf("failed to deploy MIPS contract: %w. took %d gas", err, startingGas-leftOverGas))
}
addrs.MIPS = deployedMipsAddr
contracts.Addresses.MIPS = deployedMipsAddr
rules := env.ChainConfig().Rules(header.Number, true, header.Time)
env.StateDB.Prepare(rules, addrs.Sender, addrs.FeeRecipient, &addrs.MIPS, vm.ActivePrecompiles(rules), nil)
env.StateDB.Prepare(rules, contracts.Addresses.Sender, contracts.Addresses.FeeRecipient, &contracts.Addresses.MIPS, vm.ActivePrecompiles(rules), nil)
return env, state
}
......
......@@ -30,9 +30,9 @@ type MIPSEVM struct {
lastStepInput []byte
}
func NewMIPSEVM(artifacts *Artifacts, addrs *Addresses) *MIPSEVM {
env, evmState := NewEVMEnv(artifacts, addrs)
return &MIPSEVM{env, evmState, addrs, nil, artifacts, math.MaxUint64, nil}
func NewMIPSEVM(contracts *ContractMetadata) *MIPSEVM {
env, evmState := NewEVMEnv(contracts)
return &MIPSEVM{env, evmState, contracts.Addresses, nil, contracts.Artifacts, math.MaxUint64, nil}
}
func (m *MIPSEVM) SetTracer(tracer *tracing.Hooks) {
......
package testutil
import "math/rand"
import (
"math/rand"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
)
func RandomRegisters(seed int64) [32]uint32 {
r := rand.New(rand.NewSource(seed))
......@@ -10,3 +14,9 @@ func RandomRegisters(seed int64) [32]uint32 {
}
return registers
}
func CopyRegisters(state mipsevm.FPVMState) *[32]uint32 {
copy := new([32]uint32)
*copy = *state.GetRegistersRef()
return copy
}
......@@ -49,7 +49,7 @@ func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFa
require.NoError(t, err, "load program into state")
// set the return address ($ra) to jump into when test completes
state.GetRegisters()[31] = EndAddr
state.GetRegistersRef()[31] = EndAddr
us := vmFactory(state, oracle, os.Stdout, os.Stderr, CreateLogger())
......
......@@ -133,6 +133,21 @@ contract MIPS2_Test is CommonTest {
vm.label(address(threading), "Threading");
}
/// @notice Used to debug step() behavior given a specific input.
/// This is useful to more easily debug non-forge tests.
/// For example, in cannon/mipsevm/evm_test.go step input can be pulled here:
/// https://github.com/ethereum-optimism/optimism/blob/1f64dd6db5561f3bb76ed1d1ffdaff0cde9b7c4b/cannon/mipsevm/evm_test.go#L80-L80
function test_mips2_step_debug_succeeds() external {
bytes memory input =
hex"e14ced3200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a3df82bcbdf27955e04d467b84d94d0b4662c88a70264d7ea31325bc8d826681ef000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000affffffff00cbf05eda4a03d05cc6a14cff1cf2f955bfb253097c296ea96032da307da4f353ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c6000000000000ffffffff000000000000000000000000000000280000002c00000000000000000000000000000000000000010000000000000000000000000000000000000000fffffffd00000003000000000000000000000000000000000000000000000000bffffff00000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7ef00d0ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5ae020008ae11000403e0000800000000000000000000000000000000000000003c10bfff3610fff0341100013c08ffff3508fffd34090003010950212d420001ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d4e545be579dc7118fc02cd7b19b704e4710a81bce0cb48bb7e289e403e7c969a00000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d6a3e23902bafb21ac312e717f7942f8fd8ae795f67c918083442c2ab253cc66e0000000000000000000000000000000000000000000000000000";
(bool success, bytes memory retVal) = address(mips).call(input);
bytes memory expectedRetVal = hex"03fc952a0bd8aabc407669b857af995eab91ce55c404d8b32eaf8b941a48188c";
assertTrue(success);
assertEq(retVal.length, 32, "Expect a bytes32 hash of the post-state to be returned");
assertEq(retVal, expectedRetVal);
}
function test_stepABI_succeeds() public {
uint32[32] memory registers;
registers[0] = 0xdeadbeef;
......
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