Open norswap opened 2 years ago
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 ?
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:
We move
longSupply * X
out of the short side and into the long side (this guaranteeslongPoolSize == 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 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
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
.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.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).