M0 engaged Guardian to review the security of their M Extensions. From the 23rd of June to the 5th of August, a team of 5 auditors reviewed the source code in scope.
- Published
- Review window
- June 23 to August 5, 2025
- Language
- Solidity
- Chains
- Ethereum, Arbitrum, Optimism
- Sector
- Stablecoins
- 0 Critical
- 1 High
- 4 Medium
- 33 Low
- 0 Informational
Scope
Overview
M0 engaged Guardian to review the security of their M Extensions. From the 23rd of June to the 5th of August, a team of 5 auditors reviewed the source code in scope.
Findings 38
-
H-01 High MYieldFee : Yield Accrual Rewards Resolved
Description
Proof of concept: PoC
The
MYieldFeeM Extension currently allows user balances to increase even when earning is disabled, because callingupdateIndexresets_latestRateto a non-zero value—bypassing theisEarningEnabled()check incurrentIndex.As a result,
claimYieldForcan inflate user balances even when the extension isn’t receiving yield from the M token, leading to inaccurate accounting and potential insolvency.While
SwapFacilityblocks further actions during such states, M Extensions are designed to function independently and integrate with DeFi—making this behavior risky.Examples:
- Lending protocols: Users could borrow against artificially inflated balances not backed by real yield.
- DEXs: Balance-based logic (e.g., in minting or routing) could misbehave or be exploited.
Additionally, if an extension is disabled and later re-enabled,
updateIndexwould incorrectly assume yield accrued throughout the entire disabled period.Recommendation
Update the
updateIndexfunction to respect the earning status.Consider adding
if (isEarningEnabled()) return $.latestIndex;Resolution
M0 Team: Resolved.
-
M-01 Medium MYieldFee : Extension May Drift, Causing Insolvency Risk Rewards Acknowledged
Description
Proof of concept: PoC
The
MYieldFeeextension mimics the yield accrual curve of the original M Token by independently tracking the earner rate and current index. However, the earner rate on the original M Token can be updated byMinterGateway.MinterGatewayupdates collateral.- This triggers a call to
mToken.updateIndex(). - Inside
mToken.updateIndex(),_latestRateis refreshed usingrateModel().rate(). earnerRate()of M Token subsequently reflects this new_latestRate.
Until the extension's
updateIndex()function is manually invoked, it will continue operating based on stale parameters. This can create a discrepancy between the extension’s internal index and the actual yield curve of the M Token. Additionally, ifmToken.stopEarningis triggered directly (without invoking the extension’s logic), the extension keeps accruing “fake” yield.This leads to minting of unbacked extension tokens. Over time, this mismatch can result in excess yield being distributed via the extension. If sustained long enough, and if the extension owner’s fee reserves are insufficient to absorb the difference, it could theoretically lead to insolvency.
Recommendation
- Extension owners should be made explicitly aware of the need to call
updateIndex()whenever the
underlying earner rate is updated.
- Consider building a proactive alerting or notification mechanism to inform extension owners when
a rate change is about to occur or has occurred.
- Alternatively, explore automating index updates as part of key interactions or lifecycle hooks to
minimize reliance on manual upkeep.
- Ensure that
extension.stopEarning()is invoked before or concurrently withmToken.stopEarning()to
trigger any necessary extension-side logic (such as halting internal index updates).
Resolution
M0 Team: Acknowledged.
-
M-02 Medium Wrong Swap Recipient Set Logical Error Resolved
Description
If a path is given to the
swapInfunction in theUniswapV3SwapAdaptercontractmsg.senderis used as recipient instead of the givenrecipientparameter.Recommendation
Use the given
recipientparameter.Resolution
M0 Team: Resolved.
-
M-03 Medium MEarnerManager: Old Fee Recipient Address Not Reset Logical Error Acknowledged
Description
In the
MEarnerManagercontract the fee recipient gets a fee rate of 0 (does not pay fees). When a new fee recipient is set the old fee recipient account is not reset therefore the old account still pays 0 fees.This can lead to loss of yield if not noticed. For example if the fee recipient was changed due to the address being compromised or due the employee with this address left the company etc.
Recommendation
Reset the old fee recipient account when a new on is set.
Resolution
M0 Team: Acknowledged.
-
M-04 Medium MEarnerManager: Extraction Of Fake Yield Via DeFi Rewards Resolved
Description
Even if earning is disabled for
MEarnerManager, it continues to accrue yield as per thecurrentIndex. Users will no longer be able to exit via the swap facility, since it restricts the use of non-earning tokens in extensions.That said, extension tokens are expected to be used independently in DeFi. As a result, users may still exit through other protocols using their token balances, which now include yield accrued after earning was disabled—effectively allowing them to extract fake yield.
Recommendation
Consider implementing a snapshot of the index at the time earning is disabled to prevent post-disablement yield accrual from being misused.
Resolution
M0 Team: Resolved.
-
L-01 Low swapInToken Steals Tokens From User Logical Error Resolved
Description
The
swapInTokenfunction is the main entry point for users to enter an extension. It allows users to trade external tokens (e.g., USDC) for extension tokens, and this core functionality is currently broken.The flow differs slightly depending on whether the desired extension token is
$wMor another extension token. For non-$wMtokens, the flow looks like this:- The user calls the
swapInTokenfunction. - The external tokens (e.g., USDC) are transferred into the contract.
- These tokens are swapped for
$wMtokens on Uniswap, with theSwapFacilitycontract—not the
user—set as the recipient.
- The
_swapfunction is then called to convert the received$wMtokens into the desired extension
token using an unwrap/wrap flow that assumes the tokens belong to the user.
The issue is that
_swapattempts to use the user's$wMtokens, even though$wMtokens from the initial swap were sent to theSwapFacilitycontract. This leads to two potential problems:- The call will revert (DoS) if the user doesn't already hold enough
$wM. - Or, it will steal
$wMfrom the user—causing them to effectively pay twice (e.g., swapping $100
USDC results in ~$200 worth of user tokens being deducted).
The M0 team later clarified that this behavior was designed to support the current deployment of
$wM, which usesmsg.senderin theunwrapfunction and doesn’t account forSwapFacilityas the receiver. They intend to update the$wMimplementation in the future toMExtensionInterface. Above issue is only applicable considerwMto be one ofMExtensions.Recommendation
Be aware of this issue when developing the new
$wMimplementation.Resolution
M0 Team: Resolved.
- The user calls the
-
L-02 Low Contracts Can't Be Upgraded Upgradeability Resolved
Description
As observed in tests and confirmed in discussions with the M0 team, UUPS proxies are intended to be used for upgrades. However, UUPS proxies rely on
ERC1967Proxyand are not upgradeable by default.The implementation contract must include upgrade logic and explicitly authorize upgrades by inheriting from
OpenZeppelin’sUUPSUpgradeableand implementing the necessary authorization functions.Currently, neither the Extensions nor the
SwapFacilitycontracts implement this logic. As a result, despite using a UUPS proxy structure, these contracts are not actually upgradeable.Recommendation
To enable proper upgradeability, have the implementation contracts inherit from
OpenZeppelin’sUUPSUpgradeable.soland implement the required upgrade authorization functions.Reference: OpenZeppelin UUPSUpgradeable Docs
Resolution
M0 Team: Resolved.
-
L-03 Low Swap Facility Blocks Actions DoS Acknowledged
Description
All M Extensions include a toggle to enable or disable yield earning. Any user interaction such as wrapping, unwrapping, or swapping must go through the
SwapFacility.However, the
SwapFacilityrestricts actions via_revertIfNotApprovedExtension, which only permits extensions that are currently earning.This effectively blocks all interactions with an M Extension once earning is disabled, even though the extension may still serve utility beyond yield accrual. (Swap with wM/USDC, Unwrap, Wrap)
Recommendation
Consider maintaining a dedicated list of approved extensions within
SwapFacilitythat is independent of the active earners list. This allows for greater flexibility and separation of concerns between approval and earning status.Resolution
M0 Team: Acknowledged.
-
L-04 Low Incompatibility Of M Extensions Logical Error Acknowledged
Description
As per M0’s documentation, M Extensions are intended to act as wrappers that do not rebase, i.e., their
balanceOf()should remain constant unless explicitly changed via user actions. However, due to the nature of continuous yield distribution, balances increase over time whenclaimFor(address)is called. While this isn’t technically rebasing (since it requires explicit action), the end result — a growing balance over time — introduces similar challenges. M Extensions are designed to be used independently across DeFi, but this yield-accruing behavior causes incompatibilities with many DeFi protocols, which generally assume that a token’s balance is static unless changed by transfer/mint/burn operations.Perpetual Protocols (e.g., GMX)
- Unaccounted Balance as Deposit: Call
claimFor(address)and thencreateDeposit()
GMX Deposit Logic DEXs (e.g., Uniswap)
- Unaccounted Balance as Deposit: Call
claimFor()within the callback of amint()operation.
Uniswap V3 Pool Mint Logic Lending Protocols (e.g., Aave)
- If
claimFor()is called: - The collateral balance silently increases.
- The protocol may overstate collateral value, or miscalculate interest.
- See Aave–Lido integration spec for a similar class of issues with
stETHand the design changes needed to accommodate it.
Vaults (e.g., Yearn)
- Unaccounted Balance as Deposit:Attack Vector: Call
claimFor()just beforedeposit()into a vault.
We later understood that the M0 team has accounted for this issue and introduced a mitigation via the permissioned
claimRecipientfeature, which allows designated role holders to set custom recipients for DeFi pools. While this does help address many of the risks, it comes with certain limitations:- It introduces protocol-specific setup, requiring the M0 team to manually configure a different recipient for each new DeFi pool that integrates M Extensions.
- It places the burden of yield redistribution on the integrating protocol, which may necessitate non-trivial contract modifications—such as tracking user entries
and exits to ensure fair distribution.
- This setup introduces a layer of centralization and operational friction, as each integration becomes dependent on coordination with the M0 team.
Recommendation
To ensure smoother and safer integration across DeFi: 1. Consider wrapping M Extensions in a token that behaves like
wstETHorcTokens, where:balanceOf()is constant- Yield is reflected via an
exchangeRate()model - All rebasing or claiming is internalized and deterministic
- Allow self-directed recipient configuration:
- Let users (e.g., DeFi pool creators) set their own yield recipient without needing M0’s intervention.
- This reduces bottlenecks and central dependency.
- Proactively document integration considerations:
- Highlight the
claimForbehavior clearly. - Offer best practices for protocols to adapt (e.g., use wrapper, handle rebasing-like logic off-chain).
Resolution
M0 Team: Acknowledged.
- Unaccounted Balance as Deposit: Call
-
L-05 Low Race Condition On Access Control List Access Control Acknowledged
Description
M0 intends to implement blacklist/whitelist controls in its extensions, but inclusion or exclusion transactions can be frontrun by sophisticated attackers.
They might consider DeFi pools as an escape hatch—for example, attackers have repeatedly used Curve’s 3-pool to circumvent a USDT blacklist.
Recommendation
Be aware of these scenarios and recommend frontrun-resistant RPC endpoints for extension-owner transactions that modify whitelist/blacklist status.
Resolution
M0 Team: Acknowledged.
-
L-06 Low Design For L2 Oracle Sequencer Resilience Best Practices Acknowledged
Description
The L2 rate oracle hasn’t been implemented yet. Because L2 updates will occur in discrete jumps, sequencer uptime will be critical to ensure timely and accurate rate feeds once the oracle goes live.
Recommendation
Factor in sequencer availability when building the L2 rate oracle and design retry or fallback logic to mitigate downtime.
Resolution
M0 Team: Acknowledged.
-
L-07 Low MEarnerManager: Missing Claim Rewards Resolved
Description
In
MEarnerManager, if a user is removed from the whitelist and then re-added without an intermediateclaimForcall, they will retroactively accrue yield for the period during which they were not whitelisted.Recommendation
Before returning early on a whitelist-status change, invoke an internal
claimForto settle any owed yield for the user. (100% fee)Resolution
M0 Team: Resolved.
-
L-08 Low MYieldFee: Claim Yield Rewards Resolved
Description
When setting a new claim recipient in
MYieldFee, the yield accrued to date is not claimed for the current recipient—instead, all that yield immediately goes to the new recipient. (M0 has commented out this behavior and marked it optional.)Recommendation
Revisit this behavior and, if it wasn’t intentional, insert a claim for the existing recipient before updating to the new one.
Resolution
M0 Team: Resolved.
-
L-09 Low MSpokeYieldFee: Stepwise Jumps Rewards Acknowledged
Description
MSpokeYieldFeeuses a stepwise accrual curve rather than the continuous curve of its L1 counterpart.As a result, if the oracle rate moves from point A to B, yield will accrue for the entire A–B interval—even if, in reality, only a portion of that time should count.
Recommendation
Be aware of this stepwise behavior; as long as rates remain low and the oracle updates frequently, the risk should be minimal.
Resolution
M0 Team: Acknowledged.
-
L-10 Low Blacklisted Tokens Permanently Locked Warning Acknowledged
Description
Protocol has a blacklist feature; however, once tokens are blacklisted there is no way for the extension owner or admin to seize those assets, effectively locking them in perpetuity.
Recommendation
Consider adding an option for the admin or extension owner to seize blacklisted assets.
Resolution
M0 Team: Acknowledged.
-
L-11 Low Token Path Validation Bypassed Validation Acknowledged
Description
Although M0 checks that the input and output tokens of a swap belong to the whitelist, an on-chain path could route through arbitrary intermediary tokens—circumventing those checks entirely.
Recommendation
Beware of this scenario, and if not intentional consider adding validations for all tokens included in path.
Resolution
M0 Team: Acknowledged.
-
L-12 Low Entire Balance Swaps Between Extensions Fail DoS Acknowledged
Description
M0 roundings during transfers are always in favor of the protocol. However, this can cause a revert due to insufficient balance in edge-case scenarios where the entire balance needs to be swapped, such as during migrations between extensions, potentially affecting the last swapper.
During the first step of the swap,
mTokens need to be transferred fromextensionInto theswapFacility. SinceextensionInis an earner but theswapFacilityis non-earner, this transfer requires rounding up.When the last user tries to swap their entire balance, the rounded-up
mTokenamount to transfer becomes greater than themTokenbalance of the extension, causing the swap to fail.It is expected that a few extra wei will be deducted from the extension balance during
_unwrap. However, unexpected reverts should be prevented when the entire balance is being swapped.Recommendation
Document this behavior and inform users about the implications of swapping their entire balance.
Resolution
M0 Team: Acknowledged.
-
L-13 Low MYieldFee: Earning Disabled Unexpected Behavior Resolved
Description
The
MYieldFeeextensions allows partners to set a fee for the yield generated on $M holdings. This fee can be update by theFEE_MANAGER_ROLE, with values ranging from 0 to 100%.If the fee rate is set to 100%, the
updateIndexwill set thelatestRateto 0, as the newearnerRateis 100 - 100 = 0. Therefore, theisEarningEnabledfunction will return false.This is an unexpected behavior of the
isEarningEnabled, as the $M tokens are still generating yield for the fee recipient, just not for the users. Worth comparing this behavior to the result ofdisableEarning, which will set both thelatestRateto zero and effectively stop earning yield in$ M.Additionally, the
currentIndexcalls will always early return with thelatestIndexso yield will never accrue even if the $MlatestUpdateTimestampchanges.Recommendation
Consider updating the
isEarningEnabledfunction to return:IMTokenLike(mToken()).isEarning(address(this)).Alternatively, consider not allowing fee rate to be set to 100%.
Resolution
M0 Team: Resolved.
-
L-14 Low MEarnerManager: Yield Fee Rounds Against The Protocol Logical Error Acknowledged
Description
In the
accruedYieldAndFeeOffunction, the fee is calculated asfee = (yieldWithFee * feeRate_) /ONE_HUNDRED_PERCENT, which rounds down in favor of the user and against the protocol. This also results in a zero fee when claims are made with small amounts.Recommendation
Round up the fee calculation in the
accruedYieldAndFeeOffunction.Resolution
M0 Team: Acknowledged.
-
L-15 Low MYieldFee: Accruing Yield When Earning Stops Logical Error Acknowledged
Description
When an extension is removed from the earner's list, any user can call the permissionless
mToken.stopEarningfunction with the extension's address, instead ofextension.stopEarning.Regarding the
MYieldFeeextensions, this is an issue as it calculates its own index based on the time delta and rate. Therefore, users will be earning "fake" yield, minting unbacked extension tokens.Although the
swapFacilitywon't allow users to unwrap once earning has been disabled in the registrar, the extension token might be added to a Uniswap Pool, so users may exit here, while LPs are stuck with funds they can never unwrap.Recommendation
Make sure the
extension.stopEarningis executed at the exact same time the $M earnings are disabled.Resolution
M0 Team: Acknowledged.
-
L-16 Low Users Could Own M Tokens Warning Acknowledged
Description
The
SwapFacilitycontract tries to prevent any normal user from receiving $M tokens. This invariant can be broken by malicious extensions.Even if fine in the first place an extension could upgrade it's code to be able to send out $M tokens to regular users as there is nothing blocking it.
Recommendation
Consider creating a mapping of addresses which are allowed to hold $M tokens to make sure this invariant holds.
Resolution
M0 Team: Acknowledged.
-
L-17 Low Excess Yield Can't Be Claimed Logical Error Acknowledged
Description
Extensions can accumulate excess yield ($M balance greater than projected supply) due to different factors, like rounding, donations, or rate updates, specially in L2.
Both the
MYieldOneandMYieldFeerely on the $M balance of the contract. However, theMEarnerManagerdoes not rely on the token balance but principal and current index calculations.The only way to claim fees is using
claimFor, that claims both the user's yield and protocol's fee. Therefore, owner can't claim any excess yield accumulated in the extension.Recommendation
Consider adding a
claimExcessmanager function to claim these yields.Resolution
M0 Team: Acknowledged.
-
L-18 Low Consider Using UniversalRouter For Swaps Informational Acknowledged
Description
The
UniswapV3SwapAdaptercontract creates swap parameters compatible withSwapRouter02, which is different thanUniV3 SwapRouterand does not include a deadline parameter inExactInputSingleParamsandExactInputParams.However, the official Uniswap documentation recommends using the
UniversalRouterinstead (reference):The
UniversalRoutercontract is the current preferred entrypoint forERC20and NFT swaps, replacing, among other contracts,SwapRouter02. An up-to-date list of deploy addresses by chain is hosted on GitHub.Recommendation
Consider using
UniversalRouterinstead ofSwapRouter02.Resolution
M0 Team: Acknowledged.
-
L-19 Low Unbacked Extension Balance Due To Rounding Rounding Acknowledged
Description
Proof of concept: PoC
The
MYieldFeeandMEarnerManagerextensions implement dual accounting, tracking both balance and principal. When transferring tokens between accounts or burning them, the principal subtracted from the sender is slightly overestimated. The recipient also receives this overestimated principal amount.These extensions use
getSafePrincipalAmountRoundedUpto perform this overestimation. However, this can result in an account having a non-zero balance but a zero principal, especially after transfers involving very small amounts. Additionally, the transfer and burn functionality does not restrict zero-principal transfers.These non-zero balances can be moved between accounts even when the actual principal transferred is zero. More importantly, they can be used in swaps to another extension. During such swaps, a non-zero balance with zero principal is burned on extensionIn, reducing the
mBalanceofextensionIn, while both a non-zero balance and a non-zero principal are minted onextensionOut.While we couldn't identify a large amount of profit, it is possible to hold balances without any backing principal and use those balances to mint additional principal out of thin air. This introduces an insolvency risk in edge-case scenarios where all users attempt to claim their yields.
Recommendation
One option to consider is disallowing any transfer or burn when the transferred principal is zero. This would prevent swapping a balance with zero principal to another extension. However, this alone does not prevent accounts from holding a non-zero balance with zero principal. Additionally, consider clearing an account’s balance when the return value of
getSafePrincipalAmountRoundedUpequals the sender’s principal balance.Another option is to use
getPrincipalAmountRoundedUpand revert if the principal balance is insufficient, similar to the underlying M0 behavior. However, this approach may introduce unexpected reverts during wrap or unwrap operations.Resolution
M0 Team: Acknowledged.
-
L-20 Low Whitelist Check Limits Compatibility Validation Acknowledged
Description
The
_beforeTransferfunction of theMEarnerManagercontract does not only revert if the sender or recipient is not whitelisted, but also if the executor is not whitelisted.This limits compatibility with many DeFi protocols and may prevents things like gasless transactions, multisig transactions, etc.
Recommendation
Beware of this behavior, and revisit if not intended.
Resolution
M0 Team: Acknowledged.
-
L-21 Low MExtension Implementations Can Be Initialized Logical Error Resolved
Description
The
swapFacilityis an upgradeable contract and its implementation correctly calls_disableInitializersin theconstructor.However,
MExtensionswill also be upgradeable, but there is no_disableInitializersto prevent someone initializing the implementation.Although no potential harm its evident at the contract level, these extensions could be widely known, and their contracts should not be manipulated in any way, as they may lose reputation.
Recommendation
Consider adding
_disableInitializersin the constructor ofMExtension.Resolution
M0 Team: Resolved.
-
L-22 Low Missing Fee Recipient Check In Blacklist Validation Acknowledged
Description
The
MYieldToOnemanager can change the fee recipient using thesetYieldRecipientfunction. This function reverts ifyieldRecipient_ = address(0)but does not check if this address is blacklisted.Recommendation
Validate if
yieldRecipientaddress is blacklisted before updating the recipient.Resolution
M0 Team: Acknowledged.
-
L-23 Low MYieldOne: Claim Yield Logical Error Resolved
Description
The
setYieldRecipientfunction in theMYieldToOnecontract changes the recipient without first claiming the yield for the previous recipient.As a result, all accumulated yield is attributed to the new recipient, even though it belongs to the previous one.
Recommendation
Consider claiming the yield before changing the recipient, similar to how it's handled when changing the fee recipient in the
MYieldFeecontract.Resolution
M0 Team: Resolved.
-
L-24 Low Interface feeRate Not Declared As View Informational Resolved
Description
The
IMYieldFeeinterface declares thefeeRatefunction, but is not declared as view. Using this interface in a contract function declared as view will throw compilation errors.Recommendation
Declare
feeRatefunction as view inIMYieldFee.sol.Resolution
M0 Team: Resolved.
-
L-25 Low MSpokeYieldFee: Spoke Rate Update Logical Error Acknowledged
Description
The
MSpokeYieldFeeis an extension that will be deployed in L2s. Therefore, it relies on thelatestUpdateTimestampof $M contract in L2 (which is propagated every 1-2 hours) andearnerRateof an oracle contract, to compute its own extension index. The $M index is propagation to L2 takes about 15 minutes.This delay will cause some issues when the earner rate is updated. Consider the following, where t is in minutes:
- t=0 $M index was propagated (current rate 4%)
- t=100 Earner rate increased to 5% (also increased in L2
RateOracle) - t=100 Index propagated through Wormhole starts
- t =115 $M index is updated on L2
- t=115 call extension's
updateIndex(). This will increase the current index, using 5% from [0,115] ,
while the L1 real yield was 4% from [0,100].
Therefore, the $M real yield will increase less than the yield calculated on the extension, creating unbacked tokens or affecting the protocol's fees.
Even if the
RateOracleis updated after the index is propagated, there will always be some accounting issues depending if the rate increase or decreases, the time it takes to propagate $M index, or the time whereupdateIndexis finally executed.Recommendation
Current design prevents yield accruing to be completely in sync due to the Wormhole propagation delay. However, to avoid insolvency in extensions when rate increases, make sure $M index is propagated first, and update the
RateOraclerate once the $M index updates in L2.Resolution
M0 Team: Acknowledged.
-
L-26 Low Stale balanceOf DoS DoS Acknowledged
Description
- The ERC20 functions
balanceOf&totalSupplyin theMEarnerManager&MYieldFeecontract are
usually stale as the correct values are only returned right after a yield claim.
- The ERC20 and wrap/unwrap flows in the
MExtensioncontract use thebalanceOffunction to check
if the user owns enough funds to perform the wished action and revert otherwise. This can lead to DoS and makes it hard to unwrap all tokens (dust is most likely lost). For example:
- User once wrapped 100 tokens and now owns 110 tokens at the current yield index
- The user wants to unwrap all tokens and therefore calls the
unwrapfunction with 110 tokens - The call reverts with an
InsufficientBalanceerror as the system uses thebalanceOffunction
instead of the
balanceWithYieldOffunction and therefore thinks the user only owns 100 tokens- Therefore the user needs to claim the yield and call
unwrapagain and now the call goes through
but in the meantime more yield was accrued and a dust amt is left in the extension
When users unwraps or transfers their current balance without claiming yield before it is even possible to reach a 0 balance and positive principal state.
This can lead to users likely miss that they still own some stablecoins as their wallet will read 0
balanceOfand therefore not show the token anymore.Recommendation
Always execute the normal yield claiming function for the given users before performing any action with them.
Also consider allowing users to use their total balance for a operation by supplying
type(uint256).maxfor example.Resolution
M0 Team: Acknowledged.
- The ERC20 functions
-
L-27 Low Blacklisted Users Accrue Yield Warning Acknowledged
Description
Blacklisted users continue to accrue yield.
Recommendation
Consider to change that if this behavior is not intended.
Resolution
M0 Team: Acknowledged.
-
L-28 Low IMExtension Claim Function Informational Acknowledged
Description
IMExtensionshould have a commonclaimfunction, overridden by each extension (either to revert if claim is not available like inMYieldToOneor claiming for account). This will avoid different claim signatures likeclaimForandclaimYieldFor.Recommendation
Consider creating a common
claimfunction inIMExtension.Resolution
M0 Team: Acknowledged.
-
L-29 Low Incorrect Comments Informational Resolved
Description
swapOutM(): mentions "exntesiom" in the comments_revertIfInsufficientBalance: says that it reverts ifaccountbalance is belowbalance, but it should
be
amount.Recommendation
Fix the incorrect comments.
Resolution
M0 Team: Resolved.
-
L-30 Low Incorrect Return Param Name Informational Resolved
Description
In the
NatSpecfor theISwapFacility .swapAdapter()function, the@returnparameter name is incorrect:function swapAdapter() external view returns (address registrar);Recommendation
Update return param name from
registrartoswapAdapterResolution
M0 Team: Resolved.
-
L-31 Low Rounding Creates Insolvent Scenarios Informational Acknowledged
Description
In order to remain solvent, the $M balance should always be greater or equal to the extension's total supply (or projected total supply), so users are able to withdraw all funds, including recipient fees.
However, when minting/wrapping, $M is transferred from the swap facility (non-earner) to the extension (earner).
Depending on the amount and current index, this can yield to the extension receiving 1-2 wei less $M than expected, but the full amount of extension tokens are minted to the user.
Due to the fact that the fees are calculated based on
$M balance - extension token supply, this will affect extension owners directly.Recommendation
Document this behavior so extension owners are aware of this fee reduction.
Resolution
M0 Team: Acknowledged.
-
L-32 Low baseToken Fetched In Every Iteration Gas Optimization Resolved
Description
Both
swapInTokenandswapOutTokenfetchesbaseTokenfrom theswapAdapter. ThebaseTokenis an immutable param in the adapter, while theswapAdapteraddress is immutable in theswapFacility. These calls are unnecessary and increase the transaction gas costs.Recommendation
Consider making
baseTokenimmutable also in swap facility and avoid gas spent to fetch this address.Resolution
M0 Team: Resolved.
-
L-33 Low MYieldFee:Unnecessary Index Update Validation Resolved
Description
The
MYieldFee.updateIndex()will update the index as long as theblock.timestamporearnerRatechanged since the last update.Although this seems correct for L1 deployments, L2 deployments will not work as expected. This is due to the fact that the
_latestEarnerRateAccrualTimestampon L2 is thelatestUpdateTimestampof $M, butblock.timestampon L1.Therefore, every time the
updateIndexis called on L2, the same variables will be assigned in storage, emitting the sameIndexUpdatedparams.Recommendation
Compare the
latestUpdateTimestampwith the_latestEarnerRateAccrualTimestampinstead:if ($.latestUpdateTimestamp = _latestEarnerRateAccrualTimestamp() $.latestRate = rate_) return
$.latestIndex;Resolution
M0 Team: Resolved.
No findings match.
Invariants 11
The review's fuzzing suite asserted 11 invariants. 8 held and 3 did not.
Every invariant tested
| ID | Invariant | Result |
|---|---|---|
MYF-01 | MYieldFee extension mToken Balance must be greater or equal than projectedSupply | Broken |
MYF-02 | MYieldFee extension mToken Balance must be greater or equal than projectedSupply + | Broken |
SWAP-01-00 | fee YTO-TO-YTO: MYieldToOne yield must not change after swaps | Held |
SWAP-01-01 | YFEE-TO-YFEE: MYieldFee yield must not change after swaps | Held |
SWAP-01-02 | MEARN-TO-MEARN: MEarnerManager yield must not change after swaps | Held |
SWAP-02 | Swap facility M0 balance must be 0 after swap out | Held |
SWAP-03 | Total M0 balance of all users must not change after swap | Held |
SWAP-04 | Received amount of M0 must be greater or equal than slippage | Held |
SWAP-05 | Received amount of USDC must be greater or equal than slippage | Held |
MEARN-01 | MEarnerManager extension mToken Balance must be greater or equal than | Broken |
ERR-01 | projectedTotalSupply Unexpected Error | Held |
More from M0
All 10 reports-
Liquidity Delivery Updates
4 findings 4 findings: 1 low, 3 informational -
PYUSDX
21 findings 21 findings: 8 low, 13 informational -
Liquidity Delivery
59 findings3 critical · 5 high 59 findings: 3 critical, 5 high, 10 medium, 14 low, 27 informational -
M Extensions Updates
16 findings 16 findings: 1 medium, 5 low, 10 informational
Put your code through the same review.
This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.
