sherlock-audit / 2024-03-zivoe-judging

8 stars 6 forks source link

denzi_ - Front-Running Vulnerability Due to Predictable Incentive Calculations in ZivoeTranches Contract #683

Closed sherlock-admin4 closed 6 months ago

sherlock-admin4 commented 6 months ago

denzi_

high

Front-Running Vulnerability Due to Predictable Incentive Calculations in ZivoeTranches Contract

Summary

The ZivoeTranches smart contract is susceptible to a front-running vulnerability due to the way it calculates rewards based on the current junior-to-senior tranche ratio. Transactions intending to capitalize on maximum incentive rates can be preempted by other transactions that alter the ratio, resulting in a significant reduction in expected rewards.

Vulnerability Detail

The ZivoeTranches contract provides incentives for deposits into junior and senior tranches based on a dynamic ratio that determines the allocation of ZVE tokens. When a user attempts to deposit into the junior tranche to receive maximum ZVE tokens (maxZVEPerJTTMint), the incentive calculation is impacted by the current ratio of junior to senior tranche supplies. Since the ratio is publicly visible and can be affected by other transactions, a user's transaction can be front-run by another, which deposits first, thus changing the ratio and reducing the rewards calculated for subsequent transactions.

The Incentive Ratios are defined in the contract as following

/// @dev Basis points ratio between zJTT.totalSupply():zSTT.totalSupply() for maximum rewards (affects above slope).
    uint256 public lowerRatioIncentiveBIPS = 1000; // 10%
    uint256 public upperRatioIncentiveBIPS = 3500; // 35%

The following lines of code determine the reward incentives for a deposit into Junior tranche

        if (avgRatio <= lowerRatioIncentiveBIPS) {
            // avgRatio <= 1000
            avgRate = maxZVEPerJTTMint; // we give more incentive to deposit stable coin into junior
        } else if (avgRatio >= upperRatioIncentiveBIPS) {
            // avgRatio >= 3500
            avgRate = minZVEPerJTTMint; // we give less incentive to deposit stable coin into junior
        } else {
            // avgRatio < 3500 && avgRatio > 1000
            avgRate =
                maxZVEPerJTTMint -
                (diffRate * (avgRatio - lowerRatioIncentiveBIPS)) /
                (upperRatioIncentiveBIPS - lowerRatioIncentiveBIPS);
        } // calculates the avgRate for in between avgRatio

The following lines of code determine the reward incentives for a deposit into Senior tranche

        if (avgRatio <= lowerRatioIncentiveBIPS) {
            // avgRatio <= 1000
            avgRate = minZVEPerJTTMint; // we give less incentive to deposit into senior
        } else if (avgRatio >= upperRatioIncentiveBIPS) {
            // avgRatio >= 3500
            avgRate = maxZVEPerJTTMint; // we give more incentive to deposit into senior
        } else {
            // avgRatio < 3500 && avgRatio > 1000
            avgRate =
                minZVEPerJTTMint +
                (diffRate * (avgRatio - lowerRatioIncentiveBIPS)) /
                (upperRatioIncentiveBIPS - lowerRatioIncentiveBIPS);
        }

Proof of Concept

This submission will describe the frontrun on Junior deposits.

Suppose Bob wants to deposit into the Junior tranche and he would like to receive the maximum reward for his deposit

Suppose the current state of the tranches as following

  1. Bob calls rewardZVEJuniorDeposit() with 5000 USDC to see how much reward he will receive for the amount.
  2. The reward returned by the function is calculated through the following lines of code
        uint256 startRatio = (juniorSupp * BIPS) / seniorSupp;
        uint256 finalRatio = (juniorSupp * BIPS) / (seniorSupp + deposit);
        uint256 avgRatio = (startRatio + finalRatio) / 2;
  1. The avgRatio is 1000 BIPS and since avgRatio <= lowerRatioIncentiveBIPS. Bob will receive the maxZVEPerJTTMint.

  2. Alice sees the transaction in the mempool and wants to get maximum reward for herself before the JuniorSupp exceeds the 10% threshold.

  3. Alice creates her own transaction with 5000 USDC to deposit into the Junior tranche and pays higher gas for its execution.

  4. Alice's call will execute first, the junior supp will become 10,000 and she will receive the maximum rewards, Bob's call will execute after, this will change the avgRate which he was to initially receive which will be calculated through the following lines of code

avgRate =
maxZVEPerJTTMint - (diffRate * (avgRatio - lowerRatioIncentiveBIPS)) / (upperRatioIncentiveBIPS - lowerRatioIncentiveBIPS);
  1. Bob will now receive lower reward than what he was initially going to get.

Impact

This vulnerability can lead to unexpected financial losses for users who anticipate a higher reward based on the state of the blockchain at the time of transaction creation. It can also lead to a loss of trust in the system's fairness, as users with higher gas bidding capabilities can manipulate the outcome to their advantage.

According to Zivoe Docs, for redemptions of deposited tokens from tranche. Users will have to wait for epochs and create requests. There are further complications when redeeming which can lead to decreased redemptions during defaults

Let's consider a scenario where a junior liquidity provider requests to redeem 50,000 zJTT. Suppose there's 60,000 USDC in the redemption locker and no other redemption requests are outstanding. Given the 50% default loss, this junior liquidity provider can only redeem 25,000 USDC for his 50,000 zJTT, equivalent to a rate of 0.5 USDC per zJTT.

Furthermore if the defaults exceed more than what the junior tranche can absorbed. Bob will lose all his funds.

Zivoe Redemptions Docs

Code Snippet

Deposit functions

rewardZVEJuniorDeposit

rewardZVESeniorDeposit

Tool used

Manual Review

Recommendation

To mitigate this vulnerability, Add a slippage check on the incentive user wants to receive, check the avgRatio against this slippage parameter and revert if its not what the user planned for in the deposit functions.

Duplicate of #63

sherlock-admin3 commented 6 months ago

1 comment(s) were left on this issue during the judging contest.

panprog commented:

borderline low/medium, dup of #63, the core issue here is that incentive per token deposited can change before user's transaction executes (for different reasons) and user has no control (slippage check) about it, but this is a protocol choice and this issue can be considered informational. Still, the incentive might be important to user, hence borderline