xanoutas/solidity-security-findings huggingface dataset:
Time delay for operations scheduled by the community multisig (CM) in `timelock` pose a risk to the protocol and it users 
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/lib/fx-portal/lib/openzeppelin-contracts/contracts/governance/TimelockController.sol#L220-L231
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/lib/fx-portal/lib/openzeppelin-contracts/contracts/governance/TimelockController.sol#L263-L267
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
Time sensitive operations done by the CM, like taking actions in case of an security exploit, are subject to the `_minDelay` of the `Timelock` contract. Such operations are time sensitive and executing them several minutes later can result in significant losses for the protocol and its users.


## Proof of Concept
The CM is a “multisig wallet made up of a set of trusted actors” which has the option to bypass the governance process for certain operations whitelisted by the governance to eg. act in case of “a security exploit that needs patching”. The issue is that all operations scheduled by the CM are subject to the `minDelay`;
```java
    function _schedule(bytes32 id, uint256 delay) private {
        require(!isOperation(id), "TimelockController: operation already scheduled");
        require(delay >= getMinDelay(), "TimelockController: insufficient delay");
        _timestamps[id] = block.timestamp + delay;
    }
```  

Depending on the minDelay set by for the timelock, this can delay the patch of critical issues by valuable minutes which can result in a significant loss of funds that could have been prevented otherwise. The delay for such critical operations is especially contra productive since, according to the team, only the DAO has the `CANCELLER_ROLE` that allows the holder to cancel scheduled operations. Assuming the `minDelay` of the timelock is less than the `minVotingTime` for the DAO, which is 1 week, the DAO will not be able to chancel the operation scheduled by the CM anyway.  



## Tools Used
Manual review


## Recommended Mitigation Steps
To enable the CM to react faster to security exploit and better protect the funds of the protocol and its users, consider examining operations scheduled by the CM from the time lock or at least the mots time sensitveonce like e.g. pausing contracts.



## Assessed type

Timing
Service Owner loses all of his/her topUp earnings when inflationControl returns false
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Dispenser.sol#L98-L105
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L1144-L1149
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Treasury.sol#L412-L418
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/OLAS.sol#L75-L83


# Vulnerability details

## Impact
The service Owner loses all of his topUp savings in Olas when the inflation limit is hit

## Proof of Concept
Assume, there has been donations to the service Id and it has collected some donations and has it stored in 

```solidity
mapUnitIncentives[unitTypes[i]][unitIds[i]].reward
and
mapUnitIncentives[unitTypes[i]][unitIds[i]].topUp
```

### 1) User withdraws from claimOwnerIncentives() in Dispenser
```solidity
function claimOwnerIncentives(uint256[] memory unitTypes, uint256[] memory unitIds) external
        returns (uint256 reward, uint256 topUp){
.
.
        // Calculate incentives
        (reward, topUp) = ITokenomics(tokenomics).accountOwnerIncentives(msg.sender, unitTypes, unitIds);

        bool success;
        // Request treasury to transfer funds to msg.sender if reward > 0 or topUp > 0
        if ((reward + topUp) > 0) {
            success = ITreasury(treasury).withdrawToAccount(msg.sender, reward, topUp);
.
.
        }
```

### 2) accountOwnerIncentives in tokenomics resets reward and topUp

```solidity
function accountOwnerIncentives(address account, uint256[] memory unitTypes, uint256[] memory unitIds) external
        returns (uint256 reward, uint256 topUp)
{
            .
            .
            // Accumulate total rewards and clear their balances
            reward += mapUnitIncentives[unitTypes[i]][unitIds[i]].reward;
->          (mapUnitIncentives[unitTypes[i]][unitIds[i]].reward = 0; 
            // Accumulate total top-ups and clear their balances
            topUp += mapUnitIncentives[unitTypes[i]][unitIds[i]].topUp;
->          mapUnitIncentives[unitTypes[i]][unitIds[i]].topUp = 0; //@audit
      }
}

```
Here, we can see that reward and topup are set to 0 once the function is called.
This is okay if we revert when transaction to service owner fails, but this wasn't done properly for topups.

### 3) withdrawToAccount in Treasury verifies sending rewards and topups

```solidity
    function withdrawToAccount(address account, uint256 accountRewards, uint256 accountTopUps) external
        returns (bool success)
    {
    .
    .
        // Send ETH rewards, if any
        if (accountRewards > 0 && amountETHFromServices >= accountRewards) {
            amountETHFromServices -= accountRewards;
            ETHFromServices = uint96(amountETHFromServices);
            emit Withdraw(ETH_TOKEN_ADDRESS, account, accountRewards);
Wrong invocation of Whirpools's updateFeesAndRewards will cause it to always revert
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/lockbox-solana/solidity/liquidity_lockbox.sol#L277-L293


# Vulnerability details

## Impact
Deposits will be unwithdrawable from the lockbox 

## Proof of Concept
If the entire liquidity of a position has been removed, the withdraw function calls the `updateFeesAndRewards` function on the Orca pool before attempting to close the position.

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/lockbox-solana/solidity/liquidity_lockbox.sol#L277-L293
```solidity
    function withdraw(uint64 amount) external {
        address positionAddress = positionAccounts[firstAvailablePositionAccountIndex];
      
        ......

        uint64 positionLiquidity = mapPositionAccountLiquidity[positionAddress];
        
        ......

        uint64 remainder = positionLiquidity - amount;

        ......

        if (remainder == 0) {
            // Update fees for the position
            AccountMeta[4] metasUpdateFees = [
                AccountMeta({pubkey: pool, is_writable: true, is_signer: false}),
                AccountMeta({pubkey: positionAddress, is_writable: true, is_signer: false}),
                AccountMeta({pubkey: tx.accounts.tickArrayLower.key, is_writable: false, is_signer: false}),
                AccountMeta({pubkey: tx.accounts.tickArrayUpper.key, is_writable: false, is_signer: false})
            ];
            whirlpool.updateFeesAndRewards{accounts: metasUpdateFees, seeds: [[pdaProgramSeed, pdaBump]]}();
```

This is faulty as the `updateFeesAndRewards` function will always revert if the position's liquidity is 0.

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/lockbox-solana/solidity/interfaces/whirlpool.sol#L198

### Whirlpool source code:
[update_fees_and_rewards](https://github.com/orca-so/whirlpools/blob/3206c9cdfbf27c73c30cbcf5b6df2929cbf87618/programs/whirlpool/src/instructions/update_fees_and_rewards.rs#L27) -> [calculate_fee_and_reward_growths](https://github.com/orca-so/whirlpools/blob/3206c9cdfbf27c73c30cbcf5b6df2929cbf87618/programs/whirlpool/src/manager/liquidity_manager.rs#L72) -> [_calculate_modify_liquidity](https://github.com/orca-so/whirlpools/blob/3206c9cdfbf27c73c30cbcf5b6df2929cbf87618/programs/whirlpool/src/manager/liquidity_manager.rs#L97-L99)

https://github.com/orca-so/whirlpools/blob/3206c9cdfbf27c73c30cbcf5b6df2929cbf87618/programs/whirlpool/src/manager/liquidity_manager.rs#L97-L99
```rs
fn _calculate_modify_liquidity(
    whirlpool: &Whirlpool,
    position: &Position,
    tick_lower: &Tick,
    tick_upper: &Tick,
    tick_lower_index: i32,
    tick_upper_index: i32,
    liquidity_delta: i128,
    timestamp: u64,
) -> Result<ModifyLiquidityUpdate> {
    // Disallow only updating position fee and reward growth when position has zero liquidity
    if liquidity_delta == 0 && position.liquidity == 0 {
        return E
Attacker can cause deposits to be locked in the Solana lockbox
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/lockbox-solana/solidity/liquidity_lockbox.sol#L223


# Vulnerability details

## Impact
An attacker can cause deposits to be locked in the lockbox 

## Proof of Concept
In `withdraw`, if the position has 0 liquidity the execution is reverted

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/lockbox-solana/solidity/liquidity_lockbox.sol#L223
```solidity
    function withdraw(uint64 amount) external {
        address positionAddress = positionAccounts[firstAvailablePositionAccountIndex];
        
        .....

        uint64 positionLiquidity = mapPositionAccountLiquidity[positionAddress];
        // Check that the token account exists
        if (positionLiquidity == 0) {
            revert("No liquidity on a provided token account");
        }
```

Since the withdrawal positions are chosen sequentially, a position with liquidity 0 present in the ith index of `positionAccounts` array, will make all positions from `i -> end` locked and unwithdrawable.

Due to lack of checks in the deposit function, an attacker can make a depsoit with 0 liquidity resulting in the above scenario. This will lead to all funds associated with further deposits to be locked in the lockbox. 

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/lockbox-solana/solidity/liquidity_lockbox.sol#L84

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

### POC Test
https://gist.github.com/10xhash/168166ba89447fa6fbc61981866c1b28

## Tools Used
Manual review

## Recommended Mitigation Steps
Require liquidity of depositing positions to be non-zero


## Assessed type

Invalid Validation
Pausing the `GuardCM` does not work because the `governorCheckProposalId` is never set
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/multisigs/GuardCM.sol#L170-L182


# Vulnerability details

## Impact

Since the `governorCheckProposalId` of the proposal to check the activity of the governance is never set in `GuardCM`, the CM can never pause `GuardCM`, even if the governance is inactive. This will result in a stagnation of the protocol since no significant actions like changing key parameters of the tokenomics will be possible. 






## Proof of Concept
The `community multisig` (`CM`) is “a multisig wallet made up of a set of trusted actors” that can execute operations through the timeLock contract without the need of a governance proposal to be voted on. The operations the CM can do are limited to a list of contract and functions combinations (`mapAllowedTargetSelectorChainIds`) set by the governance in the `GuardCM` contract. This list is very limited to ensure that most of the actions taken are still subject to the governance process. 

In case the CM suspects that the governance is inactive and cannot make decisions anymore, it can deactivate the `GuardCM` and thereby bypass the limitation of operations it can take. To do this, the CM makes a proposal in the DAO that needs to pass to show that the governance is still active. Once the voting time for this proposal has passed, the CM can call `GuardCM::pause()` to pause the `GuardCM`. The function checks if the proposal made by the CM passed. If its status is defeated, the CM is allowed to pause the `GuardCM` and thereby lift the limitation of operations it can do:

```java
function pause() external {
        if (msg.sender == owner) { 
            // Timelock can release the community multisig right away
            paused = 2;
        } else if (msg.sender == multisig) {
            // Multisig needs to check if the governor check proposal Id state is defeated
            ProposalState state = IGovernor(governor).state(governorCheckProposalId);
            if (state == ProposalState.Defeated) {
                paused = 2;
            } else {
                revert NotDefeated(governorCheckProposalId, state);
            }
        } else {
            // msg.sender is not a timelock, nor a multisig
            revert ManagerOnly(msg.sender, multisig);
        }

        emit GuardPaused(msg.sender);
    }
```
The issue arises in the fact that only the owner of the `GuardCM` can change the variable `governorCheckProposalId` that is used to check for the status of the “is the governance active” proposal. 

```Java
    function changeGovernorCheckProposalId(uint256 proposalId) external {
        if (msg.sender != owner) {
            revert OwnerOnly(msg.sender, owner);
        }

        // Check for the zero value
        if (proposalId == 0) {
            revert ZeroValue();
        }

        governorCheckProposalId = proposalId;
        emit GovernorCheckProposalIdChanged(proposalId);
Bonds created in year cross epoch's can lead to lost payouts 
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L1037-L1038
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/OLAS.sol#L75-L84


# Vulnerability details

## Impact
Bond depositors and agent/component owner's may never receive the payout Olas 
Incorrect inflation control

## Proof of Concept
`effectiveBond` is used to account how much of Olas is available for bonding. This includes Olas that are to be minted in the current epoch ie. `effectiveBond` will include the Olas partitioned for bonding in epoch 5 at the beginning of epoch 5 itself. In case of epoch's crossing `YEAR` intervals, a portion of the Olas would actually only be mintable in the next year due to the yearwise inflation control enforced at the mint (after 9 years due to fixed supply till 10 years). Due to silent reverts, this can lead to lost Olas payouts

The inflation for bonds are accounted using the `effectiveBond` variable.
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L609-L617
```solidity
    function reserveAmountForBondProgram(uint256 amount) external returns (bool success) {
       
       .....

        // Effective bond must be bigger than the requested amount
        uint256 eBond = effectiveBond;
        if (eBond >= amount) {

            eBond -= amount;
            effectiveBond = uint96(eBond);
            success = true;
            emit EffectiveBondUpdated(eBond);
        }
    }
```

This variable is updated with the estimated bond Olas at the beginning of an epoch itself.

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

```solidity
    function checkpoint() external returns (bool) {
        
        .....

        // Update effectiveBond with the current or updated maxBond value
        curMaxBond += effectiveBond;
        effectiveBond = uint96(curMaxBond);
```

In case of epochs crossing `YEAR` intervals after 9 years, the new Olas amount will not be fully mintable in the same year due to the inflation control check enforced in the Olas contract.

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/OLAS.sol#L75-L84
```solidity
    function mint(address account, uint256 amount) external {

        ....
        
        // Check the inflation schedule and mint
        if (inflationControl(amount)) {
            _mint(account, amount);
        }
```

Whenever a deposit is made on a bond, the required Olas is minted by the treasury and transferred to the Depository contract, from where the depositor claims the payout after the vesting time. `Olas.sol` doesn't revert for inflation check failure but fails silently. This can cause a deposit to succeed but correspondin
Miscalculation of OLAS Amount Due to Inaccurate LP Token Price in Specific Bonding Mechanism Scenarios
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Incorrect valuation of the LP Token price can result in either an excess issuance of OLAS Tokens, causing a loss to the protocol, or a lower issuance of OLAS Tokens, leading to losses for the user.

## Proof of Concept
The prototype of the create function in the Depository Contract is as follows:
```solidity
function create(address token, uint256 priceLP, uint256 supply, uint256 vesting) external returns (uint256 productId) {
        // Check for the contract ownership
        if (msg.sender != owner) {
            revert OwnerOnly(msg.sender, owner);
        }
        ...
        mapBondProducts[productId] = Product(uint160(priceLP), uint32(vesting), token, uint96(supply));
        ...
    }

```
Governance initiates the create function of the Depository Contract to set the value of the LP Token and the vesting period in the Bonding Mechanism.

A critical aspect of this mechanism is that the LP Token’s value must be accurately assessed at the time of deposit. (For reference, Olympus DAO calculates the value of the deposited tokens at the time of deposit: https://github.com/OlympusDAO/olympus-contracts/blob/main/contracts/BondDepository.sol#L64-L92)

However, the current OLAS Bonding Mechanism determines the value of the LP Token based on a price input by a centralized administrator at the time of Bonding Plan creation through the create function call, rather than at the deposit time.

This can lead to two scenarios when the two tokens constituting the LP Token are highly volatile:

1. If the actual value of the LP Token is higher than the entered Price, users may receive fewer OLAS Tokens than expected after the vesting period ends. Users might participate in the Bonding Plan at a higher cost than the actual required expense, resulting in a loss for the user.
2. If the actual value of the LP Token is lower than the entered Price, users may receive more OLAS Tokens than they should after the vesting period ends. Users can participate in the Bonding Plan at a lower cost than the actual required expense, resulting in a loss for the Protocol.

## Tools Used
Manual Audit 

## Recommended Mitigation Steps
The code should be modified to calculate the value of the LP Token at the time of deposit.

For LP Tokens on the Ethereum Mainnet, refer to the UniswapV2 Twap example (https://github.com/Uniswap/v2-periphery/blob/master/contracts/examples/ExampleOracleSimple.sol) for modifications.

For LP Tokens on Polygon and Gnosis Chain, a centralized oracle contract managed by the Autonolas team should be deployed separately on the Ethereum mainnet. This contract should be updated periodically with the LP Token Price. This updated information should then be used to calculate the value of the LP Token.


## Assessed type

Oracle
When unpausing the `GuardCM`, not setting `governorCheckProposalId` to 0 puts the assets of the protocol at risk
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/multisigs/GuardCM.sol#L560-L569


# Vulnerability details

## Impact

If the `GuardCM` was paused once, the `community multisig` (`CM`) can pause it again without checking for the governances activity. This moves the power within the system from the governance to the CM and can, in the worst case, result in the lose of all funds of the protocol if the CM gets compromised.


## Proof of Concept
The `community multisig` (`CM`) is “a multisig wallet made up of a set of trusted actors” that can execute operations through the timeLock contract without the need of a governance proposal to be voted on. The operations the CM can do are limited to a list of contract and functions combinations (`mapAllowedTargetSelectorChainIds`) set by the governance in the `GuardCM` contract. This list is very limited to ensure that most of the actions taken are still subject to the governance process. 

In case the CM suspects that the governance is inactive and cannot make decisions anymore, it can deactivate the `GuardCM` and thereby bypass the limitation of operations it can take. To do this, the CM makes a proposal in the DAO that needs to pass to show that the governance is still active. The ID of the proposal is saved in the variable `governorCheckProposalId`. Once the voting time for this proposal has passed, the CM can call `GuardCM::pause()` to pause the `GuardCM`. The function checks if the proposal made by the CM passed. If its status is defeated, the CM is allowed to pause the `GuardCM` and thereby lift the limitation of operations it can do:

```java
function pause() external {
        if (msg.sender == owner) { 
            // Timelock can release the community multisig right away
            paused = 2;
        } else if (msg.sender == multisig) {
            // Multisig needs to check if the governor check proposal Id state is defeated
            ProposalState state = IGovernor(governor).state(governorCheckProposalId);
            if (state == ProposalState.Defeated) {
                paused = 2;
            } else {
                revert NotDefeated(governorCheckProposalId, state);
            }
        } else {
            // msg.sender is not a timelock, nor a multisig
            revert ManagerOnly(msg.sender, multisig);
        }

        emit GuardPaused(msg.sender);
    }
```

For the `GuardCM` to be unpaused, the DAO needs to pass a proposal for doing so. Since the minVotingTime for proposals is 1 week, this means that the CM will have the right to do anything for at least one week. Once the proposal to unpause the `GuardCM` passes and is executed, the function `GuardCM::unpause` is called and the operations the CM can do are limited again: 

```java
    function unpause() external { 
        // Check for the ownership
        if (msg.sender != owner) {
            revert OwnerOnly(msg.sender, owner);
        }