Discussion: CGP 61 (on-chain 68) - Governance owned cUSD-USDC Liquidity

Governance owned cUSD-USDC Liquidity

Introduction

As an interim solution to offer cUSD-USDC liquidity short term, before GigaMento (see GigaMento Forum Discussion), we suggest deploying a constant sum market maker (“CSMM”) StableSwap Pool via Mobius, in which the reserve will provide liquidity - namely 5M cUSD and 5M USDC. This allows a similar experience that GigaMento would offer, that is large cUSD<->USDC trades with minimal fees and slippage, but requires no new code to be deployed, making it viable as an immediate solution. It effectively leads to protocol-owned cUSD-USDC liquidity in Mobius that can be withdrawn and returned to the reserve via governance at any time. The differences between a StableSwap pool and GigaMento can be thought of in terms of where the liquidity comes from:

*depending on how the pool is utilized, governance might add more liquidity in the future via additional governance proposals until GigaMento comes into effect, see Rebalancing.

Deployment

StableSwap Pool

The StableSwap pool (see contract) will be deployed and managed by Mobius. They will use these parameters:

CGP Mechanics

When executed, the CGP should:

  • Mint cUSD
  • Deposit cUSD + USDC (Wormhole) to the pool

Minting cUSD to Governance

Currently only Validators, GrandaMento, and the Mento Exchange contracts can mint stable tokens. To get around this we propose temporarily (that is for one transaction, the CGP execution) deploying an altered version of the StableToken.sol contract, namely StableTokenMintableByOwner. The only difference is that it adds msg.sender == owner() as a passing condition in the mint and burn functions, which allows Governance to mint cUSD.

This new implementation would only be in effect in the transaction that executes the CGP. The StableToken proxy will switch to this new implementation and then switch back, making the minting part of the CGP look like this:

Depositing cUSD and USDC to the pool

In order to deposit the tokens in the pool, Governance needs to hold the bridged USDC when the governance proposal is executed. This will be covered in more detail in the Deployment Timing section.

For the addLiquidity function call on the pool to succeed, the pool needs the approval to transfer cUSD and USDC from Governance to itself, thus requiring two ERC20 approve calls beforehand - making the process look like this:

For the more technically savvy, here’s an example of a transaction simulated on top of a mainnet fork which adds cUSD and USDC via a CGP to the existing cUSD-USDC Mobius pool.

Deployment Timing

Phase I - Prepare

This phase consists of:

  1. Deploy the StableTokenMintableByOwner implementation (responsibility of Mento team)
  2. Deploying the GnosisSafe multisig (responsibility of Mento team)
  3. Deploying the StableSwap Pool (responsibility of Mobius team)
  4. Submitting the CGP (responsibility of Mento team)
StableTokenMintableByOwner

This contract is a slight modification of the StableToken contract which allows the owner to mint/burn tokens. During the CGP execution, the StableToken proxy will temporarily point to this implementation (see Minting cUSD to Governance), so it needs to be deployed before the CGP is submitted.

Multisig

This setup involves three types of tokens:

  • cUSD
  • USDC (Wormhole)
  • LPTokens from the StableSwap pool.

Our aim is for Governance to hold these tokens for most steps throughout the process, but due to limitations in the setup, a 4/6 Multisig (GnosisSafe) on Ethereum will be used as an intermediary. It will have signers from Mento and cLabs:

The Multisig is used to orchestrate the bridging of USDC from the reserve in Phase II via Wormhole, more details in the Phase II - Bridging section.

Submitting the CGP

Because the CGP needs to execute the process defined in CGP Mechanics atomically and there’s a time window between when the transactions are crafted and when the transaction executes and great care needs to be taken when picking values for the variables. This will be covered in the CGP Variables section.

Phase II - Bridge

This phase begins after the CGP is in the Execution phase, which means that the proposal has passed and USDC can now be bridged from the Reserve Custodian on Ethereum to the Governance contract on Celo, via the Wormhole/Portal bridge.

In an ideal world, this could be done in a fully permissionless manner, the Custodian bridging directly to the Governance contract. But the Custodian only supports simple transfers of funds, not arbitrary transactions therefore an intermediate Multisig on Ethereum is necessary. It will receive a transfer from the Reserve Custodian and bridge the tokens to the Governance contract.

The process to bridge will look like the following:

  1. Transfer from USDC from Custodian to the 4/6 GnosisSafe Multisig explained above
  2. Propose USD.approve(BRIDGE_ADDRESS, USDC_AMOUNT) from the GnosisSafe
  3. MultiSig owners confirm and execute
  4. Propose depositing USDC in the bridge from GnosisSafe
  5. MultiSig owners confirm and execute
  6. Redeem bridged USDC on Celo, which transfers them to Governance

The Portal UI has a limitation: you can’t specify the recipient when bridging tokens from Ethereum to Celo, it must be the same address as the sender, but they provide a Javascript SDK that can be used to orchestrate the transfer. These two scripts will be used for steps (2) and (4) above here:

If the multisig signers are available it should take no more than 10 minutes to bridge the USDC to Governance.

Phase III - Execute

At this point USDC should be bridged and sitting with Governance, therefore the CGP can now execute the steps outlined above:

  1. Mint cUSD to itself
  2. Deposit cUSD and USDC into the Stableswap Pool

Because this is done by the Governance contract the LP tokens and thus custody of the liquidity will remain with Governance.

CGP Variables

Make governance able to mint:
{stableTokenProxy}._setImplementation({stableTokenMintableByOwner})

Mint cUSD to Governance:
{stableTokenProxy}.mint({governanceProxy}, {cUSD_amount})

Make governance unable to mint:
{stableTokenProxy}._setImplementation({stableToken})

Approve cUSD transfers:
{stableTokenProxy}.approve({pool}, {cUSD_amount})

Approve USDC transfers:
{USDC_wormhole}.approve({pool}, {USDC_amount})

Deposit cUSD and USDC:
{pool}.addLiquidity**({amounts}**, {minLPTokensToMint}, {deadline})

Because the CGP is submitted prior to the bridging of USDC, the USDC_amount needs to be well defined beforehand taking into account all fees involved in bridging the assets, so that Governance’s balance ends up greater or equal to {USDC_amount} otherwise the execution will fail.
The other point of failure is the deadline but it can set that to a timestamp that’s after the CGP expiration.

Rebalancing

The pool will start with 5M each of USDC and cUSD, but depending on the volume of inflows and outflows the pool might end up unbalanced on either side which will require rebalancing. There are four possible operations to consider:

  • Adding cUSD
  • Adding USDC
  • Removing cUSD
  • Removing USDC

Adding cUSD

If the pool gets to be mostly USDC, governance would potentially want to top up the cUSD bucket. This would require another CGP without the bridging phase which only adds cUSD liquidity to the pool. The mechanics are similar, but the Bridge phase can be skipped and there is no need to approve USDC transfers in the CGP.

Adding USDC

If the pool gets to be mostly cUSD, new CGPs can be submitted to add additional USDC. This new CGP would be similar to the Deployment one, but without the cUSD minting part:

Removing cUSD/USDC

Removing cUSD/USDC from the pool while it’s live can get tricky because the transactions to burn cUSD/transfer USDC from governance need to know how much liquidity is removed before it’s executed. For example say the pool gets to be 3M cUSD + 7M USDC, and the CGP is crafted to withdraw 5M USDC and transfer it out, but by the time the CGP executes the pool is 5.5M cUSD + 4.5M USDC, the CGP would fail.

There are three options that can be revisited depending on the evolution of the pool:

  • Issue CGPs with small amounts betting that pool wouldn’t rebalance dramatically
  • Do a two-tier process, one CGP to withdraw liquidity and one to burn/transfer once the exact amount is known.

Rely only on Unwinding, given that this is a temporary measure

Unwinding

When the GigaMento MVP gets deployed, governance will need to unwind this pool. That entails:

  1. Pausing the pool so no more swaps are possible (Mobius team will have to do it)
  2. Remove liquidity (cUSD + USDC) from the pool
  3. Burn cUSD
  4. Transfer USDC to the GigaMento Reserve contract (or back through the bridge to the Reserve Custodian)

The pool needs to be paused before submitting the CGP because then all liquidity can be withdrawn and handled easily in a single CGP.
If for whatever reason pausing the pool is problematic, two CGPs can be issued. The first is just to remove liquidity, and the second to burn/transfer with the known balances that result from the liquidity removal.

Risks

Bridge risks

Bridging 5M reserve USDC to Celo via Portal/Wormhole exposes the reserve to a 5M USD bridge risk. In the worst case, the 5M native USDC of the reserve could get lost due to a bridge hack, rendering the USDCet on Celo worthless. Since the suggested pool enforces basically a 1:1 exchange, this would likely lead to all cUSD getting drained from the pool such that the total loss for the reserve could reach 5M native USDC plus an additional debt of 5M cUSD.

Contract risk

The proposed solution relies on a Mobius liquidity pool. In case of a Mobius failure or attack the deposited amounts of cUSD and USDCet are at risk.

cUSD/USDC Price Risk

The setup of the liquidity pool is based on the assumption that the cUSD/USDC price is 1, to be precise that the cUSD/USDCet price is 1. Two market scenarios can have unwanted consequences:

  1. USDC/USD price deviation from 1,
  2. USDCet/USD price deviation from 1.

The cUSD/USDCet pool will offer cUSD (and USDCet) at an incorrect price that is either higher (cUSD/USDCet < 1) or lower (cUSD/USDCet > 1) than the market price. The availability of expensive/cheap liquidity can have a price impact that is undesirable for cUSD as it is purely caused by a USDCet price deviation.

5 Likes

If you are setting _a to 2000 and turning this effectively into CSMM, why not just put it into Uni3 pool instead?

You also have stronger governance control over the exchange ratio as well by defining explicit ranges.

Also clab is the one that deployed Uni3 and has more control over Uni3 correct?
You can enforce stronger security on it since you own the Uni3 deployment.

Also, fee charged to user is 2bps, but 50% goes to Mobius, so actual fee to LP is 1bps.
Why not go to Uni3 where protocol fee is 0bps, and just straight charge only 1bps fee to users?

2 Likes

In a way this pool serves as a 5m cUSD extension of cUSD Mento, in the sense that it makes cUSD contraction / minting available. cUSD will be contracted or minted whenever a user exchanges with the pool. One technical difference with Mento is that cUSD is pre-minted.
Allowing the total amount of cUSD to be ‘minted’ by users and injected into market is intentional. As is the inverse, the total amount of USDCet being bought and 5m cUSD being contracted. For that reason I think concentrated liquidity pools will not work.

Made a few edits:

Instead of turning this contract into a CSMM using the amplification factor, Mobius proposed using this contract which was used for the Optics V1:V2 migration. I’ve updated the post to reflect the changes.


LATER EDIT: Scratch the above, upon further reflection we decide to stick with the original proposal.

Lets say the concentrated LP range was set to [0.999, 1.0] then would it not be able to support the same type of trade? Maybe I’m missing something here.

I strongly support adding cUSD-USDC (Wormhole) liquidity to Mobius as a short-term solution!

Although, may I ask the reason for not using the existing cUSD-USDC Mobius pool ? Is the A factor considered too low or are there some security implications?

If possible, I’d prefer to consolidate rather than fragment liquidity. There’s already $0.7M of liquidity in the existing pool.

I believe in this case you could end up in a situation where the Uniswap LP ends up holding only one type of asset.

Let me illustrate: the rational thing for most actors would be to sell their cUSD for 0.999 USDCet in the Uniswap v3 pool. And then arbitrage on other DEXes or CEXes. Typically cUSD is trading at ~$0.995 on most of these other platforms. That means a $0.004 profit per $1, or a nice $20k if you take all 5M of liquidity in the Uni3 pool. Even taking into account trading fees, it is (currently) net profitable to do so.
In that case, governance would end up holding an LP position of 100% cUSD, defeating the entire purpose.

With the Curve / Mobius StableSwap design that isn’t possible. There you always have a mix of two assets representing your LP tokens (dual-sided liquidity), in the proportion that they are present in the pool. The price impact for traders will start to become a lot bigger, the more you deviate from $1 on any asset. Although I’m not sure to what extend this is still true for a very big A factor.

It could be I’m missing something as well.

1 Like

They were gonna make a = 2000 and make the formula work basically like constant sum swap, so it will be the same as Uni3 in that case. All of one token or the other depending on prices.

Whatever arbitrage that can be done on Mobius, can be done on Uni3 since both are basically saying cUSD : USDC are swappable effectively 1:1, it’s just why not Uni3 when clab owns Uni3 deployment, and Uni3 has a bigger brand name recognition for users coming from outside of Celo.

Ideally, if its Uni3 LP, it would be a small amount of the LP initially, at +/- n bps of the prevailing fair market price, and this LP would be adjusted and increased based on market price. If the whole amount was added to Mobius, or to Uni at the 1:1 price, you’re just gonna have a giant flash arb that drains the a big chunk of the LP in one transaction.

That wouldn’t be very useful to end users.

I agree that this will introduce an arbitrage opportunity but I believe this is a good thing. As long as USDC is pegged (= 1 USD), allowing arbs via 1:1 trades against USDC enforces the cUSD peg. This is what the stability mechanism aims to achieve in the first place and tightening the peg certainly benefits end users even if the pool becomes one-sided. How large these arbs are will depend on the price reaction in the other cUSD pools in response to the arb trading and with current liquidity levels of cUSD, not too much should be needed to move the needle from cUSD/USD~=0.995 close to 1.00.

1 Like

How I think about it: In a way this pool serves as a 5m cUSD extension of cUSD Mento, in the sense that it makes cUSD contraction / minting available via USDC. cUSD will be contracted or minted whenever a user exchanges with the pool. One technical difference with Mento is that cUSD is pre-minted. Allowing the total amount of cUSD to be ‘minted’ by users and injected into market is intentional. As is the inverse, the total amount of USDCet being bought and 5m cUSD being contracted.

For example take the current market cUSD/USD ~ 0.995. Arb traders will sell cUSD to the pool until prices align. Or all USDC is bought. But then it means there definitely is an over supply in the market. On the other hand the pool is now filled with 10m cUSD. We frequently hear of request for cUSD from partners who want to buy cUSD in a batch, but feel slippage in the market is too high because of low volumes. It will be a perfect opportunity for them to buy cUSD

From a ‘central bank’ / stability perspective arb traders are not removing the liquidity that gets injected via this pool. They are just ‘repricing’ it.

The loss is stability costs. And the same happens in Mento. The only difference is the auxiliary asset that is used. In the current Mento setup Celo works as a aux asset to trade cUSD/USD at a price of one (minus fees & slippage). In the cUSD/USDCet pool extension we are using USDCet as an aux asset and additionally assume cUSD/USDCet = cUSD/USD

Can you have governance give this to a multi sig you guys control so that if something weird happens, you can emergency pull LP out of the pool (whether mobius or Uni).

Some kind of emergency hatch that the mento team controls? 5M is a lot of money to not have active control over.

1 Like

In the event that emergency action is required, 67% of validators could sign a hot fix governance proposal to pull the LP out of the pool. That’s obviously a lot more signers than say a small multisig, but it at least gives the ecosystem a path towards moving faster than the usual governance proposal process would allow.

1 Like

Thank you all for your input! The governance proposal was just submitted (on-chain ID 68). The details of the final proposal can be found here: governance/cgp-0061.md at cusd-liquidity-cgp-0060 · celo-org/governance · GitHub

A new celocli release was required for this proposal - for a description, see Make governance tooling work with arbitrary transactions by bowd · Pull Request #9735 · celo-org/celo-monorepo · GitHub
Please upgrade to the 1.3.6-beta.1 release before reviewing the on-chain proposal:
npm install -g @celo/[email protected]

Make sure you submit your vote! And if you have any questions/concerns, please let me know!

I think proposal can make explicit that the Mobius team with their " 1. Pausing the pool so no more swaps are possible (Mobius team will have to do it)" escape hatch is something we can rely on in case of emergency. It seem easier than getting 67% of validators to sign a hot fix governance, and to pull LP, the Mobius team has to pause the pool anyways.

Also probably good idea to get the Mobius team’s phone numbers so if things go bad, can call them up and get the pool paused.