xanoutas/solidity-security-findings huggingface dataset:
Voters from VotingEscrow can vote infinite times in vote_for_gauge_weights() of GaugeController
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/main/src/GaugeController.sol#L211
https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L356


# Vulnerability details

## Impact
Delegate mechanism in `VotingEscrow` allows infinite votes in `vote_for_gauge_weights()` in the `GaugeController`. Users can then, for example, claim more tokens in the `LendingLedger` in the market that they inflated the votes on.

## Proof of Concept
`VotingEscrow` has a delegate mechanism which lets a user delegate the voting power to another user. 
The `GaugeController` allows voters who locked native in `VotingEscrow` to vote on the weight of a specific gauge.

Due to the fact that users can delegate their voting power in the `VotingEscrow`, they may vote once in a gauge by calling `vote_for_gauge_weights()`, delegate their votes to another address and then call again `vote_for_gauge_weights()` using this other address.

A POC was built in Foundry, add the following test to `GaugeController.t.sol`:
```solidity
function testDelegateSystemMultipleVoting() public {
    vm.deal(user1, 100 ether);
    vm.startPrank(gov);
    gc.add_gauge(user1);
    gc.change_gauge_weight(user1, 100);
    vm.stopPrank();

    vm.deal(user2, 100 ether);
    vm.startPrank(gov);
    gc.add_gauge(user2);
    gc.change_gauge_weight(user2, 100);
    vm.stopPrank();

    uint256 v = 10 ether;

    vm.startPrank(user1);
    ve.createLock{value: v}(v);
    gc.vote_for_gauge_weights(user1, 10_000);
    vm.stopPrank();

    vm.startPrank(user2);
    ve.createLock{value: v}(v);
    gc.vote_for_gauge_weights(user2, 10_000);
    vm.stopPrank();

    uint256 expectedWeight_ = gc.get_gauge_weight(user1);

    assertEq(gc.gauge_relative_weight(user1, 7 days), 50e16);

    uint256 numDelegatedTimes_ = 20;

    for (uint i_; i_ < numDelegatedTimes_; i_++) {
        address fakeUserI_ = vm.addr(i_ + 27); // random num
        vm.deal(fakeUserI_, 1);

        vm.prank(fakeUserI_);
        ve.createLock{value: 1}(1);

        vm.prank(user1);
        ve.delegate(fakeUserI_);

        vm.prank(fakeUserI_);
        gc.vote_for_gauge_weights(user1, 10_000);
    }

    // assert that the weight is approx numDelegatedTimes_ more than expected
    assertEq(gc.get_gauge_weight(user1), expectedWeight_*(numDelegatedTimes_ + 1) - numDelegatedTimes_*100);

    // relative weight has been increase by a lot, can be increased even more if wished
    assertEq(gc.gauge_relative_weight(user1, 7 days), 954545454545454545);
}
```

## Tools Used
Vscode, Foundry

## Recommended Mitigation Steps
The vulnerability comes from the fact that the voting power is fetched from the current timestamp, instead of n blocks in the past, allowing users to vote, delegate, vote again and so on. Thus, the voting power should be fetched from n blocks in the past. 

Additionaly, note that this alone is not enough, because when the current block reaches n blocks in the future, the votes can be replayed again by
Users can receive full epoch reward for only depositing in the last block of the epoch
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/498a3004d577c8c5d0c71bff99ea3a7907b5ec23/src/LendingLedger.sol#L135


# Vulnerability details

## Impact
Users can expose themselves to only a small fraction of the risk involved with being deposited while still receiving 100% of the rewards, or even doubling their rewards for a given amount of liquidity.

## Proof of Concept
When a deposit is made to a lending market, `LendingLedger.sync_ledger` is called by the market, syncing their balance at the *current epoch*. 

```solidity
// sync_ledger()
uint256 currEpoch = (block.timestamp / WEEK) * WEEK;
int256 updatedLenderBalance = int256(lendingMarketBalances[lendingMarket][_lender][currEpoch]) + _delta;
require(updatedLenderBalance >= 0, "Lender balance underflow"); // Sanity check performed here, but the market should ensure that this never happens
lendingMarketBalances[lendingMarket][_lender][currEpoch] = uint256(updatedLenderBalance);
```

When users later `claim`, it pays them out if they had a position during that epoch according to `lendingMarketBalances`.

```solidity
// claim()
for (uint256 i = claimStart; i <= claimEnd; i += WEEK) {
	uint256 userBalance = lendingMarketBalances[_market][lender][i];
	uint256 marketBalance = lendingMarketTotalBalance[_market][i];
	RewardInformation memory ri = rewardInformation[i];
	require(ri.set, "Reward not set yet"); // Can only claim for epochs where rewards are set, even if it is set to 0
	uint256 marketWeight = gaugeController.gauge_relative_weight_write(_market, i); // Normalized to 1e18
	cantoToSend += (marketWeight * userBalance * ri.amount) / (1e18 * marketBalance); // (marketWeight / 1e18) * (userBalance / marketBalance) * ri.amount;
}
```

This means that users receive the full epoch reward even if they had only deposited at the very end of the epoch.

This allows malicious users to deposit at the end of epochs and withdraw at the start, later receiving the same amount of rewards as if they held their deposit the entire time. They can repeat this for every epoch, only being exposed to the associated risk for a couple blocks per epoch, while receiving the same amount of rewards as someone being exposed to risk for the full duration.

Furthermore, they could alternate their liquidity between markets such that they get the full reward of having their full amount of liquidity deposited on both markets, effectively doubling their intended reward for the amount of liquidity at risk.

As a result of attackers doubling their rewards and the simplicity of this attack, it's likely to become commonplace, leading to unknowing users being at a significant disadvantage.

## Recommended Mitigation Steps
It's recommended that deposits only set the user's balance for the next epoch while withdrawals still set the user's balance for the current epoch. For example,

```
// sync_ledger()
uint256 currEpoch = (block.timestamp / WEEK) * WEEK;
uint256 nextEpoch = ((block.timestamp + WEEK) / WEEK) * WEEK;
Malicious user can drastically increase their voting power by delegate to another account managed by self with a dust amount in lock
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L370


# Vulnerability details

## Impact
A user's voting power by design decays over time. However, a malicious user can easily bypass the decay and drastically increases their voting power by simply using another account to create a dust lock and delegate to the dust lock. 

I think this is high severity due to easy and cheap manipulation.

## Proof of Concept
In VotingEscrow.sol `delegate()`, when a user delegates to another user's lock, the amount of their lock will be added to the other user's lock but the delegatee gets to keep their own lock expiration time `toLock.end`. 

```solidity
//VotingEscrow.sol
   function delegate(address _addr) external nonReentrant {
        LockedBalance memory locked_ = locked[msg.sender];
        // Validate inputs
        require(locked_.amount > 0, "No lock");
        require(locked_.delegatee != _addr, "Already delegated");
        // Update locks
        int128 value = locked_.amount;
        address delegatee = locked_.delegatee;
        LockedBalance memory fromLocked;
        LockedBalance memory toLocked;
        locked_.delegatee = _addr;
        if (delegatee == msg.sender) {
            // Delegate
            fromLocked = locked_;
 |>         toLocked = locked[_addr];
...
        require(toLocked.end >= fromLocked.end, "Only delegate to longer lock");
        _delegate(delegatee, fromLocked, value, LockAction.UNDELEGATE);
        _delegate(_addr, toLocked, value, LockAction.DELEGATE);
}
    function _delegate(
        address addr,
        LockedBalance memory _locked,
        int128 value,
        LockAction action
    ) internal {
|>       LockedBalance memory newLocked = _copyLock(_locked);
        if (action == LockAction.DELEGATE) {
|>          newLocked.delegated += value;
            emit Deposit(addr, uint256(int256(value)), newLocked.end, action, block.timestamp);
        } else {

```

Because `toLock.end` doesn't change, this effectively means that with the same amount of delegated token, delegatee will have drastically more voting power compared to the owner if the delegatee's lock expired further down in the future. In addition, there is no minimum value to create a lock. So this allows an attack vector where a malicious user can simply create a new lock with a different address and deposit a dust amount. Then they can simply delegate their dust lock to have more voting power and vote from their dust lock account.

See POC here:
(1) user1 creates a lock with 1000 ether locked.
(2) 3 years passed their voting power decayed. 
(3) To counter the decay, user1 creates a dust lock with 1 wei, by using another address (user2) that they managed.
(4) user1 delegated to user2 address.
(5) user1 votes for gauge1's weight from the user 2 account they own and take advantage of drastically increased voting power.

Test code is added in GaugeController.t.sol. There are two
Risk of silent overflow in Voting Escrow contract
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L300
https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L305
https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L317


# Vulnerability details

## Impact

The protocol allows the user to set the `uint128` variable `amount` & `delegated` in the struct `LockedBalance`. This variable represents the amount of tokens available to be used in the protocol. The user can set this variable to be as high as possible. The problem with this however, is that the function `increaseAmount` adds the amount of tokens to the variable `newLocked.amount` & `newLocked.delegated`. So it's possible that calling & adding tokens will overflow, causing the buy function to revert.

## Proof of Concept

```solidity
File: VotingEscrow.sol

288                function increaseAmount(uint256 _value) external payable nonReentrant {
                   ....................

300                newLocked.amount += int128(int256(_value));
                   ....................

305                newLocked.delegated += int128(int256(_value));
                   ....................

317                newLocked.delegated += int128(int256(_value));
```

The user set this to any uint128 number.
This creates a problem where say an user sets the initial amount to `type(uint128).max`, and later sometime in the future decides to increase the number of tokens because the varialbe `value` is of type `uint256` & calls the function increaseAmount.
This casting is performed without first checking that the values being cast can fit into the lower type. As a result, there is a risk of a silent overflow occurring during the casting process. 
Any input amount greater than 0 will cause `newLocked.amount` or `newLocked.delegated` to overflow, causing the function to revert.

## Tools Used

Manual Review

## Recommended Mitigation Steps

A possible mitigation would be to add a requirement to not add tokens if it's going to overflow.
Add the check shown below to each case to avoid this scenario.

```solidity
+           if (value > type(uint128).max) revert Overflow();
300         newLocked.amount += int128(int256(_value));
```


## Assessed type

Under/Overflow
Algorithm to fill gauge weights is not properly ran
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/498a3004d577c8c5d0c71bff99ea3a7907b5ec23/src/GaugeController.sol#L91-L114


# Vulnerability details

## Impact

The algorithm to fill gauge weights in `_get_weight()` does not do anything in most common situations where governance did not intervene to overwrite gauge weights.

This is a problem to fill historical data for missed updates for any gauges during a week. The value of 0 will be returned when querying about the gauge in `vote_for_gauge_weights()` and the wrong vote values may be computed for any gauge missing a week of updates. The value computed in `gauge_relative_weight_write()` for gauges that missed an update will be incorrect for historical values.

## Proof of Concept

The algorithm in `_get_weight()` starts by checking if `time_weight[_gauge_addr] > 0` and does nothing if that is not the case:

```solidity
    function _get_weight(address _gauge_addr) private returns (uint256) {
        uint256 t = time_weight[_gauge_addr];
        if (t > 0) {
            ...
        } else {
            return 0;
        }
    }
```

This value starts at 0 when a gauge is added:

```solidity
    function add_gauge(address _gauge) external onlyGovernance {
        require(!isValidGauge[_gauge], "Gauge already exists");
        isValidGauge[_gauge] = true;
        emit NewGauge(_gauge);
    }
```

The value is only updated by the `_get_weight()` function (when it already is non-zero), or in `_change_gauge_weight()`

```solidity
    function _change_gauge_weight(address _gauge, uint256 _weight) internal {
        ...
        uint256 next_time = ((block.timestamp + WEEK) / WEEK) * WEEK;
        ...
        time_weight[_gauge] = next_time;
        ...
    }
```

The `_change_gauge_weight()` function is only called in functions protected by `onlyGovernance` used to overwrite a gauge weight or remove a gauge:

```solidity
    /// @notice Remove a gauge, only callable by governance
    /// @dev Sets the gauge weight to 0
    /// @param _gauge The gauge address
    function remove_gauge(address _gauge) external onlyGovernance {
        require(isValidGauge[_gauge], "Invalid gauge address");
        isValidGauge[_gauge] = false;
        _change_gauge_weight(_gauge, 0);
        emit GaugeRemoved(_gauge);
    }

    ...

    /// @notice Allows governance to overwrite gauge weights
    /// @param _gauge Gauge address
    /// @param _weight New weight
    function change_gauge_weight(address _gauge, uint256 _weight) public onlyGovernance {
        _change_gauge_weight(_gauge, _weight);
    }
```

Overall time_weight[_gauge_addr] is never set to a non-zero value under normal circumstances.

## Tools Used

Manual review, testing

## Recommended Mitigation Steps

When adding a gauge set the value of `time_weight[_gauge_addr]` to current epoch.





## Assessed type

Context
Delegatee can prevent delegator from delegating to someone else and prevent him from withdrawing
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L384


# Vulnerability details

## Details
Couple of points to note, 
1. While delegating to someone, there'a a condition in [`delegate()`](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L384), which allows delegation to only longer lock, i.e. Delegator can only delegate to a longer lock, i.e. `to.end>from.end`

[`VotingEscrow.delegate#L384`](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L384)
```
    require(toLocked.end >= fromLocked.end, "Only delegate to longer lock");
```
2. Withdraw is only possible when the `delegatee == msg.sender`


[`VotingEscrow.withdraw#L331`](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L331)
```
    require(locked_.delegatee == msg.sender, "Lock delegated");
```
3. [`increaseAmount`](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L301) resets the lock end to 5 years

[`VotingEscrow.increaseAmount#L301`](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L301)

```
    newLocked.end = _floorToWeek(block.timestamp + LOCKTIME);
```
## Impact
- Delegatee can front-run the delegator's `delegate()` transaction with `increaseAmount` and prevent him from delegating to someone else. Thus, keeping the voting power to himself.
- Delegatee can front-run the delegator's `withdraw()` transaction  with `increaseAmount` and prevent him from withdrawing `Canto`. Thus again, keeping the voting power to himself.

## Proof of Concept
Let's see how Bob(malicious user) can prevent Alice from withdrawing. For simplicity let's assume the Locking period of 10 days
- Alice and Bob creates lock
  - Alice.locked.end = 10 days
  - Bob.locked.end = 10 days
- Alice delegates to Bob
  - Bob.locked.end = 10 days
  - Alice.locked.end = 10 days
- After 9 days, 
  - Bob.locked.end = 1 days
  - Alice.locked.end = 1 days
  - Alice now wants to withdraw her Cantonext day. So she decides to undelegate(delegate to herself) which is necessary for withdraw. 
  - She sends `delegate(Alice.address)` transaction. 
  - Bob see this txn and decide to frontrun it with [`increaseAmount(1)`](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L288) passing 1 wei of canto as msg.value
    - This results in reseting of bob's lock end
    - Bob.locked.end = 10 days
   - Now Alice's delegate txn will revert due to `to.end > from.end == false`(1 day > 10 days == false)
- And thus she won't be able to withdraw her Canto
## Tools Used
Manual Review
## Recommended Mitigation Steps
I am not 100% sure what's the purpose of this condition in the [`delegate`](https://github.com/cod
Possible to vote for gauge weights multiple times with the same vote escrowed tokens
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/498a3004d577c8c5d0c71bff99ea3a7907b5ec23/src/GaugeController.sol#L211


# Vulnerability details

## Impact
Users can reuse their voting power by voting, then delegating to an account they control, and voting again, repeating this process to have virtually infinite voting power.

## Proof of Concept
When a user creates a lock, it `_checkpoint`s the slope and lock end, which is later used in `GaugeController.vote_for_gauge_weights` to determine the user's bias and increase the gauge weight and sum accordingly.

```solidity
// vote_for_gauge_weights()
(
	,
	/*int128 bias*/
	int128 slope_, /*uint256 ts*/

) = ve.getLastUserPoint(msg.sender);

...

uint256 lock_end = ve.lockEnd(msg.sender);

...

VotedSlope memory new_slope = VotedSlope({
	slope: (slope * _user_weight) / 10_000,
	end: lock_end,
	power: _user_weight
});
```

We can see in `VotingEscrow._checkpoint`, that the user `Point` is calculated using the delegated amount.

```solidity
// _checkpoint()
if (_newLocked.end > block.timestamp && _newLocked.delegated > 0) {
	userNewPoint.slope = _newLocked.delegated / int128(int256(LOCKTIME));
	userNewPoint.bias = userNewPoint.slope * int128(int256(_newLocked.end - block.timestamp));
}

...

userPointHistory[_addr][uEpoch + 1] = userNewPoint;
```

The problem with all this is that there is nothing to invalidate a users prior votes once they delegate. As a result, they can delegate to an account they control, `_checkpoint`ing that accounts' `Point`, which is then used when they vote with `vote_for_gauge_weights` from the delegated account, thereby effectively doubling their voting power.

Furthermore, the user can then repeat this process by delegating a new account they control and voting yet again, ad infinitum.

## Recommended Mitigation Steps
It's not clear how we can solve this problem without removing delegation logic unless we were to disable delegation for users who have an existing voting weight or who have previously delegated. It is otherwise very difficult to keep track of the share of voting weight of every user throughout every delegated vote.


## Assessed type

Other
`_get_sum` and `_get_weight` like function can run out of gas
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/main/src/GaugeController.sol#L66
https://github.com/code-423n4/2023-08-verwa/blob/main/src/GaugeController.sol#L91


# Vulnerability details

## Impact
 codebase relies on several loops that can iterate 500 times with costly gas consumption. This design is error-prone and may cause the contract to be trapped because it runs out of gas.

functions like `_get_sum`, `_get_weight` , `_checkpoint` in `VotingEscrow.sol` iterate multiple times and changing state( consume high gas ) i.e reading and writing state variables.

These loops are executed on high frequency.However, it's unlikely there will be a long period of time in which these function are not called. but if it does come in such situation contract will be trapped.

## Proof of Concept
```solidity

File : src/GaugeController.sol
 function _get_sum() internal returns (uint256) {
        uint256 t = time_sum;
        Point memory pt = points_sum[t];
        for (uint256 i; i < 500; ++i) {
            if (t > block.timestamp) break;
            t += WEEK;
            uint256 d_bias = pt.slope * WEEK;
            if (pt.bias > d_bias) {
                pt.bias -= d_bias;
                uint256 d_slope = changes_sum[t];
                pt.slope -= d_slope;
            } else {
                pt.bias = 0;
                pt.slope = 0;
            }
            points_sum[t] = pt;
            if (t > block.timestamp) time_sum = t;
        }
        return pt.bias;
    }

function _get_weight(address _gauge_addr) private returns (uint256) {
        uint256 t = time_weight[_gauge_addr];
        if (t > 0) {
            Point memory pt = points_weight[_gauge_addr][t];
            for (uint256 i; i < 500; ++i) {
                if (t > block.timestamp) break;
                t += WEEK;
                uint256 d_bias = pt.slope * WEEK;
                if (pt.bias > d_bias) {
                    pt.bias -= d_bias;
                    uint256 d_slope = changes_weight[_gauge_addr][t];
                    pt.slope -= d_slope;
                } else {
                    pt.bias = 0;
                    pt.slope = 0;
                }
                points_weight[_gauge_addr][t] = pt;
                if (t > block.timestamp) time_weight[_gauge_addr] = t;
            }
            return pt.bias;
        } else {
            return 0;
        }
    }

```
```solidity
File : src/VotingEscrow.sol
function _checkpoint(
        address _addr,
        LockedBalance memory _oldLocked,
        LockedBalance memory _newLocked
    ) internal {
        Point memory userOldPoint;
        Point memory userNewPoint;
        int128 oldSlopeDelta = 0;
        int128 newSlopeDelta = 0;
        uint256 epoch = globalEpoch;

        if (_addr != address(0)) {
            // Calculate slopes and biases
            // Kept at zero when they have to
            if (_oldLocked.end > block.timestamp && _oldLocked.delegated > 0) {
                userOldPoint.slope =