<!-- canonical: https://0xsimao.com/findings/m-0-invariant-hold-minter-gateway -->

# MToken's total principal invariant doesn't hold without MinterGateway, leading to potential principal loss

Low/Info · Three Sigma · Stablecoin framework · 8th January, 2024

Finding 3S-M^0-L05 of the M^0 Minter Gateway security review.

- Protocol: https://www.m0.org/
- Report: /reports/m-0
- Codebase: https://github.com/0xsimao/ttg/tree/a8127901fa1f24a2e821cf4d9854a1aa6ac8088c
- Source: https://cdn.sanity.io/files/qoqld077/production/1cdafafad874aba76e062ad8c216c98338c096db.pdf

---

### Description

In function [MToken._mint](https://github.com/MZero-Labs/protocol/blob/3499f50ff3382729f3e59565b19386ba61ef8e36/src/MToken.sol#L217-L233), when the account is an earner, the principal gets added to both
its raw balance and to the global variable principalOfTotalEarningSupply. However, this
addition is unchecked, allowing the total principle to silently overflow the maximum uint112
amount. The final check assessing whether or not the total principal (earning and non
earning supply) is greater than type(uint112).max will pass if the earning principal amount
already overflowed before in _addEarningAmount.
This can further cause damage to earners if e.g. Alice is able to overflow Bob's balance:
1. Bob is going to call stopEarning.
2. Alice frontruns Bob's transaction and transfers him the exact principal amount to make
Bob's balance equal type(uint112).max + 2.
3. When Bob calls stopEarning, there's an unsafe cast which will truncate his entire
balance: uint112 principalAmount_ = uint112(_balances[account_].rawBalance);.
Bob's new balance will be 1.
It should be noted that the MinterGateway [prevents the invariant from easily breaking](https://github.com/MZero-Labs/protocol/blob/3499f50ff3382729f3e59565b19386ba61ef8e36/src/MinterGateway.sol#L266-L271). But
such an important MToken invariant should not be solely preserved by an external contract,
as it can lead to potential problems in the future (e.g. changing the MinterGateway
contract).

### Recommendation

Consider removing the unchecked addition of principalOfTotalEarningSupply in
_addEarningAmount:

```solidity
function _addEarningAmount(address account_, uint112 principalAmount_)
internal {
    unchecked {
        _balances[account_].rawBalance += principalAmount_;
    }
    principalOfTotalEarningSupply += principalAmount_;
}
```

### Status

Addressed in [#74523a8](https://github.com/MZero-Labs/protocol/commit/74523a8e77be8654b902baaca41201d135c72190).

---

Related findings:

- [RebalanceLogic::rebalanceCallback() doesn't update lastTotalAssets when rebalancing from morpho to morpho](https://0xsimao.com/findings/yieldoor-ii-rebalance-callback-rebalancing-morpho): Yieldoor LoopedVault
- [Total cds deposited amount is incorrectly modified when cds depositor is at a loss, leading to stuck USDa](https://0xsimao.com/findings/autonomint-deposited-modified-depositor-stuck): Autonomint
- [Reward Token Loss for LPs During NFT Position Transfer](https://0xsimao.com/findings/bmx-reward-nft-position-transfer): BMX
- [Precision Loss in `notifyRewardAmount` Function Causes Unclaimable RewardToken](https://0xsimao.com/findings/exactly-protocol-update-staking-contract-ii-precision-notify-reward-unclaimable): Exactly Protocol Update - Staking Contract
