-
Notifications
You must be signed in to change notification settings - Fork 614
Permissioned Pools #476
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
Open
gretzke
wants to merge
15
commits into
main
Choose a base branch
from
permissioned-pools
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Permissioned Pools #476
Changes from 1 commit
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
1554604
Initial commit: Permissioned Pools
gretzke 92fd04a
Hook implementation
gretzke 8249788
fix infinite loop
gretzke af9ff57
Wrapped token tests (#477)
gretzke c6b5931
Permissioned router tests, permissioned posm tests (#479)
ericneil-sanc 8482d41
expect specific errors (#486)
ericneil-sanc 8affcc9
Permissioned Pools: Transfers by admins (#483)
gretzke a8ae205
add granular allow list and multiple hooks and fix revert tests (#484)
ericneil-sanc f596e82
permissioned-pools: match universalrouter interface (#488)
ericneil-sanc 3fc39d1
Permissioned Pools: Ability to pause swapping (#485)
gretzke 5ad7877
Rename to permissions adapter (#489)
gretzke 334a58c
emit event when hooks are allowed/disallowed (#498)
gretzke fbf665b
spelling fix
gretzke 1154a7a
Fix typo
gretzke 12bd7ce
Merge branch 'main' into permissioned-pools
gretzke File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {IAllowlistChecker, IERC165} from "./interfaces/IAllowlistChecker.sol"; | ||
| import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; | ||
|
|
||
| abstract contract BaseAllowlistChecker is IAllowlistChecker, ERC165 { | ||
| function checkAllowList(address account) public view virtual returns (bool); | ||
|
|
||
| function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { | ||
| return interfaceId == type(IAllowlistChecker).interfaceId || super.supportsInterface(interfaceId); | ||
| } | ||
| } |
74 changes: 74 additions & 0 deletions
74
src/hooks/permissionedPools/PermissionedPositionManager.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.26; | ||
|
|
||
| import { | ||
| PositionManager, | ||
| PoolKey, | ||
| IPoolManager, | ||
| IAllowanceTransfer, | ||
| IPositionDescriptor, | ||
| IWETH9, | ||
| Currency | ||
| } from "../../PositionManager.sol"; | ||
| import { | ||
| IWrappedPermissionedTokenFactory, | ||
| IWrappedPermissionedToken | ||
| } from "./interfaces/IWrappedPermissionedTokenFactory.sol"; | ||
|
|
||
| contract PermissionedPositionManager is PositionManager { | ||
| IWrappedPermissionedTokenFactory public immutable WRAPPED_TOKEN_FACTORY; | ||
|
|
||
| constructor( | ||
| IPoolManager _poolManager, | ||
| IAllowanceTransfer _permit2, | ||
| uint256 _unsubscribeGasLimit, | ||
| IPositionDescriptor _tokenDescriptor, | ||
| IWETH9 _weth9, | ||
| IWrappedPermissionedTokenFactory _wrappedTokenFactory | ||
| ) PositionManager(_poolManager, _permit2, _unsubscribeGasLimit, _tokenDescriptor, _weth9) { | ||
| WRAPPED_TOKEN_FACTORY = _wrappedTokenFactory; | ||
| } | ||
|
|
||
| /// @dev Disables transfers of the ERC721 liquidity position tokens | ||
| function transferFrom(address, address, uint256) public pure override { | ||
| revert("Transfer disabled"); | ||
| } | ||
|
|
||
| /// @dev When minting a position, verify that the sender is allowed to mint the position. This prevents a disallowed user from minting one sided liquidity. | ||
| function _mint( | ||
| PoolKey calldata poolKey, | ||
| int24 tickLower, | ||
| int24 tickUpper, | ||
| uint256 liquidity, | ||
| uint128 amount0Max, | ||
| uint128 amount1Max, | ||
| address owner, | ||
| bytes calldata hookData | ||
| ) internal override { | ||
| address permissionedToken0 = | ||
| WRAPPED_TOKEN_FACTORY.verifiedPermissionedTokenOf(Currency.unwrap(poolKey.currency0)); | ||
| address permissionedToken1 = | ||
| WRAPPED_TOKEN_FACTORY.verifiedPermissionedTokenOf(Currency.unwrap(poolKey.currency1)); | ||
| if (permissionedToken0 != address(0)) { | ||
| IWrappedPermissionedToken(permissionedToken0).isAllowed(msgSender()); | ||
| } | ||
| if (permissionedToken1 != address(0)) { | ||
| IWrappedPermissionedToken(permissionedToken1).isAllowed(msgSender()); | ||
| } | ||
| super._mint(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, owner, hookData); | ||
| } | ||
|
|
||
| /// @dev When paying to settle, if the currency is a permissioned token, wrap the token and transfer it to the pool manager. | ||
| function _pay(Currency currency, address payer, uint256 amount) internal virtual override { | ||
| address permissionedToken = WRAPPED_TOKEN_FACTORY.verifiedPermissionedTokenOf(Currency.unwrap(currency)); | ||
| if (permissionedToken == address(0)) { | ||
| // token is not a permissioned token, use the default implementation | ||
| super._pay(currency, payer, amount); | ||
| return; | ||
| } | ||
| // token is a permissioned token, wrap the token | ||
| IWrappedPermissionedToken wrappedPermissionedToken = IWrappedPermissionedToken(Currency.unwrap(currency)); | ||
| permit2.transferFrom(payer, address(wrappedPermissionedToken), uint160(amount), permissionedToken); | ||
| wrappedPermissionedToken.wrapToPoolManager(amount); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {V4Router, IPoolManager, Currency} from "../../V4Router.sol"; | ||
| import {ReentrancyLock} from "../../base/ReentrancyLock.sol"; | ||
| import { | ||
| IWrappedPermissionedTokenFactory, | ||
| IWrappedPermissionedToken | ||
| } from "./interfaces/IWrappedPermissionedTokenFactory.sol"; | ||
| import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; | ||
|
|
||
| contract PermissionedV4Router is V4Router, ReentrancyLock { | ||
| IAllowanceTransfer public immutable PERMIT2; | ||
| IWrappedPermissionedTokenFactory public immutable WRAPPED_TOKEN_FACTORY; | ||
|
|
||
| constructor( | ||
| IPoolManager poolManager_, | ||
| IAllowanceTransfer _permit2, | ||
| IWrappedPermissionedTokenFactory wrappedTokenFactory | ||
| ) V4Router(poolManager_) { | ||
| PERMIT2 = _permit2; | ||
| WRAPPED_TOKEN_FACTORY = wrappedTokenFactory; | ||
| } | ||
|
|
||
| function execute(bytes calldata input) public payable isNotLocked { | ||
| _executeActions(input); | ||
| } | ||
|
|
||
| /// @notice Public view function to be used instead of msg.sender, as the contract performs self-reentrancy and at | ||
| /// times msg.sender == address(this). Instead msgSender() returns the initiator of the lock | ||
| /// @dev overrides BaseActionsRouter.msgSender in V4Router | ||
| function msgSender() public view override returns (address) { | ||
| return _getLocker(); | ||
| } | ||
|
|
||
| function _pay(Currency currency, address payer, uint256 amount) internal override { | ||
| address permissionedToken = WRAPPED_TOKEN_FACTORY.verifiedPermissionedTokenOf(Currency.unwrap(currency)); | ||
| if (permissionedToken == address(0)) { | ||
| // token is not a permissioned token, use the default implementation | ||
| if (payer == address(this)) { | ||
| currency.transfer(address(poolManager), amount); | ||
| } else { | ||
| // Casting from uint256 to uint160 is safe due to limits on the total supply of a pool | ||
| PERMIT2.transferFrom(payer, address(poolManager), uint160(amount), Currency.unwrap(currency)); | ||
| } | ||
| return; | ||
| } | ||
| // token is a permissioned token, wrap the token | ||
| IWrappedPermissionedToken wrappedPermissionedToken = IWrappedPermissionedToken(Currency.unwrap(currency)); | ||
| PERMIT2.transferFrom(payer, address(wrappedPermissionedToken), uint160(amount), permissionedToken); | ||
| wrappedPermissionedToken.wrapToPoolManager(amount); | ||
| } | ||
| } |
104 changes: 104 additions & 0 deletions
104
src/hooks/permissionedPools/WrappedPermissionedToken.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.26; | ||
|
|
||
| import {Ownable2Step, Ownable} from "@openzeppelin/contracts/access/Ownable2Step.sol"; | ||
| import {ERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
| import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; | ||
| import {IWrappedPermissionedToken} from "./interfaces/IWrappedPermissionedToken.sol"; | ||
| import {IAllowlistChecker} from "./interfaces/IAllowlistChecker.sol"; | ||
|
|
||
| contract WrappedPermissionedToken is ERC20, Ownable2Step, IWrappedPermissionedToken { | ||
| /// @inheritdoc IWrappedPermissionedToken | ||
| address public immutable POOL_MANAGER; | ||
|
|
||
| /// @inheritdoc IWrappedPermissionedToken | ||
| IERC20 public immutable PERMISSIONED_TOKEN; | ||
|
|
||
| /// @inheritdoc IWrappedPermissionedToken | ||
| IAllowlistChecker public allowListChecker; | ||
|
|
||
| /// @inheritdoc IWrappedPermissionedToken | ||
| mapping(address wrapper => bool) public allowedWrappers; | ||
|
|
||
| constructor( | ||
| IERC20 permissionedToken, | ||
| address poolManager, | ||
| address initialOwner, | ||
| IAllowlistChecker allowListChecker_ | ||
| ) ERC20(_getName(permissionedToken), _getSymbol(permissionedToken)) Ownable(initialOwner) { | ||
| PERMISSIONED_TOKEN = permissionedToken; | ||
| POOL_MANAGER = poolManager; | ||
| _updateAllowListChecker(allowListChecker_); | ||
| } | ||
|
|
||
| /// @inheritdoc IWrappedPermissionedToken | ||
| function wrapToPoolManager(uint256 amount) external { | ||
| if (!allowedWrappers[msg.sender]) revert UnauthorizedWrapper(msg.sender); | ||
| uint256 availableBalance = PERMISSIONED_TOKEN.balanceOf(address(this)) - totalSupply(); | ||
| if (amount > availableBalance) revert InsufficientBalance(amount, availableBalance); | ||
| _mint(POOL_MANAGER, amount); | ||
| } | ||
|
|
||
| /// @inheritdoc IWrappedPermissionedToken | ||
| function updateAllowListChecker(IAllowlistChecker newAllowListChecker) external onlyOwner { | ||
| _updateAllowListChecker(newAllowListChecker); | ||
| } | ||
|
|
||
| /// @inheritdoc IWrappedPermissionedToken | ||
| function updateAllowedWrapper(address wrapper, bool allowed) external onlyOwner { | ||
| _updateAllowedWrapper(wrapper, allowed); | ||
| } | ||
|
|
||
| /// @inheritdoc IWrappedPermissionedToken | ||
| function isAllowed(address account) public view returns (bool) { | ||
| return allowListChecker.checkAllowList(account); | ||
| } | ||
|
|
||
| function _updateAllowListChecker(IAllowlistChecker newAllowListChecker) internal { | ||
| if (!newAllowListChecker.supportsInterface(type(IAllowlistChecker).interfaceId)) { | ||
| revert InvalidAllowListChecker(newAllowListChecker); | ||
| } | ||
| allowListChecker = newAllowListChecker; | ||
| emit AllowListCheckerUpdated(newAllowListChecker); | ||
| } | ||
|
|
||
| function _updateAllowedWrapper(address wrapper, bool allowed) internal { | ||
| allowedWrappers[wrapper] = allowed; | ||
| emit AllowedWrapperUpdated(wrapper, allowed); | ||
| } | ||
|
|
||
| /// @dev Overrides the ERC20._update function to add the following checks and logic: | ||
| /// - Before `settle` is called on the pool manager, the token is wrapped and minted to the pool manager | ||
| /// - When `take` is called on the pool manager, the token is automatically unwrapped when the pool manager transfers the token to the recipient | ||
| /// - Enforces that the pool manager is the only holder of the wrapped token | ||
| function _update(address from, address to, uint256 amount) internal override { | ||
| if (from == address(0)) { | ||
| assert(to == POOL_MANAGER); | ||
| // token is being wrapped | ||
| super._update(from, to, amount); | ||
| return; | ||
| } else if (from != POOL_MANAGER) { | ||
| // if the pool manager is the sender, the token is automatically unwrapped, skip the checks | ||
| revert InvalidTransfer(from, to); | ||
| } | ||
| super._update(from, to, amount); | ||
| if (from == POOL_MANAGER) { | ||
| _unwrap(to, amount); | ||
| } | ||
| // the pool manager must always be the only holder of the wrapped token | ||
| assert(balanceOf(POOL_MANAGER) == totalSupply()); | ||
| } | ||
|
|
||
| function _unwrap(address account, uint256 amount) internal { | ||
| _burn(POOL_MANAGER, amount); | ||
| PERMISSIONED_TOKEN.transfer(account, amount); | ||
| } | ||
|
|
||
| function _getName(IERC20 permissionedToken) private view returns (string memory) { | ||
| return string.concat("Uniswap v4 Wrapped ", ERC20(address(permissionedToken)).name()); | ||
| } | ||
|
|
||
| function _getSymbol(IERC20 permissionedToken) private view returns (string memory) { | ||
| return string.concat("uw", ERC20(address(permissionedToken)).symbol()); | ||
| } | ||
| } |
42 changes: 42 additions & 0 deletions
42
src/hooks/permissionedPools/WrappedPermissionedTokenFactory.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.26; | ||
|
|
||
| import {IWrappedPermissionedToken, IERC20} from "./interfaces/IWrappedPermissionedToken.sol"; | ||
| import {IAllowlistChecker} from "./interfaces/IAllowlistChecker.sol"; | ||
| import {WrappedPermissionedToken} from "./WrappedPermissionedToken.sol"; | ||
| import {IWrappedPermissionedTokenFactory} from "./interfaces/IWrappedPermissionedTokenFactory.sol"; | ||
|
|
||
| contract WrappedPermissionedTokenFactory is IWrappedPermissionedTokenFactory { | ||
| address public immutable POOL_MANAGER; | ||
|
|
||
| /// @inheritdoc IWrappedPermissionedTokenFactory | ||
| mapping(address wrappedToken => address permissionedToken) public permissionedTokenOf; | ||
| /// @inheritdoc IWrappedPermissionedTokenFactory | ||
| mapping(address wrappedToken => address permissionedToken) public verifiedPermissionedTokenOf; | ||
|
|
||
| constructor(address poolManager) { | ||
| POOL_MANAGER = poolManager; | ||
| } | ||
|
|
||
| /// @inheritdoc IWrappedPermissionedTokenFactory | ||
| function createWrappedPermissionedToken( | ||
| IERC20 permissionedToken, | ||
| address initialOwner, | ||
| IAllowlistChecker allowListChecker | ||
| ) external returns (address wrappedPermissionedToken) { | ||
| wrappedPermissionedToken = | ||
| address(new WrappedPermissionedToken(permissionedToken, POOL_MANAGER, initialOwner, allowListChecker)); | ||
| permissionedTokenOf[wrappedPermissionedToken] = address(permissionedToken); | ||
| emit WrappedPermissionedTokenCreated(wrappedPermissionedToken, address(permissionedToken)); | ||
| } | ||
|
|
||
| /// @inheritdoc IWrappedPermissionedTokenFactory | ||
| function verifyWrappedToken(address wrappedToken) external { | ||
| IERC20 permissionedToken = IERC20(permissionedTokenOf[wrappedToken]); | ||
| if (address(permissionedToken) == address(0)) revert WrappedTokenNotFound(wrappedToken); | ||
| if (verifiedPermissionedTokenOf[wrappedToken] != address(0)) revert WrappedTokenAlreadyVerified(wrappedToken); | ||
| if (permissionedToken.balanceOf(wrappedToken) != 0) revert WrappedTokenNotVerified(wrappedToken); | ||
| verifiedPermissionedTokenOf[wrappedToken] = address(permissionedToken); | ||
| emit WrappedTokenVerified(wrappedToken, address(permissionedToken)); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; | ||
|
|
||
| interface IAllowlistChecker is IERC165 { | ||
| function checkAllowList(address account) external view returns (bool); | ||
| } |
60 changes: 60 additions & 0 deletions
60
src/hooks/permissionedPools/interfaces/IWrappedPermissionedToken.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
| import {IAllowlistChecker} from "./IAllowlistChecker.sol"; | ||
|
|
||
| interface IWrappedPermissionedToken is IERC20 { | ||
| /// @notice Emitted when the allow list checker is updated | ||
| event AllowListCheckerUpdated(IAllowlistChecker indexed newAllowListChecker); | ||
|
|
||
| /// @notice Emitted when an allowed wrapper is updated | ||
| event AllowedWrapperUpdated(address indexed wrapper, bool allowed); | ||
|
|
||
| /// @notice Thrown when the allow list checker does not implement the IAllowListChecker interface | ||
| error InvalidAllowListChecker(IAllowlistChecker newAllowListChecker); | ||
|
|
||
| /// @notice Thrown when the transfer is not interacting with the pool manager | ||
| error InvalidTransfer(address from, address to); | ||
|
|
||
| /// @notice Thrown when the wrapper is not allowed to trigger transfers on the wrapped token | ||
| error UnauthorizedWrapper(address wrapper); | ||
|
|
||
| /// @notice Thrown when there is an insufficient amount of permissioned tokens available to wrap | ||
| error InsufficientBalance(uint256 amount, uint256 availableBalance); | ||
|
|
||
| /// @notice Updates the allow list checker | ||
| /// @param newAllowListChecker The new allow list checker | ||
| /// @dev Only callable by the owner | ||
| function updateAllowListChecker(IAllowlistChecker newAllowListChecker) external; | ||
|
|
||
| /// @notice Wraps the permissioned token to the pool manager | ||
| /// @param amount The amount of permissioned tokens to wrap | ||
| /// @dev Only callable by allowed wrappers | ||
| /// @dev The `amount` must be sent to this contract before calling this function | ||
| function wrapToPoolManager(uint256 amount) external; | ||
|
|
||
| /// @notice Updates the allowed wrapper that can wrap the permissioned token | ||
| /// @param wrapper The wrapper to update | ||
| /// @param allowed Whether the wrapper is allowed | ||
| /// @dev Only callable by the owner | ||
| /// @dev To ensure the wrapped token cannot be wrapped in an ERC6909 token on the PoolManager, the wrapper must only implement `swap` or `modifyLiquidity` functions | ||
| function updateAllowedWrapper(address wrapper, bool allowed) external; | ||
|
|
||
| /// @notice Returns whether a transfer is allowed | ||
| /// @param account The account to check | ||
| function isAllowed(address account) external view returns (bool); | ||
|
|
||
| /// @notice Returns the allow list checker | ||
| function allowListChecker() external view returns (IAllowlistChecker); | ||
|
|
||
| /// @notice Returns the allowed wrappers that can wrap the permissioned token | ||
| /// @dev e.g., the permissioned pool manager, quoters or the universal router | ||
| function allowedWrappers(address wrapper) external view returns (bool); | ||
|
|
||
| /// @notice Returns the Uniswap v4 pool manager | ||
| function POOL_MANAGER() external view returns (address); | ||
|
|
||
| /// @notice Returns the permissioned token that is wrapped by this contract | ||
| function PERMISSIONED_TOKEN() external view returns (IERC20); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question here: Given we are dealing with a permissioned/wrapped binary, why are we casting the permissioned token to an interface for the wrapped token? Shouldn't this be on poolKey.currency0 (and poolKey.currency1 for line 56)? Unless we are expecting the permissioned tokens to have the same function? I apologize if I am missing something, which definitely may be the case 😄 .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks!!