xanoutas/solidity-security-findings huggingface dataset:
Mismatch of decimals for some underlying tokens across chains could cause a huge loss of funds
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The issue is, some ERC20 underlying tokens could have different decimals on other chains. For example, USDC in BNB chain has 18 decimals where it has 6 decimals in other chains such as Ethereum, Polygon ...etc. The root chain doesn't account for decimals changes across chains. An attacker could bridge a small amount of an underlying token (e.g. USDC) and claim it on the destination chain way much bigger. For example, in the case of USDC, 100 USDC deposited on BNB will turn into 100_000_000_000_000 USDC because of the big difference in decimals. This also impacts Innocent users who deposit the other way around (e.g. from Polygon to BNB), 100 USDC will turn into 0.00000000010000000 causing loss of funds to the user.

Note: according to the sponsor, there is not official list yet for chains that will be used since it has to pass governance first but some alt L1s and major L2s are expected to be used. So it is a variable list of chains and BNB (or even other not very popular chains) could possibly be included.

## Proof of Concept

In Maia, hToken is a mirror of its underlying token. hTokens has the same decimals as their underlying tokens. When a deposit from **chain A** to  **chain B** occurs, the same amount (hToken amount) and deposit (underlying token amount) are passed. **Chain B** assumes that the decimals on **chain A** are the same on **chain B** for the same underlying token, so it proceeds accordingly which open the vulnerability above for some ERC20 tokens.

Examples of underlying tokens:

### USDC
- **on Polygon: 6 decimals**
https://polygonscan.com/token/0x2791bca1f2de4661ed88a30c99a7a9449aa84174

- **on BNB: 18 decimals**
https://bscscan.com/token/0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d

### USDT
- **on Polygon: 6 decimals**
https://polygonscan.com/token/0xc2132d05d31c914a87c6611c10748aeb04b58e8f

- **on BNB: 18 decimals**
https://bscscan.com/token/0x55d398326f99059ff775485246999027b3197955#readContract

### WBTC

- **on Optimism:  8 decimals**
https://optimistic.etherscan.io/token/0x68f180fcce6836688e9084f035309e29bf0a2095

- **on Conflux:  18 decimals**
https://evm.confluxscan.io/token/0x1f545487c62e5acfea45dcadd9c627361d1616d8


## Tools Used
Manual analysis

## Recommended Mitigation Steps

Since all bridging should go through the root chain, the following approach should resolve the issue:

- On the root chain, convert the decimals to 18
- On the branch chain, convert the 18 decimals (that comes from the root) to the decimals of the underlying token.

Do the same for hTokens and underlying tokens since hToken decimals = underlying tokens decimals on the same chain.




## Assessed type

Decimal
code written in MulticallRootRouter.sol is wrong .
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

## Tools Used

## Recommended Mitigation Steps

 0x01         | multicallNoOutput
 *         0x02         | multicallSingleOutput
 *         0x03         | multicallMultipleOutput
 *         0x04         | multicallSignedNoOutput
 *         0x05         | multicallSignedSingleOutput
 *         0x06         | multicallSignedMultipleOutput
there is no implementation for function id 0*04 to 0*06 in the multicall.

https://github.com/code-423n4/2023-09-maia/blob/main/src/MulticallRootRouter.sol#L401
Line 401 to line 477 is repeated.


## Assessed type

Other
Lack of Access Control in public function `VirtualAccount.payableCall`
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description

The `VirtualAccount` contract allows users to manage assets and perform interactions remotely while maintaining an encapsulated user balance. The contract includes a `payableCall` function that aggregates and executes a batch of calls to external contracts. Unfortunately, this function lacks proper access control checks, allowing any external user or contract to invoke it without authorization. However, it's important to note that the function does enforce proper validation of `msg.value` to prevent unauthorized Ether withdrawals.

The lack of access control in the `payableCall` function of the `VirtualAccount` contract poses a high-security risk, as it allows unauthorized users to perform actions that may result in financial losses and unintended behavior. To address this vulnerability, it is crucial to implement proper access control checks to ensure that only authorized entities can use the function. Additionally, applying the existing `requiresApprovedCaller` modifier to `payableCall` would provide a consistent access control mechanism across all contract functions.

## Impact

This vulnerability poses a significant security risk as it allows unauthorized users to call the `payableCall` function, potentially resulting in the following adverse consequences:

1. **Unauthorized Token Withdrawals**: Attackers can exploit the lack of access control to call `payableCall` and withdraw ERC20 tokens or ERC721 tokens from the `VirtualAccount` without proper authorization.
  
2. **Potential Financial Loss**: If the function performs actions that involve transferring tokens to external contracts, an unauthorized caller could drain the contract of assets, leading to a financial loss for the `VirtualAccount` and its users.
  
3. **Attacks Over Cross-Chain**: Some of contracts trusted `VirtualAccount` to preform some cross-chain procedures like this [validation in `RootBridgeAgent`](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L312), where it can be used to extend some different attacks that would potentiality leads to a financial loss on other chains.
  

## Proof of Concept

Below is a simplified demonstrating how an unauthorized user can exploit the lack of access control in the `payableCall` function to withdraw ERC20 tokens from the `VirtualAccount`:

```solidity
// Contract representing an unauthorized user
contract UnauthorizedUser {
    function exploitPayableCall(IVirtualAccount virtualAccount, address token, uint256 amount) external {
        // Define a malicious PayableCall
        IVirtualAccount.PayableCall[] memory maliciousCall = new IVirtualAccount.PayableCall[](1);
        maliciousCall[0] = IVirtualAccount.PayableCall({
            target: address(token),
            callData: abi.encodeWithSig
Lack of access control can lead to loss of funds for users
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
All the non-view and non-pure functions in the VirtualAccount contract have the requiresApprovedCaller modifier, except the payableCall function. This would allow anyone to make arbitrary calls and steal the user's funds.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Add the requiresApprovedCaller modifier to the payableCall function as well.


## Assessed type

Access Control
Relies on address(this).balance for value - this could be manipulated by an attacker via selfdestruct() or other means to make it appear the contract has more balance than it does.
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
This could result in the contract sending out more ETH than it has, failing transactions, or other unexpected behavior.

## Proof of Concept
         function _performCall(address payable _refundee, bytes memory _payload, GasParams calldata _gParams)
        internal
        virtual
    {
        //Sends message to LayerZero messaging layer 
        ILayerZeroEndpoint(lzEndpointAddress).send{value: address(this).balance}(
            ...
        );

**The contract assumes it has a certain ETH balance based on address(this).balance.**
1. An attacker deploys another contract and sends ETH to it.
2. The attacker calls selfdestruct(contractAddress) from their contract, sending its ETH balance to the target contract.
3. Now address(this).balance on the target contract is higher than it should be based on actual deposits.
4. The contract sees the inflated balance and behaves as if it has more ETH than it really does.
This could result in the contract sending out more ETH than it has, failing transactions, or other unexpected behavior.

The key vulnerabilities are:

- address(this).balance can be manipulated independently of the contract's actual deposits.
- Selfdestruct sends ETH to any address, bypassing the contract logic.
## Tools Used
Manual
## Recommended Mitigation Steps
**the contract should keep its own record of the ETH balance it controls, and use that instead of relying on address(this).balance. For example:**

    uint internal controlledBalance;

    function deposit() external payable {
     controlledBalance += msg.value;
    }

    function _performCall() internal {
    ILayerZeroEndpoint(lzEndpointAddress).send{value: controlledBalance}(...);
    }
**Now the contract has its own "controlledBalance" separate from address(this).balance that it knows is safe to rely on. This prevents the manipulation of address(this).balance from impacting the contract's logic.**
 


## Assessed type

Other
Creation of a malicious root router allows hijacking of virtual wallets tokens and funds
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
This vulnerability leads to hijacking of a virtual account's all ERC20 tokens and all ether inside of it.

If an attacker creates a root bridge agent with a malicious router and a branch bridge agent, and a user with a virtual account tries to deposit / settle funds or execute a dApp omnichain call through the attacker's created branch bridge agent, the attacker will be able to hijack the all of the virtual account's ERC20 tokens and ether stored inside of the contract

## The vulnerability
This vulnerability exists because a root bridge agent trusts it's core router and allows it to access a virtual account's funds by executing the function `toggleVirtualAccountApproved()` on the RootPort. Check the code starting from line https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L554C37-L554C65.

That means if an attacker creates a root bridge agent with a malicious root router and creates a branch bridge agent for the user to interact with as an entry-point, if the user sends a "signed" request whether to execute a dApp omnichain call or deposit funds or settle funds (move funds from root to branch) through the attacker's branch bridge agent or branch router. The attacker-created root bridge agent will trigger the function `toggleVirtualAccountApproved()` on the RootPort and allow the attacker-created malicious root router to control and steal all of the virtual account funds (ERC20/721/1155[tokens] or native ether).

## Proof of Concept Code Explanation

I created a PoC code which you will find down below, here I will be explaining it:

--------- Initial Attack Setup ---------
1. First the attacker creates the malicious multicall root router contract (you'll find it down below, It's similar to the code of `MulticallRootRouter` but with modified code that allows theft of user funds).
2. Then the attacker will create a new root bridge agent through the factory and couple it with the newly-created malicious root multicall router
3. The attacker will then deploy a normal branch bridge agent and a normal core branch router and sync them with the attacker-created root bridge agent

--------- User Simulation ---------
1. A user virtual account is created and is given 10,000 ether, so now there will be 10,000 ether in the user's virtual account.
2. User tries to send a normal omnichain dApp call through the branch bridge agent using the function `callOutSigned` in `BranchBridgeAgent`.
3. User gets his 10,000 ether stolen from his wallet and sent to the attacker's address

## PoC Code
1. First, we will add the malicious root multicall router file. In the "src" folder, add the file "MaliciousMulticallRouter.sol" with the following code:
```
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Ownable} from "solady/aut
Unrestricted Access to payableCall Function in VirtualAccount Contract
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The unrestricted access to the payableCall function can result in unauthorized transactions, leading to the loss of funds in the Virtual Account. Additionally, since the Virtual Account is designed to be integrated with various dApps, this vulnerability could also lead to the loss of assets in those dApps.

## Proof of Concept

The `payableCall` function in the `VirtualAccount` contract allows for multiple calls to be made to other contracts. The function is publicly accessible and does not have any restrictions on who can call it.

    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();
    }

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

The VirtualAccount as a Omnichain Wallet, it stores assets of users. A malicious user can call the `payableCall` function to drain the funds of the VirtualAccount. 
The VirtualAccount is used to intergated in different dAPP. It means that anyone can interact with the dApp as the VirtualAccount and drain the funds related to the Virtual Account.

## Tools Used

Manual

## Recommended Mitigation Steps

Add `requiresApprovedCaller` modifier to restrict the caller of `payableCall` function.

```diff
- function payableCall(PayableCall[] calldata calls) public payable returns (bytes[] memory returnData) {
+ function payableCall(PayableCall[] calldata calls) public payable requiresApprovedCaller returns (bytes[] memory returnData) {
```


## Assessed type

Access Control
No access control check in payableCall()
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The virtualAccount.sol handles three main things which are the ERC20, ERC721 and ERC1155 tokens and these are tokens of different kinds. In the payableCall(), it takes in a PayableCall[] in its parameter, and this PayableCall[] can be constructed in such a way that can lead to the loss of these tokens in the contract and this is because there is no access control check in the function and can thereby make anybody call the function and execute any input of their choice leading to loss of funds.

## Proof of Concept
```solidity
 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();
    }
```
```solidity
 struct PayableCall {
    address target;
    bytes callData;
    uint256 value;
  }
```
As seen above, PayableCall struct has a target address as well as a callData that can be manipulated. For example, the target address of any ERC20 token held in the virtual account can be passed in as the address with a maliciously crafted callData to either transfer or approve the tokens to the hacker's address.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Add the "requiresApprovedCaller" modifier to the function payableCall to ensure that a valid check is done.


## Assessed type

Invalid Validation
Doesn't check for partial redemptions. Could allow duplicate redemptions
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
This could allow an attacker to drain extra funds.

## Proof of Concept

**The main vulnerability lies in the redeemDeposit() function. This function allows the deposit owner to redeem/withdraw a failed deposit.**

**It loops through the deposit's token addresses, amounts and withdrawals, clears the balances from the Port contract, and then deletes the deposit info.**


    function redeemDeposit(uint32 _depositNonce) external override lock {

    Deposit storage deposit = getDeposit[_depositNonce];

    // Loop through tokens
    for (uint256 i = 0; i < deposit.tokens.length;) {
    
      // Clear balances 
      _clearToken(
      msg.sender, 
      deposit.hTokens[i],
      deposit.tokens[i],
      deposit.amounts[i],
      deposit.deposits[i]
      );

      // Increment counter
      unchecked {++i;}
      }

      // Delete deposit info
      delete getDeposit[_depositNonce];

      }

**The redeemDeposit() function allows the owner of a deposit to redeem/withdraw it if the deposit status is STATUS_FAILED. It loops through the deposit's tokens and amounts, clears the balances from the Port contract for each one, and deletes the deposit info.**

**However, it does not check if a partial redemption has already occurred. For example:**

**- Deposit is created with 2 tokens, 10 of token A and 20 of token B.**
**- Deposit fails, status set to STATUS_FAILED.**
**- Attacker calls redeemDeposit(), redeems 10 of token A.**
**- Deposit info still exists with 20 of token B remaining.**
**- Attacker can call redeemDeposit() again to redeem the 20 of token B.**

**So the attacker was able to redeem the deposit twice for a total of 30 tokens, instead of the original 20.**

## Tools Used
Manual
## Recommended Mitigation Steps 
**1. Tracking amount redeemed for each deposit:**


      struct Deposit {
      // ...
     uint256 amountRedeemed; 
     }

     function redeemDeposit(uint32 _depositNonce) external {
     Deposit storage deposit = getDeposit[_depositNonce];

      require(deposit.amountRedeemed == 0, "Deposit already redeemed");
  
     // Redeem tokens

     deposit.amountRedeemed = deposit.totalAmount;
    }

**2. Deleting deposit info after first successful redemption:**


    function redeemDeposit(uint32 _depositNonce) external {
    Deposit storage deposit = getDeposit[_depositNonce];

    // Redeem tokens

    delete getDeposit[_depositNonce];
    }



## Assessed type

Other
an attacker can take control of `VirtualAccount` (can steal assets in `VirtualAccount`
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
an attacker can steal all assets in `VirtualAccount`. Every functions in `VirtualAccount` have `requiresApprovedCaller` 
except `function payableCall(PayableCall[] calldata calls) public payable returns (bytes[] memory returnData)` ref: https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L85,
so the attacker can call any contracts with any calldatas. (eg. transfer NFT, transfer ERC20 tokens)

## Proof of Concept
according to this line: https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L101
attacker can send 
- `_call.target` = USDC
- `_call.callData` = abi.encodeWithSelector(IERC20.transfer.selector, <attacker address>, <amount>)


## Tools Used
- manual review
## Recommended Mitigation Steps
- add `requiresApprovedCaller` modifier


## Assessed type

Access Control