# formulas/run-protocol.toml # # On-chain protocol health snapshot — collect TVL, accumulated fees, # position count, and rebalance frequency from the deployed LiquidityManager. # Write a structured JSON evidence file for planner and predictor consumption. # # Type: sense. Read-only — produces metrics only, no git artifacts. # # Staleness threshold: 1 day (matches evidence/protocol/ schema). # Cron: daily at 07:00 UTC (staggered 1 h after run-resources). [formula] id = "run-protocol" name = "On-Chain Protocol Health Snapshot" description = "Collect TVL, accumulated fees, position count, and rebalance frequency from the deployed LiquidityManager; write evidence/protocol/{date}.json." type = "sense" # "sense" → read-only, produces metrics only # "act" → produces git artifacts (cf. run-evolution, run-red-team) # ── Cron ─────────────────────────────────────────────────────────────────────── [cron] schedule = "0 7 * * *" # daily at 07:00 UTC (1 h after run-resources) description = "Matches 1-day staleness threshold — one snapshot per day keeps the record fresh." # ── Inputs ───────────────────────────────────────────────────────────────────── [inputs.rpc_url] type = "string" required = true description = """ Base network RPC endpoint used to query on-chain state. Example: https://mainnet.base.org or a running Anvil fork URL. """ [inputs.deployments_file] type = "string" required = false default = "onchain/deployments-local.json" description = """ Path to the deployments JSON file containing contract addresses. The formula reads LiquidityManager address from this file. Use onchain/deployments.json for mainnet; onchain/deployments-local.json for a local Anvil fork. """ [inputs.lookback_blocks] type = "integer" required = false default = 7200 description = """ Number of blocks to scan for Recenter events when computing rebalance_count_24h (~24 h of Base blocks at ~2 s/block). """ # ── Execution ────────────────────────────────────────────────────────────────── [execution] script = "scripts/harb-evaluator/run-protocol.sh" invocation = "RPC_URL={rpc_url} DEPLOYMENTS_FILE={deployments_file} LOOKBACK_BLOCKS={lookback_blocks} bash scripts/harb-evaluator/run-protocol.sh" # Exit codes: # 0 snapshot written successfully # 2 infrastructure error (RPC unreachable, missing deployments file, forge unavailable, etc.) # ── Steps ────────────────────────────────────────────────────────────────────── [[steps]] id = "read-addresses" description = """ Read the LiquidityManager contract address from {deployments_file}. Fail with exit code 2 if the file is absent or the address is missing. """ [[steps]] id = "collect-tvl" description = """ Query LiquidityManager total ETH via forge script LmTotalEth.s.sol against {rpc_url}. Records tvl_eth (wei string) and tvl_eth_formatted (ETH, 2 dp). LmTotalEth.s.sol uses exact Uniswap V3 integer math (LiquidityAmounts + TickMath) to sum free ETH, free WETH, and ETH locked across all three positions (floor, anchor, discovery). """ forge_script = "onchain/script/LmTotalEth.s.sol" [[steps]] id = "collect-fees" description = """ Query accumulated protocol fees from the LiquidityManager via cast call: cast call $LM "accumulatedFees()(uint256)" Records accumulated_fees_eth (wei string) and accumulated_fees_eth_formatted (ETH, 3 dp). Falls back to 0 gracefully if the function reverts or is not present on the deployed contract (older deployment without fee tracking). """ [[steps]] id = "collect-positions" description = """ Query the three Uniswap V3 positions held by the LiquidityManager: LiquidityManager.positions(0) → (liquidity, tickLower, tickUpper) # FLOOR LiquidityManager.positions(1) → (liquidity, tickLower, tickUpper) # ANCHOR LiquidityManager.positions(2) → (liquidity, tickLower, tickUpper) # DISCOVERY Records position_count (number of positions with liquidity > 0) and the positions array. """ [[steps]] id = "collect-rebalances" description = """ Count Recenter events emitted by the LiquidityManager in the past {lookback_blocks} blocks via eth_getLogs. Records: - rebalance_count_24h: total Recenter event count in the window. - last_rebalance_block: block number of the most recent Recenter event (0 if none found in the window). """ event_signature = "Recentered(int24,bool)" [[steps]] id = "collect" description = """ Assemble all collected metrics into evidence/protocol/{date}.json. Compute verdict: - "offline" if tvl_eth = 0 or RPC was unreachable. - "degraded" if position_count < 3, or rebalance_count_24h = 0 and the protocol has been live for > 1 day. - "healthy" otherwise. Write the file conforming to the schema in evidence/README.md ## Schema: protocol/YYYY-MM-DD.json. """ output = "evidence/protocol/{date}.json" schema = "evidence/README.md" # see ## Schema: protocol/YYYY-MM-DD.json [[steps]] id = "deliver" description = """ Commit evidence/protocol/{date}.json to main. Post a one-line summary comment to the originating issue (if any): verdict, tvl_eth_formatted, accumulated_fees_eth_formatted, position_count, rebalance_count_24h. On "degraded" or "offline": highlight the failing dimension and its value. """ # ── Products ─────────────────────────────────────────────────────────────────── [products.evidence_file] path = "evidence/protocol/{date}.json" delivery = "commit to main" schema = "evidence/README.md" # see ## Schema: protocol/YYYY-MM-DD.json [products.issue_comment] delivery = "post to originating issue (if any)" content = "verdict, tvl_eth_formatted, accumulated_fees_eth_formatted, position_count, rebalance_count_24h" on_degraded = "highlight failing dimension and its current value" # ── Resources ────────────────────────────────────────────────────────────────── [resources] profile = "light" compute = "local — forge script + cast calls only; no Anvil or Docker startup required" rpc = "Base network RPC ({rpc_url}) — read-only calls" concurrency = "safe to run in parallel with other formulas" # ── Notes ────────────────────────────────────────────────────────────────────── [notes] tvl_metric = """ TVL is measured as LiquidityManager total ETH: free ETH + free WETH + ETH locked across all three Uniswap V3 positions (floor, anchor, discovery). Uses the same LmTotalEth.s.sol forge script as run-red-team to ensure consistent measurement methodology. """ rebalance_staleness = """ A zero rebalance_count_24h on an established deployment indicates the recenter() upkeep bot (services/txnBot) has stalled. The "degraded" verdict triggers a planner alert. On a fresh deployment (< 1 day old) zero rebalances is expected and does not trigger degraded. """ fees_fallback = """ accumulated_fees_eth falls back to 0 for deployments without fee tracking. The verdict is not affected by a zero fee value alone — only TVL and position_count drive the verdict. """