xanoutas/solidity-security-findings huggingface dataset:
Missing Access Control check in `BranchPort::ReplenishReserves`
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Anyone can call the `replenishReserves()` function to force strategy contracts to pay their debt.

## Proof of Concept
Users can deposit funds in the branch chains and deposited funds are stored in the `BranchPort`'s. These funds can sit in the `BranchPort` without earning any interest/yield, or it can be utilized by `PortStrategy` contracts to gain income.

Tokens have to be added as `strategyToken` to be used by `PortStrategy` contracts. When a `PortStrategy` contract takes these funds to use for a strategy, a debt is created, which is called managing funds. `PortStrategy` contracts have a daily limit for managing, and they should pay their debts when necessary.

There are two different debt payment functions in the `BranchPort.sol`

* [replenishReserves](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchPort.sol#L167)(address \_token, uint256 \_amount)
    
* [replenishReserves](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchPort.sol#L188)(address \_strategy, address \_token)
    

The first one takes an `address` and a `uint256` parameter. This function is expected to be called by the `PortStrategy` contract itself, and the branch port withdraws funds from the `msg.sender`  
[https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchPort.sol#L178](https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchPort.sol#L178)

```solidity
    function replenishReserves(address _token, uint256 _amount) external override lock {
        // Update Port Strategy Token Debt. Will underflow if not enough debt to repay.
        getPortStrategyTokenDebt[msg.sender][_token] -= _amount;

        // Update Strategy Token Global Debt. Will underflow if not enough debt to repay.
        getStrategyTokenDebt[_token] -= _amount;

        // Get current balance of _token
        uint256 currBalance = ERC20(_token).balanceOf(address(this));

        // Withdraw tokens from startegy
-->     IPortStrategy(msg.sender).withdraw(address(this), _token, _amount); //@audit the amount of debt is withdrawn from the msg.sender strategy contract.

        // Check if _token balance has increased by _amount
        require(ERC20(_token).balanceOf(address(this)) - currBalance == _amount, "Port Strategy Withdraw Failed");

        // Emit DebtRepaid event
        emit DebtRepaid(msg.sender, _token, _amount);
    }
```

The second `replenishReserves()` function takes two `address` parameters. The first parameter is the `PortStrategy` contract which will pay the debt, and the second one is the debt token address. This function is
Airdropped native gas can get stuck on RootBridgeAgentExecutor, CoreRootRouter and MulticallRootRouter
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgentExecutor.sol#L50
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgentExecutor.sol#L66
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgentExecutor.sol#L82
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgentExecutor.sol#L115
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgentExecutor.sol#L150
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgentExecutor.sol#L167
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgentExecutor.sol#L201


# Vulnerability details

## Impact
When native gas tokens are airdropped to a `RootBridgeAgent` on the root chain, they are forwarded to its `RootBridgeAgentExecutor` which then forwards them to the corresponding router if needed.

If those funds are not used by neither the executor nor the router, they will be stuck and unrecoverable.

This can happen when specifying a native airdrop value in these scenarios for example :
* Calling `CoreBranchRouter@addLocalToken`
* Calling `BranchBridgeAgent@callOutAndBridge` without `_params`
* Calling `BranchBridgeAgent@callOutSignedAndBridge` without `_params`
* Calling a multicall `BranchBridgeAgent@callOutSignedAndBridge` with `_params` but no output

In general :
* `RootBridgeAgentExecutor@executeSystemRequest` doesn't forward value to the router and even if it did, it won't be used
* A multicall `RootBridgeAgentExecutor@execute` and `RootBridgeAgentExecutor@executeSigned` will forward the value but it won't be used in the case of a `multicallNoOutput`
* `RootBridgeAgentExecutor@executeWithDeposit`, `RootBridgeAgentExecutor@executeWithDepositMultiple`, `RootBridgeAgentExecutor@executeSignedWithDeposit` and `RootBridgeAgentExecutor@executeSignedWithDepositMultiple` only forward value if there is a payload
* A multicall `RootBridgeAgentExecutor@executeWithDeposit`, `RootBridgeAgentExecutor@executeWithDepositMultiple`, `RootBridgeAgentExecutor@executeSignedWithDeposit` and `RootBridgeAgentExecutor@executeSignedWithDepositMultiple` with a payload will forward the value but it won't be used in the case of a `multicallNoOutput`

## Proof of Concept
### Tests
```solidity
    function testValueLockedInExecutorsOrRouters() public {
        testAddLocalToken();

        address executor = coreRootBridgeAgent.bridgeAgentExecutorAddress();
        uint256 previousExecutorBalance = executor.balance;

        assertEq(previousExecutorBalance, 0);
        assertEq(address(coreRootRouter).balance, 0);
        assertEq(address(rootMulticallRouter).balance, 0);

        switchToLzChain(avaxChainId);

        vm.deal(owner, 100 ether);
All cross-chain calls of the ````Ulysses```` protocol would fail
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The ````_requiresEndpoint()```` function of ````BranchBridgeAgent```` contract and the ````requiresEndpoint```` modifier of  ````RootBridgeAgent```` contract don't decode remote caller correctly , which would cause all cross-chain calls of the Ulysses protocol to fail inevitably.


## Proof of Concept
Below is the call stack of ````LayerZero````  relayers submitting cross-chain call to a target chain:
```solidity
Relayer -> UltraLightNodeV2 -> Endpoint -> APP
````
we can verify the above call  procedure by the following case, which is sent by the ````Stargate```` APP from ````Arbitrum````  to ````Ethereum```` via ````LayerZero````:
https://etherscan.io/tx/0x89ad01a750b701f40106089b24730c9c9cc48860b9ba3c9f8fb7b1619a5d0685

please pay attention on the ````PacketReceived```` event ([link](https://etherscan.io/tx/0x89ad01a750b701f40106089b24730c9c9cc48860b9ba3c9f8fb7b1619a5d0685#eventlog)), emitted by ````UltraLightNodeV2```` ([0x4D73AdB72bC3DD368966edD0f0b2148401A178E2](https://etherscan.io/address/0x4d73adb72bc3dd368966edd0f0b2148401a178e2)), contains cross-chain ````path```` information:
```solidity
srcChainId = 110
dstAddress = 0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97
srcAddress = 0x352d8275AAE3e0c2404d9f68f6cEE084B5bEB3DD
```

let's dive deeper on the call between ````Endpoint````([0x66A71Dcef29A0fFBDBE3c6a460a3B5BC225Cd675](https://etherscan.io/address/0x66a71dcef29a0ffbdbe3c6a460a3b5bc225cd675)) and APP([Stargate Bridge](https://etherscan.io/address/0x296f55f8fb28e498b858d0bcda06d955b2cb3f97))
https://dashboard.tenderly.co/tx/mainnet/0x89ad01a750b701f40106089b24730c9c9cc48860b9ba3c9f8fb7b1619a5d0685?trace=0.2.0.3.0

we can get
```solidity
(Endpoint => Bridge) .lzReceive(_srcChainId = 110, _srcAddress = 0x352d8275aae3e0c2404d9f68f6cee084b5beb3dd296f55f8fb28e498b858d0bcda06d955b2cb3f97, _nonce = 150418, _payload = 0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000630f7f51707b8e6a7d000000000000000000000000000000000000000000000000011b9a6b767acd0700000000000000000000000000000000000000000000000000007114355c1f0400000000000000000000000000000000000000000000000000002c13d23113f5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c13d23113f5000000000000000000000000000000000000000000000000011c63a7503913f500000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000
lzReceive() is revertable and we lack forceResumeReceive implementation
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
By calculating gas usage and modifying _gParams.gasLimit, attacker can make lzReceive() revert with out-of-gas. Since we don't use LayerZero's last-resort interface to force resume message delivery, the blocking message can lead to DoS.

## Proof of Concept

According to layerzero's Doc: dstUA is expected to gracefully handle all errors/exceptions when receiving a message, and **any uncaught errors/exceptions (including out-of-gas) will cause the message to transition into STORED**. A STORED message will block the delivery of any future message from srcUA to all dstUA on the same destination chain and can be retried until the message becomes SUCCESS. dstUA should implement a handler to transition the stored message from STORED to SUCCESS. If a bug in dstUA contract results in an unrecoverable error/exception, LayerZero provides a last-resort interface to force resume message delivery, only by the dstUA contract.

However, in our implementation, the lzReceive() is revertable and we lack forceResumeReceive implementation:

1. The caller controls the callout*'s GasParams (_gParams.gasLimit, _gParams.remoteBranchExecutionGas), so the caller can decide how much gas is used when lzReceive() is called.

2. address(this).excessivelySafeCall() is an internal call that includes some instructions before and after the call instruction. **The root cause is that excessivelySafeCall is to prevent reversion in the external call, but itself can revert for out-of-gas since the sender controls gas**. For example, the out-of-gas revert may happen after the out-of-gas in the external call return (1/64 gas is not enough):

```solidity
    function excessivelySafeCall(
        address _target,
        uint256 _gas,
        uint16 _maxCopy,
        bytes memory _calldata
    ) internal returns (bool, bytes memory) {
        // set up for assembly call
        uint256 _toCopy;
        bool _success;
        bytes memory _returnData = new bytes(_maxCopy);
        // dispatch message to recipient
        // by assembly calling "handle" function
        // we call via assembly to avoid memcopying a very large returndata
        // returned by a malicious contract
        assembly {
            _success := call( // <---- out-of-gas, not revert
            _gas, // gas
            _target, // recipient
            0, // ether value
            add(_calldata, 0x20), // inloc
            mload(_calldata), // inlen
            0, // outloc
            0 // outlen
            )
        // limit our copy to 256 bytes
            _toCopy := returndatasize()
            if gt(_toCopy, _maxCopy) {
                _toCopy := _maxCopy
            }
        // Store the length of the copied bytes
            mstore(_returnData, _toCopy)
        // copy the bytes from returndata[0:_toCopy]
            returndatacopy(add(_returnData, 0x20), 0, _toCopy)
Airdropped tokens are stuck in the Bridge Agent contracts
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

Some of the flows inside the protocol require airdropping gas tokens to the remote chain to execute a callback message. In case the callback message fails to execute the airdropped amount is left sitting in the `BranchBridge`/`RootBridge` contracts.
There is a high likelihood of this occurring quite often because it takes (1-5 min. or more) for a Relayer to deliver a message to the remote chain during which the airdropped amount might not be sufficient to execute the callback message due to price fluctuations, or something else can revert.
In these cases the user will lose all the airdropped amount.

## Proof of Concept

One of the examples of needing a callback message is the `retrySettlement` through `BranchBridgeAgent`. In this flow the messages travels from Branch -> Root -> Branch. 
Let me illustrate it starting from a branch chain.

**Polygon**
The transaction starts from [`retrySettlement`](https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L464) function inside the BranchBridgeAgent whereby the airdropped amount covers [Root -> Branch message](https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L471).
The address to airdrop the tokens is specified in [adapterParams V2 as the receiver](https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L776), and is the RootBridgeAgent address.
The message is sent towards the RootChain (Arbitrum).

**Arbitrum**
1. The message is received and processed inside the `RootBridgeAgent:lzReceive(...)`.
2. In cases where [`dstChainId != localChainId`](https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L913) another message is sent to specified branch.  
3. If the airdropped amount is not enough to cover the cost of [sending the message](https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L915-L922) everything reverts.
4. The airdropped amount is left sitting in the `RootBridge` contract. 

One can argue that the user should always double or triple the airdropped amount to cover the price fluctuations, 
but this is also not viable since he would then be losing this whole airdropped amount in case a revert happens somewhere else.

## Tools Used
- Manual review
- Foundry

## Recommended Mitigation Steps

The most elegant solution would be to transfer any remaining airdropped amount back to the sender after the `lzReceiveNonBlocking` has finished executing.

```solidity
 /// @inheritdoc ILayerZeroReceiver
    function lzReceive(uint16 _srcChainId, bytes calldata _srcAddress, uint64, bytes calldata _payload) public {
        (bool success,) = address(this).excessivelySafeCall(
            gasleft(),
            150,
            abi.encodeWithSelector(this.lzReceiveNonBlocking.selector, msg.sender, _srcChainId, _srcAddress, _payload)
        );
pess-dubious-typecast
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgentExecutor.sol#L88-L94
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgentExecutor.sol#L173-L179
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgentExecutor.sol#L268-L348


# Vulnerability details

## Impact

An attacker who has identified that this security vulnerability exists in a vulnerable smart contract can use the exploit script provided below to trigger a change in the smart contract state causing an integer overflow to occur thus causing loss of assets to the affected.


## Proof of Concept
The PoC included here is the exploit with which to use against vulnerabilities in the code base as all the vulnerable lines of code in the contract have been given permalinks above.

PoC Exploit:

pragma solidity ^0.8.0;

contract ExploitContract {
    VulnerableContract public vulnerableContract;
    address public owner;

    constructor(address _vulnerableContractAddress) {
        vulnerableContract = VulnerableContract(_vulnerableContractAddress);
        owner = msg.sender;
    }

    // Function to exploit 'pess-dubious-typecast' issue
    function exploit() public {
        // Craft a malicious payload
        bytes memory payload = abi.encodeWithSelector(
            bytes4(keccak256("withdraw(uint256)")),
            type(uint256).max  // A large negative value that triggers the vulnerability
        );

        // Call the vulnerable contract using the malicious payload
        (bool success, ) = address(vulnerableContract).call(payload);
        
        // Check if the call was successful
        require(success, "Exploit failed");
    }

    // Ensure that only the owner can trigger the exploit
    modifier onlyOwner() {
        require(msg.sender == owner, "Not the owner");
        _;
    }
}


## Tools Used

Manual Review
VS Code
Slither
Remix IDE

## Recommended Mitigation Steps

To mitigate the 'pess-dubious-typecast' vulnerability in the codebase you can make changes to ensure safer type casting and prevent integer overflows/underflows. 

One solution is to use safe math libraries such as OpenZeppelin's SafeMath to handle arithmetic operations securely. 

Here's the modified code with the SafeMath library integrated:

First, install the SafeMath library if you haven't already:

npm install @openzeppelin/contracts

Now, modify your code:

// Import SafeMath library
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

// Contract definition
contract SafeContract {
    using SafeMath for uint256; // Use SafeMath for uint256 operations

    uint256 public balance;

    function deposit(uint256 _amount) public {
        require(_amount > 0, "Amount must be greater than zero");
        balance = balance.add(_amount); // Use SafeMath's add function
    }

    function withdraw(uint256 _amount) public
Attacker can block LayerZero channel on the root chain due to execution outside of try/catch
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
This is an issue that results in pathway blocking on the RootChain and disabling any cross-chain communication between Branches and RootChain.
The consequence of this is that anyone can with a low cost and high frequency keep on blocking the pathway between any two chains, making the whole system unusable and locking user funds.

## Proof of Concept
The root core of this issue is that [`lzReceive`](https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L430) has additional logic outside the `try/catch` block.
The objective of the attacker is to cause an "out of gas" error inside the [`lzReceiveNonBlocking`](https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L434).
As [`lzReceiveNonBlocking`](https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L424) is an external call, according to `EIP-150` 63/64 amount of gas is forwarded to this external call and the rest is left for the rest of the execution.
The 1/64 gas leftover needs to be less than the gas needed for executing the logic inside the [`if(!success) block`](https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L430).

There are several ways of achieving "out of gas" error inside the `lzReceiveNonBlocking`:
1. In case the Router is a MulticallRouter or allows any sort of arbitrary data execution the attack can just drain the gas in that external call. Since the system allows deploying your own RootBridgeAgent with any kind of router this is easy to set up.
2. On the sending side there is no check for the amount of gas passed to the receiving chain, so the attacker can send a perfectly functioning transaction to the `RootBridgeAgent` deployed by Maia DAO but with a smaller amount of gas. 
As a consequence, the transaction will revert somewhere during the `lzReceiveNonBlocking` function execution.

I've set up a POC that demonstrates the second scenario. You can place the POC inside the `RootTest.t.sol` and run it with `forge test --mt testBlockingPathwayOnRootChain -vvvv --watch` to see the stack trace and the revert due to "out of gas error".

1. On the sending side the attacker calls the `callOutAndBridgeMultiple` function.
2. This is received on the RootChain and the `lzReceive` function is invoked with the specified gasLimit.
3. 63/64 of the gas is forwarded to the `lzReceiveNonBlocking` and the rest is left for the rest of the execution.
3. Since 25k gas is not enough to finish the whole execution it reverts due to "out of gas" error and returns success as false.
4. 1/64 gas remaning is not enough to execute the logic inside the [`if(!success) block`](https://github.com/code-423n4/2023-09-maia/blob/main/src/RootBridgeAgent.sol#L430) and it reverts again.
5. This time the revert is caught inside the [`lzEndpoint catch block`](https://github.com/LayerZero-Labs/Lay
Upgraded Q -> 2 from #1067 [1725542893345]
Audit: 2024-07-traitforge-findings
Severity: HIGH

Judge has assessed an item in Issue #1067 as 2 risk. The relevant finding follows:

 [QA-10] Incorrect token limit check in mintWithBudget allows exceeding generation limit
Impact
The mintWithBudget function uses an incorrect condition to check if the token limit for the current generation has been reached. Users could mint more tokens than intended for a given generation.

Proof of Concept
Navigating to mintWithBudget function: https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L202-L226

function mintWithBudget(
  bytes32[] calldata proof
)
  public
  payable
  whenNotPaused
  nonReentrant
  onlyWhitelisted(proof, keccak256(abi.encodePacked(msg.sender)))
{
  uint256 mintPrice = calculateMintPrice();
  uint256 amountMinted = 0;
  uint256 budgetLeft = msg.value;

  while (budgetLeft >= mintPrice && _tokenIds < maxTokensPerGen) {
    _mintInternal(msg.sender, mintPrice);
    amountMinted++;
    budgetLeft -= mintPrice;
    mintPrice = calculateMintPrice();
  }
  if (budgetLeft > 0) {
      (bool refundSuccess, ) = msg.sender.call{ value: budgetLeft }('');
      require(refundSuccess, 'Refund failed.');
    }
}
The condition _tokenIds < maxTokensPerGen in the while loop is incorrect because:

_tokenIds is a global counter for all tokens across all generations and maxTokensPerGen is the limit for each individual generation.

This condition allows minting to continue as long as the total number of tokens minted across all generations is less than maxTokensPerGen, instead of checking the limit for the current generation.