The cream.finance team released their new AMM creamY today;
Meme, and name aside (not a fan), I wanted to go through the code and do a technical writeup, first thing to note, this is a governed liquidity pool.
Unlike a Uniswap or Curve pool, which are immutable, this liquidity pool allows the admin/governance/lp’s to dynamically add/remove trading pairs.
This is especially important in todays landscape, if we just look at DAI, we have cDAI (Compound), aDAI (Aave), crDAI (cream.finance), dDAI (dforce), iDAI (idle.finance), yDAI (yearn.finance), yvDAI (yearn.finance), and this is just looking at DAI, now add USDC, TUSD, USDT, BUSD, DUSD, and their variants. Now imagine you needed to deploy DAI:cDAI, DAI:aDAI, DAI:crDAI etc, what happens with DAI? Each pool, only receives a bit of DAI. So instead of having had a full orderbook between DAI:cDAI:aDAI:crDAI, you end up with fragmented liquidity.
This however comes with a trade off, it is managed and updateable. However, from the code, governance/admin only have rights to add a new asset (would recommend this being behind a timelock), and stop the trading of an already listed asset (so that it can be withdrawn, and used as an emergency shutdown).
So going into the code;
As mentioned, governance/admin have a few management functions, most importantly approveCoins and setPause (as described above).
Calculating the price of one asset vs another is the same calculation as from blackholeswap.
In/Out are straight forward enough, but what is interesting here is the normalize interface. Instead of this contract knowing the normalized value of a token, for example 1 DAI =/= 1 cDAI, each cDAI has an exchange rate, measured in getExchangeRateMantissa(), and 1 cDAI * exchange_rate = 1 DAI. But that would mean this contract would have to know every possible permutation, for example crv tokens use get_virtual_price, y tokens use getPricePerFullShare, etc. The normalize interface allows a token to implement their own normalizer which agnostically allows new assets to be added in the future.
This implementation I really like, this allows single sided liquidity. By assuming that the pool itself is a one of the trading assets, the user “trades in” when they add liquidity, and they “trade out” LP shares. The pool considers itself as one of the assets. This also means, you no longer need to provide equal shares off all assets, and further means you don’t suffer slippage from not having the other assets. You will still suffer slippage if you provide more of an already imbalanced asset however.
This is a mixture of a shared order book, with consolidated liquidity, and a few key innovations that make it stand out. I think there are still a few things to be tested and I recommend caution as this uses quite a few new introduced concepts, but this design can alleviate a lot of the current liquidity pain-points.