Skip to content

Commit

Permalink
cannon: Run common evm tests across all implementations (ethereum-opt…
Browse files Browse the repository at this point in the history
…imism#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
  • Loading branch information
mbaxter authored and samlaf committed Nov 10, 2024
1 parent c209899 commit bcd1745
Show file tree
Hide file tree
Showing 23 changed files with 1,629 additions and 1,248 deletions.
6 changes: 5 additions & 1 deletion cannon/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,19 @@ 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
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintRead ./mipsevm/tests
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 \
Expand Down
27 changes: 25 additions & 2 deletions cannon/mipsevm/iface.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,31 @@ package mipsevm

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"

"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)

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
Expand All @@ -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)
}
Expand Down
8 changes: 4 additions & 4 deletions cannon/mipsevm/multithreaded/mips.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion cannon/mipsevm/multithreaded/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
50 changes: 35 additions & 15 deletions cannon/mipsevm/multithreaded/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}

Expand All @@ -83,15 +76,15 @@ 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

return state
}

func (s *State) getCurrentThread() *ThreadState {
func (s *State) GetCurrentThread() *ThreadState {
activeStack := s.getActiveThreadStack()

activeStackSize := len(activeStack)
Expand Down Expand Up @@ -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 }
Expand All @@ -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)
}
Expand All @@ -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()
Expand Down Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions cannon/mipsevm/multithreaded/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion cannon/mipsevm/program/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 19 additions & 8 deletions cannon/mipsevm/singlethreaded/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}

Expand Down Expand Up @@ -130,14 +123,20 @@ 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 }

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)
}
Expand All @@ -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()
Expand Down
Loading

0 comments on commit bcd1745

Please sign in to comment.