Summary
The function goOnStageOrBattle() does not check if the challenger's NFT is the same as the defender's NFT.
A malicious rapper could use this to fight against itself with the same NFT, improperly adding battles won to his record.
Vulnerability Details
In this test, the defender prepares a battle, and then goes into battle against himself.
function testDefenderChallengesHilself(uint256 blockNumber) public twoSkilledRappers {
vm.assume(blockNumber < type(uint256).max / 12);
vm.roll(blockNumber);
vm.warp(blockNumber * 12);
vm.prevrandao(keccak256(abi.encodePacked(block.number)));
vm.startPrank(user);
oneShot.approve(address(rapBattle), 0);
cred.approve(address(rapBattle), 3*2);
rapBattle.goOnStageOrBattle(0, 3);
rapBattle.goOnStageOrBattle(0, 3);
vm.stopPrank();
OneShot.RapperStats memory rapperStats0 = oneShot.getRapperStats(0);
assertEq(rapperStats0.battlesWon, 1);
}
Test passes
Ran 1 test for test/OneShotTestAudit.t.sol:RapBattleTest
[PASS] testUnstakeEmitsMultipleEvents() (gas: 255720)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.49ms
Tools Used
Foundry, Manual review
Recommendations
Check if the NFT of the challenger is different from that of the defender before going into battle.
function goOnStageOrBattle(uint256 _tokenId, uint256 _credBet) external {
if (defender == address(0)) {
defender = msg.sender;
defenderBet = _credBet;
defenderTokenId = _tokenId;
emit OnStage(msg.sender, _tokenId, _credBet);
oneShotNft.transferFrom(msg.sender, address(this), _tokenId);
credToken.transferFrom(msg.sender, address(this), _credBet);
} else {
+ require(_tokenId != defenderTokenId, "You can't battle yourself");
// credToken.transferFrom(msg.sender, address(this), _credBet);
_battle(_tokenId, _credBet);
}
}