added gov params
This commit is contained in:
parent
236469f023
commit
ae8c3a1e4f
5 changed files with 182 additions and 91 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue