replaced sentiment with specific params
This commit is contained in:
parent
6fe349de9a
commit
78b48f1639
9 changed files with 187 additions and 618 deletions
|
|
@ -5,7 +5,7 @@ import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol";
|
|||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "../src/Harberg.sol";
|
||||
import "../src/Stake.sol";
|
||||
import "../src/Sentimenter.sol";
|
||||
import "../src/Optimizer.sol";
|
||||
import "../src/helpers/UniswapHelpers.sol";
|
||||
import {LiquidityManager} from "../src/LiquidityManager.sol";
|
||||
import {ERC1967Proxy} from "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
|
|
@ -34,9 +34,9 @@ contract DeployScript is Script {
|
|||
IUniswapV3Factory factory = IUniswapV3Factory(v3Factory);
|
||||
address liquidityPool = factory.createPool(weth, address(harb), FEE);
|
||||
IUniswapV3Pool(liquidityPool).initializePoolFor1Cent(token0isWeth);
|
||||
Sentimenter sentimenter = new Sentimenter();
|
||||
Optimizer optimizer = new Optimizer();
|
||||
bytes memory params = abi.encodeWithSignature("initialize(address,address)", address(harb),address(stake));
|
||||
ERC1967Proxy proxy = new ERC1967Proxy(address(sentimenter), params);
|
||||
ERC1967Proxy proxy = new ERC1967Proxy(address(optimizer), params);
|
||||
LiquidityManager liquidityManager = new LiquidityManager(v3Factory, weth, address(harb), address(proxy));
|
||||
liquidityManager.setFeeDestination(feeDest);
|
||||
// note: this delayed initialization is not a security issue.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {Math} from "@openzeppelin/utils/math/Math.sol";
|
|||
import {ABDKMath64x64} from "@abdk/ABDKMath64x64.sol";
|
||||
import "./interfaces/IWETH9.sol";
|
||||
import {Harberg} from "./Harberg.sol";
|
||||
import {Sentimenter} from "./Sentimenter.sol";
|
||||
import {Optimizer} from "./Optimizer.sol";
|
||||
|
||||
/**
|
||||
* @title LiquidityManager for Harberg Token on Uniswap V3
|
||||
|
|
@ -33,13 +33,11 @@ contract LiquidityManager {
|
|||
uint256 public cumulativeVolume;
|
||||
// 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.
|
||||
// 11000 ticks represent 3x the current price
|
||||
int24 internal constant DISCOVERY_SPACING = 11000;
|
||||
// how much more liquidity per tick discovery is holding over anchor
|
||||
uint128 internal constant DISCOVERY_DEPTH = 200; // 500 // 500%
|
||||
uint128 internal constant MIN_DISCOVERY_DEPTH = 200; // 500 // 500%
|
||||
// only working with UNI V3 1% fee tier pools
|
||||
uint24 internal constant FEE = uint24(10_000);
|
||||
// used to double-check price with uni oracle
|
||||
|
|
@ -50,7 +48,7 @@ contract LiquidityManager {
|
|||
address private immutable factory;
|
||||
IWETH9 private immutable weth;
|
||||
Harberg private immutable harb;
|
||||
Sentimenter private immutable sentimenter;
|
||||
Optimizer private immutable optimizer;
|
||||
IUniswapV3Pool private immutable pool;
|
||||
bool private immutable token0isWeth;
|
||||
PoolKey private poolKey;
|
||||
|
|
@ -73,8 +71,8 @@ contract LiquidityManager {
|
|||
error ZeroAddressInSetter();
|
||||
error AddressAlreadySet();
|
||||
|
||||
event EthScarcity(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, uint256 sentiment, int24 vwapTick);
|
||||
event EthAbundance(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, uint256 sentiment, int24 vwapTick);
|
||||
event EthScarcity(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, int24 vwapTick);
|
||||
event EthAbundance(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, int24 vwapTick);
|
||||
|
||||
/// @dev Function modifier to ensure that the caller is the feeDestination
|
||||
modifier onlyFeeDestination() {
|
||||
|
|
@ -87,14 +85,14 @@ contract LiquidityManager {
|
|||
/// @param _WETH9 The address of the WETH contract for handling ETH in trades.
|
||||
/// @param _harb The address of the Harberg token contract.
|
||||
/// @dev Computes the Uniswap pool address for the Harberg-WETH pair and sets up the initial configuration for the liquidity manager.
|
||||
constructor(address _factory, address _WETH9, address _harb, address _sentimenter) {
|
||||
constructor(address _factory, address _WETH9, address _harb, address _optimizer) {
|
||||
factory = _factory;
|
||||
weth = IWETH9(_WETH9);
|
||||
poolKey = PoolAddress.getPoolKey(_WETH9, _harb, FEE);
|
||||
pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
|
||||
harb = Harberg(_harb);
|
||||
token0isWeth = _WETH9 < _harb;
|
||||
sentimenter = Sentimenter(_sentimenter);
|
||||
optimizer = Optimizer(_optimizer);
|
||||
}
|
||||
|
||||
/// @notice Callback function that Uniswap V3 calls for liquidity actions requiring minting or burning of tokens.
|
||||
|
|
@ -203,41 +201,23 @@ 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 currentTick The current market tick.
|
||||
/// @dev Recalculates and realigns all liquidity positions according to the latest market data and strategic requirements.
|
||||
function _set(int24 currentTick, uint256 sentiment) internal {
|
||||
function _set(int24 currentTick, uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) internal {
|
||||
|
||||
// estimate the lower tick of the anchor
|
||||
int24 vwapTick;
|
||||
uint256 outstandingSupply = harb.outstandingSupply();
|
||||
uint256 ethBalance = (address(this).balance + weth.balanceOf(address(this)));
|
||||
// this enforces an floor liquidity share of 75% to 95 %;
|
||||
uint256 floorEthBalance = (3 * ethBalance / 4) + (2 * sentiment * ethBalance / 10**19);
|
||||
if (outstandingSupply > 0) {
|
||||
// this enables a "capital inefficiency" of 70% to 170%;
|
||||
uint256 balancedCapital = (7 * outstandingSupply / 10) + (outstandingSupply * sentiment / 10**18);
|
||||
vwapTick = tickAtPrice(token0isWeth, balancedCapital, floorEthBalance);
|
||||
} else {
|
||||
vwapTick = token0isWeth ? currentTick + ANCHOR_SPACING : currentTick - ANCHOR_SPACING;
|
||||
}
|
||||
|
||||
// move vwapTick below currentTick, if needed
|
||||
if (token0isWeth) {
|
||||
vwapTick = (vwapTick < currentTick + ANCHOR_SPACING) ? currentTick + ANCHOR_SPACING : vwapTick;
|
||||
} else {
|
||||
vwapTick = (vwapTick > currentTick - ANCHOR_SPACING) ? currentTick - ANCHOR_SPACING : vwapTick;
|
||||
}
|
||||
uint256 floorEthBalance = (19 * ethBalance / 20) - (2 * anchorShare * ethBalance / 10**19);
|
||||
|
||||
// set Anchor position
|
||||
uint256 pulledHarb;
|
||||
// this enforces a anchor range of 1% to 100% of the price
|
||||
int24 anchorSpacing = TICK_SPACING + (34 * int24(anchorWidth) * TICK_SPACING / 100);
|
||||
{
|
||||
int24 tickLower = token0isWeth ? currentTick - ANCHOR_SPACING : vwapTick;
|
||||
int24 tickUpper = token0isWeth ? vwapTick : currentTick + ANCHOR_SPACING;
|
||||
tickLower = tickLower / TICK_SPACING * TICK_SPACING;
|
||||
tickUpper = tickUpper / TICK_SPACING * TICK_SPACING;
|
||||
int24 tickLower = (currentTick - anchorSpacing) / TICK_SPACING * TICK_SPACING;
|
||||
int24 tickUpper = (currentTick + anchorSpacing) / TICK_SPACING * TICK_SPACING;
|
||||
uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
||||
|
||||
|
||||
uint256 anchorEthBalance = ethBalance - floorEthBalance;
|
||||
uint128 anchorLiquidity;
|
||||
if (token0isWeth) {
|
||||
|
|
@ -258,12 +238,13 @@ contract LiquidityManager {
|
|||
// set Discovery position
|
||||
uint256 discoveryAmount;
|
||||
{
|
||||
int24 tickLower = token0isWeth ? currentTick - DISCOVERY_SPACING - ANCHOR_SPACING : currentTick + ANCHOR_SPACING;
|
||||
int24 tickUpper = token0isWeth ? currentTick - ANCHOR_SPACING : currentTick + DISCOVERY_SPACING + ANCHOR_SPACING;
|
||||
int24 tickLower = token0isWeth ? currentTick - DISCOVERY_SPACING - anchorSpacing : currentTick + anchorSpacing;
|
||||
int24 tickUpper = token0isWeth ? currentTick - anchorSpacing : currentTick + DISCOVERY_SPACING + anchorSpacing;
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
||||
|
||||
discoveryAmount = pulledHarb * uint24(DISCOVERY_SPACING) * uint24(DISCOVERY_DEPTH) / uint24(ANCHOR_SPACING) / 100;
|
||||
discoveryDepth = MIN_DISCOVERY_DEPTH + (4 * discoveryDepth * MIN_DISCOVERY_DEPTH / 10**18);
|
||||
discoveryAmount = pulledHarb * uint24(DISCOVERY_SPACING) * uint24(discoveryDepth) / uint24(anchorSpacing) / 100;
|
||||
uint128 liquidity;
|
||||
if (token0isWeth) {
|
||||
liquidity = LiquidityAmounts.getLiquidityForAmount1(
|
||||
|
|
@ -280,23 +261,24 @@ contract LiquidityManager {
|
|||
|
||||
// set Floor position
|
||||
{
|
||||
outstandingSupply = harb.outstandingSupply();
|
||||
int24 vwapTick;
|
||||
uint256 outstandingSupply = harb.outstandingSupply();
|
||||
outstandingSupply -= pulledHarb;
|
||||
outstandingSupply -= (outstandingSupply >= discoveryAmount) ? discoveryAmount : outstandingSupply;
|
||||
uint256 vwapX96 = 0;
|
||||
uint256 requiredEthForBuyback = 0;
|
||||
if (cumulativeVolume > 0) {
|
||||
vwapX96 = cumulativeVolumeWeightedPriceX96 / cumulativeVolume; // in harb/eth
|
||||
vwapX96 = (7 * vwapX96 / 10) + (vwapX96 * sentiment / 10**18);
|
||||
vwapX96 = (7 * vwapX96 / 10) + (vwapX96 * capitalInefficiency / 10**18);
|
||||
requiredEthForBuyback = outstandingSupply.mulDiv(vwapX96, (1 << 96));
|
||||
}
|
||||
// make a new calculation of the vwapTick, having updated outstandingSupply
|
||||
if (floorEthBalance < requiredEthForBuyback) {
|
||||
// not enough ETH, find a lower price
|
||||
requiredEthForBuyback = floorEthBalance;
|
||||
uint256 balancedCapital = (7 * outstandingSupply / 10) + (outstandingSupply * sentiment / 10**18);
|
||||
uint256 balancedCapital = (7 * outstandingSupply / 10) + (outstandingSupply * capitalInefficiency / 10**18);
|
||||
vwapTick = tickAtPrice(token0isWeth, balancedCapital , requiredEthForBuyback);
|
||||
emit EthScarcity(currentTick, ethBalance, outstandingSupply, vwapX96, sentiment, vwapTick);
|
||||
emit EthScarcity(currentTick, ethBalance, outstandingSupply, vwapX96, vwapTick);
|
||||
} else if (vwapX96 == 0) {
|
||||
requiredEthForBuyback = floorEthBalance;
|
||||
vwapTick = currentTick;
|
||||
|
|
@ -305,13 +287,13 @@ contract LiquidityManager {
|
|||
vwapTick = tickAtPriceRatio(int128(int256(vwapX96 >> 32)));
|
||||
// convert to pool tick
|
||||
vwapTick = token0isWeth ? -vwapTick : vwapTick;
|
||||
emit EthAbundance(currentTick, ethBalance, outstandingSupply, vwapX96, sentiment, vwapTick);
|
||||
emit EthAbundance(currentTick, ethBalance, outstandingSupply, vwapX96, vwapTick);
|
||||
}
|
||||
// move floor below anchor, if needed
|
||||
if (token0isWeth) {
|
||||
vwapTick = (vwapTick < currentTick + ANCHOR_SPACING) ? currentTick + ANCHOR_SPACING : vwapTick;
|
||||
vwapTick = (vwapTick < currentTick + anchorSpacing) ? currentTick + anchorSpacing : vwapTick;
|
||||
} else {
|
||||
vwapTick = (vwapTick > currentTick - ANCHOR_SPACING) ? currentTick - ANCHOR_SPACING : vwapTick;
|
||||
vwapTick = (vwapTick > currentTick - anchorSpacing) ? currentTick - anchorSpacing : vwapTick;
|
||||
}
|
||||
|
||||
// normalize tick position for pool
|
||||
|
|
@ -379,8 +361,8 @@ contract LiquidityManager {
|
|||
fee1 += collected1 - amount1;
|
||||
if (i == uint256(Stage.ANCHOR)) {
|
||||
// the historic archor position is only an approximation for the price
|
||||
int24 tick = token0isWeth ? -1 * (position.tickLower + ANCHOR_SPACING): position.tickUpper - ANCHOR_SPACING;
|
||||
currentPrice = priceAtTick(tick);
|
||||
int24 tick = position.tickLower + (position.tickUpper - position.tickLower / 2);
|
||||
currentPrice = priceAtTick(token0isWeth ? -1 * tick : tick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -428,7 +410,7 @@ contract LiquidityManager {
|
|||
|
||||
/// @notice Adjusts liquidity positions in response to an increase or decrease in the Harberg token's price.
|
||||
/// @dev This function should be called when significant price movement is detected. It recalibrates the liquidity ranges to align with the new market conditions.
|
||||
function recenter() external returns (bool isUp, uint256 sentiment) {
|
||||
function recenter() external returns (bool isUp) {
|
||||
// Fetch the current tick from the Uniswap V3 pool
|
||||
(, int24 currentTick, , , , , ) = pool.slot0();
|
||||
|
||||
|
|
@ -447,7 +429,7 @@ contract LiquidityManager {
|
|||
int24 anchorTickUpper = positions[Stage.ANCHOR].tickUpper;
|
||||
|
||||
// center tick can be calculated positive and negative numbers the same
|
||||
int24 centerTick = token0isWeth ? anchorTickLower + ANCHOR_SPACING : anchorTickUpper - ANCHOR_SPACING;
|
||||
int24 centerTick = anchorTickLower + (anchorTickUpper - anchorTickLower);
|
||||
uint256 minAmplitude = uint24(TICK_SPACING) * 2;
|
||||
|
||||
// Determine the correct comparison direction based on token0isWeth
|
||||
|
|
@ -463,16 +445,17 @@ contract LiquidityManager {
|
|||
if (isUp) {
|
||||
harb.setPreviousTotalSupply(harb.totalSupply());
|
||||
}
|
||||
|
||||
try sentimenter.getSentiment() returns (uint256 currentSentiment) {
|
||||
sentiment = (currentSentiment > 10**18) ? 10**18 : currentSentiment;
|
||||
try optimizer.getLiquidityParams() returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) {
|
||||
capitalInefficiency = (capitalInefficiency > 10**18) ? 10**18 : capitalInefficiency;
|
||||
anchorShare = (anchorShare > 10**18) ? 10**18 : anchorShare;
|
||||
anchorWidth = (anchorWidth > 100) ? 100 : anchorWidth;
|
||||
discoveryDepth = (discoveryDepth > 10**18) ? 10**18 : discoveryDepth;
|
||||
// set new positions
|
||||
_set(currentTick, capitalInefficiency, anchorShare, anchorWidth, discoveryDepth);
|
||||
} catch {
|
||||
//sentiment = 10**18 / 2;
|
||||
sentiment = 0;
|
||||
// set new positions with default, average parameters
|
||||
_set(currentTick, 5*10**17, 5*10**17, 5*10, 5*10**17);
|
||||
}
|
||||
|
||||
// set new positions
|
||||
_set(currentTick, sentiment);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
119
onchain/src/Optimizer.sol
Normal file
119
onchain/src/Optimizer.sol
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {Harberg} from "./Harberg.sol";
|
||||
import {Stake} from "./Stake.sol";
|
||||
import {UUPSUpgradeable} from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol";
|
||||
import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol";
|
||||
|
||||
/**
|
||||
* @title Optimizer
|
||||
* @notice This contract (formerly Sentimenter) calculates a “sentiment” value and liquidity parameters
|
||||
* based on the tax rate and the percentage of Harberg staked.
|
||||
* @dev It is upgradeable using UUPS. Only the admin (set during initialization) can upgrade.
|
||||
*/
|
||||
contract Optimizer is Initializable, UUPSUpgradeable {
|
||||
Harberg private harberg;
|
||||
Stake private stake;
|
||||
|
||||
/// @dev Reverts if the caller is not the admin.
|
||||
error UnauthorizedAccount(address account);
|
||||
|
||||
/**
|
||||
* @notice Initialize the Optimizer.
|
||||
* @param _harberg The address of the Harberg token.
|
||||
* @param _stake The address of the Stake contract.
|
||||
*/
|
||||
function initialize(address _harberg, address _stake) initializer public {
|
||||
// Set the admin for upgradeability (using ERC1967Upgrade _changeAdmin)
|
||||
_changeAdmin(msg.sender);
|
||||
harberg = Harberg(_harberg);
|
||||
stake = Stake(_stake);
|
||||
}
|
||||
|
||||
modifier onlyAdmin() {
|
||||
_checkAdmin();
|
||||
_;
|
||||
}
|
||||
|
||||
function _checkAdmin() internal view virtual {
|
||||
if (_getAdmin() != msg.sender) {
|
||||
revert UnauthorizedAccount(msg.sender);
|
||||
}
|
||||
}
|
||||
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyAdmin {}
|
||||
|
||||
/**
|
||||
* @notice Calculates the sentiment based on the average tax rate and the percentage staked.
|
||||
* @param averageTaxRate The average tax rate (as returned by the Stake contract).
|
||||
* @param percentageStaked The percentage (in 1e18 precision) of the authorized stake that is currently staked.
|
||||
* @return sentimentValue A value in the range 0 to 1e18 where 1e18 represents the worst sentiment.
|
||||
*/
|
||||
function calculateSentiment(
|
||||
uint256 averageTaxRate,
|
||||
uint256 percentageStaked
|
||||
) public pure returns (uint256 sentimentValue) {
|
||||
// deltaS is the “slack” available below full staking
|
||||
uint256 deltaS = 1e18 - percentageStaked;
|
||||
|
||||
if (percentageStaked > 92e16) {
|
||||
// If more than 92% of the authorized stake is in use, the sentiment drops rapidly.
|
||||
// Penalty is computed as: (deltaS^3 * averageTaxRate) / (20 * 1e48)
|
||||
uint256 penalty = (deltaS * deltaS * deltaS * averageTaxRate) / (20 * 1e48);
|
||||
sentimentValue = penalty / 2;
|
||||
} else {
|
||||
// For lower staked percentages, sentiment decreases roughly linearly.
|
||||
uint256 baseSentiment = 1e18 - ((percentageStaked * 1e18) / (92e16));
|
||||
// Apply a penalty based on the average tax rate.
|
||||
if (averageTaxRate <= 1e16) {
|
||||
sentimentValue = baseSentiment;
|
||||
} else if (averageTaxRate <= 5e16) {
|
||||
uint256 ratePenalty = ((averageTaxRate - 1e16) * baseSentiment) / (4e16);
|
||||
sentimentValue = baseSentiment > ratePenalty ? baseSentiment - ratePenalty : 0;
|
||||
} else {
|
||||
// For very high tax rates, sentiment is maximally poor.
|
||||
sentimentValue = 1e18;
|
||||
}
|
||||
}
|
||||
return sentimentValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the current sentiment.
|
||||
* @return sentiment A number (with 1e18 precision) representing the staker sentiment.
|
||||
*/
|
||||
function getSentiment() external view returns (uint256 sentiment) {
|
||||
uint256 percentageStaked = stake.getPercentageStaked();
|
||||
uint256 averageTaxRate = stake.getAverageTaxRate();
|
||||
sentiment = calculateSentiment(averageTaxRate, percentageStaked);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns liquidity parameters for the liquidity manager.
|
||||
* @return capitalInefficiency Calculated as (1e18 - sentiment).
|
||||
* @return anchorShare Set equal to the sentiment.
|
||||
* @return anchorWidth Here set to a constant 100 (adjust as needed).
|
||||
* @return discoveryDepth Set equal to the sentiment.
|
||||
*/
|
||||
function getLiquidityParams()
|
||||
external
|
||||
view
|
||||
returns (
|
||||
uint256 capitalInefficiency,
|
||||
uint256 anchorShare,
|
||||
uint24 anchorWidth,
|
||||
uint256 discoveryDepth
|
||||
)
|
||||
{
|
||||
uint256 percentageStaked = stake.getPercentageStaked();
|
||||
uint256 averageTaxRate = stake.getAverageTaxRate();
|
||||
uint256 sentiment = calculateSentiment(averageTaxRate, percentageStaked);
|
||||
capitalInefficiency = 1e18 - sentiment;
|
||||
anchorShare = sentiment;
|
||||
// Here we simply set anchorWidth to 100; adjust this formula if needed.
|
||||
anchorWidth = 100;
|
||||
discoveryDepth = sentiment;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {Harberg} from "./Harberg.sol";
|
||||
import {Stake} from "./Stake.sol";
|
||||
import {UUPSUpgradeable} from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol";
|
||||
import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol";
|
||||
|
||||
contract Sentimenter is Initializable, UUPSUpgradeable {
|
||||
|
||||
Harberg private harberg;
|
||||
Stake private stake;
|
||||
|
||||
/**
|
||||
* @dev The caller account is not authorized to perform an operation.
|
||||
*/
|
||||
error UnauthorizedAccount(address account);
|
||||
|
||||
function initialize(address _harberg, address _stake) initializer public {
|
||||
_changeAdmin(msg.sender);
|
||||
harberg = Harberg(_harberg);
|
||||
stake = Stake(_stake);
|
||||
}
|
||||
/**
|
||||
* @dev Throws if called by any account other than the admin.
|
||||
*/
|
||||
modifier onlyAdmin() {
|
||||
_checkAdmin();
|
||||
_;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @dev Throws if the sender is not the admin.
|
||||
*/
|
||||
function _checkAdmin() internal view virtual {
|
||||
if (_getAdmin() != msg.sender) {
|
||||
revert UnauthorizedAccount(msg.sender);
|
||||
}
|
||||
}
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyAdmin {}
|
||||
|
||||
function calculateSentiment(uint256 averageTaxRate, uint256 percentageStaked) public pure returns (uint256 sentimentValue) {
|
||||
uint256 deltaS = 10**18 - percentageStaked;
|
||||
|
||||
if (percentageStaked > 92 * 10**16) {
|
||||
// Rapid drop for high percentageStaked values
|
||||
uint256 penalty = (deltaS * deltaS * deltaS * averageTaxRate) / (20 * 10**48);
|
||||
sentimentValue = penalty / 2;
|
||||
} else {
|
||||
// Linearly decreasing sentiment value with rising percentageStaked
|
||||
uint256 baseSentiment = 10**18 - (percentageStaked * 10**18) / (92 * 10**16); // Decreases from 10**18 to 0
|
||||
|
||||
// Apply penalty based on averageTaxRate
|
||||
if (averageTaxRate <= 10**16) {
|
||||
sentimentValue = baseSentiment; // No penalty for low averageTaxRate
|
||||
} else if (averageTaxRate <= 5 * 10**16) {
|
||||
uint256 ratePenalty = (averageTaxRate - 10**16) * baseSentiment / (4 * 10**16);
|
||||
sentimentValue = baseSentiment > ratePenalty ? baseSentiment - ratePenalty : 0;
|
||||
} else {
|
||||
sentimentValue = 10**18; // High averageTaxRate results in maximum sentiment value (low sentiment)
|
||||
}
|
||||
}
|
||||
|
||||
return sentimentValue;
|
||||
}
|
||||
|
||||
|
||||
/// @notice Computes the staker sentiment based on the proportion of the authorized stake that is currently staked.
|
||||
/// @return sentiment A number between 0 and 200 indicating the market sentiment.
|
||||
function getSentiment() external view returns (uint256 sentiment) {
|
||||
uint256 percentageStaked = stake.getPercentageStaked();
|
||||
uint256 averageTaxRate = stake.getAverageTaxRate();
|
||||
sentiment = calculateSentiment(averageTaxRate, percentageStaked);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -17,8 +17,8 @@ import "../src/helpers/UniswapHelpers.sol";
|
|||
import {CSVHelper} from "./helpers/CSVHelper.sol";
|
||||
import {CSVManager} from "./helpers/CSVManager.sol";
|
||||
import {UniswapTestBase} from "./helpers/UniswapTestBase.sol";
|
||||
import "../src/Sentimenter.sol";
|
||||
import "../test/mocks/MockSentimenter.sol";
|
||||
import "../src/Optimizer.sol";
|
||||
import "../test/mocks/MockOptimizer.sol";
|
||||
|
||||
address constant TAX_POOL = address(2);
|
||||
// default fee of 1%
|
||||
|
|
@ -89,9 +89,9 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
|
||||
stake = new Stake(address(harberg), feeDestination);
|
||||
harberg.setStakingPool(address(stake));
|
||||
Sentimenter senti = Sentimenter(address(new MockSentimenter()));
|
||||
senti.initialize(address(harberg), address(stake));
|
||||
lm = new LiquidityManager(address(factory), address(weth), address(harberg), address(senti));
|
||||
Optimizer optimizer = Optimizer(address(new MockOptimizer()));
|
||||
optimizer.initialize(address(harberg), address(stake));
|
||||
lm = new LiquidityManager(address(factory), address(weth), address(harberg), address(optimizer));
|
||||
lm.setFeeDestination(feeDestination);
|
||||
vm.prank(feeDestination);
|
||||
harberg.setLiquidityManager(address(lm));
|
||||
|
|
@ -104,25 +104,25 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
uint256 timeBefore = block.timestamp;
|
||||
vm.warp(timeBefore + (60 * 60 * 5));
|
||||
|
||||
try lm.recenter() returns (bool isUp, uint256 sentiment) {
|
||||
try lm.recenter() returns (bool isUp) {
|
||||
|
||||
// Check liquidity positions after slide
|
||||
Response memory rsp;
|
||||
rsp = checkLiquidity(isUp ? "shift" : "slide", sentiment);
|
||||
assertGt(rsp.ethFloor, rsp.ethAnchor, "slide - Floor should hold more ETH than Anchor");
|
||||
assertGt(rsp.harbergDiscovery, rsp.harbergAnchor * 5, "slide - Discovery should hold more HARB than Anchor");
|
||||
assertEq(rsp.harbergFloor, 0, "slide - Floor should have no HARB");
|
||||
assertEq(rsp.ethDiscovery, 0, "slide - Discovery should have no ETH");
|
||||
// Check liquidity positions after slide
|
||||
Response memory rsp;
|
||||
rsp = checkLiquidity(isUp ? "shift" : "slide");
|
||||
assertGt(rsp.ethFloor, rsp.ethAnchor, "slide - Floor should hold more ETH than Anchor");
|
||||
assertGt(rsp.harbergDiscovery, rsp.harbergAnchor * 5, "slide - Discovery should hold more HARB than Anchor");
|
||||
assertEq(rsp.harbergFloor, 0, "slide - Floor should have no HARB");
|
||||
assertEq(rsp.ethDiscovery, 0, "slide - Discovery should have no ETH");
|
||||
|
||||
} catch Error(string memory reason) {
|
||||
if (keccak256(abi.encodePacked(reason)) == keccak256(abi.encodePacked("amplitude not reached."))) {
|
||||
console.log("slide failed on amplitude");
|
||||
} else {
|
||||
if (!last) {
|
||||
revert(reason); // Rethrow the error if it's not the expected message
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch Error(string memory reason) {
|
||||
if (keccak256(abi.encodePacked(reason)) == keccak256(abi.encodePacked("amplitude not reached."))) {
|
||||
console.log("slide failed on amplitude");
|
||||
} else {
|
||||
if (!last) {
|
||||
revert(reason); // Rethrow the error if it's not the expected message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getBalancesPool(LiquidityManager.Stage s) internal view returns (int24 currentTick, int24 tickLower, int24 tickUpper, uint256 ethAmount, uint256 harbergAmount) {
|
||||
|
|
@ -167,7 +167,7 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
}
|
||||
}
|
||||
|
||||
function checkLiquidity(string memory eventName, uint256 sentiment) internal returns (Response memory) {
|
||||
function checkLiquidity(string memory eventName) internal returns (Response memory) {
|
||||
Response memory rsp;
|
||||
int24 currentTick;
|
||||
string memory floorData;
|
||||
|
|
@ -198,19 +198,19 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
}
|
||||
}
|
||||
|
||||
string memory newRow = string(abi.encodePacked(eventName, ",", CSVHelper.intToStr(currentTick), ",", CSVHelper.uintToStr(sentiment / 1e12), ",", floorData, anchorData, discoveryData));
|
||||
string memory newRow = string(abi.encodePacked(eventName, ",", CSVHelper.intToStr(currentTick), ",", floorData, anchorData, discoveryData));
|
||||
appendCSVRow(newRow); // Append the new row to the CSV
|
||||
return rsp;
|
||||
}
|
||||
|
||||
function buy(uint256 amountEth) internal {
|
||||
performSwap(amountEth, true);
|
||||
checkLiquidity(string.concat("buy ", CSVHelper.uintToStr(amountEth)), 0);
|
||||
checkLiquidity(string.concat("buy ", CSVHelper.uintToStr(amountEth)));
|
||||
}
|
||||
|
||||
function sell(uint256 amountHarb) internal {
|
||||
performSwap(amountHarb, false);
|
||||
checkLiquidity(string.concat("sell ", CSVHelper.uintToStr(amountHarb)), 0);
|
||||
checkLiquidity(string.concat("sell ", CSVHelper.uintToStr(amountHarb)));
|
||||
}
|
||||
|
||||
receive() external payable {}
|
||||
|
|
|
|||
|
|
@ -1,122 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "forge-std/console.sol";
|
||||
import "../src/Harberg.sol";
|
||||
import {TooMuchSnatch, Stake} from "../src/Stake.sol";
|
||||
import "../src/Sentimenter.sol";
|
||||
import {ERC1967Proxy} from "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
import {MockSentimenter} from "./mocks/MockSentimenter.sol";
|
||||
|
||||
contract SentimenterTest is Test {
|
||||
Harberg harberg;
|
||||
Stake stake;
|
||||
Sentimenter sentimenter;
|
||||
address liquidityManager;
|
||||
|
||||
function setUp() public {
|
||||
harberg = new Harberg("HARB", "HARB");
|
||||
stake = new Stake(address(harberg), makeAddr("taxRecipient"));
|
||||
harberg.setStakingPool(address(stake));
|
||||
liquidityManager = makeAddr("liquidityManager");
|
||||
harberg.setLiquidityManager(liquidityManager);
|
||||
// deploy upgradeable tuner contract
|
||||
Sentimenter _sentimenter = new Sentimenter();
|
||||
bytes memory params = abi.encodeWithSignature("initialize(address,address)", address(harberg),address(stake));
|
||||
ERC1967Proxy proxy = new ERC1967Proxy(address(_sentimenter), params);
|
||||
sentimenter = Sentimenter(address(proxy));
|
||||
}
|
||||
|
||||
function doSnatch(address staker, uint256 amount, uint32 taxRate) private returns (uint256 positionId) {
|
||||
vm.startPrank(staker);
|
||||
harberg.approve(address(stake), amount);
|
||||
uint256[] memory empty;
|
||||
positionId = stake.snatch(amount, staker, taxRate, empty);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function testSentiment() public {
|
||||
uint256 smallstake = 0.3e17;
|
||||
uint256 stakeOneThird = 1 ether;
|
||||
uint256 stakeTwoThird = 2 ether;
|
||||
address staker = makeAddr("staker");
|
||||
|
||||
// Mint and distribute tokens
|
||||
vm.startPrank(liquidityManager);
|
||||
// mint all the tokens we will need in the test
|
||||
harberg.mint((smallstake + stakeOneThird + stakeTwoThird) * 5);
|
||||
// send 20% of that to staker
|
||||
harberg.transfer(staker, (smallstake + stakeOneThird + stakeTwoThird) * 2);
|
||||
vm.stopPrank();
|
||||
|
||||
// Setup initial stakers
|
||||
uint256 positionId1 = doSnatch(staker, smallstake, 0);
|
||||
|
||||
uint256 sentiment;
|
||||
sentiment = sentimenter.getSentiment();
|
||||
// 0.99 - horrible sentiment
|
||||
assertApproxEqRel(sentiment, 9.9e17, 1e16);
|
||||
|
||||
vm.prank(staker);
|
||||
stake.exitPosition(positionId1);
|
||||
uint256 positionId2 = doSnatch(staker, stakeOneThird, 2);
|
||||
|
||||
sentiment = sentimenter.getSentiment();
|
||||
|
||||
// 0.64 - depressive sentiment
|
||||
assertApproxEqRel(sentiment, 6.4e17, 1e16);
|
||||
|
||||
vm.prank(staker);
|
||||
stake.exitPosition(positionId2);
|
||||
positionId1 = doSnatch(staker, stakeOneThird, 10);
|
||||
positionId2 = doSnatch(staker, stakeTwoThird, 11);
|
||||
|
||||
sentiment = sentimenter.getSentiment();
|
||||
|
||||
// 0.00018 - feaking good sentiment
|
||||
assertApproxEqRel(sentiment, 1.8e14, 1e17);
|
||||
|
||||
vm.startPrank(staker);
|
||||
stake.exitPosition(positionId1);
|
||||
stake.exitPosition(positionId2);
|
||||
vm.stopPrank();
|
||||
positionId1 = doSnatch(staker, stakeOneThird, 29);
|
||||
positionId2 = doSnatch(staker, stakeTwoThird, 29);
|
||||
|
||||
sentiment = sentimenter.getSentiment();
|
||||
// 0.024 - pretty good sentiment
|
||||
assertApproxEqRel(sentiment, 2.4e16, 2e16);
|
||||
|
||||
vm.startPrank(staker);
|
||||
stake.exitPosition(positionId1);
|
||||
stake.exitPosition(positionId2);
|
||||
vm.stopPrank();
|
||||
positionId2 = doSnatch(staker, stakeTwoThird, 15);
|
||||
|
||||
sentiment = sentimenter.getSentiment();
|
||||
|
||||
// 0.17 - positive sentiment
|
||||
assertApproxEqRel(sentiment, 1.7e17, 2e16);
|
||||
|
||||
vm.startPrank(staker);
|
||||
stake.exitPosition(positionId2);
|
||||
vm.stopPrank();
|
||||
|
||||
positionId1 = doSnatch(staker, stakeOneThird, 15);
|
||||
|
||||
sentiment = sentimenter.getSentiment();
|
||||
|
||||
// 0.4 - OK sentiment
|
||||
assertApproxEqRel(sentiment, 3.9e17, 2e16);
|
||||
}
|
||||
|
||||
function testContractUpgrade() public {
|
||||
uint256 sentiment = sentimenter.getSentiment();
|
||||
assertEq(sentiment, 1e18, "should have been upgraded");
|
||||
address newSent = address(new MockSentimenter());
|
||||
sentimenter.upgradeTo(newSent);
|
||||
sentiment = sentimenter.getSentiment();
|
||||
assertEq(sentiment, 0, "should have been upgraded");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,300 +0,0 @@
|
|||
// 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 {PoolAddress, PoolKey} from "@aperture/uni-v3-lib/PoolAddress.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import {Harberg} from "../src/Harberg.sol";
|
||||
import "../src/helpers/UniswapHelpers.sol";
|
||||
import {Stake, ExceededAvailableStake} from "../src/Stake.sol";
|
||||
import {LiquidityManager} from "../src/LiquidityManager.sol";
|
||||
import {UniswapTestBase} from "./helpers/UniswapTestBase.sol";
|
||||
import {CSVHelper} from "./helpers/CSVHelper.sol";
|
||||
import {CSVManager} from "./helpers/CSVManager.sol";
|
||||
import "../src/Sentimenter.sol";
|
||||
import "./mocks/MockSentimenter.sol";
|
||||
|
||||
// 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 SimulationsTest is UniswapTestBase, CSVManager {
|
||||
using UniswapHelpers for IUniswapV3Pool;
|
||||
using CSVHelper for *;
|
||||
|
||||
IUniswapV3Factory factory;
|
||||
Stake stakingPool;
|
||||
PoolKey private poolKey;
|
||||
LiquidityManager lm;
|
||||
address feeDestination = makeAddr("fees");
|
||||
uint256 supplyOnRecenter;
|
||||
uint256 timeOnRecenter;
|
||||
int256 supplyChange;
|
||||
|
||||
struct Position {
|
||||
uint128 liquidity;
|
||||
int24 tickLower;
|
||||
int24 tickUpper;
|
||||
}
|
||||
|
||||
enum ActionType {
|
||||
Buy,
|
||||
Sell,
|
||||
Snatch,
|
||||
Unstake,
|
||||
PayTax,
|
||||
Recenter,
|
||||
Mint,
|
||||
Burn
|
||||
}
|
||||
|
||||
struct Action {
|
||||
uint256 kind; // buy, sell, snatch, unstake, paytax, recenter, mint, burn
|
||||
uint256 amount1; // x , x , x , x , x , x , x , x
|
||||
uint256 amount2; // , , x , , , , x , x
|
||||
string position; // , , x , , , , ,
|
||||
}
|
||||
|
||||
struct Scenario {
|
||||
uint256 VWAP;
|
||||
uint256 comHarbBal;
|
||||
uint256 comStakeShare;
|
||||
Position[] liquidity; // the positions are floor, anchor, liquidity, [comPos1, comPos2 ...]
|
||||
uint160 sqrtPriceX96;
|
||||
uint256 time;
|
||||
bool token0IsWeth;
|
||||
Action[] txns;
|
||||
}
|
||||
|
||||
|
||||
// Utility to deploy dummy contracts
|
||||
function deployDummies(uint count) internal {
|
||||
for (uint i = 0; i < count; i++) {
|
||||
new Dummy(); // Just increment the nonce
|
||||
}
|
||||
}
|
||||
|
||||
function setUp() public {
|
||||
factory = UniswapHelpers.deployUniswapFactory();
|
||||
|
||||
weth = IWETH9(address(new WETH()));
|
||||
harberg = new Harberg("Harberg", "HRB");
|
||||
pool = IUniswapV3Pool(factory.createPool(address(weth), address(harberg), FEE));
|
||||
|
||||
poolKey = PoolAddress.getPoolKey(address(weth), address(harberg), FEE);
|
||||
token0isWeth = address(weth) < address(harberg);
|
||||
//pool.initializePoolFor1Cent(token0isWeth);
|
||||
|
||||
stakingPool = new Stake(address(harberg), feeDestination);
|
||||
harberg.setStakingPool(address(stakingPool));
|
||||
Sentimenter senti = new Sentimenter();
|
||||
senti.initialize(address(harberg), address(stakingPool));
|
||||
lm = new LiquidityManager(address(factory), address(weth), address(harberg), address(senti));
|
||||
lm.setFeeDestination(feeDestination);
|
||||
vm.prank(feeDestination);
|
||||
harberg.setLiquidityManager(address(lm));
|
||||
vm.deal(address(lm), 1 ether);
|
||||
timeOnRecenter = block.timestamp;
|
||||
initializeTimeSeriesCSV();
|
||||
}
|
||||
|
||||
function setUpCustomToken0(bool token0shouldBeWeth) public {
|
||||
factory = UniswapHelpers.deployUniswapFactory();
|
||||
|
||||
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()));
|
||||
harberg = new Harberg("HARB", "HARB");
|
||||
|
||||
// Check if the setup meets the required condition
|
||||
if (token0shouldBeWeth == address(weth) < address(harberg)) {
|
||||
setupComplete = true;
|
||||
} else {
|
||||
// Clear current instances for re-deployment
|
||||
delete weth;
|
||||
delete harberg;
|
||||
retryCount++;
|
||||
}
|
||||
}
|
||||
require(setupComplete, "Setup failed to meet the condition after several retries");
|
||||
|
||||
pool = IUniswapV3Pool(factory.createPool(address(weth), address(harberg), FEE));
|
||||
|
||||
token0isWeth = address(weth) < address(harberg);
|
||||
stakingPool = new Stake(address(harberg), feeDestination);
|
||||
harberg.setStakingPool(address(stakingPool));
|
||||
Sentimenter senti = Sentimenter(address(new MockSentimenter()));
|
||||
senti.initialize(address(harberg), address(stakingPool));
|
||||
lm = new LiquidityManager(address(factory), address(weth), address(harberg), address(senti));
|
||||
lm.setFeeDestination(feeDestination);
|
||||
vm.prank(feeDestination);
|
||||
harberg.setLiquidityManager(address(lm));
|
||||
vm.deal(address(lm), 10 ether);
|
||||
initializePositionsCSV(); // Set up the CSV header
|
||||
}
|
||||
|
||||
|
||||
function buy(uint256 amountEth) internal {
|
||||
performSwap(amountEth, true);
|
||||
}
|
||||
|
||||
function sell(uint256 amountHarb) internal {
|
||||
performSwap(amountHarb, false);
|
||||
}
|
||||
|
||||
receive() external payable {}
|
||||
|
||||
|
||||
function writeCsv() public {
|
||||
writeCSVToFile("./out/timeSeries.csv"); // Write CSV to file
|
||||
}
|
||||
|
||||
function recenter() internal {
|
||||
// have some time pass to record prices in uni oracle
|
||||
uint256 timeBefore = block.timestamp;
|
||||
vm.warp(timeBefore + 5 minutes);
|
||||
|
||||
// uint256
|
||||
// uint256 supplyDelta = currentSupply - supplyOnRecenter;
|
||||
// uint256 timeDelta = block.timestamp - timeOnRecenter;
|
||||
// uint256 growthPerYear = supplyDelta * 52 weeks / timeDelta;
|
||||
// // console.log("supplyOnLastRecenter");
|
||||
// // console.log(supplyOnRecenter);
|
||||
// // console.log("currentSupply");
|
||||
// // console.log(currentSupply);
|
||||
// uint256 growthPercentage = growthPerYear * 100 >= currentSupply ? 101 : growthPerYear * 100 / currentSupply;
|
||||
// supplyOnRecenter = currentSupply;
|
||||
|
||||
timeOnRecenter = block.timestamp;
|
||||
uint256 supplyBefore = harberg.totalSupply();
|
||||
lm.recenter();
|
||||
supplyChange = int256(harberg.totalSupply()) - int256(supplyBefore);
|
||||
// TODO: update supplyChangeOnLastRecenter
|
||||
|
||||
// have some time pass to record prices in uni oracle
|
||||
timeBefore = block.timestamp;
|
||||
vm.warp(timeBefore + 5 minutes);
|
||||
}
|
||||
|
||||
function getPriceInHarb(uint160 sqrtPriceX96) internal view returns (uint256 price) {
|
||||
uint256 sqrtPrice = uint256(sqrtPriceX96);
|
||||
|
||||
if (token0isWeth) {
|
||||
// WETH is token0, price = (sqrtPriceX96 / 2^96)^2
|
||||
price = (sqrtPrice * sqrtPrice) / (1 << 192);
|
||||
} else {
|
||||
// WETH is token1, price = (2^96 / sqrtPriceX96)^2
|
||||
price = ((1 << 192) * 1e18) / (sqrtPrice * sqrtPrice);
|
||||
}
|
||||
}
|
||||
|
||||
function recordState() internal {
|
||||
|
||||
uint160 sqrtPriceX96;
|
||||
uint256 outstandingStake = stakingPool.outstandingStake();
|
||||
|
||||
(sqrtPriceX96, , , , , , ) = pool.slot0();
|
||||
|
||||
string memory newRow = string(abi.encodePacked(CSVHelper.uintToStr(block.timestamp),
|
||||
",", CSVHelper.uintToStr(getPriceInHarb(sqrtPriceX96)),
|
||||
",", CSVHelper.uintToStr(harberg.totalSupply() / 1e18),
|
||||
",", CSVHelper.intToStr(supplyChange / 1e18),
|
||||
",", CSVHelper.uintToStr(outstandingStake * 500 / 1e25)
|
||||
));
|
||||
if (outstandingStake > 0) {
|
||||
uint256 sentiment;
|
||||
uint256 avgTaxRate;
|
||||
//(sentiment, avgTaxRate) = stakingPool.getSentiment();
|
||||
newRow = string.concat(newRow,
|
||||
",", CSVHelper.uintToStr(avgTaxRate),
|
||||
",", CSVHelper.uintToStr(sentiment)
|
||||
);
|
||||
} else {
|
||||
newRow = string.concat(newRow, ", 0, 100, 95, 25, 0");
|
||||
}
|
||||
appendCSVRow(newRow); // Append the new row to the CSV
|
||||
}
|
||||
|
||||
function stake(uint256 harbAmount, uint32 taxRateIndex) internal returns (uint256) {
|
||||
vm.startPrank(account);
|
||||
harberg.approve(address(stakingPool), harbAmount);
|
||||
uint256[] memory empty;
|
||||
uint256 posId = stakingPool.snatch(harbAmount, account, taxRateIndex, empty);
|
||||
vm.stopPrank();
|
||||
return posId;
|
||||
}
|
||||
|
||||
function unstake(uint256 positionId) internal {
|
||||
vm.prank(account);
|
||||
stakingPool.exitPosition(positionId);
|
||||
}
|
||||
|
||||
function handleAction(Action memory action) internal {
|
||||
if (action.kind == uint256(ActionType.Buy)) {
|
||||
buy(action.amount1 * 10**18);
|
||||
} else if (action.kind == uint256(ActionType.Sell)) {
|
||||
sell(action.amount1 * 10**18);
|
||||
} else if (action.kind == uint256(ActionType.Snatch)) {
|
||||
stake(action.amount1 ** 10**18, uint32(action.amount2));
|
||||
} else if (action.kind == uint256(ActionType.Unstake)) {
|
||||
unstake(action.amount1);
|
||||
} else if (action.kind == uint256(ActionType.PayTax)) {
|
||||
stakingPool.payTax(action.amount1);
|
||||
} else if (action.kind == uint256(ActionType.Recenter)) {
|
||||
uint256 timeBefore = block.timestamp;
|
||||
vm.warp(timeBefore + action.amount1);
|
||||
recenter();
|
||||
}
|
||||
}
|
||||
|
||||
function testGeneration() public {
|
||||
// for each member of the generation, run all scenarios
|
||||
string memory json = vm.readFile("test/data/scenarios.json");
|
||||
bytes memory data = vm.parseJson(json);
|
||||
Scenario memory scenario = abi.decode(data, (Scenario));
|
||||
// vm.deal(account, scenario.comEthBal * 10**18);
|
||||
vm.prank(account);
|
||||
pool.initialize(scenario.sqrtPriceX96);
|
||||
// initialize the liquidity
|
||||
for (uint256 i = 0; i < scenario.liquidity.length; i++) {
|
||||
|
||||
pool.mint(
|
||||
address(this),
|
||||
scenario.liquidity[i].tickLower,
|
||||
scenario.liquidity[i].tickUpper,
|
||||
scenario.liquidity[i].liquidity,
|
||||
abi.encode(poolKey)
|
||||
);
|
||||
}
|
||||
weth.deposit{value: address(account).balance}();
|
||||
|
||||
for (uint256 i = 0; i < scenario.txns.length; i++) {
|
||||
handleAction(scenario.txns[i]);
|
||||
recordState();
|
||||
}
|
||||
|
||||
//writeCsv();
|
||||
|
||||
// for each member, combine the single results into an overall fitness core
|
||||
// apply the selection
|
||||
// apply mating
|
||||
// apply mutation
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
{
|
||||
"VWAP": 0,
|
||||
"comHarbBal": 0,
|
||||
"comStakeShare": 0,
|
||||
"liquidity": [{
|
||||
"liquidity": 0,
|
||||
"tickLower": -123891,
|
||||
"tickUpper": -125000
|
||||
}, {
|
||||
"liquidity": 0,
|
||||
"tickLower": -123891,
|
||||
"tickUpper": -125000
|
||||
}, {
|
||||
"liquidity": 0,
|
||||
"tickLower": -123891,
|
||||
"tickUpper": -125000
|
||||
}],
|
||||
"sqrtPriceX96": 38813714283599478074587411019430,
|
||||
"time": 0,
|
||||
"token0IsWeth": true,
|
||||
"txns": [{
|
||||
"kind": 0,
|
||||
"amount1": 10,
|
||||
"amount2": 0,
|
||||
"position": ""
|
||||
}, {
|
||||
"kind": 1,
|
||||
"amount1": 1,
|
||||
"amount2": 0,
|
||||
"position": ""
|
||||
}]
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ import {Stake} from "../../src/Stake.sol";
|
|||
import {UUPSUpgradeable} from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol";
|
||||
import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol";
|
||||
|
||||
contract MockSentimenter is Initializable, UUPSUpgradeable {
|
||||
contract MockOptimizer is Initializable, UUPSUpgradeable {
|
||||
|
||||
Harberg private harberg;
|
||||
Stake private stake;
|
||||
Loading…
Add table
Add a link
Reference in a new issue