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 @@
125584
125619
Original file line number Diff line number Diff line change
@@ -1 +1 @@
125031
125066
Original file line number Diff line number Diff line change
@@ -1 +1 @@
132446
132481
Original file line number Diff line number Diff line change
@@ -1 +1 @@
131893
131928
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
146294
146338
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_sameRange.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
154872
154916
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_withClose.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
154872
154916
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_withTakePair.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
154193
154237
Original file line number Diff line number Diff line change
@@ -1 +1 @@
111980
112016
Original file line number Diff line number Diff line change
@@ -1 +1 @@
119753
119797
Original file line number Diff line number Diff line change
@@ -1 +1 @@
119074
119118
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_decrease_burnEmpty.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
135243
135278
Original file line number Diff line number Diff line change
@@ -1 +1 @@
128380
128416
Original file line number Diff line number Diff line change
@@ -1 +1 @@
132440
132484
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 @@
120329
120373
Original file line number Diff line number Diff line change
@@ -1 +1 @@
159057
159101
Original file line number Diff line number Diff line change
@@ -1 +1 @@
157997
158041
Original file line number Diff line number Diff line change
@@ -1 +1 @@
140872
140916
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 @@
177364
177408
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 @@
364745
364789
Original file line number Diff line number Diff line change
@@ -1 +1 @@
373268
373308
Original file line number Diff line number Diff line change
@@ -1 +1 @@
372491
372531
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_onSameTickLower.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
317617
317661
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_onSameTickUpper.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
318287
318331
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_sameRange.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
243856
243900
Original file line number Diff line number Diff line change
@@ -1 +1 @@
419060
419096
Original file line number Diff line number Diff line change
@@ -1 +1 @@
323648
323692
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_withClose.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
420170
420214
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_withSettlePair.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
419228
419272
Original file line number Diff line number Diff line change
@@ -1 +1 @@
455975
456062
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 @@ -87,4 +89,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: 0x0000000000000000000000000000000000000000\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: 0x0000000000000000000000000000000000000000\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