harb/onchain/script/backtesting/FormatLib.sol
openhands cf8e7ee6ee 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>
2026-02-27 12:02:29 +00:00

33 lines
1 KiB
Solidity

// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;
/**
* @title FormatLib
* @notice Shared integer-to-string formatting helpers for backtesting log output.
* Extracted to avoid copy-pasting the same logic across PositionTracker
* and StrategyExecutor.
*/
library FormatLib {
/// @notice Format an unsigned integer as a decimal string.
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);
}
/// @notice Format a signed integer as a decimal string (prefixed with '-' if negative).
function istr(int256 v) internal pure returns (string memory) {
if (v >= 0) return str(uint256(v));
return string.concat("-", str(uint256(-v)));
}
}