Commit 4af1fe6e authored by mbaxter's avatar mbaxter Committed by GitHub

cannon: Implement thread-safe ll and sc operations (#11906)

* cannon: Extract RMW op handling from shared logic

* cannon: Add more test cases of LL/SC behavior

* cannon: Cut ll/sc-related logic from common mips code

* cannon: Setup mips helpers to return memory modification info

* cannon: Add new fields to track reserved memory

* cannon: Add a boolean field indicating whether an ll reservation is active

* cannon: Implement ll/sc for MTCannon

* cannon: Add modified sysRead tests for MTCannon

* cannon: Test store operations cleare memory reservations

* cannon: Update SysClockGettime to clear ll memory reservation

* cannon: Fix slither warning - initialize variable

* cannon: Bump MIP2.sol version

* cannon: Run semver, snapshot tasks

* cannon: Cut unused log msg

* cannon: Add new error to MIPS interfaces

* cannon: Cut stale TODO
parent ac443ef0
...@@ -5,6 +5,11 @@ import ( ...@@ -5,6 +5,11 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
) )
const (
OpLoadLinked = 0x30
OpStoreConditional = 0x38
)
func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun uint32) { func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun uint32) {
insn = memory.GetMemory(pc) insn = memory.GetMemory(pc)
opcode = insn >> 26 // First 6-bits opcode = insn >> 26 // First 6-bits
...@@ -13,7 +18,7 @@ func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun ...@@ -13,7 +18,7 @@ func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun
return insn, opcode, fun return insn, opcode, fun
} }
func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memory *memory.Memory, insn, opcode, fun uint32, memTracker MemTracker, stackTracker StackTracker) error { func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memory *memory.Memory, insn, opcode, fun uint32, memTracker MemTracker, stackTracker StackTracker) (memUpdated bool, memAddr uint32, err error) {
// j-type j/jal // j-type j/jal
if opcode == 2 || opcode == 3 { if opcode == 2 || opcode == 3 {
linkReg := uint32(0) linkReg := uint32(0)
...@@ -23,7 +28,8 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor ...@@ -23,7 +28,8 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
// Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset // Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset
target := (cpu.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2) target := (cpu.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2)
stackTracker.PushStack(cpu.PC, target) stackTracker.PushStack(cpu.PC, target)
return HandleJump(cpu, registers, linkReg, target) err = HandleJump(cpu, registers, linkReg, target)
return
} }
// register fetch // register fetch
...@@ -57,7 +63,8 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor ...@@ -57,7 +63,8 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
} }
if (opcode >= 4 && opcode < 8) || opcode == 1 { if (opcode >= 4 && opcode < 8) || opcode == 1 {
return HandleBranch(cpu, registers, opcode, insn, rtReg, rs) err = HandleBranch(cpu, registers, opcode, insn, rtReg, rs)
return
} }
storeAddr := uint32(0xFF_FF_FF_FF) storeAddr := uint32(0xFF_FF_FF_FF)
...@@ -70,7 +77,7 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor ...@@ -70,7 +77,7 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
addr := rs & 0xFFFFFFFC addr := rs & 0xFFFFFFFC
memTracker.TrackMemAccess(addr) memTracker.TrackMemAccess(addr)
mem = memory.GetMemory(addr) mem = memory.GetMemory(addr)
if opcode >= 0x28 && opcode != 0x30 { if opcode >= 0x28 {
// store // store
storeAddr = addr storeAddr = addr
// store opcodes don't write back to a register // store opcodes don't write back to a register
...@@ -90,36 +97,42 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor ...@@ -90,36 +97,42 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
} else { } else {
stackTracker.PopStack() stackTracker.PopStack()
} }
return HandleJump(cpu, registers, linkReg, rs) err = HandleJump(cpu, registers, linkReg, rs)
return
} }
if fun == 0xa { // movz if fun == 0xa { // movz
return HandleRd(cpu, registers, rdReg, rs, rt == 0) err = HandleRd(cpu, registers, rdReg, rs, rt == 0)
return
} }
if fun == 0xb { // movn if fun == 0xb { // movn
return HandleRd(cpu, registers, rdReg, rs, rt != 0) err = HandleRd(cpu, registers, rdReg, rs, rt != 0)
return
} }
// lo and hi registers // lo and hi registers
// can write back // can write back
if fun >= 0x10 && fun < 0x1c { if fun >= 0x10 && fun < 0x1c {
return HandleHiLo(cpu, registers, fun, rs, rt, rdReg) err = HandleHiLo(cpu, registers, fun, rs, rt, rdReg)
return
} }
} }
// store conditional, write a 1 to rt
if opcode == 0x38 && rtReg != 0 {
registers[rtReg] = 1
}
// write memory // write memory
if storeAddr != 0xFF_FF_FF_FF { if storeAddr != 0xFF_FF_FF_FF {
memTracker.TrackMemAccess(storeAddr) memTracker.TrackMemAccess(storeAddr)
memory.SetMemory(storeAddr, val) memory.SetMemory(storeAddr, val)
memUpdated = true
memAddr = storeAddr
} }
// write back the value to destination register // write back the value to destination register
return HandleRd(cpu, registers, rdReg, val, true) err = HandleRd(cpu, registers, rdReg, val, true)
return
}
func SignExtendImmediate(insn uint32) uint32 {
return SignExtend(insn&0xFFFF, 16)
} }
func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 { func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
...@@ -272,10 +285,6 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 { ...@@ -272,10 +285,6 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
val := rt << (24 - (rs&3)*8) val := rt << (24 - (rs&3)*8)
mask := uint32(0xFFFFFFFF) << (24 - (rs&3)*8) mask := uint32(0xFFFFFFFF) << (24 - (rs&3)*8)
return (mem & ^mask) | val return (mem & ^mask) | val
case 0x30: // ll
return mem
case 0x38: // sc
return rt
default: default:
panic("invalid instruction") panic("invalid instruction")
} }
...@@ -382,6 +391,7 @@ func HandleRd(cpu *mipsevm.CpuScalars, registers *[32]uint32, storeReg uint32, v ...@@ -382,6 +391,7 @@ func HandleRd(cpu *mipsevm.CpuScalars, registers *[32]uint32, storeReg uint32, v
panic("invalid register") panic("invalid register")
} }
if storeReg != 0 && conditional { if storeReg != 0 && conditional {
// Register 0 is a special register that always holds a value of 0
registers[storeReg] = val registers[storeReg] = val
} }
cpu.PC = cpu.NextPC cpu.PC = cpu.NextPC
......
...@@ -187,7 +187,7 @@ func HandleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) { ...@@ -187,7 +187,7 @@ func HandleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) {
return v0, v1, newHeap return v0, v1, newHeap
} }
func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint32, preimageReader PreimageReader, memory *memory.Memory, memTracker MemTracker) (v0, v1, newPreimageOffset uint32) { func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint32, preimageReader PreimageReader, memory *memory.Memory, memTracker MemTracker) (v0, v1, newPreimageOffset uint32, memUpdated bool, memAddr uint32) {
// args: a0 = fd, a1 = addr, a2 = count // args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = read, v1 = err code // returns: v0 = read, v1 = err code
v0 = uint32(0) v0 = uint32(0)
...@@ -215,6 +215,8 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3 ...@@ -215,6 +215,8 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
binary.BigEndian.PutUint32(outMem[:], mem) binary.BigEndian.PutUint32(outMem[:], mem)
copy(outMem[alignment:], dat[:datLen]) copy(outMem[alignment:], dat[:datLen])
memory.SetMemory(effAddr, binary.BigEndian.Uint32(outMem[:])) memory.SetMemory(effAddr, binary.BigEndian.Uint32(outMem[:]))
memUpdated = true
memAddr = effAddr
newPreimageOffset += datLen newPreimageOffset += datLen
v0 = datLen v0 = datLen
//fmt.Printf("read %d pre-image bytes, new offset: %d, eff addr: %08x mem: %08x\n", datLen, m.state.PreimageOffset, effAddr, outMem) //fmt.Printf("read %d pre-image bytes, new offset: %d, eff addr: %08x mem: %08x\n", datLen, m.state.PreimageOffset, effAddr, outMem)
...@@ -226,7 +228,7 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3 ...@@ -226,7 +228,7 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
v1 = MipsEBADF v1 = MipsEBADF
} }
return v0, v1, newPreimageOffset return v0, v1, newPreimageOffset, memUpdated, memAddr
} }
func HandleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]byte, preimageOffset uint32, oracle mipsevm.PreimageOracle, memory *memory.Memory, memTracker MemTracker, stdOut, stdErr io.Writer) (v0, v1 uint32, newLastHint hexutil.Bytes, newPreimageKey common.Hash, newPreimageOffset uint32) { func HandleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]byte, preimageOffset uint32, oracle mipsevm.PreimageOracle, memory *memory.Memory, memTracker MemTracker, stdOut, stdErr io.Writer) (v0, v1 uint32, newLastHint hexutil.Bytes, newPreimageKey common.Hash, newPreimageOffset uint32) {
......
...@@ -75,8 +75,13 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -75,8 +75,13 @@ func (m *InstrumentedState) handleSyscall() error {
return nil return nil
case exec.SysRead: case exec.SysRead:
var newPreimageOffset uint32 var newPreimageOffset uint32
v0, v1, newPreimageOffset = exec.HandleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.memoryTracker) var memUpdated bool
var memAddr uint32
v0, v1, newPreimageOffset, memUpdated, memAddr = exec.HandleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.memoryTracker)
m.state.PreimageOffset = newPreimageOffset m.state.PreimageOffset = newPreimageOffset
if memUpdated {
m.handleMemoryUpdate(memAddr)
}
case exec.SysWrite: case exec.SysWrite:
var newLastHint hexutil.Bytes var newLastHint hexutil.Bytes
var newPreimageKey common.Hash var newPreimageKey common.Hash
...@@ -158,8 +163,10 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -158,8 +163,10 @@ func (m *InstrumentedState) handleSyscall() error {
effAddr := a1 & 0xFFffFFfc effAddr := a1 & 0xFFffFFfc
m.memoryTracker.TrackMemAccess(effAddr) m.memoryTracker.TrackMemAccess(effAddr)
m.state.Memory.SetMemory(effAddr, secs) m.state.Memory.SetMemory(effAddr, secs)
m.handleMemoryUpdate(effAddr)
m.memoryTracker.TrackMemAccess2(effAddr + 4) m.memoryTracker.TrackMemAccess2(effAddr + 4)
m.state.Memory.SetMemory(effAddr+4, nsecs) m.state.Memory.SetMemory(effAddr+4, nsecs)
m.handleMemoryUpdate(effAddr + 4)
default: default:
v0 = exec.SysErrorSignal v0 = exec.SysErrorSignal
v1 = exec.MipsEINVAL v1 = exec.MipsEINVAL
...@@ -286,8 +293,71 @@ func (m *InstrumentedState) mipsStep() error { ...@@ -286,8 +293,71 @@ func (m *InstrumentedState) mipsStep() error {
return m.handleSyscall() return m.handleSyscall()
} }
// Handle RMW (read-modify-write) ops
if opcode == exec.OpLoadLinked || opcode == exec.OpStoreConditional {
return m.handleRMWOps(insn, opcode)
}
// Exec the rest of the step logic // Exec the rest of the step logic
return exec.ExecMipsCoreStepLogic(m.state.getCpuRef(), m.state.GetRegistersRef(), m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker) memUpdated, memAddr, err := exec.ExecMipsCoreStepLogic(m.state.getCpuRef(), m.state.GetRegistersRef(), m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker)
if err != nil {
return err
}
if memUpdated {
m.handleMemoryUpdate(memAddr)
}
return nil
}
func (m *InstrumentedState) handleMemoryUpdate(memAddr uint32) {
if memAddr == m.state.LLAddress {
// Reserved address was modified, clear the reservation
m.clearLLMemoryReservation()
}
}
func (m *InstrumentedState) clearLLMemoryReservation() {
m.state.LLReservationActive = false
m.state.LLAddress = 0
m.state.LLOwnerThread = 0
}
// handleRMWOps handles LL and SC operations which provide the primitives to implement read-modify-write operations
func (m *InstrumentedState) handleRMWOps(insn, opcode uint32) error {
baseReg := (insn >> 21) & 0x1F
base := m.state.GetRegistersRef()[baseReg]
rtReg := (insn >> 16) & 0x1F
offset := exec.SignExtendImmediate(insn)
effAddr := (base + offset) & 0xFFFFFFFC
m.memoryTracker.TrackMemAccess(effAddr)
mem := m.state.Memory.GetMemory(effAddr)
var retVal uint32
threadId := m.state.GetCurrentThread().ThreadId
if opcode == exec.OpLoadLinked {
retVal = mem
m.state.LLReservationActive = true
m.state.LLAddress = effAddr
m.state.LLOwnerThread = threadId
} else if opcode == exec.OpStoreConditional {
// Check if our memory reservation is still intact
if m.state.LLReservationActive && m.state.LLOwnerThread == threadId && m.state.LLAddress == effAddr {
// Complete atomic update: set memory and return 1 for success
m.clearLLMemoryReservation()
rt := m.state.GetRegistersRef()[rtReg]
m.state.Memory.SetMemory(effAddr, rt)
retVal = 1
} else {
// Atomic update failed, return 0 for failure
retVal = 0
}
} else {
panic(fmt.Sprintf("Invalid instruction passed to handleRMWOps (opcode %08x)", opcode))
}
return exec.HandleRd(m.state.getCpuRef(), m.state.GetRegistersRef(), rtReg, retVal, true)
} }
func (m *InstrumentedState) onWaitComplete(thread *ThreadState, isTimedOut bool) { func (m *InstrumentedState) onWaitComplete(thread *ThreadState, isTimedOut bool) {
......
...@@ -5,7 +5,6 @@ import ( ...@@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/ethereum-optimism/optimism/cannon/serialize"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
...@@ -14,16 +13,20 @@ import ( ...@@ -14,16 +13,20 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/serialize"
) )
// STATE_WITNESS_SIZE is the size of the state witness encoding in bytes. // STATE_WITNESS_SIZE is the size of the state witness encoding in bytes.
const STATE_WITNESS_SIZE = 163 const STATE_WITNESS_SIZE = 172
const ( const (
MEMROOT_WITNESS_OFFSET = 0 MEMROOT_WITNESS_OFFSET = 0
PREIMAGE_KEY_WITNESS_OFFSET = MEMROOT_WITNESS_OFFSET + 32 PREIMAGE_KEY_WITNESS_OFFSET = MEMROOT_WITNESS_OFFSET + 32
PREIMAGE_OFFSET_WITNESS_OFFSET = PREIMAGE_KEY_WITNESS_OFFSET + 32 PREIMAGE_OFFSET_WITNESS_OFFSET = PREIMAGE_KEY_WITNESS_OFFSET + 32
HEAP_WITNESS_OFFSET = PREIMAGE_OFFSET_WITNESS_OFFSET + 4 HEAP_WITNESS_OFFSET = PREIMAGE_OFFSET_WITNESS_OFFSET + 4
EXITCODE_WITNESS_OFFSET = HEAP_WITNESS_OFFSET + 4 LL_RESERVATION_ACTIVE_OFFSET = HEAP_WITNESS_OFFSET + 4
LL_ADDRESS_OFFSET = LL_RESERVATION_ACTIVE_OFFSET + 1
LL_OWNER_THREAD_OFFSET = LL_ADDRESS_OFFSET + 4
EXITCODE_WITNESS_OFFSET = LL_OWNER_THREAD_OFFSET + 4
EXITED_WITNESS_OFFSET = EXITCODE_WITNESS_OFFSET + 1 EXITED_WITNESS_OFFSET = EXITCODE_WITNESS_OFFSET + 1
STEP_WITNESS_OFFSET = EXITED_WITNESS_OFFSET + 1 STEP_WITNESS_OFFSET = EXITED_WITNESS_OFFSET + 1
STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET = STEP_WITNESS_OFFSET + 8 STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET = STEP_WITNESS_OFFSET + 8
...@@ -40,7 +43,10 @@ type State struct { ...@@ -40,7 +43,10 @@ type State struct {
PreimageKey common.Hash PreimageKey common.Hash
PreimageOffset uint32 // note that the offset includes the 8-byte length prefix PreimageOffset uint32 // note that the offset includes the 8-byte length prefix
Heap uint32 // to handle mmap growth Heap uint32 // to handle mmap growth
LLReservationActive bool // Whether there is an active memory reservation initiated via the LL (load linked) op
LLAddress uint32 // The "linked" memory address reserved via the LL (load linked) op
LLOwnerThread uint32 // The id of the thread that holds the reservation on LLAddress
ExitCode uint8 ExitCode uint8
Exited bool Exited bool
...@@ -64,16 +70,19 @@ func CreateEmptyState() *State { ...@@ -64,16 +70,19 @@ func CreateEmptyState() *State {
initThread := CreateEmptyThread() initThread := CreateEmptyThread()
return &State{ return &State{
Memory: memory.NewMemory(), Memory: memory.NewMemory(),
Heap: 0, Heap: 0,
ExitCode: 0, LLReservationActive: false,
Exited: false, LLAddress: 0,
Step: 0, LLOwnerThread: 0,
Wakeup: exec.FutexEmptyAddr, ExitCode: 0,
TraverseRight: false, Exited: false,
LeftThreadStack: []*ThreadState{initThread}, Step: 0,
RightThreadStack: []*ThreadState{}, Wakeup: exec.FutexEmptyAddr,
NextThreadId: initThread.ThreadId + 1, TraverseRight: false,
LeftThreadStack: []*ThreadState{initThread},
RightThreadStack: []*ThreadState{},
NextThreadId: initThread.ThreadId + 1,
} }
} }
...@@ -187,6 +196,9 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) { ...@@ -187,6 +196,9 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) {
out = append(out, s.PreimageKey[:]...) out = append(out, s.PreimageKey[:]...)
out = binary.BigEndian.AppendUint32(out, s.PreimageOffset) out = binary.BigEndian.AppendUint32(out, s.PreimageOffset)
out = binary.BigEndian.AppendUint32(out, s.Heap) out = binary.BigEndian.AppendUint32(out, s.Heap)
out = mipsevm.AppendBoolToWitness(out, s.LLReservationActive)
out = binary.BigEndian.AppendUint32(out, s.LLAddress)
out = binary.BigEndian.AppendUint32(out, s.LLOwnerThread)
out = append(out, s.ExitCode) out = append(out, s.ExitCode)
out = mipsevm.AppendBoolToWitness(out, s.Exited) out = mipsevm.AppendBoolToWitness(out, s.Exited)
...@@ -264,6 +276,15 @@ func (s *State) Serialize(out io.Writer) error { ...@@ -264,6 +276,15 @@ func (s *State) Serialize(out io.Writer) error {
if err := bout.WriteUInt(s.Heap); err != nil { if err := bout.WriteUInt(s.Heap); err != nil {
return err return err
} }
if err := bout.WriteBool(s.LLReservationActive); err != nil {
return err
}
if err := bout.WriteUInt(s.LLAddress); err != nil {
return err
}
if err := bout.WriteUInt(s.LLOwnerThread); err != nil {
return err
}
if err := bout.WriteUInt(s.ExitCode); err != nil { if err := bout.WriteUInt(s.ExitCode); err != nil {
return err return err
} }
...@@ -324,6 +345,15 @@ func (s *State) Deserialize(in io.Reader) error { ...@@ -324,6 +345,15 @@ func (s *State) Deserialize(in io.Reader) error {
if err := bin.ReadUInt(&s.Heap); err != nil { if err := bin.ReadUInt(&s.Heap); err != nil {
return err return err
} }
if err := bin.ReadBool(&s.LLReservationActive); err != nil {
return err
}
if err := bin.ReadUInt(&s.LLAddress); err != nil {
return err
}
if err := bin.ReadUInt(&s.LLOwnerThread); err != nil {
return err
}
if err := bin.ReadUInt(&s.ExitCode); err != nil { if err := bin.ReadUInt(&s.ExitCode); err != nil {
return err return err
} }
......
...@@ -6,7 +6,6 @@ import ( ...@@ -6,7 +6,6 @@ import (
"encoding/json" "encoding/json"
"testing" "testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
...@@ -15,6 +14,7 @@ import ( ...@@ -15,6 +14,7 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" "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/program"
) )
...@@ -42,6 +42,8 @@ func TestState_EncodeWitness(t *testing.T) { ...@@ -42,6 +42,8 @@ func TestState_EncodeWitness(t *testing.T) {
} }
heap := uint32(12) heap := uint32(12)
llAddress := uint32(55)
llThreadOwner := uint32(99)
preimageKey := crypto.Keccak256Hash([]byte{1, 2, 3, 4}) preimageKey := crypto.Keccak256Hash([]byte{1, 2, 3, 4})
preimageOffset := uint32(24) preimageOffset := uint32(24)
step := uint64(33) step := uint64(33)
...@@ -53,6 +55,9 @@ func TestState_EncodeWitness(t *testing.T) { ...@@ -53,6 +55,9 @@ func TestState_EncodeWitness(t *testing.T) {
state.PreimageKey = preimageKey state.PreimageKey = preimageKey
state.PreimageOffset = preimageOffset state.PreimageOffset = preimageOffset
state.Heap = heap state.Heap = heap
state.LLReservationActive = true
state.LLAddress = llAddress
state.LLOwnerThread = llThreadOwner
state.Step = step state.Step = step
state.StepsSinceLastContextSwitch = stepsSinceContextSwitch state.StepsSinceLastContextSwitch = stepsSinceContextSwitch
...@@ -66,6 +71,9 @@ func TestState_EncodeWitness(t *testing.T) { ...@@ -66,6 +71,9 @@ func TestState_EncodeWitness(t *testing.T) {
setWitnessField(expectedWitness, PREIMAGE_KEY_WITNESS_OFFSET, preimageKey[:]) setWitnessField(expectedWitness, PREIMAGE_KEY_WITNESS_OFFSET, preimageKey[:])
setWitnessField(expectedWitness, PREIMAGE_OFFSET_WITNESS_OFFSET, []byte{0, 0, 0, byte(preimageOffset)}) setWitnessField(expectedWitness, PREIMAGE_OFFSET_WITNESS_OFFSET, []byte{0, 0, 0, byte(preimageOffset)})
setWitnessField(expectedWitness, HEAP_WITNESS_OFFSET, []byte{0, 0, 0, byte(heap)}) setWitnessField(expectedWitness, HEAP_WITNESS_OFFSET, []byte{0, 0, 0, byte(heap)})
setWitnessField(expectedWitness, LL_RESERVATION_ACTIVE_OFFSET, []byte{1})
setWitnessField(expectedWitness, LL_ADDRESS_OFFSET, []byte{0, 0, 0, byte(llAddress)})
setWitnessField(expectedWitness, LL_OWNER_THREAD_OFFSET, []byte{0, 0, 0, byte(llThreadOwner)})
setWitnessField(expectedWitness, EXITCODE_WITNESS_OFFSET, []byte{c.exitCode}) setWitnessField(expectedWitness, EXITCODE_WITNESS_OFFSET, []byte{c.exitCode})
if c.exited { if c.exited {
setWitnessField(expectedWitness, EXITED_WITNESS_OFFSET, []byte{1}) setWitnessField(expectedWitness, EXITED_WITNESS_OFFSET, []byte{1})
...@@ -176,6 +184,9 @@ func TestSerializeStateRoundTrip(t *testing.T) { ...@@ -176,6 +184,9 @@ func TestSerializeStateRoundTrip(t *testing.T) {
PreimageKey: common.Hash{0xFF}, PreimageKey: common.Hash{0xFF},
PreimageOffset: 5, PreimageOffset: 5,
Heap: 0xc0ffee, Heap: 0xc0ffee,
LLReservationActive: true,
LLAddress: 0x12345678,
LLOwnerThread: 0x02,
ExitCode: 1, ExitCode: 1,
Exited: true, Exited: true,
Step: 0xdeadbeef, Step: 0xdeadbeef,
......
...@@ -14,15 +14,18 @@ import ( ...@@ -14,15 +14,18 @@ import (
// ExpectedMTState is a test utility that basically stores a copy of a state that can be explicitly mutated // ExpectedMTState is a test utility that basically stores a copy of a state that can be explicitly mutated
// to define an expected post-state. The post-state is then validated with ExpectedMTState.Validate(t, postState) // to define an expected post-state. The post-state is then validated with ExpectedMTState.Validate(t, postState)
type ExpectedMTState struct { type ExpectedMTState struct {
PreimageKey common.Hash PreimageKey common.Hash
PreimageOffset uint32 PreimageOffset uint32
Heap uint32 Heap uint32
ExitCode uint8 LLReservationActive bool
Exited bool LLAddress uint32
Step uint64 LLOwnerThread uint32
LastHint hexutil.Bytes ExitCode uint8
MemoryRoot common.Hash Exited bool
expectedMemory *memory.Memory Step uint64
LastHint hexutil.Bytes
MemoryRoot common.Hash
expectedMemory *memory.Memory
// Threading-related expectations // Threading-related expectations
StepsSinceLastContextSwitch uint64 StepsSinceLastContextSwitch uint64
Wakeup uint32 Wakeup uint32
...@@ -62,14 +65,17 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState { ...@@ -62,14 +65,17 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
return &ExpectedMTState{ return &ExpectedMTState{
// General Fields // General Fields
PreimageKey: fromState.GetPreimageKey(), PreimageKey: fromState.GetPreimageKey(),
PreimageOffset: fromState.GetPreimageOffset(), PreimageOffset: fromState.GetPreimageOffset(),
Heap: fromState.GetHeap(), Heap: fromState.GetHeap(),
ExitCode: fromState.GetExitCode(), LLReservationActive: fromState.LLReservationActive,
Exited: fromState.GetExited(), LLAddress: fromState.LLAddress,
Step: fromState.GetStep(), LLOwnerThread: fromState.LLOwnerThread,
LastHint: fromState.GetLastHint(), ExitCode: fromState.GetExitCode(),
MemoryRoot: fromState.GetMemory().MerkleRoot(), Exited: fromState.GetExited(),
Step: fromState.GetStep(),
LastHint: fromState.GetLastHint(),
MemoryRoot: fromState.GetMemory().MerkleRoot(),
// Thread-related global fields // Thread-related global fields
StepsSinceLastContextSwitch: fromState.StepsSinceLastContextSwitch, StepsSinceLastContextSwitch: fromState.StepsSinceLastContextSwitch,
Wakeup: fromState.Wakeup, Wakeup: fromState.Wakeup,
...@@ -119,7 +125,7 @@ func (e *ExpectedMTState) ExpectMemoryWrite(addr uint32, val uint32) { ...@@ -119,7 +125,7 @@ func (e *ExpectedMTState) ExpectMemoryWrite(addr uint32, val uint32) {
func (e *ExpectedMTState) ExpectMemoryWriteMultiple(addr uint32, val uint32, addr2 uint32, val2 uint32) { func (e *ExpectedMTState) ExpectMemoryWriteMultiple(addr uint32, val uint32, addr2 uint32, val2 uint32) {
e.expectedMemory.SetMemory(addr, val) e.expectedMemory.SetMemory(addr, val)
e.expectedMemory.SetMemory(addr2, val) e.expectedMemory.SetMemory(addr2, val2)
e.MemoryRoot = e.expectedMemory.MerkleRoot() e.MemoryRoot = e.expectedMemory.MerkleRoot()
} }
...@@ -168,6 +174,9 @@ func (e *ExpectedMTState) Validate(t require.TestingT, actualState *multithreade ...@@ -168,6 +174,9 @@ func (e *ExpectedMTState) Validate(t require.TestingT, actualState *multithreade
require.Equalf(t, e.PreimageKey, actualState.GetPreimageKey(), "Expect preimageKey = %v", e.PreimageKey) require.Equalf(t, e.PreimageKey, actualState.GetPreimageKey(), "Expect preimageKey = %v", e.PreimageKey)
require.Equalf(t, e.PreimageOffset, actualState.GetPreimageOffset(), "Expect preimageOffset = %v", e.PreimageOffset) require.Equalf(t, e.PreimageOffset, actualState.GetPreimageOffset(), "Expect preimageOffset = %v", e.PreimageOffset)
require.Equalf(t, e.Heap, actualState.GetHeap(), "Expect heap = 0x%x", e.Heap) require.Equalf(t, e.Heap, actualState.GetHeap(), "Expect heap = 0x%x", e.Heap)
require.Equalf(t, e.LLReservationActive, actualState.LLReservationActive, "Expect LLReservationActive = %v", e.LLReservationActive)
require.Equalf(t, e.LLAddress, actualState.LLAddress, "Expect LLAddress = 0x%x", e.LLAddress)
require.Equalf(t, e.LLOwnerThread, actualState.LLOwnerThread, "Expect LLOwnerThread = %v", e.LLOwnerThread)
require.Equalf(t, e.ExitCode, actualState.GetExitCode(), "Expect exitCode = 0x%x", e.ExitCode) require.Equalf(t, e.ExitCode, actualState.GetExitCode(), "Expect exitCode = 0x%x", e.ExitCode)
require.Equalf(t, e.Exited, actualState.GetExited(), "Expect exited = %v", e.Exited) require.Equalf(t, e.Exited, actualState.GetExited(), "Expect exited = %v", e.Exited)
require.Equalf(t, e.Step, actualState.GetStep(), "Expect step = %d", e.Step) require.Equalf(t, e.Step, actualState.GetStep(), "Expect step = %d", e.Step)
......
...@@ -28,6 +28,9 @@ func TestValidate_shouldCatchMutations(t *testing.T) { ...@@ -28,6 +28,9 @@ func TestValidate_shouldCatchMutations(t *testing.T) {
{name: "PreimageKey", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.PreimageKey = emptyHash }}, {name: "PreimageKey", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.PreimageKey = emptyHash }},
{name: "PreimageOffset", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.PreimageOffset += 1 }}, {name: "PreimageOffset", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.PreimageOffset += 1 }},
{name: "Heap", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Heap += 1 }}, {name: "Heap", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Heap += 1 }},
{name: "LLReservationActive", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LLReservationActive = !e.LLReservationActive }},
{name: "LLAddress", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LLAddress += 1 }},
{name: "LLOwnerThread", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LLOwnerThread += 1 }},
{name: "ExitCode", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.ExitCode += 1 }}, {name: "ExitCode", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.ExitCode += 1 }},
{name: "Exited", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Exited = !e.Exited }}, {name: "Exited", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Exited = !e.Exited }},
{name: "Step", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Step += 1 }}, {name: "Step", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Step += 1 }},
......
package testutil package testutil
import ( import (
"math"
"math/rand" "math/rand"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -28,11 +29,19 @@ func (m *StateMutatorMultiThreaded) Randomize(randSeed int64) { ...@@ -28,11 +29,19 @@ func (m *StateMutatorMultiThreaded) Randomize(randSeed int64) {
m.state.PreimageKey = testutil.RandHash(r) m.state.PreimageKey = testutil.RandHash(r)
m.state.PreimageOffset = r.Uint32() m.state.PreimageOffset = r.Uint32()
m.state.Heap = r.Uint32()
m.state.Step = step m.state.Step = step
m.state.LastHint = testutil.RandHint(r) m.state.LastHint = testutil.RandHint(r)
m.state.StepsSinceLastContextSwitch = uint64(r.Intn(exec.SchedQuantum)) m.state.StepsSinceLastContextSwitch = uint64(r.Intn(exec.SchedQuantum))
// Randomize memory-related fields
halfMemory := math.MaxUint32 / 2
m.state.Heap = uint32(r.Intn(halfMemory) + halfMemory)
m.state.LLReservationActive = r.Intn(2) == 1
if m.state.LLReservationActive {
m.state.LLAddress = uint32(r.Intn(halfMemory))
m.state.LLOwnerThread = uint32(r.Intn(10))
}
// Randomize threads // Randomize threads
activeStackThreads := r.Intn(2) + 1 activeStackThreads := r.Intn(2) + 1
inactiveStackThreads := r.Intn(3) inactiveStackThreads := r.Intn(3)
......
package singlethreaded package singlethreaded
import ( import (
"fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
...@@ -30,7 +32,7 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -30,7 +32,7 @@ func (m *InstrumentedState) handleSyscall() error {
return nil return nil
case exec.SysRead: case exec.SysRead:
var newPreimageOffset uint32 var newPreimageOffset uint32
v0, v1, newPreimageOffset = exec.HandleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.memoryTracker) v0, v1, newPreimageOffset, _, _ = exec.HandleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.memoryTracker)
m.state.PreimageOffset = newPreimageOffset m.state.PreimageOffset = newPreimageOffset
case exec.SysWrite: case exec.SysWrite:
var newLastHint hexutil.Bytes var newLastHint hexutil.Bytes
...@@ -62,6 +64,37 @@ func (m *InstrumentedState) mipsStep() error { ...@@ -62,6 +64,37 @@ func (m *InstrumentedState) mipsStep() error {
return m.handleSyscall() return m.handleSyscall()
} }
// Handle RMW (read-modify-write) ops
if opcode == exec.OpLoadLinked || opcode == exec.OpStoreConditional {
return m.handleRMWOps(insn, opcode)
}
// Exec the rest of the step logic // Exec the rest of the step logic
return exec.ExecMipsCoreStepLogic(&m.state.Cpu, &m.state.Registers, m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker) _, _, err := exec.ExecMipsCoreStepLogic(&m.state.Cpu, &m.state.Registers, m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker)
return err
}
// handleRMWOps handles LL and SC operations which provide the primitives to implement read-modify-write operations
func (m *InstrumentedState) handleRMWOps(insn, opcode uint32) error {
baseReg := (insn >> 21) & 0x1F
base := m.state.Registers[baseReg]
rtReg := (insn >> 16) & 0x1F
offset := exec.SignExtendImmediate(insn)
effAddr := (base + offset) & 0xFFFFFFFC
m.memoryTracker.TrackMemAccess(effAddr)
mem := m.state.Memory.GetMemory(effAddr)
var retVal uint32
if opcode == exec.OpLoadLinked {
retVal = mem
} else if opcode == exec.OpStoreConditional {
rt := m.state.Registers[rtReg]
m.state.Memory.SetMemory(effAddr, rt)
retVal = 1 // 1 for success
} else {
panic(fmt.Sprintf("Invalid instruction passed to handleRMWOps (opcode %08x)", opcode))
}
return exec.HandleRd(&m.state.Cpu, &m.state.Registers, rtReg, retVal, true)
} }
...@@ -2,7 +2,6 @@ package tests ...@@ -2,7 +2,6 @@ package tests
import ( import (
"bytes" "bytes"
"encoding/binary"
"fmt" "fmt"
"io" "io"
"os" "os"
...@@ -13,14 +12,12 @@ import ( ...@@ -13,14 +12,12 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program" "github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
) )
func TestEVM(t *testing.T) { func TestEVM(t *testing.T) {
...@@ -230,85 +227,6 @@ func TestEVM_MMap(t *testing.T) { ...@@ -230,85 +227,6 @@ func TestEVM_MMap(t *testing.T) {
} }
} }
func TestEVM_SysRead_Preimage(t *testing.T) {
var tracer *tracing.Hooks
preimageValue := make([]byte, 0, 8)
preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x12_34_56_78)
preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x98_76_54_32)
versions := GetMipsVersionTestCases(t)
cases := []struct {
name string
addr uint32
count uint32
writeLen uint32
preimageOffset uint32
prestateMem uint32
postateMem uint32
shouldPanic bool
}{
{name: "Aligned addr, write 1 byte", addr: 0x00_00_FF_00, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_FF_FF_FF},
{name: "Aligned addr, write 2 byte", addr: 0x00_00_FF_00, count: 2, writeLen: 2, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_FF_FF},
{name: "Aligned addr, write 3 byte", addr: 0x00_00_FF_00, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_FF},
{name: "Aligned addr, write 4 byte", addr: 0x00_00_FF_00, count: 4, writeLen: 4, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_78},
{name: "1-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_01, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_FF_FF},
{name: "1-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_01, count: 2, writeLen: 2, preimageOffset: 9, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_34_56_FF},
{name: "1-byte misaligned addr, write 3 byte", addr: 0x00_00_FF_01, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_34_56},
{name: "2-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_02, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_12_FF},
{name: "2-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_02, count: 2, writeLen: 2, preimageOffset: 12, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_98_76},
{name: "3-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_03, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_12},
{name: "Count of 0", addr: 0x00_00_FF_03, count: 0, writeLen: 0, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF},
{name: "Count greater than 4", addr: 0x00_00_FF_00, count: 15, writeLen: 4, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_78},
{name: "Count greater than 4, unaligned", addr: 0x00_00_FF_01, count: 15, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_34_56},
{name: "Offset at last byte", addr: 0x00_00_FF_00, count: 4, writeLen: 1, preimageOffset: 15, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x32_FF_FF_FF},
{name: "Offset just out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 16, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true},
{name: "Offset out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 17, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true},
}
for i, c := range cases {
for _, v := range versions {
tName := fmt.Sprintf("%v (%v)", c.name, v.Name)
t.Run(tName, func(t *testing.T) {
effAddr := 0xFFffFFfc & c.addr
preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageValue)).PreimageKey()
oracle := testutil.StaticOracle(t, preimageValue)
goVm := v.VMFactory(oracle, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPreimageKey(preimageKey), testutil.WithPreimageOffset(c.preimageOffset))
state := goVm.GetState()
step := state.GetStep()
// Set up state
state.GetRegistersRef()[2] = exec.SysRead
state.GetRegistersRef()[4] = exec.FdPreimageRead
state.GetRegistersRef()[5] = c.addr
state.GetRegistersRef()[6] = c.count
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
state.GetMemory().SetMemory(effAddr, c.prestateMem)
// Setup expectations
expected := testutil.NewExpectedState(state)
expected.ExpectStep()
expected.Registers[2] = c.writeLen
expected.Registers[7] = 0 // no error
expected.PreimageOffset += c.writeLen
expected.ExpectMemoryWrite(effAddr, c.postateMem)
if c.shouldPanic {
require.Panics(t, func() { _, _ = goVm.Step(true) })
testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, v.Contracts, tracer)
} else {
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer)
}
})
}
}
}
func TestEVMSysWriteHint(t *testing.T) { func TestEVMSysWriteHint(t *testing.T) {
var tracer *tracing.Hooks var tracer *tracing.Hooks
......
package tests package tests
import ( import (
"encoding/binary"
"fmt" "fmt"
"os" "os"
"slices" "slices"
...@@ -8,6 +9,7 @@ import ( ...@@ -8,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
...@@ -16,8 +18,375 @@ import ( ...@@ -16,8 +18,375 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
mttestutil "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded/testutil" mttestutil "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded/testutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
) )
func TestEVM_MT_LL(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
base uint32
offset int
value uint32
effAddr uint32
rtReg int
}{
{name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5},
{name: "Aligned effAddr, signed extended", base: 0x00_00_00_01, offset: 0xFF33, value: 0xABCD, effAddr: 0xFF_FF_FF_34, rtReg: 5},
{name: "Unaligned effAddr", base: 0xFF_12_00_01, offset: 0x3401, value: 0xABCD, effAddr: 0xFF_12_34_00, rtReg: 5},
{name: "Unaligned effAddr, sign extended w overflow", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 5},
{name: "Return register set to 0", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 0},
}
for i, c := range cases {
for _, withExistingReservation := range []bool{true, false} {
tName := fmt.Sprintf("%v (withExistingReservation = %v)", c.name, withExistingReservation)
t.Run(tName, func(t *testing.T) {
rtReg := c.rtReg
baseReg := 6
pc := uint32(0x44)
insn := uint32((0b11_0000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm, state, contracts := setup(t, i, nil)
step := state.GetStep()
// Set up state
state.GetCurrentThread().Cpu.PC = pc
state.GetCurrentThread().Cpu.NextPC = pc + 4
state.GetMemory().SetMemory(pc, insn)
state.GetMemory().SetMemory(c.effAddr, c.value)
state.GetRegistersRef()[baseReg] = c.base
if withExistingReservation {
state.LLReservationActive = true
state.LLAddress = c.effAddr + uint32(4)
state.LLOwnerThread = 123
} else {
state.LLReservationActive = false
state.LLAddress = 0
state.LLOwnerThread = 0
}
// Set up expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.LLReservationActive = true
expected.LLAddress = c.effAddr
expected.LLOwnerThread = state.GetCurrentThread().ThreadId
if rtReg != 0 {
expected.ActiveThread().Registers[rtReg] = c.value
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
}
}
func TestEVM_MT_SC(t *testing.T) {
var tracer *tracing.Hooks
llVariations := []struct {
name string
llReservationActive bool
matchThreadId bool
matchEffAddr bool
shouldSucceed bool
}{
{name: "should succeed", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldSucceed: true},
{name: "mismatch addr", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldSucceed: false},
{name: "mismatched thread", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldSucceed: false},
{name: "mismatched addr & thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldSucceed: false},
{name: "no active reservation", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldSucceed: false},
}
cases := []struct {
name string
base uint32
offset int
value uint32
effAddr uint32
rtReg int
threadId uint32
}{
{name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5, threadId: 4},
{name: "Aligned effAddr, signed extended", base: 0x00_00_00_01, offset: 0xFF33, value: 0xABCD, effAddr: 0xFF_FF_FF_34, rtReg: 5, threadId: 4},
{name: "Unaligned effAddr", base: 0xFF_12_00_01, offset: 0x3401, value: 0xABCD, effAddr: 0xFF_12_34_00, rtReg: 5, threadId: 4},
{name: "Unaligned effAddr, sign extended w overflow", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 5, threadId: 4},
{name: "Return register set to 0", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 0, threadId: 4},
{name: "Zero valued ll args", base: 0x00_00_00_00, offset: 0x0, value: 0xABCD, effAddr: 0x00_00_00_00, rtReg: 5, threadId: 0},
}
for i, c := range cases {
for _, v := range llVariations {
tName := fmt.Sprintf("%v (%v)", c.name, v.name)
t.Run(tName, func(t *testing.T) {
rtReg := c.rtReg
baseReg := 6
pc := uint32(0x44)
insn := uint32((0b11_1000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm, state, contracts := setup(t, i, nil)
mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1)
step := state.GetStep()
// Define LL-related params
var llAddress, llOwnerThread uint32
if v.matchEffAddr {
llAddress = c.effAddr
} else {
llAddress = c.effAddr + 4
}
if v.matchThreadId {
llOwnerThread = c.threadId
} else {
llOwnerThread = c.threadId + 1
}
// Setup state
state.GetCurrentThread().ThreadId = c.threadId
state.GetCurrentThread().Cpu.PC = pc
state.GetCurrentThread().Cpu.NextPC = pc + 4
state.GetMemory().SetMemory(pc, insn)
state.GetRegistersRef()[baseReg] = c.base
state.GetRegistersRef()[rtReg] = c.value
state.LLReservationActive = v.llReservationActive
state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread
// Setup expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
var retVal uint32
if v.shouldSucceed {
retVal = 1
expected.ExpectMemoryWrite(c.effAddr, c.value)
expected.LLReservationActive = false
expected.LLAddress = 0
expected.LLOwnerThread = 0
} else {
retVal = 0
}
if rtReg != 0 {
expected.ActiveThread().Registers[rtReg] = retVal
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
}
}
func TestEVM_MT_SysRead_Preimage(t *testing.T) {
var tracer *tracing.Hooks
preimageValue := make([]byte, 0, 8)
preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x12_34_56_78)
preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x98_76_54_32)
llVariations := []struct {
name string
llReservationActive bool
matchThreadId bool
matchEffAddr bool
shouldClearReservation bool
}{
{name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false},
{name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
}
cases := []struct {
name string
addr uint32
count uint32
writeLen uint32
preimageOffset uint32
prestateMem uint32
postateMem uint32
shouldPanic bool
}{
{name: "Aligned addr, write 1 byte", addr: 0x00_00_FF_00, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_FF_FF_FF},
{name: "Aligned addr, write 2 byte", addr: 0x00_00_FF_00, count: 2, writeLen: 2, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_FF_FF},
{name: "Aligned addr, write 3 byte", addr: 0x00_00_FF_00, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_FF},
{name: "Aligned addr, write 4 byte", addr: 0x00_00_FF_00, count: 4, writeLen: 4, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_78},
{name: "1-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_01, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_FF_FF},
{name: "1-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_01, count: 2, writeLen: 2, preimageOffset: 9, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_34_56_FF},
{name: "1-byte misaligned addr, write 3 byte", addr: 0x00_00_FF_01, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_34_56},
{name: "2-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_02, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_12_FF},
{name: "2-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_02, count: 2, writeLen: 2, preimageOffset: 12, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_98_76},
{name: "3-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_03, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_12},
{name: "Count of 0", addr: 0x00_00_FF_03, count: 0, writeLen: 0, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF},
{name: "Count greater than 4", addr: 0x00_00_FF_00, count: 15, writeLen: 4, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_78},
{name: "Count greater than 4, unaligned", addr: 0x00_00_FF_01, count: 15, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_34_56},
{name: "Offset at last byte", addr: 0x00_00_FF_00, count: 4, writeLen: 1, preimageOffset: 15, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x32_FF_FF_FF},
{name: "Offset just out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 16, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true},
{name: "Offset out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 17, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true},
}
for i, c := range cases {
for _, v := range llVariations {
tName := fmt.Sprintf("%v (%v)", c.name, v.name)
t.Run(tName, func(t *testing.T) {
effAddr := 0xFFffFFfc & c.addr
preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageValue)).PreimageKey()
oracle := testutil.StaticOracle(t, preimageValue)
goVm, state, contracts := setup(t, i, oracle)
step := state.GetStep()
// Define LL-related params
var llAddress, llOwnerThread uint32
if v.matchEffAddr {
llAddress = effAddr
} else {
llAddress = effAddr + 4
}
if v.matchThreadId {
llOwnerThread = state.GetCurrentThread().ThreadId
} else {
llOwnerThread = state.GetCurrentThread().ThreadId + 1
}
// Set up state
state.PreimageKey = preimageKey
state.PreimageOffset = c.preimageOffset
state.GetRegistersRef()[2] = exec.SysRead
state.GetRegistersRef()[4] = exec.FdPreimageRead
state.GetRegistersRef()[5] = c.addr
state.GetRegistersRef()[6] = c.count
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
state.LLReservationActive = v.llReservationActive
state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread
state.GetMemory().SetMemory(effAddr, c.prestateMem)
// Setup expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.ActiveThread().Registers[2] = c.writeLen
expected.ActiveThread().Registers[7] = 0 // no error
expected.PreimageOffset += c.writeLen
expected.ExpectMemoryWrite(effAddr, c.postateMem)
if v.shouldClearReservation {
expected.LLReservationActive = false
expected.LLAddress = 0
expected.LLOwnerThread = 0
}
if c.shouldPanic {
require.Panics(t, func() { _, _ = goVm.Step(true) })
testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, contracts, tracer)
} else {
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
}
})
}
}
}
func TestEVM_MT_StoreOpsClearMemReservation(t *testing.T) {
var tracer *tracing.Hooks
llVariations := []struct {
name string
llReservationActive bool
matchThreadId bool
matchEffAddr bool
shouldClearReservation bool
}{
{name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
{name: "mismatched reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false},
{name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
}
pc := uint32(0x04)
rt := uint32(0x12_34_56_78)
baseReg := 5
rtReg := 6
cases := []struct {
name string
opcode int
offset int
base uint32
effAddr uint32
preMem uint32
postMem uint32
}{
{name: "Store byte", opcode: 0b10_1000, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x78_FF_FF_FF},
{name: "Store halfword", opcode: 0b10_1001, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x56_78_FF_FF},
{name: "Store word left", opcode: 0b10_1010, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x12_34_56_78},
{name: "Store word", opcode: 0b10_1011, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x12_34_56_78},
{name: "Store word right", opcode: 0b10_1110, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x78_FF_FF_FF},
}
for i, c := range cases {
for _, v := range llVariations {
tName := fmt.Sprintf("%v (%v)", c.name, v.name)
t.Run(tName, func(t *testing.T) {
insn := uint32((c.opcode << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm, state, contracts := setup(t, i, nil)
step := state.GetStep()
// Define LL-related params
var llAddress, llOwnerThread uint32
if v.matchEffAddr {
llAddress = c.effAddr
} else {
llAddress = c.effAddr + 4
}
if v.matchThreadId {
llOwnerThread = state.GetCurrentThread().ThreadId
} else {
llOwnerThread = state.GetCurrentThread().ThreadId + 1
}
// Setup state
state.GetCurrentThread().Cpu.PC = pc
state.GetCurrentThread().Cpu.NextPC = pc + 4
state.GetRegistersRef()[rtReg] = rt
state.GetRegistersRef()[baseReg] = c.base
state.GetMemory().SetMemory(state.GetPC(), insn)
state.GetMemory().SetMemory(c.effAddr, c.preMem)
state.LLReservationActive = v.llReservationActive
state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread
// Setup expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.ExpectMemoryWrite(c.effAddr, c.postMem)
if v.shouldClearReservation {
expected.LLReservationActive = false
expected.LLAddress = 0
expected.LLOwnerThread = 0
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
}
}
func TestEVM_SysClone_FlagHandling(t *testing.T) { func TestEVM_SysClone_FlagHandling(t *testing.T) {
contracts := testutil.TestContractsSetup(t, testutil.MipsMultithreaded) contracts := testutil.TestContractsSetup(t, testutil.MipsMultithreaded)
var tracer *tracing.Hooks var tracer *tracing.Hooks
...@@ -92,7 +461,7 @@ func TestEVM_SysClone_Successful(t *testing.T) { ...@@ -92,7 +461,7 @@ func TestEVM_SysClone_Successful(t *testing.T) {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
stackPtr := uint32(100) stackPtr := uint32(100)
goVm, state, contracts := setup(t, i) goVm, state, contracts := setup(t, i, nil)
mttestutil.InitializeSingleThread(i*333, state, c.traverseRight) mttestutil.InitializeSingleThread(i*333, state, c.traverseRight)
state.Memory.SetMemory(state.GetPC(), syscallInsn) state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysClone // the syscall number state.GetRegistersRef()[2] = exec.SysClone // the syscall number
...@@ -153,7 +522,7 @@ func TestEVM_SysGetTID(t *testing.T) { ...@@ -153,7 +522,7 @@ func TestEVM_SysGetTID(t *testing.T) {
for i, c := range cases { for i, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*789) goVm, state, contracts := setup(t, i*789, nil)
mttestutil.InitializeSingleThread(i*789, state, false) mttestutil.InitializeSingleThread(i*789, state, false)
state.GetCurrentThread().ThreadId = c.threadId state.GetCurrentThread().ThreadId = c.threadId
...@@ -197,7 +566,7 @@ func TestEVM_SysExit(t *testing.T) { ...@@ -197,7 +566,7 @@ func TestEVM_SysExit(t *testing.T) {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
exitCode := uint8(3) exitCode := uint8(3)
goVm, state, contracts := setup(t, i*133) goVm, state, contracts := setup(t, i*133, nil)
mttestutil.SetupThreads(int64(i*1111), state, i%2 == 0, c.threadCount, 0) mttestutil.SetupThreads(int64(i*1111), state, i%2 == 0, c.threadCount, 0)
state.Memory.SetMemory(state.GetPC(), syscallInsn) state.Memory.SetMemory(state.GetPC(), syscallInsn)
...@@ -245,7 +614,7 @@ func TestEVM_PopExitedThread(t *testing.T) { ...@@ -245,7 +614,7 @@ func TestEVM_PopExitedThread(t *testing.T) {
for i, c := range cases { for i, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*133) goVm, state, contracts := setup(t, i*133, nil)
mttestutil.SetupThreads(int64(i*222), state, c.traverseRight, c.activeStackThreadCount, 1) mttestutil.SetupThreads(int64(i*222), state, c.traverseRight, c.activeStackThreadCount, 1)
step := state.Step step := state.Step
...@@ -300,7 +669,7 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) { ...@@ -300,7 +669,7 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) {
for i, c := range cases { for i, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*1234) goVm, state, contracts := setup(t, i*1234, nil)
step := state.GetStep() step := state.GetStep()
state.Memory.SetMemory(state.GetPC(), syscallInsn) state.Memory.SetMemory(state.GetPC(), syscallInsn)
...@@ -363,7 +732,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) { ...@@ -363,7 +732,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) {
for i, c := range cases { for i, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*1122) goVm, state, contracts := setup(t, i*1122, nil)
mttestutil.SetupThreads(int64(i*2244), state, c.traverseRight, c.activeThreadCount, c.inactiveThreadCount) mttestutil.SetupThreads(int64(i*2244), state, c.traverseRight, c.activeThreadCount, c.inactiveThreadCount)
step := state.Step step := state.Step
...@@ -449,7 +818,7 @@ func TestEVM_SysFutex_UnsupportedOp(t *testing.T) { ...@@ -449,7 +818,7 @@ func TestEVM_SysFutex_UnsupportedOp(t *testing.T) {
for name, op := range unsupportedFutexOps { for name, op := range unsupportedFutexOps {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
goVm, state, contracts := setup(t, int(op)) goVm, state, contracts := setup(t, int(op), nil)
step := state.GetStep() step := state.GetStep()
state.Memory.SetMemory(state.GetPC(), syscallInsn) state.Memory.SetMemory(state.GetPC(), syscallInsn)
...@@ -504,7 +873,7 @@ func runPreemptSyscall(t *testing.T, syscallName string, syscallNum uint32) { ...@@ -504,7 +873,7 @@ func runPreemptSyscall(t *testing.T, syscallName string, syscallNum uint32) {
for _, traverseRight := range []bool{true, false} { for _, traverseRight := range []bool{true, false} {
testName := fmt.Sprintf("%v: %v (traverseRight = %v)", syscallName, c.name, traverseRight) testName := fmt.Sprintf("%v: %v (traverseRight = %v)", syscallName, c.name, traverseRight)
t.Run(testName, func(t *testing.T) { t.Run(testName, func(t *testing.T) {
goVm, state, contracts := setup(t, i*789) goVm, state, contracts := setup(t, i*789, nil)
mttestutil.SetupThreads(int64(i*3259), state, traverseRight, c.activeThreads, c.inactiveThreads) mttestutil.SetupThreads(int64(i*3259), state, traverseRight, c.activeThreads, c.inactiveThreads)
state.Memory.SetMemory(state.GetPC(), syscallInsn) state.Memory.SetMemory(state.GetPC(), syscallInsn)
...@@ -535,7 +904,7 @@ func runPreemptSyscall(t *testing.T, syscallName string, syscallNum uint32) { ...@@ -535,7 +904,7 @@ func runPreemptSyscall(t *testing.T, syscallName string, syscallNum uint32) {
func TestEVM_SysOpen(t *testing.T) { func TestEVM_SysOpen(t *testing.T) {
var tracer *tracing.Hooks var tracer *tracing.Hooks
goVm, state, contracts := setup(t, 5512) goVm, state, contracts := setup(t, 5512, nil)
state.Memory.SetMemory(state.GetPC(), syscallInsn) state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysOpen // Set syscall number state.GetRegistersRef()[2] = exec.SysOpen // Set syscall number
...@@ -560,7 +929,7 @@ func TestEVM_SysOpen(t *testing.T) { ...@@ -560,7 +929,7 @@ func TestEVM_SysOpen(t *testing.T) {
func TestEVM_SysGetPID(t *testing.T) { func TestEVM_SysGetPID(t *testing.T) {
var tracer *tracing.Hooks var tracer *tracing.Hooks
goVm, state, contracts := setup(t, 1929) goVm, state, contracts := setup(t, 1929, nil)
state.Memory.SetMemory(state.GetPC(), syscallInsn) state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysGetpid // Set syscall number state.GetRegistersRef()[2] = exec.SysGetpid // Set syscall number
...@@ -594,6 +963,25 @@ func TestEVM_SysClockGettimeRealtime(t *testing.T) { ...@@ -594,6 +963,25 @@ func TestEVM_SysClockGettimeRealtime(t *testing.T) {
func testEVM_SysClockGettime(t *testing.T, clkid uint32) { func testEVM_SysClockGettime(t *testing.T, clkid uint32) {
var tracer *tracing.Hooks var tracer *tracing.Hooks
llVariations := []struct {
name string
llReservationActive bool
matchThreadId bool
matchEffAddr bool
matchEffAddr2 bool
shouldClearReservation bool
}{
{name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, 2nd word", llReservationActive: true, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, diff thread, 2nd word", llReservationActive: true, matchThreadId: false, matchEffAddr2: true, shouldClearReservation: true},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
{name: "mismatched reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false},
{name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "no reservation, matching addr2", llReservationActive: false, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true},
{name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
}
cases := []struct { cases := []struct {
name string name string
timespecAddr uint32 timespecAddr uint32
...@@ -601,45 +989,73 @@ func testEVM_SysClockGettime(t *testing.T, clkid uint32) { ...@@ -601,45 +989,73 @@ func testEVM_SysClockGettime(t *testing.T, clkid uint32) {
{"aligned timespec address", 0x1000}, {"aligned timespec address", 0x1000},
{"unaligned timespec address", 0x1003}, {"unaligned timespec address", 0x1003},
} }
for _, c := range cases { for i, c := range cases {
t.Run(c.name, func(t *testing.T) { for _, v := range llVariations {
goVm, state, contracts := setup(t, 2101) tName := fmt.Sprintf("%v (%v)", c.name, v.name)
t.Run(tName, func(t *testing.T) {
goVm, state, contracts := setup(t, 2101, nil)
mttestutil.InitializeSingleThread(2101+i, state, i%2 == 1)
effAddr := c.timespecAddr & 0xFFffFFfc
effAddr2 := effAddr + 4
step := state.Step
state.Memory.SetMemory(state.GetPC(), syscallInsn) // Define LL-related params
state.GetRegistersRef()[2] = exec.SysClockGetTime // Set syscall number var llAddress, llOwnerThread uint32
state.GetRegistersRef()[4] = clkid // a0 if v.matchEffAddr {
state.GetRegistersRef()[5] = c.timespecAddr // a1 llAddress = effAddr
step := state.Step } else if v.matchEffAddr2 {
llAddress = effAddr2
} else {
llAddress = effAddr2 + 8
}
if v.matchThreadId {
llOwnerThread = state.GetCurrentThread().ThreadId
} else {
llOwnerThread = state.GetCurrentThread().ThreadId + 1
}
expected := mttestutil.NewExpectedMTState(state) state.Memory.SetMemory(state.GetPC(), syscallInsn)
expected.ExpectStep() state.GetRegistersRef()[2] = exec.SysClockGetTime // Set syscall number
expected.ActiveThread().Registers[2] = 0 state.GetRegistersRef()[4] = clkid // a0
expected.ActiveThread().Registers[7] = 0 state.GetRegistersRef()[5] = c.timespecAddr // a1
next := state.Step + 1 state.LLReservationActive = v.llReservationActive
var secs, nsecs uint32 state.LLAddress = llAddress
if clkid == exec.ClockGettimeMonotonicFlag { state.LLOwnerThread = llOwnerThread
secs = uint32(next / exec.HZ)
nsecs = uint32((next % exec.HZ) * (1_000_000_000 / exec.HZ))
}
effAddr := c.timespecAddr & 0xFFffFFfc
expected.ExpectMemoryWrite(effAddr, secs)
expected.ExpectMemoryWrite(effAddr+4, nsecs)
var err error expected := mttestutil.NewExpectedMTState(state)
var stepWitness *mipsevm.StepWitness expected.ExpectStep()
stepWitness, err = goVm.Step(true) expected.ActiveThread().Registers[2] = 0
require.NoError(t, err) expected.ActiveThread().Registers[7] = 0
next := state.Step + 1
var secs, nsecs uint32
if clkid == exec.ClockGettimeMonotonicFlag {
secs = uint32(next / exec.HZ)
nsecs = uint32((next % exec.HZ) * (1_000_000_000 / exec.HZ))
}
expected.ExpectMemoryWrite(effAddr, secs)
expected.ExpectMemoryWrite(effAddr2, nsecs)
if v.shouldClearReservation {
expected.LLReservationActive = false
expected.LLAddress = 0
expected.LLOwnerThread = 0
}
// Validate post-state var err error
expected.Validate(t, state) var stepWitness *mipsevm.StepWitness
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) stepWitness, err = goVm.Step(true)
}) require.NoError(t, err)
// Validate post-state
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
} }
} }
func TestEVM_SysClockGettimeNonMonotonic(t *testing.T) { func TestEVM_SysClockGettimeNonMonotonic(t *testing.T) {
var tracer *tracing.Hooks var tracer *tracing.Hooks
goVm, state, contracts := setup(t, 2101) goVm, state, contracts := setup(t, 2101, nil)
timespecAddr := uint32(0x1000) timespecAddr := uint32(0x1000)
state.Memory.SetMemory(state.GetPC(), syscallInsn) state.Memory.SetMemory(state.GetPC(), syscallInsn)
...@@ -700,7 +1116,7 @@ func TestEVM_NoopSyscall(t *testing.T) { ...@@ -700,7 +1116,7 @@ func TestEVM_NoopSyscall(t *testing.T) {
var tracer *tracing.Hooks var tracer *tracing.Hooks
for noopName, noopVal := range NoopSyscalls { for noopName, noopVal := range NoopSyscalls {
t.Run(noopName, func(t *testing.T) { t.Run(noopName, func(t *testing.T) {
goVm, state, contracts := setup(t, int(noopVal)) goVm, state, contracts := setup(t, int(noopVal), nil)
state.Memory.SetMemory(state.GetPC(), syscallInsn) state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = noopVal // Set syscall number state.GetRegistersRef()[2] = noopVal // Set syscall number
...@@ -747,7 +1163,7 @@ func TestEVM_UnsupportedSyscall(t *testing.T) { ...@@ -747,7 +1163,7 @@ func TestEVM_UnsupportedSyscall(t *testing.T) {
syscallNum := syscallNum syscallNum := syscallNum
t.Run(testName, func(t *testing.T) { t.Run(testName, func(t *testing.T) {
t.Parallel() t.Parallel()
goVm, state, contracts := setup(t, i*3434) goVm, state, contracts := setup(t, i*3434, nil)
// Setup basic getThreadId syscall instruction // Setup basic getThreadId syscall instruction
state.Memory.SetMemory(state.GetPC(), syscallInsn) state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = syscallNum state.GetRegistersRef()[2] = syscallNum
...@@ -794,7 +1210,7 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) { ...@@ -794,7 +1210,7 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) {
require.Fail(t, "Invalid test case - cannot expect a timeout with no wakeup") require.Fail(t, "Invalid test case - cannot expect a timeout with no wakeup")
} }
goVm, state, contracts := setup(t, i) goVm, state, contracts := setup(t, i, nil)
mttestutil.SetupThreads(int64(i*101), state, traverseRight, c.activeStackSize, c.otherStackSize) mttestutil.SetupThreads(int64(i*101), state, traverseRight, c.activeStackSize, c.otherStackSize)
state.Step = c.step state.Step = c.step
...@@ -856,7 +1272,7 @@ func TestEVM_NormalTraversal_Full(t *testing.T) { ...@@ -856,7 +1272,7 @@ func TestEVM_NormalTraversal_Full(t *testing.T) {
testName := fmt.Sprintf("%v (traverseRight = %v)", c.name, traverseRight) testName := fmt.Sprintf("%v (traverseRight = %v)", c.name, traverseRight)
t.Run(testName, func(t *testing.T) { t.Run(testName, func(t *testing.T) {
// Setup // Setup
goVm, state, contracts := setup(t, i*789) goVm, state, contracts := setup(t, i*789, nil)
mttestutil.SetupThreads(int64(i*2947), state, traverseRight, c.threadCount, 0) mttestutil.SetupThreads(int64(i*2947), state, traverseRight, c.threadCount, 0)
// Put threads into a waiting state so that we just traverse through them // Put threads into a waiting state so that we just traverse through them
for _, thread := range mttestutil.GetAllThreads(state) { for _, thread := range mttestutil.GetAllThreads(state) {
...@@ -926,7 +1342,7 @@ func TestEVM_WakeupTraversalStep(t *testing.T) { ...@@ -926,7 +1342,7 @@ func TestEVM_WakeupTraversalStep(t *testing.T) {
for i, c := range cases { for i, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*2000) goVm, state, contracts := setup(t, i*2000, nil)
mttestutil.SetupThreads(int64(i*101), state, c.traverseRight, c.activeStackSize, c.otherStackSize) mttestutil.SetupThreads(int64(i*101), state, c.traverseRight, c.activeStackSize, c.otherStackSize)
step := state.Step step := state.Step
...@@ -974,7 +1390,7 @@ func TestEVM_WakeupTraversal_Full(t *testing.T) { ...@@ -974,7 +1390,7 @@ func TestEVM_WakeupTraversal_Full(t *testing.T) {
for i, c := range cases { for i, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
// Setup // Setup
goVm, state, contracts := setup(t, i*789) goVm, state, contracts := setup(t, i*789, nil)
mttestutil.SetupThreads(int64(i*2947), state, false, c.threadCount, 0) mttestutil.SetupThreads(int64(i*2947), state, false, c.threadCount, 0)
state.Wakeup = 0x08 state.Wakeup = 0x08
step := state.Step step := state.Step
...@@ -1028,7 +1444,7 @@ func TestEVM_SchedQuantumThreshold(t *testing.T) { ...@@ -1028,7 +1444,7 @@ func TestEVM_SchedQuantumThreshold(t *testing.T) {
for i, c := range cases { for i, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*789) goVm, state, contracts := setup(t, i*789, nil)
// Setup basic getThreadId syscall instruction // Setup basic getThreadId syscall instruction
state.Memory.SetMemory(state.GetPC(), syscallInsn) state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysGetTID // Set syscall number state.GetRegistersRef()[2] = exec.SysGetTID // Set syscall number
...@@ -1060,9 +1476,9 @@ func TestEVM_SchedQuantumThreshold(t *testing.T) { ...@@ -1060,9 +1476,9 @@ func TestEVM_SchedQuantumThreshold(t *testing.T) {
} }
} }
func setup(t require.TestingT, randomSeed int) (mipsevm.FPVM, *multithreaded.State, *testutil.ContractMetadata) { func setup(t require.TestingT, randomSeed int, preimageOracle mipsevm.PreimageOracle) (mipsevm.FPVM, *multithreaded.State, *testutil.ContractMetadata) {
v := GetMultiThreadedTestCase(t) v := GetMultiThreadedTestCase(t)
vm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(randomSeed))) vm := v.VMFactory(preimageOracle, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(randomSeed)))
state := mttestutil.GetMtState(t, vm) state := mttestutil.GetMtState(t, vm)
return vm, state, v.Contracts return vm, state, v.Contracts
......
package tests
import (
"encoding/binary"
"os"
"testing"
"github.com/ethereum/go-ethereum/core/tracing"
"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/testutil"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
)
func TestEVM_LL(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
base uint32
offset int
value uint32
effAddr uint32
rtReg int
}{
{name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5},
{name: "Aligned effAddr, signed extended", base: 0x00_00_00_01, offset: 0xFF33, value: 0xABCD, effAddr: 0xFF_FF_FF_34, rtReg: 5},
{name: "Unaligned effAddr", base: 0xFF_12_00_01, offset: 0x3401, value: 0xABCD, effAddr: 0xFF_12_34_00, rtReg: 5},
{name: "Unaligned effAddr, sign extended w overflow", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 5},
{name: "Return register set to 0", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 0},
}
v := GetSingleThreadedTestCase(t)
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
rtReg := c.rtReg
baseReg := 6
pc := uint32(0x44)
insn := uint32((0b11_0000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(pc), testutil.WithNextPC(pc+4))
state := goVm.GetState()
state.GetMemory().SetMemory(pc, insn)
state.GetMemory().SetMemory(c.effAddr, c.value)
state.GetRegistersRef()[baseReg] = c.base
step := state.GetStep()
// Setup expectations
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = pc + 4
expected.NextPC = pc + 8
if rtReg != 0 {
expected.Registers[rtReg] = c.value
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer)
})
}
}
func TestEVM_SC(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
base uint32
offset int
value uint32
effAddr uint32
rtReg int
}{
{name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5},
{name: "Aligned effAddr, signed extended", base: 0x00_00_00_01, offset: 0xFF33, value: 0xABCD, effAddr: 0xFF_FF_FF_34, rtReg: 5},
{name: "Unaligned effAddr", base: 0xFF_12_00_01, offset: 0x3401, value: 0xABCD, effAddr: 0xFF_12_34_00, rtReg: 5},
{name: "Unaligned effAddr, sign extended w overflow", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 5},
{name: "Return register set to 0", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 0},
}
v := GetSingleThreadedTestCase(t)
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
rtReg := c.rtReg
baseReg := 6
pc := uint32(0x44)
insn := uint32((0b11_1000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(pc), testutil.WithNextPC(pc+4))
state := goVm.GetState()
state.GetMemory().SetMemory(pc, insn)
state.GetRegistersRef()[baseReg] = c.base
state.GetRegistersRef()[rtReg] = c.value
step := state.GetStep()
// Setup expectations
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = pc + 4
expected.NextPC = pc + 8
expectedMemory := memory.NewMemory()
expectedMemory.SetMemory(pc, insn)
expectedMemory.SetMemory(c.effAddr, c.value)
expected.MemoryRoot = expectedMemory.MerkleRoot()
if rtReg != 0 {
expected.Registers[rtReg] = 1 // 1 for success
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer)
})
}
}
func TestEVM_SysRead_Preimage(t *testing.T) {
var tracer *tracing.Hooks
preimageValue := make([]byte, 0, 8)
preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x12_34_56_78)
preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x98_76_54_32)
v := GetSingleThreadedTestCase(t)
cases := []struct {
name string
addr uint32
count uint32
writeLen uint32
preimageOffset uint32
prestateMem uint32
postateMem uint32
shouldPanic bool
}{
{name: "Aligned addr, write 1 byte", addr: 0x00_00_FF_00, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_FF_FF_FF},
{name: "Aligned addr, write 2 byte", addr: 0x00_00_FF_00, count: 2, writeLen: 2, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_FF_FF},
{name: "Aligned addr, write 3 byte", addr: 0x00_00_FF_00, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_FF},
{name: "Aligned addr, write 4 byte", addr: 0x00_00_FF_00, count: 4, writeLen: 4, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_78},
{name: "1-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_01, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_FF_FF},
{name: "1-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_01, count: 2, writeLen: 2, preimageOffset: 9, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_34_56_FF},
{name: "1-byte misaligned addr, write 3 byte", addr: 0x00_00_FF_01, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_34_56},
{name: "2-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_02, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_12_FF},
{name: "2-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_02, count: 2, writeLen: 2, preimageOffset: 12, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_98_76},
{name: "3-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_03, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_12},
{name: "Count of 0", addr: 0x00_00_FF_03, count: 0, writeLen: 0, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF},
{name: "Count greater than 4", addr: 0x00_00_FF_00, count: 15, writeLen: 4, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_78},
{name: "Count greater than 4, unaligned", addr: 0x00_00_FF_01, count: 15, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_34_56},
{name: "Offset at last byte", addr: 0x00_00_FF_00, count: 4, writeLen: 1, preimageOffset: 15, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x32_FF_FF_FF},
{name: "Offset just out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 16, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true},
{name: "Offset out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 17, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true},
}
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
effAddr := 0xFFffFFfc & c.addr
preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageValue)).PreimageKey()
oracle := testutil.StaticOracle(t, preimageValue)
goVm := v.VMFactory(oracle, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPreimageKey(preimageKey), testutil.WithPreimageOffset(c.preimageOffset))
state := goVm.GetState()
step := state.GetStep()
// Set up state
state.GetRegistersRef()[2] = exec.SysRead
state.GetRegistersRef()[4] = exec.FdPreimageRead
state.GetRegistersRef()[5] = c.addr
state.GetRegistersRef()[6] = c.count
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
state.GetMemory().SetMemory(effAddr, c.prestateMem)
// Setup expectations
expected := testutil.NewExpectedState(state)
expected.ExpectStep()
expected.Registers[2] = c.writeLen
expected.Registers[7] = 0 // no error
expected.PreimageOffset += c.writeLen
expected.ExpectMemoryWrite(effAddr, c.postateMem)
if c.shouldPanic {
require.Panics(t, func() { _, _ = goVm.Step(true) })
testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, v.Contracts, tracer)
} else {
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer)
}
})
}
}
...@@ -140,12 +140,12 @@ ...@@ -140,12 +140,12 @@
"sourceCodeHash": "0x3f7bd622a788b8d00fe1631b14b761488eedccf56879f7ea2d610dd5ce81efbe" "sourceCodeHash": "0x3f7bd622a788b8d00fe1631b14b761488eedccf56879f7ea2d610dd5ce81efbe"
}, },
"src/cannon/MIPS.sol": { "src/cannon/MIPS.sol": {
"initCodeHash": "0x6add59adb849ec02e13b33df7efd439ca80f6a8ceefdf69ebcb0963c0167da23", "initCodeHash": "0x4043f262804931bbbbecff64f87f2d0bdc4554b4d0a8b22df8fff940e8d239bf",
"sourceCodeHash": "0xee1aef5a502f9491b7b83dab46ea2f0fc286f87ace31edcc1367c840d462bdfe" "sourceCodeHash": "0xba4674e1846afbbc708877332a38dfabd4b8d1e48ce07d8ebf0a45c9f27f16b0"
}, },
"src/cannon/MIPS2.sol": { "src/cannon/MIPS2.sol": {
"initCodeHash": "0xeab5f44d7fa7af1072f500c754bb55aa92409a79b2765a66efed47461c0c4049", "initCodeHash": "0xdaed5d70cc84a53f224c28f24f8eef26d5d53dfba9fdc4f1b28c3b231b974e53",
"sourceCodeHash": "0xcf180ff2cab8d0a34f3bb19d7145f5bf7f67dd379722ece765d09b38dcfed2f6" "sourceCodeHash": "0x4026eb7ae7b303ec4c3c2880e14e260dbcfc0b4290459bcd22994cfed8655f80"
}, },
"src/cannon/PreimageOracle.sol": { "src/cannon/PreimageOracle.sol": {
"initCodeHash": "0x801e52f9c8439fcf7089575fa93272dfb874641dbfc7d82f36d979c987271c0b", "initCodeHash": "0x801e52f9c8439fcf7089575fa93272dfb874641dbfc7d82f36d979c987271c0b",
......
...@@ -69,5 +69,10 @@ ...@@ -69,5 +69,10 @@
"inputs": [], "inputs": [],
"name": "InvalidMemoryProof", "name": "InvalidMemoryProof",
"type": "error" "type": "error"
},
{
"inputs": [],
"name": "InvalidRMWInstruction",
"type": "error"
} }
] ]
\ No newline at end of file
...@@ -75,6 +75,11 @@ ...@@ -75,6 +75,11 @@
"name": "InvalidMemoryProof", "name": "InvalidMemoryProof",
"type": "error" "type": "error"
}, },
{
"inputs": [],
"name": "InvalidRMWInstruction",
"type": "error"
},
{ {
"inputs": [], "inputs": [],
"name": "InvalidSecondMemoryProof", "name": "InvalidSecondMemoryProof",
......
...@@ -8,6 +8,7 @@ import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.s ...@@ -8,6 +8,7 @@ import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.s
import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol"; import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol";
import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol";
import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol";
import { InvalidRMWInstruction } from "src/cannon/libraries/CannonErrors.sol";
/// @title MIPS /// @title MIPS
/// @notice The MIPS contract emulates a single MIPS instruction. /// @notice The MIPS contract emulates a single MIPS instruction.
...@@ -44,8 +45,8 @@ contract MIPS is ISemver { ...@@ -44,8 +45,8 @@ contract MIPS is ISemver {
} }
/// @notice The semantic version of the MIPS contract. /// @notice The semantic version of the MIPS contract.
/// @custom:semver 1.1.1-beta.3 /// @custom:semver 1.1.1-beta.4
string public constant version = "1.1.1-beta.3"; string public constant version = "1.1.1-beta.4";
/// @notice The preimage oracle contract. /// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE; IPreimageOracle internal immutable ORACLE;
...@@ -178,7 +179,7 @@ contract MIPS is ISemver { ...@@ -178,7 +179,7 @@ contract MIPS is ISemver {
proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1), proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1),
memRoot: state.memRoot memRoot: state.memRoot
}); });
(v0, v1, state.preimageOffset, state.memRoot) = sys.handleSysRead(args); (v0, v1, state.preimageOffset, state.memRoot,,) = sys.handleSysRead(args);
} else if (syscall_no == sys.SYS_WRITE) { } else if (syscall_no == sys.SYS_WRITE) {
(v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({ (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({
_a0: a0, _a0: a0,
...@@ -291,23 +292,59 @@ contract MIPS is ISemver { ...@@ -291,23 +292,59 @@ contract MIPS is ISemver {
return handleSyscall(_localContext); return handleSyscall(_localContext);
} }
// Handle RMW (read-modify-write) ops
if (opcode == ins.OP_LOAD_LINKED || opcode == ins.OP_STORE_CONDITIONAL) {
return handleRMWOps(state, insn, opcode);
}
// Exec the rest of the step logic // Exec the rest of the step logic
st.CpuScalars memory cpu = getCpuScalars(state); st.CpuScalars memory cpu = getCpuScalars(state);
(state.memRoot) = ins.execMipsCoreStepLogic({ ins.CoreStepLogicParams memory coreStepArgs = ins.CoreStepLogicParams({
_cpu: cpu, cpu: cpu,
_registers: state.registers, registers: state.registers,
_memRoot: state.memRoot, memRoot: state.memRoot,
_memProofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1), memProofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1),
_insn: insn, insn: insn,
_opcode: opcode, opcode: opcode,
_fun: fun fun: fun
}); });
(state.memRoot,,) = ins.execMipsCoreStepLogic(coreStepArgs);
setStateCpuScalars(state, cpu); setStateCpuScalars(state, cpu);
return outputState(); return outputState();
} }
} }
function handleRMWOps(State memory _state, uint32 _insn, uint32 _opcode) internal returns (bytes32) {
unchecked {
uint32 baseReg = (_insn >> 21) & 0x1F;
uint32 base = _state.registers[baseReg];
uint32 rtReg = (_insn >> 16) & 0x1F;
uint32 offset = ins.signExtendImmediate(_insn);
uint32 effAddr = (base + offset) & 0xFFFFFFFC;
uint256 memProofOffset = MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1);
uint32 mem = MIPSMemory.readMem(_state.memRoot, effAddr, memProofOffset);
uint32 retVal;
if (_opcode == ins.OP_LOAD_LINKED) {
retVal = mem;
} else if (_opcode == ins.OP_STORE_CONDITIONAL) {
uint32 val = _state.registers[rtReg];
_state.memRoot = MIPSMemory.writeMem(effAddr, memProofOffset, val);
retVal = 1; // 1 for success
} else {
revert InvalidRMWInstruction();
}
st.CpuScalars memory cpu = getCpuScalars(_state);
ins.handleRd(cpu, _state.registers, rtReg, retVal, true);
setStateCpuScalars(_state, cpu);
return outputState();
}
}
function getCpuScalars(State memory _state) internal pure returns (st.CpuScalars memory) { function getCpuScalars(State memory _state) internal pure returns (st.CpuScalars memory) {
return st.CpuScalars({ pc: _state.pc, nextPC: _state.nextPC, lo: _state.lo, hi: _state.hi }); return st.CpuScalars({ pc: _state.pc, nextPC: _state.nextPC, lo: _state.lo, hi: _state.hi });
} }
......
...@@ -8,7 +8,9 @@ import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol"; ...@@ -8,7 +8,9 @@ import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol";
import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol";
import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.sol"; import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.sol";
import { VMStatuses } from "src/dispute/lib/Types.sol"; import { VMStatuses } from "src/dispute/lib/Types.sol";
import { InvalidMemoryProof, InvalidSecondMemoryProof } from "src/cannon/libraries/CannonErrors.sol"; import {
InvalidMemoryProof, InvalidRMWInstruction, InvalidSecondMemoryProof
} from "src/cannon/libraries/CannonErrors.sol";
/// @title MIPS2 /// @title MIPS2
/// @notice The MIPS2 contract emulates a single MIPS instruction. /// @notice The MIPS2 contract emulates a single MIPS instruction.
...@@ -33,13 +35,16 @@ contract MIPS2 is ISemver { ...@@ -33,13 +35,16 @@ contract MIPS2 is ISemver {
} }
/// @notice Stores the VM state. /// @notice Stores the VM state.
/// Total state size: 32 + 32 + 4 + 4 + 1 + 1 + 8 + 8 + 4 + 1 + 32 + 32 + 4 = 163 bytes /// Total state size: 32 + 32 + 4 + 4 + 1 + 4 + 4 + 1 + 1 + 8 + 8 + 4 + 1 + 32 + 32 + 4 = 172 bytes
/// If nextPC != pc + 4, then the VM is executing a branch/jump delay slot. /// If nextPC != pc + 4, then the VM is executing a branch/jump delay slot.
struct State { struct State {
bytes32 memRoot; bytes32 memRoot;
bytes32 preimageKey; bytes32 preimageKey;
uint32 preimageOffset; uint32 preimageOffset;
uint32 heap; uint32 heap;
bool llReservationActive;
uint32 llAddress;
uint32 llOwnerThread;
uint8 exitCode; uint8 exitCode;
bool exited; bool exited;
uint64 step; uint64 step;
...@@ -52,8 +57,8 @@ contract MIPS2 is ISemver { ...@@ -52,8 +57,8 @@ contract MIPS2 is ISemver {
} }
/// @notice The semantic version of the MIPS2 contract. /// @notice The semantic version of the MIPS2 contract.
/// @custom:semver 1.0.0-beta.8 /// @custom:semver 1.0.0-beta.9
string public constant version = "1.0.0-beta.8"; string public constant version = "1.0.0-beta.9";
/// @notice The preimage oracle contract. /// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE; IPreimageOracle internal immutable ORACLE;
...@@ -71,7 +76,7 @@ contract MIPS2 is ISemver { ...@@ -71,7 +76,7 @@ contract MIPS2 is ISemver {
uint256 internal constant STATE_MEM_OFFSET = 0x80; uint256 internal constant STATE_MEM_OFFSET = 0x80;
// ThreadState memory offset allocated during step // ThreadState memory offset allocated during step
uint256 internal constant TC_MEM_OFFSET = 0x220; uint256 internal constant TC_MEM_OFFSET = 0x280;
/// @param _oracle The address of the preimage oracle contract. /// @param _oracle The address of the preimage oracle contract.
constructor(IPreimageOracle _oracle) { constructor(IPreimageOracle _oracle) {
...@@ -108,8 +113,8 @@ contract MIPS2 is ISemver { ...@@ -108,8 +113,8 @@ contract MIPS2 is ISemver {
// expected thread mem offset check // expected thread mem offset check
revert(0, 0) revert(0, 0)
} }
if iszero(eq(mload(0x40), shl(5, 60))) { if iszero(eq(mload(0x40), shl(5, 63))) {
// 4 + 13 state slots + 43 thread slots = 60 expected memory check // 4 + 16 state slots + 43 thread slots = 63 expected memory check
revert(0, 0) revert(0, 0)
} }
if iszero(eq(_stateData.offset, 132)) { if iszero(eq(_stateData.offset, 132)) {
...@@ -136,6 +141,9 @@ contract MIPS2 is ISemver { ...@@ -136,6 +141,9 @@ contract MIPS2 is ISemver {
c, m := putField(c, m, 32) // preimageKey c, m := putField(c, m, 32) // preimageKey
c, m := putField(c, m, 4) // preimageOffset c, m := putField(c, m, 4) // preimageOffset
c, m := putField(c, m, 4) // heap c, m := putField(c, m, 4) // heap
c, m := putField(c, m, 1) // llReservationActive
c, m := putField(c, m, 4) // llAddress
c, m := putField(c, m, 4) // llOwnerThread
c, m := putField(c, m, 1) // exitCode c, m := putField(c, m, 1) // exitCode
c, m := putField(c, m, 1) // exited c, m := putField(c, m, 1) // exited
exited := mload(sub(m, 32)) exited := mload(sub(m, 32))
...@@ -228,19 +236,95 @@ contract MIPS2 is ISemver { ...@@ -228,19 +236,95 @@ contract MIPS2 is ISemver {
return handleSyscall(_localContext); return handleSyscall(_localContext);
} }
// Handle RMW (read-modify-write) ops
if (opcode == ins.OP_LOAD_LINKED || opcode == ins.OP_STORE_CONDITIONAL) {
return handleRMWOps(state, thread, insn, opcode);
}
// Exec the rest of the step logic // Exec the rest of the step logic
st.CpuScalars memory cpu = getCpuScalars(thread); st.CpuScalars memory cpu = getCpuScalars(thread);
(state.memRoot) = ins.execMipsCoreStepLogic({ ins.CoreStepLogicParams memory coreStepArgs = ins.CoreStepLogicParams({
_cpu: cpu, cpu: cpu,
_registers: thread.registers, registers: thread.registers,
_memRoot: state.memRoot, memRoot: state.memRoot,
_memProofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), memProofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1),
_insn: insn, insn: insn,
_opcode: opcode, opcode: opcode,
_fun: fun fun: fun
}); });
bool memUpdated;
uint32 memAddr;
(state.memRoot, memUpdated, memAddr) = ins.execMipsCoreStepLogic(coreStepArgs);
setStateCpuScalars(thread, cpu); setStateCpuScalars(thread, cpu);
updateCurrentThreadRoot(); updateCurrentThreadRoot();
if (memUpdated) {
handleMemoryUpdate(state, memAddr);
}
return outputState();
}
}
function handleMemoryUpdate(State memory _state, uint32 _memAddr) internal pure {
if (_memAddr == _state.llAddress) {
// Reserved address was modified, clear the reservation
clearLLMemoryReservation(_state);
}
}
function clearLLMemoryReservation(State memory _state) internal pure {
_state.llReservationActive = false;
_state.llAddress = 0;
_state.llOwnerThread = 0;
}
function handleRMWOps(
State memory _state,
ThreadState memory _thread,
uint32 _insn,
uint32 _opcode
)
internal
returns (bytes32)
{
unchecked {
uint32 baseReg = (_insn >> 21) & 0x1F;
uint32 base = _thread.registers[baseReg];
uint32 rtReg = (_insn >> 16) & 0x1F;
uint32 offset = ins.signExtendImmediate(_insn);
uint32 effAddr = (base + offset) & 0xFFFFFFFC;
uint256 memProofOffset = MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1);
uint32 mem = MIPSMemory.readMem(_state.memRoot, effAddr, memProofOffset);
uint32 retVal = 0;
uint32 threadId = _thread.threadID;
if (_opcode == ins.OP_LOAD_LINKED) {
retVal = mem;
_state.llReservationActive = true;
_state.llAddress = effAddr;
_state.llOwnerThread = threadId;
} else if (_opcode == ins.OP_STORE_CONDITIONAL) {
// Check if our memory reservation is still intact
if (_state.llReservationActive && _state.llOwnerThread == threadId && _state.llAddress == effAddr) {
// Complete atomic update: set memory and return 1 for success
clearLLMemoryReservation(_state);
uint32 val = _thread.registers[rtReg];
_state.memRoot = MIPSMemory.writeMem(effAddr, memProofOffset, val);
retVal = 1;
} else {
// Atomic update failed, return 0 for failure
retVal = 0;
}
} else {
revert InvalidRMWInstruction();
}
st.CpuScalars memory cpu = getCpuScalars(_thread);
ins.handleRd(cpu, _thread.registers, rtReg, retVal, true);
setStateCpuScalars(_thread, cpu);
updateCurrentThreadRoot();
return outputState(); return outputState();
} }
} }
...@@ -319,7 +403,8 @@ contract MIPS2 is ISemver { ...@@ -319,7 +403,8 @@ contract MIPS2 is ISemver {
proofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), proofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1),
memRoot: state.memRoot memRoot: state.memRoot
}); });
(v0, v1, state.preimageOffset, state.memRoot) = sys.handleSysRead(args); // Encapsulate execution to avoid stack-too-deep error
(v0, v1) = execSysRead(state, args);
} else if (syscall_no == sys.SYS_WRITE) { } else if (syscall_no == sys.SYS_WRITE) {
(v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({ (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({
_a0: a0, _a0: a0,
...@@ -412,6 +497,7 @@ contract MIPS2 is ISemver { ...@@ -412,6 +497,7 @@ contract MIPS2 is ISemver {
// Recompute the new root after updating effAddr // Recompute the new root after updating effAddr
state.memRoot = state.memRoot =
MIPSMemory.writeMem(effAddr, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), secs); MIPSMemory.writeMem(effAddr, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), secs);
handleMemoryUpdate(state, effAddr);
// Verify the second memory proof against the newly computed root // Verify the second memory proof against the newly computed root
if ( if (
!MIPSMemory.isValidProof( !MIPSMemory.isValidProof(
...@@ -422,6 +508,7 @@ contract MIPS2 is ISemver { ...@@ -422,6 +508,7 @@ contract MIPS2 is ISemver {
} }
state.memRoot = state.memRoot =
MIPSMemory.writeMem(effAddr + 4, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 2), nsecs); MIPSMemory.writeMem(effAddr + 4, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 2), nsecs);
handleMemoryUpdate(state, effAddr + 4);
} else { } else {
v0 = sys.SYS_ERROR_SIGNAL; v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EINVAL; v1 = sys.EINVAL;
...@@ -502,6 +589,22 @@ contract MIPS2 is ISemver { ...@@ -502,6 +589,22 @@ contract MIPS2 is ISemver {
} }
} }
function execSysRead(
State memory _state,
sys.SysReadParams memory _args
)
internal
view
returns (uint32 v0, uint32 v1)
{
bool memUpdated;
uint32 memAddr;
(v0, v1, _state.preimageOffset, _state.memRoot, memUpdated, memAddr) = sys.handleSysRead(_args);
if (memUpdated) {
handleMemoryUpdate(_state, memAddr);
}
}
/// @notice Computes the hash of the MIPS state. /// @notice Computes the hash of the MIPS state.
/// @return out_ The hashed MIPS state. /// @return out_ The hashed MIPS state.
function outputState() internal returns (bytes32 out_) { function outputState() internal returns (bytes32 out_) {
...@@ -526,6 +629,9 @@ contract MIPS2 is ISemver { ...@@ -526,6 +629,9 @@ contract MIPS2 is ISemver {
from, to := copyMem(from, to, 32) // preimageKey from, to := copyMem(from, to, 32) // preimageKey
from, to := copyMem(from, to, 4) // preimageOffset from, to := copyMem(from, to, 4) // preimageOffset
from, to := copyMem(from, to, 4) // heap from, to := copyMem(from, to, 4) // heap
from, to := copyMem(from, to, 1) // llReservationActive
from, to := copyMem(from, to, 4) // llAddress
from, to := copyMem(from, to, 4) // llOwnerThread
let exitCode := mload(from) let exitCode := mload(from)
from, to := copyMem(from, to, 1) // exitCode from, to := copyMem(from, to, 1) // exitCode
exited := mload(from) exited := mload(from)
......
...@@ -8,6 +8,7 @@ import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; ...@@ -8,6 +8,7 @@ import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol";
/// @notice Interface for the MIPS contract. /// @notice Interface for the MIPS contract.
interface IMIPS is ISemver { interface IMIPS is ISemver {
error InvalidMemoryProof(); error InvalidMemoryProof();
error InvalidRMWInstruction();
function oracle() external view returns (IPreimageOracle oracle_); function oracle() external view returns (IPreimageOracle oracle_);
function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32); function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32);
......
...@@ -10,6 +10,7 @@ interface IMIPS2 is ISemver { ...@@ -10,6 +10,7 @@ interface IMIPS2 is ISemver {
error InvalidExitedValue(); error InvalidExitedValue();
error InvalidMemoryProof(); error InvalidMemoryProof();
error InvalidSecondMemoryProof(); error InvalidSecondMemoryProof();
error InvalidRMWInstruction();
function oracle() external view returns (IPreimageOracle oracle_); function oracle() external view returns (IPreimageOracle oracle_);
function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32); function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32);
......
...@@ -60,3 +60,6 @@ error InvalidMemoryProof(); ...@@ -60,3 +60,6 @@ error InvalidMemoryProof();
/// @notice Thrown when the second memory location is invalid /// @notice Thrown when the second memory location is invalid
error InvalidSecondMemoryProof(); error InvalidSecondMemoryProof();
/// @notice Thrown when an RMW instruction is expected, but a different instruction is provided.
error InvalidRMWInstruction();
...@@ -5,6 +5,26 @@ import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; ...@@ -5,6 +5,26 @@ import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol";
import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol";
library MIPSInstructions { library MIPSInstructions {
uint32 internal constant OP_LOAD_LINKED = 0x30;
uint32 internal constant OP_STORE_CONDITIONAL = 0x38;
struct CoreStepLogicParams {
/// @param opcode The opcode value parsed from insn_.
st.CpuScalars cpu;
/// @param registers The CPU registers.
uint32[32] registers;
/// @param memRoot The current merkle root of the memory.
bytes32 memRoot;
/// @param memProofOffset The offset in calldata specify where the memory merkle proof is located.
uint256 memProofOffset;
/// @param insn The current 32-bit instruction at the pc.
uint32 insn;
/// @param cpu The CPU scalar fields.
uint32 opcode;
/// @param fun The function value parsed from insn_.
uint32 fun;
}
/// @param _pc The program counter. /// @param _pc The program counter.
/// @param _memRoot The current memory root. /// @param _memRoot The current memory root.
/// @param _insnProofOffset The calldata offset of the memory proof for the current instruction. /// @param _insnProofOffset The calldata offset of the memory proof for the current instruction.
...@@ -30,91 +50,80 @@ library MIPSInstructions { ...@@ -30,91 +50,80 @@ library MIPSInstructions {
} }
/// @notice Execute core MIPS step logic. /// @notice Execute core MIPS step logic.
/// @notice _cpu The CPU scalar fields.
/// @notice _registers The CPU registers.
/// @notice _memRoot The current merkle root of the memory.
/// @notice _memProofOffset The offset in calldata specify where the memory merkle proof is located.
/// @param _insn The current 32-bit instruction at the pc.
/// @param _opcode The opcode value parsed from insn_.
/// @param _fun The function value parsed from insn_.
/// @return newMemRoot_ The updated merkle root of memory after any modifications, may be unchanged. /// @return newMemRoot_ The updated merkle root of memory after any modifications, may be unchanged.
function execMipsCoreStepLogic( /// @return memUpdated_ True if memory was modified.
st.CpuScalars memory _cpu, /// @return memAddr_ Holds the memory address that was updated if memUpdated_ is true.
uint32[32] memory _registers, function execMipsCoreStepLogic(CoreStepLogicParams memory _args)
bytes32 _memRoot,
uint256 _memProofOffset,
uint32 _insn,
uint32 _opcode,
uint32 _fun
)
internal internal
pure pure
returns (bytes32 newMemRoot_) returns (bytes32 newMemRoot_, bool memUpdated_, uint32 memAddr_)
{ {
unchecked { unchecked {
newMemRoot_ = _memRoot; newMemRoot_ = _args.memRoot;
memUpdated_ = false;
memAddr_ = 0;
// j-type j/jal // j-type j/jal
if (_opcode == 2 || _opcode == 3) { if (_args.opcode == 2 || _args.opcode == 3) {
// Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset // Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset
uint32 target = (_cpu.nextPC & 0xF0000000) | (_insn & 0x03FFFFFF) << 2; uint32 target = (_args.cpu.nextPC & 0xF0000000) | (_args.insn & 0x03FFFFFF) << 2;
handleJump(_cpu, _registers, _opcode == 2 ? 0 : 31, target); handleJump(_args.cpu, _args.registers, _args.opcode == 2 ? 0 : 31, target);
return newMemRoot_; return (newMemRoot_, memUpdated_, memAddr_);
} }
// register fetch // register fetch
uint32 rs = 0; // source register 1 value uint32 rs = 0; // source register 1 value
uint32 rt = 0; // source register 2 / temp value uint32 rt = 0; // source register 2 / temp value
uint32 rtReg = (_insn >> 16) & 0x1F; uint32 rtReg = (_args.insn >> 16) & 0x1F;
// R-type or I-type (stores rt) // R-type or I-type (stores rt)
rs = _registers[(_insn >> 21) & 0x1F]; rs = _args.registers[(_args.insn >> 21) & 0x1F];
uint32 rdReg = rtReg; uint32 rdReg = rtReg;
if (_opcode == 0 || _opcode == 0x1c) { if (_args.opcode == 0 || _args.opcode == 0x1c) {
// R-type (stores rd) // R-type (stores rd)
rt = _registers[rtReg]; rt = _args.registers[rtReg];
rdReg = (_insn >> 11) & 0x1F; rdReg = (_args.insn >> 11) & 0x1F;
} else if (_opcode < 0x20) { } else if (_args.opcode < 0x20) {
// rt is SignExtImm // rt is SignExtImm
// don't sign extend for andi, ori, xori // don't sign extend for andi, ori, xori
if (_opcode == 0xC || _opcode == 0xD || _opcode == 0xe) { if (_args.opcode == 0xC || _args.opcode == 0xD || _args.opcode == 0xe) {
// ZeroExtImm // ZeroExtImm
rt = _insn & 0xFFFF; rt = _args.insn & 0xFFFF;
} else { } else {
// SignExtImm // SignExtImm
rt = signExtend(_insn & 0xFFFF, 16); rt = signExtend(_args.insn & 0xFFFF, 16);
} }
} else if (_opcode >= 0x28 || _opcode == 0x22 || _opcode == 0x26) { } else if (_args.opcode >= 0x28 || _args.opcode == 0x22 || _args.opcode == 0x26) {
// store rt value with store // store rt value with store
rt = _registers[rtReg]; rt = _args.registers[rtReg];
// store actual rt with lwl and lwr // store actual rt with lwl and lwr
rdReg = rtReg; rdReg = rtReg;
} }
if ((_opcode >= 4 && _opcode < 8) || _opcode == 1) { if ((_args.opcode >= 4 && _args.opcode < 8) || _args.opcode == 1) {
handleBranch({ handleBranch({
_cpu: _cpu, _cpu: _args.cpu,
_registers: _registers, _registers: _args.registers,
_opcode: _opcode, _opcode: _args.opcode,
_insn: _insn, _insn: _args.insn,
_rtReg: rtReg, _rtReg: rtReg,
_rs: rs _rs: rs
}); });
return newMemRoot_; return (newMemRoot_, memUpdated_, memAddr_);
} }
uint32 storeAddr = 0xFF_FF_FF_FF; uint32 storeAddr = 0xFF_FF_FF_FF;
// memory fetch (all I-type) // memory fetch (all I-type)
// we do the load for stores also // we do the load for stores also
uint32 mem = 0; uint32 mem = 0;
if (_opcode >= 0x20) { if (_args.opcode >= 0x20) {
// M[R[rs]+SignExtImm] // M[R[rs]+SignExtImm]
rs += signExtend(_insn & 0xFFFF, 16); rs += signExtend(_args.insn & 0xFFFF, 16);
uint32 addr = rs & 0xFFFFFFFC; uint32 addr = rs & 0xFFFFFFFC;
mem = MIPSMemory.readMem(_memRoot, addr, _memProofOffset); mem = MIPSMemory.readMem(_args.memRoot, addr, _args.memProofOffset);
if (_opcode >= 0x28 && _opcode != 0x30) { if (_args.opcode >= 0x28) {
// store // store
storeAddr = addr; storeAddr = addr;
// store opcodes don't write back to a register // store opcodes don't write back to a register
...@@ -124,49 +133,58 @@ library MIPSInstructions { ...@@ -124,49 +133,58 @@ library MIPSInstructions {
// ALU // ALU
// Note: swr outputs more than 4 bytes without the mask 0xffFFffFF // Note: swr outputs more than 4 bytes without the mask 0xffFFffFF
uint32 val = executeMipsInstruction(_insn, _opcode, _fun, rs, rt, mem) & 0xffFFffFF; uint32 val = executeMipsInstruction(_args.insn, _args.opcode, _args.fun, rs, rt, mem) & 0xffFFffFF;
if (_opcode == 0 && _fun >= 8 && _fun < 0x1c) { if (_args.opcode == 0 && _args.fun >= 8 && _args.fun < 0x1c) {
if (_fun == 8 || _fun == 9) { if (_args.fun == 8 || _args.fun == 9) {
// jr/jalr // jr/jalr
handleJump(_cpu, _registers, _fun == 8 ? 0 : rdReg, rs); handleJump(_args.cpu, _args.registers, _args.fun == 8 ? 0 : rdReg, rs);
return newMemRoot_; return (newMemRoot_, memUpdated_, memAddr_);
} }
if (_fun == 0xa) { if (_args.fun == 0xa) {
// movz // movz
handleRd(_cpu, _registers, rdReg, rs, rt == 0); handleRd(_args.cpu, _args.registers, rdReg, rs, rt == 0);
return newMemRoot_; return (newMemRoot_, memUpdated_, memAddr_);
} }
if (_fun == 0xb) { if (_args.fun == 0xb) {
// movn // movn
handleRd(_cpu, _registers, rdReg, rs, rt != 0); handleRd(_args.cpu, _args.registers, rdReg, rs, rt != 0);
return newMemRoot_; return (newMemRoot_, memUpdated_, memAddr_);
} }
// lo and hi registers // lo and hi registers
// can write back // can write back
if (_fun >= 0x10 && _fun < 0x1c) { if (_args.fun >= 0x10 && _args.fun < 0x1c) {
handleHiLo({ _cpu: _cpu, _registers: _registers, _fun: _fun, _rs: rs, _rt: rt, _storeReg: rdReg }); handleHiLo({
_cpu: _args.cpu,
return newMemRoot_; _registers: _args.registers,
_fun: _args.fun,
_rs: rs,
_rt: rt,
_storeReg: rdReg
});
return (newMemRoot_, memUpdated_, memAddr_);
} }
} }
// stupid sc, write a 1 to rt
if (_opcode == 0x38 && rtReg != 0) {
_registers[rtReg] = 1;
}
// write memory // write memory
if (storeAddr != 0xFF_FF_FF_FF) { if (storeAddr != 0xFF_FF_FF_FF) {
newMemRoot_ = MIPSMemory.writeMem(storeAddr, _memProofOffset, val); newMemRoot_ = MIPSMemory.writeMem(storeAddr, _args.memProofOffset, val);
memUpdated_ = true;
memAddr_ = storeAddr;
} }
// write back the value to destination register // write back the value to destination register
handleRd(_cpu, _registers, rdReg, val, true); handleRd(_args.cpu, _args.registers, rdReg, val, true);
return newMemRoot_; return (newMemRoot_, memUpdated_, memAddr_);
}
}
function signExtendImmediate(uint32 _insn) internal pure returns (uint32 offset_) {
unchecked {
return signExtend(_insn & 0xFFFF, 16);
} }
} }
...@@ -417,14 +435,6 @@ library MIPSInstructions { ...@@ -417,14 +435,6 @@ library MIPSInstructions {
uint32 val = _rt << (24 - (_rs & 3) * 8); uint32 val = _rt << (24 - (_rs & 3) * 8);
uint32 mask = uint32(0xFFFFFFFF) << (24 - (_rs & 3) * 8); uint32 mask = uint32(0xFFFFFFFF) << (24 - (_rs & 3) * 8);
return (_mem & ~mask) | val; return (_mem & ~mask) | val;
}
// ll
else if (_opcode == 0x30) {
return _mem;
}
// sc
else if (_opcode == 0x38) {
return _rt;
} else { } else {
revert("invalid instruction"); revert("invalid instruction");
} }
......
...@@ -202,13 +202,22 @@ library MIPSSyscalls { ...@@ -202,13 +202,22 @@ library MIPSSyscalls {
function handleSysRead(SysReadParams memory _args) function handleSysRead(SysReadParams memory _args)
internal internal
view view
returns (uint32 v0_, uint32 v1_, uint32 newPreimageOffset_, bytes32 newMemRoot_) returns (
uint32 v0_,
uint32 v1_,
uint32 newPreimageOffset_,
bytes32 newMemRoot_,
bool memUpdated_,
uint32 memAddr_
)
{ {
unchecked { unchecked {
v0_ = uint32(0); v0_ = uint32(0);
v1_ = uint32(0); v1_ = uint32(0);
newMemRoot_ = _args.memRoot; newMemRoot_ = _args.memRoot;
newPreimageOffset_ = _args.preimageOffset; newPreimageOffset_ = _args.preimageOffset;
memUpdated_ = false;
memAddr_ = 0;
// args: _a0 = fd, _a1 = addr, _a2 = count // args: _a0 = fd, _a1 = addr, _a2 = count
// returns: v0_ = read, v1_ = err code // returns: v0_ = read, v1_ = err code
...@@ -217,9 +226,10 @@ library MIPSSyscalls { ...@@ -217,9 +226,10 @@ library MIPSSyscalls {
} }
// pre-image oracle read // pre-image oracle read
else if (_args.a0 == FD_PREIMAGE_READ) { else if (_args.a0 == FD_PREIMAGE_READ) {
uint32 effAddr = _args.a1 & 0xFFffFFfc;
// verify proof is correct, and get the existing memory. // verify proof is correct, and get the existing memory.
// mask the addr to align it to 4 bytes // mask the addr to align it to 4 bytes
uint32 mem = MIPSMemory.readMem(_args.memRoot, _args.a1 & 0xFFffFFfc, _args.proofOffset); uint32 mem = MIPSMemory.readMem(_args.memRoot, effAddr, _args.proofOffset);
// If the preimage key is a local key, localize it in the context of the caller. // If the preimage key is a local key, localize it in the context of the caller.
if (uint8(_args.preimageKey[0]) == 1) { if (uint8(_args.preimageKey[0]) == 1) {
_args.preimageKey = PreimageKeyLib.localize(_args.preimageKey, _args.localContext); _args.preimageKey = PreimageKeyLib.localize(_args.preimageKey, _args.localContext);
...@@ -246,7 +256,9 @@ library MIPSSyscalls { ...@@ -246,7 +256,9 @@ library MIPSSyscalls {
} }
// Write memory back // Write memory back
newMemRoot_ = MIPSMemory.writeMem(_args.a1 & 0xFFffFFfc, _args.proofOffset, mem); newMemRoot_ = MIPSMemory.writeMem(effAddr, _args.proofOffset, mem);
memUpdated_ = true;
memAddr_ = effAddr;
newPreimageOffset_ += uint32(datLen); newPreimageOffset_ += uint32(datLen);
v0_ = uint32(datLen); v0_ = uint32(datLen);
} }
...@@ -260,7 +272,7 @@ library MIPSSyscalls { ...@@ -260,7 +272,7 @@ library MIPSSyscalls {
v1_ = EBADF; v1_ = EBADF;
} }
return (v0_, v1_, newPreimageOffset_, newMemRoot_); return (v0_, v1_, newPreimageOffset_, newMemRoot_, memUpdated_, memAddr_);
} }
} }
......
...@@ -141,9 +141,9 @@ contract MIPS2_Test is CommonTest { ...@@ -141,9 +141,9 @@ contract MIPS2_Test is CommonTest {
/// https://github.com/ethereum-optimism/optimism/blob/1f64dd6db5561f3bb76ed1d1ffdaff0cde9b7c4b/cannon/mipsevm/evm_test.go#L80-L80 /// https://github.com/ethereum-optimism/optimism/blob/1f64dd6db5561f3bb76ed1d1ffdaff0cde9b7c4b/cannon/mipsevm/evm_test.go#L80-L80
function test_mips2_step_debug_succeeds() external { function test_mips2_step_debug_succeeds() external {
bytes memory input = bytes memory input =
hex"e14ced3200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a3df82bcbdf27955e04d467b84d94d0b4662c88a70264d7ea31325bc8d826681ef000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000affffffff00cbf05eda4a03d05cc6a14cff1cf2f955bfb253097c296ea96032da307da4f353ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c6000000000000ffffffff000000000000000000000000000000280000002c00000000000000000000000000000000000000010000000000000000000000000000000000000000fffffffd00000003000000000000000000000000000000000000000000000000bffffff00000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7ef00d0ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5ae020008ae11000403e0000800000000000000000000000000000000000000003c10bfff3610fff0341100013c08ffff3508fffd34090003010950212d420001ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d4e545be579dc7118fc02cd7b19b704e4710a81bce0cb48bb7e289e403e7c969a00000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d6a3e23902bafb21ac312e717f7942f8fd8ae795f67c918083442c2ab253cc66e0000000000000000000000000000000000000000000000000000"; hex"e14ced3200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000acab5a39c6f974b22302e96dcdef1815483eaf580639bb1ee7ac98267afac2bf1ac041d3ff12045b73c86e4ff95ff662a5eee82abdf44a2d0b75fb180daf48a79e3143a81fa7c3d90b000000000000000000000078fc2ffac2fd940100000000000080c8ffffffff006504aeffb6e08baf3f85da5476a9160fa8f9f188a722fdd29268b0cbaf596736ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c6000000000000ffffffff000000000000000000000000f1f85ff4f1f85ff8506d442dbb3938f83eb60825a7ecbff2000010185e1a31f600050f0000000064a7c3d90be5acea102ad7bda149e0bfd0e7111c77d98b335645e665389becadf140ef999cc64fbd7f04799e85c97dadc5cca510bd5b3d97166d1aec28829f3dd43d8cf1f9358e4103b16d09d466e2c7c048ea3ba1aef3141e700270581aa0b75b50e34fc926bb2d83ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618db8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
(bool success, bytes memory retVal) = address(mips).call(input); (bool success, bytes memory retVal) = address(mips).call(input);
bytes memory expectedRetVal = hex"03fc952a0bd8aabc407669b857af995eab91ce55c404d8b32eaf8b941a48188c"; bytes memory expectedRetVal = hex"0335fe4205f8443eefa7ac4541197874224df35e8536158c2fc2d5c8c2d2adb4";
assertTrue(success); assertTrue(success);
assertEq(retVal.length, 32, "Expect a bytes32 hash of the post-state to be returned"); assertEq(retVal.length, 32, "Expect a bytes32 hash of the post-state to be returned");
...@@ -177,6 +177,9 @@ contract MIPS2_Test is CommonTest { ...@@ -177,6 +177,9 @@ contract MIPS2_Test is CommonTest {
preimageKey: bytes32(0), preimageKey: bytes32(0),
preimageOffset: 0, preimageOffset: 0,
heap: 0, heap: 0,
llReservationActive: false,
llAddress: 0,
llOwnerThread: 0,
exitCode: 0, exitCode: 0,
exited: false, exited: false,
step: 1, step: 1,
...@@ -211,7 +214,7 @@ contract MIPS2_Test is CommonTest { ...@@ -211,7 +214,7 @@ contract MIPS2_Test is CommonTest {
assembly { assembly {
// Manipulate state data // Manipulate state data
// Push offset by an additional 32 bytes (0x20) to account for length prefix // Push offset by an additional 32 bytes (0x20) to account for length prefix
mstore8(add(add(stateData, 0x20), 73), exited) mstore8(add(add(stateData, 0x20), 82), exited)
} }
// Call the step function and expect a revert. // Call the step function and expect a revert.
...@@ -1992,36 +1995,50 @@ contract MIPS2_Test is CommonTest { ...@@ -1992,36 +1995,50 @@ contract MIPS2_Test is CommonTest {
} }
function test_ll_succeeds() public { function test_ll_succeeds() public {
uint32 t1 = 0x100; uint32 base = 0x100;
uint32 val = 0x12_23_45_67; uint32 memVal = 0x12_23_45_67;
uint32 insn = encodeitype(0x30, 0x9, 0x8, 0x4); // ll $t0, 4($t1) uint16 offset = 0x4;
uint32 effAddr = base + offset;
uint32 insn = encodeitype(0x30, 0x9, 0x8, offset); // ll baseReg, rtReg, offset
(MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) =
constructMIPSState(0, insn, t1 + 4, val); constructMIPSState(0, insn, effAddr, memVal);
thread.registers[8] = 0; // t0 thread.registers[8] = 0; // rtReg
thread.registers[9] = t1; thread.registers[9] = base;
bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT);
updateThreadStacks(state, thread); updateThreadStacks(state, thread);
thread.registers[8] = val; // t0 MIPS2.State memory expect = arithmeticPostState(state, thread, 8, memVal);
MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); expect.llReservationActive = true;
expect.llAddress = effAddr;
expect.llOwnerThread = thread.threadID;
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
assertEq(postState, outputState(expect), "unexpected post state"); assertEq(postState, outputState(expect), "unexpected post state");
} }
function test_sc_succeeds() public { function test_sc_succeeds() public {
uint32 t1 = 0x100; uint32 base = 0x100;
uint32 insn = encodeitype(0x38, 0x9, 0x8, 0x4); // sc $t0, 4($t1) uint16 offset = 0x4;
uint32 effAddr = base + offset;
uint32 writeMemVal = 0xaa_bb_cc_dd;
uint32 insn = encodeitype(0x38, 0x9, 0x8, offset); // ll baseReg, rtReg, offset
(MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) =
constructMIPSState(0, insn, t1 + 4, 0); constructMIPSState(0, insn, effAddr, 0);
thread.registers[8] = 0xaa_bb_cc_dd; // t0 state.llReservationActive = true;
thread.registers[9] = t1; state.llAddress = effAddr;
state.llOwnerThread = thread.threadID;
thread.registers[8] = writeMemVal;
thread.registers[9] = base;
bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT);
updateThreadStacks(state, thread); updateThreadStacks(state, thread);
thread.registers[8] = 0x1; // t0 MIPS2.State memory expect = arithmeticPostState(state, thread, 8, 0x1);
MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, effAddr, writeMemVal);
(expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0xaa_bb_cc_dd); expect.llReservationActive = false;
expect.llAddress = 0;
expect.llOwnerThread = 0;
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
assertEq(postState, outputState(expect), "unexpected post state"); assertEq(postState, outputState(expect), "unexpected post state");
...@@ -2528,23 +2545,33 @@ contract MIPS2_Test is CommonTest { ...@@ -2528,23 +2545,33 @@ contract MIPS2_Test is CommonTest {
} }
function encodeState(MIPS2.State memory _state) internal pure returns (bytes memory) { function encodeState(MIPS2.State memory _state) internal pure returns (bytes memory) {
// Split up encoding to get around stack-too-deep error
return abi.encodePacked(encodeStateA(_state), encodeStateB(_state));
}
function encodeStateA(MIPS2.State memory _state) internal pure returns (bytes memory) {
return abi.encodePacked( return abi.encodePacked(
_state.memRoot, _state.memRoot,
_state.preimageKey, _state.preimageKey,
_state.preimageOffset, _state.preimageOffset,
_state.heap, _state.heap,
_state.llReservationActive,
_state.llAddress,
_state.llOwnerThread,
_state.exitCode, _state.exitCode,
_state.exited, _state.exited,
_state.step, _state.step,
_state.stepsSinceLastContextSwitch, _state.stepsSinceLastContextSwitch,
_state.wakeup, _state.wakeup,
_state.traverseRight, _state.traverseRight,
_state.leftThreadStack, _state.leftThreadStack
_state.rightThreadStack,
_state.nextThreadID
); );
} }
function encodeStateB(MIPS2.State memory _state) internal pure returns (bytes memory) {
return abi.encodePacked(_state.rightThreadStack, _state.nextThreadID);
}
function copyState(MIPS2.State memory _state) internal pure returns (MIPS2.State memory out_) { function copyState(MIPS2.State memory _state) internal pure returns (MIPS2.State memory out_) {
bytes memory data = abi.encode(_state); bytes memory data = abi.encode(_state);
return abi.decode(data, (MIPS2.State)); return abi.decode(data, (MIPS2.State));
......
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