fix: address review feedback on PositionTracker and StrategyExecutor
- Fix fee attribution: distribute fees only to positions whose tick range contains the active tick at close time (in-range weight), not by raw liquidity. FLOOR is priced far below current tick and rarely earns fees; the old approach would over-credit it and corrupt capital-efficiency and net-P&L numbers. Fallback to raw-liquidity weighting with a WARN log when no position is in range. - Warn on first-close skip: when _closePosition finds no open record (first recenter, before any tracking), log [TRACKER][WARN] instead of silently returning so the gap is visible in reports. - Add tick range assertion: require() that the incoming close snapshot tick range matches the stored open record — a mismatch would mean IL is computed across different ranges (apples vs oranges). - Fix finalBlock accuracy: logSummary now calls tracker.logFinalSummary(tracker.lastNotifiedBlock()) instead of lastRecenterBlock, so the summary reflects the actual last replay block rather than potentially hundreds of blocks early. - Initialize lastRecenterBlock = block.number in StrategyExecutor constructor to defer the first recenter attempt by recenterInterval blocks and document the invariant. - Extract shared FormatLib: _str(uint256) and _istr(int256) were copy-pasted in both PositionTracker and StrategyExecutor. Extracted to FormatLib.sol internal library; both contracts now use `using FormatLib`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
cfcf750084
commit
cf8e7ee6ee
3 changed files with 159 additions and 106 deletions
|
|
@ -3,6 +3,7 @@ pragma solidity ^0.8.19;
|
|||
|
||||
import { LiquidityManager } from "../../src/LiquidityManager.sol";
|
||||
import { ThreePositionStrategy } from "../../src/abstracts/ThreePositionStrategy.sol";
|
||||
import { FormatLib } from "./FormatLib.sol";
|
||||
import { PositionTracker } from "./PositionTracker.sol";
|
||||
import { IERC20 } from "@openzeppelin/token/ERC20/IERC20.sol";
|
||||
import { IUniswapV3Pool } from "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
|
|
@ -33,6 +34,9 @@ import { console2 } from "forge-std/console2.sol";
|
|||
* amounts based on KrAIken's active tick ranges.
|
||||
*/
|
||||
contract StrategyExecutor {
|
||||
using FormatLib for uint256;
|
||||
using FormatLib for int256;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Configuration (immutable)
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
@ -50,6 +54,10 @@ contract StrategyExecutor {
|
|||
// Runtime state
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// @notice Block of the last recenter attempt (successful or not).
|
||||
/// Initialised to block.number at construction so the first recenter
|
||||
/// attempt is deferred by recenterInterval blocks rather than firing
|
||||
/// immediately on the first observed historical block.
|
||||
uint256 public lastRecenterBlock;
|
||||
uint256 public totalRecenters;
|
||||
uint256 public failedRecenters;
|
||||
|
|
@ -73,6 +81,9 @@ contract StrategyExecutor {
|
|||
feeDestination = _feeDestination;
|
||||
recenterInterval = _recenterInterval;
|
||||
tracker = new PositionTracker(_pool, _token0isWeth);
|
||||
// Defer the first recenter attempt by recenterInterval blocks so we don't
|
||||
// try to recenter before any meaningful price movement has occurred.
|
||||
lastRecenterBlock = block.number;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
@ -118,7 +129,7 @@ contract StrategyExecutor {
|
|||
}
|
||||
|
||||
if (!success) {
|
||||
console2.log(string.concat("[recenter SKIP @ block ", _str(blockNum), "] reason: ", failReason));
|
||||
console2.log(string.concat("[recenter SKIP @ block ", blockNum.str(), "] reason: ", failReason));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -201,11 +212,14 @@ contract StrategyExecutor {
|
|||
console2.log("Total recenters: ", totalRecenters);
|
||||
console2.log("Failed recenters: ", failedRecenters);
|
||||
console2.log("Recenter interval:", recenterInterval, "blocks");
|
||||
console2.log(string.concat("Final Floor: tick [", _istr(fLo), ", ", _istr(fHi), "] liq=", _str(uint256(fLiq))));
|
||||
console2.log(string.concat("Final Anchor: tick [", _istr(aLo), ", ", _istr(aHi), "] liq=", _str(uint256(aLiq))));
|
||||
console2.log(string.concat("Final Discovery: tick [", _istr(dLo), ", ", _istr(dHi), "] liq=", _str(uint256(dLiq))));
|
||||
console2.log(string.concat("Final Floor: tick [", int256(fLo).istr(), ", ", int256(fHi).istr(), "] liq=", uint256(fLiq).str()));
|
||||
console2.log(string.concat("Final Anchor: tick [", int256(aLo).istr(), ", ", int256(aHi).istr(), "] liq=", uint256(aLiq).str()));
|
||||
console2.log(string.concat("Final Discovery: tick [", int256(dLo).istr(), ", ", int256(dHi).istr(), "] liq=", uint256(dLiq).str()));
|
||||
|
||||
tracker.logFinalSummary(lastRecenterBlock);
|
||||
// Use lastNotifiedBlock from the tracker as the authoritative final block —
|
||||
// it reflects the last block actually processed by the replay, which may be
|
||||
// up to recenterInterval blocks later than lastRecenterBlock.
|
||||
tracker.logFinalSummary(tracker.lastNotifiedBlock());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
@ -238,38 +252,13 @@ contract StrategyExecutor {
|
|||
internal
|
||||
view
|
||||
{
|
||||
console2.log(string.concat("=== Recenter #", _str(totalRecenters), " @ block ", _str(blockNum), " ==="));
|
||||
console2.log(string.concat(" Floor pre: tick [", _istr(fLoPre), ", ", _istr(fHiPre), "] liq=", _str(uint256(fLiqPre))));
|
||||
console2.log(string.concat(" Anchor pre: tick [", _istr(aLoPre), ", ", _istr(aHiPre), "] liq=", _str(uint256(aLiqPre))));
|
||||
console2.log(string.concat(" Disc pre: tick [", _istr(dLoPre), ", ", _istr(dHiPre), "] liq=", _str(uint256(dLiqPre))));
|
||||
console2.log(string.concat(" Floor post: tick [", _istr(fLoPost), ", ", _istr(fHiPost), "] liq=", _str(uint256(fLiqPost))));
|
||||
console2.log(string.concat(" Anchor post: tick [", _istr(aLoPost), ", ", _istr(aHiPost), "] liq=", _str(uint256(aLiqPost))));
|
||||
console2.log(string.concat(" Disc post: tick [", _istr(dLoPost), ", ", _istr(dHiPost), "] liq=", _str(uint256(dLiqPost))));
|
||||
console2.log(string.concat(" Fees WETH: ", _str(feesWeth), " Fees KRK: ", _str(feesKrk)));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Formatting helpers (no vm dependency required)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
function _str(uint256 v) internal pure returns (string memory) {
|
||||
if (v == 0) return "0";
|
||||
uint256 tmp = v;
|
||||
uint256 len;
|
||||
while (tmp != 0) {
|
||||
len++;
|
||||
tmp /= 10;
|
||||
}
|
||||
bytes memory buf = new bytes(len);
|
||||
while (v != 0) {
|
||||
buf[--len] = bytes1(uint8(48 + v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
return string(buf);
|
||||
}
|
||||
|
||||
function _istr(int256 v) internal pure returns (string memory) {
|
||||
if (v >= 0) return _str(uint256(v));
|
||||
return string.concat("-", _str(uint256(-v)));
|
||||
console2.log(string.concat("=== Recenter #", totalRecenters.str(), " @ block ", blockNum.str(), " ==="));
|
||||
console2.log(string.concat(" Floor pre: tick [", int256(fLoPre).istr(), ", ", int256(fHiPre).istr(), "] liq=", uint256(fLiqPre).str()));
|
||||
console2.log(string.concat(" Anchor pre: tick [", int256(aLoPre).istr(), ", ", int256(aHiPre).istr(), "] liq=", uint256(aLiqPre).str()));
|
||||
console2.log(string.concat(" Disc pre: tick [", int256(dLoPre).istr(), ", ", int256(dHiPre).istr(), "] liq=", uint256(dLiqPre).str()));
|
||||
console2.log(string.concat(" Floor post: tick [", int256(fLoPost).istr(), ", ", int256(fHiPost).istr(), "] liq=", uint256(fLiqPost).str()));
|
||||
console2.log(string.concat(" Anchor post: tick [", int256(aLoPost).istr(), ", ", int256(aHiPost).istr(), "] liq=", uint256(aLiqPost).str()));
|
||||
console2.log(string.concat(" Disc post: tick [", int256(dLoPost).istr(), ", ", int256(dHiPost).istr(), "] liq=", uint256(dLiqPost).str()));
|
||||
console2.log(string.concat(" Fees WETH: ", feesWeth.str(), " Fees KRK: ", feesKrk.str()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue