Lucene search

K
code423n4Code4renaCODE423N4:2023-08-POOLTOGETHER-FINDINGS-ISSUES-81
HistoryAug 07, 2023 - 12:00 a.m.

Risk of silent overflow in rngComplete rewards cast

2023-08-0700:00:00
Code4rena
github.com
4
silent overflow risk
rngcomplete function
reward casting
vulnerability impact
uint104 casting
mitigation steps
manual review
under/overflow risk

Lines of code

Vulnerability details

Impact

The rngComplete function uses the rewards function from the RewardLib library to calculates the rewards that should be given, the _rewards returned by the rewards function are of type uint256 but before proceeding to the reward transfer (the call to prizePool.withdrawReserve), the reward amounts are down casted to type uint104.

The issue is that the casting is performed without first checking that the values being cast can fit into the lower type (uint104). As a result, there is a risk of a silent overflow occurring during the casting process.

Proof of Concept

The issue occurs in the rngComplete function below :

function rngComplete(
    uint256 _randomNumber,
    uint256 _rngCompletedAt,
    address _rewardRecipient, 
    uint32 _sequenceId,
    AuctionResult calldata _rngAuctionResult
) external returns (bytes32) {
    ...

    uint256 futureReserve = prizePool.reserve() + prizePool.reserveForOpenDraw();


    // @audit calculated rewards amounts of type uint256 
    uint256[] memory _rewards = RewardLib.rewards(auctionResults, futureReserve);

    emit RngSequenceCompleted(
        _sequenceId,
        drawId,
        _rewardRecipient,
        _auctionElapsedSeconds,
        rewardFraction
    );

    for (uint8 i = 0; i < _rewards.length; i++) {
        // @audit Casting without check if value fits in lower type
        uint104 _reward = uint104(_rewards[i]); 
        if (_reward > 0) {
            prizePool.withdrawReserve(auctionResults[i].recipient, _reward);
            emit AuctionRewardDistributed(_sequenceId, auctionResults[i].recipient, i, _reward);
        }
    }

    return bytes32(uint(drawId));
}

As we can see the rngComplete function calls RewardLib.rawards which returns an array of reward amounts of type uint256, the function uses a for loop to send all the rewards but before calling prizePool.withdrawReserve (which is responsible for transferring the rewards) the reward amount are casted into a lower type uint104, we can notice that the cast is done directly without checking if the casted amount fits in this lower type which can result in a silent overflow.

Because of this issue the reward recipient may receive less reward than what was intended by the protocol.

Tools Used

Manual review

Recommended Mitigation Steps

Use OZ safeCast when casting the reward amount to uint104.

function rngComplete(
    uint256 _randomNumber,
    uint256 _rngCompletedAt,
    address _rewardRecipient, 
    uint32 _sequenceId,
    AuctionResult calldata _rngAuctionResult
) external returns (bytes32) {
    ...

    for (uint8 i = 0; i < _rewards.length; i++) {
        // @audit using OZ safeCast library
        uint104 _reward = _rewards[i].toUint104(); 
        if (_reward > 0) {
            prizePool.withdrawReserve(auctionResults[i].recipient, _reward);
            emit AuctionRewardDistributed(_sequenceId, auctionResults[i].recipient, i, _reward);
        }
    }

    return bytes32(uint(drawId));
}

Assessed type

Under/Overflow


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

All reactions