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

MEV Possiblity in `RapBattle::goOnStageOrBattle()`

Description:

In the RapBattle::goOnStageOrBattle() function we can see that

Impact:

A malicious user could potentially front run another user based on the available on chain data of the defender, or the MEV bot can re-arrange the order of the transactions making someone else can the function first and then based on the data call the RapBattle::goOnStageOrBattle() function

Proof of Concept: here is the POC

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);
}
}
function _battle(uint256 _tokenId, uint256 _credBet) internal {
address _defender = defender;
require(defenderBet == _credBet, "RapBattle: Bet amounts do not match");
uint256 defenderRapperSkill = getRapperSkill(defenderTokenId);
console.log(defenderRapperSkill, string(abi.encode(" : is the defender's skill val")));
uint256 challengerRapperSkill = getRapperSkill(_tokenId);
console.log(challengerRapperSkill, string(abi.encode(" : is the challenger's skill val")));
uint256 totalBattleSkill = defenderRapperSkill + challengerRapperSkill;
uint256 totalPrize = defenderBet + _credBet;
uint256 random =
uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))) % totalBattleSkill;
console.log(random, string(abi.encode(" : is the random number")));
// Reset the defender
defender = address(0);
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);
}

Here we can see that just 1 function is used to getready for a battle and even battle itself. The getReadyForBattle this can create an opportunity to the MEV bots to reorder the transaction and simulate the possibility since weak Randomness is used, when the contract is empty the user and attacker will call the RapBattle::getReadyForBattle and the MEV bot can lookup the user stats and based on it reorder the transactions

Recommended Mitigation:

the recommended steps for mitigation will be is having different functions for different actions like RapBattle::getOnStage() and RapBattle::battle() which will help fight MEV

```diff
+ function goOnStage(uint256 _tokenId, uint256 _credBet) external {
- 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);
```diff
+ revert() // Add a custom revert
- _battle(_tokenId, _credBet);
```
}
}
```diff
+ function battle(uint256 _tokenId, uint256 _credBet) external {
- function _battle(uint256 _tokenId, uint256 _credBet) internal {
+ if (defender == address(0)) {
+ revert() // add custom revert
+ }
```
address _defender = defender;
require(defenderBet == _credBet, "RapBattle: Bet amounts do not match");
uint256 defenderRapperSkill = getRapperSkill(defenderTokenId);
console.log(defenderRapperSkill, string(abi.encode(" : is the defender's skill val")));
uint256 challengerRapperSkill = getRapperSkill(_tokenId);
console.log(challengerRapperSkill, string(abi.encode(" : is the challenger's skill val")));
uint256 totalBattleSkill = defenderRapperSkill + challengerRapperSkill;
uint256 totalPrize = defenderBet + _credBet;
uint256 random =
uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))) % totalBattleSkill;
console.log(random, string(abi.encode(" : is the random number")));
// Reset the defender
defender = address(0);
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);
}

Tools Used:

Manual Review

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.