xanoutas/solidity-security-findings huggingface dataset:
critical functions should emit an event when they called
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
some critical functions should emit an even when they called because it contains important information and an event should be emitted, some functions in this contest did not emit an event and this may led to unwanted behavior in future functions like `claim` and `setRewards` and `whiteListLendingMarket` in the `ledgerMarket.sol` should have an event and emit it when they called, same thing for the `vote_for_gauge_weights` in `GaugeController` which it should emit an event same as the curve contract do.

## Proof of Concept
the functions that should add event for them:

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

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

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

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

## Tools Used

manual review

## Recommended Mitigation Steps
add an event for the mentioned functions and emit it when the functions called


## Assessed type

Other
Failed transfer with low level call could be overlooked
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
In `LendingLedger.sol` and `votingEscrow.sol`, low level call made using the `call`, According to the Solidity docs, "The low-level functions `call`, `delegatecall` and `staticcall` return true as their first return value if the account called is non-existent, as part of the design of the EVM. Account existence must be checked prior to calling if needed".As a result, it is possible that this call will fail, but `votingEscrow#withdraw()` and `lendingLedger#claim()` will not notice anything went wrong

## Proof of Concept
the `claim` function using .call to transfer token to callee:

```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);
        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;
        }
        //@audit
        if (cantoToSend > 0) {
            (bool success, ) = msg.sender.call{value: cantoToSend}("");
            require(success, "Failed to send CANTO");
        }
    }
```
same thing for `withdraw` function
```solidity 
function withdraw() external nonReentrant {
        LockedBalance memory locked_ = locked[msg.sender];
        // Validate inputs
        require(locked_.amount > 0, "No lock");
        requir
TEST FINDING
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

## 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


## Assessed type

Access Control
User is able to delegate to delegatee with shorter lock when calling increaseAmount()
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
When a user "user A" calls increaseAmount, if he had previously delegated his lock to another user "delegatee" this would update the end of the lock for "user A" while increasing the amount of delegated for the other user aka "delegatee". This is inconsistent with the fact that we are not allowing delegation through delegate() when toLocked.end < fromLocked.end, ie "Only delegate to longer lock".

## Tools Used
Manual audit.

## Recommended Mitigation Steps
We recommend removing the require line 384 https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L384 which does not seem useful because it cannot be respected when increaseAmount is called.


## Assessed type

Context
check for the reentrancy attack is missed in the claim function
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

the function `claim` in the `LendingLedger.sol` will send native token $CANTO to the msg.sender by `.call which it can be EOA or Contracts, because there is no any RA checks the caller can make double call in the same time to get himself more tokens reward than deserved and even drain the contract tokens.

## Proof of Concept

attacker can use a contract that will reentrant the `LendingLedger.sol` when it receive native token by set a specific logic in side a `receive function` which it call the `claim` function before the first call ended and before the all state changes happen which in this case the attacker can take more tokens rewards than deserved. contract like this will allow this case happen:
https://solidity-by-example.org/hacks/re-entrancy/

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);
        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;
        }
        //@audit RA may happen
        if (cantoToSend > 0) {
            (bool success, ) = msg.sender.call{value: cantoToSend}("");
            require(success, "Failed to send CANTO");
        }
    }
```
## Tools Used

manual r
Users can abuse `VotingEscrow.delegate()` to avoid voting power decay. Additionally, users can delegate expired locks to regain full voting power.
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L356-L387
https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L111-L149
https://github.com/code-423n4/2023-08-verwa/blob/main/src/GaugeController.sol#L214-L223


# Vulnerability details

## Impact
Voting power will not decay over the course of the lock.

## Proof of Concept
The `VotingEscrow.delegate()` function allows users to delegate to locks with a longer expiry time. See the below code snippet and inline comments:
```
        require(toLocked.end >= fromLocked.end, "Only delegate to longer lock");
        _delegate(delegatee, fromLocked, value, LockAction.UNDELEGATE); // remove voting power from the previous delegatee
        _delegate(_addr, toLocked, value, LockAction.DELEGATE); // add voting power to the new delegatee
```
The initial value of a lock (`LockedBalance.amount`) is never changed (except upon increasing the lock amount or withdrawing funds), and `value` in the above snippet is equal to the user's `LockedBalance.amount`. When `_delegate()` is called in the above function on the new delegatee, it increases the delegated amount of the delegatee's lock by the user's `LockedBalance.amount` and calls `_checkpoint()` on the delegatee's lock to update it. This update proceeds according to the below code:
```
    function _checkpoint(
        address _addr,
        LockedBalance memory _oldLocked,
        LockedBalance memory _newLocked  
    ) internal {
        Point memory userOldPoint;
        Point memory userNewPoint; // Updated slope and bias for the delegatee
        int128 oldSlopeDelta = 0;
        int128 newSlopeDelta = 0;
        uint256 epoch = globalEpoch;

        if (_addr != address(0)) {
            if (_oldLocked.end > block.timestamp && _oldLocked.delegated > 0) { // not interested in this block
                userOldPoint.slope = _oldLocked.delegated / int128(int256(LOCKTIME));
                userOldPoint.bias = userOldPoint.slope * int128(int256(_oldLocked.end - block.timestamp));
            }
            if (_newLocked.end > block.timestamp && _newLocked.delegated > 0) { // this block is where the delegatee slope and bias are updated
                userNewPoint.slope = _newLocked.delegated / int128(int256(LOCKTIME)); // The new delegated amount (equal to the old delegated amount plus the original user's initial lock amount) is divided by 5 years to calculate the slope
                userNewPoint.bias = userNewPoint.slope * int128(int256(_newLocked.end - block.timestamp)); // The new bias is calculated by multiplying the slope by the remaining time in the delgatee's lock
            }

            uint256 uEpoch = userPointEpoch[_addr];
            if (uEpoch == 0) { // This won't occur
                userPointHistory[_addr][uEpoch + 1] = userOldPoint;
            }

            userPointEpoch[_addr] = uEpoch + 1;
            userNewPoint.ts = block.timestamp;
            userNewPoint.blk = block
LendingLedger Lack of method to rescue accidentally sent Canto
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
There is no function to rescue Canto accidentally sent to LendingLedger so if governance fat-finger those Canto could be lost forever.
## Proof of Concept
There is no function to transfer native token out of LendingLedger
## Tools Used
Manual inspection
## Recommended Mitigation Steps
Add function to withdraw native token from LendingLedger


## Assessed type

Payable
Unsafe typecasting can lead to tokens being locked in the contract 
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

In the createLock function the amount is calculated by casting the uint256 _value to int128 in an unsafe way.
Specifically the locked_.amount is calculated as: locked_.amount += int128(int256(_value));
could result in a negative value for locked._amount. 

For example, if _value is 868790785326998466564056403945758400791, the locked._amount would  be -152056315435816923826067418349546233577

Because the locked._amount isn't checked whether it is >0 in the rest of the function, a lock can be created with negative amount. When the user that created that lock tries to withdraw the tokens, he will be unable to withdraw the tokens.

This bug could happen if a user tries to create a lock with at least max(uint128) tokens. He has to approve at least that amount of tokens to the contract, so the call from line L346 would succeed.

This is due to the check require(locked_.amount > 0, "No lock"); that is used in withdraw function below.


## Proof of Concept

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

```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
```

The createLock function:

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


```solidity
    function createLock(uint256 _value) external payable nonReentrant {
        uint256 unlock_time = _floorToWeek(block.timestamp + LOCKTIME); // Locktime is rounded down to weeks
        LockedBalance memory locked_ = locked[msg.sender];
        // Validate inputs
        require(_value > 0, "Only non zero amount");
        require(msg.value == _value, "Invalid value");
        require(locked_.amount == 0, "Lock exists");
        // Update lock and voting power (checkpoint)
        locked_.amount += int128(int256(_value));
        locked_.end = unlock_time;
        locked_.delegated += int128(int256(_value));
        locked_.delegatee = msg.sender;
        locked[msg.sender] = locked_;
        _checkpoint(msg.sender, LockedBalance(0, 0, 0, address(0)), locked_);

        emit Deposit(msg.sender, _value, unlock_time, LockAction.CREATE, block.timestamp);
    }
```


## Tools Used

Manual review

## Recommended Mitigation Steps

Even though Solidity 0.8.x is used, type casts do not throw an error. A SafeCast library must be used everywhere a typecast is done.
Because the locked._amount isn't intended to receive negative values, it is better to use uint128 for the variables amount and delegated of the LockedBalance struct, and adjus
Malicious user can drastically boost their voting power by increase a dust amount in lock
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
A user's weight on a gauge weight voting by design decay with time. However, a user can simply bypass the decay by increasing a dust amount in their lock in VotingEscrow.sol. 

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

## Proof of Concept
In VotingEscrow.sol `increaseAmount()`, the vulnerability lies in the fact that whenever a user increase the amount in their lock, their `newLock.end` is reset to `block.timestamp + LOCKTIME`. And there is no check on the minimum amount of increase. 

```solidity
//VotingEscrow.sol
    function increaseAmount(uint256 _value) external payable nonReentrant {
...
       newLocked.amount += int128(int256(_value));
|>     newLocked.end = _floorToWeek(block.timestamp + LOCKTIME);
...

```

This effectively means that whenever user wants to bypass the decay factor mandated in the protocol, they can simply deposit 1 wei to `increaseAmount()`. Their lock end time will be reset to another 5 years and their `slope` which is used to calculate gauge weight will not be effected. Now the user will have drastically more voting power with roughly the same amount locked in the escrow.

See POC here:
(1) user1 creates a lock with 1000 ether locked in VotingEscrow.sol. 
(2) 3 years has passed. user1 voting power decayed.
(3) user1 calls VotingEscrow.sol `increaseAmount()` and deposit 1 wei.
(4) user1 now has voting power good as new and vote for gauge1's weight.
(5) user1 can repeat this process indefinitely to prevent their voting power's decay.
The below test is modified in GaugeController.t.sol with two comparison test case to show the POC:
Test case 1: user does not increase amount after 3 years and vote for guage1;
Test case 2: user increase 1 wei after 3 years and vote for gauge1;
```solidity
//GaugeController.t.sol
//Test case 1
  function testUserVoteAfter3YearsWithoutIncrease() public {
        vm.startPrank(gov);
        gc.add_gauge(gauge1);
        gc.add_gauge(gauge2);
        gc.change_gauge_weight(gauge1, 100);
        gc.change_gauge_weight(gauge2, 100);
        vm.stopPrank();

        vm.deal(user1, 1010 ether);
        //user1 create lock
        vm.prank(user1);
        ve.createLock{value: 1000 ether}(1000 ether);
        uint256[3] memory weights = [uint256(8000), 2000, 100];
        vm.warp(block.timestamp + 1 weeks);
        vm.startPrank(user1);
        gc.vote_for_gauge_weights(gauge1, weights[0]);
        gc.vote_for_gauge_weights(gauge2, weights[1]);
        vm.warp(block.timestamp + 1 weeks);
        gc.checkpoint_gauge(gauge1);
        gc.checkpoint_gauge(gauge2);
        vm.stopPrank();
        //3 years past
        vm.warp(block.timestamp + 1000 days);
        ve.checkpoint();
        console.log(
            "user1 balance without increase 1 wei:",
            ve.balanceOfAt(user1, block.number)
        );
Potential Overflow Bug in sync_ledger Function
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# 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#L135


# Vulnerability details

## Impact
The sync_ledger function in the provided Solidity smart contract is vulnerable to potential overflow issues when processing a large value for the _delta parameter. 
Depending on the magnitude of the _delta value, the contract may encounter various undesired behaviors while adding lendingMarketBalances and lendingMarketTotalBalance, including unintended balance adjustments, incorrect balance updates, or even contract crashes due to gas limit exceedance.

## 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;
      ..
    }

## Tools Used
Manual review

## Recommended Mitigation Steps
Input Validation: Implement thorough input validation to ensure that the _delta value is within reasonable and expected ranges before processing it in the sync_ledger function.





## Assessed type

Under/Overflow
Vulnerability: Setting the userClaimedEpoch to a wrong Epoch / Contract: LendingLedger / Function: claim
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
The userClaimedEpoch is setted to a different epoch week more than the actual epoch of the claim.

## Proof of Concept
In the function to claim the canto for a market, the claiming can only be made for a prior epoch and the function does it in that way, but when the variable **userClaimedEpoch** is set to the last epoch that the user claims the rewards, this variable is set to the epoch of claim time + 1 week more, so the user is losing a week of rewards.

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

## Tools Used
Manual Code Review

## Recommended Mitigation Steps
Set the **userClaimedEpoch** variable to jus the **claimEnd** variable.
```
userClaimedEpoch[_market][lender] = claimEnd; 
```


## Assessed type

Other
Delegation empowers more voting weight than expected
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Description and Impact

The user's voting power decreases linearly from the moment of the lock. To prevent this decrease, users can delegate their voting power to a new wallet.

Here is an example scenario illustrating this:

1.  The user owns two wallets, Wallet A and Wallet B.
2.  The user locks 100 ether tokens for Wallet A.
3.  Over time, the user's voting power decreases gradually. A year later, just before voting for gauges, the user locks 1 wei for Wallet B and delegates the voting power from Wallet A to Wallet B.
4.  As a result, the `delegated` value of Wallet B would be 100 ether + 1 wei.
5.  By doing so, user's voting power barely decays.

The root cause is that the `delegate` function delegates the locked amount to the delegatee, rather than delegating the remaining voting power.

## Proof of Concept
```solidity
    function testKeepVotingPower() public {
        // wallet A => user1, wallet B => user2
        // create lock for wallet A.
        testSuccessCreateLock();
        console.log("wallet A: ", ve.balanceOf(user1)); // 997260267512249604
        vm.warp(block.timestamp + 365 days);
        console.log("wallet A(1 year later): ", ve.balanceOf(user1)); // 797260267535993604
        vm.prank(user2);
        // create lock for wallet B.
        ve.createLock{value: 1 wei}(1 wei);
        vm.prank(user1);
        ve.delegate(user2);
        console.log("wallet B: ", ve.balanceOf(user2)); // 996712322306835204
    }
```

Output:
```sh
Running 1 test for src/test/VotingEscrow.t.sol:VotingEscrowTest
[PASS] testKeepVotingPower() (gas: 4430009)
Logs:
  wallet A:  997260267512249604
  wallet A(1 year later):  797260267535993604
  wallet B:  996712322306835204
```

## Tools Used

## Recommended Mitigation Steps

Possible solutions:

- Restrict users from delegating their voting rights only to a delegatee with an identical end time.
- Update the `fromLocked.end` to the `toLocked.end`, to extend users' lock time.


## Assessed type

Governance