RabbitTreasuryVault: interim custody for Rabbit fee slice until RabbitTreasury (EcoStrategy H-01) #159

Closed
opened 2026-05-05 08:52:53 +00:00 by PlasticDigits · 8 comments
PlasticDigits commented 2026-05-05 08:52:53 +00:00 (Migrated from gitlab.com)

Context

EcoStrategy audit H-01: FeeRouter.distributeFees pays sinks via ERC20.safeTransfer. The fifth sink is documented as Rabbit Treasury, but RabbitTreasury.receiveFee is what books protocol-owned backing, burn split, and Burrow fee events. RabbitTreasury is a future phase—final wiring is not frozen.

We need interim custody so CL8Y can accumulate until Rabbit is live and receiveFee integration is proven.

Goal

RabbitTreasuryVault (name flexible) that:

  1. Can be the FeeRouter fifth destination (sink) to receive the Rabbit slice.
  2. Lets governance move tokens out at any time to arbitrary addresses (multisig, future Rabbit, swap router)—no mandatory lock—because final Rabbit structure is unknown.
  3. Emits transparent events for outbound transfers (token, to, amount, optional reason string).

Suggested design (outline; implement in MR)

  • Ownable2Step or AccessControl admin.
  • withdrawERC20(IERC20 token, address to, uint256 amount) — to != 0, only admin.
  • withdrawETH if native can land.
  • No special deposit API beyond receiving from FeeRouter.

Wiring: deploy vault → FeeRouter.updateSinks points fifth destination to vault.

Future: when Rabbit is ready, either sweep vault → receiveFee path, or updateSinks to point directly at Rabbit once accounting is correct.

Tests: distributeFees increases vault balance; admin withdraw moves balance.

Acceptance criteria

  • Documented in docs/onchain/treasury-contracts.md (or equivalent).
  • Clear in docs: vault ERC20 balance is not Burrow protocolOwnedBacking until receiveFee path exists.

References

  • contracts/src/FeeRouter.sol, contracts/src/RabbitTreasury.sol
  • Audit audits/audit_ecostrategy_1777969776.md — H-01
## Context EcoStrategy audit **H-01**: `FeeRouter.distributeFees` pays sinks via `ERC20.safeTransfer`. The fifth sink is documented as Rabbit Treasury, but `RabbitTreasury.receiveFee` is what books protocol-owned backing, burn split, and Burrow fee events. **RabbitTreasury is a future phase**—final wiring is not frozen. We need interim **custody** so CL8Y can accumulate until Rabbit is live and `receiveFee` integration is proven. ## Goal **RabbitTreasuryVault** (name flexible) that: 1. Can be the **FeeRouter** fifth **destination** (sink) to receive the Rabbit slice. 2. Lets **governance move tokens out at any time** to arbitrary addresses (multisig, future Rabbit, swap router)—**no** mandatory lock—because final Rabbit structure is unknown. 3. Emits **transparent events** for outbound transfers (token, to, amount, optional reason string). ## Suggested design (outline; implement in MR) - `Ownable2Step` or `AccessControl` admin. - `withdrawERC20(IERC20 token, address to, uint256 amount)` — `to != 0`, only admin. - `withdrawETH` if native can land. - **No** special deposit API beyond receiving from FeeRouter. **Wiring:** deploy vault → `FeeRouter.updateSinks` points fifth destination to vault. **Future:** when Rabbit is ready, either sweep vault → `receiveFee` path, or `updateSinks` to point directly at Rabbit once accounting is correct. **Tests:** distributeFees increases vault balance; admin withdraw moves balance. ## Acceptance criteria - [ ] Documented in `docs/onchain/treasury-contracts.md` (or equivalent). - [ ] Clear in docs: **vault ERC20 balance is not** Burrow `protocolOwnedBacking` until `receiveFee` path exists. ## References - `contracts/src/FeeRouter.sol`, `contracts/src/RabbitTreasury.sol` - Audit `audits/audit_ecostrategy_1777969776.md` — **H-01**
PlasticDigits commented 2026-05-05 09:13:58 +00:00 (Migrated from gitlab.com)

mentioned in commit d8535b1b2b

mentioned in commit d8535b1b2bda44fd302c44fc20acf53fe7151dc6
PlasticDigits commented 2026-05-05 09:14:10 +00:00 (Migrated from gitlab.com)

Implemented on main (d8535b1)

Summary: Added RabbitTreasuryVault — interim FeeRouter fifth-sink custody (Ownable2Step, withdrawERC20 / withdrawETH, ERC20Withdrawn / ETHWithdrawn + optional reason). Documented INV-VAULT-159-* (vault ERC-20 balance ≠ protocolOwnedBacking until receiveFee or routing migration). Tests: RabbitTreasuryVault.t.sol.

@brouie please verify when you have a moment.

Verification checklist

Issue left open until you sign off.

## Implemented on `main` (d8535b1) **Summary:** Added `RabbitTreasuryVault` — interim **FeeRouter** fifth-sink custody (`Ownable2Step`, `withdrawERC20` / `withdrawETH`, `ERC20Withdrawn` / `ETHWithdrawn` + optional `reason`). Documented **INV-VAULT-159-*** (vault ERC-20 balance ≠ `protocolOwnedBacking` until `receiveFee` or routing migration). Tests: `RabbitTreasuryVault.t.sol`. @brouie please verify when you have a moment. ### Verification checklist - [ ] `cd contracts && forge test` — includes `RabbitTreasuryVaultTest` (5 tests). - [ ] Read [`docs/onchain/treasury-contracts.md` § RabbitTreasuryVault](https://gitlab.com/PlasticDigits/yieldomega/-/blob/main/docs/onchain/treasury-contracts.md) + [`fee-routing-and-governance.md`](https://gitlab.com/PlasticDigits/yieldomega/-/blob/main/docs/onchain/fee-routing-and-governance.md) interim custody note. - [ ] [`INV-VAULT-159-NOT-BURROW`](https://gitlab.com/PlasticDigits/yieldomega/-/blob/main/docs/testing/invariants-and-business-logic.md) matches your expectations for dashboards/agents. - [ ] Optional ops path: deploy vault → `FeeRouter.updateSinks` fifth destination → `distributeFees` credits vault → owner sweep with emitted reason string. Issue left **open** until you sign off.
PlasticDigits commented 2026-05-05 09:27:39 +00:00 (Migrated from gitlab.com)

mentioned in commit 9ff356526c

mentioned in commit 9ff356526cc39d1c083cb8bb3d1ae72e88cd8cea
PlasticDigits commented 2026-05-05 09:28:58 +00:00 (Migrated from gitlab.com)

mentioned in issue #158

mentioned in issue #158
Brouie commented 2026-05-06 01:29:21 +00:00 (Migrated from gitlab.com)

@PlasticDigits — verification PASS on d8535b1.

checklist walked:

  1. forge test --match-contract RabbitTreasuryVaultTest → 5/5 PASS:
  • test_distributeFees_increases_vault_balance — FeeRouter → vault wiring works
  • test_owner_withdrawERC20_moves_balance — admin sweep path works
  • test_withdrawERC20_unauthorized_reverts — access control enforced
  • test_withdrawERC20_zero_to_reverts — zero-recipient rejected
  • test_withdrawETH_moves_balance — native rescue works
  1. docs review:
  • docs/onchain/treasury-contracts.md § rabbit-treasury-vault-gitlab-159 — interim custody role defined, wiring path documented (updateSinks → vault → distributeFees), DeployDev continues sending fifth sink to RabbitTreasury for local Burrow QA, accounting boundary stated explicitly
  • docs/onchain/fee-routing-and-governance.md interim custody note at line 29 + Rabbit row at line 53 reference vault correctly
  1. invariants at docs/testing/invariants-and-business-logic.md:
  • INV-VAULT-159-NOT-BURROW: ERC20.balanceOf(RabbitTreasuryVault) is not protocolOwnedBacking (nor redeemable backing). Treat vault balances as unbooked until swept into RabbitTreasury or sinks migrate via FeeRouter.updateSinks. — clear for dashboards/agents
  • INV-VAULT-159-ROUTING: distributeFees credits vault when fifth sink targets vault; withdrawERC20 requires to != 0; Ownable2Step gates sweeps

contract surface (contracts/src/sinks/RabbitTreasuryVault.sol, 44 lines): Ownable2Step admin, ERC20Withdrawn / ETHWithdrawn events with optional reason string, no special deposit hook (passive recipient only) — matches the spec.

scope note: this verifies #159 as shipped (interim custody contract). the broader EcoStrategy H-01 finding — that the slice should eventually flow through RabbitTreasury.receiveFee to actually book Burrow protocolOwnedBacking — is explicitly out of scope per the issue body and is documented as expected interim state in INV-VAULT-159-NOT-BURROW. that integration phase is a separate future item.

live ops walk (deploy fresh vault → FeeRouter.updateSinks → distributeFees → owner sweep with reason) not run separately — the 5 forge tests cover this path with isolated mocks. happy to walk it on the live anvil stack if you want a separate signoff with explicit gas + event traces.

ready for close on your side.

@PlasticDigits — verification PASS on `d8535b1`. checklist walked: 1. `forge test --match-contract RabbitTreasuryVaultTest` → 5/5 PASS: - `test_distributeFees_increases_vault_balance` — FeeRouter → vault wiring works - `test_owner_withdrawERC20_moves_balance` — admin sweep path works - `test_withdrawERC20_unauthorized_reverts` — access control enforced - `test_withdrawERC20_zero_to_reverts` — zero-recipient rejected - `test_withdrawETH_moves_balance` — native rescue works 2. docs review: - `docs/onchain/treasury-contracts.md` § `rabbit-treasury-vault-gitlab-159` — interim custody role defined, wiring path documented (`updateSinks` → vault → `distributeFees`), `DeployDev` continues sending fifth sink to `RabbitTreasury` for local Burrow QA, accounting boundary stated explicitly - `docs/onchain/fee-routing-and-governance.md` interim custody note at line 29 + Rabbit row at line 53 reference vault correctly 3. invariants at `docs/testing/invariants-and-business-logic.md`: - `INV-VAULT-159-NOT-BURROW`: `ERC20.balanceOf(RabbitTreasuryVault) is not protocolOwnedBacking (nor redeemable backing). Treat vault balances as unbooked until swept into RabbitTreasury or sinks migrate via FeeRouter.updateSinks.` — clear for dashboards/agents - `INV-VAULT-159-ROUTING`: `distributeFees` credits vault when fifth sink targets vault; `withdrawERC20` requires `to != 0`; `Ownable2Step` gates sweeps contract surface (`contracts/src/sinks/RabbitTreasuryVault.sol`, 44 lines): Ownable2Step admin, ERC20Withdrawn / ETHWithdrawn events with optional reason string, no special deposit hook (passive recipient only) — matches the spec. scope note: this verifies #159 as shipped (interim custody contract). the broader EcoStrategy H-01 finding — that the slice should eventually flow through `RabbitTreasury.receiveFee` to actually book Burrow `protocolOwnedBacking` — is explicitly out of scope per the issue body and is documented as expected interim state in `INV-VAULT-159-NOT-BURROW`. that integration phase is a separate future item. live ops walk (deploy fresh vault → `FeeRouter.updateSinks` → `distributeFees` → owner sweep with reason) not run separately — the 5 forge tests cover this path with isolated mocks. happy to walk it on the live anvil stack if you want a separate signoff with explicit gas + event traces. ready for close on your side.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-06 08:26:35 +00:00
PlasticDigits commented 2026-05-13 15:13:33 +00:00 (Migrated from gitlab.com)

mentioned in commit f325d2896e

mentioned in commit f325d2896ec5995e5cfa174c0e4a3d7a785e6b7e
PlasticDigits commented 2026-05-13 15:13:33 +00:00 (Migrated from gitlab.com)

mentioned in commit 3545964a26

mentioned in commit 3545964a261045da319eaa3071c72759f99694e6
Brouie commented 2026-05-14 02:53:39 +00:00 (Migrated from gitlab.com)

mentioned in issue #207

mentioned in issue #207
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/yieldomega#159
No description provided.