# 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.
"""
