xanoutas/solidity-security-findings huggingface dataset:
Burned tokens are not refunded when `to` address in destination chain is banned, even when `to` address is unbanned
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/bridge/Bridge.sol#L269-#L276


# Vulnerability details

## Vulnerability details
In the `Bridge.processMessage()` function, if `to` address is banned, it is not able to invoke message call:

            if (
                _message.to == address(0) || _message.to == address(this)
                    || _message.to == signalService || addressBanned[_message.to] // <---
            ) {
                // Handle special addresses that don't require actual invocation but
                // mark message as DONE
                refundAmount = _message.value;
                _updateMessageStatus(msgHash, Status.DONE); //<---
            }

And that message will be marked as done, which mean it can not retry again. Consider scenario:

- User A from TaikoL2 make a message to `to` address in mainnet
- In mainnet, `to` address is banned through `banAddress()` function
- User `to` are not able to get token back in mainnet anymore, even when they are unbanned

## Impact
Token will be permamently lost, there is no way to retrieve them again.

## Tools Used
Manual review.

## Recommended Mitigation Steps
When address is banned, message should be marked as `RETRIABLE`, and it can be called again when they are unbanned


## Assessed type

Context
Proposal lacks multiple important logic checks
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#L69


# Vulnerability details

## Impact

In TaikoGovernor.sol contracts, the propose function lacks the multiple
logical checks listed below.

- Missing to check if the length of the provided targets is equal to
values, data length or not. Thus allowing the incorrect submission of
the proposals

- No maximum length of targets is defined, which leads to too many actions
in a proposa



## Tools Used

vscode.

## Recommended Mitigation Steps

```sol
 require(
            targets.length == values.length &&
                targets.length == signatures.length &&
                targets.length == calldatas.length,
            "GovernorBravo::propose: proposal function information arity mismatch"
        );
        require(targets.length != 0, "GovernorBravo::propose: must provide actions");
        require(targets.length <= proposalMaxOperations, "GovernorBravo::propose: too many actions");
```



## Assessed type

Governance
Discrepancy in Message Suspension Check within processMessage() Function
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/bridge/Bridge.sol#L79-L95
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/bridge/Bridge.sol#L216-L307


# Vulnerability details

## Impact
Without suspension checks in the ``processMessage`` function, unintended messages may be processed, leading to unauthorized state changes and potential security breaches. This can disrupt normal contract operations and ignore administrative controls intended to pause or review certain actions. The system becomes less adaptable to dynamic conditions, such as emergencies or updates, as it cannot halt the execution of flagged messages. Additionally, this oversight may result in wasted computational resources and increased costs due to the processing of messages meant to be suspended.

## POC

```solidity
/// @notice Suspend or unsuspend invocation for a list of messages.
    /// @param _msgHashes The array of msgHashes to be suspended.
    /// @param _suspend True if suspend, false if unsuspend.
    function suspendMessages(
        bytes32[] calldata _msgHashes,
        bool _suspend
    )
        external
        onlyFromOwnerOrNamed("bridge_watchdog")
    {
        uint64 _timestamp = _suspend ? type(uint64).max : uint64(block.timestamp);
        for (uint256 i; i < _msgHashes.length; ++i) {
            bytes32 msgHash = _msgHashes[i];
            proofReceipt[msgHash].receivedAt = _timestamp;
            emit MessageSuspended(msgHash, _suspend);
        }
    }

```

```solidity

/// @inheritdoc IBridge
    function processMessage(
        Message calldata _message,
        bytes calldata _proof
    )
        external
        nonReentrant
        whenNotPaused
        sameChain(_message.destChainId)

```

There is a crucial missing step: the function does not verify whether a message has been suspended. In the context of your smart contract, suspension is likely intended to be indicated by setting the ``receivedAt`` timestamp (within the ``proofReceipt`` mapping) to a future date (specifically, using type(uint64).max to denote a suspended state).

The processMessage function checks several conditions, such as message status (ensuring it's new) and proper signal receipt, but it does not compare the current timestamp against the receivedAt timestamp in the proofReceipt for the given message hash. A suspended message, per the suspension logic, would have a receivedAt timestamp set far into the future.

The function uses the receivedAt timestamp primarily to verify that a message has been acknowledged or proven but overlooks its potential use as a suspension flag. This is a design flaw since the timestamp, when set to a maximum value (a common method for indicating "indefinitely suspended"), should act as a clear indicator that the message should not be processed.

Ideally, when handling messages, there should b
snapshot on TaikoToken will not work for the snapshoter address
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
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/common/EssentialContract.sol#L42
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/common/AddressResolver.sol#L81


# Vulnerability details

## Impact

TaikoToken has a method for doing snapshots that can be called by the token owner or a special account called the snapshoter. But the call will only work for the owner becouse the snapshoter address cannot be resolved inside the token contract

## Proof of Concept

The followin POC is showing that any other address that is not the contract owner will get the `RESOLVER_INVALID_MANAGER` revert

Put this test into `ERC20Vault.t.sol` file:
```solidity
function test_addressManagerNotSetOnTaikoToken() public {
        vm.prank(address(0xdeadbeaf));
        vm.expectRevert(AddressResolver.RESOLVER_INVALID_MANAGER.selector);
        tko.snapshot();
    }
```

This revert happens becouse the modifier that decorates the `TaikoToken#snapshot` function tries to resolve the `snapshoter` address:

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


https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/common/EssentialContract.sol#L42


but the code faults on verifying that an address manager is not set by reverting with `RESOLVER_INVALID_MANAGER`


https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/common/AddressResolver.sol#L81

## Tools Used
Manual review

## Recommended Mitigation Steps

In the initializer pass the address manager address and use the proper initialization for the address resolver to work:

```diff
    function init(
        address _owner,
        string calldata _name,
        string calldata _symbol,
+       address addressManager,
        address _recipient
    )
        public
        initializer
    {
-       __Essential_init(_owner);
+       __Essential_init(_owner, addressManager);
        __ERC20_init(_name, _symbol);
        __ERC20Snapshot_init();
        __ERC20Votes_init();
        __ERC20Permit_init(_name);


        // Mint 1 billion tokens
        _mint(_recipient, 1_000_000_000 ether);
    }
```


## Assessed type

Access Control
re-adding an expired SGX instance is not working
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/verifiers/SgxVerifier.sol#L236-L237
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/verifiers/SgxVerifier.sol#L161
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/verifiers/SgxVerifier.sol#L100
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/verifiers/SgxVerifier.sol#L211
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/verifiers/SgxVerifier.sol#L111


# Vulnerability details

## Impact

In case an already registered SGX instance is expired it can be successfully removed but adding it again becouse the BUG explained in the POC is not be possible to do. 

The system looses an valid but expired SGX instance.

## Proof of Concept

An expired SGX instance is going to always revert on the following line:

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/verifiers/SgxVerifier.sol#L236-L237

when called to verify a proof:

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/verifiers/SgxVerifier.sol#L161

To have a valid but expried instance to work again we need to remove the instance which is possible with the call to 

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/verifiers/SgxVerifier.sol#L100

but when we try to readd the instance will get a revert of 'SGX_ALREADY_ATTESTED' becosue this line :

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/verifiers/SgxVerifier.sol#L211

The following POC will prove this kind of scenario (this test function is added to the `SgxVerifier.t.sol` file):

```solidity
function test_readdDeletedInstance() external {
        V3Struct.ParsedV3QuoteStruct memory v3quote =
            ParseV3QuoteBytes(address(pemCertChainLib), sampleQuote);

        vm.prank(Bob, Bob);
        uint256 addedInstanceId = sv.registerInstance(v3quote);

        // @ NEED to reead the instance becouse has expired (reverting on _isInstanceValid check) 
        
        // @try to readd the same instance: by first deleting it the re-register
        uint256[] memory _ids = new uint256[](1);
        _ids[0] = addedInstanceId;
        sv.deleteInstances(_ids);

        vm.expectRevert(SgxVerifier.SGX_ALREADY_ATTESTED.selector); // @-> will revert the instance cannot be used anymore
        sv.registerInstance(v3quote);
    }
```

The BUG lies inside the `deleteInstances` becouse a map entry is not deleted when the instance is removed:

https://github.com/code-423n4/2024-
The top tier prover can not re-prove
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/LibProving.sol#L219-L236
https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/L1/libs/LibProving.sol#L389


# Vulnerability details

## Impact

In the `LibProving.proveBlock` function the `top tier prover` is allowed to prove a new transition as long as the new transition is different from the previous transition and `assert` conditional checks pass.

```solidity
            if (sameTransition) revert L1_ALREADY_PROVED();

            if (isTopTier) {
                // The top tier prover re-proves.
                assert(tier.validityBond == 0);
                assert(ts.validityBond == 0 && ts.contestBond == 0 && ts.contester == address(0));
```

But the `assert condition` of this logic is wrong since it checks for the `ts.contestBond == 0` where as it should be `ts.contestBond == 1` since 1 is set as the default value for `ts.contestBond` parameter for gas savings as shown below:

```solidity
            ts_.contestBond = 1; // to save gas
```

As a result of the even though code expects the top-tier prover to re-prove a different transition, the transaction will revert.

## Proof of Concept

Add the following testcase `test_L1_GuardianProverCanOverwriteIfNotSameProof_test` to the `TaikoL1LibProvingWithTiers.t.sol` test file. 

If you change the ts.contestBond == 0
to ts.contestBond == 1 in the second `assert` statement of the `LibProving.proveBlock` function, the test will run successfully and transaction execution will succeed.

```solidity
    function test_L1_GuardianProverCanOverwriteIfNotSameProof_test() external {
        giveEthAndTko(Alice, 1e7 ether, 1000 ether);
        giveEthAndTko(Carol, 1e7 ether, 1000 ether);
        console2.log("Alice balance:", tko.balanceOf(Alice));
        // This is a very weird test (code?) issue here.
        // If this line is uncommented,
        // Alice/Bob has no balance.. (Causing reverts !!!)
        // Current investigations are ongoing with foundry team
        giveEthAndTko(Bob, 1e7 ether, 100 ether);
        console2.log("Bob balance:", tko.balanceOf(Bob));
        // Bob
        vm.prank(Bob, Bob);

        bytes32 parentHash = GENESIS_BLOCK_HASH;
        for (uint256 blockId = 1; blockId < conf.blockMaxProposals * 3; blockId++) {
            printVariables("before propose");
            (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, Bob, 1_000_000, 1024);
            //printVariables("after propose");
            mine(1);

            bytes32 blockHash = bytes32(1e10 + blockId);
            bytes32 stateRoot = bytes32(1e9 + blockId);
            // This proof cannot be verified obviously because of
            // blockhash:blockId
            proveBlock(Bob, Bob, meta, parentHash, stateRoot, stateRoot, LibTiers.TIER_GUARDIAN, "");

            // Prove as guardian                       
            proveBlock(
                Carol, Carol, me
User is still able to claim airdrop token outside claim period
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/team/airdrop/ERC20Airdrop2.sol#L78-#L84


# Vulnerability details

## Vulnerability details
In the `ERC20Airdrop2.init()` function:

    /// @param _claimStart The start time of the claim period.
    /// @param _claimEnd The end time of the claim period.
It mean that there is period for claiming, but in `claim()` function, there is no checking condition about claim period:

    function claim(address user, uint256 amount, bytes32[] calldata proof) external nonReentrant {
        // Check if this can be claimed
        _verifyClaim(abi.encode(user, amount), proof);

        // Assign the tokens
        claimedAmount[user] += amount;
    }

## Impact
Lack of checking condition about claim period, contradict documentation

## Tools Used
Manual review

## Recommended Mitigation Steps
Add `onGoingClaimPeriod` modifier to `claim()` function:

    modifier onGoingClaimPeriod {
        require(block.timestamp >= claimStart && block.timestamp <= claimEnd);
        _;
    }


## Assessed type

Other
retryMessage unable to handle edge cases.
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/bridge/Bridge.sol#L309-L337


# Vulnerability details

## Impact
The function ``retryMessage()`` is unable to handle some edge scenarios listed below.
1. Reverting or refunding a sender when the receiver is banned after the transaction is placed in a ``RETRIABLE`` state.
2. Message that is suspended after the transaction is placed in a ``RETRIABLE`` state.


## Proof of Concept

A message is set to ``RETRIABLE``in the ``processMessage`` when transfer fails or if the target address does not satisfy some conditions. 
```solidity
//IN PROCESSMESSAGE

                if (_invokeMessageCall(_message, msgHash, gasLimit)) {
                    _updateMessageStatus(msgHash, Status.DONE);
                } else {
                    _updateMessageStatus(msgHash, Status.RETRIABLE);
                }





//_invokeMessageCall() FUNCTION
function _invokeMessageCall(
        Message calldata _message,
        bytes32 _msgHash,
        uint256 _gasLimit
    )
        private
        returns (bool success_)
    {
        if (_gasLimit == 0) revert B_INVALID_GAS_LIMIT();
        assert(_message.from != address(this));

        _storeContext(_msgHash, _message.from, _message.srcChainId);

        if (
            _message.data.length >= 4 // msg can be empty
                && bytes4(_message.data) != IMessageInvocable.onMessageInvocation.selector
                && _message.to.isContract()
        ) {
            success_ = false;
        } else {
            (success_,) = ExcessivelySafeCall.excessivelySafeCall(
                _message.to,
                _gasLimit,
                _message.value,
                64, // return max 64 bytes
                _message.data
            );
        }

        // Must reset the context after the message call
        _resetContext();
    }
```
The issue here is, when the team attempts suspension of a message which is in a ``RETRIABLE`` state, the code does not block execution.
Same or a banned address.
This is because the ``proofReceipt[msgHash]`` which handles suspension is not checked. The `` addressBanned[_addr]`` is not checked too.
```solidity
    function retryMessage(
        Message calldata _message,
        bool _isLastAttempt
    )
        external
        nonReentrant
        whenNotPaused
        sameChain(_message.destChainId)
    {
        // If the gasLimit is set to 0 or isLastAttempt is true, the caller must
        // be the message.destOwner.
        if (_message.gasLimit == 0 || _isLastAttempt) {
            if (msg.sender != _message.destOwner) revert B_PERMISSION_DENIED();
        }

        bytes32 msgHash = hashMessage(_message);
        if (messageStatus[msgHash] != Status.RETRIABLE) {
            revert B_NON_RETRIABLE();
        }

        // Attempt to invoke the messageCall.
        if (_invokeMessageCall(_message, msgHash, gasleft())) {
            _update
ERC tokens lost when sent to a suspended user, not fully refunded.
Audit: 2024-03-taiko-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/bridge/Bridge.sol#L268-L277
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/bridge/Bridge.sol#L289-L299


# Vulnerability details


## Impact
When tokens are bridged to a suspended user, a refund occurs in the ``processMessage()`` function. However, there has been a critical oversight. Only the native token is refunded. The ``ERC20, ERC721, ERC1155`` tokens sent through their respective vaults does not get refunded to the owners, And unfortunately cannot recall their tokens.


## Proof of Concept
Here we see how messages are handled, in the scenario where the receiver is suspended, only the native token is refunded
```solidity
          // Process message differently based on the target address
            if (
                _message.to == address(0) || _message.to == address(this)
                    || _message.to == signalService || addressBanned[_message.to]
            ) {
                // Handle special addresses that don't require actual invocation but
                // mark message as DONE
                refundAmount = _message.value;
                _updateMessageStatus(msgHash, Status.DONE);
            } else {
               
            }
            // Determine the refund recipient
            address refundTo =
                _message.refundTo == address(0) ? _message.destOwner : _message.refundTo;

            // Refund the processing fee
            if (msg.sender == refundTo) {
                refundTo.sendEther(_message.fee + refundAmount);
            } else {
                // If sender is another address, reward it and refund the rest
                msg.sender.sendEther(_message.fee);
                refundTo.sendEther(refundAmount);
            }
            emit MessageExecuted(msgHash);
```

The tokens are not properly refunded to the sender. And the tokens can never be recovered from the contract.
## Tools Used
Manual Review, Josephdara

## Recommended Mitigation Steps

Handle the transfers of tokens properly, such that if a user sends to a banned address, they get refunded their tokens such as the  ``ERC20, ERC721, ERC1155`` tokens.



## Assessed type

Token-Transfer