diff --git a/README.md b/README.md index 60e81e762..dbe3ddfee 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ utils ├─ UpgradeableBeacon — "Upgradeable beacon for ERC1967 beacon proxies" ├─ WebAuthn — "WebAuthn helper" ├─ legacy — "Legacy support" +├─ clz - "Libraries with clz opcode" └─ ext — "Utilities for external protocols" ``` diff --git a/src/utils/clz/LibRLP.sol b/src/utils/clz/LibRLP.sol new file mode 100644 index 000000000..5ef033ac9 --- /dev/null +++ b/src/utils/clz/LibRLP.sol @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Library for RLP encoding and CREATE address computation. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibRLP.sol) +/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibRLP.sol) +library LibRLP { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STRUCTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev A pointer to a RLP item list in memory. + struct List { + // Do NOT modify the `_data` directly. + uint256 _data; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CREATE ADDRESS PREDICTION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the address where a contract will be stored if deployed via + /// `deployer` with `nonce` using the `CREATE` opcode. + /// For the specification of the Recursive Length Prefix (RLP) + /// encoding scheme, please refer to p. 19 of the Ethereum Yellow Paper + /// (https://ethereum.github.io/yellowpaper/paper.pdf) + /// and the Ethereum Wiki (https://eth.wiki/fundamentals/rlp). + /// + /// Based on the EIP-161 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md) + /// specification, all contract accounts on the Ethereum mainnet are initiated with + /// `nonce = 1`. Thus, the first contract address created by another contract + /// is calculated with a non-zero nonce. + /// + /// The theoretical allowed limit, based on EIP-2681 + /// (https://eips.ethereum.org/EIPS/eip-2681), for an account nonce is 2**64-2. + /// + /// Caution! This function will NOT check that the nonce is within the theoretical range. + /// This is for performance, as exceeding the range is extremely impractical. + /// It is the user's responsibility to ensure that the nonce is valid + /// (e.g. no dirty bits after packing / unpacking). + /// + /// This is equivalent to: + /// `address(uint160(uint256(keccak256(LibRLP.p(deployer).p(nonce).encode()))))`. + /// + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function computeAddress(address deployer, uint256 nonce) + internal + pure + returns (address deployed) + { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + // The integer zero is treated as an empty byte string, + // and as a result it only has a length prefix, 0x80, + // computed via `0x80 + 0`. + + // A one-byte integer in the [0x00, 0x7f] range uses its + // own value as a length prefix, + // there is no additional `0x80 + length` prefix that precedes it. + if iszero(gt(nonce, 0x7f)) { + mstore(0x00, deployer) + // Using `mstore8` instead of `or` naturally cleans + // any dirty upper bits of `deployer`. + mstore8(0x0b, 0x94) + mstore8(0x0a, 0xd6) + // `shl` 7 is equivalent to multiplying by 0x80. + mstore8(0x20, or(shl(7, iszero(nonce)), nonce)) + deployed := keccak256(0x0a, 0x17) + break + } + let i := 8 + // Just use a loop to generalize all the way with minimal bytecode size. + for {} shr(i, nonce) { i := add(i, 8) } {} + // `shr` 3 is equivalent to dividing by 8. + i := shr(3, i) + // Store in descending slot sequence to overlap the values correctly. + mstore(i, nonce) + mstore(0x00, shl(8, deployer)) + mstore8(0x1f, add(0x80, i)) + mstore8(0x0a, 0x94) + mstore8(0x09, add(0xd6, i)) + deployed := keccak256(0x09, add(0x17, i)) + break + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RLP ENCODING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: + // - addresses are treated like byte strings of length 20, agnostic of leading zero bytes. + // - uint256s are converted to byte strings, stripped of leading zero bytes, and encoded. + // - bools are converted to uint256s (`b ? 1 : 0`), then encoded with the uint256. + // - For bytes1 to bytes32, you must manually convert them to bytes memory + // with `abi.encodePacked(x)` before encoding. + + /// @dev Returns a new empty list. + function p() internal pure returns (List memory result) {} + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(uint256 x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(address x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(bool x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(bytes memory x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(List memory x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, uint256 x) internal pure returns (List memory result) { + result._data = x << 48; + _updateTail(list, result); + /// @solidity memory-safe-assembly + assembly { + // If `x` is too big, we cannot pack it inline with the node. + // We'll have to allocate a new slot for `x` and store the pointer to it in the node. + if shr(208, x) { + let m := mload(0x40) + mstore(m, x) + mstore(0x40, add(m, 0x20)) + mstore(result, shl(40, or(1, shl(8, m)))) + } + } + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, address x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(4, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, bool x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(48, iszero(iszero(x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, bytes memory x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(2, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, List memory x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(3, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Returns the RLP encoding of `list`. + function encode(List memory list) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + function encodeUint(x_, o_) -> _o { + _o := add(o_, 1) + if iszero(gt(x_, 0x7f)) { + mstore8(o_, or(shl(7, iszero(x_)), x_)) // Copy `x_`. + leave + } + let s_ := shr(3, clz(x_)) // Number of leading zero bytes. + mstore8(o_, sub(0xa0, s_)) // Prefix = 0x80 + (32 - s_). + mstore(_o, shl(shl(3, s_), x_)) // Left-align the significant bytes. + _o := sub(add(_o, 0x20), s_) // Advance by r_ = 32 - s_. + } + function encodeAddress(x_, o_) -> _o { + _o := add(o_, 0x15) + mstore(o_, shl(88, x_)) + mstore8(o_, 0x94) + } + function encodeBytes(x_, o_, c_) -> _o { + _o := add(o_, 1) + let n_ := mload(x_) + if iszero(gt(n_, 55)) { + let f_ := mload(add(0x20, x_)) + if iszero(and(eq(1, n_), lt(byte(0, f_), 0x80))) { + mstore8(o_, add(n_, c_)) // Store the prefix. + mstore(add(0x21, o_), mload(add(0x40, x_))) + mstore(_o, f_) + _o := add(n_, _o) + leave + } + mstore(o_, f_) // Copy `x_`. + leave + } + returndatacopy(returndatasize(), returndatasize(), shr(32, n_)) + let r_ := sub(0x20, shr(3, clz(n_))) + mstore(o_, shl(248, add(r_, add(c_, 55)))) // Store the prefix. + // Copy `x`. + let i_ := add(r_, _o) + _o := add(i_, n_) + for { let d_ := sub(add(0x20, x_), i_) } 1 {} { + mstore(i_, mload(add(d_, i_))) + i_ := add(i_, 0x20) + if iszero(lt(i_, _o)) { break } + } + mstore(o_, or(mload(o_), shl(sub(248, shl(3, r_)), n_))) // Store the prefix. + } + function encodeList(l_, o_) -> _o { + if iszero(mload(l_)) { + mstore8(o_, 0xc0) + _o := add(o_, 1) + leave + } + let j_ := add(o_, 0x20) + for { let h_ := l_ } 1 {} { + h_ := and(mload(h_), 0xffffffffff) + if iszero(h_) { break } + let t_ := byte(26, mload(h_)) + if iszero(gt(t_, 1)) { + if iszero(t_) { + j_ := encodeUint(shr(48, mload(h_)), j_) + continue + } + j_ := encodeUint(mload(shr(48, mload(h_))), j_) + continue + } + if eq(t_, 2) { + j_ := encodeBytes(shr(48, mload(h_)), j_, 0x80) + continue + } + if eq(t_, 3) { + j_ := encodeList(shr(48, mload(h_)), j_) + continue + } + j_ := encodeAddress(shr(48, mload(h_)), j_) + } + let n_ := sub(j_, add(o_, 0x20)) + if iszero(gt(n_, 55)) { + mstore8(o_, add(n_, 0xc0)) // Store the prefix. + mstore(add(0x01, o_), mload(add(0x20, o_))) + mstore(add(0x21, o_), mload(add(0x40, o_))) + _o := add(n_, add(0x01, o_)) + leave + } + mstore(o_, n_) + _o := encodeBytes(o_, o_, 0xc0) + } + result := mload(0x40) + let begin := add(result, 0x20) + let end := encodeList(list, begin) + mstore(result, sub(end, begin)) // Store the length of `result`. + mstore(end, 0) // Zeroize the slot after `result`. + mstore(0x40, add(end, 0x20)) // Allocate memory for `result`. + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(uint256 x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + result := mload(0x40) + if iszero(gt(x, 0x7f)) { + mstore(result, 1) // Store the length of `result`. + mstore(add(result, 0x20), shl(248, or(shl(7, iszero(x)), x))) // Copy `x`. + mstore(0x40, add(result, 0x40)) // Allocate memory for `result`. + break + } + let r := sub(0x21, shr(3, clz(x))) // 1 + byte length of `x`. + mstore(add(r, result), x) // Copy `x`. + mstore(add(result, 1), add(r, 0x7f)) // Store the prefix. + mstore(result, r) // Store the length of `result`. + mstore(add(r, add(result, 0x20)), 0) // Zeroize the slot after `result`. + mstore(0x40, add(result, 0x60)) // Allocate memory for `result`. + break + } + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(address x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(result, 0x15) + let o := add(0x20, result) + mstore(o, shl(88, x)) + mstore8(o, 0x94) + mstore(0x40, add(0x20, o)) + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(bool x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(result, 1) + mstore(add(0x20, result), shl(add(0xf8, mul(7, iszero(x))), 0x01)) + mstore(0x40, add(0x40, result)) + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(bytes memory x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := x + + for {} iszero(and(eq(1, mload(x)), lt(byte(0, mload(add(x, 0x20))), 0x80))) {} { + result := mload(0x40) + let n := mload(x) // Length of `x`. + if iszero(gt(n, 55)) { + mstore(0x40, add(result, 0x60)) + mstore(add(0x41, result), mload(add(0x40, x))) + mstore(add(0x21, result), mload(add(0x20, x))) + mstore(add(1, result), add(n, 0x80)) // Store the prefix. + mstore(result, add(1, n)) // Store the length of `result`. + mstore(add(add(result, 0x21), n), 0) // Zeroize the slot after `result`. + break + } + returndatacopy(returndatasize(), returndatasize(), shr(32, n)) + let r := sub(0x21, shr(3, clz(n))) + // Copy `x`. + let i := add(r, add(0x20, result)) + let end := add(i, n) + for { let d := sub(add(0x20, x), i) } 1 {} { + mstore(i, mload(add(d, i))) + i := add(i, 0x20) + if iszero(lt(i, end)) { break } + } + mstore(add(r, result), n) // Store the prefix. + mstore(add(1, result), add(r, 0xb6)) // Store the prefix. + mstore(result, add(r, n)) // Store the length of `result`. + mstore(end, 0) // Zeroize the slot after `result`. + mstore(0x40, add(end, 0x20)) // Allocate memory. + break + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PRIVATE HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Updates the tail in `list`. + function _updateTail(List memory list, List memory result) private pure { + /// @solidity memory-safe-assembly + assembly { + let v := or(shr(mload(list), result), mload(list)) + let tail := shr(40, v) + mstore(list, xor(shl(40, xor(tail, result)), v)) // Update the tail. + mstore(tail, or(mload(tail), result)) // Make the previous tail point to `result`. + } + } +} diff --git a/src/utils/clz/g/LibRLP.sol b/src/utils/clz/g/LibRLP.sol new file mode 100644 index 000000000..b313397dc --- /dev/null +++ b/src/utils/clz/g/LibRLP.sol @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +// This file is auto-generated. + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* STRUCTS */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +/// @dev A pointer to a RLP item list in memory. +struct List { + // Do NOT modify the `_data` directly. + uint256 _data; +} + +using LibRLP for List 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 { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CREATE ADDRESS PREDICTION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the address where a contract will be stored if deployed via + /// `deployer` with `nonce` using the `CREATE` opcode. + /// For the specification of the Recursive Length Prefix (RLP) + /// encoding scheme, please refer to p. 19 of the Ethereum Yellow Paper + /// (https://ethereum.github.io/yellowpaper/paper.pdf) + /// and the Ethereum Wiki (https://eth.wiki/fundamentals/rlp). + /// + /// Based on the EIP-161 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md) + /// specification, all contract accounts on the Ethereum mainnet are initiated with + /// `nonce = 1`. Thus, the first contract address created by another contract + /// is calculated with a non-zero nonce. + /// + /// The theoretical allowed limit, based on EIP-2681 + /// (https://eips.ethereum.org/EIPS/eip-2681), for an account nonce is 2**64-2. + /// + /// Caution! This function will NOT check that the nonce is within the theoretical range. + /// This is for performance, as exceeding the range is extremely impractical. + /// It is the user's responsibility to ensure that the nonce is valid + /// (e.g. no dirty bits after packing / unpacking). + /// + /// This is equivalent to: + /// `address(uint160(uint256(keccak256(LibRLP.p(deployer).p(nonce).encode()))))`. + /// + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function computeAddress(address deployer, uint256 nonce) + internal + pure + returns (address deployed) + { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + // The integer zero is treated as an empty byte string, + // and as a result it only has a length prefix, 0x80, + // computed via `0x80 + 0`. + + // A one-byte integer in the [0x00, 0x7f] range uses its + // own value as a length prefix, + // there is no additional `0x80 + length` prefix that precedes it. + if iszero(gt(nonce, 0x7f)) { + mstore(0x00, deployer) + // Using `mstore8` instead of `or` naturally cleans + // any dirty upper bits of `deployer`. + mstore8(0x0b, 0x94) + mstore8(0x0a, 0xd6) + // `shl` 7 is equivalent to multiplying by 0x80. + mstore8(0x20, or(shl(7, iszero(nonce)), nonce)) + deployed := keccak256(0x0a, 0x17) + break + } + let i := 8 + // Just use a loop to generalize all the way with minimal bytecode size. + for {} shr(i, nonce) { i := add(i, 8) } {} + // `shr` 3 is equivalent to dividing by 8. + i := shr(3, i) + // Store in descending slot sequence to overlap the values correctly. + mstore(i, nonce) + mstore(0x00, shl(8, deployer)) + mstore8(0x1f, add(0x80, i)) + mstore8(0x0a, 0x94) + mstore8(0x09, add(0xd6, i)) + deployed := keccak256(0x09, add(0x17, i)) + break + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RLP ENCODING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: + // - addresses are treated like byte strings of length 20, agnostic of leading zero bytes. + // - uint256s are converted to byte strings, stripped of leading zero bytes, and encoded. + // - bools are converted to uint256s (`b ? 1 : 0`), then encoded with the uint256. + // - For bytes1 to bytes32, you must manually convert them to bytes memory + // with `abi.encodePacked(x)` before encoding. + + /// @dev Returns a new empty list. + function p() internal pure returns (List memory result) {} + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(uint256 x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(address x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(bool x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(bytes memory x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Returns a new list with `x` as the only element. Equivalent to `LibRLP.p().p(x)`. + function p(List memory x) internal pure returns (List memory result) { + p(result, x); + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, uint256 x) internal pure returns (List memory result) { + result._data = x << 48; + _updateTail(list, result); + /// @solidity memory-safe-assembly + assembly { + // If `x` is too big, we cannot pack it inline with the node. + // We'll have to allocate a new slot for `x` and store the pointer to it in the node. + if shr(208, x) { + let m := mload(0x40) + mstore(m, x) + mstore(0x40, add(m, 0x20)) + mstore(result, shl(40, or(1, shl(8, m)))) + } + } + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, address x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(4, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, bool x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(48, iszero(iszero(x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, bytes memory x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(2, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Appends `x` to `list`. Returns `list` for function chaining. + function p(List memory list, List memory x) internal pure returns (List memory result) { + /// @solidity memory-safe-assembly + assembly { + mstore(result, shl(40, or(3, shl(8, x)))) + } + _updateTail(list, result); + result = list; + } + + /// @dev Returns the RLP encoding of `list`. + function encode(List memory list) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + function encodeUint(x_, o_) -> _o { + _o := add(o_, 1) + if iszero(gt(x_, 0x7f)) { + mstore8(o_, or(shl(7, iszero(x_)), x_)) // Copy `x_`. + leave + } + let s_ := shr(3, clz(x_)) // Number of leading zero bytes. + mstore8(o_, sub(0xa0, s_)) // Prefix = 0x80 + (32 - s_). + mstore(_o, shl(shl(3, s_), x_)) // Left-align the significant bytes. + _o := sub(add(_o, 0x20), s_) // Advance by r_ = 32 - s_. + } + function encodeAddress(x_, o_) -> _o { + _o := add(o_, 0x15) + mstore(o_, shl(88, x_)) + mstore8(o_, 0x94) + } + function encodeBytes(x_, o_, c_) -> _o { + _o := add(o_, 1) + let n_ := mload(x_) + if iszero(gt(n_, 55)) { + let f_ := mload(add(0x20, x_)) + if iszero(and(eq(1, n_), lt(byte(0, f_), 0x80))) { + mstore8(o_, add(n_, c_)) // Store the prefix. + mstore(add(0x21, o_), mload(add(0x40, x_))) + mstore(_o, f_) + _o := add(n_, _o) + leave + } + mstore(o_, f_) // Copy `x_`. + leave + } + returndatacopy(returndatasize(), returndatasize(), shr(32, n_)) + let r_ := sub(0x20, shr(3, clz(n_))) + mstore(o_, shl(248, add(r_, add(c_, 55)))) // Store the prefix. + // Copy `x`. + let i_ := add(r_, _o) + _o := add(i_, n_) + for { let d_ := sub(add(0x20, x_), i_) } 1 {} { + mstore(i_, mload(add(d_, i_))) + i_ := add(i_, 0x20) + if iszero(lt(i_, _o)) { break } + } + mstore(o_, or(mload(o_), shl(sub(248, shl(3, r_)), n_))) // Store the prefix. + } + function encodeList(l_, o_) -> _o { + if iszero(mload(l_)) { + mstore8(o_, 0xc0) + _o := add(o_, 1) + leave + } + let j_ := add(o_, 0x20) + for { let h_ := l_ } 1 {} { + h_ := and(mload(h_), 0xffffffffff) + if iszero(h_) { break } + let t_ := byte(26, mload(h_)) + if iszero(gt(t_, 1)) { + if iszero(t_) { + j_ := encodeUint(shr(48, mload(h_)), j_) + continue + } + j_ := encodeUint(mload(shr(48, mload(h_))), j_) + continue + } + if eq(t_, 2) { + j_ := encodeBytes(shr(48, mload(h_)), j_, 0x80) + continue + } + if eq(t_, 3) { + j_ := encodeList(shr(48, mload(h_)), j_) + continue + } + j_ := encodeAddress(shr(48, mload(h_)), j_) + } + let n_ := sub(j_, add(o_, 0x20)) + if iszero(gt(n_, 55)) { + mstore8(o_, add(n_, 0xc0)) // Store the prefix. + mstore(add(0x01, o_), mload(add(0x20, o_))) + mstore(add(0x21, o_), mload(add(0x40, o_))) + _o := add(n_, add(0x01, o_)) + leave + } + mstore(o_, n_) + _o := encodeBytes(o_, o_, 0xc0) + } + result := mload(0x40) + let begin := add(result, 0x20) + let end := encodeList(list, begin) + mstore(result, sub(end, begin)) // Store the length of `result`. + mstore(end, 0) // Zeroize the slot after `result`. + mstore(0x40, add(end, 0x20)) // Allocate memory for `result`. + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(uint256 x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + result := mload(0x40) + if iszero(gt(x, 0x7f)) { + mstore(result, 1) // Store the length of `result`. + mstore(add(result, 0x20), shl(248, or(shl(7, iszero(x)), x))) // Copy `x`. + mstore(0x40, add(result, 0x40)) // Allocate memory for `result`. + break + } + let r := sub(0x21, shr(3, clz(x))) // 1 + byte length of `x`. + mstore(add(r, result), x) // Copy `x`. + mstore(add(result, 1), add(r, 0x7f)) // Store the prefix. + mstore(result, r) // Store the length of `result`. + mstore(add(r, add(result, 0x20)), 0) // Zeroize the slot after `result`. + mstore(0x40, add(result, 0x60)) // Allocate memory for `result`. + break + } + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(address x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(result, 0x15) + let o := add(0x20, result) + mstore(o, shl(88, x)) + mstore8(o, 0x94) + mstore(0x40, add(0x20, o)) + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(bool x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(result, 1) + mstore(add(0x20, result), shl(add(0xf8, mul(7, iszero(x))), 0x01)) + mstore(0x40, add(0x40, result)) + } + } + + /// @dev Returns the RLP encoding of `x`. + function encode(bytes memory x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := x + + for {} iszero(and(eq(1, mload(x)), lt(byte(0, mload(add(x, 0x20))), 0x80))) {} { + result := mload(0x40) + let n := mload(x) // Length of `x`. + if iszero(gt(n, 55)) { + mstore(0x40, add(result, 0x60)) + mstore(add(0x41, result), mload(add(0x40, x))) + mstore(add(0x21, result), mload(add(0x20, x))) + mstore(add(1, result), add(n, 0x80)) // Store the prefix. + mstore(result, add(1, n)) // Store the length of `result`. + mstore(add(add(result, 0x21), n), 0) // Zeroize the slot after `result`. + break + } + returndatacopy(returndatasize(), returndatasize(), shr(32, n)) + let r := sub(0x21, shr(3, clz(n))) + // Copy `x`. + let i := add(r, add(0x20, result)) + let end := add(i, n) + for { let d := sub(add(0x20, x), i) } 1 {} { + mstore(i, mload(add(d, i))) + i := add(i, 0x20) + if iszero(lt(i, end)) { break } + } + mstore(add(r, result), n) // Store the prefix. + mstore(add(1, result), add(r, 0xb6)) // Store the prefix. + mstore(result, add(r, n)) // Store the length of `result`. + mstore(end, 0) // Zeroize the slot after `result`. + mstore(0x40, add(end, 0x20)) // Allocate memory. + break + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PRIVATE HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Updates the tail in `list`. + function _updateTail(List memory list, List memory result) private pure { + /// @solidity memory-safe-assembly + assembly { + let v := or(shr(mload(list), result), mload(list)) + let tail := shr(40, v) + mstore(list, xor(shl(40, xor(tail, result)), v)) // Update the tail. + mstore(tail, or(mload(tail), result)) // Make the previous tail point to `result`. + } + } +} diff --git a/test/clz/LibRLP.t.sol b/test/clz/LibRLP.t.sol new file mode 100644 index 000000000..fbd1eaef7 --- /dev/null +++ b/test/clz/LibRLP.t.sol @@ -0,0 +1,670 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "../utils/SoladyTest.sol"; +import {LibRLP} from "../../src/utils/clz/LibRLP.sol"; +import {FixedPointMathLib} from "../../src/utils/clz/FixedPointMathLib.sol"; + +contract LibRLPWithCLZTest is SoladyTest { + using LibRLP for LibRLP.List; + + function testComputeAddressDifferential(address deployer, uint256 nonce) public { + address computed = LibRLP.computeAddress(_brutalized(deployer), nonce); + assertEq(computed, computeAddressOriginal(deployer, nonce)); + assertEq(computed, computeAddressWithRLPList(deployer, nonce)); + } + + function testComputeAddressForSmallNonces() public { + address deployer = address(1); + assertTrue(LibRLP.computeAddress(deployer, 1) != address(0)); + assertTrue(LibRLP.computeAddress(deployer, 0x7f) != address(0)); + assertTrue(LibRLP.computeAddress(deployer, 0xff) != address(0)); + } + + function testComputeAddressOriginalForSmallNonces() public { + address deployer = address(1); + assertTrue(computeAddressOriginal(deployer, 1) != address(0)); + assertTrue(computeAddressOriginal(deployer, 0x7f) != address(0)); + assertTrue(computeAddressOriginal(deployer, 0xff) != address(0)); + } + + function testComputeAddressForLargeNonces() public { + address deployer = address(1); + assertTrue(LibRLP.computeAddress(deployer, 0xffffffff) != address(0)); + assertTrue(LibRLP.computeAddress(deployer, 0xffffffffffffff) != address(0)); + assertTrue(LibRLP.computeAddress(deployer, 0xffffffffffffffff) != address(0)); + } + + function testComputeAddressOriginalForLargeNonces() public { + address deployer = address(1); + assertTrue(computeAddressOriginal(deployer, 0xffffffff) != address(0)); + assertTrue(computeAddressOriginal(deployer, 0xffffffffffffff) != address(0)); + assertTrue(computeAddressOriginal(deployer, 0xffffffffffffffff) != address(0)); + } + + function computeAddressWithRLPList(address deployer, uint256 nonce) + internal + pure + returns (address) + { + return address(uint160(uint256(keccak256(LibRLP.p(deployer).p(nonce).encode())))); + } + + function computeAddressOriginal(address deployer, uint256 nonce) + internal + pure + returns (address) + { + return address(uint160(uint256(keccak256(_computeAddressOriginal(deployer, nonce))))); + } + + function _computeAddressOriginal(address deployer, uint256 nonce) + internal + pure + returns (bytes memory) + { + // Although the theoretical allowed limit, based on EIP-2681, + // for an account nonce is 2**64-2: https://eips.ethereum.org/EIPS/eip-2681, + // we just test all the way to 2**256-1 to ensure that the computeAddress function does not revert + // for whatever nonce we provide. + + if (nonce == 0x00) { + return abi.encodePacked(uint8(0xd6), uint8(0x94), deployer, uint8(0x80)); + } + if (nonce <= 0x7f) { + return abi.encodePacked(uint8(0xd6), uint8(0x94), deployer, uint8(nonce)); + } + bytes memory ep = _ep(nonce); + uint256 n = ep.length; + return abi.encodePacked(uint8(0xd6 + n), uint8(0x94), deployer, uint8(0x80 + n), ep); + } + + function _ep(uint256 x) internal pure returns (bytes memory) { + if (x <= type(uint8).max) return abi.encodePacked(uint8(x)); + if (x <= type(uint16).max) return abi.encodePacked(uint16(x)); + if (x <= type(uint24).max) return abi.encodePacked(uint24(x)); + if (x <= type(uint32).max) return abi.encodePacked(uint32(x)); + if (x <= type(uint40).max) return abi.encodePacked(uint40(x)); + if (x <= type(uint48).max) return abi.encodePacked(uint48(x)); + if (x <= type(uint56).max) return abi.encodePacked(uint56(x)); + if (x <= type(uint64).max) return abi.encodePacked(uint64(x)); + if (x <= type(uint72).max) return abi.encodePacked(uint72(x)); + if (x <= type(uint80).max) return abi.encodePacked(uint80(x)); + if (x <= type(uint88).max) return abi.encodePacked(uint88(x)); + if (x <= type(uint96).max) return abi.encodePacked(uint96(x)); + if (x <= type(uint104).max) return abi.encodePacked(uint104(x)); + if (x <= type(uint112).max) return abi.encodePacked(uint112(x)); + if (x <= type(uint120).max) return abi.encodePacked(uint120(x)); + if (x <= type(uint128).max) return abi.encodePacked(uint128(x)); + if (x <= type(uint136).max) return abi.encodePacked(uint136(x)); + if (x <= type(uint144).max) return abi.encodePacked(uint144(x)); + if (x <= type(uint152).max) return abi.encodePacked(uint152(x)); + if (x <= type(uint160).max) return abi.encodePacked(uint160(x)); + if (x <= type(uint168).max) return abi.encodePacked(uint168(x)); + if (x <= type(uint176).max) return abi.encodePacked(uint176(x)); + if (x <= type(uint184).max) return abi.encodePacked(uint184(x)); + if (x <= type(uint192).max) return abi.encodePacked(uint192(x)); + if (x <= type(uint200).max) return abi.encodePacked(uint200(x)); + if (x <= type(uint208).max) return abi.encodePacked(uint208(x)); + if (x <= type(uint216).max) return abi.encodePacked(uint216(x)); + if (x <= type(uint224).max) return abi.encodePacked(uint224(x)); + if (x <= type(uint232).max) return abi.encodePacked(uint232(x)); + if (x <= type(uint240).max) return abi.encodePacked(uint240(x)); + if (x <= type(uint248).max) return abi.encodePacked(uint248(x)); + return abi.encodePacked(uint256(x)); + } + + function testRLPPUint256() public { + _testRLPPUint256(0); + _testRLPPUint256(1); + _testRLPPUint256(1 << 255); + } + + function _testRLPPUint256(uint256 x) internal { + LibRLP.List memory l; + unchecked { + for (uint256 i; i != 32; ++i) { + uint256 y = x ^ i; + l.p(y); + _checkMemory(l); + assertEq(_getUint256(l, i), y); + } + for (uint256 i; i != 32; ++i) { + uint256 y = x ^ i; + assertEq(_getUint256(l, i), y); + } + } + } + + function testRLPMemory(bytes32) public returns (LibRLP.List memory l) { + while (true) { + uint256 r = _random(); + if (r & 0x0003 == 0) { + _maybeBzztMemory(); + l.p(_randomBytes()); + _checkMemory(l); + } + if (r & 0x0030 == 0) { + if (_random() & 1 == 0) { + l.p(_randomNonZeroAddress()); + } else { + l.p(_random()); + } + _checkMemory(l); + _maybeBzztMemory(); + } + if (r & 0x0100 == 0) { + l.p(_testRLPP(0)); + _checkMemory(l); + } + if (r & 0x1000 == 0) break; + } + _checkMemory(l.encode()); + } + + function _testRLPP(uint256 depth) internal returns (LibRLP.List memory l) { + if (depth <= 2) { + while (true) { + uint256 r = _random(); + if (r & 0x0007 == 0) { + _maybeBzztMemory(); + l.p(_randomBytes()); + _checkMemory(l); + } + if (r & 0x0030 == 0) { + if (_random() & 1 == 0) { + l.p(_randomNonZeroAddress()); + } else { + l.p(_random()); + } + _checkMemory(l); + _maybeBzztMemory(); + } + if (r & 0x0300 == 0) { + _maybeBzztMemory(); + unchecked { + l.p(_testRLPP(depth + 1)); + } + _checkMemory(l); + } + if (r & 0x1000 == 0) break; + } + } + } + + function _getUint256(LibRLP.List memory l, uint256 i) internal pure returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0) + let head := and(mload(l), 0xffffffffff) + if head { + for { let j := 0 } iszero(eq(j, i)) { j := add(j, 1) } { + head := and(mload(head), 0xffffffffff) + } + result := shr(48, mload(head)) + if eq(1, byte(26, mload(head))) { result := mload(result) } + } + } + } + + function testRLPEncodeBytes() public { + bytes memory s; + assertEq(LibRLP.encode(""), hex"80"); + s = "dog"; + assertEq(LibRLP.encode(s), abi.encodePacked(hex"83", s)); + assertEq(LibRLP.encode(hex"00"), hex"00"); + assertEq(LibRLP.encode(hex"0f"), hex"0f"); + assertEq(LibRLP.encode(hex"0400"), hex"820400"); + s = "Lorem ipsum dolor sit amet, consectetur adipisicing eli"; + assertEq(LibRLP.encode(s), abi.encodePacked(hex"b7", s)); + s = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"; + assertEq(LibRLP.encode(s), abi.encodePacked(hex"b838", s)); + s = new bytes(0x100); + assertEq(LibRLP.encode(s), abi.encodePacked(hex"b90100", s)); + s = new bytes(0xfffe); + assertEq(LibRLP.encode(s), abi.encodePacked(hex"b9fffe", s)); + } + + function testRLPEncodeBytes2() public { + assertEq(LibRLP.encode(""), hex"80"); + for (uint256 i = 0; i < 128; ++i) { + assertEq( + LibRLP.encode(bytes(abi.encodePacked(uint8(i)))), bytes(abi.encodePacked(uint8(i))) + ); + } + for (uint256 i = 128; i < 256; ++i) { + assertEq( + LibRLP.encode(bytes(abi.encodePacked(uint8(i)))), + bytes(abi.encodePacked(bytes1(0x81), uint8(i))) + ); + } + } + + function testRLPEncodeAddressViaList(address a0, address a1) public { + _maybeBzztMemory(); + bytes memory computed = LibRLP.p(_brutalized(a0)).p(_brutalized(a1)).encode(); + _checkMemory(computed); + _maybeBzztMemory(); + bytes memory expected = LibRLP.p(abi.encodePacked(a0)).p(abi.encodePacked(a1)).encode(); + assertEq(computed, expected); + } + + function testRLPEncodeListDifferential(bytes memory x0, uint256 x1) public { + _maybeBzztMemory(); + LibRLP.List memory list = LibRLP.p(x0).p(x1).p(x1).p(x0); + _checkMemory(list); + _maybeBzztMemory(); + bytes memory computed = LibRLP.encode(list); + _checkAndMaybeBzztMemory(computed); + bytes memory x0Encoded = LibRLP.encode(x0); + _checkAndMaybeBzztMemory(x0Encoded); + bytes memory x1Encoded = LibRLP.encode(x1); + _checkAndMaybeBzztMemory(x1Encoded); + bytes memory combined = abi.encodePacked(x0Encoded, x1Encoded, x1Encoded, x0Encoded); + assertEq(computed, _encodeSimple(combined, 0xc0)); + _checkAndMaybeBzztMemory(computed); + assertEq(computed, LibRLP.encode(list)); + } + + function testRLPEncodeBytesDifferential(bytes32) public { + bytes memory x = _randomBytesZeroRightPadded(); + _maybeBzztMemory(); + bytes memory computed = LibRLP.encode(x); + _checkAndMaybeBzztMemory(computed); + bytes memory computed2 = _encode(x); + _checkAndMaybeBzztMemory(computed2); + assertEq(computed, computed2); + assertEq(computed, _encodeSimple(x)); + } + + function testRLPEncodeUintDifferential(uint256 x) public { + _maybeBzztMemory(); + bytes memory computed = LibRLP.encode(x); + _checkAndMaybeBzztMemory(computed); + bytes memory computed2 = _encode(x); + _checkAndMaybeBzztMemory(computed2); + assertEq(computed, computed2); + assertEq(computed, _encodeSimple(x)); + } + + function testRLPEncodeAddressDifferential(address x) public { + _maybeBzztMemory(); + bytes memory computed = LibRLP.encode(_brutalized(x)); + _checkAndMaybeBzztMemory(computed); + bytes memory computed2 = _encode(x); + _checkAndMaybeBzztMemory(computed2); + assertEq(computed, computed2); + assertEq(computed, _encodeSimple(x)); + } + + function testRLPEncodeBool(bool x) public { + _maybeBzztMemory(); + bytes memory computed = LibRLP.encode(_brutalized(x)); + _checkMemory(computed); + bytes memory expected = bytes(x ? hex"01" : hex"80"); + assertEq(computed, expected); + uint256 y = x ? 1 : 0; + assertEq(LibRLP.p(y).p(y ^ 1).p(y).encode(), LibRLP.p(x).p(!x).p(x).encode()); + } + + function _maybeBzztMemory() internal { + uint256 r = _random(); + if (r & 0x000f == uint256(0)) _misalignFreeMemoryPointer(); + if (r & 0x0ff0 == uint256(0)) _brutalizeMemory(); + if (r & 0xf000 == uint256(0)) _misalignFreeMemoryPointer(); + } + + function _bzztMemory() internal view { + _misalignFreeMemoryPointer(); + _brutalizeMemory(); + } + + function _checkAndMaybeBzztMemory(bytes memory x) internal { + _checkMemory(x); + _maybeBzztMemory(); + } + + function _encode(uint256 x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + function encodeUint(x_, o_) -> _o { + _o := add(o_, 1) + if iszero(gt(x_, 0x7f)) { + mstore8(o_, or(shl(7, iszero(x_)), x_)) // Copy `x_`. + leave + } + let r_ := shl(7, lt(0xffffffffffffffffffffffffffffffff, x_)) + r_ := or(r_, shl(6, lt(0xffffffffffffffff, shr(r_, x_)))) + r_ := or(r_, shl(5, lt(0xffffffff, shr(r_, x_)))) + r_ := or(r_, shl(4, lt(0xffff, shr(r_, x_)))) + r_ := or(shr(3, r_), lt(0xff, shr(r_, x_))) + mstore8(o_, add(r_, 0x81)) // Store the prefix. + mstore(0x00, x_) + mstore(_o, mload(xor(31, r_))) // Copy `x_`. + _o := add(add(1, r_), _o) + } + result := mload(0x40) + let o := encodeUint(x, add(result, 0x20)) + mstore(result, sub(o, add(result, 0x20))) + mstore(o, 0) + mstore(0x40, add(o, 0x20)) + } + } + + function _encode(bytes memory x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + function encodeBytes(x_, o_, c_) -> _o { + _o := add(o_, 1) + let n_ := mload(x_) + if iszero(gt(n_, 55)) { + let f_ := mload(add(0x20, x_)) + if iszero(and(eq(1, n_), lt(byte(0, f_), 0x80))) { + mstore8(o_, add(n_, c_)) // Store the prefix. + mstore(add(0x21, o_), mload(add(0x40, x_))) + mstore(_o, f_) + _o := add(n_, _o) + leave + } + mstore(o_, f_) // Copy `x_`. + leave + } + returndatacopy(returndatasize(), returndatasize(), shr(32, n_)) + let r_ := add(1, add(lt(0xff, n_), add(lt(0xffff, n_), lt(0xffffff, n_)))) + mstore(o_, shl(248, add(r_, add(c_, 55)))) // Store the prefix. + // Copy `x`. + let i_ := add(r_, _o) + _o := add(i_, n_) + for { let d_ := sub(add(0x20, x_), i_) } 1 {} { + mstore(i_, mload(add(d_, i_))) + i_ := add(i_, 0x20) + if iszero(lt(i_, _o)) { break } + } + mstore(o_, or(mload(o_), shl(sub(248, shl(3, r_)), n_))) // Store the prefix. + } + result := mload(0x40) + let o := encodeBytes(x, add(result, 0x20), 0x80) + mstore(result, sub(o, add(result, 0x20))) + mstore(o, 0) + mstore(0x40, add(o, 0x20)) + } + } + + function _encode(address x) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + function encodeAddress(x_, o_) -> _o { + _o := add(o_, 0x15) + mstore(o_, shl(88, x_)) + mstore8(o_, 0x94) + } + result := mload(0x40) + let o := encodeAddress(x, add(result, 0x20)) + mstore(result, sub(o, add(result, 0x20))) + mstore(o, 0) + mstore(0x40, add(o, 0x20)) + } + } + + function _encodeSimple(uint256 x) internal pure returns (bytes memory) { + if (x == 0) return hex"80"; + if (x < 0x80) return abi.encodePacked(uint8(x)); + bytes memory ep = _ep(x); + return abi.encodePacked(uint8(0x80 + ep.length), ep); + } + + function _encodeSimple(address x) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(0x94), x); + } + + function _encodeSimple(bytes memory x, uint256 c) internal pure returns (bytes memory) { + uint256 n = x.length; + if (n == 0) return hex"80"; + if (n == 1 && uint8(bytes1(x[0])) < 0x80) return x; + if (n < 56) return abi.encodePacked(uint8(n + c), x); + bytes memory ep = _ep(n); + return abi.encodePacked(uint8(c + 55 + ep.length), ep, x); + } + + function _encodeSimple(bytes memory x) internal pure returns (bytes memory) { + return _encodeSimple(x, 0x80); + } + + function testRLPEncodeUint(uint256 x) public { + _maybeBzztMemory(); + if (x == 0) { + _testRLPEncodeUint(x, hex"80"); + return; + } + if (x < 0x80) { + _testRLPEncodeUint(x, abi.encodePacked(uint8(x))); + return; + } + bytes memory ep = _ep(x); + uint256 n = ep.length; + _testRLPEncodeUint(x, abi.encodePacked(uint8(0x80 + n), _ep(x))); + } + + function testRLPEncodeUint() public { + _testRLPEncodeUint(0, hex"80"); + _testRLPEncodeUint(0x1, hex"01"); + _testRLPEncodeUint(0x2, hex"02"); + _testRLPEncodeUint(0x7e, hex"7e"); + _testRLPEncodeUint(0x7f, hex"7f"); + _testRLPEncodeUint(0x80, hex"8180"); + _testRLPEncodeUint(0x81, hex"8181"); + _testRLPEncodeUint(0x82, hex"8182"); + _testRLPEncodeUint(0xfe, hex"81fe"); + _testRLPEncodeUint(0xff, hex"81ff"); + unchecked { + uint256 x = type(uint256).max; + while (x != 0) { + testRLPEncodeUint(x); + testRLPEncodeUint(x - 1); + x >>= 8; + } + } + } + + function testRLPEncodeListEdgeCases() public { + for (uint256 i; i < 0x80; ++i) { + bytes1 x = bytes1(uint8(i)); + assertEq(LibRLP.encode(LibRLP.p().p(abi.encodePacked(x))), abi.encodePacked(hex"c1", x)); + assertEq( + LibRLP.encode(LibRLP.p().p(LibRLP.p().p(abi.encodePacked(x)))), + abi.encodePacked(hex"c2c1", x) + ); + } + for (uint256 i = 0x80; i <= 0xff; ++i) { + bytes1 x = bytes1(uint8(i)); + assertEq( + LibRLP.encode(LibRLP.p().p(abi.encodePacked(x))), abi.encodePacked(hex"c281", x) + ); + assertEq( + LibRLP.encode(LibRLP.p().p(LibRLP.p().p(abi.encodePacked(x)))), + abi.encodePacked(hex"c3c281", x) + ); + } + for (uint256 i = 0x100; i <= 0x1ff; ++i) { + bytes2 x = bytes2(uint16(i)); + assertEq( + LibRLP.encode(LibRLP.p().p(abi.encodePacked(x))), abi.encodePacked(hex"c382", x) + ); + assertEq( + LibRLP.encode(LibRLP.p().p(LibRLP.p().p(abi.encodePacked(x)))), + abi.encodePacked(hex"c4c382", x) + ); + } + + assertEq( + LibRLP.encode(LibRLP.p().p(hex"112233445566778899aa")), hex"cb8a112233445566778899aa" + ); + assertEq( + LibRLP.encode(LibRLP.p().p(_repeatFF(3))), abi.encodePacked(hex"c483", _repeatFF(3)) + ); + assertEq( + LibRLP.encode(LibRLP.p().p(_repeatFF(7))), abi.encodePacked(hex"c887", _repeatFF(7)) + ); + assertEq( + LibRLP.encode(LibRLP.p().p(_repeatFF(52))), abi.encodePacked(hex"f5b4", _repeatFF(52)) + ); + assertEq( + LibRLP.encode(LibRLP.p().p(_repeatFF(53))), abi.encodePacked(hex"f6b5", _repeatFF(53)) + ); + assertEq( + LibRLP.encode(LibRLP.p().p(_repeatFF(54))), abi.encodePacked(hex"f7b6", _repeatFF(54)) + ); + assertEq( + LibRLP.encode(LibRLP.p().p(_repeatFF(55))), abi.encodePacked(hex"f838b7", _repeatFF(55)) + ); + assertEq( + LibRLP.encode(LibRLP.p().p(_repeatFF(56))), + abi.encodePacked(hex"f83ab838", _repeatFF(56)) + ); + assertEq( + LibRLP.encode(LibRLP.p().p(_repeatFF(57))), + abi.encodePacked(hex"f83bb839", _repeatFF(57)) + ); + assertEq( + LibRLP.encode(LibRLP.p().p(_repeatFF(254))), + abi.encodePacked(hex"f90100b8fe", _repeatFF(254)) + ); + assertEq( + LibRLP.encode(LibRLP.p().p(_repeatFF(255))), + abi.encodePacked(hex"f90101b8ff", _repeatFF(255)) + ); + assertEq( + LibRLP.encode(LibRLP.p().p(_repeatFF(256))), + abi.encodePacked(hex"f90103b90100", _repeatFF(256)) + ); + assertEq( + LibRLP.encode(LibRLP.p().p(_repeatFF(257))), + abi.encodePacked(hex"f90104b90101", _repeatFF(257)) + ); + } + + function testRLPEncodeStringEdgeCases() public { + assertEq(LibRLP.encode(_repeatFF(3)), abi.encodePacked(hex"83", _repeatFF(3))); + assertEq(LibRLP.encode(_repeatFF(7)), abi.encodePacked(hex"87", _repeatFF(7))); + assertEq(LibRLP.encode(_repeatFF(52)), abi.encodePacked(hex"b4", _repeatFF(52))); + assertEq(LibRLP.encode(_repeatFF(53)), abi.encodePacked(hex"b5", _repeatFF(53))); + assertEq(LibRLP.encode(_repeatFF(54)), abi.encodePacked(hex"b6", _repeatFF(54))); + assertEq(LibRLP.encode(_repeatFF(55)), abi.encodePacked(hex"b7", _repeatFF(55))); + assertEq(LibRLP.encode(_repeatFF(56)), abi.encodePacked(hex"b838", _repeatFF(56))); + assertEq(LibRLP.encode(_repeatFF(57)), abi.encodePacked(hex"b839", _repeatFF(57))); + assertEq(LibRLP.encode(_repeatFF(254)), abi.encodePacked(hex"b8fe", _repeatFF(254))); + assertEq(LibRLP.encode(_repeatFF(255)), abi.encodePacked(hex"b8ff", _repeatFF(255))); + assertEq(LibRLP.encode(_repeatFF(256)), abi.encodePacked(hex"b90100", _repeatFF(256))); + assertEq(LibRLP.encode(_repeatFF(257)), abi.encodePacked(hex"b90101", _repeatFF(257))); + } + + function _repeatFF(uint256 n) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(result, n) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(result, 0x20), i), not(0)) + } + mstore(0x40, add(add(result, 0x20), n)) + } + } + + function _testRLPEncodeUint(uint256 x, bytes memory expected) internal { + bytes memory computed = LibRLP.encode(x); + _checkMemory(computed); + assertEq(computed, expected); + } + + function testRLPEncodeList() public { + LibRLP.List memory l; + _bzztMemory(); + assertEq(LibRLP.encode(l), hex"c0"); + l.p(LibRLP.p()); + _checkMemory(l); + l.p(LibRLP.p(LibRLP.p())); + _checkMemory(l); + l.p(LibRLP.p(LibRLP.p()).p(LibRLP.p(LibRLP.p()))); + _checkMemory(l); + _bzztMemory(); + bytes memory computed = LibRLP.encode(l); + _checkMemory(computed); + assertEq(computed, hex"c7c0c1c0c3c0c1c0"); + _bzztMemory(); + bytes memory computed2 = LibRLP.encode(l); + assertEq(computed, computed2); + _checkMemory(computed); + _checkMemory(computed2); + } + + function testRLPEncodeList2() public { + LibRLP.List memory l; + _checkMemory(l); + _bzztMemory(); + l.p("The").p("quick").p("brown").p("fox"); + l.p("jumps").p("over").p("the").p("lazy").p("dog"); + _checkMemory(l); + { + LibRLP.List memory lSub; + lSub.p(0).p(1).p(0x7f).p(0x80).p(0x81); + lSub.p(2 ** 256 - 1); + _checkMemory(lSub); + lSub.p("Jackdaws").p("loves").p("my").p(""); + lSub.p("great").p("sphinx").p("of").p("quartz"); + _checkMemory(lSub); + l.p(lSub); + _checkMemory(l); + } + _bzztMemory(); + l.p("0123456789abcdefghijklmnopqrstuvwxyz"); + _checkMemory(l); + _bzztMemory(); + bytes memory computed = LibRLP.encode(l); + _checkMemory(computed); + bytes memory expected = + hex"f8a58354686585717569636b8562726f776e83666f78856a756d7073846f76657283746865846c617a7983646f67f85280017f81808181a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff884a61636b64617773856c6f766573826d798085677265617486737068696e78826f668671756172747aa4303132333435363738396162636465666768696a6b6c6d6e6f707172737475767778797a"; + assertEq(computed, expected); + _bzztMemory(); + bytes memory computed2 = LibRLP.encode(l); + assertEq(computed, computed2); + _checkMemory(computed); + _checkMemory(computed2); + } + + function testSmallLog256Equivalence(uint256 n) public { + n = _bound(n, 0, 0xffffffff); + assertEq(_smallLog256(n), FixedPointMathLib.log256(n)); + assertEq(_smallLog256(n), _smallLog256Simple(n)); + n = _random() & 0xffffffff; + assertEq(_smallLog256(n), _smallLog256Simple(n)); + } + + function _smallLog256(uint256 n) internal pure returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + result := add(lt(0xff, n), add(lt(0xffff, n), lt(0xffffff, n))) + } + } + + function _smallLog256Simple(uint256 n) internal pure returns (uint256 result) { + if (n <= 0x000000ff) return 0; + if (n <= 0x0000ffff) return 1; + if (n <= 0x00ffffff) return 2; + if (n <= 0xffffffff) return 3; + revert(); + } + + function _checkMemory(LibRLP.List memory l) internal pure { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let v := mload(l) + if gt(shr(40, v), m) { invalid() } + for { let head := and(v, 0xffffffffff) } head {} { + if gt(head, m) { invalid() } + head := and(mload(head), 0xffffffffff) + } + } + _checkMemory(); + } +}