xanoutas/solidity-security-findings huggingface dataset:
FallBack Function might revert
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/main/src/ArbitrumBranchBridgeAgent.solplain=1#L112


# Vulnerability details

The _performFallbackCall function appears to be designed to send a message to the RootBridgeAgent using the lzReceive function. It includes the settlement nonce encoded as part of the message.

Overall, the function seems fine for its intended purpose, but there are a few considerations:

Gas Limit: You should be aware of the gas limit when sending messages to external contracts. If the lzReceive function consumes a significant amount of gas, it might exceed the gas limit and cause the transaction to revert. Ensure that the gas provided for this call is sufficient for the lzReceive function to execute without running out of gas.

Error Handling: It's a good practice to include error handling in case the call to lzReceive fails. You can check the return value of the external call and handle any potential errors or exceptions appropriately. For example, you can use a require statement to ensure that the external call is successful, and if it fails, revert the transaction with a meaningful error message.

function _performFallbackCall(address payable, uint32 _settlementNonce) internal override {
    // Sends message to Root Bridge Agent
    (bool success, bytes memory response) = address(rootBridgeAgentAddress).call(
        abi.encodeWithSignature("lzReceive(uint32,bytes,uint256,bytes)", rootChainId, "", 0, abi.encodePacked(bytes1(0x09), _settlementNonce))
    );
    
    require(success, "Fallback call to lzReceive failed");
    
    // You can handle the response if needed
}

In this updated version, we use the call method to invoke the lzReceive function and check the success flag to ensure the call was successful. If it fails, the function will revert with an error message.

This error handling ensures that if something goes wrong with the external call, the contract won't proceed with an invalid state. However, you may need to adapt the error handling based on the behavior of the lzReceive function and your specific use case.


## Assessed type

Other
THE RETURN BOOLEAN VALUE OF THE `excessivelySafeCall` FUNCTION IS NOT CHECKED IN THE `BranchBridgeAgent.lzReceive` FUNCTION
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L579-L583


# Vulnerability details

## Impact

The `BranchBridgeAgent.lzReceive` function is used to send `cross-chain` messages using the layer zero messaging layer. The `lzReceive` function uses the `excessivelySafeCall` library on the same contract address (`address(this)`) to ensure the layer zero cross chain call was executed successfully by returning limited `returndata`. The code snippet is shown below:


        address(this).excessivelySafeCall(
            gasleft(), 
            150,
            abi.encodeWithSelector(this.lzReceiveNonBlocking.selector, msg.sender, _srcAddress, _payload)
        );

The `excessivelySafeCall` function returns a boolean return value indicating the success of the transaction execution. But this return value is not checked in the `lzReceive` function. Hence when using the `BranchBridgeAgent.lzReceive` function to execute the cross-chain transactions, the transaction can be considered successful even though it is failed. This is because the return value of the `excessivelySafeCall` function is not checked.


## Proof of Concept

```solidity
        address(this).excessivelySafeCall(
            gasleft(),
            150,
            abi.encodeWithSelector(this.lzReceiveNonBlocking.selector, msg.sender, _srcAddress, _payload)
        );
```

https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L579-L583

## Tools Used
Manual Review and VSCode

## Recommended Mitigation Steps

Hence it is recommneded to check the `reuturn boolean value` of the `excessivelySafeCall` function when calling the `BranchBridgeAgent.lzReceive` function to execute the cross-chain transactions.


## Assessed type

Other
callOut And Bridge feature always fail with core router
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/CoreRootRouter.sol#L350-L357
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/CoreRootRouter.sol#L360-L367
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/CoreRootRouter.sol#L370-L372
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/CoreRootRouter.sol#L375-L382
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/CoreRootRouter.sol#L385-L392


# Vulnerability details

## Impact

Core root router will always revert when user put `_params` parameter while bridging.

## Proof of Concept

In CoreRootRouter contract, `executeDepositSingle`,  `executeDepositMultiple`, `executeSigned`, `executeSignedDepositSingle`, `executeSignedDepositMultiple` functions will always be reverted.

```solidity

/// @inheritdoc IRootRouter
  function executeDepositSingle(bytes memory, DepositParams memory, uint16)
      external
      payable
      override
      requiresExecutor
  {
      revert();
  }

  /// @inheritdoc IRootRouter
  function executeDepositMultiple(bytes calldata, DepositMultipleParams memory, uint16)
      external
      payable
      override
      requiresExecutor
  {
      revert();
  }

  /// @inheritdoc IRootRouter
  function executeSigned(bytes memory, address, uint16) external payable override requiresExecutor {
      revert();
  }

  /// @inheritdoc IRootRouter
  function executeSignedDepositSingle(bytes memory, DepositParams memory, address, uint16)
      external
      payable
      override
      requiresExecutor
  {
      revert();
  }

  /// @inheritdoc IRootRouter
  function executeSignedDepositMultiple(bytes memory, DepositMultipleParams memory, address, uint16)
      external
      payable
      override
      requiresExecutor
  {
      revert();
  }
```

These functions are called when bridging with `_params` parameter at BranchBridgeAgent contract. This is BranchBridgeAgent contract’s functions which get `_params` parameter.

```solidity
function callOutAndBridge(
    address payable _refundee,
@>  bytes calldata _params,
    DepositInput memory _dParams,
    GasParams calldata _gParams
) external payable override lock {
    //Cache Deposit Nonce
    uint32 _depositNonce = depositNonce;

    //Encode Data for cross-chain call.
    bytes memory payload = abi.encodePacked(
@>      bytes1(0x02), _depositNonce, _dParams.hToken, _dParams.token, _dParams.amount, _dParams.deposit, _params
    );

    //Create Deposit and Send Cross-Chain request
    _createDeposit(_depositNonce, _refundee, _dParams.hToken, _dParams.token, _dParams.amount, _dParams.deposit);

    //Perform Call
    _performCall(_refundee, payload, _gParams);
}

/// @inheritdoc IBranchBridgeAgent
function callOutAndBridgeMultiple(
    address payable _refundee,
@>  bytes calldata _params,
    Deposi
fund will struck
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/main/src/RootPort.sol#L290


# Vulnerability details





if (_deposit > 0) if (!ERC20hTokenRoot(_hToken).mint(_recipient, _deposit, _srcChainId)) revert UnableToMint();

deposit will struck in contract.


## Assessed type

Other
All hTokens are 18 decimals.
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/main/src/CoreBranchRouter.sol#L164


# Vulnerability details

     *  @dev all hTokens have 18 decimals.

but we are using ERC20htokens and all are not having 18 decimals .



## Assessed type

Decimal
USDT token transfer will not work with the current implementation
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BaseBranchRouter.sol#L160-L177


# Vulnerability details

USDT token has a protection for the approve race condition. 
It does not allow to change the spender allowance without first changing it to 0.

approve function in USDT on ethereum: https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7#code#L205

```solidity
    function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {

        // To change the approve amount you first have to reduce the addresses`
        //  allowance to zero by calling `approve(_spender, 0)` if it is not
        //  already 0 to mitigate the race condition described here:
        //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
        require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));

        allowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);
    }
```

When the protocol tries to `_transferAndApproveToken` (a token such is) **USDT** without first changing allowance of the `_localPortAddress` (the spender) to 0 will be reverted.

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BaseBranchRouter.sol#L160-L177

## Impact

USDT like tokens will not work with the current implementation and will be reverted.


## Proof of Concept

Add this POC to `BranchBridgeAgentTest.t.sol`

```solidity

// contract at the beging of the file after import
import {ERC20} from "solmate/tokens/ERC20.sol";
contract USDTLikeToken is ERC20 {
    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) ERC20(_name, _symbol, _decimals) {}

    function approve(address spender, uint256 amount) public override returns (bool) {
        require(!((amount != 0) && (allowance[msg.sender][spender] != 0)), "APPROVE PROTECTION");
        
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function mint(address to, uint256 value) public virtual {
        _mint(to, value);
    }

    function burn(address from, uint256 value) public virtual {
        _burn(from, value);
    }
}

function testApproveToZeroFirst() public {
    address user = address(0x03);
    address localPort = address(0x911);

    USDTLikeToken usdt = new USDTLikeToken("USDT like", "USDTL", 6);
    //Mint Test tokens.
    usdt.mint(user, 100);

    //Approve spend by router
    vm.startPrank(user);
    usdt.approve(address(localPort), 50);
    usdt.transfer(address(localPort), 50);

    usdt.approve(address(localPort), 10);
    vm.stopPrank();
}
```

When running the POC will get the reverit like :

```bash
Running 1 test for test/ulysses-omnichain/BranchBridgeAgentTest.t.sol:BranchBridgeAgentTest
[FAIL. Reason: APPROVE PROTECTION] testApproveToZeroFirst() (gas: 806108)
```


## Tools Used
Manual revie
`_retryDeposit` dos not work correctly
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L827-L838


# Vulnerability details

The method `_retryDeposit` is used in case we need to change some parameters (payload or gas) in a previously made deposit by providing the correct nonce.

But because `_createDeposit` in the first place has bug the method will not retransmit the correct data to layer0.

## Impact

`_retryDeposit` will retransmits the wrong `payload` for the previously created deposit.

The wrong data happens only when used with a nonce that created a non multitoken deposit.

## Proof of Concept

The code for creating the deposit has a subtle bug and the wrong data is saved in the contract storage:

The `deposit` storage object has `deposit.hTokens` rewritten by the `deposit.tokens` data.

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L827-L838

```solidity
// Cast to Dynamic
address[] memory addressArray = new address[](1);
uint256[] memory uintArray = new uint256[](1);

// Save deposit to storage
Deposit storage deposit = getDeposit[_depositNonce];
deposit.owner = _refundee;

addressArray[0] = _hToken;
deposit.hTokens = addressArray;

addressArray[0] = _token;
deposit.tokens = addressArray; // BUG: hTokens is rewritten here

```

The following POC will prove the overwrite:

```solidity
function testDepositArrayRewrite() public {

    address[] memory addressArray = new address[](1);

    address _hToken = address(0x01);
    address _token = address(0x02);

    // Save deposit to storage
    DepositTest memory deposit;

    addressArray[0] = _hToken;
    deposit.hTokens = addressArray;

    addressArray[0] = _token;
    deposit.tokens = addressArray; 

    assertEq(_hToken, deposit.hTokens[0], "_hToken overwritten");
    assertEq(_token,  deposit.tokens[0], "_token overwritten");
}
```

When run will show :

```bash
[FAIL. Reason: Assertion failed.] testDepositArrayRewrite() (gas: 18394)
Logs:
  Error: _hToken overwritten
  Error: a == b not satisfied [address]
        Left: 0x0000000000000000000000000000000000000001
       Right: 0x0000000000000000000000000000000000000002

```

## Tools Used
Manual review

## Recommended Mitigation Steps

Create new array for second token

```diff
    function _createDeposit(
        uint32 _depositNonce,
        address payable _refundee,
        address _hToken,
        address _token,
        uint256 _amount,
        uint256 _deposit
    ) internal {
        // Update Deposit Nonce
        depositNonce = _depositNonce + 1;


        // Deposit / Lock Tokens into Port
        IPort(localPortAddress).bridgeOut(msg.sender, _hToken, _token, _amount, _deposit);


        // Cast to Dynamic
        address[] memory addressArray = new address[](1);
+       address[] memory addressArray2 = new address[](1);
        uint256[] memory uintArray = new uint256[](1);


        // Save deposit to storage
Upon lzReceiveNonBlocking failure on the root, the airdropped value in the root chain is stuck and not refunded
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/c0dc3550e0754571b82d7bfd8f0282ac8fa5e42f/src/RootBridgeAgent.sol#L755-L756


# Vulnerability details

## Impact

When sending a message with deposit (i.e. callOutAndBridge) from **chain A** to **chain B**, the message will go through the root chain. If `rootBridgeAgent.lzReceiveNonBlocking` fails for any reason, the airdropped native token in **rootBridgeAgent** will be kept and not refunded to the user. The airdrop caps out at values that are not negligible. Some examples of these values:
- Ethereum: 0.24
- Avalanche: 18.47
- Polygon: 681

Check [Relayer Adapter Parameters](https://layerzero.gitbook.io/docs/evm-guides/advanced/relayer-adapter-parameters)

This airdropped value should be returned to the user since it wasn't spent at all. To undertsnad this better, the airdropped value is meant to send another cross-chain request to the destination chain (e.g. chain B). However, if `lzReceiveNonBlocking` fails in the root chain, the request is never made and the funds are stuck in the root. 

In case that the airdrop value is big enough, anyone (e.g. a stealer) is tempted to send a successful deposit to sweep this value and get it as a refund. This is because:
1. When making a cross-chain request from root chain to the destination chain. `address(this).balance` is passed. 
```solidity
        (bool success,) = bridgeAgentExecutorAddress.call{value: address(this).balance}(_calldata); // check if address(this).balance has impact . we need tto understand how LZ airdrops the rootAgentAddress 
```
[RootBridgeAgent: L755-L756](https://github.com/code-423n4/2023-09-maia/blob/c0dc3550e0754571b82d7bfd8f0282ac8fa5e42f/src/RootBridgeAgent.sol#L755-L756)

2. When the request is made, the extra gas will be refunded by LZ.
```solidity
        // assert the user has attached enough native token for this address
        require(totalNativeFee <= msg.value, "LayerZero: not enough native for fees");
        // refund if they send too much
        uint amount = msg.value.sub(totalNativeFee);
        if (amount > 0) {
            (bool success, ) = _refundAddress.call{value: amount}("");
            require(success, "LayerZero: failed to refund");
        }
```
[UltraLightNodeV2: L151-L156](https://github.com/LayerZero-Labs/LayerZero/blob/main/contracts/UltraLightNodeV2.sol#L151-L156)

So (at least) the old balance of root chain will be sent to the stealer.
This is applicable to other types of cross-chain requests.

Note: A similiar issue exists for BranchBridgeAgent as well. I didn't submit it as a separate one since I though it is gonna be merged by the judge.

## Proof of Concept

- When calling `BranchBridgeAgent.callOutAndBridge`, we pass GasParams
```solidity
    function callOutAndBridge(
        address payable _refundee,
        bytes calldata _params,
        DepositInput memory _dParams,
        GasParams calldata _gParams
    ) external payable override lock {
        //Cache Deposit Nonce
`
Minimum gas limit isn't enforced which allows an attacker to break the deposit/settlement functionality via LZ completely from any chain to any chain
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/c0dc3550e0754571b82d7bfd8f0282ac8fa5e42f/src/BranchBridgeAgent.sol#L578-L584


# Vulnerability details

## Impact

#### Context 

*Note:skip this if you have a good understanding of how LayerZero works*

**=> Adapter Parameters**

When sending a message through LayerZero, the default gas limit is 200000. However, the message sender can tell LayerZero to use a custom gas limit.

From LayerZero docs
> Abstract: Every transaction costs a certain amount of gas. Since LayerZero delivers the destination transaction when a message is sent it must pay for that destination gas. A default of 200,000 gas is priced into the call for simplicity

[LayerZero Adapter Parameters doc](https://layerzero.gitbook.io/docs/evm-guides/advanced/relayer-adapter-parameters)

**=> Delivering Messages**

LayerZero delivers the messages in the same order they were sent. In case a message delivery fails, it will block the messaging channel.

> LayerZero provides ordered delivery of messages from a given sender to a destination chain, i.e. srcUA-> dstChain. In other words, the message order nonce is shared by all dstUA on the same dstChain. That's why a STORED message blocks the message pathway from srcUA to all dstUA on the same destination chain.

[Reference from LZ docs](https://layerzero.gitbook.io/docs/faq/messaging-properties#message-ordering)

```solidity
        // block if any message blocking
        StoredPayload storage sp = storedPayload[_srcChainId][_srcAddress];
        require(sp.payloadHash == bytes32(0), "LayerZero: in message blocking");

        try ILayerZeroReceiver(_dstAddress).lzReceive{gas: _gasLimit}(_srcChainId, _srcAddress, _nonce, _payload) {
            // success, do nothing, end of the message delivery
        } catch (bytes memory reason) {
            // revert nonce if any uncaught errors/exceptions if the ua chooses the blocking mode
            storedPayload[_srcChainId][_srcAddress] = StoredPayload(uint64(_payload.length), _dstAddress, keccak256(_payload));
            emit PayloadStored(_srcChainId, _srcAddress, _dstAddress, _nonce, _payload, reason);
        }
```

[LayerZero Endpoint: L114-L124](https://github.com/LayerZero-Labs/LayerZero/blob/main/contracts/Endpoint.sol#L114-L124)

To prevent this from happening, UAs should implement a non-blocking pattern in the contract code. So that whenever `lzReceive`  method is called by LayerZero endpoint, it should always succeed. This way we guarantee messaging channel will never be blocked.

[More details on this](https://layerzero.gitbook.io/docs/evm-guides/advanced/nonblockinglzapp)


#### Implementation and Vulnerability

The non-blocking pattern in **BranchBridgeAgent** is implemented as follows: 

```solidity
    function lzReceive(uint16, bytes calldata _srcAddress, uint64, bytes calldata _payload) public override {
        address(this).excessivelySafeCall(
            gasleft(),
            150,
            abi.encodeWith
Anyone Can Block LayerZero Channel un/intentionally Due to Absents of Minimum Gas Checking
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L829
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L921
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L776


# Vulnerability details

## Description

Many function in the Maia Protocol's contracts allow users to send a cross-chain messages with specified gas parameters without any limitation. Where they eventually will be sent through these functions:

- `RootBridgeAgent._performCall`
- `RootBridgeAgent._performRetrySettlementCall`
- `BranchBridgeAgent._performCall`
  

A potential vulnerability has been identified in multiple functions that using `_perform*` functions, due to the lack of proper gas validation and enforcement. This vulnerability allows any caller to specify `GasParams` (used to build LayerZero's Relayer `adapterParams`) for the cross-chain call, including a potentially low gas value. Without proper gas validation, this could lead to scenarios where cross-chain calls fail due to insufficient gas, potentially causing denial-of-service (DoS) situations or blockage of the message pathway.

#### External Vulnerable Functions

1. `BranchBridgeAgent` contract
   - `callOutAndBridge`
   - `callOutAndBridgeMultiple` 
   - `callOutSigned` 
   - `callOutSignedAndBridge` 
   - `callOutSignedAndBridgeMultiple`
   - `retryDeposit`
   - `retrieveDeposit`
   - `retrySettlement` - 2 level cross-chain messages
    
2. `BaseBranchRouter`
   - `callOut`

3. `RootBridgeAgent` contract
   - `retrySettlement` 
   - `retrieveSettlement`
    
4. `CoreBranchRouter`
   - `addGlobalToken` - 3 level cross-chain messages
   - `addLocalToken`
    
5. `CoreRootRouter`
   - `addBranchToBridgeAgent` - 2 level cross-chain messages
   - `toggleBranchBridgeAgentFactory`
   - `removeBranchBridgeAgent`

...

Some of these functions controlled by a trusted entities but this issue could happens unintentionally, and It's important to note that multi level cross-chain calls is allowed! while this vulnerability can be occurs in very complex chain of cross-chain calls.

## Impact

In scenarios where a LayerZero cross-chain call fails due to low gas, it could potentially block the message pathway, preventing the normal flow of messages channel between two chains.

## Proof of Concept

#### Explanation:

When the gas limit specified in the [`try`](https://github.com/LayerZero-Labs/LayerZero/blob/main/contracts/Endpoint.sol#L118) block (inside LayerZero Endpoint) extremely low and cannot cover the gas requirements of the internal calls within `lzReceive`, It's important to note that while `excessivelySafeCall` can help prevent reverts due to out-of-gas conditions caused by low-level calls, it cannot overcome extremely low gas limits set at the transaction level. To ensure the successful execution of desired operatio