sherlock-audit / 2024-03-zivoe-judging

8 stars 6 forks source link

Ironsidesec - Yield distribution cannot be done after 30 days days after ITO end, or better put, First yield distribution is possible only after 60 days of ITO conclusion. #670

Closed sherlock-admin3 closed 6 months ago

sherlock-admin3 commented 6 months ago

Ironsidesec

high

Yield distribution cannot be done after 30 days days after ITO end, or better put, First yield distribution is possible only after 60 days of ITO conclusion.

Summary

ITO is between January 1 and January 31. If ITO concludes on jan 31, then the first distribution should be doable on Feb 28, according to docs, and comments. But it is only possibe to do on March 30, after a 60 day wait after ITO end. But after that, it is done properly per each month. Look at recommendation for better understanding.

Vulnerability Detail

https://github.com/sherlock-audit/2024-03-zivoe/blob/01e00e6f27b58392a6fa0b82c84a46a783a0df3c/zivoe-core-foundry/src/ZivoeYDL.sol#L328

https://github.com/sherlock-audit/2024-03-zivoe/blob/01e00e6f27b58392a6fa0b82c84a46a783a0df3c/zivoe-core-foundry/src/ZivoeYDL.sol#L216


File: 2024-03-zivoe\zivoe-core-foundry\src\ZivoeYDL.sol

322: function unlock() external {
323:         require(
324:             _msgSender() == IZivoeGlobals_YDL(GBL).ITO(), 
325:             "ZivoeYDL::unlock() _msgSender() != IZivoeGlobals_YDL(GBL).ITO()"
326:         );
327: 
328:         unlocked = true;
329:   >>>   lastDistribution = block.timestamp + 30 days; 

            ***

355:     }

213:     function distributeYield() external nonReentrant {
214:         require(unlocked, "ZivoeYDL::distributeYield() !unlocked"); 
215:         require(
216:   >>>       block.timestamp >= lastDistribution + daysBetweenDistributions * 86400, 
217:             "ZivoeYDL::distributeYield() block.timestamp < lastDistribution + daysBetweenDistributions * 86400"
218:         );
219: 
220:         // Calculate protocol earnings.
221:         uint256 earnings = IERC20(distributedAsset).balanceOf(address(this));
222:         uint256 protocolEarnings = protocolEarningsRateBIPS * earnings / BIPS;
223:         uint256 postFeeYield = earnings.floorSub(protocolEarnings);
224: 
225:         // Update timeline.
226:         distributionCounter += 1;
227:         lastDistribution = block.timestamp;

            ***

        }

Issue Path:

  1. Its jan 31, ITO gets concluded and ITO will call YDL.unock on line 322 above
  2. So the lastDistribution will be updated to now + 30 days = March 1 probably
  3. ZIVOE distributes the yield monthly because daysBetweenDistributions = 30 in YDL contract. And docs also says so.

lastDistribution = March 1 , look at point 2 above. daysBetweenDistributions = 30.

  1. So, its March 1st now, so 1 month passed after ITO, so stakers try to call distributeYield to claim their monthly yield, But the line 216 reverts because it checks 

(block.timestamp >= lastDistribution + daysBetweenDistributions 86400) = (March 1 >= March 1 + 30 86400) = (March 1 >= March 30 ), so it reverts. So, stakers have to wait another 30 days to claim the yield.

If the line 329 was lastDistribution = just  block.timestamp , instead of  block.timestamp + 30 days, then the above revert would not happen.

Impact

Stakers will not farm if they cannot claim rewards for 60 days, they need low interval of maximum 30 days. So any stakers trying to stake just after ITO will have to wait till 60 days to claim his rewards for first  time. So giving it an High.

Code Snippet

https://github.com/sherlock-audit/2024-03-zivoe/blob/01e00e6f27b58392a6fa0b82c84a46a783a0df3c/zivoe-core-foundry/src/ZivoeYDL.sol#L216

https://github.com/sherlock-audit/2024-03-zivoe/blob/01e00e6f27b58392a6fa0b82c84a46a783a0df3c/zivoe-core-foundry/src/ZivoeYDL.sol#L328

Tool used

Manual Review

Recommendation

Modify the lastDistribution on YDL.unlock()

https://github.com/sherlock-audit/2024-03-zivoe/blob/01e00e6f27b58392a6fa0b82c84a46a783a0df3c/zivoe-core-foundry/src/ZivoeYDL.sol#L328

function unlock() external {
        require(
            _msgSender() == IZivoeGlobals_YDL(GBL).ITO(), 
            "ZivoeYDL::unlock() _msgSender() != IZivoeGlobals_YDL(GBL).ITO()"
        );

        unlocked = true;
-       lastDistribution = block.timestamp + 30 days; 
+       lastDistribution = block.timestamp; 
        
        emaSTT = IERC20(IZivoeGlobals_YDL(GBL).zSTT()).totalSupply();
        emaJTT = IERC20(IZivoeGlobals_YDL(GBL).zJTT()).totalSupply();

        ***

    }
sherlock-admin2 commented 6 months ago

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

panprog commented:

invalid, this is intended according to developers