xanoutas/solidity-security-findings huggingface dataset:
Missing deadline checks allow pending transactions to be maliciously executed 
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/main/src/swappers/UniSwapper.sol#L153


# Vulnerability details

## Impact
Not allowing users to supply their own deadline could potentially expose them to sandwich attacks.

## Proof of Concept
AMMs provide their users with an option to limit the execution of their pending actions, such as swaps or adding and removing liquidity. The most common solution is to include a deadline timestamp as a parameter (for example see Uniswap V2 and Uniswap V3). If such an option is not present, users can unknowingly perform bad trades:

```solidity
function swapExactOut(
        SwapParams memory swapParams,
        address receiver,
        address refundAddress
    ) public payable routerIsSet returns (uint256 amountIn) {
        swapParams = _receiveAndWrapIfNeeded(swapParams);
        IV3SwapRouter.ExactOutputParams memory params = IV3SwapRouter
            .ExactOutputParams({
                path: swapParams.path,
                recipient: address(this),
                //deadline: block.timestamp, //@audit no deadline
                amountOut: swapParams.amountOut,
                amountInMaximum: swapParams.amountIn
            });
```

Consider the following scenario:

Alice wants to swap 30 vBNB tokens for 1 BNB and later sell the 1 BNB for 300 DAI. She signs the transaction calling RiskFund.swapPoolsAsset() with inputAmount = 30 vBNB and amountOutmin = 0.99 BNB to allow for 1% slippage.

The transaction is submitted to the mempool; however, Alice chose a transaction fee that is too low for validators to be interested in including her transaction in a block. The transaction stays pending in the mempool for extended periods, which could be hours, days, weeks, or even longer.

When the average gas fee dropped far enough for Alice's transaction to become interesting again for miners to include it, her swap will be executed. In the meantime, the price of BNB could have drastically decreased. She will still at least get 0.99 BNB due to amountOutmin, but the DAI value of that output might be significantly lower. She has unknowingly performed a bad trade due to the pending transaction she forgot about.

An even worse way this issue can be maliciously exploited is through MEV:

The swap transaction is still pending in the mempool. Average fees are still too high for validators to be interested in it. The price of BNB has gone up significantly since the transaction was signed, meaning Alice would receive a lot more ETH when the swap is executed. But that also means that her minOutput value is outdated and would allow for significant slippage.

A MEV bot detects the pending transaction. Since the outdated minOut now allows for high slippage, the bot sandwiches Alice, resulting in significant profit for the bot and significant loss for Alice.


## Recommended Mitigation Steps
Introduce a deadline parameter to all functions which potentially perform a swap on the user's behalf.




## Assessed
UTBExecutor.sol#execute - Lingering approval to an arbitrary address can loss of funds
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/011f62059f3a0b1f3577c8ccd1140f0cf3e7bb29/src/UTBExecutor.sol#L73-L74


# Vulnerability details

## Impact
The `UTBExecutor` is used to make an arbitrary call to a `target` address.
```jsx
function execute(
        address target,
        address paymentOperator,
        bytes memory payload,
        address token,
        uint amount,
        address payable refund,
        uint extraNative
    ) public onlyOwner {
        bool success;
        if (token == address(0)) {
            (success, ) = target.call{value: amount}(payload);
            if (!success) {
                
                (refund.call{value: amount}(""));
            }
            return;
        }

        uint initBalance = IERC20(token).balanceOf(address(this));

        IERC20(token).transferFrom(msg.sender, address(this), amount);
        IERC20(token).approve(paymentOperator, amount);

        if (extraNative > 0) {
            (success, ) = target.call{value: extraNative}(payload);
            if (!success) {
                (refund.call{value: extraNative}(""));
            }
        } else {
            (success, ) = target.call(payload);
        }

        uint remainingBalance = IERC20(token).balanceOf(address(this)) -
            initBalance;

        if (remainingBalance == 0) {
            return;
        }

        IERC20(token).transfer(refund, remainingBalance);
    }
```

The `execute` function also has a concept of a `paymentOperator` which again can be any arbitrary address.

The contract approves the `paymentOperator` to spend X amount of tokens on his behalf.

```jsx
 IERC20(token).approve(paymentOperator, amount);
```

The contract also implements a refund mechanism, which will refund any remaining tokens in the contract to the `refund` address.

```jsx
uint remainingBalance = IERC20(token).balanceOf(address(this)) -
            initBalance;

        if (remainingBalance == 0) {
            return;
        }

        IERC20(token).transfer(refund, remainingBalance);
```

The problem here is that the `paymentOperator` is not forced to spend the tokens that the `UTBExecutor` approved. This will result in a lingering approval of tokens to an arbitrary address.

If the contract holds any `token` balance for whatever reason, either the protocol decides it should receive tokens, or if users send tokens to it by accident, these tokens can easily be stolen at any time by the `paymentOperator`.

Leaving the severity as Medium, since currently the protocol doesn't plan for the `UTBExecutor` to hold any tokens, but having a lingering approval of tokens to an arbitrary, user supplied address is extremely bad practice and should be fixed.

## Proof of Concept
1. A certain `paymentOperator` accumulates an approval of 10e18 tokens by the `UTBExecutor` and doesn't spend them.
2. In the future the `UTBExecutor` receives 15e18 tokens of that specific token.
3. The `paymentOperator` address spends the toke
Signature malleability of EVMs ecrecover
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/011f62059f3a0b1f3577c8ccd1140f0cf3e7bb29/src/UTBFeeCollector.sol#L52-L53


# Vulnerability details

## Impact

EVM's ecrecover is susceptible to signature malleability which allows replay attacks.

## Proof of Concept

The ecrecover function takes four parameters: hash, v,r and s. The attacker can modify these parameters to produce a different signature that is still valid.

## Tools Used
- Manual review

## Recommended Mitigation Steps
Consider using OpenZeppelin’s ECDSA library: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol


## Assessed type

Other
User losses the difference between minAmount and actual amount with stargate
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/011f62059f3a0b1f3577c8ccd1140f0cf3e7bb29/src/UTB.sol#L186


# Vulnerability details

## Impact

The `UTB:bridgeAndExecute()` assumes that stargate transfers the amount of tokens specified in the minAmountOut parameter of the swap function.
In reality, it can transfer more than that and this difference is lost for the user.

As this is a direct loss of funds for the user, I rate this as a high severity issue.

## Proof of Concept

The flow while using the `UTB:bridgeAndExecute()` function is as follows:

1. First a swap is executed, i.e. `UTB:performSwap` function. 
2. After that the [amountIn is set for the newPostSwapParams](https://github.com/code-423n4/2024-01-decent/blob/011f62059f3a0b1f3577c8ccd1140f0cf3e7bb29/src/UTB.sol#L186)
3. If the adapter is `StargatBridgeAdapter`, this amount is calculated using the actual amount received after the swap and applying the fee deduction.
4. The fee is hardcoded inside the `StargatBridgeAdapter` and is set to 6 BPS.
5. The issue here that stargate swap function works so that you can pass the minimum amount out but the actual tokens transferred can be higher than that.
6. The minimum amount is just there to protect the user from getting less than the minimum amount.
7. This can be observed by looking at the Stargate Pool contract code: https://github.com/stargate-protocol/stargate/blob/main/contracts/Pool.sol#L187

Let's take an example of 5 tokens received after the swap. 
1. After applying the fee deduction we get 4.997 tokens which is the minimum amount the user expects to receive.
2. But in reality the user receives 4.999 tokens on the destination chain.
3. The difference is lost for the user. 

As only the set [amountIn in the `newPostSwapParams` is approved and sent to utb](https://github.com/code-423n4/2024-01-decent/blob/011f62059f3a0b1f3577c8ccd1140f0cf3e7bb29/src/bridge_adapters/StargateBridgeAdapter.sol#L207), the leftover tokens are left sitting in the `StargateBridgeAdapter` contract. 

## Tools Used
- Manual review

## Recommended Mitigation Steps

When the `sgReceive` is invoked on the receiving chain check the actual amount of received tokens and used that for swapping. 
Don't leave any tokens hanging in the `StargateBridgeAdapter` contract.


## Assessed type

Token-Transfer
User will not have to pay any fees if Fee Token is native Eth
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L228-L250


# Vulnerability details

## Impact
User will not have to pay any fees while calling `swapAndExecute` and `bridgeAndExecute` if Fee Token is native Eth

## Proof of Concept
The issue is in the implementation of the modifier `retrieveAndCollectFees` as it does not validate the msg.value and itself pays the fees to `UTBFeeCollector.sol`.
```Solidity
    modifier retrieveAndCollectFees(
        FeeStructure calldata fees,
        bytes memory packedInfo,
        bytes calldata signature
    ) {
        if (address(feeCollector) != address(0)) {
            uint value = 0;
            if (fees.feeToken != address(0)) {
                IERC20(fees.feeToken).transferFrom(
                    msg.sender,
                    address(this),
                    fees.feeAmount
                );
                IERC20(fees.feeToken).approve(
                    address(feeCollector),
                    fees.feeAmount
                );
            } else {
                value = fees.feeAmount;
            }
            feeCollector.collectFees{value: value}(fees, packedInfo, signature);
        }
        _;
    }
```

The only validation on msg.value is in the function performSwap which make sure that `msg.value >= amountIn`

## Tools Used
Manual Review

## Recommended Mitigation Steps
Better to have a validation on `msg.value` that `msg.value >= fees.feeAmount + amountIn` if the Fee token is native token.




## Assessed type

Invalid Validation
Users can easily bypass paying fees
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/011f62059f3a0b1f3577c8ccd1140f0cf3e7bb29/src/UTB.sol#L311-L319


# Vulnerability details

## Impact

Users can bypass any fees that should be paid to the protocol as mentioned in the project's documentation:

```
The Box charges 0.00044 ETH on Arbitrum, Base, Zora, Optimism, Avalanche, Moonbeam, and Fantom, 0.00077 ETH on Ethereum, and 0.81 MATIC on Polygon for transactions that require a bridge or a swap. There are no fees on direct transactions.
```

The `swapAndExecute` method has the `retrieveAndCollectFees` modifier, which takes care of transferring the fees to the `FeeCollector`. Then, the private `_swapAndExecute` function is called which takes care of the swap logic. 

However, the private `_swapAndExecute` is called within the `receiveFromBridge` function which is public and callable by anybody:

```solidity
    function receiveFromBridge(
        SwapInstructions memory postBridge,
        address target,
        address paymentOperator,
        bytes memory payload,
        address payable refund
    ) public {
        _swapAndExecute(postBridge, target, paymentOperator, payload, refund);
    }
```

It takes the same input parameters which are forwarded in the external `swapAndExecute`. This means that any user can just call `receiveFromBridge` and achieve the same outcome without paying any fees because there is no access control or the `retrieveAndCollectFees` modifier. This will lead to a significant loss of funds for the project as the likelihood is very high.


## Tools Used

Manual Review

## Recommended Mitigation Steps

The `receiveFromBridge` is designed to handle the logic when funds are received from the bridge adapter. Consider adding appropriate access control and place a modifier which allows only adapters to call the function.


## Assessed type

Access Control
Lack of Unregister Mechanism for Swappers and Bridge Adapters
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L321-L328
https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L330-L337


# Vulnerability details

## Impact:

The identified issue in the `registerSwapper` and `registerBridge` functions reveals a potential security concern due to the lack of an unregister mechanism. Once a swapper or a bridge adapter is registered, there is no way to remove it from the mapping. This absence of an unregister mechanism could pose a future risk if a registered swapper or bridge adapter turns out to be malicious or corrupted. The protocol would be unable to isolate and remove the problematic entity, potentially leading to incorrect or unexpected behavior of the protocol, and even potential financial loss.

## Proof of Concept:

The issue is evident in the provided comments:

```solidity
/**
    * @dev Registers and maps a swapper to a swapper ID.
    * @param swapper The address of the swapper.
    */
function registerSwapper(address swapper) public onlyOwner {
    ISwapper s = ISwapper(swapper);
    swappers[s.getId()] = swapper;
}

/**
    * @dev Registers and maps a bridge adapter to a bridge adapter ID.
    * @param bridge The address of the bridge adapter.
    */
function registerBridge(address bridge) public onlyOwner {
    IBridgeAdapter b = IBridgeAdapter(bridge);
    bridgeAdapters[b.getId()] = bridge;
}
```

The two functions allow the owner to register a new swapper or bridge by submitting its address in the `registerSwapper` or `registerBridge` function argument. 

In the UTB contract there is no other function that allow the owner to unregister an already added swapper or bridge, and the `registerSwapper` and `registerBridge` functions can be used for that as they work only for registering a new address.

So the owner has no way to remove already registered bridges or swappers which could pose a future risk if a registered swapper or bridge adapter turns out to be malicious or corrupted, this can result in unexpected behavior of the protocol, and even potential financial loss for users or the protocol.

## Tools Used:

Manual review

## Recommended Mitigation Steps:

Implement unregister mechanisms in UTB contract for both swappers and bridge adapters to allow the owner to remove registered entities when necessary. 


## Assessed type

Context
Excess Eth is not refunded
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L75


# Vulnerability details

## Impact
Excess Eth provided while performing swap is not refunded.

## Proof of Concept
```Solidity
require(msg.value >= swapParams.amountIn, "not enough native");
```
The `UTB.sol/performSwap()` function only checks if the `msg.value` sent with the transaction is bigger than `swapParams.amountIn`. If a user sends a bigger amount than what he is expected to pay(AmountIn + Fee), the excess ETH won't be returned.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider returning excess Eth to the refund Address provided in the params.


## Assessed type

Other
Users can use the protocol freely without paying any fees by calling the `DecentEthRouter::bridgeWithPayload()` function directly.
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DecentEthRouter.sol#L148-L194


# Vulnerability details

## The execution flow of `bridgeAndExecute` function

To understand the vulnerability, we need to understand the execution flow of the [`bridgeAndExecute()`](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L259-L274) function, at least a small portion of it.

When the user wants to bridge tokens of him and execute an action on another chain, he will need to execute the [`UTB::bridgeAndExecute()`](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L259-L274) function.

Suppose the user exists in Polygon, he has USDC and he wants to mint an NFT in Optimism which costs 1000 DAI. What will happen is that the protocol will first, in polygon, swap the user's USDC with WETH, then bridge the WETH to Optimism, then swap the WETH with DAI and then execute the arbitrary call the user wants to execute, which will be to mint the NFT in exchange for the resulting 1000 DAI from the post-bridge swap operation.

When this function is called, the following will happen:

1. Step 1: When the user calls the [`UTB::bridgeAndExecute()`](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L259-L274) function, it will do three things: first, it will collect the fees by calling the [`UTBFeeCollector:collectFees()`](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L228-L251) function, secondly, it will conduct the pre-bridge swap operation (occurs in the source destination), it will swap the user's USDC to WETH. thirdly, it will modify the `swapInstructions` which the user supplied to prepare for the post-bridge swap. Then after all of the 3 operations take place, it will invoke the [`UTB::callBridge()`](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L282-L301) function.

2. Step 2: In the [`UTB::callBridge()`](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L282-L301) function, some approvals are granted to the `DecentBridgeAdapter` contract, and then the it will invoke the function [`DecentBridgeAdapter::bridge()`](https://github.com/code-423n4/2024-01-decent/blob/main/src/bridge_adapters/DecentBridgeAdapter.sol#L81-L125) in the `DecentBridgeAdapter` contract.

3. Step 3: In the [`DecentBridgeAdapter::bridge()`](https://github.com/code-423n4/2024-01-decent/blob/main/src/bridge_adapters/DecentBridgeAdapter.sol#L81-L125) function, some data like the post-bridge swap payload and bridge payload (what to execute when the TX reaches destination) will be encoded, then it will reach out to the [`DecentEthRouter`](https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DecentEthRouter.sol) contract and invoke the function [`DecentEthRouter::bridgeWithPayload`](https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DecentEthRouter.sol#L19
UTB::receiveFromBridge function does not check whether swap and execute parameters are signed by Decent Protocol or not
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L311-L319


# Vulnerability details

## Impact
Medium - users are not required to pay any fees to Decent for executing a `swapAndExecute`. Also, the `UTBExecutor` allows the invocation of arbitrary addresses with arbitrary data. However, given that `UTBExecutor` does not hold funds and doesnt yet have privileged roles in critical contracts, IMHO the severity is considered medium.

## Proof of Concept
As we see [here](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L115), parameters for `swapAndExecute` [must be signed by `signer` and user must pay fees before calling swapAndExecute](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTBFeeCollector.sol#L49-L60), which means that `swapAndExecute` is assumed to only perform swaps which are signed by the Decent protocol, giving them control over the swap instructions and addresses that are going to be [called from `UTBExecutor`](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTBExecutor.sol#L41-L81). However, we see that the `receiveFromBridge` function [does not have any signature checks, and no fees are collected from the user](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L311-L319). This means that an attacker can use [`UTBExecutor` to call arbitrary addresses with arbitrary data](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L142-L161), contrary to the desired behavior of the Decent protocol.

## Tools Used
Manual review

## Recommended Mitigation Steps
`receiveFromBridge` is suppose to be only called by `DecentBridgeAdapter`, so we can simply check whether msg.sender is `DecentBridgeAdapter` or not.


## Assessed type

Invalid Validation
Missing deadline checks allow pending transactions to be maliciously executed
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/main/src/swappers/UniSwapper.sol#L153


# Vulnerability details

## Impact
Missing deadline checks allow pending transactions to be maliciously executed

it should provide their users with an option to limit the execution of their pending actions, such as swaps or adding and removing liquidity. The most common solution is to include a deadline timestamp as a parameter (for example see [Uniswap V2](https://github.com/Uniswap/v2-periphery/blob/0335e8f7e1bd1e8d8329fd300aea2ef2f36dd19f/contracts/UniswapV2Router02.sol#L229)). If such an option is not present, users can unknowingly perform bad trades.An even worse way this issue can be maliciously exploited is through MEV.

```solidity
src/swappers/UniSwapper.sol:
  142  
  143:     function swapExactOut(
  144:         SwapParams memory swapParams,
  145:         address receiver,
  146:         address refundAddress
  147:     ) public payable routerIsSet returns (uint256 amountIn) {
  148:         swapParams = _receiveAndWrapIfNeeded(swapParams);
  149:         IV3SwapRouter.ExactOutputParams memory params = IV3SwapRouter
  150:             .ExactOutputParams({
  151:                 path: swapParams.path,
  152:                 recipient: address(this),
  153:                 //deadline: block.timestamp,   ==> @audit
  154:                 amountOut: swapParams.amountOut,
  155:                 amountInMaximum: swapParams.amountIn
  156:             });
  157: 
  158:         IERC20(swapParams.tokenIn).approve(uniswap_router, swapParams.amountIn);
  159:         amountIn = IV3SwapRouter(uniswap_router).exactOutput(params);
  160: 
  161:         // refund sender
  162:         _refundUser(
  163:             refundAddress,
  164:             swapParams.tokenIn,
  165:             params.amountInMaximum - amountIn
  166:         );
  167: 
  168:         _sendToRecipient(receiver, swapParams.tokenOut, swapParams.amountOut);
  169:     }

```


## Recommended Mitigation Steps

Add a _deadline check that a user can enter after which the trade is invalid . This is similar to what uniswap does.


## Assessed type

Uniswap