Skip to content

Commit

Permalink
Add wrap, unwrap functionality (#369)
Browse files Browse the repository at this point in the history
* add wrap unwrap

* checkpoint

* fix addresses

* remove test

* eth-weth update

* comments

* add decode fuzz test

* fmt

* wrap tests

* unwrap tests

* use amount

* final comments

* update comment

* test naming update

---------

Co-authored-by: dianakocsis <[email protected]>
Co-authored-by: marktoda <[email protected]>
  • Loading branch information
3 people authored Oct 24, 2024
1 parent 1b50ca2 commit 01ddf3a
Show file tree
Hide file tree
Showing 48 changed files with 553 additions and 44 deletions.
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
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;
}
if (amount == ActionConstants.OPEN_DELTA) {
// 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 {
/// @notice The address for WETH9
IWETH9 public immutable WETH9;

/// @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;
}
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) {
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

0 comments on commit 01ddf3a

Please sign in to comment.