Skip to content

feat(morpho): group V1 governance alerts per vault; denominate V2 absolute caps - #337

Merged
spalen0 merged 1 commit into
mainfrom
feat/morpho-v1-grouped-alerts
Aug 7, 2026
Merged

feat(morpho): group V1 governance alerts per vault; denominate V2 absolute caps#337
spalen0 merged 1 commit into
mainfrom
feat/morpho-v1-grouped-alerts

Conversation

@spalen0

@spalen0 spalen0 commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #330, which grouped V2 governance alerts per vault. This does the same for the V1 monitor and fixes a cap-formatting bug reported from a live alert.

1. V1 governance alerts group per vault

A vault queueing two new markets in one multicall sent two near-identical messages, each repeating the vault name and chain:

⚠️ Adding new market cbETH/USDC (86.00%) with cap 10.00M to vault Yearn OG USDC on BASE. Queued for 2026-08-09 10:29:51

⚠️ Adding new market cbETH/USDC (77.00%) with cap 10.00M to vault Yearn OG USDC on BASE. Queued for 2026-08-09 10:29:51

⚠️ Adding new market cbETH/USDC (77.00%) with cap 10.00M to vault OUSD on BASE. Queued for 2026-08-09 10:29:51

Now three messages become two — grouping is per vault, so the two Yearn OG USDC markets merge and OUSD stays its own message:

⚠️ [Yearn OG USDC](https://app.morpho.org/base/vault/0xef417a…) on BASE

➕ Adding new market [cbETH/USDC (86.00%)](…) with cap 10.00M.
Queued for 2026-08-09 10:29:51

---

➕ Adding new market [cbETH/USDC (77.00%)](…) with cap 10.00M.
Queued for 2026-08-09 10:29:51
⚠️ [OUSD](https://app.morpho.org/base/vault/0x581Cc9…) on BASE

➕ Adding new market [cbETH/USDC (77.00%)](…) with cap 10.00M.
Queued for 2026-08-09 10:29:51

The vault name and chain move to the header, so each section carries only what differs. Cap updates and market removals get the same treatment, and timelock/guardian changes join the same message at HIGH severity.

2. Shared grouping module

The machinery #330 added inside governance_v2.py moves to protocols/morpho/_alerts.py (VaultDiff, send_vault_alerts, the splitter) so both monitors use one implementation. V2 behaviour is unchanged — its tests pass untouched apart from the import path.

VaultDiff.defer() now takes any writer callable, since V1 records cursors through write_last_executed_morpho_to_file(vault, market, type, ts) rather than V2's key, value form. V1 cursors are therefore deferred like V2's: a failed Telegram send is retried next run instead of being recorded as already alerted.

The splitter also gained a case it did not handle: a single section larger than one Telegram message — a batched multicall submit renders one bullet per operation — now splits on line boundaries instead of being handed to Telegram whole and truncated.

3. Absolute caps are denominated in the vault's asset

Reported from a live alert:

📥 Submitted 5 operations:
  • increaseAbsoluteCap(collateral token cbETH → cap 25000000000000)
  • increaseAbsoluteCap(market cbETH/USDC → cap 10.00M USDC)

Every V2 cap limits the vault's own allocation, so it is denominated in the vault's asset whatever id it is keyed by. The market path resolved decimals from market metadata; the collateralToken path had no asset to hand and printed the raw integer. governance_v2 now reads asset { symbol decimals } in the query it already issues and threads it into decode_submit, so the same operation renders:

  • increaseAbsoluteCap(collateral token [cbETH](…) → cap 25.00M USDC)

Verified against the live pending configs on Yearn OG USDC V2 (BASE, USDC, 6 decimals): 25000000000000 / 1e6 = 25.00M, matching the sibling market caps.

type(uint128).max — Morpho's "no cap" sentinel, seen on Sentora RLUSD Main — now renders as unlimited instead of a 340-undecillion figure.

Tests

718 passed, ruff check / ruff format clean. New coverage: V1 findings for one vault collapse into a single message with the vault named once; V1 cursors are deferred until after the send. Existing V1 tests moved to the buffered API.

🤖 Generated with Claude Code

Extends the V2 per-vault grouping to the V1 governance monitor and moves the
shared machinery into protocols/morpho/_alerts.py, so both monitors buffer
findings into one VaultDiff and flush a single Telegram message per vault.

A vault queueing two new markets in the same multicall used to send two
near-identical messages that repeated the vault name and chain in each; it
now sends one with a header naming the vault once and a section per market.
Section bodies drop the "to vault X on CHAIN" suffix the header carries.

Two fixes the shared module picks up:

* A single section larger than one Telegram message (a batched multicall
  submit renders one bullet per operation) now splits on line boundaries
  instead of being handed to Telegram whole and truncated.
* V1 cache cursors are deferred like V2's, so a failed send is retried on the
  next run rather than being recorded as alerted.

Absolute-cap decoding: every V2 cap limits the vault's own allocation, so it
is denominated in the vault's asset whatever id it is keyed by. The
collateralToken path had no asset to hand and printed the raw integer —
"cap 25000000000000" for what is 25.00M USDC. governance_v2 now reads
asset { symbol decimals } in the query it already issues and threads it into
decode_submit. type(uint128).max renders as "unlimited" rather than a
340-undecillion figure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@spalen0
spalen0 marked this pull request as draft August 7, 2026 08:37
@spalen0
spalen0 marked this pull request as ready for review August 7, 2026 08:44
@spalen0
spalen0 merged commit 51eb2c9 into main Aug 7, 2026
3 checks passed
@spalen0
spalen0 deleted the feat/morpho-v1-grouped-alerts branch August 7, 2026 08:44
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.

1 participant