Modern Stablecoins, How They're Made: M^0

Author(s): Sergey Boogerwooger, Artem Petrov
Security researcher(s) at MixBytes
Intro
We continue our series about stablecoins, and today's focus is the M^0 Protocol. This project sits between fully custodial stablecoins like USDT/USDC and "pure" algorithmic stablecoins, maintaining their peg through the activity of permissionless market makers. We aim to review this project to demonstrate a simpler stablecoin design that employs governance-controlled actors for price oracle and validation purposes. This approach enables the use of real-world assets (RWA) as collateral and primarily depends on the actions of Validators and Minters, validated by protocol governance, rather than market activity.

The design of such protocols is much simpler than that of typical algorithmic stablecoins as it only requires honest decisions by Validators and protocol governance regarding the amounts and prices/amounts of collateral assets. This simplicity strengthens these protocols against algorithmic vulnerabilities, as they do not rely on complex mathematical models, distribution, or incentivization schemes. The security of these protocols rests mainly on the integrity of protocol management.

This makes such projects highly attractive for operating at the intersection of "CeFi" and "DeFi." If there is, in any case, a connection to RWAs, the resulting stablecoin can also interact with RWAs, sharing the same risks associated with the centralized financial world. Additionally, this kind of protocol offers a straightforward and reliable method to issue stablecoins backed by RWA assets on any verifiable market. It serves as an effective solution for stablecoins pegged to various national currencies, leveraging financial markets of different countries as a source of collateral.
Let's explore how the protocol works.
Higher level design
Participants in the M^0 Protocol assume one of three main roles: Minters, Validators, and Earners. Minters create M stablecoin (MToken), backed by collateral that is verified and approved by Validators. In essence, this is similar to the stablecoin backing and minting process seen in USDT/USDC, but managed by Validator approvals and operational delays, which enable Validators to intervene at every step. Minters are required to update the amounts of their collateral, with penalties imposed for failing to provide timely updates. An additional mechanism in the protocol allows Earners (M token holders, approved by governance) to earn M tokens, generating income from minting fees.

From a real-world perspective, the M^0 protocol can function as follows:

  • A Minter possesses RWAs that generate yield, such as shares or bonds.
  • The Minter regularly updates the M^0 protocol with the amount of collateral (reflecting the accumulating yield from RWA assets).
  • The Minter mints MTokens while maintaining the collateralization ratio and distributes them to regular users.
  • Users activate "earning" on their MToken balances, causing their balances to grow.
  • The growing MToken balances are backed by the increasing collateral, keeping the protocol overcollateralized.

There are no algorithmic stabilization mechanisms in the M^0 Protocol; the peg is maintained simply by economic incentives for Miners: if the MToken price is below $1, Minters repurchase MTokens on secondary markets and retrieve their collateral, pushing the price up. If the price of MTokens is above $1, Minters deposit collateral and mint more MTokens, pushing the price down.

The core lifecycle of an MToken is straightforward: a Minter, approved by governance, provides proof of ownership of a certain amount of eligible collateral. This collateral value is submitted on-chain and approved via a vote by Validators. Once approved, the Minter can mint M stablecoins (up to the collateral value they own). The Minter must also provide regular updates on the collateral amounts. The protocol imposes fees and penalties, such as a mint fee and a penalty for failing to provide collateral updates. In practice, the provision of collateral amounts acts as a price oracle, continually supplying the protocol with the total value of owned assets and maintaining the collateralization ratio.

All amounts (MToken and collateral) within the protocol are measured in the same unit (USD), eliminating the need for conversions, exchange rates, or external oracles, which greatly simplifies the protocol. Additional mechanics, such as penalties for missed collateral updates and a two-phase minting process for MTokens, also exist and will be explored later in the article.

These scenarios are described in the protocol whitepaper, while our task is to examine the implementation, where we will directly analyze these mechanisms in the code. Let's dive in.
MinterGateway
Let's start with MinterGateway.sol, the main contract implementing the lifecycle of MToken. First, we have the MinterState struct, which contains information about the Minter. It includes "is active" flags, the total amount of collateral, the amount of pending collateral retrievals, and a set of timestamps related to the last update, penalization, freezing, and collateral retrieval events.

The main global state of MinterGateway contains two "total supplies": principalOfTotalActiveOwedM (the "raw" total of MTokens owned by "active" Minters) and totalInactiveOwedM (the MTokens owned by "inactive" Minters). The global state mappings include Minter states, raw balances of MTokens, mint proposals, and collateral retrieval requests. Additionally, there is a special mapping used for "per-minter" approvals from Validators.

Now, let's discuss the "principal" and "present" amounts of MTokens, which appear in various functions (for examples, see here or here). The Principal amount refers to the "raw" MToken balance, while the Present amount represents the principal amount multiplied by a continuously increasing index (we will discuss this later). Conversions between these amounts can be done using these functions, which offer "rounding up" and "rounding down" variants. As we mentioned in many previous articles, the secure approach to rounding in DeFi protocols is always rounding in favor of the protocol; that is, rounding down when tokens are "out" of the protocol and rounding up when tokens are "in" the protocol.

Another key aspect to consider is calculating the collateral amount for a Minter using the collateralOf() function. This function returns only "active" collateral, meaning collateral that is not part of pending retrievals. A retrieval request reduces the active collateral at the moment it is created, removing that portion from active calculations.
Collateral operations
Now let's examine the updateCollateral() function, which is used when a Minter wants to prove its collateral. This function accepts extensive data in its parameters, such as a list of validators, timestamps, and signatures. This design enables the Minter to collect all necessary approvals off-chain and present them in a single transaction, eliminating the need for on-chain approvals. Additionally, there is a bytes32 metadataHash_ parameter, allowing the inclusion of an off-chain information hash if the collateral approval process requires additional off-chain activity validation.

The function begins with signature verification, saving the minimum timestamp from all signatures. The signature validation procedure iterates over all the signatures to check whether it meets the required threshold, which is a constant determined by governance. The signature check itself is straightforward, and the main verifications include ensuring that the signature's timestamp is not in the future and is not earlier than this validator's last recorded timestamp. The validator must also be listed in the approved Validators list.

Once all signatures are verified, it's time to impose penalties, and the first type of penalty is a "missed collateral update penalty", imposed if a validator has failed to update the collateral amount in the past periods. At this stage, the function determines missed intervals, calculating how many intervals were missed since the Minter's last update. The penalty is then applied to the Minter.

Interestingly, in the _imposePenalty() function, the penalty is added to the Minter's MToken balance. At first glance, it might seem that penalties reward the Minter. However, since "owed MTokens" actually represent debt, increasing the balance heightens the Minter's collateral redemption costs(Minter will need to repay more BOLD to return their collateral). The protocol cannot touch the collateral itself or perform operations such as "mint", "burn", or "transfer", as the collateral represents a real-world asset (RWA) and this asset cannot be transferred or modified without off-chain interactions. Such constraints pose challenges to all RWA-related blockchain projects, where off-chain operations cannot be conducted fully on-chain, and RWA handling is limited to off-chain participants verifying off-chain operations.

The _imposePenaltyIfUndercollateralized() function implements the second type of penalty - "undercollateralization penalty". During this step, the function retrieves the current amount of owed MTokens and calculates the maximum allowed MTokens or collateral amount using the mintRatio, a value set by governance (ranging from 100% to 650%). If the collateralization ratio is valid, no penalty is applied. Otherwise, the function calculates the excess MTokens, determines the duration of undercollateralized intervals, and applies a penalty corresponding to the excess MTokens, weighted by the time the Minter was undercollateralized.

After this, the updateCollateral() function processes the pending retrievals of collateral. This operation essentially "does nothing" besides updating the retrieval queue and the "total retrievals" value, as actual interactions with RWAs are handled off-chain by the Minter.

The function then updates the collateral balance of the Minter and the last update timestamp.

The final step in updateCollateral() is the updateIndex(), which adjusts two indices (rates): the minter index, which continuously increases the MToken balance, and the earning index, which increases the MToken balances of holders who have enabled the "is earning" flag for their MTokens. We will explore these indices in detail in a separate section of this article.

Next in the protocol is collateral retrieval, which involves two steps: proposing a retrieval and "executing" it later during the next call to updateCollateral(). The proposal is implemented in the proposeRetrieval() function. Each retrieval is assigned a unique retrievalId, and its amount is added to the "pending" retrievals field. This "pending" amount is excluded from the "active" collateral balance through the collateralOf() function.
MToken operations
Let’s now discuss the minting and burning of MTokens. Minting follows a two-step procedure: the proposal of minting and its execution.

The proposeMint() function performs an undercollateralization check and creates a MintProposal. There can only be one MintProposal for one Minter at a time; any new proposal simply overwrites the previous one.

After that, the Minter can call the mintM() function. This is permitted only after the mintDelay() (to allow Validators to respond to malicious mints) and before the mintTTL() (to exclude outdated mints). Both values are set by governance. The subsequent steps include an undercollateralization check and updating the "raw" MToken balance of the Minter. Notably, the Minter can mint MTokens to an external destination_ address rather than their own.

The burn process is simpler and can be completed in one step using the burnM() function. This function works exclusively with active or "officially" isDeactivated Minters, imposes penalties for missed collateral updates, burns the MTokens of the transaction sender, and updates indices. This function is permissionless, allowing anyone to "fix" Minters in case of issues.

Additionally, any Validator has the ability to freeze a Minter, preventing them from minting MTokens for the duration of minterFreezeTime(), a period set by governance. Activation and deactivation are governed through governance mechanisms. First, a Minter must be approved, and only after approval can it be activated. A similar process applies to deactivation, where only a disapproved Minter can be deactivated.

Protocol governance, which is responsible for handling activation and deactivation processes and determining various protocol parameters, falls outside the scope of this article. For more details about the governance process, refer to the protocol docs. The TTG Registrar contract deployed on the mainnet can be found here.

Now, let’s proceed to interest accrual and indices.
ContinuousIndexing
The code of the updateIndex() function shows two index updates: the Minter index and the Earning index.

Firstly, the Minter Rate directly uses the rate set by governance in MinterRateModel.sol.

Secondly, there’s the Earner Rate. The primary challenge here is ensuring that the Earner Rate does not result in a situation where more MTokens are paid to Earners than what is collected from Minters. This rate must therefore depend on the Minter Rate and utilization, defined as amount_receiving_minter_rate / amount_receiving_earner_rate (represented in the code by totalActiveOwedM_ and totalEarningSupply_). The system adjusts for differences in utilization: when utilization < 1 (indicating there are more "earners" than "minters"), the system decreases the Earner Rate ("lag behind"), whereas, when utilization > 1 (indicating fewer "earners" than "minters"), it increases the rate ("catch up").

The "lag behind" scenario is handled by this branch. However, the "catch up" branch is more complex because it must ensure that the number of MTokens generated by the Minter Rate does not exceed the number of MTokens generated by the Earner Rate. To address this, "more frequent and responsive" rate adjustments are needed. The protocol uses the RATE_CONFIDENCE_INTERVAL (30 days) to calculate "cashflows" over a shorter period, involving exp and ln functions. Additionally, the Earner Rate is restricted from exceeding hardlimit set by governance.
MToken
Now, let’s examine the MToken.sol contract.

Firstly, MToken balances are not simple uint256 values. Instead, they are represented as a MBalance struct that includes the isEarning flag. The minting and burning of MTokens are governed by two immutable governance addresses.

The ability to earn MTokens requires two "total supply" values: both represent the "raw" total amounts for non-earning and earning MTokens (the second value is used in the Earning Rate indexing). The process of making a user's balance "earning" is restricted by governance. Only approved accounts can earn (allowing for the implementation of KYC requirements). The procedures for starting and stopping earning are straightforward and include adjustments to the "total earning" and "total non-earning" supplies.

The balanceOf() function returns the raw balance for non-earning accounts and the indexed balance for earning accounts.
Implementation details
As the M^0 protocol lacks a complex codebase, nearly all of its logic is straightforward and simple.

One noteworthy aspect is the handling of overflows, as seen here or here.

Another interesting feature is the "diamond" storage for governance-controlled values. These values are identified by names and accessed using these names as keys.
Conclusion
The M^0 Protocol provides a solution that enables the implementation of the M stablecoin, backed by real-world assets, while maintaining full governance control over Validators, Minters, and Earners. At the same time, the M token itself can be used as a regular ERC20 token for any purpose.

The simplicity of the codebase for such projects enhances their reliability within the DeFi space. Because of the permissioned nature of these protocols, the risks of programmable hacks are lower. However, off-chain risks related to governance and external financial markets are shared among protocol users and service providers, and, if the underlying RWAs are unstable, the protocol becomes unstable as well, which impacts governance, Minters, Validators, and Earners collectively.

M^0 Protocol serves as a bridge between traditional financial institutions and decentralized assets, empowering users to own and use M tokens, knowing they are supported by real bonds, shares, and other traditional financial assets. For instance, the M^0 Protocol can be employed to launch stablecoins pegged to national currencies or to create specialized digital assets tailored to specific industries.

Thank you for reading! See you in the next article!
  • Who is MixBytes?
    MixBytes is a team of expert blockchain auditors and security researchers specializing in providing comprehensive smart contract audits and technical advisory services for EVM-compatible and Substrate-based projects. Join us on X to stay up-to-date with the latest industry trends and insights.
  • Disclaimer
    The information contained in this Website is for educational and informational purposes only and shall not be understood or construed as financial or investment advice.
Other posts