Lucene search

K
code423n4Code4renaCODE423N4:2022-09-VTVL-FINDINGS-ISSUES-238
HistorySep 23, 2022 - 12:00 a.m.

User will lose rewards

2022-09-2300:00:00
Code4rena
github.com
3

Lines of code

Vulnerability details

Impact

User will lose there rewards even when vesting period has completed. Also the reward will get stuck in the contract with no one able to retrieve them

Proof of Concept

  1. Admin creates a new claim using createClaim function
function createClaim(
            address _recipient, 
            uint40 _startTimestamp, 
            uint40 _endTimestamp, 
            uint40 _cliffReleaseTimestamp, 
            uint40 _releaseIntervalSecs, 
            uint112 _linearVestAmount, 
            uint112 _cliffAmount
                ) external onlyAdmin {
        _createClaimUnchecked(_recipient, _startTimestamp, _endTimestamp, _cliffReleaseTimestamp, _releaseIntervalSecs, _linearVestAmount, _cliffAmount);
    }
  1. Admin provided all the parameters such that _endTimestamp-_startTimestamp<releaseIntervalSecs. This passes since only check related to this in _createClaimUnchecked function is
require((_endTimestamp - _startTimestamp) % _releaseIntervalSecs == 0, "INVALID_INTERVAL_LENGTH");
  1. Assume the params were below:
start timestamp = 100
end timestamp = 200
releaseIntervalSecs=300

Hence _endTimestamp-_startTimestamp&lt;releaseIntervalSecs
  1. Now assume end timestamp has reached and claimant is expecting his full 100% rewards for which he calls withdraw function
function withdraw() hasActiveClaim(_msgSender()) external {
        // Get the message sender claim - if any

        Claim storage usrClaim = claims[_msgSender()];

        // we can use block.timestamp directly here as reference TS, as the function itself will make sure to cap it to endTimestamp
        // Conversion of timestamp to uint40 should be safe since 48 bit allows for a lot of years.
        uint112 allowance = vestedAmount(_msgSender(), uint40(block.timestamp));

        // Make sure we didn't already withdraw more that we're allowed.
        require(allowance &gt; usrClaim.amountWithdrawn, "NOTHING_TO_WITHDRAW");
...
}
  1. As we can see this calls vestedAmount function which internally calls _baseVestedAmount function
function _baseVestedAmount(Claim memory _claim, uint40 _referenceTs) internal pure returns (uint112) {
...
uint40 currentVestingDurationSecs = _referenceTs - _claim.startTimestamp; // How long since the start
                // Next, we need to calculated the duration truncated to nearest releaseIntervalSecs
                uint40 truncatedCurrentVestingDurationSecs = (currentVestingDurationSecs / _claim.releaseIntervalSecs) * _claim.releaseIntervalSecs;
...
}
  1. This means below happens
currentVestingDurationSecs = _referenceTs - _claim.startTimestamp = 200-100 = 100
truncatedCurrentVestingDurationSecs = (100/300)*300 = 0*300 = 0
  1. This means User has vested 0 amount and will not be able to withdraw anything which is wrong

  2. Even Admin wont be able to correct this since revokeClaim function has below condition which always fails since 0<0

require( _claim.amountWithdrawn &lt; finalVestAmt, "NO_UNVESTED_AMOUNT");
// Here _claim.amountWithdrawn is 0 since user has withdrawn nothing
// finalVestAmt is also 0 as seen in above steps

Recommended Mitigation Steps

Make sure that endtime-startTime>=releaseIntervalSecs or perform multiplication before division in VTVLVesting.sol#L169

 uint40 truncatedCurrentVestingDurationSecs = (currentVestingDurationSecs * _claim.releaseIntervalSecs) / _claim.releaseIntervalSecs;

The text was updated successfully, but these errors were encountered:

šŸ‘Ž 1 indijanc reacted with thumbs down emoji

All reactions

  • šŸ‘Ž 1 reaction