Commit db61d2bb authored by Inphi's avatar Inphi Committed by Adrian Sutton

Add gas input to precompile pre-images (#186) (#252)

* contracts: Add gas input to precompile pre-images (#186)

Also update the cannon evm tests to use the new precompile preimage scheme.

---------
Co-authored-by: default avatarAdrian Sutton <adrian@oplabs.co>

* op-challenger: Support uploading data in new format. (#188)

* op-program: Add required gas to precompile oracle key (#176)

* op-challenger: Support multiple versions of the preimage oracle contract

---------
Co-authored-by: default avatarAdrian Sutton <adrian@oplabs.co>

---------
Co-authored-by: default avatarinphi <mlaw2501@gmail.com>

---------
Co-authored-by: default avatarAdrian Sutton <adrian@oplabs.co>
parent f54765ce
...@@ -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)
......
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 {
......
...@@ -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.
......
...@@ -670,7 +670,7 @@ func (g *OutputGameHelper) UploadPreimage(ctx context.Context, data *types.Preim ...@@ -670,7 +670,7 @@ func (g *OutputGameHelper) UploadPreimage(ctx context.Context, data *types.Preim
g.Require.NoError(err) 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
......
...@@ -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),
......
...@@ -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)
......
...@@ -148,8 +148,8 @@ ...@@ -148,8 +148,8 @@
"sourceCodeHash": "0x115bd6a4c4d77ed210dfd468675b409fdae9f79b932063c138f0765ba9063462" "sourceCodeHash": "0x115bd6a4c4d77ed210dfd468675b409fdae9f79b932063c138f0765ba9063462"
}, },
"src/cannon/PreimageOracle.sol": { "src/cannon/PreimageOracle.sol": {
"initCodeHash": "0x32c08eaf9873874cd56ce5e0518fc2c0b033bf56395d006578a1d8e1e813a624", "initCodeHash": "0x2cfd9b2a6d9aa864696209cf34194d495111200b7c1c7a1196e022f2045e90f7",
"sourceCodeHash": "0x4907fc15e4ff74f749365e2a95e4c3fe43e5b4d364c5e051275e8e016e205a45" "sourceCodeHash": "0xe912619c3aeebec4634e7484f162daa35a43368e335d7f98a0154549edb00d65"
}, },
"src/dispute/AnchorStateRegistry.sol": { "src/dispute/AnchorStateRegistry.sol": {
"initCodeHash": "0x0305c21e50829b9e07d43358d8c2c82f1449534c90d4391400d46e76d0503a49", "initCodeHash": "0x0305c21e50829b9e07d43358d8c2c82f1449534c90d4391400d46e76d0503a49",
......
...@@ -54,6 +54,19 @@ ...@@ -54,6 +54,19 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "PRECOMPILE_CALL_RESERVED_GAS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
...@@ -378,6 +391,11 @@ ...@@ -378,6 +391,11 @@
"name": "_precompile", "name": "_precompile",
"type": "address" "type": "address"
}, },
{
"internalType": "uint64",
"name": "_requiredGas",
"type": "uint64"
},
{ {
"internalType": "bytes", "internalType": "bytes",
"name": "_input", "name": "_input",
......
...@@ -27,10 +27,12 @@ contract PreimageOracle is IPreimageOracle, ISemver { ...@@ -27,10 +27,12 @@ contract PreimageOracle is IPreimageOracle, ISemver {
uint256 public constant KECCAK_TREE_DEPTH = 16; uint256 public constant KECCAK_TREE_DEPTH = 16;
/// @notice The maximum number of keccak blocks that can fit into the merkle tree. /// @notice The maximum number of keccak blocks that can fit into the merkle tree.
uint256 public constant MAX_LEAF_COUNT = 2 ** KECCAK_TREE_DEPTH - 1; uint256 public constant MAX_LEAF_COUNT = 2 ** KECCAK_TREE_DEPTH - 1;
/// @notice The reserved gas for precompile call setup.
uint256 public constant PRECOMPILE_CALL_RESERVED_GAS = 100_000;
/// @notice The semantic version of the Preimage Oracle contract. /// @notice The semantic version of the Preimage Oracle contract.
/// @custom:semver 1.0.1 /// @custom:semver 1.1.1
string public constant version = "1.0.1"; string public constant version = "1.1.1";
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// Authorized Preimage Parts // // Authorized Preimage Parts //
...@@ -332,7 +334,14 @@ contract PreimageOracle is IPreimageOracle, ISemver { ...@@ -332,7 +334,14 @@ contract PreimageOracle is IPreimageOracle, ISemver {
} }
/// @inheritdoc IPreimageOracle /// @inheritdoc IPreimageOracle
function loadPrecompilePreimagePart(uint256 _partOffset, address _precompile, bytes calldata _input) external { function loadPrecompilePreimagePart(
uint256 _partOffset,
address _precompile,
uint64 _requiredGas,
bytes calldata _input
)
external
{
bytes32 res; bytes32 res;
bytes32 key; bytes32 key;
bytes32 part; bytes32 part;
...@@ -341,21 +350,32 @@ contract PreimageOracle is IPreimageOracle, ISemver { ...@@ -341,21 +350,32 @@ contract PreimageOracle is IPreimageOracle, ISemver {
// we leave solidity slots 0x40 and 0x60 untouched, and everything after as scratch-memory. // we leave solidity slots 0x40 and 0x60 untouched, and everything after as scratch-memory.
let ptr := 0x80 let ptr := 0x80
// copy precompile address and input into memory // copy precompile address, requiredGas, and input into memory to compute the key
// len(sig) + len(_partOffset) + address-offset-in-slot mstore(ptr, shl(96, _precompile))
calldatacopy(ptr, 48, 20) mstore(add(ptr, 20), shl(192, _requiredGas))
calldatacopy(add(20, ptr), _input.offset, _input.length) calldatacopy(add(28, ptr), _input.offset, _input.length)
// compute the hash // compute the hash
let h := keccak256(ptr, add(20, _input.length)) let h := keccak256(ptr, add(28, _input.length))
// mask out prefix byte, replace with type 6 byte // mask out prefix byte, replace with type 6 byte
key := or(and(h, not(shl(248, 0xFF))), shl(248, 0x06)) key := or(and(h, not(shl(248, 0xFF))), shl(248, 0x06))
// Check if the precompile call has at least the required gas.
// This assumes there are no further memory expansion costs until after the staticall on the precompile
// Also assumes that the gas expended in setting up the staticcall is less than PRECOMPILE_CALL_RESERVED_GAS
// require(gas() >= (requiredGas * 64 / 63) + reservedGas)
if lt(mul(gas(), 63), add(mul(_requiredGas, 64), mul(PRECOMPILE_CALL_RESERVED_GAS, 63))) {
// Store "NotEnoughGas()"
mstore(0, 0xdd629f86)
revert(0x1c, 4)
}
// Call the precompile to get the result. // Call the precompile to get the result.
// SAFETY: Given the above gas check, the staticall cannot fail due to insufficient gas.
res := res :=
staticcall( staticcall(
gas(), // forward all gas gas(), // forward all gas
_precompile, _precompile,
add(20, ptr), // input ptr add(28, ptr), // input ptr
_input.length, _input.length,
0x0, // Unused as we don't copy anything 0x0, // Unused as we don't copy anything
0x00 // don't copy anything 0x00 // don't copy anything
......
...@@ -75,6 +75,13 @@ interface IPreimageOracle { ...@@ -75,6 +75,13 @@ interface IPreimageOracle {
/// The preimage key is `6 ++ keccak256(precompile ++ input)[1:]`. /// The preimage key is `6 ++ keccak256(precompile ++ input)[1:]`.
/// @param _partOffset The offset of the precompile result being loaded. /// @param _partOffset The offset of the precompile result being loaded.
/// @param _precompile The precompile address /// @param _precompile The precompile address
/// @param _requiredGas The gas required to fully execute an L1 precompile.
/// @param _input The input to the precompile call. /// @param _input The input to the precompile call.
function loadPrecompilePreimagePart(uint256 _partOffset, address _precompile, bytes calldata _input) external; function loadPrecompilePreimagePart(
uint256 _partOffset,
address _precompile,
uint64 _requiredGas,
bytes calldata _input
)
external;
} }
...@@ -4,6 +4,9 @@ pragma solidity 0.8.15; ...@@ -4,6 +4,9 @@ pragma solidity 0.8.15;
/// @notice Thrown when a passed part offset is out of bounds. /// @notice Thrown when a passed part offset is out of bounds.
error PartOffsetOOB(); error PartOffsetOOB();
/// @notice Thrown when insufficient gas is provided when loading precompile preimages.
error NotEnoughGas();
/// @notice Thrown when a merkle proof fails to verify. /// @notice Thrown when a merkle proof fails to verify.
error InvalidProof(); error InvalidProof();
......
...@@ -178,8 +178,9 @@ contract PreimageOracle_Test is Test { ...@@ -178,8 +178,9 @@ contract PreimageOracle_Test is Test {
bytes memory input = hex"deadbeef"; bytes memory input = hex"deadbeef";
uint256 offset = 0; uint256 offset = 0;
address precompile = address(bytes20(uint160(0x02))); // sha256 address precompile = address(bytes20(uint160(0x02))); // sha256
bytes32 key = precompilePreimageKey(precompile, input); uint64 gas = 72;
oracle.loadPrecompilePreimagePart(offset, precompile, input); bytes32 key = precompilePreimageKey(precompile, gas, input);
oracle.loadPrecompilePreimagePart(offset, precompile, gas, input);
bytes32 part = oracle.preimageParts(key, offset); bytes32 part = oracle.preimageParts(key, offset);
// size prefix - 1-byte result + 32-byte sha return data // size prefix - 1-byte result + 32-byte sha return data
...@@ -203,8 +204,9 @@ contract PreimageOracle_Test is Test { ...@@ -203,8 +204,9 @@ contract PreimageOracle_Test is Test {
bytes memory input = hex"deadbeef"; bytes memory input = hex"deadbeef";
uint256 offset = 9; uint256 offset = 9;
address precompile = address(bytes20(uint160(0x02))); // sha256 address precompile = address(bytes20(uint160(0x02))); // sha256
bytes32 key = precompilePreimageKey(precompile, input); uint64 gas = 72;
oracle.loadPrecompilePreimagePart(offset, precompile, input); bytes32 key = precompilePreimageKey(precompile, gas, input);
oracle.loadPrecompilePreimagePart(offset, precompile, gas, input);
bytes32 part = oracle.preimageParts(key, offset); bytes32 part = oracle.preimageParts(key, offset);
// 32-byte sha return data // 32-byte sha return data
...@@ -224,8 +226,9 @@ contract PreimageOracle_Test is Test { ...@@ -224,8 +226,9 @@ contract PreimageOracle_Test is Test {
bytes memory input = new bytes(193); // invalid input to induce a failed precompile call bytes memory input = new bytes(193); // invalid input to induce a failed precompile call
uint256 offset = 0; uint256 offset = 0;
address precompile = address(bytes20(uint160(0x08))); // bn256Pairing address precompile = address(bytes20(uint160(0x08))); // bn256Pairing
bytes32 key = precompilePreimageKey(precompile, input); uint64 gas = 72;
oracle.loadPrecompilePreimagePart(offset, precompile, input); bytes32 key = precompilePreimageKey(precompile, gas, input);
oracle.loadPrecompilePreimagePart(offset, precompile, gas, input);
bytes32 part = oracle.preimageParts(key, offset); bytes32 part = oracle.preimageParts(key, offset);
// size prefix - 1-byte result + 0-byte sha return data // size prefix - 1-byte result + 0-byte sha return data
...@@ -249,8 +252,9 @@ contract PreimageOracle_Test is Test { ...@@ -249,8 +252,9 @@ contract PreimageOracle_Test is Test {
bytes memory input = hex"deadbeef"; bytes memory input = hex"deadbeef";
uint256 offset = 41; // 8-byte prefix + 1-byte result + 32-byte sha return data uint256 offset = 41; // 8-byte prefix + 1-byte result + 32-byte sha return data
address precompile = address(bytes20(uint160(0x02))); // sha256 address precompile = address(bytes20(uint160(0x02))); // sha256
uint64 gas = 72;
vm.expectRevert(PartOffsetOOB.selector); vm.expectRevert(PartOffsetOOB.selector);
oracle.loadPrecompilePreimagePart(offset, precompile, input); oracle.loadPrecompilePreimagePart(offset, precompile, gas, input);
} }
/// @notice Tests that a global precompile result cannot be set with an out-of-bounds offset. /// @notice Tests that a global precompile result cannot be set with an out-of-bounds offset.
...@@ -258,8 +262,34 @@ contract PreimageOracle_Test is Test { ...@@ -258,8 +262,34 @@ contract PreimageOracle_Test is Test {
bytes memory input = hex"deadbeef"; bytes memory input = hex"deadbeef";
uint256 offset = 42; uint256 offset = 42;
address precompile = address(bytes20(uint160(0x02))); // sha256 address precompile = address(bytes20(uint160(0x02))); // sha256
uint64 gas = 72;
vm.expectRevert(PartOffsetOOB.selector); vm.expectRevert(PartOffsetOOB.selector);
oracle.loadPrecompilePreimagePart(offset, precompile, input); oracle.loadPrecompilePreimagePart(offset, precompile, gas, input);
}
/// @notice Tests that a global precompile load succeeds on a variety of gas inputs.
function testFuzz_loadPrecompilePreimagePart_withVaryingGas_succeeds(uint64 _gas) public {
uint64 requiredGas = 100_000;
bytes memory input = hex"deadbeef";
address precompile = address(uint160(0xdeadbeef));
vm.mockCall(precompile, input, hex"abba");
uint256 offset = 0;
uint64 minGas = uint64(bound(_gas, requiredGas * 3, 20_000_000));
vm.expectCallMinGas(precompile, 0, requiredGas, input);
oracle.loadPrecompilePreimagePart{ gas: minGas }(offset, precompile, requiredGas, input);
}
/// @notice Tests that a global precompile load succeeds on insufficient gas.
function test_loadPrecompilePreimagePart_withInsufficientGas_reverts() public {
uint64 requiredGas = 1_000_000;
bytes memory input = hex"deadbeef";
uint256 offset = 0;
address precompile = address(uint160(0xdeadbeef));
// This gas is sufficient to reach the gas checks in `loadPrecompilePreimagePart` but not enough to pass those
// checks
uint64 insufficientGas = requiredGas * 63 / 64;
vm.expectRevert(NotEnoughGas.selector);
oracle.loadPrecompilePreimagePart{ gas: insufficientGas }(offset, precompile, requiredGas, input);
} }
} }
...@@ -1374,9 +1404,9 @@ function _setStatusByte(bytes32 _hash, uint8 _status) pure returns (bytes32 out_ ...@@ -1374,9 +1404,9 @@ function _setStatusByte(bytes32 _hash, uint8 _status) pure returns (bytes32 out_
} }
/// @notice Computes a precompile key for a given precompile address and input. /// @notice Computes a precompile key for a given precompile address and input.
function precompilePreimageKey(address _precompile, bytes memory _input) pure returns (bytes32 key_) { function precompilePreimageKey(address _precompile, uint64 _gas, bytes memory _input) pure returns (bytes32 key_) {
bytes memory p = abi.encodePacked(_precompile, _input); bytes memory p = abi.encodePacked(_precompile, _gas, _input);
uint256 sz = 20 + _input.length; uint256 sz = 20 + 8 + _input.length;
assembly { assembly {
let h := keccak256(add(0x20, p), sz) let h := keccak256(add(0x20, p), sz)
// Mask out prefix byte, replace with type 6 byte // Mask out prefix byte, replace with type 6 byte
......
...@@ -27,13 +27,13 @@ contract DeploymentSummary is DeploymentSummaryCode { ...@@ -27,13 +27,13 @@ contract DeploymentSummary is DeploymentSummaryCode {
address internal constant l1StandardBridgeProxyAddress = 0x20A42a5a785622c6Ba2576B2D6e924aA82BFA11D; address internal constant l1StandardBridgeProxyAddress = 0x20A42a5a785622c6Ba2576B2D6e924aA82BFA11D;
address internal constant l2OutputOracleAddress = 0x19652082F846171168Daf378C4fD3ee85a0D4A60; address internal constant l2OutputOracleAddress = 0x19652082F846171168Daf378C4fD3ee85a0D4A60;
address internal constant l2OutputOracleProxyAddress = 0x39Af23E00F1e662025aA01b0cEdA19542B78DF99; address internal constant l2OutputOracleProxyAddress = 0x39Af23E00F1e662025aA01b0cEdA19542B78DF99;
address internal constant mipsAddress = 0x4e6F9442A0B4810fD9Ce770740e1d33e9B848555; address internal constant mipsAddress = 0x3629E5c6FCCaA06C25aD8Fe5c9de82d7A39E9Df8;
address internal constant optimismMintableERC20FactoryAddress = 0x39Aea2Dd53f2d01c15877aCc2791af6BDD7aD567; address internal constant optimismMintableERC20FactoryAddress = 0x39Aea2Dd53f2d01c15877aCc2791af6BDD7aD567;
address internal constant optimismMintableERC20FactoryProxyAddress = 0xc7B87b2b892EA5C3CfF47168881FE168C00377FB; address internal constant optimismMintableERC20FactoryProxyAddress = 0xc7B87b2b892EA5C3CfF47168881FE168C00377FB;
address internal constant optimismPortalAddress = 0xbdD90485FCbcac869D5b5752179815a3103d8131; address internal constant optimismPortalAddress = 0xbdD90485FCbcac869D5b5752179815a3103d8131;
address internal constant optimismPortal2Address = 0xae5DadFc48928543f706a9E6Ce25c682aaD2b63b; address internal constant optimismPortal2Address = 0xae5DadFc48928543f706a9E6Ce25c682aaD2b63b;
address internal constant optimismPortalProxyAddress = 0x1c23A6d89F95ef3148BCDA8E242cAb145bf9c0E4; address internal constant optimismPortalProxyAddress = 0x1c23A6d89F95ef3148BCDA8E242cAb145bf9c0E4;
address internal constant preimageOracleAddress = 0xD408EC347b99Ed3Aa5E65A986adFCA7ef5E36582; address internal constant preimageOracleAddress = 0xF8a536Ff3Ee2c4adD28f34F471A427E539e4fCb1;
address internal constant protocolVersionsAddress = 0xfbfD64a6C0257F613feFCe050Aa30ecC3E3d7C3F; address internal constant protocolVersionsAddress = 0xfbfD64a6C0257F613feFCe050Aa30ecC3E3d7C3F;
address internal constant protocolVersionsProxyAddress = 0x4C52a6277b1B84121b3072C0c92b6Be0b7CC10F1; address internal constant protocolVersionsProxyAddress = 0x4C52a6277b1B84121b3072C0c92b6Be0b7CC10F1;
address internal constant proxyAdminAddress = 0x62c20Aa1e0272312BC100b4e23B4DC1Ed96dD7D1; address internal constant proxyAdminAddress = 0x62c20Aa1e0272312BC100b4e23B4DC1Ed96dD7D1;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -27,13 +27,13 @@ contract DeploymentSummaryFaultProofs is DeploymentSummaryFaultProofsCode { ...@@ -27,13 +27,13 @@ contract DeploymentSummaryFaultProofs is DeploymentSummaryFaultProofsCode {
address internal constant l1StandardBridgeProxyAddress = 0x20A42a5a785622c6Ba2576B2D6e924aA82BFA11D; address internal constant l1StandardBridgeProxyAddress = 0x20A42a5a785622c6Ba2576B2D6e924aA82BFA11D;
address internal constant l2OutputOracleAddress = 0x19652082F846171168Daf378C4fD3ee85a0D4A60; address internal constant l2OutputOracleAddress = 0x19652082F846171168Daf378C4fD3ee85a0D4A60;
address internal constant l2OutputOracleProxyAddress = 0x39Af23E00F1e662025aA01b0cEdA19542B78DF99; address internal constant l2OutputOracleProxyAddress = 0x39Af23E00F1e662025aA01b0cEdA19542B78DF99;
address internal constant mipsAddress = 0x4e6F9442A0B4810fD9Ce770740e1d33e9B848555; address internal constant mipsAddress = 0x3629E5c6FCCaA06C25aD8Fe5c9de82d7A39E9Df8;
address internal constant optimismMintableERC20FactoryAddress = 0x39Aea2Dd53f2d01c15877aCc2791af6BDD7aD567; address internal constant optimismMintableERC20FactoryAddress = 0x39Aea2Dd53f2d01c15877aCc2791af6BDD7aD567;
address internal constant optimismMintableERC20FactoryProxyAddress = 0xc7B87b2b892EA5C3CfF47168881FE168C00377FB; address internal constant optimismMintableERC20FactoryProxyAddress = 0xc7B87b2b892EA5C3CfF47168881FE168C00377FB;
address internal constant optimismPortalAddress = 0xbdD90485FCbcac869D5b5752179815a3103d8131; address internal constant optimismPortalAddress = 0xbdD90485FCbcac869D5b5752179815a3103d8131;
address internal constant optimismPortal2Address = 0xae5DadFc48928543f706a9E6Ce25c682aaD2b63b; address internal constant optimismPortal2Address = 0xae5DadFc48928543f706a9E6Ce25c682aaD2b63b;
address internal constant optimismPortalProxyAddress = 0x1c23A6d89F95ef3148BCDA8E242cAb145bf9c0E4; address internal constant optimismPortalProxyAddress = 0x1c23A6d89F95ef3148BCDA8E242cAb145bf9c0E4;
address internal constant preimageOracleAddress = 0xD408EC347b99Ed3Aa5E65A986adFCA7ef5E36582; address internal constant preimageOracleAddress = 0xF8a536Ff3Ee2c4adD28f34F471A427E539e4fCb1;
address internal constant protocolVersionsAddress = 0xfbfD64a6C0257F613feFCe050Aa30ecC3E3d7C3F; address internal constant protocolVersionsAddress = 0xfbfD64a6C0257F613feFCe050Aa30ecC3E3d7C3F;
address internal constant protocolVersionsProxyAddress = 0x4C52a6277b1B84121b3072C0c92b6Be0b7CC10F1; address internal constant protocolVersionsProxyAddress = 0x4C52a6277b1B84121b3072C0c92b6Be0b7CC10F1;
address internal constant proxyAdminAddress = 0x62c20Aa1e0272312BC100b4e23B4DC1Ed96dD7D1; address internal constant proxyAdminAddress = 0x62c20Aa1e0272312BC100b4e23B4DC1Ed96dD7D1;
......
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