feat(USDat): keep M earning through migration and add claimMYield#4
Open
PierrickGT wants to merge 1 commit into
Open
feat(USDat): keep M earning through migration and add claimMYield#4PierrickGT wants to merge 1 commit into
PierrickGT wants to merge 1 commit into
Conversation
The M reserves are drained into PYUSDX progressively via `replaceAsset`, which takes as long as it takes. Opting out of M earning at upgrade time forfeited all yield accrued across that entire window, so `migrate` no longer calls `stopEarning`. Earning on alone is not enough: once M is registered as a replaceable alt-asset, MultiMint only tracks the balance snapshot taken at registration. Yield accrues on the real `balanceOf`, invisible to `totalAssets` and unreachable by both `claimYield` (PYUSDX excess only) and `replaceAsset` (tracked balance only). `claimMYield` realizes that surplus: it registers the untracked M as backing and mints the matching USDat to the yield recipient, exactly as `migrate` already did for its own surplus. Registering alongside the mint is what keeps the PYUSDX-backed portion (`totalSupply - totalAssets`) unchanged. `mYield` exposes the pending amount, which is otherwise untrackable on-chain. The claim is permissionless: the proceeds can only ever reach `yieldRecipient`, so no role gate buys anything. It keeps the frozen-recipient guard from `_beforeClaimYield` for the same incident-response reason. On `cap`: it stays at the value `migrate` set. `claimMYield` raises only `balance`, pushing it past `cap`, which is what keeps M wraps blocked across a claim. The cap is a governance parameter (`setAssetCap`) and this path should not move it. That block is narrower than it looks, though, and is worth a reviewer's attention: `cap == balance` holds only at migration. `replaceAsset` lowers `balance` while `cap` stays put, so each drain of D opens exactly D of wrap room and M can be wrapped back in -- undoing the drain, and round-tripping to PYUSDX at the treasury's expense. Zeroing `cap` is not the fix: `replaceAsset` and `claimMYield` both gate on `isAllowedAsset` (`cap != 0`), so it would halt the very drain it protects. This predates this change and is contained today only by the whitelist being enabled. Tracked separately; not addressed here. M is now a hardcoded constant rather than a parameter, so `migrate` and `claimMYield` take no asset argument and no other token can be passed in. This changes the `migrate` ABI; the upgrade script is updated in step, and nothing external consumes it. `IMTokenLike.stopEarning` is now unused and dropped. The fork test warps 30 days past a real mainnet upgrade and asserts the surplus is non-zero, which only holds if earning genuinely survived.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The M reserves are drained into PYUSDX progressively via
replaceAsset, which takes as long as it takes. Opting out of M earning at upgrade time forfeited all yield accrued across that entire window, somigrateno longer callsstopEarning.Earning on alone is not enough: once M is registered as a replaceable alt-asset, MultiMint only tracks the balance snapshot taken at registration. Yield accrues on the real
balanceOf, invisible tototalAssetsand unreachable by bothclaimYield(PYUSDX excess only) andreplaceAsset(tracked balance only).claimMYieldrealizes that surplus: it registers the untracked M as backing and mints the matching USDat to the yield recipient, exactly asmigratealready did for its own surplus. Registering alongside the mint is what keeps the PYUSDX-backed portion (totalSupply - totalAssets) unchanged.mYieldexposes the pending amount, which is otherwise untrackable on-chain.The claim is permissionless: the proceeds can only ever reach
yieldRecipient, so no role gate buys anything. It keeps the frozen-recipient guard from_beforeClaimYieldfor the same incident-response reason.On
cap: it stays at the valuemigrateset.claimMYieldraises onlybalance, pushing it pastcap, which is what keeps M wraps blocked across a claim. The cap is a parameter (setAssetCap) and this path should not move it.That block is narrower than it looks, though, and is worth a reviewer's attention:
cap == balanceholds only at migration.replaceAssetlowersbalancewhilecapstays put, so each drain of D opens exactly D of wrap room and M can be wrapped back in -- undoing the drain, and round-tripping to PYUSDX at the treasury's expense. Zeroingcapis not the fix:replaceAssetandclaimMYieldboth gate onisAllowedAsset(cap != 0), so it would halt the very drain it protects. This predates this change and is contained today only by the whitelist being enabled. Tracked separately; not addressed here.M is now a hardcoded constant rather than a parameter, so
migrateandclaimMYieldtake no asset argument and no other token can be passed in. This changes themigrateABI; the upgrade script is updated in step, and nothing external consumes it.IMTokenLike.stopEarningis now unused and dropped.The fork test warps 30 days past a real mainnet upgrade and asserts the surplus is non-zero, which only holds if earning genuinely survived.