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 5 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 @@
50222
49205
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 @@
50040
49023
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_burn_nonEmpty.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
135866
134184
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 @@
128788
127105
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
158157
156278
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
149309
147430
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_sameRange.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
158157
156278
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_decreaseLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
123700
121821
Original file line number Diff line number Diff line change
@@ -1 +1 @@
114922
113419
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_decrease_burnEmpty.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
142193
140280
Original file line number Diff line number Diff line change
@@ -1 +1 @@
134932
133019
Original file line number Diff line number Diff line change
@@ -1 +1 @@
136416
134537
Original file line number Diff line number Diff line change
@@ -1 +1 @@
150444
148697
Original file line number Diff line number Diff line change
@@ -1 +1 @@
135654
133879
Original file line number Diff line number Diff line change
@@ -1 +1 @@
142305
140501
Original file line number Diff line number Diff line change
@@ -1 +1 @@
178459
176655
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
370660
368776
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
338770
336858
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_nativeWithSweep.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
345708
343796
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_onSameTickLower.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
313342
311458
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_onSameTickUpper.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
313984
312100
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_sameRange.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
239566
237682
Original file line number Diff line number Diff line change
@@ -1 +1 @@
319360
317476
Original file line number Diff line number Diff line change
@@ -1 +1 @@
415150
413266
120 changes: 79 additions & 41 deletions src/PositionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import {ERC20} from "solmate/src/tokens/ERC20.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 {BaseActionsRouterReturns} from "./base/BaseActionsRouterReturns.sol";
import {Actions} from "./libraries/Actions.sol";

contract PositionManager is
IPositionManager,
Expand All @@ -28,7 +30,8 @@ contract PositionManager is
Multicall,
SafeCallback,
DeltaResolver,
ReentrancyLock
ReentrancyLock,
BaseActionsRouterReturns
{
using SafeTransferLib for *;
using CurrencyLibrary for Currency;
Expand All @@ -44,8 +47,10 @@ contract PositionManager is
/// @inheritdoc IPositionManager
mapping(uint256 tokenId => bytes32 configId) public positionConfigs;

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)
SafeCallback(_poolManager)
BaseActionsRouterReturns(_poolManager)
ERC721Permit("Uniswap V4 Positions NFT", "UNI-V4-POSM", "1")
{}

Expand All @@ -63,41 +68,34 @@ contract PositionManager is
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();
}
// For now POSM will bubble up sub call return values.
return _executeActions(unlockData);
}

function _handleAction(uint256 action, bytes calldata params) internal override returns (bytes memory) {
if (action == Actions.INCREASE_LIQUIDITY) {
return _increase(params);
} else if (action == Actions.DECREASE_LIQUIDITY) {
return _decrease(params);
} else if (action == Actions.MINT_POSITION) {
return _mint(params);
} else if (action == Actions.CLOSE_CURRENCY) {
return _close(params);
} else if (action == Actions.BURN_POSITION) {
return _burn(params);
} else if (action == Actions.SETTLE_WITH_BALANCE) {
return _settleWithBalance(params);
} else if (action == Actions.SWEEP_ERC20_TO) {
_sweepERC20To(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
Expand All @@ -118,7 +116,7 @@ contract PositionManager is
(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.
Expand Down Expand Up @@ -157,7 +155,7 @@ contract PositionManager is
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));

Expand All @@ -170,13 +168,27 @@ contract PositionManager is
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
/// @dev Should not be called for NATIVE settling bc does not sweep.
function _settleWithBalance(bytes memory params) internal returns (bytes memory) {
(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);
return abi.encode(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) {
(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));

Expand Down Expand Up @@ -225,9 +237,35 @@ contract PositionManager is
if (nativeBalance > 0) recipient.safeTransferETH(nativeBalance);
}

/// @param params an encoding of Currency, address
function _sweepERC20To(bytes calldata params) internal {
snreynolds marked this conversation as resolved.
Show resolved Hide resolved
(Currency currency, address to) = abi.decode(params, (Currency, address));
uint256 tokenBalance = ERC20(Currency.unwrap(currency)).balanceOf(address(this));
if (tokenBalance > 0) currency.transfer(to, tokenBalance);
}

// implementation of abstract function DeltaResolver._pay
function _pay(Currency token, address payer, uint256 amount) internal override {
// TODO this should use Permit2
ERC20(Currency.unwrap(token)).safeTransferFrom(payer, address(poolManager), amount);
function _pay(Currency currency, address payer, uint256 amount) internal override {
if (payer == address(this)) {
// TODO: This transfer no eth check. This is guaranteed to not be eth.
hensha256 marked this conversation as resolved.
Show resolved Hide resolved
currency.transfer(address(poolManager), amount);
} else {
// TODO this should use Permit2
ERC20(Currency.unwrap(currency)).safeTransferFrom(payer, address(poolManager), amount);
}
}

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);
}
}
4 changes: 2 additions & 2 deletions src/base/BaseActionsRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ abstract contract BaseActionsRouter is SafeCallback {

/// @notice internal function that triggers the execution of a set of actions on v4
/// @dev inheriting contracts should call this function to trigger execution
function _executeActions(bytes calldata params) internal {
poolManager.unlock(params);
function _executeActions(bytes calldata unlockData) internal {
poolManager.unlock(unlockData);
}

/// @notice function that is called by the PoolManager through the SafeCallback.unlockCallback
Expand Down
55 changes: 55 additions & 0 deletions src/base/BaseActionsRouterReturns.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;

import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {SafeCallback} from "./SafeCallback.sol";
import {CalldataDecoder} from "../libraries/CalldataDecoder.sol";

/// @notice Abstract contract for performing a combination of actions on Uniswap v4.
/// @dev Suggested uint256 action values are defined in Actions.sol, however any definition can be used
/// @dev This contract handles return values from sub calls to _handleAction.
abstract contract BaseActionsRouterReturns is SafeCallback {
using CalldataDecoder for bytes;

/// @notice emitted when different numbers of parameters and actions are provided
error LengthMismatch();

/// @notice emitted when an inheriting contract does not support an action
error UnsupportedAction(uint256 action);

constructor(IPoolManager _poolManager) SafeCallback(_poolManager) {}

/// @notice internal function that triggers the execution of a set of actions on v4
/// @dev inheriting contracts should call this function to trigger execution
function _executeActions(bytes calldata unlockData) internal returns (bytes[] memory) {
return abi.decode(poolManager.unlock(unlockData), (bytes[]));
}

/// @notice function that is called by the PoolManager through the SafeCallback.unlockCallback
function _unlockCallback(bytes calldata data) internal override returns (bytes memory) {
// abi.decode(data, (uint256[], bytes[]));
(uint256[] calldata actions, bytes[] calldata params) = data.decodeActionsRouterParams();

uint256 numActions = actions.length;
if (numActions != params.length) revert LengthMismatch();

bytes[] memory results = new bytes[](numActions);
for (uint256 actionIndex = 0; actionIndex < numActions; actionIndex++) {
uint256 action = actions[actionIndex];

results[actionIndex] = _handleAction(action, params[actionIndex]);
}

return abi.encode(results);
}

/// @notice function to handle the parsing and execution of an action and its parameters
function _handleAction(uint256 action, bytes calldata params) internal virtual returns (bytes memory);

/// @notice function that returns address considered executer of the actions
/// @dev The other context functions, _msgData and _msgValue, are not supported by this contract
/// In many contracts this will be the address that calls the initial entry point that calls `_executeActions`
/// `msg.sender` shouldnt be used, as this will be the v4 pool manager contract that calls `unlockCallback`
/// If using ReentrancyLock.sol, this function can return Locker.get() - locker of the contract
function _msgSender() internal view virtual returns (address);
}
Loading
Loading