xanoutas/solidity-security-findings huggingface dataset:
`SignalService::_cacheChainData()` caches incorrectly due to incorrect if assumption
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/signal/SignalService.sol#L293


# Vulnerability details

In `_cacheChainData()` two things mainly occur:


1. The caching of the state root,

2. And / or the caching of the signal root


However, when the signal root should sync it does'nt sync chain data correctly and rather sync when either `cacheSignalRoot` and `_isFullProof` is true or when `cacheSignalRoot` is true and `_isLastHop` is not true when in fact it should only sync when `cacheSignalRoot` and `_isFoolProof` is true and `_isLastHop` is not true.

This will occur when `cacheStateRoot = _hop.cacheOption == CacheOption.CACHE_BOTH` or `cacheSignalRoot = _hop.cacheOption == CacheOption.CACHE_SIGNAL_ROOT`

## Impact

The signal root of chain data will sync incorrectly leading to the caching of the signal root under incorrect assumptions.

## Proof of Concept

Assumption 1:

`cacheStateRoot = _hop.cacheOption == CacheOption.CACHE_BOTH`

1. `_cacheChainData` gets called

2. `cacheStateRoot` checks if `_hop.cacheOption` equals either  `CacheOption.CACHE_BOTH` or `CacheOption.CACHE_STATE_ROOT`

```javascript
	bool cacheStateRoot = _hop.cacheOption == CacheOption.CACHE_BOTH
            || _hop.cacheOption == CacheOption.CACHE_STATE_ROOT;
```

3. If `cacheStateRoot = _hop.cacheOption == CacheOption.CACHE_BOTH`, the `_cacheChainData` proceeds to the first `if statement`

```javascript
	if (cacheStateRoot && _isFullProof && !_isLastHop) {
```

4. Here it checks if `cacheStateRoot` and `_isFullProof` is true and if `_isLastHop` is not true

5. If the effects of `step 4.` correlates as described, the `if statement` proceeds to `sync the chain data` which contains the `_chainId`, `LibSignals.STATE_ROOT`, `_blockId`, and `_hop.rootHash`

```javascript
	_syncChainData(_chainId, LibSignals.STATE_ROOT, _blockId, _hop.rootHash);
```

6. Following this, `cacheSignalRoot` checks if `_hop.cacheOption` equals either  `CacheOption.CACHE_BOTH` or `CacheOption.CACHE_SIGNAL_ROOT`

```javascript
	bool cacheSignalRoot = _hop.cacheOption == CacheOption.CACHE_BOTH
            || _hop.cacheOption == CacheOption.CACHE_SIGNAL_ROOT;
```

7. If `cacheStateRoot = _hop.cacheOption == CacheOption.CACHE_BOTH` is true, it proceeds to the second `if statement` associated with it:

```javascript
	if (cacheSignalRoot && (_isFullProof || !_isLastHop)) {
```

8. When proceeding however it checks if either `cacheSignalRoot` and  `_isFullProof`  is true, or if `cacheSignalRoot` is true and `_isLastHop` is not true

9. If either statement is true, as described in `step 8.` the chain data will sync which contains the `_chainId`, `LibSignals.SIGNAL_ROOT`, `_blockId`, and `_signalRoot`

```javascript
	_syncChainData(_chainId, LibSignals.SIGNAL_ROOT, _blockId, _signalRoot);
```

If `step 8.` gets reached with either being as described it will lead to chain data being incorrectly synced in `step 9.` as the
Potential overflow risks leads to incorrect calculation of fees
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/libs/LibDepositing.sol#L67


# Vulnerability details

## Impact
In the processDeposits function, each iteration of the loop potentially increases totalFee by _fee. If the loop processes a large number of deposits, or if the fee values are significant, totalFee could exceed the maximum value representable by uint96, leading to an overflow. This overflow would not be caught due to the unchecked block, resulting in totalFee wrapping around to a much lower value than intended, thereby corrupting the fee accounting logic.


## Proof of Concept
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/libs/LibDepositing.sol#L67


1. Initially, `totalFee` is set to 0.
2. In the first iteration of the loop, `_fee` is calculated based on the deposit amount and added to `totalFee`. Let's assume `_fee` is equal to the maximum value of `uint96` (2^96 - 1).
3. After the first iteration, `totalFee` becomes 2^96 - 1.
4. In the second iteration, `_fee` is again equal to the maximum value of `uint96` (2^96 - 1).
5. When `totalFee += _fee;` is executed, the operation `(2^96 - 1) + (2^96 - 1)` overflows the `uint96` type, and `totalFee` wraps around to a very small value (instead of the expected 2^97 - 2).

This overflow can lead to incorrect fee calculations and potentially cause issues with the contract's logic.

## Tools Used
Manual

## Recommended Mitigation Steps
Move the line `totalFee += _fee;` outside the `unchecked` block.

```solidity
for (uint256 i; i < deposits_.length;) {
    uint256 data = _state.ethDeposits[j % _config.ethDepositRingBufferSize];
    deposits_[i] = TaikoData.EthDeposit({
        recipient: address(uint160(data >> 96)),
        amount: uint96(data),
        id: j
    });

    uint96 _fee = deposits_[i].amount > fee ? fee : deposits_[i].amount;

    // Unchecked is safe:
    // - _fee cannot be bigger than deposits_[i].amount
    // - all values are in the same range (uint96) except loop
    // counter, which obviously cannot be bigger than uint95
    // otherwise the function would be gassing out.
    unchecked {
        deposits_[i].amount -= _fee;
    }

    totalFee += _fee; // @audit Moved outside the unchecked block

    ++i;
    ++j;
}
```



## Assessed type

Under/Overflow
Contract Initialization is unrestricted 
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/TimelockTokenPool.sol#L106-L129


# Vulnerability details

## Impact
Any external contract or individual can initialize the contract and set up their initial state. The comments state the following...
    
    /// @notice Initializes the contract.
    /// @param _owner The owner address.
    /// @param _taikoToken The Taiko token address.
    /// @param _costToken The cost token address.
    /// @param _sharedVault The shared vault address.

The function does not check if the owner is indeed the owner of the contract. Same goes for "Taiko Token Address, The Cost Token Address and The Shared Vault Address. The "init" function only checks for address(0).

## Proof of Concept
    /// @notice Initializes the contract.
    /// @param _owner The owner address.
    /// @param _taikoToken The Taiko token address.
    /// @param _costToken The cost token address.
    /// @param _sharedVault The shared vault address.
    function init(
        address _owner,
        address _taikoToken,
        address _costToken,
        address _sharedVault
    )
        external
        initializer
    {
        __Essential_init(_owner);
        if (_taikoToken == address(0)) revert INVALID_PARAM();
        taikoToken = _taikoToken;

        if (_costToken == address(0)) revert INVALID_PARAM();
        costToken = _costToken;

        if (_sharedVault == address(0)) revert INVALID_PARAM();
        sharedVault = _sharedVault;
    }


## Recommended Mitigation Steps
Add "if" statements or "access modifiers" 


## Assessed type

Access Control
`onBlockProposed` will always revert if there is a zero fee with some tokens
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/hooks/AssignmentHook.sol#L114-L116


# Vulnerability details

## Impact
Nobody can propose new blocks in case the `proverFee` is zero for a specific tier, as the transaction will always revert, depending on which token is used to pay the fees.

## Proof of Concept

The README states:
> - The AssignmentHook contract shall support any ERC20 tokens and Ether as proving fees.

Not all `ERC20` implementations are totally compliant, and some (e.g `LEND`) may fail while transferring a zero amount:

```solidity
File: packages/protocol/contracts/L1/hooks/AssignmentHook.sol

114: 		            IERC20(assignment.feeToken).safeTransferFrom(
115: 		                _meta.coinbase, _blk.assignedProver, proverFee
116: 		            );
```
[[114-116](https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/hooks/AssignmentHook.sol#L114-L116)]

In this case, if the `proverFee` is zero for a specific tier, it won't be possible to propose a new block, as the transaction will always revert.


## Tools Used
Bot/Automated review

## Recommended Mitigation Steps
Consider skipping the transfer in case the `proverFee` is zero:

```solidity
if(proverFee > 0) {
    IERC20(assignment.feeToken).safeTransferFrom(
        _meta.coinbase, _blk.assignedProver, proverFee
    );
}
```


## Assessed type

ERC20
Dangerous use of `mint` instead of `safeMint`
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/tokenvault/ERC721Vault.sol#L17


# Vulnerability details

## Impact
If the recipient is not a EOA, `safeMint` ensures that the contract is able to receive the token. This is not the case with `mint`: In the worst-case scenario, it may result in tokens frozen permanently.

## Proof of Concept
`BridgedERC721` uses `mint` instead of `safeMint`, so if the receiver cannot handle the NFT, it will be frozen permanently:

```solidity
File: packages/protocol/contracts/tokenvault/ERC721Vault.sol

176: 		                BridgedERC721(token_).mint(_to, _tokenIds[i]);
```
[[176](https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/tokenvault/ERC721Vault.sol#L176)]

## Tools Used
Bot/Automated review

## Recommended Mitigation Steps
Consider using `safeMint` instead of `mint`.


## Assessed type

ERC721
state variable changed after external call
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L2/CrossChainOwned.sol#L53


# Vulnerability details

## Impact
incorrect logic that should revert won't revert , possible reentrancy

## Proof of Concept

`nextTxId` is being updated after the external call for the events
```solidity

function onMessageInvocation(bytes calldata _data)
        external
        payable
        whenNotPaused
        onlyFromNamed("bridge")
    {
        (uint64 txId, bytes memory txdata) = abi.decode(_data, (uint64, bytes));
@>        if (txId != nextTxId) revert XCO_INVALID_TX_ID();

        IBridge.Context memory ctx = IBridge(msg.sender).context();
        if (ctx.srcChainId != ownerChainId || ctx.from != owner()) {
            revert XCO_PERMISSION_DENIED();
        }

        (bool success,) = address(this).call(txdata);
        if (!success) revert XCO_TX_REVERTED();

@>        emit TransactionExecuted(nextTxId++, bytes4(txdata));
    }
```
## Tools Used

manual

## Recommended Mitigation Steps
use check effect interaction
update state variable after external call

update `nextTxId` before the external call


## Assessed type

Error
Message will be lost forever the `refundTo` address doesn't implement neither fallback nor receive
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/0xA5DF/taiko/blob/ade888fd023ea1c793cd5f0db7066d1ed2ab370c/packages/protocol/contracts/bridge/Bridge.sol#L299


# Vulnerability details





## Impact
The `processMessage()` function sends ether to the `refundTo` address even when the `refundAmount` is zero.
This executes a call with empty data (and `value=refundAmount`) to the `refundTo` address.
If the `refundTo` address is a contract that doesn't implement neither fallback nor receive then the `processMessage()` function would revert.
This would mean the message is lost forever, there's no way to neither execute nor recall it.


Note that the `refundTo` is by default the `destOwner`, the sender wouldn't expect the `destOwner` to be required to implement fallback or receive (esp. if the `message.value` is zero, in that case the refund amount would also be zero).

This can also be done by the owner of the `refundTo` address if they become malicious - they can change the contract implementation to revert on the ETH transfer. The sender wouldn't expect the `refundTo` to be trusted for anything more than holding the ETH value for them, but in reality they hold the power to prevent the execution of the message.

## Proof of Concept


Add the following PoC to `test\bridge\Bridge.t.sol`, it'll revert:

<details>
<summary>Coded PoC</summary>

```solidity
contract NoFallback{

}
```

```solidity
    function test_poc_refundToNoFallback() public {
        IBridge.Message memory message = IBridge.Message({
            id: 0,
            from: address(bridge),
            srcChainId: uint64(block.chainid),
            destChainId: destChainId,
            srcOwner: Alice,
            destOwner: address(new NoFallback()),
            to: Alice,
            refundTo: address(0),
            value: 1000,
            fee: 1000,
            gasLimit: 1_000_000,
            data: "",
            memo: ""
        });
        // Mocking proof - but obviously it needs to be created in prod
        // corresponding to the message
        bytes memory proof = hex"00";

        bytes32 msgHash = dest2StepBridge.hashMessage(message);

        vm.chainId(destChainId);
        // This in is the first transaction setting the proofReceipt
        vm.prank(Bob, Bob);
        dest2StepBridge.processMessage(message, proof);

        IBridge.Status status = dest2StepBridge.messageStatus(msgHash);
        // Still new ! Because of the delay, no processing happened
        assertEq(status == IBridge.Status.NEW, true);
        // Alice has 100 ether
        assertEq(Alice.balance, 100_000_000_000_000_000_000);

        // Go in the future, 5 hours, still not processable
        vm.warp(block.timestamp + 5 hours);

        vm.expectRevert(Bridge.B_INVOCATION_TOO_EARLY.selector);
        vm.prank(Bob, Bob);
        dest2StepBridge.processMessage(message, proof);

        // Go in the future, +6 hours, all in all 11 hours from first processing
        // Carol cannot process (as not preferred exec
Lack of vetoer can lead to 51% attack
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/L1/gov/TaikoGovernor.sol#L1


# Vulnerability details

## Vulnerability details

The veto power is important functionality in a governance system in order to protect from malicious proposals. However there is lack of vetoer in `TaikoGovernor` contract, this might open to 51% attack

With 51% attack a malicous actor can execute any function that require owner role in L1, such as ban any address, suspend message, ..

References:
- https://blakeir.com/a-nouns-governance-attack
- https://code4rena.com/reports/2022-09-nouns-builder/#m-11-loss-of-veto-power-can-lead-to-51-attack

## Impact
Attacker can control protocol by using 51% attack

## Tools Used
Manual review

## Recommended Mitigation Steps
Add vetoer with two step validation and add the function to execute a veto


## Assessed type

Context
Uninitialized Address Manager in TaikoToken Contract
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/TaikoToken.sol#L52


# Vulnerability details

The `TaikoToken` contract inherits from the `EssentialContract`, which in turn inherits from the `AddressResolver` contract. However, during initialization, the `TaikoToken` contract does not initialize the address manager, leading to the `addressManager` variable remaining uninitialized. This results in any attempt to use functions or modifiers dependent on the address manager, such as the `resolve` function or the `onlyFromOwnerOrNamed` modifier, to revert with the `RESOLVER_INVALID_MANAGER()` error.

## Impact

The uninitialized address manager poses a critical vulnerability as it prevents essential functions and modifiers in the `TaikoToken` contract from functioning correctly. This could potentially lead to unexpected behavior or disruptions in the protocol's functionality.

## Proof of Concept

```solidity
packages/protocol/contracts/L1/TaikoToken.sol:
51      /// @notice Creates a new token snapshot.
52:     function snapshot() public onlyFromOwnerOrNamed("snapshooter") {
53:         _snapshot();
54:     }

packages/protocol/contracts/common/AddressResolver.sol:
72:     function _resolve(
73:         uint64 _chainId,
74:         bytes32 _name,
75:         bool _allowZeroAddress
76:     )
77:         private
78:         view
79:         returns (address payable addr_)
80:     {
81:         if (addressManager == address(0)) revert RESOLVER_INVALID_MANAGER();
```

Explanation:

1. **TaikoToken.sol - Line 52**: The `snapshot` function is defined in the `TaikoToken` contract. This function is intended to create a new token snapshot. However, it includes the `onlyFromOwnerOrNamed` modifier, which depends on the `resolve` function from the `AddressResolver` contract.
  
2. **AddressResolver.sol - Lines 72-81**: The `_resolve` function in the `AddressResolver` contract checks whether the `addressManager` variable is initialized. If the `addressManager` is not initialized (i.e., it is set to `address(0)`), the function reverts with the `RESOLVER_INVALID_MANAGER()` error.
  

The issue arises because the `TaikoToken` contract does not initialize the address manager during initialization, causing the `addressManager` variable in the `AddressResolver` contract to remain uninitialized. As a result, any attempt to use functions or modifiers dependent on the address manager, such as the `snapshot` function in the `TaikoToken` contract, will revert with the `RESOLVER_INVALID_MANAGER()` error.

## Tools Used

- Manual review

## Recommended Mitigation Steps

To mitigate this vulnerability, it is recommended to ensure that the address manager is properly initialized during the initialization of the `TaikoToken` contract. This can be achieved by passing the address of the address manager to the initialization function of the `TaikoToken` contract or by setting the address manag
There is no slippage check for the eth deposits processing in the `LibDepositing.processDeposits`
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/L1/libs/LibDepositing.sol#L138-L142
https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/L1/TaikoL1.sol#L209-L211
https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/L1/libs/LibDepositing.sol#L83
https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/L1/libs/LibDepositing.sol#L93
https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/L1/libs/LibDepositing.sol#L101


# Vulnerability details

## Impact

The `LibDepositing.depositEtherToL2` function is called by the TaikoL1.depositEtherToL2 to deposit ether to the `L2 chain`. The maximum number of `unprocessed eth deposits` are capped at `_config.ethDepositRingBufferSize - 1` as shown here:

```solidity
        unchecked {
            return _amount >= _config.ethDepositMinAmount && _amount <= _config.ethDepositMaxAmount
                && _state.slotA.numEthDeposits - _state.slotA.nextEthDepositToProcess
                    < _config.ethDepositRingBufferSize - 1;
        }
```

The Taiko configuration states that `ethDepositRingBufferSize == 1024`. Hence the maximum unprocessed eth deposits allowed by the taiko L1 contract is `capped at 1023`.

When the L2 block is proposed by calling the `LibProposing.proposeBlock` function by the TaikoL1 contract,  it processes the unprocessed `eth deposits` by calling the `LibDepositing.processDeposits`. But it is allowed to process `at most 32 eth deposits per block` as per the following conditional check in the `processDeposits` function.

```solidity
deposits_ = new TaikoData.EthDeposit[(numPending.min(_config.ethDepositMaxCountPerBlock));
```

Here the `ethDepositMaxCountPerBlock == 32` as configured in the `TaikoL1.getConfig` function.

And the `fee amount` for each of the `eth deposits` are calculated as follows:

```solidity
uint96 fee = uint96(_config.ethDepositMaxFee.min(block.basefee * _config.ethDepositGas));

uint96 _fee = deposits_[i].amount > fee ? fee : deposits_[i].amount;

deposits_[i].amount -= _fee; 
```

Hence the `deposited eth amount` is deducted by the calculated `_fee amount` for the `eth deposit transaction`. If the basefee of the `L1 block` increases significantly then the maximum fee of `ethDepositMaxFee: 1 ether / 10` will be applied. Thus deducting that amount from the `transferred eth deposit to the recipient in L2`.

Now let's consider the following scenario:

1. `nextEthDepositToProcess` is currently `100`.
2. `numEthDeposits` is `1060` currently.
3. The number of proposed L2 blocks required to process 1060th eth deposit is = 1060 - 100 / 32 = 30 L2 blocks.
4. As a result all the `above 30 L2 blocks` will not be proposed in a single L1 block and will require multiple L1 blocks for it.
5. If there is `huge congestion` in the mainnet during this time the `block.basefee` of the subsequent `L1 blocks` would increase. And this
TaikoL2.anchor() will unexpectedly revert if block.basefee is 0
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L2/TaikoL2.sol#L139-L143
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L2/TaikoL2.sol#L293-L297


# Vulnerability details

## Impact

Cross-layer message verification will not work for edge case.

## Proof of Concept

In TaikoL2.anchor(), the base fee per bas is verified as such: 

```
 // Verify the base fee per gas is correct
        uint256 basefee;
        (basefee, gasExcess) = _calc1559BaseFee(config, _l1BlockId, _parentGasUsed);
        if (!skipFeeCheck() && block.basefee != basefee) {
            revert L2_BASEFEE_MISMATCH();
        }
```

The `block.basefee` must be equal to the `basefee` if fee check is not skipped. `basefee` is obtained through `_calc1559BaseFee()`. At the end of `_calc1559BaseFee()`, there is a check that if basefee is 0, return 1.

```
            //@audit - How base fee is calculated
            basefee_ = Lib1559Math.basefee(
                gasExcess_, uint256(_config.basefeeAdjustmentQuotient) * _config.gasTargetPerL1Block
            );
        }

        //@audit - Return 1 if base fee is 0
        // Always make sure basefee is nonzero, this is required by the node.
        if (basefee_ == 0) basefee_ = 1;
```

If the `basefee` is 0, then `block.basefee` will be 0, but `basefee` will be 1, which will revert the whole operation.

## Tools Used

Manual Review

## Recommended Mitigation Steps

Recommend adding an additional check on `block.basefee` to return 1 if it returns 0, to match with `_calc1559BaseFee()`.


## Assessed type

Invalid Validation