wip
This commit is contained in:
parent
f749975c81
commit
5ce676f045
4 changed files with 188 additions and 56 deletions
|
|
@ -1,5 +1,5 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.20;
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@uniswap-v3-periphery/libraries/PositionKey.sol";
|
||||
import "@uniswap-v3-core/libraries/FixedPoint128.sol";
|
||||
|
|
@ -9,6 +9,7 @@ import "@aperture/uni-v3-lib/LiquidityAmounts.sol";
|
|||
import "@aperture/uni-v3-lib/PoolAddress.sol";
|
||||
import "@aperture/uni-v3-lib/CallbackValidation.sol";
|
||||
import "@openzeppelin/token/ERC20/IERC20.sol";
|
||||
import {ABDKMath64x64} from "abdk/ABDKMath64x64.sol";
|
||||
import "./interfaces/IWETH9.sol";
|
||||
import {Harb} from "./Harb.sol";
|
||||
|
||||
|
|
@ -18,8 +19,16 @@ import {Harb} from "./Harb.sol";
|
|||
* take strategic liqudity positions.
|
||||
*/
|
||||
contract BaseLineLP {
|
||||
int24 constant TICK_SPACING = 200;
|
||||
// default fee of 1%
|
||||
uint24 constant FEE = uint24(10_000);
|
||||
// uint256 constant FLOOR = 0;
|
||||
// uint256 constant ANCHOR = 1;
|
||||
// uint256 constant DISCOVERY = 2;
|
||||
|
||||
enum Stage { FLOOR, ANCHOR, DISCOVERY }
|
||||
|
||||
uint256 constant LIQUIDITY_RATIO_DIVISOR = 100;
|
||||
|
||||
// the address of the Uniswap V3 factory
|
||||
address immutable factory;
|
||||
|
|
@ -40,9 +49,7 @@ contract BaseLineLP {
|
|||
uint256 feeGrowthInside1LastX128;
|
||||
}
|
||||
|
||||
TokenPosition public floor;
|
||||
TokenPosition public anchor;
|
||||
TokenPosition public discovery;
|
||||
mapping(uint256 => TokenPosition) positions;
|
||||
|
||||
modifier checkDeadline(uint256 deadline) {
|
||||
require(block.timestamp <= deadline, "Transaction too old");
|
||||
|
|
@ -68,30 +75,25 @@ contract BaseLineLP {
|
|||
token0isWeth = _WETH9 < _harb;
|
||||
}
|
||||
|
||||
function posKey(int24 tickLower, int24 tickUpper) internal pure returns (bytes6 _posKey) {
|
||||
bytes memory _posKeyBytes = abi.encodePacked(tickLower, tickUpper);
|
||||
assembly {
|
||||
_posKey := mload(add(_posKeyBytes, 6))
|
||||
}
|
||||
}
|
||||
|
||||
function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata) external {
|
||||
CallbackValidation.verifyCallback(factory, poolKey);
|
||||
// ## mint harb if needed
|
||||
if (amount0Owed > 0) IERC20(poolKey.token0).transfer(msg.sender, amount0Owed);
|
||||
if (amount1Owed > 0) IERC20(poolKey.token1).transfer(msg.sender, amount1Owed);
|
||||
}
|
||||
|
||||
function createPosition(int24 tickLower, int24 tickUpper, uint256 amount) internal {
|
||||
function createPosition(uint256 positionIndex, int24 tickLower, int24 tickUpper, uint256 amount) internal {
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
||||
uint128 liquidity = LiquidityAmounts.getLiquidityForAmount1(
|
||||
sqrtRatioAX96, sqrtRatioBX96, amount / 20
|
||||
sqrtRatioAX96, sqrtRatioBX96, amount
|
||||
);
|
||||
pool.mint(address(this), tickLower, tickUpper, liquidity, abi.encode(poolKey));
|
||||
(uint256 amount0, uint256 amount1) = pool.mint(address(this), tickLower, tickUpper, liquidity, abi.encode(poolKey));
|
||||
// TODO: check slippage
|
||||
// read position and start tracking in storage
|
||||
bytes32 positionKey = PositionKey.compute(address(this), tickLower, tickUpper);
|
||||
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);
|
||||
discovery = TokenPosition({
|
||||
positions[positionIndex] = TokenPosition({
|
||||
liquidity: liquidity,
|
||||
tickLower: tickLower,
|
||||
tickUpper: tickUpper,
|
||||
|
|
@ -103,9 +105,9 @@ contract BaseLineLP {
|
|||
|
||||
// called once at the beginning
|
||||
function deployLiquidity(int24 startTick, uint256 amount) external {
|
||||
require(floor.liquidity == 0, "already set up");
|
||||
require(anchor.liquidity == 0, "already set up");
|
||||
require(discovery.liquidity == 0, "already set up");
|
||||
require(positions[Stage.FLOOR].liquidity == 0, "already set up");
|
||||
require(positions[Stage.ANCHOR].liquidity == 0, "already set up");
|
||||
require(positions[Stage.DISCOVERY].liquidity == 0, "already set up");
|
||||
harb.mint(amount);
|
||||
int24 tickLower;
|
||||
int24 tickUpper;
|
||||
|
|
@ -145,51 +147,177 @@ contract BaseLineLP {
|
|||
}
|
||||
|
||||
|
||||
function getInverseAmountsForLiquidity(
|
||||
uint160 sqrtRatioX96,
|
||||
uint160 sqrtRatioAX96,
|
||||
uint160 sqrtRatioBX96,
|
||||
uint128 liquidity
|
||||
) internal pure returns (uint256 amount0, uint256 amount1) {
|
||||
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
|
||||
function outstanding() public view returns (uint256 _outstanding) {
|
||||
harb.totalSupply() - harb.balanceOf(address(pool)) - harb.balanceOf(address(this));
|
||||
}
|
||||
|
||||
if (sqrtRatioX96 <= sqrtRatioAX96) {
|
||||
amount1 = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
|
||||
} else if (sqrtRatioX96 < sqrtRatioBX96) {
|
||||
amount1 = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity);
|
||||
amount0 = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity);
|
||||
function ethInAnchor() public view returns (uint256 _ethInAnchor) {
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(positions[Stage.ANCHOR].tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(positions[Stage.ANCHOR].tickUpper);
|
||||
if (token0isWeth) {
|
||||
_ethInAnchor = LiquidityAmounts.getAmount0ForLiquidity(
|
||||
sqrtRatioAX96, sqrtRatioBX96, positions[Stage.ANCHOR].liquidity
|
||||
);
|
||||
} else {
|
||||
amount0 = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
|
||||
_ethInAnchor = LiquidityAmounts.getAmount1ForLiquidity(
|
||||
sqrtRatioAX96, sqrtRatioBX96, positions[Stage.ANCHOR].liquidity
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function calculateCapacity() public view returns (uint256 capacity) {
|
||||
capacity = 0;
|
||||
(, int24 currentTick, , , , , ) = pool.slot0();
|
||||
uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
// handle floor
|
||||
/// @dev Calculates cycle tick from cycle price.
|
||||
function tickAtPrice(uint256 price) internal view returns (int24 tick_) {
|
||||
tick_ = TickMath.getTickAtSqrtRatio(
|
||||
uint160(int160(ABDKMath64x64.sqrt(int128(int256(price << 64))) << 32))
|
||||
);
|
||||
tick_ = tick_ / TICK_SPACING * TICK_SPACING;
|
||||
tick_ = token0isWeth ? tick_ : -tick_;
|
||||
}
|
||||
|
||||
// call this function when price has moved up 15%
|
||||
function shift() external {
|
||||
// Fetch the current tick from the Uniswap V3 pool
|
||||
(uint160 sqrtPriceX96, int24 currentTick, , , , , ) = pool.slot0();
|
||||
// TODO: check slippage with oracle
|
||||
// ## check price moved up
|
||||
{
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(floor.tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(floor.tickUpper);
|
||||
(uint256 amount0, uint256 amount1) = getInverseAmountsForLiquidity(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, floor.liquidity);
|
||||
capacity += token0isWeth ? amount1 : amount0;
|
||||
// Check if current tick is within the specified range
|
||||
int24 anchorTickLower = anchor.tickLower;
|
||||
int24 anchorTickUpper = anchor.tickUpper;
|
||||
// center tick can be calculated positive and negative numbers the same
|
||||
int24 centerTick = anchorTickLower + ((anchorTickUpper - anchorTickLower) / 2);
|
||||
int24 amplitudeTick = anchorTickLower + (anchorTickUpper - anchorTickLower) * 3 / 20;
|
||||
if (token0isWeth) {
|
||||
require(currentTick > centerTick, "call slide(), not shift()");
|
||||
// ### check price moved more than 15% of range
|
||||
require(currentTick > amplitudeTick, "amplitude not reached, come back later");
|
||||
} else {
|
||||
// ### check price moved up enough and add hysteresis
|
||||
require(currentTick < centerTick, "call slide(), not shift()");
|
||||
// ### check price moved more than 15% of range
|
||||
require(currentTick < amplitudeTick, "amplitude not reached, come back later");
|
||||
}
|
||||
}
|
||||
// ## scrape positions
|
||||
uint256 ethAmountAnchor;
|
||||
for (uint256 i=Stage.FLOOR; i <= Stage.DISCOVERY; i++) {
|
||||
position = positions[i];
|
||||
(uint256 amount0, uint256 amount1) = pool.burn(position.tickLower, position.tickUpper, position.liquidity);
|
||||
if (i == ANCHOR) {
|
||||
ethAmountAnchor = token0isWeth ? amount0 : amount1;
|
||||
}
|
||||
}
|
||||
// TODO: handle fees
|
||||
|
||||
// ## set new positions
|
||||
// move 10% of new ETH into Floor
|
||||
ethAmountAnchor -= (ethAmountAnchor - ethInAnchor()) * 10 / LIQUIDITY_RATIO_DIVISOR;
|
||||
|
||||
// ### set Floor position
|
||||
uint256 anchorLiquidity;
|
||||
{
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(currentTick - 300);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(currentTick + 300);
|
||||
if (token0isWeth) {
|
||||
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount0(
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethAmountAnchor
|
||||
);
|
||||
} else {
|
||||
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount1(
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethAmountAnchor
|
||||
);
|
||||
}
|
||||
(uint256 amount0, uint256 amount1) = pool.mint(address(this), currentTick - 300, currentTick + 300, liquidity, abi.encode(poolKey));
|
||||
bytes32 positionKey = PositionKey.compute(address(this), currentTick - 300, currentTick + 300);
|
||||
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);
|
||||
positions[Stage.ANCHOR] = TokenPosition({
|
||||
liquidity: anchorLiquidity,
|
||||
tickLower: currentTick - 300,
|
||||
tickUpper: currentTick + 300,
|
||||
feeGrowthInside0LastX128: feeGrowthInside0LastX128,
|
||||
feeGrowthInside1LastX128: feeGrowthInside1LastX128
|
||||
});
|
||||
}
|
||||
|
||||
// handle anchor
|
||||
// ### set Floor position
|
||||
{
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(anchor.tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(anchor.tickUpper);
|
||||
(uint256 amount0, uint256 amount1) = getInverseAmountsForLiquidity(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, anchor.liquidity);
|
||||
capacity += token0isWeth ? amount1 : amount0;
|
||||
uint256 ethAmountFloor = address(this).balance;
|
||||
int24 floorTick = tickAtPrice(outstanding() / ethAmountFloor);
|
||||
floorTick -= (currentTick - floorTick);
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(floorTick);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(currentTick - 300);
|
||||
uint256 liquidity = LiquidityAmounts.getLiquidityForAmounts(
|
||||
sqrtPriceX96,
|
||||
sqrtRatioAX96,
|
||||
sqrtRatioBX96,
|
||||
token0isWeth ? ethAmountFloor : 0,
|
||||
token0isWeth ? 0: ethAmountFloor
|
||||
);
|
||||
pool.mint(
|
||||
address(this),
|
||||
floorTick,
|
||||
currentTick - 300,
|
||||
liquidity,
|
||||
abi.encode(poolKey)
|
||||
);
|
||||
bytes32 positionKey = PositionKey.compute(address(this), floorTick, currentTick - 300);
|
||||
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);
|
||||
positions[Stage.FLOOR] = TokenPosition({
|
||||
liquidity: liquidity,
|
||||
tickLower: floorTick,
|
||||
tickUpper: currentTick - 300,
|
||||
feeGrowthInside0LastX128: feeGrowthInside0LastX128,
|
||||
feeGrowthInside1LastX128: feeGrowthInside1LastX128
|
||||
});
|
||||
}
|
||||
|
||||
// handle discovery
|
||||
// ##set discovery position
|
||||
{
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(discovery.tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(discovery.tickUpper);
|
||||
(uint256 amount0, uint256 amount1) = getInverseAmountsForLiquidity(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, discovery.liquidity);
|
||||
capacity += token0isWeth ? amount1 : amount0;
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(currentTick + 300);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(currentTick + 11300);
|
||||
// discovery with 1.5 times as much liquidity per tick as anchor
|
||||
uint256 liquidity = anchorLiquidity * 55 / 2;
|
||||
uint256 harbInDiscovery;
|
||||
if (token0isWeth) {
|
||||
harbInDiscovery = TickMath.getAmount1ForLiquidity(
|
||||
sqrtRatioAX96,
|
||||
sqrtRatioBX96,
|
||||
liquidity
|
||||
);
|
||||
} else {
|
||||
harbInDiscovery = TickMath.getAmount0ForLiquidity(
|
||||
sqrtRatioAX96,
|
||||
sqrtRatioBX96,
|
||||
liquidity
|
||||
);
|
||||
}
|
||||
pool.mint(
|
||||
address(this),
|
||||
currentTick + 300,
|
||||
currentTick + 11300,
|
||||
liquidity,
|
||||
abi.encode(poolKey)
|
||||
);
|
||||
bytes32 positionKey = PositionKey.compute(address(this), currentTick + 300, currentTick + 11300);
|
||||
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);
|
||||
positions[Stage.DISCOVERY] = TokenPosition({
|
||||
liquidity: liquidity,
|
||||
tickLower: floorTick,
|
||||
tickUpper: currentTick - 300,
|
||||
feeGrowthInside0LastX128: feeGrowthInside0LastX128,
|
||||
feeGrowthInside1LastX128: feeGrowthInside1LastX128
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function slide() external {
|
||||
// check price moved down
|
||||
// check price moved down enough
|
||||
// scrape positions
|
||||
// get outstanding upply and capacity
|
||||
// set new positions
|
||||
// burn harb
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
pragma solidity ^0.8.13;
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {IERC20} from "@openzeppelin/token/ERC20/ERC20.sol";
|
||||
import "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.13;
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "forge-std/console.sol";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue