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:
- Create Proxies (BrokerProxy, BiPoolManagerProxy, BreakerBoxProxy, PartialReserveProxy)
- Create Nonupgradable contracts (ConstantSumPricingModule, ConstantProductPricingModule, MedianDeltaBreaker, ValueDeltaBreaker)
- 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:
- New Proxy Initialization (0-3)
- Update implementation for existing proxies (4-7)
- Setup the partial Reserve (8-13)
- Update Registry (14)
- Configure exchanges (15-18)
- Configure breaker box (19-30)
- 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
- _rateFeedIDs an array of oracle feeds ids
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
-
_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
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.
-
rateFeedIDs: the array of rate feeds to configure, should contain the rate feed identifiers for:
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.
-
rateFeedIDs: the array of rate feeds to configure, should contain the rate feed identifiers for:
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”)))
-
USDC/USD:
- _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.
-
rateFeedIDs: the array of rate feeds to configure, should contain the rate feed identifiers for:
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”)))
-
USDC/USD:
- cooldownTimes: the array of cooldown times to be configured, the indices will match with the rateFeedIDs array. The values are seconds.
-
rateFeedIDs: the array of rate feeds to configure, should contain the rate feed identifiers for:
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”)))
-
USDC/USD:
- 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.
-
rateFeedIDs: the array of rate feeds to configure, should contain the rate feed identifiers for:
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.
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:
- Is it a Mento contract?
- Yes:
- Did it exist prior to MU01? (e.g. SortedOracles, StableToken, etc)
- Yes:
- It’s probably a proxy so you can check the addresses in the doc https://docs.mento.org/mento-protocol/developers/deployment-addresses
- You can also query the Registry contract on the target network
- No:
- New contracts can also be checked in the docs https://docs.mento.org/mento-protocol/developers/deployment-addresses
- If it’s a new proxy or implementation deployed in the preparation phase, we have some tooling to verify that the byte code on-chain matches what was deployed and audited. This is a bit WIP but I’ll share more info here soon.
- Yes:
- Did it exist prior to MU01? (e.g. SortedOracles, StableToken, etc)
- No:
- Is it a Core Celo contract?
- Yes:
- It’s probably GoldToken which can be verified in the Celo Registry
- No:
- It’s probably axlUSDC which can be verified here: https://docs.axelar.dev/resources/mainnet
- Yes:
- Is it a Core Celo contract?
- Yes:
Appendix B: Verifying a BiPoolManager exchange
Coming soon, stay tuned.
Appendix C: Mento Protocol Configuration
Coming soon, stay tuned.