fix: Red-team: replace ethPerToken with exact total-LM-ETH metric (#539)

Replace the ethPerToken metric (free balance / adjusted supply) with total
LM ETH (free + WETH + position-locked) using a forge script with exact
Uni V3 integer math. Collapses 4+ RPC calls and Python float approximation
into a single forge script call using LiquidityAmounts + TickMath.

Also updates red-team prompt, report format, memory extraction, and adds
roadmap items for #536-#538 (backtesting pipeline, Push3 evolution).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-10 19:11:11 +00:00
parent 9832b454df
commit 0ddc1ccd80
3 changed files with 242 additions and 100 deletions

View file

@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;
import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol";
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
import "forge-std/Script.sol";
import "uni-v3-lib/LiquidityAmounts.sol";
import "uni-v3-lib/TickMath.sol";
interface ILM {
function positions(uint8 stage) external view returns (uint128 liquidity, int24 tickLower, int24 tickUpper);
}
interface IWETH {
function balanceOf(address) external view returns (uint256);
}
/// @title LmTotalEth
/// @notice Read-only script: prints total ETH controlled by LiquidityManager
/// (free ETH + free WETH + ETH locked in all 3 Uni V3 positions).
/// @dev forge script script/LmTotalEth.s.sol --rpc-url $RPC_URL
/// Env: LM, WETH, POOL
contract LmTotalEth is Script {
function run() external view {
address lm = vm.envAddress("LM");
address weth = vm.envAddress("WETH");
address pool = vm.envAddress("POOL");
// Free balances
uint256 freeEth = lm.balance;
uint256 freeWeth = IWETH(weth).balanceOf(lm);
// Current sqrtPrice from pool
(uint160 sqrtPriceX96,,,,,,) = IUniswapV3Pool(pool).slot0();
// Determine which token is WETH (token0 or token1)
bool wethIsToken0 = IUniswapV3Pool(pool).token0() == weth;
// Sum ETH in all 3 positions: FLOOR=0, ANCHOR=1, DISCOVERY=2
uint256 positionEth = 0;
for (uint8 stage = 0; stage < 3; stage++) {
(uint128 liquidity, int24 tickLower, int24 tickUpper) = ILM(lm).positions(stage);
if (liquidity == 0) continue;
uint160 sqrtA = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtB = TickMath.getSqrtRatioAtTick(tickUpper);
(uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity(sqrtPriceX96, sqrtA, sqrtB, liquidity);
positionEth += wethIsToken0 ? amount0 : amount1;
}
uint256 total = freeEth + freeWeth + positionEth;
// Output as plain number for easy bash consumption
console2.log(total);
}
}