sherlock-audit / 2024-03-zivoe-judging

8 stars 6 forks source link

mt030d - Attackers can front-run the ZivoeYDL.distributeYield() function and call returnAsset() to prevent the available yield in YDL from being distributed to the appropriate recipients #187

Closed sherlock-admin2 closed 6 months ago

sherlock-admin2 commented 6 months ago

mt030d

high

Attackers can front-run the ZivoeYDL.distributeYield() function and call returnAsset() to prevent the available yield in YDL from being distributed to the appropriate recipients

Summary

The ZivoeYDL.distributeYield() function distributes the current balance in the contract as yield to recipients. Since anyone can call the ZivoeYDL.returnAsset() function to move the asset from YDL to the DAO. An attacker can front-run the distributeYield() function and call the returnAsset() function to clean the asset balance, resulting in the distributeYield() function distributing 0 yield. As a result, the recipients can not get their reward in time.

Vulnerability Detail

    function distributeYield() external nonReentrant {
        ...
        // Calculate protocol earnings.
        uint256 earnings = IERC20(distributedAsset).balanceOf(address(this));
        ...
    }

The distributeYield() function in the ZivoeYDL contract is responsible for distributing the available earnings to the yield recipients (protocol recipients, senior tranche, junior tranche, residual recipients). It uses the current balance in the contract as the available earnings to distribute.

    function returnAsset(address asset) external {
        require(asset != distributedAsset, "ZivoeYDL::returnAsset() asset == distributedAsset");
        emit AssetReturned(asset, IERC20(asset).balanceOf(address(this)));
        IERC20(asset).safeTransfer(IZivoeGlobals_YDL(GBL).DAO(), IERC20(asset).balanceOf(address(this)));
    }

However, there is a permissionless returnAsset() function in the ZivoeYDL contract, which can be used to transfer all the balance to the DAO. Attackers can front-run the distributeYield() TX and call returnAsset() to set the balance in the YDL to 0, which causes the distributeYield() to distribute 0 amount of earnings to recipients.

    function distributeYield() external nonReentrant {
        ...
        require(
            block.timestamp >= lastDistribution + daysBetweenDistributions * 86400, 
            "ZivoeYDL::distributeYield() block.timestamp < lastDistribution + daysBetweenDistributions * 86400"
        );

        ...
        lastDistribution = block.timestamp;
        ...
    }

To make matters worse, the distributeYield() call will update the lastDistribution state variable, which disables the distributeYield() for 30 days (daysBetweenDistributions) due to the above block timestamp check. This means that if a distributeYield() call is subject to a front-running attack, the team cannot use the distributeYield() function to redistribute the yields for 30 days. In other words, without other ways to redistribute the yields, the yield recipients can not get their rightful yields in at least 30 days.

Impact

Attackers can front-run the distributeYield() with returnAsset(), causing the distributeYield() to distribute 0 amount of yield. Since there is a 30-day interval between two distributeYield() calls, without other ways to redistribute the yields, the yield recipients cannot get their yield redistributed by distributeYield() in 30 days.

Code Snippet

https://github.com/sherlock-audit/2024-03-zivoe/blob/main/zivoe-core-foundry/src/ZivoeYDL.sol#L213-L310 https://github.com/sherlock-audit/2024-03-zivoe/blob/main/zivoe-core-foundry/src/ZivoeYDL.sol#L314C1-L318C6

Tool used

Manual Review

Recommendation

One way to mitigate the issue is to prevent the distribution of 0 earnings in distributeYield(). Another approach is to implement proper access control for the returnAsset() function.

sherlock-admin2 commented 6 months ago

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

panprog commented:

invalid, returnAsset reverts on distributedAsset, and distributeYield distributes only this asset