curvefi / twocrypto-ng

Other
10 stars 6 forks source link

Cryptoswap Third Gen #10

Open bout3fiddy opened 6 months ago

bout3fiddy commented 6 months ago

Background

TricryptoNGWETH is the first optimised implementation of the old 3-coin cryptoswap AMM, allowing native token transfers. Its immediate upgrade (almost a year after its launch) removes some of the features and adds new ones.

The different implementations of Curve's CryptoSwap invariant AMM are noted in the following:

  1. The genesis cryptoswap invariant amm contracts: a. tricrypto2 (genesis) b. twocrypto (genesis)
  2. TricryptoNGWETH (1st gen)
  3. TwocryptoNG (second gen)
  4. TricryptoNG (second gen)

There are significant improvements from the genesis cryptoswap invariant AMM contract to the 1st gen (NG, or next-gen). Gas costs are reduced by half between the genesis and the first gen implementations. This was a labour of love, requiring development work in the compiler, dev tools (titanoboa was built to build tricryptong), coordination with etherscan, coordination with math researchers to optimise math in tricrypto, come up with better cube roots, auditors coming up with their optimisations etc. The optimisations are listed in the following:

  1. Replace Bubble sort with dumb sorting
  2. Bespoke cube root algorithm that costs 2000 gas on average. Implemented in Snekmate as well.
  3. Replace expensive geometric mean with simple geometric mean
  4. Use unsafe math operations wherever it is safe to do so. Old implementation -> new implementation with explanation for why we can do unsafe maths
  5. Replace newton_y for mathematically verified analytical solution with fallback to newton method for edge cases.
  6. Bespoke and very cheap calculation of partial derivatives of x w.r.t y, which allows the calculation of state prices. This was a contribution from Taynan Richards of ChainSecurity, which replaced the old and initially proposed version.
  7. Introduce Blueprint contracts!. This allowed factory deployed contracts to have immutables, since blueprint contracts are not like minimal proxies where immutables are not possible whatsoever. The verification pipeline of blueprint-deployed contracts still needs to be fixed. Still, when it was first deployed, it was only possible to verify the contract when Etherscan added features to read the first few bytes of the bytecode and classify the contract as a blueprint.

The implementation of state prices allowed the creation of very good oracles that power today's curve stablecoin. State prices, as opposed to last traded prices (the widely prevalent industry standard), significantly reduce the impact of price manipulation.

Cryptoswap (like everything Curve has) is an ongoing process of improvement. The features added in the second iteration of NG are simply features that come out of a natural progression of improving contracts after user feedback, experiences with speaking to auditors, etc. No known vulnerabilities in the first NG implementation prompted the second iteration of cryptoswap NG contracts.

Some of the features removed from the first gen (in the second-gen) are (not an exhaustive list):

  1. Native token transfers
  2. Gulping of tokens (i.e. when the self.balances can be updated with a read of coin.balanceOf(self)).
  3. exchange_extended, which is exchanging after calling an external callback first).
  4. Admin fees collected in LP tokens.
  5. exposed claim_admin_fees
  6. commit-apply scheme for parameters. New version applies parameters simply which is quicker to do (in one tx after governance approves).

The second-gen adds several new features including:

  1. An xcp oracle to measure the amount of liquidity in the pool
  2. fees are collected in individual tokens and not lp tokens.
  3. Claiming individual tokens means LP token supply does not go up
  4. stricter conditions to claiming fees a. claim sparingly b. do not claim if virtual price goes below 1e18
  5. exchange_received: swap tokens donated to the pool.The advantage of the new implementation is that if tokens in the pool are rebasing, there is no self.balances[i] = coins[i].balanceOf(self) in the self._claim_admin_fees() method like the old contract does

With that in mind, there are new features and more improvements that Cryptoswap contracts could accommodate.

Improvements Suggested

1. Rebasing token support

The second-gen cryptoswap NG contracts do not gulp rebases: if the amm contract holds rebasing tokens, the rebases are not absorbed by the pool contract and are simply available as: rebase_balance[i] = coins[i].balanceOf(self) - self.balances[i]. These could be recovered by the LPs somehow and added to their LP token share. This is something to consider: can we add support for pairs like stETH <> USDM where both tokens in the pool are rebasing and are very tightly pegged to their underlying collateral's value (at least historically as of writing this feature)?

2. Boost

Cryptoswap invariant AMMs take half the earned profits to rebalance liquidity closer to the exponential moving average prices. This means, if the pool had some form of capability to accept donations to inflate its profits, it could absorb these donated profits to rebalance and deepen liquidity significantly. This is even more effective in pools where the tokens do not have significant volatility but are not volatile pairs (e.g., USD <> EUR forex markets, which are generally very deep).

The ability to donate was discontinued in the second generation since the removal of the gulp.

The idea would be to a. Donate to the pool contract safely and not manipulate prices in any direction b. call tweak_price c. Donated profits should not go into user positions, and only be used for amm rebalancing.

This is quite tricky to achieve but increases flexibility and utility of these AMM contracts.

Additional considerations

  1. Admin fees can be made to be dynamic such that the dao only earns fees when volatility is high and earn nothing when volatility is low: this can be parametrised in such a way that the percentage of admin fees depends on the distance of fee from the lowest fee to the highest fee.
  2. rebalances can only come from donated funds for boosts, and optionally from a part of earned revenue by the pool: this can be parametrised in a way such that the amount of earned revenue that can be spent by the pool for rebalancing be parametrised. normally take 0% of earn fees such that LPs earn everything. and let the dao set which percentage should go for rebalancing.

Security considerations

Task List

Nice to have

AlbertoCentonze commented 6 months ago

My rationale for not reintroducing rebase support is:

Very excited about the boost, a couple of questions:

bout3fiddy commented 6 months ago
Implementing such a functionality would encourage developers to go for this approach more. It's not just about Curve, rebases are a big foot gun in the industry and I think that it is dangerous to "endorse" them.

Curve has been a long standing supporter of rebasing markets and it's a pedigree of ours to have rebasing token support in our AMMs. But: I don't disagree. I think rebasing tokens are a small market, and we could revisit them in the future if security around the composability of rebasing tokens improves.

I'm not sure boost is a good name given that we already have a boost concept for the gauges. What about rebalancing donations?

Naming rights go to whoever writes the algorithm. We can discuss re-iterate internally.

Is it more effective to fully subsidize the rebalancing cost so that LPs earn more fees or should we add these rebalancing donations on top of them?

I think we should parameterise them: so a parameter that decides how much of earned revenue from swaps goes into the 'rebalancing funds' which is then used to rebalance?