harb/onchain/test/BaseLineLP2.t.sol

556 lines
70 KiB
Solidity
Raw Normal View History

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);
2024-07-09 18:00:39 +02:00
int24 constant TICK_SPACING = 200;
int24 constant ANCHOR_SPACING = 5 * TICK_SPACING;
2024-07-04 10:24:06 +02:00
// Dummy.sol
contract Dummy {
// This contract can be empty as it is only used to affect the nonce
}
contract BaseLineLP2Test is Test {
2024-07-09 18:00:39 +02:00
2024-07-04 10:24:06 +02:00
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
2024-07-13 14:56:13 +02:00
price = uint256(10**16) / 3000; // Adjust for 18 decimal places
2024-07-04 10:24:06 +02:00
}
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();
}
2024-07-13 14:56:13 +02:00
function slide(bool last) internal {
2024-07-04 10:24:06 +02:00
// have some time pass to record prices in uni oracle
uint256 timeBefore = block.timestamp;
vm.warp(timeBefore + (60 * 60 * 5));
2024-07-09 18:00:39 +02:00
//lm.slide();
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");
2024-07-09 18:00:39 +02:00
assertGt(ethFloor, ethAnchor * 3, "slide - Floor should hold more ETH than Anchor");
2024-07-06 19:25:09 +02:00
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");
2024-07-09 18:00:39 +02:00
} 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 {
2024-07-13 14:56:13 +02:00
if (!last) {
revert(reason); // Rethrow the error if it's not the expected message
}
2024-07-09 18:00:39 +02:00
}
2024-07-06 19:25:09 +02:00
}
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
2024-07-13 14:56:13 +02:00
//lm.shift();
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");
2024-07-09 18:00:39 +02:00
assertGt(ethFloor, ethAnchor * 3, "shift - Floor should hold more ETH than Anchor");
2024-07-06 19:25:09 +02:00
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");
2024-07-09 18:00:39 +02:00
} 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
}
2024-07-06 19:25:09 +02:00
}
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);
2024-07-09 18:00:39 +02:00
(address seller, , bool isBuy) = abi.decode(_data, (address, uint256, bool));
2024-07-04 10:24:06 +02:00
2024-07-09 18:00:39 +02:00
(, uint256 amountToPay) = amount0Delta > 0
2024-07-04 10:24:06 +02:00
? (!token0isWeth, uint256(amount0Delta))
: (token0isWeth, uint256(amount1Delta));
if (isBuy) {
weth.transfer(msg.sender, amountToPay);
return;
}
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
2024-07-13 14:56:13 +02:00
// slide(false);
2024-07-06 18:36:13 +02:00
2024-07-13 14:56:13 +02:00
// buy(200 ether);
2024-07-06 18:36:13 +02:00
// shift();
2024-07-13 14:56:13 +02:00
// //revert();
2024-07-06 18:36:13 +02:00
// sell(harb.balanceOf(account));
2024-07-13 14:56:13 +02:00
// slide(true);
2024-07-06 18:36:13 +02:00
// writeCsv();
2024-07-09 18:00:39 +02:00
2024-07-06 18:36:13 +02:00
// uint256 traderBalanceAfter = weth.balanceOf(account);
2024-07-09 18:00:39 +02:00
2024-07-06 18:36:13 +02:00
// 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
2024-07-13 14:56:13 +02:00
// slide(false);
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-13 14:56:13 +02:00
// slide(true);
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-13 14:56:13 +02:00
setUpCustomToken0(numActions % 2 == 0 ? true : false);
2024-07-06 19:25:09 +02:00
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
2024-07-13 14:56:13 +02:00
slide(false);
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);
2024-07-09 18:00:39 +02:00
int24 midTick = token0isWeth ? tickLower + ANCHOR_SPACING : tickUpper - ANCHOR_SPACING;
2024-07-06 19:25:09 +02:00
if (currentTick < midTick) {
// Current tick is below the midpoint, so call slide()
2024-07-13 14:56:13 +02:00
token0isWeth ? shift(): slide(false);
2024-07-06 19:25:09 +02:00
} else if (currentTick > midTick) {
// Current tick is above the midpoint, so call shift()
2024-07-13 14:56:13 +02:00
token0isWeth ? slide(false): shift();
2024-07-06 19:25:09 +02:00
}
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-13 14:56:13 +02:00
slide(true);
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
}