Skip to content

feat(yvusd): add yvUSD vault monitoring#194

Open
spalen0 wants to merge 8 commits into
mainfrom
yvusd
Open

feat(yvusd): add yvUSD vault monitoring#194
spalen0 wants to merge 8 commits into
mainfrom
yvusd

Conversation

@spalen0
Copy link
Copy Markdown
Collaborator

@spalen0 spalen0 commented Mar 29, 2026

Summary

  • Add monitoring script for yvUSD vault (closes Yvusd monitoring #193)
  • APY anomaly detection: alerts when unlocked APY > locked APY for >6 hours (incentive misalignment), and when any strategy has negative APR (per-strategy debounce so a persistent negative APR doesn't re-alert every hour)
  • Cross-chain bridge delays: checks cross-chain strategy lastReport timestamps on both mainnet and the remote chain, alerts on staleness (>48h) or skew (>6h). Bridge-agnostic — works for CCTP (Arbitrum) and the Katana yvUSDC Compounder ($2.5M debt) alike. Gracefully skips with a one-shot alert when PROVIDER_URL_{CHAIN} isn't configured, so the live Katana strategy doesn't trip "Remote Lookup Failed" every run.
  • Flashloan liquidity: checks Morpho borrow positions vs available liquidity (Balancer vault + Morpho market) for looper strategy unwinding, including cross-chain wrappers whose remote side is itself a Morpho looper (Arbitrum syrupUSDC, etc.). Per-(chain, borrower) debounce.
  • Large cooldown requests: scans CooldownStarted events on LockedyvUSD, alerts on >$100K requests. Surfaces a MEDIUM alert on extended get_logs RPC failure so outages don't silently mute the monitor.

Strategy discovery

Looper and cross-chain strategies are discovered dynamically from the yvUSD APR API, so no hardcoded strategy list to maintain. Chains are resolved generically via Chain.from_chain_id, so new cross-chain strategies on already-configured chains require no code changes.

Contract ABIs

Minimal ABIs for:

  • Yearn V3 Vault (strategies(), totalAssets())
  • Morpho Blue (market(), position())
  • LockedyvUSD (CooldownStarted event, getCooldownStatus(), totalSupply())
  • V3 Tokenized Strategy (lastReport(), totalAssets() — used for remote-side bridge health checks)

State persistence

All state (APY-inversion timer, dedup flags, last scanned block) is keyed under YVUSD_* in the shared cache-id.txt file that the hourly workflow already restores/saves — same convention as maple, ustb, 3jane, timelock, morpho, and yearn/alert_large_flows.

Test plan

  • Verify API fetch works against yvusd-api.yearn.fi/api/aprs
  • Verify on-chain calls succeed with a mainnet RPC
  • Verify remote lastReport() / totalAssets() calls succeed on Arbitrum (0x78b7…, 0xBCf0…)
  • Verify cross-chain looper discovery picks up the live Arbitrum syrupUSDC position
  • Verify Katana yvUSDC Compounder ($2.5M) is routed to Chain.KATANA for the staleness check
  • Confirm get_logs snake-case kwargs work on web3 7.x
  • Confirm CooldownStarted event signature matches LockedyvUSD contract
  • Unit tests cover: dedup behaviour (negative-APR, flashloan, RPC-missing, cooldown-failure), recovery flag clearing, cache persistence
  • Cache file matches hourly workflow's cache_file: cache-id.txt so state actually persists across runs
  • Added to hourly GitHub Actions workflow

🤖 Generated with Claude Code

spalen0 and others added 4 commits March 29, 2026 11:20
Add monitoring for yvUSD vault covering:
- APY inversion detection (unlocked > locked for extended periods)
- Negative strategy APR alerts
- CCTP cross-chain strategy staleness detection
- Flashloan liquidity checks for Morpho looper unwinding
- Large LockedyvUSD cooldown request detection

Closes #193

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move yvusd monitoring into yearn/ to match project conventions:
- yearn/yvusd.py (was yvusd/main.py)
- yearn/abi/ for contract ABIs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@spalen0 spalen0 marked this pull request as ready for review March 29, 2026 20:01
Comment thread yearn/yvusd.py Outdated
Comment thread yearn/yvusd.py Outdated
Comment thread yearn/yvusd.py Outdated
spalen0 and others added 3 commits April 23, 2026 08:24
… kwargs

- Remote cross-chain "vaults" (0x78b7…, 0xBCf0…) are V3 tokenized
  strategies and revert on strategies(); switch to direct lastReport() /
  totalAssets() calls and surface a MEDIUM alert on lookup failure instead
  of silently disabling the CCTP health check.
- Flashloan liquidity check now picks up cross-chain wrappers whose
  remote_vault_type is a looper (borrower = remote_vault on the remote
  chain) and runs per-chain via LOOPER_CHAIN_CONFIG (mainnet + Arbitrum).
- events.CooldownStarted.get_logs now uses snake_case from_block /
  to_block for web3 7.x; camelCase raised TypeError and silently returned.
- Add morpho/abi/morpho_blue.json — the previous shared-abi refactor
  pointed ABI_MORPHO at the MetaMorpho vault ABI, which lacks
  market()/position() and would have raised ABIFunctionNotFound.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…on cooldown RPC failure

- Skip CCTP staleness + cross-chain flashloan checks when PROVIDER_URL_{CHAIN}
  is not set (one-shot MEDIUM alert per chain via dedup flag) so the live
  Katana yvUSDC Compounder ($2.22M) doesn't trip "Remote Lookup Failed"
  every hourly run when no Katana RPC is configured.
- Surface MEDIUM alert when CooldownStarted get_logs fails so an extended
  RPC outage doesn't silently mute large-cooldown monitoring; flag clears
  automatically on next successful fetch.
- Debounce negative-APR and flashloan-liquidity alerts per-strategy /
  per-(chain, borrower) to stop alert spam while a condition persists;
  flags clear on recovery so a re-occurrence will alert again.
- Rename cache file from generic cache-id.txt to cache-yvusd.txt.
- Use local_chain.network_name in CCTP alert summary instead of literal
  "Mainnet" for consistency with the remote-side line.

Tests cover: RPC-gap one-shot alert + dedup, cooldown failure alert +
dedup, negative-APR alert/dedup/recovery, flashloan alert/dedup. 17 tests,
all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…restores it

The hourly workflow caches only cache-id.txt. yvUSD was writing to
cache-yvusd.txt, so the APY-inversion 6-hour timer, dedup flags, RPC-missing
flag, cooldown-failure flag, and last scanned block would reset every run —
the 6h threshold could never be reached and persistent conditions would
re-alert hourly. Match the shared-cache convention used by maple, ustb,
3jane, timelock, morpho, and yearn/alert_large_flows (all run in the same
hourly job).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Yvusd monitoring

1 participant