Vulnerability Details
RapperStats.battlesWon
is not incremented after winning a battle in RapBattle.goOnStageOrBattle
.
Impact
The information provided in OneShot.getRapperStats
does not reflect correctly rapper metadata. This can lead to misconceptions by users about the status of their rappers.
Tools Used
Manual review.
Recommendations
Make the following changes in contract IOneShot.sol
:
https://github.com/Cyfrin/2024-02-one-shot/blob/main/src/interfaces/IOneShot.sol
interface IOneShot is IERC721 {
struct RapperStats {
bool weakKnees;
bool heavyArms;
bool spaghettiSweater;
bool calmAndReady;
uint256 battlesWon;
}
// Mint a new rapper token
function mintRapper() external;
// Add functions for direct metadata manipulation
function getRapperStats(uint256 tokenId) external view returns (RapperStats memory);
// Update metadata for a token
function updateRapperStats(
uint256 tokenId,
bool weakKnees,
bool heavyArms,
bool spaghettiSweater,
bool calmAndReady,
uint256 battlesWon
) external;
+ function increaseRapperBattlesWon(uint256 tokenId) external;
}
Make the following changes in contract OneShot.sol
:
https://github.com/Cyfrin/2024-02-one-shot/blob/47f820dfe0ffde32f5c713bbe112ab6566435bf7/src/OneShot.sol#L13
+ address private _rapButtleContract;
https://github.com/Cyfrin/2024-02-one-shot/blob/47f820dfe0ffde32f5c713bbe112ab6566435bf7/src/OneShot.sol#L28
+function setRapButtleContract(address rapButtleContract) public onlyOwner {
+ require(rapButtleContract != address(0));
+ _rapButtleContract = rapButtleContract;
+}
+
+modifier onlyRapButtleContract() {
+ require(msg.sender == address(_rapButtleContract), "Not the RapButtle contract");
+ _;
+}
https://github.com/Cyfrin/2024-02-one-shot/blob/47f820dfe0ffde32f5c713bbe112ab6566435bf7/src/OneShot.sol#L53
+function increaseRapperBattlesWon(uint256 tokenId) external onlyRapButtleContract {
+ require(tokenId < _nextTokenId, "Wrong tokenId");
+ RapperStats storage metadata = rapperStats[tokenId];
+ metadata.battlesWon += 1;
+}
Make the following changes in contract RapBattle.sol
:
https://github.com/Cyfrin/2024-02-one-shot/blob/47f820dfe0ffde32f5c713bbe112ab6566435bf7/src/RapBattle.sol#L54C1-L81C6
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);
+ oneShotNft.increaseRapperBattlesWon(random <= defenderRapperSkill ? defenderTokenId : _tokenId);
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);
}