Beginner FriendlyFoundryNFT
100 EXP
View results
Submission Details
Severity: medium
Invalid

No cooldowns on `RapBattle::goOnStageOrBattle` allow for a DoS

Summary

No cooldowns on RapBattle::goOnStageOrBattle allow for a DoS

Vulnerability Details

The function RapBattle::goOnStageOrBattle sets up the enviroment for the NFT RapBattle to happen.

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 {
// credToken.transferFrom(msg.sender, address(this), _credBet);
_battle(_tokenId, _credBet);
}
}

However, the function does not track the last time a NFT battled!

Impact

The attacker can call repeatedly in a loop the function with the same 2 NFTs, betting 0 cred. That would create an infinite loop, creating a DoS attack

Tools Used

Foundry

Proof of Concept:

1: Attacker mints 2 NFTs

2: Attacker calls RapBattle::goOnStageOrBattle with both NFTs, betting 0 cred, in an infinite loop.

Code
function testDOSAttack() public twoSkilledRappers {
address attacker = makeAddr("Exploiter");
vm.startPrank(attacker);
oneShot.mintRapper();
oneShot.mintRapper();
while (true) {
oneShot.approve(address(rapBattle), 2);
oneShot.approve(address(rapBattle), 3);
rapBattle.goOnStageOrBattle(2, 0);
rapBattle.goOnStageOrBattle(3, 0);
}
vm.stopPrank();
}

Recommendations

Recommended Mitigation:

Adding a mapping that stores the last time an NFT participated in a battle and a cooldown time. Then, checking that last time the function was called is bigger than the cooldown

contract RapBattle {
.
.
.
address public defender;
uint256 public defenderBet;
uint256 public defenderTokenId;
uint256 public constant BASE_SKILL = 65; // The starting base skill of a rapper
uint256 public constant VICE_DECREMENT = 5; // -5 for each vice the rapper has
uint256 public constant VIRTUE_INCREMENT = 10; // +10 for each virtue the rapper has
+ uint256 public constant BATTLE_COOLDOWN = 2 days;
+ mapping(uint256 tokenId => uint256 lastTimeBattle) public battleLog;
.
.
.
function goOnStageOrBattle(uint256 _tokenId, uint256 _credBet) external {
+ require(block.timestamp - battleLog[_tokenId] >= BATTLE_COOLDOWN, "RapBattle: Cooldown still in progress");
+ battleLog[_tokenId] = block.timestamp;
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 {
// credToken.transferFrom(msg.sender, address(this), _credBet);
_battle(_tokenId, _credBet);
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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