-
Notifications
You must be signed in to change notification settings - Fork 38
feat: add position manager helper #89
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
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
4351cc4
feat: add position manager helper
ChefMist 55f01a5
feat: format code
ChefMist 0a8de4c
feat: include permit2 forwarder
ChefMist 5d15829
feat: add re-entrancy lock
ChefMist 92f20e9
feat: add deployment script
ChefMist 69eebcd
bug: check liquidityParam.to for slippage
ChefMist ed12279
feat: remove duplicate isNative check
ChefMist 0ab63be
fix: expecting accending binId -- no duplicates
ChefMist 873a0e8
Merge remote-tracking branch 'origin/main' into feat/add-liquidity-he…
ChefMist 8e7aab3
feat: deploy on bnb and base
ChefMist 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| pragma solidity ^0.8.24; | ||
|
|
||
| import "forge-std/Script.sol"; | ||
| import {BaseScript} from "./BaseScript.sol"; | ||
| import {IBinPoolManager} from "infinity-core/src/pool-bin/interfaces/IBinPoolManager.sol"; | ||
| import {IBinPositionManagerWithERC1155} from "../src/pool-bin/interfaces/IBinPositionManagerWithERC1155.sol"; | ||
| import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; | ||
| import {IWETH9} from "../src/interfaces/external/IWETH9.sol"; | ||
| import {Create3Factory} from "pancake-create3-factory/src/Create3Factory.sol"; | ||
| import {BinPositionManagerHelper} from "../src/pool-bin/BinPositionManagerHelper.sol"; | ||
|
|
||
| /** | ||
| * Pre-req: foundry on stable (1.0) otherwise verify will fail: ref https://github.com/foundry-rs/foundry/issues/9698 | ||
| * | ||
| * Step 1: Deploy | ||
| * forge script script/10_DeployBinPositionManagerHelper.s.sol:DeployBinPositionManagerHelper -vvv \ | ||
| * --rpc-url $RPC_URL \ | ||
| * --broadcast \ | ||
| * --slow \ | ||
| * --verify | ||
| */ | ||
| contract DeployBinPositionManagerHelper is BaseScript { | ||
| function getDeploymentSalt() public pure override returns (bytes32) { | ||
| return keccak256("INFINITY-PERIPHERY/BinPositionManagerHelper/1.0.0"); | ||
| } | ||
|
|
||
| function run() public { | ||
| Create3Factory factory = Create3Factory(getAddressFromConfig("create3Factory")); | ||
|
|
||
| uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); | ||
| vm.startBroadcast(deployerPrivateKey); | ||
|
|
||
| address binPoolManager = getAddressFromConfig("binPoolManager"); | ||
| emit log_named_address("binPoolManager", binPoolManager); | ||
|
|
||
| address binPositionManager = getAddressFromConfig("binPositionManager"); | ||
| emit log_named_address("binPositionManager", binPositionManager); | ||
|
|
||
| address permit2 = getAddressFromConfig("permit2"); | ||
| emit log_named_address("Permit2", permit2); | ||
|
|
||
| address weth = getAddressFromConfig("weth"); | ||
| emit log_named_address("WETH", weth); | ||
|
|
||
| bytes memory creationCodeData = abi.encode( | ||
| IBinPoolManager(binPoolManager), | ||
| IBinPositionManagerWithERC1155(binPositionManager), | ||
| IAllowanceTransfer(permit2), | ||
| IWETH9(weth) | ||
| ); | ||
| bytes memory creationCode = abi.encodePacked(type(BinPositionManagerHelper).creationCode, creationCodeData); | ||
| address binPositionManagerHelper = | ||
| factory.deploy(getDeploymentSalt(), creationCode, keccak256(creationCode), 0, new bytes(0), 0); | ||
| emit log_named_address("BinPositionManagerHelper", binPositionManagerHelper); | ||
|
|
||
| vm.stopBroadcast(); | ||
| } | ||
| } |
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
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
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,8 @@ | ||
| { | ||
| "BinPositionManagerHelper size": "8702", | ||
| "test_addLiquidities_existingPool": "1044057", | ||
| "test_addLiquidities_existingPool_bobReceiver": "1044057", | ||
| "test_addLiquidities_existingPool_nativeToken": "962326", | ||
| "test_addLiquidities_newPool": "1168114", | ||
| "test_addLiquidities_newPool_WithPermit": "1227164" | ||
| } |
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,176 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
| // Copyright (C) 2024 PancakeSwap | ||
| pragma solidity 0.8.26; | ||
|
|
||
| import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
| import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; | ||
| import {Currency, CurrencyLibrary} from "infinity-core/src/types/Currency.sol"; | ||
| import {IBinPoolManager} from "infinity-core/src/pool-bin/interfaces/IBinPoolManager.sol"; | ||
| import {PoolKey} from "infinity-core/src/types/PoolKey.sol"; | ||
| import {PoolId} from "infinity-core/src/types/PoolId.sol"; | ||
| import {IVault} from "infinity-core/src/interfaces/IVault.sol"; | ||
|
|
||
| import {IBinPositionManager} from "./interfaces/IBinPositionManager.sol"; | ||
| import {IBinPositionManagerWithERC1155} from "./interfaces/IBinPositionManagerWithERC1155.sol"; | ||
| import {IWETH9} from "../interfaces/external/IWETH9.sol"; | ||
| import {Actions} from "../libraries/Actions.sol"; | ||
| import {BinCalldataDecoder} from "./libraries/BinCalldataDecoder.sol"; | ||
| import {CalldataDecoder} from "../libraries/CalldataDecoder.sol"; | ||
| import {BinTokenLibrary} from "./libraries/BinTokenLibrary.sol"; | ||
| import {Multicall} from "../base/Multicall.sol"; | ||
| import {Permit2Forwarder} from "../base/Permit2Forwarder.sol"; | ||
| import {ReentrancyLock} from "../base/ReentrancyLock.sol"; | ||
|
|
||
| /// @title BinPositionManagerHelper | ||
| /// @notice Helper contract for adding liquidity to bin pool with additional slippage protection | ||
| contract BinPositionManagerHelper is Multicall, Permit2Forwarder, ReentrancyLock { | ||
| using CalldataDecoder for bytes; | ||
| using BinCalldataDecoder for bytes; | ||
| using BinTokenLibrary for PoolId; | ||
|
|
||
| /// @notice Thrown when an unexpected address sends ETH to this contract | ||
| error InvalidEthSender(); | ||
| /// @notice Thrown when theres multiple BIN_ADD_LIQUIDITY actions | ||
| error DuplicateAddLiquidity(); | ||
| /// @notice Thrown when there's unsupported action in the payload | ||
| error UnsupportedAction(); | ||
| /// @notice Thrown when no BIN_ADD_LIQUIDITY action is found in the payload | ||
| error NoAddLiquidityAction(); | ||
| /// @notice Thrown when minLiquidityParam's binIds and minLiquidities length mismatch | ||
| error MinLiquidityParamsLengthMismatch(); | ||
| /// @notice Thrown when slippage checks fail | ||
| error SlippageCheck(uint24 binId, uint256 liquidityAdded); | ||
| /// @notice Thrown when invalid (duplicate or non accending) binIds are found in minLiquidityParam | ||
| error InvalidBinId(uint24 binid); | ||
|
|
||
| struct MinLiquidityParams { | ||
| /// @dev expect accending order of binIds eg. [20, 21, 22] | ||
| uint24[] binIds; | ||
| uint256[] minLiquidities; | ||
| } | ||
|
|
||
| IBinPoolManager public immutable binPoolManager; | ||
| IBinPositionManagerWithERC1155 public immutable binPositionManager; | ||
| IWETH9 public immutable WETH9; | ||
|
|
||
| constructor( | ||
| IBinPoolManager _binPoolManager, | ||
| IBinPositionManagerWithERC1155 _binPositionManager, | ||
| IAllowanceTransfer _permit2, | ||
| IWETH9 _weth9 | ||
| ) Permit2Forwarder(_permit2) { | ||
| binPoolManager = _binPoolManager; | ||
| binPositionManager = _binPositionManager; | ||
| permit2 = _permit2; | ||
| WETH9 = _weth9; | ||
| } | ||
|
|
||
| /// @notice Add liquidities to bin pool with slippage protection | ||
| /// @param payload - encoded actions and parameters for bin position manager | ||
| /// @param deadline - deadline for the transaction | ||
| /// @param minLiquidityParam - amount of [binId, liquidity] to mint | ||
| /// @dev This function only support 1 BIN_ADD_LIQUIDITY call | ||
| function addLiquidities(bytes calldata payload, uint256 deadline, MinLiquidityParams memory minLiquidityParam) | ||
| external | ||
| payable | ||
| isNotLocked | ||
| { | ||
| if (minLiquidityParam.binIds.length != minLiquidityParam.minLiquidities.length) { | ||
| revert MinLiquidityParamsLengthMismatch(); | ||
| } | ||
|
|
||
| // Step 1: decode payload | ||
| IBinPositionManager.BinAddLiquidityParams memory liquidityParams = _getAddLiquidityParam(payload); | ||
| Currency currency0 = liquidityParams.poolKey.currency0; | ||
| Currency currency1 = liquidityParams.poolKey.currency1; | ||
|
|
||
| // Step 2: Transfer token from user and permit2 approve so BinPositionManager can take the tokens later | ||
| if (!currency0.isNative()) { | ||
| // for native case, do not need to check msg.value, as binPositionManager will revert | ||
| permit2.transferFrom(msg.sender, address(this), liquidityParams.amount0Max, Currency.unwrap(currency0)); | ||
| _approveBinPm(currency0, liquidityParams.amount0Max); | ||
| } | ||
| permit2.transferFrom(msg.sender, address(this), liquidityParams.amount1Max, Currency.unwrap(currency1)); | ||
| _approveBinPm(currency1, liquidityParams.amount1Max); | ||
|
|
||
| // Step 3a: Before Check user balance before | ||
| address[] memory owners = new address[](minLiquidityParam.binIds.length); | ||
| uint256[] memory tokenIds = new uint256[](minLiquidityParam.binIds.length); | ||
| PoolId poolId = liquidityParams.poolKey.toId(); | ||
| uint24 tempBinId = 0; // check duplicate binId | ||
| for (uint256 i = 0; i < minLiquidityParam.binIds.length; i++) { | ||
| owners[i] = liquidityParams.to; | ||
| // Sanity check -- eg. assume binId is accending order, so this will check duplicate as well | ||
| if (tempBinId >= minLiquidityParam.binIds[i]) revert InvalidBinId(minLiquidityParam.binIds[i]); | ||
| tempBinId = minLiquidityParam.binIds[i]; | ||
| tokenIds[i] = poolId.toTokenId(tempBinId); | ||
| } | ||
| uint256[] memory balBefore = binPositionManager.balanceOfBatch(owners, tokenIds); | ||
|
|
||
| // Step 3b: modify liquidities | ||
| binPositionManager.modifyLiquidities{value: msg.value}(payload, deadline); | ||
|
|
||
| // Step 3c: Check user balance after | ||
| uint256[] memory balAfter = binPositionManager.balanceOfBatch(owners, tokenIds); | ||
| for (uint256 i = 0; i < minLiquidityParam.minLiquidities.length; i++) { | ||
| uint256 liquidityAdded = balAfter[i] - balBefore[i]; | ||
| if (liquidityAdded < minLiquidityParam.minLiquidities[i]) { | ||
| revert SlippageCheck(minLiquidityParam.binIds[i], liquidityAdded); | ||
|
ChefMist marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
| // Step 4: refund the user of any balance t0, t1 in contract | ||
| if (currency0.balanceOfSelf() > 0) { | ||
| currency0.transfer(msg.sender, currency0.balanceOfSelf()); | ||
| } | ||
| if (currency1.balanceOfSelf() > 0) { | ||
| currency1.transfer(msg.sender, currency1.balanceOfSelf()); | ||
| } | ||
| } | ||
|
|
||
| /// @notice Initialize a infinity PCS bin pool | ||
| /// @dev For a new pool, user will use multiCall[initializePool, addLiquidities], implementation copied from BinPositionManager | ||
| function initializePool(PoolKey memory key, uint24 activeId) external payable { | ||
|
ChefMist marked this conversation as resolved.
|
||
| /// @dev if the pool revert due to other error (currencyOutOfOrder etc..), then the follow-up action to the pool will still revert accordingly | ||
| try binPoolManager.initialize(key, activeId) {} catch {} | ||
| } | ||
|
|
||
| /// @notice Approve the bin position manager to spend the currency | ||
| /// @dev assume currency is not native | ||
| function _approveBinPm(Currency _currency, uint160 _amount) internal { | ||
| IERC20(Currency.unwrap(_currency)).approve(address(permit2), _amount); | ||
| permit2.approve(Currency.unwrap(_currency), address(binPositionManager), _amount, uint48(block.timestamp)); | ||
| } | ||
|
|
||
| function _getAddLiquidityParam(bytes calldata payload) | ||
| internal | ||
| pure | ||
| returns (IBinPositionManager.BinAddLiquidityParams memory liquidityParams) | ||
| { | ||
| (bytes calldata actions, bytes[] calldata params) = payload.decodeActionsRouterParams(); | ||
| uint256 numActions = actions.length; | ||
| for (uint256 actionIndex = 0; actionIndex < numActions; actionIndex++) { | ||
| uint256 action = uint8(actions[actionIndex]); | ||
|
|
||
| if (action == Actions.BIN_ADD_LIQUIDITY) { | ||
| if (liquidityParams.activeIdDesired != 0) revert DuplicateAddLiquidity(); | ||
| liquidityParams = params[actionIndex].decodeBinAddLiquidityParams(); | ||
| } | ||
|
|
||
| // FE won't call BIN_ADD_LIQUIDITY_FROM_DELTAS | ||
| if (action == Actions.BIN_ADD_LIQUIDITY_FROM_DELTAS || action == Actions.BIN_REMOVE_LIQUIDITY) { | ||
|
ChefMist marked this conversation as resolved.
|
||
| revert UnsupportedAction(); | ||
| } | ||
| } | ||
|
|
||
| if (liquidityParams.activeIdDesired == 0) { | ||
| revert NoAddLiquidityAction(); | ||
| } | ||
|
|
||
| return liquidityParams; | ||
| } | ||
|
|
||
| receive() external payable { | ||
| if (msg.sender != address(WETH9) && msg.sender != address(binPositionManager)) revert InvalidEthSender(); | ||
| } | ||
| } | ||
10 changes: 10 additions & 0 deletions
10
src/pool-bin/interfaces/IBinPositionManagerWithERC1155.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,10 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {IBinPositionManager} from "./IBinPositionManager.sol"; | ||
|
|
||
| interface IBinPositionManagerWithERC1155 is IBinPositionManager { | ||
| function balanceOfBatch(address[] calldata owners, uint256[] calldata ids) | ||
| external | ||
| returns (uint256[] memory balances); | ||
| } |
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.