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 }