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 {
......
[
{
"inputs": [
{
"internalType": "uint256",
"name": "_minProposalSize",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_challengePeriod",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "KECCAK_TREE_DEPTH",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_LEAF_COUNT",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MIN_BOND_SIZE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_uuid",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_inputStartBlock",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "_input",
"type": "bytes"
},
{
"internalType": "bytes32[]",
"name": "_stateCommitments",
"type": "bytes32[]"
},
{
"internalType": "bool",
"name": "_finalize",
"type": "bool"
}
],
"name": "addLeavesLPP",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_claimant",
"type": "address"
},
{
"internalType": "uint256",
"name": "_uuid",
"type": "uint256"
},
{
"components": [
{
"internalType": "bytes",
"name": "input",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "stateCommitment",
"type": "bytes32"
}
],
"internalType": "struct PreimageOracle.Leaf",
"name": "_postState",
"type": "tuple"
},
{
"internalType": "bytes32[]",
"name": "_postStateProof",
"type": "bytes32[]"
}
],
"name": "challengeFirstLPP",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_claimant",
"type": "address"
},
{
"internalType": "uint256",
"name": "_uuid",
"type": "uint256"
},
{
"components": [
{
"internalType": "uint64[25]",
"name": "state",
"type": "uint64[25]"
}
],
"internalType": "struct LibKeccak.StateMatrix",
"name": "_stateMatrix",
"type": "tuple"
},
{
"components": [
{
"internalType": "bytes",
"name": "input",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "stateCommitment",
"type": "bytes32"
}
],
"internalType": "struct PreimageOracle.Leaf",
"name": "_preState",
"type": "tuple"
},
{
"internalType": "bytes32[]",
"name": "_preStateProof",
"type": "bytes32[]"
},
{
"components": [
{
"internalType": "bytes",
"name": "input",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "stateCommitment",
"type": "bytes32"
}
],
"internalType": "struct PreimageOracle.Leaf",
"name": "_postState",
"type": "tuple"
},
{
"internalType": "bytes32[]",
"name": "_postStateProof",
"type": "bytes32[]"
}
],
"name": "challengeLPP",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "challengePeriod",
"outputs": [
{
"internalType": "uint256",
"name": "challengePeriod_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
},
{
"internalType": "uint256",
"name": "_uuid",
"type": "uint256"
}
],
"name": "getTreeRootLPP",
"outputs": [
{
"internalType": "bytes32",
"name": "treeRoot_",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_uuid",
"type": "uint256"
},
{
"internalType": "uint32",
"name": "_partOffset",
"type": "uint32"
},
{
"internalType": "uint32",
"name": "_claimedSize",
"type": "uint32"
}
],
"name": "initLPP",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_z",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_y",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "_commitment",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "_proof",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "_partOffset",
"type": "uint256"
}
],
"name": "loadBlobPreimagePart",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_partOffset",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "_preimage",
"type": "bytes"
}
],
"name": "loadKeccak256PreimagePart",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_ident",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "_localContext",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "_word",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "_size",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_partOffset",
"type": "uint256"
}
],
"name": "loadLocalData",
"outputs": [
{
"internalType": "bytes32",
"name": "key_",
"type": "bytes32"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_partOffset",
"type": "uint256"
},
{
"internalType": "address",
"name": "_precompile",
"type": "address"
},
{
"internalType": "bytes",
"name": "_input",
"type": "bytes"
}
],
"name": "loadPrecompilePreimagePart",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_partOffset",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "_preimage",
"type": "bytes"
}
],
"name": "loadSha256PreimagePart",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "minProposalSize",
"outputs": [
{
"internalType": "uint256",
"name": "minProposalSize_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "preimageLengths",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "preimagePartOk",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "preimageParts",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "proposalBlocks",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_claimant",
"type": "address"
},
{
"internalType": "uint256",
"name": "_uuid",
"type": "uint256"
}
],
"name": "proposalBlocksLen",
"outputs": [
{
"internalType": "uint256",
"name": "len_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "proposalBonds",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "proposalBranches",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "proposalCount",
"outputs": [
{
"internalType": "uint256",
"name": "count_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "proposalMetadata",
"outputs": [
{
"internalType": "LPPMetaData",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "proposalParts",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "proposals",
"outputs": [
{
"internalType": "address",
"name": "claimant",
"type": "address"
},
{
"internalType": "uint256",
"name": "uuid",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_key",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "_offset",
"type": "uint256"
}
],
"name": "readPreimage",
"outputs": [
{
"internalType": "bytes32",
"name": "dat_",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "datLen_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_claimant",
"type": "address"
},
{
"internalType": "uint256",
"name": "_uuid",
"type": "uint256"
},
{
"components": [
{
"internalType": "uint64[25]",
"name": "state",
"type": "uint64[25]"
}
],
"internalType": "struct LibKeccak.StateMatrix",
"name": "_stateMatrix",
"type": "tuple"
},
{
"components": [
{
"internalType": "bytes",
"name": "input",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "stateCommitment",
"type": "bytes32"
}
],
"internalType": "struct PreimageOracle.Leaf",
"name": "_preState",
"type": "tuple"
},
{
"internalType": "bytes32[]",
"name": "_preStateProof",
"type": "bytes32[]"
},
{
"components": [
{
"internalType": "bytes",
"name": "input",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "stateCommitment",
"type": "bytes32"
}
],
"internalType": "struct PreimageOracle.Leaf",
"name": "_postState",
"type": "tuple"
},
{
"internalType": "bytes32[]",
"name": "_postStateProof",
"type": "bytes32[]"
}
],
"name": "squeezeLPP",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "zeroHashes",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ActiveProposal",
"type": "error"
},
{
"inputs": [],
"name": "AlreadyFinalized",
"type": "error"
},
{
"inputs": [],
"name": "BadProposal",
"type": "error"
},
{
"inputs": [],
"name": "BondTransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientBond",
"type": "error"
},
{
"inputs": [],
"name": "InvalidInputSize",
"type": "error"
},
{
"inputs": [],
"name": "InvalidPreimage",
"type": "error"
},
{
"inputs": [],
"name": "InvalidProof",
"type": "error"
},
{
"inputs": [],
"name": "NotEOA",
"type": "error"
},
{
"inputs": [],
"name": "NotInitialized",
"type": "error"
},
{
"inputs": [],
"name": "PartOffsetOOB",
"type": "error"
},
{
"inputs": [],
"name": "PostStateMatches",
"type": "error"
},
{
"inputs": [],
"name": "StatesNotContiguous",
"type": "error"
},
{
"inputs": [],
"name": "TreeSizeOverflow",
"type": "error"
},
{
"inputs": [],
"name": "WrongStartingBlock",
"type": "error"
}
]
\ No newline at end of file
...@@ -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
......
...@@ -60,8 +60,8 @@ type libKeccakStateMatrix struct { ...@@ -60,8 +60,8 @@ type libKeccakStateMatrix struct {
State [25]uint64 State [25]uint64
} }
// PreimageOracleContract is a binding that works with contracts implementing the IPreimageOracle interface // PreimageOracleContractLatest is a binding that works with contracts implementing the IPreimageOracle interface
type PreimageOracleContract struct { type PreimageOracleContractLatest struct {
addr common.Address addr common.Address
multiCaller *batching.MultiCaller multiCaller *batching.MultiCaller
contract *batching.BoundContract contract *batching.BoundContract
...@@ -83,21 +83,36 @@ func toPreimageOracleLeaf(l keccakTypes.Leaf) preimageOracleLeaf { ...@@ -83,21 +83,36 @@ func toPreimageOracleLeaf(l keccakTypes.Leaf) preimageOracleLeaf {
} }
} }
func NewPreimageOracleContract(addr common.Address, caller *batching.MultiCaller) *PreimageOracleContract { func NewPreimageOracleContract(ctx context.Context, addr common.Address, caller *batching.MultiCaller) (PreimageOracleContract, error) {
oracleAbi := snapshots.LoadPreimageOracleABI() oracleAbi := snapshots.LoadPreimageOracleABI()
return &PreimageOracleContract{ var builder VersionedBuilder[PreimageOracleContract]
addr: addr, builder.AddVersion(1, 0, func() (PreimageOracleContract, error) {
multiCaller: caller, legacyAbi := mustParseAbi(preimageOracleAbi100)
contract: batching.NewBoundContract(oracleAbi, addr), return &PreimageOracleContract100{
} PreimageOracleContractLatest{
addr: addr,
multiCaller: caller,
contract: batching.NewBoundContract(legacyAbi, addr),
},
}, nil
})
return builder.Build(ctx, caller, oracleAbi, addr, func() (PreimageOracleContract, error) {
return &PreimageOracleContractLatest{
addr: addr,
multiCaller: caller,
contract: batching.NewBoundContract(oracleAbi, addr),
}, nil
})
} }
func (c *PreimageOracleContract) Addr() common.Address { func (c *PreimageOracleContractLatest) Addr() common.Address {
return c.addr return c.addr
} }
func (c *PreimageOracleContract) AddGlobalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error) { func (c *PreimageOracleContractLatest) AddGlobalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
if len(data.OracleKey) == 0 { if len(data.OracleKey) == 0 {
return txmgr.TxCandidate{}, ErrInvalidPreimageKey return txmgr.TxCandidate{}, ErrInvalidPreimageKey
} }
...@@ -121,6 +136,7 @@ func (c *PreimageOracleContract) AddGlobalDataTx(data *types.PreimageOracleData) ...@@ -121,6 +136,7 @@ func (c *PreimageOracleContract) AddGlobalDataTx(data *types.PreimageOracleData)
call := c.contract.Call(methodLoadPrecompilePreimagePart, call := c.contract.Call(methodLoadPrecompilePreimagePart,
new(big.Int).SetUint64(uint64(data.OracleOffset)), new(big.Int).SetUint64(uint64(data.OracleOffset)),
data.GetPrecompileAddress(), data.GetPrecompileAddress(),
data.GetPrecompileRequiredGas(),
data.GetPrecompileInput()) data.GetPrecompileInput())
return call.ToTxCandidate() return call.ToTxCandidate()
default: default:
...@@ -128,7 +144,7 @@ func (c *PreimageOracleContract) AddGlobalDataTx(data *types.PreimageOracleData) ...@@ -128,7 +144,7 @@ func (c *PreimageOracleContract) AddGlobalDataTx(data *types.PreimageOracleData)
} }
} }
func (c *PreimageOracleContract) InitLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) (txmgr.TxCandidate, error) { func (c *PreimageOracleContractLatest) InitLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) (txmgr.TxCandidate, error) {
bond, err := c.GetMinBondLPP(context.Background()) bond, err := c.GetMinBondLPP(context.Background())
if err != nil { if err != nil {
return txmgr.TxCandidate{}, fmt.Errorf("failed to get min bond for large preimage proposal: %w", err) return txmgr.TxCandidate{}, fmt.Errorf("failed to get min bond for large preimage proposal: %w", err)
...@@ -142,13 +158,13 @@ func (c *PreimageOracleContract) InitLargePreimage(uuid *big.Int, partOffset uin ...@@ -142,13 +158,13 @@ func (c *PreimageOracleContract) InitLargePreimage(uuid *big.Int, partOffset uin
return candidate, nil return candidate, nil
} }
func (c *PreimageOracleContract) AddLeaves(uuid *big.Int, startingBlockIndex *big.Int, input []byte, commitments []common.Hash, finalize bool) (txmgr.TxCandidate, error) { func (c *PreimageOracleContractLatest) AddLeaves(uuid *big.Int, startingBlockIndex *big.Int, input []byte, commitments []common.Hash, finalize bool) (txmgr.TxCandidate, error) {
call := c.contract.Call(methodAddLeavesLPP, uuid, startingBlockIndex, input, commitments, finalize) call := c.contract.Call(methodAddLeavesLPP, uuid, startingBlockIndex, input, commitments, finalize)
return call.ToTxCandidate() return call.ToTxCandidate()
} }
// MinLargePreimageSize returns the minimum size of a large preimage. // MinLargePreimageSize returns the minimum size of a large preimage.
func (c *PreimageOracleContract) MinLargePreimageSize(ctx context.Context) (uint64, error) { func (c *PreimageOracleContractLatest) MinLargePreimageSize(ctx context.Context) (uint64, error) {
result, err := c.multiCaller.SingleCall(ctx, rpcblock.Latest, c.contract.Call(methodMinProposalSize)) result, err := c.multiCaller.SingleCall(ctx, rpcblock.Latest, c.contract.Call(methodMinProposalSize))
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to fetch min lpp size bytes: %w", err) return 0, fmt.Errorf("failed to fetch min lpp size bytes: %w", err)
...@@ -157,7 +173,7 @@ func (c *PreimageOracleContract) MinLargePreimageSize(ctx context.Context) (uint ...@@ -157,7 +173,7 @@ func (c *PreimageOracleContract) MinLargePreimageSize(ctx context.Context) (uint
} }
// ChallengePeriod returns the challenge period for large preimages. // ChallengePeriod returns the challenge period for large preimages.
func (c *PreimageOracleContract) ChallengePeriod(ctx context.Context) (uint64, error) { func (c *PreimageOracleContractLatest) ChallengePeriod(ctx context.Context) (uint64, error) {
if period := c.challengePeriod.Load(); period != 0 { if period := c.challengePeriod.Load(); period != 0 {
return period, nil return period, nil
} }
...@@ -170,7 +186,7 @@ func (c *PreimageOracleContract) ChallengePeriod(ctx context.Context) (uint64, e ...@@ -170,7 +186,7 @@ func (c *PreimageOracleContract) ChallengePeriod(ctx context.Context) (uint64, e
return period, nil return period, nil
} }
func (c *PreimageOracleContract) CallSqueeze( func (c *PreimageOracleContractLatest) CallSqueeze(
ctx context.Context, ctx context.Context,
claimant common.Address, claimant common.Address,
uuid *big.Int, uuid *big.Int,
...@@ -188,7 +204,7 @@ func (c *PreimageOracleContract) CallSqueeze( ...@@ -188,7 +204,7 @@ func (c *PreimageOracleContract) CallSqueeze(
return nil return nil
} }
func (c *PreimageOracleContract) Squeeze( func (c *PreimageOracleContractLatest) Squeeze(
claimant common.Address, claimant common.Address,
uuid *big.Int, uuid *big.Int,
prestateMatrix keccakTypes.StateSnapshot, prestateMatrix keccakTypes.StateSnapshot,
...@@ -214,7 +230,7 @@ func abiEncodeSnapshot(packedState keccakTypes.StateSnapshot) libKeccakStateMatr ...@@ -214,7 +230,7 @@ func abiEncodeSnapshot(packedState keccakTypes.StateSnapshot) libKeccakStateMatr
return libKeccakStateMatrix{State: packedState} return libKeccakStateMatrix{State: packedState}
} }
func (c *PreimageOracleContract) GetActivePreimages(ctx context.Context, blockHash common.Hash) ([]keccakTypes.LargePreimageMetaData, error) { func (c *PreimageOracleContractLatest) GetActivePreimages(ctx context.Context, blockHash common.Hash) ([]keccakTypes.LargePreimageMetaData, error) {
block := rpcblock.ByHash(blockHash) block := rpcblock.ByHash(blockHash)
results, err := batching.ReadArray(ctx, c.multiCaller, block, c.contract.Call(methodProposalCount), func(i *big.Int) *batching.ContractCall { results, err := batching.ReadArray(ctx, c.multiCaller, block, c.contract.Call(methodProposalCount), func(i *big.Int) *batching.ContractCall {
return c.contract.Call(methodProposals, i) return c.contract.Call(methodProposals, i)
...@@ -231,7 +247,7 @@ func (c *PreimageOracleContract) GetActivePreimages(ctx context.Context, blockHa ...@@ -231,7 +247,7 @@ func (c *PreimageOracleContract) GetActivePreimages(ctx context.Context, blockHa
return c.GetProposalMetadata(ctx, block, idents...) return c.GetProposalMetadata(ctx, block, idents...)
} }
func (c *PreimageOracleContract) GetProposalMetadata(ctx context.Context, block rpcblock.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error) { func (c *PreimageOracleContractLatest) GetProposalMetadata(ctx context.Context, block rpcblock.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error) {
var calls []batching.Call var calls []batching.Call
for _, ident := range idents { for _, ident := range idents {
calls = append(calls, c.contract.Call(methodProposalMetadata, ident.Claimant, ident.UUID)) calls = append(calls, c.contract.Call(methodProposalMetadata, ident.Claimant, ident.UUID))
...@@ -256,7 +272,7 @@ func (c *PreimageOracleContract) GetProposalMetadata(ctx context.Context, block ...@@ -256,7 +272,7 @@ func (c *PreimageOracleContract) GetProposalMetadata(ctx context.Context, block
return proposals, nil return proposals, nil
} }
func (c *PreimageOracleContract) GetProposalTreeRoot(ctx context.Context, block rpcblock.Block, ident keccakTypes.LargePreimageIdent) (common.Hash, error) { func (c *PreimageOracleContractLatest) GetProposalTreeRoot(ctx context.Context, block rpcblock.Block, ident keccakTypes.LargePreimageIdent) (common.Hash, error) {
call := c.contract.Call(methodGetTreeRootLPP, ident.Claimant, ident.UUID) call := c.contract.Call(methodGetTreeRootLPP, ident.Claimant, ident.UUID)
result, err := c.multiCaller.SingleCall(ctx, block, call) result, err := c.multiCaller.SingleCall(ctx, block, call)
if err != nil { if err != nil {
...@@ -265,7 +281,7 @@ func (c *PreimageOracleContract) GetProposalTreeRoot(ctx context.Context, block ...@@ -265,7 +281,7 @@ func (c *PreimageOracleContract) GetProposalTreeRoot(ctx context.Context, block
return result.GetHash(0), nil return result.GetHash(0), nil
} }
func (c *PreimageOracleContract) GetInputDataBlocks(ctx context.Context, block rpcblock.Block, ident keccakTypes.LargePreimageIdent) ([]uint64, error) { func (c *PreimageOracleContractLatest) GetInputDataBlocks(ctx context.Context, block rpcblock.Block, ident keccakTypes.LargePreimageIdent) ([]uint64, error) {
results, err := batching.ReadArray(ctx, c.multiCaller, block, results, err := batching.ReadArray(ctx, c.multiCaller, block,
c.contract.Call(methodProposalBlocksLen, ident.Claimant, ident.UUID), c.contract.Call(methodProposalBlocksLen, ident.Claimant, ident.UUID),
func(i *big.Int) *batching.ContractCall { func(i *big.Int) *batching.ContractCall {
...@@ -285,7 +301,7 @@ func (c *PreimageOracleContract) GetInputDataBlocks(ctx context.Context, block r ...@@ -285,7 +301,7 @@ func (c *PreimageOracleContract) GetInputDataBlocks(ctx context.Context, block r
// An [ErrInvalidAddLeavesCall] error is returned if the call is not a valid call to addLeavesLPP. // An [ErrInvalidAddLeavesCall] error is returned if the call is not a valid call to addLeavesLPP.
// Otherwise, the uuid and input data is returned. The raw data supplied is returned so long as it can be parsed. // Otherwise, the uuid and input data is returned. The raw data supplied is returned so long as it can be parsed.
// Specifically the length of the input data is not validated to ensure it is consistent with the number of commitments. // Specifically the length of the input data is not validated to ensure it is consistent with the number of commitments.
func (c *PreimageOracleContract) DecodeInputData(data []byte) (*big.Int, keccakTypes.InputData, error) { func (c *PreimageOracleContractLatest) DecodeInputData(data []byte) (*big.Int, keccakTypes.InputData, error) {
method, args, err := c.contract.DecodeCall(data) method, args, err := c.contract.DecodeCall(data)
if errors.Is(err, batching.ErrUnknownMethod) { if errors.Is(err, batching.ErrUnknownMethod) {
return nil, keccakTypes.InputData{}, ErrInvalidAddLeavesCall return nil, keccakTypes.InputData{}, ErrInvalidAddLeavesCall
...@@ -312,7 +328,7 @@ func (c *PreimageOracleContract) DecodeInputData(data []byte) (*big.Int, keccakT ...@@ -312,7 +328,7 @@ func (c *PreimageOracleContract) DecodeInputData(data []byte) (*big.Int, keccakT
}, nil }, nil
} }
func (c *PreimageOracleContract) GlobalDataExists(ctx context.Context, data *types.PreimageOracleData) (bool, error) { func (c *PreimageOracleContractLatest) GlobalDataExists(ctx context.Context, data *types.PreimageOracleData) (bool, error) {
call := c.contract.Call(methodPreimagePartOk, common.Hash(data.OracleKey), new(big.Int).SetUint64(uint64(data.OracleOffset))) call := c.contract.Call(methodPreimagePartOk, common.Hash(data.OracleKey), new(big.Int).SetUint64(uint64(data.OracleOffset)))
results, err := c.multiCaller.SingleCall(ctx, rpcblock.Latest, call) results, err := c.multiCaller.SingleCall(ctx, rpcblock.Latest, call)
if err != nil { if err != nil {
...@@ -321,7 +337,7 @@ func (c *PreimageOracleContract) GlobalDataExists(ctx context.Context, data *typ ...@@ -321,7 +337,7 @@ func (c *PreimageOracleContract) GlobalDataExists(ctx context.Context, data *typ
return results.GetBool(0), nil return results.GetBool(0), nil
} }
func (c *PreimageOracleContract) ChallengeTx(ident keccakTypes.LargePreimageIdent, challenge keccakTypes.Challenge) (txmgr.TxCandidate, error) { func (c *PreimageOracleContractLatest) ChallengeTx(ident keccakTypes.LargePreimageIdent, challenge keccakTypes.Challenge) (txmgr.TxCandidate, error) {
var call *batching.ContractCall var call *batching.ContractCall
if challenge.Prestate == (keccakTypes.Leaf{}) { if challenge.Prestate == (keccakTypes.Leaf{}) {
call = c.contract.Call( call = c.contract.Call(
...@@ -344,7 +360,7 @@ func (c *PreimageOracleContract) ChallengeTx(ident keccakTypes.LargePreimageIden ...@@ -344,7 +360,7 @@ func (c *PreimageOracleContract) ChallengeTx(ident keccakTypes.LargePreimageIden
return call.ToTxCandidate() return call.ToTxCandidate()
} }
func (c *PreimageOracleContract) GetMinBondLPP(ctx context.Context) (*big.Int, error) { func (c *PreimageOracleContractLatest) GetMinBondLPP(ctx context.Context) (*big.Int, error) {
if bondSize := c.minBondSizeLPP.Load(); bondSize != 0 { if bondSize := c.minBondSizeLPP.Load(); bondSize != 0 {
return big.NewInt(int64(bondSize)), nil return big.NewInt(int64(bondSize)), nil
} }
...@@ -357,7 +373,7 @@ func (c *PreimageOracleContract) GetMinBondLPP(ctx context.Context) (*big.Int, e ...@@ -357,7 +373,7 @@ func (c *PreimageOracleContract) GetMinBondLPP(ctx context.Context) (*big.Int, e
return period, nil return period, nil
} }
func (c *PreimageOracleContract) decodePreimageIdent(result *batching.CallResult) keccakTypes.LargePreimageIdent { func (c *PreimageOracleContractLatest) decodePreimageIdent(result *batching.CallResult) keccakTypes.LargePreimageIdent {
return keccakTypes.LargePreimageIdent{ return keccakTypes.LargePreimageIdent{
Claimant: result.GetAddress(0), Claimant: result.GetAddress(0),
UUID: result.GetBigInt(1), UUID: result.GetBigInt(1),
...@@ -428,3 +444,39 @@ func (m *metadata) setCountered(value bool) { ...@@ -428,3 +444,39 @@ func (m *metadata) setCountered(value bool) {
func (m *metadata) countered() bool { func (m *metadata) countered() bool {
return binary.BigEndian.Uint64(m[24:32]) != 0 return binary.BigEndian.Uint64(m[24:32]) != 0
} }
type PreimageOracleContract interface {
Addr() common.Address
AddGlobalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error)
InitLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) (txmgr.TxCandidate, error)
AddLeaves(uuid *big.Int, startingBlockIndex *big.Int, input []byte, commitments []common.Hash, finalize bool) (txmgr.TxCandidate, error)
MinLargePreimageSize(ctx context.Context) (uint64, error)
ChallengePeriod(ctx context.Context) (uint64, error)
CallSqueeze(
ctx context.Context,
claimant common.Address,
uuid *big.Int,
prestateMatrix keccakTypes.StateSnapshot,
preState keccakTypes.Leaf,
preStateProof merkle.Proof,
postState keccakTypes.Leaf,
postStateProof merkle.Proof,
) error
Squeeze(
claimant common.Address,
uuid *big.Int,
prestateMatrix keccakTypes.StateSnapshot,
preState keccakTypes.Leaf,
preStateProof merkle.Proof,
postState keccakTypes.Leaf,
postStateProof merkle.Proof,
) (txmgr.TxCandidate, error)
GetActivePreimages(ctx context.Context, blockHash common.Hash) ([]keccakTypes.LargePreimageMetaData, error)
GetProposalMetadata(ctx context.Context, block rpcblock.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error)
GetProposalTreeRoot(ctx context.Context, block rpcblock.Block, ident keccakTypes.LargePreimageIdent) (common.Hash, error)
GetInputDataBlocks(ctx context.Context, block rpcblock.Block, ident keccakTypes.LargePreimageIdent) ([]uint64, error)
DecodeInputData(data []byte) (*big.Int, keccakTypes.InputData, error)
GlobalDataExists(ctx context.Context, data *types.PreimageOracleData) (bool, error)
ChallengeTx(ident keccakTypes.LargePreimageIdent, challenge keccakTypes.Challenge) (txmgr.TxCandidate, error)
GetMinBondLPP(ctx context.Context) (*big.Int, error)
}
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()
}
...@@ -17,272 +17,367 @@ import ( ...@@ -17,272 +17,367 @@ import (
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test" batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/testutils"
"github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
const (
oracle100 = "1.0.0"
oracleLatest = "1.1.0"
)
var oracleVersions = []contractVersion{
{
version: oracle100,
loadAbi: func() *abi.ABI {
return mustParseAbi(preimageOracleAbi100)
},
},
{
version: oracleLatest,
loadAbi: snapshots.LoadPreimageOracleABI,
},
}
func TestPreimageOracleContract_AddGlobalDataTx(t *testing.T) { func TestPreimageOracleContract_AddGlobalDataTx(t *testing.T) {
t.Run("UnknownType", func(t *testing.T) { for _, version := range oracleVersions {
_, oracle := setupPreimageOracleTest(t) version := version
data := types.NewPreimageOracleData(common.Hash{0xcc}.Bytes(), make([]byte, 20), uint32(545)) t.Run(version.version, func(t *testing.T) {
_, err := oracle.AddGlobalDataTx(data)
require.ErrorIs(t, err, ErrUnsupportedKeyType) t.Run("UnknownType", func(t *testing.T) {
}) _, oracle := setupPreimageOracleTest(t, version)
data := types.NewPreimageOracleData(common.Hash{0xcc}.Bytes(), make([]byte, 20), uint32(545))
t.Run("Keccak256", func(t *testing.T) { _, err := oracle.AddGlobalDataTx(data)
stubRpc, oracle := setupPreimageOracleTest(t) require.ErrorIs(t, err, ErrUnsupportedKeyType)
data := types.NewPreimageOracleData(common.Hash{byte(preimage.Keccak256KeyType), 0xcc}.Bytes(), make([]byte, 20), uint32(545)) })
stubRpc.SetResponse(oracleAddr, methodLoadKeccak256PreimagePart, rpcblock.Latest, []interface{}{
new(big.Int).SetUint64(uint64(data.OracleOffset)), t.Run("Keccak256", func(t *testing.T) {
data.GetPreimageWithoutSize(), stubRpc, oracle := setupPreimageOracleTest(t, version)
}, nil) data := types.NewPreimageOracleData(common.Hash{byte(preimage.Keccak256KeyType), 0xcc}.Bytes(), make([]byte, 20), uint32(545))
stubRpc.SetResponse(oracleAddr, methodLoadKeccak256PreimagePart, rpcblock.Latest, []interface{}{
tx, err := oracle.AddGlobalDataTx(data) new(big.Int).SetUint64(uint64(data.OracleOffset)),
require.NoError(t, err) data.GetPreimageWithoutSize(),
stubRpc.VerifyTxCandidate(tx) }, nil)
})
tx, err := oracle.AddGlobalDataTx(data)
t.Run("Sha256", func(t *testing.T) { require.NoError(t, err)
stubRpc, oracle := setupPreimageOracleTest(t) stubRpc.VerifyTxCandidate(tx)
data := types.NewPreimageOracleData(common.Hash{byte(preimage.Sha256KeyType), 0xcc}.Bytes(), make([]byte, 20), uint32(545)) })
stubRpc.SetResponse(oracleAddr, methodLoadSha256PreimagePart, rpcblock.Latest, []interface{}{
new(big.Int).SetUint64(uint64(data.OracleOffset)), t.Run("Sha256", func(t *testing.T) {
data.GetPreimageWithoutSize(), stubRpc, oracle := setupPreimageOracleTest(t, version)
}, nil) data := types.NewPreimageOracleData(common.Hash{byte(preimage.Sha256KeyType), 0xcc}.Bytes(), make([]byte, 20), uint32(545))
stubRpc.SetResponse(oracleAddr, methodLoadSha256PreimagePart, rpcblock.Latest, []interface{}{
tx, err := oracle.AddGlobalDataTx(data) new(big.Int).SetUint64(uint64(data.OracleOffset)),
require.NoError(t, err) data.GetPreimageWithoutSize(),
stubRpc.VerifyTxCandidate(tx) }, nil)
})
tx, err := oracle.AddGlobalDataTx(data)
t.Run("Blob", func(t *testing.T) { require.NoError(t, err)
stubRpc, oracle := setupPreimageOracleTest(t) stubRpc.VerifyTxCandidate(tx)
fieldData := testutils.RandomData(rand.New(rand.NewSource(23)), 32) })
data := types.NewPreimageOracleData(common.Hash{byte(preimage.BlobKeyType), 0xcc}.Bytes(), fieldData, uint32(545))
stubRpc.SetResponse(oracleAddr, methodLoadBlobPreimagePart, rpcblock.Latest, []interface{}{ t.Run("Blob", func(t *testing.T) {
new(big.Int).SetUint64(data.BlobFieldIndex), stubRpc, oracle := setupPreimageOracleTest(t, version)
new(big.Int).SetBytes(data.GetPreimageWithoutSize()), fieldData := testutils.RandomData(rand.New(rand.NewSource(23)), 32)
data.BlobCommitment, data := types.NewPreimageOracleData(common.Hash{byte(preimage.BlobKeyType), 0xcc}.Bytes(), fieldData, uint32(545))
data.BlobProof, stubRpc.SetResponse(oracleAddr, methodLoadBlobPreimagePart, rpcblock.Latest, []interface{}{
new(big.Int).SetUint64(uint64(data.OracleOffset)), new(big.Int).SetUint64(data.BlobFieldIndex),
}, nil) new(big.Int).SetBytes(data.GetPreimageWithoutSize()),
data.BlobCommitment,
tx, err := oracle.AddGlobalDataTx(data) data.BlobProof,
require.NoError(t, err) new(big.Int).SetUint64(uint64(data.OracleOffset)),
stubRpc.VerifyTxCandidate(tx) }, nil)
})
tx, err := oracle.AddGlobalDataTx(data)
t.Run("Precompile", func(t *testing.T) { require.NoError(t, err)
stubRpc, oracle := setupPreimageOracleTest(t) stubRpc.VerifyTxCandidate(tx)
input := testutils.RandomData(rand.New(rand.NewSource(23)), 200) })
data := types.NewPreimageOracleData(common.Hash{byte(preimage.PrecompileKeyType), 0xcc}.Bytes(), input, uint32(545))
stubRpc.SetResponse(oracleAddr, methodLoadPrecompilePreimagePart, rpcblock.Latest, []interface{}{ t.Run("Precompile", func(t *testing.T) {
new(big.Int).SetUint64(uint64(data.OracleOffset)), stubRpc, oracle := setupPreimageOracleTest(t, version)
data.GetPrecompileAddress(), input := testutils.RandomData(rand.New(rand.NewSource(23)), 200)
data.GetPrecompileInput(), data := types.NewPreimageOracleData(common.Hash{byte(preimage.PrecompileKeyType), 0xcc}.Bytes(), input, uint32(545))
}, nil) if version.Is(oracle100) {
keyData := data.GetPreimageWithoutSize()
tx, err := oracle.AddGlobalDataTx(data) stubRpc.SetResponse(oracleAddr, methodLoadPrecompilePreimagePart, rpcblock.Latest, []interface{}{
require.NoError(t, err) new(big.Int).SetUint64(uint64(data.OracleOffset)),
stubRpc.VerifyTxCandidate(tx) common.BytesToAddress(keyData[0:20]),
}) keyData[20:],
}, nil)
} else {
stubRpc.SetResponse(oracleAddr, methodLoadPrecompilePreimagePart, rpcblock.Latest, []interface{}{
new(big.Int).SetUint64(uint64(data.OracleOffset)),
data.GetPrecompileAddress(),
data.GetPrecompileRequiredGas(),
data.GetPrecompileInput(),
}, nil)
}
tx, err := oracle.AddGlobalDataTx(data)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
})
})
}
} }
func TestPreimageOracleContract_ChallengePeriod(t *testing.T) { func TestPreimageOracleContract_ChallengePeriod(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t) for _, version := range oracleVersions {
stubRpc.SetResponse(oracleAddr, methodChallengePeriod, rpcblock.Latest, version := version
[]interface{}{}, t.Run(version.version, func(t *testing.T) {
[]interface{}{big.NewInt(123)},
) stubRpc, oracle := setupPreimageOracleTest(t, version)
challengePeriod, err := oracle.ChallengePeriod(context.Background()) stubRpc.SetResponse(oracleAddr, methodChallengePeriod, rpcblock.Latest,
require.NoError(t, err) []interface{}{},
require.Equal(t, uint64(123), challengePeriod) []interface{}{big.NewInt(123)},
)
// Should cache responses challengePeriod, err := oracle.ChallengePeriod(context.Background())
stubRpc.ClearResponses() require.NoError(t, err)
challengePeriod, err = oracle.ChallengePeriod(context.Background()) require.Equal(t, uint64(123), challengePeriod)
require.NoError(t, err)
require.Equal(t, uint64(123), challengePeriod) // Should cache responses
stubRpc.ClearResponses()
challengePeriod, err = oracle.ChallengePeriod(context.Background())
require.NoError(t, err)
require.Equal(t, uint64(123), challengePeriod)
})
}
} }
func TestPreimageOracleContract_MinLargePreimageSize(t *testing.T) { func TestPreimageOracleContract_MinLargePreimageSize(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t) for _, version := range oracleVersions {
stubRpc.SetResponse(oracleAddr, methodMinProposalSize, rpcblock.Latest, version := version
[]interface{}{}, t.Run(version.version, func(t *testing.T) {
[]interface{}{big.NewInt(123)},
) stubRpc, oracle := setupPreimageOracleTest(t, version)
minProposalSize, err := oracle.MinLargePreimageSize(context.Background()) stubRpc.SetResponse(oracleAddr, methodMinProposalSize, rpcblock.Latest,
require.NoError(t, err) []interface{}{},
require.Equal(t, uint64(123), minProposalSize) []interface{}{big.NewInt(123)},
)
minProposalSize, err := oracle.MinLargePreimageSize(context.Background())
require.NoError(t, err)
require.Equal(t, uint64(123), minProposalSize)
})
}
} }
func TestPreimageOracleContract_MinBondSizeLPP(t *testing.T) { func TestPreimageOracleContract_MinBondSizeLPP(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t) for _, version := range oracleVersions {
stubRpc.SetResponse(oracleAddr, methodMinBondSizeLPP, rpcblock.Latest, version := version
[]interface{}{}, t.Run(version.version, func(t *testing.T) {
[]interface{}{big.NewInt(123)},
) stubRpc, oracle := setupPreimageOracleTest(t, version)
minBond, err := oracle.GetMinBondLPP(context.Background()) stubRpc.SetResponse(oracleAddr, methodMinBondSizeLPP, rpcblock.Latest,
require.NoError(t, err) []interface{}{},
require.Equal(t, big.NewInt(123), minBond) []interface{}{big.NewInt(123)},
)
// Should cache responses minBond, err := oracle.GetMinBondLPP(context.Background())
stubRpc.ClearResponses() require.NoError(t, err)
minBond, err = oracle.GetMinBondLPP(context.Background()) require.Equal(t, big.NewInt(123), minBond)
require.NoError(t, err)
require.Equal(t, big.NewInt(123), minBond) // Should cache responses
stubRpc.ClearResponses()
minBond, err = oracle.GetMinBondLPP(context.Background())
require.NoError(t, err)
require.Equal(t, big.NewInt(123), minBond)
})
}
} }
func TestPreimageOracleContract_PreimageDataExists(t *testing.T) { func TestPreimageOracleContract_PreimageDataExists(t *testing.T) {
t.Run("exists", func(t *testing.T) { for _, version := range oracleVersions {
stubRpc, oracle := setupPreimageOracleTest(t) version := version
data := types.NewPreimageOracleData(common.Hash{0xcc}.Bytes(), make([]byte, 20), 545) t.Run(version.version, func(t *testing.T) {
stubRpc.SetResponse(oracleAddr, methodPreimagePartOk, rpcblock.Latest,
[]interface{}{common.Hash(data.OracleKey), new(big.Int).SetUint64(uint64(data.OracleOffset))}, t.Run("exists", func(t *testing.T) {
[]interface{}{true}, stubRpc, oracle := setupPreimageOracleTest(t, version)
) data := types.NewPreimageOracleData(common.Hash{0xcc}.Bytes(), make([]byte, 20), 545)
exists, err := oracle.GlobalDataExists(context.Background(), data) stubRpc.SetResponse(oracleAddr, methodPreimagePartOk, rpcblock.Latest,
require.NoError(t, err) []interface{}{common.Hash(data.OracleKey), new(big.Int).SetUint64(uint64(data.OracleOffset))},
require.True(t, exists) []interface{}{true},
}) )
t.Run("does not exist", func(t *testing.T) { exists, err := oracle.GlobalDataExists(context.Background(), data)
stubRpc, oracle := setupPreimageOracleTest(t) require.NoError(t, err)
data := types.NewPreimageOracleData(common.Hash{0xcc}.Bytes(), make([]byte, 20), 545) require.True(t, exists)
stubRpc.SetResponse(oracleAddr, methodPreimagePartOk, rpcblock.Latest, })
[]interface{}{common.Hash(data.OracleKey), new(big.Int).SetUint64(uint64(data.OracleOffset))}, t.Run("does not exist", func(t *testing.T) {
[]interface{}{false}, stubRpc, oracle := setupPreimageOracleTest(t, version)
) data := types.NewPreimageOracleData(common.Hash{0xcc}.Bytes(), make([]byte, 20), 545)
exists, err := oracle.GlobalDataExists(context.Background(), data) stubRpc.SetResponse(oracleAddr, methodPreimagePartOk, rpcblock.Latest,
require.NoError(t, err) []interface{}{common.Hash(data.OracleKey), new(big.Int).SetUint64(uint64(data.OracleOffset))},
require.False(t, exists) []interface{}{false},
}) )
exists, err := oracle.GlobalDataExists(context.Background(), data)
require.NoError(t, err)
require.False(t, exists)
})
})
}
} }
func TestPreimageOracleContract_InitLargePreimage(t *testing.T) { func TestPreimageOracleContract_InitLargePreimage(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t) for _, version := range oracleVersions {
version := version
uuid := big.NewInt(123) t.Run(version.version, func(t *testing.T) {
partOffset := uint32(1)
claimedSize := uint32(2) stubRpc, oracle := setupPreimageOracleTest(t, version)
bond := big.NewInt(42984)
stubRpc.SetResponse(oracleAddr, methodMinBondSizeLPP, rpcblock.Latest, nil, []interface{}{bond}) uuid := big.NewInt(123)
stubRpc.SetResponse(oracleAddr, methodInitLPP, rpcblock.Latest, []interface{}{ partOffset := uint32(1)
uuid, claimedSize := uint32(2)
partOffset, bond := big.NewInt(42984)
claimedSize, stubRpc.SetResponse(oracleAddr, methodMinBondSizeLPP, rpcblock.Latest, nil, []interface{}{bond})
}, nil) stubRpc.SetResponse(oracleAddr, methodInitLPP, rpcblock.Latest, []interface{}{
uuid,
tx, err := oracle.InitLargePreimage(uuid, partOffset, claimedSize) partOffset,
require.NoError(t, err) claimedSize,
stubRpc.VerifyTxCandidate(tx) }, nil)
require.Truef(t, bond.Cmp(tx.Value) == 0, "Expected bond %v got %v", bond, tx.Value)
tx, err := oracle.InitLargePreimage(uuid, partOffset, claimedSize)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
require.Truef(t, bond.Cmp(tx.Value) == 0, "Expected bond %v got %v", bond, tx.Value)
})
}
} }
func TestPreimageOracleContract_AddLeaves(t *testing.T) { func TestPreimageOracleContract_AddLeaves(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t) for _, version := range oracleVersions {
version := version
uuid := big.NewInt(123) t.Run(version.version, func(t *testing.T) {
startingBlockIndex := big.NewInt(0)
input := []byte{0x12} stubRpc, oracle := setupPreimageOracleTest(t, version)
commitments := []common.Hash{{0x34}}
finalize := true uuid := big.NewInt(123)
stubRpc.SetResponse(oracleAddr, methodAddLeavesLPP, rpcblock.Latest, []interface{}{ startingBlockIndex := big.NewInt(0)
uuid, input := []byte{0x12}
startingBlockIndex, commitments := []common.Hash{{0x34}}
input, finalize := true
commitments, stubRpc.SetResponse(oracleAddr, methodAddLeavesLPP, rpcblock.Latest, []interface{}{
finalize, uuid,
}, nil) startingBlockIndex,
input,
tx, err := oracle.AddLeaves(uuid, startingBlockIndex, input, commitments, finalize) commitments,
require.NoError(t, err) finalize,
stubRpc.VerifyTxCandidate(tx) }, nil)
tx, err := oracle.AddLeaves(uuid, startingBlockIndex, input, commitments, finalize)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
})
}
} }
func TestPreimageOracleContract_Squeeze(t *testing.T) { func TestPreimageOracleContract_Squeeze(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t) for _, version := range oracleVersions {
version := version
claimant := common.Address{0x12} t.Run(version.version, func(t *testing.T) {
uuid := big.NewInt(123)
preStateMatrix := keccakTypes.StateSnapshot{0, 1, 2, 3, 4} stubRpc, oracle := setupPreimageOracleTest(t, version)
preState := keccakTypes.Leaf{
Input: [keccakTypes.BlockSize]byte{0x12}, claimant := common.Address{0x12}
Index: 123, uuid := big.NewInt(123)
StateCommitment: common.Hash{0x34}, preStateMatrix := keccakTypes.StateSnapshot{0, 1, 2, 3, 4}
} preState := keccakTypes.Leaf{
preStateProof := merkle.Proof{{0x34}} Input: [keccakTypes.BlockSize]byte{0x12},
postState := keccakTypes.Leaf{ Index: 123,
Input: [keccakTypes.BlockSize]byte{0x34}, StateCommitment: common.Hash{0x34},
Index: 456, }
StateCommitment: common.Hash{0x56}, preStateProof := merkle.Proof{{0x34}}
postState := keccakTypes.Leaf{
Input: [keccakTypes.BlockSize]byte{0x34},
Index: 456,
StateCommitment: common.Hash{0x56},
}
postStateProof := merkle.Proof{{0x56}}
stubRpc.SetResponse(oracleAddr, methodSqueezeLPP, rpcblock.Latest, []interface{}{
claimant,
uuid,
abiEncodeSnapshot(preStateMatrix),
toPreimageOracleLeaf(preState),
preStateProof,
toPreimageOracleLeaf(postState),
postStateProof,
}, nil)
tx, err := oracle.Squeeze(claimant, uuid, preStateMatrix, preState, preStateProof, postState, postStateProof)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
})
} }
postStateProof := merkle.Proof{{0x56}}
stubRpc.SetResponse(oracleAddr, methodSqueezeLPP, rpcblock.Latest, []interface{}{
claimant,
uuid,
abiEncodeSnapshot(preStateMatrix),
toPreimageOracleLeaf(preState),
preStateProof,
toPreimageOracleLeaf(postState),
postStateProof,
}, nil)
tx, err := oracle.Squeeze(claimant, uuid, preStateMatrix, preState, preStateProof, postState, postStateProof)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
} }
func TestGetActivePreimages(t *testing.T) { func TestGetActivePreimages(t *testing.T) {
blockHash := common.Hash{0xaa} for _, version := range oracleVersions {
_, oracle, proposals := setupPreimageOracleTestWithProposals(t, rpcblock.ByHash(blockHash)) version := version
preimages, err := oracle.GetActivePreimages(context.Background(), blockHash) t.Run(version.version, func(t *testing.T) {
require.NoError(t, err)
require.Equal(t, proposals, preimages) blockHash := common.Hash{0xaa}
_, oracle, proposals := setupPreimageOracleTestWithProposals(t, version, rpcblock.ByHash(blockHash))
preimages, err := oracle.GetActivePreimages(context.Background(), blockHash)
require.NoError(t, err)
require.Equal(t, proposals, preimages)
})
}
} }
func TestGetProposalMetadata(t *testing.T) { func TestGetProposalMetadata(t *testing.T) {
blockHash := common.Hash{0xaa} for _, version := range oracleVersions {
block := rpcblock.ByHash(blockHash) version := version
stubRpc, oracle, proposals := setupPreimageOracleTestWithProposals(t, block) t.Run(version.version, func(t *testing.T) {
preimages, err := oracle.GetProposalMetadata(
context.Background(), blockHash := common.Hash{0xaa}
block, block := rpcblock.ByHash(blockHash)
proposals[0].LargePreimageIdent, stubRpc, oracle, proposals := setupPreimageOracleTestWithProposals(t, version, block)
proposals[1].LargePreimageIdent, preimages, err := oracle.GetProposalMetadata(
proposals[2].LargePreimageIdent, context.Background(),
) block,
require.NoError(t, err) proposals[0].LargePreimageIdent,
require.Equal(t, proposals, preimages) proposals[1].LargePreimageIdent,
proposals[2].LargePreimageIdent,
// Fetching a proposal that doesn't exist should return an empty metadata object. )
ident := keccakTypes.LargePreimageIdent{Claimant: common.Address{0x12}, UUID: big.NewInt(123)} require.NoError(t, err)
meta := new(metadata) require.Equal(t, proposals, preimages)
stubRpc.SetResponse(
oracleAddr, // Fetching a proposal that doesn't exist should return an empty metadata object.
methodProposalMetadata, ident := keccakTypes.LargePreimageIdent{Claimant: common.Address{0x12}, UUID: big.NewInt(123)}
block, meta := new(metadata)
[]interface{}{ident.Claimant, ident.UUID}, stubRpc.SetResponse(
[]interface{}{meta}) oracleAddr,
preimages, err = oracle.GetProposalMetadata(context.Background(), rpcblock.ByHash(blockHash), ident) methodProposalMetadata,
require.NoError(t, err) block,
require.Equal(t, []keccakTypes.LargePreimageMetaData{{LargePreimageIdent: ident}}, preimages) []interface{}{ident.Claimant, ident.UUID},
[]interface{}{meta})
preimages, err = oracle.GetProposalMetadata(context.Background(), rpcblock.ByHash(blockHash), ident)
require.NoError(t, err)
require.Equal(t, []keccakTypes.LargePreimageMetaData{{LargePreimageIdent: ident}}, preimages)
})
}
} }
func TestGetProposalTreeRoot(t *testing.T) { func TestGetProposalTreeRoot(t *testing.T) {
blockHash := common.Hash{0xaa} for _, version := range oracleVersions {
expectedRoot := common.Hash{0xbb} version := version
ident := keccakTypes.LargePreimageIdent{Claimant: common.Address{0x12}, UUID: big.NewInt(123)} t.Run(version.version, func(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t)
stubRpc.SetResponse(oracleAddr, methodGetTreeRootLPP, rpcblock.ByHash(blockHash), blockHash := common.Hash{0xaa}
[]interface{}{ident.Claimant, ident.UUID}, expectedRoot := common.Hash{0xbb}
[]interface{}{expectedRoot}) ident := keccakTypes.LargePreimageIdent{Claimant: common.Address{0x12}, UUID: big.NewInt(123)}
actualRoot, err := oracle.GetProposalTreeRoot(context.Background(), rpcblock.ByHash(blockHash), ident) stubRpc, oracle := setupPreimageOracleTest(t, version)
require.NoError(t, err) stubRpc.SetResponse(oracleAddr, methodGetTreeRootLPP, rpcblock.ByHash(blockHash),
require.Equal(t, expectedRoot, actualRoot) []interface{}{ident.Claimant, ident.UUID},
[]interface{}{expectedRoot})
actualRoot, err := oracle.GetProposalTreeRoot(context.Background(), rpcblock.ByHash(blockHash), ident)
require.NoError(t, err)
require.Equal(t, expectedRoot, actualRoot)
})
}
} }
func setupPreimageOracleTestWithProposals(t *testing.T, block rpcblock.Block) (*batchingTest.AbiBasedRpc, *PreimageOracleContract, []keccakTypes.LargePreimageMetaData) { func setupPreimageOracleTestWithProposals(t *testing.T, version contractVersion, block rpcblock.Block) (*batchingTest.AbiBasedRpc, PreimageOracleContract, []keccakTypes.LargePreimageMetaData) {
stubRpc, oracle := setupPreimageOracleTest(t) stubRpc, oracle := setupPreimageOracleTest(t, version)
stubRpc.SetResponse( stubRpc.SetResponse(
oracleAddr, oracleAddr,
methodProposalCount, methodProposalCount,
...@@ -357,11 +452,11 @@ func setupPreimageOracleTestWithProposals(t *testing.T, block rpcblock.Block) (* ...@@ -357,11 +452,11 @@ func setupPreimageOracleTestWithProposals(t *testing.T, block rpcblock.Block) (*
return stubRpc, oracle, proposals return stubRpc, oracle, proposals
} }
func setupPreimageOracleTest(t *testing.T) (*batchingTest.AbiBasedRpc, *PreimageOracleContract) { func setupPreimageOracleTest(t *testing.T, version contractVersion) (*batchingTest.AbiBasedRpc, PreimageOracleContract) {
oracleAbi := snapshots.LoadPreimageOracleABI() stubRpc := batchingTest.NewAbiBasedRpc(t, oracleAddr, version.loadAbi())
stubRpc.SetResponse(oracleAddr, methodVersion, rpcblock.Latest, nil, []interface{}{version.version})
stubRpc := batchingTest.NewAbiBasedRpc(t, oracleAddr, oracleAbi) oracleContract, err := NewPreimageOracleContract(context.Background(), oracleAddr, batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize))
oracleContract := NewPreimageOracleContract(oracleAddr, batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize)) require.NoError(t, err)
return stubRpc, oracleContract return stubRpc, oracleContract
} }
...@@ -396,68 +491,93 @@ func TestMetadata(t *testing.T) { ...@@ -396,68 +491,93 @@ func TestMetadata(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
test := test test := test
for _, value := range uint32Values {
value := value for _, version := range oracleVersions {
t.Run(fmt.Sprintf("%v-%v", test.name, value), func(t *testing.T) { version := version
meta := new(metadata) t.Run(version.version, func(t *testing.T) {
require.Zero(t, test.getter(meta))
test.setter(meta, value) for _, value := range uint32Values {
require.Equal(t, value, test.getter(meta)) value := value
t.Run(fmt.Sprintf("%v-%v", test.name, value), func(t *testing.T) {
meta := new(metadata)
require.Zero(t, test.getter(meta))
test.setter(meta, value)
require.Equal(t, value, test.getter(meta))
})
}
}) })
} }
} }
} }
func TestMetadata_Timestamp(t *testing.T) { func TestMetadata_Timestamp(t *testing.T) {
values := []uint64{0, 1, 2, 3252354, math.MaxUint32, math.MaxUint32 + 1, math.MaxUint64} for _, version := range oracleVersions {
var meta metadata version := version
require.Zero(t, meta.timestamp()) t.Run(version.version, func(t *testing.T) {
for _, value := range values {
meta.setTimestamp(value) values := []uint64{0, 1, 2, 3252354, math.MaxUint32, math.MaxUint32 + 1, math.MaxUint64}
require.Equal(t, value, meta.timestamp()) var meta metadata
require.Zero(t, meta.timestamp())
for _, value := range values {
meta.setTimestamp(value)
require.Equal(t, value, meta.timestamp())
}
})
} }
} }
func TestMetadata_Countered(t *testing.T) { func TestMetadata_Countered(t *testing.T) {
var meta metadata for _, version := range oracleVersions {
require.False(t, meta.countered()) version := version
meta.setCountered(true) t.Run(version.version, func(t *testing.T) {
require.True(t, meta.countered())
meta.setCountered(false) var meta metadata
require.False(t, meta.countered()) require.False(t, meta.countered())
meta.setCountered(true)
require.True(t, meta.countered())
meta.setCountered(false)
require.False(t, meta.countered())
})
}
} }
func TestGetInputDataBlocks(t *testing.T) { func TestGetInputDataBlocks(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t) for _, version := range oracleVersions {
block := rpcblock.ByHash(common.Hash{0xaa}) version := version
t.Run(version.version, func(t *testing.T) {
preimage := keccakTypes.LargePreimageIdent{ stubRpc, oracle := setupPreimageOracleTest(t, version)
Claimant: common.Address{0xbb}, block := rpcblock.ByHash(common.Hash{0xaa})
UUID: big.NewInt(2222),
}
stubRpc.SetResponse( preimage := keccakTypes.LargePreimageIdent{
oracleAddr, Claimant: common.Address{0xbb},
methodProposalBlocksLen, UUID: big.NewInt(2222),
block, }
[]interface{}{preimage.Claimant, preimage.UUID},
[]interface{}{big.NewInt(3)})
blockNums := []uint64{10, 35, 67} stubRpc.SetResponse(
oracleAddr,
methodProposalBlocksLen,
block,
[]interface{}{preimage.Claimant, preimage.UUID},
[]interface{}{big.NewInt(3)})
blockNums := []uint64{10, 35, 67}
for i, blockNum := range blockNums {
stubRpc.SetResponse(
oracleAddr,
methodProposalBlocks,
block,
[]interface{}{preimage.Claimant, preimage.UUID, big.NewInt(int64(i))},
[]interface{}{blockNum})
}
for i, blockNum := range blockNums { actual, err := oracle.GetInputDataBlocks(context.Background(), block, preimage)
stubRpc.SetResponse( require.NoError(t, err)
oracleAddr, require.Len(t, actual, 3)
methodProposalBlocks, require.Equal(t, blockNums, actual)
block, })
[]interface{}{preimage.Claimant, preimage.UUID, big.NewInt(int64(i))},
[]interface{}{blockNum})
} }
actual, err := oracle.GetInputDataBlocks(context.Background(), block, preimage)
require.NoError(t, err)
require.Len(t, actual, 3)
require.Equal(t, blockNums, actual)
} }
func TestDecodeInputData(t *testing.T) { func TestDecodeInputData(t *testing.T) {
...@@ -472,7 +592,6 @@ func TestDecodeInputData(t *testing.T) { ...@@ -472,7 +592,6 @@ func TestDecodeInputData(t *testing.T) {
Claimant: common.Address{0xaa}, Claimant: common.Address{0xaa},
UUID: big.NewInt(1111), UUID: big.NewInt(1111),
} }
_, oracle := setupPreimageOracleTest(t)
tests := []struct { tests := []struct {
name string name string
...@@ -553,106 +672,125 @@ func TestDecodeInputData(t *testing.T) { ...@@ -553,106 +672,125 @@ func TestDecodeInputData(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.name, func(t *testing.T) { for _, version := range oracleVersions {
var input []byte version := version
if len(test.input) > 0 { t.Run(version.version, func(t *testing.T) {
input = test.input
} else { t.Run(test.name, func(t *testing.T) {
input = toAddLeavesTxData(t, oracle, ident.UUID, test.inputData) _, oracle := setupPreimageOracleTest(t, version)
} var input []byte
require.Equal(t, test.expectedTxData, common.Bytes2Hex(input), if len(test.input) > 0 {
"ABI has been changed. Add tests to ensure historic transactions can be parsed before updating expectedTxData") input = test.input
uuid, leaves, err := oracle.DecodeInputData(input) } else {
if test.expectedErr != nil { input = toAddLeavesTxData(t, oracle, ident.UUID, test.inputData)
require.ErrorIs(t, err, test.expectedErr) }
} else { require.Equal(t, test.expectedTxData, common.Bytes2Hex(input),
require.NoError(t, err) "ABI has been changed. Add tests to ensure historic transactions can be parsed before updating expectedTxData")
require.Equal(t, ident.UUID, uuid) uuid, leaves, err := oracle.DecodeInputData(input)
require.Equal(t, test.inputData, leaves) if test.expectedErr != nil {
} require.ErrorIs(t, err, test.expectedErr)
}) } else {
require.NoError(t, err)
require.Equal(t, ident.UUID, uuid)
require.Equal(t, test.inputData, leaves)
}
})
})
}
} }
} }
func TestChallenge_First(t *testing.T) { func TestChallenge_First(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t) for _, version := range oracleVersions {
version := version
t.Run(version.version, func(t *testing.T) {
ident := keccakTypes.LargePreimageIdent{ stubRpc, oracle := setupPreimageOracleTest(t, version)
Claimant: common.Address{0xab},
UUID: big.NewInt(4829), ident := keccakTypes.LargePreimageIdent{
} Claimant: common.Address{0xab},
challenge := keccakTypes.Challenge{ UUID: big.NewInt(4829),
StateMatrix: keccakTypes.StateSnapshot{1, 2, 3, 4, 5}, }
Prestate: keccakTypes.Leaf{}, challenge := keccakTypes.Challenge{
Poststate: keccakTypes.Leaf{ StateMatrix: keccakTypes.StateSnapshot{1, 2, 3, 4, 5},
Input: [136]byte{5, 4, 3, 2, 1}, Prestate: keccakTypes.Leaf{},
Index: 0, Poststate: keccakTypes.Leaf{
StateCommitment: common.Hash{0xbb}, Input: [136]byte{5, 4, 3, 2, 1},
}, Index: 0,
PoststateProof: merkle.Proof{common.Hash{0x01}, common.Hash{0x02}}, StateCommitment: common.Hash{0xbb},
},
PoststateProof: merkle.Proof{common.Hash{0x01}, common.Hash{0x02}},
}
stubRpc.SetResponse(oracleAddr, methodChallengeFirstLPP, rpcblock.Latest,
[]interface{}{
ident.Claimant, ident.UUID,
preimageOracleLeaf{
Input: challenge.Poststate.Input[:],
Index: new(big.Int).SetUint64(challenge.Poststate.Index),
StateCommitment: challenge.Poststate.StateCommitment,
},
challenge.PoststateProof,
},
nil)
tx, err := oracle.ChallengeTx(ident, challenge)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
})
} }
stubRpc.SetResponse(oracleAddr, methodChallengeFirstLPP, rpcblock.Latest,
[]interface{}{
ident.Claimant, ident.UUID,
preimageOracleLeaf{
Input: challenge.Poststate.Input[:],
Index: new(big.Int).SetUint64(challenge.Poststate.Index),
StateCommitment: challenge.Poststate.StateCommitment,
},
challenge.PoststateProof,
},
nil)
tx, err := oracle.ChallengeTx(ident, challenge)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
} }
func TestChallenge_NotFirst(t *testing.T) { func TestChallenge_NotFirst(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t) for _, version := range oracleVersions {
version := version
t.Run(version.version, func(t *testing.T) {
ident := keccakTypes.LargePreimageIdent{ stubRpc, oracle := setupPreimageOracleTest(t, version)
Claimant: common.Address{0xab},
UUID: big.NewInt(4829), ident := keccakTypes.LargePreimageIdent{
} Claimant: common.Address{0xab},
challenge := keccakTypes.Challenge{ UUID: big.NewInt(4829),
StateMatrix: keccakTypes.StateSnapshot{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}, }
Prestate: keccakTypes.Leaf{ challenge := keccakTypes.Challenge{
Input: [136]byte{9, 8, 7, 6, 5}, StateMatrix: keccakTypes.StateSnapshot{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25},
Index: 3, Prestate: keccakTypes.Leaf{
StateCommitment: common.Hash{0xcc}, Input: [136]byte{9, 8, 7, 6, 5},
}, Index: 3,
PrestateProof: merkle.Proof{common.Hash{0x01}, common.Hash{0x02}}, StateCommitment: common.Hash{0xcc},
Poststate: keccakTypes.Leaf{ },
Input: [136]byte{5, 4, 3, 2, 1}, PrestateProof: merkle.Proof{common.Hash{0x01}, common.Hash{0x02}},
Index: 4, Poststate: keccakTypes.Leaf{
StateCommitment: common.Hash{0xbb}, Input: [136]byte{5, 4, 3, 2, 1},
}, Index: 4,
PoststateProof: merkle.Proof{common.Hash{0x03}, common.Hash{0x04}}, StateCommitment: common.Hash{0xbb},
},
PoststateProof: merkle.Proof{common.Hash{0x03}, common.Hash{0x04}},
}
stubRpc.SetResponse(oracleAddr, methodChallengeLPP, rpcblock.Latest,
[]interface{}{
ident.Claimant, ident.UUID,
libKeccakStateMatrix{State: challenge.StateMatrix},
preimageOracleLeaf{
Input: challenge.Prestate.Input[:],
Index: new(big.Int).SetUint64(challenge.Prestate.Index),
StateCommitment: challenge.Prestate.StateCommitment,
},
challenge.PrestateProof,
preimageOracleLeaf{
Input: challenge.Poststate.Input[:],
Index: new(big.Int).SetUint64(challenge.Poststate.Index),
StateCommitment: challenge.Poststate.StateCommitment,
},
challenge.PoststateProof,
},
nil)
tx, err := oracle.ChallengeTx(ident, challenge)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
})
} }
stubRpc.SetResponse(oracleAddr, methodChallengeLPP, rpcblock.Latest,
[]interface{}{
ident.Claimant, ident.UUID,
libKeccakStateMatrix{State: challenge.StateMatrix},
preimageOracleLeaf{
Input: challenge.Prestate.Input[:],
Index: new(big.Int).SetUint64(challenge.Prestate.Index),
StateCommitment: challenge.Prestate.StateCommitment,
},
challenge.PrestateProof,
preimageOracleLeaf{
Input: challenge.Poststate.Input[:],
Index: new(big.Int).SetUint64(challenge.Poststate.Index),
StateCommitment: challenge.Poststate.StateCommitment,
},
challenge.PoststateProof,
},
nil)
tx, err := oracle.ChallengeTx(ident, challenge)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
} }
func toAddLeavesTxData(t *testing.T, oracle *PreimageOracleContract, uuid *big.Int, inputData keccakTypes.InputData) []byte { func toAddLeavesTxData(t *testing.T, oracle PreimageOracleContract, uuid *big.Int, inputData keccakTypes.InputData) []byte {
tx, err := oracle.AddLeaves(uuid, big.NewInt(1), inputData.Input, inputData.Commitments, inputData.Finalize) tx, err := oracle.AddLeaves(uuid, big.NewInt(1), inputData.Input, inputData.Commitments, inputData.Finalize)
require.NoError(t, err) require.NoError(t, err)
return tx.TxData return tx.TxData
......
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",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -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