diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index 492cae309..9301ad1e7 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47167 \ No newline at end of file +47320 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index 6ad16fc4b..b05cde238 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -46984 \ No newline at end of file +47137 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap index 4cbd25401..2e97d16f1 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap @@ -1 +1 @@ -123815 \ No newline at end of file +123968 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap index 42940dd01..5cac1d241 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -1 +1 @@ -123322 \ No newline at end of file +123465 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap index de0d9eda3..f61e3d033 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap @@ -1 +1 @@ -130893 \ No newline at end of file +131046 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap index 5ffb63a66..9ad20e49d 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -1 +1 @@ -130400 \ No newline at end of file +130544 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withTakePair.snap b/.forge-snapshots/PositionManager_collect_withTakePair.snap index 8fb961f41..e1d82d967 100644 --- a/.forge-snapshots/PositionManager_collect_withTakePair.snap +++ b/.forge-snapshots/PositionManager_collect_withTakePair.snap @@ -1 +1 @@ -150653 \ No newline at end of file +150641 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap index 75b62f53c..8ec8cc23c 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap @@ -1 +1 @@ -116196 \ No newline at end of file +116184 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index 053a81869..a992e8cf3 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134968 \ No newline at end of file +135121 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index aec2b78e8..12d922cf7 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -127708 \ No newline at end of file +127860 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index 35d11906e..0deae34bb 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -341353 \ No newline at end of file +341053 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index b50b4e031..3c36d95dd 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -349845 \ No newline at end of file +349545 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index 989fe3283..8ff987d61 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -349147 \ No newline at end of file +348847 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 86727b2b3..e4b71faf1 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -319335 \ No newline at end of file +319035 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index efd69c9f9..604fa0a7a 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -319977 \ No newline at end of file +319677 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index 57a5ab280..3d62eb22d 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -245559 \ No newline at end of file +245259 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 85d82e1f7..a1935f6a3 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -375377 \ No newline at end of file +375077 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index 79911f01a..81609fe56 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -325353 \ No newline at end of file +325053 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index 487cdc461..201bbccf9 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -376653 \ No newline at end of file +376353 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index be6c44420..6bacbc55c 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -375793 \ No newline at end of file +375493 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index ef991ef7d..7d7ac2ca0 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -421080 \ No newline at end of file +420802 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit.snap b/.forge-snapshots/PositionManager_permit.snap index e62c334b6..b852adad4 100644 --- a/.forge-snapshots/PositionManager_permit.snap +++ b/.forge-snapshots/PositionManager_permit.snap @@ -1 +1 @@ -79506 \ No newline at end of file +79492 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_secondPosition.snap b/.forge-snapshots/PositionManager_permit_secondPosition.snap index a0c684a2e..dbca4d250 100644 --- a/.forge-snapshots/PositionManager_permit_secondPosition.snap +++ b/.forge-snapshots/PositionManager_permit_secondPosition.snap @@ -1 +1 @@ -62394 \ No newline at end of file +62380 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_twice.snap b/.forge-snapshots/PositionManager_permit_twice.snap index 2439b1797..8bef075e2 100644 --- a/.forge-snapshots/PositionManager_permit_twice.snap +++ b/.forge-snapshots/PositionManager_permit_twice.snap @@ -1 +1 @@ -45282 \ No newline at end of file +45268 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index 026a3b223..f2faf802d 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -8591 \ No newline at end of file +8596 \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fb5820e5f..73de4cdbe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,7 @@ jobs: version: nightly - name: Run tests - run: forge test -vvv + run: forge test --isolate -vvv env: FOUNDRY_PROFILE: ci + FORGE_SNAPSHOT_CHECK: true diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 53e079a09..5aad7f8bc 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; +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"; @@ -192,6 +192,7 @@ contract PositionManager is bytes calldata hookData ) = params.decodeModifyLiquidityParams(); _increase(tokenId, config, liquidity, amount0Max, amount1Max, hookData); + return; } else if (action == Actions.DECREASE_LIQUIDITY) { ( uint256 tokenId, @@ -202,6 +203,7 @@ contract PositionManager is bytes calldata hookData ) = params.decodeModifyLiquidityParams(); _decrease(tokenId, config, liquidity, amount0Min, amount1Min, hookData); + return; } else if (action == Actions.MINT_POSITION) { ( PositionConfig calldata config, @@ -212,6 +214,7 @@ contract PositionManager is bytes calldata hookData ) = params.decodeMintParams(); _mint(config, liquidity, amount0Max, amount1Max, _mapRecipient(owner), hookData); + return; } else if (action == Actions.BURN_POSITION) { // Will automatically decrease liquidity to 0 if the position is not already empty. ( @@ -222,35 +225,40 @@ contract PositionManager is bytes calldata hookData ) = params.decodeBurnParams(); _burn(tokenId, config, amount0Min, amount1Min, hookData); - } else { - revert UnsupportedAction(action); + return; } } else { if (action == Actions.SETTLE_PAIR) { (Currency currency0, Currency currency1) = params.decodeCurrencyPair(); _settlePair(currency0, currency1); + return; } else if (action == Actions.TAKE_PAIR) { - (Currency currency0, Currency currency1, address to) = params.decodeCurrencyPairAndAddress(); - _takePair(currency0, currency1, to); + (Currency currency0, Currency currency1, address recipient) = params.decodeCurrencyPairAndAddress(); + _takePair(currency0, currency1, _mapRecipient(recipient)); + return; } else if (action == Actions.SETTLE) { (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); + return; } else if (action == Actions.TAKE) { (Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256(); _take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency)); + return; } else if (action == Actions.CLOSE_CURRENCY) { Currency currency = params.decodeCurrency(); _close(currency); + return; } else if (action == Actions.CLEAR_OR_TAKE) { (Currency currency, uint256 amountMax) = params.decodeCurrencyAndUint256(); _clearOrTake(currency, amountMax); + return; } else if (action == Actions.SWEEP) { (Currency currency, address to) = params.decodeCurrencyAndAddress(); _sweep(currency, _mapRecipient(to)); - } else { - revert UnsupportedAction(action); + return; } } + revert UnsupportedAction(action); } /// @dev Calling increase with 0 liquidity will credit the caller with any underlying fees of the position @@ -301,10 +309,9 @@ contract PositionManager is } _mint(owner, tokenId); - (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = - _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); - // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued - (liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max); + // fee delta can be ignored as this is a new position + (BalanceDelta liquidityDelta,) = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); + liquidityDelta.validateMaxIn(amount0Max, amount1Max); positionConfigs[tokenId].setConfigId(config.toId()); emit MintPosition(tokenId, config); @@ -318,7 +325,7 @@ contract PositionManager is uint128 amount1Min, bytes calldata hookData ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { - uint256 liquidity = uint256(getPositionLiquidity(tokenId, config)); + uint256 liquidity = getPositionLiquidity(tokenId, config); // Can only call modify if there is non zero liquidity. if (liquidity > 0) { @@ -328,6 +335,8 @@ contract PositionManager is (liquidityDelta - feesAccrued).validateMinOut(amount0Min, amount1Min); } + if (positionConfigs[tokenId].hasSubscriber()) _unsubscribe(tokenId, config); + delete positionConfigs[tokenId]; // Burn the token. _burn(tokenId); @@ -340,8 +349,7 @@ contract PositionManager is _settle(currency1, caller, _getFullDebt(currency1)); } - function _takePair(Currency currency0, Currency currency1, address to) internal { - address recipient = _mapRecipient(to); + function _takePair(Currency currency0, Currency currency1, address recipient) internal { _take(currency0, recipient, _getFullCredit(currency0)); _take(currency1, recipient, _getFullCredit(currency1)); } diff --git a/src/V4Router.sol b/src/V4Router.sol index e6b447068..c14ad76a7 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity 0.8.26; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; @@ -37,46 +37,53 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { if (action == Actions.SWAP_EXACT_IN) { IV4Router.ExactInputParams calldata swapParams = params.decodeSwapExactInParams(); _swapExactInput(swapParams); + return; } else if (action == Actions.SWAP_EXACT_IN_SINGLE) { IV4Router.ExactInputSingleParams calldata swapParams = params.decodeSwapExactInSingleParams(); _swapExactInputSingle(swapParams); + return; } else if (action == Actions.SWAP_EXACT_OUT) { IV4Router.ExactOutputParams calldata swapParams = params.decodeSwapExactOutParams(); _swapExactOutput(swapParams); + return; } else if (action == Actions.SWAP_EXACT_OUT_SINGLE) { IV4Router.ExactOutputSingleParams calldata swapParams = params.decodeSwapExactOutSingleParams(); _swapExactOutputSingle(swapParams); - } else { - revert UnsupportedAction(action); + return; } } else { if (action == Actions.SETTLE_TAKE_PAIR) { (Currency settleCurrency, Currency takeCurrency) = params.decodeCurrencyPair(); _settle(settleCurrency, msgSender(), _getFullDebt(settleCurrency)); _take(takeCurrency, msgSender(), _getFullCredit(takeCurrency)); + return; } else if (action == Actions.SETTLE_ALL) { (Currency currency, uint256 maxAmount) = params.decodeCurrencyAndUint256(); uint256 amount = _getFullDebt(currency); if (amount > maxAmount) revert V4TooMuchRequested(maxAmount, amount); _settle(currency, msgSender(), amount); + return; } else if (action == Actions.TAKE_ALL) { (Currency currency, uint256 minAmount) = params.decodeCurrencyAndUint256(); uint256 amount = _getFullCredit(currency); if (amount < minAmount) revert V4TooLittleReceived(minAmount, amount); _take(currency, msgSender(), amount); + return; } else if (action == Actions.SETTLE) { (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); + return; } else if (action == Actions.TAKE) { (Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256(); _take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency)); + return; } else if (action == Actions.TAKE_PORTION) { (Currency currency, address recipient, uint256 bips) = params.decodeCurrencyAddressAndUint256(); _take(currency, _mapRecipient(recipient), _getFullCredit(currency).calculatePortion(bips)); - } else { - revert UnsupportedAction(action); + return; } } + revert UnsupportedAction(action); } function _swapExactInputSingle(IV4Router.ExactInputSingleParams calldata params) private { diff --git a/src/base/BaseActionsRouter.sol b/src/base/BaseActionsRouter.sol index 22abab016..56e311906 100644 --- a/src/base/BaseActionsRouter.sol +++ b/src/base/BaseActionsRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {SafeCallback} from "./SafeCallback.sol"; @@ -40,7 +40,7 @@ abstract contract BaseActionsRouter is SafeCallback { if (numActions != params.length) revert InputLengthMismatch(); for (uint256 actionIndex = 0; actionIndex < numActions; actionIndex++) { - uint256 action = uint256(uint8(actions[actionIndex])); + uint256 action = uint8(actions[actionIndex]); _handleAction(action, params[actionIndex]); } diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index 801323c9b..a31bf1392 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -73,15 +73,17 @@ abstract contract DeltaResolver is ImmutableState { return currency.balanceOfSelf(); } else if (amount == ActionConstants.OPEN_DELTA) { return _getFullDebt(currency); + } else { + return amount; } - return amount; } /// @notice Calculates the amount for a take action function _mapTakeAmount(uint256 amount, Currency currency) internal view returns (uint256) { if (amount == ActionConstants.OPEN_DELTA) { return _getFullCredit(currency); + } else { + return amount; } - return amount; } } diff --git a/src/base/EIP712_v4.sol b/src/base/EIP712_v4.sol index 3ebbd1ca4..e66261aa5 100644 --- a/src/base/EIP712_v4.sol +++ b/src/base/EIP712_v4.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; import {IEIP712_v4} from "../interfaces/IEIP712_v4.sol"; @@ -26,7 +26,7 @@ contract EIP712_v4 is IEIP712_v4 { } /// @inheritdoc IEIP712_v4 - function DOMAIN_SEPARATOR() public view override returns (bytes32) { + function DOMAIN_SEPARATOR() public view returns (bytes32) { // uses cached version if chainid is unchanged from construction return block.chainid == _CACHED_CHAIN_ID ? _CACHED_DOMAIN_SEPARATOR : _buildDomainSeparator(); } @@ -46,6 +46,11 @@ contract EIP712_v4 is IEIP712_v4 { mstore(add(fmp, 0x02), domainSeparator) mstore(add(fmp, 0x22), dataHash) digest := keccak256(fmp, 0x42) + + // now clean the memory we used + mstore(fmp, 0) // fmp held "\x19\x01", domainSeparator + mstore(add(fmp, 0x20), 0) // fmp+0x20 held domainSeparator, dataHash + mstore(add(fmp, 0x40), 0) // fmp+0x40 held dataHash } } } diff --git a/src/base/ERC721Permit_v4.sol b/src/base/ERC721Permit_v4.sol index a9cf58f27..aab76f7c2 100644 --- a/src/base/ERC721Permit_v4.sol +++ b/src/base/ERC721Permit_v4.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; import {ERC721} from "solmate/src/tokens/ERC721.sol"; import {EIP712_v4} from "./EIP712_v4.sol"; @@ -28,8 +28,8 @@ abstract contract ERC721Permit_v4 is ERC721, IERC721Permit_v4, EIP712_v4, Unorde payable checkSignatureDeadline(deadline) { - address owner = ownerOf(tokenId); - _checkNoSelfPermit(owner, spender); + // the .verify function checks the owner is non-0 + address owner = _ownerOf[tokenId]; bytes32 digest = ERC721PermitHashLibrary.hashPermit(spender, tokenId, nonce, deadline); signature.verify(_hashTypedData(digest), owner); @@ -47,8 +47,6 @@ abstract contract ERC721Permit_v4 is ERC721, IERC721Permit_v4, EIP712_v4, Unorde uint256 nonce, bytes calldata signature ) external payable checkSignatureDeadline(deadline) { - _checkNoSelfPermit(owner, operator); - bytes32 digest = ERC721PermitHashLibrary.hashPermitForAll(operator, approved, nonce, deadline); signature.verify(_hashTypedData(digest), owner); @@ -97,10 +95,6 @@ abstract contract ERC721Permit_v4 is ERC721, IERC721Permit_v4, EIP712_v4, Unorde || isApprovedForAll[ownerOf(tokenId)][spender]; } - function _checkNoSelfPermit(address owner, address permitted) internal pure { - if (owner == permitted) revert NoSelfPermit(); - } - // TODO: to be implemented after audits function tokenURI(uint256) public pure override returns (string memory) { return "https://example.com"; diff --git a/src/base/ImmutableState.sol b/src/base/ImmutableState.sol index dab1563cc..70992aa1d 100644 --- a/src/base/ImmutableState.sol +++ b/src/base/ImmutableState.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.19; +pragma solidity ^0.8.0; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; diff --git a/src/base/Multicall_v4.sol b/src/base/Multicall_v4.sol index e648cca8d..e632270af 100644 --- a/src/base/Multicall_v4.sol +++ b/src/base/Multicall_v4.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.19; +pragma solidity ^0.8.0; import {IMulticall_v4} from "../interfaces/IMulticall_v4.sol"; @@ -7,7 +7,7 @@ import {IMulticall_v4} from "../interfaces/IMulticall_v4.sol"; /// @notice Enables calling multiple methods in a single call to the contract abstract contract Multicall_v4 is IMulticall_v4 { /// @inheritdoc IMulticall_v4 - function multicall(bytes[] calldata data) external payable override returns (bytes[] memory results) { + function multicall(bytes[] calldata data) external payable returns (bytes[] memory results) { results = new bytes[](data.length); for (uint256 i = 0; i < data.length; i++) { (bool success, bytes memory result) = address(this).delegatecall(data[i]); diff --git a/src/base/Notifier.sol b/src/base/Notifier.sol index 146824a0b..2992b6a8b 100644 --- a/src/base/Notifier.sol +++ b/src/base/Notifier.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; import {ISubscriber} from "../interfaces/ISubscriber.sol"; import {PositionConfig} from "../libraries/PositionConfig.sol"; @@ -53,12 +53,16 @@ abstract contract Notifier is INotifier { } /// @inheritdoc INotifier - function unsubscribe(uint256 tokenId, PositionConfig calldata config, bytes calldata data) + function unsubscribe(uint256 tokenId, PositionConfig calldata config) external payable onlyIfApproved(msg.sender, tokenId) onlyValidConfig(tokenId, config) { + _unsubscribe(tokenId, config); + } + + function _unsubscribe(uint256 tokenId, PositionConfig calldata config) internal { _positionConfigs(tokenId).setUnsubscribe(); ISubscriber _subscriber = subscriber[tokenId]; @@ -67,7 +71,7 @@ abstract contract Notifier is INotifier { // A gas limit and a try-catch block are used to protect users from a malicious subscriber. // Users should always be able to unsubscribe, not matter how the subscriber behaves. uint256 subscriberGasLimit = block.gaslimit.calculatePortion(BLOCK_LIMIT_BPS); - try _subscriber.notifyUnsubscribe{gas: subscriberGasLimit}(tokenId, config, data) {} catch {} + try _subscriber.notifyUnsubscribe{gas: subscriberGasLimit}(tokenId, config) {} catch {} emit Unsubscription(tokenId, address(_subscriber)); } diff --git a/src/base/Permit2Forwarder.sol b/src/base/Permit2Forwarder.sol index 41525c6e3..d6f28ccce 100644 --- a/src/base/Permit2Forwarder.sol +++ b/src/base/Permit2Forwarder.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; diff --git a/src/base/PoolInitializer.sol b/src/base/PoolInitializer.sol index 09059f513..2e0918985 100644 --- a/src/base/PoolInitializer.sol +++ b/src/base/PoolInitializer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; import {ImmutableState} from "./ImmutableState.sol"; diff --git a/src/base/SafeCallback.sol b/src/base/SafeCallback.sol index af3f079f7..ab80cd845 100644 --- a/src/base/SafeCallback.sol +++ b/src/base/SafeCallback.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; diff --git a/src/base/UnorderedNonce.sol b/src/base/UnorderedNonce.sol index 13feb1081..fc33b63d8 100644 --- a/src/base/UnorderedNonce.sol +++ b/src/base/UnorderedNonce.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; /// @title Unordered Nonce /// @notice Contract state and methods for using unordered nonces in signatures diff --git a/src/base/hooks/BaseHook.sol b/src/base/hooks/BaseHook.sol index 0c983cf6e..c4c0c6b16 100644 --- a/src/base/hooks/BaseHook.sol +++ b/src/base/hooks/BaseHook.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; @@ -45,8 +45,7 @@ abstract contract BaseHook is IHooks, SafeCallback { if (success) return returnData; if (returnData.length == 0) revert LockFailure(); // if the call failed, bubble up the reason - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { revert(add(returnData, 32), mload(returnData)) } } diff --git a/src/interfaces/IERC721Permit_v4.sol b/src/interfaces/IERC721Permit_v4.sol index 637e9b33f..eab4d1285 100644 --- a/src/interfaces/IERC721Permit_v4.sol +++ b/src/interfaces/IERC721Permit_v4.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.7.5; +pragma solidity ^0.8.0; /// @title ERC721 with permit /// @notice Extension to ERC721 that includes a permit function for signature based approvals diff --git a/src/interfaces/IMulticall_v4.sol b/src/interfaces/IMulticall_v4.sol index ac70a9696..1d053a97d 100644 --- a/src/interfaces/IMulticall_v4.sol +++ b/src/interfaces/IMulticall_v4.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.0; /// @title Multicall_v4 interface /// @notice Enables calling multiple methods in a single call to the contract diff --git a/src/interfaces/INotifier.sol b/src/interfaces/INotifier.sol index 4e4675982..885a93289 100644 --- a/src/interfaces/INotifier.sol +++ b/src/interfaces/INotifier.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; import {PositionConfig} from "../libraries/PositionConfig.sol"; import {ISubscriber} from "./ISubscriber.sol"; @@ -39,10 +39,9 @@ interface INotifier { /// @notice Removes the subscriber from receiving notifications for a respective position /// @param tokenId the ERC721 tokenId /// @param config the corresponding PositionConfig for the tokenId - /// @param data caller-provided data that's forwarded to the subscriber contract /// @dev payable so it can be multicalled with NATIVE related actions /// @dev Must always allow a user to unsubscribe. In the case of a malicious subscriber, a user can always unsubscribe safely, ensuring liquidity is always modifiable. - function unsubscribe(uint256 tokenId, PositionConfig calldata config, bytes calldata data) external payable; + function unsubscribe(uint256 tokenId, PositionConfig calldata config) external payable; /// @notice Returns whether a position should call out to notify a subscribing contract on modification or transfer /// @param tokenId the ERC721 tokenId diff --git a/src/interfaces/IPositionManager.sol b/src/interfaces/IPositionManager.sol index 3d13f721a..8f7a59c63 100644 --- a/src/interfaces/IPositionManager.sol +++ b/src/interfaces/IPositionManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; import {PositionConfig} from "../libraries/PositionConfig.sol"; diff --git a/src/interfaces/IQuoter.sol b/src/interfaces/IQuoter.sol index 57db3778c..8f113f4c6 100644 --- a/src/interfaces/IQuoter.sol +++ b/src/interfaces/IQuoter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; diff --git a/src/interfaces/ISubscriber.sol b/src/interfaces/ISubscriber.sol index 18d428c7b..ceab3ac81 100644 --- a/src/interfaces/ISubscriber.sol +++ b/src/interfaces/ISubscriber.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; import {PositionConfig} from "../libraries/PositionConfig.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; @@ -12,8 +12,7 @@ interface ISubscriber { function notifySubscribe(uint256 tokenId, PositionConfig memory config, bytes memory data) external; /// @param tokenId the token ID of the position /// @param config details about the position - /// @param data additional data passed in by the caller - function notifyUnsubscribe(uint256 tokenId, PositionConfig memory config, bytes memory data) external; + function notifyUnsubscribe(uint256 tokenId, PositionConfig memory config) external; /// @param tokenId the token ID of the position /// @param config details about the position /// @param liquidityChange the change in liquidity on the underlying position diff --git a/src/interfaces/IV4Router.sol b/src/interfaces/IV4Router.sol index 56b8b5039..13ed4775f 100644 --- a/src/interfaces/IV4Router.sol +++ b/src/interfaces/IV4Router.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.0; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; diff --git a/src/interfaces/external/IERC20PermitAllowed.sol b/src/interfaces/external/IERC20PermitAllowed.sol deleted file mode 100644 index 7f2cf6570..000000000 --- a/src/interfaces/external/IERC20PermitAllowed.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.5.0; - -/// @title Interface for permit -/// @notice Interface used by DAI/CHAI for permit -interface IERC20PermitAllowed { - /// @notice Approve the spender to spend some tokens via the holder signature - /// @dev This is the permit interface used by DAI and CHAI - /// @param holder The address of the token holder, the token owner - /// @param spender The address of the token spender - /// @param nonce The holder's nonce, increases at each call to permit - /// @param expiry The timestamp at which the permit is no longer valid - /// @param allowed Boolean that sets approval amount, true for type(uint256).max and false for 0 - /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` - /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` - /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` - function permit( - address holder, - address spender, - uint256 nonce, - uint256 expiry, - bool allowed, - uint8 v, - bytes32 r, - bytes32 s - ) external; -} diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol index 17443b6b2..ee996043c 100644 --- a/src/lens/Quoter.sol +++ b/src/lens/Quoter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; @@ -54,7 +54,6 @@ contract Quoter is IQuoter, SafeCallback { /// @inheritdoc IQuoter function quoteExactInputSingle(QuoteExactSingleParams memory params) public - override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { try poolManager.unlock(abi.encodeCall(this._quoteExactInputSingle, (params))) {} @@ -81,7 +80,6 @@ contract Quoter is IQuoter, SafeCallback { /// @inheritdoc IQuoter function quoteExactOutputSingle(QuoteExactSingleParams memory params) public - override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { try poolManager.unlock(abi.encodeCall(this._quoteExactOutputSingle, (params))) {} @@ -94,7 +92,6 @@ contract Quoter is IQuoter, SafeCallback { /// @inheritdoc IQuoter function quoteExactOutput(QuoteExactParams memory params) public - override returns ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, @@ -112,8 +109,7 @@ contract Quoter is IQuoter, SafeCallback { if (success) return returnData; if (returnData.length == 0) revert LockFailure(); // if the call failed, bubble up the reason - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { revert(add(returnData, 32), mload(returnData)) } } diff --git a/src/lens/StateView.sol b/src/lens/StateView.sol index abee97bdc..f361b905a 100644 --- a/src/lens/StateView.sol +++ b/src/lens/StateView.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; diff --git a/src/libraries/ActionConstants.sol b/src/libraries/ActionConstants.sol index e371f5496..f51b9600d 100644 --- a/src/libraries/ActionConstants.sol +++ b/src/libraries/ActionConstants.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.19; +pragma solidity ^0.8.0; library ActionConstants { /// @notice used to signal that an action should use the input value of the open delta on the pool manager diff --git a/src/libraries/Actions.sol b/src/libraries/Actions.sol index 0b2592dd6..49d3e04f1 100644 --- a/src/libraries/Actions.sol +++ b/src/libraries/Actions.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; /// @notice Library to define different pool actions. /// @dev These are suggested common commands, however additional commands should be defined as required diff --git a/src/libraries/BipsLibrary.sol b/src/libraries/BipsLibrary.sol index 32bd97fc8..f5a842fa3 100644 --- a/src/libraries/BipsLibrary.sol +++ b/src/libraries/BipsLibrary.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.19; +pragma solidity ^0.8.0; /// @title For calculating a percentage of an amount, using bips // TODO: Post-audit move to core, as v4-core will use something similar. diff --git a/src/libraries/ERC721PermitHash.sol b/src/libraries/ERC721PermitHash.sol index 4301dbfb0..1ed5c2baa 100644 --- a/src/libraries/ERC721PermitHash.sol +++ b/src/libraries/ERC721PermitHash.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; library ERC721PermitHashLibrary { /// @dev Value is equal to keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)"); @@ -17,11 +17,18 @@ library ERC721PermitHashLibrary { assembly ("memory-safe") { let fmp := mload(0x40) mstore(fmp, PERMIT_TYPEHASH) - mstore(add(fmp, 0x20), spender) + mstore(add(fmp, 0x20), and(spender, 0xffffffffffffffffffffffffffffffffffffffff)) mstore(add(fmp, 0x40), tokenId) mstore(add(fmp, 0x60), nonce) mstore(add(fmp, 0x80), deadline) digest := keccak256(fmp, 0xa0) + + // now clean the memory we used + mstore(fmp, 0) // fmp held PERMIT_TYPEHASH + mstore(add(fmp, 0x20), 0) // fmp+0x20 held spender + mstore(add(fmp, 0x40), 0) // fmp+0x40 held tokenId + mstore(add(fmp, 0x60), 0) // fmp+0x60 held nonce + mstore(add(fmp, 0x80), 0) // fmp+0x80 held deadline } } @@ -34,11 +41,18 @@ library ERC721PermitHashLibrary { assembly ("memory-safe") { let fmp := mload(0x40) mstore(fmp, PERMIT_FOR_ALL_TYPEHASH) - mstore(add(fmp, 0x20), operator) - mstore(add(fmp, 0x40), approved) + mstore(add(fmp, 0x20), and(operator, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(fmp, 0x40), and(approved, 0x1)) mstore(add(fmp, 0x60), nonce) mstore(add(fmp, 0x80), deadline) digest := keccak256(fmp, 0xa0) + + // now clean the memory we used + mstore(fmp, 0) // fmp held PERMIT_FOR_ALL_TYPEHASH + mstore(add(fmp, 0x20), 0) // fmp+0x20 held operator + mstore(add(fmp, 0x40), 0) // fmp+0x40 held approved + mstore(add(fmp, 0x60), 0) // fmp+0x60 held nonce + mstore(add(fmp, 0x80), 0) // fmp+0x80 held deadline } } } diff --git a/src/libraries/PathKey.sol b/src/libraries/PathKey.sol index a286076d3..b46fb8b59 100644 --- a/src/libraries/PathKey.sol +++ b/src/libraries/PathKey.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; diff --git a/src/libraries/PoolTicksCounter.sol b/src/libraries/PoolTicksCounter.sol index 49b3ff360..f808e61ca 100644 --- a/src/libraries/PoolTicksCounter.sol +++ b/src/libraries/PoolTicksCounter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.8.20; +pragma solidity ^0.8.0; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; diff --git a/src/libraries/PositionConfig.sol b/src/libraries/PositionConfig.sol index 8f3d6ead9..007e7bb9d 100644 --- a/src/libraries/PositionConfig.sol +++ b/src/libraries/PositionConfig.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; diff --git a/src/libraries/PositionConfigId.sol b/src/libraries/PositionConfigId.sol index 40127102a..4e31c760c 100644 --- a/src/libraries/PositionConfigId.sol +++ b/src/libraries/PositionConfigId.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; /// @notice A configId is set per tokenId /// The lower 255 bits are used to store the truncated hash of the corresponding PositionConfig diff --git a/test/mocks/MockBadSubscribers.sol b/test/mocks/MockBadSubscribers.sol index 30bafba79..b5fd64f40 100644 --- a/test/mocks/MockBadSubscribers.sol +++ b/test/mocks/MockBadSubscribers.sol @@ -34,7 +34,7 @@ contract MockReturnDataSubscriber is ISubscriber { notifySubscribeCount++; } - function notifyUnsubscribe(uint256, PositionConfig memory, bytes memory) external onlyByPosm { + function notifyUnsubscribe(uint256, PositionConfig memory) external onlyByPosm { notifyUnsubscribeCount++; uint256 _memPtr = memPtr; assembly { @@ -83,7 +83,7 @@ contract MockRevertSubscriber is ISubscriber { } } - function notifyUnsubscribe(uint256, PositionConfig memory, bytes memory) external view onlyByPosm { + function notifyUnsubscribe(uint256, PositionConfig memory) external view onlyByPosm { revert TestRevert("notifyUnsubscribe"); } diff --git a/test/mocks/MockSubscriber.sol b/test/mocks/MockSubscriber.sol index 032bea868..83ea2e39c 100644 --- a/test/mocks/MockSubscriber.sol +++ b/test/mocks/MockSubscriber.sol @@ -18,7 +18,6 @@ contract MockSubscriber is ISubscriber { BalanceDelta public feesAccrued; bytes public subscribeData; - bytes public unsubscribeData; error NotAuthorizedNotifer(address sender); @@ -38,9 +37,8 @@ contract MockSubscriber is ISubscriber { subscribeData = data; } - function notifyUnsubscribe(uint256, PositionConfig memory, bytes memory data) external onlyByPosm { + function notifyUnsubscribe(uint256, PositionConfig memory) external onlyByPosm { notifyUnsubscribeCount++; - unsubscribeData = data; } function notifyModifyLiquidity(uint256, PositionConfig memory, int256 _liquidityChange, BalanceDelta _feesAccrued) diff --git a/test/position-managers/PositionManager.notifier.t.sol b/test/position-managers/PositionManager.notifier.t.sol index 90c532ef4..ea7931152 100644 --- a/test/position-managers/PositionManager.notifier.t.sol +++ b/test/position-managers/PositionManager.notifier.t.sol @@ -241,7 +241,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); - lpm.unsubscribe(tokenId, config, ZERO_BYTES); + lpm.unsubscribe(tokenId, config); assertEq(sub.notifyUnsubscribeCount(), 1); assertEq(lpm.hasSubscriber(tokenId), false); @@ -260,7 +260,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.subscribe(tokenId, config, address(badSubscriber), ZERO_BYTES); MockReturnDataSubscriber(badSubscriber).setReturnDataSize(0x600000); - lpm.unsubscribe(tokenId, config, ZERO_BYTES); + lpm.unsubscribe(tokenId, config); // the subscriber contract call failed bc it used too much gas assertEq(MockReturnDataSubscriber(badSubscriber).notifyUnsubscribeCount(), 0); @@ -340,7 +340,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { vm.stopPrank(); vm.expectRevert(); - lpm.unsubscribe(tokenId, config, ZERO_BYTES); + lpm.unsubscribe(tokenId, config); } function test_subscribe_withData() public { @@ -362,27 +362,6 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { assertEq(abi.decode(sub.subscribeData(), (address)), address(this)); } - function test_unsubscribe_withData() public { - uint256 tokenId = lpm.nextTokenId(); - mint(config, 100e18, alice, ZERO_BYTES); - - bytes memory subData = abi.encode(address(this)); - - // approve this contract to operate on alices liq - vm.startPrank(alice); - lpm.approve(address(this), tokenId); - vm.stopPrank(); - - lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); - - lpm.unsubscribe(tokenId, config, subData); - - assertEq(sub.notifyUnsubscribeCount(), 1); - assertEq(lpm.hasSubscriber(tokenId), false); - assertEq(address(lpm.subscriber(tokenId)), address(0)); - assertEq(abi.decode(sub.unsubscribeData(), (address)), address(this)); - } - function test_subscribe_wraps_revert() public { uint256 tokenId = lpm.nextTokenId(); mint(config, 100e18, alice, ZERO_BYTES); @@ -496,4 +475,29 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { ); lpm.safeTransferFrom(alice, bob, tokenId, ""); } + + /// @notice burning a position will automatically notify unsubscribe + function test_burn_unsubscribe() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + bytes memory subData = abi.encode(address(this)); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(sub), subData); + + assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(sub.notifyUnsubscribeCount(), 0); + + // burn the position, causing an unsubscribe + burn(tokenId, config, ZERO_BYTES); + + // position is now unsubscribed + assertEq(lpm.hasSubscriber(tokenId), false); + assertEq(sub.notifyUnsubscribeCount(), 1); + } }