-
Notifications
You must be signed in to change notification settings - Fork 0
Add packages/tooling with DTF rebalance validation #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| --- | ||
| title: Tooling Domain | ||
| updated: 2026-08-03 | ||
| type: domain | ||
| sources: | ||
| - packages/tooling/** | ||
| --- | ||
|
|
||
| # Tooling Domain | ||
|
|
||
| ## Boundary | ||
|
|
||
| `@reserve-protocol/tooling` is the private workspace home for operational scripts and | ||
| agent skills that review protocol activity. It consumes `@reserve-protocol/sdk` and | ||
| `@reserve-protocol/dtf-catalog`; nothing depends on it, and it publishes nothing. Logic | ||
| that other surfaces would need belongs in the SDK, not here. | ||
|
|
||
| ## Shape | ||
|
|
||
| - One folder per workflow under `src/` (`rebalance-validation/`), with its checks in | ||
| `checks/` and its outside data providers in `sources/`. | ||
| - Each workflow has a CLI entry (`cli.ts`) run through `tsx`, and a skill in | ||
| `skills/<name>/SKILL.md` that says how to act on the output. | ||
| - Protocol reads, decoding and math go through the SDK. The package holds only the | ||
| review logic and the sources the SDK deliberately does not own. | ||
|
|
||
| ## Invariants | ||
|
|
||
| - Rebalance review is two passes: `disasters` gates (nonzero exit), `outcomes` informs. | ||
| Only disaster-pass failures can block a proposal. | ||
| - Disaster checks use data from outside Reserve (pool prices, third-party token | ||
| listings). The proposal is built from the Reserve API, so an API-vs-calldata | ||
| comparison cannot detect a wrong API price. | ||
| - Calldata correctness is established by re-deriving it through | ||
| `buildIndexDtfStartRebalanceArgs`, not by re-implementing the weight math. | ||
| - Every run prints the questions that cannot be checked mechanically; a clean report | ||
| is not a completed review. | ||
|
|
||
| ## Encoding | ||
|
|
||
| `weight.spot` is `D27{tok/share}` (per share, so whole tokens per whole share is | ||
| `spot / 1e27 * 1e18 / 10**decimals`); `price` is `D27{nanoUSD/tok}` with | ||
| `low = p*(1-e)` / `high = p/(1-e)`, so price is `sqrt(low*high)`; `maxAuctionSize` is | ||
| `{tok}`, not USD. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # @reserve-protocol/tooling | ||
|
|
||
| Operational tooling for reviewing protocol activity, built on `@reserve-protocol/sdk` | ||
| and `@reserve-protocol/dtf-catalog`. Private to the workspace — scripts and agent | ||
| skills, not a published surface. | ||
|
|
||
| ## Rebalance validation | ||
|
|
||
| ```bash | ||
| pnpm --filter @reserve-protocol/tooling validate:rebalance \ | ||
| "https://app.reserve.org/bsc/index-dtf/cmc20/governance/proposal/<id>" | ||
| ``` | ||
|
|
||
| Exits 1 when a disaster check fails, 0 otherwise. The agent-facing procedure — | ||
| how to triage the output and who owns each warning — is | ||
| [`skills/validating-dtf-rebalances/SKILL.md`](./skills/validating-dtf-rebalances/SKILL.md). | ||
|
|
||
| ### What it checks | ||
|
|
||
| Pass one, "preventing disasters" (blocking): | ||
|
|
||
| | Check | Catches | | ||
| | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | ||
| | single `startRebalance` on the DTF, routed through the governor whose timelock holds `REBALANCE_MANAGER` | a proposal that passes and then reverts, or a rebalance smuggled in with other actions | | ||
| | calldata re-derived through `@reserve-protocol/dtf-rebalance-lib` (via the SDK) | hand-edited weights, price ranges or rebalance limits | | ||
| | every held asset present in the calldata; additions and zero-weight exits surfaced | assets stranded outside the rebalance | | ||
| | encoded price vs the deepest-pool price, per asset | wrong decimals, a stale or wrong API price, the wrong token | | ||
| | proposed basket shares re-valued at pool prices | value shifted into the wrong place while each price still looks plausible | | ||
| | per-share units vs the last executed rebalance | order-of-magnitude weight jumps | | ||
| | basket addresses against a third-party address→coin map | look-alike and scam addresses | | ||
|
|
||
| Pass two, "optimizing outcomes" (informational): auction launcher window vs TTL | ||
| and the resulting permissionless tail, turnover as a share of AUM, legs above | ||
| $10,000, constituents with less than $50,000 of pooled liquidity, and price | ||
| impact per leg from the production `POST /rebalance/liquidity` route. | ||
|
|
||
| Prices and token identity in pass one come from sources outside Reserve | ||
| (DEXScreener, CoinGecko) on purpose: the proposal was built from the Reserve API, | ||
| so only an independent mark can catch that API being wrong. | ||
|
|
||
| ### Encoding notes | ||
|
|
||
| `weight.spot` is `D27{tok/share}` — per _share_, so whole tokens per whole share is | ||
| `spot / 1e27 * 1e18 / 10**decimals`. `price` is `D27{nanoUSD/tok}` with | ||
| `low = p*(1-e)`, `high = p/(1-e)`, so the asset price is `sqrt(low*high)` and the | ||
| price-error preset is `1 - low/price`. `maxAuctionSize` is encoded in `{tok}`, not | ||
| USD. `limits.high` is `1/(1-basketError)` for TRACKING DTFs and `1e18` for NATIVE. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| { | ||
| "name": "@reserve-protocol/tooling", | ||
| "version": "0.0.0", | ||
| "private": true, | ||
| "description": "Operational tooling and agent skills for DTF review workflows.", | ||
| "license": "MIT", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/reserve-protocol/dtf-interface", | ||
| "directory": "packages/tooling" | ||
| }, | ||
| "type": "module", | ||
| "scripts": { | ||
| "clean": "rm -rf *.tsbuildinfo", | ||
| "test": "vitest run --passWithNoTests", | ||
| "typecheck": "tsc -p tsconfig.json --noEmit", | ||
| "validate:rebalance": "tsx src/rebalance-validation/cli.ts" | ||
| }, | ||
| "dependencies": { | ||
| "@reserve-protocol/sdk": "workspace:*", | ||
| "viem": "catalog:" | ||
| }, | ||
| "devDependencies": { | ||
| "@dtf-interface/tsconfig": "workspace:*", | ||
| "@types/node": "catalog:", | ||
| "tsx": "^4.20.6", | ||
| "typescript": "catalog:", | ||
| "vitest": "catalog:" | ||
| }, | ||
| "engines": { | ||
| "node": ">=24" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| --- | ||
| name: validating-dtf-rebalances | ||
| description: Use when asked to review, validate, or sanity-check an Index DTF rebalance governance proposal (an app.reserve.org `.../governance/proposal/<id>` link), before voting on or executing one. | ||
| --- | ||
|
|
||
| # Validating DTF rebalances | ||
|
|
||
| A rebalance proposal encodes weights, price ranges and auction timing as calldata. | ||
| Two kinds of thing go wrong, and they are not equally bad: | ||
|
|
||
| - **Disasters** — the calldata moves real value into the wrong place: a mispriced | ||
| asset, wrong decimals, a scam or look-alike token address, the wrong governor. | ||
| These block the proposal. | ||
| - **Execution** — the trade is correct but fills badly: thin liquidity, high price | ||
| impact, an auction nobody opens. These need preparation, not a veto. | ||
|
|
||
| Run the disaster pass first and do not weigh execution findings against it. | ||
|
|
||
| ## Steps | ||
|
|
||
| 1. Run the checker on the proposal URL: | ||
|
|
||
| ```bash | ||
| pnpm --filter @reserve-protocol/tooling validate:rebalance "<proposal url>" | ||
| ``` | ||
|
|
||
| Done when it prints a verdict line. Exit code 1 means a disaster check failed; | ||
| 0 with warnings means execution risks only. | ||
|
|
||
| 2. Resolve every `FAIL`. A failure is a claim about the calldata, so answer it | ||
| with the calldata: read the check's detail line, then confirm against an | ||
| explorer or an independent price source. Done when each failure is either a | ||
| fixed proposal or a written explanation of why the check is wrong here. | ||
|
|
||
| 3. Take each `WARN` to the person who owns it. Done when each warning has a | ||
| named owner and an answer: | ||
| - trade above $10,000, price impact above 5%, or liquidity below $50,000 → | ||
| the trading desk, who decides whether to buy inventory before the auction. | ||
| - basket addition or removal → whoever owns the index mandate. | ||
| - permissionless tail (`ttl` beyond the launcher window) → the auction | ||
| launcher operator. | ||
|
Comment on lines
+35
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Assign an owner for turnover warnings. The outcomes pass includes turnover, but this section assigns owners for trade size, price impact, liquidity, basket changes, and TTL only. Add an explicit owner and decision path for turnover warnings. Otherwise Step 3 can leave a 🤖 Prompt for AI Agents |
||
|
|
||
| 4. Answer the "still needs a human" questions the run prints. They cannot be | ||
| checked mechanically (is the constituent universe official? is this wrapper | ||
| canonical?), and a clean report without them is not a review. Done when each | ||
| is answered or explicitly deferred to a named person. | ||
|
|
||
| 5. Report the verdict as: disaster pass result, then execution flags with owners, | ||
| then unanswered questions. Never report "looks good" while a question from | ||
| step 4 is open. | ||
|
|
||
| ## Adding a check | ||
|
|
||
| Add a check when a real proposal could go wrong in a way the current run would | ||
| miss, not to restate something already covered. | ||
|
|
||
| - Independent data only for disaster checks. The proposal was built from the | ||
| Reserve API, so an API-vs-calldata comparison cannot detect a wrong API price; | ||
| pool prices and third-party token listings can. New disaster checks belong in | ||
| `src/rebalance-validation/checks/`, new outside sources in | ||
| `src/rebalance-validation/sources/`. | ||
| - A check needs a threshold that separates "wrong model of the world" from | ||
| "market moved". Order-of-magnitude comparisons and band-usage fractions do; | ||
| "looks different" does not. | ||
| - Failing is for things that make the proposal wrong. Anything about execution | ||
| quality is a warning, in the `outcomes` pass. | ||
| - Add a unit test for the pure math in `tests/`, and re-run the checker against a | ||
| known-good historical proposal to confirm it still passes. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the SDK boundary statement.
Line [24] says that decoding and math go through the SDK. The validator performs local decoding in
recoverTokenInputsand local review math inordersOfMagnitudeandbandUsage. Narrow this statement to protocol reads and canonical calldata construction.Suggested wording
📝 Committable suggestion
🤖 Prompt for AI Agents