xanoutas/solidity-security-findings huggingface dataset:
Access Control Flaw in UTB Bridge System
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Attackers can  initiate swaps and draining user funds.

## Proof of Concept
In  `UTB` contract, the `receiveFromBridge()` function is intended to be called by a designated bridge adapter. However the implementation lacks  proper access controls. This oversight allows any address including malicious actors, to directly call the function and bypass the expected flow of controlled interactions.
```
 function receiveFromBridge(
        SwapInstructions memory postBridge,
        address target,
        address paymentOperator,
        bytes memory payload,
        address payable refund
    ) public {
```
The function allows malicious any user to access `_swapAndExecute` function with arbitrary parameters that are not part of the intended logic flow. This leads to arbitrary swaps which can steal user funds.

## Tools Used
Manual Review
## Recommended Mitigation Steps
Implement a modifier to restrict the execution of `receiveFromBridge()` in the UTB contract to the designated bridge adapter


## Assessed type

Access Control
`UTBExecutor.execute` function doesn't revert on failure
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/main/src/UTBExecutor.sol#L69-L80


# Vulnerability details

## Impact:

The identified issue in the `execute` function poses a potential risk due to the absence of a revert mechanism in case the external call to `target` fails. If the external call execution fails, there is no reversion of state, which may result in funds being left in the contract without proper handling. This lack of a revert mechanism on failed external calls could lead to potential funds loss and unexpected behavior in the protocol.

## Proof of Concept:

The issue is evident in the `execute` function, where there is no revert mechanism if the external call to `target` fails:

```solidity
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);
        //@audit doesn't revert if call fails
    }

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

    if (remainingBalance == 0) {
        return;
    }

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

As it can be seen when making the external call to the `target` contract, the bool return value `success` is not checked and so if that call did fail (for some reason but did not revert just returned `success == false`) the `execute` function will not revert which mean that any funds sent/taken by that `target` contract won't be returned to the caller leading to a financial loss.

## Tools Used:

Manual review

## Recommended Mitigation Steps:

Update the `execute` function to check the return value `success` and revert in case the external call to `target` fails `success == false`. 


## Assessed type

call/delegatecall
Users can bypass fees by calling `receiveFromBridge` to swap & execute
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
https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L108-L125


# Vulnerability details

## Impact:

Any user can call the `receiveFromBridge` function to perform a swap & execute without paying the protocol fees, the `receiveFromBridge` function allows the users to invoke `_swapAndExecute` without the processing of fees through the `retrieveAndCollectFees` modifier that it's present in the `swapAndExecute` function. Basically the fees can be bypassed by calling `receiveFromBridge` instead of `swapAndExecute` resulting in a financial loss for the protocol.

## Proof of Concept:

The `swapAndExecute` function allows a user to swap currency from the incoming to the outgoing token and executes a transaction with that payment, in the process the user must also pay a protocol fee which is handled by the `retrieveAndCollectFees` modifier :

```solidity
function swapAndExecute(
    SwapAndExecuteInstructions calldata instructions,
    FeeStructure calldata fees,
    bytes calldata signature
)
    public
    payable
    retrieveAndCollectFees(fees, abi.encode(instructions, fees), signature)
{
    _swapAndExecute(
        instructions.swapInstructions,
        instructions.target,
        instructions.paymentOperator,
        instructions.payload,
        instructions.refund
    );
}
```

As it can be seen the function internally invokes `_swapAndExecute` for handling the swap & execution logic.

The issue now is that there is another function that also internally invokes `_swapAndExecute` which is the `receiveFromBridge` function:

```solidity
function receiveFromBridge(
    SwapInstructions memory postBridge,
    address target,
    address paymentOperator,
    bytes memory payload,
    address payable refund
) public {
    //@audit callable by anyone
    _swapAndExecute(postBridge, target, paymentOperator, payload, refund);
}
```

The function is public so it's callable by anyone to perform a swap and execute a tx but this function doesn't include the `retrieveAndCollectFees` modifier as in `swapAndExecute` and so it doesn't charge fees when it's called.

In normal situations the `receiveFromBridge` function should only be called by the approved bridge adapters (registered by the owner) but because there is no check for that the caller is really a bridge adapter, any user can use the function instead of `swapAndExecute` to bypass the fees while running his swap transaction.

## Tools Used:

Manual review

## Recommended Mitigation Steps:

Update the `receiveFromBridge` function to incorporate proper access control allowing only approved bridge adapters as caller, the following check could be added: 

```solidity
function receiveFromBridge(
    SwapInstructions memory postBridge,
    address target,
    address paymentOperator,
    bytes memory payload,
    address payable refund
) public {
    //@audit callable only by bridge adapters
    requir
No withdraw functionality in contracts with receive function
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/main/src/UTBFeeCollector.sol#L44-L62
https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DecentEthRouter.sol#L285-L291


# Vulnerability details

## Impact

Contracts that have `receive` and `fallback` functions implemented:

- `DecentEthRouter`
- `UTB`
- `UTBFeeCollector`
- `DecentBridgeAdapter`
- `StargatBridgeAdapter`
- `UniSwapper`

The problem is that these contracts can receive `ETH` mistakenly or not, but they will be locked inside because there is no withdraw/skim functionality implemented in any of them, `UTBFeeCollector` and `DecentEthRouter` have functions to transfer the funds: `redeemFees` and `redeemEth` respectively:

[UTBFeeCollector.sol#L44-L63](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTBFeeCollector.sol#L44-L62)

```solidity
/**
 * @dev Redeems collected fees in the specified token and amount.
 * @param token The address of the token to be redeemed, zero address for native.
 * @param amount The amount to be redeemed.
 */
function redeemFees(address token, uint256 amount) public onlyOwner {
    if (token == address(0)) {
        payable(owner).transfer(amount);
    } else {
        IERC20(token).transfer(owner, amount);
    }
}
```

And it will be able for owner to rescue the funds **but other contracts will have them locked inside**.

Router also has function for redeeming ETH but it serves another purpose.

[DecentEthRouter.sol#L285-L291](https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DecentEthRouter.sol#L285-L291)

```solidity
function redeemEth(
    uint256 amount
) public onlyIfWeHaveEnoughReserves(amount) {
    dcntEth.transferFrom(msg.sender, address(this), amount);
    weth.withdraw(amount);
    payable(msg.sender).transfer(amount);
}
```

and `[onlyIfWeHaveEnoughReserves](https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DecentEthRouter.sol#L50-L53)`:

```solidity
modifier onlyIfWeHaveEnoughReserves(uint256 amount) {
    require(weth.balanceOf(address(this)) > amount, "not enough reserves");
    _;
}
```

As we can see this function requires `dcntEth` to be transferred first which leaves the mistakenly sent funds untouched.

## Proof of Concept

If someone sends transaction with msg.value ≠ 0 and wrong `ABI` encoding and this does not execute any of the available bridge functions, fallback function will be executed and msg.value will be transferred to one of the contracts. 

Since there is no withdraw functionality implemented nobody, including the admins will be able to retrieve tokens, and this will lead to the total loss of the person who wanted to interact with the contracts.

This can happen in all the contracts that serve the purpose to initiate bridging and they are expected to be frequently called by both users and MEV bots for various reasons: presumably arbitrage from one chain to another. 

## Tool
For token transfer (approve) must be given before token transfer (transferFrom)
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

In the function below, a call to `IERC20(swapParams.tokenIn).transferFrom(...)` is made and immediately afterwards a call to `IERC20(swapParams.tokenIn).approve(...)` takes place.
This order is incorrect because the necessary approval for the token transfer (`approve`) must be given before the token transfer (`transferFrom`).

This may cause the token transfer operation to fail, as the `transferFrom` function will throw an error when called without sufficient confirmation. The correct order should be:

1. With the `approve` function, the necessary permission is given for token transfer.
2. Token transfer is performed with the `transferFrom` function.

This allows the token holder to transfer a certain amount of tokens to the smart contract, which then has the contract transfer those tokens to another address. Following this order correctly is important for a safe and successful token transfer.

```solidity
src/UTB.sol:
   62       */
   63:     function performSwap(
   64:         SwapInstructions memory swapInstructions,
   65:         bool retrieveTokenIn
   66:     ) private returns (address tokenOut, uint256 amountOut) {
   67:         ISwapper swapper = ISwapper(swappers[swapInstructions.swapperId]);
   68: 
   69:         SwapParams memory swapParams = abi.decode(
   70:             swapInstructions.swapPayload,
   71:             (SwapParams)
   72:         );
   73: 
   74:         if (swapParams.tokenIn == address(0)) {
   75:             require(msg.value >= swapParams.amountIn, "not enough native");
   76:             wrapped.deposit{value: swapParams.amountIn}();
   77:             swapParams.tokenIn = address(wrapped);
   78:             swapInstructions.swapPayload = swapper.updateSwapParams(
   79:                 swapParams,
   80:                 swapInstructions.swapPayload
   81:             );
   82:         } else if (retrieveTokenIn) {
   83:             IERC20(swapParams.tokenIn).transferFrom( // @audit transferFrom must be 2.
   84:                 msg.sender,
   85:                 address(this),
   86:                 swapParams.amountIn
   87:             );
   88:         }
   89: 
   90:         IERC20(swapParams.tokenIn).approve( // @audit approve must be 1.
   91:             address(swapper),
   92:             swapParams.amountIn
   93:         );
   94: 
   95:         (tokenOut, amountOut) = swapper.swap(swapInstructions.swapPayload);
   96: 
   97:         if (tokenOut == address(0)) {
   98:             wrapped.withdraw(amountOut);
   99:         }
  100:     }

```


## Proof of Consept

It can be tested by adding the following test file to the project's test file;

```solidity
contract SwapTest is Test {
    ERC20Mock token;
    SwapperContract swapper;
    address user;

    function setUp() public {
        user = address(1);
        token = new ERC20Mock();
        swapper = ne
No deadline specified for uniswap swaps
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/011f62059f3a0b1f3577c8ccd1140f0cf3e7bb29/src/swappers/UniSwapper.sol#L123-L142
https://github.com/code-423n4/2024-01-decent/blob/main/src/swappers/UniSwapper.sol#L143-L156


# Vulnerability details

## Impact

Missing deadline checks in both `swapExactIn` and `swapExactOut` functions in UniSwapper will allow maliciously executed transactions.

That will lead to a bad experience for the initiator and unnecessary swaps because the user will have to swap and bridge his once again in order to retrieve the tokens to the originator chain.

[UniSwapper.sol#L123-L141](https://github.com/code-423n4/2024-01-decent/blob/011f62059f3a0b1f3577c8ccd1140f0cf3e7bb29/src/swappers/UniSwapper.sol#L123C4-L142C1)

```solidity
function swapExactIn(
    SwapParams memory swapParams, // SwapParams is a struct
    address receiver
) public payable routerIsSet returns (uint256 amountOut) {
    swapParams = _receiveAndWrapIfNeeded(swapParams);

    IV3SwapRouter.ExactInputParams memory params = IV3SwapRouter
        .ExactInputParams({
            path: swapParams.path,
            recipient: address(this),
						//@audit missing deadline
            amountIn: swapParams.amountIn,
            amountOutMinimum: swapParams.amountOut
        });

    IERC20(swapParams.tokenIn).approve(uniswap_router, swapParams.amountIn);
    amountOut = IV3SwapRouter(uniswap_router).exactInput(params);

    _sendToRecipient(receiver, swapParams.tokenOut, amountOut);
}
```

[UniSwapper.sol#L143-L156](https://github.com/code-423n4/2024-01-decent/blob/main/src/swappers/UniSwapper.sol#L143-L156)

```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 deadline commented out
            amountOut: swapParams.amountOut,
            amountInMaximum: swapParams.amountIn
        });
```

## Proof of Concept

Imagine a situation where a user is trying to mint an `NFT` from one chain to another (any Decent protocol-supported chain). However, the swap takes a while to execute. During this time, someone else mints a token with the same `tokenId`, and when the original user's funds finally are swapped on Uniswap, it will fail because the `NFT` will already have a holder.

Funds will be successfully refunded to the address which is passed from the initiator, but the initiator will take the loss because he must pay for the:

- fee to the Decent protocol.
- fee for cross-chain execution on `LayerZero`.
- fee twice for UniSwap swaps both on the source and destination chain swaps.

## Tools Used

Manual Review

## Recommended Mitigation Steps

Add proper deadlin
If user send msg.value > amount the difference will be locked in UTB.sol
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

When a user wants to buy an NFT priced in WETH with ETH, they need to send the exact amount of ETH specified in the transaction. If they accidentally send more ETH than the specified amount in `SwapParams::amountIn`, the extra ETH will be stuck in `UTB.sol` and cannot be withdrawn or moved.

According to the README, only `UTBFeeCollector` and `DcntEth` are designed to hold both tokens and native value, while other contracts must refund any differences.

Read Here: https://github.com/code-423n4/2024-01-decent?tab=readme-ov-file#attack-ideas-where-to-look-for-bugs

## Proof of Concept

1. User intends to buy an NFT priced at 1 WETH using ETH.
2. He calls `swapAndExecute()` with amountIn set to 1e18 and msg.value > amountIn.
3. The contract converts the 1e18 ETH into WETH through the WETH contract.
4. Subsequently, the NFT is purchased.
5. Any excess amount, calculated as `msg.value - amountIn`, becomes locked in the `UTB.sol` contract.

### Coded PoC

Paste the test in `test/swappers/UTBExactOutRoutesTestEth2Eth.t.sol`
Run with:

```solidity
forge test --match-test "testMsgValueNotRefunded" -vvv
```

```solidity
function testMsgValueNotRefunded() public {
    string memory chain = arbitrum;
    (
        UTB utb,
        /*UTBExecutor executor*/
        ,
        UniSwapper swapper,
        ,
        ,
    ) = deployUTBAndItsComponents(chain);
    uint256 slippage = 0;

    address weth = getWeth(chain);

    cat = deployTheCat(chain);
    uint256 wethOut = cat.wethPrice();

    (SwapParams memory swapParams, uint256 expected) =
        getSwapParamsExactOut(chain, address(0), weth, wethOut, slippage);

    address refunder = makeAddr("ref");
    address payable refund = payable(refunder);

    SwapInstructions memory swapInstructions =
        SwapInstructions({swapperId: swapper.getId(), swapPayload: abi.encode(swapParams, address(utb), refund)});

    vm.deal(alice, swapParams.amountIn + 1 ether);
    
    console.log("------------------------BEFORE------------------------");
    console.log("Cat NFT price in WETH:               ", wethOut);
    console.log("UTB Balance BEFORE:                                 ", address(utb).balance);
    console.log("WETH balance in Cat contract BEFORE:                ",ERC20(weth).balanceOf(address(cat)));
    console.log("Refunder ETH balance BEFORE:                        ", address(refunder).balance);
    console.log("Refunder WETH balance BEFORE:                       ", ERC20(weth).balanceOf(refunder)); 
    console.log("Alice ETH balance BEFORE:         ",address(alice).balance);

    startImpersonating(alice);

    SwapAndExecuteInstructions memory instructions = SwapAndExecuteInstructions({
        swapInstructions: swapInstructions,
        target: address(cat),
        paymentOperator: address(cat),
        refund: refund,
        paylo
Transactions may revert because the protocol does not account for Stargate's rebalancing fees, wasting gas for users.
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/main/src/bridge_adapters/StargateBridgeAdapter.sol#L176
https://github.com/code-423n4/2024-01-decent/blob/main/src/bridge_adapters/StargateBridgeAdapter.sol#L23


# Vulnerability details

## Impact
User transactions will revert because the protocol does not account for Stargate's rebalancing fees. There is a hardcoded 6bps that is substracted for the swaps. This is the base stargate swap fee, however an addional rebalancing fee can be incurred if the pools are not balanced. As a result users will have their transactions revert and they will waste gas fees.

This minimum acceptable value that is passed in to the swap function to prevent sandwich attacks, however with the value hardcoded it can used for DoSing sandwich attacks where an attacker removes liquidity from the stargate pool to force rebalancing fees to be incurred, thus making user tranasctions revert.

Furthermore, if Stargate increases their base bps, or make any change that increases fees overall, all transactions through stargate will revert.

The Stargate fees: https://stargateprotocol.gitbook.io/stargate/v/user-docs/stargate-features/transfer#transfer-fees-and-how-they-work

- Each non-STG token transfer has a .06% fee. Additionally, Stargate transfers may incur a pool rebalance fee depending on how imbalanced the source & destination transfer pools are with respect to pool equilibrium weights. STG tokens transfers do not incur a pool rebalancing fee.

## Proof of Concept
When calling pool.swap(), the protocol uses a hardcoded 0.06% to calculate the minimum swap value:

https://github.com/code-423n4/2024-01-decent/blob/main/src/bridge_adapters/StargateBridgeAdapter.sol#L176
```js
    function callBridge(
        uint256 amt2Bridge,
        uint256 dstChainId,
        bytes memory bridgePayload,
        bytes calldata additionalArgs,
        address payable refund
    ) private {
        router.swap{value: msg.value}(
            getDstChainId(additionalArgs), //lzBridgeData._dstChainId, // send to LayerZero chainId
            getSrcPoolId(additionalArgs), //lzBridgeData._srcPoolId, // source pool id
            getDstPoolId(additionalArgs), //lzBridgeData._dstPoolId, // dst pool id
            refund, // refund adddress. extra gas (if any) is returned to this address
            amt2Bridge, // quantity to swap
            (amt2Bridge * (10000 - SG_FEE_BPS)) / 10000, // the min qty you would accept on the destination, fee is 6 bips
            getLzTxObj(additionalArgs), // additional gasLimit increase, airdrop, at address
            abi.encodePacked(getDestAdapter(dstChainId)),
            bridgePayload // bytes param, if you wish to send additional payload you can abi.encode() them here
        );
    }
```
As we can see minimum acceptable quantity at destination is: `(amt2Bridge * (10000 - SG_FEE_BPS)) / 10000`
This uses the hardcoded:

https://github.com/code-423n4/2024-01-decent/blob/main/src/bridge_adapters/S
executor's execute might be DOSed leading to initaitor losing all transaction gas
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Medium, tranasaction initiator might lost whole gas while the transaction reverts.

## Proof of Concept

executor.execute() is run with extraNative parameter hardocoded to 0, thus the flow of execution is as below:

```
        uint256 initBalance = IERC20(token).balanceOf(address(this));

        IERC20(token).transferFrom(msg.sender, address(this), amount); 
        IERC20(token).approve(paymentOperator, amount);
        
        (success,) = target.call(payload); // 63/64 of gas is forwarded

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

        if (remainingBalance == 0) {
            return;
        }

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

The target.call is perfomed without specifying the gas sent, thus 63/64 of available gas is sent with the call.
In the case of malicous 'target' the whole gas might be spend on this call, while the 1/64 might not be enough to cover the token balanceOF call thus reverting the transaction. 
As a result, the transaction initiator lost gas.


## Tools Used

VSCode

## Recommended Mitigation Steps

- decide on gas limits sent via call, either as protocol setting or function parameter
- do not copy response from call to minimize gas spend, consider using https://github.com/nomad-xyz/ExcessivelySafeCall


## Assessed type

Other