xanoutas/solidity-security-findings huggingface dataset:
Users can vote infinitely via delegation
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Summary
`GaugeController#vote_for_gauge_weights()` is designed to allow users to vote for gauge rewards based on the amount of $CANTO they have locked in the `VotingEscrow` contract. `VotingEscrow` includes functionality for users to delegate their voting power to another address via `delegate()`. The delegatee can then vote freely using the delegator's stake. The issue is that there is no check to ensure that the delegatee's vote hadn't already been used by the delegator (or any other prior delegatees). This allows for infinite voting with the same stake.

The basic exploit flow is:
1. User 1 locks funds and votes for a gauge
2. User 1 delegates to user 2
3. User 2 votes for the same gauge using the same stake

## Vulnerability details
`delegate()` increases the delegatee's voting power by increasing the amount of `lock.delegated` in the delegatee's lock:

```solidity
function _delegate(address addr, LockedBalance memory _locked, int128 value,...) internal {
        LockedBalance memory newLocked = _copyLock(_locked);
        if (action == LockAction.DELEGATE) {
            newLocked.delegated += value; // @audit add delegated value
            emit Deposit(addr, uint256(int256(value)), newLocked.end, action, block.timestamp);
        ...
        locked[addr] = newLocked;
        if (newLocked.amount > 0) {
            _checkpoint(addr, _locked, newLocked); // @audit checkpoint it
        }
    }
```

The updated `lock.delegated` value is then used in the call to `_checkpoint` to calculate the delegatee's lock's slope: 

```solidity
function _checkpoint(address _addr, ..., LockedBalance memory _newLocked) internal {
        Point memory userOldPoint;
        Point memory userNewPoint;
        ...
        if (_addr != address(0)) {
            // Calculate slopes and biases
            // Kept at zero when they have to
            ...
            if (_newLocked.end > block.timestamp && _newLocked.delegated > 0) {
                userNewPoint.slope = _newLocked.delegated //@audit new slope here from delegated amount
                    / int128(int256(LOCKTIME));
                userNewPoint.bias = userNewPoint.slope * int128(int256(_newLocked.end - block.timestamp));
            }
```

The checkpointed slope is then used to calculate the share of rewards 

```
function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external
    {
        ...
        (, int128 slope_, ) = ve.getLastUserPoint(msg.sender);
        require(slope_ >= 0, "Invalid slope");
        ... // @audit take the _checkpointed slope and use it to calculate voting power (new_slope.slope)
        VotedSlope memory new_slope = 
            VotedSlope({slope: (slope * _user_weight) / 10_000, end: lock_end, power: _user_weight});
        uint256 new_dt = lock_end - next_time;
        uint256 new_bias = new
`GaugeController.remove_gauge()` may indefinitely trap user voting power
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/main/src/GaugeController.sol#L124-L132
https://github.com/code-423n4/2023-08-verwa/blob/main/src/GaugeController.sol#L208-L278


# Vulnerability details

## Impact
Users will permanently lose voting power equal to their power used to vote in the removed gauge unless the gauge is re-added and users re-vote to remove their voting power from the gauge.

## Proof of Concept
`GaugeController.sol` does not have any function for users to remove their voting power from gauges, apart from `vote_for_gauge_weights()`. Notice the below code snippets and inline comments:
```
    function remove_gauge(address _gauge) external onlyGovernance {
        require(isValidGauge[_gauge], "Invalid gauge address");
        isValidGauge[_gauge] = false; // Removing a gauge renders it "invalid"
```
```
    function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external {
        require(_user_weight >= 0 && _user_weight <= 10_000, "Invalid user weight"); 
        require(isValidGauge[_gauge_addr], "Invalid gauge address"); // Error is thrown if this function is called on a removed gauge
```
Because users cannot remove their voting power from a removed gauge, they may indefinitely lose voting power.

## Tools Used
Manual Review
## Recommended Mitigation Steps
Only require that a valid gauge is passed to `vote_for_gauge_weights()` if the `_user_weight` argument is greater than zero. Alternatively, add a function that allows users to remove their votes from any gauge.


## Assessed type

Other
the `_checkpoint_market` may not work as expected when it called more than twice at least
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/498a3004d577c8c5d0c71bff99ea3a7907b5ec23/src/LendingLedger.sol#L83-L88
https://github.com/code-423n4/2023-08-verwa/blob/498a3004d577c8c5d0c71bff99ea3a7907b5ec23/src/LendingLedger.sol#L106-L112
https://github.com/code-423n4/2023-08-verwa/blob/498a3004d577c8c5d0c71bff99ea3a7907b5ec23/src/LendingLedger.sol#L129-L143


# Vulnerability details

## Impact

the `_checkpoint_market` function in the `lendingLedger.sol` contract did not work as it expected when the caller make a call to the function for the third time. the `_checkpoint_market` would called more than once to update the market, however the function will not set the correct epoch for the third call(and above) when its called by the caller. more details in POC

## Proof of Concept
let's imagine this scenario happen with a caller:

-Alice(caller) will call the `checkpoint_market` which call the `_checkpoint_market` and this is the first time that this functon is called for the specific market and because of that the `lastMarketUpdateEpoch` will be equal to the zero and the `if (lastMarketUpdateEpoch == 0)`:

```solidity
 function _checkpoint_market(address _market, uint256 _forwardTimestampLimit) private {
        uint256 currEpoch = (block.timestamp / WEEK) * WEEK;
        uint256 lastMarketUpdateEpoch = lendingMarketTotalBalanceEpoch[_market];
        uint256 updateUntilEpoch = Math.min(currEpoch, _forwardTimestampLimit);
        if (lastMarketUpdateEpoch == 0) {
            lendingMarketTotalBalanceEpoch[_market] = currEpoch;     
        }
        ...
 }
```
as its clear in the code that we set the `lendingMarketTotalBalanceEpoch` to the current epoch, because its our first call for the checkpoint and for this market we can say that we update the epoch from zero to `currentEpoch(2 for example)`. 

-time passes and Alice(caller) will make second call to update the epoch for the same market but this time the `lastMarketUpdateEpoch` will be set to 2 because the last update set the epoch to 2,however in this call the `else if` will be run because this is the second time we update the epoch, and lets say the `_forwardTimestampLimit` is set to 5 and `currEpoch` is 4 so the `updateUntilEpoch` will return 4:

```
function _checkpoint_market(address _market, uint256 _forwardTimestampLimit) private {
        uint256 currEpoch = (block.timestamp / WEEK) * WEEK;
        uint256 lastMarketUpdateEpoch = lendingMarketTotalBalanceEpoch[_market];
        uint256 updateUntilEpoch = Math.min(currEpoch, _forwardTimestampLimit);
        ... 
        else if (lastMarketUpdateEpoch < currEpoch) {
            // Fill in potential gaps in the market total balances history
            uint256 lastMarketBalance = lendingMarketTotalBalance[_market][lastMarketUpdateEpoch];
            for (uint256 i = lastMarketUpdateEpoch; i <= updateUntilEpoch; i += WEEK) {
                lendingMarketTotalBalance[_market][i] = lastMarketBalance;
            }
Undelegation logic doesn't work as expected, duo to that an owner of a lock would be enforced to extend their lock time for another 5 years.
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Undelegation logic doesn't work as expected, duo to that an owner of a lock would be enforced to reset his lock time for another 5 years through the function `increaseAmount` in order to successfully undelegate.

## Proof of Concept
There are few differences between the Voting Escrow of fiat dao and the fork of it made in veRWA.
- in fiat dao a lock owner is able to increase their lock amount without resetting the lock time, also there is a function only intended for increasing the unlock time of a lock.
-  on the other hand the voting escrow in veRWA has only the function to increase the amount of the lock, which also resets the unlock time for another 5 year based on the current block.timestamp

Duo to this changes in order to undelegate in veRWA, the owner of the lock would need to extend his unlock time for another 5 years, which can be really frustrating if the lock owner wants to undelegate right before the lock end time in order to be able to withdraw.

Explanation of the issue:
1. Owner of a lock calls the function `delegate`. In the function `delegate` the if statement will be triggered which indicates that the owner is going to delegate to someone else. Right before the delegation happens, the function ensures that the delegatee's lock end time is longer than the lock end time of the owner.
2. Time passes and before the owner's lock end time expires, the owner calls the function `delegate` in order to undelegate himself. In the function `delegate` the else if statement will be triggered which transfers the delegate back to the owner. Before undelegation happens the function enforces that the owner's lock end time is bigger than the end time of the delegatee's lock. 
3. This is a problem considering that when the delegation happened, it was the opposite and the delegatee was enforced to have a bigger lock end time than the owner's lock.
4. In order to undelegate the owner would need to extend his lock time to bigger than the delegatee. But the only option for that is to call the function `increaseAmount` which will reset his lock end time for another 5 years.

```solidity
        require(toLocked.end >= fromLocked.end, "Only delegate to longer lock");
```

In fiat dao, lock owner is able to call the function `increaseUnlockTime` and set their lock time just to be over the delegatee's one in order to undelegate. However as there is no such function in veRWA, the only option for a lock owner to undelegate would be to call the function `increaseAmount` and reset their unlock time for another 5 years.

```solidity
    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 v
If user has delegated someone when locktime expires his tokens will be stuck forever.
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/498a3004d577c8c5d0c71bff99ea3a7907b5ec23/src/VotingEscrow.sol#L331
https://github.com/code-423n4/2023-08-verwa/blob/498a3004d577c8c5d0c71bff99ea3a7907b5ec23/src/VotingEscrow.sol#L383


# Vulnerability details

## Impact
tokens will be stuck forever if user's locktime expired when his delegatee someone differant than him. 
Because he cannot withdraw his money also he cannot delegate himself too because his lock expired.Also he cannot use increaseamount for lockexpired and createLock too due to lock.amount>0. so his tokens will be stuck forever.
## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

## Tools Used

## Recommended Mitigation Steps
Can be add extra function for delegate himself when lock.expired and making special checkpoint station can be good.


## Assessed type

DoS
`GaugeController` - Vulnerability with changing gauge weight would make the contract stop working
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details


## Impact
The issue is applied differently based on how `change_gauge_weight` works.

#### 1. When changing gauge weight is essential for every enabled gauge before any vote happens
An attacker can front-run `change_gauge_weight` transaction to manipulate `slope` which can result in reverts in the future in getting the gauge weight

#### 2. When changing gauge weight is optional for gauges
Vote does not have effect before `change_gauge_weight` is called once.

## Proof of Concept
```Solidity
  function _get_weight(address _gauge_addr) private returns (uint256) {
      uint256 t = time_weight[_gauge_addr];
      if (t > 0) {
        // time_weight[_gauge_addr] is updated here
        ...
      } else {
          return 0;
      }
  }
```

In `_get_weight` function, time_weight is updated to `next_time` when original value is set.
The `time_weight` for a gauge is initialized only through `change_gauge_weight`.

So when `vote_for_gauge_weights` is called before `time_weight` is initialized, `_get_weight` always return zero and `time_weight` also remains zero.
Thus, updating `points_weight` has no effect but `changes_weight` has effect.

```Solidity
  function _get_weight(address _gauge_addr) private returns (uint256) {
      uint256 t = time_weight[_gauge_addr];
      if (t > 0) {
        ...
          if (pt.bias > d_bias) {
              pt.bias -= d_bias;
              uint256 d_slope = changes_weight[_gauge_addr][t];
              pt.slope -= d_slope; // <-- where issue happens
          } else {
              pt.bias = 0;
              pt.slope = 0;
          }
        ...
      } else {
          return 0;
      }
  }
```

Assuming there are some vote transactions that are front-run before `change_gauge_weight` is called, at some point in the future, `_get_weight` can be reverted on the line mentioned above.
Because, `points_weight` is not changed so `pt.bias > d_bias` will be true, but inside it, `pt.slope` will be smaller than `d_slope` since there had been changes made to `changes_weight`.

This will cause revert, once this is reverted, the `GaugeController` will not work anymore.

## Used Tools
Manual Review

## Recommended Mitigation Steps
There can be more than one ways to resolve this issue as follows:
#### 1. Update gauge weight to zero when `add_gauge` is called. (as in remove_gauge)
```Solidity
  function add_gauge(address _gauge) external onlyGovernance { // @audit-ok
      require(!isValidGauge[_gauge], "Gauge already exists");
      _change_gauge_weight(_gauge, 0);
      isValidGauge[_gauge] = true;
      emit NewGauge(_gauge);
  }
```

#### 2. In `vote_for_gauge_weights`, set `time_weight` to next_time
```Solidity
  points_weight[_gauge_addr][next_time].bias = Math.max(old_weight_bias + new_bias, old_bias) - old_bias;
  points_sum[next_time].bias = Math.max(old_su
Lenders can deposit at anytime during the week and still claim rewards for that week
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L129-L143
https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L152-L182


# Vulnerability details

## Impact
Anytime a user deposits during an epoch (i.e a week) he immediately is eligible to earn from the rewards for that lending market that week. This occurs because when the lending pool calls `sync_ledger`, LendingLedger adds the deposit to the users deposit for that week. It updates the lenders balance using these [lines](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L135-L136) and the market's deposits [here](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L140-L141). It calculates the current epoch and uses it to update these balances.

This allows a user to deposit even 1 second before an epoch ends and still claim the rewards for that epoch.

## Proof of Concept
```solidity
       function testDepositAndClaimEndOfEpoch() public {
        uint248 amountPerEpoch = 1 ether;

        uint256 fromEpoch = WEEK * 5;
        uint256 toEpoch = WEEK * 10;

        address lendingMarket = vm.addr(5201314);

        vm.prank(goverance);
        ledger.whiteListLendingMarket(lendingMarket, true);
        payable(ledger).transfer(1000 ether);

        vm.prank(goverance);
        ledger.setRewards(fromEpoch, toEpoch, amountPerEpoch);

        // first attack 
        vm.warp(WEEK * 6 - 1); // 1 second before week 5 ends
        address lender = users[1];

        int256 delta = 1.1 ether;
        vm.prank(lendingMarket);
        ledger.sync_ledger(lender, delta); // user deposits in lending pool

        vm.prank(lender);
        vm.warp(WEEK * 6); // now in week 6
        uint256 balanceBefore = address(lender).balance;
        ledger.claim(lendingMarket, fromEpoch, fromEpoch);
        uint256 balanceAfter = address(lender).balance;
        assertTrue(balanceAfter - balanceBefore == 1 ether); // user claimed 1 ether

        vm.startPrank(lendingMarket);
        ledger.sync_ledger(lender, -delta); // user withdraws


        // second attack
        vm.warp(WEEK * 7 - 1); // 1 second before week 6 ends

        ledger.sync_ledger(lender, delta); // user deposits in lending pool
        vm.stopPrank();

        vm.prank(lender);
        vm.warp(WEEK * 7); // now in week 6
        balanceBefore = address(lender).balance;
        ledger.claim(lendingMarket, WEEK*6, WEEK*6); // user claims 1 ether
        balanceAfter = address(lender).balance;
        assertTrue(balanceAfter - balanceBefore == 1 ether); 

        vm.startPrank(lendingMarket);
        ledger.sync_ledger(lender, -delta); // user withdraws
    }
```

## Tools Used
Foundry, Vscode

## Recommended Mitigation Steps
Let the deposits during an epoch count towards the rewards earned
`add_gauge` doesn't initialize `time_weight` and update `time_sum`
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
In Curve's implementation, when adding gauge, `time_weight` of gauge type is being initialized and `time_sum` being updated.
```
    if self.time_sum[gauge_type] == 0:
        self.time_sum[gauge_type] = next_time
    self.time_weight[addr] = next_time
```

Since `time_sum` has been set in constructor and can be updated in `_get_sum` so impact of not updating it while adding gauge is not much. But  `_get_weight` relies on `time_weight` to enter the loop to backfill previous points. If the value are not initialized (zero) it won't backfill properly and will always return 0. Thus, `points_weight` won't be updated correctly when voting.
```
uint256 old_weight_bias = _get_weight(_gauge_addr); // @audit - always return 0
        uint256 old_weight_slope = points_weight[_gauge_addr][next_time].slope;
        uint256 old_sum_bias = _get_sum();
        uint256 old_sum_slope = points_sum[next_time].slope;

        // @audit - max(0 + new_bias, 0) at best it will be the max of new_bias
        // meaning that each user vote will override each other but not accumulated
        points_weight[_gauge_addr][next_time].bias = Math.max(old_weight_bias + new_bias, old_bias) - old_bias;
        // @audit - while sum accumulates correctly
        points_sum[next_time].bias = Math.max(old_sum_bias + new_bias, old_bias) - old_bias;
```

For the gauge to work, it requires admin to call `change_gauge_weight` to initialize at least once before that gauge become functional.

There is also another issue. If a user vote between adding gauge and changing weight, their subsequent vote will be counted incorrectly until they reset their weight for that gauge due to their  bias being reset when changing gauge weight.
## Proof of Concept
Following Forge test compare vote result of
1. Voting before `change_gauge_weight` being called
2. Voting after  `change_gauge_weight` being called
It showed that relative weight is correct only when `change_gauge_weight` is being called prior to voting
```
contract PoC is Test {
    uint256 WEEK = 7 days;

    VotingEscrow ve;
    GaugeController controller;

    address governance = makeAddr("governance");
    address mockGauge = makeAddr("mockGauge");
    address ALICE = makeAddr("ALICE");
    address BOB = makeAddr("BOB");

    function setUp() public {
        ve = new VotingEscrow("ve", "ve");
        controller = new GaugeController(address(ve), governance);
    }

    function testAddGaugeInit() public {
        // Prepare gauge (incorrectly initialized)
        vm.warp(1 weeks);
        vm.prank(governance);
        controller.add_gauge(mockGauge);
        // ALICE create lock
        deal(ALICE, 1 ether);
        vm.prank(ALICE);
        ve.createLock{value: 1 ether}(1 ether);
        // BOB create lock
        deal(BOB, 1 ether);
        vm.prank(BOB);
        ve.createLock{value: 1 ether}(1 ether);