Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions automation/jobs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
# - the yearn-monitor systemd unit, whose ExecStartPre renders this into a supercronic
# crontab on start (see deploy/runbook.md).
#
# Adding or removing a script: edit the relevant profile's `tasks:` and merge to main. The
# Adding or removing a task in an existing profile: edit its `tasks:` and merge to main. The
# multisig profile's pre-run sync (sync_before_run, below) lands it on the box within
# ~10 min — no restart needed. Only a `cron:` cadence change requires sync +
# ~10 min — no restart needed. Adding/removing a profile or changing `cron:` requires sync +
# `sudo systemctl restart monitoring` to re-render the crontab (see deploy/runbook.md).
#
# Each task invokes one script. CLI flags go under `args:` as `key: value` and are emitted as
Expand All @@ -17,6 +17,22 @@

profiles:

half_hourly:
cron: "*/30 * * * *"
description: "Thirty-minute risk monitoring for active Yearn strategies."
tasks:
- name: "yearn-lender-borrower-ltv"
script: protocols/yearn/lender_borrower.py
args: { checks: ltv }

six_hourly:
cron: "17 */6 * * *"
description: "Six-hour rate and debt-coverage monitoring for active Yearn strategies."
tasks:
- name: "yearn-lender-borrower-rates-and-coverage"
script: protocols/yearn/lender_borrower.py
args: { checks: rates-and-coverage }

hourly:
cron: "5 * * * *"
description: "Hourly monitoring (was .github/workflows/hourly.yml)."
Expand Down
7 changes: 4 additions & 3 deletions deploy/runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ uv run python -m automation run hourly --dry-run
uv run python -m automation run hourly
```

Available profiles: `hourly`, `daily`, `multisig` (see `automation/jobs.yaml`).
Available profiles: `half_hourly`, `six_hourly`, `hourly`, `daily`, `multisig`
(see `automation/jobs.yaml`).

---

Expand Down Expand Up @@ -101,8 +102,8 @@ PR and the next ~10-min multisig tick syncs it in; no SSH needed.**
auto-sync but stay inert until a restart, because they're read once at scheduler
boot, not per-tick:

- a `cron:` *cadence* change in `jobs.yaml` (the crontab is rendered at unit
start by `ExecStartPre`), and
- adding/removing a profile or changing a profile's `cron:` *cadence* in
`jobs.yaml` (the crontab is rendered at unit start by `ExecStartPre`), and
- a `pyproject.toml` / `uv.lock` change (the venv).

For those, after the PR merges:
Expand Down
8 changes: 6 additions & 2 deletions monitoring.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -455,15 +455,19 @@ protocols:

yearn:
display_name: "Yearn"
description: "Yearn vault flow and timelock monitoring"
cadence: "Hourly / Daily"
description: "Yearn vault, strategy risk, and timelock monitoring"
cadence: "Every 30 minutes / 6 hours / Hourly / Daily"
tasks:
- protocols/yearn/lender_borrower.py
- protocols/yearn/alert_large_flows.py
- protocols/yearn/check_timelock_delay.py
- protocols/yearn/check_indexer_freshness.py
monitors:
- name: "Indexer Freshness"
description: "Envio indexer lag per chain; alerts the Envio channel when a chain's newest indexed block is older than 60 minutes or the GraphQL endpoint is down"
- name: "Lender-Borrower Risk"
description: "Katana Morpho vbWBTC/yvUSDC: LTV every 30m; 24h net spread and debt coverage every 6h"
severity: "MEDIUM"
- name: "Large Flows"
description: "Deposit/withdrawal flows >=$500k USD (Katana withdrawals >=$50k; or 10% of vault totalSupply fallback for unpriced tokens)"
- name: "Timelock Delay"
Expand Down
21 changes: 21 additions & 0 deletions protocols/yearn/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

This folder contains monitoring scripts for Yearn vault activity, Safe multisig queues, and timelock operations.

## Lender-Borrower Risk

The script `yearn/lender_borrower.py` monitors the active Katana Morpho `vbWBTC/yvUSDC` lender-borrower strategy. The strategy deposits vbWBTC as Morpho collateral, borrows vbUSDC, and lends the borrowed vbUSDC into the Yearn vbUSDC vault.

### Checks

1. **Liquidation risk**: reproduces the strategy warning LTV from Morpho's LLTV and `warningLTVMultiplier()`, then alerts when `getCurrentLTV()` exceeds it. The displayed vbWBTC and vbUSDC prices come from the strategy's Morpho and USD oracles. The borrow-token USD feed must have updated within 26 hours. Runs every 30 minutes.
2. **Net spread**: derives Morpho's instantaneous borrow APR from the adaptive IRM's window-average rate and subtracts it from the lender vault APR returned by Yearn's APR oracle. A medium alert fires after at least three samples when the rolling 24-hour average is below `-1%`. A zero lender APR is treated as unavailable data, alerts, and is not stored as a rate sample. Runs every six hours.
3. **Debt coverage**: compares `balanceOfLentAssets() + balanceOfBorrowToken()` with `balanceOfDebt()`. A medium alert fires when the deficit is both at least 10 basis points of debt and worth at least $100. Runs every six hours with the net-spread check.

All breach, unavailable-data, and monitor-error alerts use `MEDIUM` severity and route to the internal curation Telegram channel, falling back to the Yearn channel when curation is not configured. MEDIUM sends Telegram without invoking the HIGH/CRITICAL emergency-dispatch hook. Persistent breaches and errors are deduplicated and reminded once per 24 hours. The monitor is read-only and does not initiate deleveraging.

### Usage

```bash
uv run protocols/yearn/lender_borrower.py --checks=ltv --dry-run
uv run protocols/yearn/lender_borrower.py --checks=rates-and-coverage --dry-run
```

Omit `--dry-run` to persist rate samples and send configured alerts.

## Large Flows

The script `yearn/alert_large_flows.py` checks recent deposit and withdrawal events and sends a Telegram alert when a single flow exceeds a USD threshold. It runs hourly via the [monitoring runner](../automation/jobs.yaml).
Expand Down
9 changes: 9 additions & 0 deletions protocols/yearn/abi/AprOracle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"inputs": [{"name": "strategy", "type": "address"}, {"name": "delta", "type": "int256"}],
"name": "getStrategyApr",
"outputs": [{"name": "apr", "type": "uint256"}],
"stateMutability": "view",
"type": "function"
}
]
30 changes: 30 additions & 0 deletions protocols/yearn/abi/LenderBorrower.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[
{"inputs": [], "name": "name", "outputs": [{"type": "string"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "asset", "outputs": [{"type": "address"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "borrowToken", "outputs": [{"type": "address"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "lenderVault", "outputs": [{"type": "address"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "morpho", "outputs": [{"type": "address"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "marketId", "outputs": [{"type": "bytes32"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "borrowUsdOracle", "outputs": [{"type": "address"}], "stateMutability": "view", "type": "function"},
{
"inputs": [],
"name": "marketParams",
"outputs": [
{"name": "loanToken", "type": "address"},
{"name": "collateralToken", "type": "address"},
{"name": "oracle", "type": "address"},
{"name": "irm", "type": "address"},
{"name": "lltv", "type": "uint256"}
],
"stateMutability": "view",
"type": "function"
},
{"inputs": [], "name": "balanceOfCollateral", "outputs": [{"type": "uint256"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "balanceOfDebt", "outputs": [{"type": "uint256"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "balanceOfLentAssets", "outputs": [{"type": "uint256"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "balanceOfBorrowToken", "outputs": [{"type": "uint256"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "getCurrentLTV", "outputs": [{"type": "uint256"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "getLiquidateCollateralFactor", "outputs": [{"type": "uint256"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "warningLTVMultiplier", "outputs": [{"type": "uint16"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "targetLTVMultiplier", "outputs": [{"type": "uint16"}], "stateMutability": "view", "type": "function"}
]
16 changes: 16 additions & 0 deletions protocols/yearn/abi/MorphoCore.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[
{
"inputs": [{"name": "id", "type": "bytes32"}],
"name": "market",
"outputs": [
{"name": "totalSupplyAssets", "type": "uint128"},
{"name": "totalSupplyShares", "type": "uint128"},
{"name": "totalBorrowAssets", "type": "uint128"},
{"name": "totalBorrowShares", "type": "uint128"},
{"name": "lastUpdate", "type": "uint128"},
{"name": "fee", "type": "uint128"}
],
"stateMutability": "view",
"type": "function"
}
]
33 changes: 33 additions & 0 deletions protocols/yearn/abi/MorphoIrm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[
{
"inputs": [
{
"name": "marketParams",
"type": "tuple",
"components": [
{"name": "loanToken", "type": "address"},
{"name": "collateralToken", "type": "address"},
{"name": "oracle", "type": "address"},
{"name": "irm", "type": "address"},
{"name": "lltv", "type": "uint256"}
]
},
{
"name": "market",
"type": "tuple",
"components": [
{"name": "totalSupplyAssets", "type": "uint128"},
{"name": "totalSupplyShares", "type": "uint128"},
{"name": "totalBorrowAssets", "type": "uint128"},
{"name": "totalBorrowShares", "type": "uint128"},
{"name": "lastUpdate", "type": "uint128"},
{"name": "fee", "type": "uint128"}
]
}
],
"name": "borrowRateView",
"outputs": [{"name": "rate", "type": "uint256"}],
"stateMutability": "view",
"type": "function"
}
]
3 changes: 3 additions & 0 deletions protocols/yearn/abi/MorphoOracle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
{"inputs": [], "name": "price", "outputs": [{"type": "uint256"}], "stateMutability": "view", "type": "function"}
]
Loading