harb/onchain/script/LmTotalEth.s.sol

81 lines
3.4 KiB
Solidity
Raw Normal View History

// 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 "@aperture/uni-v3-lib/LiquidityAmounts.sol";
import "@aperture/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 **What is counted:**
/// 1. Free native ETH balance of the LM contract
/// 2. Free WETH (ERC-20) balance of the LM contract
/// 3. ETH-side principal of all 3 Uniswap V3 positions (FLOOR, ANCHOR, DISCOVERY),
/// computed via LiquidityAmounts.getAmountsForLiquidity at the current sqrtPrice.
///
/// **What is NOT counted:**
/// - Uncollected trading fees accrued inside positions (these only become visible
/// after a recenter() calls pool.burn + pool.collect and rolls them into free WETH).
/// - KRK held by the LM (either as free balance or as the KRK side of positions).
/// KRK fees collected during recenter() are transferred to feeDestination and
/// excluded from this measurement entirely.
/// - KRK sent to feeDestination is also subtracted from outstandingSupply for floor
/// calculation purposes (see LiquidityManager._getOutstandingSupply).
///
/// **Implication for delta_bps:** Because uncollected fees are invisible, delta_bps
/// measured between two LmTotalEth snapshots reflects position principal changes
/// plus any fees that were materialized by a recenter() between snapshots.
/// See evidence/README.md § "Fee-Income Calculation Model" for the full formula.
///
/// 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);
}
}