2024-07-04 10:24:06 +02:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
pragma solidity ^0.8.19;
|
|
|
|
|
|
|
|
|
|
import "forge-std/Test.sol";
|
|
|
|
|
import "@aperture/uni-v3-lib/TickMath.sol";
|
|
|
|
|
import {LiquidityAmounts} from "@aperture/uni-v3-lib/LiquidityAmounts.sol";
|
|
|
|
|
import "../src/interfaces/IWETH9.sol";
|
|
|
|
|
import {WETH} from "solmate/tokens/WETH.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 {Harb} from "../src/Harb.sol";
|
|
|
|
|
|
|
|
|
|
import {Stake, ExceededAvailableStake} from "../src/Stake.sol";
|
|
|
|
|
import {BaseLineLP} from "../src/BaseLineLP.sol";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
address constant TAX_POOL = address(2);
|
|
|
|
|
// default fee of 1%
|
|
|
|
|
uint24 constant FEE = uint24(10_000);
|
|
|
|
|
|
|
|
|
|
// Dummy.sol
|
|
|
|
|
contract Dummy {
|
|
|
|
|
// This contract can be empty as it is only used to affect the nonce
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
contract BaseLineLP2Test is Test {
|
|
|
|
|
IWETH9 weth;
|
|
|
|
|
Harb harb;
|
|
|
|
|
IUniswapV3Factory factory;
|
|
|
|
|
Stake stake;
|
|
|
|
|
BaseLineLP lm;
|
|
|
|
|
IUniswapV3Pool pool;
|
|
|
|
|
bool token0isWeth;
|
|
|
|
|
address account = makeAddr("alice");
|
|
|
|
|
address feeDestination = makeAddr("fees");
|
|
|
|
|
string csv;
|
|
|
|
|
|
|
|
|
|
// Utility to deploy dummy contracts
|
|
|
|
|
function deployDummies(uint count) internal {
|
|
|
|
|
for (uint i = 0; i < count; i++) {
|
|
|
|
|
new Dummy(); // Just increment the nonce
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(address _pool) public {
|
|
|
|
|
uint256 price;
|
|
|
|
|
if (token0isWeth) {
|
|
|
|
|
// ETH as token0, so we are setting the price of 1 ETH in terms of token1 (USD cent)
|
|
|
|
|
price = 3000 * 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**9); // Adjust sqrt value to 96-bit precision
|
|
|
|
|
|
|
|
|
|
// Initialize pool with the calculated sqrtPriceX96
|
|
|
|
|
IUniswapV3Pool(_pool).initialize(sqrtPriceX96);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function deployContract(bytes memory bytecode, bytes memory constructorArgs) internal returns (address addr) {
|
|
|
|
|
bytes memory deploymentData = abi.encodePacked(bytecode, constructorArgs);
|
|
|
|
|
assembly {
|
|
|
|
|
addr := create(0, add(deploymentData, 0x20), mload(deploymentData))
|
|
|
|
|
}
|
|
|
|
|
require(addr != address(0), "Contract deployment failed");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setUpCustomToken0(bool token0shouldBeWeth) public {
|
|
|
|
|
bytes memory factoryBytecode = hex"60a060405234801561001057600080fd5b503060601b608052600380546001600160a01b031916339081179091556040516000907fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c908290a36101f4600081815260046020527ffb8cf1d12598d1a039dd1d106665851a96aadf67d0d9ed76fceea282119208b7805462ffffff1916600a90811790915560405190929160008051602061614b83398151915291a3610bb8600081815260046020527f72dffa9b822156d9cf4b0090fa0b656bcb9cc2b2c60eb6acfc20a34f54b31743805462ffffff1916603c90811790915560405190929160008051602061614b83398151915291a3612710600081815260046020527f8cc740d51daa94ff54f33bd779c2d20149f524c340519b49181be5a08615f829805462ffffff191660c890811790915560405190929160008051602061614b83398151915291a360805160601c615fd7610174600039806105515250615fd76000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063890357301161005b578063890357301461013b5780638a7c195f146101855780638da5cb5b146101b0578063a1671295146101b85761007d565b806313af4035146100825780631698ee82146100aa57806322afcccb14610102575b600080fd5b6100a86004803603602081101561009857600080fd5b50356001600160a01b03166101f4565b005b6100e6600480360360608110156100c057600080fd5b5080356001600160a01b03908116916020810135909116906040013562ffffff16610267565b604080516001600160a01b039092168252519081900360200190f35b6101246004803603602081101561011857600080fd5b503562ffffff16610293565b6040805160029290920b8252519081900360200190f35b6101436102a8565b604080516001600160a01b0396871681529486166020860152929094168383015262ffffff16606083015260029290920b608082015290519081900360a00190f35b6100a86004803603604081101561019b57600080fd5b5062ffffff813516906020013560020b6102de565b6100e66103a1565b6100e6600480360360608110156101ce57600080fd5b5080356001600160a01b03908116916020810135909116906040013562ffffff166103b0565b6003546001600160a01b0316331461020b57600080fd5b6003546040516001600160a01b038084169216907fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c90600090a3600380546001600160a01b0319166001600160a01b0392909216919091179055565b60056020908152600093845260408085208252928452828420905282529020546001600160a01b031681565b60046020526000908152604090205460020b81565b600054600154600280546001600160a01b03938416939283169281169162ffffff600160a01b83041691600160b81b9004900b85565b6003546001600160a01b031633146102f557600080fd5b620f42408262ffffff161061030957600080fd5b60008160020b13801561032057506140008160020b125b61032957600080fd5b62ffffff8216600090815260046020526040902054600290810b900b1561034f57600080fd5b62ffffff828116600081815260046020526040808220805462ffffff1916600287900b958616179055517fc66a3fdf07232cdd185febcc6579d408c241b47ae2f9907d84be655141eeaecc9190a35050565b6003546001600160a01b031681565b60006103ba610546565b826001600160a01b0316846001600160a01b031614156103d957600080fd5b600080846001600160a01b0316866001600160a01b0316106103fc5784866103ff565b85855b90925090506001600160a01b03821661041757600080fd5b62ffffff8416600090815260046020526040902054600290810b9081900b61043e57600080fd5b6001600160a01b0383811660009081526005602090815260408083208685168452825280832062ffffff8a168452909152902054161561047d57600080fd5b61048a308484888561057d565b6001600160a01b03808516600081815260056020818152604080842089871680865290835281852062ffffff8e168087529084528286208054988a166001600160a01b0319998a1681179091558287529484528286208787528452828620818752845294829020805490971684179096558051600289900b815291820192909252815195995091947f783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b71189281900390910190a45050509392505050565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461057b57600080fd5b565b6040805160a0810182526001600160a01b03878116808352878216602080850182905292881684860181905262ffffff888116606080880182905260028a810b6080998a01819052600080546001600160a01b03199081169099178155600180548a1689179055825490981686177fffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffff16600160a01b8502177fffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffff16600160b81b91830b909516029390931790925587518087019490945283880192909252828101919091528551808303909101815293019384905282519290
|
|
|
|
|
address factoryAddress = deployContract(factoryBytecode, "");
|
|
|
|
|
factory = IUniswapV3Factory(factoryAddress);
|
|
|
|
|
|
|
|
|
|
TwabController tc = new TwabController(60 * 60 * 24, uint32(block.timestamp));
|
|
|
|
|
|
|
|
|
|
bool setupComplete = false;
|
|
|
|
|
uint retryCount = 0;
|
|
|
|
|
while (!setupComplete && retryCount < 5) {
|
|
|
|
|
// Clean slate if retrying
|
|
|
|
|
if (retryCount > 0) {
|
|
|
|
|
deployDummies(1); // Deploy a dummy contract to shift addresses
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
weth = IWETH9(address(new WETH()));
|
|
|
|
|
harb = new Harb("HARB", "HARB", tc);
|
|
|
|
|
|
|
|
|
|
// Check if the setup meets the required condition
|
|
|
|
|
if (token0shouldBeWeth == address(weth) < address(harb)) {
|
|
|
|
|
setupComplete = true;
|
|
|
|
|
} else {
|
|
|
|
|
// Clear current instances for re-deployment
|
|
|
|
|
delete weth;
|
|
|
|
|
delete harb;
|
|
|
|
|
retryCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
require(setupComplete, "Setup failed to meet the condition after several retries");
|
|
|
|
|
|
|
|
|
|
pool = IUniswapV3Pool(factory.createPool(address(weth), address(harb), FEE));
|
|
|
|
|
|
|
|
|
|
token0isWeth = address(weth) < address(harb);
|
|
|
|
|
initializePoolFor1Cent(address(pool));
|
|
|
|
|
|
|
|
|
|
stake = new Stake(address(harb));
|
|
|
|
|
harb.setStakingPool(address(stake));
|
|
|
|
|
lm = new BaseLineLP(factoryAddress, address(weth), address(harb));
|
|
|
|
|
lm.setFeeDestination(feeDestination);
|
|
|
|
|
harb.setLiquidityManager(address(lm));
|
|
|
|
|
vm.deal(address(lm), 10 ether);
|
|
|
|
|
createCSVHeader();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function slide() internal {
|
|
|
|
|
// have some time pass to record prices in uni oracle
|
|
|
|
|
uint256 timeBefore = block.timestamp;
|
|
|
|
|
vm.warp(timeBefore + (60 * 60 * 5));
|
|
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
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(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");
|
|
|
|
|
}
|
2024-07-04 10:24:06 +02:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function shift() internal {
|
|
|
|
|
// have some time pass to record prices in uni oracle
|
|
|
|
|
uint256 timeBefore = block.timestamp;
|
|
|
|
|
vm.warp(timeBefore + (60 * 60 * 5));
|
2024-07-06 19:25:09 +02:00
|
|
|
|
|
|
|
|
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(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");
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-04 10:24:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getBalancesPool(BaseLineLP.Stage s) internal view returns (int24 currentTick, int24 tickLower, int24 tickUpper, uint256 ethAmount, uint256 harbAmount) {
|
|
|
|
|
(,tickLower, tickUpper) = lm.positions(s);
|
|
|
|
|
(uint128 liquidity, , , ,) = pool.positions(keccak256(abi.encodePacked(address(lm), tickLower, tickUpper)));
|
|
|
|
|
|
|
|
|
|
// Fetch the current price from the pool
|
|
|
|
|
uint160 sqrtPriceX96;
|
|
|
|
|
(sqrtPriceX96, currentTick, , , , , ) = pool.slot0();
|
|
|
|
|
uint160 sqrtPriceAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
|
|
|
|
uint160 sqrtPriceBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
|
|
|
|
|
|
|
|
|
// Calculate amounts based on the current tick position relative to provided ticks
|
|
|
|
|
if (token0isWeth) {
|
|
|
|
|
if (currentTick < tickLower) {
|
|
|
|
|
// Current price is below the lower bound of the liquidity position
|
|
|
|
|
ethAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity);
|
|
|
|
|
harbAmount = 0; // All liquidity is in token0 (ETH)
|
|
|
|
|
} else if (currentTick > tickUpper) {
|
|
|
|
|
// Current price is above the upper bound of the liquidity position
|
|
|
|
|
ethAmount = 0; // All liquidity is in token1 (HARB)
|
|
|
|
|
harbAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity);
|
|
|
|
|
} else {
|
|
|
|
|
// Current price is within the bounds of the liquidity position
|
|
|
|
|
ethAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtPriceBX96, liquidity);
|
2024-07-06 18:36:13 +02:00
|
|
|
harbAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceX96, liquidity);
|
2024-07-04 10:24:06 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (currentTick < tickLower) {
|
|
|
|
|
// Current price is below the lower bound of the liquidity position
|
|
|
|
|
harbAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity);
|
|
|
|
|
ethAmount = 0; // All liquidity is in token1 (ETH)
|
|
|
|
|
} else if (currentTick > tickUpper) {
|
|
|
|
|
// Current price is above the upper bound of the liquidity position
|
|
|
|
|
harbAmount = 0; // All liquidity is in token0 (HARB)
|
|
|
|
|
ethAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity);
|
|
|
|
|
} else {
|
|
|
|
|
// Current price is within the bounds of the liquidity position
|
|
|
|
|
harbAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtPriceBX96, liquidity);
|
2024-07-06 18:36:13 +02:00
|
|
|
ethAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceX96, liquidity);
|
2024-07-04 10:24:06 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// csv = "precedingAction, currentTick, floorTickLower, floorTickUpper, floorEth, floorHarb, anchorTickLower, anchorTickUpper, anchorEth, anchorHarb, discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryHarb";
|
|
|
|
|
function checkLiquidityPositionsAfter(string memory eventName) internal returns (uint ethFloor, uint ethAnchor, uint ethDiscovery, uint harbFloor, uint harbAnchor, uint harbDiscovery) {
|
|
|
|
|
int24 currentTick;
|
|
|
|
|
int24 tickLower;
|
|
|
|
|
int24 tickUpper;
|
|
|
|
|
(currentTick, tickLower, tickUpper, ethFloor, harbFloor) = getBalancesPool(BaseLineLP.Stage.FLOOR);
|
|
|
|
|
string memory floorData = string(abi.encodePacked(intToStr(tickLower), ",", intToStr(tickUpper), ",", uintToStr(ethFloor), ",", uintToStr(harbFloor), ","));
|
|
|
|
|
|
|
|
|
|
(,tickLower, tickUpper, ethAnchor, harbAnchor) = getBalancesPool(BaseLineLP.Stage.ANCHOR);
|
|
|
|
|
string memory anchorData = string(abi.encodePacked(intToStr(tickLower), ",", intToStr(tickUpper), ",", uintToStr(ethAnchor), ",", uintToStr(harbAnchor), ","));
|
|
|
|
|
|
|
|
|
|
(,tickLower, tickUpper, ethDiscovery, harbDiscovery) = getBalancesPool(BaseLineLP.Stage.DISCOVERY);
|
|
|
|
|
string memory discoveryData = string(abi.encodePacked(intToStr(tickLower), ",", intToStr(tickUpper), ",", uintToStr(ethDiscovery), ",", uintToStr(harbDiscovery), ","));
|
|
|
|
|
|
|
|
|
|
csv = string(abi.encodePacked(csv, "\n", eventName, ",", intToStr(currentTick), ",", floorData, anchorData, discoveryData));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper function to calculate absolute value of integer
|
|
|
|
|
function abs(int x) internal pure returns (uint) {
|
|
|
|
|
return x >= 0 ? uint(x) : uint(-x);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buy(uint256 amountEth) internal {
|
|
|
|
|
performSwap(amountEth, true);
|
|
|
|
|
checkLiquidityPositionsAfter(string.concat("buy ", uintToStr(amountEth)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function sell(uint256 amountHarb) internal {
|
|
|
|
|
performSwap(amountHarb, false);
|
|
|
|
|
checkLiquidityPositionsAfter(string.concat("sell ", uintToStr(amountHarb)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function performSwap(uint256 amount, bool isBuy) internal {
|
|
|
|
|
uint160 limit;
|
|
|
|
|
if (isBuy) {
|
|
|
|
|
vm.startPrank(account);
|
|
|
|
|
//vm.deal(account, amount);
|
|
|
|
|
//weth.deposit{value: account.balance}();
|
|
|
|
|
weth.transfer(address(this), amount);
|
|
|
|
|
vm.stopPrank();
|
|
|
|
|
limit = token0isWeth ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1;
|
|
|
|
|
} else {
|
|
|
|
|
// vm.startPrank(address(lm));
|
|
|
|
|
// harb.mint(amount);
|
|
|
|
|
// harb.transfer(address(account), amount);
|
|
|
|
|
// vm.stopPrank();
|
|
|
|
|
vm.prank(account);
|
|
|
|
|
harb.approve(address(this), amount);
|
|
|
|
|
limit = !token0isWeth ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1;
|
|
|
|
|
}
|
|
|
|
|
pool.swap(
|
|
|
|
|
account,
|
|
|
|
|
isBuy ? token0isWeth : !token0isWeth,
|
|
|
|
|
int256(amount),
|
|
|
|
|
limit,
|
|
|
|
|
abi.encode(account, int256(amount), isBuy)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////
|
|
|
|
|
CALLBACKS
|
|
|
|
|
//////////////////////////////////////////////////////////////*/
|
|
|
|
|
function uniswapV3SwapCallback(
|
|
|
|
|
int256 amount0Delta,
|
|
|
|
|
int256 amount1Delta,
|
|
|
|
|
bytes calldata _data
|
|
|
|
|
)
|
|
|
|
|
external
|
|
|
|
|
{
|
|
|
|
|
require(amount0Delta > 0 || amount1Delta > 0);
|
|
|
|
|
|
|
|
|
|
(address seller, uint256 harbIn, bool isBuy) = abi.decode(_data, (address, uint256, bool));
|
|
|
|
|
|
|
|
|
|
(bool isExactInput, 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");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
receive() external payable {}
|
|
|
|
|
|
|
|
|
|
// 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 = "precedingAction, currentTick, floorTickLower, floorTickUpper, floorEth, floorHarb, anchorTickLower, anchorTickUpper, anchorEth, anchorHarb, discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryHarb";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function writeCsv() public {
|
|
|
|
|
string memory path = "./out/positions.csv";
|
|
|
|
|
vm.writeFile(path, csv);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// function testLiquidityPositions() public {
|
|
|
|
|
// setUpCustomToken0(false);
|
|
|
|
|
|
|
|
|
|
// // Setup initial liquidity
|
|
|
|
|
// slide();
|
|
|
|
|
|
|
|
|
|
// // Initial checks of liquidity positions
|
|
|
|
|
// (uint ethFloor, uint ethAnchor, uint ethDiscovery, uint harbFloor, uint harbAnchor, uint harbDiscovery) = checkLiquidityPositionsAfter("slide");
|
|
|
|
|
|
|
|
|
|
// // Assertions to verify initial setup
|
|
|
|
|
// assertGt(ethFloor, 9 ether, "Floor should have initial ETH");
|
|
|
|
|
// assertGt(ethAnchor, 0.8 ether, "Anchor should have initial ETH");
|
|
|
|
|
// assertEq(ethDiscovery, 0, "Discovery should not have ETH");
|
|
|
|
|
// assertEq(harbFloor, 0, "Floor should have no HARB");
|
|
|
|
|
// assertGt(harbAnchor, 0, "Anchor should have HARB");
|
|
|
|
|
// assertGt(harbDiscovery, 0, "Discovery should have HARB");
|
|
|
|
|
|
|
|
|
|
// // Introduce large buy to push into discovery
|
|
|
|
|
// buy(3 ether);
|
|
|
|
|
|
|
|
|
|
// // Check liquidity positions after buy
|
|
|
|
|
// (, ethAnchor, ethDiscovery, , , ) = checkLiquidityPositionsAfter("buy 3");
|
|
|
|
|
// assertGt(ethAnchor, 2 ether, "Anchor should have more ETH");
|
|
|
|
|
// assertGt(ethDiscovery, 2.2 ether, "Discovery should have ETH");
|
|
|
|
|
|
|
|
|
|
// shift();
|
|
|
|
|
|
|
|
|
|
// // Check liquidity positions after shift
|
|
|
|
|
// (ethFloor, ethAnchor, ethDiscovery, harbFloor, , ) = checkLiquidityPositionsAfter("shift");
|
|
|
|
|
// assertGt(ethFloor, 11.5 ether, "Floor should have more ETH");
|
|
|
|
|
// assertGt(ethAnchor, 1.4 ether, "Anchor should have more ETH");
|
|
|
|
|
// assertEq(ethDiscovery, 0, "Discovery should not have ETH after shift");
|
|
|
|
|
// assertEq(harbFloor, 0, "Floor should have no HARB");
|
|
|
|
|
|
|
|
|
|
// // Simulate large sell to push price down to floor
|
|
|
|
|
// sell(6 * 10**23);
|
|
|
|
|
|
|
|
|
|
// // Check liquidity positions after sell
|
|
|
|
|
// (ethFloor, ethAnchor, ethDiscovery, harbFloor, harbAnchor, harbDiscovery) = checkLiquidityPositionsAfter("sell 600000");
|
|
|
|
|
// assertGt(ethFloor, 0, "Floor should still have ETH after manipulation");
|
|
|
|
|
// assertGt(harbDiscovery, 0, "Discovery should have increased HARB after buys");
|
|
|
|
|
|
|
|
|
|
// slide();
|
|
|
|
|
|
|
|
|
|
// // Check liquidity positions after slide
|
|
|
|
|
// (ethFloor, ethAnchor, ethDiscovery, harbFloor, harbAnchor, harbDiscovery) = checkLiquidityPositionsAfter("slide");
|
|
|
|
|
|
|
|
|
|
// // Ensure liquidity positions are as expected
|
|
|
|
|
// assertGt(ethFloor, 0, "Floor should still have ETH after manipulation");
|
|
|
|
|
// assertEq(harbFloor, 0, "Floor should have no HARB");
|
|
|
|
|
// assertEq(ethDiscovery, 0, "Discovery should not have ETH after slide");
|
|
|
|
|
// assertGt(harbDiscovery, 0, "Discovery should have increased HARB after buys");
|
|
|
|
|
// writeCsv();
|
|
|
|
|
// }
|
|
|
|
|
|
2024-07-06 18:36:13 +02:00
|
|
|
// function testScenarioBuyAll() public {
|
|
|
|
|
// setUpCustomToken0(false);
|
|
|
|
|
// vm.deal(account, 300 ether);
|
|
|
|
|
// vm.prank(account);
|
|
|
|
|
// weth.deposit{value: 300 ether}();
|
|
|
|
|
|
|
|
|
|
// uint256 traderBalanceBefore = weth.balanceOf(account);
|
|
|
|
|
|
|
|
|
|
// // Setup initial liquidity
|
|
|
|
|
// slide();
|
|
|
|
|
|
|
|
|
|
// buy(100 ether);
|
|
|
|
|
|
|
|
|
|
// shift();
|
|
|
|
|
|
|
|
|
|
// sell(harb.balanceOf(account));
|
|
|
|
|
|
|
|
|
|
// slide();
|
|
|
|
|
|
|
|
|
|
// writeCsv();
|
|
|
|
|
// uint256 traderBalanceAfter = weth.balanceOf(account);
|
|
|
|
|
// console.log(traderBalanceBefore);
|
|
|
|
|
// console.log(traderBalanceAfter);
|
|
|
|
|
// assertGt(traderBalanceBefore, traderBalanceAfter, "trader should not have made profit");
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
// function testScenarioB() public {
|
|
|
|
|
// setUpCustomToken0(false);
|
|
|
|
|
// vm.deal(account, 100 ether);
|
|
|
|
|
// vm.prank(account);
|
|
|
|
|
// weth.deposit{value: 100 ether}();
|
2024-07-04 10:24:06 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
// uint256 traderBalanceBefore = weth.balanceOf(account);
|
2024-07-04 10:24:06 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
// // Setup initial liquidity
|
|
|
|
|
// slide();
|
2024-07-04 10:24:06 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
// buy(2 ether);
|
2024-07-06 18:36:13 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
// shift();
|
2024-07-06 18:36:13 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
// buy(2 ether);
|
2024-07-06 18:36:13 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
// shift();
|
2024-07-06 18:36:13 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
// buy(2 ether);
|
2024-07-04 10:24:06 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
// shift();
|
2024-07-04 10:24:06 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
// sell(harb.balanceOf(account));
|
2024-07-04 10:24:06 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
// slide();
|
2024-07-04 10:24:06 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
// writeCsv();
|
|
|
|
|
// uint256 traderBalanceAfter = weth.balanceOf(account);
|
|
|
|
|
// console.log(traderBalanceBefore);
|
|
|
|
|
// console.log(traderBalanceAfter);
|
|
|
|
|
// assertGt(traderBalanceBefore, traderBalanceAfter, "trader should not have made profit");
|
|
|
|
|
// }
|
2024-07-04 10:24:06 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
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);
|
2024-07-04 10:24:06 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
setUpCustomToken0(false);
|
|
|
|
|
vm.deal(account, 100 ether);
|
|
|
|
|
vm.prank(account);
|
|
|
|
|
weth.deposit{value: 100 ether}();
|
2024-07-04 10:24:06 +02:00
|
|
|
|
|
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
// Setup initial liquidity
|
|
|
|
|
slide();
|
2024-07-04 10:24:06 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
if (harbBal == 0) {
|
|
|
|
|
amount = amount % (weth.balanceOf(account) / 2);
|
|
|
|
|
amount = amount == 0 ? weth.balanceOf(account) : amount;
|
|
|
|
|
buy(amount);
|
|
|
|
|
} else if (weth.balanceOf(account) == 0) {
|
|
|
|
|
sell(amount % harbBal);
|
|
|
|
|
} else {
|
|
|
|
|
if (amount % 2 == 0) {
|
|
|
|
|
amount = amount % (weth.balanceOf(account) / 2);
|
|
|
|
|
amount = amount == 0 ? weth.balanceOf(account) : amount;
|
|
|
|
|
buy(amount);
|
|
|
|
|
} else {
|
|
|
|
|
sell(amount % harbBal);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-04 10:24:06 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
if (f >= frequency) {
|
|
|
|
|
(, int24 currentTick, , , , , ) = pool.slot0();
|
|
|
|
|
(, int24 tickLower, int24 tickUpper) = lm.positions(BaseLineLP.Stage.ANCHOR);
|
|
|
|
|
int24 midTick = (tickLower + tickUpper) / 2;
|
|
|
|
|
if (currentTick < midTick) {
|
|
|
|
|
// Current tick is below the midpoint, so call slide()
|
|
|
|
|
slide();
|
|
|
|
|
} else if (currentTick > midTick) {
|
|
|
|
|
// Current tick is above the midpoint, so call shift()
|
|
|
|
|
shift();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f = 0;
|
|
|
|
|
} else {
|
|
|
|
|
f++;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-04 10:24:06 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
// Simulate large sell to push price down to floor
|
|
|
|
|
sell(harb.balanceOf(account));
|
2024-07-04 10:24:06 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
slide();
|
2024-07-06 18:36:13 +02:00
|
|
|
|
2024-07-06 19:25:09 +02:00
|
|
|
uint256 traderBalanceAfter = weth.balanceOf(account);
|
|
|
|
|
|
|
|
|
|
if (traderBalanceAfter > traderBalanceBefore){
|
|
|
|
|
writeCsv();
|
|
|
|
|
}
|
|
|
|
|
assertGt(traderBalanceBefore, traderBalanceAfter, "trader should not have made profit");
|
|
|
|
|
}
|
2024-07-04 10:24:06 +02:00
|
|
|
|
|
|
|
|
}
|