- Add PositionTracker.sol: tracks position lifecycle (open/close per recenter), records tick ranges, liquidity, entry/exit blocks/timestamps, token amounts (via LiquidityAmounts math), fees (proportional to liquidity share), IL (LP exit value − HODL value at exit price), and net P&L per position. Aggregates total fees, cumulative IL, net P&L, rebalance count, Anchor time-in-range, and capital efficiency accumulators. Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks. - Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to constructor (creates PositionTracker internally), call tracker.notifyBlock() on every block for time-in-range, and call tracker.recordRecenter() on each successful recenter. logSummary() now delegates to tracker.logFinalSummary(). - Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to StrategyExecutor constructor; log tracker address. - forge fmt: reformat all backtesting scripts and affected src/test files to project style (number_underscore=thousands, multiline_func_header=all). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
101 lines
3.4 KiB
Solidity
101 lines
3.4 KiB
Solidity
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
pragma solidity ^0.8.19;
|
|
|
|
import { OptimizerV3Push3 } from "../src/OptimizerV3Push3.sol";
|
|
import "forge-std/Test.sol";
|
|
|
|
/**
|
|
* @title OptimizerV3Push3Test
|
|
* @notice Verifies the correctness of OptimizerV3Push3 isBullMarket logic.
|
|
*/
|
|
contract OptimizerV3Push3Test is Test {
|
|
OptimizerV3Push3 push3;
|
|
|
|
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 {
|
|
push3 = new OptimizerV3Push3();
|
|
}
|
|
|
|
function _norm(uint256 taxIdx) internal view returns (uint256) {
|
|
return TAX_RATES[taxIdx] * 1e18 / MAX_TAX;
|
|
}
|
|
|
|
function _pct(uint256 pct) internal pure returns (uint256) {
|
|
return pct * 1e18 / 100;
|
|
}
|
|
|
|
// ---- Direct correctness tests ----
|
|
|
|
function testAlwaysBearAt0Percent() public view {
|
|
for (uint256 t = 0; t < 30; t++) {
|
|
assertFalse(push3.isBullMarket(0, _norm(t)));
|
|
}
|
|
}
|
|
|
|
function testAlwaysBearAt91Percent() public view {
|
|
for (uint256 t = 0; t < 30; t++) {
|
|
assertFalse(push3.isBullMarket(_pct(91), _norm(t)));
|
|
}
|
|
}
|
|
|
|
function testBoundary92PercentLowestTax() public view {
|
|
// deltaS=8, effIdx=0 → penalty=0 < 50 → BULL
|
|
assertTrue(push3.isBullMarket(_pct(92), _norm(0)));
|
|
}
|
|
|
|
function testBoundary92PercentTaxIdx1() public view {
|
|
// deltaS=8, effIdx=1 → penalty=512*1/20=25 < 50 → BULL
|
|
assertTrue(push3.isBullMarket(_pct(92), _norm(1)));
|
|
}
|
|
|
|
function testBoundary92PercentTaxIdx2() public view {
|
|
// deltaS=8, effIdx=2 → penalty=512*2/20=51 >= 50 → BEAR
|
|
assertFalse(push3.isBullMarket(_pct(92), _norm(2)));
|
|
}
|
|
|
|
function testAt95PercentTaxIdx7() public view {
|
|
// deltaS=5, effIdx=7 → penalty=125*7/20=43 < 50 → BULL
|
|
assertTrue(push3.isBullMarket(_pct(95), _norm(7)));
|
|
}
|
|
|
|
function testAt95PercentTaxIdx8() public view {
|
|
// deltaS=5, effIdx=8 → penalty=125*8/20=50 NOT < 50 → BEAR
|
|
assertFalse(push3.isBullMarket(_pct(95), _norm(8)));
|
|
}
|
|
|
|
function testAt97PercentHighTax() public view {
|
|
// deltaS=3, effIdx=29 → penalty=27*29/20=39 < 50 → BULL
|
|
assertTrue(push3.isBullMarket(_pct(97), _norm(29)));
|
|
}
|
|
|
|
function testAt100PercentAlwaysBull() public view {
|
|
for (uint256 t = 0; t < 30; t++) {
|
|
assertTrue(push3.isBullMarket(1e18, _norm(t)));
|
|
}
|
|
}
|
|
|
|
function testEffIdxShiftAtBoundary() public view {
|
|
// taxIdx=13: effIdx=13, penalty=64*13/20=41 < 50 → BULL
|
|
assertTrue(push3.isBullMarket(_pct(96), _norm(13)));
|
|
// taxIdx=14: effIdx=15 (shift!), penalty=64*15/20=48 < 50 → BULL
|
|
assertTrue(push3.isBullMarket(_pct(96), _norm(14)));
|
|
// taxIdx=15: effIdx=16, penalty=64*16/20=51 >= 50 → BEAR
|
|
assertFalse(push3.isBullMarket(_pct(96), _norm(15)));
|
|
}
|
|
|
|
function testRevertsAbove100Percent() public {
|
|
vm.expectRevert("Invalid percentage staked");
|
|
push3.isBullMarket(1e18 + 1, 0);
|
|
}
|
|
|
|
// ---- Fuzz ----
|
|
|
|
function testFuzzNeverReverts(uint256 percentageStaked, uint256 averageTaxRate) public view {
|
|
percentageStaked = bound(percentageStaked, 0, 1e18);
|
|
averageTaxRate = bound(averageTaxRate, 0, 1e18);
|
|
push3.isBullMarket(percentageStaked, averageTaxRate);
|
|
}
|
|
}
|