xanoutas/solidity-security-findings huggingface dataset:
Timelock.sol 's admin functions such as grantRole() or revokeRole() is highly vulnerable to DOS, due to vulnerabilities in GovernorOLAS.sol
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/GovernorOLAS.sol#L76


# Vulnerability details

## Impact
When the initial deployer of Timelock.sol transfer admin role to GovernorOLAS.sol (through `grantRole()` and `renoucneRole()`), admin functions in Timelock.sol will be effectively DOSsed. In case there is the need to update a role account (e.g. governor address change due to bug fixes, etc.), Timelock.sol's `PROPOSER_ROLE`, `CANCELLER_ROLE` and `EXECUTOR_ROLE` can no longer be updated.

## Proof of Concept
Timelock.sol will set `PROPOSER_ROLE`, `EXECUTOR_ROLE`, `CANCELLER_ROLE` and `TIMELOCK_ADMIN_ROLE` in TimelockController.sol's `constructor()`. These roles will be set at deployment time. Specifically, `TIMELOCK_ADMIN_ROLE` will be set to `msg.sender` which will be the deployer address. The deployer account will then grant the admin role to `governorOLAS.sol` and will renounce the admin role itself afterward. This is intended behavior.

However, the vulnerability is after `GovernorOLAS.sol` gains `TIMELOCK_ADMIN_ROLE` and deployer renounced the admin role, current implementations in GovernorOLAS.sol doesn't allow governance to exercise admin functions such as `grantRole()` or `revokeRole()`. Any proposal in GovernorOLAS.sol to update role in Timelock.sol will be reverted at execution in Timelock.sol due to `onlyRole(getRoleAdmin(role))` modifier reverts.

```solidity
//contracts/Timelock.sol
    constructor(uint256 minDelay, address[] memory proposers, address[] memory executors)
|>      TimelockController(minDelay, proposers, executors, msg.sender)
    {}

//node_modules/@openzeppelin/contracts/governance/TimelockController.sol
    constructor(
        uint256 minDelay,
        address[] memory proposers,
        address[] memory executors,
        address admin
    ) {
...
        // optional admin
        if (admin != address(0)) {
|>          _setupRole(TIMELOCK_ADMIN_ROLE, admin);
        }
```
(https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/Timelock.sol#L11)
```solidity
//node_modules/@openzeppelin/contracts/access/AccessControl.sol
//@audit-info note: Timelock.sol inherits AccessControl.sol which uses onlyRole() modifier for grantRole()
    function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }
```

**POC:**
Suppose `GovernorOLAS.sol` needs to change the governor address in Timelock.sol due to the need to upgrade or fix bugs. (see the scenario in [docs](https://github.com/code-423n4/2023-12-autonolas/blob/main/governance/docs/Governance_process.pdf)). 

A proposal needs to be created to call `grantRole()` on Timelock.sol to grant the new governor address `TIMELOCK_ADMIN_ROLE`,`PROPOSER_ROLE`,`EXECUTOR_ROLE` and `CANCELLER_ROLE`. When the proposal succeeds in GovernorOLAS.sol and after the requir
wrong implementation in blanceOfLocked leads to wrong data
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
protocol implement votes to be based on how much time they staked .The more time they staked , there are more voting weight .But there is wrong calculation at determining how much weight they owned .This lead to unfair voting system.

## Proof of Concept
There is only way to know that how much weight they are getting is to call getVotes .But In getVotes , there is called to _balanceOfLocked . Wrong calculation is happing in here . 

       function _balanceOfLocked(address account, uint64 ts) internal view 
       returns (uint256 vBalance) {
        uint256 pointNumber = mapUserPoints[account].length;
        if (pointNumber > 0) {
            PointVoting memory uPoint = mapUserPoints[account][pointNumber - 1];
            //*@audit-info ------->> end time - block.timestamp
            uPoint.bias -= uPoint.slope * int128(int64(ts) - int64(uPoint.ts));
            if (uPoint.bias > 0) {
                vBalance = uint256(int256(uPoint.bias));
            }
        }
    }


As far as i know , upoint.bias is the whole weight for 4 years calculation ,and we have to reduce it by the remaining time  to know actual weight of that block.timestamp .But here it implement that upoint.bias reduced by the amount (i.e slope * (block.timestamp - uPoint.ts) ).So basically it means when u stake and call get votes, it will have highest voting weight in the beginning which is literally opposite to what protocol intended to implement.

## Tools Used
manual view
## Recommended Mitigation Steps
change it lockbackbalance.endtime - ts instead of int128(int64(ts) - int64(uPoint.ts)


## Assessed type

Invalid Validation
In Tokenomics.sol, donated Eth will be double spent for component and agent incentives, causing other component/agent owners losing rewards
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
In Tokenomics.sol, when ETH is donated through `trackServiceDonations()`, `amount` of ETH added to pending rewards will double the actual amount donated and transferred to treasury, due to incorrect implementations. Some component/agent owners will be able to claim more rewards, while other compoenent/agent will lose rewards.

There is not enough ETH to cover the total amount accounted for in storage.

## Proof of Concept
When donating ETH, first Treasury.sol - `depositServiceDonationsETH()` will be called to transfer ETH to be donated with amount set for each service id passed. 
There are checks in `depositServiceDonationsETH()` to ensure individual amount in `amounts[]` add up to total `msg.value`.

Then `amounts[]` and `serviceIds[]` are passed to `ITokenomics(tokenomics).trackServiceDonations()`, which will pass to `_trackServiceDonations()`.

```solidity
//contracts/Treasury.sol
  function depositServiceDonationsETH(
    uint256[] memory serviceIds,
    uint256[] memory amounts
  ) external payable {
...
    uint256 totalAmount;
    for (uint256 i = 0; i < numServices; ++i) {
      if (amounts[i] == 0) {
        revert ZeroValue();
      }
      totalAmount += amounts[i];
    }
    // Check if the total transferred amount corresponds to the sum of amounts from services
    //@audit-info note: msg.value is the total amounts of amounts[]
|>  if (msg.value != totalAmount) {
      revert WrongAmount(msg.value, totalAmount);
    }
...
    //@audit-info note: this passes serviceIds[], amounts[] to trackServiceDonations()
|>  ITokenomics(tokenomics).trackServiceDonations(
      msg.sender,
      serviceIds,
      amounts,
      msg.value
    );
...
```
(https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Treasury.sol#L298)

The vulnerability lies in `_trackServiceDonations()`. In `_trackServiceDonations()`, service Ids will be iterated in the 1st for-loop : `for (uint256 i = 0; i < numServices; ++i) {`. Inside each iteration, 2nd for-loop of `unitType<2` will run and assign pending rewards for component units (`unitType==0`) at the first iteration and agent units (`unitType==1`) at the second iteration.

In each iteration of for-loop `unitType<2`, a value of `uint96(amounts[i] / numServiceUnits)` will be credited to each component/agent unit's pending rewards `mapUnitIncentives[unitType][serviceUnitIds[j]].pendingRelativeReward += amount;`. This is incorrect, because `amounts[i]` is the entire value reserved for `serviceIds[i]`, and when dividing `amounts[i]` by `numServiceUnits` in `unitYpe<2` for-loop, `amounts[i]` will be added twice into pending rewards - the first time, adding total of `amounts[i]` to component unit ids, the second time, adding the same total of `amounts[i]` to agent unit ids.
In Tokenomics.sol, the initial maxBond will be set incorrectly and cause over inflation, when epochLen cross the end of zero-year 
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
In Tokenomics.sol, the zero-year `maxBond` will be incorrect if `epochLen` crosses a year, which causes over-inflation, violating the hard-coded inflation schedule. 

Due to hard-coded inflation schedule in TokenomicsConstants.sol can be violated in `initializeTokenomics()`,and extreme over-inflation is possible, I consider this high impact.

## Proof of Concept
When Tokenomics.sol is initialized(`initializeTokenomics()`), `maxBond` will be calculated based on three values: (1)`getInflationForYear(0)`- zero-year inflation schedule; (2)`zeroYearSecondsLeft`- the time left in the zero-year from the timestamp of the initialization call till the end of the zero-year; (3)`_epochLen`- the duration of the first epoch. 

The vulnerability is that there's no check on whether the initial `_epochLen` will result in crossing a year during the epoch. When the first epoch cross the end of zero-year, the first-year inflation schedule should be used for the second portion of the epoch. Current implementation will uniformly calculate the initial `maxBond` based on zero-year inflation schedule(`getInflationForYear(0)`) and `zeroYearSecondsLeft`, this is incorrect and will cause over-inflation.

```solidity
//contracts/Tokenomics.sol
  function initializeTokenomics(
    address _olas,
    address _treasury,
    address _depository,
    address _dispenser,
    address _ve,
    uint256 _epochLen,
    address _componentRegistry,
    address _agentRegistry,
    address _serviceRegistry,
    address _donatorBlacklist
  ) external {
...
    uint256 zeroYearSecondsLeft = uint32(
      _timeLaunch + ONE_YEAR - block.timestamp
    );
...
//@audit-info note: this is zero-year inflation schedule divided by seconds left in  zero-year
    uint256 _inflationPerSecond = getInflationForYear(0) / zeroYearSecondsLeft;
    inflationPerSecond = uint96(_inflationPerSecond);
    timeLaunch = uint32(_timeLaunch);
...
    uint256 _maxBondFraction = 50;
...
//@audit when _epochLen cross the end of zero-year, `_maxBond` will be incorrectly calculated using inflationPerSecond based on zero-year inflation, extreme over-inflation may occur
|>  uint256 _maxBond = (_inflationPerSecond * _epochLen * _maxBondFraction) /
      100;
    maxBond = uint96(_maxBond);
    effectiveBond = uint96(_maxBond);
```
(https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L367)

```solidity
//contracts/TokenomicsConstants.sol
  function getInflationForYear(
    uint256 numYears
  ) public pure returns (uint256 inflationAmount) {
...
//@audit-info note: the first ten year inflation schedule for each year
      uint88[10]
Calculated OLAS amount can be incorrect
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
When creating a bond, the calculatePayoutOLAS function will calculate the OLAS payout amount with a discount.

```solidity
function deposit(uint256 productId, uint256 tokenAmount) external
        returns (uint256 payout, uint256 maturity, uint256 bondId)
    {
        // Check the token amount
        if (tokenAmount == 0) {
            revert ZeroValue();
        }

        // Get the bonding product
        Product storage product = mapBondProducts[productId];

        // Check for the product supply, which is zero if the product was closed or never existed
        uint256 supply = product.supply;
        if (supply == 0) {
            revert ProductClosed(productId);
        }

        // Calculate the bond maturity based on its vesting time
        maturity = block.timestamp + product.vesting;
        // Check for the time limits
        if (maturity > type(uint32).max) {
            revert Overflow(maturity, type(uint32).max);
        }

        // Get the LP token address
        address token = product.token;

        // Calculate the payout in OLAS tokens based on the LP pair with the discount factor (DF) calculation
        // Note that payout cannot be zero since the price LP is non-zero, otherwise the product would not be created
        payout = IGenericBondCalculator(bondCalculator).calculatePayoutOLAS(tokenAmount, product.priceLP);
```

the calculatePayoutOLAS function gets the latest discount factor and calculates the OLAS amount.

```solidity
amountOLAS = ITokenomics(tokenomics).getLastIDF() * totalTokenValue / 1e36;
```
The discount factor is [updated](https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L1040-L1049) inside the checkpoint function.

```solidity
 // Update the IDF value for the next epoch or assign a default one if there are no ETH donations
        if (incentives[0] > 0) {
            // Calculate IDF based on the incoming donations
            uint256 idf = _calculateIDF(incentives[1], tp.epochPoint.numNewOwners);
            nextEpochPoint.epochPoint.idf = uint64(idf);
            emit IDFUpdated(idf);
        } 
```
When users make a deposit, the discount factor can be outdated because the checkpoint function is never called during the bonding process. Due to that, users may receive an incorrect amount of OLAS tokens.
## Proof of Concept
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/GenericBondCalculator.sol#L64

## Tools Used
Manual Review
## Recommended Mitigation Steps
When obtaining the latest IDF, call the checkpoint() function to ensure that you receive the updated discount factor.


## Assessed type

Other
[H-05] Low level call in the Treasury contract on the withdraw frunction
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Use of "call": should be avoided whenever possible. It can lead to unexpected behavior if return value is not handled properly. Please use Direct Calls via specifying the called contract's interface.
more
Pos: 339:30.

Potential for Unexpected Behavior:

Incorrect Return Value Handling: If the return value of a call is not properly checked or handled, it can lead to incorrect logic execution, unexpected state changes, or even vulnerabilities like reentrancy attacks.
Type Mismatch Errors: Using call without explicit type information can result in runtime errors if return values don't match expected types.
Gas Cost Inefficiency: call operations are generally more gas-intensive than direct calls due to additional overhead for dynamic dispatch.

Security Vulnerabilities:

Reentrancy Attacks: If a contract calls an external contract that can recursively call back into the original contract before the first call has completed, it can lead to reentrancy attacks, potentially draining funds or compromising the contract's state.

## Proof of Concept
1. Deploy a vulnerable contract:
Create the Treasury contract with the withdraw function that calls to transfer ETH to another contract without checking the return value.

The vulnerable function is as follows in the treasury contract.
```sol
    function withdraw(address to, uint256 tokenAmount, address token) external returns (bool success) {
        // Check for the contract ownership
        if (msg.sender != owner) {
            revert OwnerOnly(msg.sender, owner);
        }


        // Check that the withdraw address is not treasury itself
        if (to == address(this)) {
            revert TransferFailed(token, address(this), to, tokenAmount);
        }


        // Check for the zero withdraw amount
        if (tokenAmount == 0) {
            revert ZeroValue();
        }


        // ETH address is taken separately, and all the LP tokens must be validated with corresponding token reserves
        if (token == ETH_TOKEN_ADDRESS) {
            uint256 amountOwned = ETHOwned;
            // Check if treasury has enough amount of owned ETH
            if (amountOwned >= tokenAmount) {
                // This branch is used to transfer ETH to a specified address
                amountOwned -= tokenAmount;
                ETHOwned = uint96(amountOwned);
                emit Withdraw(ETH_TOKEN_ADDRESS, to, tokenAmount);
                // Send ETH to the specified address
                (success, ) = to.call{value: tokenAmount}("");
                if (!success) {
                    revert TransferFailed(ETH_TOKEN_ADDRESS, address(this), to, tokenAmount);
                }
            } else {
                // Insufficient amount of treasury owned ETH
                revert LowerThan(tokenAmount, amountOwned);
veOLAS#createLockFor can modify the `unlockTime` of others
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
deposit a small number of tokens and change the unlockTime of any user.

## Proof of Concept

createLockFor checks if the deposit amount is equal to 0, but an attacker can pass in 1wei

```solidity
    function _createLockFor(address account, uint256 amount, uint256 unlockTime) private {
        // Check if the amount is zero
        if (amount == 0) {
            revert ZeroValue();
        }
        .....
        _depositFor(account, amount, unlockTime, lockedBalance, DepositType.CREATE_LOCK_TYPE);
    }
```

An attacker can pass in an `unlockTime` of any value (within range).

The `_depositFor` function directly modifies the `unlockTime`:

```solidity
  function _depositFor(
        address account,
        uint256 amount,
        uint256 unlockTime,
        LockedBalance memory lockedBalance,
        DepositType depositType
    ) internal {
        .....
        unchecked {
            lockedBalance.amount += uint128(amount);
        }
        if (unlockTime > 0) {
@>          lockedBalance.endTime = uint64(unlockTime);
        }
@>      mapLockedBalances[account] = lockedBalance;
```


## Tools Used
vscode, manual

## Recommended Mitigation Steps
Added a limit that only allows owner to modify the unlockTime


## Assessed type

Access Control
Token price oracle manipulation in GenericBondCalculator.sol getCurrentPriceLP( ) function
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Token price oracle manipulation in GenericBondCalculator.sol getCurrentPriceLP( ) function.

## Proof of Concept
This function directly uses uniswap pair reserves to calculate the price of LP tokens. However, such price oracle is vulnerable to attacks such as flash loan, thus highly unrecommended.
Uniswap itself also warns against this and recommends implementing a [TWAP price oracle](https://docs.uniswap.org/contracts/v2/concepts/core-concepts/oracles)

## Tools Used
Manual analysis

## Recommended Mitigation Steps
Use TWAP price oracle or Chainlink oracle instead.



## Assessed type

Oracle
overflow exist at transfer and  transferFrom function at ERC20.sol contract  solmate lib
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/lib/solmate/src/tokens/ERC20.sol#L77
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/lib/solmate/src/tokens/ERC20.sol#L99


# Vulnerability details

## Impact
transfer function overflow can cause too many token for caller.

## Proof of Concept
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/lib/solmate/src/tokens/ERC20.sol#L99
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/lib/solmate/src/tokens/ERC20.sol#L77

any one call OLAS's transfer or transferFrom may be add balanceOf when balanceOf[from] = 0;

## Tools Used
vscode
## Recommended Mitigation Steps
use safemath lib











## Assessed type

Solmate
No `fxChildTunnel` is set in `FxERC20RootTunnel`
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/bridges/FxERC20RootTunnel.sol#L40


# Vulnerability details

## Impact

The malicious user can front-running the `setFxChildTunnel` function and `fxChildTunnel` is set to the malicious address.

## Proof of Concept

The `_validateAndExtractMessage` function uses `fxChildTunnel` to verify that the caller is legitimate.

```solidity
    function _validateAndExtractMessage(bytes memory inputData) internal returns (bytes memory) {
        ....
        require(fxChildTunnel == log.getEmitter(), "FxRootTunnel: INVALID_FX_CHILD_TUNNEL");
        ....
    }
```

The problem is that `fxChildTunnel` is not set in the `FxERC20RootTunnel` contract.
A malicious user can use front-running to set `fxChildTunnel` to a different address.

```solidity
     function setFxChildTunnel(address _fxChildTunnel) public virtual {
        require(fxChildTunnel == address(0x0), "FxBaseRootTunnel: CHILD_TUNNEL_ALREADY_SET");
        fxChildTunnel = _fxChildTunnel;
    }
```

## Tools Used
vscode, manual

## Recommended Mitigation Steps
Call `setFxChildTunnel` in the constructor



## Assessed type

Access Control
No `fxRootTunnel` is set in `FxERC20ChildTunnel`
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/governance/contracts/bridges/FxERC20ChildTunnel.sol#L74


# Vulnerability details

## Impact

The malicious user can front-running the `setFxRootTunnel` function and `fxRootTunnel` is set to the malicious address.

## Proof of Concept

The FxERC20ChildTunnel contract inherits the FxBaseChildTunnel contract
```solidity
    contract FxERC20ChildTunnel is FxBaseChildTunnel
```

The `_processMessageFromRoot` function validates the source of the message through the `validateSender` modifier.

The `validateSender` function uses `fxRootTunnel` to verify that the caller is legitimate.

```solidity
    modifier validateSender(address sender) {
        require(sender == fxRootTunnel, "FxBaseChildTunnel: INVALID_SENDER_FROM_ROOT");
        _;
    }
```

The problem is that `fxRootTunnel` is not set in the `FxERC20ChildTunnel` contract.
A malicious user can use front-running to set `fxRootTunnel` to a different address.

```solidity
    function setFxRootTunnel(address _fxRootTunnel) external virtual {
        require(fxRootTunnel == address(0x0), "FxBaseChildTunnel: ROOT_TUNNEL_ALREADY_SET");
        fxRootTunnel = _fxRootTunnel;
    }
```

## Tools Used
vscode, manual

## Recommended Mitigation Steps
Call `setFxRootTunnel` in the constructor



## Assessed type

Access Control