xanoutas/solidity-security-findings huggingface dataset:
M-01 from previous audit still present
Audit: 2024-03-neobase-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Proof of Concept
[M-01 from previous audit is still present](https://github.com/code-423n4/2023-08-verwa-findings/issues/294). In this issue sponsor said [that fixed it](https://github.com/code-423n4/2023-08-verwa-findings/issues/294#issuecomment-1698755740). I tried to check how it was fixed, but the link doesn't work for me.

But `change_gauge_weight` function [still exists](https://github.com/code-423n4/2024-03-neobase/blob/main/src/GaugeController.sol#L346-L348) which makes it possible to reproduce this. Also `remove_gauge_weight` function is present, that allows [to completely remove gauge](https://github.com/code-423n4/2024-03-neobase/blob/main/src/GaugeController.sol#L378-L380) and i think that fix was to remove `change_gauge_weight` function.

## Impact
Gauge can have bigger weight than was intended by protocol.

## Tools Used
VsCode
## Recommendation
Remove `change_gauge_weight` function.



## Assessed type

Error
Improper Adjustment of Lending Ledger Configuration
Audit: 2024-03-neobase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-neobase/blob/main/src/LendingLedger.sol#L136-L149
https://github.com/code-423n4/2024-03-neobase/blob/main/src/LendingLedger.sol#L173-L185


# Vulnerability details

## Description

The `LendingLedger` utilizes an approximation system (to evaluate the time that has elapsed between two block numbers) which contains adjustable values. Additionally, it permits the governance to adjust the `cantoPerBlock` value of multiple epochs at any time.

The way these values are adjusted is insecure and thus can cause them to retroactively apply to markets that have not yet been updated.

In the case of the `LendingLedger::setBlockTimeParameters`, a notice exists within the `LendingLedger::update_market` function that warns that:

> If this ever drifts significantly, the average block time and/or reference block time & number can be updated. However, update_market needs to be called for all markets beforehand.

I do not consider the warning sufficient, **as the `LendingLedger::setRewards` function illustrates that the problem is not understood accurately**.

Any market that was not updated on **the exact same block** that either rewards or the block time parameters are adjusted will have these adjustments **retroactively applied**, leading to over-estimations or under-estimations of the rewards that should be attributed to the market.

## Impact

Reward measurements for markets that were not updated **in the exact same block** that rewards and/or block-time parameters were re-configured will result in over- or under-estimations, depending on the direction of these configurations.

## Severity Rationalization

Administrator mistakes usually fall under QA / Analysis reports, however, in this circumstance, the mistake is not based on input but rather on the state of the contract. Additionally, there are cases whereby the change cannot be performed securely (i.e. if the number of markets introduced would reach the gas limit if all are attempted to be updated at the same block).

Based on the above, I believe this constitutes a valid operational vulnerability that stems from an improperly coded configuration procedure for both rewards and block time parameters. 

While there is a warning for the `LendingLedger::setBlockTimeParameters` function, it is located in an entirely different function and is insufficient IMO. Even if considered sufficient, **there is absolutely no warning or indication that the same restriction applies for the `LendingLedger::setRewards` function**.

This submission may be split into two distinct ones if the reward-related and block-time-related impacts are considered distinct, however, I grouped them under the same submission as they pertain to the same operation (update of all markets) being absent albeit from two different code segments.

## Proof of Concept

I have coded the following PoC in `foundry` for completeness. Please add the following segment after `LendingLedger.t.sol::setupS
Truncation Exploitation of Partial Transfer System
Audit: 2024-03-neobase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-neobase/blob/main/src/LendingLedger.sol#L105-L113


# Vulnerability details

## Description

The `LendingLedger::sync_ledger` function is meant to permit partial transfers of each user's positions by updating the `amount`, `rewardDebt`, and `secRewardDebt` variables.

In the current system, the partial transfers performed are insecure as **a non-zero `_delta` can be transferred with zero-value `rewardDebt` and `secRewardDebt` increments**.

Specifically, any non-zero `_delta` value that would result in `(_delta * market.accCanotPerShare) / 1e18` to result in a truncation can be transferred multiple times to exploit the truncation.

As the `LendingLedger::claim` function will utilize the **cumulative `user.amount` value**, multiple `_delta` transfers can result in a non-truncated `(user.amount * market.accCanotPerShare) / 1e18` value that would result in **immediate profit as the user would be able to instantly claim the truncated amounts**.

## Impact

A user is able to claim rewards from the `LendingLedger` without any time elapsing and without being eligible for them by taking advantage of debt truncations in the `LendingLedger::sync_ledger` function. 

This exploit can be repeated infinitely to compound the rewards extracted via this mechanism.

## Proof of Concept

I coded a simple PoC in `foundry`, simply add the following function after the `LendingLedger.t.sol::setupStateBeforeClaim` function:

```solidity
function testFreeRewards() public {
    setupStateBeforeClaim();

    // Ensure accCantoPerShare is not 1e18
    vm.prank(lender);
    vm.roll(BLOCK_EPOCH * 5 + 1);
    ledger.claim(lendingMarket);

    // Demonstrate that user can claim but will acquire `0` due to having no balance
    address noBalanceLender = users[2];
    uint256 balanceBefore = address(noBalanceLender).balance;
    vm.prank(noBalanceLender);
    ledger.claim(lendingMarket);
    uint256 balanceAfter = address(noBalanceLender).balance;

    // Ensure user is normally entitled to no reward
    assertEq(balanceBefore, balanceAfter);

    // We calculate the exact amount that we would need to transfer to capture an instant reward
    (uint256 currentAccCantoPerShare, , ) = ledger.marketInfo(
        lendingMarket
    );
    uint256 remainder = currentAccCantoPerShare % 1e18;
    uint256 numberOfTimesForWholeValue = 1e18 / remainder;

    for (uint256 i = 0; i <= numberOfTimesForWholeValue; i++) {
        // We simulate a LiquidityGauge::_afterTokenTransfer between lender and noBalanceLender
        vm.prank(lendingMarket);
        ledger.sync_ledger(lender, -1);

        vm.prank(lendingMarket);
        ledger.sync_ledger(noBalanceLender, 1);
    }

    balanceBefore = address(noBalanceLender).balance;
    vm.prank(noBalanceLender);
    ledger.claim(lendingMarket);
    balanceAfter = address(noBalanceLender).balance;

    uint256 profit = balanceAfter - balanceBefore;

    // Ensure user is normally entitled to no re
Users will have the ability to vote for the gauge weights an infinite number of times, whenever the `VotingEscrow` withdrawals are unlocked
Audit: 2024-03-neobase-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Users will be able to massively increase the gauge weights for particular markets, which in turn will give them the ability to claim much more tokens from the `LendingLedger` than they should be able to

## Proof of Concept
Since the last audit of the codebase here at Code4rena, a new state variable named `unlockOverride` has been added to the `VotingEscrow` contract. By default, its value is set to `false`, but it can also be toggled to `true` through a governance proposal. Once it is set to true, it allows everyone to call the `withdraw` function and withdraw their locked CANTO immediately, no matter if their lock period has passed or not. This is due to the addition of the second condition in the following require statement:

```solidity
require(locked_.end <= block.timestamp || unlockOverride, "Lock not expired");
```

Unfortunately though, this can easily be abused in order to game the gauge weight voting functionality of the `GaugeController` contract. Once the withdrawal of locked tokens is unlocked, malicious users can simply come in and perform the following actions:

1. Create a lock through the `VotingEscrow::createLock` function;
2. Call the `GaugeController::vote_for_gauge_weights` in order to make a vote;
3. Call `VotingEscrow::withdraw` in order to withdraw their funds;
4. Transfer their CANTO to another EoA controlled by them and repeat the first 3 steps

The above 4 steps can be repeated an infinite number of times, as long as `unlockOverride` is equal to `true`. What's worse is that in order for `unlockOverride` to be toggled back to `false`, a new governance proposal for doing so has to go through, meaning that the malicious users will have plenty of time to exploit that vulnerability before the protocol has a chance to react to it. And on a final note, if the malicious users have access to CANTO flash loans, then this means that they will also be able to exploit the voting system even further using them, since the above mentioned actions can be executed within a single transaction.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Do not allow the creation of new locks when the withdrawals are unlocked.

Alternatively, you can also consider allowing users to only decrease their vote weights when the withdrawals are unlocked.








## Assessed type

Other
Wrong gauge weight used during `update_market` in `LendingLedger.sol` contract
Audit: 2024-03-neobase-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The function `update_market` in the `LendingLedger.sol` contract is used to update the market rewards in the contract. The rewards are calculated as the product of the `blockDelta`, the `cantoPerBlock[epoch]` and the `gaugeWeight`. The function also has the ability to update the rewards for the past epochs, in case this function wasn't called for a few epochs in between.

The update of the past is handled by using the iterator `i` to keep track of the block number. It is initialized to the last reward update block and incremented by an epoch every loop until it is at the current epoch.

```solidity
uint256 i = market.lastRewardBlock;
while (i < block.number) {
    uint256 epoch = (i / BLOCK_EPOCH) * BLOCK_EPOCH; // Rewards and voting weights are aligned on a weekly basis
    uint256 nextEpoch = epoch + BLOCK_EPOCH;
    uint256 blockDelta = Math.min(nextEpoch, block.number) - i;
    // Calculate rewards for the `blockDelta` blocks in the past
    i += blockDelta;
}
```

The actual reward calculation step is shown below. It uses the `blockDelta` from before, and if it is updating the rewards in past epochs, `blockDelta` is equal to a full epoch length. After the rewards for the epoch in the past is calculated, `i` is updated to move on to the next epoch.

```solidity
uint256 epochTime = referenceBlockTime +
    ((block.number - referenceBlockNumber) * averageBlockTime) /
    1000;
market.accCantoPerShare += uint128(
    (blockDelta *
        cantoPerBlock[epoch] *
        gaugeController.gauge_relative_weight_write(_market, epochTime)) / marketSupply
);
```

The actual reward computation uses 3 values:

1. `blockDelta`, which is 1 epoch in block numbers
2. `cantoPerBlock`, an admin controlled value
3. `gauge_relative_weight_write`, the gauge weight for that epoch

To find the gauge weight, a timestamp `epochTime` needs to be passed in and the gaugecontroller contract will find the gauge weight corresponding to that timestamp. This is calculated using the expression shown above.

The issue is that `epochTime` is calculated incorrectly. It is calculated as `referenceBlockTime + (block.number - refBlock) * block_time_in_ms`. Since the current `block.number` is used, it gives a good estimate of the current `block.timestamp`. This is because the `referenceBlockTime` is the creation block timestamp and `refBlock` is the creation block number.

However, if we are updating the rewards for the pat epochs, we should instead use the gauge weight for that past epoch, not the current epoch. Using `block.number` gives the current timestamp, and thus the gauge weight of the current epoch. This is incorrect, and should instead use `i`, which will calculate the timestamp at the start of that epoch, and the corresponding gauge weight present then.

Since past epoch rewards are updated based o
LendingLedger.update_market takes wrong weight for markets
Audit: 2024-03-neobase-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Proof of Concept
Users of the system use their VE tokens to vote for some markets. Votes are casted for each next week, which means that each next week market can have different weight.

`LendingLedger.update_market` function loops through the weeks that have passed, since last update. For each week it should fetch [market's weight](https://github.com/code-423n4/2024-03-neobase/blob/main/src/LendingLedger.sol#L83) to correctly calculate part of rewards that market should receive.

The problem is that `epochTime` calculation [uses `block.number](https://github.com/code-423n4/2024-03-neobase/blob/main/src/LendingLedger.sol#L78) for each week, which means that for all previous weeks function will apply current weigh of market and thus rewards calculation will be incorrect.

## Impact
Rewards calculation is incorrect.

## Tools Used
VsCode
## Recommended Mitigation Steps
You need to fetch weight of each week instead.


## Assessed type

Error
Improper Parallel Time System
Audit: 2024-03-neobase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-neobase/blob/main/src/LendingLedger.sol#L80-L83


# Vulnerability details

## Description

The `LendingLedger` system will employ a distinct time system from the `VotingEscrow` and `GaugeController` implementations whereby rewards are denoted per block rather than based on time. 

From an implementation perspective, there is no real reason to implement this distinction as the `LendingLedger` can be simply updated to utilize a reward-per-second system instead.

To work around this discrepancy, the `LendingLedger` implements an imprecise time measurement for evaluating the `epochTime` whenever a market update occurs through `LendingLedger::update_market`.

In detail, the following variables are configured and utilized:

- `referenceBlockTime`: Indicates the time from which a reference data point is drawn from 
- `referenceBlockNumber`: Indicates the block number from which we should count the blocks that have elapsed since the reference block time
- `averageBlockTime`: The average block time per block expressed in milliseconds

**The vulnerability lies in the fact that an overestimation of the time (i.e. a future time) will result in a `IGaugeController::gauge_relative_weight_write` result of `0`**, effectively nullifying the full reward of the epoch which may be up to `1 week` worth of rewards.

Specifically, `GaugeController::_gauge_relative_weight` will yield `0` in the case the total weight of the epoch is `0` (`points_total`), a case that will occur 100% if the input time of the function exceeds the current `block.timestamp` to an extent that it would cause it to flow into the next `WEEK`.

This error does not need to necessarily be a full `WEEK`, as a timestamp at `5 weeks - 1` will fall in week `4` which can be calculated, while a timestamp at `5 weeks` will fall in week `5` which would be in the future and thus not calculate-able.

**It is impossible to programmatically approximate the number of blocks in a particular time period for any blockchain**, meaning that the vulnerability will manifest itself with a high possibility as either an overestimation or underestimation of time elapsed for the number of blocks will occur in the `LendingLedger`.

## TL;DR

An overestimation of time elapsed by the `LendingLedger::update_market` function will lead to the rewards for a particular epoch being `0` even though the gauge may have had a non-zero relative weight due to the weight not having been tracked yet for the miscalculated future timestamp.

## Impact

The non-zero rewards that should have been distributed for the latest epoch will not be processed correctly and would be considered `0`.

## Proof of Concept

I tried implementing a PoC in the `forge` toolkit, however, the test harness of `LendingLedger.t.sol` utilizes a **mock `GaugeController` (`DummyGaugeController`)** that yields a fixed value for the lookup and thus would require **extensive time to re-build the test suite for just thi
When the `unlockOverride` flag is true, users can "freely" vote for gauge weights.
Audit: 2024-03-neobase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-neobase/blob/main/src/VotingEscrow.sol#L357
https://github.com/code-423n4/2024-03-neobase/blob/main/src/VotingEscrow.sol#L295-L311
https://github.com/code-423n4/2024-03-neobase/blob/main/src/VotingEscrow.sol#L315-L350


# Vulnerability details

## Impact
Due to the lack of restrictions on `createLock` and `increaseAmount` when the `unlockOverride` flag is set to true, users can `createLock`/`increaseAmount` on `VotingEscrow`, vote for a gauge inside the `GaugeController` to increase the desired gauge's weight, and then immediately withdraw the locked native token from `VotingEscrow` to boost their desired gauge's weight for "free".

## Proof of Concept

When users lock their native token on `VotingEscrow`, they gain the ability to increase gauge weight on `GaugeController` based on the amount of native token they've locked, at the expense of having to lock the native token for `LOCKTIME` (1825 days) period, or until the governance toggle `unlockOverride` and set it to true.

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

```solidity
    function withdraw() external nonReentrant {
        LockedBalance memory locked_ = locked[msg.sender];
        // Validate inputs
        require(locked_.amount > 0, "No lock");
>>>     require(locked_.end <= block.timestamp || unlockOverride, "Lock not expired");
        require(locked_.delegatee == msg.sender, "Lock delegated");
        // Update lock
        uint256 amountToSend = uint256(uint128(locked_.amount));
        LockedBalance memory newLocked = _copyLock(locked_);
        newLocked.amount = 0;
        newLocked.end = 0;
        newLocked.delegated -= int128(int256(amountToSend));
        newLocked.delegatee = address(0);
        locked[msg.sender] = newLocked;
        newLocked.delegated = 0;
        // oldLocked can have either expired <= timestamp or zero end
        // currentLock has only 0 end
        // Both can have >= 0 amount
        _checkpoint(msg.sender, locked_, newLocked);
        // Send back deposited tokens
        (bool success, ) = msg.sender.call{value: amountToSend}("");
        require(success, "Failed to send CANTO");
        emit Withdraw(msg.sender, amountToSend, LockAction.WITHDRAW, block.timestamp);
    }
```

However, due to the lack of restrictions on calling `createLock` and `increaseAmount` when `unlockOverride` is set to true, users can lock their native token, use it to vote on `GaugeController`, and immediately withdraw the token to manipulate and boost the desired gauge weights.

PoC :

Add the following test inside `GaugeController.t.sol` : 

```solidity
    function testVoteGaugeWeightUnlockOverride() public {
        vm.startPrank(gov);
        gc.add_gauge(gauge1, 0);
        gc.add_gauge(gauge2, 0);
        ve.toggleUnlockOverride();
        vm.stopPrank();

        vm.startPrank(user1);
        ve.createLock{value: 1 ether}(1 ether);
        uint256 nextEpoch = ((block.ti
`update_market` could use wrong `epochTime` when query relative weight from Gauge Controller
Audit: 2024-03-neobase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-neobase/blob/main/src/LendingLedger.sol#L77-L84


# Vulnerability details

## Impact

If `update_market` is called to update several epochs of market rewards data, it will use wrong `epochTime` when when query relative weight from Gauge Controller, causing past epoch's reward can be boosted using recent's gauge weight.

## Proof of Concept

When `update_market` is called, it will update market's reward data (`accCantoPerShare`) by looping trough each epoch from `lastRewardBlock` until `block.number`, which depend on `blockDelta`, `cantoPerBlock` of that epoch and gauge relative weight's of the market from gauge controller.

https://github.com/code-423n4/2024-03-neobase/blob/main/src/LendingLedger.sol#L77-L84

```solidity
    function update_market(address _market) public {
        require(lendingMarketWhitelist[_market], "Market not whitelisted");
        MarketInfo storage market = marketInfo[_market];
        if (block.number > market.lastRewardBlock) {
            uint256 marketSupply = lendingMarketTotalBalance[_market];
            if (marketSupply > 0) {
                uint256 i = market.lastRewardBlock;
                while (i < block.number) {
                    uint256 epoch = (i / BLOCK_EPOCH) * BLOCK_EPOCH; // Rewards and voting weights are aligned on a weekly basis
                    uint256 nextEpoch = epoch + BLOCK_EPOCH;
                    uint256 blockDelta = Math.min(nextEpoch, block.number) - i;
                    // May not be the exact time, but will ensure that it is equal for all users and epochs.
                    // If this ever drifts significantly, the average block time and / or reference block time & number can be updated. However, update_market needs to be called for all markets beforehand.
>>>                 uint256 epochTime = referenceBlockTime +
                        ((block.number - referenceBlockNumber) * averageBlockTime) /
                        1000;
                    market.accCantoPerShare += uint128(
                        (blockDelta *
                            cantoPerBlock[epoch] *
>>>                         gaugeController.gauge_relative_weight_write(_market, epochTime)) / marketSupply
                    );
                    market.secRewardsPerShare += uint128((blockDelta * 1e36) / marketSupply); // Scale by 1e18, consumers need to divide by it
                    i += blockDelta;
                }
            }
            market.lastRewardBlock = uint64(block.number);
        }
    }
```

However, when calculating `epochTime`, which will be provided to `gaugeController.gauge_relative_weight_write`, it will always use the delta from `referenceBlockNumber` to the current `block.number.` This means if `update_market` is called and updates several epochs, it will always use the latest relative weight of that market instead of the actual relative weight at the current epoch time.

https://github.com/code-423n4/2024-03-neobase/blo