xanoutas/solidity-security-findings huggingface dataset:
Voting power can be increased by delegating
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
In `delegate()` of VoteEscrow.sol, a user can delegate their voting power to another lock. The total voting power is determined by lock time and amount of the *delegated* lock. This is problematic because locks can essentially increase their voting power without committing more lock time. For example, they can have max voting power for the entire duration of their lock by delegate to a lock with max end time.

## Proof of Concept
1. Alice the whale has a lock that is about to expire. Let's say there's less than 1 day left, her voting power would have decayed by 99%
2. Alice creates a new lock with 1 wei
3. Alice delegates her whale lock to her second lock
4. She now has max voting power
5. Alice is able to max out her voting power at any point during her lock. All she has to do is manage her second lock time (e.g., call `increaseAmount()`.

This is a test to be added into VoteEscrow.t.sol. It can be manually run by executing `forge test --match-test testSuccessDelegateWithExpiredLock`. 
```solidity
function testSuccessDelegateWithExpiredLock() public {
	// successful redelegate
	testSuccessDelegate();

	// Expire locks
	vm.warp(ve.LOCKTIME() + 1 days);
	assertEq(ve.balanceOf(user1), 0);
	assertEq(ve.balanceOf(user2), 0);

	// Create a new lock for user3
	vm.prank(user3);
	ve.createLock{value: LOCK_AMT}(LOCK_AMT);

	vm.prank(user1);
	ve.delegate(user3);

	vm.prank(user2);
	ve.delegate(user3);

	// Should have the lock amount from user1
	(, , int128 afterDelegated, address delegatee) = ve.locked(user3);
	assertEq(delegatee, user3);
	assertEq(uint256(uint128(afterDelegated)), 3 * LOCK_AMT);
	assertApproxEqRel(ve.balanceOf(user3), 3 * LOCK_AMT, 0.1e18);
}
```

## Tools Used
Manual

## Recommended Mitigation Steps
Consider updating the delegator's lock end time to *increase* to the delegatee's lock end time. This time sync up mitigates the effects of receiving "free" voting power through delegating. In the above scenario, Alice's end time will increase to 5 years. However, this is still an issue if Alice does not delegate to anyone else within the 5 years.

Another consideration is to remove the time element of voting power. Unlike other vote escrow systems (e.g. Curve), users don't have a choice of lock time. Thus, there is no need to incentivize to max lock.


## Assessed type

Timing
Users can front-run calls to `change_gauge_weight` to gain extra voting power
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Users can get extra voting power by front-running calls to `change_gauge_weight`

## Proof of Concept
It can be expected that in some cases calls will be made to `change_gauge_weight` to increase or decrease a gauge's weight. The problem is users can be monitoring the mempool expecting such calls. Upon seeing such, any people who have voted for said gauge can just remove their vote prior to `change_gauge_weight`. Once it executes, they can vote again for their gauge, increasing its weight more than it was expected to be: 
Example: 
1. Gauge has 1 user who has voted and contributed for 10_000 weight
2. They see an admin calling `change_gauge_weight` with value 15_000.
3. User front-runs it and removes all their weight. Gauge weight is now 0.
4. Admin function executes. Gauge weight is now 15_000
5. User votes once again for the gauge for the same initial 10_000 weight. Gauge weight is now 25_000.

Gauge weight was supposed to be changed from 10_000 to 15_000, but due to the user front-running, gauge weight is now 25_000

## Tools Used
Manual review

## Recommended Mitigation Steps
Instead of having a set function, use increase/ decrease methods.


## Assessed type

Other
Unsafe downcasting in `VotingEscrow::createLock` could prevent a user from redeeming all of their locked CANTO
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
If the user locks more than `type(int128).max` the downcast in `VotingEscrow` L276 would incorrectly set the `locked._amount` value, so the user would receive less than the CANTO they locked into `VoteEscrow` when they call `withdraw`. 

This issue was mentioned in the FIAT DAO veFDT contest (of which the `VotingEscrow` is a fork) here: https://github.com/code-423n4/2022-08-fiatdao-findings/issues/228

Given the current price of CANTO it may be unlikely that someone would hold enough to cause this issue but if the price drops significantly it may be possible.
## Proof of Concept
1. Bob locks `2**128` CANTO by calling `createLock(uint256 2**128)` and sending 2**128 CANTO in the call
2. The downcasting of the `_value` on L276 `int128(int256(_value))` results in the `locked_.amount = 0`
3. When Bob tries to call `withdraw` after his lock has expired he will receive no CANTO

## Tools Used
Manual Review

## Recommended Mitigation Steps
Perform a check that limits the size of the `_value` passed into a lock to 2**128. If a user wants to lock more than this require them to use `increaseAmount` to reach their desired locked amount.  



## Assessed type

Under/Overflow
Unsafe downcasting in `VotingEscrow::createLock` could prevent a user from redeeming all of their locked CANTO
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
If the user locks more than `type(int128).max` the downcast in `VotingEscrow` L276 would incorrectly set the `locked._amount` value, so the user would receive less than the CANTO they locked into `VoteEscrow` when they call `withdraw`. 

This issue was mentioned in the FIAT DAO veFDT contest (of which the `VotingEscrow` is a fork) here: https://github.com/code-423n4/2022-08-fiatdao-findings/issues/228

## Proof of Concept
1. Bob tries to lock `2**128` CANTO 

## Tools Used
Manual Review

## Recommended Mitigation Steps



## Assessed type

DoS
Add ReentrancyGuard to smart contract `LendingLedger.sol`
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The `claim` function in smart contract `LendingLedger.sol` used external `call` function to sent Canto.

## Proof of Concept
The `claim` function invokes external `call` function to sent Canto. 
`File: LendingLedger.sol`
`179: (bool success, ) = msg.sender.call{value: cantoToSend}("");`
https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L179
When a method makes external call, it should use nonReentrant modifier to prevent the function from reentracy attack.

## Tools Used
Manual Review
## Recommended Mitigation Steps

It is highly recommended to add nonReentrant modifier to this function. This can prevent potential reentrancy attack or if it's necessary to prevent multiple claims from the same user within a single transaction. This will ensure that the function can only be called once per transaction.



## Assessed type

Reentrancy
Empty receive() function
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
There is an empty `receive payable` function in smart contract `LendingLedger.sol` and there is no withdraw function in this contract. An empty receive function without a withdraw function can be a security vulnerability. If the receive function is empty, it means that the contract will accept incoming transactions but won't perform any actions or store the received funds.


## Proof of Concept
While having an empty receive function alone may not pose an immediate security risk, it can lead to unintended consequences. 
For example, if the contract is meant to handle incoming funds and perform certain actions based on the received amount, an empty receive function can prevent those actions from being executed.
Additionally, if the contract doesn't have a withdraw function, it means that the received funds cannot be transferred out of the contract. This can potentially lead to funds being locked within the contract indefinitely, making them inaccessible to the contract owner or users.

## Reference Code 
`File: src/LendingLedger.sol`
`209:  receive() external payable {}`
https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L209

## Tools Used
Manual Review

## Recommended Mitigation Steps
To ensure the security and functionality of your contract, it's recommended to carefully design and implement the receive and withdraw functions based on specific use case. The receive function should handle incoming funds appropriately, and the withdraw function should allow the contract owner or users to retrieve the funds as needed.


## Assessed type

Other
USER CAN VOTE SO MANY TIMES IN GaugeController.sol
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
User can vote so many time in a short period, this is bad if a user has a lot power, he can take advantage of that, voting in specific time to his benefecy, even this can be used to some kind of manipulating.

## Proof of Concept
The contract is creating last_user_vote variable and assigning values but those values are never checked.

```
mapping(address => mapping(address => uint256)) public last_user_vote;

function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external {
        ...

        // Record last action time
        last_user_vote[msg.sender][_gauge_addr] = block.timestamp;
    }

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

I have to say that in the curve contract this is implemented:

```
def vote_for_gauge_weights(_gauge_addr: address, _user_weight: uint256):
    ...
    assert block.timestamp >= self.last_user_vote[msg.sender][_gauge_addr] + WEIGHT_VOTE_DELAY, "Cannot vote so often"
    ...
    self.last_user_vote[msg.sender][_gauge_addr] = block.timestamp
```
https://github.com/curvefi/curve-dao-contracts/blob/567927551903f71ce5a73049e077be87111963cc/contracts/GaugeController.vy#L498

    
## Tools Used
manual

## Recommended Mitigation Steps
Add check for the last_user_vote, you can follow the vyper contract approach:
```
assert block.timestamp >= self.last_user_vote[msg.sender][_gauge_addr] + WEIGHT_VOTE_DELAY, "Cannot vote so often"
```


## Assessed type

Other
Inconsistent logic of increase unlock time to the expired locks
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/9a2e7be003bc1a77b3b87db31f3d5a1bcb48ed32/src/VotingEscrow.sol#L288-L323
https://github.com/code-423n4/2023-08-verwa/blob/9a2e7be003bc1a77b3b87db31f3d5a1bcb48ed32/src/VotingEscrow.sol#L294


# Vulnerability details

## Vulnerability details
 Inconsistent logic of increase unlock time to the expired locks

## Impact
Can not prevent expired locks being extended.

## Proof of Concept
https://github.com/code-423n4/2023-08-verwa/blob/9a2e7be003bc1a77b3b87db31f3d5a1bcb48ed32/src/VotingEscrow.sol#L288C1-L323C6

Call function increaseUnlockTime() with an expired lock `(locked[msg.sender].end < block.timestamp)`

Case 1: if sender's lock was not delegated to another address, function will be revert because of the requirement
https://github.com/code-423n4/2023-08-verwa/blob/9a2e7be003bc1a77b3b87db31f3d5a1bcb48ed32/src/VotingEscrow.sol#L294
Case 2: if sender's lock was delegated to another address, function will not check anything and the lock can be extended.
But in case 1, sender’s lock was not delegated to another, the sender can delegate to new address with end time of lock equal to new end time. After that he can call increaseUnlockTime() and move to case 2. Then sender can undelegate and the lock will be extended, and sender will take back vote power.

##Tools Used
Manual review, previous reports

##Recommended Mitigation Steps
additional require check to ensure the delegator's lock is not expired.


## Assessed type

Other
Missing contract-existence checks before low-level calls
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
As written in the solidity documentation, 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.

## Proof of Concept
The low-level functions call is used in some places in the code and it can be problematic. For example, in the 'claim' function of the LendingLedger contract there is a low level call in order to call the ERC20 functions, but if the given Address has mo code success will be equal to true and the function will return true and the code execution will be continued like the call was successful.
https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L179

## Tools Used
Manual Review

## Recommended Mitigation Steps
Check before any low-level call that the address actually exists, for example before the low level call in the callERC20 function you can check that the address is a contract by checking its code size.


## Assessed type

ERC20
Unsafe downcasting from int128 can cause wrong accounting of locked amounts
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L268-#L284
https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L288-#L323


# Vulnerability details

## Impact
Deposited value can be counted wrong

## Proof of Concept
In the `increaseAmount` and `createLock` function, the addition to locked amount variable is done by

        locked_.amount += int128(int256(_value));

In that case, if _value is greater than 2**127 - 1, the accounting will be wrong and the amount that will be added to locked_.amount will be less than the amount that will be transferred from the user

## Tools Used
Manual review

## Recommended Mitigation Steps
Make require that value must less than int128, or handle case value is greatẻ than int128


## Assessed type

Under/Overflow
User can lost eth by accidentally send eth to LendingLedger contract.
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
User can lost eth by accidentally send eth to LendingLedger contract.

## Proof of Concept
In `LendingLedger` contract, there is a receive() function to receive eth:

    receive() external payable {}

But problem is there is no mechanism to withdraw these eth outside of contract. So if someone accidentally send eth to this contract, they will lost it forever.

## Tools Used
Manual review.

## Recommended Mitigation Steps
Remove receive() function


## Assessed type

Other
Unrestricted Frequency of User Voting in GaugeController
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The GaugeController's design currently doesn't restrict users from frequently reallocating their votes. The system as is would give more power to the people having more votes, thus increasing centralization. 

## Proof of Concept

Missing delay period for voting 

## Tools Used

Manual review

## Recommended Mitigation Steps

Impose a restriction on voting frequency just like the GaugeController.vy does.


## Assessed type

Timing
Denial of Service due to pointHistory fixed-size array 
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

#### Impact
When `pointHistory` reaches the last position of the array (1e18), users will be unable to use most functions in the contract eg., users cannot withdraw their funds.

#### Proof of Concept

The `pointHistory` array has a size of 1e18.

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

```solidity=37
Point[1000000000000000000] public pointHistory; // 1e9 * userPointHistory-length, so sufficient for 1e9 users
```

The usage of `pointHistory` is in the `_checkpoint()` function, where the epoch value in `pointHistory[epoch]` will increase by 1 every time the `_checkpoint()` is called. If the epoch increase reaches the last array position of the `pointHistory`, every transaction that calls this function will be reverted.

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

```solidity=211
    epoch = epoch + 1;
    if (iterativeTime == block.timestamp) {
        lastPoint.blk = block.number;
        break;
    } else {
        pointHistory[epoch] = lastPoint;
    }
}

globalEpoch = epoch;
```

Most of the functions in the contract call the `_checkpoint()` function to update the checkpoints of both individual and global slopes, for example, the `createLock()` function.

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

```solidity=268
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);
}
```

As a result, if the `pointHistory` size reaches the last position of the array, every transaction that calls `_checkpoint()` function will be reverted, which will make most of the functions in the contract unusable.

Effected function
* checkpoint()
* createLock()
* increaseAmount()
* withdraw()
* delegate()

Furthermore, the attacker can execute `checkpoint()` function `1e18-1` times after the contract has been deployed to make `pointHistory` size reach the last position of the array.

The estimated cost for attacker to execute the `checkpoint()` function `1e18-1` times and potentially cause a Denial of Service (DoS) attack on the contract is approximately `71256 * (1e18-1) / 1e18` = 71,256 C
No way to handle or withdraw received ether.
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
There is a payable ``receive()`` function in ``src/LendingLedger.sol`` but has no functionality and is a empty function. It means anyone can send ether to the ``LendingLedger`` contract which will increase the balance of this contract but there is no way to withdraw that balance by anyone resulting in ether getting stuck forever. 

## Tools Used
Manual Analysis

## Recommended Mitigation Steps
Remove the payable ``receive()`` function or add functionality to withdraw ether.








## Assessed type

ETH-Transfer
Unsafe casting from int128 can cause wrong accounting of locked amounts
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The unsafe casting to int128 variable can cause its value to be different from the correct value. In the `createLock` and `increaseAmount` functions, the addition to the locked amount variable is done by `locked_.amount += int128(int256(_value))`. In that case, if `_value` is greater than `type(int128).max` which is `2**127 - 1`, then the accounting will be wrong and the amount that will be added to `locked_.amount` will be less than the amount of token that will be transferred from the user. Then the user won't be able to withdraw the tokens that he transferred, and they'll be stuck in the contract forever.

## Proof of Concept
1. Alice tries to lock `2**128 tokens`. She calls `createLock(2**128)`.
2. The addition of the given value is done by `locked_.amount += int128(int256(_value))`, which actually does nothing to the `locked_.amount` variable and it remains 0. That's because when casting `int128(int256(2**128))` truncates to 0, and that leaves the locked amount unchanged but the tokens are transferred.

## Tools Used
Manual review

## Recommended Mitigation Steps
Make sure that the values fit in the variables you are trying to assign them to when casting variables to smaller types.

```solidity
require(_value <= type(int128).max, "Value too large");
```


## Assessed type

Decimal
`GaugeController._get_weight()` function does not check  if  `pt.slope < d_slope` ,it may cause revert and DoS
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

`GaugeController._get_weight()` function does not check  if  `pt.slope < d_slope` ,it may cause revert and DoS

## Proof of Concept

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

## Tools Used

vs code

## Recommended Mitigation Steps

Verify it such as :
```solidity
uint256 d_slope = changes_weight[_gauge_addr][t];
if (pt.slope < d_slope)
{
  pt.bias = 0;
  pt.slope = 0;
}
...
```


## Assessed type

Error