xanoutas/solidity-security-findings huggingface dataset:
add sendBack function to origin when  lzReceiveNonBlocking is not executed 
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L587-L701
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L434-L737


# Vulnerability details

## Impact
lzReceiveNonBlocking won't  revert to the  ILayerZeroEndpoint.send could lead to that execution on side of send is exectued and execution on side of receieve is not executed.


## Proof of Concept

## Tools Used
manual view
## Recommended Mitigation Steps
   if calls are not executed in lzReceiveNonBlocking . layerzero.send back to origin to revert things that are executed.


## Assessed type

Other
DoS in System Component lead to System Failure 
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
 

## Proof Of Concept


## Tools Used

## Recommended Mitigation Steps





## Assessed type

DoS
Virtual account lacks access control
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/VirtualAccount.sol#L85


# Vulnerability details

## Impact
The Virtual Account lacks access control on a function that allows arbitrary calls. This enables anyone to take any assets contained within the account.

## Proof of Concept
The Virtual account has the `requiresApprovedCaller` modifier to prevent use from non-approved actors:

```js
    modifier requiresApprovedCaller() {
        if (!IRootPort(localPortAddress).isRouterApproved(this, msg.sender)) {
            if (msg.sender != userAddress) {
                revert UnauthorizedCaller();
            }
        }
        _;
    }
```

However, the `payableCall` function is missing that modifier:

```js
    function payableCall(PayableCall[] calldata calls) public payable returns (bytes[] memory returnData) {
        uint256 valAccumulator;
        uint256 length = calls.length;
        returnData = new bytes[](length);
        PayableCall calldata _call;
        for (uint256 i = 0; i < length;) {
            _call = calls[i];
            uint256 val = _call.value;
            // Humanity will be a Type V Kardashev Civilization before this overflows - andreas
            // ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256
            unchecked {
                valAccumulator += val;
            }

            bool success;

            if (isContract(_call.target)) (success, returnData[i]) = _call.target.call{value: val}(_call.callData);

            if (!success) revert CallFailed();

            unchecked {
                ++i;
            }
        }

        // Finally, make sure the msg.value = SUM(call[0...i].value)
        if (msg.value != valAccumulator) revert CallFailed();
    }
```

Since this function performs arbitrary calls with arbitrary msg.values, this allows anyone to take all assets from the contract.

## Tools Used

Manual Review

## Recommended Mitigation Steps

Add the `requiresApprovedCaller` modifier to the `payableCall` function (just like all other critical functions have it)


## Assessed type

Other
All tokens can be stolen from `VirtualAccount` due to missing access modifier
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/VirtualAccount.sol#L85


# Vulnerability details

## Impact
All non-native assets (ERC20 tokens, NFTs, etc.) can be stolen by anyone from a `VirtualAccount` using its [payableCall(...)](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/VirtualAccount.sol#L84-L112) method, which lacks the necessary access control modifier [requiresApprovedCaller](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/VirtualAccount.sol#L159-L167). See also, the [call(...)](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/VirtualAccount.sol#L65-L82) method which utilizes the [requiresApprovedCaller](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/VirtualAccount.sol#L159-L167) modifier.  
Therefore, an attacker can craft a call to e.g. `ERC20.transfer(...)` on behalf of the contract, like the [withdrawERC20(...)](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/VirtualAccount.sol#L55-L58) method does, while bypassing access control by executing the call via [payableCall(...)](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/VirtualAccount.sol#L84-L112).  

As a consequence, all non-native assets of the `VirtualAccount` can be stolen by anyone causing a loss for its owner.

## Proof of Concept
Add the *code* below as new test file `test/ulysses-omnichain/VirtualAccount.t.sol` and run it using `forge test -vv --match-contract VirtualAccountTest` in order to verify the above claims.

```solidity
//SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;

import {VirtualAccount} from "@omni/VirtualAccount.sol";
import {PayableCall} from "@omni/interfaces/IVirtualAccount.sol";

import {ERC20} from "solmate/tokens/ERC20.sol";

import "./helpers/ImportHelper.sol";


contract VirtualAccountTest is Test {

    address public alice;
    address public bob;

    VirtualAccount public vAcc;

    function setUp() public {
        alice = makeAddr("Alice");
        bob = makeAddr("Bob");

        // create new VirtualAccount for user Alice and this test contract as mock local port
        vAcc = new VirtualAccount(alice, address(this));
    }

    function testWithdrawERC20_AliceSuccess() public {
        vm.prank(alice);
        vAcc.withdrawERC20(address(this), 1); // caller is authorized
    }

    function testWithdrawERC20_BobFailure() public {
        vm.prank(bob);
        vm.expectRevert();
        vAcc.withdrawERC20(address(this), 1); // caller is not authorized
    }

    function testWithdrawERC20_BobBypassSuccess() public {
        PayableCall[] memory calls = new PayableCall[](1);
        calls[0].target = address(this);
        calls[0].callData = abi.encodeCall(ERC20.transfer, (bob, 1));

        v
A Malicious user can create a `rootBridgeAgent` with a malicious endpoint and execute calls directly with the `rootBridgeAgent`.
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L423-L439


# Vulnerability details

## Impact

A Malicious user can create a `rootBridgeAgent` with a malicious endpoint and execute calls directly with the `rootBridgeAgent`. Since anyone can create a `rootBridgeAgent` with desired values for port, endpoint and router address in anychain. 

The Attacker can create a malicious endpoint that can be used to call the [rootBridgeAgent#lzReceive()](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L423C4-L431C6) , thus bypassing the checks by [requiresEndpoint](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L1204C4-L1217C6) modifier in [rootBridgeAgent#lzReceiveNonBlocking()](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L434C4-L439C78).

Thus any call can be executed from the [rootBridgeAgent#lzReceiveNonBlocking()](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L434C4-L439C78) function which includes callOutSystem, CalloutAndBridge etc..

## Proof of Concept

The  [rootBridgeAgent#lzReceive()](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L423C4-L431C6) needs to be called by the `lzEndPointAddress` , which can be achieved by creating a `rootBridgeAgent` with the malicious contract that is supposedly the `lzEndPointAddress`. Then  the `lzReceive()` function can be called directly. Thus also allows bypassing the [requiresEndpoint](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L1204C4-L1217C6) modifier:

```solidity
modifier requiresEndpoint(address _endpoint, uint16 _srcChain, bytes calldata _srcAddress) virtual {
        if (msg.sender != address(this)) revert LayerZeroUnauthorizedEndpoint();

        if (_endpoint != getBranchBridgeAgent[localChainId]) {
            if (_endpoint != lzEndpointAddress) revert LayerZeroUnauthorizedEndpoint();

            if (_srcAddress.length != 40) revert LayerZeroUnauthorizedCaller();

            if (getBranchBridgeAgent[_srcChain] != address(uint160(bytes20(_srcAddress[PARAMS_ADDRESS_SIZE:])))) {
                revert LayerZeroUnauthorizedCaller();
            }
        }
        _;
    }
```

Attack scenario is as follows:

1. Alice creates a new `rootBridgeAgent` using her malicious contract as `lzEndPointAdress`.
2. Alice creates the payload and params that is desirable for her.
3. Now she calls the `rootBridgeAgent#lzRecieve()` through her malicious contract with a function that is marked as payable to send gas for execution, since lzRecieve() is non-payable function.
4. The call executes successfully by Alice not needing to deposit any tok
Smart Contract calling callOutSignedAndBridge via BranchBridgeAgent can cause loss of fund
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Smart Contract calling callOutSignedAndBridge via BranchBridgeAgent can cause loss of fun

## Proof of Concept

One of the cross-chain request pass is that when user calling [callOutSignedAndBridge](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L276) via BranchBridgeAgent 

the payload is created

```solidity
	//Encode Data for cross-chain call.
	bytes memory payload = abi.encodePacked(
		_hasFallbackToggled ? bytes1(0x85) : bytes1(0x05),
		msg.sender,
		_depositNonce,
		_dParams.hToken,
		_dParams.token,
		_dParams.amount,
		_dParams.deposit,
		_params
	);
```

this would trigger the code on [RootBridgeAgent.sol](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L577)

```solidity
 } else if (_payload[0] & 0x7F == 0x05) {
            // Parse deposit nonce
            nonce = uint32(bytes4(_payload[PARAMS_START_SIGNED:PARAMS_TKN_START_SIGNED]));

            //Check if tx has already been executed
            if (executionState[_srcChainId][nonce] != STATUS_READY) {
                revert AlreadyExecutedTransaction();
            }

            // Get User Virtual Account
            VirtualAccount userAccount = IPort(localPortAddress).fetchVirtualAccount(
                address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED])))
            );

            // Toggle Router Virtual Account use for tx execution
            IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress);

            // Avoid stack too deep
            uint16 srcChainId = _srcChainId;

            // Try to execute remote request
            // Flag 5 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSignedWithDeposit(address(userAccount), localRouterAddress, data, _srcChainId)
            _execute(
                _payload[0] == 0x85,
                nonce,
                address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED]))),
                abi.encodeWithSelector(
                    RootBridgeAgentExecutor.executeSignedWithDeposit.selector,
                    address(userAccount),
                    localRouterAddress,
                    _payload,
                    srcChainId
                ),
                srcChainId
            );

            // Toggle Router Virtual Account use for tx execution
            IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress);
```

the msg.sender address in source chain (branch bridge agent chain) will be used to either fetch or [create a new virtual wallet](https://github.com/code-423n4/2023-09-maia/blob/f5
`BranchBridgeAgent.retrieveDeposit` doesn't check if the deposit is in FAILED state
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The `BranchBridgeAgent.retrieveDeposit` function is supposed to be called for retrieving a failed deposit in order to get funds back to branch chain, but the function doesn't check that the deposit being used is really in FAILED state, so a malicious user can call the function to retrieve a successful deposit, this will allow him to get funds on both the branche and root chains.

## Proof of Concept

The issue occurs in the `BranchBridgeAgent.retrieveDeposit` function below :

```solidity
function retrieveDeposit(
    uint32 _depositNonce,
    GasParams calldata _gParams
) external payable override lock {
    // Check if the deposit belongs to the message sender
    if (getDeposit[_depositNonce].owner != msg.sender)
        revert NotDepositOwner();
    //@audit not checking deposit status is FAILED

    //Encode Data for cross-chain call.
    bytes memory payload = abi.encodePacked(
        bytes1(0x08),
        msg.sender,
        _depositNonce
    );

    //Update State and Perform Call
    _performCall(payable(msg.sender), payload, _gParams);
}
```

As you can see in the function doesn't at all check the deposit status (if it's FAILED or SUCCESS), even though the function comments say that it should only be called for Failed deposit :

```solidity
 /**
 * @notice External function to request tokens back to branch chain after failing omnichain environment interaction.
 *    @param depositNonce Identifier for user deposit to retrieve.
 *    @param gasParams gas parameters for the cross-chain call.
 *    @dev DEPOSIT ID: 8
 *
 */
function retrieveDeposit(uint32 depositNonce, GasParams calldata gasParams) external payable;
```

Because there is no check on the deposit state a malicious user can creates a deposit (for example using `callOutAndBridge`) and after the deposit has succeeded and the assets are transferred to the user on  to the root chain, he will be able to call the `retrieveDeposit` function to change the deposit state to `FAILED`.

After this the malicious user can now call the `redeemDeposit` function which is callable when a deposit has failed, and this function will give his back his assets on the branche chain. In the end the user will have his assets back on the branche chain and will get new assets in the Root chain.

## Tools Used

Manual review

## Recommended Mitigation Steps

The `BranchBridgeAgent.retrieveDeposit` function should check that the deposit status is FAILED : 

```solidity
function retrieveDeposit(
    uint32 _depositNonce,
    GasParams calldata _gParams
) external payable override lock {
    // Check if the deposit belongs to the message sender
    if (getDeposit[_depositNonce].owner != msg.sender)
        revert NotDepositOwner();
    //@audit Check if deposit has failed
    if (getDeposit[_depositNonce].status != == STATUS_FAILED)
        revert();

    //Encode Da
`RootBridgeAgent.retrieveSettlement` doesn't check if settlement is in FAILED state
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L274-L296


# Vulnerability details

## Impact

The `RootBridgeAgent.retrieveSettlement` function is supposed to be used to retrieve a failed settlement to be able to redeem it but because the function doesn't actually check that the input settlement is in fact in FAILED state, the function can be used to retrieve a successful settlement making it redeemable again which will allow the user to get more funds on the branche and root chain.

## Proof of Concept

The issue occurs in the `RootBridgeAgent.retrieveSettlement` function below :

```solidity
function retrieveSettlement(
    uint32 _settlementNonce,
    GasParams calldata _gParams
) external payable lock {
    //Get settlement storage reference
    Settlement storage settlementReference = getSettlement[
        _settlementNonce
    ];

    // Get Settlement owner.
    address settlementOwner = settlementReference.owner;

    // Check if Settlement is Retrieve.
    if (settlementOwner == address(0))
        revert SettlementRetrieveUnavailable();

    // Check Settlement Owner
    if (msg.sender != settlementOwner) {
        if (
            msg.sender !=
            address(IPort(localPortAddress).getUserAccount(settlementOwner))
        ) {
            revert NotSettlementOwner();
        }
    }
    
    //@audit not checking settlement state is FAILED

    //Encode Data for cross-chain call.
    bytes memory payload = abi.encodePacked(
        bytes1(0x03),
        settlementOwner,
        _settlementNonce
    );

    //Retrieve Deposit
    _performCall(
        settlementReference.dstChainId,
        payable(settlementOwner),
        payload,
        _gParams
    );
}
```

As you can see in the function there is no check on the settlement state even though it is stated in the function comments that it should only be used for Failed settlements :


```solidity
/**
 * @notice Function that allows retrieval of failed Settlement's foricng fallback to be triggered.
 *   @param _settlementNonce Identifier for token settlement.
 *   @param _gParams Gas Parameters for cross-chain message.
 *
 */
function retrieveSettlement(uint32 _settlementNonce, GasParams calldata _gParams) external payable;
```

Because this check is missing after a user creates a successful settlement (using `callOutAndBridge` or `callOutAndBridgeMultiple`) and moved the assets to the root chain, he will be able to call the `retrieveSettlement` function which will not revert (even if his settlement in `SUCCESS` state) and will change the settlement state to `FAILED`.

When a settlement is in a `FAILED` state, the `redeemSettlement` function can be called to get the asset back on the branche chain: 

```solidity
function redeemSettlement(uint32 _settlementNonce) external override lock {
    // Get setttlement storage reference
    Settlement storage settlement = getSettlement[_settlementNonce];

    // Get deposit owner.
    address
Anyone can call replenishReserves(...) (token version) to repay borrowed reserves with reserves
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchPort.sol#L167


# Vulnerability details

## Impact
The ```replenishReserves(token, ...)``` can be called by anyone.

## Proof of Concept
The NATSPEC comment in the ```IBranchPort``` states the ```replenishReserves(token, ...)``` can only be called by the port strategy itself as shown below

```solidity
    /**
     * @notice allow approved address to repay borrowed reserves with reserves
     *     @param _amount uint
     *     @param _token address
     *     @dev must be called by the port strategy itself
     */
    function replenishReserves(address _token, uint256 _amount) external;
```

but in the [implementation](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchPort.sol#L167-L185), there is no access control in place to ensure that only the port strategy can call it.


## Tools Used
Manual review.


## Recommended Mitigation Steps
Consider adding a modifier to ensure only strategy can call the function.



## Assessed type

Access Control
User can selectively turn on the fallback flag to take all ETH on the agent contract as layerzero fee refund
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L788
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L938
https://github.com/LayerZero-Labs/LayerZero/blob/48c21c3921931798184367fc02d3a8132b041942/contracts/Endpoint.sol#L95


# Vulnerability details

## Impact

_performFallbackCall can revert sliently when refundee is not capable of taking ETH refund from layerzero side

## Proof of Concept

In RootBridgeAgent.sol when the has fall back toggle flag is on, the smart contract aim to perform a fallback call to notify the dest chain the failure to deliver the message

for example, the relevent code is [here](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L788)

```solidity
//Update tx state if execution failed
if (!success) {
	//Read the fallback flag.
	if (_hasFallbackToggled) {
		// Update tx state as retrieve only
		executionState[_srcChainId][_depositNonce] = STATUS_RETRIEVE;
		// Perform the fallback call
		_performFallbackCall(payable(_refundee), _depositNonce, _srcChainId);
	} else {
		// No fallback is requested revert allowing for retry.
		revert ExecutionFailure();
	}
}
```

and [here](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L712) when retrieve the deposit 

```solidity
	//Check if deposit is in retrieve mode
	if (executionState[_srcChainId][nonce] == STATUS_DONE) {
		revert AlreadyExecutedTransaction();
	} else {
		//Set settlement to retrieve mode, if not already set.
		if (executionState[_srcChainId][nonce] == STATUS_READY) {
			executionState[_srcChainId][nonce] = STATUS_RETRIEVE;
		}
		//Trigger fallback/Retry failed fallback
		_performFallbackCall(
			payable(address(uint160(bytes20(_payload[PARAMS_START:PARAMS_START_SIGNED])))), nonce, _srcChainId
		);
	}
```

the logicof the [_performFallbackCall](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L938) is here

```solidity
function _performFallbackCall(address payable _refundee, uint32 _depositNonce, uint16 _dstChainId) internal {
	//Sends message to LayerZero messaging layer
	ILayerZeroEndpoint(lzEndpointAddress).send{value: address(this).balance}(
		_dstChainId,
		getBranchBridgeAgentPath[_dstChainId],
		abi.encodePacked(bytes1(0x04), _depositNonce),
		payable(_refundee),
		address(0),
		""
	);
}
```

the code forward all ETH (address(this).balance) and aim to use the ETH to pay for the layerzero fee

after the message is [sent via the endpoint](https://github.com/LayerZero-Labs/LayerZero/blob/48c21c3921931798184367fc02d3a8132b041942/contracts/Endpoint.sol#L95), the layerzero endpoint foward the message to UltraLightNodeV2

the code quote the fee and refund the excessive fee to the refundee address

but if the refundee address is capable o