Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add v4 NonfungiblePositionManager skeleton #76

Closed
wants to merge 31 commits into from

Conversation

tinaszheng
Copy link
Contributor

@tinaszheng tinaszheng commented Nov 21, 2023

Description of changes

This PR adds the base skeleton for NonfungiblePositionManagerV4

So this is mostly copied from v3-periphery but I made a few changes:

  • Pool IDs are now just PoolKeys (instead of having a separate counter => address mapping)
  • I removed references to WETH9 specifically (until we decide on what to do about WETH in v4)
  • Added LiquidityManagement to manage lock/lock callback logic
  • Added createAndInitializePoolIfNecessary directly to the contract and func args take in PoolKey instead of ids
  • Added tokenByIndex, tokenOfOwnerByIndex, and totalSupply - think these were added to ERC721Enumerable?

TODO (next steps):

  • fill in function definitions (everywhere that says // TODO: implement this)
  • add tests
  • Use permit2 version of ERC721Permit?

@tinaszheng tinaszheng changed the title add v4 position manager contracts Add v4 NonfungiblePositionManager Nov 21, 2023
@tinaszheng tinaszheng changed the title Add v4 NonfungiblePositionManager Add v4 NonfungiblePositionManager (WIP) Nov 21, 2023
@tinaszheng tinaszheng force-pushed the tina/position-manager branch from 2834b62 to 38e78db Compare November 21, 2023 21:54
@tinaszheng tinaszheng force-pushed the tina/position-manager branch from 88119a9 to a26a6e9 Compare November 21, 2023 23:10
@tinaszheng tinaszheng force-pushed the tina/position-manager branch from a26a6e9 to 05c9543 Compare November 26, 2023 22:43
@tinaszheng tinaszheng force-pushed the tina/add-base-contracts-for-pm branch from 02a5cd2 to 324976e Compare November 28, 2023 02:33
Base automatically changed from tina/add-base-contracts-for-pm to main November 28, 2023 16:41
@tinaszheng tinaszheng force-pushed the tina/position-manager branch from ca4e05c to ad34755 Compare November 29, 2023 17:14
@tinaszheng tinaszheng changed the title Add v4 NonfungiblePositionManager (WIP) Add v4 NonfungiblePositionManager skeleton Nov 29, 2023
import {PeripheryImmutableState} from "./PeripheryImmutableState.sol";

abstract contract LiquidityManagement is ILockCallback, ILiquidityManagement, PeripheryImmutableState {
function mintEntry(MintParams memory params)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just an example of how LiquidityManagement can be used with contracts/NonfungiblePositionManagerV4.sol - mintEntry calls the lock, then lockAcquired handles rest of the codepath within this file

Comment on lines +17 to +21
if (result.length == 4) {
assembly {
revert(add(result, 0x20), mload(result))
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops mark pointed out that custom errors can revert with params so this doesn't necessarily catch all custom errors 😅 , we could probably just revert with the reason again or we can look for a fix

mapping(bytes32 => PoolKey) private _poolIdToPoolKey;

/// @dev The token ID position data
mapping(uint256 => TokenPosition) private _positions;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason for this to be private? im down for this to be public i can imagine other contracts or interfaces wanting to look up position info

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thinking they probably want to use the positions getter for getting position data, since it includes helpers for unwrapping poolId - otherwise they're kind of exposed to our internal representation of how we store pools right? they'll need both _poolIdToPoolKey and _positions to get useful position data

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah good point

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Would just add to the natspec that they key is specifically the tokenId (tokenId) ie mapping from tokenId to position data

/// @inheritdoc INonfungiblePositionManagerV4
function burn(uint256 tokenId) external payable override isAuthorizedForToken(tokenId) {
TokenPosition storage position = _positions[tokenId];
if (position.liquidity != 0 || position.tokensOwed0 != 0 || position.tokensOwed1 != 0) revert NotCleared();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to make sure theyve collected all their rewards before they can burn? or does this already check that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok ive dug into it and these checks should be enough for burn

} else {
if (currency.isNative()) revert NativeTokenTransferFrom();
// pull payment
ERC20(Currency.unwrap(currency)).safeTransferFrom(payer, recipient, value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to use Permit2 for this? or are we good with approve and transferFrom?

/// @title Periphery Payments
/// @notice Functions to ease deposits and withdrawals of ETH
interface IPeripheryPayments {
// TODO: figure out if we still need unwrapWETH9 from v3?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm good question. I think its more of an opinionated decision nowadays... I would lean towards not adding it? People can add liquidity in ETH and remove it in ETH, people can add liquidity in WETH and remove it in WETH. It feels opinionated now to allow the WETH people to remove it as ETH if we arent doing it the other way around?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

however we do like to be helpful if we think its a common usecase of course

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah agreed on not making it opinionated

pay(poolKey.currency0, from, address(poolManager), uint256(int256(delta.amount0())));
poolManager.settle(poolKey.currency0);
} else if (delta.amount0() < 0) {
poolManager.take(poolKey.currency0, address(this), uint128(-delta.amount0()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i feel like we might want this to send directly to the recipient instead of to address(this) - so that we dont then have to send it onwards which is extra gas?

}

/// @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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is a pool that is created but not initialized?

payable
returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);

struct IncreaseLiquidityParams {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer all structs to be defined at the top/grouped together. I think that is just a consistent styling choice I've noticed for most of our contracts

bytes params;
}

struct AddLiquidityParams {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems weird to have bothAddLiquidityParams and IncreaseLiquidityParams. Like hard to know why you use one and not the other.

Also, its possible that we actually can just have ModifyLiquidityParams that handle both increase and decrease..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we should be consistent with the naming add/remove vs. increase/decrease. I prefer increase/decrease I think


function lockAcquired(bytes calldata data) external override returns (bytes memory) {
CallbackData memory callbackData = abi.decode(data, (CallbackData));
if (callbackData.callbackType == CallbackType.AddLiquidity) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this will be a big block of if statements?

probably will then want to move the settle logic and the return statement outside the if statement since we will want to settle/return deltas in all of the cases

);
}

function addLiquidityCallback(AddLiquidityParams memory params)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addLiquidityCallback is a strange name for an internal handler function because callbacks imply an external caller. Also for internal functions we can use the _

maybe _addLiquidity?

// the nonce for permits
uint96 nonce;
// the address that is approved for spending this token
address operator;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm this is kind of strange because you can have more than 1 operator with the 721 spec... so not sure why we save just one. did we do the same in v3? if so.. why?

}

/// @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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm I'm kinda confused with the approval/auth scheme we are defining. It sounds like we are trying to override the traditional 721 spec but we are still using(not dis-allowing) the old 721 interfaces.

and we are using the old traditional 721 checks like _isApprovedOrOwner which doesnt look at the operator in the TokenPositions struct

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also it seems like we want to allow permits... but where is all that sig verification?

}

function settleDeltas(address from, PoolKey memory poolKey, BalanceDelta delta) internal {
if (delta.amount0() > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that this won't work for pools that have custom accounting


function settleDeltas(address from, PoolKey memory poolKey, BalanceDelta delta) internal {
if (delta.amount0() > 0) {
pay(poolKey.currency0, from, address(poolManager), uint256(int256(delta.amount0())));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how are we handling native?

) revert PriceSlippage();
}

function settleDeltas(address from, PoolKey memory poolKey, BalanceDelta delta) internal {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One high level thing here... I don't think we want to use take and settle. We will want to use mint/ burn.

When the user is withdrawing fully we will use take/settle though to give them the underlying erc20s. But when we are in the state where the position manager is still managing positions for a user we just want to hold claims to underlying imo (which will interface with the 6909 path)

@hensha256 hensha256 added the posm position manager label Jun 26, 2024
@snreynolds
Copy link
Member

Closing in favor of #141

@snreynolds snreynolds closed this Jul 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
posm position manager
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants