diff --git a/src/libraries/PositionConfig.sol b/src/libraries/PositionConfig.sol new file mode 100644 index 00000000..007e7bb9 --- /dev/null +++ b/src/libraries/PositionConfig.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; + +// A PositionConfig is the input for creating and modifying a Position in core, whose truncated hash is set per tokenId +struct PositionConfig { + PoolKey poolKey; + int24 tickLower; + int24 tickUpper; +} + +/// @notice Library to calculate the PositionConfigId from the PositionConfig struct +library PositionConfigLibrary { + function toId(PositionConfig calldata config) internal pure returns (bytes32 id) { + // id = keccak256(abi.encodePacked(currency0, currency1, fee, tickSpacing, hooks, tickLower, tickUpper))) >> 1 + assembly ("memory-safe") { + let fmp := mload(0x40) + mstore(add(fmp, 0x34), calldataload(add(config, 0xc0))) // tickUpper: [0x51, 0x54) + mstore(add(fmp, 0x31), calldataload(add(config, 0xa0))) // tickLower: [0x4E, 0x51) + mstore(add(fmp, 0x2E), calldataload(add(config, 0x80))) // hooks: [0x3A, 0x4E) + mstore(add(fmp, 0x1A), calldataload(add(config, 0x60))) // tickSpacing: [0x37, 0x3A) + mstore(add(fmp, 0x17), calldataload(add(config, 0x40))) // fee: [0x34, 0x37) + mstore(add(fmp, 0x14), calldataload(add(config, 0x20))) // currency1: [0x20, 0x34) + mstore(fmp, calldataload(config)) // currency0: [0x0c, 0x20) + + id := shr(1, keccak256(add(fmp, 0x0c), 0x48)) // len is 72 bytes, truncate lower bit of the hash + + // now clean the memory we used + mstore(add(fmp, 0x40), 0) // fmp+0x40 held hooks (14 bytes), tickLower, tickUpper + mstore(add(fmp, 0x20), 0) // fmp+0x20 held currency1, fee, tickSpacing, hooks (6 bytes) + mstore(fmp, 0) // fmp held currency0 + } + } +} diff --git a/src/libraries/PositionConfigId.sol b/src/libraries/PositionConfigId.sol new file mode 100644 index 00000000..4e31c760 --- /dev/null +++ b/src/libraries/PositionConfigId.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +/// @notice A configId is set per tokenId +/// The lower 255 bits are used to store the truncated hash of the corresponding PositionConfig +/// The upper bit is used to signal if the tokenId has a subscriber +struct PositionConfigId { + bytes32 id; +} + +library PositionConfigIdLibrary { + bytes32 constant MASK_UPPER_BIT = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + bytes32 constant DIRTY_UPPER_BIT = 0x8000000000000000000000000000000000000000000000000000000000000000; + + /// @notice returns the truncated hash of the PositionConfig for a given tokenId + function getConfigId(PositionConfigId storage _configId) internal view returns (bytes32 configId) { + configId = _configId.id & MASK_UPPER_BIT; + } + + /// @dev We only set the config on mint, guaranteeing that the most significant bit is unset, so we can just assign the entire 32 bytes to the id. + function setConfigId(PositionConfigId storage _configId, bytes32 configId) internal { + _configId.id = configId; + } + + function setSubscribe(PositionConfigId storage configId) internal { + configId.id |= DIRTY_UPPER_BIT; + } + + function setUnsubscribe(PositionConfigId storage configId) internal { + configId.id &= MASK_UPPER_BIT; + } + + function hasSubscriber(PositionConfigId storage configId) internal view returns (bool subscribed) { + bytes32 _id = configId.id; + assembly ("memory-safe") { + subscribed := shr(255, _id) + } + } +}