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

`RapBattle::_battle` does not increment the `battlesWon` of the winner of battle which is a crucial part of `RapperStats`

Summary

RapBattle contract allows users to get their Rapper on the stage and compete with one another, and winner is decided on the basis of some critera.
But it doesn't increment the battlesWon of the winner, where battlesWon is a crucial part of tracking the total wins of a rapper and on the basis of which the protocol may create a leaderboard but due to not handling the wins of a rapper, the participants will not like the protocol.

The battlesWon is a part of the RapperStats struct inside OneShot contract.

Vulnerability Details

  • The vulnerability is present inside the RapBattle contract where the _battle function doesn't handle the incrementation of the battlesWon for the winning rapper.

  • battlesWon is used to track the total rap battles won by the Rapper NFT of a person which is a part of RapperStats struct in OneShot contract.

  • It is expected for the rapper who won the battle should have their battlesWon incremented but it doesn't have any implementation to interact with the OneShot contract and increment the winning of winner Rapper Stats.

Impact

  • The winnings of rapper can never be tracked.

  • If the protocol designs a leaderboard, as the winnings are not updated, the leaderboard will be of no use.

Tools Used

Manual Review, Unit Test in Foundry

Add the test in the file: test/OneShotTest.t.sol

Run the test:

forge test --mt test_RapBattle_WinsOfWinnerIsNotIncremented
function test_RapBattle_WinsOfWinnerIsNotIncremented() public twoSkilledRappers {
uint256 userTokenId = 0;
uint256 challengerTokenId = 1;
// -------------------- Initital Data ---------------------------
uint256 userInitWins = oneShot.getRapperStats(userTokenId).battlesWon;
uint256 challengerInitWins = oneShot.getRapperStats(challengerTokenId).battlesWon;
uint256 betAmount = 1;
vm.startPrank(user);
oneShot.approve(address(rapBattle), userTokenId);
cred.approve(address(rapBattle), betAmount);
rapBattle.goOnStageOrBattle(userTokenId, betAmount);
vm.stopPrank();
vm.startPrank(challenger);
cred.approve(address(rapBattle), betAmount);
rapBattle.goOnStageOrBattle(challengerTokenId, betAmount);
vm.stopPrank();
uint256 userFinalWins = oneShot.getRapperStats(userTokenId).battlesWon;
uint256 challengerFinalWins = oneShot.getRapperStats(challengerTokenId).battlesWon;
// now one of them will be the winner, therefore wins should be incremented for that rapper
// the following assert validates that wins are not incremented
// it was expected that the wins of the winner must be incremented
assert(userFinalWins == userInitWins);
assert(challengerFinalWins == challengerInitWins);
}

Recommendations

Increment the wins of the winner in the rapperStats mapping of OneShot contract

  • Modify the OneShot contract to allow RapBattle to increment the winnings for the desired winner.

diff --git a/src/OneShot.sol b/src/OneShot.sol
index 5c4e9eb..f07c4e7 100644
--- a/src/OneShot.sol
+++ b/src/OneShot.sol
@@ -8,8 +8,11 @@ import {IOneShot} from "./interfaces/IOneShot.sol";
import {Streets} from "./Streets.sol";
contract OneShot is IOneShot, ERC721URIStorage, Ownable {
+ error OneShot__OnlyRapBattleCanIncrement();
+
uint256 private _nextTokenId;
Streets private _streetsContract;
+ address private rapBattleAddr;
// Mapping from token ID to its stats
mapping(uint256 => RapperStats) public rapperStats;
@@ -21,6 +24,10 @@ contract OneShot is IOneShot, ERC721URIStorage, Ownable {
_streetsContract = Streets(streetsContract);
}
+ function setRapBattleAddr(address _rapBattle) public onlyOwner {
+ rapBattleAddr = _rapBattle;
+ }
+
modifier onlyStreetContract() {
require(msg.sender == address(_streetsContract), "Not the streets contract");
_;
@@ -51,6 +58,14 @@ contract OneShot is IOneShot, ERC721URIStorage, Ownable {
metadata.battlesWon = battlesWon;
}
+ function incrementWinnings(uint256 tokenId) external {
+ if (msg.sender != rapBattleAddr) {
+ revert OneShot__OnlyRapBattleCanIncrement();
+ }
+
+ rapperStats[tokenId].battlesWon += 1;
+ }
+
/*//////////////////////////////////////////////////////////////
VIEW
//////////////////////////////////////////////////////////////*/
  • Modify the IOneShot interface

--- a/src/interfaces/IOneShot.sol
+++ b/src/interfaces/IOneShot.sol
@@ -27,4 +27,6 @@ interface IOneShot is IERC721 {
bool calmAndReady,
uint256 battlesWon
) external;
+
+ function incrementWinnings(uint256 tokenId) external;
}
  • Update the RapBattle::_battle function

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);
+ // update the winnings of defender
+ oneShotNft.incrementWinnings(defenderTokenId);
} else {
// Otherwise, since the challenger never sent us the money, we just give the money in the contract
credToken.transfer(msg.sender, _credBet);
+ // update the winnings of challenger
+ oneShotNft.incrementWinnings(_tokenId);
}
totalPrize = 0;
// Return the defender's NFT
oneShotNft.transferFrom(address(this), _defender, defenderTokenId);
}
Updates

Lead Judging Commences

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

`battlesWon` is never updated

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

`battlesWon` is never updated

Support

FAQs

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