A single stake Position
can have arbitrary amounts of both parts TST and/or EUROs. When returning a stakes "value" with stake()
, it returns whichever token TST or EUROs has the smaller balance of tokens for the staker. The staking rewards
are determined by the stakers _portion
, which is just the split based on their personal stake amount versus the total staked amount.
Because the stake()
function returns the smaller balance between TST and EUROs for stakers, and has no relation to the USD amount staked, the rewards for stakers are never distributed fairly based on how much USD the user staked.
Generally speaking, because TST ($0.005?
) is much cheaper than EUROs ($1
), most stakes will have more TST than EUROs. This can create massive underpayment for people who afk stake TST, if just a single or few users stake some EUROs. This incentivizes people to stake EUROs, but heavily penalizes people who just want to stake TST.
There is an infinite cycle of arbitragers attempting to squeeze out uneven rewards based on raw USD staked. If they fail to any degree then the average user who is staking "unoptimally" will always get the short end of the stick. Consider if the benefits of an infinite arbitrage race outweighs the alternative of having exact USD amounts rewarded exactly proportionally. The arbitrage is completely unnecessary unless it's somehow tying into other aspects or incentives of the protocol. When a user interacts with a staking mechanism, the default assumption is that their USD value of their stake gets rewarded exactly proportionally to all the other stakes.
Imagine there are 101 Position
's that stakers have. 100 of them have 1M TST and 1 EUROs, and one has 101 TST and 100 EUROs. A rough case like this happens if you have a lot of lazy TST whale stakers who accumulate EUROs in their position only from distributeFees()
rewards, versus someone who manually decides to stake 100 EUROs.
Liquidation happens and LiquidationPool::distributeAssets()
is called. This starts a loop to determine every holder's split of the profits from the liquidation, for each collateral asset that was in the vault. Calculation of reward splits: uint256 _portion = asset.amount * _positionStake / stakeTotal;
Rewards from 1M TST and 1 EUROs stake: LiquidationPool::getStakeTotal()
is called which returns the sum of all the EUROs token amounts for every position because they are always the smaller amount, so it returns 100 + 100 = 200e18 EUROs. Then LiquidationPool::stake()
is called, and returns the user's 1e18 token of EUROs as their stake. This user's cut (_portion
) is 1/200 or 0.5%.
Rewards from 101 TST and 100 EUROs stake: LiquidationPool::getStakeTotal()
is called, returns 200e18 EUROs. Then LiquidationPool:stake()
is called, and returns the user's 100e18 EUROs as their stake. This user's cut is 100/200 or 50%.
Note: The asset.amount
is just an irrelevant magnitude because it's the same in both cases. Assume 10 LINK
is the total awards asset.amount
being distributed as the current collateral asset to stakers).
The result of rewards
incremented by _portion
is drastically disproportionate to how much was actually staked.
The 1M TST and 1 EUROs staker ($5,001
): Rewards are 10 LINK * 0.005 = 0.05 LINK
($1
)
The 101 TST and 100 EUROs ($100.5
): Rewards are 10 LINK * 0.5 = 5 LINK
($100
).
A $5,000
position is getting about $1
of rewards, whereas a $100
position is getting $100
of rewards, this is disproportionate by 5,000x.
Extremely disproportionate rewards for people prefer to only afk stake TST instead of EUROs.
Infinite arbitrage that always gives afk or suboptimal stakers the short end of the stick unless perfectly arbitraged, compared to alternative default of perfectly balanced rewards based on USD values staked.
Manual Review
Consider distributing rewards based on the USD value of the position. (Changing LiquidationPool::stake()
to return position's USD value?)
A mechanism that doesn't punish staking one coin instead of the other and defaults to perfectly balanced rewards vs arbitrage races that are never perfect.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.