diff --git a/src/utils/FixedPointMathLib.sol b/src/utils/FixedPointMathLib.sol index 1eb2a8f8c..37103d59f 100644 --- a/src/utils/FixedPointMathLib.sol +++ b/src/utils/FixedPointMathLib.sol @@ -489,7 +489,7 @@ library FixedPointMathLib { // Invert `d mod 2**256` // Now that `d` is an odd number, it has an inverse // modulo `2**256` such that `d * inv = 1 mod 2**256`. - // Compute the inverse by starting with a seed that is correct + // Compute the inverse by starting with a seed that is // correct for four bits. That is, `d * inv = 1 mod 2**4`. let inv := xor(2, mul(3, d)) // Now use Newton-Raphson iteration to improve the precision. @@ -1178,7 +1178,7 @@ library FixedPointMathLib { /// @dev Returns `a + (b - a) * (t - begin) / (end - begin)`, /// with `t` clamped between `begin` and `end` (inclusive). /// Agnostic to the order of (`a`, `b`) and (`end`, `begin`). - /// If `begins == end`, returns `t <= begin ? a : b`. + /// If `begin == end`, returns `t <= begin ? a : b`. function lerp(uint256 a, uint256 b, uint256 t, uint256 begin, uint256 end) internal pure @@ -1196,7 +1196,7 @@ library FixedPointMathLib { /// @dev Returns `a + (b - a) * (t - begin) / (end - begin)`. /// with `t` clamped between `begin` and `end` (inclusive). /// Agnostic to the order of (`a`, `b`) and (`end`, `begin`). - /// If `begins == end`, returns `t <= begin ? a : b`. + /// If `begin == end`, returns `t <= begin ? a : b`. function lerp(int256 a, int256 b, int256 t, int256 begin, int256 end) internal pure diff --git a/src/utils/LibRLP.sol b/src/utils/LibRLP.sol index 3e9f55ead..e589f46c9 100644 --- a/src/utils/LibRLP.sol +++ b/src/utils/LibRLP.sol @@ -15,6 +15,23 @@ library LibRLP { uint256 _data; } + /// @dev A pointer to a decoded RLP item in memory. + /// It is a lightweight view (offset, length, and type) into the RLP encoded + /// data being decoded. The fields are packed into a single word for efficiency. + /// Items returned by `decodeList` reference memory in the original RLP encoded data. + /// Mutating the original RLP encoded data may invalidate the items. + struct Item { + // Do NOT modify the `_data` directly. + uint256 _data; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The RLP item is malformed and cannot be decoded. + error RLPDecodingFailed(); + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CREATE ADDRESS PREDICTION */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -374,10 +391,271 @@ library LibRLP { } } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RLP DECODING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: + // - The decoding takes a permissive approach, accepting non-canonical encodings + // (e.g. scalar values with leading zero bytes). This prioritizes compatibility + // over strict adherence to the Yellow Paper's canonicalization rules. + // - Single bytes in the `[0x00, 0x7f]` range that are improperly wrapped with a + // length prefix are still rejected as invalid. + // - The top-level decoders do NOT require that the item spans the entire input; + // trailing bytes after a valid item are ignored. + // - `Item`s returned by `decodeList` are views into the original RLP encoded data. + // Mutating the original encoded data may invalidate them. + + /// @dev Decodes the RLP encoded `encoded` into a bytes string. + /// Reverts if `encoded` is not a single RLP data item. + function decodeBytes(bytes memory encoded) internal pure returns (bytes memory) { + return decodeBytes(_toItem(encoded)); + } + + /// @dev Decodes the RLP encoded `encoded` into a uint256 scalar. + /// Reverts if `encoded` is not a single RLP data item of at most 32 bytes. + function decodeUint256(bytes memory encoded) internal pure returns (uint256) { + return decodeUint256(_toItem(encoded)); + } + + /// @dev Decodes the RLP encoded `encoded` into an address. + /// Reverts if `encoded` is not a single RLP data item of 1 or 21 bytes. + function decodeAddress(bytes memory encoded) internal pure returns (address) { + return decodeAddress(_toItem(encoded)); + } + + /// @dev Decodes the RLP encoded `encoded` into a bytes32. + /// Reverts if `encoded` is not a single RLP data item of at most 32 bytes. + function decodeBytes32(bytes memory encoded) internal pure returns (bytes32) { + return decodeBytes32(_toItem(encoded)); + } + + /// @dev Decodes the RLP encoded `encoded` into an array of RLP items. + /// Reverts if `encoded` is not a single RLP list. + /// The returned items are views into `encoded`. See: {Item}. + function decodeList(bytes memory encoded) internal pure returns (Item[] memory) { + return decodeList(_toItem(encoded)); + } + + /// @dev Decodes the RLP `item` into a bytes string. + /// Reverts if `item` is not a single RLP data item. + function decodeBytes(Item memory item) internal pure returns (bytes memory result) { + (uint256 ptr, uint256 end) = _unpack(item); + (uint256 payloadPtr, uint256 payloadLen, uint256 itemIsList) = _decodeLength(ptr, end); + if (itemIsList != 0) revert RLPDecodingFailed(); + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(result, payloadLen) // Store the length. + let o := add(result, 0x20) + // Copy the payload. May read some bytes past the payload, which are zeroized below. + for { let i := 0 } lt(i, payloadLen) { i := add(i, 0x20) } { + mstore(add(o, i), mload(add(payloadPtr, i))) + } + mstore(add(o, payloadLen), 0) // Zeroize the slot after the string. + mstore(0x40, add(o, and(add(payloadLen, 0x1f), not(0x1f)))) // Allocate memory. + } + } + + /// @dev Decodes the RLP `item` into a uint256 scalar. + /// Reverts if `item` is not a single RLP data item of at most 32 bytes. + function decodeUint256(Item memory item) internal pure returns (uint256 result) { + (uint256 ptr, uint256 end) = _unpack(item); + (uint256 payloadPtr, uint256 payloadLen, uint256 itemIsList) = _decodeLength(ptr, end); + if (itemIsList != 0) revert RLPDecodingFailed(); + if (payloadLen > 32) revert RLPDecodingFailed(); + result = _toUint(payloadPtr, payloadLen); + } + + /// @dev Decodes the RLP `item` into an address. + /// Reverts if `item` is not a single RLP data item of 1 or 21 bytes. + function decodeAddress(Item memory item) internal pure returns (address result) { + (uint256 ptr, uint256 end) = _unpack(item); + (uint256 payloadPtr, uint256 payloadLen, uint256 itemIsList) = _decodeLength(ptr, end); + uint256 itemLen; + unchecked { + itemLen = payloadPtr + payloadLen - ptr; + } + if (itemIsList != 0) revert RLPDecodingFailed(); + if (itemLen != 1) if (itemLen != 21) revert RLPDecodingFailed(); + result = address(uint160(_toUint(payloadPtr, payloadLen))); + } + + /// @dev Decodes the RLP `item` into a bytes32. + /// Reverts if `item` is not a single RLP data item of at most 32 bytes. + function decodeBytes32(Item memory item) internal pure returns (bytes32) { + return bytes32(decodeUint256(item)); + } + + /// @dev Decodes the RLP `item` into an array of RLP items. + /// Reverts if `item` is not a single RLP list. + /// The returned items are views into the original encoded data. See: {Item}. + function decodeList(Item memory item) internal pure returns (Item[] memory result) { + (uint256 ptr, uint256 end) = _unpack(item); + (uint256 payloadPtr, uint256 payloadLen, uint256 itemIsList) = _decodeLength(ptr, end); + if (itemIsList == 0) revert RLPDecodingFailed(); + uint256 listEnd; + unchecked { + listEnd = payloadPtr + payloadLen; + } + uint256 structsStart; + /// @solidity memory-safe-assembly + assembly { + structsStart := mload(0x40) + } + // Write the packed `_data` of each child `Item` into the unallocated space, + // and count the number of children. The children tile the list payload exactly. + uint256 n; + for (uint256 q = payloadPtr; q < listEnd;) { + (uint256 pp, uint256 pl,) = _decodeLength(q, listEnd); + /// @solidity memory-safe-assembly + assembly { + mstore(add(structsStart, shl(5, n)), or(q, shl(64, listEnd))) + } + unchecked { + q = pp + pl; + ++n; + } + } + // Build the array header, followed by pointers to each `Item` struct. + /// @solidity memory-safe-assembly + assembly { + let header := add(structsStart, shl(5, n)) + result := header + mstore(header, n) // Store the length of the array. + let ptrs := add(header, 0x20) + for { let i := 0 } lt(i, n) { i := add(i, 1) } { + mstore(add(ptrs, shl(5, i)), add(structsStart, shl(5, i))) + } + mstore(0x40, add(ptrs, shl(5, n))) // Allocate memory. + } + } + + /// @dev Returns the next sibling of `item` within its enclosing list. + /// Returns an empty item if `item` is empty or is the last item in its list. + /// Useful for iterating without allocating an array. See: {decodeList}, {isEmpty}. + function next(Item memory item) internal pure returns (Item memory result) { + (uint256 ptr, uint256 end) = _unpack(item); + if (ptr == 0) return result; + (uint256 payloadPtr, uint256 payloadLen,) = _decodeLength(ptr, end); + unchecked { + uint256 np = payloadPtr + payloadLen; + if (np < end) result._data = np | (end << 64); + } + } + + /// @dev Returns whether `item` is a list. + /// Returns false if `item` is empty. + function isList(Item memory item) internal pure returns (bool result) { + (uint256 ptr, uint256 end) = _unpack(item); + if (ptr == 0) return false; + (,, uint256 itemIsList) = _decodeLength(ptr, end); + result = itemIsList != 0; + } + + /// @dev Returns whether `item` is empty (i.e. a null item). + /// This is the case for items returned by `next` after the last item in a list. + function isEmpty(Item memory item) internal pure returns (bool result) { + result = item._data == 0; + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PRIVATE HELPERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + /// @dev Returns an `Item` viewing the entirety of the RLP encoded `encoded`. + /// The item packs the start pointer of the encoded data in the low 64 bits, + /// and the end pointer (exclusive) in the next 64 bits. + function _toItem(bytes memory encoded) private pure returns (Item memory item) { + /// @solidity memory-safe-assembly + assembly { + let s := add(encoded, 0x20) + item := mload(0x40) + mstore(item, or(s, shl(64, add(s, mload(encoded))))) + mstore(0x40, add(item, 0x20)) + } + } + + /// @dev Unpacks the start pointer `p` and end pointer `e` from `item`. + function _unpack(Item memory item) private pure returns (uint256 ptr, uint256 end) { + uint256 data = item._data; + ptr = uint64(data); + end = uint64(data >> 64); + } + + /// @dev Returns the big-endian integer of the `payloadLen` bytes at `payloadPtr`. + /// Assumes `payloadLen <= 32`. + function _toUint(uint256 payloadPtr, uint256 payloadLen) private pure returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + if payloadLen { result := shr(shl(3, sub(32, payloadLen)), mload(payloadPtr)) } + } + } + + /// @dev Decodes the length and type of the RLP item starting at pointer `p`, + /// bounded by the end pointer `e` (exclusive). + /// Returns the pointer to the payload, the length of the payload, + /// and whether the item is a list (`itemIsList` is 1 for a list, 0 otherwise). + /// Reverts if the item is malformed or extends beyond `e`. + function _decodeLength(uint256 ptr, uint256 end) + private + pure + returns (uint256 payloadPtr, uint256 payloadLen, uint256 itemIsList) + { + uint256 failed; + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + if iszero(lt(ptr, end)) { + failed := 1 + break + } + let b := byte(0, mload(ptr)) + payloadPtr := add(ptr, 1) + // Case: single byte in `[0x00, 0x7f]`. + if lt(b, 0x80) { + payloadPtr := ptr + payloadLen := 1 + break + } + // Case: short string (0-55 bytes). + if lt(b, 0xb8) { + payloadLen := sub(b, 0x80) + // A single byte below `0x80` must not be wrapped with a prefix. + if eq(payloadLen, 1) { failed := lt(byte(0, mload(payloadPtr)), 0x80) } + break + } + // Case: long string (>55 bytes). + if lt(b, 0xc0) { + let l := sub(b, 0xb7) // Length of the length, in `[1, 8]`. + let chunk := mload(payloadPtr) + payloadLen := shr(sub(256, shl(3, l)), chunk) + payloadPtr := add(payloadPtr, l) + // The length must not have a leading zero byte, and must be > 55. + failed := or(iszero(byte(0, chunk)), iszero(gt(payloadLen, 55))) + break + } + itemIsList := 1 + // Case: short list. + if lt(b, 0xf8) { + payloadLen := sub(b, 0xc0) + break + } + // Case: long list. + let ll := sub(b, 0xf7) // Length of the length, in `[1, 8]`. + let c := mload(payloadPtr) + payloadLen := shr(sub(256, shl(3, ll)), c) + payloadPtr := add(payloadPtr, ll) + // The length must not have a leading zero byte, and must be > 55. + failed := or(iszero(byte(0, c)), iszero(gt(payloadLen, 55))) + break + } + // The item must not extend beyond `e`. + if gt(add(payloadPtr, payloadLen), end) { failed := 1 } + } + if (failed != 0) revert RLPDecodingFailed(); + } + /// @dev Updates the tail in `list`. function _updateTail(List memory list, List memory result) private pure { /// @solidity memory-safe-assembly diff --git a/src/utils/g/LibRLP.sol b/src/utils/g/LibRLP.sol index f13e82e86..c67cac869 100644 --- a/src/utils/g/LibRLP.sol +++ b/src/utils/g/LibRLP.sol @@ -13,12 +13,30 @@ struct List { uint256 _data; } +/// @dev A pointer to a decoded RLP item in memory. +/// It is a lightweight view (offset, length, and type) into the RLP encoded +/// data being decoded. The fields are packed into a single word for efficiency. +/// Items returned by `decodeList` reference memory in the original RLP encoded data. +/// Mutating the original RLP encoded data may invalidate the items. +struct Item { + // Do NOT modify the `_data` directly. + uint256 _data; +} + using LibRLP for List global; +using LibRLP for Item global; /// @notice Library for RLP encoding and CREATE address computation. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/g/LibRLP.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibRLP.sol) library LibRLP { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The RLP item is malformed and cannot be decoded. + error RLPDecodingFailed(); + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CREATE ADDRESS PREDICTION */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -378,10 +396,271 @@ library LibRLP { } } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RLP DECODING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: + // - The decoding takes a permissive approach, accepting non-canonical encodings + // (e.g. scalar values with leading zero bytes). This prioritizes compatibility + // over strict adherence to the Yellow Paper's canonicalization rules. + // - Single bytes in the `[0x00, 0x7f]` range that are improperly wrapped with a + // length prefix are still rejected as invalid. + // - The top-level decoders do NOT require that the item spans the entire input; + // trailing bytes after a valid item are ignored. + // - `Item`s returned by `decodeList` are views into the original RLP encoded data. + // Mutating the original encoded data may invalidate them. + + /// @dev Decodes the RLP encoded `encoded` into a bytes string. + /// Reverts if `encoded` is not a single RLP data item. + function decodeBytes(bytes memory encoded) internal pure returns (bytes memory) { + return decodeBytes(_toItem(encoded)); + } + + /// @dev Decodes the RLP encoded `encoded` into a uint256 scalar. + /// Reverts if `encoded` is not a single RLP data item of at most 32 bytes. + function decodeUint256(bytes memory encoded) internal pure returns (uint256) { + return decodeUint256(_toItem(encoded)); + } + + /// @dev Decodes the RLP encoded `encoded` into an address. + /// Reverts if `encoded` is not a single RLP data item of 1 or 21 bytes. + function decodeAddress(bytes memory encoded) internal pure returns (address) { + return decodeAddress(_toItem(encoded)); + } + + /// @dev Decodes the RLP encoded `encoded` into a bytes32. + /// Reverts if `encoded` is not a single RLP data item of at most 32 bytes. + function decodeBytes32(bytes memory encoded) internal pure returns (bytes32) { + return decodeBytes32(_toItem(encoded)); + } + + /// @dev Decodes the RLP encoded `encoded` into an array of RLP items. + /// Reverts if `encoded` is not a single RLP list. + /// The returned items are views into `encoded`. See: {Item}. + function decodeList(bytes memory encoded) internal pure returns (Item[] memory) { + return decodeList(_toItem(encoded)); + } + + /// @dev Decodes the RLP `item` into a bytes string. + /// Reverts if `item` is not a single RLP data item. + function decodeBytes(Item memory item) internal pure returns (bytes memory result) { + (uint256 ptr, uint256 end) = _unpack(item); + (uint256 payloadPtr, uint256 payloadLen, uint256 itemIsList) = _decodeLength(ptr, end); + if (itemIsList != 0) revert RLPDecodingFailed(); + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(result, payloadLen) // Store the length. + let o := add(result, 0x20) + // Copy the payload. May read some bytes past the payload, which are zeroized below. + for { let i := 0 } lt(i, payloadLen) { i := add(i, 0x20) } { + mstore(add(o, i), mload(add(payloadPtr, i))) + } + mstore(add(o, payloadLen), 0) // Zeroize the slot after the string. + mstore(0x40, add(o, and(add(payloadLen, 0x1f), not(0x1f)))) // Allocate memory. + } + } + + /// @dev Decodes the RLP `item` into a uint256 scalar. + /// Reverts if `item` is not a single RLP data item of at most 32 bytes. + function decodeUint256(Item memory item) internal pure returns (uint256 result) { + (uint256 ptr, uint256 end) = _unpack(item); + (uint256 payloadPtr, uint256 payloadLen, uint256 itemIsList) = _decodeLength(ptr, end); + if (itemIsList != 0) revert RLPDecodingFailed(); + if (payloadLen > 32) revert RLPDecodingFailed(); + result = _toUint(payloadPtr, payloadLen); + } + + /// @dev Decodes the RLP `item` into an address. + /// Reverts if `item` is not a single RLP data item of 1 or 21 bytes. + function decodeAddress(Item memory item) internal pure returns (address result) { + (uint256 ptr, uint256 end) = _unpack(item); + (uint256 payloadPtr, uint256 payloadLen, uint256 itemIsList) = _decodeLength(ptr, end); + uint256 itemLen; + unchecked { + itemLen = payloadPtr + payloadLen - ptr; + } + if (itemIsList != 0) revert RLPDecodingFailed(); + if (itemLen != 1) if (itemLen != 21) revert RLPDecodingFailed(); + result = address(uint160(_toUint(payloadPtr, payloadLen))); + } + + /// @dev Decodes the RLP `item` into a bytes32. + /// Reverts if `item` is not a single RLP data item of at most 32 bytes. + function decodeBytes32(Item memory item) internal pure returns (bytes32) { + return bytes32(decodeUint256(item)); + } + + /// @dev Decodes the RLP `item` into an array of RLP items. + /// Reverts if `item` is not a single RLP list. + /// The returned items are views into the original encoded data. See: {Item}. + function decodeList(Item memory item) internal pure returns (Item[] memory result) { + (uint256 ptr, uint256 end) = _unpack(item); + (uint256 payloadPtr, uint256 payloadLen, uint256 itemIsList) = _decodeLength(ptr, end); + if (itemIsList == 0) revert RLPDecodingFailed(); + uint256 listEnd; + unchecked { + listEnd = payloadPtr + payloadLen; + } + uint256 structsStart; + /// @solidity memory-safe-assembly + assembly { + structsStart := mload(0x40) + } + // Write the packed `_data` of each child `Item` into the unallocated space, + // and count the number of children. The children tile the list payload exactly. + uint256 n; + for (uint256 q = payloadPtr; q < listEnd;) { + (uint256 pp, uint256 pl,) = _decodeLength(q, listEnd); + /// @solidity memory-safe-assembly + assembly { + mstore(add(structsStart, shl(5, n)), or(q, shl(64, listEnd))) + } + unchecked { + q = pp + pl; + ++n; + } + } + // Build the array header, followed by pointers to each `Item` struct. + /// @solidity memory-safe-assembly + assembly { + let header := add(structsStart, shl(5, n)) + result := header + mstore(header, n) // Store the length of the array. + let ptrs := add(header, 0x20) + for { let i := 0 } lt(i, n) { i := add(i, 1) } { + mstore(add(ptrs, shl(5, i)), add(structsStart, shl(5, i))) + } + mstore(0x40, add(ptrs, shl(5, n))) // Allocate memory. + } + } + + /// @dev Returns the next sibling of `item` within its enclosing list. + /// Returns an empty item if `item` is empty or is the last item in its list. + /// Useful for iterating without allocating an array. See: {decodeList}, {isEmpty}. + function next(Item memory item) internal pure returns (Item memory result) { + (uint256 ptr, uint256 end) = _unpack(item); + if (ptr == 0) return result; + (uint256 payloadPtr, uint256 payloadLen,) = _decodeLength(ptr, end); + unchecked { + uint256 np = payloadPtr + payloadLen; + if (np < end) result._data = np | (end << 64); + } + } + + /// @dev Returns whether `item` is a list. + /// Returns false if `item` is empty. + function isList(Item memory item) internal pure returns (bool result) { + (uint256 ptr, uint256 end) = _unpack(item); + if (ptr == 0) return false; + (,, uint256 itemIsList) = _decodeLength(ptr, end); + result = itemIsList != 0; + } + + /// @dev Returns whether `item` is empty (i.e. a null item). + /// This is the case for items returned by `next` after the last item in a list. + function isEmpty(Item memory item) internal pure returns (bool result) { + result = item._data == 0; + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PRIVATE HELPERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + /// @dev Returns an `Item` viewing the entirety of the RLP encoded `encoded`. + /// The item packs the start pointer of the encoded data in the low 64 bits, + /// and the end pointer (exclusive) in the next 64 bits. + function _toItem(bytes memory encoded) private pure returns (Item memory item) { + /// @solidity memory-safe-assembly + assembly { + let s := add(encoded, 0x20) + item := mload(0x40) + mstore(item, or(s, shl(64, add(s, mload(encoded))))) + mstore(0x40, add(item, 0x20)) + } + } + + /// @dev Unpacks the start pointer `p` and end pointer `e` from `item`. + function _unpack(Item memory item) private pure returns (uint256 ptr, uint256 end) { + uint256 data = item._data; + ptr = uint64(data); + end = uint64(data >> 64); + } + + /// @dev Returns the big-endian integer of the `payloadLen` bytes at `payloadPtr`. + /// Assumes `payloadLen <= 32`. + function _toUint(uint256 payloadPtr, uint256 payloadLen) private pure returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + if payloadLen { result := shr(shl(3, sub(32, payloadLen)), mload(payloadPtr)) } + } + } + + /// @dev Decodes the length and type of the RLP item starting at pointer `p`, + /// bounded by the end pointer `e` (exclusive). + /// Returns the pointer to the payload, the length of the payload, + /// and whether the item is a list (`itemIsList` is 1 for a list, 0 otherwise). + /// Reverts if the item is malformed or extends beyond `e`. + function _decodeLength(uint256 ptr, uint256 end) + private + pure + returns (uint256 payloadPtr, uint256 payloadLen, uint256 itemIsList) + { + uint256 failed; + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + if iszero(lt(ptr, end)) { + failed := 1 + break + } + let b := byte(0, mload(ptr)) + payloadPtr := add(ptr, 1) + // Case: single byte in `[0x00, 0x7f]`. + if lt(b, 0x80) { + payloadPtr := ptr + payloadLen := 1 + break + } + // Case: short string (0-55 bytes). + if lt(b, 0xb8) { + payloadLen := sub(b, 0x80) + // A single byte below `0x80` must not be wrapped with a prefix. + if eq(payloadLen, 1) { failed := lt(byte(0, mload(payloadPtr)), 0x80) } + break + } + // Case: long string (>55 bytes). + if lt(b, 0xc0) { + let l := sub(b, 0xb7) // Length of the length, in `[1, 8]`. + let chunk := mload(payloadPtr) + payloadLen := shr(sub(256, shl(3, l)), chunk) + payloadPtr := add(payloadPtr, l) + // The length must not have a leading zero byte, and must be > 55. + failed := or(iszero(byte(0, chunk)), iszero(gt(payloadLen, 55))) + break + } + itemIsList := 1 + // Case: short list. + if lt(b, 0xf8) { + payloadLen := sub(b, 0xc0) + break + } + // Case: long list. + let ll := sub(b, 0xf7) // Length of the length, in `[1, 8]`. + let c := mload(payloadPtr) + payloadLen := shr(sub(256, shl(3, ll)), c) + payloadPtr := add(payloadPtr, ll) + // The length must not have a leading zero byte, and must be > 55. + failed := or(iszero(byte(0, c)), iszero(gt(payloadLen, 55))) + break + } + // The item must not extend beyond `e`. + if gt(add(payloadPtr, payloadLen), end) { failed := 1 } + } + if (failed != 0) revert RLPDecodingFailed(); + } + /// @dev Updates the tail in `list`. function _updateTail(List memory list, List memory result) private pure { /// @solidity memory-safe-assembly diff --git a/test/LibRLP.t.sol b/test/LibRLP.t.sol index 0b7787794..c3be3747e 100644 --- a/test/LibRLP.t.sol +++ b/test/LibRLP.t.sol @@ -7,6 +7,7 @@ import {FixedPointMathLib} from "../src/utils/FixedPointMathLib.sol"; contract LibRLPTest is SoladyTest { using LibRLP for LibRLP.List; + using LibRLP for LibRLP.Item; function testComputeAddressDifferential(address deployer, uint256 nonce) public { address computed = LibRLP.computeAddress(_brutalized(deployer), nonce); @@ -654,6 +655,278 @@ contract LibRLPTest is SoladyTest { revert(); } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RLP DECODING TESTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + function testRLPDecodeKnownVectors() public { + // Empty string. + assertEq(LibRLP.decodeBytes(hex"80"), hex""); + // Single byte below 0x80. + assertEq(LibRLP.decodeBytes(hex"00"), hex"00"); + assertEq(LibRLP.decodeBytes(hex"0f"), hex"0f"); + assertEq(LibRLP.decodeBytes(hex"7f"), hex"7f"); + // Short string. + assertEq(LibRLP.decodeBytes(hex"83646f67"), "dog"); + // Scalars. + assertEq(LibRLP.decodeUint256(hex"80"), 0); + assertEq(LibRLP.decodeUint256(hex"00"), 0); + assertEq(LibRLP.decodeUint256(hex"0f"), 15); + assertEq(LibRLP.decodeUint256(hex"820400"), 1024); + assertEq(LibRLP.decodeUint256(hex"8180"), 0x80); + assertEq(LibRLP.decodeUint256(hex"88ab54a98ceb1f0ad2"), 12345678901234567890); + } + + function testRLPDecodeBytesRoundTrip(bytes32) public { + bytes memory x = _randomBytesZeroRightPadded(); + _maybeBzztMemory(); + bytes memory encoded = LibRLP.encode(x); + _checkAndMaybeBzztMemory(encoded); + bytes memory decoded = LibRLP.decodeBytes(encoded); + _checkMemory(decoded); + assertEq(decoded, x); + } + + function testRLPDecodeBytesEdgeCases() public { + assertEq(LibRLP.decodeBytes(LibRLP.encode(_repeatFF(0))), _repeatFF(0)); + assertEq(LibRLP.decodeBytes(LibRLP.encode(_repeatFF(1))), _repeatFF(1)); + assertEq(LibRLP.decodeBytes(LibRLP.encode(_repeatFF(55))), _repeatFF(55)); + assertEq(LibRLP.decodeBytes(LibRLP.encode(_repeatFF(56))), _repeatFF(56)); + assertEq(LibRLP.decodeBytes(LibRLP.encode(_repeatFF(255))), _repeatFF(255)); + assertEq(LibRLP.decodeBytes(LibRLP.encode(_repeatFF(256))), _repeatFF(256)); + assertEq(LibRLP.decodeBytes(LibRLP.encode(_repeatFF(1000))), _repeatFF(1000)); + } + + function testRLPDecodeUintRoundTrip(uint256 x) public { + _maybeBzztMemory(); + bytes memory encoded = LibRLP.encode(x); + _checkAndMaybeBzztMemory(encoded); + assertEq(LibRLP.decodeUint256(encoded), x); + } + + function testRLPDecodeUint() public { + unchecked { + uint256 x = type(uint256).max; + while (x != 0) { + assertEq(LibRLP.decodeUint256(LibRLP.encode(x)), x); + assertEq(LibRLP.decodeUint256(LibRLP.encode(x - 1)), x - 1); + x >>= 8; + } + } + } + + function testRLPDecodeAddressRoundTrip(address x) public { + _maybeBzztMemory(); + bytes memory encoded = LibRLP.encode(_brutalized(x)); + _checkAndMaybeBzztMemory(encoded); + assertEq(LibRLP.decodeAddress(encoded), x); + } + + function testRLPDecodeAddressSingleByte() public { + // External encoders may encode small addresses (e.g. precompiles) as a single byte. + assertEq(LibRLP.decodeAddress(hex"01"), address(uint160(1))); + assertEq(LibRLP.decodeAddress(hex"7f"), address(uint160(0x7f))); + // The standard 21-byte encoding. + assertEq( + LibRLP.decodeAddress(hex"941111111111111111111111111111111111111111"), + 0x1111111111111111111111111111111111111111 + ); + } + + function testRLPDecodeBytes32() public { + bytes32 x = keccak256("hello"); + bytes memory encoded = abi.encodePacked(hex"a0", x); + assertEq(LibRLP.decodeBytes32(encoded), x); + // Scalar encodings are right-aligned into the bytes32. + assertEq(LibRLP.decodeBytes32(hex"820400"), bytes32(uint256(1024))); + assertEq(LibRLP.decodeBytes32(hex"80"), bytes32(0)); + } + + function testRLPDecodeNonCanonical() public { + // Leading zero bytes in a scalar are accepted (permissive). + assertEq(LibRLP.decodeUint256(hex"820000"), 0); + assertEq( + LibRLP.decodeUint256(hex"8900ab54a98ceb1f0ad2"), + LibRLP.decodeUint256(hex"88ab54a98ceb1f0ad2") + ); + } + + function decodeBytesExt(bytes memory x) external pure returns (bytes memory) { + return LibRLP.decodeBytes(x); + } + + function decodeUint256Ext(bytes memory x) external pure returns (uint256) { + return LibRLP.decodeUint256(x); + } + + function decodeAddressExt(bytes memory x) external pure returns (address) { + return LibRLP.decodeAddress(x); + } + + function decodeListLengthExt(bytes memory x) external pure returns (uint256) { + return LibRLP.decodeList(x).length; + } + + function testRLPDecodeInvalidReverts() public { + // Empty input. + vm.expectRevert(LibRLP.RLPDecodingFailed.selector); + this.decodeBytesExt(hex""); + // A single byte below 0x80 wrapped with a prefix. + vm.expectRevert(LibRLP.RLPDecodingFailed.selector); + this.decodeBytesExt(hex"8100"); + // Short string claiming more bytes than available. + vm.expectRevert(LibRLP.RLPDecodingFailed.selector); + this.decodeBytesExt(hex"83646f"); + // Long string with a leading zero in the length. + vm.expectRevert(LibRLP.RLPDecodingFailed.selector); + this.decodeBytesExt(hex"b90001"); + // Long string whose decoded length is not greater than 55. + vm.expectRevert(LibRLP.RLPDecodingFailed.selector); + this.decodeBytesExt(hex"b837"); + // A list cannot be decoded as bytes. + vm.expectRevert(LibRLP.RLPDecodingFailed.selector); + this.decodeBytesExt(hex"c0"); + // A 33-byte string cannot be decoded as a uint256. + vm.expectRevert(LibRLP.RLPDecodingFailed.selector); + this.decodeUint256Ext(abi.encodePacked(hex"a1", uint256(0), uint8(0))); + // A string cannot be decoded as a list. + vm.expectRevert(LibRLP.RLPDecodingFailed.selector); + this.decodeListLengthExt(hex"83646f67"); + // An address must be 1 or 21 bytes. + vm.expectRevert(LibRLP.RLPDecodingFailed.selector); + this.decodeAddressExt(hex"820400"); + } + + function testRLPDecodeEmptyList() public { + LibRLP.Item[] memory items = LibRLP.decodeList(hex"c0"); + assertEq(items.length, 0); + } + + function testRLPDecodeListKnownVector() public { + // ["cat", "dog"] + LibRLP.Item[] memory items = LibRLP.decodeList(hex"c88363617483646f67"); + assertEq(items.length, 2); + assertEq(items[0].decodeBytes(), "cat"); + assertEq(items[1].decodeBytes(), "dog"); + assertFalse(items[0].isList()); + assertFalse(items[1].isList()); + } + + function testRLPDecodeNestedList() public { + // The "set of three": [ [], [[]], [ [], [[]] ] ] + LibRLP.Item[] memory items = LibRLP.decodeList(hex"c7c0c1c0c3c0c1c0"); + assertEq(items.length, 3); + assertTrue(items[0].isList()); + assertTrue(items[1].isList()); + assertTrue(items[2].isList()); + + assertEq(items[0].decodeList().length, 0); + + LibRLP.Item[] memory sub1 = items[1].decodeList(); + assertEq(sub1.length, 1); + assertEq(sub1[0].decodeList().length, 0); + + LibRLP.Item[] memory sub2 = items[2].decodeList(); + assertEq(sub2.length, 2); + assertEq(sub2[0].decodeList().length, 0); + assertEq(sub2[1].decodeList().length, 1); + } + + function testRLPDecodeListIteratorEquivalence() public { + bytes memory encoded = LibRLP.p(uint256(1)).p(uint256(2)).p(uint256(3)).encode(); + LibRLP.Item[] memory items = LibRLP.decodeList(encoded); + assertEq(items.length, 3); + + // Iterating with `next` must visit the same items as the array. + uint256 i; + for (LibRLP.Item memory it = items[0]; !it.isEmpty(); it = it.next()) { + assertEq(it._data, items[i]._data); + assertEq(it.decodeUint256(), i + 1); + unchecked { + ++i; + } + } + assertEq(i, 3); + // `next` past the last item yields an empty item. + assertTrue(items[2].next().isEmpty()); + } + + function testRLPDecodeListRoundTrip(uint256) public { + unchecked { + uint256 n = _bound(_random(), 0, 8); + LibRLP.List memory l; + uint256[] memory values = new uint256[](n); + for (uint256 i; i != n; ++i) { + uint256 v = _random(); + values[i] = v; + l.p(v); + } + _maybeBzztMemory(); + bytes memory encoded = LibRLP.encode(l); + _checkAndMaybeBzztMemory(encoded); + LibRLP.Item[] memory items = LibRLP.decodeList(encoded); + assertEq(items.length, n); + for (uint256 i; i != n; ++i) { + assertEq(items[i].decodeUint256(), values[i]); + assertFalse(items[i].isList()); + } + } + } + + function testRLPDecodeLongListRoundTrip() public { + unchecked { + // 20 elements of 32 bytes each forces a long-list encoding (>55 byte payload). + uint256 n = 20; + LibRLP.List memory l; + for (uint256 i; i != n; ++i) { + l.p((1 << 255) ^ i); + } + bytes memory encoded = LibRLP.encode(l); + LibRLP.Item[] memory items = LibRLP.decodeList(encoded); + assertEq(items.length, n); + for (uint256 i; i != n; ++i) { + assertEq(items[i].decodeUint256(), (1 << 255) ^ i); + } + } + } + + function testRLPDecodeMixedListRoundTrip(bytes32) public { + uint256 x0 = _random(); + bytes memory x1 = _randomBytesZeroRightPadded(); + address x2 = _randomNonZeroAddress(); + _maybeBzztMemory(); + bytes memory encoded = LibRLP.p(x0).p(x1).p(x2).encode(); + _checkAndMaybeBzztMemory(encoded); + LibRLP.Item[] memory items = LibRLP.decodeList(encoded); + assertEq(items.length, 3); + assertEq(items[0].decodeUint256(), x0); + assertEq(items[1].decodeBytes(), x1); + assertEq(items[2].decodeAddress(), x2); + } + + function testRLPDecodeListDifferential(bytes32) public { + // Build a possibly-nested list, encode it, decode it, and verify structurally. + LibRLP.List memory l = _testRLPP(0); + bytes memory encoded = LibRLP.encode(l); + _checkAndMaybeBzztMemory(encoded); + LibRLP.Item[] memory items = LibRLP.decodeList(encoded); + // Every decoded item must re-encode to a slice of the original payload. + for (uint256 i; i < items.length; ++i) { + if (items[i].isList()) { + items[i].decodeList(); // Must not revert. + } else { + items[i].decodeBytes(); // Must not revert. + } + } + } + + function testRLPDecodeEmptyItemQueries() public { + LibRLP.Item memory empty; + assertTrue(empty.isEmpty()); + assertFalse(empty.isList()); + assertTrue(empty.next().isEmpty()); + } + function _checkMemory(LibRLP.List memory l) internal pure { /// @solidity memory-safe-assembly assembly {