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

`RapBattle.sol::_battle` uses dangerous equality. If the bet is too high and the opponent is too strong, nobody will challenge causing unintended lock to protocol.

  • Description:

    • RapBattle.sol::_battle requires that both bets must have the same value. The protocol design allows one battle at a time, so if the defender is too skilled and bets too much Cred, nobody will accept the challenge.

    • Impact:

      • Protocol will locked because the function will be blocked with a giant bet.

    • Proof of Concept:

      • After a big bet

      • If a user doesn't match the bet, the protocol will be blocked until someone matches

        Add the code below to `OneShotTest.t.sol`
        function testGoOnBlocked() public mintRapper {
        vm.startPrank(address(streets));
        cred.mint(user, 100);
        vm.stopPrank();
        vm.warp(51684120);
        vm.startPrank(user);
        oneShot.approve(address(rapBattle), 0);
        cred.approve(address(rapBattle), 100);
        rapBattle.goOnStageOrBattle(0, 100);
        address defender = rapBattle.defender();
        vm.stopPrank();
        vm.startPrank(challenger);
        rapBattle.goOnStageOrBattle(1, 0);
        }
    • Recommendation:

      • Create a counter to control the battles

      • Create a struct to control the line-up

      • Create a mapping to keep track of battles

      • Redesign the RapBattle::goOnStageOrBattle function into a RapBattle::credDefender & RapBattle::credChallenger functions

        Create the variables in `RapBattle.sol` as follows
        + struct Lineup {
        + address defender;
        + address challenger;
        + uint256 defenderNFTId;
        + uint256 bet;
        + }
        + uint256 battleCounter = 1;
        + mapping(uint256 battleCounter => Lineup) private stageLimits;
        + error RapBattle__NotEnoughCred();
        Convert the `RapBattle.sol::goOnStageOrBattle` into `RapBattle.sol::credDefender` and `RapBattle.sol::credChallenger` as follows
        - 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 credDefender(uint256 _tokenId, uint256 _credBet) external {
        + if (credToken.balanceOf(msg.sender) < _credBet){
        + revert RapBattle__NotEnoughCred();
        + }
        + stageLimits[battleCounter] = Lineup ({
        + defender: msg.sender,
        + challenger: address(0),
        + defenderNFTId: _tokenId,
        + bet: _credBet
        + });
        +
        + emit OnStage(msg.sender, _tokenId, _credBet);
        +
        + battleCounter++;
        +
        + oneShotNft.transferFrom(msg.sender, address(this), _tokenId);
        + credToken.transferFrom(msg.sender, address(this), _credBet);
        + }
        + function credChallenger(uint256 _stageId, uint256 _tokenId) external {
        + if(credToken.balanceOf(msg.sender) < stageLimits[_stageId].bet || oneShotNft.ownerOf(_tokenId) != msg.sender){
        + revert RapBattle__NotEnoughCredOrItsNotTheNFTOwner();
        + }
        +
        + stageLimits[_stageId].challenger = msg.sender;
        +
        + // credToken.transferFrom(msg.sender, address(this), _credBet);
        + _battle(_stageId, _tokenId);
        + }
        Adjust the `RapBattle.sol::_battle` as follow
        + function _battle(uint256 _stageId, uint256 _tokenId) internal {
        - address _defender = defender;
        - require(defenderBet == _credBet, "RapBattle: Bet amounts do not match");
        - uint256 defenderRapperSkill = getRapperSkill(defenderTokenId);
        + uint256 defenderRapperSkill = getRapperSkill(stageLimits[_stageId].defenderNFTId);
        + uint256 challengerRapperSkill = getRapperSkill(_tokenId);
        + uint256 totalBattleSkill = defenderRapperSkill + challengerRapperSkill;
        - uint256 totalPrize = defenderBet + _credBet;
        +
        + //Must adjust this too.
        uint256 random =
        uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))) % totalBattleSkill;
        - defender = address(0);
        + emit Battle(msg.sender, _tokenId, random <= defenderRapperSkill ? stageLimits[_stageId].defender : msg.sender);
        - emit Battle(msg.sender, _tokenId, random < defenderRapperSkill ? _defender : msg.sender);
        if (random <= defenderRapperSkill) {
        - credToken.transfer(_defender, defenderBet);
        + credToken.transfer(stageLimits[_stageId].defender, stageLimits[_stageId].bet);
        - credToken.transferFrom(msg.sender, _defender, _credBet);
        + credToken.transferFrom(msg.sender, stageLimits[_stageId].defender, stageLimits[_stageId].bet);
        } else {
        - credToken.transfer(msg.sender, _credBet);
        + credToken.transfer(msg.sender, stageLimits[_stageId].bet);
        }
        - totalPrize = 0;
        - oneShotNft.transferFrom(address(this), _defender, defenderTokenId);
        + oneShotNft.transferFrom(address(this), stageLimits[_stageId].defender, stageLimits[_stageId].defenderNFTId);
        }
Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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