This commit is contained in:
JulesCrown 2023-11-25 22:06:38 +01:00
parent b1c5bb014c
commit 340f29ac63
4 changed files with 65 additions and 57 deletions

View file

@ -1,27 +1,29 @@
// 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-periphery/contracts/libraries/PoolAddress.sol";
import "@uniswap/v3-periphery/contracts/libraries/CallbackValidation.sol";
import '@uniswap/v3-periphery/contracts/base/PeripheryImmutableState.sol';
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
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-periphery/contracts/libraries/PoolAddress.sol';
import '@uniswap/v3-periphery/contracts/libraries/PositionKey.sol';
import '@uniswap/v3-core/contracts/libraries/FixedPoint128.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 is PeripheryImmutableState {
contract LiquidityManager {
// default fee of 1%
uint24 constant FEE = uint24(10_000);
/// @notice WETH9, which is an underlying pool token.
address immutable WETH;
// the address of the Uniswap V3 factory
address public immutable factory;
// the address of WETH9
address public immutable WETH9;
struct AddLiquidityParams {
address token0;
@ -54,48 +56,59 @@ contract LiquidityManager is PeripheryImmutableState {
uint256 feeGrowthInside1LastX128;
}
struct FeesOwed {
// how many uncollected tokens are owed
// updated by each position, as of the last computation
uint128 tokensOwed;
uint128 ethOwed;
}
mapping(address => FeesOwed) private _feesOwed;
// uint256 = uint128 tokensOwed + uint128 ethOwed
mapping(address => uint256) private _feesOwed;
/// @dev The token ID position data
mapping(bytes32 => TokenPosition) private _positions;
constructor(
address _factory,
address _WETH9
) {
WETH = _weth;
}
mapping(bytes26 => TokenPosition) private _positions;
modifier checkDeadline(uint256 deadline) {
require(block.timestamp <= deadline, 'Transaction too old');
_;
}
function getToken(address token0, address token1) internal view returns (address) {
bool token0isWeth = (token0 == WETH);
require(token0isWeth || token1 == WETH, "token error");
return (token0isWeth) ? token1 : token0;
/// @notice Emitted when liquidity is increased for a position
/// @param liquidity The amount by which liquidity for the NFT position was increased
/// @param amount0 The amount of token0 that was paid for the increase in liquidity
/// @param amount1 The amount of token1 that was paid for the increase in liquidity
event IncreaseLiquidity(address indexed token, uint128 liquidity, uint256 amount0, uint256 amount1);
/// @notice Emitted when liquidity is decreased for a position
/// @param liquidity The amount by which liquidity for the NFT position was decreased
/// @param amount0 The amount of token0 that was accounted for the decrease in liquidity
/// @param amount1 The amount of token1 that was accounted for the decrease in liquidity
event DecreaseLiquidity(address indexed token, uint128 liquidity, uint256 amount0, uint256 amount1);
constructor(
address _factory,
address _WETH9
) {
factory = _factory;
WETH9 = _WETH9;
}
function getToken(address token0, address token1) internal view returns (bool token0isWeth, address token) {
token0isWeth = (token0 == WETH9);
require(token0isWeth || token1 == WETH9, "token error");
token = (token0isWeth) ? token1 : token0;
}
function getFeesOwed(address token) public view returns (uint128 tokensOwed, uint128 ethOwed) {
(tokensOwed, ethOwed) = _feesOwed[token];
uint256 feesOwed = _feesOwed[token];
return (uint128(feesOwed >> 128), uint128(feesOwed));
}
function updateFeesOwed(bool token0isEth, address token, uint128 tokensOwed0, uint128 tokensOwed1) internal {
FeesOwed storage feesOwed = _feesOwed[token];
feesOwed = (feesOwed.tokensOwed += token0isEth ? tokensOwed1 : tokensOwed0,
feesOwed.ethOwed += token0isEth ? tokensOwed0 : tokensOwed1);
(uint128 tokensOwed, uint128 ethOwed) = getFeesOwed(token);
tokensOwed += token0isEth ? tokensOwed1 : tokensOwed0;
ethOwed += token0isEth ? tokensOwed0 : tokensOwed1;
_feesOwed[token] = uint256(tokensOwed) << 128 + ethOwed;
}
function posKey(address token, uint24 tickLower, uint24 tickUpper) internal pure returns (uint256) {
return uint160(token) << 48 + tickLower << 24 + tickUpper;
function posKey(address token, int24 tickLower, int24 tickUpper) internal pure returns (bytes26 _posKey) {
bytes memory _posKeyBytes = abi.encodePacked(token, tickLower, tickUpper);
assembly {
_posKey := mload(add(_posKeyBytes, 26))
}
}
function positions(address token, int24 tickLower, int24 tickUpper) external view
@ -117,12 +130,13 @@ contract LiquidityManager is PeripheryImmutableState {
uint256 amount0Owed,
uint256 amount1Owed,
bytes calldata data
) external override {
) external {
PoolAddress.PoolKey memory poolKey = abi.decode(data, (PoolAddress.PoolKey));
CallbackValidation.verifyCallback(factory, poolKey);
IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
require(msg.sender == address(pool));
if (amount0Owed > 0) ERC20(poolKey.token0).transfer(msg.sender, amount0Owed);
if (amount1Owed > 0) ERC20(poolKey.token1).transfer(msg.sender, amount1Owed);
if (amount0Owed > 0) IERC20(poolKey.token0).transfer(msg.sender, amount0Owed);
if (amount1Owed > 0) IERC20(poolKey.token1).transfer(msg.sender, amount1Owed);
}
/// @notice Add liquidity to an initialized pool
@ -137,7 +151,7 @@ contract LiquidityManager is PeripheryImmutableState {
{
PoolAddress.PoolKey memory poolKey =
PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: FEE});
pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
// compute the liquidity amount
{
@ -159,7 +173,7 @@ contract LiquidityManager is PeripheryImmutableState {
params.tickLower,
params.tickUpper,
liquidity,
abi.encode(PoolAddress.PoolKey(poolKey))
abi.encode(poolKey)
);
require(amount0 >= params.amount0Min && amount1 >= params.amount1Min, 'Price slippage check');
@ -171,7 +185,7 @@ contract LiquidityManager is PeripheryImmutableState {
(bool token0isWeth, address token) = getToken(params.token0, params.token1);
TokenPosition memory position = _positions[posKey(token, tickLower, tickUpper)];
TokenPosition memory position = _positions[posKey(token, params.tickLower, params.tickUpper)];
if (liquidity == 0) {
// create entry
position = TokenPosition({
@ -181,7 +195,7 @@ contract LiquidityManager is PeripheryImmutableState {
});
} else {
// update entry
updateTokensOwed(token0isWeth, token, uint128(
updateFeesOwed(token0isWeth, token, uint128(
FullMath.mulDiv(
feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
position.liquidity,
@ -200,26 +214,24 @@ contract LiquidityManager is PeripheryImmutableState {
position.liquidity += liquidity;
}
emit IncreaseLiquidity(tokenId, liquidity, amount0, amount1);
emit IncreaseLiquidity(token, liquidity, amount0, amount1);
}
function decreaseLiquidity(DecreaseLiquidityParams calldata params)
external
payable
override
checkDeadline(params.deadline)
returns (uint256 amount0, uint256 amount1)
{
require(params.liquidity > 0);
(bool token0isWeth, address token) = getToken(params.token0, params.token1);
TokenPosition memory position = _positions[posKey(token, tickLower, tickUpper)];
TokenPosition memory position = _positions[posKey(token, params.tickLower, params.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));
IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
(amount0, amount1) = pool.burn(params.tickLower, params.tickUpper, params.liquidity);
@ -229,7 +241,7 @@ contract LiquidityManager is PeripheryImmutableState {
// this is now updated to the current transaction
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);
updateTokensOwed(token0isWeth, token, uint128(amount0) +
updateFeesOwed(token0isWeth, token, uint128(amount0) +
uint128(
FullMath.mulDiv(
feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
@ -250,6 +262,6 @@ contract LiquidityManager is PeripheryImmutableState {
// subtraction is safe because we checked positionLiquidity is gte params.liquidity
position.liquidity = positionLiquidity - params.liquidity;
emit DecreaseLiquidity(params.tokenId, params.liquidity, amount0, amount1);
emit DecreaseLiquidity(token, params.liquidity, amount0, amount1);
}
}