harb/src/LiquidityManager.sol

238 lines
8.5 KiB
Solidity
Raw Normal View History

2023-11-23 22:14:47 +01:00
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;
import {BloodX} from "src/BloodX.sol";
import "@uniswap/v3-core/contracts/libraries/TickMath.sol";
import "@uniswap/v3-periphery/contracts/libraries/LiquidityAmounts.sol";
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title LiquidityManager - A contract that supports the $bloodX ecosystem. It
* protects the communities liquidity while allowing a manager role to
* take strategic liqudity positions.
*/
contract LiquidityManager {
// default fee of 1%
uint24 constant FEE = uint24(10_000);
/// @notice WETH9, which is an underlying pool token.
address immutable WETH;
struct AddLiquidityParams {
address token0;
address token1;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
struct DecreaseLiquidityParams {
address token0;
address token1;
int24 tickLower;
int24 tickUpper;
uint128 liquidity;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
struct TokenPosition {
// the liquidity of the position
uint128 liquidity;
// the fee growth of the aggregate position as of the last action on the individual position
uint256 feeGrowthInside0LastX128;
uint256 feeGrowthInside1LastX128;
// how many uncollected tokens are owed to the position, as of the last computation
uint128 tokensOwed0;
uint128 tokensOwed1;
}
/// @dev The token ID position data
mapping(bytes32 => TokenPosition) private _positions;
constructor(address _weth) {
WETH = _weth;
}
function getToken(address token0, address token1) internal view returns (address) {
bool token0isWeth = (token0 == WETH);
require(token0isWeth || token1 == WETH, "token error");
return (token0isWeth) ? token1 : token0;
}
function posKey(address _token, uint24 _tickLower, uint24, tickUpper) internal pure returns (uint256) {
return uint160(_token) << 48 + tickLower << 24 + tickUpper;
}
function positions(address _token, int24 _tickLower, int24 _tickUpper) external view
returns (
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
)
{
TokenPosition memory position = _positions[posKey(_token, tickLower, tickUpper)];
return (
position.liquidity,
position.feeGrowthInside0LastX128,
position.feeGrowthInside1LastX128,
position.tokensOwed0,
position.tokensOwed1
);
}
/// @inheritdoc IUniswapV3MintCallback
function uniswapV3MintCallback(
uint256 amount0Owed,
uint256 amount1Owed,
bytes calldata data
) external override {
PoolAddress.PoolKey memory poolKey = abi.decode(data, (PoolAddress.PoolKey));
CallbackValidation.verifyCallback(factory, poolKey);
if (amount0Owed > 0) ERC20(poolKey.token0).transfer(msg.sender, amount0Owed);
if (amount1Owed > 0) ERC20(poolKey.token1).transfer(msg.sender, amount1Owed);
}
/// @notice Add liquidity to an initialized pool
function addLiquidity(AddLiquidityParams memory params)
external
checkDeadline(params.deadline)
returns (
uint128 liquidity,
uint256 amount0,
uint256 amount1
)
{
PoolAddress.PoolKey memory poolKey =
PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: FEE});
pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
// compute the liquidity amount
{
(uint160 sqrtPriceX96, , , , , , ) = pool.slot0();
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(params.tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(params.tickUpper);
liquidity = LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
sqrtRatioAX96,
sqrtRatioBX96,
params.amount0Desired,
params.amount1Desired
);
}
(amount0, amount1) = pool.mint(
address(this),
params.tickLower,
params.tickUpper,
liquidity,
abi.encode(PoolAddress.PoolKey(poolKey))
);
require(amount0 >= params.amount0Min && amount1 >= params.amount1Min, 'Price slippage check');
bytes32 positionKey = PositionKey.compute(address(this), params.tickLower, params.tickUpper);
// this is now updated to the current transaction
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);
TokenPosition memory position = _positions[posKey(getToken(params.token0, params.token1), tickLower, tickUpper)];
if (liquidity == 0) {
// create entry
position = TokenPosition({
liquidity: liquidity,
feeGrowthInside0LastX128: feeGrowthInside0LastX128,
feeGrowthInside1LastX128: feeGrowthInside1LastX128,
tokensOwed0: 0,
tokensOwed1: 0
});
} else {
// update entry
position.tokensOwed0 += uint128(
FullMath.mulDiv(
feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
position.liquidity,
FixedPoint128.Q128
)
);
position.tokensOwed1 += uint128(
FullMath.mulDiv(
feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
position.liquidity,
FixedPoint128.Q128
)
);
position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
position.liquidity += liquidity;
}
emit IncreaseLiquidity(tokenId, liquidity, amount0, amount1);
}
/// @inheritdoc INonfungiblePositionManager
function decreaseLiquidity(DecreaseLiquidityParams calldata params)
external
payable
override
checkDeadline(params.deadline)
returns (uint256 amount0, uint256 amount1)
{
require(params.liquidity > 0);
TokenPosition memory position = _positions[posKey(getToken(params.token0, params.token1), tickLower, tickUpper)];
uint128 positionLiquidity = position.liquidity;
require(positionLiquidity >= params.liquidity);
PoolAddress.PoolKey memory poolKey =
PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: FEE});
pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
(amount0, amount1) = pool.burn(params.tickLower, params.tickUpper, params.liquidity);
require(amount0 >= params.amount0Min && amount1 >= params.amount1Min, 'Price slippage check');
bytes32 positionKey = PositionKey.compute(address(this), params.tickLower, params.tickUpper);
// this is now updated to the current transaction
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);
position.tokensOwed0 +=
uint128(amount0) +
uint128(
FullMath.mulDiv(
feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
positionLiquidity,
FixedPoint128.Q128
)
);
position.tokensOwed1 +=
uint128(amount1) +
uint128(
FullMath.mulDiv(
feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
positionLiquidity,
FixedPoint128.Q128
)
);
position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
// subtraction is safe because we checked positionLiquidity is gte params.liquidity
position.liquidity = positionLiquidity - params.liquidity;
emit DecreaseLiquidity(params.tokenId, params.liquidity, amount0, amount1);
}
}