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 wrap, unwrap functionality #369

Merged
merged 17 commits into from
Oct 24, 2024
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_burn_empty.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
50446
50481
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 @@
50446
50481
Original file line number Diff line number Diff line change
@@ -1 +1 @@
125624
125659
Original file line number Diff line number Diff line change
@@ -1 +1 @@
125106
125141
Original file line number Diff line number Diff line change
@@ -1 +1 @@
132486
132521
Original file line number Diff line number Diff line change
@@ -1 +1 @@
131968
132004
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
146344
146388
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_sameRange.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
154922
154966
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_withClose.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
154922
154966
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_withTakePair.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
154287
154331
Original file line number Diff line number Diff line change
@@ -1 +1 @@
112020
112056
Original file line number Diff line number Diff line change
@@ -1 +1 @@
119803
119847
Original file line number Diff line number Diff line change
@@ -1 +1 @@
119168
119212
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_decrease_burnEmpty.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
135283
135318
Original file line number Diff line number Diff line change
@@ -1 +1 @@
128420
128456
Original file line number Diff line number Diff line change
@@ -1 +1 @@
132490
132534
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_decrease_take_take.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
120423
120467
Original file line number Diff line number Diff line change
@@ -1 +1 @@
159083
159127
Original file line number Diff line number Diff line change
@@ -1 +1 @@
158035
158079
Original file line number Diff line number Diff line change
@@ -1 +1 @@
140898
140942
Original file line number Diff line number Diff line change
@@ -1 +1 @@
136359
136403
Original file line number Diff line number Diff line change
@@ -1 +1 @@
177414
177458
Original file line number Diff line number Diff line change
@@ -1 +1 @@
148040
148084
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
364771
364815
Original file line number Diff line number Diff line change
@@ -1 +1 @@
373294
373334
Original file line number Diff line number Diff line change
@@ -1 +1 @@
372529
372569
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_onSameTickLower.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
317643
317687
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_onSameTickUpper.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
318313
318357
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_sameRange.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
243882
243926
Original file line number Diff line number Diff line change
@@ -1 +1 @@
419098
419134
Original file line number Diff line number Diff line change
@@ -1 +1 @@
323674
323718
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_withClose.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
420196
420240
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_withSettlePair.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
419266
419310
Original file line number Diff line number Diff line change
@@ -1 +1 @@
456001
456088
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_permit_twice.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
44852
44876
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_unsubscribe.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
59238
59260
4 changes: 3 additions & 1 deletion script/DeployPosm.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {PositionManager} from "../src/PositionManager.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {IPositionDescriptor} from "../src/interfaces/IPositionDescriptor.sol";
import {PositionDescriptor} from "../src/PositionDescriptor.sol";
import {IWETH9} from "../src/interfaces/external/IWETH9.sol";

contract DeployPosmTest is Script {
function setUp() public {}
Expand All @@ -30,7 +31,8 @@ contract DeployPosmTest is Script {
IPoolManager(poolManager),
IAllowanceTransfer(permit2),
unsubscribeGasLimit,
IPositionDescriptor(address(positionDescriptor))
IPositionDescriptor(address(positionDescriptor)),
IWETH9(wrappedNative)
);
console2.log("PositionManager", address(posm));

Expand Down
19 changes: 16 additions & 3 deletions src/PositionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.26;
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {Position} from "@uniswap/v4-core/src/libraries/Position.sol";
Expand All @@ -26,6 +26,8 @@ import {CalldataDecoder} from "./libraries/CalldataDecoder.sol";
import {Permit2Forwarder} from "./base/Permit2Forwarder.sol";
import {SlippageCheck} from "./libraries/SlippageCheck.sol";
import {PositionInfo, PositionInfoLibrary} from "./libraries/PositionInfoLibrary.sol";
import {NativeWrapper} from "./base/NativeWrapper.sol";
import {IWETH9} from "./interfaces/external/IWETH9.sol";

// 444444444
// 444444444444 444444
Expand Down Expand Up @@ -102,7 +104,8 @@ contract PositionManager is
ReentrancyLock,
BaseActionsRouter,
Notifier,
Permit2Forwarder
Permit2Forwarder,
NativeWrapper
{
using PoolIdLibrary for PoolKey;
using StateLibrary for IPoolManager;
Expand All @@ -126,12 +129,14 @@ contract PositionManager is
IPoolManager _poolManager,
IAllowanceTransfer _permit2,
uint256 _unsubscribeGasLimit,
IPositionDescriptor _tokenDescriptor
IPositionDescriptor _tokenDescriptor,
IWETH9 _weth9
)
BaseActionsRouter(_poolManager)
Permit2Forwarder(_permit2)
ERC721Permit_v4("Uniswap v4 Positions NFT", "UNI-V4-POSM")
Notifier(_unsubscribeGasLimit)
NativeWrapper(_weth9)
{
tokenDescriptor = _tokenDescriptor;
}
Expand Down Expand Up @@ -242,6 +247,14 @@ contract PositionManager is
(Currency currency, address to) = params.decodeCurrencyAndAddress();
_sweep(currency, _mapRecipient(to));
return;
} else if (action == Actions.WRAP) {
uint256 amount = params.decodeUint256();
_wrap(_mapWrapUnwrapAmount(CurrencyLibrary.ADDRESS_ZERO, amount, Currency.wrap(address(WETH9))));
return;
} else if (action == Actions.UNWRAP) {
uint256 amount = params.decodeUint256();
_unwrap(_mapWrapUnwrapAmount(Currency.wrap(address(WETH9)), amount, CurrencyLibrary.ADDRESS_ZERO));
return;
}
}
revert UnsupportedAction(action);
Expand Down
28 changes: 28 additions & 0 deletions src/base/DeltaResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ abstract contract DeltaResolver is ImmutableState {
error DeltaNotPositive(Currency currency);
/// @notice Emitted trying to take a negative delta.
error DeltaNotNegative(Currency currency);
/// @notice Emitted when the contract does not have enough balance to wrap or unwrap.
error InsufficientBalance();

/// @notice Take an amount of currency out of the PoolManager
/// @param currency Currency to take
Expand Down Expand Up @@ -91,4 +93,30 @@ abstract contract DeltaResolver is ImmutableState {
return amount;
}
}

/// @notice Calculates the sanitized amount before wrapping/unwrapping.
/// @param inputCurrency The currency, either native or wrapped native, that this contract holds
/// @param amount The amount to wrap or unwrap. Can be CONTRACT_BALANCE, OPEN_DELTA or a specific amount
/// @param outputCurrency The currency after the wrap/unwrap that the user may owe a balance in on the poolManager
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// @param outputCurrency The currency after the wrap/unwrap that the user may owe a balance in on the poolManager
/// @param outputCurrency The currency after the wrap/unwrap

Copy link
Contributor

Choose a reason for hiding this comment

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

They also could be wrapping/unwrapping before sweeping to themselves etc. I feel like theres a few valid use-cases so shouldnt single one out

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah but outputCurrency is only used to get the OPEN_DELTA balance on the contract

Copy link
Member Author

Choose a reason for hiding this comment

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

i didn't change this but lmk i can change if you still think so after reading this comment above^

Copy link
Contributor

Choose a reason for hiding this comment

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

i see, maybe it could be clearer to say something that describes that better like

The currency after the wrap/unwrap, used when amount is OPEN_DELTA to look up the balance owed to the poolManager

or something like that?

function _mapWrapUnwrapAmount(Currency inputCurrency, uint256 amount, Currency outputCurrency)
internal
view
returns (uint256)
{
// if wrapping, the balance in this contract is in ETH
// if unwrapping, the balance in this contract is in WETH
uint256 balance = inputCurrency.balanceOf(address(this));
if (amount == ActionConstants.CONTRACT_BALANCE) {
// return early to avoid unnecessary balance check
return balance;
snreynolds marked this conversation as resolved.
Show resolved Hide resolved
}
if (amount == ActionConstants.OPEN_DELTA) {
snreynolds marked this conversation as resolved.
Show resolved Hide resolved
// if wrapping, the open currency on the PoolManager is WETH.
// if unwrapping, the open currency on the PoolManager is ETH.
// note that we use the DEBT amount. Positive deltas can be taken and then wrapped.
amount = _getFullDebt(outputCurrency);
}
if (amount > balance) revert InsufficientBalance();
return amount;
}
}
34 changes: 34 additions & 0 deletions src/base/NativeWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {IWETH9} from "../interfaces/external/IWETH9.sol";
import {ActionConstants} from "../libraries/ActionConstants.sol";
import {ImmutableState} from "./ImmutableState.sol";

/// @title Native Wrapper
/// @notice Used for wrapping and unwrapping native
abstract contract NativeWrapper is ImmutableState {
hensha256 marked this conversation as resolved.
Show resolved Hide resolved
/// @notice The address for WETH9
IWETH9 public immutable WETH9;
snreynolds marked this conversation as resolved.
Show resolved Hide resolved

/// @notice Thrown when an unexpected address sends ETH to this contract
error InvalidEthSender();

constructor(IWETH9 _weth9) {
WETH9 = _weth9;
}

/// @dev The amount should already be <= the current balance in this contract.
function _wrap(uint256 amount) internal {
if (amount > 0) WETH9.deposit{value: amount}();
}

/// @dev The amount should already be <= the current balance in this contract.
function _unwrap(uint256 amount) internal {
if (amount > 0) WETH9.withdraw(amount);
}

receive() external payable {
if (msg.sender != address(WETH9) && msg.sender != address(poolManager)) revert InvalidEthSender();
}
}
13 changes: 13 additions & 0 deletions src/interfaces/external/IWETH9.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title Interface for WETH9
interface IWETH9 is IERC20 {
/// @notice Deposit ether to get wrapped ether
function deposit() external payable;

/// @notice Withdraw wrapped ether to get ether
function withdraw(uint256) external;
}
6 changes: 4 additions & 2 deletions src/libraries/Actions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ library Actions {
uint256 constant CLOSE_CURRENCY = 0x17;
uint256 constant CLEAR_OR_TAKE = 0x18;
uint256 constant SWEEP = 0x19;
uint256 constant WRAP = 0x20;
uint256 constant UNWRAP = 0x21;

// minting/burning 6909s to close deltas
uint256 constant MINT_6909 = 0x20;
uint256 constant BURN_6909 = 0x21;
uint256 constant MINT_6909 = 0x22;
uint256 constant BURN_6909 = 0x23;
snreynolds marked this conversation as resolved.
Show resolved Hide resolved
}
7 changes: 7 additions & 0 deletions src/libraries/CalldataDecoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,13 @@ library CalldataDecoder {
}
}

/// @dev equivalent to: abi.decode(params, (uint256)) in calldata
function decodeUint256(bytes calldata params) internal pure returns (uint256 amount) {
snreynolds marked this conversation as resolved.
Show resolved Hide resolved
assembly ("memory-safe") {
amount := calldataload(params.offset)
}
}

/// @dev equivalent to: abi.decode(params, (Currency, uint256, bool)) in calldata
function decodeCurrencyUint256AndBool(bytes calldata params)
internal
Expand Down
2 changes: 1 addition & 1 deletion test/PositionDescriptor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ contract PositionDescriptorTest is Test, PosmTestSetup, GasSnapshot {
assertEq(token.name, "Uniswap - 0.3% - TEST/TEST - 1.0060<>1.0121");
assertEq(
token.description,
unicode"This NFT represents a liquidity position in a Uniswap v4 TEST-TEST pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: 0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f\nTEST Address: 0x5991a2df15a8f6a256d3ec51e99254cd3fb576a9\nTEST Address: 0x2e234dae75c793f67a35089c9d99245e1c58470b\nHook Address: No Hook\nFee Tier: 0.3%\nToken ID: 1\n\n⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated."
unicode"This NFT represents a liquidity position in a Uniswap v4 TEST-TEST pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: 0x2e234dae75c793f67a35089c9d99245e1c58470b\nTEST Address: 0xf62849f9a0b5bf2913b396098f7c7019b51a820a\nTEST Address: 0xc7183455a4c133ae270771860664b6b7ec320bb1\nHook Address: No Hook\nFee Tier: 0.3%\nToken ID: 1\n\n⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated."
);
}

Expand Down
7 changes: 7 additions & 0 deletions test/libraries/CalldataDecoder.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,13 @@ contract CalldataDecoderTest is Test {
assertEq(amount, _amount);
}

function test_fuzz_decodeUint256(uint256 _amount) public {
bytes memory params = abi.encode(_amount);
uint256 amount = decoder.decodeUint256(params);

assertEq(amount, _amount);
}

function _assertEq(PathKey[] memory path1, PathKey[] memory path2) internal pure {
assertEq(path1.length, path2.length);
for (uint256 i = 0; i < path1.length; i++) {
Expand Down
Loading
Loading