harb/onchain/test/OptimizerV3.t.sol

233 lines
10 KiB
Solidity
Raw Normal View History

// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;
import { OptimizerV3 } from "../src/OptimizerV3.sol";
import { Stake } from "../src/Stake.sol";
import "forge-std/Test.sol";
contract OptimizerV3Test is Test {
OptimizerV3 optimizer;
// TAX_RATES from Stake.sol
uint256[30] TAX_RATES =
[uint256(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];
uint256 constant MAX_TAX = 9700;
function setUp() public {
// Deploy without initialization (we only test pure functions)
optimizer = new OptimizerV3();
}
function _normalizedTaxRate(uint256 taxRateIndex) internal view returns (uint256) {
return TAX_RATES[taxRateIndex] * 1e18 / MAX_TAX;
}
function _percentageStaked(uint256 pct) internal pure returns (uint256) {
return pct * 1e18 / 100;
}
// ==================== Always Bear (staked <= 91%) ====================
function testAlwaysBearAt0Percent() public view {
for (uint256 taxIdx = 0; taxIdx < 30; taxIdx++) {
assertFalse(optimizer.isBullMarket(0, _normalizedTaxRate(taxIdx)), "0% staked should always be bear");
}
}
function testAlwaysBearAt50Percent() public view {
for (uint256 taxIdx = 0; taxIdx < 30; taxIdx++) {
assertFalse(optimizer.isBullMarket(_percentageStaked(50), _normalizedTaxRate(taxIdx)), "50% staked should always be bear");
}
}
function testAlwaysBearAt91Percent() public view {
for (uint256 taxIdx = 0; taxIdx < 30; taxIdx++) {
assertFalse(optimizer.isBullMarket(_percentageStaked(91), _normalizedTaxRate(taxIdx)), "91% staked should always be bear");
}
}
function testAlwaysBearAt39Percent() public view {
assertFalse(optimizer.isBullMarket(_percentageStaked(39), _normalizedTaxRate(0)), "39% staked should be bear");
}
function testAlwaysBearAt80Percent() public view {
assertFalse(optimizer.isBullMarket(_percentageStaked(80), _normalizedTaxRate(0)), "80% staked should be bear even with lowest tax");
}
// ==================== 92% Boundary ====================
function testBoundary92PercentLowestTax() public view {
// deltaS=8, effIdx=0 → penalty = 512*0/20 = 0 < 50 → BULL
assertTrue(optimizer.isBullMarket(_percentageStaked(92), _normalizedTaxRate(0)), "92% staked, lowest tax should be bull");
}
function testBoundary92PercentTaxIdx1() public view {
// deltaS=8, effIdx=1 → penalty = 512*1/20 = 25 < 50 → BULL
assertTrue(optimizer.isBullMarket(_percentageStaked(92), _normalizedTaxRate(1)), "92% staked, taxIdx=1 should be bull");
}
function testBoundary92PercentTaxIdx2() public view {
// deltaS=8, effIdx=2 → penalty = 512*2/20 = 51 >= 50 → BEAR
assertFalse(optimizer.isBullMarket(_percentageStaked(92), _normalizedTaxRate(2)), "92% staked, taxIdx=2 should be bear");
}
function testBoundary92PercentHighTax() public view {
// deltaS=8, effIdx=29 → penalty = 512*29/20 = 742 → BEAR
assertFalse(optimizer.isBullMarket(_percentageStaked(92), _normalizedTaxRate(29)), "92% staked, max tax should be bear");
}
// ==================== 95% Staked ====================
function testAt95PercentLowTax() public view {
// deltaS=5, effIdx=0 → penalty = 125*0/20 = 0 < 50 → BULL
assertTrue(optimizer.isBullMarket(_percentageStaked(95), _normalizedTaxRate(0)), "95% staked, lowest tax should be bull");
}
function testAt95PercentTaxIdx7() public view {
// deltaS=5, effIdx=7 → penalty = 125*7/20 = 43 < 50 → BULL
assertTrue(optimizer.isBullMarket(_percentageStaked(95), _normalizedTaxRate(7)), "95% staked, taxIdx=7 should be bull");
}
function testAt95PercentTaxIdx8() public view {
// deltaS=5, effIdx=8 → penalty = 125*8/20 = 50, NOT < 50 → BEAR
assertFalse(optimizer.isBullMarket(_percentageStaked(95), _normalizedTaxRate(8)), "95% staked, taxIdx=8 should be bear");
}
function testAt95PercentTaxIdx9() public view {
// deltaS=5, effIdx=9 → penalty = 125*9/20 = 56 → BEAR
assertFalse(optimizer.isBullMarket(_percentageStaked(95), _normalizedTaxRate(9)), "95% staked, taxIdx=9 should be bear");
}
// ==================== 97% Staked ====================
function testAt97PercentLowTax() public view {
// deltaS=3, effIdx=0 → penalty = 27*0/20 = 0 < 50 → BULL
assertTrue(optimizer.isBullMarket(_percentageStaked(97), _normalizedTaxRate(0)), "97% staked, lowest tax should be bull");
}
function testAt97PercentHighTax() public view {
// deltaS=3, effIdx=29 → penalty = 27*29/20 = 39 < 50 → BULL
assertTrue(optimizer.isBullMarket(_percentageStaked(97), _normalizedTaxRate(29)), "97% staked, max tax should still be bull");
}
// ==================== 100% Staked ====================
function testAt100PercentAlwaysBull() public view {
// deltaS=0 → penalty = 0 → always BULL
for (uint256 taxIdx = 0; taxIdx < 30; taxIdx++) {
assertTrue(optimizer.isBullMarket(1e18, _normalizedTaxRate(taxIdx)), "100% staked should always be bull");
}
}
// ==================== 96% Sweep ====================
function testAt96PercentSweep() public view {
// deltaS=4, cubic=64
// penalty = 64 * effIdx / 20
// Bull when penalty < 50, i.e., 64 * effIdx / 20 < 50 → effIdx < 15.625
// effIdx 0-15: bull (penalty 0..48). effIdx 16+: bear (penalty 51+)
for (uint256 taxIdx = 0; taxIdx < 30; taxIdx++) {
bool result = optimizer.isBullMarket(_percentageStaked(96), _normalizedTaxRate(taxIdx));
// Compute expected: effIdx from the tax rate
uint256 effIdx = taxIdx;
if (taxIdx >= 14) {
effIdx = taxIdx + 1;
if (effIdx > 29) effIdx = 29;
}
uint256 penalty = 64 * effIdx / 20;
bool expectedBull = penalty < 50;
assertEq(result, expectedBull, string.concat("96% sweep mismatch at taxIdx=", vm.toString(taxIdx)));
}
}
// ==================== 94% Sweep ====================
function testAt94PercentSweep() public view {
// deltaS=6, cubic=216
// penalty = 216 * effIdx / 20
// Bull when penalty < 50, i.e., 216 * effIdx / 20 < 50 → effIdx < 4.629
// effIdx 0-4: bull. effIdx 5+: bear.
for (uint256 taxIdx = 0; taxIdx < 30; taxIdx++) {
bool result = optimizer.isBullMarket(_percentageStaked(94), _normalizedTaxRate(taxIdx));
uint256 effIdx = taxIdx;
if (taxIdx >= 14) {
effIdx = taxIdx + 1;
if (effIdx > 29) effIdx = 29;
}
uint256 penalty = 216 * effIdx / 20;
bool expectedBull = penalty < 50;
assertEq(result, expectedBull, string.concat("94% sweep mismatch at taxIdx=", vm.toString(taxIdx)));
}
}
// ==================== Revert on Invalid Input ====================
function testRevertsAbove100Percent() public {
vm.expectRevert("Invalid percentage staked");
optimizer.isBullMarket(1e18 + 1, 0);
}
// ==================== 93% Staked ====================
function testAt93PercentSweep() public view {
// deltaS=7, cubic=343
// penalty = 343 * effIdx / 20
// Bull when penalty < 50, i.e., 343 * effIdx / 20 < 50 → effIdx < 2.915
// effIdx 0-2: bull. effIdx 3+: bear.
for (uint256 taxIdx = 0; taxIdx < 30; taxIdx++) {
bool result = optimizer.isBullMarket(_percentageStaked(93), _normalizedTaxRate(taxIdx));
uint256 effIdx = taxIdx;
if (taxIdx >= 14) {
effIdx = taxIdx + 1;
if (effIdx > 29) effIdx = 29;
}
uint256 penalty = 343 * effIdx / 20;
bool expectedBull = penalty < 50;
assertEq(result, expectedBull, string.concat("93% sweep mismatch at taxIdx=", vm.toString(taxIdx)));
}
}
// ==================== 99% Staked ====================
function testAt99PercentAlwaysBull() public view {
// deltaS=1, cubic=1 → penalty = effIdx/20, always < 50 for effIdx <= 29
for (uint256 taxIdx = 0; taxIdx < 30; taxIdx++) {
assertTrue(optimizer.isBullMarket(_percentageStaked(99), _normalizedTaxRate(taxIdx)), "99% staked should always be bull");
}
}
// ==================== EffIdx Shift at Boundary (taxIdx 13 vs 14) ====================
function testEffIdxShiftAtBoundary() public view {
// At 96% staked, deltaS=4, cubic=64
// taxIdx=13: effIdx=13, penalty = 64*13/20 = 41 < 50 → BULL
assertTrue(optimizer.isBullMarket(_percentageStaked(96), _normalizedTaxRate(13)), "taxIdx=13 should be bull at 96%");
// taxIdx=14: effIdx=15 (shift!), penalty = 64*15/20 = 48 < 50 → BULL
assertTrue(optimizer.isBullMarket(_percentageStaked(96), _normalizedTaxRate(14)), "taxIdx=14 should be bull at 96% (effIdx shift)");
// taxIdx=15: effIdx=16, penalty = 64*16/20 = 51 >= 50 → BEAR
assertFalse(optimizer.isBullMarket(_percentageStaked(96), _normalizedTaxRate(15)), "taxIdx=15 should be bear at 96%");
}
// ==================== Fuzz Tests ====================
function testFuzzBearBelow92(uint256 percentageStaked, uint256 taxIdx) public view {
percentageStaked = bound(percentageStaked, 0, 91e18 / 100);
taxIdx = bound(taxIdx, 0, 29);
assertFalse(optimizer.isBullMarket(percentageStaked, _normalizedTaxRate(taxIdx)), "Should always be bear below 92%");
}
function testFuzz100PercentAlwaysBull(uint256 taxIdx) public view {
taxIdx = bound(taxIdx, 0, 29);
assertTrue(optimizer.isBullMarket(1e18, _normalizedTaxRate(taxIdx)), "100% staked should always be bull");
}
function testFuzzNeverReverts(uint256 percentageStaked, uint256 averageTaxRate) public view {
percentageStaked = bound(percentageStaked, 0, 1e18);
averageTaxRate = bound(averageTaxRate, 0, 1e18);
// Should not revert
optimizer.isBullMarket(percentageStaked, averageTaxRate);
}
}