Commit 93e9d153 authored by clabby's avatar clabby Committed by GitHub

feat(ctb): Add new move type to FDG for OR counters (#10438)

* feat(ctb): Add new move type to FDG for OR counters

Adds a potential new move type to the `FaultDisputeGame` that allows for
a participant to reveal the preimage of the claimed output root to
display to the dispute game that the claimed L2 block number does not
match up with the block number that the block header within the output
root commits to.

The root output can be challenged with the new special move type iff:
1. The passed `OutputRootProof` hashes to equal the claimed output root.
1. The passed Header RLP hashes to equal the block hash within the
`OutputRootProof` above.
1. The claimed block number in the dispute game does not equal the block
number that the output root commits to.

If there is a successful challenge with the new move type, that claim
itself is inserted as a special case counter. In `resolveClaim`, the
contract will always consider the creator of the L2 block challenge the
winner of the bond. Notably, this only applies for the root claim
subgame.

* feat(ctb): Transition `RLPReader` to 4byte errors (#10439)

* feat(ctb): Transition `RLPReader` to 4byte errors

* semver

* update summary

* portal semver

* extra checks
parent 84723638
......@@ -8,4 +8,4 @@ GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (g
GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68453)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68923)
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155612)
\ No newline at end of file
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155618)
\ No newline at end of file
......@@ -32,12 +32,12 @@
"sourceCodeHash": "0xf5fcf570721e25459fadbb37e02f9efe349e1c8afcbf1e3b5fdb09c9f612cdc0"
},
"src/L1/OptimismPortal.sol": {
"initCodeHash": "0xc820f7434345b665a77bc17e8b486ba97b7ff9845d336a51f024a41599c4c155",
"initCodeHash": "0xbb8e7b110d3aa0f4807cd790174d6fb6d98c44348c977007ad7a11aa7053f1c5",
"sourceCodeHash": "0x3d0e9dc8ed75b6f2e59444e9204341b18d343af02d6e37ecd9f759da2c8c118b"
},
"src/L1/OptimismPortal2.sol": {
"initCodeHash": "0x6bc8659932ab7eed221880a1d606704a0446d80cf4826f13623fc6cc57053751",
"sourceCodeHash": "0x411ad7aea4d36d7a89ecb2f0dc2c6c200e5fb022b528b3beb1e1b37c557bc7dc"
"initCodeHash": "0x45cae622788a795c2fc4f4bc8e6b85d8edf284a1dc20e1b5fa01e88d737deb23",
"sourceCodeHash": "0xea564dbff9831ad1bf0c1b345fbc3da4675cf112d2605ba94e1ef5c7b745b7ae"
},
"src/L1/ProtocolVersions.sol": {
"initCodeHash": "0x72cd467e8bcf019c02675d72ab762e088bcc9cc0f1a4e9f587fa4589f7fdd1b8",
......@@ -128,8 +128,8 @@
"sourceCodeHash": "0x918c395ac5d77357f2551616aad0613e68893862edd14e554623eb16ee6ba148"
},
"src/dispute/FaultDisputeGame.sol": {
"initCodeHash": "0x8398caaff1da5d81730c95104c15b14c2fb7ff394bab005d9ec77372a2b1f5ca",
"sourceCodeHash": "0x5888aea16645a18ce54032c1787644afcdf07c6df2b7c6546caa957a047f03fc"
"initCodeHash": "0x165b86287737cc00981215510f8f5fcd9197c953cefe14aa5d355fa2c88d257e",
"sourceCodeHash": "0x5f1d3e7cbb1a705ad3b297313178bd7a8acfa2ddf1bde68be1b0b5468942d003"
},
"src/dispute/weth/DelayedWETH.sol": {
"initCodeHash": "0xb9bbe005874922cd8f499e7a0a092967cfca03e012c1e41912b0c77481c71777",
......
......@@ -122,6 +122,46 @@
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "bytes32",
"name": "version",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "stateRoot",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "messagePasserStorageRoot",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "latestBlockhash",
"type": "bytes32"
}
],
"internalType": "struct Types.OutputRootProof",
"name": "_outputRootProof",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "_headerRLP",
"type": "bytes"
}
],
"name": "challengeRootL2Block",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
......@@ -431,6 +471,19 @@
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "l2BlockNumberChallenged",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "l2ChainId",
......@@ -812,6 +865,11 @@
"name": "AnchorRootNotFound",
"type": "error"
},
{
"inputs": [],
"name": "BlockNumberMatches",
"type": "error"
},
{
"inputs": [],
"name": "BondTransferFailed",
......@@ -847,11 +905,21 @@
"name": "ClockTimeExceeded",
"type": "error"
},
{
"inputs": [],
"name": "ContentLengthMismatch",
"type": "error"
},
{
"inputs": [],
"name": "DuplicateStep",
"type": "error"
},
{
"inputs": [],
"name": "EmptyItem",
"type": "error"
},
{
"inputs": [],
"name": "GameDepthExceeded",
......@@ -872,11 +940,31 @@
"name": "InvalidClockExtension",
"type": "error"
},
{
"inputs": [],
"name": "InvalidDataRemainder",
"type": "error"
},
{
"inputs": [],
"name": "InvalidHeader",
"type": "error"
},
{
"inputs": [],
"name": "InvalidHeaderRLP",
"type": "error"
},
{
"inputs": [],
"name": "InvalidLocalIdent",
"type": "error"
},
{
"inputs": [],
"name": "InvalidOutputRootProof",
"type": "error"
},
{
"inputs": [],
"name": "InvalidParent",
......@@ -892,6 +980,11 @@
"name": "InvalidSplitDepth",
"type": "error"
},
{
"inputs": [],
"name": "L2BlockNumberChallenged",
"type": "error"
},
{
"inputs": [],
"name": "MaxDepthTooLarge",
......@@ -907,6 +1000,11 @@
"name": "OutOfOrderResolution",
"type": "error"
},
{
"inputs": [],
"name": "UnexpectedList",
"type": "error"
},
{
"inputs": [
{
......@@ -918,6 +1016,11 @@
"name": "UnexpectedRootClaim",
"type": "error"
},
{
"inputs": [],
"name": "UnexpectedString",
"type": "error"
},
{
"inputs": [],
"name": "ValidStep",
......
......@@ -575,11 +575,31 @@
"name": "CallPaused",
"type": "error"
},
{
"inputs": [],
"name": "ContentLengthMismatch",
"type": "error"
},
{
"inputs": [],
"name": "EmptyItem",
"type": "error"
},
{
"inputs": [],
"name": "GasEstimation",
"type": "error"
},
{
"inputs": [],
"name": "InvalidDataRemainder",
"type": "error"
},
{
"inputs": [],
"name": "InvalidHeader",
"type": "error"
},
{
"inputs": [],
"name": "LargeCalldata",
......@@ -619,5 +639,15 @@
"inputs": [],
"name": "Unauthorized",
"type": "error"
},
{
"inputs": [],
"name": "UnexpectedList",
"type": "error"
},
{
"inputs": [],
"name": "UnexpectedString",
"type": "error"
}
]
\ No newline at end of file
......@@ -752,11 +752,31 @@
"name": "CallPaused",
"type": "error"
},
{
"inputs": [],
"name": "ContentLengthMismatch",
"type": "error"
},
{
"inputs": [],
"name": "EmptyItem",
"type": "error"
},
{
"inputs": [],
"name": "GasEstimation",
"type": "error"
},
{
"inputs": [],
"name": "InvalidDataRemainder",
"type": "error"
},
{
"inputs": [],
"name": "InvalidHeader",
"type": "error"
},
{
"inputs": [],
"name": "LargeCalldata",
......@@ -776,5 +796,15 @@
"inputs": [],
"name": "Unauthorized",
"type": "error"
},
{
"inputs": [],
"name": "UnexpectedList",
"type": "error"
},
{
"inputs": [],
"name": "UnexpectedString",
"type": "error"
}
]
\ No newline at end of file
......@@ -132,6 +132,46 @@
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "bytes32",
"name": "version",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "stateRoot",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "messagePasserStorageRoot",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "latestBlockhash",
"type": "bytes32"
}
],
"internalType": "struct Types.OutputRootProof",
"name": "_outputRootProof",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "_headerRLP",
"type": "bytes"
}
],
"name": "challengeRootL2Block",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "challenger",
......@@ -454,6 +494,19 @@
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "l2BlockNumberChallenged",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "l2ChainId",
......@@ -853,6 +906,11 @@
"name": "BadAuth",
"type": "error"
},
{
"inputs": [],
"name": "BlockNumberMatches",
"type": "error"
},
{
"inputs": [],
"name": "BondTransferFailed",
......@@ -888,11 +946,21 @@
"name": "ClockTimeExceeded",
"type": "error"
},
{
"inputs": [],
"name": "ContentLengthMismatch",
"type": "error"
},
{
"inputs": [],
"name": "DuplicateStep",
"type": "error"
},
{
"inputs": [],
"name": "EmptyItem",
"type": "error"
},
{
"inputs": [],
"name": "GameDepthExceeded",
......@@ -913,11 +981,31 @@
"name": "InvalidClockExtension",
"type": "error"
},
{
"inputs": [],
"name": "InvalidDataRemainder",
"type": "error"
},
{
"inputs": [],
"name": "InvalidHeader",
"type": "error"
},
{
"inputs": [],
"name": "InvalidHeaderRLP",
"type": "error"
},
{
"inputs": [],
"name": "InvalidLocalIdent",
"type": "error"
},
{
"inputs": [],
"name": "InvalidOutputRootProof",
"type": "error"
},
{
"inputs": [],
"name": "InvalidParent",
......@@ -933,6 +1021,11 @@
"name": "InvalidSplitDepth",
"type": "error"
},
{
"inputs": [],
"name": "L2BlockNumberChallenged",
"type": "error"
},
{
"inputs": [],
"name": "MaxDepthTooLarge",
......@@ -948,6 +1041,11 @@
"name": "OutOfOrderResolution",
"type": "error"
},
{
"inputs": [],
"name": "UnexpectedList",
"type": "error"
},
{
"inputs": [
{
......@@ -959,6 +1057,11 @@
"name": "UnexpectedRootClaim",
"type": "error"
},
{
"inputs": [],
"name": "UnexpectedString",
"type": "error"
},
{
"inputs": [],
"name": "ValidStep",
......
......@@ -27,53 +27,67 @@
"slot": "0",
"type": "bool"
},
{
"bytes": "1",
"label": "l2BlockNumberChallenged",
"offset": 18,
"slot": "0",
"type": "bool"
},
{
"bytes": "20",
"label": "l2BlockNumberChallenger",
"offset": 0,
"slot": "1",
"type": "address"
},
{
"bytes": "32",
"label": "claimData",
"offset": 0,
"slot": "1",
"slot": "2",
"type": "struct IFaultDisputeGame.ClaimData[]"
},
{
"bytes": "32",
"label": "credit",
"offset": 0,
"slot": "2",
"slot": "3",
"type": "mapping(address => uint256)"
},
{
"bytes": "32",
"label": "claims",
"offset": 0,
"slot": "3",
"slot": "4",
"type": "mapping(Hash => bool)"
},
{
"bytes": "32",
"label": "subgames",
"offset": 0,
"slot": "4",
"slot": "5",
"type": "mapping(uint256 => uint256[])"
},
{
"bytes": "32",
"label": "resolvedSubgames",
"offset": 0,
"slot": "5",
"slot": "6",
"type": "mapping(uint256 => bool)"
},
{
"bytes": "32",
"label": "resolutionCheckpoints",
"offset": 0,
"slot": "6",
"slot": "7",
"type": "mapping(uint256 => struct IFaultDisputeGame.ResolutionCheckpoint)"
},
{
"bytes": "64",
"label": "startingOutputRoot",
"offset": 0,
"slot": "7",
"slot": "8",
"type": "struct OutputRoot"
}
]
\ No newline at end of file
......@@ -27,53 +27,67 @@
"slot": "0",
"type": "bool"
},
{
"bytes": "1",
"label": "l2BlockNumberChallenged",
"offset": 18,
"slot": "0",
"type": "bool"
},
{
"bytes": "20",
"label": "l2BlockNumberChallenger",
"offset": 0,
"slot": "1",
"type": "address"
},
{
"bytes": "32",
"label": "claimData",
"offset": 0,
"slot": "1",
"slot": "2",
"type": "struct IFaultDisputeGame.ClaimData[]"
},
{
"bytes": "32",
"label": "credit",
"offset": 0,
"slot": "2",
"slot": "3",
"type": "mapping(address => uint256)"
},
{
"bytes": "32",
"label": "claims",
"offset": 0,
"slot": "3",
"slot": "4",
"type": "mapping(Hash => bool)"
},
{
"bytes": "32",
"label": "subgames",
"offset": 0,
"slot": "4",
"slot": "5",
"type": "mapping(uint256 => uint256[])"
},
{
"bytes": "32",
"label": "resolvedSubgames",
"offset": 0,
"slot": "5",
"slot": "6",
"type": "mapping(uint256 => bool)"
},
{
"bytes": "32",
"label": "resolutionCheckpoints",
"offset": 0,
"slot": "6",
"slot": "7",
"type": "mapping(uint256 => struct IFaultDisputeGame.ResolutionCheckpoint)"
},
{
"bytes": "64",
"label": "startingOutputRoot",
"offset": 0,
"slot": "7",
"slot": "8",
"type": "struct OutputRoot"
}
]
\ No newline at end of file
......@@ -140,8 +140,8 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver {
}
/// @notice Semantic version.
/// @custom:semver 3.9.0
string public constant version = "3.9.0";
/// @custom:semver 3.10.0
string public constant version = "3.10.0";
/// @notice Constructs the OptimismPortal contract.
constructor(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds) {
......
......@@ -14,6 +14,9 @@ import { Clone } from "@solady/utils/Clone.sol";
import { Types } from "src/libraries/Types.sol";
import { ISemver } from "src/universal/ISemver.sol";
import { Types } from "src/libraries/Types.sol";
import { Hashing } from "src/libraries/Hashing.sol";
import { RLPReader } from "src/libraries/rlp/RLPReader.sol";
import "src/dispute/lib/Types.sol";
import "src/dispute/lib/Errors.sol";
......@@ -60,9 +63,14 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
/// @notice The global root claim's position is always at gindex 1.
Position internal constant ROOT_POSITION = Position.wrap(1);
/// @notice The index of the block number in the RLP-encoded block header.
/// @dev Consensus encoding reference:
/// https://github.com/paradigmxyz/reth/blob/5f82993c23164ce8ccdc7bf3ae5085205383a5c8/crates/primitives/src/header.rs#L368
uint256 internal constant HEADER_BLOCK_NUMBER_INDEX = 8;
/// @notice Semantic version.
/// @custom:semver 1.0.0
string public constant version = "1.0.0";
/// @custom:semver 1.1.0
string public constant version = "1.1.0";
/// @notice The starting timestamp of the game
Timestamp public createdAt;
......@@ -76,6 +84,13 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
/// @notice Flag for the `initialize` function to prevent re-initialization.
bool internal initialized;
/// @notice Flag for whether or not the L2 block number claim has been invalidated via `challengeRootL2Block`.
bool public l2BlockNumberChallenged;
/// @notice The challenger of the L2 block number claim. Should always be `address(0)` if `l2BlockNumberChallenged`
/// is `false`. Should be the address of the challenger if `l2BlockNumberChallenged` is `true`.
address internal l2BlockNumberChallenger;
/// @notice An append-only array of all claims made during the dispute game.
ClaimData[] public claimData;
......@@ -321,6 +336,10 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
revert CannotDefendRootClaim();
}
// INVARIANT: No moves against the root claim can be made after it has been challenged with
// `challengeRootL2Block`.`
if (l2BlockNumberChallenged && _challengeIndex == 0) revert L2BlockNumberChallenged();
// INVARIANT: A move can never surpass the `MAX_GAME_DEPTH`. The only option to counter a
// claim at this depth is to perform a single instruction step on-chain via
// the `step` function to prove that the state transition produces an unexpected
......@@ -462,6 +481,54 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
startingRootHash_ = startingOutputRoot.root;
}
/// @notice Challenges the root L2 block number by providing the preimage of the output root and the L2 block header
/// and showing that the committed L2 block number is incorrect relative to the claimed L2 block number.
/// @param _outputRootProof The output root proof.
/// @param _headerRLP The RLP-encoded L2 block header.
function challengeRootL2Block(
Types.OutputRootProof calldata _outputRootProof,
bytes calldata _headerRLP
)
external
{
// INVARIANT: Moves cannot be made unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();
// The root L2 block claim can only be challenged once.
if (l2BlockNumberChallenged) revert L2BlockNumberChallenged();
// Verify the output root preimage.
if (Hashing.hashOutputRootProof(_outputRootProof) != rootClaim().raw()) revert InvalidOutputRootProof();
// Verify the block hash preimage.
if (keccak256(_headerRLP) != _outputRootProof.latestBlockhash) revert InvalidHeaderRLP();
// Decode the header RLP to find the number of the block. In the consensus encoding, the timestamp
// is the 9th element in the list that represents the block header.
RLPReader.RLPItem[] memory headerContents = RLPReader.readList(RLPReader.toRLPItem(_headerRLP));
bytes memory rawBlockNumber = RLPReader.readBytes(headerContents[HEADER_BLOCK_NUMBER_INDEX]);
// Sanity check the block number string length.
if (rawBlockNumber.length > 32) revert InvalidHeaderRLP();
// Convert the raw, left-aligned block number to a uint256 by aligning it as a big-endian
// number in the low-order bytes of a 32-byte word.
//
// SAFETY: The length of `rawBlockNumber` is checked above to ensure it is at most 32 bytes.
uint256 blockNumber;
assembly {
blockNumber := shr(shl(0x03, sub(0x20, mload(rawBlockNumber))), mload(add(rawBlockNumber, 0x20)))
}
// Ensure the block number does not match the block number claimed in the dispute game.
if (blockNumber == l2BlockNumber()) revert BlockNumberMatches();
// Issue a special counter to the root claim. This counter will always win the root claim subgame, and receive
// the bond from the root claimant.
l2BlockNumberChallenger = msg.sender;
l2BlockNumberChallenged = true;
}
////////////////////////////////////////////////////////////////
// `IDisputeGame` impl //
////////////////////////////////////////////////////////////////
......@@ -565,16 +632,25 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
if (checkpoint.subgameIndex == challengeIndicesLen) {
address countered = checkpoint.counteredBy;
// Once a subgame is resolved, we percolate the result up the DAG so subsequent calls to
// resolveClaim will not need to traverse this subgame.
subgameRootClaim.counteredBy = countered;
// Mark the subgame as resolved.
resolvedSubgames[_claimIndex] = true;
// If the parent was not successfully countered, pay out the parent's bond to the claimant.
// If the parent was successfully countered, pay out the parent's bond to the challenger.
_distributeBond(countered == address(0) ? subgameRootClaim.claimant : countered, subgameRootClaim);
// Distribute the bond to the appropriate party.
if (_claimIndex == 0 && l2BlockNumberChallenged) {
// Special case: If the root claim has been challenged with the `challengeRootL2Block` function,
// the bond is always paid out to the issuer of that challenge.
address challenger = l2BlockNumberChallenger;
_distributeBond(challenger, subgameRootClaim);
subgameRootClaim.counteredBy = challenger;
} else {
// If the parent was not successfully countered, pay out the parent's bond to the claimant.
// If the parent was successfully countered, pay out the parent's bond to the challenger.
_distributeBond(countered == address(0) ? subgameRootClaim.claimant : countered, subgameRootClaim);
// Once a subgame is resolved, we percolate the result up the DAG so subsequent calls to
// resolveClaim will not need to traverse this subgame.
subgameRootClaim.counteredBy = countered;
}
}
}
......@@ -670,9 +746,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone, ISemver {
credit[_recipient] = 0;
// Revert if the recipient has no credit to claim.
if (recipientCredit == 0) {
revert NoCreditToClaim();
}
if (recipientCredit == 0) revert NoCreditToClaim();
// Try to withdraw the WETH amount so it can be used here.
WETH.withdraw(_recipient, recipientCredit);
......
......@@ -45,7 +45,7 @@ error CannotDefendRootClaim();
/// @notice Thrown when a claim is attempting to be made that already exists.
error ClaimAlreadyExists();
/// @notice Thrown when a given claim is invalid (0).
/// @notice Thrown when a given claim is invalid.
error InvalidClaim();
/// @notice Thrown when an action that requires the game to be `IN_PROGRESS` is invoked when
......@@ -104,6 +104,19 @@ error DuplicateStep();
/// @notice Thrown when an anchor root is not found for a given game type.
error AnchorRootNotFound();
/// @notice Thrown when an output root proof is invalid.
error InvalidOutputRootProof();
/// @notice Thrown when header RLP is invalid with respect to the block hash in an output root proof.
error InvalidHeaderRLP();
/// @notice Thrown when there is a match between the block number in the output root proof and the block number
/// claimed in the dispute game.
error BlockNumberMatches();
/// @notice Thrown when the L2 block number claim has already been challenged.
error L2BlockNumberChallenged();
////////////////////////////////////////////////////////////////
// `PermissionedDisputeGame` Errors //
////////////////////////////////////////////////////////////////
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
/// @notice The length of an RLP item must be greater than zero to be decodable
error EmptyItem();
/// @notice The decoded item type for list is not a list item
error UnexpectedString();
/// @notice The RLP item has an invalid data remainder
error InvalidDataRemainder();
/// @notice Decoded item type for bytes is not a string item
error UnexpectedList();
/// @notice The length of the content must be greater than the RLP item length
error ContentLengthMismatch();
/// @notice Invalid RLP header for RLP item
error InvalidHeader();
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import "./RLPErrors.sol";
/// @custom:attribution https://github.com/hamdiallam/Solidity-RLP
/// @title RLPReader
/// @notice RLPReader is a library for parsing RLP-encoded byte arrays into Solidity types. Adapted
......@@ -34,7 +36,7 @@ library RLPReader {
/// @return out_ Output memory reference.
function toRLPItem(bytes memory _in) internal pure returns (RLPItem memory out_) {
// Empty arrays are not RLP items.
require(_in.length > 0, "RLPReader: length of an RLP item must be greater than zero to be decodable");
if (_in.length == 0) revert EmptyItem();
MemoryPointer ptr;
assembly {
......@@ -50,9 +52,9 @@ library RLPReader {
function readList(RLPItem memory _in) internal pure returns (RLPItem[] memory out_) {
(uint256 listOffset, uint256 listLength, RLPItemType itemType) = _decodeLength(_in);
require(itemType == RLPItemType.LIST_ITEM, "RLPReader: decoded item type for list is not a list item");
if (itemType != RLPItemType.LIST_ITEM) revert UnexpectedString();
require(listOffset + listLength == _in.length, "RLPReader: list item has an invalid data remainder");
if (listOffset + listLength != _in.length) revert InvalidDataRemainder();
// Solidity in-memory arrays can't be increased in size, but *can* be decreased in size by
// writing to the length. Since we can't know the number of RLP items without looping over
......@@ -97,9 +99,9 @@ library RLPReader {
function readBytes(RLPItem memory _in) internal pure returns (bytes memory out_) {
(uint256 itemOffset, uint256 itemLength, RLPItemType itemType) = _decodeLength(_in);
require(itemType == RLPItemType.DATA_ITEM, "RLPReader: decoded item type for bytes is not a data item");
if (itemType != RLPItemType.DATA_ITEM) revert UnexpectedList();
require(_in.length == itemOffset + itemLength, "RLPReader: bytes value contains an invalid remainder");
if (_in.length != itemOffset + itemLength) revert InvalidDataRemainder();
out_ = _copy(_in.ptr, itemOffset, itemLength);
}
......@@ -131,7 +133,7 @@ library RLPReader {
// Short-circuit if there's nothing to decode, note that we perform this check when
// the user creates an RLP item via toRLPItem, but it's always possible for them to bypass
// that function and create an RLP item directly. So we need to check this anyway.
require(_in.length > 0, "RLPReader: length of an RLP item must be greater than zero to be decodable");
if (_in.length == 0) revert EmptyItem();
MemoryPointer ptr = _in.ptr;
uint256 prefix;
......@@ -148,50 +150,37 @@ library RLPReader {
// slither-disable-next-line variable-scope
uint256 strLen = prefix - 0x80;
require(
_in.length > strLen, "RLPReader: length of content must be greater than string length (short string)"
);
if (_in.length <= strLen) revert ContentLengthMismatch();
bytes1 firstByteOfContent;
assembly {
firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff))
}
require(
strLen != 1 || firstByteOfContent >= 0x80,
"RLPReader: invalid prefix, single byte < 0x80 are not prefixed (short string)"
);
if (strLen == 1 && firstByteOfContent < 0x80) revert InvalidHeader();
return (1, strLen, RLPItemType.DATA_ITEM);
} else if (prefix <= 0xbf) {
// Long string.
uint256 lenOfStrLen = prefix - 0xb7;
require(
_in.length > lenOfStrLen,
"RLPReader: length of content must be > than length of string length (long string)"
);
if (_in.length <= lenOfStrLen) revert ContentLengthMismatch();
bytes1 firstByteOfContent;
assembly {
firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff))
}
require(
firstByteOfContent != 0x00, "RLPReader: length of content must not have any leading zeros (long string)"
);
if (firstByteOfContent == 0x00) revert InvalidHeader();
uint256 strLen;
assembly {
strLen := shr(sub(256, mul(8, lenOfStrLen)), mload(add(ptr, 1)))
}
require(strLen > 55, "RLPReader: length of content must be greater than 55 bytes (long string)");
if (strLen <= 55) revert InvalidHeader();
require(
_in.length > lenOfStrLen + strLen,
"RLPReader: length of content must be greater than total length (long string)"
);
if (_in.length <= lenOfStrLen + strLen) revert ContentLengthMismatch();
return (1 + lenOfStrLen, strLen, RLPItemType.DATA_ITEM);
} else if (prefix <= 0xf7) {
......@@ -199,38 +188,30 @@ library RLPReader {
// slither-disable-next-line variable-scope
uint256 listLen = prefix - 0xc0;
require(_in.length > listLen, "RLPReader: length of content must be greater than list length (short list)");
if (_in.length <= listLen) revert ContentLengthMismatch();
return (1, listLen, RLPItemType.LIST_ITEM);
} else {
// Long list.
uint256 lenOfListLen = prefix - 0xf7;
require(
_in.length > lenOfListLen,
"RLPReader: length of content must be > than length of list length (long list)"
);
if (_in.length <= lenOfListLen) revert ContentLengthMismatch();
bytes1 firstByteOfContent;
assembly {
firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff))
}
require(
firstByteOfContent != 0x00, "RLPReader: length of content must not have any leading zeros (long list)"
);
if (firstByteOfContent == 0x00) revert InvalidHeader();
uint256 listLen;
assembly {
listLen := shr(sub(256, mul(8, lenOfListLen)), mload(add(ptr, 1)))
}
require(listLen > 55, "RLPReader: length of content must be greater than 55 bytes (long list)");
if (listLen <= 55) revert InvalidHeader();
require(
_in.length > lenOfListLen + listLen,
"RLPReader: length of content must be greater than total length (long list)"
);
if (_in.length <= lenOfListLen + listLen) revert ContentLengthMismatch();
return (1 + lenOfListLen, listLen, RLPItemType.LIST_ITEM);
}
......
......@@ -21,7 +21,7 @@ contract DeploymentSummary is DeploymentSummaryCode {
address internal constant l1StandardBridgeProxyAddress = 0x0c8b5822b6e02CDa722174F19A1439A7495a3fA6;
address internal constant l2OutputOracleAddress = 0x19652082F846171168Daf378C4fD3ee85a0D4A60;
address internal constant l2OutputOracleProxyAddress = 0x8B71b41D4dBEb2b6821d44692d3fACAAf77480Bb;
address internal constant optimismPortalAddress = 0xb7B7121AEcAd4F33131222efFFA34aB6c382ef4c;
address internal constant optimismPortalAddress = 0xFc9AD479AC641888D36EE5Dc265bcD3ecceBDc58;
address internal constant optimismPortalProxyAddress = 0x978e3286EB805934215a88694d80b09aDed68D90;
address internal constant protocolVersionsAddress = 0xfbfD64a6C0257F613feFCe050Aa30ecC3E3d7C3F;
address internal constant protocolVersionsProxyAddress = 0x416C42991d05b31E9A6dC209e91AD22b79D87Ae6;
......@@ -437,7 +437,7 @@ contract DeploymentSummary is DeploymentSummaryCode {
value = hex"000000000000000000000000000000000000000000000000000000000000000a";
vm.store(systemOwnerSafeAddress, slot, value);
slot = hex"360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
value = hex"000000000000000000000000b7b7121aecad4f33131222efffa34ab6c382ef4c";
value = hex"000000000000000000000000fc9ad479ac641888d36ee5dc265bcd3eccebdc58";
vm.store(optimismPortalProxyAddress, slot, value);
slot = hex"0000000000000000000000000000000000000000000000000000000000000000";
value = hex"0000000000000000000000000000000000000000000000000000000000000001";
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -4,6 +4,7 @@ pragma solidity 0.8.15;
import { stdError } from "forge-std/Test.sol";
import { Test } from "forge-std/Test.sol";
import { RLPReader } from "src/libraries/rlp/RLPReader.sol";
import "src/libraries/rlp/RLPErrors.sol";
contract RLPReader_readBytes_Test is Test {
function test_readBytes_bytestring00_succeeds() external pure {
......@@ -19,27 +20,27 @@ contract RLPReader_readBytes_Test is Test {
}
function test_readBytes_revertListItem_reverts() external {
vm.expectRevert("RLPReader: decoded item type for bytes is not a data item");
vm.expectRevert(UnexpectedList.selector);
RLPReader.readBytes(hex"c7c0c1c0c3c0c1c0");
}
function test_readBytes_invalidStringLength_reverts() external {
vm.expectRevert("RLPReader: length of content must be > than length of string length (long string)");
vm.expectRevert(ContentLengthMismatch.selector);
RLPReader.readBytes(hex"b9");
}
function test_readBytes_invalidListLength_reverts() external {
vm.expectRevert("RLPReader: length of content must be > than length of list length (long list)");
vm.expectRevert(ContentLengthMismatch.selector);
RLPReader.readBytes(hex"ff");
}
function test_readBytes_invalidRemainder_reverts() external {
vm.expectRevert("RLPReader: bytes value contains an invalid remainder");
vm.expectRevert(InvalidDataRemainder.selector);
RLPReader.readBytes(hex"800a");
}
function test_readBytes_invalidPrefix_reverts() external {
vm.expectRevert("RLPReader: invalid prefix, single byte < 0x80 are not prefixed (short string)");
vm.expectRevert(InvalidHeader.selector);
RLPReader.readBytes(hex"810a");
}
}
......@@ -135,101 +136,101 @@ contract RLPReader_readList_Test is Test {
}
function test_readList_invalidShortList_reverts() external {
vm.expectRevert("RLPReader: length of content must be greater than list length (short list)");
vm.expectRevert(ContentLengthMismatch.selector);
RLPReader.readList(hex"efdebd");
}
function test_readList_longStringLength_reverts() external {
vm.expectRevert("RLPReader: length of content must be greater than list length (short list)");
vm.expectRevert(ContentLengthMismatch.selector);
RLPReader.readList(hex"efb83600");
}
function test_readList_notLongEnough_reverts() external {
vm.expectRevert("RLPReader: length of content must be greater than list length (short list)");
vm.expectRevert(ContentLengthMismatch.selector);
RLPReader.readList(hex"efdebdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
}
function test_readList_int32Overflow_reverts() external {
vm.expectRevert("RLPReader: length of content must be greater than total length (long string)");
vm.expectRevert(ContentLengthMismatch.selector);
RLPReader.readList(hex"bf0f000000000000021111");
}
function test_readList_int32Overflow2_reverts() external {
vm.expectRevert("RLPReader: length of content must be greater than total length (long list)");
vm.expectRevert(ContentLengthMismatch.selector);
RLPReader.readList(hex"ff0f000000000000021111");
}
function test_readList_incorrectLengthInArray_reverts() external {
vm.expectRevert("RLPReader: length of content must not have any leading zeros (long string)");
vm.expectRevert(InvalidHeader.selector);
RLPReader.readList(hex"b9002100dc2b275d0f74e8a53e6f4ec61b27f24278820be3f82ea2110e582081b0565df0");
}
function test_readList_leadingZerosInLongLengthArray1_reverts() external {
vm.expectRevert("RLPReader: length of content must not have any leading zeros (long string)");
vm.expectRevert(InvalidHeader.selector);
RLPReader.readList(
hex"b90040000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
);
}
function test_readList_leadingZerosInLongLengthArray2_reverts() external {
vm.expectRevert("RLPReader: length of content must not have any leading zeros (long string)");
vm.expectRevert(InvalidHeader.selector);
RLPReader.readList(hex"b800");
}
function test_readList_leadingZerosInLongLengthList1_reverts() external {
vm.expectRevert("RLPReader: length of content must not have any leading zeros (long list)");
vm.expectRevert(InvalidHeader.selector);
RLPReader.readList(
hex"fb00000040000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
);
}
function test_readList_nonOptimalLongLengthArray1_reverts() external {
vm.expectRevert("RLPReader: length of content must be greater than 55 bytes (long string)");
vm.expectRevert(InvalidHeader.selector);
RLPReader.readList(hex"b81000112233445566778899aabbccddeeff");
}
function test_readList_nonOptimalLongLengthArray2_reverts() external {
vm.expectRevert("RLPReader: length of content must be greater than 55 bytes (long string)");
vm.expectRevert(InvalidHeader.selector);
RLPReader.readList(hex"b801ff");
}
function test_readList_invalidValue_reverts() external {
vm.expectRevert("RLPReader: length of content must be greater than string length (short string)");
vm.expectRevert(ContentLengthMismatch.selector);
RLPReader.readList(hex"91");
}
function test_readList_invalidRemainder_reverts() external {
vm.expectRevert("RLPReader: list item has an invalid data remainder");
vm.expectRevert(InvalidDataRemainder.selector);
RLPReader.readList(hex"c000");
}
function test_readList_notEnoughContentForString1_reverts() external {
vm.expectRevert("RLPReader: length of content must be greater than total length (long string)");
vm.expectRevert(ContentLengthMismatch.selector);
RLPReader.readList(hex"ba010000aabbccddeeff");
}
function test_readList_notEnoughContentForString2_reverts() external {
vm.expectRevert("RLPReader: length of content must be greater than total length (long string)");
vm.expectRevert(ContentLengthMismatch.selector);
RLPReader.readList(hex"b840ffeeddccbbaa99887766554433221100");
}
function test_readList_notEnoughContentForList1_reverts() external {
vm.expectRevert("RLPReader: length of content must be greater than total length (long list)");
vm.expectRevert(ContentLengthMismatch.selector);
RLPReader.readList(hex"f90180");
}
function test_readList_notEnoughContentForList2_reverts() external {
vm.expectRevert("RLPReader: length of content must be greater than total length (long list)");
vm.expectRevert(ContentLengthMismatch.selector);
RLPReader.readList(hex"ffffffffffffffffff0001020304050607");
}
function test_readList_longStringLessThan56Bytes_reverts() external {
vm.expectRevert("RLPReader: length of content must be greater than 55 bytes (long string)");
vm.expectRevert(InvalidHeader.selector);
RLPReader.readList(hex"b80100");
}
function test_readList_longListLessThan56Bytes_reverts() external {
vm.expectRevert("RLPReader: length of content must be greater than 55 bytes (long list)");
vm.expectRevert(InvalidHeader.selector);
RLPReader.readList(hex"f80100");
}
}
......@@ -4,6 +4,7 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { MerkleTrie } from "src/libraries/trie/MerkleTrie.sol";
import { FFIInterface } from "test/setup/FFIInterface.sol";
import "src/libraries/rlp/RLPErrors.sol";
contract MerkleTrie_get_Test is Test {
FFIInterface constant ffi = FFIInterface(address(uint160(uint256(keccak256(abi.encode("optimism.ffi"))))));
......@@ -186,7 +187,7 @@ contract MerkleTrie_get_Test is Test {
hex"f84580a0582eed8dd051b823d13f8648cdcd08aa2d8dac239f458863c4620e8c4d605debca83206262856176616c32ca83206363856176616c3380808080808080808080808080";
proof[4] = hex"ca83206262856176616c32";
vm.expectRevert("RLPReader: decoded item type for list is not a list item");
vm.expectRevert(UnexpectedString.selector);
MerkleTrie.get(key, proof, root);
}
......@@ -198,7 +199,7 @@ contract MerkleTrie_get_Test is Test {
proof[1] = hex"d780808080808080808080c32081aac32081ab8080808080";
proof[2] = hex"c32081aa000000000000000000000000000000";
vm.expectRevert("RLPReader: list item has an invalid data remainder");
vm.expectRevert(InvalidDataRemainder.selector);
MerkleTrie.get(key, proof, root);
}
......@@ -328,7 +329,7 @@ contract MerkleTrie_get_Test is Test {
// Generate an invalid test case where the proof is malformed.
(bytes32 root, bytes memory key,, bytes[] memory proof) = ffi.getMerkleTrieFuzzCase("corrupted_proof");
vm.expectRevert("RLPReader: decoded item type for list is not a list item");
vm.expectRevert(UnexpectedString.selector);
MerkleTrie.get(key, proof, root);
}
......@@ -338,7 +339,7 @@ contract MerkleTrie_get_Test is Test {
// length designates within the RLP list encoding.
(bytes32 root, bytes memory key,, bytes[] memory proof) = ffi.getMerkleTrieFuzzCase("invalid_data_remainder");
vm.expectRevert("RLPReader: list item has an invalid data remainder");
vm.expectRevert(InvalidDataRemainder.selector);
MerkleTrie.get(key, proof, root);
}
......
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