Commit a81de910 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

Merge pull request #11503 from ethereum-optimism/inphi/audit-fixes

Fault Proof Fixes
parents 2a995793 3a5e07f1
...@@ -153,7 +153,8 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 { ...@@ -153,7 +153,8 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
case 0x06: // srlv case 0x06: // srlv
return rt >> (rs & 0x1F) return rt >> (rs & 0x1F)
case 0x07: // srav case 0x07: // srav
return SignExtend(rt>>rs, 32-rs) shamt := rs & 0x1F
return SignExtend(rt>>shamt, 32-shamt)
// functs in range [0x8, 0x1b] are handled specially by other functions // functs in range [0x8, 0x1b] are handled specially by other functions
case 0x08: // jr case 0x08: // jr
return rs return rs
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
) )
// Syscall codes // Syscall codes
...@@ -132,7 +133,6 @@ const ( ...@@ -132,7 +133,6 @@ const (
// Other constants // Other constants
const ( const (
SchedQuantum = 100_000 SchedQuantum = 100_000
BrkStart = 0x40000000
) )
func GetSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2, a3 uint32) { func GetSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2, a3 uint32) {
...@@ -158,6 +158,12 @@ func HandleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) { ...@@ -158,6 +158,12 @@ func HandleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) {
v0 = heap v0 = heap
//fmt.Printf("mmap heap 0x%x size 0x%x\n", v0, sz) //fmt.Printf("mmap heap 0x%x size 0x%x\n", v0, sz)
newHeap += sz newHeap += sz
// Fail if new heap exceeds memory limit, newHeap overflows around to low memory, or sz overflows
if newHeap > program.HEAP_END || newHeap < heap || sz < a1 {
v0 = SysErrorSignal
v1 = MipsEINVAL
return v0, v1, heap
}
} else { } else {
v0 = a0 v0 = a0
//fmt.Printf("mmap hint 0x%x size 0x%x\n", v0, sz) //fmt.Printf("mmap hint 0x%x size 0x%x\n", v0, sz)
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
) )
func (m *InstrumentedState) handleSyscall() error { func (m *InstrumentedState) handleSyscall() error {
...@@ -26,7 +27,7 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -26,7 +27,7 @@ func (m *InstrumentedState) handleSyscall() error {
v0, v1, newHeap = exec.HandleSysMmap(a0, a1, m.state.Heap) v0, v1, newHeap = exec.HandleSysMmap(a0, a1, m.state.Heap)
m.state.Heap = newHeap m.state.Heap = newHeap
case exec.SysBrk: case exec.SysBrk:
v0 = exec.BrkStart v0 = program.PROGRAM_BREAK
case exec.SysClone: // clone case exec.SysClone: // clone
// a0 = flag bitmask, a1 = stack pointer // a0 = flag bitmask, a1 = stack pointer
if exec.ValidCloneFlags != a0 { if exec.ValidCloneFlags != a0 {
......
...@@ -9,7 +9,11 @@ import ( ...@@ -9,7 +9,11 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
) )
const HEAP_START = 0x05000000 const (
HEAP_START = 0x05_00_00_00
HEAP_END = 0x60_00_00_00
PROGRAM_BREAK = 0x40_00_00_00
)
type CreateInitialFPVMState[T mipsevm.FPVMState] func(pc, heapStart uint32) T type CreateInitialFPVMState[T mipsevm.FPVMState] func(pc, heapStart uint32) T
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
) )
func (m *InstrumentedState) handleSyscall() error { func (m *InstrumentedState) handleSyscall() error {
...@@ -20,7 +21,7 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -20,7 +21,7 @@ func (m *InstrumentedState) handleSyscall() error {
v0, v1, newHeap = exec.HandleSysMmap(a0, a1, m.state.Heap) v0, v1, newHeap = exec.HandleSysMmap(a0, a1, m.state.Heap)
m.state.Heap = newHeap m.state.Heap = newHeap
case exec.SysBrk: case exec.SysBrk:
v0 = exec.BrkStart v0 = program.PROGRAM_BREAK
case exec.SysClone: // clone (not supported) case exec.SysClone: // clone (not supported)
v0 = 1 v0 = 1
case exec.SysExitGroup: case exec.SysExitGroup:
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"io" "io"
"os" "os"
"path" "path"
"strings"
"testing" "testing"
"time" "time"
...@@ -18,6 +19,7 @@ import ( ...@@ -18,6 +19,7 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded" "github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
) )
...@@ -48,6 +50,7 @@ func TestEVM(t *testing.T) { ...@@ -48,6 +50,7 @@ func TestEVM(t *testing.T) {
oracle := testutil.SelectOracleFixture(t, f.Name()) oracle := testutil.SelectOracleFixture(t, f.Name())
// Short-circuit early for exit_group.bin // Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin" exitGroup := f.Name() == "exit_group.bin"
expectPanic := strings.HasSuffix(f.Name(), "panic.bin")
evm := testutil.NewMIPSEVM(contracts, addrs) evm := testutil.NewMIPSEVM(contracts, addrs)
evm.SetTracer(tracer) evm.SetTracer(tracer)
...@@ -66,6 +69,17 @@ func TestEVM(t *testing.T) { ...@@ -66,6 +69,17 @@ func TestEVM(t *testing.T) {
goState := singlethreaded.NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, nil) goState := singlethreaded.NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, nil)
// Catch panics and check if they are expected
defer func() {
if r := recover(); r != nil {
if expectPanic {
// Success
} else {
t.Errorf("unexpected panic: %v", r)
}
}
}()
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
curStep := goState.GetState().GetStep() curStep := goState.GetState().GetStep()
if goState.GetState().GetPC() == testutil.EndAddr { if goState.GetState().GetPC() == testutil.EndAddr {
...@@ -90,6 +104,8 @@ func TestEVM(t *testing.T) { ...@@ -90,6 +104,8 @@ func TestEVM(t *testing.T) {
require.NotEqual(t, uint32(testutil.EndAddr), goState.GetState().GetPC(), "must not reach end") require.NotEqual(t, uint32(testutil.EndAddr), goState.GetState().GetPC(), "must not reach end")
require.True(t, goState.GetState().GetExited(), "must set exited state") require.True(t, goState.GetState().GetExited(), "must set exited state")
require.Equal(t, uint8(1), goState.GetState().GetExitCode(), "must exit with 1") require.Equal(t, uint8(1), goState.GetState().GetExitCode(), "must exit with 1")
} else if expectPanic {
require.NotEqual(t, uint32(testutil.EndAddr), goState.GetState().GetPC(), "must not reach end")
} else { } else {
require.Equal(t, uint32(testutil.EndAddr), state.Cpu.PC, "must reach end") require.Equal(t, uint32(testutil.EndAddr), state.Cpu.PC, "must reach end")
// inspect test result // inspect test result
...@@ -193,6 +209,88 @@ func TestEVMSingleStep(t *testing.T) { ...@@ -193,6 +209,88 @@ func TestEVMSingleStep(t *testing.T) {
} }
} }
func TestEVM_MMap(t *testing.T) {
contracts, addrs := testContractsSetup(t)
var tracer *tracing.Hooks
cases := []struct {
name string
heap uint32
address uint32
size uint32
shouldFail bool
expectedHeap uint32
}{
{name: "Increment heap by max value", heap: program.HEAP_START, address: 0, size: ^uint32(0), shouldFail: true},
{name: "Increment heap to 0", heap: program.HEAP_START, address: 0, size: ^uint32(0) - program.HEAP_START + 1, shouldFail: true},
{name: "Increment heap to previous page", heap: program.HEAP_START, address: 0, size: ^uint32(0) - program.HEAP_START - memory.PageSize + 1, shouldFail: true},
{name: "Increment max page size", heap: program.HEAP_START, address: 0, size: ^uint32(0) & ^uint32(memory.PageAddrMask), shouldFail: true},
{name: "Increment max page size from 0", heap: 0, address: 0, size: ^uint32(0) & ^uint32(memory.PageAddrMask), shouldFail: true},
{name: "Increment heap at limit", heap: program.HEAP_END, address: 0, size: 1, shouldFail: true},
{name: "Increment heap to limit", heap: program.HEAP_END - memory.PageSize, address: 0, size: 1, shouldFail: false, expectedHeap: program.HEAP_END},
{name: "Increment heap within limit", heap: program.HEAP_END - 2*memory.PageSize, address: 0, size: 1, shouldFail: false, expectedHeap: program.HEAP_END - memory.PageSize},
{name: "Request specific address", heap: program.HEAP_START, address: 0x50_00_00_00, size: 0, shouldFail: false, expectedHeap: program.HEAP_START},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
state := singlethreaded.CreateEmptyState()
state.Heap = c.heap
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.Registers = testutil.RandomRegisters(77)
state.Registers[2] = exec.SysMmap
state.Registers[4] = c.address
state.Registers[5] = c.size
step := state.Step
expectedRegisters := state.Registers
expectedHeap := state.Heap
expectedMemoryRoot := state.Memory.MerkleRoot()
if c.shouldFail {
expectedRegisters[2] = exec.SysErrorSignal
expectedRegisters[7] = exec.MipsEINVAL
} else {
expectedHeap = c.expectedHeap
if c.address == 0 {
expectedRegisters[2] = state.Heap
expectedRegisters[7] = 0
} else {
expectedRegisters[2] = c.address
expectedRegisters[7] = 0
}
}
us := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil)
stepWitness, err := us.Step(true)
require.NoError(t, err)
// Check expectations
require.Equal(t, step+1, state.Step)
require.Equal(t, expectedHeap, state.Heap)
require.Equal(t, expectedRegisters, state.Registers)
require.Equal(t, expectedMemoryRoot, state.Memory.MerkleRoot())
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, uint32(0), state.PreimageOffset)
require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, false, state.Exited)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, hexutil.Bytes(nil), state.LastHint)
evm := testutil.NewMIPSEVM(contracts, addrs)
evm.SetTracer(tracer)
testutil.LogStepFailureAtCleanup(t, evm)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := us.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
}
func TestEVMSysWriteHint(t *testing.T) { func TestEVMSysWriteHint(t *testing.T) {
contracts, addrs := testContractsSetup(t) contracts, addrs := testContractsSetup(t)
var tracer *tracing.Hooks var tracer *tracing.Hooks
......
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded" "github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
preimage "github.com/ethereum-optimism/optimism/op-preimage" preimage "github.com/ethereum-optimism/optimism/op-preimage"
...@@ -45,7 +46,7 @@ func FuzzStateSyscallBrk(f *testing.F) { ...@@ -45,7 +46,7 @@ func FuzzStateSyscallBrk(f *testing.F) {
state.Memory.SetMemory(pc, syscallInsn) state.Memory.SetMemory(pc, syscallInsn)
preStateRoot := state.Memory.MerkleRoot() preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers expectedRegisters := state.Registers
expectedRegisters[2] = 0x4000_0000 expectedRegisters[2] = program.PROGRAM_BREAK
goState := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil) goState := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil)
stepWitness, err := goState.Step(true) stepWitness, err := goState.Step(true)
...@@ -127,7 +128,14 @@ func FuzzStateSyscallClone(f *testing.F) { ...@@ -127,7 +128,14 @@ func FuzzStateSyscallClone(f *testing.F) {
func FuzzStateSyscallMmap(f *testing.F) { func FuzzStateSyscallMmap(f *testing.F) {
contracts, addrs := testContractsSetup(f) contracts, addrs := testContractsSetup(f)
step := uint64(0) step := uint64(0)
f.Fuzz(func(t *testing.T, addr uint32, siz uint32, heap uint32) {
// Add special cases for large memory allocation
f.Add(uint32(0), uint32(0x1000), uint32(program.HEAP_END), int64(1))
f.Add(uint32(0), uint32(1<<31), uint32(program.HEAP_START), int64(2))
// Check edge case - just within bounds
f.Add(uint32(0), uint32(0x1000), uint32(program.HEAP_END-4096), int64(3))
f.Fuzz(func(t *testing.T, addr uint32, siz uint32, heap uint32, seed int64) {
state := &singlethreaded.State{ state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{ Cpu: mipsevm.CpuScalars{
PC: 0, PC: 0,
...@@ -139,11 +147,14 @@ func FuzzStateSyscallMmap(f *testing.F) { ...@@ -139,11 +147,14 @@ func FuzzStateSyscallMmap(f *testing.F) {
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
Memory: memory.NewMemory(), Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysMmap, 4: addr, 5: siz}, Registers: testutil.RandomRegisters(seed),
Step: step, Step: step,
PreimageOffset: 0, PreimageOffset: 0,
} }
state.Memory.SetMemory(0, syscallInsn) state.Memory.SetMemory(0, syscallInsn)
state.Registers[2] = exec.SysMmap
state.Registers[4] = addr
state.Registers[5] = siz
preStateRoot := state.Memory.MerkleRoot() preStateRoot := state.Memory.MerkleRoot()
preStateRegisters := state.Registers preStateRegisters := state.Registers
...@@ -152,32 +163,42 @@ func FuzzStateSyscallMmap(f *testing.F) { ...@@ -152,32 +163,42 @@ func FuzzStateSyscallMmap(f *testing.F) {
require.NoError(t, err) require.NoError(t, err)
require.False(t, stepWitness.HasPreimage()) require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.Cpu.PC) var expectedHeap uint32
require.Equal(t, uint32(8), state.Cpu.NextPC) expectedRegisters := preStateRegisters
require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, uint32(0), state.PreimageOffset)
if addr == 0 { if addr == 0 {
expectedRegisters := preStateRegisters
expectedRegisters[2] = heap
require.Equal(t, expectedRegisters, state.Registers)
sizAlign := siz sizAlign := siz
if sizAlign&memory.PageAddrMask != 0 { // adjust size to align with page size if sizAlign&memory.PageAddrMask != 0 { // adjust size to align with page size
sizAlign = siz + memory.PageSize - (siz & memory.PageAddrMask) sizAlign = siz + memory.PageSize - (siz & memory.PageAddrMask)
} }
require.Equal(t, uint32(heap+sizAlign), state.Heap) newHeap := heap + sizAlign
if newHeap > program.HEAP_END || newHeap < heap || sizAlign < siz {
expectedHeap = heap
expectedRegisters[2] = exec.SysErrorSignal
expectedRegisters[7] = exec.MipsEINVAL
} else {
expectedRegisters[2] = heap
expectedRegisters[7] = 0 // no error
expectedHeap = heap + sizAlign
}
} else { } else {
expectedRegisters := preStateRegisters
expectedRegisters[2] = addr expectedRegisters[2] = addr
require.Equal(t, expectedRegisters, state.Registers) expectedRegisters[7] = 0 // no error
require.Equal(t, uint32(heap), state.Heap) expectedHeap = heap
} }
require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, uint32(0), state.PreimageOffset)
require.Equal(t, expectedHeap, state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, expectedRegisters, state.Registers)
evm := testutil.NewMIPSEVM(contracts, addrs) evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn()) evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness() goPost, _ := goState.GetState().EncodeWitness()
......
...@@ -5,36 +5,37 @@ ...@@ -5,36 +5,37 @@
.ent test .ent test
# load hash at 0x30001000 # load hash at 0x30001000
# point evaluation precompile input - 01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a # requiredGas is 50_000
# 0x0a44472c cb798bc5 954fc466 e6ee2c31 e1ca8a87 d000966c 629d679a 4a29921f = keccak(address(0xa) ++ precompile_input) # point evaluation precompile input (requiredGas ++ precompileInput) - 000000000000c35001e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a
# 0x0644472c cb798bc5 954fc466 e6ee2c31 e1ca8a87 d000966c 629d679a 4a29921f = keccak(address(0xa) ++ precompile_input).key (precompile) # 0x3efd5c3c 1c555298 0c63aee5 4570c276 cbff7532 796b4d75 3132d51a 6bedf0c6 = keccak(address(0xa) ++ required_gas ++ precompile_input)
# 0x06fd5c3c 1c555298 0c63aee5 4570c276 cbff7532 796b4d75 3132d51a 6bedf0c6 = keccak(address(0xa) ++ required_gas ++ precompile_input).key (precompile)
test: test:
lui $s0, 0x3000 lui $s0, 0x3000
ori $s0, 0x1000 ori $s0, 0x1000
lui $t0, 0x0644 lui $t0, 0x06fd
ori $t0, 0x472c ori $t0, 0x5c3c
sw $t0, 0($s0) sw $t0, 0($s0)
lui $t0, 0xcb79 lui $t0, 0x1c55
ori $t0, 0x8bc5 ori $t0, 0x5298
sw $t0, 4($s0) sw $t0, 4($s0)
lui $t0, 0x954f lui $t0, 0x0c63
ori $t0, 0xc466 ori $t0, 0xaee5
sw $t0, 8($s0) sw $t0, 8($s0)
lui $t0, 0xe6ee lui $t0, 0x4570
ori $t0, 0x2c31 ori $t0, 0xc276
sw $t0, 0xc($s0) sw $t0, 0xc($s0)
lui $t0, 0xe1ca lui $t0, 0xcbff
ori $t0, 0x8a87 ori $t0, 0x7532
sw $t0, 0x10($s0) sw $t0, 0x10($s0)
lui $t0, 0xd000 lui $t0, 0x796b
ori $t0, 0x966c ori $t0, 0x4d75
sw $t0, 0x14($s0) sw $t0, 0x14($s0)
lui $t0, 0x629d lui $t0, 0x3132
ori $t0, 0x679a ori $t0, 0xd51a
sw $t0, 0x18($s0) sw $t0, 0x18($s0)
lui $t0, 0x4a29 lui $t0, 0x6bed
ori $t0, 0x921f ori $t0, 0xf0c6
sw $t0, 0x1c($s0) sw $t0, 0x1c($s0)
# preimage request - write(fdPreimageWrite, preimageData, 32) # preimage request - write(fdPreimageWrite, preimageData, 32)
......
###############################################################################
# Description:
# Tests that the 'srav' instruction properly only utilizes the lower 5 bits
# of the rs register rather than using the entire 32 bits.
#
###############################################################################
.section .test, "x"
.balign 4
.set noreorder
.global test
.ent test
test:
lui $s0, 0xbfff # Load the base address 0xbffffff0
ori $s0, 0xfff0
ori $s1, $0, 1 # Prepare the 'done' status
#### Test code start ####
lui $t0, 0xdeaf # A = 0xdeafbeef
ori $t0, 0xbeef
ori $t1, $0, 0x2c
srav $t2, $t0, $t1 # B = 0xdeafbeef >> (0x2c & 0x1f) = 0xdeadbeef >> 12 = 0xfffdeafb
lui $t3, 0xfffd
ori $t3, 0xeafb
subu $t4, $t2, $t3
sltiu $v0, $t4, 1
#### Test code end ####
sw $v0, 8($s0) # Set the test result
sw $s1, 4($s0) # Set 'done'
$done:
jr $ra
nop
.end test
package testutil package testutil
import ( import (
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"math" "math"
...@@ -121,11 +122,13 @@ func EncodePreimageOracleInput(t *testing.T, wit *mipsevm.StepWitness, localCont ...@@ -121,11 +122,13 @@ func EncodePreimageOracleInput(t *testing.T, wit *mipsevm.StepWitness, localCont
} }
preimage := localOracle.GetPreimage(preimage.Keccak256Key(wit.PreimageKey).PreimageKey()) preimage := localOracle.GetPreimage(preimage.Keccak256Key(wit.PreimageKey).PreimageKey())
precompile := common.BytesToAddress(preimage[:20]) precompile := common.BytesToAddress(preimage[:20])
callInput := preimage[20:] requiredGas := binary.BigEndian.Uint64(preimage[20:28])
callInput := preimage[28:]
input, err := oracle.ABI.Pack( input, err := oracle.ABI.Pack(
"loadPrecompilePreimagePart", "loadPrecompilePreimagePart",
new(big.Int).SetUint64(uint64(wit.PreimageOffset)), new(big.Int).SetUint64(uint64(wit.PreimageOffset)),
precompile, precompile,
requiredGas,
callInput, callInput,
) )
require.NoError(t, err) require.NoError(t, err)
......
...@@ -42,11 +42,13 @@ func StaticOracle(t *testing.T, preimageData []byte) *TestOracle { ...@@ -42,11 +42,13 @@ func StaticOracle(t *testing.T, preimageData []byte) *TestOracle {
} }
} }
func StaticPrecompileOracle(t *testing.T, precompile common.Address, input []byte, result []byte) *TestOracle { func StaticPrecompileOracle(t *testing.T, precompile common.Address, requiredGas uint64, input []byte, result []byte) *TestOracle {
return &TestOracle{ return &TestOracle{
hint: func(v []byte) {}, hint: func(v []byte) {},
getPreimage: func(k [32]byte) []byte { getPreimage: func(k [32]byte) []byte {
keyData := append(precompile.Bytes(), input...) requiredGasB := binary.BigEndian.AppendUint64(nil, requiredGas)
keyData := append(precompile.Bytes(), requiredGasB...)
keyData = append(keyData, input...)
switch k[0] { switch k[0] {
case byte(preimage.Keccak256KeyType): case byte(preimage.Keccak256KeyType):
if k != preimage.Keccak256Key(crypto.Keccak256Hash(keyData)).PreimageKey() { if k != preimage.Keccak256Key(crypto.Keccak256Hash(keyData)).PreimageKey() {
...@@ -135,7 +137,8 @@ func SelectOracleFixture(t *testing.T, programName string) mipsevm.PreimageOracl ...@@ -135,7 +137,8 @@ func SelectOracleFixture(t *testing.T, programName string) mipsevm.PreimageOracl
precompile := common.BytesToAddress([]byte{0xa}) precompile := common.BytesToAddress([]byte{0xa})
input := common.FromHex("01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a") input := common.FromHex("01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a")
blobPrecompileReturnValue := common.FromHex("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001") blobPrecompileReturnValue := common.FromHex("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001")
return StaticPrecompileOracle(t, precompile, input, append([]byte{0x1}, blobPrecompileReturnValue...)) requiredGas := uint64(50_000)
return StaticPrecompileOracle(t, precompile, requiredGas, input, append([]byte{0x1}, blobPrecompileReturnValue...))
} else if strings.HasPrefix(programName, "oracle") { } else if strings.HasPrefix(programName, "oracle") {
return StaticOracle(t, []byte("hello world")) return StaticOracle(t, []byte("hello world"))
} else { } else {
......
package testutil
import "math/rand"
func RandomRegisters(seed int64) [32]uint32 {
r := rand.New(rand.NewSource(seed))
var registers [32]uint32
for i := 0; i < 32; i++ {
registers[i] = r.Uint32()
}
return registers
}
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"io" "io"
"os" "os"
"path" "path"
"strings"
"testing" "testing"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -32,6 +33,7 @@ func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFa ...@@ -32,6 +33,7 @@ func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFa
oracle := SelectOracleFixture(t, f.Name()) oracle := SelectOracleFixture(t, f.Name())
// Short-circuit early for exit_group.bin // Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin" exitGroup := f.Name() == "exit_group.bin"
expectPanic := strings.HasSuffix(f.Name(), "panic.bin")
// TODO: currently tests are compiled as flat binary objects // TODO: currently tests are compiled as flat binary objects
// We can use more standard tooling to compile them to ELF files and get remove maketests.py // We can use more standard tooling to compile them to ELF files and get remove maketests.py
...@@ -51,6 +53,17 @@ func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFa ...@@ -51,6 +53,17 @@ func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFa
us := vmFactory(state, oracle, os.Stdout, os.Stderr, CreateLogger()) us := vmFactory(state, oracle, os.Stdout, os.Stderr, CreateLogger())
// Catch panics and check if they are expected
defer func() {
if r := recover(); r != nil {
if expectPanic {
// Success
} else {
t.Errorf("unexpected panic: %v", r)
}
}
}()
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
if us.GetState().GetPC() == EndAddr { if us.GetState().GetPC() == EndAddr {
break break
...@@ -66,6 +79,8 @@ func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFa ...@@ -66,6 +79,8 @@ func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFa
require.NotEqual(t, uint32(EndAddr), us.GetState().GetPC(), "must not reach end") require.NotEqual(t, uint32(EndAddr), us.GetState().GetPC(), "must not reach end")
require.True(t, us.GetState().GetExited(), "must set exited state") require.True(t, us.GetState().GetExited(), "must set exited state")
require.Equal(t, uint8(1), us.GetState().GetExitCode(), "must exit with 1") require.Equal(t, uint8(1), us.GetState().GetExitCode(), "must exit with 1")
} else if expectPanic {
require.NotEqual(t, uint32(EndAddr), us.GetState().GetPC(), "must not reach end")
} else { } else {
require.Equal(t, uint32(EndAddr), us.GetState().GetPC(), "must reach end") require.Equal(t, uint32(EndAddr), us.GetState().GetPC(), "must reach end")
done, result := state.GetMemory().GetMemory(BaseAddrEnd+4), state.GetMemory().GetMemory(BaseAddrEnd+8) done, result := state.GetMemory().GetMemory(BaseAddrEnd+4), state.GetMemory().GetMemory(BaseAddrEnd+8)
......
...@@ -7,7 +7,6 @@ import ( ...@@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
"strings"
"time" "time"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics"
...@@ -26,7 +25,6 @@ import ( ...@@ -26,7 +25,6 @@ import (
var maxChildChecks = big.NewInt(512) var maxChildChecks = big.NewInt(512)
var ( var (
methodVersion = "version"
methodMaxClockDuration = "maxClockDuration" methodMaxClockDuration = "maxClockDuration"
methodMaxGameDepth = "maxGameDepth" methodMaxGameDepth = "maxGameDepth"
methodAbsolutePrestate = "absolutePrestate" methodAbsolutePrestate = "absolutePrestate"
...@@ -84,14 +82,8 @@ type outputRootProof struct { ...@@ -84,14 +82,8 @@ type outputRootProof struct {
func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMetricer, addr common.Address, caller *batching.MultiCaller) (FaultDisputeGameContract, error) { func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMetricer, addr common.Address, caller *batching.MultiCaller) (FaultDisputeGameContract, error) {
contractAbi := snapshots.LoadFaultDisputeGameABI() contractAbi := snapshots.LoadFaultDisputeGameABI()
result, err := caller.SingleCall(ctx, rpcblock.Latest, batching.NewContractCall(contractAbi, addr, methodVersion)) var builder VersionedBuilder[FaultDisputeGameContract]
if err != nil { builder.AddVersion(0, 8, func() (FaultDisputeGameContract, error) {
return nil, fmt.Errorf("failed to retrieve version of dispute game %v: %w", addr, err)
}
version := result.GetString(0)
if strings.HasPrefix(version, "0.8.") {
// Detected an older version of contracts, use a compatibility shim.
legacyAbi := mustParseAbi(faultDisputeGameAbi020) legacyAbi := mustParseAbi(faultDisputeGameAbi020)
return &FaultDisputeGameContract080{ return &FaultDisputeGameContract080{
FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{ FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{
...@@ -100,8 +92,8 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe ...@@ -100,8 +92,8 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe
contract: batching.NewBoundContract(legacyAbi, addr), contract: batching.NewBoundContract(legacyAbi, addr),
}, },
}, nil }, nil
} else if strings.HasPrefix(version, "0.18.") || strings.HasPrefix(version, "1.0.") { })
// Detected an older version of contracts, use a compatibility shim. builder.AddVersion(0, 18, func() (FaultDisputeGameContract, error) {
legacyAbi := mustParseAbi(faultDisputeGameAbi0180) legacyAbi := mustParseAbi(faultDisputeGameAbi0180)
return &FaultDisputeGameContract0180{ return &FaultDisputeGameContract0180{
FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{ FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{
...@@ -110,8 +102,18 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe ...@@ -110,8 +102,18 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe
contract: batching.NewBoundContract(legacyAbi, addr), contract: batching.NewBoundContract(legacyAbi, addr),
}, },
}, nil }, nil
} else if strings.HasPrefix(version, "1.1.") { })
// Detected an older version of contracts, use a compatibility shim. builder.AddVersion(1, 0, func() (FaultDisputeGameContract, error) {
legacyAbi := mustParseAbi(faultDisputeGameAbi0180)
return &FaultDisputeGameContract0180{
FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{
metrics: metrics,
multiCaller: caller,
contract: batching.NewBoundContract(legacyAbi, addr),
},
}, nil
})
builder.AddVersion(1, 1, func() (FaultDisputeGameContract, error) {
legacyAbi := mustParseAbi(faultDisputeGameAbi111) legacyAbi := mustParseAbi(faultDisputeGameAbi111)
return &FaultDisputeGameContract111{ return &FaultDisputeGameContract111{
FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{ FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{
...@@ -120,13 +122,14 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe ...@@ -120,13 +122,14 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe
contract: batching.NewBoundContract(legacyAbi, addr), contract: batching.NewBoundContract(legacyAbi, addr),
}, },
}, nil }, nil
} else { })
return builder.Build(ctx, caller, contractAbi, addr, func() (FaultDisputeGameContract, error) {
return &FaultDisputeGameContractLatest{ return &FaultDisputeGameContractLatest{
metrics: metrics, metrics: metrics,
multiCaller: caller, multiCaller: caller,
contract: batching.NewBoundContract(contractAbi, addr), contract: batching.NewBoundContract(contractAbi, addr),
}, nil }, nil
} })
} }
func mustParseAbi(json []byte) *abi.ABI { func mustParseAbi(json []byte) *abi.ABI {
...@@ -364,7 +367,7 @@ func (f *FaultDisputeGameContractLatest) getDelayedWETH(ctx context.Context, blo ...@@ -364,7 +367,7 @@ func (f *FaultDisputeGameContractLatest) getDelayedWETH(ctx context.Context, blo
return NewDelayedWETHContract(f.metrics, result.GetAddress(0), f.multiCaller), nil return NewDelayedWETHContract(f.metrics, result.GetAddress(0), f.multiCaller), nil
} }
func (f *FaultDisputeGameContractLatest) GetOracle(ctx context.Context) (*PreimageOracleContract, error) { func (f *FaultDisputeGameContractLatest) GetOracle(ctx context.Context) (PreimageOracleContract, error) {
defer f.metrics.StartContractRequest("GetOracle")() defer f.metrics.StartContractRequest("GetOracle")()
vm, err := f.Vm(ctx) vm, err := f.Vm(ctx)
if err != nil { if err != nil {
...@@ -616,7 +619,7 @@ type FaultDisputeGameContract interface { ...@@ -616,7 +619,7 @@ type FaultDisputeGameContract interface {
GetRequiredBond(ctx context.Context, position types.Position) (*big.Int, error) GetRequiredBond(ctx context.Context, position types.Position) (*big.Int, error)
UpdateOracleTx(ctx context.Context, claimIdx uint64, data *types.PreimageOracleData) (txmgr.TxCandidate, error) UpdateOracleTx(ctx context.Context, claimIdx uint64, data *types.PreimageOracleData) (txmgr.TxCandidate, error)
GetWithdrawals(ctx context.Context, block rpcblock.Block, recipients ...common.Address) ([]*WithdrawalRequest, error) GetWithdrawals(ctx context.Context, block rpcblock.Block, recipients ...common.Address) ([]*WithdrawalRequest, error)
GetOracle(ctx context.Context) (*PreimageOracleContract, error) GetOracle(ctx context.Context) (PreimageOracleContract, error)
GetMaxClockDuration(ctx context.Context) (time.Duration, error) GetMaxClockDuration(ctx context.Context) (time.Duration, error)
GetMaxGameDepth(ctx context.Context) (types.Depth, error) GetMaxGameDepth(ctx context.Context) (types.Depth, error)
GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error) GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error)
......
...@@ -797,6 +797,7 @@ func setupFaultDisputeGameTest(t *testing.T, version contractVersion) (*batching ...@@ -797,6 +797,7 @@ func setupFaultDisputeGameTest(t *testing.T, version contractVersion) (*batching
caller := batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize) caller := batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize)
stubRpc.SetResponse(fdgAddr, methodVersion, rpcblock.Latest, nil, []interface{}{version.version}) stubRpc.SetResponse(fdgAddr, methodVersion, rpcblock.Latest, nil, []interface{}{version.version})
stubRpc.SetResponse(oracleAddr, methodVersion, rpcblock.Latest, nil, []interface{}{oracleLatest})
game, err := NewFaultDisputeGameContract(context.Background(), contractMetrics.NoopContractMetrics, fdgAddr, caller) game, err := NewFaultDisputeGameContract(context.Background(), contractMetrics.NoopContractMetrics, fdgAddr, caller)
require.NoError(t, err) require.NoError(t, err)
return stubRpc, game return stubRpc, game
......
package contracts
import (
_ "embed"
"math/big"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
)
//go:embed abis/PreimageOracle-1.0.0.json
var preimageOracleAbi100 []byte
type PreimageOracleContract100 struct {
PreimageOracleContractLatest
}
func (c *PreimageOracleContract100) AddGlobalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
if len(data.OracleKey) == 0 || preimage.KeyType(data.OracleKey[0]) != preimage.PrecompileKeyType {
return c.PreimageOracleContractLatest.AddGlobalDataTx(data)
}
inputs := data.GetPreimageWithoutSize()
call := c.contract.Call(methodLoadPrecompilePreimagePart,
new(big.Int).SetUint64(uint64(data.OracleOffset)),
common.BytesToAddress(inputs[0:20]),
inputs[20:])
return call.ToTxCandidate()
}
package contracts
import (
"context"
"fmt"
"strings"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
)
var (
methodVersion = "version"
)
type version[C any] struct {
prefixes string
factory func() (C, error)
}
type VersionedBuilder[C any] struct {
versions []version[C]
}
func (v *VersionedBuilder[C]) AddVersion(major int, minor int, factory func() (C, error)) {
v.versions = append(v.versions, version[C]{fmt.Sprintf("%d.%d.", major, minor), factory})
}
func (v *VersionedBuilder[C]) Build(ctx context.Context, caller *batching.MultiCaller, contractAbi *abi.ABI, addr common.Address, defaultVersion func() (C, error)) (C, error) {
var nilC C
result, err := caller.SingleCall(ctx, rpcblock.Latest, batching.NewContractCall(contractAbi, addr, methodVersion))
if err != nil {
return nilC, fmt.Errorf("failed to retrieve version of dispute game %v: %w", addr, err)
}
contractVersion := result.GetString(0)
for _, version := range v.versions {
if strings.HasPrefix(contractVersion, version.prefixes) {
return version.factory()
}
}
return defaultVersion()
}
package contracts
import (
"context"
"testing"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
var versionABI = `[{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}]`
func TestVersionedBuilder(t *testing.T) {
var builder VersionedBuilder[string]
builder.AddVersion(1, 1, func() (string, error) { return "v1.1", nil })
builder.AddVersion(1, 2, func() (string, error) { return "v1.2", nil })
require.Equal(t, "v1.1", buildWithVersion(t, builder, "1.1.0"))
require.Equal(t, "v1.1", buildWithVersion(t, builder, "1.1.1"))
require.Equal(t, "v1.1", buildWithVersion(t, builder, "1.1.2"))
require.Equal(t, "default", buildWithVersion(t, builder, "1.10.0"))
}
func buildWithVersion(t *testing.T, builder VersionedBuilder[string], version string) string {
addr := common.Address{0xaa}
contractABI := mustParseAbi(([]byte)(versionABI))
stubRpc := batchingTest.NewAbiBasedRpc(t, addr, contractABI)
stubRpc.SetResponse(addr, methodVersion, rpcblock.Latest, nil, []interface{}{version})
actual, err := builder.Build(context.Background(), batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize), contractABI, addr, func() (string, error) {
return "default", nil
})
require.NoError(t, err)
return actual
}
...@@ -33,10 +33,10 @@ func (c *VMContract) Addr() common.Address { ...@@ -33,10 +33,10 @@ func (c *VMContract) Addr() common.Address {
return c.contract.Addr() return c.contract.Addr()
} }
func (c *VMContract) Oracle(ctx context.Context) (*PreimageOracleContract, error) { func (c *VMContract) Oracle(ctx context.Context) (PreimageOracleContract, error) {
results, err := c.multiCaller.SingleCall(ctx, rpcblock.Latest, c.contract.Call(methodOracle)) results, err := c.multiCaller.SingleCall(ctx, rpcblock.Latest, c.contract.Call(methodOracle))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load oracle address: %w", err) return nil, fmt.Errorf("failed to load oracle address: %w", err)
} }
return NewPreimageOracleContract(results.GetAddress(0), c.multiCaller), nil return NewPreimageOracleContract(ctx, results.GetAddress(0), c.multiCaller)
} }
...@@ -21,6 +21,8 @@ func TestVMContract_Oracle(t *testing.T) { ...@@ -21,6 +21,8 @@ func TestVMContract_Oracle(t *testing.T) {
vmContract := NewVMContract(vmAddr, batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize)) vmContract := NewVMContract(vmAddr, batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize))
stubRpc.SetResponse(vmAddr, methodOracle, rpcblock.Latest, nil, []interface{}{oracleAddr}) stubRpc.SetResponse(vmAddr, methodOracle, rpcblock.Latest, nil, []interface{}{oracleAddr})
stubRpc.AddContract(oracleAddr, snapshots.LoadPreimageOracleABI())
stubRpc.SetResponse(oracleAddr, methodVersion, rpcblock.Latest, nil, []interface{}{oracleLatest})
oracleContract, err := vmContract.Oracle(context.Background()) oracleContract, err := vmContract.Oracle(context.Background())
require.NoError(t, err) require.NoError(t, err)
......
...@@ -60,7 +60,7 @@ type GameContract interface { ...@@ -60,7 +60,7 @@ type GameContract interface {
GetStatus(ctx context.Context) (gameTypes.GameStatus, error) GetStatus(ctx context.Context) (gameTypes.GameStatus, error)
GetMaxGameDepth(ctx context.Context) (types.Depth, error) GetMaxGameDepth(ctx context.Context) (types.Depth, error)
GetMaxClockDuration(ctx context.Context) (time.Duration, error) GetMaxClockDuration(ctx context.Context) (time.Duration, error)
GetOracle(ctx context.Context) (*contracts.PreimageOracleContract, error) GetOracle(ctx context.Context) (contracts.PreimageOracleContract, error)
GetL1Head(ctx context.Context) (common.Hash, error) GetL1Head(ctx context.Context) (common.Hash, error)
} }
......
...@@ -2,6 +2,7 @@ package types ...@@ -2,6 +2,7 @@ package types
import ( import (
"context" "context"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"math" "math"
...@@ -149,8 +150,12 @@ func (p *PreimageOracleData) GetPrecompileAddress() common.Address { ...@@ -149,8 +150,12 @@ func (p *PreimageOracleData) GetPrecompileAddress() common.Address {
return common.BytesToAddress(p.oracleData[8:28]) return common.BytesToAddress(p.oracleData[8:28])
} }
func (p *PreimageOracleData) GetPrecompileRequiredGas() uint64 {
return binary.BigEndian.Uint64(p.oracleData[28:36])
}
func (p *PreimageOracleData) GetPrecompileInput() []byte { func (p *PreimageOracleData) GetPrecompileInput() []byte {
return p.oracleData[28:] return p.oracleData[36:]
} }
// NewPreimageOracleData creates a new [PreimageOracleData] instance. // NewPreimageOracleData creates a new [PreimageOracleData] instance.
......
This diff is collapsed.
...@@ -47,23 +47,49 @@ func (g *OutputCannonGameHelper) StartChallenger(ctx context.Context, name strin ...@@ -47,23 +47,49 @@ func (g *OutputCannonGameHelper) StartChallenger(ctx context.Context, name strin
return c return c
} }
func (g *OutputCannonGameHelper) CreateHonestActor(ctx context.Context, l2Node string, options ...challenger.Option) *OutputHonestHelper { type honestActorConfig struct {
opts := g.defaultChallengerOptions() prestateBlock uint64
opts = append(opts, options...) poststateBlock uint64
cfg := challenger.NewChallengerConfig(g.T, g.System, l2Node, opts...) challengerOpts []challenger.Option
}
type HonestActorOpt func(cfg *honestActorConfig)
func WithClaimedL2BlockNumber(num uint64) HonestActorOpt {
return func(cfg *honestActorConfig) {
cfg.poststateBlock = num
}
}
func WithPrivKey(privKey *ecdsa.PrivateKey) HonestActorOpt {
return func(cfg *honestActorConfig) {
cfg.challengerOpts = append(cfg.challengerOpts, challenger.WithPrivKey(privKey))
}
}
func (g *OutputCannonGameHelper) CreateHonestActor(ctx context.Context, l2Node string, options ...HonestActorOpt) *OutputHonestHelper {
logger := testlog.Logger(g.T, log.LevelInfo).New("role", "HonestHelper", "game", g.Addr) logger := testlog.Logger(g.T, log.LevelInfo).New("role", "HonestHelper", "game", g.Addr)
l2Client := g.System.NodeClient(l2Node) l2Client := g.System.NodeClient(l2Node)
prestateBlock, poststateBlock, err := g.Game.GetBlockRange(ctx) realPrestateBlock, realPostStateBlock, err := g.Game.GetBlockRange(ctx)
g.Require.NoError(err, "Failed to load block range") g.Require.NoError(err, "Failed to load block range")
dir := filepath.Join(cfg.Datadir, "honest")
splitDepth := g.SplitDepth(ctx) splitDepth := g.SplitDepth(ctx)
rollupClient := g.System.RollupClient(l2Node) rollupClient := g.System.RollupClient(l2Node)
prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock) actorCfg := &honestActorConfig{
prestateBlock: realPrestateBlock,
poststateBlock: realPostStateBlock,
challengerOpts: g.defaultChallengerOptions(),
}
for _, option := range options {
option(actorCfg)
}
cfg := challenger.NewChallengerConfig(g.T, g.System, l2Node, actorCfg.challengerOpts...)
dir := filepath.Join(cfg.Datadir, "honest")
prestateProvider := outputs.NewPrestateProvider(rollupClient, actorCfg.prestateBlock)
l1Head := g.GetL1Head(ctx) l1Head := g.GetL1Head(ctx)
accessor, err := outputs.NewOutputCannonTraceAccessor( accessor, err := outputs.NewOutputCannonTraceAccessor(
logger, metrics.NoopMetrics, cfg.Cannon, vm.NewOpProgramServerExecutor(), l2Client, prestateProvider, cfg.CannonAbsolutePreState, rollupClient, dir, l1Head, splitDepth, prestateBlock, poststateBlock) logger, metrics.NoopMetrics, cfg.Cannon, vm.NewOpProgramServerExecutor(), l2Client, prestateProvider, cfg.CannonAbsolutePreState, rollupClient, dir, l1Head, splitDepth, actorCfg.prestateBlock, actorCfg.poststateBlock)
g.Require.NoError(err, "Failed to create output cannon trace accessor") g.Require.NoError(err, "Failed to create output cannon trace accessor")
return NewOutputHonestHelper(g.T, g.Require, &g.OutputGameHelper, g.Game, accessor) return NewOutputHonestHelper(g.T, g.Require, &g.OutputGameHelper, g.Game, accessor)
} }
...@@ -128,7 +154,7 @@ func (g *OutputCannonGameHelper) ChallengeToPreimageLoad(ctx context.Context, ou ...@@ -128,7 +154,7 @@ func (g *OutputCannonGameHelper) ChallengeToPreimageLoad(ctx context.Context, ou
if preloadPreimage { if preloadPreimage {
_, _, preimageData, err := provider.GetStepData(ctx, types.NewPosition(execDepth, big.NewInt(int64(targetTraceIndex)))) _, _, preimageData, err := provider.GetStepData(ctx, types.NewPosition(execDepth, big.NewInt(int64(targetTraceIndex))))
g.Require.NoError(err) g.Require.NoError(err)
g.UploadPreimage(ctx, preimageData, challengerKey) g.UploadPreimage(ctx, preimageData)
g.WaitForPreimageInOracle(ctx, preimageData) g.WaitForPreimageInOracle(ctx, preimageData)
} }
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
"math/big" "math/big"
"strings"
"testing" "testing"
"time" "time"
...@@ -14,10 +15,8 @@ import ( ...@@ -14,10 +15,8 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-e2e/bindings"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-service/errutil" "github.com/ethereum-optimism/optimism/op-service/errutil"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
...@@ -586,7 +585,11 @@ func (g *OutputGameHelper) StepFails(ctx context.Context, claimIdx int64, isAtta ...@@ -586,7 +585,11 @@ func (g *OutputGameHelper) StepFails(ctx context.Context, claimIdx int64, isAtta
_, _, err = transactions.SendTx(ctx, g.Client, candidate, g.PrivKey, transactions.WithReceiptFail()) _, _, err = transactions.SendTx(ctx, g.Client, candidate, g.PrivKey, transactions.WithReceiptFail())
err = errutil.TryAddRevertReason(err) err = errutil.TryAddRevertReason(err)
g.Require.Error(err, "Transaction should fail") g.Require.Error(err, "Transaction should fail")
g.Require.Contains(err.Error(), "0xfb4e40dd", "Revert reason should be abi encoded ValidStep()") validStepErr := "0xfb4e40dd"
invalidPrestateErr := "0x696550ff"
if !strings.Contains(err.Error(), validStepErr) && !strings.Contains(err.Error(), invalidPrestateErr) {
g.Require.Failf("Revert reason should be abi encoded ValidStep() or InvalidPrestate() but was: %v", err.Error())
}
} }
// ResolveClaim resolves a single subgame // ResolveClaim resolves a single subgame
...@@ -649,28 +652,14 @@ func (g *OutputGameHelper) WaitForPreimageInOracle(ctx context.Context, data *ty ...@@ -649,28 +652,14 @@ func (g *OutputGameHelper) WaitForPreimageInOracle(ctx context.Context, data *ty
g.Require.NoErrorf(err, "Did not find preimage (%v) in oracle", common.Bytes2Hex(data.OracleKey)) g.Require.NoErrorf(err, "Did not find preimage (%v) in oracle", common.Bytes2Hex(data.OracleKey))
} }
func (g *OutputGameHelper) UploadPreimage(ctx context.Context, data *types.PreimageOracleData, privateKey *ecdsa.PrivateKey) { func (g *OutputGameHelper) UploadPreimage(ctx context.Context, data *types.PreimageOracleData) {
oracle := g.oracle(ctx) oracle := g.oracle(ctx)
boundOracle, err := bindings.NewPreimageOracle(oracle.Addr(), g.Client) tx, err := oracle.AddGlobalDataTx(data)
g.Require.NoError(err) g.Require.NoError(err, "Failed to create preimage upload tx")
var tx *gethtypes.Transaction transactions.RequireSendTx(g.T, ctx, g.Client, tx, g.PrivKey)
switch data.OracleKey[0] {
case byte(preimage.PrecompileKeyType):
tx, err = boundOracle.LoadPrecompilePreimagePart(
g.Opts,
new(big.Int).SetUint64(uint64(data.OracleOffset)),
data.GetPrecompileAddress(),
data.GetPrecompileInput(),
)
default:
tx, err = boundOracle.LoadKeccak256PreimagePart(g.Opts, new(big.Int).SetUint64(uint64(data.OracleOffset)), data.GetPreimageWithoutSize())
}
g.Require.NoError(err, "Failed to load preimage part")
_, err = wait.ForReceiptOK(ctx, g.Client, tx.Hash())
g.Require.NoError(err)
} }
func (g *OutputGameHelper) oracle(ctx context.Context) *contracts.PreimageOracleContract { func (g *OutputGameHelper) oracle(ctx context.Context) contracts.PreimageOracleContract {
oracle, err := g.Game.GetOracle(ctx) oracle, err := g.Game.GetOracle(ctx)
g.Require.NoError(err, "Failed to create oracle contract") g.Require.NoError(err, "Failed to create oracle contract")
return oracle return oracle
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -99,8 +100,13 @@ func (h *OutputHonestHelper) StepFails(ctx context.Context, claimIdx int64, isAt ...@@ -99,8 +100,13 @@ func (h *OutputHonestHelper) StepFails(ctx context.Context, claimIdx int64, isAt
// If we're defending, then the step will be from the trace to the next one // If we're defending, then the step will be from the trace to the next one
pos = pos.MoveRight() pos = pos.MoveRight()
} }
prestate, proofData, _, err := h.correctTrace.GetStepData(ctx, game, claim, pos) prestate, proofData, preimage, err := h.correctTrace.GetStepData(ctx, game, claim, pos)
h.require.NoError(err, "Get step data") h.require.NoError(err, "Get step data")
if preimage != nil {
tx, err := h.game.Game.UpdateOracleTx(ctx, uint64(claimIdx), preimage)
h.require.NoError(err)
transactions.RequireSendTx(h.t, ctx, h.game.Client, tx, h.game.PrivKey)
}
h.game.StepFails(ctx, claimIdx, isAttack, prestate, proofData) h.game.StepFails(ctx, claimIdx, isAttack, prestate, proofData)
} }
......
...@@ -33,11 +33,11 @@ type Helper struct { ...@@ -33,11 +33,11 @@ type Helper struct {
require *require.Assertions require *require.Assertions
client *ethclient.Client client *ethclient.Client
privKey *ecdsa.PrivateKey privKey *ecdsa.PrivateKey
oracle *contracts.PreimageOracleContract oracle contracts.PreimageOracleContract
uuidProvider atomic.Int64 uuidProvider atomic.Int64
} }
func NewHelper(t *testing.T, privKey *ecdsa.PrivateKey, client *ethclient.Client, oracle *contracts.PreimageOracleContract) *Helper { func NewHelper(t *testing.T, privKey *ecdsa.PrivateKey, client *ethclient.Client, oracle contracts.PreimageOracleContract) *Helper {
return &Helper{ return &Helper{
t: t, t: t,
require: require.New(t), require: require.New(t),
......
...@@ -194,7 +194,7 @@ func TestOutputCannonDefendStep(t *testing.T) { ...@@ -194,7 +194,7 @@ func TestOutputCannonDefendStep(t *testing.T) {
game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Mallory)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Mallory))
maxDepth := game.MaxDepth(ctx) maxDepth := game.MaxDepth(ctx)
game.DefendClaim(ctx, outputRootClaim, func(claim *disputegame.ClaimHelper) *disputegame.ClaimHelper { game.DefendClaim(ctx, outputRootClaim, func(claim *disputegame.ClaimHelper) *disputegame.ClaimHelper {
...@@ -410,7 +410,7 @@ func TestOutputCannonProposedOutputRootValid(t *testing.T) { ...@@ -410,7 +410,7 @@ func TestOutputCannonProposedOutputRootValid(t *testing.T) {
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", 1) game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", 1)
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Mallory)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Mallory))
game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
...@@ -445,7 +445,7 @@ func TestOutputCannonPoisonedPostState(t *testing.T) { ...@@ -445,7 +445,7 @@ func TestOutputCannonPoisonedPostState(t *testing.T) {
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
// Root claim is dishonest // Root claim is dishonest
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0xaa}) game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0xaa})
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice))
// Honest first attack at "honest" level // Honest first attack at "honest" level
claim := correctTrace.AttackClaim(ctx, game.RootClaim(ctx)) claim := correctTrace.AttackClaim(ctx, game.RootClaim(ctx))
...@@ -509,7 +509,7 @@ func TestDisputeOutputRootBeyondProposedBlock_ValidOutputRoot(t *testing.T) { ...@@ -509,7 +509,7 @@ func TestDisputeOutputRootBeyondProposedBlock_ValidOutputRoot(t *testing.T) {
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
// Root claim is dishonest // Root claim is dishonest
game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", 1) game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", 1)
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice))
// Start the honest challenger // Start the honest challenger
game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob))
...@@ -559,7 +559,7 @@ func TestDisputeOutputRootBeyondProposedBlock_InvalidOutputRoot(t *testing.T) { ...@@ -559,7 +559,7 @@ func TestDisputeOutputRootBeyondProposedBlock_InvalidOutputRoot(t *testing.T) {
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
// Root claim is dishonest // Root claim is dishonest
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0xaa}) game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0xaa})
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice))
// Start the honest challenger // Start the honest challenger
game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob))
...@@ -610,7 +610,7 @@ func TestDisputeOutputRoot_ChangeClaimedOutputRoot(t *testing.T) { ...@@ -610,7 +610,7 @@ func TestDisputeOutputRoot_ChangeClaimedOutputRoot(t *testing.T) {
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
// Root claim is dishonest // Root claim is dishonest
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0xaa}) game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0xaa})
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice))
// Start the honest challenger // Start the honest challenger
game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob))
...@@ -700,7 +700,7 @@ func TestInvalidateUnsafeProposal(t *testing.T) { ...@@ -700,7 +700,7 @@ func TestInvalidateUnsafeProposal(t *testing.T) {
// Root claim is _dishonest_ because the required data is not available on L1 // Root claim is _dishonest_ because the required data is not available on L1
game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", blockNum, disputegame.WithUnsafeProposal()) game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", blockNum, disputegame.WithUnsafeProposal())
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice))
// Start the honest challenger // Start the honest challenger
game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Bob))
...@@ -762,7 +762,7 @@ func TestInvalidateProposalForFutureBlock(t *testing.T) { ...@@ -762,7 +762,7 @@ func TestInvalidateProposalForFutureBlock(t *testing.T) {
// Root claim is _dishonest_ because the required data is not available on L1 // Root claim is _dishonest_ because the required data is not available on L1
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", farFutureBlockNum, common.Hash{0xaa}, disputegame.WithFutureProposal()) game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", farFutureBlockNum, common.Hash{0xaa}, disputegame.WithFutureProposal())
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice))
// Start the honest challenger // Start the honest challenger
game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob))
...@@ -815,3 +815,98 @@ func TestInvalidateCorrectProposalFutureBlock(t *testing.T) { ...@@ -815,3 +815,98 @@ func TestInvalidateCorrectProposalFutureBlock(t *testing.T) {
game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon)
game.LogGameData(ctx) game.LogGameData(ctx)
} }
func TestOutputCannonHonestSafeTraceExtension_ValidRoot(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon)
ctx := context.Background()
sys, l1Client := StartFaultDisputeSystem(t)
t.Cleanup(sys.Close)
// Wait for there to be there are safe L2 blocks past the claimed safe head that have data available on L1 within
// the commitment stored in the dispute game.
safeHeadNum := uint64(3)
require.NoError(t, wait.ForSafeBlock(ctx, sys.RollupClient("sequencer"), safeHeadNum))
// Create a dispute game with an honest claim
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", safeHeadNum-1)
require.NotNil(t, game)
// Create a correct trace actor with an honest trace extending to L2 block #4
correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Mallory))
// Create a correct trace actor with an honest trace extending to L2 block #5
// Notably, L2 block #5 is a valid block within the safe chain, and the data required to reproduce it
// will be committed to within the L1 head of the dispute game.
correctTracePlus1 := game.CreateHonestActor(ctx, "sequencer",
disputegame.WithPrivKey(sys.Cfg.Secrets.Mallory),
disputegame.WithClaimedL2BlockNumber(safeHeadNum))
// Start the honest challenger. They will defend the root claim.
game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
claim := game.RootClaim(ctx)
game.ChallengeClaim(ctx, claim, func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper {
// Have to disagree with the root claim - we're trying to invalidate a valid output root
if parent.IsRootClaim() {
return parent.Attack(ctx, common.Hash{0xdd})
}
return correctTracePlus1.CounterClaim(ctx, parent)
}, func(parentClaimIdx int64) {
correctTrace.StepFails(ctx, parentClaimIdx, true)
correctTrace.StepFails(ctx, parentClaimIdx, false)
correctTracePlus1.StepFails(ctx, parentClaimIdx, true)
correctTracePlus1.StepFails(ctx, parentClaimIdx, false)
})
game.LogGameData(ctx)
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForInactivity(ctx, 10, true)
game.LogGameData(ctx)
require.EqualValues(t, gameTypes.GameStatusDefenderWon, game.Status(ctx))
}
func TestOutputCannonHonestSafeTraceExtension_InvalidRoot(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon)
ctx := context.Background()
sys, l1Client := StartFaultDisputeSystem(t)
t.Cleanup(sys.Close)
// Wait for there to be there are safe L2 blocks past the claimed safe head that have data available on L1 within
// the commitment stored in the dispute game.
safeHeadNum := uint64(2)
require.NoError(t, wait.ForSafeBlock(ctx, sys.RollupClient("sequencer"), safeHeadNum))
// Create a dispute game with a dishonest claim @ L2 block #4
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", safeHeadNum-1, common.Hash{0xCA, 0xFE})
require.NotNil(t, game)
// Create a correct trace actor with an honest trace extending to L2 block #5
// Notably, L2 block #5 is a valid block within the safe chain, and the data required to reproduce it
// will be committed to within the L1 head of the dispute game.
correctTracePlus1 := game.CreateHonestActor(ctx, "sequencer",
disputegame.WithPrivKey(sys.Cfg.Secrets.Mallory),
disputegame.WithClaimedL2BlockNumber(safeHeadNum))
// Start the honest challenger. They will challenge the root claim.
game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
claim := game.RootClaim(ctx)
game.DefendClaim(ctx, claim, func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper {
return correctTracePlus1.CounterClaim(ctx, parent)
})
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForInactivity(ctx, 10, true)
game.LogGameData(ctx)
require.EqualValues(t, gameTypes.GameStatusChallengerWon, game.Status(ctx))
}
...@@ -24,6 +24,7 @@ import ( ...@@ -24,6 +24,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e" op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/disputegame"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/ioutil"
...@@ -35,14 +36,16 @@ func TestPrecompiles(t *testing.T) { ...@@ -35,14 +36,16 @@ func TestPrecompiles(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon) op_e2e.InitParallel(t, op_e2e.UsesCannon)
// precompile test vectors copied from go-ethereum // precompile test vectors copied from go-ethereum
tests := []struct { tests := []struct {
name string name string
address common.Address address common.Address
input []byte input []byte
accelerated bool
}{ }{
{ {
name: "ecrecover", name: "ecrecover",
address: common.BytesToAddress([]byte{0x01}), address: common.BytesToAddress([]byte{0x01}),
input: common.FromHex("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"), input: common.FromHex("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"),
accelerated: true,
}, },
{ {
name: "sha256", name: "sha256",
...@@ -55,9 +58,10 @@ func TestPrecompiles(t *testing.T) { ...@@ -55,9 +58,10 @@ func TestPrecompiles(t *testing.T) {
input: common.FromHex("68656c6c6f20776f726c64"), input: common.FromHex("68656c6c6f20776f726c64"),
}, },
{ {
name: "bn256Pairing", name: "bn256Pairing",
address: common.BytesToAddress([]byte{0x08}), address: common.BytesToAddress([]byte{0x08}),
input: common.FromHex("1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa"), input: common.FromHex("1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa"),
accelerated: true,
}, },
{ {
name: "blake2F", name: "blake2F",
...@@ -65,9 +69,10 @@ func TestPrecompiles(t *testing.T) { ...@@ -65,9 +69,10 @@ func TestPrecompiles(t *testing.T) {
input: common.FromHex("0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"), input: common.FromHex("0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"),
}, },
{ {
name: "kzgPointEvaluation", name: "kzgPointEvaluation",
address: common.BytesToAddress([]byte{0x0a}), address: common.BytesToAddress([]byte{0x0a}),
input: common.FromHex("01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a"), input: common.FromHex("01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a"),
accelerated: true,
}, },
} }
for _, test := range tests { for _, test := range tests {
...@@ -134,6 +139,45 @@ func TestPrecompiles(t *testing.T) { ...@@ -134,6 +139,45 @@ func TestPrecompiles(t *testing.T) {
} }
runCannon(t, ctx, sys, inputs, "sequencer") runCannon(t, ctx, sys, inputs, "sequencer")
}) })
t.Run("DisputePrecompile-"+test.name, func(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon)
if !test.accelerated {
t.Skipf("%v is not accelerated so no preimgae to upload", test.name)
}
ctx := context.Background()
sys, _ := StartFaultDisputeSystem(t, WithBlobBatches())
defer sys.Close()
l2Seq := sys.Clients["sequencer"]
aliceKey := sys.Cfg.Secrets.Alice
receipt := op_e2e.SendL2Tx(t, sys.Cfg, l2Seq, aliceKey, func(opts *op_e2e.TxOpts) {
opts.Gas = 1_000_000
opts.ToAddr = &test.address
opts.Nonce = 0
opts.Data = test.input
})
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", receipt.BlockNumber.Uint64(), common.Hash{0x01, 0xaa})
require.NotNil(t, game)
outputRootClaim := game.DisputeLastBlock(ctx)
game.LogGameData(ctx)
honestChallenger := game.StartChallenger(ctx, "HonestActor", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
// Wait for the honest challenger to dispute the outputRootClaim. This creates a root of an execution game that we challenge by coercing
// a step at a preimage trace index.
outputRootClaim = outputRootClaim.WaitForCounterClaim(ctx)
// Now the honest challenger is positioned as the defender of the execution game
// We then move to challenge it to induce a preimage load
preimageLoadCheck := game.CreateStepPreimageLoadCheck(ctx)
game.ChallengeToPreimageLoad(ctx, outputRootClaim, sys.Cfg.Secrets.Alice, utils.FirstPreimageLoadOfType("precompile"), preimageLoadCheck, false)
// The above method already verified the image was uploaded and step called successfully
// So we don't waste time resolving the game - that's tested elsewhere.
require.NoError(t, honestChallenger.Close())
})
} }
} }
......
...@@ -95,12 +95,12 @@ func (o *CachingOracle) GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHash ...@@ -95,12 +95,12 @@ func (o *CachingOracle) GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHash
return blob return blob
} }
func (o *CachingOracle) Precompile(address common.Address, input []byte) ([]byte, bool) { func (o *CachingOracle) Precompile(address common.Address, input []byte, requiredGas uint64) ([]byte, bool) {
cacheKey := crypto.Keccak256Hash(append(address.Bytes(), input...)) cacheKey := crypto.Keccak256Hash(append(address.Bytes(), input...))
if val, ok := o.pcmps.Get(cacheKey); ok { if val, ok := o.pcmps.Get(cacheKey); ok {
return val.result, val.ok return val.result, val.ok
} }
res, ok := o.oracle.Precompile(address, input) res, ok := o.oracle.Precompile(address, input, requiredGas)
o.pcmps.Add(cacheKey, precompileResult{res, ok}) o.pcmps.Add(cacheKey, precompileResult{res, ok})
return res, ok return res, ok
} }
package l1 package l1
import ( import (
"encoding/binary"
"math/rand" "math/rand"
"testing" "testing"
...@@ -99,18 +100,21 @@ func TestCachingOracle_Precompile(t *testing.T) { ...@@ -99,18 +100,21 @@ func TestCachingOracle_Precompile(t *testing.T) {
oracle := NewCachingOracle(stub) oracle := NewCachingOracle(stub)
input := []byte{0x01, 0x02, 0x03, 0x04} input := []byte{0x01, 0x02, 0x03, 0x04}
requiredGas := uint64(100)
output := []byte{0x0a, 0x0b, 0x0c, 0x0d} output := []byte{0x0a, 0x0b, 0x0c, 0x0d}
addr := common.Address{0x1} addr := common.Address{0x1}
key := crypto.Keccak256Hash(append(append(addr.Bytes(), binary.BigEndian.AppendUint64(nil, requiredGas)...), input...))
// Initial call retrieves from the stub // Initial call retrieves from the stub
stub.PcmpResults[crypto.Keccak256Hash(append(addr.Bytes(), input...))] = output stub.PcmpResults[key] = output
actualResult, actualStatus := oracle.Precompile(addr, input) actualResult, actualStatus := oracle.Precompile(addr, input, requiredGas)
require.True(t, actualStatus) require.True(t, actualStatus)
require.EqualValues(t, output, actualResult) require.EqualValues(t, output, actualResult)
// Later calls should retrieve from cache // Later calls should retrieve from cache
delete(stub.PcmpResults, crypto.Keccak256Hash(append(addr.Bytes(), input...))) delete(stub.PcmpResults, key)
actualResult, actualStatus = oracle.Precompile(addr, input) actualResult, actualStatus = oracle.Precompile(addr, input, requiredGas)
require.True(t, actualStatus) require.True(t, actualStatus)
require.EqualValues(t, output, actualResult) require.EqualValues(t, output, actualResult)
} }
...@@ -13,6 +13,7 @@ const ( ...@@ -13,6 +13,7 @@ const (
HintL1Receipts = "l1-receipts" HintL1Receipts = "l1-receipts"
HintL1Blob = "l1-blob" HintL1Blob = "l1-blob"
HintL1Precompile = "l1-precompile" HintL1Precompile = "l1-precompile"
HintL1PrecompileV2 = "l1-precompile-v2"
) )
type BlockHeaderHint common.Hash type BlockHeaderHint common.Hash
...@@ -54,3 +55,11 @@ var _ preimage.Hint = PrecompileHint{} ...@@ -54,3 +55,11 @@ var _ preimage.Hint = PrecompileHint{}
func (l PrecompileHint) Hint() string { func (l PrecompileHint) Hint() string {
return HintL1Precompile + " " + hexutil.Encode(l) return HintL1Precompile + " " + hexutil.Encode(l)
} }
type PrecompileHintV2 []byte
var _ preimage.Hint = PrecompileHintV2{}
func (l PrecompileHintV2) Hint() string {
return HintL1PrecompileV2 + " " + hexutil.Encode(l)
}
...@@ -29,7 +29,7 @@ type Oracle interface { ...@@ -29,7 +29,7 @@ type Oracle interface {
GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHash) *eth.Blob GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHash) *eth.Blob
// Precompile retrieves the result and success indicator of a precompile call for the given input. // Precompile retrieves the result and success indicator of a precompile call for the given input.
Precompile(precompileAddress common.Address, input []byte) ([]byte, bool) Precompile(precompileAddress common.Address, input []byte, requiredGas uint64) ([]byte, bool)
} }
// PreimageOracle implements Oracle using by interfacing with the pure preimage.Oracle // PreimageOracle implements Oracle using by interfacing with the pure preimage.Oracle
...@@ -119,9 +119,10 @@ func (p *PreimageOracle) GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHas ...@@ -119,9 +119,10 @@ func (p *PreimageOracle) GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHas
return &blob return &blob
} }
func (p *PreimageOracle) Precompile(address common.Address, input []byte) ([]byte, bool) { func (p *PreimageOracle) Precompile(address common.Address, input []byte, requiredGas uint64) ([]byte, bool) {
hintBytes := append(address.Bytes(), input...) hintBytes := append(address.Bytes(), binary.BigEndian.AppendUint64(nil, requiredGas)...)
p.hint.Hint(PrecompileHint(hintBytes)) hintBytes = append(hintBytes, input...)
p.hint.Hint(PrecompileHintV2(hintBytes))
key := preimage.PrecompileKey(crypto.Keccak256Hash(hintBytes)) key := preimage.PrecompileKey(crypto.Keccak256Hash(hintBytes))
result := p.oracle.Get(key) result := p.oracle.Get(key)
if len(result) == 0 { // must contain at least the status code if len(result) == 0 { // must contain at least the status code
......
package test package test
import ( import (
"encoding/binary"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
...@@ -75,8 +76,10 @@ func (o StubOracle) GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHash) *e ...@@ -75,8 +76,10 @@ func (o StubOracle) GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHash) *e
return blob return blob
} }
func (o StubOracle) Precompile(addr common.Address, input []byte) ([]byte, bool) { func (o StubOracle) Precompile(addr common.Address, input []byte, requiredGas uint64) ([]byte, bool) {
result, ok := o.PcmpResults[crypto.Keccak256Hash(append(addr.Bytes(), input...))] arg := append(addr.Bytes(), binary.BigEndian.AppendUint64(nil, requiredGas)...)
arg = append(arg, input...)
result, ok := o.PcmpResults[crypto.Keccak256Hash(arg)]
if !ok { if !ok {
o.t.Fatalf("unknown kzg point evaluation %x", input) o.t.Fatalf("unknown kzg point evaluation %x", input)
} }
......
package l2 package l2
import ( import (
"encoding/binary"
"math/big" "math/big"
"testing" "testing"
...@@ -40,6 +41,12 @@ var ( ...@@ -40,6 +41,12 @@ var (
blobPrecompileReturnValue = common.FromHex("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001") blobPrecompileReturnValue = common.FromHex("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001")
) )
var (
ecRecoverRequiredGas uint64 = 3000
bn256PairingRequiredGas uint64 = 113000
kzgRequiredGas uint64 = 50_000
)
func TestInitialState(t *testing.T) { func TestInitialState(t *testing.T) {
blocks, chain := setupOracleBackedChain(t, 5) blocks, chain := setupOracleBackedChain(t, 5)
head := blocks[5] head := blocks[5]
...@@ -200,28 +207,32 @@ func TestGetHeaderByNumber(t *testing.T) { ...@@ -200,28 +207,32 @@ func TestGetHeaderByNumber(t *testing.T) {
func TestPrecompileOracle(t *testing.T) { func TestPrecompileOracle(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
input []byte input []byte
target common.Address target common.Address
result []byte requiredGas uint64
result []byte
}{ }{
{ {
name: "EcRecover", name: "EcRecover",
input: ecRecoverInputData, input: ecRecoverInputData,
target: common.BytesToAddress([]byte{0x1}), target: common.BytesToAddress([]byte{0x1}),
result: ecRecoverReturnValue, requiredGas: ecRecoverRequiredGas,
result: ecRecoverReturnValue,
}, },
{ {
name: "Bn256Pairing", name: "Bn256Pairing",
input: bn256PairingInputData, input: bn256PairingInputData,
target: common.BytesToAddress([]byte{0x8}), target: common.BytesToAddress([]byte{0x8}),
result: bn256PairingReturnValue, requiredGas: bn256PairingRequiredGas,
result: bn256PairingReturnValue,
}, },
{ {
name: "KZGPointEvaluation", name: "KZGPointEvaluation",
input: kzgInputData, input: kzgInputData,
target: common.BytesToAddress([]byte{0xa}), target: common.BytesToAddress([]byte{0xa}),
result: blobPrecompileReturnValue, requiredGas: kzgRequiredGas,
result: blobPrecompileReturnValue,
}, },
} }
...@@ -234,9 +245,11 @@ func TestPrecompileOracle(t *testing.T) { ...@@ -234,9 +245,11 @@ func TestPrecompileOracle(t *testing.T) {
chainCfg, blocks, oracle := setupOracle(t, blockCount, headBlockNumber, true) chainCfg, blocks, oracle := setupOracle(t, blockCount, headBlockNumber, true)
head := blocks[headBlockNumber].Hash() head := blocks[headBlockNumber].Hash()
stubOutput := eth.OutputV0{BlockHash: head} stubOutput := eth.OutputV0{BlockHash: head}
precompileOracle := new(l2test.StubPrecompileOracle) precompileOracle := l2test.NewStubPrecompileOracle(t)
arg := append(test.target.Bytes(), binary.BigEndian.AppendUint64(nil, test.requiredGas)...)
arg = append(arg, test.input...)
precompileOracle.Results = map[common.Hash]l2test.PrecompileResult{ precompileOracle.Results = map[common.Hash]l2test.PrecompileResult{
crypto.Keccak256Hash(append(test.target.Bytes(), test.input...)): {Result: test.result, Ok: true}, crypto.Keccak256Hash(arg): {Result: test.result, Ok: true},
} }
chain, err := NewOracleBackedL2Chain(logger, oracle, precompileOracle, chainCfg, common.Hash(eth.OutputRoot(&stubOutput))) chain, err := NewOracleBackedL2Chain(logger, oracle, precompileOracle, chainCfg, common.Hash(eth.OutputRoot(&stubOutput)))
require.NoError(t, err) require.NoError(t, err)
...@@ -265,7 +278,7 @@ func setupOracleBackedChainWithLowerHead(t *testing.T, blockCount int, headBlock ...@@ -265,7 +278,7 @@ func setupOracleBackedChainWithLowerHead(t *testing.T, blockCount int, headBlock
chainCfg, blocks, oracle := setupOracle(t, blockCount, headBlockNumber, false) chainCfg, blocks, oracle := setupOracle(t, blockCount, headBlockNumber, false)
head := blocks[headBlockNumber].Hash() head := blocks[headBlockNumber].Hash()
stubOutput := eth.OutputV0{BlockHash: head} stubOutput := eth.OutputV0{BlockHash: head}
precompileOracle := new(l2test.StubPrecompileOracle) precompileOracle := l2test.NewStubPrecompileOracle(t)
chain, err := NewOracleBackedL2Chain(logger, oracle, precompileOracle, chainCfg, common.Hash(eth.OutputRoot(&stubOutput))) chain, err := NewOracleBackedL2Chain(logger, oracle, precompileOracle, chainCfg, common.Hash(eth.OutputRoot(&stubOutput)))
require.NoError(t, err) require.NoError(t, err)
return blocks, chain return blocks, chain
......
...@@ -46,7 +46,7 @@ var ( ...@@ -46,7 +46,7 @@ var (
// PrecompileOracle defines the high-level API used to retrieve the result of a precompile call // PrecompileOracle defines the high-level API used to retrieve the result of a precompile call
// The caller is expected to validate the input to the precompile call // The caller is expected to validate the input to the precompile call
type PrecompileOracle interface { type PrecompileOracle interface {
Precompile(address common.Address, input []byte) ([]byte, bool) Precompile(address common.Address, input []byte, requiredGas uint64) ([]byte, bool)
} }
func CreatePrecompileOverrides(precompileOracle PrecompileOracle) vm.PrecompileOverrides { func CreatePrecompileOverrides(precompileOracle PrecompileOracle) vm.PrecompileOverrides {
...@@ -104,7 +104,7 @@ func (c *ecrecoverOracle) Run(input []byte) ([]byte, error) { ...@@ -104,7 +104,7 @@ func (c *ecrecoverOracle) Run(input []byte) ([]byte, error) {
// v needs to be at the end for libsecp256k1 // v needs to be at the end for libsecp256k1
// Modification note: below replaces the crypto.Ecrecover call // Modification note: below replaces the crypto.Ecrecover call
result, ok := c.Oracle.Precompile(ecrecoverPrecompileAddress, input) result, ok := c.Oracle.Precompile(ecrecoverPrecompileAddress, input, c.RequiredGas(input))
if !ok { if !ok {
return nil, errors.New("invalid ecrecover input") return nil, errors.New("invalid ecrecover input")
} }
...@@ -147,7 +147,7 @@ func (b *bn256PairingOracle) Run(input []byte) ([]byte, error) { ...@@ -147,7 +147,7 @@ func (b *bn256PairingOracle) Run(input []byte) ([]byte, error) {
} }
// Modification note: below replaces point verification and pairing checks // Modification note: below replaces point verification and pairing checks
// Assumes both L2 and the L1 oracle have an identical range of valid points // Assumes both L2 and the L1 oracle have an identical range of valid points
result, ok := b.Oracle.Precompile(bn256PairingPrecompileAddress, input) result, ok := b.Oracle.Precompile(bn256PairingPrecompileAddress, input, b.RequiredGas(input))
if !ok { if !ok {
return nil, errors.New("invalid bn256Pairing check") return nil, errors.New("invalid bn256Pairing check")
} }
...@@ -214,7 +214,7 @@ func (b *kzgPointEvaluationOracle) Run(input []byte) ([]byte, error) { ...@@ -214,7 +214,7 @@ func (b *kzgPointEvaluationOracle) Run(input []byte) ([]byte, error) {
copy(proof[:], input[144:]) copy(proof[:], input[144:])
// Modification note: below replaces the kzg4844.VerifyProof call // Modification note: below replaces the kzg4844.VerifyProof call
result, ok := b.Oracle.Precompile(kzgPointEvaluationPrecompileAddress, input) result, ok := b.Oracle.Precompile(kzgPointEvaluationPrecompileAddress, input, b.RequiredGas(input))
if !ok { if !ok {
return nil, fmt.Errorf("%w: invalid KZG point evaluation", errBlobVerifyKZGProof) return nil, fmt.Errorf("%w: invalid KZG point evaluation", errBlobVerifyKZGProof)
} }
......
package test package test
import ( import (
"encoding/binary"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
...@@ -130,15 +131,21 @@ type StubPrecompileOracle struct { ...@@ -130,15 +131,21 @@ type StubPrecompileOracle struct {
Calls int Calls int
} }
func NewStubPrecompileOracle(t *testing.T) *StubPrecompileOracle {
return &StubPrecompileOracle{t: t, Results: make(map[common.Hash]PrecompileResult)}
}
type PrecompileResult struct { type PrecompileResult struct {
Result []byte Result []byte
Ok bool Ok bool
} }
func (o *StubPrecompileOracle) Precompile(address common.Address, input []byte) ([]byte, bool) { func (o *StubPrecompileOracle) Precompile(address common.Address, input []byte, requiredGas uint64) ([]byte, bool) {
result, ok := o.Results[crypto.Keccak256Hash(append(address.Bytes(), input...))] arg := append(address.Bytes(), binary.BigEndian.AppendUint64(nil, requiredGas)...)
arg = append(arg, input...)
result, ok := o.Results[crypto.Keccak256Hash(arg)]
if !ok { if !ok {
o.t.Fatalf("no value for point evaluation %v", input) o.t.Fatalf("no value for point evaluation %x required gas %v", input, requiredGas)
} }
o.Calls++ o.Calls++
return result.Result, result.Ok return result.Result, result.Ok
......
...@@ -205,6 +205,36 @@ func (p *Prefetcher) prefetch(ctx context.Context, hint string) error { ...@@ -205,6 +205,36 @@ func (p *Prefetcher) prefetch(ctx context.Context, hint string) error {
return err return err
} }
return p.kvStore.Put(preimage.PrecompileKey(inputHash).PreimageKey(), result) return p.kvStore.Put(preimage.PrecompileKey(inputHash).PreimageKey(), result)
case l1.HintL1PrecompileV2:
if len(hintBytes) < 28 {
return fmt.Errorf("invalid precompile hint: %x", hint)
}
precompileAddress := common.BytesToAddress(hintBytes[:20])
// requiredGas := hintBytes[20:28] - unused by the host. Since the client already validates gas requirements.
// The requiredGas is only used by the L1 PreimageOracle to enforce complete precompile execution.
// For extra safety, avoid accelerating unexpected precompiles
if !slices.Contains(acceleratedPrecompiles, precompileAddress) {
return fmt.Errorf("unsupported precompile address: %s", precompileAddress)
}
// NOTE: We use the precompiled contracts from Cancun because it's the only set that contains the addresses of all accelerated precompiles
// We assume the precompile Run function behavior does not change across EVM upgrades.
// As such, we must not rely on upgrade-specific behavior such as precompile.RequiredGas.
precompile := getPrecompiledContract(precompileAddress)
// KZG Point Evaluation precompile also verifies its input
result, err := precompile.Run(hintBytes[28:])
if err == nil {
result = append(precompileSuccess[:], result...)
} else {
result = append(precompileFailure[:], result...)
}
inputHash := crypto.Keccak256Hash(hintBytes)
// Put the input preimage so it can be loaded later
if err := p.kvStore.Put(preimage.Keccak256Key(inputHash).PreimageKey(), hintBytes); err != nil {
return err
}
return p.kvStore.Put(preimage.PrecompileKey(inputHash).PreimageKey(), result)
case l2.HintL2BlockHeader, l2.HintL2Transactions: case l2.HintL2BlockHeader, l2.HintL2Transactions:
if len(hintBytes) != 32 { if len(hintBytes) != 32 {
return fmt.Errorf("invalid L2 header/tx hint: %x", hint) return fmt.Errorf("invalid L2 header/tx hint: %x", hint)
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/binary"
"fmt"
"math/rand" "math/rand"
"testing" "testing"
...@@ -25,6 +26,11 @@ import ( ...@@ -25,6 +26,11 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/testutils"
) )
var (
ecRecoverInput = common.FromHex("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549")
kzgPointEvalInput = common.FromHex("01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a")
)
func TestNoHint(t *testing.T) { func TestNoHint(t *testing.T) {
t.Run("NotFound", func(t *testing.T) { t.Run("NotFound", func(t *testing.T) {
prefetcher, _, _, _, _ := createPrefetcher(t) prefetcher, _, _, _, _ := createPrefetcher(t)
...@@ -221,9 +227,6 @@ func TestFetchL1Blob(t *testing.T) { ...@@ -221,9 +227,6 @@ func TestFetchL1Blob(t *testing.T) {
} }
func TestFetchPrecompileResult(t *testing.T) { func TestFetchPrecompileResult(t *testing.T) {
ecRecoverInput := common.FromHex("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549")
kzgPointEvalInput := common.FromHex("01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a")
failure := []byte{0} failure := []byte{0}
success := []byte{1} success := []byte{1}
...@@ -239,6 +242,18 @@ func TestFetchPrecompileResult(t *testing.T) { ...@@ -239,6 +242,18 @@ func TestFetchPrecompileResult(t *testing.T) {
input: ecRecoverInput, input: ecRecoverInput,
result: append(success, common.FromHex("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b")...), result: append(success, common.FromHex("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b")...),
}, },
{
name: "KzgPointEvaluation-Valid",
addr: common.BytesToAddress([]byte{0xa}),
input: kzgPointEvalInput,
result: append(success, common.FromHex("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001")...),
},
{
name: "KzgPointEvaluation-Invalid",
addr: common.BytesToAddress([]byte{0xa}),
input: []byte{0x0},
result: failure,
},
{ {
name: "Bn256Pairing-Valid", name: "Bn256Pairing-Valid",
addr: common.BytesToAddress([]byte{0x8}), addr: common.BytesToAddress([]byte{0x8}),
...@@ -251,17 +266,88 @@ func TestFetchPrecompileResult(t *testing.T) { ...@@ -251,17 +266,88 @@ func TestFetchPrecompileResult(t *testing.T) {
input: []byte{0x1}, input: []byte{0x1},
result: failure, result: failure,
}, },
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
prefetcher, _, _, _, _ := createPrefetcher(t)
oracle := newLegacyPrecompileOracle(asOracleFn(t, prefetcher), asHinter(t, prefetcher))
result, ok := oracle.Precompile(test.addr, test.input)
require.Equal(t, test.result[0] == 1, ok)
require.EqualValues(t, test.result[1:], result)
key := crypto.Keccak256Hash(append(test.addr.Bytes(), test.input...))
val, err := prefetcher.kvStore.Get(preimage.Keccak256Key(key).PreimageKey())
require.NoError(t, err)
require.NotEmpty(t, val)
val, err = prefetcher.kvStore.Get(preimage.PrecompileKey(key).PreimageKey())
require.NoError(t, err)
require.EqualValues(t, test.result, val)
})
}
t.Run("Already Known", func(t *testing.T) {
input := []byte("test input")
addr := common.BytesToAddress([]byte{0x1})
result := []byte{0x1}
prefetcher, _, _, _, kv := createPrefetcher(t)
err := kv.Put(preimage.PrecompileKey(crypto.Keccak256Hash(append(addr.Bytes(), input...))).PreimageKey(), append([]byte{1}, result...))
require.NoError(t, err)
oracle := newLegacyPrecompileOracle(asOracleFn(t, prefetcher), asHinter(t, prefetcher))
actualResult, status := oracle.Precompile(addr, input)
require.EqualValues(t, result, actualResult)
require.True(t, status)
})
}
func TestFetchPrecompileResultV2(t *testing.T) {
failure := []byte{0}
success := []byte{1}
tests := []struct {
name string
addr common.Address
input []byte
requiredGas uint64
result []byte
}{
{
name: "EcRecover-Valid",
addr: common.BytesToAddress([]byte{0x1}),
input: ecRecoverInput,
requiredGas: 3000,
result: append(success, common.FromHex("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b")...),
},
{ {
name: "KzgPointEvaluation-Valid", name: "Bn256Pairing-Valid",
addr: common.BytesToAddress([]byte{0xa}), addr: common.BytesToAddress([]byte{0x8}),
input: kzgPointEvalInput, input: []byte{}, // empty is valid
result: append(success, common.FromHex("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001")...), requiredGas: 6000,
result: append(success, common.FromHex("0000000000000000000000000000000000000000000000000000000000000001")...),
}, },
{ {
name: "KzgPointEvaluation-Invalid", name: "Bn256Pairing-Invalid",
addr: common.BytesToAddress([]byte{0xa}), addr: common.BytesToAddress([]byte{0x8}),
input: []byte{0x0}, input: []byte{0x1},
result: failure, requiredGas: 6000,
result: failure,
},
{
name: "KzgPointEvaluation-Valid",
addr: common.BytesToAddress([]byte{0xa}),
input: kzgPointEvalInput,
requiredGas: 50_000,
result: append(success, common.FromHex("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001")...),
},
{
name: "KzgPointEvaluation-Invalid",
addr: common.BytesToAddress([]byte{0xa}),
input: []byte{0x0},
requiredGas: 50_000,
result: failure,
}, },
} }
for _, test := range tests { for _, test := range tests {
...@@ -270,11 +356,11 @@ func TestFetchPrecompileResult(t *testing.T) { ...@@ -270,11 +356,11 @@ func TestFetchPrecompileResult(t *testing.T) {
prefetcher, _, _, _, _ := createPrefetcher(t) prefetcher, _, _, _, _ := createPrefetcher(t)
oracle := l1.NewPreimageOracle(asOracleFn(t, prefetcher), asHinter(t, prefetcher)) oracle := l1.NewPreimageOracle(asOracleFn(t, prefetcher), asHinter(t, prefetcher))
result, ok := oracle.Precompile(test.addr, test.input) result, ok := oracle.Precompile(test.addr, test.input, test.requiredGas)
require.Equal(t, test.result[0] == 1, ok) require.Equal(t, test.result[0] == 1, ok)
require.EqualValues(t, test.result[1:], result) require.EqualValues(t, test.result[1:], result)
key := crypto.Keccak256Hash(append(test.addr.Bytes(), test.input...)) key := crypto.Keccak256Hash(append(append(test.addr.Bytes(), binary.BigEndian.AppendUint64(nil, test.requiredGas)...), test.input...))
val, err := prefetcher.kvStore.Get(preimage.Keccak256Key(key).PreimageKey()) val, err := prefetcher.kvStore.Get(preimage.Keccak256Key(key).PreimageKey())
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, val) require.NotEmpty(t, val)
...@@ -287,19 +373,35 @@ func TestFetchPrecompileResult(t *testing.T) { ...@@ -287,19 +373,35 @@ func TestFetchPrecompileResult(t *testing.T) {
t.Run("Already Known", func(t *testing.T) { t.Run("Already Known", func(t *testing.T) {
input := []byte("test input") input := []byte("test input")
requiredGas := uint64(3000)
addr := common.BytesToAddress([]byte{0x1}) addr := common.BytesToAddress([]byte{0x1})
result := []byte{0x1} result := []byte{0x1}
prefetcher, _, _, _, kv := createPrefetcher(t) prefetcher, _, _, _, kv := createPrefetcher(t)
err := kv.Put(preimage.PrecompileKey(crypto.Keccak256Hash(append(addr.Bytes(), input...))).PreimageKey(), append([]byte{1}, result...)) keyArg := append(addr.Bytes(), binary.BigEndian.AppendUint64(nil, requiredGas)...)
keyArg = append(keyArg, input...)
err := kv.Put(preimage.PrecompileKey(crypto.Keccak256Hash(keyArg)).PreimageKey(), append([]byte{1}, result...))
require.NoError(t, err) require.NoError(t, err)
oracle := l1.NewPreimageOracle(asOracleFn(t, prefetcher), asHinter(t, prefetcher)) oracle := l1.NewPreimageOracle(asOracleFn(t, prefetcher), asHinter(t, prefetcher))
actualResult, status := oracle.Precompile(addr, input) actualResult, status := oracle.Precompile(addr, input, requiredGas)
require.EqualValues(t, actualResult, result) require.EqualValues(t, actualResult, result)
require.True(t, status) require.True(t, status)
}) })
} }
func TestUnsupportedPrecompile(t *testing.T) {
prefetcher, _, _, _, _ := createPrefetcher(t)
oracleFn := func(t *testing.T, prefetcher *Prefetcher) preimage.OracleFn {
return func(key preimage.Key) []byte {
_, err := prefetcher.GetPreimage(context.Background(), key.PreimageKey())
require.ErrorContains(t, err, "unsupported precompile address")
return []byte{1}
}
}
oracle := newLegacyPrecompileOracle(oracleFn(t, prefetcher), asHinter(t, prefetcher))
oracle.Precompile(common.HexToAddress("0xdead"), nil)
}
func TestRestrictedPrecompileContracts(t *testing.T) { func TestRestrictedPrecompileContracts(t *testing.T) {
for _, addr := range acceleratedPrecompiles { for _, addr := range acceleratedPrecompiles {
require.NotNil(t, getPrecompiledContract(addr)) require.NotNil(t, getPrecompiledContract(addr))
...@@ -596,3 +698,28 @@ func assertReceiptsEqual(t *testing.T, expectedRcpt types.Receipts, actualRcpt t ...@@ -596,3 +698,28 @@ func assertReceiptsEqual(t *testing.T, expectedRcpt types.Receipts, actualRcpt t
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
} }
} }
// legacyOracleImpl is a wrapper around the new preimage.Oracle interface that uses the legacy preimage hint API.
// It's used to test backwards-compatibility with clients using legacy preimage hints.
type legacyPrecompileOracle struct {
oracle preimage.Oracle
hint preimage.Hinter
}
func newLegacyPrecompileOracle(raw preimage.Oracle, hint preimage.Hinter) *legacyPrecompileOracle {
return &legacyPrecompileOracle{
oracle: raw,
hint: hint,
}
}
func (o *legacyPrecompileOracle) Precompile(address common.Address, input []byte) ([]byte, bool) {
hintBytes := append(address.Bytes(), input...)
o.hint.Hint(l1.PrecompileHint(hintBytes))
key := preimage.PrecompileKey(crypto.Keccak256Hash(hintBytes))
result := o.oracle.Get(key)
if len(result) == 0 { // must contain at least the status code
panic(fmt.Errorf("unexpected precompile oracle behavior, got result: %x", result))
}
return result[1:], result[0] == 1
}
...@@ -5,7 +5,7 @@ SCRIPTS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) ...@@ -5,7 +5,7 @@ SCRIPTS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
COMPAT_DIR="${SCRIPTS_DIR}/../temp/compat" COMPAT_DIR="${SCRIPTS_DIR}/../temp/compat"
TESTNAME="${1?Must specify compat file to run}" TESTNAME="${1?Must specify compat file to run}"
BASEURL="${2:-https://github.com/ethereum-optimism/chain-test-data/releases/download/2024-03-14.3}" BASEURL="${2:-https://github.com/ethereum-optimism/chain-test-data/releases/download/2024-08-02}"
URL="${BASEURL}/${TESTNAME}.tar.bz" URL="${BASEURL}/${TESTNAME}.tar.bz"
......
...@@ -56,7 +56,7 @@ func (c *expectedCall) Matches(rpcMethod string, args ...interface{}) error { ...@@ -56,7 +56,7 @@ func (c *expectedCall) Matches(rpcMethod string, args ...interface{}) error {
return fmt.Errorf("expected input to have at least 4 bytes but was %v", len(data)) return fmt.Errorf("expected input to have at least 4 bytes but was %v", len(data))
} }
if !slices.Equal(c.abiMethod.ID, data[:4]) { if !slices.Equal(c.abiMethod.ID, data[:4]) {
return fmt.Errorf("expected abi method ID %x but was %x", c.abiMethod.ID, data[:4]) return fmt.Errorf("expected abi method ID %x but was %v", c.abiMethod.ID, data[:4])
} }
if !slices.Equal(c.packedArgs, data[4:]) { if !slices.Equal(c.packedArgs, data[4:]) {
return fmt.Errorf("expected args %x but was %x", c.packedArgs, data[4:]) return fmt.Errorf("expected args %x but was %x", c.packedArgs, data[4:])
......
...@@ -59,7 +59,7 @@ func (r *RpcStub) findExpectedCall(rpcMethod string, args ...interface{}) Expect ...@@ -59,7 +59,7 @@ func (r *RpcStub) findExpectedCall(rpcMethod string, args ...interface{}) Expect
if err := call.Matches(rpcMethod, args...); err == nil { if err := call.Matches(rpcMethod, args...); err == nil {
return call return call
} else { } else {
matchResults += fmt.Sprintf("%v: %v", call, err) matchResults += fmt.Sprintf("%v: %v\n", call, err)
} }
} }
require.Failf(r.t, "No matching expected calls.", matchResults) require.Failf(r.t, "No matching expected calls.", matchResults)
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
"requiredProtocolVersion": "0x0000000000000000000000000000000000000004000000000000000000000001", "requiredProtocolVersion": "0x0000000000000000000000000000000000000004000000000000000000000001",
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000004000000000000000000000001", "recommendedProtocolVersion": "0x0000000000000000000000000000000000000004000000000000000000000001",
"fundDevAccounts": false, "fundDevAccounts": false,
"faultGameAbsolutePrestate": "0x0385c3f8ee78491001d92b90b07d0cf387b7b52ab9b83b4d87c994e92cf823ba", "faultGameAbsolutePrestate": "0x030de10d9da911a2b180ecfae2aeaba8758961fc28262ce989458c6f9a547922",
"faultGameMaxDepth": 73, "faultGameMaxDepth": 73,
"faultGameClockExtension": 10800, "faultGameClockExtension": 10800,
"faultGameMaxClockDuration": 302400, "faultGameMaxClockDuration": 302400,
......
...@@ -208,6 +208,32 @@ library ChainAssertions { ...@@ -208,6 +208,32 @@ library ChainAssertions {
} }
} }
/// @notice Asserts that the permissioned DelayedWETH is setup correctly
function checkPermissionedDelayedWETH(
Types.ContractSet memory _contracts,
DeployConfig _cfg,
bool _isProxy,
address _expectedOwner
)
internal
view
{
console.log("Running chain assertions on the permissioned DelayedWETH");
DelayedWETH weth = DelayedWETH(payable(_contracts.PermissionedDelayedWETH));
// Check that the contract is initialized
assertSlotValueIsOne({ _contractAddress: address(weth), _slot: 0, _offset: 0 });
if (_isProxy) {
require(weth.owner() == _expectedOwner);
require(weth.delay() == _cfg.faultGameWithdrawalDelay());
require(weth.config() == SuperchainConfig(_contracts.SuperchainConfig));
} else {
require(weth.owner() == _expectedOwner);
require(weth.delay() == _cfg.faultGameWithdrawalDelay());
}
}
/// @notice Asserts that the L2OutputOracle is setup correctly /// @notice Asserts that the L2OutputOracle is setup correctly
function checkL2OutputOracle( function checkL2OutputOracle(
Types.ContractSet memory _contracts, Types.ContractSet memory _contracts,
......
...@@ -92,6 +92,8 @@ contract FPACOPS is Deploy, StdAssertions { ...@@ -92,6 +92,8 @@ contract FPACOPS is Deploy, StdAssertions {
function initializeAnchorStateRegistryProxy() internal broadcast { function initializeAnchorStateRegistryProxy() internal broadcast {
console.log("Initializing AnchorStateRegistryProxy with AnchorStateRegistry."); console.log("Initializing AnchorStateRegistryProxy with AnchorStateRegistry.");
address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy");
SuperchainConfig superchainConfig = SuperchainConfig(superchainConfigProxy);
AnchorStateRegistry.StartingAnchorRoot[] memory roots = new AnchorStateRegistry.StartingAnchorRoot[](2); AnchorStateRegistry.StartingAnchorRoot[] memory roots = new AnchorStateRegistry.StartingAnchorRoot[](2);
roots[0] = AnchorStateRegistry.StartingAnchorRoot({ roots[0] = AnchorStateRegistry.StartingAnchorRoot({
...@@ -111,7 +113,8 @@ contract FPACOPS is Deploy, StdAssertions { ...@@ -111,7 +113,8 @@ contract FPACOPS is Deploy, StdAssertions {
address asrProxy = mustGetAddress("AnchorStateRegistryProxy"); address asrProxy = mustGetAddress("AnchorStateRegistryProxy");
Proxy(payable(asrProxy)).upgradeToAndCall( Proxy(payable(asrProxy)).upgradeToAndCall(
mustGetAddress("AnchorStateRegistry"), abi.encodeCall(AnchorStateRegistry.initialize, (roots)) mustGetAddress("AnchorStateRegistry"),
abi.encodeCall(AnchorStateRegistry.initialize, (roots, superchainConfig))
); );
} }
......
This diff is collapsed.
...@@ -23,4 +23,9 @@ cannon-prestate: # Generate the cannon prestate, and tar the `op-program` + `can ...@@ -23,4 +23,9 @@ cannon-prestate: # Generate the cannon prestate, and tar the `op-program` + `can
.PHONY: deploy-fresh .PHONY: deploy-fresh
deploy-fresh: cannon-prestate # Deploy a fresh version of the FPAC contracts. Pass `--broadcast` to send to the network. deploy-fresh: cannon-prestate # Deploy a fresh version of the FPAC contracts. Pass `--broadcast` to send to the network.
forge script FPACOPS.sol --sig "deployFPAC(address,address)" $(proxy-admin) $(system-owner-safe) --chain $(chain) -vvv $(args) forge script FPACOPS.s.sol --sig "deployFPAC(address,address,address)" $(proxy-admin) $(system-owner-safe) $(superchain-config-proxy) --chain $(chain) -vvv $(args)
# TODO: Convert this whole file to a justfile
.PHONY: deploy-upgrade
deploy-upgrade: cannon-prestate # Deploy upgraded FP contracts. Pass `--broadcast` to send to the network.
forge script FPACOPS2.s.sol --sig "deployFPAC2(address,address,address,address,address)" $(proxy-admin) $(system-owner-safe) $(superchain-config-proxy) $(dispute-game-factory-proxy) $(anchor-state-registry-proxy) --chain $(chain) -vvv $(args)
...@@ -9,6 +9,7 @@ library Types { ...@@ -9,6 +9,7 @@ library Types {
address L2OutputOracle; address L2OutputOracle;
address DisputeGameFactory; address DisputeGameFactory;
address DelayedWETH; address DelayedWETH;
address PermissionedDelayedWETH;
address AnchorStateRegistry; address AnchorStateRegistry;
address OptimismMintableERC20Factory; address OptimismMintableERC20Factory;
address OptimismPortal; address OptimismPortal;
......
This diff is collapsed.
...@@ -71,6 +71,24 @@ ...@@ -71,6 +71,24 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [
{
"internalType": "contract AnchorStateRegistry",
"name": "_registry",
"type": "address"
},
{
"internalType": "contract IFaultDisputeGame",
"name": "_game",
"type": "address"
}
],
"name": "setAnchorState",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment