Maren
GitHub
How it works

The pooled vault

Shares, the per-user ledger, and the minimum-viable-vault finding.

MarenVault maintains one MUSD trove and holds a per-user ledger of collateral and debt inside it. This page is the mechanism: how one shared position accounts for many users, and one sizing result that decides whether the vault can serve a borrow at all.

One trove, many ledgers

A user's collateral is segregated in accounting, not in custody. The vault records each user's share of collateral and debt. Those shares sum to everything the vault custodies — what the shared trove holds in Mezo's TroveManager, plus any BTC the vault holds idle because it has not been pushed into the trove yet. The invariant is totalCollateral == troveColl + idleCollateral; on matsnet today that reads 0.156061 = 0.069061 + 0.087000 BTC.

This accounting is what lets the vault offer sub-minimum borrowing: the trove as a whole clears the protocol's 1,800 MUSD minimum, while an individual user inside it borrows far less.

Because the vault holds exactly one trove regardless of how many users sit inside it, Maren's cost of computing SortedTroves insertion hints does not grow with Maren's user count. One trove, one hint computation, however many depositors.

How a borrow is served

A pooled borrow can be served two ways, and the accounting differs by design:

From new trove debt

The vault draws new trove debt with BorrowerOperations.withdrawMUSD and credits the user's ledger. This adds real protocol debt and is subject to the trove-wide 180% floor.

From the MUSD reserve

The vault holds a reserveMUSD balance. A borrow served from the reserve adds no trove debt at all and pays no origination fee. It is still refused while the pooled trove sits under the 180% floor, because the floor is checked unconditionally.

This is why the pooled position math deliberately does not match the direct-trove math. A pooled user pays no per-user 200 MUSD gas compensation, because one payment covers the whole trove, and a reserve-served borrow pays no origination fee. Applying the direct-trove collateralRequiredFor() to a pooled position would over-quote every user by 200 MUSD of collateral they do not owe.

The two floors

The contract checks two collateral-ratio floors on every borrow, and they close borrowing for different populations:

ConstantMeaningEffect
MIN_BORROW_CR = 180%The floor your position must clearRefuses your borrow with InsufficientCollateralRatio
MIN_TROVE_CR = 180%The floor the shared trove must clear after any borrowCloses borrowing for everyone at once, even a reserve-served borrow
LIQUIDATION_CR = 140%Where Maren's keeper liquidates a pooled positionAbove the protocol's 110% cliff

MIN_TROVE_CR is the constraint that closes borrowing for the whole pool at once, and it is checked unconditionally. Even a borrow served entirely from the reserve, which adds no trove debt, is refused while the pooled trove sits under it. This is a system-wide state, not a per-user one.

The minimum-viable-vault finding

A pooled vault seeded at the protocol minimum can never serve a borrow. The reason is exact: MUSD's 1,800 MUSD minimum net debt pins the seed position's debt right at the borrowing floor, so there is no headroom to lend from. The vault has to be seeded above the minimum before it can do anything.

Maren hit this in practice on matsnet, and the fix is on-chain in two transactions: the pooled trove's ICR moved from 153% to about 220% by adding collateral, which opened borrowing headroom. Read the live figures rather than trusting these: at the time of writing (2026-09-07) the trove held 0.069061 BTC against 2,088.9 MUSD of debt, an ICR of 262% at a BTC price of $79,048, with borrowing enabled.

cast call 0xE47c80e8c23f6B4A1aE41c34837a0599D5D16bb0 \
  "getTroveColl(address)(uint256)" \
  0x46252B00AB59384518d9dB6D1f210ffF1221645b \
  --rpc-url https://rpc.test.mezo.org

The first argument is Mezo's TroveManager; the second is MarenVault, which owns the trove.

The keeper

A keeper runs per-block health checks on the pooled trove. It tops up collateral, repays debt, and internally liquidates unhealthy users by seizing their collateral at a 5% penalty and repaying their share of pooled debt. Liquidation is gated on KEEPER_ROLE and is not permissionless — a call from any other address reverts with AccessControlUnauthorizedAccount — and the 5% penalty goes to the insurance buffer, not to the caller. On matsnet that role is held by a single operator key, and the keeper process itself is not yet written, so keeper liveness is a single point of failure today. Opening liquidation to any caller is the intended design.

If the keeper cannot act, borrowing stops but withdrawals do not. The residual risk is a BTC drawdown faster than the keeper can respond. See the risk inventory.