Skip to content

Latest commit

 

History

History
57 lines (42 loc) · 3.59 KB

File metadata and controls

57 lines (42 loc) · 3.59 KB

Inefficient reward split could cause unbalanced ratio and favor SafEth staking

Summary

Reward splitting is executed in a greedy fashion, potentially causing a permanent unbalanced state which deviates from the desired ratio.

Impact

Collaterals in the AfEth protocol are balanced according to a ratio. New deposits in the contract are split between SafEth and VotiumStrategy according to this ratio. Compounded rewards coming from the Votium strategy are also affected by this ratio, this can be seen in the implementation of depositRewards():

https://github.com/code-423n4/2023-09-asymmetry/blob/main/contracts/AfEth.sol#L272-L293

272:     function depositRewards(uint256 _amount) public payable {
273:         IVotiumStrategy votiumStrategy = IVotiumStrategy(vEthAddress);
274:         uint256 feeAmount = (_amount * protocolFee) / 1e18;
275:         if (feeAmount > 0) {
276:             // solhint-disable-next-line
277:             (bool sent, ) = feeAddress.call{value: feeAmount}("");
278:             if (!sent) revert FailedToSend();
279:         }
280:         uint256 amount = _amount - feeAmount;
281:         uint256 safEthTvl = (ISafEth(SAF_ETH_ADDRESS).approxPrice(true) *
282:             safEthBalanceMinusPending()) / 1e18;
283:         uint256 votiumTvl = ((votiumStrategy.cvxPerVotium() *
284:             votiumStrategy.ethPerCvx(true)) *
285:             IERC20(vEthAddress).balanceOf(address(this))) / 1e36;
286:         uint256 totalTvl = (safEthTvl + votiumTvl);
287:         uint256 safEthRatio = (safEthTvl * 1e18) / totalTvl;
288:         if (safEthRatio < ratio) {
289:             ISafEth(SAF_ETH_ADDRESS).stake{value: amount}(0);
290:         } else {
291:             votiumStrategy.depositRewards{value: amount}(amount);
292:         }
293:     }

Both SafEth and Votium TVLs are calculated in order to normalize and determine the proportion between the two. This result is compared to the configured ratio in order to know where to route rewards.

As we can see in the previous snippet of code, rewards go either to SafEth or to Votium. This means that rewards are allocated in a greedy fashion: if the SafEth ratio is below the threshold, then everything gets staked in SafEth. Else, everything goes to Votium.

This can derive in a permanent state of unbalanced weights that fails to converge to the desired ratio. If the safEthRatio is slightly below the threshold, then all goes there. Similarly, if the safEthRatio is slightly above the ratio, then everything goes to Votium. This could create an scenario in which the resulting ratio bounces off the desired value.

Recommendation

The issue can be fixed by considering a split of the rewards between both collaterals so that the ratio is respected (or try to minimize the offset, if the reward is not enough).

If the offset is too large in the direction of SafEth, then all rewards are staked in SafEth. If the offset is too large in the direction of Votium, then all rewards are deposited in VotiumStrategy. Here, too large means that adding the whole set of rewards into a single collateral it doesn't offset the ratio in the other direction (e.g. for SafEth this would be (T_s + R_s) / (T + R) < ratio) . In any other case, the rewards should be proportionally distributed so that the ratio is kept (i.e. (T_s + R_s) / (T + R) ~= ratio).

Say T is the total TVL, Ts is the TVL of SafEth, Tv is the TVL of Votium, R are the total new rewards, Rs the rewards that should go to SafEth, and Rv the rewards that should go to Votium.

T = Tv + Ts
R = Rv + Rs

(Ts + Rs) / (Ts + Tv + Rs + Rv) = (Ts + Rs) / (T + R) = ratio