more bugs out

This commit is contained in:
JulesCrown 2024-07-09 18:00:39 +02:00
parent b903c88ee9
commit 8c0a45a0f3
4 changed files with 94 additions and 531 deletions

View file

@ -24,15 +24,15 @@ import {Harb} from "./Harb.sol";
* The liquidity surplus obtained from selling tokens in the discovery range is directed back into the floor and anchor positions.
*/
contract BaseLineLP {
int24 constant TICK_SPACING = 200;
int24 constant ANCHOR_SPACING = 5 * TICK_SPACING;
int24 constant DISCOVERY_SPACING = 11000;
int24 constant MAX_TICK_DEVIATION = 50; // how much is that?
int24 internal constant TICK_SPACING = 200;
int24 internal constant ANCHOR_SPACING = 5 * TICK_SPACING;
int24 internal constant DISCOVERY_SPACING = 11000;
int24 internal constant MAX_TICK_DEVIATION = 50; // how much is that?
// default fee of 1%
uint24 constant FEE = uint24(10_000);
// uint256 constant FLOOR = 0;
// uint256 constant ANCHOR = 1;
// uint256 constant DISCOVERY = 2;
uint24 internal constant FEE = uint24(10_000);
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
uint256 internal constant ANCHOR_LIQ_SHARE = 5; // 5%
uint256 internal constant CAPITAL_INEFFICIENCY = 120; // 20%
enum Stage { FLOOR, ANCHOR, DISCOVERY }
@ -43,9 +43,8 @@ contract BaseLineLP {
IWETH9 immutable weth;
Harb immutable harb;
IUniswapV3Pool immutable pool;
PoolKey private poolKey;
bool immutable token0isWeth;
PoolKey private poolKey;
struct TokenPosition {
// the liquidity of the position
@ -54,35 +53,13 @@ contract BaseLineLP {
int24 tickUpper;
}
// for minting limits
uint256 private lastDay;
uint256 private mintedToday;
uint256 constant ANCHOR_LIQ_SHARE = 5; // 5%
uint256 constant CAPITAL_INEFFICIENCY = 120;
// State variables to track total ETH spent
uint256 public cumulativeVolumeWeightedPrice;
uint256 public cumulativeVolume;
mapping(Stage => TokenPosition) public positions;
address public feeDestination;
address private feeDestination;
modifier checkDeadline(uint256 deadline) {
require(block.timestamp <= deadline, "Transaction too old");
_;
}
/// @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(int24 indexed tickLower, int24 indexed tickUpper, 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 ethReceived The amount of WETH that was accounted for the decrease in liquidity
event PositionLiquidated(int24 indexed tickLower, int24 indexed tickUpper, uint128 liquidity, uint256 ethReceived);
// TODO: add events
constructor(address _factory, address _WETH9, address _harb) {
factory = _factory;
@ -93,8 +70,6 @@ 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
@ -128,58 +103,7 @@ contract BaseLineLP {
_outstanding = (harb.totalSupply() - harb.balanceOf(address(pool)) - harb.balanceOf(address(this)));
}
function spendingLimit() public view returns (uint256, uint256) {
return (lastDay, mintedToday);
}
function tokensIn(Stage s) public view returns (uint256 _ethInPosition, uint256 _harbInPosition) {
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(positions[s].tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(positions[s].tickUpper);
if (token0isWeth) {
if (s == Stage.FLOOR) {
_ethInPosition = LiquidityAmounts.getAmount0ForLiquidity(
sqrtRatioAX96, sqrtRatioBX96, positions[s].liquidity
);
_harbInPosition = 0;
} else if (s == Stage.ANCHOR) {
_ethInPosition = LiquidityAmounts.getAmount0ForLiquidity(
sqrtRatioAX96, sqrtRatioBX96, positions[s].liquidity / 2
);
_harbInPosition = LiquidityAmounts.getAmount1ForLiquidity(
sqrtRatioAX96, sqrtRatioBX96, positions[s].liquidity / 2
);
} else {
_ethInPosition = 0;
_harbInPosition = LiquidityAmounts.getAmount1ForLiquidity(
sqrtRatioAX96, sqrtRatioBX96, positions[s].liquidity
);
}
} else {
if (s == Stage.FLOOR) {
_ethInPosition = LiquidityAmounts.getAmount1ForLiquidity(
sqrtRatioAX96, sqrtRatioBX96, positions[s].liquidity
);
_harbInPosition = 0;
} else if (s == Stage.ANCHOR) {
_ethInPosition = LiquidityAmounts.getAmount1ForLiquidity(
sqrtRatioAX96, sqrtRatioBX96, positions[s].liquidity / 2
);
_harbInPosition = LiquidityAmounts.getAmount0ForLiquidity(
sqrtRatioAX96, sqrtRatioBX96, positions[s].liquidity / 2
);
} else {
_ethInPosition = 0;
_harbInPosition = LiquidityAmounts.getAmount0ForLiquidity(
sqrtRatioAX96, sqrtRatioBX96, positions[s].liquidity
);
}
}
}
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
function tickAtPrice(uint256 tokenAmount, uint256 ethAmount) internal view returns (int24 tick_) {
function tickAtPrice(bool t0isWeth, uint256 tokenAmount, uint256 ethAmount) internal pure returns (int24 tick_) {
require(ethAmount > 0, "ETH amount cannot be zero");
uint160 sqrtPriceX96;
if (tokenAmount == 0) {
@ -196,10 +120,14 @@ contract BaseLineLP {
int160(ABDKMath64x64.sqrt(priceRatio) << 32)
);
}
// Proceed as before.
tick_ = TickMath.getTickAtSqrtRatio(sqrtPriceX96);
tick_ = tick_ / TICK_SPACING * TICK_SPACING;
tick_ = token0isWeth ? tick_ : -tick_;
tick_ = t0isWeth ? tick_ : -tick_;
}
function tickToPrice(int24 tick) public pure returns (uint256 priceRatio) {
uint160 sqrtRatio = TickMath.getSqrtRatioAtTick(tick);
uint256 adjustedSqrtRatio = uint256(sqrtRatio) / (1 << 48);
priceRatio = adjustedSqrtRatio * adjustedSqrtRatio;
}
function _mint(Stage stage, int24 tickLower, int24 tickUpper, uint128 liquidity) internal {
@ -220,13 +148,6 @@ contract BaseLineLP {
});
}
// Calculate current VWAP
function calculateVWAP() public view returns (uint256) {
if (cumulativeVolume == 0) return 0;
return cumulativeVolumeWeightedPrice / cumulativeVolume;
}
function _set(uint160 sqrtPriceX96, int24 currentTick) internal {
// ### set Floor position
@ -237,27 +158,27 @@ contract BaseLineLP {
uint256 requiredEthForBuyback = 0;
if (cumulativeVolume > 0) {
vwap = cumulativeVolumeWeightedPrice / cumulativeVolume;
requiredEthForBuyback = outstandingSupply / vwap * 10**18;
requiredEthForBuyback = outstandingSupply * 10**18 / vwap;
}
uint256 ethBalance = (address(this).balance + weth.balanceOf(address(this)));
// leave at least x% of supply for anchor
// leave at least ANCHOR_LIQ_SHARE% of supply for anchor
ethBalance = ethBalance * (100 - ANCHOR_LIQ_SHARE) / 100;
if (ethBalance < requiredEthForBuyback) {
// not enough ETH, find a lower price
requiredEthForBuyback = ethBalance;
vwapTick = tickAtPrice(outstandingSupply * CAPITAL_INEFFICIENCY / 100, requiredEthForBuyback);
outstandingSupply = outstandingSupply * CAPITAL_INEFFICIENCY / 100;
vwapTick = tickAtPrice(token0isWeth, outstandingSupply , requiredEthForBuyback);
} else if (vwap == 0) {
requiredEthForBuyback = ethBalance;
vwapTick = currentTick;
} else {
vwapTick = tickAtPrice(cumulativeVolumeWeightedPrice * CAPITAL_INEFFICIENCY / 100 / 10**18, cumulativeVolume);
vwap = cumulativeVolumeWeightedPrice * CAPITAL_INEFFICIENCY / 100 / cumulativeVolume; // in harb/eth
vwapTick = tickAtPrice(token0isWeth, token0isWeth ? vwap : 10**18, token0isWeth ? 10**18 : vwap);
if (requiredEthForBuyback < ethBalance) {
// invest a majority of the ETH still in floor, even though not needed
requiredEthForBuyback = (requiredEthForBuyback + (5 * ethBalance)) / 6;
}
}
// move floor below anchor, if needed
if (token0isWeth) {
vwapTick = (vwapTick < currentTick + ANCHOR_SPACING) ? currentTick + ANCHOR_SPACING : vwapTick;
@ -267,11 +188,9 @@ contract BaseLineLP {
// normalize tick position for pool
vwapTick = vwapTick / TICK_SPACING * TICK_SPACING;
int24 floorTick = token0isWeth ? vwapTick + TICK_SPACING: vwapTick - TICK_SPACING;
// calculate liquidity
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(vwapTick);
int24 floorTick = token0isWeth ? vwapTick + TICK_SPACING: vwapTick - TICK_SPACING;
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(floorTick);
uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
@ -290,26 +209,23 @@ contract BaseLineLP {
{
int24 tickLower = token0isWeth ? currentTick - ANCHOR_SPACING : vwapTick;
int24 tickUpper = token0isWeth ? vwapTick : currentTick + ANCHOR_SPACING;
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
uint256 ethBalance = (address(this).balance + weth.balanceOf(address(this)));
if (token0isWeth) {
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount0(
sqrtRatioAX96, sqrtRatioBX96, ethBalance
);
} else {
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount1(
sqrtRatioAX96, sqrtRatioBX96, ethBalance
);
}
tickLower = tickLower / TICK_SPACING * TICK_SPACING;
tickUpper = tickUpper / TICK_SPACING * TICK_SPACING;
uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(currentTick);
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
uint256 ethBalance = (address(this).balance + weth.balanceOf(address(this))) * 98 / 100;
anchorLiquidity = LiquidityAmounts.getLiquidityForAmounts(
sqrtRatioX96,
sqrtRatioAX96,
sqrtRatioBX96,
token0isWeth ? ethBalance : 10**30,
token0isWeth ? 10**30: ethBalance
);
_mint(Stage.ANCHOR, tickLower, tickUpper, anchorLiquidity);
}
currentTick = currentTick / TICK_SPACING * TICK_SPACING;
// ## set Discovery position
{
int24 tickLower = token0isWeth ? currentTick - DISCOVERY_SPACING - ANCHOR_SPACING : currentTick + ANCHOR_SPACING;
@ -341,16 +257,6 @@ contract BaseLineLP {
}
}
function tickToPrice(int24 tick) public pure returns (uint256) {
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(tick);
// Convert the sqrt price to price using fixed point arithmetic
// sqrtPriceX96 is a Q64.96 format (96 fractional bits)
// price = (sqrtPriceX96 ** 2) / 2**192
// To avoid overflow, perform the division by 2**96 first before squaring
uint256 price = uint256(sqrtPriceX96) / (1 << 48); // Reducing the scale before squaring
return price * price;
}
function _scrape() internal {
uint256 fee0 = 0;
uint256 fee1 = 0;
@ -371,11 +277,12 @@ contract BaseLineLP {
fee0 += collected0 - amount0;
fee1 += collected1 - amount1;
if (i == uint256(Stage.ANCHOR)) {
int24 priceTick = position.tickLower + (position.tickUpper - position.tickLower);
currentPrice = tickToPrice(priceTick);
int24 tick = token0isWeth ? -1 * (position.tickLower + ANCHOR_SPACING): position.tickUpper - ANCHOR_SPACING;
currentPrice = tickToPrice(tick);
}
}
}
// Transfer fees to the fee destination
// and record transaction totals
@ -409,15 +316,25 @@ contract BaseLineLP {
secondsAgo[0] = timeInterval; // 5 minutes ago
secondsAgo[1] = 0; // current block timestamp
(int56[] memory tickCumulatives,) = pool.observe(secondsAgo);
int56 tickCumulativeDiff = tickCumulatives[1] - tickCumulatives[0];
int24 averageTick = int24(tickCumulativeDiff / int56(int32(timeInterval)));
//(int56[] memory tickCumulatives,) = pool.observe(secondsAgo);
int56 tickCumulativeDiff;
int24 averageTick;
try pool.observe(secondsAgo) returns (int56[] memory tickCumulatives, uint160[] memory) {
tickCumulativeDiff = tickCumulatives[1] - tickCumulatives[0];
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
return true;
}
return (currentTick >= averageTick - MAX_TICK_DEVIATION && currentTick <= averageTick + MAX_TICK_DEVIATION);
}
// call this function when price has moved up 15%
// 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
@ -430,8 +347,9 @@ contract BaseLineLP {
// Check if current tick is within the specified range
int24 anchorTickLower = positions[Stage.ANCHOR].tickLower;
int24 anchorTickUpper = positions[Stage.ANCHOR].tickUpper;
// center tick can be calculated positive and negative numbers the same
int24 centerTick = anchorTickLower + ((anchorTickUpper - anchorTickLower) / 2);
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
@ -439,8 +357,8 @@ contract BaseLineLP {
bool isEnough = SignedMath.abs(currentTick - centerTick) > minAmplitude;
// Check Conditions
require(isUp, "call slide(), not shift()");
require(isEnough, "amplitude not reached, come back later!");
require(isUp, "call slide(), not shift()");
}
// ## scrape positions
@ -449,6 +367,7 @@ contract BaseLineLP {
_set(sqrtPriceX96, currentTick);
}
function slide() external {
// Fetch the current tick from the Uniswap V3 pool
(uint160 sqrtPriceX96, int24 currentTick, , , , , ) = pool.slot0();
@ -462,7 +381,7 @@ contract BaseLineLP {
int24 anchorTickUpper = positions[Stage.ANCHOR].tickUpper;
// center tick can be calculated positive and negative numbers the same
int24 centerTick = anchorTickLower + ((anchorTickUpper - anchorTickLower) / 2);
int24 centerTick = token0isWeth ? anchorTickLower + ANCHOR_SPACING : anchorTickUpper - ANCHOR_SPACING;
uint256 minAmplitude = uint256(uint24((anchorTickUpper - anchorTickLower) * 3 / 20));
// Determine the correct comparison direction based on token0isWeth
@ -470,8 +389,8 @@ contract BaseLineLP {
bool isEnough = SignedMath.abs(currentTick - centerTick) > minAmplitude;
// Check Conditions
require(isDown, "call shift(), not slide()");
require(isEnough, "amplitude not reached, diamond hands!");
require(isDown, "call shift(), not slide()");
}
_scrape();

File diff suppressed because one or more lines are too long

View file

@ -19,6 +19,8 @@ import {BaseLineLP} from "../src/BaseLineLP.sol";
address constant TAX_POOL = address(2);
// default fee of 1%
uint24 constant FEE = uint24(10_000);
int24 constant TICK_SPACING = 200;
int24 constant ANCHOR_SPACING = 5 * TICK_SPACING;
// Dummy.sol
contract Dummy {
@ -26,6 +28,7 @@ contract Dummy {
}
contract BaseLineLP2Test is Test {
IWETH9 weth;
Harb harb;
IUniswapV3Factory factory;
@ -133,17 +136,21 @@ contract BaseLineLP2Test is Test {
uint256 timeBefore = block.timestamp;
vm.warp(timeBefore + (60 * 60 * 5));
//lm.slide();
try lm.slide() {
// Check liquidity positions after slide
(uint256 ethFloor, uint256 ethAnchor, uint256 ethDiscovery, uint256 harbFloor, uint256 harbAnchor, uint256 harbDiscovery) = checkLiquidityPositionsAfter("slide");
assertGt(ethFloor, ethAnchor * 4, "slide - Floor should hold more ETH than Anchor");
assertGt(ethFloor, ethAnchor * 3, "slide - Floor should hold more ETH than Anchor");
assertGt(harbDiscovery, harbAnchor * 90, "slide - Discovery should hold more HARB than Anchor");
assertEq(harbFloor, 0, "slide - Floor should have no HARB");
assertEq(ethDiscovery, 0, "slide - Discovery should have no ETH");
} catch {
console.log("slide failed");
} catch Error(string memory reason) {
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
}
}
}
function shift() internal {
@ -151,17 +158,21 @@ contract BaseLineLP2Test is Test {
uint256 timeBefore = block.timestamp;
vm.warp(timeBefore + (60 * 60 * 5));
lm.shift();
try lm.shift() {
// Check liquidity positions after shift
(uint256 ethFloor, uint256 ethAnchor, uint256 ethDiscovery, uint256 harbFloor, uint256 harbAnchor, uint256 harbDiscovery) = checkLiquidityPositionsAfter("shift");
assertGt(ethFloor, ethAnchor * 4, "shift - Floor should hold more ETH than Anchor");
assertGt(ethFloor, ethAnchor * 3, "shift - Floor should hold more ETH than Anchor");
assertGt(harbDiscovery, harbAnchor * 90, "shift - Discovery should hold more HARB than Anchor");
assertEq(harbFloor, 0, "shift - Floor should have no HARB");
assertEq(ethDiscovery, 0, "shift - Discovery should have no ETH");
} catch {
console.log("shift failed");
} catch Error(string memory reason) {
if (keccak256(abi.encodePacked(reason)) == keccak256(abi.encodePacked("amplitude not reached, come back later!"))) {
console.log("shift failed on amplitude");
} else {
revert(reason); // Rethrow the error if it's not the expected message
}
}
}
@ -229,12 +240,15 @@ 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)));
}
@ -278,19 +292,15 @@ contract BaseLineLP2Test is Test {
{
require(amount0Delta > 0 || amount1Delta > 0);
(address seller, uint256 harbIn, bool isBuy) = abi.decode(_data, (address, uint256, bool));
(address seller, , bool isBuy) = abi.decode(_data, (address, uint256, bool));
(bool isExactInput, uint256 amountToPay) = amount0Delta > 0
(, uint256 amountToPay) = amount0Delta > 0
? (!token0isWeth, uint256(amount0Delta))
: (token0isWeth, uint256(amount1Delta));
if (isBuy) {
weth.transfer(msg.sender, amountToPay);
return;
}
require(isExactInput, "reason 1");
// check input amount
require(amountToPay == harbIn, "reason 2");
// transfer eth to univ3 pool
require(harb.transferFrom(seller, msg.sender, amountToPay), "reason 3");
}
@ -435,9 +445,9 @@ contract BaseLineLP2Test is Test {
// slide();
// writeCsv();
// uint256 traderBalanceAfter = weth.balanceOf(account);
// console.log(traderBalanceBefore);
// console.log(traderBalanceAfter);
// assertGt(traderBalanceBefore, traderBalanceAfter, "trader should not have made profit");
// }
@ -476,13 +486,14 @@ 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(false);
setUpCustomToken0(true);
vm.deal(account, 100 ether);
vm.prank(account);
weth.deposit{value: 100 ether}();
@ -496,7 +507,7 @@ contract BaseLineLP2Test is Test {
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;
@ -516,15 +527,16 @@ contract BaseLineLP2Test is Test {
if (f >= frequency) {
(, int24 currentTick, , , , , ) = pool.slot0();
(, int24 tickLower, int24 tickUpper) = lm.positions(BaseLineLP.Stage.ANCHOR);
int24 midTick = (tickLower + tickUpper) / 2;
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()
slide();
token0isWeth ? shift(): slide();
} else if (currentTick > midTick) {
// Current tick is above the midpoint, so call shift()
shift();
token0isWeth ? slide(): shift();
}
f = 0;
} else {
f++;

View file

@ -1,111 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import {BaseLineLP} from "../../src/BaseLineLP.sol";
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
contract PoolSerializer is Test {
string csv;
// Helper function to convert uint to string
function uintToStr(uint256 _i) internal pure returns (string memory _uintAsString) {
if (_i == 0) {
return "0";
}
uint256 j = _i;
uint256 len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint256 k = len;
while (_i != 0) {
k = k-1;
uint8 temp = (48 + uint8(_i - _i / 10 * 10));
bytes1 b1 = bytes1(temp);
bstr[k] = b1;
_i /= 10;
}
return string(bstr);
}
// Helper function to convert int to string
function intToStr(int256 _i) internal pure returns (string memory) {
if (_i == 0) {
return "0";
}
bool negative = _i < 0;
uint256 j = uint256(negative ? -_i : _i);
uint256 len;
while (j != 0) {
len++;
j /= 10;
}
if (negative) {
len++;
}
bytes memory bstr = new bytes(len);
uint256 k = len;
uint256 l = uint256(negative ? -_i : _i);
while (l != 0) {
k = k-1;
uint8 temp = (48 + uint8(l - l / 10 * 10));
bytes1 b1 = bytes1(temp);
bstr[k] = b1;
l /= 10;
}
if (negative) {
bstr[0] = '-';
}
return string(bstr);
}
function createCSVHeader() public {
csv = "FLOOR liquidity, FLOOR tickLower, FLOOR tickUpper, FLOOR ETH, FLOOR HARB, ANCHOR liquidity, ANCHOR tickLower, ANCHOR tickUpper, ANCHOR ETH, ANCHOR HARB, DISCOVERY liquidity, DISCOVERY tickLower, DISCOVERY tickUpper, DISCOVERY ETH, DISCOVERY HARB, CURRENT TICK";
}
function appendPossitions(BaseLineLP lm, IUniswapV3Pool pool, bool token0isWeth) public {
(, int24 tickLower, int24 tickUpper) = lm.positions(BaseLineLP.Stage.FLOOR);
(uint128 liquidity,,, uint128 tokensOwed0, uint128 tokensOwed1
) = pool.positions(keccak256(abi.encodePacked(address(lm), tickLower, tickUpper)));
string memory floorData = string(abi.encodePacked(
uintToStr(liquidity), ",",
intToStr(tickLower), ",",
intToStr(tickUpper), ",",
uintToStr(token0isWeth ? tokensOwed0 : tokensOwed1), ",",
uintToStr(token0isWeth ? tokensOwed1 : tokensOwed0), ","
));
(liquidity, tickLower, tickUpper) = lm.positions(BaseLineLP.Stage.ANCHOR);
(uint256 ethAmount, uint256 harbAmount) = lm.tokensIn(BaseLineLP.Stage.ANCHOR);
string memory anchorData = string(abi.encodePacked(
uintToStr(liquidity), ",",
intToStr(tickLower), ",",
intToStr(tickUpper), ",",
uintToStr(ethAmount), ",",
uintToStr(harbAmount), ","
));
(liquidity, tickLower, tickUpper) = lm.positions(BaseLineLP.Stage.DISCOVERY);
(ethAmount, harbAmount) = lm.tokensIn(BaseLineLP.Stage.DISCOVERY);
(, int24 currentTick, , , , , ) = pool.slot0();
string memory discoveryData = string(abi.encodePacked(
uintToStr(liquidity), ",",
intToStr(tickLower), ",",
intToStr(tickUpper), ",",
uintToStr(ethAmount), ",",
uintToStr(harbAmount), ",",
intToStr(currentTick)
));
csv = string(abi.encodePacked(csv, "\n", floorData, anchorData, discoveryData));
}
function writeCsv() public {
string memory path = "./out/positions.csv";
vm.writeFile(path, csv);
}
}