xanoutas/solidity-security-findings huggingface dataset:
Validity and contests bond ca be incorrectly burned for the correct and ultimately verified transition
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/libs/LibProving.sol#L387-L392
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/libs/LibProving.sol#L189-L199
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/libs/LibVerifying.sol#L178-L189


# Vulnerability details

## Impact

Both validity and contests bonds can be wrongfully slashed even if the transition ends up being the correct and verified one.

The issue comes from the fact that the history of the final verified transition is not taken into account. 

Example 1: Validity bond is wrongfully burned:

1. Bob Proves transition T1 for parent P1
2. Alice contests and proves T2 for parent P1 with higher tier proof. 
3. Guardians steps in to correctly prove T1 for parent P2.

At step 2 Bob loses his bond and is permanentley written out of the history of P1 
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/libs/LibProving.sol#L387-L392

```solidity
    _ts.validityBond = _tier.validityBond; 
    _ts.contestBond = 1; 
    _ts.contester = address(0);
    _ts.prover = msg.sender;
    _ts.tier = _proof.tier; 
```

Example 2: Contest bond wrongfully slashed:
1. Alice proves T1 for parent P1 with SGX
2. Bob contests T1 for parent P1
3. Alice proves T1 with SGX_ZK parent P1
4. Guardian steps in to correctly disprove T1 with T2 for parent P1

Bob was correct and T1 was ultimately proven false. Bob still loses his contest bond.

When the guardian overrides the proof they can not pay back Bob's validity or contesting bond. They are only able to pay back a liveness bond
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/libs/LibProving.sol#L189-L199

```solidity
if (isTopTier) { 
	// A special return value from the top tier prover can signal this
	// contract to return all liveness bond.
	bool returnLivenessBond = blk.livenessBond > 0 && _proof.data.length == 32
		&& bytes32(_proof.data) == RETURN_LIVENESS_BOND;

	if (returnLivenessBond) {
		tko.transfer(blk.assignedProver, blk.livenessBond);
		blk.livenessBond = 0;
	} 
}
```

These funds are now frozen since they are sent to the Guardian contract which has no ability to recover them

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

```solidity
                uint256 bondToReturn = uint256(ts.validityBond) + blk.livenessBond;

                if (ts.prover != blk.assignedProver) {
                    bondToReturn -= blk.livenessBond >> 1;
                }

                IERC20 tko = IERC20(_resolver.resolve("taiko_token", false));
                tko.transfer(ts.prover, bondToReturn)
`
there could be DOS for anchor 
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
there could be potential DOS for anchor due to mis calculation of l2 base fee .

## Proof of Concept
     if (!skipFeeCheck() && block.basefee != basefee) {
            revert L2_BASEFEE_MISMATCH();
        }

protocol use bonding curve mechanism for calculation base fee .Actually it's really different from eip 1559 base fee calculation .This will cause the mis match of base fee and lead to DOS for anchor .

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md
https://www.blocknative.com/blog/eip-1559-fees
## Tools Used
manual view
## Recommended Mitigation Steps
use eip 1559 base fee calculation mechanism 


## Assessed type

DoS
Signature Reuse Attack
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
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/TimelockTokenPool.sol#L208-L223
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/TimelockTokenPool.sol#L176-L199


# Vulnerability details

## Impact

User airdropped tokens may be stolen

## Proof of Concept

With the [withdraw](https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/TimelockTokenPool.sol#L168-L173) method, we can see that the signature of this withdrawal has no reuse prevention mechanism added to it
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/TimelockTokenPool.sol#L168-L173
```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); // @audit The signature can always be reused
        _withdraw(recipient, _to);
    }
```
With the [_getAmountUnlocked](https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/TimelockTokenPool.sol#L239-L243) method, we can see that the number of tokens that can be withdrawn is unlocked over time.
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/TimelockTokenPool.sol#L239-L243
```solidity
    function _getAmountUnlocked(Grant memory _grant) private view returns (uint128) {
        return _calcAmount(
            _getAmountOwned(_grant), _grant.unlockStart, _grant.unlockCliff, _grant.unlockPeriod
        ); // @audit Unlocked over time
    }
```
If the user grants another user the right to withdraw the now unlocked amount of tokens, after the user withdraws. As time goes by, more tokens will be unlocked, and the user who got the signature can continue to withdraw tokens, and there is no way to cancel the signature. Restrictions are grant.costPerToken or the user granting the TimelockTokenPool contract can get the number of costToken. If costPerToken is 0, or the number of approvals is max, the user acquiring the signature can acquire as many as they want. If there is a finite number of approvals, the user signing can also withdraw money early after seeing a new grant from the user

## Tools Used

Manual Review

## Recommended Mitigation Steps
Add nonces to prevent the signature from being reused.







## Assessed type

Access Control
Attacker could specify any `params.coinbase` when calling `proposeBlock()` to drain address that has approved the TaikoL1 contract
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/libs/LibProposing.sol#L68
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/hooks/AssignmentHook.sol#L115


# Vulnerability details

## Impact
When a block is proposed using the `proposeBlock()` function, the sender can specify the `params.coinbase` address. This is the L2 block proposer address, which receives fees from ETH deposits when processing deposits. It also pays fees to the assigned prover in either ETH or ERC20 tokens in the `onBlockProposed()` function.

```solidity
// 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 // @audit coinbase address could be arbitrary
    );
}
```

As shown, if the proposer pays in ERC20 tokens, it uses `safeTransferFrom()` to directly transfer from the `_meta.coinbase` address to the `assignedProver`. As a result, the sender can specify the coinbase address as any address that has approved the contract, to pay the fee to the assigned prover.


## Proof of Concept
There is no validation for `params.coinbase` in the `proposeBlock()` function. Even if it's set to `address(0)`, it defaults to the sender's address.

```solidity
function proposeBlock(
    ...
)
    internal
    returns (TaikoData.BlockMetadata memory meta_, TaikoData.EthDeposit[] memory deposits_)
{
    ...
    if (params.coinbase == address(0)) {
        params.coinbase = msg.sender;
    }
    ...
    deposits_ = LibDepositing.processDeposits(_state, _config, params.coinbase);
    ...
    unchecked {
        meta_ = TaikoData.BlockMetadata({
            l1Hash: blockhash(block.number - 1),
            difficulty: 0, // to be initialized below
            blobHash: 0, // to be initialized below
            extraData: params.extraData,
            depositsHash: keccak256(abi.encode(deposits_)),
            coinbase: params.coinbase,
            id: b.numBlocks,
            gasLimit: _config.blockMaxGasLimit,
            timestamp: uint64(block.timestamp),
            l1Height: uint64(block.number - 1),
            txListByteOffset: 0, // to be initialized below
            txListByteSize: 0, // to be initialized below
            minTier: 0, // to be initialized below
            blobUsed: _txList.length == 0,
            parentMetaHash: parentMetaHash
        });
    }
    ...
    IHook(params.hookCalls[i].hook).onBlockProposed{ value: address(this).balance }(
        blk, meta_, params.hookCalls[i].data
    );
}
```

The `meta_.coinbase` address is set to the `params.coinbase` address. Then `meta_` is passed as an input paramet
ether deposited to l2 by multi sig can be stolen 
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-L120
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/libs/LibDepositing.sol#L44-L45


# Vulnerability details

## Impact
ether that 's gonna deposit to l2 by multi sig contract can be stolen  from same address on l2 .

## Proof of Concept


     address recipient_ = _recipient == address(0) ? msg.sender : _recipient;

if sender didn't set the recipient , protocol will set to msg.sender . However if msg.sender is deployed using Gnosis Safe contracts . It's possible that there will be another address on l2 which is identical to original msg.sender address .This can lead to same address , different owner from l2  can access to these deposited ether . 

https://rekt.news/wintermute-rekt/
this scenario is also happened in the past /
## Tools Used
manual view
## Recommended Mitigation Steps
check that recipient is contract or not 


## Assessed type

Context
Users will never be able to withdraw their claimed airdrop fully in ERC20Airdrop2.sol contract
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#L117


# Vulnerability details

## Impact
**Context:**
The ERC20Airdrop2.sol contract is for managing Taiko token airdrop for eligible users, but the withdrawal is not immediate and is subject to a withdrawal window.

Users can claim their tokens within claimStart and claimEnd. Once the claim window is over at claimEnd, they can withdraw their tokens between claimEnd and claimEnd + withdrawalWindow. During this withdrawal period, the tokens unlock linearly i.e. the tokens only become fully withdrawable at claimEnd + withdrawalWindow.

**Issue:**
The issue is that once the tokens for a user are fully unlocked, the [withdraw()](https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/airdrop/ERC20Airdrop2.sol#L88) function cannot be called anymore due to the [ongoingWithdrawals modifier](https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/team/airdrop/ERC20Airdrop2.sol#L39) having a strict `claimEnd + withdrawalWindow < block.timestamp` check in its second condition.

**Impact:**
Although the tokens become fully unlocked when block.timestamp = claimEnd + withdrawalWindow, it is extremely difficult or close to impossible for normal users to time this to get their full allocated claim amount. This means that users are always bound to lose certain amount of their eligible claim amount. This lost amount can be small for users who claim closer to claimEnd + withdrawalWindow and higher for those who partially claimed initially or did not claim at all thinking that they would claim once their tokens are fully unlocked.

## Proof of Concept

### Coded POC

How to use this POC:
 - Add the POC to `test/team/airdrop/ERC20Airdrop2.t.sol`
 - Run the POC using `forge test --match-test testAirdropIssue -vvv`
 - The POC demonstrates how alice was only able to claim half her tokens out of her total 100 tokens claimable amount.
```solidity
      function testAirdropIssue() public {
        vm.warp(uint64(block.timestamp + 11));

        vm.prank(Alice, Alice);
        airdrop2.claim(Alice, 100, merkleProof);

        // Roll 5 days after
        vm.roll(block.number + 200);
        vm.warp(claimEnd + 5 days);

        airdrop2.withdraw(Alice);

        console.log("Alice balance:", token.balanceOf(Alice));

        // Roll 6 days after
        vm.roll(block.number + 200);
        vm.warp(claimEnd + 11 days);

        vm.expectRevert(ERC20Airdrop2.WITHDRAWALS_NOT_ONGOING.selector);
        airdrop2.withdraw(Alice);
    }
```

### Logs

```solidity
Logs:
  > MockERC20Airdrop @ 0x0000000000000000000000000000000000000000
    proxy      : 0xF62849F9A0B5Bf2913b396098F7c7019b51A820a
    impl       : 0x2e234DAe75C793f67A35089C9d99245E1C58470b
    owner      : 0x7FA9385bE102ac3EAc29
potentially proving blocks that haven't proposed due to reorg
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#L75
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/TaikoL1.sol#L55


# Vulnerability details

## Impact
There could be proving blocks that haven't actually  proposed cause of the reorg on ethereum chain.

## Proof of Concept
Block reorg is a situation where a competing chain replaces the main blockchain. This can occur when multiple miners find valid blocks at the same time, and the network has to decide which block to include in the blockchain. In order to reduce the risk of block reorgs, the Fast Bridge project may need to take additional precautions, such as waiting for multiple confirmations before processing token transfers or implementing a fallback mechanism in case of a block reorg.

let's assume propose block tx is at block N and prove block tx is at block N + 1 .Both block N and N+1 are executed. Unfortunately block reorg happen at block N . due to that, propose block tx will be reverted . However proving block that is already executed on block N + 1 will not reverted ,it will still exist on the mainnet . This means taiko L1 contract proving blocks that haven't actually proposed .This may lead to  Double Spending ,Transaction Delays and Loss of contest Rewards .

## Tools Used
manual view
## Recommended Mitigation Steps
set the wait time for proving blocks instead of proving instantly


## Assessed type

Context
Users may lose airdropped tokens in `ERC20Airdrop2` contract
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Issue Description

During an airdrop organized with the `ERC20Airdrop2` contract, there are two steps involved: first, the chosen users must claim their airdrop amounts by calling the `claim` function, and then they must wait until the withdrawal period begins to start withdrawing those amounts using the `withdraw` function.

The withdrawal period operates similarly to a vesting period, during which initially `block.timestamp < claimEnd`, no amount can be withdrawn. Subsequently, users can gradually withdraw as time passes until the moment `block.timestamp == claimEnd + withdrawalWindow`, when the entire amount becomes withdrawable.

In typical vesting scenarios, the full airdropped amount remains withdrawable after the withdrawal period has ended. However, in the case of `ERC20Airdrop2`, this is not the case due to the presence of the `ongoingWithdrawals` modifier within the `withdraw` function:

```solidity
function withdraw(address user) external ongoingWithdrawals {
    (, uint256 amount) = getBalance(user);
    withdrawnAmount[user] += amount;
    IERC20(token).transferFrom(vault, user, amount);

    emit Withdrawn(user, amount);
}
```

The `ongoingWithdrawals` modifier allows users to withdraw only during the withdrawal period, defined as `claimEnd <= block.timestamp <= claimEnd + withdrawalWindow`, as illustrated in the code below:

```solidity
modifier ongoingWithdrawals() {
    if (claimEnd > block.timestamp || claimEnd + withdrawalWindow < block.timestamp) {
        revert WITHDRAWALS_NOT_ONGOING();
    }
    _;
}
```

It's important to note that the full airdropped amount becomes withdrawable only when `block.timestamp == claimEnd + withdrawalWindow`, as enforced by the `getBalance` function invoked within the `withdraw` function:

```solidity
function getBalance(address user)
    public
    view
    returns (uint256 balance, uint256 withdrawableAmount)
{
    balance = claimedAmount[user];
    // If balance is 0 then there is no balance and withdrawable amount
    if (balance == 0) return (0, 0);
    // Balance might be positive before end of claiming (claimEnd - if claimed already) but
    // withdrawable is 0.
    if (block.timestamp < claimEnd) return (balance, 0);

    // Hard cap timestamp - so range cannot go over - to get more allocation over time.
    uint256 timeBasedAllowance = balance
        * (block.timestamp.min(claimEnd + withdrawalWindow) - claimEnd) / withdrawalWindow;

    withdrawableAmount = timeBasedAllowance - withdrawnAmount[user];
}
```

This setup can lead to users losing a part or all of their airdropped t
Prover loses funds even if proven right after multiple contests
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description & Impact
Consider the following flow:
- Assumption: Tier levels from low to high are `TIER_OPTIMISTIC` --> `TIER_SGX` --> `TIER_GUARDIAN` (assuming this so that it's in line with the [code logic inside TaikoL1LibProvingWithTiers.t.sol](https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol#L40-L44) and our PoC matches).
- Alice proposes a block with Bob as the prover in tier `TIER_OPTIMISTIC`. 
- The correct proof entails `blockHash = blockHash` and `stateRoot = stateRoot`.
- Bob calls `proveBlock()` with above params.
- Carol contests this by calling `proveBlock()` with `blockHash = stateRoot` and `stateRoot = stateRoot`.
- At the next higher tier `TIER_SGX`, Carol calls `proveBlock()` to accept the proof and is rewarded.
- Bob loses his bond deposit.
- Alice immediately contests Carol's proof by calling `proveBlock()` with `blockHash = blockHash` and `stateRoot = stateRoot`.
- David proves Alice right at the next higher tier of `TIER_GUARDIAN`. Carol loses her bond deposit.
- Hence eventually, it turns out Bob was right all along but has still endured a loss of funds.

## Proof of Concept
Add the following helper function and test case inside `protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol` and run via `forge test -vv --mt test_t0x1c_L1_MultipleContests` to see the subsequent output:
```js
    function proveHigherTierProof(
        address caller,
        TaikoData.BlockMetadata memory meta,
        bytes32 parentHash,
        bytes32 stateRoot,
        bytes32 blockHash,
        uint16 minTier
    )
        internal
    {
        uint16 tierToProveWith;
        if (minTier == LibTiers.TIER_OPTIMISTIC) {
            tierToProveWith = LibTiers.TIER_SGX;
        } else if (minTier == LibTiers.TIER_SGX) {
            tierToProveWith = LibTiers.TIER_GUARDIAN;
        } 
        proveBlock(caller, caller, meta, parentHash, blockHash, stateRoot, tierToProveWith, "");
    }
    
    function test_t0x1c_L1_MultipleContests() external {
        giveEthAndTko(Alice, 1e8 ether, 1000 ether);
        giveEthAndTko(Bob, 1e8 ether, 100 ether);
        giveEthAndTko(Carol, 1e8 ether, 1000 ether);
        giveEthAndTko(David, 1e8 ether, 1000 ether);

        console2.log("\nBefore Test");
        emit log_named_decimal_uint("Bob balance", tko.balanceOf(Bob), 18);

        bytes32 parentHash = GENESIS_BLOCK_HASH;
        uint256 blockId = 1;

        (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, Bob, 1_000_000, 1024);
        mine(1);

        bytes32 blockHash = bytes32(1e10 + blockId);
        bytes32 stateRoot = bytes32(1e9 + blockId);
        
        uint16 minTier = meta.minTier; // TIER_OPTIMISTIC
        proveBlock(Bob, Bob, meta, parentHash, blockHash, stateRoot, minTier, "");

        // Carol contests
        p