github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/src/libraries/rlp/RLPReader.sol (about)

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity ^0.8.8;
     3  
     4  /// @custom:attribution https://github.com/hamdiallam/Solidity-RLP
     5  /// @title RLPReader
     6  /// @notice RLPReader is a library for parsing RLP-encoded byte arrays into Solidity types. Adapted
     7  ///         from Solidity-RLP (https://github.com/hamdiallam/Solidity-RLP) by Hamdi Allam with
     8  ///         various tweaks to improve readability.
     9  library RLPReader {
    10      /// @notice Custom pointer type to avoid confusion between pointers and uint256s.
    11      type MemoryPointer is uint256;
    12  
    13      /// @notice RLP item types.
    14      /// @custom:value DATA_ITEM Represents an RLP data item (NOT a list).
    15      /// @custom:value LIST_ITEM Represents an RLP list item.
    16      enum RLPItemType {
    17          DATA_ITEM,
    18          LIST_ITEM
    19      }
    20  
    21      /// @notice Struct representing an RLP item.
    22      /// @custom:field length Length of the RLP item.
    23      /// @custom:field ptr    Pointer to the RLP item in memory.
    24      struct RLPItem {
    25          uint256 length;
    26          MemoryPointer ptr;
    27      }
    28  
    29      /// @notice Max list length that this library will accept.
    30      uint256 internal constant MAX_LIST_LENGTH = 32;
    31  
    32      /// @notice Converts bytes to a reference to memory position and length.
    33      /// @param _in Input bytes to convert.
    34      /// @return out_ Output memory reference.
    35      function toRLPItem(bytes memory _in) internal pure returns (RLPItem memory out_) {
    36          // Empty arrays are not RLP items.
    37          require(_in.length > 0, "RLPReader: length of an RLP item must be greater than zero to be decodable");
    38  
    39          MemoryPointer ptr;
    40          assembly {
    41              ptr := add(_in, 32)
    42          }
    43  
    44          out_ = RLPItem({ length: _in.length, ptr: ptr });
    45      }
    46  
    47      /// @notice Reads an RLP list value into a list of RLP items.
    48      /// @param _in RLP list value.
    49      /// @return out_ Decoded RLP list items.
    50      function readList(RLPItem memory _in) internal pure returns (RLPItem[] memory out_) {
    51          (uint256 listOffset, uint256 listLength, RLPItemType itemType) = _decodeLength(_in);
    52  
    53          require(itemType == RLPItemType.LIST_ITEM, "RLPReader: decoded item type for list is not a list item");
    54  
    55          require(listOffset + listLength == _in.length, "RLPReader: list item has an invalid data remainder");
    56  
    57          // Solidity in-memory arrays can't be increased in size, but *can* be decreased in size by
    58          // writing to the length. Since we can't know the number of RLP items without looping over
    59          // the entire input, we'd have to loop twice to accurately size this array. It's easier to
    60          // simply set a reasonable maximum list length and decrease the size before we finish.
    61          out_ = new RLPItem[](MAX_LIST_LENGTH);
    62  
    63          uint256 itemCount = 0;
    64          uint256 offset = listOffset;
    65          while (offset < _in.length) {
    66              (uint256 itemOffset, uint256 itemLength,) = _decodeLength(
    67                  RLPItem({ length: _in.length - offset, ptr: MemoryPointer.wrap(MemoryPointer.unwrap(_in.ptr) + offset) })
    68              );
    69  
    70              // We don't need to check itemCount < out.length explicitly because Solidity already
    71              // handles this check on our behalf, we'd just be wasting gas.
    72              out_[itemCount] = RLPItem({
    73                  length: itemLength + itemOffset,
    74                  ptr: MemoryPointer.wrap(MemoryPointer.unwrap(_in.ptr) + offset)
    75              });
    76  
    77              itemCount += 1;
    78              offset += itemOffset + itemLength;
    79          }
    80  
    81          // Decrease the array size to match the actual item count.
    82          assembly {
    83              mstore(out_, itemCount)
    84          }
    85      }
    86  
    87      /// @notice Reads an RLP list value into a list of RLP items.
    88      /// @param _in RLP list value.
    89      /// @return out_ Decoded RLP list items.
    90      function readList(bytes memory _in) internal pure returns (RLPItem[] memory out_) {
    91          out_ = readList(toRLPItem(_in));
    92      }
    93  
    94      /// @notice Reads an RLP bytes value into bytes.
    95      /// @param _in RLP bytes value.
    96      /// @return out_ Decoded bytes.
    97      function readBytes(RLPItem memory _in) internal pure returns (bytes memory out_) {
    98          (uint256 itemOffset, uint256 itemLength, RLPItemType itemType) = _decodeLength(_in);
    99  
   100          require(itemType == RLPItemType.DATA_ITEM, "RLPReader: decoded item type for bytes is not a data item");
   101  
   102          require(_in.length == itemOffset + itemLength, "RLPReader: bytes value contains an invalid remainder");
   103  
   104          out_ = _copy(_in.ptr, itemOffset, itemLength);
   105      }
   106  
   107      /// @notice Reads an RLP bytes value into bytes.
   108      /// @param _in RLP bytes value.
   109      /// @return out_ Decoded bytes.
   110      function readBytes(bytes memory _in) internal pure returns (bytes memory out_) {
   111          out_ = readBytes(toRLPItem(_in));
   112      }
   113  
   114      /// @notice Reads the raw bytes of an RLP item.
   115      /// @param _in RLP item to read.
   116      /// @return out_ Raw RLP bytes.
   117      function readRawBytes(RLPItem memory _in) internal pure returns (bytes memory out_) {
   118          out_ = _copy(_in.ptr, 0, _in.length);
   119      }
   120  
   121      /// @notice Decodes the length of an RLP item.
   122      /// @param _in RLP item to decode.
   123      /// @return offset_ Offset of the encoded data.
   124      /// @return length_ Length of the encoded data.
   125      /// @return type_ RLP item type (LIST_ITEM or DATA_ITEM).
   126      function _decodeLength(RLPItem memory _in)
   127          private
   128          pure
   129          returns (uint256 offset_, uint256 length_, RLPItemType type_)
   130      {
   131          // Short-circuit if there's nothing to decode, note that we perform this check when
   132          // the user creates an RLP item via toRLPItem, but it's always possible for them to bypass
   133          // that function and create an RLP item directly. So we need to check this anyway.
   134          require(_in.length > 0, "RLPReader: length of an RLP item must be greater than zero to be decodable");
   135  
   136          MemoryPointer ptr = _in.ptr;
   137          uint256 prefix;
   138          assembly {
   139              prefix := byte(0, mload(ptr))
   140          }
   141  
   142          if (prefix <= 0x7f) {
   143              // Single byte.
   144              return (0, 1, RLPItemType.DATA_ITEM);
   145          } else if (prefix <= 0xb7) {
   146              // Short string.
   147  
   148              // slither-disable-next-line variable-scope
   149              uint256 strLen = prefix - 0x80;
   150  
   151              require(
   152                  _in.length > strLen, "RLPReader: length of content must be greater than string length (short string)"
   153              );
   154  
   155              bytes1 firstByteOfContent;
   156              assembly {
   157                  firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff))
   158              }
   159  
   160              require(
   161                  strLen != 1 || firstByteOfContent >= 0x80,
   162                  "RLPReader: invalid prefix, single byte < 0x80 are not prefixed (short string)"
   163              );
   164  
   165              return (1, strLen, RLPItemType.DATA_ITEM);
   166          } else if (prefix <= 0xbf) {
   167              // Long string.
   168              uint256 lenOfStrLen = prefix - 0xb7;
   169  
   170              require(
   171                  _in.length > lenOfStrLen,
   172                  "RLPReader: length of content must be > than length of string length (long string)"
   173              );
   174  
   175              bytes1 firstByteOfContent;
   176              assembly {
   177                  firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff))
   178              }
   179  
   180              require(
   181                  firstByteOfContent != 0x00, "RLPReader: length of content must not have any leading zeros (long string)"
   182              );
   183  
   184              uint256 strLen;
   185              assembly {
   186                  strLen := shr(sub(256, mul(8, lenOfStrLen)), mload(add(ptr, 1)))
   187              }
   188  
   189              require(strLen > 55, "RLPReader: length of content must be greater than 55 bytes (long string)");
   190  
   191              require(
   192                  _in.length > lenOfStrLen + strLen,
   193                  "RLPReader: length of content must be greater than total length (long string)"
   194              );
   195  
   196              return (1 + lenOfStrLen, strLen, RLPItemType.DATA_ITEM);
   197          } else if (prefix <= 0xf7) {
   198              // Short list.
   199              // slither-disable-next-line variable-scope
   200              uint256 listLen = prefix - 0xc0;
   201  
   202              require(_in.length > listLen, "RLPReader: length of content must be greater than list length (short list)");
   203  
   204              return (1, listLen, RLPItemType.LIST_ITEM);
   205          } else {
   206              // Long list.
   207              uint256 lenOfListLen = prefix - 0xf7;
   208  
   209              require(
   210                  _in.length > lenOfListLen,
   211                  "RLPReader: length of content must be > than length of list length (long list)"
   212              );
   213  
   214              bytes1 firstByteOfContent;
   215              assembly {
   216                  firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff))
   217              }
   218  
   219              require(
   220                  firstByteOfContent != 0x00, "RLPReader: length of content must not have any leading zeros (long list)"
   221              );
   222  
   223              uint256 listLen;
   224              assembly {
   225                  listLen := shr(sub(256, mul(8, lenOfListLen)), mload(add(ptr, 1)))
   226              }
   227  
   228              require(listLen > 55, "RLPReader: length of content must be greater than 55 bytes (long list)");
   229  
   230              require(
   231                  _in.length > lenOfListLen + listLen,
   232                  "RLPReader: length of content must be greater than total length (long list)"
   233              );
   234  
   235              return (1 + lenOfListLen, listLen, RLPItemType.LIST_ITEM);
   236          }
   237      }
   238  
   239      /// @notice Copies the bytes from a memory location.
   240      /// @param _src    Pointer to the location to read from.
   241      /// @param _offset Offset to start reading from.
   242      /// @param _length Number of bytes to read.
   243      /// @return out_ Copied bytes.
   244      function _copy(MemoryPointer _src, uint256 _offset, uint256 _length) private pure returns (bytes memory out_) {
   245          out_ = new bytes(_length);
   246          if (_length == 0) {
   247              return out_;
   248          }
   249  
   250          // Mostly based on Solidity's copy_memory_to_memory:
   251          // https://github.com/ethereum/solidity/blob/34dd30d71b4da730488be72ff6af7083cf2a91f6/libsolidity/codegen/YulUtilFunctions.cpp#L102-L114
   252          uint256 src = MemoryPointer.unwrap(_src) + _offset;
   253          assembly {
   254              let dest := add(out_, 32)
   255              let i := 0
   256              for { } lt(i, _length) { i := add(i, 32) } { mstore(add(dest, i), mload(add(src, i))) }
   257  
   258              if gt(i, _length) { mstore(add(dest, _length), 0) }
   259          }
   260      }
   261  }