xanoutas/solidity-security-findings huggingface dataset:
LP rewards in `liquidity_lockbox` can be arbitraged
Audit: 2023-12-autonolas-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/main/lockbox-solana/solidity/liquidity_lockbox.sol#L295-L307


# Vulnerability details

## Impact
The [`liquidity_lockbox`](https://github.com/code-423n4/2023-12-autonolas/blob/main/lockbox-solana/solidity/liquidity_lockbox.sol) contract is designed to handle liquidity positions in a specific Orca LP pool. Users can deposit their LP NFTs into the contract, receiving in exchange tokens according to their position size. These tokens are minted with the goal of allowing users to bridge them to Ethereum later on and exchange them for OLAS at a discount.

However, a potential vulnerability arises from the unrestricted nature of the deposit and withdrawal functions. Specifically, a user can deposit a large amount of assets and immediately withdraw all existing positions using the tokens they just received. This sequence of actions can be repeated to continuously exploit the LP rewards system, leading to an unfair distribution of rewards.

The root cause of this issue lies in the `withdraw()` function, which does not have any restrictions or checks that prevent immediate withdrawal after a deposit and pays all accrued LP rewards [to the caller](https://github.com/code-423n4/2023-12-autonolas/blob/main/lockbox-solana/solidity/liquidity_lockbox.sol#L295-L307).

## Proof of Concept
Consider the following scenario:

1. Alice obtains a large amount of tokens either through a [flashloan](https://github.com/solendprotocol/solana-program-library/blob/master/token-lending/program/src/processor.rs#L102) or by buying them.
2. Alice uses these tokens to open a large position in the Orca pool.
3. Alice deposits the position NFT into the `liquidity_lockbox` contract, receiving a large amount of bridge tokens.
4. Alice withdraws all existing positions using the tokens she just received and receives the LP rewards.
5. Alice closes the received positions in the Orca pool, repays the flashloan (if used) and pockets the rewards.

This sequence of actions can be repeated by Alice to continuously exploit the LP rewards system.

## Tools Used
Manual review

## Recommended Mitigation Steps
To mitigate this issue, a possible solution could be to implement a lock-up period for deposited positions. This would prevent users from immediately withdrawing their positions after depositing. Additionally, consider not distributing rewards to the withdrawing user (which will never be fair) and instead collecting them for the protocol.





## Assessed type

Other
Griefing attack on `liquidity_lockbox` withdrawals due to lack of minimum deposit
Audit: 2023-12-autonolas-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/main/lockbox-solana/solidity/liquidity_lockbox.sol#L140-L190


# Vulnerability details

## Impact
The [`liquidity_lockbox`](https://github.com/code-423n4/2023-12-autonolas/blob/main/lockbox-solana/solidity/liquidity_lockbox.sol) contract does not enforce a minimum deposit limit. This allows a user to open many positions with minimum liquidity, forcing other users to close these positions one by one in order to withdraw. This could lead to a griefing attack where the transaction cost accounts for a large portion of the withdrawn amount. 

While transactions on Solana are cheap and it is difficult to assess the cost of a withdrawal as the external call on line fails, an accumulation of small deposits as portrayed will in any case disrupt contract operations by making substantial fund withdrawals labor-intensive.

The root cause of this issue is the lack of a minimum deposit threshold in the [`deposit()`](https://github.com/code-423n4/2023-12-autonolas/blob/main/lockbox-solana/solidity/liquidity_lockbox.sol#L140) function in `lockbox-solana/solidity/liquidity_lockbox.sol`.

## Proof of Concept
Consider the following scenario:

1. Alice, a malicious user, opens a large number of positions with minimum liquidity in the `liquidity_lockbox` contract.
2. Bob, a regular user, wants to withdraw his funds. However, he is forced to close Alice's positions one by one due to the lack of a minimum deposit threshold.
3. The transaction cost for Bob becomes a significant portion of the withdrawn amount, making the withdrawal of funds inefficient and costly.

## Tools Used
Manual review

## Recommended Mitigation Steps
To mitigate this issue, a minimum deposit threshold should be implemented in the `deposit()` function. This would prevent users from opening positions with minimum liquidity and protect other users from potential griefing attacks. The threshold should be carefully chosen to balance the need for user flexibility and the protection against potential attacks. Additionally, consider implementing a mechanism to batch close positions to further protect against such scenarios.


## Assessed type

Other
[M2] DrainServicesSlashedFunds has not check for received funds
Audit: 2023-12-autonolas-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Treasury.sol#L466


# Vulnerability details

### Impact

​    Loss of funds.

### Analysis of the vulnerability

The function to drain funds from  Service Registry is not checking that is receiving the correct amount from ServiceRegistry.



             /// @dev Drains slashed funds from the service registry.
        /// @return amount Drained amount.
        /// #if_succeeds {:msg "correct update total eth balance"} address(this).balance == old(address(this).balance) + amount;
        /// #if_succeeds {:msg "conservation law"} ETHFromServices + ETHOwned == old(ETHFromServices + ETHOwned) + amount;
        ///if_succeeds {:msg "slashed funds check"} IServiceRegistry(ITokenomics(tokenomics).serviceRegistry()).slashedFunds() >= minAcceptedETH
        /// ==> old(IServiceRegistry(ITokenomics(tokenomics).serviceRegistry()).slashedFunds()) == amount;
         
         function drainServiceSlashedFunds() external returns (uint256 amount) {
            // Check for the contract ownership
            if (msg.sender != owner) {
                revert OwnerOnly(msg.sender, owner);
            }
    
            // Get the service registry contract address
            address serviceRegistry = ITokenomics(tokenomics).serviceRegistry();
    
            // Check if the amount of slashed funds are at least the minimum required amount to receive by the Treasury
            uint256 slashedFunds = IServiceRegistry(serviceRegistry).slashedFunds();
            if (slashedFunds < minAcceptedETH) {
                revert LowerThan(slashedFunds, minAcceptedETH);
            }
    
            // Call the service registry drain function
            amount = IServiceRegistry(serviceRegistry).drain();
        }



If there's a bug in the updateable serviceRegistry, funds could be sent elsewhere.



## Recommendation



Add at the beginning of the function.

   ```
uint balanceBefore = address(this).balance;
   ```

And at the end

 uint balanceAfter = address(this).balance;
 require(balanceBefore + amount = balanceAfter, "Balance not propertly updated")


## Assessed type

ETH-Transfer
GovernorOLAS is susceptible to DOS via proposal frontrunning
Audit: 2023-12-autonolas-findings
Severity: MEDIUM

# Lines of code

 https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/GovernorOLAS.sol#L14-L15
 https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/package.json#L28
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/0a25c1940ca220686588c4af3ec526f725fe2582/contracts/governance/Governor.sol#L263
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/0a25c1940ca220686588c4af3ec526f725fe2582/contracts/governance/compatibility/GovernorCompatibilityBravo.sol#L107


# Vulnerability details

## Impact
The `GovernorOLAS` contract inherits from OpenZeppelin's `GovernorCompatibilityBravo` v4.8.3, which has a known vulnerability in the proposal creation process that can be exploited to halt proposals sent to the governor. 

The root cause of this vulnerability is the exposure of the `cancel()` function. This can be exploited by a malicious user by frontrunning a call to `propose()` in the mempool. The attacker can submit the exact same proposal and then call `cancel()` on their proposal, effectively blocking the original proposal.

This vulnerability can lead to a denial of service attack on the governance system, preventing legitimate proposals from being processed.

For more details, refer to OpenZeppelin's [security advisory](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-5h3x-9wvq-w4m2) and this [vulnerability report](https://www.coinspect.com/openzeppelin-governor-dos/).

## Proof of Concept
Consider the following scenario:

1. Alice creates a proposal by calling the `propose()` function.
2. Bob, a malicious user, sees Alice's transaction in the mempool and submits the same transaction with a higher gas price.
3. Bob's transaction is executed before Alice's.
4. Alice's transaction fails since the proposal already exists.
5. Bob then calls the `cancel()` function on his proposal, blocking its execution.

## Tools Used
Manual review

## Recommended Mitigation Steps
Upgrade `@openzeppelin/contracts` to version v4.9.1 or later, which introduces opt-in frontrunning protection.


## Assessed type

Governance
Change owner() function can pass in wrong address and authorize malicious user in Treasury.sol
Audit: 2023-12-autonolas-findings
Severity: MEDIUM

# Lines of code

 https://github.com/code-423n4/2023-12-autonolas/blob/main/tokenomics/contracts/Treasury.sol#L135-L150


# Vulnerability details

## Impact
A Wrong or malicious address can be made owner of the treasury.sol contract which is used to manage the Olas treasury.
This is possible because there is no additional function in the treasury.sol Contract which will enable a new Owner (actually meant to be a pending Owner at the onset of the ChangeOwner function initialization) to confirm the Update.
## Proof of Concept
changeOwner() function is called by the treasury contract owner to change ownership but a wrong address was passed.
Access to Treasury contract is compromised which may albeit be irreversble.
Funds in treasury contract are exploited by malicius user or in the most possible scenario rendered useless.

```
 function changeOwner(address newOwner) external {
        // Check for the contract ownership
        if (msg.sender != owner) {
            revert OwnerOnly(msg.sender, owner);
        }

        // Check for the zero address
        if (newOwner == address(0)) {
            revert ZeroAddress();
        }

        owner = newOwner;
        emit OwnerUpdated(newOwner);
    }
```


## Tools Used
Manual review
## Recommended Mitigation Steps

A confirmOwner function should be provided with access controls to confirm that a pending owner address is as intended by the previous owner.

```
function changeOwner(address newOwner) external {
        // Check for the contract ownership
        if (msg.sender != owner) {
            revert OwnerOnly(msg.sender, owner);
        }

        // Check for the zero address
        if (newOwner == address(0)) {
            revert ZeroAddress();
        }

        pendingOwner = newOwner;
        emit pendingOwnerUpdated(pendingOwner);
    }
```


```
 function confirmPendingOwner() external {
        // Check for the contract ownership
        if (msg.sender != pendingOwner) {
            revert pendingOwnerOnly(msg.sender, pendingOwner);
        }

        // Check for the zero address
        if (pendingOwner == address(0)) {
            revert ZeroAddress();
        }

        owner = pendingOwner;
        emit OwnerUpdated(owner);
    }
```


## Assessed type

Access Control
Decisionmaking in the DAO is to slow which results in the DAO not been able to use the `CANCELLER_ROLE` properly
Audit: 2023-12-autonolas-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/lib/fx-portal/lib/openzeppelin-contracts/contracts/governance/TimelockController.sol#L276-L281


# Vulnerability details

## Impact
The time that takes the DAO to vote on a proposal is to long for the DAO to be a proper address to cancel scheduled operations. That is why no scheduled operation will ever be cancelled and potentially malicious operations from security exploits will be executed.


## Proof of Concept
According to the team, the DAO will be the only address to hold the `CANCELLER_ROLE`. This role is needed to cancel any operations that have been scheduled in the timelock contract. For the DAO to be able to execute any operation, like cancelling a scheduled operation in `Timelock`, it must pass an according proposal. The problem is that the `_votingPeriod` for a proposal will be set to 1 week. The `minDelay` between scheduling an operation in `Timelock` and executing it, is smaller than one week. This means that the DAO will not be able to cancel any operation that has a delay of less than a week. Even if the `_votingPeriod` would be shorter, the DAO would not be able to cancel any operation with the `minDelay` since executing the proposal to cancel the operation will itself be subject to the `minDelay`. This means that the security measure of canceling potentialy melicious operations will be useless. 

## Tools Used
Manual review


## Recommended Mitigation Steps
To be able to use the cancel feature of the timelock propperly, there should be another address holding the `CANCELLER_ROLE` beside the DAO. This address should be able to call `cancel()` without any delay to ensure maximum security. Such an address could be the community multisig.



## Assessed type

Governance
Olas can be locked less than 1 week 
Audit: 2023-12-autonolas-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/main/governance/contracts/veOLAS.sol#L441-L443


# Vulnerability details

## Impact
The minimum lock time for `OLAS` is 1 week. If a user locks their `OLAS` for a duration less than that, their voting power becomes zero. However, in `OLAS:_createLockFor`, the system only checks if the unlockTime is greater than the block.timestamp. Consequently, this results in zero voting power, regardless of the amount of OLAS tokens a user deposits or locks.

## Proof of Concept
Add this code at `veOLAS.js`
```javascript
        it("Olas can be locked less than 1 week ", async function () {

            const deployer = signers[0];

            await olas.approve(ve.address, oneOLABalance);

            console.log(await ve.getVotes(deployer.address));

            const lockDuration = 1 * 86400;

            ve.createLock(oneOLABalance, lockDuration);

            console.log(await ve.getVotes(deployer.address));
        });

```

## Tools Used

Hardhat

## Recommended Mitigation Steps
Update code at `OLAS:_createLockFor`
```diff
// Check for the lock time correctness
++if (unlockTime < (block.timestamp + WEEK)) {
--if (unlockTime < (block.timestamp + 1)) {
   revert UnlockTimeIncorrect(account, block.timestamp, unlockTime);
}
```


## Assessed type

Invalid Validation
Leap year not accounted for OLAS and veOLAS contract
Audit: 2023-12-autonolas-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/OLAS.sol#L22
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/veOLAS.sol#L101


# Vulnerability details

## Impact
OLAS and veOLAS contract does not account for years with 366 days in it's state variables ``` uint256 public constant oneYear = 1 days * 365;``` and ```uint256 internal constant MAXTIME = 4 * 365 * 86400;```

The problem with this is that, in OLAS.sol, minter can exceed ```uint256 public constant tenYearSupplyCap = 1_000_000_000e18;``` 2.5days earlier than expected, and in veOLAS.sol, MAXTIME is 1 day shorter in real life.

## Proof of Concept 
In ten years there will be at least two years with 366 days, and in 4 years, there will be a year with 366 days, oneYear and MAXTIME does not account for this real-life instance.
``` solidity 
uint256 public constant oneYear = 1 days * 365;
```
``` solidity
uint256 internal constant MAXTIME = 4 * 365 * 86400;
```

## Tools Used
Manual review

## Recommended Mitigation Steps
``` solidity 
uint256 public constant oneYear = 31_557_600;
```
``` solidity
uint256 internal constant MAXTIME = 4 * 31_557_600;
```


## Assessed type

Timing
[M1] LastDonationBlockNumber should be updated at the beginning of the function to prevent from reentracy attack
Audit: 2023-12-autonolas-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L824


# Vulnerability details

### Impact

  The ineffectiveness of an  update against a flashloans attack.

### Analysis of the vulnerability


   You update `lastDonationBlockNumber` after every donation.  However, you update is done at the end of the function  `trackServiceDonations`

     function trackServiceDonations(
        address donator,
        uint256[] memory serviceIds,
        uint256[] memory amounts,
        uint256 donationETH
    ) external {
        // Check for the treasury access
        if (treasury != msg.sender) {
            revert ManagerOnly(msg.sender, treasury);
        }
    
        // Check if the donator blacklist is enabled, and the status of the donator address
        address bList = donatorBlacklist;
        if (bList != address(0) && IDonatorBlacklist(bList).isDonatorBlacklisted(donator)) {
            revert DonatorBlacklisted(donator);
        }
    
        // Get the number of services
        uint256 numServices = serviceIds.length;
        // Loop over service Ids, accumulate donation value and check for the service existence
        for (uint256 i = 0; i < numServices; ++i) {
            // Check for the service Id existence
            if (!IServiceRegistry(serviceRegistry).exists(serviceIds[i])) {
                revert ServiceDoesNotExist(serviceIds[i]);
            }
        }
        // Get the current epoch
        uint256 curEpoch = epochCounter;
        // Increase the total service donation balance per epoch
        donationETH += mapEpochTokenomics[curEpoch].epochPoint.totalDonationsETH;
        mapEpochTokenomics[curEpoch].epochPoint.totalDonationsETH = uint96(donationETH);
    
        // Track service donations
        _trackServiceDonations(donator, serviceIds, amounts, curEpoch);
    
        // Set the current block number
        lastDonationBlockNumber = uint32(block.number);
    }



It is recommended to update at the beginning due to external calls that could invoke `checkpoint()` in the same block.

For now, it seems to be safe, otherwise I would have classified this as high-risk.    

However, keep in mind that the contracts mentioned above can be upgraded using the `changeRegistries` and `changeDonatorBlacklist` functions, which means you may not be fully protected.



## Recommendation



​     It is always safer to update the storage before any external call .

Add

​    `lastDonationBlockNumber = uint32(block.number)` at the beginning.


## Assessed type

Reentrancy
NO access control in decreaseAllowance and increaseAllowance
Audit: 2023-12-autonolas-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/main/governance/contracts/OLAS.sol#L128
https://github.com/code-423n4/2023-12-autonolas/blob/main/governance/contracts/OLAS.sol#L145


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
NO access control in decreaseAllowance and increaseAllowance.Anyone can call this function and increase or decrease the allowlance.
## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

 function decreaseAllowance(address spender, uint256 amount) external returns (bool) {
        uint256 spenderAllowance = allowance[msg.sender][spender];

        if (spenderAllowance != type(uint256).max) {
            spenderAllowance -= amount;
            allowance[msg.sender][spender] = spenderAllowance;
            emit Approval(msg.sender, spender, spenderAllowance);
        }

        return true;
    }

    /// @dev Increases the allowance of another account over their tokens.
    /// @notice The overflow condition is treated by the default code generation check.
    /// @param spender Account that tokens are approved for.
    /// @param amount Amount to increase approval by.
    /// @return True if the operation succeeded.
    function increaseAllowance(address spender, uint256 amount) external returns (bool) {
        uint256 spenderAllowance = allowance[msg.sender][spender];

        spenderAllowance += amount;
        allowance[msg.sender][spender] = spenderAllowance;
        emit Approval(msg.sender, spender, spenderAllowance);

        return true;
    }
}
## Tools Used

## Recommended Mitigation Steps
use some modifier for access control


## Assessed type

Access Control