diff --git a/.circleci/config.yml b/.circleci/config.yml index d96b1051514a..4c5e401ada57 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -849,6 +849,64 @@ jobs: paths: - "/root/.cache/golangci-lint" + go-test-kurtosis: + parameters: + module: + description: Go Module Name + type: string + uses_artifacts: + description: Uses contract artifacts + type: boolean + default: false + test_directory: + description: Test directory + type: string + default: "./..." + machine: + image: <> # only used to enable codecov. + resource_class: xlarge + steps: + - run: + name: Install components + command: | + go version + go install gotest.tools/gotestsum@v1.11.0 + - run: + name: Install Kurtosis + command: | + echo "deb [trusted=yes] https://apt.fury.io/kurtosis-tech/ /" | sudo tee /etc/apt/sources.list.d/kurtosis.list + sudo apt update + sudo apt install kurtosis-cli + kurtosis engine start + - checkout + - when: + condition: <> + steps: + - attach_workspace: { at: "." } + - run: + name: prep results dir + command: | + # Make sure the workspace is properly owned + sudo chown -R $USER:$USER . + mkdir -p /tmp/test-results + mkdir -p /tmp/testlogs + - run: + name: run tests + command: | + ENABLE_KURTOSIS=true gotestsum \ + --format=testname \ + --junitfile=/tmp/test-results/<>.xml \ + --jsonfile=/tmp/testlogs/log.json \ + -- -parallel=8 \ + -coverpkg=github.com/ethereum-optimism/optimism/... \ + -coverprofile=coverage.out <> + working_directory: <> + - store_test_results: + path: /tmp/test-results + - store_artifacts: + path: /tmp/testlogs + when: always + go-test: parameters: module: @@ -881,10 +939,6 @@ jobs: key: golang-build-cache-test-<>-{{ checksum "go.sum" }} paths: - "/root/.cache/go-build" - # TODO(CLI-148): Fix codecov - #- run: - #name: upload coverage - #command: codecov --verbose --clean --flags bedrock-go-tests - store_test_results: path: /tmp/test-results - store_artifacts: @@ -1011,53 +1065,6 @@ jobs: - notify-failures-on-develop: mentions: "<>" - go-lint-test-build: - parameters: - binary_name: - description: Binary name to build - type: string - working_directory: - description: Working directory - type: string - build: - description: Whether or not to build the binary - type: boolean - default: true - dependencies: - description: Regex matching dependent packages - type: string - default: this-package-does-not-exist - docker: - - image: <> - resource_class: medium - steps: - - checkout - - check-changed: - patterns: <>,<> - - run: - name: Lint - command: make lint - working_directory: <> - - run: - name: Test - command: | - mkdir -p /test-results - gotestsum --format=testname --junitfile /test-results/tests.xml --jsonfile /test-results/log.json -- -parallel=2 - working_directory: <> - - store_test_results: - path: /test-results - - store_artifacts: - path: /testlogs - when: always - - when: - condition: - equal: [ true, <> ] - steps: - - run: - name: Build - command: make <> - working_directory: <> - cannon-prestate: docker: - image: <> @@ -1664,7 +1671,12 @@ workflows: - go-test: name: op-chain-ops-tests module: op-chain-ops - requires: ["go-mod-download"] + - go-test-kurtosis: + name: op-chain-ops-integration + module: op-chain-ops + test_directory: ./deployer/integration_test + uses_artifacts: true + requires: ["contracts-bedrock-build"] - go-test: name: op-node-tests module: op-node @@ -1740,6 +1752,7 @@ workflows: - go-mod-download - op-batcher-tests - op-chain-ops-tests + - op-chain-ops-integration - op-node-tests - op-proposer-tests - op-challenger-tests diff --git a/cannon/mipsevm/exec/mips_instructions.go b/cannon/mipsevm/exec/mips_instructions.go index d016dd2c07e0..aec14192df93 100644 --- a/cannon/mipsevm/exec/mips_instructions.go +++ b/cannon/mipsevm/exec/mips_instructions.go @@ -5,6 +5,11 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" ) +const ( + OpLoadLinked = 0x30 + OpStoreConditional = 0x38 +) + func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun uint32) { insn = memory.GetMemory(pc) opcode = insn >> 26 // First 6-bits @@ -13,7 +18,7 @@ func GetInstructionDetails(pc uint32, memory *memory.Memory) (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 if opcode == 2 || opcode == 3 { linkReg := uint32(0) @@ -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 target := (cpu.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2) stackTracker.PushStack(cpu.PC, target) - return HandleJump(cpu, registers, linkReg, target) + err = HandleJump(cpu, registers, linkReg, target) + return } // register fetch @@ -57,7 +63,8 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor } 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) @@ -70,7 +77,7 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor addr := rs & 0xFFFFFFFC memTracker.TrackMemAccess(addr) mem = memory.GetMemory(addr) - if opcode >= 0x28 && opcode != 0x30 { + if opcode >= 0x28 { // store storeAddr = addr // store opcodes don't write back to a register @@ -90,36 +97,42 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor } else { stackTracker.PopStack() } - return HandleJump(cpu, registers, linkReg, rs) + err = HandleJump(cpu, registers, linkReg, rs) + return } 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 - return HandleRd(cpu, registers, rdReg, rs, rt != 0) + err = HandleRd(cpu, registers, rdReg, rs, rt != 0) + return } // lo and hi registers // can write back 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 if storeAddr != 0xFF_FF_FF_FF { memTracker.TrackMemAccess(storeAddr) memory.SetMemory(storeAddr, val) + memUpdated = true + memAddr = storeAddr } // 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 { @@ -272,10 +285,6 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 { val := rt << (24 - (rs&3)*8) mask := uint32(0xFFFFFFFF) << (24 - (rs&3)*8) return (mem & ^mask) | val - case 0x30: // ll - return mem - case 0x38: // sc - return rt default: panic("invalid instruction") } @@ -382,6 +391,7 @@ func HandleRd(cpu *mipsevm.CpuScalars, registers *[32]uint32, storeReg uint32, v panic("invalid register") } if storeReg != 0 && conditional { + // Register 0 is a special register that always holds a value of 0 registers[storeReg] = val } cpu.PC = cpu.NextPC diff --git a/cannon/mipsevm/exec/mips_syscalls.go b/cannon/mipsevm/exec/mips_syscalls.go index 33a8e489bdb7..71a14e4fe5e2 100644 --- a/cannon/mipsevm/exec/mips_syscalls.go +++ b/cannon/mipsevm/exec/mips_syscalls.go @@ -187,7 +187,7 @@ func HandleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) { 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 // returns: v0 = read, v1 = err code v0 = uint32(0) @@ -215,6 +215,8 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3 binary.BigEndian.PutUint32(outMem[:], mem) copy(outMem[alignment:], dat[:datLen]) memory.SetMemory(effAddr, binary.BigEndian.Uint32(outMem[:])) + memUpdated = true + memAddr = effAddr newPreimageOffset += 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) @@ -226,7 +228,7 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3 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) { diff --git a/cannon/mipsevm/multithreaded/mips.go b/cannon/mipsevm/multithreaded/mips.go index 73eb33c03a81..89ecadd01366 100644 --- a/cannon/mipsevm/multithreaded/mips.go +++ b/cannon/mipsevm/multithreaded/mips.go @@ -75,8 +75,13 @@ func (m *InstrumentedState) handleSyscall() error { return nil case exec.SysRead: 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 + if memUpdated { + m.handleMemoryUpdate(memAddr) + } case exec.SysWrite: var newLastHint hexutil.Bytes var newPreimageKey common.Hash @@ -158,8 +163,10 @@ func (m *InstrumentedState) handleSyscall() error { effAddr := a1 & 0xFFffFFfc m.memoryTracker.TrackMemAccess(effAddr) m.state.Memory.SetMemory(effAddr, secs) + m.handleMemoryUpdate(effAddr) m.memoryTracker.TrackMemAccess2(effAddr + 4) m.state.Memory.SetMemory(effAddr+4, nsecs) + m.handleMemoryUpdate(effAddr + 4) default: v0 = exec.SysErrorSignal v1 = exec.MipsEINVAL @@ -286,8 +293,71 @@ func (m *InstrumentedState) mipsStep() error { 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 - 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) { diff --git a/cannon/mipsevm/multithreaded/state.go b/cannon/mipsevm/multithreaded/state.go index d0bf4bf5dd82..f93a99564958 100644 --- a/cannon/mipsevm/multithreaded/state.go +++ b/cannon/mipsevm/multithreaded/state.go @@ -5,7 +5,6 @@ import ( "fmt" "io" - "github.com/ethereum-optimism/optimism/cannon/serialize" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" @@ -14,16 +13,20 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" + "github.com/ethereum-optimism/optimism/cannon/serialize" ) // STATE_WITNESS_SIZE is the size of the state witness encoding in bytes. -const STATE_WITNESS_SIZE = 163 +const STATE_WITNESS_SIZE = 172 const ( MEMROOT_WITNESS_OFFSET = 0 PREIMAGE_KEY_WITNESS_OFFSET = MEMROOT_WITNESS_OFFSET + 32 PREIMAGE_OFFSET_WITNESS_OFFSET = PREIMAGE_KEY_WITNESS_OFFSET + 32 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 STEP_WITNESS_OFFSET = EXITED_WITNESS_OFFSET + 1 STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET = STEP_WITNESS_OFFSET + 8 @@ -40,7 +43,10 @@ type State struct { PreimageKey common.Hash 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 Exited bool @@ -64,16 +70,19 @@ func CreateEmptyState() *State { initThread := CreateEmptyThread() return &State{ - Memory: memory.NewMemory(), - Heap: 0, - ExitCode: 0, - Exited: false, - Step: 0, - Wakeup: exec.FutexEmptyAddr, - TraverseRight: false, - LeftThreadStack: []*ThreadState{initThread}, - RightThreadStack: []*ThreadState{}, - NextThreadId: initThread.ThreadId + 1, + Memory: memory.NewMemory(), + Heap: 0, + LLReservationActive: false, + LLAddress: 0, + LLOwnerThread: 0, + ExitCode: 0, + Exited: false, + Step: 0, + Wakeup: exec.FutexEmptyAddr, + TraverseRight: false, + LeftThreadStack: []*ThreadState{initThread}, + RightThreadStack: []*ThreadState{}, + NextThreadId: initThread.ThreadId + 1, } } @@ -187,6 +196,9 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) { out = append(out, s.PreimageKey[:]...) out = binary.BigEndian.AppendUint32(out, s.PreimageOffset) 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 = mipsevm.AppendBoolToWitness(out, s.Exited) @@ -264,6 +276,15 @@ func (s *State) Serialize(out io.Writer) error { if err := bout.WriteUInt(s.Heap); err != nil { 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 { return err } @@ -324,6 +345,15 @@ func (s *State) Deserialize(in io.Reader) error { if err := bin.ReadUInt(&s.Heap); err != nil { 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 { return err } diff --git a/cannon/mipsevm/multithreaded/state_test.go b/cannon/mipsevm/multithreaded/state_test.go index 7aa3580f58ff..6d776632bf0f 100644 --- a/cannon/mipsevm/multithreaded/state_test.go +++ b/cannon/mipsevm/multithreaded/state_test.go @@ -6,7 +6,6 @@ import ( "encoding/json" "testing" - "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" @@ -15,6 +14,7 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/cannon/mipsevm/program" ) @@ -42,6 +42,8 @@ func TestState_EncodeWitness(t *testing.T) { } heap := uint32(12) + llAddress := uint32(55) + llThreadOwner := uint32(99) preimageKey := crypto.Keccak256Hash([]byte{1, 2, 3, 4}) preimageOffset := uint32(24) step := uint64(33) @@ -53,6 +55,9 @@ func TestState_EncodeWitness(t *testing.T) { state.PreimageKey = preimageKey state.PreimageOffset = preimageOffset state.Heap = heap + state.LLReservationActive = true + state.LLAddress = llAddress + state.LLOwnerThread = llThreadOwner state.Step = step state.StepsSinceLastContextSwitch = stepsSinceContextSwitch @@ -66,6 +71,9 @@ func TestState_EncodeWitness(t *testing.T) { setWitnessField(expectedWitness, PREIMAGE_KEY_WITNESS_OFFSET, preimageKey[:]) 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, 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}) if c.exited { setWitnessField(expectedWitness, EXITED_WITNESS_OFFSET, []byte{1}) @@ -176,6 +184,9 @@ func TestSerializeStateRoundTrip(t *testing.T) { PreimageKey: common.Hash{0xFF}, PreimageOffset: 5, Heap: 0xc0ffee, + LLReservationActive: true, + LLAddress: 0x12345678, + LLOwnerThread: 0x02, ExitCode: 1, Exited: true, Step: 0xdeadbeef, diff --git a/cannon/mipsevm/multithreaded/testutil/expectations.go b/cannon/mipsevm/multithreaded/testutil/expectations.go index 24158be8e235..559ed2de8c4f 100644 --- a/cannon/mipsevm/multithreaded/testutil/expectations.go +++ b/cannon/mipsevm/multithreaded/testutil/expectations.go @@ -14,15 +14,18 @@ import ( // 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) type ExpectedMTState struct { - PreimageKey common.Hash - PreimageOffset uint32 - Heap uint32 - ExitCode uint8 - Exited bool - Step uint64 - LastHint hexutil.Bytes - MemoryRoot common.Hash - expectedMemory *memory.Memory + PreimageKey common.Hash + PreimageOffset uint32 + Heap uint32 + LLReservationActive bool + LLAddress uint32 + LLOwnerThread uint32 + ExitCode uint8 + Exited bool + Step uint64 + LastHint hexutil.Bytes + MemoryRoot common.Hash + expectedMemory *memory.Memory // Threading-related expectations StepsSinceLastContextSwitch uint64 Wakeup uint32 @@ -62,14 +65,17 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState { return &ExpectedMTState{ // General Fields - PreimageKey: fromState.GetPreimageKey(), - PreimageOffset: fromState.GetPreimageOffset(), - Heap: fromState.GetHeap(), - ExitCode: fromState.GetExitCode(), - Exited: fromState.GetExited(), - Step: fromState.GetStep(), - LastHint: fromState.GetLastHint(), - MemoryRoot: fromState.GetMemory().MerkleRoot(), + PreimageKey: fromState.GetPreimageKey(), + PreimageOffset: fromState.GetPreimageOffset(), + Heap: fromState.GetHeap(), + LLReservationActive: fromState.LLReservationActive, + LLAddress: fromState.LLAddress, + LLOwnerThread: fromState.LLOwnerThread, + ExitCode: fromState.GetExitCode(), + Exited: fromState.GetExited(), + Step: fromState.GetStep(), + LastHint: fromState.GetLastHint(), + MemoryRoot: fromState.GetMemory().MerkleRoot(), // Thread-related global fields StepsSinceLastContextSwitch: fromState.StepsSinceLastContextSwitch, Wakeup: fromState.Wakeup, @@ -119,7 +125,7 @@ func (e *ExpectedMTState) ExpectMemoryWrite(addr uint32, val uint32) { func (e *ExpectedMTState) ExpectMemoryWriteMultiple(addr uint32, val uint32, addr2 uint32, val2 uint32) { e.expectedMemory.SetMemory(addr, val) - e.expectedMemory.SetMemory(addr2, val) + e.expectedMemory.SetMemory(addr2, val2) e.MemoryRoot = e.expectedMemory.MerkleRoot() } @@ -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.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.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.Exited, actualState.GetExited(), "Expect exited = %v", e.Exited) require.Equalf(t, e.Step, actualState.GetStep(), "Expect step = %d", e.Step) diff --git a/cannon/mipsevm/multithreaded/testutil/expectations_test.go b/cannon/mipsevm/multithreaded/testutil/expectations_test.go index 15cba8b00469..a40e15e0f8d5 100644 --- a/cannon/mipsevm/multithreaded/testutil/expectations_test.go +++ b/cannon/mipsevm/multithreaded/testutil/expectations_test.go @@ -28,6 +28,9 @@ func TestValidate_shouldCatchMutations(t *testing.T) { {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: "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: "Exited", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Exited = !e.Exited }}, {name: "Step", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Step += 1 }}, diff --git a/cannon/mipsevm/multithreaded/testutil/mutators.go b/cannon/mipsevm/multithreaded/testutil/mutators.go index 30a97691609e..529f496f950f 100644 --- a/cannon/mipsevm/multithreaded/testutil/mutators.go +++ b/cannon/mipsevm/multithreaded/testutil/mutators.go @@ -1,6 +1,7 @@ package testutil import ( + "math" "math/rand" "github.com/ethereum/go-ethereum/common" @@ -28,11 +29,19 @@ func (m *StateMutatorMultiThreaded) Randomize(randSeed int64) { m.state.PreimageKey = testutil.RandHash(r) m.state.PreimageOffset = r.Uint32() - m.state.Heap = r.Uint32() m.state.Step = step m.state.LastHint = testutil.RandHint(r) 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 activeStackThreads := r.Intn(2) + 1 inactiveStackThreads := r.Intn(3) diff --git a/cannon/mipsevm/singlethreaded/mips.go b/cannon/mipsevm/singlethreaded/mips.go index 48a25e084291..a88d0c66b0e6 100644 --- a/cannon/mipsevm/singlethreaded/mips.go +++ b/cannon/mipsevm/singlethreaded/mips.go @@ -1,6 +1,8 @@ package singlethreaded import ( + "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -30,7 +32,7 @@ func (m *InstrumentedState) handleSyscall() error { return nil case exec.SysRead: 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 case exec.SysWrite: var newLastHint hexutil.Bytes @@ -62,6 +64,37 @@ func (m *InstrumentedState) mipsStep() error { 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 - 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) } diff --git a/cannon/mipsevm/tests/evm_common_test.go b/cannon/mipsevm/tests/evm_common_test.go index 9c4619fb2fc2..ea6c7b2de957 100644 --- a/cannon/mipsevm/tests/evm_common_test.go +++ b/cannon/mipsevm/tests/evm_common_test.go @@ -2,7 +2,6 @@ package tests import ( "bytes" - "encoding/binary" "fmt" "io" "os" @@ -13,14 +12,12 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "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/program" "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" - preimage "github.com/ethereum-optimism/optimism/op-preimage" ) func TestEVM(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) { var tracer *tracing.Hooks diff --git a/cannon/mipsevm/tests/evm_multithreaded_test.go b/cannon/mipsevm/tests/evm_multithreaded_test.go index 480dee57dfbb..f7ead804c707 100644 --- a/cannon/mipsevm/tests/evm_multithreaded_test.go +++ b/cannon/mipsevm/tests/evm_multithreaded_test.go @@ -1,6 +1,7 @@ package tests import ( + "encoding/binary" "fmt" "os" "slices" @@ -8,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" @@ -16,8 +18,375 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" mttestutil "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded/testutil" "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" + preimage "github.com/ethereum-optimism/optimism/op-preimage" ) +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) { contracts := testutil.TestContractsSetup(t, testutil.MipsMultithreaded) var tracer *tracing.Hooks @@ -92,7 +461,7 @@ func TestEVM_SysClone_Successful(t *testing.T) { t.Run(c.name, func(t *testing.T) { stackPtr := uint32(100) - goVm, state, contracts := setup(t, i) + goVm, state, contracts := setup(t, i, nil) mttestutil.InitializeSingleThread(i*333, state, c.traverseRight) state.Memory.SetMemory(state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = exec.SysClone // the syscall number @@ -153,7 +522,7 @@ func TestEVM_SysGetTID(t *testing.T) { for i, c := range cases { 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) state.GetCurrentThread().ThreadId = c.threadId @@ -197,7 +566,7 @@ func TestEVM_SysExit(t *testing.T) { t.Run(c.name, func(t *testing.T) { 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) state.Memory.SetMemory(state.GetPC(), syscallInsn) @@ -245,7 +614,7 @@ func TestEVM_PopExitedThread(t *testing.T) { for i, c := range cases { 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) step := state.Step @@ -300,7 +669,7 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) { for i, c := range cases { 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() state.Memory.SetMemory(state.GetPC(), syscallInsn) @@ -363,7 +732,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) { for i, c := range cases { 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) step := state.Step @@ -449,7 +818,7 @@ func TestEVM_SysFutex_UnsupportedOp(t *testing.T) { for name, op := range unsupportedFutexOps { 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() state.Memory.SetMemory(state.GetPC(), syscallInsn) @@ -504,7 +873,7 @@ func runPreemptSyscall(t *testing.T, syscallName string, syscallNum uint32) { for _, traverseRight := range []bool{true, false} { testName := fmt.Sprintf("%v: %v (traverseRight = %v)", syscallName, c.name, traverseRight) 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) state.Memory.SetMemory(state.GetPC(), syscallInsn) @@ -535,7 +904,7 @@ func runPreemptSyscall(t *testing.T, syscallName string, syscallNum uint32) { func TestEVM_SysOpen(t *testing.T) { var tracer *tracing.Hooks - goVm, state, contracts := setup(t, 5512) + goVm, state, contracts := setup(t, 5512, nil) state.Memory.SetMemory(state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = exec.SysOpen // Set syscall number @@ -560,7 +929,7 @@ func TestEVM_SysOpen(t *testing.T) { func TestEVM_SysGetPID(t *testing.T) { var tracer *tracing.Hooks - goVm, state, contracts := setup(t, 1929) + goVm, state, contracts := setup(t, 1929, nil) state.Memory.SetMemory(state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = exec.SysGetpid // Set syscall number @@ -594,6 +963,25 @@ func TestEVM_SysClockGettimeRealtime(t *testing.T) { func testEVM_SysClockGettime(t *testing.T, clkid uint32) { 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 { name string timespecAddr uint32 @@ -601,45 +989,73 @@ func testEVM_SysClockGettime(t *testing.T, clkid uint32) { {"aligned timespec address", 0x1000}, {"unaligned timespec address", 0x1003}, } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - goVm, state, contracts := setup(t, 2101) + 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) { + 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) - state.GetRegistersRef()[2] = exec.SysClockGetTime // Set syscall number - state.GetRegistersRef()[4] = clkid // a0 - state.GetRegistersRef()[5] = c.timespecAddr // a1 - step := state.Step + // Define LL-related params + var llAddress, llOwnerThread uint32 + if v.matchEffAddr { + llAddress = effAddr + } 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) - expected.ExpectStep() - expected.ActiveThread().Registers[2] = 0 - 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)) - } - effAddr := c.timespecAddr & 0xFFffFFfc - expected.ExpectMemoryWrite(effAddr, secs) - expected.ExpectMemoryWrite(effAddr+4, nsecs) + state.Memory.SetMemory(state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = exec.SysClockGetTime // Set syscall number + state.GetRegistersRef()[4] = clkid // a0 + state.GetRegistersRef()[5] = c.timespecAddr // a1 + state.LLReservationActive = v.llReservationActive + state.LLAddress = llAddress + state.LLOwnerThread = llOwnerThread - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ActiveThread().Registers[2] = 0 + 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 - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) - }) + var err error + var stepWitness *mipsevm.StepWitness + stepWitness, err = goVm.Step(true) + require.NoError(t, err) + + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + }) + } } } func TestEVM_SysClockGettimeNonMonotonic(t *testing.T) { var tracer *tracing.Hooks - goVm, state, contracts := setup(t, 2101) + goVm, state, contracts := setup(t, 2101, nil) timespecAddr := uint32(0x1000) state.Memory.SetMemory(state.GetPC(), syscallInsn) @@ -700,7 +1116,7 @@ func TestEVM_NoopSyscall(t *testing.T) { var tracer *tracing.Hooks for noopName, noopVal := range NoopSyscalls { 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.GetRegistersRef()[2] = noopVal // Set syscall number @@ -747,7 +1163,7 @@ func TestEVM_UnsupportedSyscall(t *testing.T) { syscallNum := syscallNum t.Run(testName, func(t *testing.T) { t.Parallel() - goVm, state, contracts := setup(t, i*3434) + goVm, state, contracts := setup(t, i*3434, nil) // Setup basic getThreadId syscall instruction state.Memory.SetMemory(state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = syscallNum @@ -794,7 +1210,7 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) { 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) state.Step = c.step @@ -856,7 +1272,7 @@ func TestEVM_NormalTraversal_Full(t *testing.T) { testName := fmt.Sprintf("%v (traverseRight = %v)", c.name, traverseRight) t.Run(testName, func(t *testing.T) { // 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) // Put threads into a waiting state so that we just traverse through them for _, thread := range mttestutil.GetAllThreads(state) { @@ -926,7 +1342,7 @@ func TestEVM_WakeupTraversalStep(t *testing.T) { for i, c := range cases { 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) step := state.Step @@ -974,7 +1390,7 @@ func TestEVM_WakeupTraversal_Full(t *testing.T) { for i, c := range cases { t.Run(c.name, func(t *testing.T) { // 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) state.Wakeup = 0x08 step := state.Step @@ -1028,7 +1444,7 @@ func TestEVM_SchedQuantumThreshold(t *testing.T) { for i, c := range cases { 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 state.Memory.SetMemory(state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = exec.SysGetTID // Set syscall number @@ -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) - 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) return vm, state, v.Contracts diff --git a/cannon/mipsevm/tests/evm_singlethreaded_test.go b/cannon/mipsevm/tests/evm_singlethreaded_test.go new file mode 100644 index 000000000000..32cad32cc00e --- /dev/null +++ b/cannon/mipsevm/tests/evm_singlethreaded_test.go @@ -0,0 +1,196 @@ +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) + } + }) + } +} diff --git a/go.mod b/go.mod index e1b4e14530a1..08dd890d7025 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-leveldb v0.5.0 github.com/klauspost/compress v1.17.9 + github.com/kurtosis-tech/kurtosis/api/golang v1.2.0 github.com/libp2p/go-libp2p v0.36.2 github.com/libp2p/go-libp2p-mplex v0.9.0 github.com/libp2p/go-libp2p-pubsub v0.12.0 @@ -53,8 +54,10 @@ require ( require ( github.com/DataDog/zstd v1.5.6-0.20230824185856-869dae002e5e // indirect + github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/VictoriaMetrics/fastcache v1.12.2 // indirect + github.com/adrg/xdg v0.4.0 // indirect github.com/allegro/bigcache v1.2.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/benbjohnson/clock v1.3.5 // indirect @@ -85,6 +88,7 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 // indirect github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 // indirect + github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/elastic/gosigar v0.14.3 // indirect github.com/ethereum/c-kzg-4844 v1.0.0 // indirect @@ -96,15 +100,18 @@ require ( github.com/francoispqt/gojay v1.2.13 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/go-yaml/yaml v2.1.0+incompatible // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect github.com/google/uuid v1.6.0 // indirect @@ -136,6 +143,11 @@ require ( github.com/koron/go-ssdp v0.0.4 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect + github.com/kurtosis-tech/kurtosis-portal/api/golang v0.0.0-20230818182330-1a86869414d2 // indirect + github.com/kurtosis-tech/kurtosis/contexts-config-store v0.0.0-20230818184218-f4e3e773463b // indirect + github.com/kurtosis-tech/kurtosis/grpc-file-transfer/golang v0.0.0-20230803130419-099ee7a4e3dc // indirect + github.com/kurtosis-tech/kurtosis/path-compression v0.0.0-20240307154559-64d2929cd265 // indirect + github.com/kurtosis-tech/stacktrace v0.0.0-20211028211901-1c67a77b5409 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-flow-metrics v0.1.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect @@ -148,6 +160,7 @@ require ( github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mholt/archiver v3.1.1+incompatible // indirect github.com/miekg/dns v1.1.62 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect @@ -166,11 +179,13 @@ require ( github.com/multiformats/go-varint v0.0.7 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/naoina/go-stringutil v0.1.0 // indirect + github.com/nwaples/rardecode v1.1.3 // indirect github.com/onsi/ginkgo/v2 v2.20.0 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 // indirect + github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pion/datachannel v1.5.8 // indirect github.com/pion/dtls/v2 v2.2.12 // indirect github.com/pion/ice/v2 v2.3.34 // indirect @@ -199,11 +214,12 @@ require ( github.com/quic-go/webtransport-go v0.8.0 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/rs/cors v1.11.0 // indirect github.com/rs/xid v1.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/status-im/keycard-go v0.2.0 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -212,7 +228,9 @@ require ( github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect + github.com/ulikunitz/xz v0.5.11 // indirect github.com/wlynxg/anet v0.0.4 // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect go.etcd.io/bbolt v1.3.5 // indirect @@ -227,6 +245,10 @@ require ( golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect golang.org/x/tools v0.24.0 // indirect + google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect + google.golang.org/grpc v1.57.1 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 22368b8d57d1..cdfe4f7b43f0 100644 --- a/go.sum +++ b/go.sum @@ -16,11 +16,15 @@ github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3 github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.6-0.20230824185856-869dae002e5e h1:ZIWapoIRN1VqT8GR8jAwb1Ie9GyehWjVcGh32Y2MznE= github.com/DataDog/zstd v1.5.6-0.20230824185856-869dae002e5e/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= +github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -168,6 +172,9 @@ github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 h1:qwcF+vdFrvPSEUDSX5R github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= +github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= +github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= @@ -196,6 +203,8 @@ github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= @@ -207,6 +216,7 @@ github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= @@ -232,6 +242,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -400,8 +412,10 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -420,6 +434,18 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kurtosis-tech/kurtosis-portal/api/golang v0.0.0-20230818182330-1a86869414d2 h1:izciXrFyFR+ihJ7nLTOkoIX5GzBPIp8gVKlw94gIc98= +github.com/kurtosis-tech/kurtosis-portal/api/golang v0.0.0-20230818182330-1a86869414d2/go.mod h1:bWSMQK3WHVTGHX9CjxPAb/LtzcmfOxID2wdzakSWQxo= +github.com/kurtosis-tech/kurtosis/api/golang v1.2.0 h1:NaNkkAvLiAVgRVTM9yeLCg4FIoIe/BVJKfU93KIFqdc= +github.com/kurtosis-tech/kurtosis/api/golang v1.2.0/go.mod h1:9T22P7Vv3j5g6sbm78DxHQ4s9C4Cj3s9JjFQ7DFyYpM= +github.com/kurtosis-tech/kurtosis/contexts-config-store v0.0.0-20230818184218-f4e3e773463b h1:hMoIM99QKcYQqsnK4AF7Lovi9ZD9ac6lZLZ5D/jx2x8= +github.com/kurtosis-tech/kurtosis/contexts-config-store v0.0.0-20230818184218-f4e3e773463b/go.mod h1:4pFdrRwDz5R+Fov2ZuTaPhAVgjA2jhGh1Izf832sX7A= +github.com/kurtosis-tech/kurtosis/grpc-file-transfer/golang v0.0.0-20230803130419-099ee7a4e3dc h1:7IlEpSehmWcNXOFpNP24Cu5HQI3af7GCBQw//m+LnvQ= +github.com/kurtosis-tech/kurtosis/grpc-file-transfer/golang v0.0.0-20230803130419-099ee7a4e3dc/go.mod h1:TOWMQgvAJH/NiWWERGXg/plT9lS7aFcXFxCa0M5sfHo= +github.com/kurtosis-tech/kurtosis/path-compression v0.0.0-20240307154559-64d2929cd265 h1:uSDftcGStwuAjHv8fV2TleNCKSWPvUKe7EaplFG3yBI= +github.com/kurtosis-tech/kurtosis/path-compression v0.0.0-20240307154559-64d2929cd265/go.mod h1:aDMrPeS7Gii8W6SDKSKyrBNgEQAUYidriyeKGf+Ml3I= +github.com/kurtosis-tech/stacktrace v0.0.0-20211028211901-1c67a77b5409 h1:YQTATifMUwZEtZYb0LVA7DK2pj8s71iY8rzweuUQ5+g= +github.com/kurtosis-tech/stacktrace v0.0.0-20211028211901-1c67a77b5409/go.mod h1:y5weVs5d9wXXHcDA1awRxkIhhHC1xxYJN8a7aXnE6S8= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= @@ -478,6 +504,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU= +github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= @@ -544,6 +572,8 @@ github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcou github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= +github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= @@ -581,6 +611,8 @@ github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2D github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= +github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pion/datachannel v1.5.8 h1:ph1P1NsGkazkjrvyMfhRBUAWMxugJjq2HfQifaOoSNo= @@ -686,8 +718,8 @@ github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -725,6 +757,8 @@ github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5k github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -764,6 +798,9 @@ github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9f github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8= github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= @@ -775,6 +812,8 @@ github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMI github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/wlynxg/anet v0.0.4 h1:0de1OFQxnNqAu+x2FAKKCVIrnfGKQbs7FQz++tB0+Uw= github.com/wlynxg/anet v0.0.4/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -937,11 +976,13 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1023,10 +1064,18 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e h1:xIXmWJ303kJCuogpj0bHq+dcjcZHU+XFyc1I0Yl9cRg= +google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:0ggbjUrZYpy1q+ANUS30SEoGZ53cdfwtbuG7Ptgy108= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg= +google.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/op-chain-ops/deployer/apply.go b/op-chain-ops/deployer/apply.go index a58a0654c3be..e604eb8a9ffe 100644 --- a/op-chain-ops/deployer/apply.go +++ b/op-chain-ops/deployer/apply.go @@ -6,6 +6,8 @@ import ( "fmt" "strings" + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline" opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" @@ -92,7 +94,6 @@ func Apply(ctx context.Context, cfg ApplyConfig) error { env := &pipeline.Env{ Workdir: cfg.Workdir, - L1RPCUrl: cfg.L1RPCUrl, L1Client: l1Client, Logger: cfg.Logger, Signer: signer, @@ -113,23 +114,48 @@ func Apply(ctx context.Context, cfg ApplyConfig) error { return err } - pline := []struct { - name string - stage pipeline.Stage - }{ + if err := ApplyPipeline(ctx, env, intent, st); err != nil { + return err + } + + st.AppliedIntent = intent + if err := env.WriteState(st); err != nil { + return err + } + + return nil +} + +type pipelineStage struct { + name string + stage pipeline.Stage +} + +func ApplyPipeline( + ctx context.Context, + env *pipeline.Env, + intent *state.Intent, + st *state.State, +) error { + pline := []pipelineStage{ {"init", pipeline.Init}, {"deploy-superchain", pipeline.DeploySuperchain}, + {"deploy-implementations", pipeline.DeployImplementations}, + } + + for _, chain := range intent.Chains { + pline = append(pline, pipelineStage{ + fmt.Sprintf("deploy-opchain-%s", chain.ID.Hex()), + func(ctx context.Context, env *pipeline.Env, intent *state.Intent, st *state.State) error { + return pipeline.DeployOPChain(ctx, env, intent, st, chain.ID) + }, + }) } + for _, stage := range pline { if err := stage.stage(ctx, env, intent, st); err != nil { return fmt.Errorf("error in pipeline stage: %w", err) } } - - st.AppliedIntent = intent - if err := env.WriteState(st); err != nil { - return err - } - return nil } diff --git a/op-chain-ops/deployer/broadcaster/discard.go b/op-chain-ops/deployer/broadcaster/discard.go new file mode 100644 index 000000000000..42f5b1b0a964 --- /dev/null +++ b/op-chain-ops/deployer/broadcaster/discard.go @@ -0,0 +1,20 @@ +package broadcaster + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script" +) + +type discardBroadcaster struct { +} + +func DiscardBroadcaster() Broadcaster { + return &discardBroadcaster{} +} + +func (d *discardBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, error) { + return nil, nil +} + +func (d *discardBroadcaster) Hook(bcast script.Broadcast) {} diff --git a/op-chain-ops/deployer/broadcaster/keyed.go b/op-chain-ops/deployer/broadcaster/keyed.go index 3df4cd9ed1eb..2784c4d455be 100644 --- a/op-chain-ops/deployer/broadcaster/keyed.go +++ b/op-chain-ops/deployer/broadcaster/keyed.go @@ -20,21 +20,23 @@ import ( ) const ( - GasPadFactor = 1.5 + GasPadFactor = 2.0 ) type KeyedBroadcaster struct { lgr log.Logger mgr txmgr.TxManager bcasts []script.Broadcast + client *ethclient.Client } type KeyedBroadcasterOpts struct { - Logger log.Logger - ChainID *big.Int - Client *ethclient.Client - Signer opcrypto.SignerFn - From common.Address + Logger log.Logger + ChainID *big.Int + Client *ethclient.Client + Signer opcrypto.SignerFn + From common.Address + TXManagerLogger log.Logger } func NewKeyedBroadcaster(cfg KeyedBroadcasterOpts) (*KeyedBroadcaster, error) { @@ -66,9 +68,14 @@ func NewKeyedBroadcaster(cfg KeyedBroadcasterOpts) (*KeyedBroadcaster, error) { mgrCfg.MinTipCap.Store(minTipCap) mgrCfg.MinTipCap.Store(minBaseFee) + txmLogger := log.NewLogger(log.DiscardHandler()) + if cfg.TXManagerLogger != nil { + txmLogger = cfg.TXManagerLogger + } + mgr, err := txmgr.NewSimpleTxManagerFromConfig( "transactor", - log.NewLogger(log.DiscardHandler()), + txmLogger, &metrics.NoopTxMetrics{}, mgrCfg, ) @@ -78,8 +85,9 @@ func NewKeyedBroadcaster(cfg KeyedBroadcasterOpts) (*KeyedBroadcaster, error) { } return &KeyedBroadcaster{ - lgr: cfg.Logger, - mgr: mgr, + lgr: cfg.Logger, + mgr: mgr, + client: cfg.Client, }, nil } @@ -92,8 +100,13 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er futures := make([]<-chan txmgr.SendResponse, len(t.bcasts)) ids := make([]common.Hash, len(t.bcasts)) + latestBlock, err := t.client.BlockByNumber(ctx, nil) + if err != nil { + return nil, fmt.Errorf("failed to get latest block: %w", err) + } + for i, bcast := range t.bcasts { - futures[i], ids[i] = t.broadcast(ctx, bcast) + futures[i], ids[i] = t.broadcast(ctx, bcast, latestBlock.GasLimit()) t.lgr.Info( "transaction broadcasted", "id", ids[i], @@ -101,7 +114,7 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er ) } - var err *multierror.Error + var txErr *multierror.Error var completed int for i, fut := range futures { bcastRes := <-fut @@ -116,7 +129,7 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er if bcastRes.Receipt.Status == 0 { failErr := fmt.Errorf("transaction failed: %s", outRes.Receipt.TxHash.String()) - err = multierror.Append(err, failErr) + txErr = multierror.Append(txErr, failErr) outRes.Err = failErr t.lgr.Error( "transaction failed on chain", @@ -138,7 +151,7 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er ) } } else { - err = multierror.Append(err, bcastRes.Err) + txErr = multierror.Append(txErr, bcastRes.Err) outRes.Err = bcastRes.Err t.lgr.Error( "transaction failed", @@ -151,12 +164,13 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er results = append(results, outRes) } - return results, err.ErrorOrNil() + return results, txErr.ErrorOrNil() } -func (t *KeyedBroadcaster) broadcast(ctx context.Context, bcast script.Broadcast) (<-chan txmgr.SendResponse, common.Hash) { - id := bcast.ID() +func (t *KeyedBroadcaster) broadcast(ctx context.Context, bcast script.Broadcast, blockGasLimit uint64) (<-chan txmgr.SendResponse, common.Hash) { + ch := make(chan txmgr.SendResponse, 1) + id := bcast.ID() value := ((*uint256.Int)(bcast.Value)).ToBig() var candidate txmgr.TxCandidate switch bcast.Type { @@ -166,27 +180,45 @@ func (t *KeyedBroadcaster) broadcast(ctx context.Context, bcast script.Broadcast TxData: bcast.Input, To: to, Value: value, - GasLimit: padGasLimit(bcast.Input, bcast.GasUsed, false), + GasLimit: padGasLimit(bcast.Input, bcast.GasUsed, false, blockGasLimit), } case script.BroadcastCreate: candidate = txmgr.TxCandidate{ TxData: bcast.Input, To: nil, - GasLimit: padGasLimit(bcast.Input, bcast.GasUsed, true), + GasLimit: padGasLimit(bcast.Input, bcast.GasUsed, true, blockGasLimit), + } + case script.BroadcastCreate2: + txData := make([]byte, len(bcast.Salt)+len(bcast.Input)) + copy(txData, bcast.Salt[:]) + copy(txData[len(bcast.Salt):], bcast.Input) + + candidate = txmgr.TxCandidate{ + TxData: txData, + To: &script.DeterministicDeployerAddress, + Value: value, + GasLimit: padGasLimit(bcast.Input, bcast.GasUsed, true, blockGasLimit), } } - ch := make(chan txmgr.SendResponse, 1) t.mgr.SendAsync(ctx, candidate, ch) return ch, id } -func padGasLimit(data []byte, gasUsed uint64, creation bool) uint64 { +// padGasLimit calculates the gas limit for a transaction based on the intrinsic gas and the gas used by +// the underlying call. Values are multiplied by a pad factor to account for any discrepancies. The output +// is clamped to the block gas limit since Geth will reject transactions that exceed it before letting them +// into the mempool. +func padGasLimit(data []byte, gasUsed uint64, creation bool, blockGasLimit uint64) uint64 { intrinsicGas, err := core.IntrinsicGas(data, nil, creation, true, true, false) // This method never errors - we should look into it if it does. if err != nil { panic(err) } - return uint64(float64(intrinsicGas+gasUsed) * GasPadFactor) + limit := uint64(float64(intrinsicGas+gasUsed) * GasPadFactor) + if limit > blockGasLimit { + return blockGasLimit + } + return limit } diff --git a/op-chain-ops/deployer/integration_test/apply_test.go b/op-chain-ops/deployer/integration_test/apply_test.go new file mode 100644 index 000000000000..ca03b4998004 --- /dev/null +++ b/op-chain-ops/deployer/integration_test/apply_test.go @@ -0,0 +1,200 @@ +package integration_test + +import ( + "context" + "fmt" + "log/slog" + "math/big" + "net/url" + "path" + "runtime" + "testing" + + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer" + "github.com/holiman/uint256" + + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-service/testutils/kurtosisutil" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" +) + +const TestParams = ` +participants: + - el_type: geth + cl_type: lighthouse +network_params: + prefunded_accounts: '{ "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": { "balance": "1000000ETH" } }' + additional_preloaded_contracts: '{ + "0x4e59b44847b379578588920cA78FbF26c0B4956C": { + balance: "0ETH", + code: "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", + storage: {}, + nonce: 0, + secretKey: "0x" + } + }' + network_id: "77799777" + seconds_per_slot: 3 +` + +type deployerKey struct{} + +func (d *deployerKey) HDPath() string { + return "m/44'/60'/0'/0/0" +} + +func (d *deployerKey) String() string { + return "deployer-key" +} + +func TestEndToEndApply(t *testing.T) { + kurtosisutil.Test(t) + + lgr := testlog.Logger(t, slog.LevelInfo) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _, testFilename, _, ok := runtime.Caller(0) + require.Truef(t, ok, "failed to get test filename") + monorepoDir := path.Join(path.Dir(testFilename), "..", "..", "..") + artifactsDir := path.Join(monorepoDir, "packages", "contracts-bedrock", "forge-artifacts") + + enclaveCtx := kurtosisutil.StartEnclave(t, ctx, lgr, "github.com/ethpandaops/ethereum-package", TestParams) + + service, err := enclaveCtx.GetServiceContext("el-1-geth-lighthouse") + require.NoError(t, err) + + ip := service.GetMaybePublicIPAddress() + ports := service.GetPublicPorts() + rpcURL := fmt.Sprintf("http://%s:%d", ip, ports["rpc"].GetNumber()) + l1Client, err := ethclient.Dial(rpcURL) + require.NoError(t, err) + + artifactsURL, err := url.Parse(fmt.Sprintf("file://%s", artifactsDir)) + require.NoError(t, err) + + depKey := new(deployerKey) + l1ChainID := big.NewInt(77799777) + dk, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) + require.NoError(t, err) + pk, err := dk.Secret(depKey) + require.NoError(t, err) + signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(pk, l1ChainID)) + + id := uint256.NewInt(1) + + addrFor := func(key devkeys.Key) common.Address { + addr, err := dk.Address(key) + require.NoError(t, err) + return addr + } + env := &pipeline.Env{ + Workdir: t.TempDir(), + L1Client: l1Client, + Signer: signer, + Deployer: addrFor(depKey), + Logger: lgr, + } + intent := &state.Intent{ + L1ChainID: l1ChainID.Uint64(), + SuperchainRoles: state.SuperchainRoles{ + ProxyAdminOwner: addrFor(devkeys.L1ProxyAdminOwnerRole.Key(l1ChainID)), + ProtocolVersionsOwner: addrFor(devkeys.SuperchainDeployerKey.Key(l1ChainID)), + Guardian: addrFor(devkeys.SuperchainConfigGuardianKey.Key(l1ChainID)), + }, + UseFaultProofs: true, + FundDevAccounts: true, + ContractArtifactsURL: (*state.ArtifactsURL)(artifactsURL), + Chains: []state.ChainIntent{ + { + ID: id.Bytes32(), + Roles: state.ChainRoles{ + ProxyAdminOwner: addrFor(devkeys.L2ProxyAdminOwnerRole.Key(l1ChainID)), + SystemConfigOwner: addrFor(devkeys.SystemConfigOwner.Key(l1ChainID)), + GovernanceTokenOwner: addrFor(devkeys.L2ProxyAdminOwnerRole.Key(l1ChainID)), + UnsafeBlockSigner: addrFor(devkeys.SequencerP2PRole.Key(l1ChainID)), + Batcher: addrFor(devkeys.BatcherRole.Key(l1ChainID)), + Proposer: addrFor(devkeys.ProposerRole.Key(l1ChainID)), + Challenger: addrFor(devkeys.ChallengerRole.Key(l1ChainID)), + }, + }, + }, + } + st := &state.State{ + Version: 1, + } + + require.NoError(t, deployer.ApplyPipeline( + ctx, + env, + intent, + st, + )) + + addrs := []struct { + name string + addr common.Address + }{ + {"SuperchainProxyAdmin", st.SuperchainDeployment.ProxyAdminAddress}, + {"SuperchainConfigProxy", st.SuperchainDeployment.SuperchainConfigProxyAddress}, + {"SuperchainConfigImpl", st.SuperchainDeployment.SuperchainConfigImplAddress}, + {"ProtocolVersionsProxy", st.SuperchainDeployment.ProtocolVersionsProxyAddress}, + {"ProtocolVersionsImpl", st.SuperchainDeployment.ProtocolVersionsImplAddress}, + {"Opsm", st.ImplementationsDeployment.OpsmAddress}, + {"DelayedWETHImpl", st.ImplementationsDeployment.DelayedWETHImplAddress}, + {"OptimismPortalImpl", st.ImplementationsDeployment.OptimismPortalImplAddress}, + {"PreimageOracleSingleton", st.ImplementationsDeployment.PreimageOracleSingletonAddress}, + {"MipsSingleton", st.ImplementationsDeployment.MipsSingletonAddress}, + {"SystemConfigImpl", st.ImplementationsDeployment.SystemConfigImplAddress}, + {"L1CrossDomainMessengerImpl", st.ImplementationsDeployment.L1CrossDomainMessengerImplAddress}, + {"L1ERC721BridgeImpl", st.ImplementationsDeployment.L1ERC721BridgeImplAddress}, + {"L1StandardBridgeImpl", st.ImplementationsDeployment.L1StandardBridgeImplAddress}, + {"OptimismMintableERC20FactoryImpl", st.ImplementationsDeployment.OptimismMintableERC20FactoryImplAddress}, + {"DisputeGameFactoryImpl", st.ImplementationsDeployment.DisputeGameFactoryImplAddress}, + } + for _, addr := range addrs { + t.Run(addr.name, func(t *testing.T) { + code, err := l1Client.CodeAt(ctx, addr.addr, nil) + require.NoError(t, err) + require.NotEmpty(t, code, "contracts %s at %s has no code", addr.name, addr.addr) + }) + } + + for _, chainState := range st.Chains { + chainAddrs := []struct { + name string + addr common.Address + }{ + {"ProxyAdminAddress", chainState.ProxyAdminAddress}, + {"AddressManagerAddress", chainState.AddressManagerAddress}, + {"L1ERC721BridgeProxyAddress", chainState.L1ERC721BridgeProxyAddress}, + {"SystemConfigProxyAddress", chainState.SystemConfigProxyAddress}, + {"OptimismMintableERC20FactoryProxyAddress", chainState.OptimismMintableERC20FactoryProxyAddress}, + {"L1StandardBridgeProxyAddress", chainState.L1StandardBridgeProxyAddress}, + {"L1CrossDomainMessengerProxyAddress", chainState.L1CrossDomainMessengerProxyAddress}, + {"OptimismPortalProxyAddress", chainState.OptimismPortalProxyAddress}, + {"DisputeGameFactoryProxyAddress", chainState.DisputeGameFactoryProxyAddress}, + {"DisputeGameFactoryImplAddress", chainState.DisputeGameFactoryImplAddress}, + {"AnchorStateRegistryProxyAddress", chainState.AnchorStateRegistryProxyAddress}, + {"AnchorStateRegistryImplAddress", chainState.AnchorStateRegistryImplAddress}, + {"FaultDisputeGameAddress", chainState.FaultDisputeGameAddress}, + {"PermissionedDisputeGameAddress", chainState.PermissionedDisputeGameAddress}, + {"DelayedWETHPermissionedGameProxyAddress", chainState.DelayedWETHPermissionedGameProxyAddress}, + {"DelayedWETHPermissionlessGameProxyAddress", chainState.DelayedWETHPermissionlessGameProxyAddress}, + } + for _, addr := range chainAddrs { + t.Run(fmt.Sprintf("chain %s - %s", chainState.ID, addr.name), func(t *testing.T) { + code, err := l1Client.CodeAt(ctx, addr.addr, nil) + require.NoError(t, err) + require.NotEmpty(t, code, "contracts %s at %s for chain %s has no code", addr.name, addr.addr, chainState.ID) + }) + } + } +} diff --git a/op-chain-ops/interopgen/deployers/implementations.go b/op-chain-ops/deployer/opsm/implementations.go similarity index 67% rename from op-chain-ops/interopgen/deployers/implementations.go rename to op-chain-ops/deployer/opsm/implementations.go index 29a1fb6dee99..d5b576fb9cb6 100644 --- a/op-chain-ops/interopgen/deployers/implementations.go +++ b/op-chain-ops/deployer/opsm/implementations.go @@ -1,4 +1,4 @@ -package deployers +package opsm import ( "fmt" @@ -19,8 +19,9 @@ type DeployImplementationsInput struct { Release string SuperchainConfigProxy common.Address ProtocolVersionsProxy common.Address - SuperchainProxyAdmin common.Address UseInterop bool // if true, deploy Interop implementations + + SuperchainProxyAdmin common.Address } func (input *DeployImplementationsInput) InputSet() bool { @@ -49,21 +50,24 @@ type DeployImplementationsScript struct { Run func(input, output common.Address) error } -func DeployImplementations(l1Host *script.Host, input *DeployImplementationsInput) (*DeployImplementationsOutput, error) { - output := &DeployImplementationsOutput{} - inputAddr := l1Host.NewScriptAddress() - outputAddr := l1Host.NewScriptAddress() +func DeployImplementations( + host *script.Host, + input DeployImplementationsInput, +) (DeployImplementationsOutput, error) { + var output DeployImplementationsOutput + inputAddr := host.NewScriptAddress() + outputAddr := host.NewScriptAddress() - cleanupInput, err := script.WithPrecompileAtAddress[*DeployImplementationsInput](l1Host, inputAddr, input) + cleanupInput, err := script.WithPrecompileAtAddress[*DeployImplementationsInput](host, inputAddr, &input) if err != nil { - return nil, fmt.Errorf("failed to insert DeployImplementationsInput precompile: %w", err) + return output, fmt.Errorf("failed to insert DeployImplementationsInput precompile: %w", err) } defer cleanupInput() - cleanupOutput, err := script.WithPrecompileAtAddress[*DeployImplementationsOutput](l1Host, outputAddr, output, + cleanupOutput, err := script.WithPrecompileAtAddress[*DeployImplementationsOutput](host, outputAddr, &output, script.WithFieldSetter[*DeployImplementationsOutput]) if err != nil { - return nil, fmt.Errorf("failed to insert DeployImplementationsOutput precompile: %w", err) + return output, fmt.Errorf("failed to insert DeployImplementationsOutput precompile: %w", err) } defer cleanupOutput() @@ -71,9 +75,9 @@ func DeployImplementations(l1Host *script.Host, input *DeployImplementationsInpu if input.UseInterop { implContract = "DeployImplementationsInterop" } - deployScript, cleanupDeploy, err := script.WithScript[DeployImplementationsScript](l1Host, "DeployImplementations.s.sol", implContract) + deployScript, cleanupDeploy, err := script.WithScript[DeployImplementationsScript](host, "DeployImplementations.s.sol", implContract) if err != nil { - return nil, fmt.Errorf("failed to load %s script: %w", implContract, err) + return output, fmt.Errorf("failed to load %s script: %w", implContract, err) } defer cleanupDeploy() @@ -81,8 +85,8 @@ func DeployImplementations(l1Host *script.Host, input *DeployImplementationsInpu if input.UseInterop { opsmContract = "OPStackManagerInterop" } - if err := l1Host.RememberOnLabel("OPStackManager", opsmContract+".sol", opsmContract); err != nil { - return nil, fmt.Errorf("failed to link OPStackManager label: %w", err) + if err := host.RememberOnLabel("OPStackManager", opsmContract+".sol", opsmContract); err != nil { + return output, fmt.Errorf("failed to link OPStackManager label: %w", err) } // So we can see in detail where the SystemConfig interop initializer fails @@ -90,12 +94,12 @@ func DeployImplementations(l1Host *script.Host, input *DeployImplementationsInpu if input.UseInterop { sysConfig = "SystemConfigInterop" } - if err := l1Host.RememberOnLabel("SystemConfigImpl", sysConfig+".sol", sysConfig); err != nil { - return nil, fmt.Errorf("failed to link SystemConfig label: %w", err) + if err := host.RememberOnLabel("SystemConfigImpl", sysConfig+".sol", sysConfig); err != nil { + return output, fmt.Errorf("failed to link SystemConfig label: %w", err) } if err := deployScript.Run(inputAddr, outputAddr); err != nil { - return nil, fmt.Errorf("failed to run %s script: %w", implContract, err) + return output, fmt.Errorf("failed to run %s script: %w", implContract, err) } return output, nil diff --git a/op-chain-ops/interopgen/deployers/opchain.go b/op-chain-ops/deployer/opsm/opchain.go similarity index 76% rename from op-chain-ops/interopgen/deployers/opchain.go rename to op-chain-ops/deployer/opsm/opchain.go index 184ccc946bf0..6bab3fdbfe75 100644 --- a/op-chain-ops/interopgen/deployers/opchain.go +++ b/op-chain-ops/deployer/opsm/opchain.go @@ -1,4 +1,4 @@ -package deployers +package opsm import ( "fmt" @@ -35,6 +35,7 @@ type DeployOPChainOutput struct { OptimismMintableERC20FactoryProxy common.Address L1StandardBridgeProxy common.Address L1CrossDomainMessengerProxy common.Address + // Fault proof contracts below. OptimismPortalProxy common.Address DisputeGameFactoryProxy common.Address @@ -55,33 +56,33 @@ type DeployOPChainScript struct { Run func(input, output common.Address) error } -func DeployOPChain(l1Host *script.Host, input *DeployOPChainInput) (*DeployOPChainOutput, error) { - output := &DeployOPChainOutput{} - inputAddr := l1Host.NewScriptAddress() - outputAddr := l1Host.NewScriptAddress() +func DeployOPChain(host *script.Host, input DeployOPChainInput) (DeployOPChainOutput, error) { + var dco DeployOPChainOutput + inputAddr := host.NewScriptAddress() + outputAddr := host.NewScriptAddress() - cleanupInput, err := script.WithPrecompileAtAddress[*DeployOPChainInput](l1Host, inputAddr, input) + cleanupInput, err := script.WithPrecompileAtAddress[*DeployOPChainInput](host, inputAddr, &input) if err != nil { - return nil, fmt.Errorf("failed to insert DeployOPChainInput precompile: %w", err) + return dco, fmt.Errorf("failed to insert DeployOPChainInput precompile: %w", err) } defer cleanupInput() - cleanupOutput, err := script.WithPrecompileAtAddress[*DeployOPChainOutput](l1Host, outputAddr, output, + cleanupOutput, err := script.WithPrecompileAtAddress[*DeployOPChainOutput](host, outputAddr, &dco, script.WithFieldSetter[*DeployOPChainOutput]) if err != nil { - return nil, fmt.Errorf("failed to insert DeployOPChainOutput precompile: %w", err) + return dco, fmt.Errorf("failed to insert DeployOPChainOutput precompile: %w", err) } defer cleanupOutput() - deployScript, cleanupDeploy, err := script.WithScript[DeployOPChainScript](l1Host, "DeployOPChain.s.sol", "DeployOPChain") + deployScript, cleanupDeploy, err := script.WithScript[DeployOPChainScript](host, "DeployOPChain.s.sol", "DeployOPChain") if err != nil { - return nil, fmt.Errorf("failed to load DeployOPChain script: %w", err) + return dco, fmt.Errorf("failed to load DeployOPChain script: %w", err) } defer cleanupDeploy() if err := deployScript.Run(inputAddr, outputAddr); err != nil { - return nil, fmt.Errorf("failed to run DeployOPChain script: %w", err) + return dco, fmt.Errorf("failed to run DeployOPChain script: %w", err) } - return output, nil + return dco, nil } diff --git a/op-chain-ops/deployer/opsm/superchain.go b/op-chain-ops/deployer/opsm/superchain.go index 99adb77817e1..0505d34760a9 100644 --- a/op-chain-ops/deployer/opsm/superchain.go +++ b/op-chain-ops/deployer/opsm/superchain.go @@ -1,11 +1,9 @@ package opsm import ( - "context" "fmt" "math/big" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/broadcaster" "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" "github.com/ethereum-optimism/optimism/op-chain-ops/script" opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" @@ -29,11 +27,11 @@ func (dsi *DeploySuperchainInput) InputSet() bool { } type DeploySuperchainOutput struct { - SuperchainProxyAdmin common.Address `toml:"superchainProxyAdmin"` - SuperchainConfigImpl common.Address `toml:"superchainConfigImpl"` - SuperchainConfigProxy common.Address `toml:"superchainConfigProxy"` - ProtocolVersionsImpl common.Address `toml:"protocolVersionsImpl"` - ProtocolVersionsProxy common.Address `toml:"protocolVersionsProxy"` + SuperchainProxyAdmin common.Address + SuperchainConfigImpl common.Address + SuperchainConfigProxy common.Address + ProtocolVersionsImpl common.Address + ProtocolVersionsProxy common.Address } func (output *DeploySuperchainOutput) CheckOutput() error { @@ -54,46 +52,13 @@ type DeploySuperchainOpts struct { Logger log.Logger } -func DeploySuperchainForge(ctx context.Context, opts DeploySuperchainOpts) (DeploySuperchainOutput, error) { +func DeploySuperchain(h *script.Host, input DeploySuperchainInput) (DeploySuperchainOutput, error) { var dso DeploySuperchainOutput - bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{ - Logger: opts.Logger, - ChainID: opts.ChainID, - Client: opts.Client, - Signer: opts.Signer, - From: opts.Deployer, - }) - if err != nil { - return dso, fmt.Errorf("failed to create broadcaster: %w", err) - } - - scriptCtx := script.DefaultContext - scriptCtx.Sender = opts.Deployer - scriptCtx.Origin = opts.Deployer - artifacts := &foundry.ArtifactsFS{FS: opts.ArtifactsFS} - h := script.NewHost( - opts.Logger, - artifacts, - nil, - scriptCtx, - script.WithBroadcastHook(bcaster.Hook), - script.WithIsolatedBroadcasts(), - ) - - if err := h.EnableCheats(); err != nil { - return dso, fmt.Errorf("failed to enable cheats: %w", err) - } - - nonce, err := opts.Client.NonceAt(ctx, opts.Deployer, nil) - if err != nil { - return dso, fmt.Errorf("failed to get deployer nonce: %w", err) - } - inputAddr := h.NewScriptAddress() outputAddr := h.NewScriptAddress() - cleanupInput, err := script.WithPrecompileAtAddress[*DeploySuperchainInput](h, inputAddr, &opts.Input) + cleanupInput, err := script.WithPrecompileAtAddress[*DeploySuperchainInput](h, inputAddr, &input) if err != nil { return dso, fmt.Errorf("failed to insert DeploySuperchainInput precompile: %w", err) } @@ -116,17 +81,9 @@ func DeploySuperchainForge(ctx context.Context, opts DeploySuperchainOpts) (Depl } defer cleanupDeploy() - h.SetNonce(opts.Deployer, nonce) - - opts.Logger.Info("deployer nonce", "nonce", nonce) - if err := deployScript.Run(inputAddr, outputAddr); err != nil { return dso, fmt.Errorf("failed to run DeploySuperchain script: %w", err) } - if _, err := bcaster.Broadcast(ctx); err != nil { - return dso, fmt.Errorf("failed to broadcast transactions: %w", err) - } - return dso, nil } diff --git a/op-chain-ops/deployer/pipeline/env.go b/op-chain-ops/deployer/pipeline/env.go index d77beacac596..9ee28e3843f6 100644 --- a/op-chain-ops/deployer/pipeline/env.go +++ b/op-chain-ops/deployer/pipeline/env.go @@ -16,7 +16,6 @@ import ( type Env struct { Workdir string L1Client *ethclient.Client - L1RPCUrl string Signer opcrypto.SignerFn Deployer common.Address Logger log.Logger diff --git a/op-chain-ops/deployer/pipeline/host.go b/op-chain-ops/deployer/pipeline/host.go new file mode 100644 index 000000000000..f62a2ee8bfae --- /dev/null +++ b/op-chain-ops/deployer/pipeline/host.go @@ -0,0 +1,70 @@ +package pipeline + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/broadcaster" + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-chain-ops/script" + opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" +) + +type CallScriptBroadcastOpts struct { + L1ChainID *big.Int + Logger log.Logger + ArtifactsFS foundry.StatDirFs + Deployer common.Address + Signer opcrypto.SignerFn + Client *ethclient.Client + Handler func(host *script.Host) error +} + +func CallScriptBroadcast( + ctx context.Context, + opts CallScriptBroadcastOpts, +) error { + bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{ + Logger: opts.Logger, + ChainID: opts.L1ChainID, + Client: opts.Client, + Signer: opts.Signer, + From: opts.Deployer, + }) + if err != nil { + return fmt.Errorf("failed to create broadcaster: %w", err) + } + + scriptCtx := script.DefaultContext + scriptCtx.Sender = opts.Deployer + scriptCtx.Origin = opts.Deployer + artifacts := &foundry.ArtifactsFS{FS: opts.ArtifactsFS} + h := script.NewHost( + opts.Logger, + artifacts, + nil, + scriptCtx, + script.WithBroadcastHook(bcaster.Hook), + script.WithIsolatedBroadcasts(), + script.WithCreate2Deployer(), + ) + + if err := h.EnableCheats(); err != nil { + return fmt.Errorf("failed to enable cheats: %w", err) + } + + err = opts.Handler(h) + if err != nil { + return fmt.Errorf("failed to run handler: %w", err) + } + + if _, err := bcaster.Broadcast(ctx); err != nil { + return fmt.Errorf("failed to broadcast: %w", err) + } + + return nil +} diff --git a/op-chain-ops/deployer/pipeline/implementations.go b/op-chain-ops/deployer/pipeline/implementations.go new file mode 100644 index 000000000000..e315dc6fbf1d --- /dev/null +++ b/op-chain-ops/deployer/pipeline/implementations.go @@ -0,0 +1,101 @@ +package pipeline + +import ( + "context" + "fmt" + "math/big" + "os" + + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opsm" + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-chain-ops/script" +) + +func DeployImplementations(ctx context.Context, env *Env, intent *state.Intent, st *state.State) error { + lgr := env.Logger.New("stage", "deploy-implementations") + + if !shouldDeployImplementations(intent, st) { + lgr.Info("implementations deployment not needed") + return nil + } + + lgr.Info("deploying implementations") + + var artifactsFS foundry.StatDirFs + var err error + if intent.ContractArtifactsURL.Scheme == "file" { + fs := os.DirFS(intent.ContractArtifactsURL.Path) + artifactsFS = fs.(foundry.StatDirFs) + } else { + return fmt.Errorf("only file:// artifacts URLs are supported") + } + + var dump *foundry.ForgeAllocs + var dio opsm.DeployImplementationsOutput + err = CallScriptBroadcast( + ctx, + CallScriptBroadcastOpts{ + L1ChainID: big.NewInt(int64(intent.L1ChainID)), + Logger: lgr, + ArtifactsFS: artifactsFS, + Deployer: env.Deployer, + Signer: env.Signer, + Client: env.L1Client, + Handler: func(host *script.Host) error { + host.SetEnvVar("IMPL_SALT", st.Create2Salt.Hex()[2:]) + host.ImportState(st.SuperchainDeployment.StateDump) + dio, err = opsm.DeployImplementations( + host, + opsm.DeployImplementationsInput{ + WithdrawalDelaySeconds: big.NewInt(604800), + MinProposalSizeBytes: big.NewInt(126000), + ChallengePeriodSeconds: big.NewInt(86400), + ProofMaturityDelaySeconds: big.NewInt(604800), + DisputeGameFinalityDelaySeconds: big.NewInt(302400), + Release: "op-contracts/v1.6.0", + SuperchainConfigProxy: st.SuperchainDeployment.SuperchainConfigProxyAddress, + ProtocolVersionsProxy: st.SuperchainDeployment.ProtocolVersionsProxyAddress, + SuperchainProxyAdmin: st.SuperchainDeployment.ProxyAdminAddress, + UseInterop: false, + }, + ) + if err != nil { + return fmt.Errorf("error deploying implementations: %w", err) + } + dump, err = host.StateDump() + if err != nil { + return fmt.Errorf("error dumping state: %w", err) + } + return nil + }, + }, + ) + if err != nil { + return fmt.Errorf("error deploying implementations: %w", err) + } + + st.ImplementationsDeployment = &state.ImplementationsDeployment{ + OpsmAddress: dio.Opsm, + DelayedWETHImplAddress: dio.DelayedWETHImpl, + OptimismPortalImplAddress: dio.OptimismPortalImpl, + PreimageOracleSingletonAddress: dio.PreimageOracleSingleton, + MipsSingletonAddress: dio.MipsSingleton, + SystemConfigImplAddress: dio.SystemConfigImpl, + L1CrossDomainMessengerImplAddress: dio.L1CrossDomainMessengerImpl, + L1ERC721BridgeImplAddress: dio.L1ERC721BridgeImpl, + L1StandardBridgeImplAddress: dio.L1StandardBridgeImpl, + OptimismMintableERC20FactoryImplAddress: dio.OptimismMintableERC20FactoryImpl, + DisputeGameFactoryImplAddress: dio.DisputeGameFactoryImpl, + StateDump: dump, + } + if err := env.WriteState(st); err != nil { + return err + } + + return nil +} + +func shouldDeployImplementations(intent *state.Intent, st *state.State) bool { + return st.ImplementationsDeployment == nil +} diff --git a/op-chain-ops/deployer/pipeline/init.go b/op-chain-ops/deployer/pipeline/init.go index 9f4227f856b0..55cbe9793d6a 100644 --- a/op-chain-ops/deployer/pipeline/init.go +++ b/op-chain-ops/deployer/pipeline/init.go @@ -2,8 +2,11 @@ package pipeline import ( "context" + "crypto/rand" "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" ) @@ -20,6 +23,13 @@ func Init(ctx context.Context, env *Env, intent *state.Intent, st *state.State) return fmt.Errorf("unsupported state version: %d", st.Version) } + if st.Create2Salt == (common.Hash{}) { + _, err := rand.Read(st.Create2Salt[:]) + if err != nil { + return fmt.Errorf("failed to generate CREATE2 salt: %w", err) + } + } + // If the state has never been applied, we don't need to perform // any additional checks. if st.AppliedIntent == nil { diff --git a/op-chain-ops/deployer/pipeline/opchain.go b/op-chain-ops/deployer/pipeline/opchain.go new file mode 100644 index 000000000000..6a27ded2d520 --- /dev/null +++ b/op-chain-ops/deployer/pipeline/opchain.go @@ -0,0 +1,110 @@ +package pipeline + +import ( + "context" + "fmt" + "math/big" + "os" + + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opsm" + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-chain-ops/script" + "github.com/ethereum/go-ethereum/common" +) + +func DeployOPChain(ctx context.Context, env *Env, intent *state.Intent, st *state.State, chainID common.Hash) error { + lgr := env.Logger.New("stage", "deploy-opchain") + + if !shouldDeployOPChain(intent, st, chainID) { + lgr.Info("opchain deployment not needed") + return nil + } + + lgr.Info("deploying OP chain", "id", chainID.Hex()) + + var artifactsFS foundry.StatDirFs + var err error + if intent.ContractArtifactsURL.Scheme == "file" { + fs := os.DirFS(intent.ContractArtifactsURL.Path) + artifactsFS = fs.(foundry.StatDirFs) + } else { + return fmt.Errorf("only file:// artifacts URLs are supported") + } + + thisIntent, err := intent.Chain(chainID) + if err != nil { + return fmt.Errorf("failed to get chain intent: %w", err) + } + + var dco opsm.DeployOPChainOutput + err = CallScriptBroadcast( + ctx, + CallScriptBroadcastOpts{ + L1ChainID: big.NewInt(int64(intent.L1ChainID)), + Logger: lgr, + ArtifactsFS: artifactsFS, + Deployer: env.Deployer, + Signer: env.Signer, + Client: env.L1Client, + Handler: func(host *script.Host) error { + host.ImportState(st.ImplementationsDeployment.StateDump) + dco, err = opsm.DeployOPChain( + host, + opsm.DeployOPChainInput{ + OpChainProxyAdminOwner: thisIntent.Roles.ProxyAdminOwner, + SystemConfigOwner: thisIntent.Roles.SystemConfigOwner, + Batcher: thisIntent.Roles.Batcher, + UnsafeBlockSigner: thisIntent.Roles.UnsafeBlockSigner, + Proposer: thisIntent.Roles.Proposer, + Challenger: thisIntent.Roles.Challenger, + BasefeeScalar: 1368, + BlobBaseFeeScalar: 801949, + L2ChainId: chainID.Big(), + Opsm: st.ImplementationsDeployment.OpsmAddress, + }, + ) + return err + }, + }, + ) + if err != nil { + return fmt.Errorf("error deploying OP chain: %w", err) + } + + st.Chains = append(st.Chains, state.ChainState{ + ID: chainID, + + ProxyAdminAddress: dco.OpChainProxyAdmin, + AddressManagerAddress: dco.AddressManager, + L1ERC721BridgeProxyAddress: dco.L1ERC721BridgeProxy, + SystemConfigProxyAddress: dco.SystemConfigProxy, + OptimismMintableERC20FactoryProxyAddress: dco.OptimismMintableERC20FactoryProxy, + L1StandardBridgeProxyAddress: dco.L1StandardBridgeProxy, + L1CrossDomainMessengerProxyAddress: dco.L1CrossDomainMessengerProxy, + OptimismPortalProxyAddress: dco.OptimismPortalProxy, + DisputeGameFactoryProxyAddress: dco.DisputeGameFactoryProxy, + DisputeGameFactoryImplAddress: dco.DisputeGameFactoryImpl, + AnchorStateRegistryProxyAddress: dco.AnchorStateRegistryProxy, + AnchorStateRegistryImplAddress: dco.AnchorStateRegistryImpl, + FaultDisputeGameAddress: dco.FaultDisputeGame, + PermissionedDisputeGameAddress: dco.PermissionedDisputeGame, + DelayedWETHPermissionedGameProxyAddress: dco.DelayedWETHPermissionedGameProxy, + DelayedWETHPermissionlessGameProxyAddress: dco.DelayedWETHPermissionlessGameProxy, + }) + if err := env.WriteState(st); err != nil { + return err + } + + return nil +} + +func shouldDeployOPChain(intent *state.Intent, st *state.State, chainID common.Hash) bool { + for _, chain := range st.Chains { + if chain.ID == chainID { + return false + } + } + + return true +} diff --git a/op-chain-ops/deployer/pipeline/superchain.go b/op-chain-ops/deployer/pipeline/superchain.go index 5b44b2ee01c3..3a91c867ff3a 100644 --- a/op-chain-ops/deployer/pipeline/superchain.go +++ b/op-chain-ops/deployer/pipeline/superchain.go @@ -6,14 +6,14 @@ import ( "math/big" "os" + "github.com/ethereum-optimism/optimism/op-chain-ops/script" + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opsm" "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" "github.com/ethereum-optimism/optimism/op-node/rollup" ) -const DefaultContractsBedrockRepo = "us-docker.pkg.dev/oplabs-tools-artifacts/images/contracts-bedrock" - func DeploySuperchain(ctx context.Context, env *Env, intent *state.Intent, st *state.State) error { lgr := env.Logger.New("stage", "deploy-superchain") @@ -33,23 +33,38 @@ func DeploySuperchain(ctx context.Context, env *Env, intent *state.Intent, st *s return fmt.Errorf("only file:// artifacts URLs are supported") } - dso, err := opsm.DeploySuperchainForge( + var dump *foundry.ForgeAllocs + var dso opsm.DeploySuperchainOutput + err = CallScriptBroadcast( ctx, - opsm.DeploySuperchainOpts{ - Input: opsm.DeploySuperchainInput{ - ProxyAdminOwner: intent.SuperchainRoles.ProxyAdminOwner, - ProtocolVersionsOwner: intent.SuperchainRoles.ProtocolVersionsOwner, - Guardian: intent.SuperchainRoles.Guardian, - Paused: false, - RequiredProtocolVersion: rollup.OPStackSupport, - RecommendedProtocolVersion: rollup.OPStackSupport, - }, + CallScriptBroadcastOpts{ + L1ChainID: big.NewInt(int64(intent.L1ChainID)), + Logger: lgr, ArtifactsFS: artifactsFS, - ChainID: big.NewInt(int64(intent.L1ChainID)), - Client: env.L1Client, - Signer: env.Signer, Deployer: env.Deployer, - Logger: lgr, + Signer: env.Signer, + Client: env.L1Client, + Handler: func(host *script.Host) error { + dso, err = opsm.DeploySuperchain( + host, + opsm.DeploySuperchainInput{ + ProxyAdminOwner: intent.SuperchainRoles.ProxyAdminOwner, + ProtocolVersionsOwner: intent.SuperchainRoles.ProtocolVersionsOwner, + Guardian: intent.SuperchainRoles.Guardian, + Paused: false, + RequiredProtocolVersion: rollup.OPStackSupport, + RecommendedProtocolVersion: rollup.OPStackSupport, + }, + ) + if err != nil { + return fmt.Errorf("failed to deploy superchain: %w", err) + } + dump, err = host.StateDump() + if err != nil { + return fmt.Errorf("error dumping state: %w", err) + } + return nil + }, }, ) if err != nil { @@ -62,8 +77,8 @@ func DeploySuperchain(ctx context.Context, env *Env, intent *state.Intent, st *s SuperchainConfigImplAddress: dso.SuperchainConfigImpl, ProtocolVersionsProxyAddress: dso.ProtocolVersionsProxy, ProtocolVersionsImplAddress: dso.ProtocolVersionsImpl, + StateDump: dump, } - if err := env.WriteState(st); err != nil { return err } @@ -72,13 +87,5 @@ func DeploySuperchain(ctx context.Context, env *Env, intent *state.Intent, st *s } func shouldDeploySuperchain(intent *state.Intent, st *state.State) bool { - if st.AppliedIntent == nil { - return true - } - - if st.SuperchainDeployment == nil { - return true - } - - return false + return st.SuperchainDeployment == nil } diff --git a/op-chain-ops/deployer/state/intent.go b/op-chain-ops/deployer/state/intent.go index 5184a3bb28ef..3c41169e1d45 100644 --- a/op-chain-ops/deployer/state/intent.go +++ b/op-chain-ops/deployer/state/intent.go @@ -24,7 +24,7 @@ type Intent struct { ContractArtifactsURL *ArtifactsURL `json:"contractArtifactsURL" toml:"contractArtifactsURL"` - Chains []Chain `json:"chains" toml:"chains"` + Chains []ChainIntent `json:"chains" toml:"chains"` } func (c Intent) L1ChainIDBig() *big.Int { @@ -62,14 +62,14 @@ func (c Intent) Check() error { return nil } -func (c Intent) Chain(id uint64) (Chain, error) { +func (c Intent) Chain(id common.Hash) (ChainIntent, error) { for i := range c.Chains { if c.Chains[i].ID == id { return c.Chains[i], nil } } - return Chain{}, fmt.Errorf("chain %d not found", id) + return ChainIntent{}, fmt.Errorf("chain %d not found", id) } func (c Intent) WriteToFile(path string) error { @@ -84,32 +84,33 @@ type SuperchainRoles struct { Guardian common.Address `json:"guardian" toml:"guardian"` } -type Chain struct { - ID uint64 `json:"id"` +type ChainIntent struct { + ID common.Hash `json:"id" toml:"id"` - Roles ChainRoles `json:"roles"` + Roles ChainRoles `json:"roles" toml:"roles"` - Overrides map[string]any `json:"overrides"` + Overrides map[string]any `json:"overrides" toml:"overrides"` } type ChainRoles struct { - ProxyAdminOwner common.Address `json:"proxyAdminOwner"` + ProxyAdminOwner common.Address `json:"proxyAdminOwner" toml:"proxyAdminOwner"` - SystemConfigOwner common.Address `json:"systemConfigOwner"` + SystemConfigOwner common.Address `json:"systemConfigOwner" toml:"systemConfigOwner"` - GovernanceTokenOwner common.Address `json:"governanceTokenOwner"` + GovernanceTokenOwner common.Address `json:"governanceTokenOwner" toml:"governanceTokenOwner"` - UnsafeBlockSigner common.Address `json:"unsafeBlockSigner"` + UnsafeBlockSigner common.Address `json:"unsafeBlockSigner" toml:"unsafeBlockSigner"` - Batcher common.Address `json:"batcher"` + Batcher common.Address `json:"batcher" toml:"batcher"` - Proposer common.Address `json:"proposer"` + Proposer common.Address `json:"proposer" toml:"proposer"` - Challenger common.Address `json:"challenger"` + Challenger common.Address `json:"challenger" toml:"challenger"` } -func (c *Chain) Check() error { - if c.ID == 0 { +func (c *ChainIntent) Check() error { + var emptyHash common.Hash + if c.ID == emptyHash { return fmt.Errorf("id must be set") } diff --git a/op-chain-ops/deployer/state/state.go b/op-chain-ops/deployer/state/state.go index 19addf79938d..315f25b64b76 100644 --- a/op-chain-ops/deployer/state/state.go +++ b/op-chain-ops/deployer/state/state.go @@ -1,6 +1,7 @@ package state import ( + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" "github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/jsonutil" "github.com/ethereum/go-ethereum/common" @@ -12,6 +13,9 @@ type State struct { // Version versions the state so we can update it later. Version int `json:"version"` + // Create2Salt is the salt used for CREATE2 deployments. + Create2Salt common.Hash `json:"create2Salt"` + // AppliedIntent contains the chain intent that was last // successfully applied. It is diffed against new intent // in order to determine what deployment steps to take. @@ -22,6 +26,13 @@ type State struct { // deployment. It only contains the proxies because the implementations // can be looked up on chain. SuperchainDeployment *SuperchainDeployment `json:"superchainDeployment"` + + // ImplementationsDeployment contains the addresses of the common implementation + // contracts required for the Superchain to function. + ImplementationsDeployment *ImplementationsDeployment `json:"implementationsDeployment"` + + // Chains contains data about L2 chain deployments. + Chains []ChainState `json:"opChainDeployments"` } func (s State) WriteToFile(path string) error { @@ -29,9 +40,46 @@ func (s State) WriteToFile(path string) error { } type SuperchainDeployment struct { - ProxyAdminAddress common.Address `json:"proxyAdminAddress"` - SuperchainConfigProxyAddress common.Address `json:"superchainConfigProxyAddress"` - SuperchainConfigImplAddress common.Address `json:"superchainConfigImplAddress"` - ProtocolVersionsProxyAddress common.Address `json:"protocolVersionsProxyAddress"` - ProtocolVersionsImplAddress common.Address `json:"protocolVersionsImplAddress"` + ProxyAdminAddress common.Address `json:"proxyAdminAddress"` + SuperchainConfigProxyAddress common.Address `json:"superchainConfigProxyAddress"` + SuperchainConfigImplAddress common.Address `json:"superchainConfigImplAddress"` + ProtocolVersionsProxyAddress common.Address `json:"protocolVersionsProxyAddress"` + ProtocolVersionsImplAddress common.Address `json:"protocolVersionsImplAddress"` + StateDump *foundry.ForgeAllocs `json:"stateDump"` +} + +type ImplementationsDeployment struct { + OpsmAddress common.Address `json:"opsmAddress"` + DelayedWETHImplAddress common.Address `json:"delayedWETHImplAddress"` + OptimismPortalImplAddress common.Address `json:"optimismPortalImplAddress"` + PreimageOracleSingletonAddress common.Address `json:"preimageOracleSingletonAddress"` + MipsSingletonAddress common.Address `json:"mipsSingletonAddress"` + SystemConfigImplAddress common.Address `json:"systemConfigImplAddress"` + L1CrossDomainMessengerImplAddress common.Address `json:"l1CrossDomainMessengerImplAddress"` + L1ERC721BridgeImplAddress common.Address `json:"l1ERC721BridgeImplAddress"` + L1StandardBridgeImplAddress common.Address `json:"l1StandardBridgeImplAddress"` + OptimismMintableERC20FactoryImplAddress common.Address `json:"optimismMintableERC20FactoryImplAddress"` + DisputeGameFactoryImplAddress common.Address `json:"disputeGameFactoryImplAddress"` + StateDump *foundry.ForgeAllocs `json:"stateDump"` +} + +type ChainState struct { + ID common.Hash `json:"id"` + + ProxyAdminAddress common.Address `json:"proxyAdminAddress"` + AddressManagerAddress common.Address `json:"addressManagerAddress"` + L1ERC721BridgeProxyAddress common.Address `json:"l1ERC721BridgeProxyAddress"` + SystemConfigProxyAddress common.Address `json:"systemConfigProxyAddress"` + OptimismMintableERC20FactoryProxyAddress common.Address `json:"optimismMintableERC20FactoryProxyAddress"` + L1StandardBridgeProxyAddress common.Address `json:"l1StandardBridgeProxyAddress"` + L1CrossDomainMessengerProxyAddress common.Address `json:"l1CrossDomainMessengerProxyAddress"` + OptimismPortalProxyAddress common.Address `json:"optimismPortalProxyAddress"` + DisputeGameFactoryProxyAddress common.Address `json:"disputeGameFactoryProxyAddress"` + DisputeGameFactoryImplAddress common.Address `json:"disputeGameFactoryImplAddress"` + AnchorStateRegistryProxyAddress common.Address `json:"anchorStateRegistryProxyAddress"` + AnchorStateRegistryImplAddress common.Address `json:"anchorStateRegistryImplAddress"` + FaultDisputeGameAddress common.Address `json:"faultDisputeGameAddress"` + PermissionedDisputeGameAddress common.Address `json:"permissionedDisputeGameAddress"` + DelayedWETHPermissionedGameProxyAddress common.Address `json:"delayedWETHPermissionedGameProxyAddress"` + DelayedWETHPermissionlessGameProxyAddress common.Address `json:"delayedWETHPermissionlessGameProxyAddress"` } diff --git a/op-chain-ops/interopgen/deploy.go b/op-chain-ops/interopgen/deploy.go index 89982a876337..65cb474d9f2b 100644 --- a/op-chain-ops/interopgen/deploy.go +++ b/op-chain-ops/interopgen/deploy.go @@ -5,6 +5,8 @@ import ( "fmt" "math/big" + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opsm" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -147,7 +149,7 @@ func prepareInitialL1(l1Host *script.Host, cfg *L1Config) (*L1Deployment, error) func deploySuperchainToL1(l1Host *script.Host, superCfg *SuperchainConfig) (*SuperchainDeployment, error) { l1Host.SetTxOrigin(superCfg.Deployer) - superDeployment, err := deployers.DeploySuperchain(l1Host, &deployers.DeploySuperchainInput{ + superDeployment, err := opsm.DeploySuperchain(l1Host, opsm.DeploySuperchainInput{ ProxyAdminOwner: superCfg.ProxyAdminOwner, ProtocolVersionsOwner: superCfg.ProtocolVersionsOwner, Guardian: superCfg.SuperchainConfigGuardian, @@ -159,7 +161,7 @@ func deploySuperchainToL1(l1Host *script.Host, superCfg *SuperchainConfig) (*Sup return nil, fmt.Errorf("failed to deploy Superchain contracts: %w", err) } - implementationsDeployment, err := deployers.DeployImplementations(l1Host, &deployers.DeployImplementationsInput{ + implementationsDeployment, err := opsm.DeployImplementations(l1Host, opsm.DeployImplementationsInput{ WithdrawalDelaySeconds: superCfg.Implementations.FaultProof.WithdrawalDelaySeconds, MinProposalSizeBytes: superCfg.Implementations.FaultProof.MinProposalSizeBytes, ChallengePeriodSeconds: superCfg.Implementations.FaultProof.ChallengePeriodSeconds, @@ -178,7 +180,7 @@ func deploySuperchainToL1(l1Host *script.Host, superCfg *SuperchainConfig) (*Sup // Collect deployment addresses // This could all be automatic once we have better output-contract typing/scripting return &SuperchainDeployment{ - Implementations: Implementations(*implementationsDeployment), + Implementations: Implementations(implementationsDeployment), ProxyAdmin: superDeployment.SuperchainProxyAdmin, ProtocolVersions: superDeployment.ProtocolVersionsImpl, ProtocolVersionsProxy: superDeployment.ProtocolVersionsProxy, @@ -194,7 +196,7 @@ func deployL2ToL1(l1Host *script.Host, superCfg *SuperchainConfig, superDeployme l1Host.SetTxOrigin(cfg.Deployer) - output, err := deployers.DeployOPChain(l1Host, &deployers.DeployOPChainInput{ + output, err := opsm.DeployOPChain(l1Host, opsm.DeployOPChainInput{ OpChainProxyAdminOwner: cfg.ProxyAdminOwner, SystemConfigOwner: cfg.SystemConfigOwner, Batcher: cfg.BatchSenderAddress, @@ -212,7 +214,7 @@ func deployL2ToL1(l1Host *script.Host, superCfg *SuperchainConfig, superDeployme // Collect deployment addresses return &L2Deployment{ - L2OpchainDeployment: L2OpchainDeployment(*output), + L2OpchainDeployment: L2OpchainDeployment(output), }, nil } diff --git a/op-chain-ops/interopgen/deployers/superchain.go b/op-chain-ops/interopgen/deployers/superchain.go deleted file mode 100644 index 60e9805d6711..000000000000 --- a/op-chain-ops/interopgen/deployers/superchain.go +++ /dev/null @@ -1,70 +0,0 @@ -package deployers - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/params" - - "github.com/ethereum-optimism/optimism/op-chain-ops/script" -) - -type DeploySuperchainInput struct { - ProxyAdminOwner common.Address // TODO(#11783): also used as interop-dependency-set owner - ProtocolVersionsOwner common.Address - Guardian common.Address - Paused bool - RequiredProtocolVersion params.ProtocolVersion - RecommendedProtocolVersion params.ProtocolVersion -} - -func (input *DeploySuperchainInput) InputSet() bool { - return true -} - -type DeploySuperchainOutput struct { - SuperchainProxyAdmin common.Address - SuperchainConfigImpl common.Address - SuperchainConfigProxy common.Address - ProtocolVersionsImpl common.Address - ProtocolVersionsProxy common.Address -} - -func (output *DeploySuperchainOutput) CheckOutput() error { - return nil -} - -type DeploySuperchainScript struct { - Run func(input, output common.Address) error -} - -func DeploySuperchain(l1Host *script.Host, input *DeploySuperchainInput) (*DeploySuperchainOutput, error) { - output := &DeploySuperchainOutput{} - inputAddr := l1Host.NewScriptAddress() - outputAddr := l1Host.NewScriptAddress() - - cleanupInput, err := script.WithPrecompileAtAddress[*DeploySuperchainInput](l1Host, inputAddr, input) - if err != nil { - return nil, fmt.Errorf("failed to insert DeploySuperchainInput precompile: %w", err) - } - defer cleanupInput() - - cleanupOutput, err := script.WithPrecompileAtAddress[*DeploySuperchainOutput](l1Host, outputAddr, output, - script.WithFieldSetter[*DeploySuperchainOutput]) - if err != nil { - return nil, fmt.Errorf("failed to insert DeploySuperchainOutput precompile: %w", err) - } - defer cleanupOutput() - - deployScript, cleanupDeploy, err := script.WithScript[DeploySuperchainScript](l1Host, "DeploySuperchain.s.sol", "DeploySuperchain") - if err != nil { - return nil, fmt.Errorf("failed to load DeploySuperchain script: %w", err) - } - defer cleanupDeploy() - - if err := deployScript.Run(inputAddr, outputAddr); err != nil { - return nil, fmt.Errorf("failed to run DeploySuperchain script: %w", err) - } - - return output, nil -} diff --git a/op-chain-ops/script/script.go b/op-chain-ops/script/script.go index 5bbc0942a52b..d156adb67187 100644 --- a/op-chain-ops/script/script.go +++ b/op-chain-ops/script/script.go @@ -386,6 +386,20 @@ func (h *Host) GetNonce(addr common.Address) uint64 { return h.state.GetNonce(addr) } +// ImportState imports a set of foundry.ForgeAllocs into the +// host's state database. It does not erase existing state +// when importing. +func (h *Host) ImportState(allocs *foundry.ForgeAllocs) { + for addr, alloc := range allocs.Accounts { + h.state.SetBalance(addr, uint256.MustFromBig(alloc.Balance), tracing.BalanceChangeUnspecified) + h.state.SetNonce(addr, alloc.Nonce) + h.state.SetCode(addr, alloc.Code) + for key, value := range alloc.Storage { + h.state.SetState(addr, key, value) + } + } +} + // getPrecompile overrides any accounts during runtime, to insert special precompiles, if activated. func (h *Host) getPrecompile(rules params.Rules, original vm.PrecompiledContract, addr common.Address) vm.PrecompiledContract { if p, ok := h.precompiles[addr]; ok { @@ -436,8 +450,8 @@ func (h *Host) onEnter(depth int, typ byte, from common.Address, to common.Addre return } - // Bump nonce value, such that a broadcast Call appears like a tx - if parentCallFrame.LastOp == vm.CALL { + // Bump nonce value, such that a broadcast Call or CREATE2 appears like a tx + if parentCallFrame.LastOp == vm.CALL || parentCallFrame.LastOp == vm.CREATE2 { sender := parentCallFrame.Ctx.Address() if parentCallFrame.Prank.Sender != nil { sender = *parentCallFrame.Prank.Sender diff --git a/op-chain-ops/script/script_test.go b/op-chain-ops/script/script_test.go index 53415d1f0583..9d029c801060 100644 --- a/op-chain-ops/script/script_test.go +++ b/op-chain-ops/script/script_test.go @@ -164,7 +164,7 @@ func TestScriptBroadcast(t *testing.T) { require.EqualValues(t, 0, h.GetNonce(senderAddr)) require.EqualValues(t, 3, h.GetNonce(scriptAddr)) require.EqualValues(t, 2, h.GetNonce(coffeeAddr)) - // This is zero because the deterministic deployer is the - // address that actually deploys the contract using CREATE2. - require.EqualValues(t, 0, h.GetNonce(cafeAddr)) + // This is one because we still need to bump the nonce of the + // address that will perform the send to the Create2Deployer. + require.EqualValues(t, 1, h.GetNonce(cafeAddr)) } diff --git a/op-service/testutils/kurtosisutil/kurtosis.go b/op-service/testutils/kurtosisutil/kurtosis.go new file mode 100644 index 000000000000..8be6c9b3f2c7 --- /dev/null +++ b/op-service/testutils/kurtosisutil/kurtosis.go @@ -0,0 +1,12 @@ +package kurtosisutil + +import ( + "os" + "testing" +) + +func Test(t *testing.T) { + if os.Getenv("ENABLE_KURTOSIS") == "" { + t.Skip("skipping Kurtosis test") + } +} diff --git a/op-service/testutils/kurtosisutil/runner.go b/op-service/testutils/kurtosisutil/runner.go new file mode 100644 index 000000000000..6440cedef681 --- /dev/null +++ b/op-service/testutils/kurtosisutil/runner.go @@ -0,0 +1,64 @@ +package kurtosisutil + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/enclaves" + "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/starlark_run_config" + "github.com/kurtosis-tech/kurtosis/api/golang/engine/lib/kurtosis_context" + "github.com/stretchr/testify/require" +) + +func StartEnclave(t *testing.T, ctx context.Context, lgr log.Logger, pkg string, params string) *enclaves.EnclaveContext { + kurtosisCtx, err := kurtosis_context.NewKurtosisContextFromLocalEngine() + require.NoError(t, err) + + enclaveID := fmt.Sprintf("kurtosis-%s", t.Name()) + enclaveCtx, err := kurtosisCtx.CreateEnclave(ctx, enclaveID) + require.NoError(t, err) + + t.Cleanup(func() { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + err = kurtosisCtx.DestroyEnclave(ctx, enclaveID) + if err != nil { + lgr.Error("Error destroying enclave", "err", err) + } + }) + + stream, _, err := enclaveCtx.RunStarlarkRemotePackage( + ctx, + pkg, + &starlark_run_config.StarlarkRunConfig{ + SerializedParams: params, + }, + ) + require.NoError(t, err) + + logKurtosisOutput := func(msg string) { + lgr.Info(fmt.Sprintf("[KURTOSIS] %s", msg)) + } + + for responseLine := range stream { + if responseLine.GetProgressInfo() != nil { + stepInfo := responseLine.GetProgressInfo().CurrentStepInfo + logKurtosisOutput(stepInfo[len(stepInfo)-1]) + } else if responseLine.GetInstruction() != nil { + logKurtosisOutput(responseLine.GetInstruction().Description) + } else if responseLine.GetError() != nil { + if responseLine.GetError().GetInterpretationError() != nil { + t.Fatalf("interpretation error: %s", responseLine.GetError().GetInterpretationError().String()) + } else if responseLine.GetError().GetValidationError() != nil { + t.Fatalf("validation error: %s", responseLine.GetError().GetValidationError().String()) + } else if responseLine.GetError().GetExecutionError() != nil { + t.Fatalf("execution error: %s", responseLine.GetError().GetExecutionError().String()) + } + } + } + + return enclaveCtx +} diff --git a/packages/contracts-bedrock/scripts/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/DeployImplementations.s.sol index f68db7511f5a..5cb6fe9d0c6d 100644 --- a/packages/contracts-bedrock/scripts/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/DeployImplementations.s.sol @@ -33,6 +33,7 @@ import { OptimismPortalInterop } from "src/L1/OptimismPortalInterop.sol"; import { SystemConfigInterop } from "src/L1/SystemConfigInterop.sol"; import { Blueprint } from "src/libraries/Blueprint.sol"; +import { Config } from "scripts/libraries/Config.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { Solarray } from "scripts/libraries/Solarray.sol"; @@ -338,7 +339,7 @@ contract DeployImplementations is Script { // First we deploy the blueprints for the singletons deployed by OPSM. // forgefmt: disable-start - bytes32 salt = bytes32(0); + bytes32 salt = keccak256(bytes(Config.implSalt())); OPStackManager.Blueprints memory blueprints; vm.startBroadcast(msg.sender); diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 8e5def6450d1..fc7c5aa8be86 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -140,12 +140,12 @@ "sourceCodeHash": "0x3f7bd622a788b8d00fe1631b14b761488eedccf56879f7ea2d610dd5ce81efbe" }, "src/cannon/MIPS.sol": { - "initCodeHash": "0x6add59adb849ec02e13b33df7efd439ca80f6a8ceefdf69ebcb0963c0167da23", - "sourceCodeHash": "0xee1aef5a502f9491b7b83dab46ea2f0fc286f87ace31edcc1367c840d462bdfe" + "initCodeHash": "0x4043f262804931bbbbecff64f87f2d0bdc4554b4d0a8b22df8fff940e8d239bf", + "sourceCodeHash": "0xba4674e1846afbbc708877332a38dfabd4b8d1e48ce07d8ebf0a45c9f27f16b0" }, "src/cannon/MIPS2.sol": { - "initCodeHash": "0xeab5f44d7fa7af1072f500c754bb55aa92409a79b2765a66efed47461c0c4049", - "sourceCodeHash": "0xcf180ff2cab8d0a34f3bb19d7145f5bf7f67dd379722ece765d09b38dcfed2f6" + "initCodeHash": "0xdaed5d70cc84a53f224c28f24f8eef26d5d53dfba9fdc4f1b28c3b231b974e53", + "sourceCodeHash": "0x4026eb7ae7b303ec4c3c2880e14e260dbcfc0b4290459bcd22994cfed8655f80" }, "src/cannon/PreimageOracle.sol": { "initCodeHash": "0x801e52f9c8439fcf7089575fa93272dfb874641dbfc7d82f36d979c987271c0b", diff --git a/packages/contracts-bedrock/snapshots/abi/MIPS.json b/packages/contracts-bedrock/snapshots/abi/MIPS.json index 7741a95c6260..afdc8c355ddc 100644 --- a/packages/contracts-bedrock/snapshots/abi/MIPS.json +++ b/packages/contracts-bedrock/snapshots/abi/MIPS.json @@ -69,5 +69,10 @@ "inputs": [], "name": "InvalidMemoryProof", "type": "error" + }, + { + "inputs": [], + "name": "InvalidRMWInstruction", + "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/MIPS2.json b/packages/contracts-bedrock/snapshots/abi/MIPS2.json index eacce2991e21..2294e23e754e 100644 --- a/packages/contracts-bedrock/snapshots/abi/MIPS2.json +++ b/packages/contracts-bedrock/snapshots/abi/MIPS2.json @@ -75,6 +75,11 @@ "name": "InvalidMemoryProof", "type": "error" }, + { + "inputs": [], + "name": "InvalidRMWInstruction", + "type": "error" + }, { "inputs": [], "name": "InvalidSecondMemoryProof", diff --git a/packages/contracts-bedrock/src/cannon/MIPS.sol b/packages/contracts-bedrock/src/cannon/MIPS.sol index 8c2f1465756c..f1d216c8e6de 100644 --- a/packages/contracts-bedrock/src/cannon/MIPS.sol +++ b/packages/contracts-bedrock/src/cannon/MIPS.sol @@ -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 { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; +import { InvalidRMWInstruction } from "src/cannon/libraries/CannonErrors.sol"; /// @title MIPS /// @notice The MIPS contract emulates a single MIPS instruction. @@ -44,8 +45,8 @@ contract MIPS is ISemver { } /// @notice The semantic version of the MIPS contract. - /// @custom:semver 1.1.1-beta.3 - string public constant version = "1.1.1-beta.3"; + /// @custom:semver 1.1.1-beta.4 + string public constant version = "1.1.1-beta.4"; /// @notice The preimage oracle contract. IPreimageOracle internal immutable ORACLE; @@ -178,7 +179,7 @@ contract MIPS is ISemver { proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1), 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) { (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({ _a0: a0, @@ -291,23 +292,59 @@ contract MIPS is ISemver { 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 st.CpuScalars memory cpu = getCpuScalars(state); - (state.memRoot) = ins.execMipsCoreStepLogic({ - _cpu: cpu, - _registers: state.registers, - _memRoot: state.memRoot, - _memProofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1), - _insn: insn, - _opcode: opcode, - _fun: fun + ins.CoreStepLogicParams memory coreStepArgs = ins.CoreStepLogicParams({ + cpu: cpu, + registers: state.registers, + memRoot: state.memRoot, + memProofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1), + insn: insn, + opcode: opcode, + fun: fun }); + (state.memRoot,,) = ins.execMipsCoreStepLogic(coreStepArgs); setStateCpuScalars(state, cpu); 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) { return st.CpuScalars({ pc: _state.pc, nextPC: _state.nextPC, lo: _state.lo, hi: _state.hi }); } diff --git a/packages/contracts-bedrock/src/cannon/MIPS2.sol b/packages/contracts-bedrock/src/cannon/MIPS2.sol index 08c7856f7aec..b9da8fa8cc37 100644 --- a/packages/contracts-bedrock/src/cannon/MIPS2.sol +++ b/packages/contracts-bedrock/src/cannon/MIPS2.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 { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.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 /// @notice The MIPS2 contract emulates a single MIPS instruction. @@ -33,13 +35,16 @@ contract MIPS2 is ISemver { } /// @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. struct State { bytes32 memRoot; bytes32 preimageKey; uint32 preimageOffset; uint32 heap; + bool llReservationActive; + uint32 llAddress; + uint32 llOwnerThread; uint8 exitCode; bool exited; uint64 step; @@ -52,8 +57,8 @@ contract MIPS2 is ISemver { } /// @notice The semantic version of the MIPS2 contract. - /// @custom:semver 1.0.0-beta.8 - string public constant version = "1.0.0-beta.8"; + /// @custom:semver 1.0.0-beta.9 + string public constant version = "1.0.0-beta.9"; /// @notice The preimage oracle contract. IPreimageOracle internal immutable ORACLE; @@ -71,7 +76,7 @@ contract MIPS2 is ISemver { uint256 internal constant STATE_MEM_OFFSET = 0x80; // 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. constructor(IPreimageOracle _oracle) { @@ -108,8 +113,8 @@ contract MIPS2 is ISemver { // expected thread mem offset check revert(0, 0) } - if iszero(eq(mload(0x40), shl(5, 60))) { - // 4 + 13 state slots + 43 thread slots = 60 expected memory check + if iszero(eq(mload(0x40), shl(5, 63))) { + // 4 + 16 state slots + 43 thread slots = 63 expected memory check revert(0, 0) } if iszero(eq(_stateData.offset, 132)) { @@ -136,6 +141,9 @@ contract MIPS2 is ISemver { c, m := putField(c, m, 32) // preimageKey c, m := putField(c, m, 4) // preimageOffset 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) // exited exited := mload(sub(m, 32)) @@ -228,19 +236,95 @@ contract MIPS2 is ISemver { 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 st.CpuScalars memory cpu = getCpuScalars(thread); - (state.memRoot) = ins.execMipsCoreStepLogic({ - _cpu: cpu, - _registers: thread.registers, - _memRoot: state.memRoot, - _memProofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), - _insn: insn, - _opcode: opcode, - _fun: fun + ins.CoreStepLogicParams memory coreStepArgs = ins.CoreStepLogicParams({ + cpu: cpu, + registers: thread.registers, + memRoot: state.memRoot, + memProofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), + insn: insn, + opcode: opcode, + fun: fun }); + bool memUpdated; + uint32 memAddr; + (state.memRoot, memUpdated, memAddr) = ins.execMipsCoreStepLogic(coreStepArgs); setStateCpuScalars(thread, cpu); 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(); } } @@ -319,7 +403,8 @@ contract MIPS2 is ISemver { proofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), 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) { (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({ _a0: a0, @@ -412,6 +497,7 @@ contract MIPS2 is ISemver { // Recompute the new root after updating effAddr state.memRoot = MIPSMemory.writeMem(effAddr, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), secs); + handleMemoryUpdate(state, effAddr); // Verify the second memory proof against the newly computed root if ( !MIPSMemory.isValidProof( @@ -422,6 +508,7 @@ contract MIPS2 is ISemver { } state.memRoot = MIPSMemory.writeMem(effAddr + 4, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 2), nsecs); + handleMemoryUpdate(state, effAddr + 4); } else { v0 = sys.SYS_ERROR_SIGNAL; v1 = sys.EINVAL; @@ -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. /// @return out_ The hashed MIPS state. function outputState() internal returns (bytes32 out_) { @@ -526,6 +629,9 @@ contract MIPS2 is ISemver { from, to := copyMem(from, to, 32) // preimageKey from, to := copyMem(from, to, 4) // preimageOffset 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) from, to := copyMem(from, to, 1) // exitCode exited := mload(from) diff --git a/packages/contracts-bedrock/src/cannon/interfaces/IMIPS.sol b/packages/contracts-bedrock/src/cannon/interfaces/IMIPS.sol index d96432c68096..8e42ccf6dfa0 100644 --- a/packages/contracts-bedrock/src/cannon/interfaces/IMIPS.sol +++ b/packages/contracts-bedrock/src/cannon/interfaces/IMIPS.sol @@ -8,6 +8,7 @@ import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; /// @notice Interface for the MIPS contract. interface IMIPS is ISemver { error InvalidMemoryProof(); + error InvalidRMWInstruction(); function oracle() external view returns (IPreimageOracle oracle_); function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32); diff --git a/packages/contracts-bedrock/src/cannon/interfaces/IMIPS2.sol b/packages/contracts-bedrock/src/cannon/interfaces/IMIPS2.sol index e60b1d876b60..39653f031e3f 100644 --- a/packages/contracts-bedrock/src/cannon/interfaces/IMIPS2.sol +++ b/packages/contracts-bedrock/src/cannon/interfaces/IMIPS2.sol @@ -10,6 +10,7 @@ interface IMIPS2 is ISemver { error InvalidExitedValue(); error InvalidMemoryProof(); error InvalidSecondMemoryProof(); + error InvalidRMWInstruction(); function oracle() external view returns (IPreimageOracle oracle_); function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32); diff --git a/packages/contracts-bedrock/src/cannon/libraries/CannonErrors.sol b/packages/contracts-bedrock/src/cannon/libraries/CannonErrors.sol index d8ecdfe2af9a..3649852cec6f 100644 --- a/packages/contracts-bedrock/src/cannon/libraries/CannonErrors.sol +++ b/packages/contracts-bedrock/src/cannon/libraries/CannonErrors.sol @@ -60,3 +60,6 @@ error InvalidMemoryProof(); /// @notice Thrown when the second memory location is invalid error InvalidSecondMemoryProof(); + +/// @notice Thrown when an RMW instruction is expected, but a different instruction is provided. +error InvalidRMWInstruction(); diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol index 0652bea0d0e1..a2402531c82d 100644 --- a/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol @@ -5,6 +5,26 @@ import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; 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 _memRoot The current memory root. /// @param _insnProofOffset The calldata offset of the memory proof for the current instruction. @@ -30,91 +50,80 @@ library MIPSInstructions { } /// @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. - function execMipsCoreStepLogic( - st.CpuScalars memory _cpu, - uint32[32] memory _registers, - bytes32 _memRoot, - uint256 _memProofOffset, - uint32 _insn, - uint32 _opcode, - uint32 _fun - ) + /// @return memUpdated_ True if memory was modified. + /// @return memAddr_ Holds the memory address that was updated if memUpdated_ is true. + function execMipsCoreStepLogic(CoreStepLogicParams memory _args) internal pure - returns (bytes32 newMemRoot_) + returns (bytes32 newMemRoot_, bool memUpdated_, uint32 memAddr_) { unchecked { - newMemRoot_ = _memRoot; + newMemRoot_ = _args.memRoot; + memUpdated_ = false; + memAddr_ = 0; // 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 - uint32 target = (_cpu.nextPC & 0xF0000000) | (_insn & 0x03FFFFFF) << 2; - handleJump(_cpu, _registers, _opcode == 2 ? 0 : 31, target); - return newMemRoot_; + uint32 target = (_args.cpu.nextPC & 0xF0000000) | (_args.insn & 0x03FFFFFF) << 2; + handleJump(_args.cpu, _args.registers, _args.opcode == 2 ? 0 : 31, target); + return (newMemRoot_, memUpdated_, memAddr_); } // register fetch uint32 rs = 0; // source register 1 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) - rs = _registers[(_insn >> 21) & 0x1F]; + rs = _args.registers[(_args.insn >> 21) & 0x1F]; uint32 rdReg = rtReg; - if (_opcode == 0 || _opcode == 0x1c) { + if (_args.opcode == 0 || _args.opcode == 0x1c) { // R-type (stores rd) - rt = _registers[rtReg]; - rdReg = (_insn >> 11) & 0x1F; - } else if (_opcode < 0x20) { + rt = _args.registers[rtReg]; + rdReg = (_args.insn >> 11) & 0x1F; + } else if (_args.opcode < 0x20) { // rt is SignExtImm // 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 - rt = _insn & 0xFFFF; + rt = _args.insn & 0xFFFF; } else { // 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 - rt = _registers[rtReg]; + rt = _args.registers[rtReg]; // store actual rt with lwl and lwr rdReg = rtReg; } - if ((_opcode >= 4 && _opcode < 8) || _opcode == 1) { + if ((_args.opcode >= 4 && _args.opcode < 8) || _args.opcode == 1) { handleBranch({ - _cpu: _cpu, - _registers: _registers, - _opcode: _opcode, - _insn: _insn, + _cpu: _args.cpu, + _registers: _args.registers, + _opcode: _args.opcode, + _insn: _args.insn, _rtReg: rtReg, _rs: rs }); - return newMemRoot_; + return (newMemRoot_, memUpdated_, memAddr_); } uint32 storeAddr = 0xFF_FF_FF_FF; // memory fetch (all I-type) // we do the load for stores also uint32 mem = 0; - if (_opcode >= 0x20) { + if (_args.opcode >= 0x20) { // M[R[rs]+SignExtImm] - rs += signExtend(_insn & 0xFFFF, 16); + rs += signExtend(_args.insn & 0xFFFF, 16); uint32 addr = rs & 0xFFFFFFFC; - mem = MIPSMemory.readMem(_memRoot, addr, _memProofOffset); - if (_opcode >= 0x28 && _opcode != 0x30) { + mem = MIPSMemory.readMem(_args.memRoot, addr, _args.memProofOffset); + if (_args.opcode >= 0x28) { // store storeAddr = addr; // store opcodes don't write back to a register @@ -124,49 +133,58 @@ library MIPSInstructions { // ALU // 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 (_fun == 8 || _fun == 9) { + if (_args.opcode == 0 && _args.fun >= 8 && _args.fun < 0x1c) { + if (_args.fun == 8 || _args.fun == 9) { // jr/jalr - handleJump(_cpu, _registers, _fun == 8 ? 0 : rdReg, rs); - return newMemRoot_; + handleJump(_args.cpu, _args.registers, _args.fun == 8 ? 0 : rdReg, rs); + return (newMemRoot_, memUpdated_, memAddr_); } - if (_fun == 0xa) { + if (_args.fun == 0xa) { // movz - handleRd(_cpu, _registers, rdReg, rs, rt == 0); - return newMemRoot_; + handleRd(_args.cpu, _args.registers, rdReg, rs, rt == 0); + return (newMemRoot_, memUpdated_, memAddr_); } - if (_fun == 0xb) { + if (_args.fun == 0xb) { // movn - handleRd(_cpu, _registers, rdReg, rs, rt != 0); - return newMemRoot_; + handleRd(_args.cpu, _args.registers, rdReg, rs, rt != 0); + return (newMemRoot_, memUpdated_, memAddr_); } // lo and hi registers // can write back - if (_fun >= 0x10 && _fun < 0x1c) { - handleHiLo({ _cpu: _cpu, _registers: _registers, _fun: _fun, _rs: rs, _rt: rt, _storeReg: rdReg }); - - return newMemRoot_; + if (_args.fun >= 0x10 && _args.fun < 0x1c) { + handleHiLo({ + _cpu: _args.cpu, + _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 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 - 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 { uint32 val = _rt << (24 - (_rs & 3) * 8); uint32 mask = uint32(0xFFFFFFFF) << (24 - (_rs & 3) * 8); return (_mem & ~mask) | val; - } - // ll - else if (_opcode == 0x30) { - return _mem; - } - // sc - else if (_opcode == 0x38) { - return _rt; } else { revert("invalid instruction"); } diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol index cf2d34a56a66..a835b6feef58 100644 --- a/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol @@ -202,13 +202,22 @@ library MIPSSyscalls { function handleSysRead(SysReadParams memory _args) internal view - returns (uint32 v0_, uint32 v1_, uint32 newPreimageOffset_, bytes32 newMemRoot_) + returns ( + uint32 v0_, + uint32 v1_, + uint32 newPreimageOffset_, + bytes32 newMemRoot_, + bool memUpdated_, + uint32 memAddr_ + ) { unchecked { v0_ = uint32(0); v1_ = uint32(0); newMemRoot_ = _args.memRoot; newPreimageOffset_ = _args.preimageOffset; + memUpdated_ = false; + memAddr_ = 0; // args: _a0 = fd, _a1 = addr, _a2 = count // returns: v0_ = read, v1_ = err code @@ -217,9 +226,10 @@ library MIPSSyscalls { } // pre-image oracle read else if (_args.a0 == FD_PREIMAGE_READ) { + uint32 effAddr = _args.a1 & 0xFFffFFfc; // verify proof is correct, and get the existing memory. // 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 (uint8(_args.preimageKey[0]) == 1) { _args.preimageKey = PreimageKeyLib.localize(_args.preimageKey, _args.localContext); @@ -246,7 +256,9 @@ library MIPSSyscalls { } // 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); v0_ = uint32(datLen); } @@ -260,7 +272,7 @@ library MIPSSyscalls { v1_ = EBADF; } - return (v0_, v1_, newPreimageOffset_, newMemRoot_); + return (v0_, v1_, newPreimageOffset_, newMemRoot_, memUpdated_, memAddr_); } } diff --git a/packages/contracts-bedrock/test/cannon/MIPS2.t.sol b/packages/contracts-bedrock/test/cannon/MIPS2.t.sol index 84ae9fb6b4f1..969486265ff6 100644 --- a/packages/contracts-bedrock/test/cannon/MIPS2.t.sol +++ b/packages/contracts-bedrock/test/cannon/MIPS2.t.sol @@ -141,9 +141,9 @@ contract MIPS2_Test is CommonTest { /// https://github.com/ethereum-optimism/optimism/blob/1f64dd6db5561f3bb76ed1d1ffdaff0cde9b7c4b/cannon/mipsevm/evm_test.go#L80-L80 function test_mips2_step_debug_succeeds() external { bytes memory input = - hex"e14ced3200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a3df82bcbdf27955e04d467b84d94d0b4662c88a70264d7ea31325bc8d826681ef000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000affffffff00cbf05eda4a03d05cc6a14cff1cf2f955bfb253097c296ea96032da307da4f353ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c6000000000000ffffffff000000000000000000000000000000280000002c00000000000000000000000000000000000000010000000000000000000000000000000000000000fffffffd00000003000000000000000000000000000000000000000000000000bffffff00000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7ef00d0ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5ae020008ae11000403e0000800000000000000000000000000000000000000003c10bfff3610fff0341100013c08ffff3508fffd34090003010950212d420001ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d4e545be579dc7118fc02cd7b19b704e4710a81bce0cb48bb7e289e403e7c969a00000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d6a3e23902bafb21ac312e717f7942f8fd8ae795f67c918083442c2ab253cc66e0000000000000000000000000000000000000000000000000000"; + hex"e14ced3200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000acab5a39c6f974b22302e96dcdef1815483eaf580639bb1ee7ac98267afac2bf1ac041d3ff12045b73c86e4ff95ff662a5eee82abdf44a2d0b75fb180daf48a79e3143a81fa7c3d90b000000000000000000000078fc2ffac2fd940100000000000080c8ffffffff006504aeffb6e08baf3f85da5476a9160fa8f9f188a722fdd29268b0cbaf596736ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c6000000000000ffffffff000000000000000000000000f1f85ff4f1f85ff8506d442dbb3938f83eb60825a7ecbff2000010185e1a31f600050f0000000064a7c3d90be5acea102ad7bda149e0bfd0e7111c77d98b335645e665389becadf140ef999cc64fbd7f04799e85c97dadc5cca510bd5b3d97166d1aec28829f3dd43d8cf1f9358e4103b16d09d466e2c7c048ea3ba1aef3141e700270581aa0b75b50e34fc926bb2d83ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618db8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; (bool success, bytes memory retVal) = address(mips).call(input); - bytes memory expectedRetVal = hex"03fc952a0bd8aabc407669b857af995eab91ce55c404d8b32eaf8b941a48188c"; + bytes memory expectedRetVal = hex"0335fe4205f8443eefa7ac4541197874224df35e8536158c2fc2d5c8c2d2adb4"; assertTrue(success); assertEq(retVal.length, 32, "Expect a bytes32 hash of the post-state to be returned"); @@ -177,6 +177,9 @@ contract MIPS2_Test is CommonTest { preimageKey: bytes32(0), preimageOffset: 0, heap: 0, + llReservationActive: false, + llAddress: 0, + llOwnerThread: 0, exitCode: 0, exited: false, step: 1, @@ -211,7 +214,7 @@ contract MIPS2_Test is CommonTest { assembly { // Manipulate state data // 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. @@ -1992,36 +1995,50 @@ contract MIPS2_Test is CommonTest { } function test_ll_succeeds() public { - uint32 t1 = 0x100; - uint32 val = 0x12_23_45_67; - uint32 insn = encodeitype(0x30, 0x9, 0x8, 0x4); // ll $t0, 4($t1) + uint32 base = 0x100; + uint32 memVal = 0x12_23_45_67; + 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) = - constructMIPSState(0, insn, t1 + 4, val); - thread.registers[8] = 0; // t0 - thread.registers[9] = t1; + constructMIPSState(0, insn, effAddr, memVal); + thread.registers[8] = 0; // rtReg + thread.registers[9] = base; bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - thread.registers[8] = val; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); + MIPS2.State memory expect = arithmeticPostState(state, thread, 8, memVal); + expect.llReservationActive = true; + expect.llAddress = effAddr; + expect.llOwnerThread = thread.threadID; bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); } function test_sc_succeeds() public { - uint32 t1 = 0x100; - uint32 insn = encodeitype(0x38, 0x9, 0x8, 0x4); // sc $t0, 4($t1) + uint32 base = 0x100; + 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) = - constructMIPSState(0, insn, t1 + 4, 0); - thread.registers[8] = 0xaa_bb_cc_dd; // t0 - thread.registers[9] = t1; + constructMIPSState(0, insn, effAddr, 0); + state.llReservationActive = true; + 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); updateThreadStacks(state, thread); - thread.registers[8] = 0x1; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); - (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0xaa_bb_cc_dd); + MIPS2.State memory expect = arithmeticPostState(state, thread, 8, 0x1); + (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, effAddr, writeMemVal); + expect.llReservationActive = false; + expect.llAddress = 0; + expect.llOwnerThread = 0; bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2528,23 +2545,33 @@ contract MIPS2_Test is CommonTest { } 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( _state.memRoot, _state.preimageKey, _state.preimageOffset, _state.heap, + _state.llReservationActive, + _state.llAddress, + _state.llOwnerThread, _state.exitCode, _state.exited, _state.step, _state.stepsSinceLastContextSwitch, _state.wakeup, _state.traverseRight, - _state.leftThreadStack, - _state.rightThreadStack, - _state.nextThreadID + _state.leftThreadStack ); } + 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_) { bytes memory data = abi.encode(_state); return abi.decode(data, (MIPS2.State));