LiqMan WIP
This commit is contained in:
parent
e51f49c612
commit
4e895f57e9
4 changed files with 106 additions and 116 deletions
|
|
@ -2,6 +2,7 @@ pragma solidity ^0.8.4;
|
|||
|
||||
import "forge-std/Script.sol";
|
||||
import {TwabController} from "pt-v5-twab-controller/TwabController.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol";
|
||||
import "../src/Harb.sol";
|
||||
import "../src/Stake.sol";
|
||||
|
||||
|
|
@ -21,9 +22,9 @@ contract SepoliaScript is Script {
|
|||
Harb harb = new Harb("Harberger Tax", "HARB", V3_FACTORY, WETH, tc);
|
||||
Stake stake = new Stake(address(harb));
|
||||
harb.setStakingPool(address(stake));
|
||||
factory = IUniswapV3Factory(V3_FACTORY);
|
||||
IUniswapV3Pool(factory.createPool(WETH, address(harb), FEE));
|
||||
liquidityManager = new LiquidityManager(V3_FACTORY, WETH, address(harb));
|
||||
IUniswapV3Factory factory = IUniswapV3Factory(V3_FACTORY);
|
||||
factory.createPool(WETH, address(harb), FEE);
|
||||
LiquidityManager liquidityManager = new LiquidityManager(V3_FACTORY, WETH, address(harb));
|
||||
harb.setLiquidityManager(address(liquidityManager));
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import "@aperture/uni-v3-lib/CallbackValidation.sol";
|
|||
import "@openzeppelin/token/ERC20/IERC20.sol";
|
||||
|
||||
/**
|
||||
* @title LiquidityManager - A contract that supports the $bloodX ecosystem. It
|
||||
* @title LiquidityManager - A contract that supports the harb ecosystem. It
|
||||
* protects the communities liquidity while allowing a manager role to
|
||||
* take strategic liqudity positions.
|
||||
*/
|
||||
|
|
@ -22,10 +22,14 @@ contract LiquidityManager {
|
|||
// the address of the Uniswap V3 factory
|
||||
address public immutable factory;
|
||||
// the address of WETH9
|
||||
address immutable WETH9;
|
||||
IWETH9 immutable weth;
|
||||
|
||||
Harb immutable harb;
|
||||
|
||||
IUniswapV3Pool public immutable pool;
|
||||
|
||||
bool immutable token0isWeth;
|
||||
|
||||
struct AddLiquidityParams {
|
||||
address token0;
|
||||
address token1;
|
||||
|
|
@ -52,14 +56,12 @@ contract LiquidityManager {
|
|||
struct TokenPosition {
|
||||
// the liquidity of the position
|
||||
uint128 liquidity;
|
||||
uint128 ethOwed;
|
||||
// the fee growth of the aggregate position as of the last action on the individual position
|
||||
uint256 feeGrowthInside0LastX128;
|
||||
uint256 feeGrowthInside1LastX128;
|
||||
}
|
||||
|
||||
// uint256 = uint128 tokensOwed + uint128 ethOwed
|
||||
mapping(address => uint256) private _feesOwed;
|
||||
|
||||
/// @dev The token ID position data
|
||||
mapping(bytes26 => TokenPosition) private _positions;
|
||||
|
||||
|
|
@ -79,26 +81,13 @@ contract LiquidityManager {
|
|||
/// @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, address harb) {
|
||||
constructor(address _factory, address _WETH9, address _harb) {
|
||||
factory = _factory;
|
||||
WETH9 = _WETH9;
|
||||
PoolKey memory poolKey = PoolAddress.getPoolKey(_WETH9, harb, FEE);
|
||||
weth = IWETH9(_WETH9);
|
||||
PoolKey memory poolKey = PoolAddress.getPoolKey(_WETH9, _harb, FEE);
|
||||
pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
|
||||
}
|
||||
|
||||
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) {
|
||||
uint256 feesOwed = _feesOwed[token];
|
||||
return (uint128(feesOwed >> 128), uint128(feesOwed));
|
||||
}
|
||||
|
||||
function getPool() public view returns (address) {
|
||||
return address(pool);
|
||||
harb = Harb(_harb);
|
||||
token0isWeth = _WETH9 < _harb;
|
||||
}
|
||||
|
||||
function updateFeesOwed(bool token0isEth, address token, uint128 tokensOwed0, uint128 tokensOwed1) internal {
|
||||
|
|
@ -108,20 +97,20 @@ contract LiquidityManager {
|
|||
_feesOwed[token] = uint256(tokensOwed) << 128 + ethOwed;
|
||||
}
|
||||
|
||||
function posKey(address token, int24 tickLower, int24 tickUpper) internal pure returns (bytes26 _posKey) {
|
||||
bytes memory _posKeyBytes = abi.encodePacked(token, tickLower, tickUpper);
|
||||
function posKey(int24 tickLower, int24 tickUpper) internal pure returns (bytes6 _posKey) {
|
||||
bytes memory _posKeyBytes = abi.encodePacked(tickLower, tickUpper);
|
||||
assembly {
|
||||
_posKey := mload(add(_posKeyBytes, 26))
|
||||
_posKey := mload(add(_posKeyBytes, 6))
|
||||
}
|
||||
}
|
||||
|
||||
function positions(address token, int24 tickLower, int24 tickUpper)
|
||||
function positions(int24 tickLower, int24 tickUpper)
|
||||
external
|
||||
view
|
||||
returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128)
|
||||
returns (uint128 liquidity, uint128 ethOwed, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128)
|
||||
{
|
||||
TokenPosition memory position = _positions[posKey(token, tickLower, tickUpper)];
|
||||
return (position.liquidity, position.feeGrowthInside0LastX128, position.feeGrowthInside1LastX128);
|
||||
TokenPosition memory position = _positions[posKey(tickLower, tickUpper)];
|
||||
return (position.liquidity, position.ethOwed, position.feeGrowthInside0LastX128, position.feeGrowthInside1LastX128);
|
||||
}
|
||||
|
||||
function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata data) external {
|
||||
|
|
@ -133,7 +122,7 @@ contract LiquidityManager {
|
|||
}
|
||||
|
||||
/// @notice Add liquidity to an initialized pool
|
||||
function addLiquidity(AddLiquidityParams memory params) external checkDeadline(params.deadline) {
|
||||
function addLiquidity(AddLiquidityParams memory params) internal checkDeadline(params.deadline) {
|
||||
|
||||
// compute the liquidity amount
|
||||
uint128 liquidity;
|
||||
|
|
@ -147,7 +136,7 @@ contract LiquidityManager {
|
|||
);
|
||||
}
|
||||
|
||||
(bool token0isWeth, address token) = getToken(params.token0, params.token1);
|
||||
|
||||
{
|
||||
PoolKey memory poolKey = PoolAddress.getPoolKey(params.token0, params.token1, FEE);
|
||||
(uint256 amount0, uint256 amount1) =
|
||||
|
|
@ -197,49 +186,40 @@ contract LiquidityManager {
|
|||
}
|
||||
}
|
||||
|
||||
function decreaseLiquidity(DecreaseLiquidityParams calldata params)
|
||||
external
|
||||
checkDeadline(params.deadline)
|
||||
returns (uint256 amount0, uint256 amount1)
|
||||
function liquidatePosition(int24 tickLower, int24 tickUpper, uint256 amount0Min, uint256 amount1Min)
|
||||
internal
|
||||
returns (uint256 ethReceived, uint256 liquidity)
|
||||
{
|
||||
require(params.liquidity > 0);
|
||||
// load position
|
||||
TokenPosition storage position = _positions[posKey(tickLower, tickUpper)];
|
||||
|
||||
(bool token0isWeth, address token) = getToken(params.token0, params.token1);
|
||||
TokenPosition memory position = _positions[posKey(token, params.tickLower, params.tickUpper)];
|
||||
uint128 positionLiquidity = position.liquidity;
|
||||
require(positionLiquidity >= params.liquidity);
|
||||
// burn and check slippage
|
||||
uint256 liquidity = position.liquidity;
|
||||
(uint256 amount0, uint256 amount1) = pool.burn(tickLower, tickUpper, liquidity);
|
||||
require(amount0 >= amount0Min && amount1 >= amount1Min, "Price slippage check");
|
||||
harb.burn(token0isWeth ? amount1 : amount0);
|
||||
|
||||
(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
|
||||
// calculate and transfer fees
|
||||
bytes32 positionKey = PositionKey.compute(address(this), tickLower, tickUpper);
|
||||
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);
|
||||
|
||||
updateFeesOwed(
|
||||
token0isWeth,
|
||||
token,
|
||||
uint128(amount0)
|
||||
+ uint128(
|
||||
FullMath.mulDiv(
|
||||
feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128, positionLiquidity, FixedPoint128.Q128
|
||||
)
|
||||
),
|
||||
uint128(amount1)
|
||||
+ uint128(
|
||||
FullMath.mulDiv(
|
||||
feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128, positionLiquidity, FixedPoint128.Q128
|
||||
)
|
||||
)
|
||||
uint256 ethOwed = position.ethOwed;
|
||||
ethOwed +=
|
||||
FullMath.mulDiv(
|
||||
(token0isWeth) ? feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128 : feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
|
||||
liquidity,
|
||||
FixedPoint128.Q128
|
||||
);
|
||||
weth.withdraw(ethOwed);
|
||||
(bool sent, ) = feeRecipient.call{value: ethOwed}("");
|
||||
require(sent, "Failed to send Ether");
|
||||
|
||||
position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
|
||||
position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
|
||||
// subtraction is safe because we checked positionLiquidity is gte params.liquidity
|
||||
position.liquidity = positionLiquidity - params.liquidity;
|
||||
|
||||
emit DecreaseLiquidity(token, params.liquidity, amount0, amount1);
|
||||
// event, cleanup and return
|
||||
ethReceived = token0isWeth ? amount0 - ethOwed : amount1 - ethOwed;
|
||||
emit PositionLiquidated(tickLower, tickUpper, liquidity, ethReceived);
|
||||
delete position.liquidity;
|
||||
delete position.ethOwed;
|
||||
delete position.feeGrowthInside0LastX128;
|
||||
delete position.feeGrowthInside1LastX128;
|
||||
}
|
||||
|
||||
// function compareTokenToEthBalance(uint256 ethAmountInPosition, uint256 tokenAmountInPosition) external view returns (bool hasMoreToken) {
|
||||
|
|
@ -272,52 +252,52 @@ contract LiquidityManager {
|
|||
// - withdraw
|
||||
// - burn tokens
|
||||
|
||||
// function rebalance(address token, int24 tickLower, int24 tickUpper) external {
|
||||
// bool ETH_TOKEN_ZERO = WETH9 < token;
|
||||
|
||||
// PoolKey memory poolKey = PoolAddress.getPoolKey(params.token0, params.token1, FEE);
|
||||
// IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
|
||||
|
||||
// // Fetch the current tick from the Uniswap V3 pool
|
||||
// (, int24 currentTick, , , , , ) = pool.slot0();
|
||||
function stretch(int24 tickLower, int24 tickUpper, uint256 deadline, uint256 amount0Min, uint256 amount1Min) external checkDeadline(deadline) {
|
||||
|
||||
// // Check if current tick is within the specified range
|
||||
// require(currentTick >= tickLower && currentTick <= tickUpper, "Current tick out of range");
|
||||
// Fetch the current tick from the Uniswap V3 pool
|
||||
(, int24 currentTick, , , , , ) = pool.slot0();
|
||||
|
||||
// // load position
|
||||
// TokenPosition memory position = _positions[posKey(token, tickLower, tickUpper)];
|
||||
// Check if current tick is within the specified range
|
||||
int24 centerTick = tickLower + ((tickUpper - tickLower) / 2);
|
||||
// TODO: add hysteresis
|
||||
if (token0isWeth) {
|
||||
require(currentTick > centerTick && currentTick <= tickUpper, "Current tick out of range for stretch");
|
||||
} else {
|
||||
require(currentTick >= tickLower && currentTick < centerTick, "Current tick out of range for stretch");
|
||||
}
|
||||
|
||||
// // take the position out
|
||||
// uint256 (amount0, amount1) = pool.burn(tickLower, tickUpper, position.liquidity);
|
||||
// // TODO: this position might have earned fees, update them here
|
||||
(uint256 ethReceived, uint256 oldliquidity) = liquidatePosition(tickLower, tickUpper, amount0Min, amount1Min);
|
||||
|
||||
// // calculate liquidity
|
||||
// uint128 liquidity;
|
||||
// if (ETH_TOKEN_ZERO) {
|
||||
// // extend/contract the range up
|
||||
// uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
||||
// uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(currentTick + (currentTick - tickLower));
|
||||
// liquidity = LiquidityAmounts.getLiquidityForAmount0(
|
||||
// sqrtRatioAX96, sqrtRatioBX96, amount0
|
||||
// );
|
||||
// // calculate amount for new liquidity
|
||||
// uint256 newAmount1 = LiquidityAmounts.getAmount1ForLiquidity(
|
||||
// sqrtRatioAX96, sqrtRatioBX96, liquidity
|
||||
// );
|
||||
// if (newAmount1 > amount1) {
|
||||
// IERC20(token).mint(address(this), newAmount1 - amount1 + 1);
|
||||
// } else {
|
||||
// IERC20(token).burn(address(this), amount1 - newAmount1 + 1);
|
||||
// }
|
||||
uint256 liquidity;
|
||||
if (token0isWeth) {
|
||||
// extend the range up
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(currentTick + (currentTick - tickLower));
|
||||
liquidity = LiquidityAmounts.getLiquidityForAmount0(
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethReceived
|
||||
);
|
||||
// calculate amount for new liquidity
|
||||
uint256 newAmount1 = LiquidityAmounts.getAmount1ForLiquidity(
|
||||
sqrtRatioAX96, sqrtRatioBX96, liquidity
|
||||
);
|
||||
harb.mint(address(this), newAmount1);
|
||||
// TODO: addLiquidity(...)
|
||||
} else {
|
||||
// extend the range down
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(currentTick - (tickUpper - currentTick));
|
||||
liquidity = LiquidityAmounts.getLiquidityForAmount1(
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethReceived
|
||||
);
|
||||
// calculate amount for new liquidity
|
||||
uint256 newAmount0 = LiquidityAmounts.getAmount1ForLiquidity(
|
||||
sqrtRatioAX96, sqrtRatioBX96, liquidity
|
||||
);
|
||||
harb.mint(address(this), newAmount0);
|
||||
// TODO: addLiquidity(...)
|
||||
}
|
||||
}
|
||||
|
||||
// } else {
|
||||
// // extend/contract the range down
|
||||
// uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
||||
// uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(currentTick - (tickUpper - currentTick));
|
||||
// liquidity = LiquidityAmounts.getLiquidityForAmount1(
|
||||
// sqrtRatioAX96, sqrtRatioBX96, ethAmountToProvide
|
||||
// );
|
||||
// }
|
||||
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import {Math} from "@openzeppelin/utils/math/Math.sol";
|
|||
import "./interfaces/IStake.sol";
|
||||
import "./Harb.sol";
|
||||
|
||||
error ExceededAvailableStake(address receiver, uint256 stakeWanted, uint256 availableStake);
|
||||
|
||||
contract Stake is IStake {
|
||||
using Math for uint256;
|
||||
|
||||
|
|
@ -21,7 +23,6 @@ contract Stake is IStake {
|
|||
* @dev Attempted to deposit more assets than the max amount for `receiver`.
|
||||
*/
|
||||
|
||||
error ExceededAvailableStake(address receiver, uint256 stakeWanted, uint256 availableStake);
|
||||
error TaxTooLow(address receiver, uint64 taxRateWanted, uint64 taxRateMet, uint256 positionId);
|
||||
error SharesTooLow(address receiver, uint256 assets, uint256 sharesWanted, uint256 minStake);
|
||||
error NoPermission(address requester, address owner);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol";
|
|||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "./interfaces/IWETH9.sol";
|
||||
import "../src/Harb.sol";
|
||||
import "../src/Stake.sol";
|
||||
import {Stake, ExceededAvailableStake} from "../src/Stake.sol";
|
||||
|
||||
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
||||
address constant V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
|
||||
|
|
@ -32,7 +32,7 @@ contract HarbTest is Test {
|
|||
TwabController tc = new TwabController(60 * 60 * 24, uint32(block.timestamp));
|
||||
harb = new Harb("HARB", "HARB", V3_FACTORY, WETH, tc);
|
||||
factory = IUniswapV3Factory(V3_FACTORY);
|
||||
IUniswapV3Pool(factory.createPool(address(weth), address(harb), FEE));
|
||||
factory.createPool(address(weth), address(harb), FEE);
|
||||
stake = new Stake(address(harb));
|
||||
harb.setStakingPool(address(stake));
|
||||
liquidityManager = new LiquidityManager(V3_FACTORY, WETH, address(harb));
|
||||
|
|
@ -95,6 +95,14 @@ contract HarbTest is Test {
|
|||
assertEq(taxRate, 1, "tax rate should match");
|
||||
}
|
||||
|
||||
// test stake when stake full
|
||||
{
|
||||
uint256[] memory empty;
|
||||
vm.prank(account);
|
||||
vm.expectRevert(ExceededAvailableStake.selector);
|
||||
stake.snatch(amount, account, 2, empty);
|
||||
}
|
||||
|
||||
// test unstake
|
||||
{
|
||||
// advance the time
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue