xanoutas/solidity-security-findings huggingface dataset:
Delegated Votes Blocking Delegator Undelegation
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Once a delegator has delegated their votes to a delegatee, and the delegatee employs those votes in an ongoing proposal, the delegator loses the ability to undelegate their votes. VotingEscrow::delegate is used to delegate user A's votes to User B. Once User a did this, User B can use User A's votes. User A can easily undelegate votes to him/herself or to someone else when this votes are not used in any active proposal. But if User B used them on any ongoing proposal, User A can't undelegate votes back.

This creates a possibility for User B to never give back delegated votes. He/She can create new proposal every time when the previous one was canceled if he/she has amount of votes more than proposalThreshold. 

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

## Tools Used
Manual Review

## Recommended Mitigation Steps
Maybe it's needed to have some small cooldown period, after the last user's proposal was canceled.


## Assessed type

Governance
Decayed voting power can be restored by delegating to newer lock
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Delegation to newer lock updates slope and bias of delegatee according to new delegated amount and lengthier lock time which leads to decayed voting power from delegator (older lock) to be restored
```
            // @audit - slope and bias being updated according to delegated balance which includes old lock balance
            // it is updated as if the entire balance is new 
            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));
            }
```
## Proof of Concept
```
contract PoC is Test {
    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 testDelegateToNewerLock() public {
        // Prepare
        vm.warp(1 weeks);
        vm.startPrank(governance);
        controller.add_gauge(mockGauge);
        controller.change_gauge_weight(mockGauge, 100);
        vm.stopPrank();

        // ALICE create lock
        deal(ALICE, 1 ether);
        vm.prank(ALICE);
        ve.createLock{value: 1 ether}(1 ether);

        // ALICE try voting
        vm.prank(ALICE);
        controller.vote_for_gauge_weights(mockGauge, 10000);
        console.log(controller.get_gauge_weight(mockGauge));
        // Initial voting power
        assertEq(controller.get_gauge_weight(mockGauge), 993424657416307300);

        // Almost 5 years passed. ALICE voting power should go down significantly
        skip(5 * 365 days - 15 days);

        controller.checkpoint_gauge(mockGauge);
        assertEq(controller.get_gauge_weight(mockGauge), 3835616437900900);

        // Reset gauge vote to ensure that follwing vote is not affected by previous vote
        vm.prank(ALICE);
        controller.vote_for_gauge_weights(mockGauge, 0);
        assertEq(controller.get_gauge_weight(mockGauge), 100);

        // ALICE can delegate to newer lock to regain her decayed voting power

        // Assume BOB voting power is negligible
        deal(BOB, 1);
        vm.prank(BOB);
        ve.createLock{value: 1}(1);
        vm.prank(BOB);
        controller.vote_for_gauge_weights(mockGauge, 10000);
        assertEq(controller.get_gauge_weight(mockGauge), 100); // no change from BOB vote

        // ALICE delegate to BOB the newer lock
        vm.prank(ALICE);
        ve.delegate(BOB);

        vm.prank(BOB);
        controller.vote_for_gauge_weights(mockGauge, 10000);
        // Decayed voting power of ALICE is restored
receive function allows any user to lose funds
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Lending ledger receive function allows for any user to send funds and lose them forever.

## Proof of Concept
Any user that sends funds to the Lending Ledger contract through the receive function, can't withdraw their funds meaning they would be stuck in Lending Ledger contract. Nor are his funds taken into account / give him some benefit.

## Tools Used
Foundry

## Recommended Mitigation Steps
Only allow governance to deposit funds.


## Assessed type

ETH-Transfer
The LendingLedger.sync_ledger function is susceptible to a potential reentrancy attack
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The primary focus is on the functions (deposit/withdraw) situated within the [LendingMarket](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L15) smart contracts, as they trigger the invocation of the [LendingLedger.sync_ledger](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L129) function. These functions carry the potential risk of containing a reentrancy issue, which, in turn, has implications for the overall integrity of the [LendingLedger.sync_ledger](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L129) function itself. The initial phase involves the governance adding the lending market to the [whitelist](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L204). It's noteworthy that all calls executed by the governance address are meticulously managed with the correct parameters. However, there remains a plausible scenario wherein the LendingMarket could exhibit a reentrancy vulnerability. 
If that happens, it could impact [lendingMarketBalances](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L137) and [lendingMarketTotalBalance](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L142) varaibles for user.

The probability of the LendingMarket with a reentrancy issue being added to the whitelist by governance is minimal. Nonetheless, this step is more of a "sanity check". Like this one:
 - [require(updatedLenderBalance >= 0, "Lender balance underflow");](https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L136)

## Proof of Concept
The provided code indicates that the sync_ledger function could potentially be susceptible to a reentrancy attack if the functions from LendingMarket smart contract that interact with sync_ledger contain reentrancy issue.

```solidity
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;

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

import "../LendingLedger.sol";

contract DummyGaugeController {
    function gauge_relative_weight_write(
        address _gauge,
        uint256 _time
    ) external returns (uint256) {
        return 1 ether;
    }
}

contract ReentrancyContract {
    LendingMarketTest market;

    constructor(address payable market_) payable {
        market = LendingMarketTest(market_);
    }

    function attack() external {
        market.withdraw(-1 ether);
    }

    receive() external pay
createLock, increaseAmount int128(int256(_value)) unsafe downcast can lead to asset loss
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Suppose users deposit more than type(int128).max value through createLock and increaseAmount, they may get less voting power and can't get the assets back.

## Proof of Concept
The VotingEscrow.sol is forked from FIATDAO, but it seems don't consider a MEDIUM bug: https://github.com/code-423n4/2022-08-fiatdao-findings/issues/228.

In this PoC, a user deposit 2**128 and get nothing. The user also can't call withdraw() to get his assets back since lock.amount is zero:


```solidity
src/test/VotingEscrow.t.sol


    function testCreateLockOverflow() public {
        uint256 value = 2**128;
        vm.deal(user1, value);
        // User deposits 2**128 canto but can't get anything back with withdraw
        vm.prank(user1);
        ve.createLock{value: value}(value);
        (int128 amount, , int128 delegated, address delegatee) = ve.locked(user1);
        assertEq(delegatee, user1);
        assertEq(amount, 0);
        assertEq(delegated, 0);
    }
```
Output:
```
qiuhao@pc:~/web3/c4/2023-08-verwa$ forge test --match-test testCreateLockOverflow
[⠒] Compiling...
No files changed, compilation skipped

Running 1 test for src/test/VotingEscrow.t.sol:VotingEscrowTest
[PASS] testCreateLockOverflow() (gas: 219540)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 870.48µs
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

```
## Tools Used
Manual Review.
## Recommended Mitigation Steps
Use safe cast lib and document the risk of depositing overflow.


## Assessed type

Math
Misaligned Epoch Calculation for Reward Claims
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
When users attempt to claim rewards, the contract calculates the claimEnd and subsequently updates the userClaimedEpoch using claimEnd + WEEK. This might result in misaligned epochs in scenarios where _claimUpToTimestamp is less than or more than a week. If not correctly aligned, users could potentially claim incorrect reward amounts or even double-claim for specific epochs.

## Proof of Concept
Misaligned Epochs: When _claimUpToTimestamp is taken as less than a week, and the subsequent calculation of userClaimedEpoch[_market][lender] = claimEnd + WEEK; occurs, epochs can overlap, potentially allowing double-claiming.

Potential Claiming Gaps: If _claimUpToTimestamp is more than a week, there's a risk of creating a gap where rewards for certain epochs might be skipped entirely.

## Tools Used

## Recommended Mitigation Steps
Refactor Epoch Calculation: Adjust the userClaimedEpoch setting mechanism to be dynamic based on the actual duration of _claimUpToTimestamp. This ensures alignment with expected epoch boundaries.


## Assessed type

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

# 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/GaugeController.sol#L240
https://github.com/code-423n4/2023-08-verwa/blob/main/src/GaugeController.sol#L230


# Vulnerability details

## Summary
The GaugeController voting can be abused to apply all of the user's weight in every gauge's vote.
GaugeController's voting changes the weight of the gauge. Each user can split their voting weight power between the gauges
```solidity
 function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external {
        require(_user_weight >= 0 && _user_weight <= 10_000, "Invalid user weight");
        [............]
        require(slope_ >= 0, "Invalid slope");
        uint256 slope = uint256(uint128(slope_));
        [............]
        VotedSlope memory new_slope = VotedSlope({
            slope: (slope * _user_weight) / 10_000,
            end: lock_end,
            power: _user_weight
        });
        [............]
        //The sum of all the weight used must not exceed 10,000:
        uint256 power_used = vote_user_power[msg.sender];
        power_used = power_used + new_slope.power - old_slope.power;
        require(power_used >= 0 && power_used <= 10_000, "Used too much power");

    }
 


```
while A gauge's weight can be updated every week
```solidity
function _change_gauge_weight(address _gauge, uint256 _weight) internal {
        uint256 old_gauge_weight = _get_weight(_gauge);
        uint256 old_sum = _get_sum();
        uint256 next_time = ((block.timestamp + WEEK) / WEEK) * WEEK;

        points_weight[_gauge][next_time].bias = _weight;
        time_weight[_gauge] = next_time;

        uint256 new_sum = old_sum + _weight - old_gauge_weight;
        points_sum[next_time].bias = new_sum;
        time_sum = next_time;
    }

    /// @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);
    }

```
There is no lock to prevent a user from removing their weight after a vote. As a result , an Attacker can put 100% of its voting power on a gauge's vote, and remove it right afterwards to re-use all its voting power on another vote

## Impact

See summary
### Exploit Scenario

Alice has 1,000,000 tokens locked. Alice calls `vote_for_guage_weights` with a voting power of 10000(100%) and then all the user voted as seeing/understand the consequences of Alice votes on gauge and after sometime Alice withdraw his votes by calling voting power of 0.

Here Alice manipulating the weights updates more than she should

## Proof of Concept

see summary and impact

This issue arise from the original fork of Curve DAO [GaugeController.vy](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/GaugeController.vy). This issue wa
Missing events for critical parameter changing operations by governance
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

Functions designated as 'onlyGovernance' that modify crucial contract parameters, addresses, or states should emit events. This allows users and other privileged roles to monitor impending changes off-chain and provides them time to react. Given the significant financial or trust impact that privileged roles in contracts hold, users should be empowered to act—whether to withdraw or join—without unexpected shifts resulting from non-transparent changes (lacking events) or instantaneous alterations (devoid of timelocks).


## Proof of Concept
The following critical functions miss events

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

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

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

## Tools Used

Manual Analysis

## Recommended Mitigation Steps

Add events to all critical function and consider adding timelocks to all onlyGovernance functions.





## Assessed type

Governance
Missing Check for Too-Frequent Voting
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/GaugeController.vy#L498
https://github.com/code-423n4/2023-08-verwa/blob/main/src/GaugeController.sol#L225


# Vulnerability details

## Impact

`Cannot vote so often` check is absent in this contract since it was derived from the curve project.

The check in place(`curve` side) makes sure that a user can't vote too frequently by comparing the current time to the last time they voted, plus a delay called ` WEIGHT_VOTE_DELAY`. If the time difference is too short, the message "Cannot vote so often" is shown in the `vote_for_gauge_weights` method(`GaugeController.vy`).

     assert block.timestamp >= self.last_user_vote[msg.sender][_gauge_addr] + WEIGHT_VOTE_DELAY, "Cannot vote so often"

On the `verwa` side, this check is missing(in the `vote_for_gauge_weights` function). This leaves room for malicious users to manipulate the system. This check would prevent people from exploiting the system by voting too often.

## Proof of Concept

Curve contract
https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/GaugeController.vy#L498

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

    it('should revert when a user votes too often', async function () {
     
    
    const gaugeAddress = '0xYourGaugeAddress';

    
    await contract.vote_for_gauge_weights(gaugeAddress, 5000);

     
    await expect(
      contract.vote_for_gauge_weights(gaugeAddress, 3000)
    ).to.be.revertedWith('Cannot vote so often');
      });

  
    it('should allow a user to vote successfully after waiting for the delay', async function () {
    
    
    const gaugeAddress = '0xYourGaugeAddress';
    const delay = 24 * 60 * 60; 

     
    await contract.vote_for_gauge_weights(gaugeAddress, 5000);

    
    await ethers.provider.send('evm_increaseTime', [delay]);
    await ethers.provider.send('evm_mine');

   
    await expect(
      contract.vote_for_gauge_weights(gaugeAddress, 3000)
    ).to.not.be.reverted;

    
      });

## Tools Used
Manual
## Recommended Mitigation Steps

Add similar check(Cannot vote so often) to the verwa.
https://github.com/code-423n4/2023-08-verwa/blob/main/src/GaugeController.sol#L225



## Assessed type

DoS
Rounding and Precision error on block time
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
For the blockTime variable it is possible to have rounding error and the decimal value is lost.
## Proof of Concept
Provide direct links to all referenced code in GitHub. 
```sol
// https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L518
            blockTime = blockTime + ((dTime * (_blockNumber - point0.blk)) / dBlock);
```
Add screenshots, logs, or any other relevant proof that illustrates the concept.
```sol
// For the following value you will have rounding division error.
1 + ((1 * (4 - 2)) / 3)
Results == 1.66666666667
Loss == 0.66666666667
```

## Tools Used
Manual review.

## Recommended Mitigation Steps
use safe math.


## Assessed type

Math
``claim()`` function will most likely always fail.
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

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

The ``claim()`` function of ``src/LendingLedger.sol`` calculates ``cantoToSend`` and transfers it to ``msg.sender`` by using 
```
179:    (bool success,) = msg.sender.call{value: cantoToSend}("");
180:    require(success, "Failed to send CANTO");
```
And it also reverts when call doesn't succeed. This creates a huge problem as only way to send this contract some tokens/value is using the receive function.
```
209:    receive() external payable {}
```
There might never be enough ether in the contract for the ``claim()`` function to succeed as there is no way to check whether contract has enough balance for ``claim()`` function to actually succeed. Also, the ether is most likely to be received from the governance in this contract. Hence, lenders have to wait to use ``claim()`` unless the contract has enough ether but there is no way to track it.

## Impact
1.  Canto can't be claimed until this contract has enough ether balance. Hence, ``claim()`` may revert again and again because there is no way to track contract balance and even if ether balance is sent to this contract, it still may not be enough.

2. There is no way to retrieve ether other than by using ``claim()`` function. Hence, excess ether may always be stuck.


## Tools Used
Manual Analysis

## Recommended Mitigation Steps
1. Add a proper tracking mechanism like a getter function to track the amount of ether in this contract and ensure that this contract has excess ether.

2. Revert early if the contract doesn't have enough ether than required for ``claim()``.

3. Add a withdraw method to withdraw excess ether from the contract.








## Assessed type

ETH-Transfer
Delegate may extend the old voting power
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Users may renew their voting power by delegating to a new wallet.

## Proof of Concept
The voting power will be decreased linearly over time:
```solidity
    function balanceOf(address _owner) public view returns (uint256) {
        uint256 epoch = userPointEpoch[_owner];
        if (epoch == 0) {
            return 0;
        }
        Point memory lastPoint = userPointHistory[_owner][epoch];
        lastPoint.bias = lastPoint.bias - (lastPoint.slope * int128(int256(block.timestamp - lastPoint.ts)));
        if (lastPoint.bias < 0) {
            lastPoint.bias = 0;
        }
        return uint256(uint128(lastPoint.bias));
    }
```
However, there is no corresponding linear decreasing mechanism when handling delegate. The full locked amount will be used for the update.

For example, 
Assume, Alice maintains two address A and B.
At the first year, she locked 1000 tokens with address A.
At the end of the first year, the balance A suppose to decrease, and at the same time, she locks 1 token with B.
Now, she delegates the locked value in A to B and as a result, the total voting power will be less affected.
 

## Tools Used
Manual

## Recommended Mitigation Steps
Considering updating the voting power based on the balanceOf result.


## Assessed type

Governance