The RapBattle::goOnStageOrBattle function allows the second participant in the battle (challenger) to participate in the battle with non-existence or someone else's tokenId.
The RapBattle::goOnStageOrBattle function allows users to battle their Rappers. The function doesn't check if the input argument _tokenId is a valid ID. If the defender calls the function with invalid _tokenId the oneShotNft.transferFrom function will revert and the transaction will fail. But if a defender is waiting for a battle and challenger calls the function with invalid or someone else's _tokenId, the function will call the internal _battle function with this _tokenId as first argument.
The RapBattle::_battle function also doesn't check the existence and the ownership of the _tokenId:
The RapBattle::_battle function calls the RapBattle::getRapperSkill function with argument _tokenId to retrive the rapper skills of the defender and challenger.
The RapBattle::getRapperSkill function also doesn't check the validity of the input argument _tokenId:
The RapBattle::getRapperSkill function calls the OneShot::getRapperStats to retrieve the RapperStats for a given _tokenId.
We see that the OneShot::getRapperStats function also doesn't perform a check if the input argument tokenId is valid. If the tokenId is non-existing token, the function will return the default values for rapperStats: RapperStats({weakKnees: false, heavyArms: false, spaghettiSweater: false, calmAndReady: false, battlesWon: 0});
Then the RapBattle::getRapperSkill function will return finalSkill = BASE_SKILL; which is 65. So the challenger who provided invalid _tokenId will have challengerRapperSkill equals to 65.
The challenger is the second person of the battle. The challenger doesn't transfer the rapper token to the RapBattle contract. And the existence or ownership of the provided tokenId from challenger is not checked in the RapBattle::goOnStageOrBattle function. Therefore, the challenger can participate in a battle with non-existing token or with tokenId of someone else. In both cases the challenger can win the battle and receive the _credBet without actually to have rapper token.
Let's consider the following scenario: There is user (Alice) who has minted a rapper token and calls the RapBattle::goOnStageOrBattle function to participate in a battle. Now the user is waiting for opponent. The challenger (Slim Shady) calls also the RapBattle::goOnStageOrBattle function with non-existing tokenId as first argument. There is no check for that and the battle begins. His rapperSkill is 65. The test function testGoOnStageOrBattleOpponentWithNonExistingTokenId shows the described scenario:
After the execution of the test function we can see that the challenger has won the battle and received the _credBet in this example 0. Here is part of the traces. You can execute the test function using the foundry command:
forge test --match-test "testGoOnStageOrBattleOpponentWithNonExistingTokenId" -vvvvv
Now, let's consider the second scenario: There is user (Alice) who has minted a rapper token and staked it for 4 days. So the user has updated rapperStats and 4 Cred. Bob has done the same thing. He also has updated rapperStats and 4 Cred. Bob's tokenId is 1. Bob calls RapBattle::goOnStageOrBattle function with his tokenId and _credBet equals to 1. Now Bob is waiting for opponent. Challenger (Slim Shady) calls the RapBattle::goOnStageOrBattle function with user's tokenId and _credBet equals to 1. There is no check if the challenger is the owner of the tokenId and the battle begins. The challenger uses the rapperSkill of someone else's rapper.
The test function testGoOnStageOrBattleOpponentWithStolenTokenId shows the described scenario:
After the battle is finished and the function is executed we can see that the winner is the challenger and he received the _credBet that is equal to 1. The challenger won this battle with someone else's rapper token. Part of the traces of the testGoOnStageOrBattleOpponentWithStolenTokenId test function can be seen below. You can execute the test function using the foundry command:
forge test --match-test "testGoOnStageOrBattleOpponentWithStolenTokenId" -vvvvv
The function RapBattle::goOnStageOrBattle should not allow to participate in battle with non-existing or someone else's rapper token.
Manual Review, Foundry
Add a check in the RapBattle::goOnStageOrBattle function to ensure that the msg.sender is an owner of the provided tokenId:
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.