xanoutas/solidity-security-findings huggingface dataset:
minRentalDayDivisor can be different between markets and treasury
Audit: 2021-06-realitycards-findings
Severity: MEDIUM

# Handle

gpersoon


# Vulnerability details

## Impact
The minRentalDayDivisor is defined in RCTreasury.sol and copied to each market.
The minRentalDayDivisor can be updated via setMinRental, but then it isn't updated in the already created market.

To calculate the minimum rent time, in function withdrawDeposit of RCTreasury.sol, the latest version of minRentalDayDivisor is used, which could be different than the values in the market.
So the markets will calculate the minimum rent time different.
This could lead to unexpected results

## Proof of Concept
// https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCMarket.sol#L191
function initialize(
     ...
        minRentalDayDivisor = treasury.minRentalDayDivisor();

https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCTreasury.sol#L322
 function withdrawDeposit(uint256 _amount, bool _localWithdrawal)
...
  require( user[_msgSender].bidRate == 0 || block.timestamp - (user[_msgSender].lastRentalTime) > uint256(1 days) / minRentalDayDivisor, "Too soon");
..
 if ( user[_msgSender].bidRate != 0 &&  user[_msgSender].bidRate / (minRentalDayDivisor) >  user[_msgSender].deposit ) {
..

// https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCTreasury.sol#L169
  function setMinRental(uint256 _newDivisor) public override onlyOwner {
        minRentalDayDivisor = _newDivisor;
    }

## Tools Used

## Recommended Mitigation Steps
Either accept and document the risk or change to code to prevent this from happening.
payout doesn't fix isForeclosed state
Audit: 2021-06-realitycards-findings
Severity: MEDIUM

# Handle

gpersoon


# Vulnerability details

## Impact
The function payout of RCTreasury.sol doesn't undo the isForeclosed state of a user.
This would be possible because with a payout a user will receive funds so he can lose his isForeclosed status.

For example the function refundUser does check and update the isForeclosed status.

## Proof of Concept
// https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCTreasury.sol#L429
 function payout(address _user, uint256 _amount)  external override balancedBooks onlyMarkets returns (bool) {
        require(!globalPause, "Payouts are disabled");
        assert(marketPot[msgSender()] >= _amount);
        user[_user].deposit += SafeCast.toUint128(_amount);
        marketPot[msgSender()] -= _amount;
        totalMarketPots -= _amount;
        totalDeposits += _amount;
        emit LogAdjustDeposit(_user, _amount, true);
        return true;
    }

// https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCTreasury.sol#L447 
function refundUser(address _user, uint256 _refund)  external override onlyMarkets {
     ...
        if ( isForeclosed[_user] && user[_user].deposit > user[_user].bidRate / minRentalDayDivisor )  {
            isForeclosed[_user] = false;
            emit LogUserForeclosed(_user, false);
        }

## Tools Used

## Recommended Mitigation Steps
Check and update the isForeclosed state in the payout function
modifier balancedBooks missing in a few functions
Audit: 2021-06-realitycards-findings
Severity: MEDIUM

# Handle

gpersoon


# Vulnerability details

## Impact
Most of the functions of RCTreasury.sol, that manipulate totalDeposits, marketBalance  or totalMarketPots use the modifier balancedBooks.
However the functions refundUser and topupMarketBalance don't use the modifier.
It doesn't hurt to add the extra safeguard.

## Proof of Concept
// https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCTreasury.sol#L124
/// @notice check that funds haven't gone missing during this function call
    modifier balancedBooks {
        _;
        // using >= not == in case anyone sends tokens direct to contract
        require(erc20.balanceOf(address(this)) >= totalDeposits + marketBalance + totalMarketPots,"Books are unbalanced!");
    }

// https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCTreasury.sol#L447
 function refundUser(address _user, uint256 _refund)  external override  onlyMarkets {
        marketBalance -= _refund;
         ...
        totalDeposits += _refund;

// https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCTreasury.sol#L372
    function topupMarketBalance(uint256 _amount) external override {
       ....
        marketBalance += _amount;
    }

## Tools Used

## Recommended Mitigation Steps
Add the modifier balancedBooks  to the functions refundUser and topupMarketBalance
underflow with _timeHeldToIncrement 
Audit: 2021-06-realitycards-findings
Severity: MEDIUM

# Handle

gpersoon


# Vulnerability details

## Impact
In the function _processRentCollection of RCMarket.sol a variable _timeHeldToIncrement is calculated by subtracting two timestamps.
This could have more or less any value.
Later on this is subtracted from cardTimeLimit and timeHeldLimit (see code below). The values of cardTimeLimit and timeHeldLimit will get closer to 0,
However its possible that the subtraction will "overshoot", which will lead to an underflow.
As solidity 8.x is used, a revert will occur and the code will stop.
This is probably not what is desired.

## Proof of Concept
// https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCMarket.sol#L854
function _collectRentAction(uint256 _card)
 uint256 _timeOfThisCollection = block.timestamp;
...
if ....    _timeOfThisCollection = marketLockingTime;
if .....   _timeOfThisCollection = _cardTimeLimitTimestamp;
if ....    _timeOfThisCollection = _timeUserForeclosed;
....
 _processRentCollection(_user, _card, _timeOfThisCollection); // where the rent collection actually happens

// https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCMarket.sol#L1052
function _processRentCollection(address _user,uint256 _card,uint256 _timeOfCollection) {
    ....
        uint256 _timeHeldToIncrement =  (_timeOfCollection - timeLastCollected[_card]);
     ....
            orderbook.reduceTimeHeldLimit(_user, _card, _timeHeldToIncrement);
            cardTimeLimit[_card] -= _timeHeldToIncrement;   // could underflow
        }

// https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCOrderbook.sol#L850
 function reduceTimeHeldLimit(address _user, uint256 _card, uint256 _timeToReduce ) external override onlyMarkets {
        user[_user][index[_user][msgSender()][_card]].timeHeldLimit -= SafeCast.toUint64(_timeToReduce);   // could underflow
    }

## Tools Used

## Recommended Mitigation Steps
Make sure the subtraction doesn't overshoot and assign the value of 0 if it would be negative.
For example with the following code:
        user[_user][index[_user][msgSender()][_card]].timeHeldLimit -= min ( SafeCast.toUint64(_timeToReduce), user[_user][index[_user][msgSender()][_card]].timeHeldLimit )
        cardTimeLimit[_card] -= min ( _timeHeldToIncrement, cardTimeLimit[_card] )
reentrancy-no-eth
Audit: 2021-06-realitycards-findings
Severity: MEDIUM

# Handle

heiho1


# Vulnerability details

## Impact

Function RCMarket#lockMarket() is public and so can be invoked by anyone.  It claims to be called within the context of the autoLock modifier but said modifier is not applied to the function call and so this function can be repeatedly called publicly by anyone.  This could be quite expensive an operation as it collects all rent , change market state, and iterates over all cards for card transfers.  In short it appears this function could cause market misbehavior simply by repeatedly invoking it within a block.

## Proof of Concept

https://github.com/code-423n4/2021-06-realitycards/blob/86a816abb058cc0ed9b6f5c4a8ad146f22b8034c/contracts/RCMarket.sol#L441

## Tools Used

Slither

## Recommended Mitigation Steps

It appears this market should either be restricted to admin/owner access or marked internal and be applied by the modifier.
Locked ether
Audit: 2021-06-realitycards-findings
Severity: MEDIUM

# Handle

heiho1


# Vulnerability details

## Impact

RCMarket overrides NativeMetaTransaction which declares payable executeMetaTransaction
---- This function accepts a functionSignature and calls the function from the userAddress
---- This appears to be a possible attack vector

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

https://github.com/code-423n4/2021-06-realitycards/blob/86a816abb058cc0ed9b6f5c4a8ad146f22b8034c/contracts/lib/NativeMetaTransaction.sol#L31

This function is payable but the the encoded function invocation is arbitrary.  This could potentially lead to locked ether as there is no clear withdrawal semantics.

## Tools Used

Slither

## Recommended Mitigation Steps

If there is no clear reason for the method to be payable then it should not be made payable.  If there is a reason then a withdrawal mechanism should be supported, i.e. 

https://docs.soliditylang.org/en/v0.5.3/solidity-by-example.html#safe-remote-purchase
extra security for changeUberOwner
Audit: 2021-06-realitycards-findings
Severity: MEDIUM

# Handle

gpersoon


# Vulnerability details

## Impact
The function changeUberOwner of RCOrderbook.sol allow you to change the UberOwner.
If you accidentally make a mistake with the _newUberOwner parameter, you can shoot yourself in the foot and are never able to update the UberOwner again.

## Proof of Concept
// https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCOrderbook.sol#L117
    function changeUberOwner(address _newUberOwner) external override {
        require(msgSender() == uberOwner, "Extremely Verboten");
        require(_newUberOwner != address(0));
        uberOwner = _newUberOwner;
    }

## Tools Used

## Recommended Mitigation Steps
Consider using a solution to set an UberOwner candidate, where the new UberOwner should claim ownership, while proving it exits.

For example with the following code:
address uberOwnercandidate;
    function setUberOwnercandidate(address _newUberOwner) external  {
        require(msg.sender == uberOwner, "Extremely Verboten");
        require(_newUberOwner != address(0));
        uberOwnercandidate = _newUberOwner;
    }
    
    function confirmNewUberOwner() external  {
        require(msg.sender == uberOwnercandidate, "Extremely Verboten");
        uberOwner = uberOwnercandidate;
    }
If a gauge that an user has voted for gets removed, their voting power allocated for that gauge will be lost
Audit: 2024-03-neobase-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-neobase/blob/d6e6127e6763b93c23ee95cdf7622fe950d9ed30/src/GaugeController.sol#L224-L229
https://github.com/code-423n4/2024-03-neobase/blob/d6e6127e6763b93c23ee95cdf7622fe950d9ed30/src/GaugeController.sol#L402


# Vulnerability details

## Impact
When a gauge that an user has voted for gets removed by the governance, their voting power allocated for that gauge will be lost forever

## Proof of Concept
Due to the current way the `GaugeController::vote_for_gauge_weights` function is implemented, whenever a given gauge that users have voted for gets removed, all of the voting powers allocated by those users to that gauge will be permanently lost. The same issue has actually already been reported in [this report](https://github.com/code-423n4/2023-08-verwa-findings/issues/62) from the last Code4rena contest of the codebase. And as it can be seen from the following snippet:

```solidity
    function testLostVotingPower() public {
        // prepare
        uint256 v = 10 ether;
        vm.deal(gov, v);
        vm.startPrank(gov);
        ve.createLock{value: v}(v);

        // add gauges
        gc.add_gauge(gauge1, 0);
        gc.add_type("", 0);
        gc.add_gauge(gauge2, 1);

        // all-in on gauge1
        gc.vote_for_gauge_weights(gauge1, 10000);

        // governance removes gauge1
        gc.remove_gauge_weight(gauge1);
        gc.remove_gauge(gauge1);

        // cannot vote for gauge2
        vm.expectRevert("Used too much power");
        gc.vote_for_gauge_weights(gauge2, 10000);

        // cannot remove vote for gauge1
        vm.expectRevert("Gauge not added"); // @audit remove after mitigation
        gc.vote_for_gauge_weights(gauge1, 0);

        // cannot vote for gauge2 (to demonstrate again that voting power is not removed)
        vm.expectRevert("Used too much power");  // @audit remove after mitigation
        gc.vote_for_gauge_weights(gauge2, 10000);
    }
```

the recommended fix from that report has actually been implemented. However, there are two other changes there as well. The `isValidGauge` mapping has been replaced with a new one named `gauge_types_`, which practically serves the same purpose as the old one in the context of this snippet. More importantly though, a new require statement has been added, as it can be seen on the last code line of the snippet, that checks whether the gauge type is greater than 0 and reverts elsewise. Since the gauge type for a given gauge address can only be 0 in the case where the gauge does not actually exist (i.e. it has been removed or it was never created in the first place), this means that the implemented fix will no longer work and because of that the issue is once again present in the current implementation of the `GaugeController`.

The following coded PoC, which is a modification of the one in the above linked report verifies the existence of the issue:

```solidity
    function testLostVotingPower() public {
        //
Users cannot withdrawing votes from removed gauges
Audit: 2024-03-neobase-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-neobase/blob/main/src/GaugeController.sol#L385-L467


# Vulnerability details

## Impact
When gauges are removed, users cannot withdraw votes from the removed gauges, causing user's voting power to become stuck.

## Proof of Concept

From the documentation and input validation, `vote_for_gauge_weights` allow users to provide `_user_weight` to 0 and withdrawing their votes from gauges, especially if the gauge is removed from the system.

https://github.com/code-423n4/2024-03-neobase/blob/main/src/GaugeController.sol#L387

```solidity
  function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external {
      require(_user_weight >= 0 && _user_weight <= 10_000, "Invalid user weight");
>>>   require(_user_weight == 0 || gauge_types_[_gauge_addr] != 0, "Can only vote 0 on non-gauges"); // We allow withdrawing voting power from invalid (removed) gauges
      VotingEscrow ve = votingEscrow;
      (
          ,
          /*int128 bias*/
          int128 slope_, /*uint256 ts*/

      ) = ve.getLastUserPoint(msg.sender);
      require(slope_ >= 0, "Invalid slope");
      uint256 slope = uint256(uint128(slope_));
      uint256 lock_end = ve.lockEnd(msg.sender);
      uint256 next_time = ((block.timestamp + WEEK) / WEEK) * WEEK;
      require(lock_end > next_time, "Lock expires too soon");

>>>   int128 gauge_type = gauge_types_[_gauge_addr] - 1;
>>>   require(gauge_type >= 0, "Gauge not added");
      // ...
}
```

However, later in the execution of `vote_for_gauge_weights`, it will access `gauge_types_` and ensure that the gauge exists `(gauge_type >= 0)`. This will prevent users from withdrawing their votes from removed gauges.

## Tools Used

Manual review

## Recommended Mitigation Steps

Add a conditional path, if `gauge_type` does not exist, consider updating the user's `vote_user_power` based on the `old_slope` and `new_slope` (which is 0), and return early.


## Assessed type

Error
In case a gauge weight reduction is performed via `GaugeController::change_gauge_weight`, it is possible that all functions will stop functioning permanently for that gauge
Audit: 2024-03-neobase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-neobase/blob/d6e6127e6763b93c23ee95cdf7622fe950d9ed30/src/GaugeController.sol#L189


# Vulnerability details

## Impact
If the weight of a given gauge has been manually changed using the `GaugeController::change_gauge_weight` function, all functions related to that gauge might become permanently DoSed

## Proof of Concept
This issue has been reported in [this](https://github.com/code-423n4/2023-08-verwa-findings/issues/206) and [this](https://github.com/code-423n4/2023-08-verwa-findings/issues/386) report from the previous Code4rena contest of the codebase. However, by the looks of it, no fix has been applied in order to mitigate it, so it is still present.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Whenever `pt.slope` is less than `d.slope`, set the value of `pt.slope` to 0:

```diff
        if (pt.bias > d_bias) {
            pt.bias -= d_bias;
            uint256 d_slope = changes_weight[_gauge_addr][t];
-           pt.slope -= d_slope;
+           if(pt.slope >= d.slope) {
+               pt.slope -= d_slope;
+           } else {
+               pt.slope = 0;
+           }
        } else {
            pt.bias = 0;
            pt.slope = 0;
        }
```


## Assessed type

Under/Overflow
`GaugeController::remove_gauge` will always revert whenever the gauge it's being called for has any weight attached to it
Audit: 2024-03-neobase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-neobase/blob/d6e6127e6763b93c23ee95cdf7622fe950d9ed30/src/GaugeController.sol#L226-L227


# Vulnerability details

## Impact
`GaugeController::remove_gauge` will always revert when it is called for gauges with weight that is != 0

## Proof of Concept
The current implementation of the `GaugeController::remove_gauge` function has a major flaw within it. Because it erases the gauge type of the gauge being removed prior to calling `_remove_gauge_weight`, it will always revert whenever it is called for gauges that have a weight value different from 0. This is due to the fact that in `_remove_gauge_weight` there are subtractions being made to gauge type mappings (where the gauge type key being used will be -1, since the gauge type for the gauge has already been erased) using values from gauge mappings.

```solidity
    function _remove_gauge_weight(address _gauge) internal {
        int128 gauge_type = gauge_types_[_gauge] - 1;
        uint256 next_time = ((block.timestamp + WEEK) / WEEK) * WEEK;

        uint256 old_weight_bias = _get_weight(_gauge);
        uint256 old_weight_slope = points_weight[_gauge][next_time].slope;
        uint256 old_sum_bias = _get_sum(gauge_type);

        points_weight[_gauge][next_time].bias = 0;
        points_weight[_gauge][next_time].slope = 0;

        uint256 new_sum = old_sum_bias - old_weight_bias;
        points_sum[gauge_type][next_time].bias = new_sum;
        points_sum[gauge_type][next_time].slope -= old_weight_slope;
        // We have to cancel all slope changes (gauge specific and global) that were caused by this gauge
        // This is not very efficient, but does the job for a governance function that is called very rarely
        for (uint256 i; i < 263; ++i) {
            uint256 time_to_check = next_time + i * WEEK;
            uint256 gauge_weight_change = changes_weight[_gauge][time_to_check];
            if (gauge_weight_change > 0) {
                changes_weight[_gauge][time_to_check] = 0;
                changes_sum[gauge_type][time_to_check] -= gauge_weight_change;
            }
        }
    }
```

Although an argument can be made that this issue can be avoided by calling `GaugeController::remove_gauge_weight` prior to calling `GaugeController::remove_gauge`, it can still prove to be quite problematic in an event where an urgent removal of a given gauge is required, but the initial `remove_gauge` call for doing so gets reverted, since the governance didn't know about the issue and the proper way of bypass it up until that point. In that case, a separate new governance proposal will have to be executed in order to remove the gauge, which will take a significant amount of time.

The following short coded PoC demonstrates the issue:

```solidity
    function testRemoveGaugeArithmeticUnderflow() public {
        uint256 v = 10 ether;
        vm.deal(gov, v);
        vm.startPrank(gov);
        ve.createLock{value: v}(v);

        gc.add_gaug
`LendingLedger.sol`:`setRewards` can have unpredictable behavior if reward of the current epoch or the past is changed
Audit: 2024-03-neobase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-neobase/blob/d6e6127e6763b93c23ee95cdf7622fe950d9ed30/src/LendingLedger.sol#L140-L149


# Vulnerability details

## Impact

The function `setRewards` is used to set the canto rewards awarded per block by a gauge. This is controlled by the governance, which is a DAO. The function take a range of block numbers, and sets the canto reward rate between those epochs.

```solidity
function setRewards(
  uint256 _fromEpoch,
  uint256 _toEpoch,
  uint256 _amountPerBlock
) external onlyGovernance {
  require(_fromEpoch % BLOCK_EPOCH == 0 && _toEpoch % BLOCK_EPOCH == 0, "Invalid block number");
  for (uint256 i = _fromEpoch; i <= _toEpoch; i += BLOCK_EPOCH) {
    cantoPerBlock[i] = _amountPerBlock;
  }
}

```

The issue is that there is no validation of the range of epochs. So users can even vote to change the rewards for epochs in the past. This can lead to unpredictable/undefined behavior of markets.

For example, lets say there are two markets each with 50% gauge weight, thus implying they will receive the same rewards. Say the governance changes the reward rate for the range of epochs 5e6 to 10e6 to 1000 canto/block from the older value of 500 canto/block. Lets assume the current block number corresponds to the epoch 6e6. Also lets assume market A has ben recently updated, but market B hasnt been updated since the last epoch.

Now, if `update_market` is called, Market A will calculate rewards for the epoch 6e6 as 1000 canto/block, but Market B will calculate rewards for both epochs 5e6 and 6e6 as 1000 canto/block. In this scenario, due to Market B having not been updated recently, it calculates the past rewards differently than Market A. This can lead to unpredictable reward calculations of the markets.

## Proof of Concept

Lets assume the current epoch is 6e6, and the governance changes the reward rate for the epochs 5e6 to 10e6 to 1000 canto/block from the older value of 500 canto/block.

Also, assume there are two markets with equal gauge weights, and Market A has been updated recently, but Market B hasnt been updated since the last epoch.

1. Call `update_market` on both markets
2. Market A was updated after epoch 5e6, so it has calculated rewards for the 5e6 epoch at 500 canto/block.
3. Market B was not updated and calculates the rewards for the 5e6 epoch now, which is 1000 canto/block.
4. Reward calculations of Market A and Market B are different for the 5e6 epoch.

This function is not admin-controlled and is instead controlled by a DAO, so remediation can be slow, and delays can be possible.

## Tools Used

Manual Review

## Recommended Mitigation Steps

Add a check so that the reward rates for only future epochs can be changed.



## Assessed type

Math