xanoutas/solidity-security-findings huggingface dataset:
Vaults can be drained due to the way that messages are handled within _handleMessage
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/tokenvault/ERC20Vault.sol#L218-L219
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/tokenvault/ERC20Vault.sol#L384
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/tokenvault/ERC20Vault.sol#L270-L271
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/bridge/Bridge.sol#L199-L201
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/tokenvault/ERC20Vault.sol#L303


# Vulnerability details

## Impact

The way that messages are handled in ERC20 vaults and prior to them being sent via the bridge, coupled with the fact that users are allowed to pass arbitrary arguments in the `BridgeTransferOp` struct allows for the vaults to be drained. The example which will be pointed to below will show how with an arbitrary `_op.to` argument the user can immediately receive the bridged or canonical tokens, intentionally set a low gas limit so that the message fails and then recalls the said message to get back the tokens once again.

## Proof of Concept

A malicious user decides to transfer ERC20 tokens via the Taiko bridge from the ERC20Vault, calls sendToken with some token, for the purpose of this example let's say that the ERC20 token is bridgedWETH, here is the `BridgeTransferOp` struct:

```
 struct BridgeTransferOp {
        uint64 destChainId;
        address destOwner;
        address to;
        address token;
        uint256 amount;
        uint256 gasLimit;
        uint256 fee;
        address refundTo;
        string memo;
    }
``` 

The malicious user, let's call him Bob, will use two addresses of his to perform this attack:
- Address 1 which he will set as the `destOwner` and also the source address(msg.sender).
- Address 2 which he will set as `to`
- And the address three is the source one which he uses to interact with the ERC20 vault on the source chain.
- Token is bridgedWETH
- Amount is 10 (just for this example)

Bob calls `sendToken()` with the above struct elements, before the function logic "constructs" the new struct which will be forwarded to the bridge by calling `sendMessage()`, first `_handleMessage()` will be called with the following arguments:

```
 (bytes memory data, CanonicalERC20 memory ctoken, uint256 balanceChange) =
            _handleMessage(msg.sender, _op.token, _op.to, _op.amount);
```
Once `_handleMessage` is called, it will first determine whether the token is a bridged or a canonical one, for the purpose of this example it's the bridged token: 

```
  // If it's a bridged token
        if (bridgedToCanonical[_token].addr != address(0)) {
            ctoken_ = bridgedToCanonical[_token];
            IBridgedERC20(_token).b
User canonical tokens will remain in vault after unbridging their tokens 
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/tokenvault/ERC1155Vault.sol#L39-L90
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/tokenvault/ERC1155Vault.sol#L247-L254


# Vulnerability details

## Impact
`Users` that `bridged` their `tokens` won't get them back

## Proof of Concept
`Tokens` are `bridged` by interacting with the `sendtoken()` function
```solidity
  function sendToken(BridgeTransferOp memory _op)
        external
        payable
        nonReentrant
        whenNotPaused
        withValidOperation(_op)
        returns (IBridge.Message memory message_)
```
 in any respective `ERCVault` contract and later `processed` by the `Bridge.sol` contract. The process for `bridging` `tokens` involves `locking` the respective `token` the user intends on `bridging` in the `ERCVault` contract and then `minting` a `bridged` version of that `token`. The process of `bridging` `tokens` to a `destchain` works quite fine and `tokens` are bridged successfully.
 Whereas, when `unbridging`, the `bridged` `token` will be `burnt`
```solidity
   unchecked {
            // is a btoken, meaning, it does not live on this chain
            if (bridgedToCanonical[_op.token].addr != address(0)) {
                ctoken_ = bridgedToCanonical[_op.token];
                for (uint256 i; i < _op.tokenIds.length; ++i) {
                    BridgedERC1155(_op.token).burn(_user, _op.tokenIds[i], _op.amounts[i]);
                }
```
 and the `locked` `token` in the `vault` transfered back to the owner. 
However, an issue comes up when trying to `unbridge` the `token` as the `minted` version will be `burnt` but the `locked` `tokens` will still remain in the `vault` contract and not be sent to the `User`.

*The following POC best describes the issue and should be placed in ERC1155.t.sol and run with forge test --match-path test/tokenvault/ERC1155Vault.t.sol -vv*
```solidity
    function test_1155Vault_bridge_back_but_token_remains_in_vault()
        public
    {
        vm.prank(Alice, Alice);
        ctoken1155.setApprovalForAll(address(erc1155Vault), true);

        assertEq(ctoken1155.balanceOf(Alice, 1), 10);
        assertEq(ctoken1155.balanceOf(address(erc1155Vault), 1), 0);

        uint256[] memory tokenIds = new uint256[](1);
        tokenIds[0] = 1;

        uint256[] memory amounts = new uint256[](1);
        amounts[0] = 1;

        BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp(
            destChainId,
            address(0),
            Alice,
            address(ctoken1155),
            tokenIds,
            amounts,
            140_000,
            140_000,
            Alice,
            ""
        );
        vm.prank(Alice, Alice);
        erc1155Vault.sendToken{ value: 140_000 }(sendOpts);

// BALANCE OF VAULT AFTER LOCKING TOKENS
        assertEq(ctoken1155.balance
WIP - TaikoL1.depositEtherToL2 does not send message to Bridge?
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/TaikoL1.sol#L119-L121
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/libs/LibDepositing.sol#L41
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/bridge/Bridge.sol#L70


# Vulnerability details

## Impact

## Proof of Concept

## Tools Used

## Recommended Mitigation Steps


## Assessed type

ETH-Transfer
Taiko L1 - Proposer can maliciously cause loss of funds by forcing someone else to pay prover's fee
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/0d081a40e0b9637eddf8e760fabbecc250f23599/packages/protocol/contracts/L1/hooks/AssignmentHook.sol#L113-L116
https://github.com/code-423n4/2024-03-taiko/blob/0d081a40e0b9637eddf8e760fabbecc250f23599/packages/protocol/contracts/L1/libs/LibProposing.sol#L85-L87
https://github.com/code-423n4/2024-03-taiko/blob/0d081a40e0b9637eddf8e760fabbecc250f23599/packages/protocol/contracts/L1/libs/LibProposing.sol#L249-L255


# Vulnerability details

## Impact
Proposal of new blocks triggers a call to proposeBlock in the libProposing library. In that function, there is this the following block of code:

            if (params.coinbase == address(0)) {
            params.coinbase = msg.sender;
        }

This sets the params.coinbase variable set by the caller of the function to be the msg.sender if it was empty. 

As part of the process of proposal, hooks can be called of type AssignmentHook. An assignment hook's onBlockProposed will be triggered as follows:

                    // When a hook is called, all ether in this contract will be send to the hook.
                // If the ether sent to the hook is not used entirely, the hook shall send the Ether
                // back to this contract for the next hook to use.
                // Proposers shall choose use extra hooks wisely.
                IHook(params.hookCalls[i].hook).onBlockProposed{ value: address(this).balance }(
                    blk, meta_, params.hookCalls[i].data
                );

Notice how the meta data is passed to this function. Part of the function of the onBlockProposed is to pay the assigned prover their fee and the payee should be the current proposer of the block. this is done as follows:

            // The proposer irrevocably pays a fee to the assigned prover, either in
        // Ether or ERC20 tokens.
        if (assignment.feeToken == address(0)) {
            // Paying Ether
            _blk.assignedProver.sendEther(proverFee, MAX_GAS_PAYING_PROVER);
        } else {
            // Paying ERC20 tokens
            IERC20(assignment.feeToken).safeTransferFrom(
                _meta.coinbase, _blk.assignedProver, proverFee
            );
        }

Notice how if the payment is in ERC20 tokens, the payee will be the variable  _meta.coinbase, and like we showed earlier, this can be set to any arbitrary address by the proposer. This can lead to a scenario as such:

1. proposer A approves the assignmentHook contract to spend a portion of their tokens, the allowance is set higher than the actual fee they will be paying.
2. proposer A proposes a block, and a fee is charged and payed to the assigned prover, but there remains allowance that the assignment hook contract can still use. 
3. proposer B proposes a block and sets params.coinbase as the the address of proposer A.
4. proposer A address will be the payee of the fee for the assigned prover for the block proposed by proposer B.


The scenario above describes how
User can't withdraw full reward from `ERC20Airdrop2`
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/airdrop/ERC20Airdrop2.sol#L104-L122
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/airdrop/ERC20Airdrop2.sol#L88-L94
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/airdrop/ERC20Airdrop2.sol#L39-L44


# Vulnerability details

## Summary

`ERC20Airdrop2.getBalance()` function calculates the reward based on the time that has passed after `claimEnd`. To get full reward time should be more or equal to `claimEnd + withdrawalWindow`. It is obvious that the user will try to make one transaction at the end of this period to get a full reward, but it's almost impossible. It's because if user spends an additional 1 second, executing `ERC20Airdrop2.withdraw()` would revert because it has `ERC20Airdrop2.ongoingWithdrawals()` modifier. The condition of this modifier says that user can not call function if period in which reward accumulates (`claimEnd + withdrawalWindow`) has passed. That means user has only 1 second to withdraw full reward, so when user executes `ERC20Airdrop2.withdraw()` `block.timestamp` must be equal to `claimEnd + withdrawalWindow`. User can not predict at what timestamp transaction will be executed. Also, we need to remember that `block.timestamp` can be manipulated by miners in their favour (for example, your transaction can be specially delayed (not for a long time) to make `block.timestamp` more then `claimEnd + withdrawalWindow`).

## Impact

User can not withdraw full reward that was given to him in `ERC20Airdrop2`.

## Proof of Concept

This test shows that user can not withdraw full reward when he is late even by one second.

Add this test to `ERC20Airdrop2.t.sol` and run with

```bash
    forge test --match-contract TestERC20Airdrop2 --match-test test_withdraw_user_can_withdraw_full_amount_only_if_withdraw_is_executed_in_last_second
```

```solidity
    function test_withdraw_user_can_withdraw_full_amount_only_if_withdraw_is_executed_in_last_second()
        public
    {
        vm.warp(claimStart + 1);

        // variable  get from test setUp when deploying airdrop2
        uint256 withdrawalWindow = 10 days;
        uint256 fullRewardTime = claimEnd + withdrawalWindow;

        // Alice claimed 100e18 tokens
        vm.prank(Alice, Alice);
        airdrop2.claim(Alice, 100e18, merkleProof);

        // average user needs at least 5 minutes to make transaction (time to open app, time to login into wallet, time to approve transaction and  execution time of transaction)
        vm.roll(block.number + 200);
        vm.warp(fullRewardTime - 5 minutes); // 5 minutes before full reward will be accumulated

        (, uint256 withdrawable) = airdrop2.getBalance(Alice);

        // shows that withdrawable is less than balance
        console.log(with
Unrestricted `pauseProving()` Function Enables Cross-Chain Denial of Service Attack on L1 Network
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/TaikoL1.sol#L109-L114
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/common/AddressManager.sol#L12


# Vulnerability details

## Impact (High): Unrestricted `pauseProving()` Function Enables Cross-Chain Denial of Service Attack on L1 Network

- [function pauseProving(bool \_pause) external {](https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/TaikoL1.sol#L111-L114)
- [mapping(uint256 chainId => mapping(bytes32 name => address addr)) private \_\_addresses;](https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/common/AddressManager.sol#L12-L13)

`pauseProving()` is a function that allows authorized addresses in the `private __addresses` mapping to pause/unpause proving of blocks. Note that there is no restrictions for which chain they are authorized to pause/unpause proving of blocks on L1. This means that an attacker with access to any authorized address of a different chain can pause/unpause proving of blocks on all other chains on L1, which is a security risk that will compromise the entire network from proving blocks for all chains. To mitigate this risk, it is recommended to restrict authorized addresses to pause/unpause proving of blocks on their own chain only so in the case of a malicious actor, the effect is limited to their one chain only, and not the entire network.

### POC

The provided POC demonstrates how I verified that any call to `pauseProving()` that is from an authorized address of a specified chain will not revert even in cases they are not the owner or authorized address on L1 chain, and they are a third party authorized address on a different chain.

```conf
// packages/protocol/certora/TaikoL1.conf
{
  "files": ["contracts/L1/TaikoL1.sol", "contracts/common/AddressManager.sol"],
  "link": ["TaikoL1:addressManager=AddressManager"],
  "verify": "TaikoL1:certora/TaikoL1.spec",
  "rule_sanity": "basic",
  "msg": "TaikoL1 Contracts Verification",
  "optimistic_loop": true,
  "solc_evm_version": "cancun"
}
```

```spec
// packages/protocol/certora/TaikoL1.spec
using AddressManager as manager;

methods {
  // ITaikoL1
  function proposeBlock(bytes, bytes) external returns (TaikoData.BlockMetadata memory, TaikoData.EthDeposit[] memory);
  function proveBlock(uint64, bytes) external envfree;
  function verifyBlocks(uint64) external envfree;
  function getConfig() external returns (TaikoData.Config memory) envfree;

  // TaikoL1
  function verifyBlocks(uint64) external envfree;
  function pauseProving(bool) external;
  function unpause() external;

  // Ownable2StepUpgradeable, OwnableUpgradeable
  function owner() external returns address envfree;

  // Manager
  function manager.setAddress(uint6
Incomplete validation in _invokeMessageCall() allows attackers to pass arbitrary data and steal tokens
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/bridge/Bridge.sol#L492
https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/tokenvault/ERC20Vault.sol#L253


# Vulnerability details

## Summary
In `_invokeMessageCall()`, the [check for msg.data](https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/bridge/Bridge.sol#L492) not being equal to `IMessageInvocable.onMessageInvocation.selector` is made to ensure the low-level `excessivelySafeCall()` can't be used to execute malicious arbitrary data. This check however is not enough because in `ERC20Vault.sol`, `ERC721Vault.sol` and `ERC1155Vault.sol`, the function `onMessageInvocation()` is [marked external](https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/tokenvault/ERC20Vault.sol#L254) and the ['onlyFromBridge' check](https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/tokenvault/ERC20Vault.sol#L262) will pass too because in the case of a low-level call, `msg.sender` would be the bridge contract.
This enables the attacker to pass `_message` inside `_invokeMessageCall()` which has `_message.to` equal to the address of the Vault contract and `msg.data[0:4]` equalling `IMessageInvocable.onMessageInvocation.selector`, thus bypassing all checks on [L491-L493](https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/bridge/Bridge.sol#L491-L493).

```js
  File: contracts/bridge/Bridge.sol

  477:              function _invokeMessageCall(
  478:                  Message calldata _message,
  479:                  bytes32 _msgHash,
  480:                  uint256 _gasLimit
  481:              )
  482:                  private
  483:                  returns (bool success_)
  484:              {
  485:                  if (_gasLimit == 0) revert B_INVALID_GAS_LIMIT();
  486:                  assert(_message.from != address(this));
  487:          
  488:                  _storeContext(_msgHash, _message.from, _message.srcChainId);
  489:          
  490:                  if (
  491:                      _message.data.length >= 4 // msg can be empty
  492: @--->                    && bytes4(_message.data) != IMessageInvocable.onMessageInvocation.selector
  493:                          && _message.to.isContract()
  494:                  ) {
  495:                      success_ = false;
  496:                  } else {
  497:                      (success_,) = ExcessivelySafeCall.excessivelySafeCall(
  498:                          _message.to,
  499:                          _gasLimit,
  500:                          _message.value,
  501:                          64, // return max 64 bytes
  502:                          _message.data
  503:                      );
  504:                  }
```

and 

```js
  File: contracts/tokenvault/ERC20Vault.sol

  253:              function onMessageInvocation(bytes calldata _data
Signature Replay Vulnerability in `TimelockTokenPool`
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
`TimelockTokenPool` contract has two withdrawal functionality, one that allows withdrawal to self and another that allows withdrawal to a different address.
The latter allows withdrawal to other addresses via a signature, it works by providing a signature and a receiving address on call, on the signature recovery, the returned signer acts as the recipient whose grants will be sent to the receiving `_to` address.
```solidity
    function withdraw(address _to, bytes memory _sig) external {
        if (_to == address(0)) revert INVALID_PARAM();
        bytes32 hash = keccak256(abi.encodePacked("Withdraw unlocked Taiko token to: ", _to));
        address recipient = ECDSA.recover(hash, _sig);
        _withdraw(recipient, _to);
    }
```
The problem here is:
+ Due to the lack of signature expiration or the use of a nonce value, the receiving address can continuously re-use the same signature to withdraw the recipient's accumulated grant before he can.

 **To Illustrate:**

Consider the scenario where Alice, entitled to 200 grants, initially withdraws 100 for herself. She later decides to distribute 50 grants each to Bob and Charlie. Alice then signs a transaction to withdraw her current allowance of 50 grants to Bob. However, days later, when she intends to repeat the process for Charlie,  Bob reuses Alice's previous transaction signature, to redirect her intended grant withdrawal to himself, leaving Charlie with either nothing or only a fraction of the intended amount, depending on Alice's remaining allowance at the time of execution.
Bob can continually reuse the same signature to withdraw Alice's grants whenever she's allocated any in the future


+ **Final thoughts**

From the current implementation, I believe whenever the recipient wishes to withdraw to self, she uses [TimelockTokenPool::withdraw()](https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/TimelockTokenPool.sol#L161-L163) and [TimelockTokenPool::withdraw](https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/TimelockTokenPool.sol#L168-L173)(_to, sig) when she intends to send her withdrawal to a different address. The receiving address could be owned or it might not, but regardless the signature should only be usable once to prevent this issue.
Since the use of this functionality potentially puts the recipient's grants at risk of being stolen, I have labelled this issue to be of a High severity


## Proof of Concept
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/TimelockTokenPool.sol#L168-L173

## Tools Used
Manual Review
## Recommended Mitigation Steps
Either add a req