[Proposal] Mento Upgrade MU03 - Enable more trading pairs and continue phasing out V1

Overview

The aim of MU03 is to introduce three new trading pairs – cEUR/USDC, cBRL/USDC, and cEUR/EUROC – and to increase the trading limits on the existing pairs, as a next step towards fully deprecating Mento V1 exchanges.

In order to support those new pairs safely we improved our circuit breaker and our constant sum pricing calculation. For the circuit breaker, we introduced the idea of dependent rate feeds, i.e. a trading pair like cEUR/USDC will react to both EUR/USD and USDC/USD oracle rate movement that is outside of normal ranges. For the constant sum pricing logic, we updated it to allow for 1:1 swaps between assets with different exchange rates, as opposed to the initial implementation which was aimed at cUSD/USDC swaps.

The deployment of the new contracts was done by the mento team prior to creating the CGP and Ownership over all new implementations was transferred to Governance.

In preparation for this CGP, we tested the proposal on Alfajores and Baklava. Additionally, we’ve simulated the proposal on a fork of Celo mainnet and ran tests against the fork.

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

  1. Add axlEUROC to the PartialReserve (0-1)
  2. Update implementation for existing proxies (2-4)
  3. Update BreakerBox references to the new instance (5-6)
  4. Configure the exchanges (7-12)
  5. Configure trading limits (13-19)
  6. Scale down Exchange reserve fractions(20-22)
  7. Configure BreakerBox (23-34)
  8. Configure the MedianDeltaBreaker (35-38)
  9. Configure the ValueDeltaBreaker (39-41)
  10. Whitelist Diwu as an oracle provider for EUROCEUR (42)

Now, let’s dive into each section of the CGP, and look at each transaction. We recommend following along with your celocli output handy.

Some of the steps that follow require verifying that an address is correct, We’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 we are describing here in our deployment tooling repository as code.

1. Add axlEUROC to the PartialReserve

As part of MU03, we introduce a new cEUR/EUROC exchange therefore we need to add bridged EUROC as a collateral asset to the PartialReserve and also configure the daily spending ratio.

TX#0 adds axlEUROC as a collateral asset to the PartialReserve

  • Verify the PartialReserveProxy address
  • Verify the axlEUROC address

TX#1 sets the daily spending ratio for axlEUROC to 100%. The value is represented as a fixed point number with 24 decimals, i.e. 1 is written as 1e24

  • Verify the PartialReserveProxy address
  • Verify the axlEUROC address
  • Verify the ratio: 1e24 = 100%

2. Update implementation for existing proxies

In MU03, we’ve made a few changes to existing contracts, namely:

BiPoolManager – where we’ve changed some logic to support a value-based constant sum PricingModule.

Broker - where we’ve added additional validation checks on TradingLimits.

SortedOracles - where we’ve added an additional check of the on-chain circuit breakers on removing reports.

For the corresponding proxies, we need to update the implementations.

TX#2 upgrades BiPoolMangerProxy

  • Verify the BiPoolMangerProxy address
  • Verify the new implementation address

TX#3 upgrades BrokerProxy

  • Verify the BrokerProxy address
  • Verify the new implementation address

TX#4 upgrades SortedOraclesProxy

  • Verify the SortedOraclesProxy address
  • Verify the new implementation address

3. Update BreakerBox references to the new instance

As explained in the intro we’ve deployed a new BreakerBox so we need to update references in the contracts that interact with it.

TX#5 Set BreakerBox address in BiPoolManager

  • Verify the BiPoolMangerProxy address
  • Verify the BreakerBox address

TX#6 Set BreakerBox address in SortedOracles

  • Verify the SortedOraclesProxy address
  • Verify the BreakerBox address

4. Configure the exchanges

Since MU03 deploys an updated version of the ConstantSumPricingModule the current cUSD/axlUSDC exchange that is referencing the previous version needs to be destroyed and redeployed with an updated reference.

In order to prevent future exchanges from being created with a reference to an outdated Pricing Module we introduced a pricingModules mapping in MU03. The mapping tracks the addresses of the current Pricing Module implementations and is checked when exchanges get created. This mapping needs to be filled with the current version of the ConstantSum and ConstantProduct Pricing Module before creating exchanges.

Also as part of MU03 three new exchanges are created, namely cEUR/axlUSDC, cBRL/axlUSDC, and cEUR/axlEUROC.

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 are repeated 4 times in the outputs making it feel harder to parse. When reading the output from celocli we recommend ignoring the args field and focusing on the params, and also ignoring the numbered fields and focusing only on the named fields.

To verify the exchange configuration is sane, check the docs BiPoolManager - Mento Protocol for an explanation of the structure.

TX#7 Set current Pricing Modules

  • Verify the BiPoolManagerProxy address
  • Verify ConstantSumPricingModule address
  • Verify ConstantProductPricingModule address

TX#8 Destroy the cUSD/axlUSDC exchange

  • Verify the BiPoolManagerProxy address
  • Verify parameters:
    • exchangeId: the deterministic id of the cUSD/axlUSDC exchange
    • index: the index of the exchange in the BiPoolManager internal array

TX#9 Re-Create the cUSD/axlUSDC exchange

  • Verify the BiPoolManagerProxy address
  • Verify the exchange configuration

TX#10 Create the cEUR/axlUSDC exchange

  • Verify the BiPoolManagerProxy address
  • Verify the exchange configuration

TX#11 Create the cBRL/axlUSDC exchange

  • Verify the BiPoolManagerProxy address
  • Verify the exchange configuration

TX#12 Create the cEUR/axlEUROC exchange

  • Verify the BiPoolManagerProxy address
  • Verify the exchange configuration

5. Configure trading limits

In order to proceed with the transition from Mento V1 to Multi Collateral Mento the trading limits for existing exchanges are increased and trading limits for the new exchanges are set.

Similar to the previous section, the out of celocli is too verbose when dealing with arguments and structs, so it’s recommended to ignore the args field and focus only on params and there ignore the numbered fields and focus on the named ones.

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) - just L0
    • 3 (or 0b011)- L0 and L1
    • 5 (or 0b101) - L0 and LG
    • 7 (or 0b111)- 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#13 Configure trading limits for the cUSD/CELO exchange

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

TX#14 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#15 Configure trading limits for the cREAL/CELO exchange

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

TX#16 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 cUSD token address
    • config: the trading limit configuration

TX#17 Configure trading limits for the cEUR/USDC exchange

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

TX#18 Configure trading limits for the cBRL/USDC exchange

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

TX#19 Configure trading limits for the cEUR/EUROC exchange

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

6. Scale down Exchange reserve fractions

Since we are increasing the feasible trading volume for Mento V2 we are going to continue scaling down Mento V1. This is done by decreasing the reserve fractions on the exchange contracts. In MU03 we are going to half them for all three exchanges.

TX#20 Reduce the reserve fraction of the cUSD Exchange to half of its current value.

  • Verify ExchangeProxy address
  • Verify new reserve fraction is half of its current value.

TX#21 Reduce the reserve fraction of the cBRL ExchangeBRL to half of its current value.

  • Verify ExchangeBRLProxy address
  • Verify new reserve fraction is half of its current value.

TX#22 Reduce the reserve fraction of the cEUR ExchangeEUR to half of its current value.

  • Verify ExchangeEURProxy address
  • Verify new reserve fraction is half of its current value.

7. Configure BreakerBox

As part of MU03, we made some changes to the BreakerBox contract. These include changing the meaning of different trading modes, introducing configurable dependencies between rate feeds, and making the BreakerBox Non-upgradable.

With this update, each breaker can set rate feeds into one trading mode that is configured when calling addBreaker. These trading modes are interpreted by the BiPoolManager.

Trading mode 0 ⇒ Bidirectional trading

Trading mode 1 ⇒ Inflow only

Trading mode 2 ⇒ Outflow only

Trading mode 3 ⇒ Trading halted

TX#23 adding all rate feeds to the BreakerBox:

  • Verify that the BreakerBox address is correct
  • Verify that the rate feed identifiers are correct:
    • newRateFeedIDs an array of oracle feed ids
      • CELO/USD - cUSD token address
      • CELO/EUR - cEUR token address
      • CELO/BRL - cREAL token address
      • USDC/USD - address(uint160(keccak256(“USDCUSD”)))
      • USDC/EUR - address(uint160(keccak256(“USDCEUR”)))
      • USDC/BRL - address(uint160(keccak256(“USDCBRL”)))
      • EUROC/EUR - address(uint160(keccak256(“EUROCEUR”)))

TX#24 Add the MedianDeltaBreaker to the BreakerBox

  • Verify the BreakerBox address
  • Verify the MedianDeltaBreaker address
  • Verify trading mode is set to 3

TX#25 Add the ValueDeltaBreaker to the BreakerBox

  • Verify the BreakerBox address
  • Verify the ValueDeltaBreaker address
  • Verify trading mode is set to 3

TX#26 Set USDC/EUR rate feed dependency to USDC/USD

  • Verify the BreakerBox address
  • Verify the USDC/EUR rate feed identifier: address(uint160(keccak256(“USDCEUR”)))
  • Verify the USDC/USD rate feed identifier: address(uint160(keccak256(“USDCUSD”)))

TX#27 Set USDC/BRL rate feed dependency to USDC/USD

  • Verify the BreakerBox address
  • Verify the USDC/BRL rate feed identifier: address(uint160(keccak256(“USDCBRL”)))
  • Verify the USDC/USD rate feed identifier:address(uint160(keccak256(“USDCUSD”)))

TX#28 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
    • enable true

TX#29 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
    • enable true

TX#30 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
    • enable true

TX#31 Enable the MedianDeltaBreaker on the USDC/EUR rate feed

  • Verify the BreakerBox address
  • Verify the arguments:
    • breakerAddress should be the MedianDeltaBreaker
    • rateFeedID should be the identifier for the USDC/EUR rate, address(uint160(keccak256(“USDCEUR”)))
    • enable true

TX#32 Enable the MedianDeltaBreaker on the USDC/BRL rate feed

  • Verify the BreakerBox address
  • Verify the arguments:
    • breakerAddress should be the MedianDeltaBreaker
    • rateFeedID should be the identifier for the USDC/BRL rate, address(uint160(keccak256(“USDCBRL”)))
    • enable true

TX#33 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, address(uint160(keccak256(“USDCUSD”)))
    • enable true

TX#34 Enable the ValueDeltaBreaker on the EUROC/EUR rate feed

  • Verify the BreakerBox address
  • Verify the arguments:
    • breakerAddress should be the ValueDeltaBreaker
    • rateFeedID should be the identifier for the EUROC/EUR rate, address(uint160(keccak256(“EUROCEUR”)))
    • enable true

8. Configure the MedianDeltaBreaker

This Upgrade makes some improvements to the MedianDeltaBreaker. The new MedianDeltaBreaker calculates an exponential moving average (EMA) over previous medians that is compared to the current median. When the difference between the two values exceeds a defined threshold the MedianDeltaBreaker breaks. The smoothing factor is the weight of the current median in the calculation of the next EMA.

Smoothing factor = 1 :

$=> new EMA = current median * 1 + previous EMA * 0$

If not set a default smoothing factor of 1 is used, which simply translates to using the current median as the reference value.

TX#35 Set the cooldown times on the MedianDeltaBreaker

  • Verify the MedianDeltaBreaker 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/BRL: the cBRL token address
      • USDC/EUR: address(uint160(keccak256(“USDCEUR”)))
      • USDC/BRL: address(uint160(keccak256(“USDCBRL”)))
    • cooldownTimes: the array of cooldown times to be configured, the indices will match with the rateFeedIDs array. The values are seconds.
      • CELO/USD: 30 minutes = 1800s
      • CELO/EUR: 30 minutes = 1800s
      • CELO/BRL: 30 minutes = 1800s
      • USDC/EUR: 15 minutes = 900s
      • USDC/BRL: 15 minutes = 900s

TX#36 Set the rate change thresholds on the MedianDeltaBreaker

  • Verify the MedianDeltaBreaker 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/BRL: the cBRL token address
      • USDC/EUR: address(uint160(keccak256(“USDCEUR”)))
      • USDC/BRL: address(uint160(keccak256(“USDCBRL”)))
    • rateChangeThresholds: The array of rate change thresholds to be configured, the indices will match with the rateFeedIDs array. The values are fixed-point numbers – a number with 24 decimals, i.e. 1 is written as 1e24.
      • CELO/USD: 3e22 = 3%
      • CELO/EUR: 3e22 = 3%
      • CELO/BRL: 3e22 = 3%
      • USDC/EUR: 2e22 = ****2%
      • USDC/BRL: 25e21 = ****2.5%

TX#37 Set the smoothing factor on the MedianDeltaBreaker for the USDC/EUR rate feed. The smoothing factor values are fixed-point numbers – a number with 24 decimals, i.e. 1 is written as 1e24.

  • Verify the MedianDeltaBreaker address
  • Verify the parameters:
    • rateFeedID: USDC/EUR: address(uint160(keccak256(“USDCEUR”)))
    • newSmoothingFactor: 5e20 = 0.05%

TX#38 Set the smoothing factor on the MedianDeltaBreaker for the USDC/BRL rate feed.

  • Verify the MedianDeltaBreaker address
  • Verify the parameters:
    • rateFeedID: USDC/BRL: address(uint160(keccak256(“USDCBRL”)))
    • newSmoothingFactor: 5e20 = 0.05%

9. Configure the ValueDeltaBreaker

ValueDeltaBreaker: verifies that a new median is within a percentage threshold of a configurable reference value. Since the ValueDeltaBreaker hasn’t changed in this update and it is already configured for the USDC/USD rate feed we only need to add the configuration for the new EUROC/EUR rate feed.

TX#39 Set the EUROC/EUR reference value for the ValueDeltaBreaker

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

TX#40 Set the EUROC/EUR cooldown time for the ValueDeltaBreaker

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

TX#41 Set the EUROC/EUR rate change percentage threshold for the ValueDeltaBreaker

  • Verify the address
  • Verify the parameters:
    • rateFeedIDs: the array of rate feeds to configure, should contain the rate feed identifiers for:
      • EUROC/EUR: address(uint160(keccak256(“EUROCEUR”)))
    • rateChangeThresholds: the array of rate change thresholds to be configured, the indices will match with the rateFeedIDs array. The values are fixed point numbers – a number with 24 decimals, i.e. 1 is written as 1e24.
      • EUROC/EUR: 5e21 = 0.5%

9. Whitelist Diwu as an oracle provider for EUROCEUR

We’ve executed a preparatory CGP centered around Oracles previously but didn’t get one of the 3rd party oracles provided aligned in time – details here: https://github.com/celo-org/governance/blob/main/CGPs/cgp-0094.md#di-wu – so we’re also whitelisting them as an oracle provider for EUROCEUR as part of this CGP.

TX#42 Whitelist Diwu as an oracle provider for EUROCEUR:

  • Verify SortedOracles proxy address
  • Verify the parameters:
    • rateFeedId: EUROC/EUR: address(uint160(keccak256(“EUROCEUR”)))
    • oracle address confirmed by Diwu: 0xBD136a625299A0ac5Ca7Ce9220aCA6e08a624e37

Appendix A: Verifying an address

Verifying that an address in the CGP is correct requires a few different strategies depending on what that address is. Here are common situations:

5 Likes

Hey all, @marek identified an issue with the verification steps. There are a few transactions out of order in the guide, compared to the actual proposal. I can’t edit the post anymore so I will add the verification steps for TX31 until TX34 here. Also, I’ve added this section in our docs to help verify rate feed IDs and exchange IDs.

Replacement guide follows:

TX#31 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, address(uint160(keccak256(“USDCUSD”)))
    • enable true

TX#32 Enable the MedianDeltaBreaker on the USDC/EUR rate feed

  • Verify the BreakerBox address
  • Verify the arguments:
    • breakerAddress should be the MedianDeltaBreaker
    • rateFeedID should be the identifier for the USDC/EUR rate, address(uint160(keccak256(“USDCEUR”)))
    • enable true

TX#33 Enable the MedianDeltaBreaker on the USDC/BRL rate feed

  • Verify the BreakerBox address
  • Verify the arguments:
    • breakerAddress should be the MedianDeltaBreaker
    • rateFeedID should be the identifier for the USDC/BRL rate, address(uint160(keccak256(“USDCBRL”)))
    • enable true

TX#34 Enable the ValueDeltaBreaker on the EUROC/EUR rate feed

  • Verify the BreakerBox address
  • Verify the arguments:
    • breakerAddress should be the ValueDeltaBreaker
    • rateFeedID should be the identifier for the EUROC/EUR rate, address(uint160(keccak256(“EUROCEUR”)))
    • enable true
1 Like

Just a note here. In general, changing formula of a pool can be pretty backwards incompatible and can cause issues with other services.

A better approach would have been to create a new “ConstantSumPricingV2Module” and use that as a new one, so no old code/assumptions would break.

Something to keep in mind for potential future improvements.

I see what you mean but in this case, we would have needed to update the pricing module anyway as this new one is actually better suited for all pool calculations. Can we help with updating the router logic?

Deprecating old one is fine. Just changing logic in-place is generally very non-backwards compatible and can have a lot of unintended consequences for other applications. (Imagine any application that was calculating Mento v2 prices off line).

If you had created ConstantSumPricingV2Module, you could create new pools with this pricing module (or update pricing module name for all old pools). This way, old apps would just ignore this new pricing module and all new pools with it because they wouldn’t know how to handle it.

Ignoring and not handling new stuff is much better than doing incorrect calculations due to mismatch between what is actually in smart contracts vs what the app assumes is happening inside smart contracts.