better tests
This commit is contained in:
parent
ac715c544a
commit
ab127336c8
1 changed files with 216 additions and 106 deletions
|
|
@ -24,25 +24,36 @@ import {Harberg} from "../src/Harberg.sol";
|
|||
import {Stake, ExceededAvailableStake} from "../src/Stake.sol";
|
||||
import {LiquidityManager} from "../src/LiquidityManager.sol";
|
||||
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/Optimizer.sol";
|
||||
import "../test/mocks/MockOptimizer.sol";
|
||||
|
||||
// Test constants
|
||||
address constant TAX_POOL = address(2);
|
||||
uint24 constant FEE = uint24(10_000); // 1% fee
|
||||
int24 constant TICK_SPACING = 200;
|
||||
int24 constant ANCHOR_SPACING = 5 * TICK_SPACING;
|
||||
int24 constant EXTREME_PRICE_MARGIN = 12000; // Safety margin from tick boundaries
|
||||
|
||||
// Time constants
|
||||
uint256 constant ORACLE_UPDATE_INTERVAL = 5 hours;
|
||||
|
||||
// Trading constants
|
||||
uint256 constant NORMALIZATION_SELL_PERCENTAGE = 100; // 1%
|
||||
uint256 constant NORMALIZATION_BUY_AMOUNT = 0.01 ether;
|
||||
uint256 constant BALANCE_DIVISOR = 2;
|
||||
uint256 constant MIN_TRADE_AMOUNT = 1 ether;
|
||||
|
||||
// Error handling constants
|
||||
bytes32 constant AMPLITUDE_ERROR = keccak256("amplitude not reached.");
|
||||
bytes32 constant EXPENSIVE_HARB_ERROR = keccak256("HARB extremely expensive: perform swap to normalize price before recenter");
|
||||
bytes32 constant PROTOCOL_DEATH_ERROR = keccak256("Protocol death: Insufficient ETH reserves to support HARB at extremely low prices");
|
||||
|
||||
// Dummy.sol
|
||||
contract Dummy {
|
||||
// This contract can be empty as it is only used to affect the nonce
|
||||
}
|
||||
|
||||
contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
||||
contract LiquidityManagerTest is UniswapTestBase {
|
||||
|
||||
// Setup configuration
|
||||
bool constant DEFAULT_TOKEN0_IS_WETH = false;
|
||||
|
|
@ -51,7 +62,6 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
// Flag to skip automatic setUp for tests that need custom setup
|
||||
bool private _skipAutoSetup;
|
||||
using UniswapHelpers for IUniswapV3Pool;
|
||||
using CSVHelper for *;
|
||||
|
||||
IUniswapV3Factory factory;
|
||||
Stake stake;
|
||||
|
|
@ -67,18 +77,36 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
uint256 harbergDiscovery;
|
||||
}
|
||||
|
||||
// Utility to deploy dummy contracts
|
||||
/// @notice Utility to deploy dummy contracts for address manipulation
|
||||
/// @param count Number of dummy contracts to deploy
|
||||
/// @dev Used to manipulate contract deployment addresses for token ordering
|
||||
function deployDummies(uint count) internal {
|
||||
for (uint i = 0; i < count; i++) {
|
||||
new Dummy(); // Just increment the nonce
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Main setup function with custom token order
|
||||
/// @param token0shouldBeWeth Whether token0 should be WETH (affects pool pair ordering)
|
||||
function setUpCustomToken0(bool token0shouldBeWeth) public {
|
||||
_deployFactory();
|
||||
_deployTokensWithOrder(token0shouldBeWeth);
|
||||
_createAndInitializePool();
|
||||
_deployProtocolContracts();
|
||||
_configurePermissions();
|
||||
}
|
||||
|
||||
/// @notice Deploys the Uniswap factory
|
||||
function _deployFactory() internal {
|
||||
factory = UniswapHelpers.deployUniswapFactory();
|
||||
|
||||
}
|
||||
|
||||
/// @notice Deploys tokens in the specified order
|
||||
/// @param token0shouldBeWeth Whether token0 should be WETH
|
||||
function _deployTokensWithOrder(bool token0shouldBeWeth) internal {
|
||||
bool setupComplete = false;
|
||||
uint retryCount = 0;
|
||||
|
||||
while (!setupComplete && retryCount < 5) {
|
||||
// Clean slate if retrying
|
||||
if (retryCount > 0) {
|
||||
|
|
@ -99,72 +127,113 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
}
|
||||
}
|
||||
require(setupComplete, "Setup failed to meet the condition after several retries");
|
||||
|
||||
}
|
||||
|
||||
/// @notice Creates and initializes the Uniswap pool
|
||||
function _createAndInitializePool() internal {
|
||||
pool = IUniswapV3Pool(factory.createPool(address(weth), address(harberg), FEE));
|
||||
|
||||
token0isWeth = address(weth) < address(harberg);
|
||||
pool.initializePoolFor1Cent(token0isWeth);
|
||||
|
||||
}
|
||||
|
||||
/// @notice Deploys protocol contracts (Stake, Optimizer, LiquidityManager)
|
||||
function _deployProtocolContracts() internal {
|
||||
stake = new Stake(address(harberg), feeDestination);
|
||||
harberg.setStakingPool(address(stake));
|
||||
Optimizer optimizer = Optimizer(address(new MockOptimizer()));
|
||||
optimizer.initialize(address(harberg), address(stake));
|
||||
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);
|
||||
}
|
||||
|
||||
/// @notice Configures permissions and initial funding
|
||||
function _configurePermissions() internal {
|
||||
harberg.setStakingPool(address(stake));
|
||||
vm.prank(feeDestination);
|
||||
harberg.setLiquidityManager(address(lm));
|
||||
vm.deal(address(lm), 10 ether);
|
||||
initializePositionsCSV(); // Set up the CSV header
|
||||
}
|
||||
|
||||
/// @notice Intelligent recenter function that handles extreme price conditions
|
||||
/// @param last Whether this is the last attempt (affects error handling)
|
||||
function recenter(bool last) internal {
|
||||
// have some time pass to record prices in uni oracle
|
||||
_updateOracleTime();
|
||||
_handleExtremePrice();
|
||||
_attemptRecenter(last);
|
||||
}
|
||||
|
||||
/// @notice Updates oracle time to ensure accurate price data
|
||||
function _updateOracleTime() internal {
|
||||
uint256 timeBefore = block.timestamp;
|
||||
vm.warp(timeBefore + (60 * 60 * 5));
|
||||
|
||||
// Check current price before attempting recenter
|
||||
vm.warp(timeBefore + ORACLE_UPDATE_INTERVAL);
|
||||
}
|
||||
|
||||
/// @notice Handles extreme price conditions with normalizing swaps
|
||||
function _handleExtremePrice() internal {
|
||||
(, int24 currentTick, , , , , ) = pool.slot0();
|
||||
|
||||
// Handle extreme expensive HARB (near MAX_TICK) - perform swap first
|
||||
if (currentTick >= TickMath.MAX_TICK - EXTREME_PRICE_MARGIN) {
|
||||
if (_isExtremelyExpensive(currentTick)) {
|
||||
console.log("Detected extremely expensive HARB, performing normalizing swap...");
|
||||
_performNormalizingSwap(currentTick, true); // true = expensive HARB
|
||||
}
|
||||
|
||||
// Handle extreme cheap HARB (near MIN_TICK) - perform swap first
|
||||
if (currentTick <= TickMath.MIN_TICK + EXTREME_PRICE_MARGIN) {
|
||||
_performNormalizingSwap(currentTick, true);
|
||||
} else if (_isExtremelyCheap(currentTick)) {
|
||||
console.log("Detected extremely cheap HARB, performing normalizing swap...");
|
||||
_performNormalizingSwap(currentTick, false); // false = cheap HARB
|
||||
_performNormalizingSwap(currentTick, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @notice Attempts the recenter operation with proper error handling
|
||||
/// @param last Whether this is the last attempt (affects error handling)
|
||||
function _attemptRecenter(bool last) internal {
|
||||
try lm.recenter() returns (bool isUp) {
|
||||
|
||||
// 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");
|
||||
|
||||
_validateRecenterResult(isUp);
|
||||
} catch Error(string memory reason) {
|
||||
if (keccak256(abi.encodePacked(reason)) == keccak256(abi.encodePacked("amplitude not reached."))) {
|
||||
console.log("slide failed on amplitude");
|
||||
} else if (keccak256(abi.encodePacked(reason)) == keccak256(abi.encodePacked("HARB extremely expensive: perform swap to normalize price before recenter"))) {
|
||||
console.log("[SUCCESS] LiquidityManager correctly detected expensive HARB and provided clear guidance");
|
||||
console.log("This demonstrates proper error handling when client-side normalization fails");
|
||||
// This is success - the protocol is working as designed
|
||||
} else if (keccak256(abi.encodePacked(reason)) == keccak256(abi.encodePacked("Protocol death: Insufficient ETH reserves to support HARB at extremely low prices"))) {
|
||||
console.log("Protocol death detected - insufficient ETH reserves");
|
||||
if (!last) {
|
||||
revert(reason);
|
||||
}
|
||||
} else {
|
||||
if (!last) {
|
||||
revert(reason); // Rethrow the error if it's not the expected message
|
||||
}
|
||||
_handleRecenterError(reason, last);
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Checks if HARB price is extremely expensive
|
||||
/// @param currentTick The current price tick
|
||||
/// @return True if HARB is extremely expensive
|
||||
function _isExtremelyExpensive(int24 currentTick) internal pure returns (bool) {
|
||||
return currentTick >= TickMath.MAX_TICK - EXTREME_PRICE_MARGIN;
|
||||
}
|
||||
|
||||
/// @notice Checks if HARB price is extremely cheap
|
||||
/// @param currentTick The current price tick
|
||||
/// @return True if HARB is extremely cheap
|
||||
function _isExtremelyCheap(int24 currentTick) internal pure returns (bool) {
|
||||
return currentTick <= TickMath.MIN_TICK + EXTREME_PRICE_MARGIN;
|
||||
}
|
||||
|
||||
/// @notice Validates recenter operation results
|
||||
/// @param isUp Whether the recenter moved positions up or down
|
||||
function _validateRecenterResult(bool isUp) internal view {
|
||||
Response memory liquidityResponse = checkLiquidity(isUp ? "shift" : "slide");
|
||||
assertGt(liquidityResponse.ethFloor, liquidityResponse.ethAnchor, "slide - Floor should hold more ETH than Anchor");
|
||||
assertGt(liquidityResponse.harbergDiscovery, liquidityResponse.harbergAnchor * 5, "slide - Discovery should hold more HARB than Anchor");
|
||||
assertEq(liquidityResponse.harbergFloor, 0, "slide - Floor should have no HARB");
|
||||
assertEq(liquidityResponse.ethDiscovery, 0, "slide - Discovery should have no ETH");
|
||||
}
|
||||
|
||||
/// @notice Handles recenter operation errors
|
||||
/// @param reason The error reason string
|
||||
/// @param last Whether this is the last attempt
|
||||
function _handleRecenterError(string memory reason, bool last) internal view {
|
||||
bytes32 errorHash = keccak256(abi.encodePacked(reason));
|
||||
|
||||
if (errorHash == AMPLITUDE_ERROR) {
|
||||
console.log("slide failed on amplitude");
|
||||
} else if (errorHash == EXPENSIVE_HARB_ERROR) {
|
||||
console.log("[SUCCESS] LiquidityManager correctly detected expensive HARB and provided clear guidance");
|
||||
console.log("This demonstrates proper error handling when client-side normalization fails");
|
||||
// This is success - the protocol is working as designed
|
||||
} else if (errorHash == PROTOCOL_DEATH_ERROR) {
|
||||
console.log("Protocol death detected - insufficient ETH reserves");
|
||||
if (!last) {
|
||||
revert(reason);
|
||||
}
|
||||
} else {
|
||||
if (!last) {
|
||||
revert(reason); // Rethrow the error if it's not the expected message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -182,7 +251,7 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
// Get HARB balance from account (who has been buying) to use for normalization
|
||||
uint256 accountHarbBalance = harberg.balanceOf(account);
|
||||
if (accountHarbBalance > 0) {
|
||||
uint256 harbToSell = accountHarbBalance / 100; // Sell 1% of account's HARB balance
|
||||
uint256 harbToSell = accountHarbBalance / NORMALIZATION_SELL_PERCENTAGE; // Sell 1% of account's HARB balance
|
||||
if (harbToSell == 0) harbToSell = 1; // Minimum 1 wei
|
||||
|
||||
vm.prank(account);
|
||||
|
|
@ -202,7 +271,7 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
} else {
|
||||
// HARB is extremely cheap - we need to bring the price UP
|
||||
// This means we need to BUY HARB with ETH (not sell HARB)
|
||||
uint256 ethToBuy = 0.01 ether; // Small amount for price normalization
|
||||
uint256 ethToBuy = NORMALIZATION_BUY_AMOUNT; // Small amount for price normalization
|
||||
|
||||
// Ensure we have enough ETH
|
||||
if (weth.balanceOf(address(this)) < ethToBuy) {
|
||||
|
|
@ -220,6 +289,14 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
console.log("Price change:", vm.toString(newTick - currentTick), "ticks");
|
||||
}
|
||||
|
||||
/// @notice Retrieves liquidity position information for a specific stage
|
||||
/// @param s The liquidity stage (FLOOR, ANCHOR, DISCOVERY)
|
||||
/// @return currentTick Current price tick of the pool
|
||||
/// @return tickLower Lower bound of the position's price range
|
||||
/// @return tickUpper Upper bound of the position's price range
|
||||
/// @return ethAmount Amount of ETH in the position
|
||||
/// @return harbergAmount Amount of HARB in the position
|
||||
/// @dev Calculates actual token amounts based on current pool price and position liquidity
|
||||
function getBalancesPool(LiquidityManager.Stage s) internal view returns (int24 currentTick, int24 tickLower, int24 tickUpper, uint256 ethAmount, uint256 harbergAmount) {
|
||||
(,tickLower, tickUpper) = lm.positions(s);
|
||||
(uint128 liquidity, , , ,) = pool.positions(keccak256(abi.encodePacked(address(lm), tickLower, tickUpper)));
|
||||
|
|
@ -262,12 +339,13 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
}
|
||||
}
|
||||
|
||||
function checkLiquidity(string memory eventName) internal returns (Response memory) {
|
||||
Response memory rsp;
|
||||
/// @notice Checks and validates current liquidity positions across all stages
|
||||
/// @return liquidityResponse Structure containing ETH and HARB amounts for each position
|
||||
/// @dev Aggregates position data from FLOOR, ANCHOR, and DISCOVERY stages
|
||||
function checkLiquidity(string memory /* eventName */) internal view returns (Response memory) {
|
||||
Response memory liquidityResponse;
|
||||
int24 currentTick;
|
||||
string memory floorData;
|
||||
string memory anchorData;
|
||||
string memory discoveryData;
|
||||
|
||||
{
|
||||
int24 tickLower;
|
||||
int24 tickUpper;
|
||||
|
|
@ -275,48 +353,50 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
uint256 harb;
|
||||
{
|
||||
(currentTick, tickLower, tickUpper, eth, harb) = getBalancesPool(LiquidityManager.Stage.FLOOR);
|
||||
floorData = string(abi.encodePacked(CSVHelper.intToStr(tickLower), ",", CSVHelper.intToStr(tickUpper), ",", CSVHelper.uintToStr(eth), ",", CSVHelper.uintToStr(harb), ","));
|
||||
rsp.ethFloor = eth;
|
||||
rsp.harbergFloor = harb;
|
||||
liquidityResponse.ethFloor = eth;
|
||||
liquidityResponse.harbergFloor = harb;
|
||||
}
|
||||
{
|
||||
(,tickLower, tickUpper, eth, harb) = getBalancesPool(LiquidityManager.Stage.ANCHOR);
|
||||
anchorData = string(abi.encodePacked(CSVHelper.intToStr(tickLower), ",", CSVHelper.intToStr(tickUpper), ",", CSVHelper.uintToStr(eth), ",", CSVHelper.uintToStr(harb), ","));
|
||||
rsp.ethAnchor = eth;
|
||||
rsp.harbergAnchor = harb;
|
||||
liquidityResponse.ethAnchor = eth;
|
||||
liquidityResponse.harbergAnchor = harb;
|
||||
}
|
||||
{
|
||||
(,tickLower, tickUpper, eth, harb) = getBalancesPool(LiquidityManager.Stage.DISCOVERY);
|
||||
discoveryData = string(abi.encodePacked(CSVHelper.intToStr(tickLower), ",", CSVHelper.intToStr(tickUpper), ",", CSVHelper.uintToStr(eth), ",", CSVHelper.uintToStr(harb), ","));
|
||||
rsp.ethDiscovery = eth;
|
||||
rsp.harbergDiscovery = harb;
|
||||
liquidityResponse.ethDiscovery = eth;
|
||||
liquidityResponse.harbergDiscovery = harb;
|
||||
}
|
||||
}
|
||||
|
||||
string memory newRow = string(abi.encodePacked(eventName, ",", CSVHelper.intToStr(currentTick), ",", floorData, anchorData, discoveryData));
|
||||
appendCSVRow(newRow); // Append the new row to the CSV
|
||||
return rsp;
|
||||
return liquidityResponse;
|
||||
}
|
||||
|
||||
/// @notice Executes a buy operation (ETH -> HARB)
|
||||
/// @param amountEth Amount of ETH to spend buying HARB
|
||||
/// @dev Wrapper around performSwap with liquidity validation
|
||||
function buy(uint256 amountEth) internal {
|
||||
performSwap(amountEth, true);
|
||||
checkLiquidity(string.concat("buy ", CSVHelper.uintToStr(amountEth)));
|
||||
checkLiquidity("buy");
|
||||
}
|
||||
|
||||
/// @notice Executes a sell operation (HARB -> ETH)
|
||||
/// @param amountHarb Amount of HARB to sell for ETH
|
||||
/// @dev Wrapper around performSwap with liquidity validation
|
||||
function sell(uint256 amountHarb) internal {
|
||||
performSwap(amountHarb, false);
|
||||
checkLiquidity(string.concat("sell ", CSVHelper.uintToStr(amountHarb)));
|
||||
checkLiquidity("sell");
|
||||
}
|
||||
|
||||
/// @notice Allows contract to receive ETH directly
|
||||
/// @dev Required for WETH unwrapping operations during testing
|
||||
receive() external payable {}
|
||||
|
||||
|
||||
/// @notice Write CSV data - moved to ScenarioAnalysis for research use
|
||||
/// @dev This function remains for backward compatibility but should not be used in unit tests
|
||||
function writeCsv() public {
|
||||
writeCSVToFile("./out/positions.csv");
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// OVERFLOW AND ARITHMETIC TESTS
|
||||
// ========================================
|
||||
|
||||
/// @notice Tests overflow handling in cumulative calculations
|
||||
/// @dev Simulates extreme values that could cause arithmetic overflow
|
||||
function testHandleCumulativeOverflow() public {
|
||||
|
|
@ -397,6 +477,10 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
recenter(false);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// EXTREME PRICE HANDLING TESTS
|
||||
// ========================================
|
||||
|
||||
/// @notice Tests handling of extremely expensive HARB prices near MAX_TICK
|
||||
/// @dev Validates client-side price detection and normalization swaps
|
||||
function testExtremeExpensiveHarbHandling() public {
|
||||
|
|
@ -422,8 +506,8 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
console.log("! Price not extreme enough, pushing further...");
|
||||
// Try to push further if needed
|
||||
uint256 remainingEth = weth.balanceOf(account);
|
||||
if (remainingEth > 1 ether) {
|
||||
buy(remainingEth / 2);
|
||||
if (remainingEth > MIN_TRADE_AMOUNT) {
|
||||
buy(remainingEth / BALANCE_DIVISOR);
|
||||
(, postBuyTick, , , , , ) = pool.slot0();
|
||||
console.log("Tick after additional buy:", vm.toString(postBuyTick));
|
||||
}
|
||||
|
|
@ -521,6 +605,10 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
return abi.decode(data, (string));
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// EDGE CASE AND FAILURE CLASSIFICATION TESTS
|
||||
// ========================================
|
||||
|
||||
/// @notice Tests systematic classification of different failure modes
|
||||
/// @dev Performs multiple trading cycles to trigger various edge cases
|
||||
function testEdgeCaseClassification() public {
|
||||
|
|
@ -533,19 +621,19 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
|
||||
// Perform a series of trades that might push to different edge cases
|
||||
for (uint i = 0; i < 30; i++) {
|
||||
uint256 amount = (i * 1 ether / 10) + 1 ether;
|
||||
uint256 amount = (i * MIN_TRADE_AMOUNT / 10) + MIN_TRADE_AMOUNT;
|
||||
uint256 harbergBal = harberg.balanceOf(account);
|
||||
|
||||
// Trading logic
|
||||
if (harbergBal == 0) {
|
||||
amount = amount % (weth.balanceOf(account) / 2);
|
||||
amount = amount % (weth.balanceOf(account) / BALANCE_DIVISOR);
|
||||
amount = amount == 0 ? weth.balanceOf(account) / 10 : amount;
|
||||
if (amount > 0) buy(amount);
|
||||
} else if (weth.balanceOf(account) == 0) {
|
||||
if (harbergBal > 0) sell(amount % harbergBal);
|
||||
} else {
|
||||
if (i % 2 == 0) {
|
||||
amount = amount % (weth.balanceOf(account) / 2);
|
||||
amount = amount % (weth.balanceOf(account) / BALANCE_DIVISOR);
|
||||
amount = amount == 0 ? weth.balanceOf(account) / 10 : amount;
|
||||
if (amount > 0) buy(amount);
|
||||
} else {
|
||||
|
|
@ -597,6 +685,10 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
// Test passes if we reach here without reverting
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// PROTOCOL DEATH AND SCENARIO ANALYSIS TESTS
|
||||
// ========================================
|
||||
|
||||
/// @notice Tests distinction between protocol death and recoverable edge cases
|
||||
/// @dev Analyzes ETH reserves vs outstanding HARB to diagnose scenario type
|
||||
function testProtocolDeathVsEdgeCase() public {
|
||||
|
|
@ -658,8 +750,43 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
|
||||
// Test passes if we reach here without reverting
|
||||
}
|
||||
|
||||
/// @notice Executes a single random trade based on available balances
|
||||
/// @param amount Base amount for trade calculations
|
||||
/// @param harbergBal Current HARB balance of the account
|
||||
/// @dev Uses balance-based logic to determine trade type and amount
|
||||
function _executeRandomTrade(uint256 amount, uint256 harbergBal) internal {
|
||||
if (harbergBal == 0) {
|
||||
// Only WETH available - buy HARB
|
||||
amount = _calculateBuyAmount(amount);
|
||||
if (amount > 0) buy(amount);
|
||||
} else if (weth.balanceOf(account) == 0) {
|
||||
// Only HARB available - sell HARB
|
||||
sell(amount % harbergBal);
|
||||
} else {
|
||||
// Both tokens available - decide randomly
|
||||
if (amount % 2 == 0) {
|
||||
amount = _calculateBuyAmount(amount);
|
||||
if (amount > 0) buy(amount);
|
||||
} else {
|
||||
sell(amount % harbergBal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Calculates appropriate buy amount based on available WETH
|
||||
/// @param baseAmount Base amount for calculation
|
||||
/// @return Calculated buy amount bounded by available WETH
|
||||
function _calculateBuyAmount(uint256 baseAmount) internal view returns (uint256) {
|
||||
uint256 wethBalance = weth.balanceOf(account);
|
||||
uint256 amount = baseAmount % (wethBalance / BALANCE_DIVISOR);
|
||||
return amount == 0 ? wethBalance / 10 : amount;
|
||||
}
|
||||
|
||||
|
||||
// ========================================
|
||||
// ROBUSTNESS AND FUZZ TESTS
|
||||
// ========================================
|
||||
|
||||
/// @notice Fuzz test to ensure protocol robustness under random trading sequences
|
||||
/// @dev Validates that traders cannot extract value through arbitrary trading patterns
|
||||
/// This is a pure unit test with no CSV recording or scenario analysis
|
||||
|
|
@ -687,31 +814,14 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
/// @notice Helper to execute a sequence of random trades and recentering
|
||||
/// @dev Extracted for reuse in both unit tests and scenario analysis
|
||||
function _executeRandomTradingSequence(uint8 numActions, uint8 frequency, uint8[] calldata amounts) internal {
|
||||
uint8 f = 0;
|
||||
uint8 recenterFrequencyCounter = 0;
|
||||
|
||||
for (uint i = 0; i < numActions; i++) {
|
||||
uint256 amount = (uint256(amounts[i]) * 1 ether) + 1 ether;
|
||||
uint256 amount = (uint256(amounts[i]) * MIN_TRADE_AMOUNT) + MIN_TRADE_AMOUNT;
|
||||
uint256 harbergBal = harberg.balanceOf(account);
|
||||
|
||||
// Execute trade based on current balances and random input
|
||||
if (harbergBal == 0) {
|
||||
// Only WETH available - buy HARB
|
||||
amount = amount % (weth.balanceOf(account) / 2);
|
||||
amount = amount == 0 ? weth.balanceOf(account) / 10 : amount;
|
||||
if (amount > 0) buy(amount);
|
||||
} else if (weth.balanceOf(account) == 0) {
|
||||
// Only HARB available - sell HARB
|
||||
sell(amount % harbergBal);
|
||||
} else {
|
||||
// Both tokens available - decide randomly
|
||||
if (amount % 2 == 0) {
|
||||
amount = amount % (weth.balanceOf(account) / 2);
|
||||
amount = amount == 0 ? weth.balanceOf(account) / 10 : amount;
|
||||
if (amount > 0) buy(amount);
|
||||
} else {
|
||||
sell(amount % harbergBal);
|
||||
}
|
||||
}
|
||||
_executeRandomTrade(amount, harbergBal);
|
||||
|
||||
// Handle extreme price conditions to prevent test failures
|
||||
(, int24 currentTick, , , , , ) = pool.slot0();
|
||||
|
|
@ -727,11 +837,11 @@ contract LiquidityManagerTest is UniswapTestBase, CSVManager {
|
|||
}
|
||||
|
||||
// Periodic recentering based on frequency
|
||||
if (f >= frequency) {
|
||||
if (recenterFrequencyCounter >= frequency) {
|
||||
recenter(false);
|
||||
f = 0;
|
||||
recenterFrequencyCounter = 0;
|
||||
} else {
|
||||
f++;
|
||||
recenterFrequencyCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue