Farseen0/scar-pairs huggingface dataset:
MEDIUM severity: `SessionManager::rescheduleGame` advances the start time but not the end time allowing for a griefing attack where the game creator can collect fees while preventing users from participating. **Description:** When a game creator reschedules a game, they set a new start time, but the end time is never updated.

The maximum game duration is 10 minutes, and the `minimumRescheduleTime` is 15 minutes, so the `rescheduleGame` function will always set a new start time that is later than the...

Vulnerable code:
function rescheduleGame(uint256 _gameId, uint256 _newStartTime)
        external
        onlyCreator(_gameId)
        onlyState(_gameId, SessionState.Created)
    {
        Game storage game = games[_gameId];
        require(
            _newStartTime > game.startTime + minimumRescheduleTime,
            RescheduleTooSoon(game.startTime, minimumRescheduleTime, _newStartTime)
        );
        require(game.originalStartTime == 0, GameIsAlreadyRescheduled(_gameId));
        game.originalStartTime = game.startTime;
        game.startTime = _newStartTime; <------ just moving start time?
        emit GameRescheduled(_gameId, _newStartTime);
    }

// file: /test/SessionManagerRescheduleTest

 function test_rescheduleGame_VerifyOriginalStartTime() public {
        uint256 gameId = _createGame();

        uint256 originalStartTime = sessionManager.getStartTime(gameId);
        uint256 newStartTime = originalStartTime + sessionManager.minimumRescheduleTime() + 1 days;

        vm.prank(creator);
        sessionManager.rescheduleGame(gameId, newStartTime);

        // Verify original start time is preserved
        assertEq(sessionManager.getOriginalStartTime(gameId), originalStartTime);
    }
LOW severity: Prevent negative assertion following previous truthful assertion in `DefaultSession::assertionResolvedCallback`. **Description:** `DefaultSession::assertionResolvedCallback` does not handle the state where:
* it is first called with `assertedTruthfully = true` for given `assertionId`
* it is later called with `assertedTruthfully = false` for the same `assertionId`

**Impact:** In this state `delete...

Vulnerable code:
function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) public override {
        require(msg.sender == address(optimisticOracle), NotOptimisticOracle(msg.sender));
+       require(!assertions[assertionId].resolved, AssertionAlreadyResolved(assertionId));
LOW severity: Excessive amount `maximumContestants` could make games to revert in `DefaultSession::recordResults` due to out of gas. **Description:** `SessionManager::maximumContestants` is initially set to 1 million so potentially a large number of contestants can join each game:

`DefaultSession::recordResults` iterates over all the winners to record the result for each question in the respective strategies:

If there are...

Vulnerable code:
/**
 * @notice Maximum number of contestants allowed in a game
 */
 uint256 public maximumContestants = 1_000_000;

function recordResults(uint256 sessionId, bytes32 assertionId) public {
     ...
        for (uint256 i = 0; i < assertion.winners.length; ++i) { <------
            address winner = assertion.winners[i]; //@audit how many winners could be?
            for (uint256 j = 0; j < questionIds.length; ++j) {
                (, address promptStrategy) = SessionManager(sessionManager).questionCommitment(questionIds[j]);
                IPromptStrategy(promptStrategy).recordResult(
                    questionIds[j], winner, assertion.totalXPs[i], assertion.totalTimes[i]
                );
            }
         ...
    }
LOW severity: `SessionManager::cancelGameIfCreatorMissing, endGame` could revert due to out of gas if there are too many question in a game. **Description:** Since there are no restrictions on the number of questions a game can support, a game could have so many questions that it causes `SessionManager::cancelGameIfCreatorMissing, endGame` to revert due to out-of-gas errors.

* `endGame` iterates over all questions:

* so does...

Vulnerable code:
function endGame(uint256 _gameId) external onlyState(_gameId, SessionState.Ongoing) {
        require(block.timestamp >= games[_gameId].endTime, GameIsNotEnded(games[_gameId].endTime, block.timestamp));
        uint256[] storage questions = gameQuestions[_gameId];
        for (uint256 i = 0; i < questions.length; i++) {
            require(_isRevealed(questions[i]), QuestionNotRevealed(_gameId, questions[i]));
        } <-----------
        games[_gameId].state = SessionState.Ended;
        emit GameEnded(_gameId);
    }

function cancelGameIfCreatorMissing(uint256 _gameId) external {
        require(
            games[_gameId].state != SessionState.Cancelled,
            InvalidGameState(SessionState.Cancelled, games[_gameId].state)
        );
        require(
            games[_gameId].state != SessionState.Concluded,
            InvalidGameState(SessionState.Concluded, games[_gameId].state)
        );
        require(block.timestamp >= games[_gameId].endTime, GameIsNotEnded(games[_gameId].endTime, block.timestamp));
        uint256[] storage questions = gameQuestions[_gameId];
        for (uint256 i = 0; i < questions.length; i++) { <-------

            if (!_isRevealed(questions[i])) {
                games[_gameId].state = SessionState.Cancelled;
                emit GameCancelled(_gameId);
                return;
            }
        }
        revert GameWaitingForConclusion(_gameId);
    }
HIGH severity: `DepositManager::sponsorGame` should revert if the game is `Cancelled` or `Concluded`. **Description:** `DepositManager::sponsorGame` doesn't verify the state of the game when accepting sponsorship amounts:

**Impact:** Sponsors can sponsor `Cancelled` or `Concluded` games; in the case of `Concluded` games there is no way to retrieve their tokens. Sponsors can also sponsor...

Vulnerable code:
function sponsorGame(uint256 gameId, uint256 amount) external {
    GamePool storage pool = gamePools[gameId];
    pool.totalCollectedAmount += amount;
    sponsorAmounts[msg.sender][gameId] += amount;
    emit GameSponsored(gameId, msg.sender, pool.token, amount);
    SafeERC20.safeTransferFrom(IERC20(pool.token), msg.sender, address(this), amount);
}
LOW severity: `SessionManager::revealGameQuestion` doesn't validate that input `_questionId` belongs to input `_gameId`. **Description:** `SessionManager::revealGameQuestion` doesn't validate that input `_questionId` belongs to input `_gameId`. It calls `QuestionManager::_revealPrompt` which ends up calling the `revealQuestion` function of the relevant prompt contract, but none of these verify that the input...

Vulnerable code:
function _revealPrompt(uint256 _gameId, uint256 _questionId, bytes memory _prompt, uint256 _salt) internal {
        PromptInitData storage promptInitData = questionCommitment[_questionId];
+       require(
+               _gameId == promptInitData.sessionId,
+               InvalidSessionIdForQuestion(_questionId, _gameId, promptInitData.sessionId)
+       );
HIGH severity: Perform storage updates prior to external calls. **Description:** Most times it is safer to perform storage updates prior to external calls:
* `DepositManager.sol`

* `SessionManager.sol`

**Majestic Games:**
Fixed in commit [6525ee1](https://github.com/Engage-Protocol/engage-protocol/commit/6525ee1547e0b7834cb99a786773bed1861369c7).

**Cyfrin:**....

Vulnerable code:
// switch these around in `_refundEntryFee`
186:        SafeERC20.safeTransfer(IERC20(pool.token), player, pool.ticketPrice);
187:        pool.totalCollectedAmount -= pool.ticketPrice;

// in `joinGame` perform the 2 storage updates prior to calling `_payEntryFee`
311:        _payEntryFee(_gameId, msg.sender);
312:        contestants[_gameId][msg.sender] = true;
313:        games[_gameId].numContestants++;
HIGH severity: Perform input-related checks prior to reading storage. **Description:** Since reading from storage is expensive, it is more efficient to "fail fast" by performing input-related checks prior to reading storage:

* `SessionManager.sol`:

**Majority Games:**
Fixed in commit...

Vulnerable code:
// in `createGame`, perform this check before all the others
require(_promptStrategies.length == _promptHashes.length, ArrayLengthMismatch());
HIGH severity: Wrong maths in getNonSlashRate. **Severity:** High

**Context:** [`MetaManager.sol#L102-L107`](https://github.com/getmetafinance/meta/blob/00bbac1613fa69e4c180ff53515451df4df9f69e/contracts/meta/MetaManager.sol#L102-L107)

**Description:**
The purpose of the `MetaManager::getNonSlashRate` function is to calculate and return the...

Vulnerable code:
function getNonSlashRate(uint8 timeInDays) internal view returns (uint8) {
    uint256 minDays = minExitCycle / Constants.ONE_DAY;
    uint256 slope = ((maxExitCycle - minExitCycle) * Constants.PINT) / (50 * Constants.ONE_DAY);
    uint256 result = 50 + ((timeInDays - minDays) * slope) / Constants.PINT;//@audit it should be divided by slope and multipled by PINT
    return uint8(result) ;
}

function getNonSlashRate(uint8 timeInDays) internal view returns (uint8) {
    uint256 minDays = minExitCycle / Constants.ONE_DAY;
    uint256 slope = ((maxExitCycle - minExitCycle) * Constants.PINT) / (50 * Constants.ONE_DAY);
-    uint256 result = 50 + ((timeInDays - minDays) * slope) / Constants.PINT;
+    uint256 result = 50 + ((timeInDays - minDays) * Constants.PINT) / slope ;
    return uint8(result) ;
}

(commit : 007c1b9183cdb65a500928173608ebff0a5197ef)

function getNonSlashRate(uint8 timeInDays) internal view returns (uint8) {
        uint256 minDays = minExitCycle / Constants.ONE_DAY;
+       uint256 slope = (50 * Constants.PINT * Constants.ONE_DAY ) / (maxExitCycle - minExitCycle);
+       uint256 result = 50 + ((timeInDays - minDays) * slope) / Constants.PINT;
        return uint8(result) ;
    }
HIGH severity: `Boost.setLockStatus()` should update the caller's rewards first.. **Severity:** High

**Context:** [`Boost.sol#L35-50`](https://github.com/getmetafinance/meta/blob/00bbac1613fa69e4c180ff53515451df4df9f69e/contracts/reward/Boost.sol#L35-50)

**Description:**
In the rewards contracts(`StakeRewards.sol`, `MintRewards.sol`), the staking rewards are multiplied by the.....

Vulnerable code:
function earned(address _account) public view returns (uint256) {
        return
            ((balanceOf[_account] *
                getBoost(_account) * //@audit apply boost to all rewards
                (rewardPerToken() - userRewardPerTokenPaid[_account])) / 1e38) +
            rewards[_account];
    }

function setLockStatus(uint256 id) external {
        address _user = msg.sender;
        rewardsManager.refreshReward(_user); //@audit rewardsManager should be added by owner

        LockSetting memory _setting = lockSettings[id];
        LockStatus memory userStatus = userLockStatus[_user];
        if (userStatus.unlockTime > block.timestamp) {
            require(
                userStatus.duration <= _setting.duration,
                "Your lock-in period has not ended, and the term can only be extended, not reduced."
            );
        }
        userLockStatus[_user] = LockStatus(
            block.timestamp + _setting.duration,
            _setting.duration,
            _setting.miningBoost
        );
    }

+ address[] public rewardStores;

+    function addRS(address _rs) external onlyOwner {
+        require(_rs != address(0) && _rs != address(this), "Boost: Invalid address");
+        rewardStores.push(_rs);
+    }

+    function removeRS(uint index) external onlyOwner{
+        require(index <= rewardStores.length -1, "Boost: index out of bounds");
+        rewardStores[index] = rewardStores[rewardStores.length - 1];
+       rewardStores.pop();
+    }

+    function updateRewards(address user) internal {
+        for(uint i=0; i<rewardStores.length ; i++) {
+            IRewards(rewardStores[i]).refreshReward(user);
+        }
+    }

+    function getRSSize() external view returns (uint256) {
+       return rewardStores.length ;
+    }

    function setLockStatus(uint256 id) external {
        address _user = msg.sender;
+        updateRewards(_user);
        LockSetting memory _setting = lockSettings[id];
        LockStatus memory userStatus = userLockStatus[_user];
        if (userStatus.unlockTime > block.timestamp) {
            require(
                userStatus.duration <= _setting.duration,
                "Boost: Your lock-in period has not ended, and the term can only be extended, not reduced."
            );
        }
        userLockStatus[_user] = LockStatus(
            block.timestamp + _setting.duration,
            _setting.duration,
            _setting.miningBoost
        );
    }
HIGH severity: Final price calculation is wrong in decimal. **Severity:** High

**Context:** [`IDO.sol#L107-L113`](https://github.com/getmetafinance/meta/blob/00bbac1613fa69e4c180ff53515451df4df9f69e/contracts/ido/IDO.sol#L107-L113)

**Description:**
The final price is updated as follows in `IDO.setFinalPrice`.

The total supply and total deposits are in...

Vulnerable code:
- price = totalSupply / totalDeposit;
+ price = totalSupply * 1e18 / totalDeposit;

(commit : 007c1b9183cdb65a500928173608ebff0a5197ef)

    function setFinalPrice() internal {
        if(!computedFinalPrice) {
            uint256 totalDeposit = totalDeposits();
            if( totalDeposit > (totalSupply * Constants.PINT / price)) {
+                price = (totalSupply * Constants.PINT) / totalDeposit;
            }
            computedFinalPrice = true;
        }
    }
HIGH severity: Non-accessible function of GMX is used. **Severity:** High

**Context:** [`Helper.sol#L84-89`](https://github.com/getmetafinance/meta/blob/00bbac1613fa69e4c180ff53515451df4df9f69e/contracts/musd/Helper.sol#L84-89)

**Description:**
`Helper::harvest` calls a GMX router’s `compoundForAccount` function but according to GMX code it’s only...

Vulnerable code:
function harvest(address _account) external returns (uint256) {
        router.compoundForAccount(_account);
        gmxRewards.claimForAccount(_account, _account);
        glpRewards.claimForAccount(_account, _account);
        return native.balanceOf(_account);
    }

function compoundForAccount(address _account) external nonReentrant onlyGov {
        _compound(_account);
    }

(commit: 007c1b9183cdb65a500928173608ebff0a5197ef)

+    interface IGLPRewardsRouter {
+        function claimFees() external;
+    }

+    IGLPRewardsRouter public router;


+    function harvest() public {
+        router.claimFees();
+    }
HIGH severity: `MetaManager.unclaimedRewards` should work with shares instead of asset amounts.. **Severity:** Medium

**Context:** [`MetaManager.sol#L174-L197`](https://github.com/getmetafinance/meta/blob/00bbac1613fa69e4c180ff53515451df4df9f69e/contracts/meta/MetaManager.sol#L174-L197)

**Description:**
After the `mUSDManager` calls the `notifyRewardAmount()` function, the rewards are...

Vulnerable code:
function notifyRewardAmount(uint amount) external {
        require(msg.sender == address(mUSDManager), "Fund: Invalid caller");
        if (totalStaked() == 0) {
            /**
             * These rewards are unclaimable by the users
             * these tokens are forever locked in the contract
             * Happens if esMETA balance is zero
             *  a) When dApp - launched before IDO
             *  b) When circulation of esMETA is zero (rare-event)
             */
            unclaimedRewards += amount;//@audit should track with shares
            return;
        }
        require(amount > 0, "amount = 0");
        uint256 share = mUSD.getSharesByMintedMUSD(amount);
        rewardPerTokenStored = rewardPerTokenStored + (share * Constants.PINT) / totalStaked();
    }

    function withdrawToTreasury() external onlyOwner {
        require(unclaimedRewards > 0, "Fund: No locked rewards");
        IERC20 _mUSD = IERC20(address(mUSD));
        _mUSD.transfer(treasury, unclaimedRewards); //@audit should transfer shares
        unclaimedRewards = 0;
    }

function _transfer(address _sender, address _recipient, uint256 _amount) internal virtual {
        uint256 _sharesToTransfer = getSharesByMintedMUSD(_amount);
        _transferShares(_sender, _recipient, _sharesToTransfer);
        emit Transfer(_sender, _recipient, _amount);
        emit TransferShares(_sender, _recipient, _sharesToTransfer);
    }

function notifyRewardAmount(uint amount) external {
        require(msg.sender == address(mUSDManager), "Fund: Invalid caller");
        if (totalStaked() == 0) {
            /**
             * These rewards are unclaimable by the users
             * these tokens are forever locked in the contract
             * Happens if esMETA balance is zero
             *  a) When dApp - launched before IDO
             *  b) When circulation of esMETA is zero (rare-event)
             */
            unclaimedRewards += mUSD.getSharesByMintedMUSD(amount); //@audit add shares
            return;
        }
        require(amount > 0, "amount = 0");
        uint256 share = mUSD.getSharesByMintedMUSD(amount);
        rewardPerTokenStored = rewardPerTokenStored + (share * Constants.PINT) / totalStaked();
    }

    function withdrawToTreasury() external onlyOwner {
        require(unclaimedRewards > 0, "Fund: No locked rewards");
        IERC20 _mUSD = IERC20(address(mUSD));
        _mUSD.transferShares(treasury, unclaimedRewards); //@audit transfer shares
        unclaimedRewards = 0;
    }
HIGH severity: Chainlink price is used without checking validity. **Severity:** Medium

**Context:** [`Helper.sol#L75-L78`](https://github.com/getmetafinance/meta/blob/00bbac1613fa69e4c180ff53515451df4df9f69e/contracts/musd/Helper.sol#L75-L78)

**Description:**
The Meta protocol relies on a Chainlink price oracle to calculate the excess income distributed to all.....

Vulnerable code:
function getPriceOfRewardToken() external view returns (uint256) {
    (,int256 price,,,) = priceFeed.latestRoundData();//@audit chainlink price feed - stale price check is missing
    return (uint256(price) * Constants.PINT) / PRICE_FEED_PRECISION;
}

( roundId, rawPrice, , updateTime, answeredInRound ) = priceFeed.latestRoundData();
require(rawPrice > 0, "Chainlink price <= 0");
require(updateTime != 0, "Incomplete round");
require(answeredInRound >= roundId, "Stale price");

+    function isSequencerActive() internal view returns (bool) {
+        (, int256 answer, uint256 startedAt,,) = sequencer.latestRoundData();
+        if (block.timestamp - startedAt <= GRACE_PERIOD_TIME || answer == 1)
+            return false;
+        return true;
+    }

   function getPriceOfRewardToken() external view returns (uint256) {
        (uint80 roundId,int256 price,,uint256 updateTime, uint80 answeredInRound) = priceFeed.latestRoundData();
+       require(isSequencerActive(), "HLP: Sequencer is down");
+       require(price > 0, "HLP: Invalid chainlink price");
+       require(updateTime > 0, "HLP: Incomplete round");
+       require(answeredInRound >= roundId, "HLP: Stale price");
        return (uint256(price) * Constants.PINT) / PRICE_FEED_PRECISION;
    }
HIGH severity: Some constants wouldn't work as expected.. **Severity:** Medium

**Context:** [`Constants.sol#L5`](https://github.com/getmetafinance/meta/blob/00bbac1613fa69e4c180ff53515451df4df9f69e/contracts/lib/Constants.sol#L5)

**Description:**
Two constants `KEEPER_RATE` and `MAX_KEEPER_RATE` are declared like the below.

But when we use the...

Vulnerable code:
uint256 constant KEEPER_RATE = 0.01e18; //1%
    uint256 constant MAX_KEEPER_RATE = 0.05e18; //5%

if (provider == msg.sender) {
        glp.transfer(msg.sender, reducedGLP);
    } else {
        reward2keeper = (reducedGLP * rates.getKR()) / 110 / Constants.PINT; //@audit divide by 100 again
        glp.transfer(provider, reducedGLP - reward2keeper);
        glp.transfer(msg.sender, reward2keeper);
    }

Constants.sol

library Constants {
    // Base constants
    uint256 constant PINT = 1e18;
    uint256 constant HUNDRED_PERCENT = 100e18; //100

    // Collateral constants
    uint256 constant BAD_COLLATERAL_LIMIT = 150e18; //150
    uint256 constant SAFE_COLLATERAL_LIMIT = 160e18; //160
    uint256 constant SUPER_BAD_COLLATERAL_LIMIT = 125e18; //125
    uint256 constant REDEMPTION_PROVIDER_CR = 100e18; //100

    // Reward constants
    uint256 constant REDEEMER_BOOST_RATE = 20e18; //20% Boost
    uint256 constant MAX_EXCESS_DISTRIBUTION_REWARD = 0.5e18; //0.5%

    // Fee & Rate constants
    uint256 constant MAX_HOLDING_FEE = 1.5e18; //1.5%
    uint256 constant REDEMPTION_FEE = 0.5e18; //0.5%
    uint256 constant KEEPER_RATE = 1e18; //1%
    uint256 constant MAX_KEEPER_RATE = 5e18; //5%
    uint256 constant MAX_TREASURY_FEE = 30e18; //30%

    // Supply constants
    uint256 constant META_MAX_SUPPLY = 100_000_000 * 1e18;
    uint256 constant ESMETA_MAX_SUPPLY = 100_000_000 * 1e18;

    // Constants for Meta Manager & Time periods
    uint256 constant YEAR_IN_SECONDS = 365 days;
    uint256 constant VESTING_PERIOD = 60 days;
    uint256 constant REWARDS_PAYOUT_PERIOD = 30 days;
    uint256 constant ONE_DAY = 1 days;

    // Numerical constant/s
    uint256 constant HUNDERED = 100;
}
HIGH severity: On restaking, funds should be unslashed back. **Severity:** Medium

**Context:** [`MetaManager.sol#L117-L126`](https://github.com/getmetafinance/meta/blob/00bbac1613fa69e4c180ff53515451df4df9f69e/contracts/meta/MetaManager.sol#L117-L126)

**Description:**
In `MetaManager::reStake`, `toMint` is calculated as `getReservedForVesting(caller) +...

Vulnerable code:
function reStake() external updateReward(msg.sender) {
        address caller = msg.sender;
        uint256 toMint = getReservedForVesting(caller) + getClaimable(caller);
+       toMint = (toMint * 100 * Constants.PINT) / (100 - lastSlashRate[caller]);
+       toMint /= Constants.PINT;
        if (toMint > 0) {
            esMeta.mint(caller, toMint);
            unstakeRate[caller] = 0;
            time2fullRedemption[caller] = 0;
            lastSlashRate[caller] = 0;
        }
    }
HIGH severity: Borrower with bad debt gets mint reward. **Severity:** Medium

**Context:** [`MUSDManager.sol#L249`](https://github.com/getmetafinance/meta/blob/00bbac1613fa69e4c180ff53515451df4df9f69e/contracts/musd/MUSDManager.sol#L249), [`MUSDManager.sol#L299`](https://github.com/getmetafinance/meta/blob/00bbac1613fa69e4c180ff53515451df4df9f69e/contrac...

Vulnerable code:
function earned(address _account) public view returns (uint256) {
    return
        ((stakedOf(_account) *
            getBoost(_account) *
            (rewardPerToken() - userRewardPerTokenPaid[_account])) / 1e38) +
        rewards[_account];
}

function stakedOf(address user) public view returns (uint256) {
    return mUSDManager.getBorrowedOf(user);
}

mUSDManager.sol
(commit : 007c1b9183cdb65a500928173608ebff0a5197ef)


    // contract level variable
+    uint256 public totalBadDebt;

    function superLiquidation(
        address provider,
        address onBehalfOf,
        uint256 glpAmount
    ) external whenNotPaused nonReentrant {

...
+        if(deposited[onBehalfOf] == 0 && borrowed[onBehalfOf] > 0) { // Accounting bad debt
+        totalBadDebt += borrowed[onBehalfOf];
+        borrowed[onBehalfOf] = 0;
+    }
...
    }

    // function through debt will be repaid
+ function repayBadDebt(uint256 _amount) public {
+    require(totalBadDebt >= _amount, "Low debt");
+    mUSD.burnShares(msg.sender, mUSD.getSharesByMintedMUSD(_amount), _amount);
+    totalBadDebt -= _amount;
+ }