This commit is contained in:
JulesCrown 2024-07-13 14:56:13 +02:00
parent 969d78247c
commit ba298cfd50
4 changed files with 38 additions and 47 deletions

View file

@ -20,7 +20,7 @@ import {Harb} from "./Harb.sol";
* It maintains 3 positions:
* - The floor position guarantees the capacity needed to maintain a minimum price of the HARB token It is a very tight liquidity range with enough reserve assets to buy back the circulating supply.
* - The anchor range provides liquidity around the current market price, ensuring liquid trading conditions for the token, regardless of the market environment.
* - The discovery range starts 500 ticks above the current market price and increases from there. It consists solely of unissued tokens, which are sold as the market price increases.
* - The discovery range starts 1000 ticks above the current market price and increases from there. It consists solely of unissued tokens, which are sold as the market price increases.
* The liquidity surplus obtained from selling tokens in the discovery range is directed back into the floor and anchor positions.
*/
contract BaseLineLP {
@ -36,14 +36,12 @@ contract BaseLineLP {
enum Stage { FLOOR, ANCHOR, DISCOVERY }
uint256 constant LIQUIDITY_RATIO_DIVISOR = 100;
// the address of the Uniswap V3 factory
address immutable factory;
IWETH9 immutable weth;
Harb immutable harb;
IUniswapV3Pool immutable pool;
bool immutable token0isWeth;
address private immutable factory;
IWETH9 private immutable weth;
Harb private immutable harb;
IUniswapV3Pool private immutable pool;
bool private immutable token0isWeth;
PoolKey private poolKey;
struct TokenPosition {
@ -59,6 +57,9 @@ contract BaseLineLP {
mapping(Stage => TokenPosition) public positions;
address public feeDestination;
error ZeroAddressInSetter();
error AddressAlreadySet();
// TODO: add events
constructor(address _factory, address _WETH9, address _harb) {
@ -84,13 +85,9 @@ contract BaseLineLP {
if (amount1Owed > 0) IERC20(poolKey.token1).transfer(msg.sender, amount1Owed);
}
function liquidityPool() external view returns (address) {
return address(pool);
}
function setFeeDestination(address feeDestination_) external {
// TODO: add trapdoor
require(address(0) != feeDestination_, "zero addr");
if (address(0) == feeDestination_) revert ZeroAddressInSetter();
if (feeDestination != address(0)) revert AddressAlreadySet();
feeDestination = feeDestination_;
}
@ -99,10 +96,6 @@ contract BaseLineLP {
}
function outstanding() public view returns (uint256 _outstanding) {
_outstanding = (harb.totalSupply() - harb.balanceOf(address(pool)) - harb.balanceOf(address(this)));
}
function tickAtPrice(bool t0isWeth, uint256 tokenAmount, uint256 ethAmount) internal pure returns (int24 tick_) {
require(ethAmount > 0, "ETH amount cannot be zero");
uint160 sqrtPriceX96;
@ -153,7 +146,7 @@ contract BaseLineLP {
// ### set Floor position
int24 vwapTick;
{
uint256 outstandingSupply = outstanding();
uint256 outstandingSupply = harb.outstandingSupply();
uint256 vwap = 0;
uint256 requiredEthForBuyback = 0;
if (cumulativeVolume > 0) {
@ -174,6 +167,7 @@ contract BaseLineLP {
} else {
vwap = cumulativeVolumeWeightedPrice * CAPITAL_INEFFICIENCY / 100 / cumulativeVolume; // in harb/eth
vwapTick = tickAtPrice(token0isWeth, token0isWeth ? vwap : 10**18, token0isWeth ? 10**18 : vwap);
vwapTick = token0isWeth ? vwapTick : -vwapTick;
if (requiredEthForBuyback < ethBalance) {
// invest a majority of the ETH still in floor, even though not needed
requiredEthForBuyback = (requiredEthForBuyback + (5 * ethBalance)) / 6;
@ -282,7 +276,6 @@ contract BaseLineLP {
}
}
}
// Transfer fees to the fee destination
// and record transaction totals
@ -324,17 +317,15 @@ contract BaseLineLP {
averageTick = int24(tickCumulativeDiff / int56(int32(timeInterval)));
// Process the data
} catch {
// Handle the error, possibly by trying with a different time interval or providing a default response
// TODO: Handle the error, possibly by trying with a different time interval or providing a default response
return true;
}
return (currentTick >= averageTick - MAX_TICK_DEVIATION && currentTick <= averageTick + MAX_TICK_DEVIATION);
}
// call this function when price has moved up 15%
// call this function when price has moved up x%
// TODO: write a bot that calls this function regularly
event DEBUG(int24 a, int24 b, int24 c, int24 d);
function shift() external {
require(positions[Stage.ANCHOR].liquidity > 0, "Not initialized");
// Fetch the current tick from the Uniswap V3 pool
@ -349,7 +340,6 @@ contract BaseLineLP {
int24 anchorTickUpper = positions[Stage.ANCHOR].tickUpper;
int24 centerTick = token0isWeth ? anchorTickLower + ANCHOR_SPACING : anchorTickUpper - ANCHOR_SPACING;
emit DEBUG(anchorTickLower, anchorTickUpper, centerTick, currentTick);
uint256 minAmplitude = uint256(uint24((anchorTickUpper - anchorTickLower) * 3 / 20));
// Determine the correct comparison direction based on token0isWeth

View file

@ -130,6 +130,10 @@ contract Harb is ERC20, ERC20Permit {
return twabController.totalSupply(address(this));
}
function outstandingSupply() public view returns (uint256) {
return totalSupply() - balanceOf(liquidityPool) - balanceOf(liquidityManager);
}
/* ============ Internal ERC20 Overrides ============ */
/**

View file

@ -208,7 +208,7 @@ contract Stake {
require(taxRate > pos.taxRate, "tax too low to snatch");
_payTax(positionId, pos, TAX_FLOOR_DURATION);
pos.taxRate = taxRate;
emit PositionRateHiked(positionId, pos.owner, taxRate);
}
function exitPosition(uint256 positionId) public {

View file

@ -70,7 +70,7 @@ contract BaseLineLP2Test is Test {
} 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
price = uint256(10**16) / 3000; // Adjust for 18 decimal places
}
uint160 sqrtPriceX96 = uint160(sqrt(price) * 2**96 / 10**9); // Adjust sqrt value to 96-bit precision
@ -131,7 +131,7 @@ contract BaseLineLP2Test is Test {
createCSVHeader();
}
function slide() internal {
function slide(bool last) internal {
// have some time pass to record prices in uni oracle
uint256 timeBefore = block.timestamp;
vm.warp(timeBefore + (60 * 60 * 5));
@ -148,7 +148,9 @@ contract BaseLineLP2Test is Test {
if (keccak256(abi.encodePacked(reason)) == keccak256(abi.encodePacked("amplitude not reached, diamond hands!"))) {
console.log("slide failed on amplitude");
} else {
revert(reason); // Rethrow the error if it's not the expected message
if (!last) {
revert(reason); // Rethrow the error if it's not the expected message
}
}
}
}
@ -158,7 +160,7 @@ contract BaseLineLP2Test is Test {
uint256 timeBefore = block.timestamp;
vm.warp(timeBefore + (60 * 60 * 5));
lm.shift();
//lm.shift();
try lm.shift() {
// Check liquidity positions after shift
(uint256 ethFloor, uint256 ethAnchor, uint256 ethDiscovery, uint256 harbFloor, uint256 harbAnchor, uint256 harbDiscovery) = checkLiquidityPositionsAfter("shift");
@ -240,15 +242,12 @@ contract BaseLineLP2Test is Test {
return x >= 0 ? uint(x) : uint(-x);
}
event DEBUG(uint256 a, bool);
function buy(uint256 amountEth) internal {
emit DEBUG(amountEth, true);
performSwap(amountEth, true);
checkLiquidityPositionsAfter(string.concat("buy ", uintToStr(amountEth)));
}
function sell(uint256 amountHarb) internal {
emit DEBUG(amountHarb, false);
performSwap(amountHarb, false);
checkLiquidityPositionsAfter(string.concat("sell ", uintToStr(amountHarb)));
}
@ -434,15 +433,17 @@ contract BaseLineLP2Test is Test {
// uint256 traderBalanceBefore = weth.balanceOf(account);
// // Setup initial liquidity
// slide();
// slide(false);
// buy(100 ether);
// buy(200 ether);
// shift();
// //revert();
// sell(harb.balanceOf(account));
// slide();
// slide(true);
// writeCsv();
@ -461,7 +462,7 @@ contract BaseLineLP2Test is Test {
// uint256 traderBalanceBefore = weth.balanceOf(account);
// // Setup initial liquidity
// slide();
// slide(false);
// buy(2 ether);
@ -477,7 +478,7 @@ contract BaseLineLP2Test is Test {
// sell(harb.balanceOf(account));
// slide();
// slide(true);
// writeCsv();
// uint256 traderBalanceAfter = weth.balanceOf(account);
@ -486,28 +487,26 @@ contract BaseLineLP2Test is Test {
// assertGt(traderBalanceBefore, traderBalanceAfter, "trader should not have made profit");
// }
event DEBUG2(int24 a, int24 b);
function testScenarioFuzz(uint8 numActions, uint8 frequency, uint8[] calldata amounts) public {
vm.assume(numActions > 5);
vm.assume(frequency > 0);
vm.assume(frequency < 20);
vm.assume(amounts.length >= numActions);
setUpCustomToken0(true);
setUpCustomToken0(numActions % 2 == 0 ? true : false);
vm.deal(account, 100 ether);
vm.prank(account);
weth.deposit{value: 100 ether}();
// Setup initial liquidity
slide();
slide(false);
uint256 traderBalanceBefore = weth.balanceOf(account);
uint8 f = 0;
for (uint i = 0; i < numActions; i++) {
uint256 amount = (uint256(amounts[i]) * 1 ether) + 1 ether;
uint256 harbBal = harb.balanceOf(account);
emit DEBUG(i, true);
if (harbBal == 0) {
amount = amount % (weth.balanceOf(account) / 2);
amount = amount == 0 ? weth.balanceOf(account) : amount;
@ -528,14 +527,12 @@ contract BaseLineLP2Test is Test {
(, int24 currentTick, , , , , ) = pool.slot0();
(, int24 tickLower, int24 tickUpper) = lm.positions(BaseLineLP.Stage.ANCHOR);
int24 midTick = token0isWeth ? tickLower + ANCHOR_SPACING : tickUpper - ANCHOR_SPACING;
emit DEBUG2(tickLower, tickUpper);
emit DEBUG2(currentTick, midTick);
if (currentTick < midTick) {
// Current tick is below the midpoint, so call slide()
token0isWeth ? shift(): slide();
token0isWeth ? shift(): slide(false);
} else if (currentTick > midTick) {
// Current tick is above the midpoint, so call shift()
token0isWeth ? slide(): shift();
token0isWeth ? slide(false): shift();
}
f = 0;
} else {
@ -546,7 +543,7 @@ contract BaseLineLP2Test is Test {
// Simulate large sell to push price down to floor
sell(harb.balanceOf(account));
slide();
slide(true);
uint256 traderBalanceAfter = weth.balanceOf(account);