Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions script/10_DeployBinPositionManagerHelper.s.sol
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();
}
}
3 changes: 2 additions & 1 deletion script/config/base-mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@
"factoryV2": "0x02a84c1b3BBD7401a5f7fa98a384EBC70bB5749E",
"factoryStable": "0x64D74e1EAAe3176744b5767b93B7Bee39Cf7898F",
"mixedQuoter": "0x2dCbF7B985c8C5C931818e4E107bAe8aaC8dAB7C",
"clTickLens": "0x8bcf30285413f25032fb983c2bf4defe29a33f3a"
"clTickLens": "0x8bcf30285413f25032fb983c2bf4defe29a33f3a",
"binPositionManagerHelper": "0xF4c3E0A83Ed93f6be55634C979e71c46c2453D4C"
}
3 changes: 2 additions & 1 deletion script/config/bsc-mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@
"factoryV2": "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73",
"factoryStable": "0x25a55f9f2279A54951133D503490342b50E5cd15",
"mixedQuoter": "0x2dCbF7B985c8C5C931818e4E107bAe8aaC8dAB7C",
"clTickLens": "0x8BcF30285413F25032fb983C2bF4deFe29a33f3a"
"clTickLens": "0x8BcF30285413F25032fb983C2bF4deFe29a33f3a",
"binPositionManagerHelper": "0xF4c3E0A83Ed93f6be55634C979e71c46c2453D4C"
}
3 changes: 2 additions & 1 deletion script/config/bsc-testnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@
"factoryV2": "0x6725F303b657a9451d8BA641348b6761A6CC7a17",
"factoryStable": "0xe6A00f8b819244e8Ab9Ea930e46449C2F20B6609",
"mixedQuoter": "0xdf70a0A2DADC2a01dbE165702aa6dCdf034628b0",
"clTickLens": "0x05a732bb9A23256F57f9FdF255212979368Ece39"
"clTickLens": "0x05a732bb9A23256F57f9FdF255212979368Ece39",
"binPositionManagerHelper": "0xF4c3E0A83Ed93f6be55634C979e71c46c2453D4C"
}
3 changes: 2 additions & 1 deletion script/config/ethereum-mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
"binQuoter": "0x",
"clMigrator": "0x",
"binMigrator": "0x",
"clTickLens": "0x"
"clTickLens": "0x",
"binPositionManagerHelper": "0x"
}
3 changes: 2 additions & 1 deletion script/config/ethereum-sepolia.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
"factoryV2": "0x1bdc540dEB9Ed1fA29964DeEcCc524A8f5e2198e",
"factoryStable": "0x000000000000000000000000000000000000dEaD",
"mixedQuoter": "0x3F47BDA24e069FC5f28B74d4FAB27EeBA006eF92",
"clTickLens": "0xE95fce3eEFcFf86B1d6a8951405432eA96505c98"
"clTickLens": "0xE95fce3eEFcFf86B1d6a8951405432eA96505c98",
"binPositionManagerHelper": "0x"
}
8 changes: 8 additions & 0 deletions snapshots/BinPositionManagerHelperTest.json
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"
}
176 changes: 176 additions & 0 deletions src/pool-bin/BinPositionManagerHelper.sol
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];
Comment thread
ChefMist marked this conversation as resolved.
if (liquidityAdded < minLiquidityParam.minLiquidities[i]) {
revert SlippageCheck(minLiquidityParam.binIds[i], liquidityAdded);
Comment thread
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 {
Comment thread
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) {
Comment thread
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 src/pool-bin/interfaces/IBinPositionManagerWithERC1155.sol
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);
}
Loading