feat: add MidasFund for Midas mToken integration#201
Conversation
Wraps Midas issuance and redemption vaults behind the IFund state machine. Settlement is configurable per direction (INSTANT via depositInstant/redeemInstant, REQUEST via depositRequest/redeemRequest) and changeable while no order is live; vault setters atomically update the paired settlement mode with full safeguards (no live order, same mToken, payment token registered, vault not paused, fund + wrapped share greenlisted). Generic over any mToken: the token is derived from the deposit vault at initialize and all amounts are converted between native and Midas base-18 decimals at the boundary. Rejected Midas requests are refunded off-band, handled via the RECOVERING balance-threshold flow with operator resolve()/recovering() escape hatches. Shares are WrappedAsset tokens; multiple fund instances can share one wrapper to run concurrent settlements. Includes beacon-proxy factory, Midas integration interfaces, unit + factory + fuzz/invariant tests with full Midas mocks, and mainnet fork tests against the live mGLOBAL vaults (instant/request deposit and redemption on both the Aave and Swapper redemption vaults).
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- remove REQUEST settlement mode entirely: commit() always settles synchronously via depositInstant/redeemInstant; SettlementMode, request-id tracking and setSettlementMode are gone - add mandatory per-order holdback gate: every order (both directions) stays PROCESSING after the instant settlement until HOLDBACK_ROLE confirms the off-band payment via confirmHoldback() (deposits: the remaining mTokens airdropped after month-end NAV publication; redeems: the holdback returned in the payment token); unlock() then sweeps the fund's full output-token balance - add VAULT_MANAGER_ROLE gating setDepositVault/setRedemptionVault, intended for an EOA distinct from the operator, with the existing safeguards (no live order, mToken match, payment token, pause and greenlist checks) - trim the Midas integration interfaces to the instant-only surface - rework the Midas test suite: 131 unit + 11 factory + 8 fuzz tests, 5 invariants, 7 mainnet fork tests, all green
Solidity lines of code (production
|
| Metric | Value |
|---|---|
src/ files changed |
9 (8 new) |
| Raw diff (git) | +1,106 / −0 lines |
| Solidity code lines (cloc, comments & blanks excluded) | +510 added |
Counted with cloc --diff <base>/src <head>/src --include-lang=Solidity.
- deposits no longer settle instantly: commit() calls depositRequest and records the mint request id (activeRequestId view, requestId in OrderCommitted); the order stays PROCESSING until the Midas admin approves the request, which mints the full amount at the approval NAV directly to the fund — no deposit holdback - rejected requests are refunded off-band: state() auto-reports RECOVERING on a CANCELED mint request once the refund covers the effective input - holdback gate is now redeem-only: create() presets holdbackPaid for deposit orders, so holdbackPending() is false and confirmHoldback() reverts HoldbackNotPending for deposits - create()/commit()/setDepositVault/setRedemptionVault preflight fnPaused for the commit-time selectors (depositRequest/redeemInstant) in addition to the global pause - restore MidasRequestStatus + depositRequest/mintRequests in the integration interfaces; mock deposit vault gets request storage with approve/reject helpers - rework the Midas test suite: 131 unit + 11 factory + 9 fuzz tests, 6 invariants, 8 mainnet fork tests (real depositRequest approve and reject lifecycles), all green
A committed redeem settles irreversibly on-chain via redeemInstant, so recovering it would strand the instant payment-token settlement in the fund (recover only returned the re-wrapped mToken). Remove the redeem recovery path entirely: the only completion path for a committed redeem is confirmHoldback() — confirming the payment or deliberately waiving it — followed by unlock(). - recovering() now takes the Order struct (instead of the order id) so the mode can be checked, and reverts RecoverNotSupported for redeems - recover() and the RECOVERING branch of _state() are asset-only now - confirmHoldback() NatSpec documents the waive-as-write-off procedure
Summary
Adds
MidasFund, anIFundadapter for the Midas protocol (targeting mGLOBAL/mGLO, generic over any mToken), plus its beacon-proxy factory, integration interfaces, and a full test suite.Design
depositRequest(no holdback) —commit()transfers the payment token to Midas and records the mint request id (exposed viaactiveRequestId()and therequestIdparam onOrderCommitted). The order staysPROCESSINGuntil a Midas vault admin approves the request, which mints the full mToken amount at the approval NAV directly to the fund →UNLOCKING. A rejected request is not refunded on-chain:state()reports the order recoverable (RECOVERING) once Midas returns the payment token off-band (e.g. viawithdrawToken).redeemInstantwith mandatory holdback — Midas settles ~93% on-chain (±7%-adjusted feeds) and returns the holdback off-band in the payment token. Every redeem staysPROCESSINGafter the instant settlement until an account withHOLDBACK_ROLE(or the owner) confirms receipt viaconfirmHoldback(orderId);unlock()then sweeps the fund's full output-token balance (instant settlement + holdback) to the receiver.confirmHoldbackreverts (HoldbackNotPending) for deposit orders.OPERATOR_ROLE(resolve/recovering/referrer),DEPOSITOR_ROLE(Facility order lifecycle),HOLDBACK_ROLE(confirm redeem holdback payments; intended for an EOA distinct from the operator),VAULT_MANAGER_ROLE(set deposit/redemption vaults; also intended for a dedicated EOA).MidasFundFactory, all sharing the sameWrappedAsset(wmGLOBAL) as the share token. Note each order occupies its instance until the Midas admin approves the mint request (deposits) or the holdback is confirmed (redeems), so size the instance count accordingly.initializeand validated against the redemption vault and wrapper underlying; all amounts are converted between the payment token's native decimals and the Midas base-18 API at the boundary.setDepositVault(address)/setRedemptionVault(address)(VAULT_MANAGER_ROLE or owner) enforce: no live order, same mToken, payment token registered, vault not paused (globally or for the commit-time function the fund calls), and (when the vault greenlist is enabled) both the fund and the wrapped share greenlisted. Supports routing between the Aave/Swapper redemption vaults or a dedicated instant-redemption vault.create(),commit()and the vault setters check Midas' per-function pause (fnPaused) for the two selectors the fund calls (depositRequest(address,uint256,bytes32),redeemInstant(address,uint256,uint256)) in addition to the global pause, so orders fail early instead of at settlement.state()auto-reportsRECOVERINGon aCANCELEDmint request once the refund covers the effective input; the operator can also flagrecovering()manually, withresolve()/cancelRecovering()escape hatches (USCCFund model).Tests
UNLOCKINGwithout a confirmed holdback and no deposit while its mint request is stillPENDING(regardless of token balances)depositRequest+ adminapproveRequest, mint within 0.1% of the deposit-feed expectation), request rejection + off-band-refund recovery, instant redemption holdback lifecycles, off-band holdback payment swept at unlock, Swapper-vault redemption, greenlist gating, totalAssetsOperational notes for launch
WrappedAssetmust holdM_GLOBAL_GREENLISTED_ROLE(mGLOBAL ismTokenPermissioned— every transfer checks both parties).withdrawTokento the fund contract address).HOLDBACK_ROLEis a liveness dependency for every redeem: the Facility cannot unlock a redemption without aconfirmHoldbacktransaction (owner is the fallback confirmer). Midas must deliver holdback payments to the fund contract address — that is what the unlock sweep captures.depositRequestfn-paused on mainnet; only instant redemption is live. Midas must unpause + greenlist before the fund can operate (the fund now pre-checksfnPausedfor its own selectors at create/commit time).minMTokenAmountForFirstDeposit— applies todepositRequesttoo).waivedFeeRestrictionfee waiver — worth requesting).