-
Notifications
You must be signed in to change notification settings - Fork 508
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0b01a0a
commit 785960e
Showing
2 changed files
with
381 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.19; | ||
|
||
import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; | ||
import {IERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; | ||
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; | ||
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; | ||
import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; | ||
import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; | ||
import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; | ||
|
||
import {INonfungiblePositionManagerV4} from "./interfaces/INonfungiblePositionManagerV4.sol"; | ||
import {ERC721Permit} from "./base/ERC721Permit.sol"; | ||
import {PeripheryValidation} from "./base/PeripheryValidation.sol"; | ||
import {PeripheryPayments} from "./base/PeripheryPayments.sol"; | ||
import {SelfPermit} from "./base/SelfPermit.sol"; | ||
import {Multicall} from "./base/Multicall.sol"; | ||
|
||
contract NonfungiblePositionManagerV4 is | ||
INonfungiblePositionManagerV4, | ||
ERC721Permit, | ||
PeripheryValidation, | ||
PeripheryPayments, | ||
SelfPermit, | ||
Multicall | ||
{ | ||
IPoolManager public immutable override poolManager; | ||
|
||
// details about the Uniswap position | ||
struct Position { | ||
// the nonce for permits | ||
uint96 nonce; | ||
// the address that is approved for spending this token | ||
address operator; | ||
// the hashed poolKey of the pool with which this token is connected | ||
bytes32 poolId; | ||
// the tick range of the position | ||
int24 tickLower; | ||
int24 tickUpper; | ||
// the liquidity of the position | ||
uint128 liquidity; | ||
// the fee growth of the aggregate position as of the last action on the individual position | ||
uint256 feeGrowthInside0LastX128; | ||
uint256 feeGrowthInside1LastX128; | ||
// how many uncollected tokens are owed to the position, as of the last computation | ||
uint128 tokensOwed0; | ||
uint128 tokensOwed1; | ||
} | ||
|
||
/// @dev Pool keys by poolIds | ||
mapping(bytes32 => PoolKey) private _poolIdToPoolKey; | ||
|
||
/// @dev The token ID position data | ||
mapping(uint256 => Position) private _positions; | ||
|
||
/// @dev The ID of the next token that will be minted. Skips 0 | ||
uint176 private _nextId = 1; | ||
|
||
/// @dev The address of the token descriptor contract, which handles generating token URIs for position tokens | ||
address private immutable _tokenDescriptor; | ||
|
||
// TODO: does it still need WETH address in the constructor here? | ||
// TODO: use ERC721Permit2 here | ||
constructor(IPoolManager _poolManager, address _tokenDescriptor_) | ||
ERC721Permit("Uniswap V4 Positions NFT-V1", "UNI-V4-POS", "1") | ||
{ | ||
poolManager = _poolManager; | ||
_tokenDescriptor = _tokenDescriptor_; | ||
} | ||
|
||
/// @inheritdoc INonfungiblePositionManagerV4 | ||
function positions(uint256 tokenId) | ||
external | ||
view | ||
override | ||
returns ( | ||
uint96 nonce, | ||
address operator, | ||
Currency currency0, | ||
Currency currency1, | ||
uint24 fee, | ||
int24 tickLower, | ||
int24 tickUpper, | ||
uint128 liquidity, | ||
uint256 feeGrowthInside0LastX128, | ||
uint256 feeGrowthInside1LastX128, | ||
uint128 tokensOwed0, | ||
uint128 tokensOwed1 | ||
) | ||
{ | ||
Position memory position = _positions[tokenId]; | ||
require(position.poolId != 0, "Invalid token ID"); | ||
PoolKey memory poolKey = _poolIdToPoolKey[position.poolId]; | ||
return ( | ||
position.nonce, | ||
position.operator, | ||
poolKey.currency0, | ||
poolKey.currency1, | ||
poolKey.fee, | ||
position.tickLower, | ||
position.tickUpper, | ||
position.liquidity, | ||
position.feeGrowthInside0LastX128, | ||
position.feeGrowthInside1LastX128, | ||
position.tokensOwed0, | ||
position.tokensOwed1 | ||
); | ||
} | ||
|
||
/// @inheritdoc INonfungiblePositionManagerV4 | ||
function createAndInitializePoolIfNecessary(PoolKey memory poolkey, uint160 sqrtPriceX96, bytes memory initData) | ||
external | ||
payable | ||
{ | ||
// TODO: implement this | ||
} | ||
|
||
/// @inheritdoc INonfungiblePositionManagerV4 | ||
function mint(MintParams calldata params) | ||
external | ||
payable | ||
override | ||
checkDeadline(params.deadline) | ||
returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) | ||
{ | ||
// TODO: implement this | ||
} | ||
|
||
modifier isAuthorizedForToken(uint256 tokenId) { | ||
require(_isApprovedOrOwner(msg.sender, tokenId), "Not approved"); | ||
_; | ||
} | ||
|
||
function tokenURI(uint256 tokenId) public view override(ERC721, IERC721Metadata) returns (string memory) { | ||
// TODO: implement this | ||
} | ||
|
||
/// @inheritdoc INonfungiblePositionManagerV4 | ||
function increaseLiquidity(IncreaseLiquidityParams calldata params) | ||
external | ||
payable | ||
override | ||
checkDeadline(params.deadline) | ||
returns (uint128 liquidity, uint256 amount0, uint256 amount1) | ||
{ | ||
// TODO: implement this | ||
} | ||
|
||
/// @inheritdoc INonfungiblePositionManagerV4 | ||
function decreaseLiquidity(DecreaseLiquidityParams calldata params) | ||
external | ||
payable | ||
override | ||
isAuthorizedForToken(params.tokenId) | ||
checkDeadline(params.deadline) | ||
returns (uint256 amount0, uint256 amount1) | ||
{ | ||
// TODO: implement this | ||
} | ||
|
||
/// @inheritdoc INonfungiblePositionManagerV4 | ||
function collect(CollectParams calldata params) | ||
external | ||
payable | ||
override | ||
isAuthorizedForToken(params.tokenId) | ||
returns (uint256 amount0, uint256 amount1) | ||
{ | ||
// TODO: implement this | ||
} | ||
|
||
/// @inheritdoc INonfungiblePositionManagerV4 | ||
function burn(uint256 tokenId) external payable override isAuthorizedForToken(tokenId) { | ||
Position storage position = _positions[tokenId]; | ||
require(position.liquidity == 0 && position.tokensOwed0 == 0 && position.tokensOwed1 == 0, "Not cleared"); | ||
delete _positions[tokenId]; | ||
_burn(tokenId); | ||
} | ||
|
||
function _getAndIncrementNonce(uint256 tokenId) internal override returns (uint256) { | ||
return uint256(_positions[tokenId].nonce++); | ||
} | ||
|
||
/// @inheritdoc IERC721 | ||
function getApproved(uint256 tokenId) public view override(ERC721, IERC721) returns (address) { | ||
require(_exists(tokenId), "ERC721: approved query for nonexistent token"); | ||
|
||
return _positions[tokenId].operator; | ||
} | ||
|
||
/// @dev Overrides _approve to use the operator in the position, which is packed with the position permit nonce | ||
function _approve(address to, uint256 tokenId) internal override(ERC721) { | ||
_positions[tokenId].operator = to; | ||
emit Approval(ownerOf(tokenId), to, tokenId); | ||
} | ||
|
||
/// @inheritdoc IERC721Enumerable | ||
function tokenByIndex(uint256 index) external view returns (uint256) { | ||
// TODO: implement this | ||
} | ||
|
||
/// @inheritdoc IERC721Enumerable | ||
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256) { | ||
// TODO: implement this | ||
} | ||
|
||
/// @inheritdoc IERC721Enumerable | ||
function totalSupply() external view returns (uint256) { | ||
// TODO: implement this | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.19; | ||
|
||
import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; | ||
import {IERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; | ||
import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; | ||
import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; | ||
|
||
import {IPeripheryPayments} from "./IPeripheryPayments.sol"; | ||
import {IERC721Permit} from "./IERC721Permit.sol"; | ||
|
||
/// @title Non-fungible token for positions | ||
/// @notice Wraps Uniswap V4 positions in a non-fungible token interface which allows for them to be transferred | ||
/// and authorized. | ||
interface INonfungiblePositionManagerV4 is IPeripheryPayments, IERC721Metadata, IERC721Enumerable, IERC721Permit { | ||
/// @notice Emitted when liquidity is increased for a position NFT | ||
/// @dev Also emitted when a token is minted | ||
/// @param tokenId The ID of the token for which liquidity was increased | ||
/// @param liquidity The amount by which liquidity for the NFT position was increased | ||
/// @param amount0 The amount of token0 that was paid for the increase in liquidity | ||
/// @param amount1 The amount of token1 that was paid for the increase in liquidity | ||
event IncreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1); | ||
/// @notice Emitted when liquidity is decreased for a position NFT | ||
/// @param tokenId The ID of the token for which liquidity was decreased | ||
/// @param liquidity The amount by which liquidity for the NFT position was decreased | ||
/// @param amount0 The amount of token0 that was accounted for the decrease in liquidity | ||
/// @param amount1 The amount of token1 that was accounted for the decrease in liquidity | ||
event DecreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1); | ||
/// @notice Emitted when tokens are collected for a position NFT | ||
/// @dev The amounts reported may not be exactly equivalent to the amounts transferred, due to rounding behavior | ||
/// @param tokenId The ID of the token for which underlying tokens were collected | ||
/// @param recipient The address of the account that received the collected tokens | ||
/// @param amount0 The amount of token0 owed to the position that was collected | ||
/// @param amount1 The amount of token1 owed to the position that was collected | ||
event Collect(uint256 indexed tokenId, address recipient, uint256 amount0, uint256 amount1); | ||
|
||
/// @notice Creates a new pool if it does not exist, then initializes if not initialized | ||
/// @dev This method can be bundled with others via IMulticall for the first action (e.g. mint) performed against a pool | ||
/// @param sqrtPriceX96 The initial square root price of the pool as a Q64.96 value | ||
/// @param initData The initial square root price of the pool as a Q64.96 value | ||
function createAndInitializePoolIfNecessary(PoolKey memory poolkey, uint160 sqrtPriceX96, bytes memory initData) | ||
external | ||
payable; | ||
|
||
/// @notice Returns the position information associated with a given token ID. | ||
/// @dev Throws if the token ID is not valid. | ||
/// @param tokenId The ID of the token that represents the position | ||
/// @return nonce The nonce for permits | ||
/// @return operator The address that is approved for spending | ||
/// @return currency0 The address of the currency0 for a specific pool | ||
/// @return currency1 The address of the currency1 for a specific pool | ||
/// @return fee The fee associated with the pool | ||
/// @return tickLower The lower end of the tick range for the position | ||
/// @return tickUpper The higher end of the tick range for the position | ||
/// @return liquidity The liquidity of the position | ||
/// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position | ||
/// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position | ||
/// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation | ||
/// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computation | ||
function positions(uint256 tokenId) | ||
external | ||
view | ||
returns ( | ||
uint96 nonce, | ||
address operator, | ||
Currency currency0, | ||
Currency currency1, | ||
uint24 fee, | ||
int24 tickLower, | ||
int24 tickUpper, | ||
uint128 liquidity, | ||
uint256 feeGrowthInside0LastX128, | ||
uint256 feeGrowthInside1LastX128, | ||
uint128 tokensOwed0, | ||
uint128 tokensOwed1 | ||
); | ||
|
||
struct MintParams { | ||
address token0; | ||
address token1; | ||
uint24 fee; | ||
int24 tickLower; | ||
int24 tickUpper; | ||
uint256 amount0Desired; | ||
uint256 amount1Desired; | ||
uint256 amount0Min; | ||
uint256 amount1Min; | ||
address recipient; | ||
uint256 deadline; | ||
} | ||
|
||
/// @notice Creates a new position wrapped in a NFT | ||
/// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized | ||
/// a method does not exist, i.e. the pool is assumed to be initialized. | ||
/// @param params The params necessary to mint a position, encoded as `MintParams` in calldata | ||
/// @return tokenId The ID of the token that represents the minted position | ||
/// @return liquidity The amount of liquidity for this position | ||
/// @return amount0 The amount of token0 | ||
/// @return amount1 The amount of token1 | ||
function mint(MintParams calldata params) | ||
external | ||
payable | ||
returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1); | ||
|
||
struct IncreaseLiquidityParams { | ||
uint256 tokenId; | ||
uint256 amount0Desired; | ||
uint256 amount1Desired; | ||
uint256 amount0Min; | ||
uint256 amount1Min; | ||
uint256 deadline; | ||
} | ||
|
||
/// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender` | ||
/// @param params tokenId The ID of the token for which liquidity is being increased, | ||
/// amount0Desired The desired amount of token0 to be spent, | ||
/// amount1Desired The desired amount of token1 to be spent, | ||
/// amount0Min The minimum amount of token0 to spend, which serves as a slippage check, | ||
/// amount1Min The minimum amount of token1 to spend, which serves as a slippage check, | ||
/// deadline The time by which the transaction must be included to effect the change | ||
/// @return liquidity The new liquidity amount as a result of the increase | ||
/// @return amount0 The amount of token0 to acheive resulting liquidity | ||
/// @return amount1 The amount of token1 to acheive resulting liquidity | ||
function increaseLiquidity(IncreaseLiquidityParams calldata params) | ||
external | ||
payable | ||
returns (uint128 liquidity, uint256 amount0, uint256 amount1); | ||
|
||
struct DecreaseLiquidityParams { | ||
uint256 tokenId; | ||
uint128 liquidity; | ||
uint256 amount0Min; | ||
uint256 amount1Min; | ||
uint256 deadline; | ||
} | ||
|
||
/// @notice Decreases the amount of liquidity in a position and accounts it to the position | ||
/// @param params tokenId The ID of the token for which liquidity is being decreased, | ||
/// amount The amount by which liquidity will be decreased, | ||
/// amount0Min The minimum amount of token0 that should be accounted for the burned liquidity, | ||
/// amount1Min The minimum amount of token1 that should be accounted for the burned liquidity, | ||
/// deadline The time by which the transaction must be included to effect the change | ||
/// @return amount0 The amount of token0 accounted to the position's tokens owed | ||
/// @return amount1 The amount of token1 accounted to the position's tokens owed | ||
function decreaseLiquidity(DecreaseLiquidityParams calldata params) | ||
external | ||
payable | ||
returns (uint256 amount0, uint256 amount1); | ||
|
||
struct CollectParams { | ||
uint256 tokenId; | ||
address recipient; | ||
uint128 amount0Max; | ||
uint128 amount1Max; | ||
} | ||
|
||
/// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient | ||
/// @param params tokenId The ID of the NFT for which tokens are being collected, | ||
/// recipient The account that should receive the tokens, | ||
/// amount0Max The maximum amount of token0 to collect, | ||
/// amount1Max The maximum amount of token1 to collect | ||
/// @return amount0 The amount of fees collected in token0 | ||
/// @return amount1 The amount of fees collected in token1 | ||
function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1); | ||
|
||
/// @notice Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens | ||
/// must be collected first. | ||
/// @param tokenId The ID of the token that is being burned | ||
function burn(uint256 tokenId) external payable; | ||
} |