xanoutas/solidity-security-findings huggingface dataset:
User can't undelegate if delegatee.end > user.end, which is the normal case
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
User can't undelegate if delegatee.end > user.end, which is the normal case.

## Proof of Concept
In delegate() we ensure toLocked.end >= fromLocked.end so users won't delegate to a lock which will expire before the user's lock. However, when undelegating, fromLocked becomes the delegatee, so toLocked.end >= fromLocked.end check won't pass.

In VotingEscrow.t.sol:testSuccessUnDelegate, we make user1 and user2 locks' end time the same, thus missing the case mentioned above. PoC:

```solidity
    function testSuccessDelegate() public {
        // successful delegate
        testSuccessCreateLock();
        vm.warp(block.timestamp + WEEK + 1); // <---- delegatee end is bigger
        vm.prank(user2);
        ve.createLock{value: LOCK_AMT}(LOCK_AMT);
        vm.prank(user1);
        ve.delegate(user2);
        (, , , address delegatee) = ve.locked(user1);
        assertEq(delegatee, user2);
        (, , int128 delegated, ) = ve.locked(user2);
        assertEq(delegated, 2000000000000000000);
    }
```
Output (testSuccessUnDelegate can't pass now):
```
qiuhao@HW0018292:~/web3/c4/2023-08-verwa$ forge test --match-test "testSuccessDelegate"
[⠒] Compiling...
No files changed, compilation skipped

Running 1 test for src/test/VotingEscrow.t.sol:VotingEscrowTest
[PASS] testSuccessDelegate() (gas: 907149)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.95ms
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)


qiuhao@HW0018292:~/web3/c4/2023-08-verwa$ forge test --match-test "testSuccessUnDelegate"
[⠒] Compiling...
No files changed, compilation skipped

    ├─ [5698] VotingEscrow::delegate(0x0000000000000000000000000000000000002711) 
    │   └─ ← "Only delegate to longer lock"
    └─ ← "Only delegate to longer lock"

```
## Tools Used
Manual Review.
## Recommended Mitigation Steps
Undelegating should be handled separately, allowing users to undelegate without the lock.end check.


## Assessed type

Governance
users can set _claimFromTimestamp arbitrarily far back and creating a potentially unbounded gas cost
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
A malicious user could repeatedly call claim() with a very old _claimFromTimestamp to waste the gas of the contract.

## Proof of Concept
If _claimFromTimestamp is set to a very old timestamp, like 0, then claimStart could end up being 0 or a very small number.
This means the for loop would have to iterate over a huge number of epochs from the beginning to claim all the rewards.
For example, if the contract has been running for 5 years (260 weeks), and _claimFromTimestamp is set to 0, the for loop would have to iterate over 260 iterations to claim the rewards.
This can cause the transaction to run out of gas if there are too many epochs to loop through

In addition: 
Here is how it works:
- _claimFromTimestamp is only restricted to be a valid epoch (divisible by WEEK). There is no upper limit on how far back it can be.
- claimStart is set to the max of _claimFromTimestamp and userLastClaimed. So if _claimFromTimestamp is very old, claimStart will also be very old.
- The for loop iterates from claimStart to claimEnd by WEEK intervals. So if claimStart is very old, the loop will have to iterate over a huge number of epochs.
- Each iteration of the loop does calculations and storage reads/writes, so a large number of iterations will consume a lot of gas.

## Tools Used
Manual

## Recommended Mitigation Steps
add a limit to how far back _claimFromTimestamp can be


## Assessed type

Other
`remove_gauge` is prone to front run attack 
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
`remove_gauge` is a method present in `GaugeController.sol` which can be called only by governance. This method is prone to frontrun attack and due to that majority user's will be able to dissolve their votes for specific gauge.

All the users will call `vote_for_gauge_weights` with `user_weight` param zero and hence they will be able to use their votes on other gauge but weight of users who won't able to front run this tx , will be stuck in the removed gauge and no way to get them back

## Proof of Concept
```solidity
 function remove_gauge(address _gauge) external onlyGovernance {
        require(isValidGauge[_gauge], "Invalid gauge address");
        isValidGauge[_gauge] = false;
        _change_gauge_weight(_gauge, 0);
        emit GaugeRemoved(_gauge);
    }
```
this can be called by governance and which can be front run for the tx
```solidity
 function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external {
     [....]
       power_used = power_used + new_slope.power - old_slope.power;
       require(power_used >= 0 && power_used <= 10_000, "Used too much power");

      [.....]
      }
```
some of all the weigh should not be more than 10000, hence they will front run initial tx to withdraw votes from the target gauge while other user's weight will be stuck here who won't able to front run the tx and their power used here will be stuck such that they won't able to retrieve it

## Tools Used

Manual Testing

## Recommended Mitigation Steps

SOrry i can't suggest any mitigation steps, yet need to dig more in the protocol to finally suggest a mitigation


## Assessed type

Access Control
Loss of funds, as the lock owner won't be able to undelegate back to himself in order to withdraw if his lock has expired.
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Lock owner won't be able to undelegate back to himself after his lock has expired, as a result his lock will be permanently stuck in the system and he won't be able to withdraw his funds back.

## Proof of Concept
In order for a user to withdraw his lock, the lock will need to pass three important requirements:
- locked amount should be above zero indicating that the user has lock
- the current block.timestamp should be bigger than the lock end indicating that the lock has expired
- user should be his own delegatee indicating that the lock wasn't delegated to someone else
```solidity
    function withdraw() external nonReentrant {
        LockedBalance memory locked_ = locked[msg.sender];
        // Validate inputs
        require(locked_.amount > 0, "No lock");
        require(locked_.end <= block.timestamp, "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);
    }
```

The third require statement in the function `withdraw` enforces a user to undelegate his lock in order to withdraw, but currently the system doesn't predict a scenario when a delegated lock expires.

The following issue will occur:

1. Delegated lock expires.
2. The owner of the lock tries to withdraw, but without success as the lock needs to be undelegated first.
3. Owner tries to call the function `delegate` in order to undelegate the lock back to himself.
4. In the function `delegate`, when a user tries to delegate back to himself, the else if statement will be triggered which transfers the current delegatee back to the owner of the lock. In the end the function will revert as the second require statement will be triggered `toLocked.end > block.timestamp` indicating that the owner's lock has expired.

```solidity
    function delegate(address _addr) external nonReentrant {
        LockedBalance memory locked_ = locked[msg.sender];
        // Validate inputs
        require(locked_.amount > 0, "No lock");
        require(lo
Users cannot reassign voting power from delisted gauges in `GaugeController#vote_for_gauge_weights` function
Audit: 2023-08-verwa-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

In the `vote_for_gauge_weights` function, users can vote to allocate their voting power towards a specific gauge by specifying its address and the weight of their vote. The function contains a check to ensure that the gauge being voted for is valid via the `isValidGauge` mapping.

However, there's an oversight. If a gauge gets delisted (i.e., removed from the valid gauges list), any user who had previously allocated voting power to this gauge cannot reassign their voting power elsewhere. This is because the function will revert when attempting to set the weight to any value, including 0, for an invalid gauge.

## Impact

Users who have allocated voting power towards a gauge which later gets delisted are locked out from reallocating their voting power. This means they lose their influence in future voting events.

## Proof of Concept

1. The governance entity adds two gauges, `gauge1` and `gauge2`.
2. A user (`user1`) creates a lock and votes for both gauges with a weight of `5000` each. Their total voting power becomes `10000`.
3. Governance decides to remove `gauge2`.
4. When `user1` attempts to reassign their voting power from the removed `gauge2`, they're met with a revert due to "Invalid gauge address". Their voting power is effectively stuck.

## PoC Test

Add test to `src/test/GaugeController.t.sol` and run `forge test --match-test testMultiVoteWithRemovedGauge`.

```solidity
function testMultiVoteWithRemovedGauge() public {
    vm.startPrank(gov);
    gc.add_gauge(gauge1);
    gc.add_gauge(gauge2);
    vm.stopPrank();

    vm.startPrank(user1);
    ve.createLock{value: 1 ether}(1 ether);
    gc.vote_for_gauge_weights(gauge1, 5000);
    gc.vote_for_gauge_weights(gauge2, 5000);
    vm.stopPrank();

    assertEq(gc.vote_user_power(user1), 10000);
    uint256 power;
    (,power,) = gc.vote_user_slopes(user1, gauge1);
    assertEq(power, 5000);
    (,power,) = gc.vote_user_slopes(user1, gauge2);
    assertEq(power, 5000);
        
    vm.startPrank(gov);
    gc.remove_gauge(gauge2);
    vm.stopPrank();

    vm.startPrank(user1);
    // should revert when gauge removed and weight is not 0
    vm.expectRevert("Invalid gauge address");
    gc.vote_for_gauge_weights(gauge2, 2000);

    // should allow to remove power from removed gauge when weight is 0 and power > 0
    gc.vote_for_gauge_weights(gauge2, 0);
        
    // should revert when gauge removed and weight is 0
    vm.expectRevert("Invalid gauge address");
    gc.vote_for_gauge_weights(gauge2, 0);

    // should be able to allocate released power
    gc.vote_for_gauge_weights(gauge1, 7000);
    vm.stopPrank();

    assertEq(gc.vote_user_power(user1), 7000);
    (,power,) = gc.vote_user_slopes(user1, ga
LendingLedger.sol sends ETH instead of CANTO
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
In the documentations for the contest it is clearly stated that LendingLedger contract is expected to send CANTO to the user when they call the claim function but in reality it transfers ETH. This could be problematic if the user is a contract that expects to receive CANTO and cannot receive ETH.

## Proof of Concept
``if (cantoToSend > 0) {
            (bool success, ) = msg.sender.call{value: cantoToSend}("");
            require(success, "Failed to send CANTO");
        }``

## Tools Used
Manual review
## Recommended Mitigation Steps
Transfer CANTO token instead of ETH.


## Assessed type

Other
Missing access control for whitelisted smart contract
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L288
https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/VotingEscrow.vy#L185
https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/VotingEscrow.vy#L432


# Vulnerability details

## Impact
 

Because this contract is a copy from the curve side, the verwa side is missing the check of a whitelisted/blacklisted smart contract.

If the "blacklist" check is not added or implemented within the function, then the contract would lack the necessary mechanism to prevent or control the participation of certain smart contracts who might be listed on the blacklist. This means that s. contracts on the blacklist could still execute the increaseAmount function and perform actions within the contract, even though they are intended to be restricted or blocked.


## Proof of Concept
 
Missing check for whitelisted/blacklisted smart contract and zero address check
Related function
https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L288
Related modifier
https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/VotingEscrow.vy#L185

https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/VotingEscrow.vy#L432
    self.assert_not_contract(msg.sender)
Above code checks for the whitelisted smart contracts.


    @internal
    def assert_not_contract(addr: address):
    """
    @notice Check if the call is from a whitelisted smart contract, revert if not
    @param addr Address to be checked
    """
    if addr != tx.origin:
        checker: address = self.smart_wallet_checker
        if checker != ZERO_ADDRESS:
            if SmartWalletChecker(checker).check(addr):
                return
        raise "Smart contract depositors not allowed"


Similar check in the fiat dao side

https://github.com/code-423n4/2022-08-fiatdao/blob/main/contracts/VotingEscrow.sol#L124-L129

## Tools Used
manual
## Recommended Mitigation Steps
whitelist check should be added critical functions(createLock, increaseAmount etc)


## Assessed type

Access Control
User point history for first epoch remains empty
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
`balanceOfAt()` will not work correctly if the block number queried is related to user's first epoch.

## Proof of Concept
[VotingEscrow.sol#L142-L149](https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L142-L149) first assigns `userOldPoint` to `userPointHistory[_addr][uEpoch + 1]` if `uEpoch==0` and then always assigns `userNewPoint` to the same LHS:
```solidity
if (uEpoch == 0) {//@audit  line 151 gets executed anyway
    userPointHistory[_addr][uEpoch + 1] = userOldPoint; // <-- same LHS
}

userPointEpoch[_addr] = uEpoch + 1;
userNewPoint.ts = block.timestamp;
userNewPoint.blk = block.number;
userPointHistory[_addr][uEpoch + 1] = userNewPoint; // <-- same LHS
```

There is no need to have the special case for `uEpoch == 0` as its overridden later by `userNewPoint`. However, the correct assignment for `uEpoch == 0` would be:
```solidity
if (uEpoch == 0) {//@audit  line 151 gets executed anyway
    userPointHistory[_addr][uEpoch] = userOldPoint;
}
```

This is because when `uEpoch == 0`, this is the first time, we're writing to `userPointHistory[_addr][0]`, hence it's important to set it to its correct value `userOldPoint`.

## Tools Used
Manual

## Recommended Mitigation Steps
Replace [VotingEscrow.sol#L143](https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L143) with:
```diff
-    userPointHistory[_addr][uEpoch + 1] = userOldPoint;
+    userPointHistory[_addr][uEpoch] = userOldPoint;
```



## Assessed type

Other
GaugeController` allows for quick vote and withdraw
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

*This issue was identified in the original Curve Finance audit, finding ID TOB-CURVE-DAO-004. It remains unresolved in the `mkt.market` implementation, but was fixed in the Curve implementation.*

# Impact

A malicious voter is able to use full voting power for multiple or all gauges' vote in the same epoch, which should not be possible. This completely breaks governance.

# Root Cause

`GaugeController` voting is used to determine governance outcomes. Users are allocated voting weight power, which is then meant to be allocated between gauges of the choice:

https://github.com/code-423n4/2023-08-verwa/blob/498a3004d577c8c5d0c71bff99ea3a7907b5ec23/src/GaugeController.sol#L210
```solidity
src/GaugeController.sol:210
210:     /// @param _user_weight Weight for a gauge in bps (units of 0.01%). Minimal is 0.01%. Ignored if 0
211:     function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external {
212:         require(_user_weight >= 0 && _user_weight <= 10_000, "Invalid user weight");
...
230:         VotedSlope memory new_slope = VotedSlope({
231:             slope: (slope * _user_weight) / 10_000,
232:             end: lock_end,
233:             power: _user_weight
234:         });
```

The old slope is compensated for (cancelled out) and the user's last vote time is recorded.

https://github.com/code-423n4/2023-08-verwa/blob/498a3004d577c8c5d0c71bff99ea3a7907b5ec23/src/GaugeController.sol#L238
```solidity
src/GaugeController.sol:238
...
244:         // Remove old and schedule new slope changes
245:         // Remove slope changes for old slopes
246:         // Schedule recording of initial slope for next_time
247:         uint256 old_weight_bias = _get_weight(_gauge_addr);
248:         uint256 old_weight_slope = points_weight[_gauge_addr][next_time].slope;
249:         uint256 old_sum_bias = _get_sum();
250:         uint256 old_sum_slope = points_sum[next_time].slope;
...
274:         vote_user_slopes[msg.sender][_gauge_addr] = new_slope;
275: 
276:         // Record last action time
277:         last_user_vote[msg.sender][_gauge_addr] = block.timestamp;
278:     }
```

After this, `last_user_vote` is never referenced again i.e. the user can alter gauge weights after a vote ends to reset their power used, and then re-use that power elsewhere without restriction.

# Proof of Concept

For every gauge in the `GaugeController`, Mallory can perform the following steps:

1. Mallory calls `vote_for_gauge_weights` with a voting power of 10,000 (100%) just before the vote ends.
2. Once the vote ends, Mallory calls `vote_for_gauge_weights` with a voting power of 0

Mallory has used their full voting power multiple times, rather than once.

# Recommendation
The resolution of this issue in Curve was to add a [10 day vote weight change delay](https://github.com/curvefi/curve-dao-
the `claim` function may underFlow when it calculate the `claimEnd `
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
in the `claim` function there is possibility of the underflow which lead the transaction to revert, the function may underflow in this line ` uint256 claimEnd = Math.min(currEpoch - WEEK, _claimUpToTimestamp)` because the currEpoch will return the current epoch time rounded down to week which mean it return thursday in seconds(current epoch always returns thursday because epoch is set as weeks) and if the `WEEK` was bigger than the currEpoch (if it was friday) then this line will underflow and return negative value. even if the currEpoch was sunday or monday or any day in second it will revert if it wasn't friday in term of seconds.
## Proof of Concept
the claim function:

```solidity 
function claim(
        address _market,
        uint256 _claimFromTimestamp,
        uint256 _claimUpToTimestamp
    )
        external
        is_valid_epoch(_claimFromTimestamp)
        is_valid_epoch(_claimUpToTimestamp)
    {
        address lender = msg.sender;
        uint256 userLastClaimed = userClaimedEpoch[_market][lender];
        require(userLastClaimed > 0, "No deposits for this user");
        _checkpoint_lender(_market, lender, _claimUpToTimestamp);
        _checkpoint_market(_market, _claimUpToTimestamp);
        uint256 currEpoch = (block.timestamp / WEEK) * WEEK;
        uint256 claimStart = Math.max(userLastClaimed, _claimFromTimestamp);
        //@audit this may underflow
        uint256 claimEnd = Math.min(currEpoch - WEEK, _claimUpToTimestamp);
        uint256 cantoToSend;
        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"); // 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;
            }
            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
recommend to change the line mentioned to this to avoid underflow and returning of negative va
receive() in LendingLedger should be replaced by an appropriate function to prevent users from sending native by mistake
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Users lose funds by transferring them by mistake to `LendingLedger`.

## Proof of Concept
Since the `LendingLedger` contract has a `receive() external payable` function which receives native without any validation, which means that anyone can transfer native to it by mistake.

## Tools Used
Vscode, Foundry

## Recommended Mitigation Steps
Create an appropriate function to transfer native so that users never send by mistake, such as:
```solidity
depositNative() external payable {}
```


## Assessed type

ETH-Transfer