added gov params

This commit is contained in:
JulesCrown 2024-07-16 19:47:39 +02:00
parent 236469f023
commit ae8c3a1e4f
5 changed files with 182 additions and 91 deletions

View file

@ -143,7 +143,23 @@ open features:
- coverage
- overflows
- reentry
- definition of severity of finding
- definition of severity and rewards
- Minor: Results in some unexpected or undesired behavior or disrupts a system function, causing at least minor loss of funds for user or Liquidity Manager. - reward 0.5% share of fees
- Major: Bug capable of creating a severe loss of funds for HARB holders or collapsing large parts of the system - reward: 2% share of fees
- Critical: Bug capable of triggering severe loss of funds for LiquidityManager contract or complete shutdown in Harb/Stake/LiquidityManager contracts - reward: 5%
- limitations: max 10% share of fees, no seat in multisig
- HARB
- mint - limit supply to 2^96?
- Stake
- sharesToAssets - should the average total supply be used for this calculation?
- what if someone calls payTax and exitPosition in the same transaction?
-
- LiquidityManager
- add events
- what to do with stuck funds if slide/shift become inoperable?
- _isPriceStable - // Handle try catch, possibly by trying with a different time interval or providing a default response
- find a way to account harb/eth for attacker
- NFT support of etherscan

View file

@ -17,9 +17,12 @@ import {Math} from "@openzeppelin/utils/math/Math.sol";
contract Harb is ERC20, ERC20Permit {
using Math for uint256;
// only working with UNI V3 1% fee tier pools
uint24 private constant FEE = uint24(10_000);
// from PoolTogether: the beginning timestamp for the first period. This allows us to maximize storage as well as line up periods with a chosen timestamp.
uint256 private immutable PERIOD_OFFSET;
uint256 private immutable PERIOD_LENGTH;
// from PoolTogether: the minimum period length for Observations. When a period elapses, a new Observation is recorded, otherwise the most recent Observation is updated.
uint256 private immutable PERIOD_LENGTH;
//periphery contracts
TwabController private immutable twabController;
@ -54,8 +57,6 @@ contract Harb is ERC20, ERC20Permit {
_;
}
/* ============ Constructor ============ */
/**
* @notice TwabERC20 Constructor
* @param name_ The name of the token
@ -105,8 +106,6 @@ contract Harb is ERC20, ERC20Permit {
return (address(twabController), liquidityManager, stakingPool, liquidityPool);
}
/* ============ External Functions ============ */
/// @notice Allows the liquidityManager to mint tokens for itself.
/// @dev Tokens minted are managed as community liquidity in the Uniswap pool to stabilize HARB prices.
/// Only callable by the Liquidity Manager. Minting rules and limits are defined externally.
@ -156,20 +155,21 @@ contract Harb is ERC20, ERC20Permit {
* @param amount Tokens to mint
*/
function _mint(address receiver, uint256 amount) internal override {
// TODO: limit supply to 2^96?
// make sure staking pool grows proportional to economy
uint256 stakingPoolBalance = balanceOf(stakingPool);
if (stakingPoolBalance > 0) {
uint256 newStake = stakingPoolBalance * amount / (totalSupply() - stakingPoolBalance);
twabController.mint(stakingPool, SafeCast.toUint96(newStake));
emit Transfer(address(0), stakingPool, newStake);
}
twabController.mint(receiver, SafeCast.toUint96(amount));
emit Transfer(address(0), receiver, amount);
if (ubiTitles[receiver].time == 0 && amount > 0) {
// new account, start UBI title
ubiTitles[receiver].sumTaxCollected = sumTaxCollected;
ubiTitles[receiver].time = block.timestamp;
if (amount > 0) {
// make sure staking pool grows proportional to economy
uint256 stakingPoolBalance = balanceOf(stakingPool);
if (stakingPoolBalance > 0) {
uint256 newStake = stakingPoolBalance * amount / (totalSupply() - stakingPoolBalance);
twabController.mint(stakingPool, SafeCast.toUint96(newStake));
emit Transfer(address(0), stakingPool, newStake);
}
twabController.mint(receiver, SafeCast.toUint96(amount));
emit Transfer(address(0), receiver, amount);
if (ubiTitles[receiver].time == 0 && amount > 0) {
// new account, start UBI title
ubiTitles[receiver].sumTaxCollected = sumTaxCollected;
ubiTitles[receiver].time = block.timestamp;
}
}
}
@ -182,15 +182,17 @@ contract Harb is ERC20, ERC20Permit {
* @param _amount The amount of tokens to burn
*/
function _burn(address _owner, uint256 _amount) internal override {
// shrink staking pool proportional to economy
uint256 stakingPoolBalance = balanceOf(stakingPool);
if (stakingPoolBalance > 0) {
uint256 excessStake = stakingPoolBalance * _amount / (totalSupply() - stakingPoolBalance);
twabController.burn(stakingPool, SafeCast.toUint96(excessStake));
emit Transfer(stakingPool, address(0), excessStake);
if (_amount > 0) {
// shrink staking pool proportional to economy
uint256 stakingPoolBalance = balanceOf(stakingPool);
if (stakingPoolBalance > 0) {
uint256 excessStake = stakingPoolBalance * _amount / (totalSupply() - stakingPoolBalance);
twabController.burn(stakingPool, SafeCast.toUint96(excessStake));
emit Transfer(stakingPool, address(0), excessStake);
}
twabController.burn(_owner, SafeCast.toUint96(_amount));
emit Transfer(_owner, address(0), _amount);
}
twabController.burn(_owner, SafeCast.toUint96(_amount));
emit Transfer(_owner, address(0), _amount);
}
/**

View file

@ -26,16 +26,35 @@ import {Harb} from "./Harb.sol";
* @dev Utilizes Uniswap V3's concentrated liquidity feature, enabling highly efficient use of capital.
*/
contract LiquidityManager {
// the minimum granularity of liquidity positions in the Uniswap V3 pool. this is a 1% pool.
int24 internal constant TICK_SPACING = 200;
// defines the width of the anchor position from the current price to discovery position.
int24 internal constant ANCHOR_SPACING = 5 * TICK_SPACING;
// DISCOVERY_SPACING determines the range above the current price where new tokens are minted and sold.
// This spacing is much wider, allowing the contract to place liquidity far from the current market price,
// aiming to capture potential price increases and support token issuance within strategic market bounds.
int24 internal constant DISCOVERY_SPACING = 11000;
// how much more liquidity per tick discovery is holding over anchor
uint128 internal constant DISCOVERY_DEPTH = 450; // 500 // 500%
int24 internal constant MAX_TICK_DEVIATION = 50; // how much is that?
// default fee of 1%
// only working with UNI V3 1% fee tier pools
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%
/* ============ Potential Gov Params ============ */
// ANCHOR_LIQ_SHARE is the mininum share of total ETH in control
// that will be left to put into anchor positon.
uint256 internal constant MIN_ANCHOR_LIQ_SHARE = 5; // 5 = 5%
uint256 internal constant MAX_ANCHOR_LIQ_SHARE = 25;
// virtual liabilities that are added to push the calculated floor price down artificially,
// creating a security margin for attacks on liquidity
uint256 internal constant MIN_CAPITAL_INEFFICIENCY = 100; // 120 = 20%
uint256 internal constant MAX_CAPITAL_INEFFICIENCY = 200;
// the 3 positions this contract is managing
enum Stage { FLOOR, ANCHOR, DISCOVERY }
// the address of the Uniswap V3 factory
@ -53,16 +72,28 @@ contract LiquidityManager {
int24 tickUpper;
}
mapping(Stage => TokenPosition) public positions;
// State variables to track total ETH spent
uint256 public cumulativeVolumeWeightedPrice;
uint256 public cumulativeVolume;
mapping(Stage => TokenPosition) public positions;
// the address where liquidity fees will be sent
address public feeDestination;
// the minimum share of ETH that will be put into the anchor
uint256 public anchorLiquidityShare;
// the higher the inefficiency, the more conservative the positioning of floor
uint256 public capitalInfefficiency;
error ZeroAddressInSetter();
error AddressAlreadySet();
// TODO: add events
event EthScarcity(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, uint256 CAPITAL_INEFFICIENCY, uint256 ANCHOR_LIQ_SHARE, int24 vwapTick);
event EthAbundance(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, uint256 CAPITAL_INEFFICIENCY, uint256 ANCHOR_LIQ_SHARE, int24 vwapTick);
/// @dev Function modifier to ensure that the caller is the feeDestination
modifier onlyFeeDestination() {
require(msg.sender == address(feeDestination), "only callable by feeDestination");
_;
}
/// @notice Creates a liquidity manager for managing Harb token liquidity on Uniswap V3.
/// @param _factory The address of the Uniswap V3 factory.
@ -76,6 +107,8 @@ contract LiquidityManager {
pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
harb = Harb(_harb);
token0isWeth = _WETH9 < _harb;
anchorLiquidityShare = MAX_ANCHOR_LIQ_SHARE;
capitalInfefficiency = MIN_CAPITAL_INEFFICIENCY;
}
/// @notice Callback function that Uniswap V3 calls for liquidity actions requiring minting or burning of tokens.
@ -105,7 +138,18 @@ contract LiquidityManager {
feeDestination = feeDestination_;
}
//TODO: what to do with stuck funds if slide/shift become inoperable?
function setAnchorLiquidityShare(uint256 anchorLiquidityShare_) external onlyFeeDestination {
require(anchorLiquidityShare_ >= MIN_ANCHOR_LIQ_SHARE, "");
require(anchorLiquidityShare_ <= MAX_ANCHOR_LIQ_SHARE, "");
anchorLiquidityShare = anchorLiquidityShare_;
}
function setCapitalInfefficiency(uint256 capitalInfefficiency_) external onlyFeeDestination {
require(capitalInfefficiency_ >= MIN_CAPITAL_INEFFICIENCY, "");
require(capitalInfefficiency_ <= MAX_CAPITAL_INEFFICIENCY, "");
capitalInfefficiency = capitalInfefficiency_;
}
receive() external payable {
}
@ -169,10 +213,9 @@ contract LiquidityManager {
}
/// @notice Internal function to set or adjust the floor, anchor, and discovery positions based on current market conditions and the manager's strategy.
/// @param sqrtPriceX96 The current price, expressed as a square root value that Uniswap V3 uses.
/// @param currentTick The current market tick.
/// @dev Recalculates and realigns all liquidity positions according to the latest market data and strategic requirements.
function _set(uint160 sqrtPriceX96, int24 currentTick) internal {
function _set(int24 currentTick) internal {
// ### set Floor position
int24 vwapTick;
@ -186,24 +229,27 @@ contract LiquidityManager {
}
uint256 ethBalance = (address(this).balance + weth.balanceOf(address(this)));
// leave at least ANCHOR_LIQ_SHARE% of supply for anchor
ethBalance = ethBalance * (100 - ANCHOR_LIQ_SHARE) / 100;
if (ethBalance < requiredEthForBuyback) {
uint256 floorEthBalance = ethBalance * (100 - anchorLiquidityShare) / 100;
if (floorEthBalance < requiredEthForBuyback) {
// not enough ETH, find a lower price
requiredEthForBuyback = ethBalance;
outstandingSupply = outstandingSupply * CAPITAL_INEFFICIENCY / 100;
vwapTick = tickAtPrice(token0isWeth, outstandingSupply , requiredEthForBuyback);
requiredEthForBuyback = floorEthBalance;
vwapTick = tickAtPrice(token0isWeth, outstandingSupply * capitalInfefficiency / 100 , requiredEthForBuyback);
emit EthScarcity(currentTick, ethBalance, outstandingSupply, vwap, capitalInfefficiency, anchorLiquidityShare, vwapTick);
} else if (vwap == 0) {
requiredEthForBuyback = ethBalance;
requiredEthForBuyback = floorEthBalance;
vwapTick = currentTick;
} else {
vwap = cumulativeVolumeWeightedPrice * CAPITAL_INEFFICIENCY / 100 / cumulativeVolume; // in harb/eth
// recalculate vwap with capital inefficiency
vwap = cumulativeVolumeWeightedPrice * capitalInfefficiency / 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;
}
}
emit EthAbundance(currentTick, ethBalance, outstandingSupply, vwap, capitalInfefficiency, anchorLiquidityShare, vwapTick);
}
// never make floor smaller than anchor
if (requiredEthForBuyback < ethBalance * 3 / 4) {
// use 3/4 instead of 1/2 to also account for liquidity of harb in anchor
requiredEthForBuyback = ethBalance * 3 / 4;
}
// move floor below anchor, if needed
if (token0isWeth) {
vwapTick = (vwapTick < currentTick + ANCHOR_SPACING) ? currentTick + ANCHOR_SPACING : vwapTick;
@ -217,13 +263,25 @@ contract LiquidityManager {
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(vwapTick);
int24 floorTick = token0isWeth ? vwapTick + TICK_SPACING: vwapTick - TICK_SPACING;
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(floorTick);
uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
sqrtRatioAX96,
sqrtRatioBX96,
token0isWeth ? requiredEthForBuyback : 0,
token0isWeth ? 0 : requiredEthForBuyback
);
uint128 liquidity;
if (token0isWeth) {
liquidity = LiquidityAmounts.getLiquidityForAmount0(
sqrtRatioAX96, sqrtRatioBX96, requiredEthForBuyback
);
} else {
liquidity = LiquidityAmounts.getLiquidityForAmount1(
sqrtRatioAX96, sqrtRatioBX96, requiredEthForBuyback
);
}
// uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
// sqrtPriceX96,
// sqrtRatioAX96,
// sqrtRatioBX96,
// token0isWeth ? requiredEthForBuyback : 0,
// token0isWeth ? 0 : requiredEthForBuyback
// );
// mint
_mint(Stage.FLOOR, token0isWeth ? vwapTick : floorTick, token0isWeth ? floorTick : vwapTick, liquidity);
@ -231,6 +289,7 @@ contract LiquidityManager {
// ### set Anchor position
uint128 anchorLiquidity;
uint24 anchorWidth;
{
int24 tickLower = token0isWeth ? currentTick - ANCHOR_SPACING : vwapTick;
int24 tickUpper = token0isWeth ? vwapTick : currentTick + ANCHOR_SPACING;
@ -239,14 +298,20 @@ contract LiquidityManager {
uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(currentTick);
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
// adding a 2% balance margin, because liquidity calculations are inherently unprecise
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
);
if (token0isWeth) {
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount0(
sqrtRatioX96, sqrtRatioBX96, ethBalance
);
} else {
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount1(
sqrtRatioAX96, sqrtRatioX96, ethBalance
);
}
anchorWidth = uint24(tickUpper - tickLower);
_mint(Stage.ANCHOR, tickLower, tickUpper, anchorLiquidity);
}
currentTick = currentTick / TICK_SPACING * TICK_SPACING;
@ -257,11 +322,8 @@ contract LiquidityManager {
int24 tickUpper = token0isWeth ? currentTick - ANCHOR_SPACING : currentTick + DISCOVERY_SPACING + ANCHOR_SPACING;
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
// discovery with 1.5 times as much liquidity per tick as anchor
// A * 3 11000 A * 55
// D = ----- * ----- = ------
// 2 600 2
uint128 liquidity = anchorLiquidity * 55 / 2;
// discovery with x times as much liquidity per tick as anchor
uint128 liquidity = anchorLiquidity * uint128(uint24(DISCOVERY_SPACING)) * DISCOVERY_DEPTH / 100 / anchorWidth;
uint256 harbInDiscovery;
if (token0isWeth) {
harbInDiscovery = LiquidityAmounts.getAmount0ForLiquidity(
@ -276,7 +338,6 @@ contract LiquidityManager {
liquidity
);
}
harb.mint(harbInDiscovery);
_mint(Stage.DISCOVERY, tickLower, tickUpper, liquidity);
harb.burn(harb.balanceOf(address(this)));
}
@ -348,7 +409,6 @@ contract LiquidityManager {
averageTick = int24(tickCumulativeDiff / int56(int32(timeInterval)));
// Process the data
} catch {
// TODO: Handle the error, possibly by trying with a different time interval or providing a default response
return true;
}
@ -360,7 +420,7 @@ contract LiquidityManager {
function shift() external {
require(positions[Stage.ANCHOR].liquidity > 0, "Not initialized");
// Fetch the current tick from the Uniswap V3 pool
(uint160 sqrtPriceX96, int24 currentTick, , , , , ) = pool.slot0();
(, int24 currentTick, , , , , ) = pool.slot0();
// check slippage with oracle
require(_isPriceStable(currentTick), "price deviated from oracle");
@ -385,14 +445,14 @@ contract LiquidityManager {
// ## scrape positions
_scrape();
harb.setPreviousTotalSupply(harb.totalSupply());
_set(sqrtPriceX96, currentTick);
_set(currentTick);
}
/// @notice Adjusts liquidity positions downward in response to a decrease in the Harb token's price.
/// @dev This function should be called when significant downward price movement is detected. It recalibrates the liquidity ranges to align with the new market conditions.
function slide() external {
// Fetch the current tick from the Uniswap V3 pool
(uint160 sqrtPriceX96, int24 currentTick, , , , , ) = pool.slot0();
(, int24 currentTick, , , , , ) = pool.slot0();
// check slippage with oracle
require(_isPriceStable(currentTick), "price deviated from oracle");
@ -416,7 +476,7 @@ contract LiquidityManager {
}
_scrape();
_set(sqrtPriceX96, currentTick);
_set(currentTick);
}
}

View file

@ -30,12 +30,17 @@ error TooMuchSnatch(address receiver, uint256 stakeWanted, uint256 availableStak
contract Stake {
using Math for uint256;
// the offset between the "precision" of the representation of shares and assets
// see https://docs.openzeppelin.com/contracts/4.x/erc4626 for reason and details
uint256 internal DECIMAL_OFFSET = 5 + 2;
// only 20% of the total HARB supply can be staked.
uint256 internal constant MAX_STAKE = 20; // 20% of HARB supply
uint256 internal constant TAX_RATE_BASE = 100;
uint256 internal constant TAX_FLOOR_DURATION = 60 * 60 * 24 * 3; //this duration is the minimum basis for fee calculation, regardless of actual holding time.
uint256 internal constant MIN_SUPPLY_FRACTION = 3000;
// the tax rates are discrete to prevent users from snatching by micro incroments of tax
uint256[] public TAX_RATES = [1, 3, 5, 8, 12, 18, 24, 30, 40, 50, 60, 80, 100, 130, 180, 250, 320, 420, 540, 700, 920, 1200, 1600, 2000, 2600, 3400, 4400, 5700, 7500, 9700];
// this is the base for the values in the array above: e.g. 1/100 = 1%
uint256 internal constant TAX_RATE_BASE = 100;
/**
* @dev Attempted to deposit more assets than the max amount for `receiver`.
*/
@ -92,7 +97,6 @@ contract Stake {
/// @param shares Number of shares to convert.
/// @return The equivalent number of Harb tokens for the given shares.
function sharesToAssets(uint256 shares) public view returns (uint256) {
// TODO: should the average total supply be used for this calculation?
return shares.mulDiv(harb.totalSupply(), totalSupply, Math.Rounding.Down);
}
@ -164,7 +168,6 @@ contract Stake {
smallestPositionShare = pos.share;
}
// dissolve position
// TODO: what if someone calls payTax and exitPosition in the same transaction?
_payTax(positionsToSnatch[i], pos, 0);
_exitPosition(positionsToSnatch[i], pos);
}
@ -176,7 +179,6 @@ contract Stake {
uint256 index = positionsToSnatch.length - 1;
StakingPosition storage lastPos = positions[positionsToSnatch[index]];
if (lastPos.creationTime == 0) {
//TODO:
revert PositionNotFound(positionsToSnatch[index], receiver);
}
// check that tax lower
@ -266,7 +268,6 @@ contract Stake {
/// @dev Calculates and pays the tax due, possibly adjusting the position's share count.
function payTax(uint256 positionId) public {
StakingPosition storage pos = positions[positionId];
// TODO: what if someone calls payTax and exitPosition in the same transaction?
_payTax(positionId, pos, 0);
}

View file

@ -136,12 +136,12 @@ contract LiquidityManagerTest 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 * 3, "slide - Floor should hold more ETH than Anchor");
assertGt(harbDiscovery, harbAnchor * 90, "slide - Discovery should hold more HARB than Anchor");
assertGt(ethFloor, ethAnchor, "slide - Floor should hold more ETH than Anchor");
assertGt(harbDiscovery, harbAnchor * 5, "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 Error(string memory reason) {
@ -164,8 +164,8 @@ contract LiquidityManagerTest is Test {
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 * 3, "shift - Floor should hold more ETH than Anchor");
assertGt(harbDiscovery, harbAnchor * 90, "shift - Discovery should hold more HARB than Anchor");
assertGt(ethFloor, ethAnchor, "shift - Floor should hold more ETH than Anchor");
assertGt(harbDiscovery, harbAnchor * 5, "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 Error(string memory reason) {
@ -452,27 +452,30 @@ contract LiquidityManagerTest is Test {
// assertGt(traderBalanceBefore, traderBalanceAfter, "trader should not have made profit");
// }
// function testScenarioB() public {
// setUpCustomToken0(false);
// vm.deal(account, 100 ether);
// vm.deal(account, 201 ether);
// vm.prank(account);
// weth.deposit{value: 100 ether}();
// weth.deposit{value: 201 ether}();
// uint256 traderBalanceBefore = weth.balanceOf(account);
// // Setup initial liquidity
// slide(false);
// buy(2 ether);
// buy(50 ether);
// shift();
// buy(2 ether);
// buy(50 ether);
// shift();
// buy(2 ether);
// buy(50 ether);
// shift();
// buy(50 ether);
// shift();
@ -529,10 +532,18 @@ contract LiquidityManagerTest is Test {
int24 midTick = token0isWeth ? tickLower + ANCHOR_SPACING : tickUpper - ANCHOR_SPACING;
if (currentTick < midTick) {
// Current tick is below the midpoint, so call slide()
token0isWeth ? shift(): slide(false);
if (token0isWeth) {
shift();
} else {
slide(false);
}
} else if (currentTick > midTick) {
// Current tick is above the midpoint, so call shift()
token0isWeth ? slide(false): shift();
if (token0isWeth) {
slide(false);
} else {
shift();
}
}
f = 0;
} else {
@ -550,6 +561,7 @@ contract LiquidityManagerTest is Test {
if (traderBalanceAfter > traderBalanceBefore){
writeCsv();
}
// TODO: take 1% fee into account
assertGt(traderBalanceBefore, traderBalanceAfter, "trader should not have made profit");
}