This commit is contained in:
JulesCrown 2024-04-11 07:28:54 +02:00
parent 2b3b981bfa
commit d9ee15f812
3 changed files with 156 additions and 65 deletions

View file

@ -80,13 +80,23 @@ contract BaseLineLP {
token0isWeth = _WETH9 < _harb;
}
event UniCallback(uint256 indexed amount0Owed, uint256 indexed amount1Owed);
function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata) external {
CallbackValidation.verifyCallback(factory, poolKey);
// take care of harb
harb.mint(token0isWeth ? amount1Owed : amount0Owed);
// pack ETH
weth.deposit{value: token0isWeth ? amount0Owed : amount1Owed}();
// ## mint harb if needed
if (amount0Owed > 0) IERC20(poolKey.token0).transfer(msg.sender, amount0Owed);
if (amount1Owed > 0) IERC20(poolKey.token1).transfer(msg.sender, amount1Owed);
}
receive() external payable {
}
function createPosition(Stage positionIndex, int24 tickLower, int24 tickUpper, uint256 amount) internal {
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
@ -108,50 +118,6 @@ contract BaseLineLP {
}
// called once at the beginning
function deployLiquidity(int24 startTick, uint256 amount) external {
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;
// create floor
if (token0isWeth) {
tickLower = startTick;
tickUpper = startTick + 200;
createPosition(Stage.FLOOR, tickLower, tickUpper, amount / 10);
} else {
tickLower = startTick - 200;
tickUpper = startTick;
createPosition(Stage.FLOOR, tickLower, tickUpper, amount / 10);
}
// create anchor
if (token0isWeth) {
tickLower += 201;
tickUpper += 601;
createPosition(Stage.ANCHOR, tickLower, tickUpper, amount / 20);
} else {
tickLower -= 601;
tickUpper -= 201;
createPosition(Stage.ANCHOR, tickLower, tickUpper, amount / 10);
}
// create discovery
if (token0isWeth) {
tickLower += 601;
tickUpper += 11001;
createPosition(Stage.DISCOVERY, tickLower, tickUpper, harb.balanceOf(address(this)));
} else {
tickLower -= 11001;
tickUpper -= 601;
createPosition(Stage.DISCOVERY, tickLower, tickUpper, harb.balanceOf(address(this)));
}
}
function outstanding() public view returns (uint256 _outstanding) {
_outstanding = harb.totalSupply() - harb.balanceOf(address(pool)) - harb.balanceOf(address(this));
}
@ -174,18 +140,25 @@ contract BaseLineLP {
}
}
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
function tickAtPrice(uint256 tokenAmount, uint256 ethAmount) internal view returns (int24 tick_) {
require(ethAmount > 0, "ETH amount cannot be zero");
// Use a fixed-point library or more precise arithmetic for the division here.
// For example, using ABDKMath64x64 for a more precise division and square root calculation.
int128 priceRatio = ABDKMath64x64.div(
int128(int256(tokenAmount)),
int128(int256(ethAmount))
);
// Convert the price ratio into a sqrt price in the format expected by Uniswap's TickMath.
uint160 sqrtPriceX96 = uint160(
int160(ABDKMath64x64.sqrt(priceRatio) << 32)
);
uint160 sqrtPriceX96;
if (tokenAmount == 0) {
sqrtPriceX96 = MIN_SQRT_RATIO;
} else {
// Use a fixed-point library or more precise arithmetic for the division here.
// For example, using ABDKMath64x64 for a more precise division and square root calculation.
int128 priceRatio = ABDKMath64x64.div(
int128(int256(tokenAmount)),
int128(int256(ethAmount))
);
// Convert the price ratio into a sqrt price in the format expected by Uniswap's TickMath.
sqrtPriceX96 = uint160(
int160(ABDKMath64x64.sqrt(priceRatio) << 32)
);
}
// Proceed as before.
tick_ = TickMath.getTickAtSqrtRatio(sqrtPriceX96);
tick_ = tick_ / TICK_SPACING * TICK_SPACING;
@ -235,20 +208,22 @@ contract BaseLineLP {
return amount;
}
event FLOOR_TICK(int24 indexed floorTick, int24 indexed startTick);
function _set(uint160 sqrtPriceX96, int24 currentTick, uint256 ethInNewAnchor) internal {
// ### set Anchor position
uint128 anchorLiquidity;
{
int24 tickLower = currentTick - 300;
int24 tickUpper = currentTick + 300;
int24 tickLower = currentTick - 400;
int24 tickUpper = currentTick + 400;
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
if (token0isWeth) {
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount0(
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount1(
sqrtRatioAX96, sqrtRatioBX96, ethInNewAnchor
);
} else {
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount1(
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount0(
sqrtRatioAX96, sqrtRatioBX96, ethInNewAnchor
);
}
@ -257,14 +232,16 @@ contract BaseLineLP {
// ### set Floor position
{
int24 startTick = token0isWeth ? currentTick - 301 : currentTick + 301;
int24 startTick = token0isWeth ? currentTick - 400 : currentTick + 400;
// all remaining eth will be put into this position
uint256 ethInFloor = address(this).balance;
// calculate price at which all HARB can be bought back
int24 floorTick = tickAtPrice(outstanding(), ethInFloor);
emit FLOOR_TICK(floorTick, startTick);
// put a position symetrically around the price, startTick being edge on one side
floorTick = token0isWeth ? floorTick - (startTick - floorTick) : startTick + (floorTick - startTick);
emit FLOOR_TICK(floorTick, startTick);
// calculate liquidity
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(floorTick);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(startTick);
@ -281,8 +258,8 @@ contract BaseLineLP {
// ## set Discovery position
{
int24 tickLower = token0isWeth ? currentTick + 301 : currentTick - 11301;
int24 tickUpper = token0isWeth ? currentTick + 11301 : currentTick - 301;
int24 tickLower = token0isWeth ? currentTick + 401 : currentTick - 11401;
int24 tickUpper = token0isWeth ? currentTick + 11401 : currentTick - 401;
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
// discovery with 1.5 times as much liquidity per tick as anchor
@ -316,9 +293,10 @@ contract BaseLineLP {
if(amount < harbInDiscovery) {
// calculate new ticks so that discovery liquidity is still
// deeper than anchor, but less wide
int24 tickWidth = int24(int256(11000 * amount / harbInDiscovery)) + 301;
tickLower = token0isWeth ? currentTick + 301 : currentTick - tickWidth;
tickUpper = token0isWeth ? currentTick + tickWidth : currentTick - 301;
int24 tickWidth = int24(int256(11000 * amount / harbInDiscovery)) + 401;
tickWidth = tickWidth / TICK_SPACING * TICK_SPACING;
tickLower = token0isWeth ? currentTick + 401 : currentTick - tickWidth;
tickUpper = token0isWeth ? currentTick + tickWidth : currentTick - 401;
sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
liquidity = LiquidityAmounts.getLiquidityForAmounts(
@ -423,11 +401,15 @@ contract BaseLineLP {
uint256 ethInFloor = ethIn(Stage.FLOOR);
// use previous ration of Floor to Anchor
uint256 ethInNewAnchor = ethBalance * ethInAnchor / (ethInAnchor + ethInFloor);
uint256 ethInNewAnchor = ethBalance / 10;
if (ethInFloor > 0) {
ethInNewAnchor = ethBalance * ethInAnchor / (ethInAnchor + ethInFloor);
}
// but cap anchor size at 10 % of total ETH
ethInNewAnchor = (ethInNewAnchor > ethBalance / 10) ? ethBalance / 10 : ethInNewAnchor;
currentTick = currentTick / TICK_SPACING * TICK_SPACING;
_set(sqrtPriceX96, currentTick, ethInNewAnchor);
}

View file

@ -4,6 +4,7 @@ pragma solidity ^0.8.19;
import {IERC20} from "@openzeppelin/token/ERC20/ERC20.sol";
import "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/utils/math/Math.sol";
import "./interfaces/IStake.sol";
@ -72,6 +73,25 @@ contract Stake is IStake {
return shares.mulDiv(tokenContract.totalSupply(), totalSupply, rounding);
}
function permitAndSnatch(
uint256 assets,
address receiver,
uint32 taxRate,
uint256[] calldata positionsToSnatch,
// address owner,
// address spender,
// uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external
returns (uint256 positionId)
{
ERC20Permit(address(tokenContract)).permit(receiver, address(this), assets, deadline, v, r, s);
return snatch(assets, receiver, taxRate, positionsToSnatch);
}
/**
* TODO: deal with metatransactions: While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct

View file

@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import {TwabController} from "pt-v5-twab-controller/TwabController.sol";
import {PoolAddress, PoolKey} from "@aperture/uni-v3-lib/PoolAddress.sol";
import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol";
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
import "../src/interfaces/IWETH9.sol";
import "../src/Harb.sol";
import {BaseLineLP} from "../src/BaseLineLP.sol";
import {Stake, ExceededAvailableStake} from "../src/Stake.sol";
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address constant V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
address constant TAX_POOL = address(2);
// default fee of 1%
uint24 constant FEE = uint24(10_000);
contract BaseLineLPTest is Test {
uint256 mainnetFork;
IWETH9 weth;
Harb harb;
IUniswapV3Factory factory;
Stake stake;
BaseLineLP liquidityManager;
function sqrt(uint256 y) internal pure returns (uint256 z) {
if (y > 3) {
z = y;
uint256 x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
// z is now the integer square root of y, or the closest integer to the square root of y.
}
function initializePoolFor1Cent(bool isEthToken0, address pool) public {
uint256 price;
if (isEthToken0) {
// ETH as token0, so we are setting the price of 1 ETH in terms of token1 (USD cent)
price = 3700 * 10**20; // 1 ETH = 3700 USD, scaled by 10^18 for precision
} else {
// Token (valued at 1 USD cent) as token0, ETH as token1
// We invert the logic to represent the price of 1 token in terms of ETH
price = uint256(10**16) / 3700; // Adjust for 18 decimal places
}
uint160 sqrtPriceX96 = uint160(sqrt(price) * 2**96 / 10**18); // Adjust sqrt value to 96-bit precision
// Initialize pool with the calculated sqrtPriceX96
IUniswapV3Pool(pool).initialize(sqrtPriceX96);
}
function setUp() public {
mainnetFork = vm.createFork(vm.envString("ETH_NODE_URI_MAINNET"), 19615864);
vm.selectFork(mainnetFork);
weth = IWETH9(WETH);
TwabController tc = new TwabController(60 * 60 * 24, uint32(block.timestamp));
harb = new Harb("HARB", "HARB", V3_FACTORY, WETH, tc);
factory = IUniswapV3Factory(V3_FACTORY);
address pool = factory.createPool(address(weth), address(harb), FEE);
initializePoolFor1Cent(address(weth) < address(harb), pool);
stake = new Stake(address(harb));
harb.setStakingPool(address(stake));
liquidityManager = new BaseLineLP(V3_FACTORY, WETH, address(harb));
harb.setLiquidityManager(address(liquidityManager));
}
function testLP(address account, uint256 amount) public {
vm.deal(account, 10 ether);
vm.prank(account);
(bool sent, ) = address(liquidityManager).call{value: 10 ether}("");
require(sent, "Failed to send Ether");
vm.expectRevert();
liquidityManager.shift();
int24 startTick = (address(weth) < address(harb)) ? int24(128219) : int24(-128219); //initialize at 1 cent
liquidityManager.slide();
}
}