Proposal: MU01-Phase1 MultiCollateral Mento

MentoLabs has been hard at work preparing a major update to the Mento protocol. This was previously discussed on the Celo forum. In this post, I want to dive a bit into the technicals of the upcoming governance proposal MU01-Phase1.

This CGP will be quite hefty as we are configuring a bunch of things in the system all at once. We could have kept this CGP smaller by doing most of the configuration out-of-band and then transferring ownership to Governance but decided against that in favor of transparency. We feel that it’s more straightforward to verify the governance proposal instead of verifying multiple transactions and the blockchain state of contracts that we would configure prior to the CGP.

We’ve also considered whether to break this down into multiple CGP but that has proven unhelpful in the past, and also introduces significant delays to the rollout process.

In the following, I will dive into each transaction that’s part of the proposal. This can be used as a verification guide for approves and voters during the governance process, and as means to deepdive into the new iteration of Mento. You can follow along with an example of Alfajores CGP cli output (what you would see when running celocli governance:show).

Overview

The MU01 deployment, in its totality, consists of a preparation phase – putting new protocol building blocks in place – and the proposal phase – wiring everything up.

The preparation phase is executed by the mento team prior to the CGP and ownership of ownable contracts is transferred to Governance. It can be broken down into these steps:

  1. Create Proxies (BrokerProxy, BiPoolManagerProxy, BreakerBoxProxy, PartialReserveProxy)
  2. Create Nonupgradable contracts (ConstantSumPricingModule, ConstantProductPricingModule, MedianDeltaBreaker, ValueDeltaBreaker)
  3. Create Implementations (Broker, BiPoolManager, BreakerBox, Reserve, StableToken)

Once we have all the new contracts deployed, we can move to the proposal phase and submit the CGP, this is done programmatically by our deployment tooling. We’ve also built tooling which also allows us to simulate the CGP execution in a fork of the target chain and then run tests against that fork before submitting.

We can break down the CGP logically into the following sections, noted here with transaction indices in brackets:

  1. New Proxy Initialization (0-3)
  2. Update implementation for existing proxies (4-7)
  3. Setup the partial Reserve (8-13)
  4. Update Registry (14)
  5. Configure exchanges (15-18)
  6. Configure breaker box (19-30)
  7. Configure trading limits (31-34)

Now, let’s dive into each section of the CGP, and look at each transaction. I recommend following along with this example CGP outbut or, if this is not a drill, with your celocli output handy.

Some of the steps that follow require verifying that an address is correct, I’ve extracted what that entails into a separate section Appendix A: Verifying an address where you’ll find instructions for the different scenarios that come up, including how to verify contracts deployed in the preparation phase.

For the technically savvy, you can find everything I’m describing here in our deployment tooling repository as code.

1. New Proxy initialization

As part of MU01, we introduce 4 new proxies:

  • BrokerProxy
  • BiPoolManagerProxy
  • BreakerBoxProxy
  • PartialReserveProxy

The Broker is the new entry point for the Mento Protocol. It has spender rights on the Reserve and minting rights on the stable assets. It, however, has no exchange pricing logic, it relies on exchange providers, which implement the IExchangeProvider interface to price trades. The first of which is the BiPoolManager which is a more modular and generalized version of the exchange mechanism from the current Mento’s Exchange contract. Check the Mento documentation for more information.

The PartialReserve is a temporary reserve that we will use during the rollout phases of MU01 in order to limit the amount of collateral exposed to the new system, as a safety measure.

TX#0 BreakerBoxProxy initialization:

  • Verify that the proxy address is correct
  • Verify that the implementation address is correct
  • Verify that the arguments to the initialization call are correct:
    • _rateFeedIDs an array of oracle feeds ids
      • CELO/USD - cUSD token address
      • CELO/EUR - cEUR token address
      • CELO/BRL - cREAL token address
      • USDC/USD - address(uint160(keccak256(“USDCUSD”)))
    • _sortedOracles address

TX#1 BiPoolManagerProxy initialization:

  • Verify that the proxy address is correct
  • Verify that the implementation address is correct
  • Verify that the arguments to the initialization call are correct:
    • _broker address of the BrokerProxy
    • _reserve address of the PartialReserveProxy
    • _sortedOracles address of the SortedOraclesProxy
    • _breakerBox address of the BreakerBoxProxy

TX#2 BrokerProxy initialization:

  • Verify that the proxy address is correct
  • Verify that the implementation address is correct
  • Verify that the arguments to the initialization call are correct:
    • _exchangeProviders: list of exchange providers that broker can use for pricing swaps, currently that’s only the BiPoolManager which implements a more modular and generalized version of what the Exchange contract from Mento V1
      • Length should 1
      • Element at index 0 should be BiPoolManagerProxy
    • _reserve: PartialReserveProxy address

TX#3 PartialReserveProxy initialization

  • Verify that the proxy address is correct
  • Verify that the implementation address is correct
  • Verify that the arguments to the initialization call are correct:
    • registryAddress well-known registry address
    • _tobinTaxStalenessThreshold is ****not very relevant or this partial reserve, but uses the same value as the main Reserve
    • _spendingRatioForCelo 100%, spenders can fully drain the reserve, we will move all collateral back to the primary reserve after the rollout is done. The number is a Fixidity float – a number with 24 decimals, i.e. 1 is written as 1e24.
    • _frozenGold set to zero
    • _frozenDays set to zero
    • _assetAllocationSymbols and _assetAllocationWeights
      • Same as the primary reserve
    • _tobinTax and _tobinTaxReserveRatio
      • Tobin tax is not enabled on the partial reserve.
    • _collateralAssets the list of ERC20 tokens that act as collateral in the system, it should contain:
      • GoldToken address
      • axlUSDC Celo address
    • _collateralAssetDailySpendingRatios 100% on both, same logic as _spendingRatioForCelo above

2. Update implementation for existing proxies

As part of MU01, we’ve made a few changes to existing contracts, namely StableToken – where we’ve added a condition allowing the Broker to mint and burn – and SortedOracles – where we’ve added a touch point with the BreakerBox called when the median updates.

TX#4 Upgrade StableTokenProxy

  • Verify the new implementation address

TX#5 Upgrade StableTokenEURProxy

  • Verify the new implementation address

TX#6 Upgrade StableTokenBRLProxy

  • Verify the new implementation address

TX#7 Upgrade SortedOraclesProxy

  • Verify the new implementation address

3. Setup the partial reserve

To be able to use the partial reserve we need to add all stable asset addresses and configure spenders.

TX#8 Add StableToken to the Reserve

  • Verify the addresses are correct.

TX#9 Add StableTokenEUR to the Reserve

  • Verify the addresses are correct.

TX#10 Add StableTokenBRL to the Reserve

  • Verify the addresses are correct.

TX#11 BrokerProxy is added as an exchange spender

  • Verify the address is correct.

TX#12 The Mento Partial Reserve Spender Multsig is added as a spender.

  • Verify the address is correct.

TX#13 adds the main Reserve as an “otherReserveAddress”, this limits where the spender (in this case the Mento multisig) can transfer funds to

  • Verify the main Reserve address is correct.

4. Update the registry

Mento is moving away from using the Registry so we haven’t used it in our new contracts, however, we haven’t removed it from existing contracts, therefore in StableToken we’ve decided to keep the same pattern where checking for mint/burn rights, therefore we decided to add the Broker as the only new Mento contract in the registry, until further notice.

TX#14 Add Broker to the registry:

  • Verify that the address is the BrokerProxy

5. Configure the exchanges

In this step, we set what asset pairs can be traded by configuring the BiPoolManager which exposes these via the Broker.

Verifying an exchange payload feels a bit complicated at first sight because the argument to the function is a solidity struct with a nested struct as well, which gets serialized verbosely in celocli with all arguments both named and using indices. The structure also gets serialized both under args and params so something like 11 attributes is repeated 4 times in the outputs making it feel harder to parse.

To understand the structure check Appendix B: Verifying a BiPoolManager exchange. In this and the following two sections, we will start seeing a bunch of configuration parameters. Check Appendix C: Mento Protocol Configuration on how to think about configuration.

TX#15 Create the cUSD/CELO exchange

  • Verify the BiPoolManager address
  • Verify the exchange configuration

TX#16 Create the cEUR/CELO exchange

  • Verify the BiPoolManager address
  • Verify the exchange configuration

TX#17 Create the cREAL/CELO exchange

  • Verify the BiPoolManager address
  • Verify the exchange configuration

TX#18 Create the cUSD/axlUSDC exchange

  • Verify the BiPoolManager address
  • Verify the exchange configuration

6. Configure the breaker box

The BreakerBox is our on-chain oracle risk protection mechanism, it integrates with SortedOracles and lets us add breakers – contracts that implement the IBreaker interface – to oracle rate feeds. These breakers define conditions under which trading is suspended. The first breakers that we implemented are:

  • MedianDeltaBreaker: verifies that a new median is within a percentage threshold of the previous median. Used for the CELO/USD, CELO/EUR, and CELO/BRL rate feeds.
  • ValueDeltaBreaker: verifies that a new median is within a percentage threshold of a configurable reference value. Used for the USDC/USD rate feed, which should stay in a tight band around $1.

Once a breaker is tripped it will have a cooldown period. After that, the breaker is automatically reset once there’s a median update that meets the breaker’s requirements, otherwise, it can trip again.

The MedianDeltaBreaker and ValueDeltaBreaker have been deployed as non-upgradable contracts during the preparation phase.

On a high level, what needs to happen in this stage is:

  • Add breakers to BreakerBox
  • Configure breakers
  • Enable breakers for rate feeds
  • Set breaker box on SortedOracles

TX#19 Add the MedianDeltaBreaker to the BreakerBox

  • Verify the addresses

TX#20 Add the ValueDeltaBreaker to the BreakerBox

  • Verify the addresses

TX#21 Set the cooldown times on the MedianDeltaBreaker

  • Verify the address
  • Verify the parameters:
    • rateFeedIDs: the array of rate feeds to configure, should contain the rate feed identifiers for:
      • CELO/USD: the cUSD token address
      • CELO/EUR: the cEUR token address
      • CELO/USD: the cUSD token address
    • cooldownTimes: the array of cooldown times to be configured, the indices will match with the rateFeedIDs array. The values are seconds.

TX#22 Set the rate change percentage thresholds for the MedianDeltaBreaker

  • Verify the address
  • Verify the parameters:
    • rateFeedIDs: the array of rate feeds to configure, should contain the rate feed identifiers for:
      • CELO/USD: the cUSD token address
      • CELO/EUR: the cEUR token address
      • CELO/USD: the cUSD token address
    • rateChangeThresholds: the array of rate change thresholds to be configured, the indices will match with the rateFeedIDs array. The values are Fixidity floats – a number with 24 decimals, i.e. 1 is written as 1e24.

TX#23 Set the reference values for the ValueDeltaBreaker

  • Verify the address
  • Verify the parameters:
    • rateFeedIDs: the array of rate feeds to configure, should contain the rate feed identifiers for:
      • USDC/USD: address(uint160(keccak256(“USDCUSD”)))
    • _referenceValues: the array of reference values to set, indices will match the rateFeedIDs array. The values are Fixidity floats – a number with 24 decimals, i.e. 1 is written as 1e24.

TX#24 Set the cooldown times on the ValueDeltaBreaker

  • Verify the address
  • Verify the parameters:
    • rateFeedIDs: the array of rate feeds to configure, should contain the rate feed identifiers for:
      • USDC/USD: address(uint160(keccak256(“USDCUSD”)))
    • cooldownTimes: the array of cooldown times to be configured, the indices will match with the rateFeedIDs array. The values are seconds.

TX#25 Set the rate change percentage thresholds for the ValueDeltaBreaker

  • Verify the address
  • Verify the parameters:
    • rateFeedIDs: the array of rate feeds to configure, should contain the rate feed identifiers for:
      • USDC/USD: address(uint160(keccak256(“USDCUSD”)))
    • rateChangeThresholds: the array of rate change thresholds to be configured, the indices will match with the rateFeedIDs array. The values are Fixidity floats – a number with 24 decimals, i.e. 1 is written as 1e24.

TX#26 Enable the MedianDeltaBreaker on the CELO/USD rate feed

  • Verify the BreakerBox address
  • Verify the arguments:
    • breakerAddress should be the MedianDeltaBreaker
    • rateFeedID should be the identifier for the CELO/USD rate, i.e. the cUSD token address
    • isEnabled true

TX#27 Enable the MedianDeltaBreaker on the CELO/EUR rate feed

  • Verify the BreakerBox address
  • Verify the arguments:
    • breakerAddress should be the MedianDeltaBreaker
    • rateFeedID should be the identifier for the CELO/EUR rate, i.e. the cEUR token address
    • isEnabled true

TX#28 Enable the MedianDeltaBreaker on the CELO/BRL rate feed

  • Verify the BreakerBox address
  • Verify the arguments:
    • breakerAddress should be the MedianDeltaBreaker
    • rateFeedID should be the identifier for the CELO/BRL rate, i.e. the cREAL token address
    • isEnabled true

TX#29 Enable the ValueDeltaBreaker on the USDC/USD rate feed

  • Verify the BreakerBox address
  • Verify the arguments:
    • breakerAddress should be the ValueDeltaBreaker
    • rateFeedID should be the identifier for the USDC/USD rate, i.e. address(uint160(keccak256(“USDCUSD”)))

TX#30 Sets the BreakerBox reference on SortedOracles

  • Verify the addresses

7. Configure trading limits

Another major feature shipped in MU01 is trading limits. The limits are configured for each exchange and are enforced by the Broker. We can define:

  • L0 and L1: Two time-based limits (per exchange), for example:
    • 5m cUSD per day (L0) on the cUSD/USDC exchange, or
    • 100k cUSD in 5min (L0) and 1mil cUSD in 1 day (L1) on the cUSD/CELO exchange.
  • LG: Global limit, can be used with temporary pool configurations.

The limit affects both contraction and expansion, so 5m cUSD in 1 day on cUSD/USDC means that the protocol can neither redeem nor issue more than 5m cUSD net during a 1**-**day window, but it can in absolute terms if you have both inflows and outflows happening. For example, building on the cUSD/USDC case above, if we have an outflow of 4mil cUSD (+4m), then an inflow of 2m cUSD (-2m), then some more outflow of 2m (+2m). The net flow will never be >5mil, but the total outflow will be 6m.

The TradingLimit.Config structure consists of these fields:

  • timestep0: the time-window duration of L0
  • timestep1: the time-window duration of L1
  • limit0: the net flow limit of L0
  • limit1: the net flow limit of L1
  • limitGlobal: the net flow limit of LG
  • flags: binary flags determining which limits are enabled possible values:
    • 1 (or 0b001 in binary) - just L0
    • 3 (or 0b011 in binary)- L0 and L1
    • 5 (or 0b101 in binary) - L0 and LG
    • 7 (or 0b111 in binary)- L0, L1 and LG
    • to simplify some logic L1 can be enabled if and only if L0 is enabled, therefore 6 (0b110) is not a valid configuration

TX#31 Configure trading limits for the cUSD/CELO exchange

  • Verify Broker address
  • Verify parameters:
    • exchangeId: the deterministic id of the cUSD/CELO exchange – check Appendix B: Verifying a BiPoolManager exchange for more info on how it’s derived
    • token: which asset in the pair to target, in this case, the cUSD token address
    • config: the trading limit configuration

TX#32 Configure trading limits for the cEUR/CELO exchange

  • Verify Broker address
  • Verify parameters:
    • exchangeId: the deterministic id of the cEUR/CELO exchange
    • token: which asset in the pair to target, in this case, the cEUR token address
    • config: the trading limit configuration

TX#33 Configure trading limits for the cREAL/CELO exchange

  • Verify Broker address
  • Verify parameters:
    • **exchangeId:**the deterministic id of the cREAL/CELOexchange
    • token: which asset in the pair to target, in this case, the cREAL token address
    • *config: the trading limit configuration

TX#34 Configure trading limits for the cUSD/USDC exchange

  • Verify Broker address
  • Verify parameters:
    • exchangeId: the deterministic id of the cUSD/USDC exchange
    • token: which asset in the pair to target, in this case, the cUSDtoken address
    • config: the trading limit configuration

That’s it.

If you’ve made it so far you deserve a cookie. :cookie:

Appendix A: Verifying an address

Verifying an address is the CGP is correct requires a few different strategies depending on what that address is. Here is a flowchart that can help with that:

Appendix B: Verifying a BiPoolManager exchange

Coming soon, stay tuned.

Appendix C: Mento Protocol Configuration

Coming soon, stay tuned.

3 Likes

I’ll add more color and fill in the sections above, but I wanted to shoot this out before the Governance Call today so that there’s content out there to see after the presentation. :peace_symbol:

Following up with the missing content from the post we’ve published an updated version of our dcos, which contain a few important sections relevant ot this CGP:

  • Mento Deployment Addresses where you can verify the contracts that were deployed by the MentoLabs team and are used in this CGP.
  • Mento Deployment Verification which describes our bytecode verification tooling which allows anybody to verify that the bytecode of the addresses on-chain matches the compiled bytecode of the mento-core repo at the commit hash that the audit references.
  • BiPoolManager Structs Description which describes the structures used when calling createExchange with descriptions of what the parameters mean.
  • TradingLimits Structs Description which describes the structs related to trading limits, used when calling setTradingLimit on the Broker.

All of these should fill in the gaps needed to verify the CGP and understand how it’s being configured.

1 Like

Also important note, you will need the latest beta version of celocli in order to decode the proposal, please update:

$ npm install -g @celo/celocli@1.8.1-beta.1

This beta version makes use of an improvement that MentoLabs has made on the tooling which can now decode proposals that interact with any contract that’s verified on https://sourcify.dev.

2 Likes

its now possible to use the latest cli for this yarn global add @celo/celocli@latest (@celo/celocli@2.0.0)

2 Likes