FEATURE: Impermanent Loss Protection
Overview
Impermanent Loss Protection ensures LPs always either make a profit, or leave with at break even after a minimum period of time (set at 100 days), and partially covered before that point. This should alleviate most of the concerns regarding become an LP.
THORChain tracks a member's deposit values. When the member goes to redeem, their loss (against their original deposit value) is calculated, and is subsidised with RUNE from the reserve.
There is a 100 day linear increase in the amount of coverage receives, such that at 50 days, the member receives 50%, 90 days is 90% etc.
Implementation
Firstly, ensure the following are tracked per member:
liquidityUnits // units to track pool ownership
heightLastAdded // block height member last made a deposit
assetDepositValue // the deposit value of the asset received
runeDepositValue // the deposit value of the rune received
Deposit Values
Deposit values are not the amounts the member deposited. They are the immediate symmetrical value of what the member deposited instead, taken at the price of the pool when member deposited. This is to ensure asymmetrical deposits can be tracked.
Using the GetShare(part, total, allocation)
function:
liquidityUnits = units issued to member
assetDepositValue = GetShare(liquidityUnits, poolUnits, assetDepth);
runeDepositValue = GetShare(liquidityUnits, poolUnits, runeDepth);
*This scenario assumes first time member, for subsequent deposits, see "Additional Deposits" below
*Depths are used after the member adds.
Coverage
Coverage for the member is calculated by the following:
A0 = assetDepositValue; R0 = runeDepositValue;
liquidityUnits = units the member wishes to redeem after applying withdrawBasisPoints
A1 = GetShare(liquidityUnits, poolUnits, assetDepth);
R1 = GetShare(liquidityUnits, poolUnits, runeDepth);
P1 = R1/A1
coverage = (R0 - R1) + (A0 - A1) * P1
Protection
The final protection is a function of the daily linear metering. It specifies the number of blocks for 100%.
blocksForFullProtection = 1728000 // 100 days
should be set in constants.go
with mimir controls.
protectionProgress = min(((currentHeight - heightLastAdded) * 1e8 / blocksForFullProtection), 1e8)
protection = (protectionProgress * coverage) / 1e8
The protection amount can be added to the members' RUNE payout.
Single-side Liquidity Withdrawals
If the member is withdrawing to the asset (and not either RUNE or symmetrically), then the protection amount is converted in price to the asset, and the member is paid out in extra asset, then the protection amount in RUNE is added into the pool balance:
assetProtection = GetPriceInAsset(protection)
pool.runeDepth += protection
Partial Withdrawals
If a member does a partial withdrawal, deduct the equivalent in their deposit values.
assetDepositValue -= GetShare(basisPoints, 10000, assetDepositValue)
runeDepositValue -= GetShare(basisPoints, 10000, runeDepositValue)
Additional Deposits
If a member makes additional deposits, then calculate and add the new deposit values, and then reset the height.
liquidityUnits = additional units issued to member from this deposit event
assetDepositValue += GetShare(liquidityUnits, poolUnits, assetDepth);
runeDepositValue += GetShare(liquidityUnits, poolUnits, runeDepth);
heightLastAdded = currentHeight