You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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).
The text was updated successfully, but these errors were encountered:
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!
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 circulationshortSupply
: number of scfdETH in circulationlongPoolSize
: the amount of $C on the long side of the poolshortPoolSize
: the mount of $C on the short side of the poolprice
: current oracle-determined ETH pricelongRedeemPrice
: price at which you can acquire/redeem cfdETHshortRedeemPrice
: price at which you can acquire/redeem scfdETHThan, when the pools are balanced we want to preserve the following invariants:
longPoolSize == shortPoolSize
longRedeemPrice == price
longPoolSize == longSupply * longRedeemPrice
shortPoolSize == shortSupply * shortRedeemPrice
To do that when the pool is perfectly balanced (protocol neither long nor short):
When the price goes up by X:
longSupply * X
out of the short side and into the long side (this guaranteeslongPoolSize == longSupply * longRedeemPrice == longSupply * price
).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?).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 soshortRedeemPrice
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 guaranteesshortPoolSize == shortSupply * shortRedeemPrice
).longSupply * X
, since one's side gains are another side's loss, andlongRedeemPrice
is anchored to the asset price, unlikeshortRedeemPrice
.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?).shortRedeemPrice
is similarly dtermine so thatshortPoolSize == 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 thelongPoolSize
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).
The text was updated successfully, but these errors were encountered: