Skip to content

Commit

Permalink
Sra/edits (#137)
Browse files Browse the repository at this point in the history
* consolidate using owner, update burn

* fix: accrue deltas to caller in increase
  • Loading branch information
snreynolds authored Jul 1, 2024
1 parent a2186d7 commit 0f39420
Show file tree
Hide file tree
Showing 14 changed files with 62 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .forge-snapshots/autocompound_exactUnclaimedFees.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
299933
300126
Original file line number Diff line number Diff line change
@@ -1 +1 @@
239395
239588
2 changes: 1 addition & 1 deletion .forge-snapshots/autocompound_excessFeesCredit.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
320472
320665
2 changes: 1 addition & 1 deletion .forge-snapshots/decreaseLiquidity_erc20.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
215156
215187
2 changes: 1 addition & 1 deletion .forge-snapshots/decreaseLiquidity_erc6909.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
215168
215199
2 changes: 1 addition & 1 deletion .forge-snapshots/increaseLiquidity_erc20.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
194836
195029
2 changes: 1 addition & 1 deletion .forge-snapshots/increaseLiquidity_erc6909.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
194848
195041
2 changes: 1 addition & 1 deletion .forge-snapshots/mintWithLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
511680
512049
54 changes: 25 additions & 29 deletions contracts/NonfungiblePositionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientSta
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {TransientLiquidityDelta} from "./libraries/TransientLiquidityDelta.sol";

import "forge-std/console2.sol";

contract NonfungiblePositionManager is INonfungiblePositionManager, BaseLiquidityManagement, ERC721Permit {
using CurrencyLibrary for Currency;
using CurrencySettleTake for Currency;
Expand All @@ -39,12 +41,18 @@ contract NonfungiblePositionManager is INonfungiblePositionManager, BaseLiquidit
// TODO: TSTORE this jawn
address internal msgSender;

// TODO: Context is inherited through ERC721 and will be not useful to use _msgSender() which will be address(this) with our current mutlicall.
function _msgSenderInternal() internal override returns (address) {
return msgSender;
}

constructor(IPoolManager _manager)
BaseLiquidityManagement(_manager)
ERC721Permit("Uniswap V4 Positions NFT-V1", "UNI-V4-POS", "1")
{}

function unlockAndExecute(bytes[] memory data, Currency[] memory currencies) public returns (bytes memory) {
msgSender = msg.sender;
return manager.unlock(abi.encode(data, currencies));
}

Expand All @@ -65,6 +73,9 @@ contract NonfungiblePositionManager is INonfungiblePositionManager, BaseLiquidit
currencies[i].close(manager, address(this));
}

// Should just be returning the netted amount that was settled on behalf of the caller (msgSender)
// And any recipient deltas settled earlier.

// TODO: @sara handle the return
// vanilla: return int128[2]
// batch: return int128[data.length]
Expand All @@ -77,21 +88,21 @@ contract NonfungiblePositionManager is INonfungiblePositionManager, BaseLiquidit
LiquidityRange calldata range,
uint256 liquidity,
uint256 deadline,
address recipient,
address owner,
bytes calldata hookData
) public payable returns (BalanceDelta delta) {
// TODO: optimization, read/write manager.isUnlocked to avoid repeated external calls for batched execution
if (manager.isUnlocked()) {
_increaseLiquidity(recipient, range, liquidity, hookData);
_increaseLiquidity(owner, range, liquidity, hookData);

// mint receipt token
uint256 tokenId;
_mint(recipient, (tokenId = nextTokenId++));
tokenPositions[tokenId] = TokenPosition({owner: recipient, range: range});
_mint(owner, (tokenId = nextTokenId++));
tokenPositions[tokenId] = TokenPosition({owner: owner, range: range});
} else {
msgSender = msg.sender;
bytes[] memory data = new bytes[](1);
data[0] = abi.encodeWithSelector(this.mint.selector, range, liquidity, deadline, recipient, hookData);
data[0] = abi.encodeWithSelector(this.mint.selector, range, liquidity, deadline, owner, hookData);

Currency[] memory currencies = new Currency[](2);
currencies[0] = range.poolKey.currency0;
Expand Down Expand Up @@ -131,7 +142,6 @@ contract NonfungiblePositionManager is INonfungiblePositionManager, BaseLiquidit
if (manager.isUnlocked()) {
_increaseLiquidity(tokenPos.owner, tokenPos.range, liquidity, hookData);
} else {
msgSender = msg.sender;
bytes[] memory data = new bytes[](1);
data[0] = abi.encodeWithSelector(this.increaseLiquidity.selector, tokenId, liquidity, hookData, claims);

Expand All @@ -153,10 +163,9 @@ contract NonfungiblePositionManager is INonfungiblePositionManager, BaseLiquidit
if (manager.isUnlocked()) {
_decreaseLiquidity(tokenPos.owner, tokenPos.range, liquidity, hookData);
} else {
msgSender = msg.sender;
bytes[] memory data = new bytes[](1);
data[0] = abi.encodeWithSelector(this.decreaseLiquidity.selector, tokenId, liquidity, hookData, claims);

Currency[] memory currencies = new Currency[](2);
currencies[0] = tokenPos.range.poolKey.currency0;
currencies[1] = tokenPos.range.poolKey.currency1;
Expand All @@ -165,27 +174,15 @@ contract NonfungiblePositionManager is INonfungiblePositionManager, BaseLiquidit
}
}

function burn(uint256 tokenId, address recipient, bytes calldata hookData, bool claims)
external
isAuthorizedForToken(tokenId)
returns (BalanceDelta delta)
{
// TODO: Burn currently decreases and collects. However its done under different locks.
// Replace once we have the execute multicall.
// remove liquidity
TokenPosition storage tokenPosition = tokenPositions[tokenId];
LiquidityRangeId rangeId = tokenPosition.range.toId();
Position storage position = positions[msg.sender][rangeId];
if (position.liquidity > 0) {
delta = decreaseLiquidity(tokenId, position.liquidity, hookData, claims);
}

collect(tokenId, recipient, hookData, claims);
require(position.tokensOwed0 == 0 && position.tokensOwed1 == 0, "NOT_EMPTY");
delete positions[msg.sender][rangeId];
// TODO return type?
function burn(uint256 tokenId) public isAuthorizedForToken(tokenId) returns (BalanceDelta delta) {
// TODO: Burn currently requires a decrease and collect call before the token can be deleted. Possible to combine.
// We do not need to enforce the pool manager to be unlocked bc this function is purely clearing storage for the minted tokenId.
TokenPosition memory tokenPos = tokenPositions[tokenId];
// Checks that the full position's liquidity has been removed and all tokens have been collected from tokensOwed.
_validateBurn(tokenPos.owner, tokenPos.range);
delete tokenPositions[tokenId];

// burn the token
// Burn the token.
_burn(tokenId);
}

Expand All @@ -198,7 +195,6 @@ contract NonfungiblePositionManager is INonfungiblePositionManager, BaseLiquidit
if (manager.isUnlocked()) {
_collect(recipient, tokenPos.range, hookData);
} else {
msgSender = msg.sender;
bytes[] memory data = new bytes[](1);
data[0] = abi.encodeWithSelector(this.collect.selector, tokenId, recipient, hookData, claims);

Expand Down
15 changes: 14 additions & 1 deletion contracts/base/BaseLiquidityManagement.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ abstract contract BaseLiquidityManagement is IBaseLiquidityManagement, SafeCallb

constructor(IPoolManager _manager) ImmutableState(_manager) {}

function _msgSenderInternal() internal virtual returns (address);

function _closeCallerDeltas(
BalanceDelta callerDeltas,
Currency currency0,
Expand Down Expand Up @@ -120,7 +122,9 @@ abstract contract BaseLiquidityManagement is IBaseLiquidityManagement, SafeCallb
(tokensOwed, callerDelta, thisDelta) =
_moveCallerDeltaToTokensOwed(false, tokensOwed, callerDelta, thisDelta);
}
callerDelta.flush(owner, range.poolKey.currency0, range.poolKey.currency1);

// Accrue all deltas to the caller.
callerDelta.flush(_msgSenderInternal(), range.poolKey.currency0, range.poolKey.currency1);
thisDelta.flush(address(this), range.poolKey.currency0, range.poolKey.currency1);

position.addTokensOwed(tokensOwed);
Expand Down Expand Up @@ -201,6 +205,7 @@ abstract contract BaseLiquidityManagement is IBaseLiquidityManagement, SafeCallb
position.subtractLiquidity(liquidityToRemove);
}

// The recipient may not be the original owner.
function _collect(address owner, LiquidityRange memory range, bytes memory hookData) internal {
BalanceDelta callerDelta;
BalanceDelta thisDelta;
Expand Down Expand Up @@ -234,6 +239,14 @@ abstract contract BaseLiquidityManagement is IBaseLiquidityManagement, SafeCallb
position.clearTokensOwed();
}

function _validateBurn(address owner, LiquidityRange memory range) internal {
LiquidityRangeId rangeId = range.toId();
Position storage position = positions[owner][rangeId];
if (position.liquidity > 0) revert PositionMustBeEmpty();
if (position.tokensOwed0 != 0 && position.tokensOwed1 != 0) revert TokensMustBeCollected();
delete positions[owner][rangeId];
}

function _updateFeeGrowth(LiquidityRange memory range, Position storage position)
internal
returns (BalanceDelta callerFeesAccrued)
Expand Down
3 changes: 3 additions & 0 deletions contracts/interfaces/IBaseLiquidityManagement.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {LiquidityRange, LiquidityRangeId} from "../types/LiquidityRange.sol";

interface IBaseLiquidityManagement {
error PositionMustBeEmpty();
error TokensMustBeCollected();

// details about the liquidity position
struct Position {
// the nonce for permits
Expand Down
10 changes: 3 additions & 7 deletions contracts/interfaces/INonfungiblePositionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,12 @@ interface INonfungiblePositionManager {
external
returns (BalanceDelta delta);

// TODO Can decide if we want burn to auto encode a decrease/collect.
/// @notice Burn a position and delete the tokenId
/// @dev It removes liquidity and collects fees if the position is not empty
/// @dev It enforces that there is no open liquidity or tokens to be collected
/// @param tokenId The ID of the position
/// @param recipient The address to send the collected tokens to
/// @param hookData Arbitrary data passed to the hook
/// @param claims Whether the removed liquidity is sent as ERC-6909 claim tokens
/// @return delta Corresponding balance changes as a result of burning the position
function burn(uint256 tokenId, address recipient, bytes calldata hookData, bool claims)
external
returns (BalanceDelta delta);
function burn(uint256 tokenId) external returns (BalanceDelta delta);

// TODO: in v3, we can partially collect fees, but what was the usecase here?
/// @notice Collect fees for a position
Expand Down
6 changes: 5 additions & 1 deletion contracts/libraries/TransientLiquidityDelta.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDe
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";

import {CurrencySettleTake} from "../libraries/CurrencySettleTake.sol";
import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";

import "forge-std/console2.sol";

/// @title a library to store callers' currency deltas in transient storage
/// @dev this library implements the equivalent of a mapping, as transient storage can only be accessed in assembly
library TransientLiquidityDelta {
using CurrencySettleTake for Currency;
using TransientStateLibrary for IPoolManager;

/// @notice calculates which storage slot a delta should be stored in for a given caller and currency
function _computeSlot(address caller_, Currency currency) internal pure returns (bytes32 hashSlot) {
Expand Down Expand Up @@ -58,7 +62,7 @@ library TransientLiquidityDelta {
delta := tload(hashSlot)
}

// close the delta by paying or taking
// TODO support claims field
if (delta < 0) {
currency.settle(manager, holder, uint256(-delta), false);
} else {
Expand Down
5 changes: 4 additions & 1 deletion test/position-managers/NonfungiblePositionManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,10 @@ contract NonfungiblePositionManagerTest is Test, Deployers, GasSnapshot, Liquidi
// burn liquidity
uint256 balance0BeforeBurn = currency0.balanceOfSelf();
uint256 balance1BeforeBurn = currency1.balanceOfSelf();
BalanceDelta delta = lpm.burn(tokenId, address(this), ZERO_BYTES, false);
// TODO, encode this under one call
lpm.decreaseLiquidity(tokenId, liquidity, ZERO_BYTES, false);
lpm.collect(tokenId, address(this), ZERO_BYTES, false);
BalanceDelta delta = lpm.burn(tokenId);
(,, liquidity,,,,) = lpm.positions(address(this), range.toId());
assertEq(liquidity, 0);

Expand Down

0 comments on commit 0f39420

Please sign in to comment.