Skip to content

Simplify Pool Balancing #3

@norswap

Description

@norswap

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions