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 transfer support #218

Merged
merged 30 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d59846b
move to a base actions router
snreynolds Jul 27, 2024
46f47b2
use one Planner file
snreynolds Jul 27, 2024
b198c30
msgSender
snreynolds Jul 27, 2024
3482b23
add transfer
snreynolds Jul 27, 2024
2cee496
add transfer test
snreynolds Jul 27, 2024
bf369e6
merge main
snreynolds Jul 29, 2024
494f7d6
use delta saving hook to get deltas
snreynolds Jul 29, 2024
0449cac
remove return values
snreynolds Jul 29, 2024
47bbc6d
add burn comment
snreynolds Jul 29, 2024
7c911cb
make gas snapshots more accurate, remove hook
snreynolds Jul 29, 2024
5f26936
merge main
snreynolds Jul 29, 2024
03cf453
move to 1 planner, fix merge conf
snreynolds Jul 29, 2024
2aba16d
sweep currency, pr comments
snreynolds Jul 30, 2024
486617e
merge updates from use-actions-router
snreynolds Jul 30, 2024
b3aa874
rename, add liquidityDelta return param
snreynolds Jul 30, 2024
6885931
rename
snreynolds Jul 30, 2024
a60f16d
comment
snreynolds Jul 30, 2024
7ac7043
gas check
snreynolds Jul 30, 2024
64897c3
add gas test, using uint256
snreynolds Jul 30, 2024
b0b1b73
gas check, using 0
snreynolds Jul 30, 2024
bdfc04d
comments
snreynolds Jul 30, 2024
767605d
Merge branch 'main' into use-actions-router
snreynolds Jul 30, 2024
68e3a66
remove SafeCallback
snreynolds Jul 30, 2024
9d403c3
merge actions router
snreynolds Jul 30, 2024
0778130
remove FULL_DELTA
snreynolds Jul 30, 2024
9087916
merge main
snreynolds Jul 30, 2024
03e978f
move helpers to delta resolver
snreynolds Jul 30, 2024
dedf310
remove import
snreynolds Jul 30, 2024
428db01
increase liq with sttle with balance test
hensha256 Jul 31, 2024
858707e
Merge branch 'main' into add-transfer-support
hensha256 Jul 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_burn_empty.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
50204
47286
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_burn_empty_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
50021
47104
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_burn_nonEmpty.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
135849
130140
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_burn_nonEmpty_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
128771
123061
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
158136
151228
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
149288
142380
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_sameRange.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
158136
151228
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_decreaseLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
123679
116771
Original file line number Diff line number Diff line change
@@ -1 +1 @@
114905
109379
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_decrease_burnEmpty.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
142176
135179
Original file line number Diff line number Diff line change
@@ -1 +1 @@
134916
127918
Original file line number Diff line number Diff line change
@@ -1 +1 @@
136395
129487
Original file line number Diff line number Diff line change
@@ -1 +1 @@
159575
152741
Original file line number Diff line number Diff line change
@@ -1 +1 @@
141458
134541
Original file line number Diff line number Diff line change
@@ -1 +1 @@
142282
135437
Original file line number Diff line number Diff line change
@@ -1 +1 @@
178438
171593
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
379791
372812
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
344574
337512
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_nativeWithSweep.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
351512
346374
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_onSameTickLower.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
322473
315494
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_onSameTickUpper.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
323115
316136
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_sameRange.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
248697
241718
Original file line number Diff line number Diff line change
@@ -1 +1 @@
328491
321512
Original file line number Diff line number Diff line change
@@ -1 +1 @@
424259
417192
158 changes: 84 additions & 74 deletions src/PositionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"

import {ERC721Permit} from "./base/ERC721Permit.sol";
import {ReentrancyLock} from "./base/ReentrancyLock.sol";
import {IPositionManager, Actions} from "./interfaces/IPositionManager.sol";
import {IPositionManager} from "./interfaces/IPositionManager.sol";
import {SafeCallback} from "./base/SafeCallback.sol";
import {Multicall} from "./base/Multicall.sol";
import {PoolInitializer} from "./base/PoolInitializer.sol";
import {DeltaResolver} from "./base/DeltaResolver.sol";
import {PositionConfig, PositionConfigLibrary} from "./libraries/PositionConfig.sol";
import {BaseActionsRouter} from "./base/BaseActionsRouter.sol";
import {Actions} from "./libraries/Actions.sol";

contract PositionManager is
IPositionManager,
Expand All @@ -29,7 +31,8 @@ contract PositionManager is
Multicall,
SafeCallback,
DeltaResolver,
ReentrancyLock
ReentrancyLock,
BaseActionsRouter
{
using SafeTransferLib for *;
using CurrencyLibrary for Currency;
Expand All @@ -47,8 +50,10 @@ contract PositionManager is

IAllowanceTransfer public immutable permit2;

uint256 public constant FULL_DELTA = type(uint256).max;
Copy link
Contributor

Choose a reason for hiding this comment

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

I do think youll find if you make this 0 gas should go down noticeably because of removing non-0 calldata bytes.
It should go down by (16-4) * 32) = 384

Copy link
Member Author

Choose a reason for hiding this comment

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

lemme check!

Copy link
Member Author

Choose a reason for hiding this comment

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

went down by ~700

Copy link
Member Author

Choose a reason for hiding this comment

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


constructor(IPoolManager _poolManager, IAllowanceTransfer _permit2)
SafeCallback(_poolManager)
BaseActionsRouter(_poolManager)
ERC721Permit("Uniswap V4 Positions NFT", "UNI-V4-POSM", "1")
{
permit2 = _permit2;
Expand All @@ -60,80 +65,66 @@ contract PositionManager is
}

/// @param unlockData is an encoding of actions, params, and currencies
/// @return returnData is the endocing of each actions return information
function modifyLiquidities(bytes calldata unlockData, uint256 deadline)
external
payable
isNotLocked
checkDeadline(deadline)
returns (bytes[] memory)
{
// TODO: Edit the encoding/decoding.
return abi.decode(poolManager.unlock(unlockData), (bytes[]));
}

function _unlockCallback(bytes calldata payload) internal override returns (bytes memory) {
(Actions[] memory actions, bytes[] memory params) = abi.decode(payload, (Actions[], bytes[]));

bytes[] memory returnData = _dispatch(actions, params);

return abi.encode(returnData);
}

function _dispatch(Actions[] memory actions, bytes[] memory params) internal returns (bytes[] memory returnData) {
uint256 length = actions.length;
if (length != params.length) revert MismatchedLengths();
returnData = new bytes[](length);
for (uint256 i; i < length; i++) {
if (actions[i] == Actions.INCREASE) {
returnData[i] = _increase(params[i]);
} else if (actions[i] == Actions.DECREASE) {
returnData[i] = _decrease(params[i]);
} else if (actions[i] == Actions.MINT) {
// TODO: Mint will be coupled with increase.
returnData[i] = _mint(params[i]);
} else if (actions[i] == Actions.CLOSE_CURRENCY) {
returnData[i] = _close(params[i]);
} else if (actions[i] == Actions.BURN) {
// Will automatically decrease liquidity to 0 if the position is not already empty.
returnData[i] = _burn(params[i]);
} else {
revert UnsupportedAction();
}
_executeActions(unlockData);
}

function _handleAction(uint256 action, bytes calldata params) internal override {
if (action == Actions.INCREASE_LIQUIDITY) {
_increase(params);
} else if (action == Actions.DECREASE_LIQUIDITY) {
_decrease(params);
} else if (action == Actions.MINT_POSITION) {
_mint(params);
} else if (action == Actions.CLOSE_CURRENCY) {
_close(params);
} else if (action == Actions.BURN_POSITION) {
// Will automatically decrease liquidity to 0 if the position is not already empty.
_burn(params);
} else if (action == Actions.SETTLE_WITH_BALANCE) {
_settleWithBalance(params);
} else if (action == Actions.SWEEP) {
_sweep(params);
} else {
revert UnsupportedAction(action);
}
}

function _msgSender() internal view override returns (address) {
return _getLocker();
}

/// @param params is an encoding of uint256 tokenId, PositionConfig memory config, uint256 liquidity, bytes hookData
/// @return returns an encoding of the BalanceDelta applied by this increase call, including credited fees.
/// @dev Calling increase with 0 liquidity will credit the caller with any underlying fees of the position
function _increase(bytes memory params) internal returns (bytes memory) {
function _increase(bytes memory params) internal {
(uint256 tokenId, PositionConfig memory config, uint256 liquidity, bytes memory hookData) =
abi.decode(params, (uint256, PositionConfig, uint256, bytes));

if (positionConfigs[tokenId] != config.toId()) revert IncorrectPositionConfigForTokenId(tokenId);
// Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager.
BalanceDelta delta = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData);
return abi.encode(delta);
_modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData);
}

/// @param params is an encoding of uint256 tokenId, PositionConfig memory config, uint256 liquidity, bytes hookData
/// @return returns an encoding of the BalanceDelta applied by this increase call, including credited fees.
/// @dev Calling decrease with 0 liquidity will credit the caller with any underlying fees of the position
function _decrease(bytes memory params) internal returns (bytes memory) {
function _decrease(bytes memory params) internal {
(uint256 tokenId, PositionConfig memory config, uint256 liquidity, bytes memory hookData) =
abi.decode(params, (uint256, PositionConfig, uint256, bytes));

if (!_isApprovedOrOwner(_getLocker(), tokenId)) revert NotApproved(_getLocker());
if (!_isApprovedOrOwner(_msgSender(), tokenId)) revert NotApproved(_msgSender());
if (positionConfigs[tokenId] != config.toId()) revert IncorrectPositionConfigForTokenId(tokenId);

// Note: the tokenId is used as the salt.
BalanceDelta delta = _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData);
return abi.encode(delta);
_modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData);
}

/// @param params is an encoding of PositionConfig memory config, uint256 liquidity, address recipient, bytes hookData where recipient is the receiver / owner of the ERC721
/// @return returns an encoding of the BalanceDelta from the initial increase
function _mint(bytes memory params) internal returns (bytes memory) {
function _mint(bytes memory params) internal {
(PositionConfig memory config, uint256 liquidity, address owner, bytes memory hookData) =
abi.decode(params, (PositionConfig, uint256, address, bytes));

Expand All @@ -146,61 +137,61 @@ contract PositionManager is
_mint(owner, tokenId);

// _beforeModify is not called here because the tokenId is newly minted
BalanceDelta delta = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData);
_modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData);

positionConfigs[tokenId] = config.toId();

return abi.encode(delta);
}

/// @param params is an encoding of the Currency to close
/// @return bytes an encoding of int256 the balance of the currency being settled by this call
function _close(bytes memory params) internal returns (bytes memory) {
function _close(bytes memory params) internal {
(Currency currency) = abi.decode(params, (Currency));
// this address has applied all deltas on behalf of the user/owner
// it is safe to close this entire delta because of slippage checks throughout the batched calls.
int256 currencyDelta = poolManager.currencyDelta(address(this), currency);

// the locker is the payer or receiver
address caller = _getLocker();
address caller = _msgSender();
if (currencyDelta < 0) {
_settle(currency, caller, uint256(-currencyDelta));

// if there are native tokens left over after settling, return to locker
if (currency.isNative()) _sweepNativeToken(caller);
hensha256 marked this conversation as resolved.
Show resolved Hide resolved
} else if (currencyDelta > 0) {
_take(currency, caller, uint256(currencyDelta));
}
}

return abi.encode(currencyDelta);
/// @param params is an encoding of Currency, uint256 amount
/// @dev if amount == FULL_DELTA, it settles the full negative delta
/// @dev uses this addresses balance to settle a negative delta
function _settleWithBalance(bytes memory params) internal {
(Currency currency, uint256 amount) = abi.decode(params, (Currency, uint256));

amount = amount == FULL_DELTA ? _getFullSettleAmount(currency) : amount;

// set the payer to this address, performs a transfer.
_settle(currency, address(this), amount);
}

/// @param params is an encoding of uint256 tokenId, PositionConfig memory config, bytes hookData
/// @dev this is overloaded with ERC721Permit._burn
function _burn(bytes memory params) internal returns (bytes memory) {
function _burn(bytes memory params) internal {
(uint256 tokenId, PositionConfig memory config, bytes memory hookData) =
abi.decode(params, (uint256, PositionConfig, bytes));

if (!_isApprovedOrOwner(_getLocker(), tokenId)) revert NotApproved(_getLocker());
if (!_isApprovedOrOwner(_msgSender(), tokenId)) revert NotApproved(_msgSender());
if (positionConfigs[tokenId] != config.toId()) revert IncorrectPositionConfigForTokenId(tokenId);
uint256 liquidity = uint256(_getPositionLiquidity(config, tokenId));

// Can only call modify if there is non zero liquidity.
BalanceDelta delta;

if (liquidity > 0) delta = _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData);
if (liquidity > 0) _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData);

delete positionConfigs[tokenId];
// Burn the token.
_burn(tokenId);
return abi.encode(delta);
}

function _modifyLiquidity(PositionConfig memory config, int256 liquidityChange, bytes32 salt, bytes memory hookData)
internal
returns (BalanceDelta liquidityDelta)
{
(liquidityDelta,) = poolManager.modifyLiquidity(
poolManager.modifyLiquidity(
config.poolKey,
IPoolManager.ModifyLiquidityParams({
tickLower: config.tickLower,
Expand All @@ -223,16 +214,35 @@ contract PositionManager is
liquidity = poolManager.getPositionLiquidity(config.poolKey.toId(), positionId);
}

/// @dev Send excess native tokens back to the recipient (locker)
/// @param recipient the receiver of the excess native tokens. Should be the caller, the one that sent the native tokens
function _sweepNativeToken(address recipient) internal {
uint256 nativeBalance = address(this).balance;
if (nativeBalance > 0) recipient.safeTransferETH(nativeBalance);
/// @notice Sweeps the entire contract balance of specified currency to the recipient
/// @param params an encoding of Currency, address
function _sweep(bytes calldata params) internal {
(Currency currency, address to) = abi.decode(params, (Currency, address));
uint256 balance = currency.balanceOfSelf();
if (balance > 0) currency.transfer(to, balance);
}

// implementation of abstract function DeltaResolver._pay
function _pay(Currency token, address payer, uint256 amount) internal override {
// TODO: Should we also support direct transfer?
permit2.transferFrom(payer, address(poolManager), uint160(amount), Currency.unwrap(token));
function _pay(Currency currency, address payer, uint256 amount) internal override {
if (payer == address(this)) {
// TODO: currency is guaranteed to not be eth so the native check in transfer is not optimal.
currency.transfer(address(poolManager), amount);
} else {
permit2.transferFrom(payer, address(poolManager), uint160(amount), Currency.unwrap(currency));
}
}

function _getFullSettleAmount(Currency currency) private view returns (uint256 amount) {
hensha256 marked this conversation as resolved.
Show resolved Hide resolved
int256 _amount = poolManager.currencyDelta(address(this), currency);
// If the amount is positive, it should be taken not settled for.
if (_amount > 0) revert IncorrectUseOfSettle();
amount = uint256(-_amount);
}

function _getFullTakeAmount(Currency currency) private view returns (uint256 amount) {
int256 _amount = poolManager.currencyDelta(address(this), currency);
// If the amount is negative, it should be settled not taken.
if (_amount < 0) revert IncorrectUseOfTake();
amount = uint256(_amount);
}
}
Loading
Loading