Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify Pool Balancing #3

Open
norswap opened this issue Mar 24, 2022 · 1 comment
Open

Simplify Pool Balancing #3

norswap opened this issue Mar 24, 2022 · 1 comment

Comments

@norswap
Copy link
Contributor

norswap commented Mar 24, 2022

Hey there! Great work on the repo!

When looking at the code, I spotted that the pool balancing logic is much more complex than it needs to be.

Currently, you manually adjust every single position. If there are a lot of users, this will cost a ton of gas.

In reality, it suffices to transfer tokens from one side of the pool to the other, and to adjust the protocol's position.

Assume we're doing an ETH synth with long token cfdETH, short token scfdETH and our stablecoin si $C. If we define the following variables:

  • longSupply: number of cfdETH in circulation
  • shortSupply: number of scfdETH in circulation
  • longPoolSize: the amount of $C on the long side of the pool
  • shortPoolSize: the mount of $C on the short side of the pool
  • price: current oracle-determined ETH price
  • longRedeemPrice: price at which you can acquire/redeem cfdETH
  • shortRedeemPrice: price at which you can acquire/redeem scfdETH
    • for both of these redemption prices, we ignore the imbalance bonus given by the protocol

Than, when the pools are balanced we want to preserve the following invariants:

  • longPoolSize == shortPoolSize
  • longRedeemPrice == price
  • longPoolSize == longSupply * longRedeemPrice
  • shortPoolSize == shortSupply * shortRedeemPrice
  • the protocol cannot be long and short at the same time

To do that when the pool is perfectly balanced (protocol neither long nor short):

  • When the price goes up by X:

    • We move longSupply * X out of the short side and into the long side (this guarantees longPoolSize == longSupply * longRedeemPrice == longSupply * price).
    • If the protocol has a long position, he cashes out of part of his cfdETH (at the new price) until longPoolSize == shortPoolSize. If he doesn't have enough cfdETH for that, he simply cashes out of all his cfdETH. The obtained $C is burned (maybe a fee is taken?).
    • If the two sides are still not balanced, the protocol mints $C and purchases as much scfdETH as is needed (at the new price) so that longPoolSize == shortPoolSize.
      • The new scfdETH price is such that shortPoolSize == shortSupply * shortRedeemPrice.

        For example: If the token price gains 50% (100 → 150), then shortPoolSize halves, and so shortRedeemPrice must halve too (e.g. 100 → 50, though the starting price could be different from the long starting price!).

        Since the long side is now twice as big as the short side, the protocol will need to buy scfdETH at that new price (50) and will now own 2/3 of the short side (if he didn't own any of the short side beforehand).

        In general the idea is that cfdETH/scfdETH gives a right to a proportional part of the underlying pool (which determines their redeem price). However since we want to keep the pool sizes constant, we have to dilute the supply (through the protocol buying into the pool) to reduce the token redeem value.

        There is an important edge case here when the price goes up more than 100%, which would empty the pool and make the scfdETH price 0. There are multiple mitigations and checks we can use around that but I won't enter into them here.

  • When the price goes down by X

    • We move longSupply * X out of the long side and into the short side (this guarantees shortPoolSize == shortSupply * shortRedeemPrice).

      • Yes, we use longSupply * X, since one's side gains are another side's loss, and longRedeemPrice is anchored to the asset price, unlike shortRedeemPrice.
    • If the protocol has a short position, he cashes out of part of his scfdETH (at the new price) until longPoolSize == shortPoolSize. If he doesn't have enough cfdETH for that, he simply cashes out of all his scfdETH. The obtained $C is burned (maybe a fee is taken?).

      • The new shortRedeemPrice is similarly dtermine so that shortPoolSize == shortSupply * shortRedeemPrice. Since the short pool size just increases, the short redeem price does too.
    • If the two sides are still not balanced, the protocol mints $C and purchases as much cfdETH as is needed (at the new ETH price) so that longPoolSize == shortPoolSize.

      This is much simpler. If the token price halves (100 → 50), then both the longRedeemPrice and the longPoolSize halves and the protocol purchase cfdETH at this new price and will now own 2/3 of the long side.

That's the gist of it!

Of course there are other scenarios, mainly when users purchase cfdETH / scfdETH or redeem. Again, the procedure to follow is dictated by preservation of the invariants above!

I'm wondering if we can't write a single pool rebalancing function. We'd perform the transfer of interest (purchase, redemption, or side transfer after a price change) then call this function. It would lookup the current ETH price and rebalance the pool in order to satisfy the invariants.

One open question I have is whether we want to represent the protocol stake in each pool as an explicit variable (a bit more effective, probably more gas efficient too) or the balance of a "normal" address (the contract's address).

@2xic
Copy link
Owner

2xic commented Mar 24, 2022

Thanks for taking a look, and for another well formulated + in depth write-up!

The design you describe does sound like a more efficient design both in terms of the code structure, and the gas usage!

I will refactor the code to use this new design over the coming days. If you want to, I can mention this issue when the code has been refactored to the new design, and then you can take a new look!

Sounds good ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants