xanoutas/solidity-security-findings huggingface dataset:
Excess Eth on the swapExactIn(...) function is not refunded.
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/011f62059f3a0b1f3577c8ccd1140f0cf3e7bb29/src/swappers/UniSwapper.sol#L133


# Vulnerability details

## Impact
Loss of excess ETH sent to the Uniswapper.sol#swapExactIn(...) function.


## Proof of Concept
The user specifies the amount of Exact ETh to be swapped and if the msg.value is more than the specified amountIn, excess ETH is not refunded.

## Tools Used
Manual review

## Recommended Mitigation Steps
Consider refunding excess Eth


## Assessed type

ETH-Transfer
hardcoding the gas refundee of the lz calls will cause the users to lost the excess gas that should've been refunded to them
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Users will lose any excess gas they pay for LZ calls because the `refundee` address is incorrectly hardcoded to be the `msg.sender` of the [`DecentEthRouter::bridgeWithPayload() function`]() rather than the `refund` address the user specified

## Proof of Concept
When a crosschain message is sent through LayerZero it is required to prepare a calldata that it'll be sent to the LayerZero contracts. One of the parameter for this calldata it is the `refundAddress`, which indicates to the LayerZero contracts where to refund the excess gas that was paid for the transaction in case that the transaction's cost is cheaper than the amount of value passed.

This is the execution flow when a user wants to perform a bridge of funds from one chain to another
1. The user starts the execution by calling the a [`UTB::bridgeAndExecute()`](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L259-L274) function, here the fees for the operation are charged, a swap pre-bridge its executed && the swapParams for the swap post-bridge are prepared, then execution goes to the [`UTB::callBridge()`](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L282-L301) function
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 execution is forwarded to the [`DecentBridgeAdapter::bridge()`](https://github.com/code-423n4/2024-01-decent/blob/main/src/bridge_adapters/DecentBridgeAdapter.sol#L81-L125) function
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 is encoded and decoded to prepare the calldata for the bridge, and then the [`DecentEthRouter::bridgeWithPayload() function`](https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DecentEthRouter.sol#L197-L215) is called.
4. When the execution reaches the [`DecentEthRouter::bridgeWithPayload()`](https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DecentEthRouter.sol#L197-L215) function, **the `msg.sender` is be the `DecentBridgeAdapter` contract's address**, (from step 3 to step 4, the caller is the `DecentBridgeAdapter` contract), when the LZCallParams are defined in the [`DecentEthRouter::_bridgeWithPayload()`](https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DecentEthRouter.sol#L148-L194) function, the [`callParams.refundAddress` is set to `msg.sender`](https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DecentEthRouter.sol#L171).
- By setting the `callParams.refundAddress` to be the `msg.sender`, what
Signatures can be replayed in fee collection, users can use old signatures to pay for less fees in case fees are increased
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/main/src/UTBFeeCollector.sol#L49-L54


# Vulnerability details

## Impact
By reusing old signatures that computed a lower fee, users can pay a lower amount of fees after the fees are increased. 

## Proof of Concept
In the [`UTBFeeCollector::collectFees() function`](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTBFeeCollector.sol#L44-L62) it is validated that the provided signature was sign by the assigned signer in the contract. The problem is that the signatures don't use a nonce neither an expiry time, without these two parameters, signatures can be replayed because there are no means to determine if a signature has already been used or not, and also, because signatures don't have an expiry time, old signatures can be replayed at any time.
- The hash that is used to validate if the signature is a valid signature is only composed by the received `packedInfo` in the `collectFees()`, and such `packedInfo` is the result of encoding two structs, the [`SwapAndExecuteInstructions` struct](https://github.com/code-423n4/2024-01-decent/blob/main/src/CommonTypes.sol#L15-L21) & the [`FeeStructure` struct](https://github.com/code-423n4/2024-01-decent/blob/main/src/CommonTypes.sol#L9-L13), which none of the two structs uses a nonce or an expiry time.

If the protocol's fees are increased, all signatures that were generated before the increase in fees could be reused and users would be able to pay the previous fees. effectively bypassing the new bigger fees.

> UTBFeeCollector.sol
```solidity
function collectFees(
        FeeStructure calldata fees,
        //@audit-info => packedInfo is the result of encoding the SwapAndExecuteInstructions & FeeStructure structs (which none of them has a nonce or an expiry time)
        bytes memory packedInfo,
        bytes memory signature
    ) public payable onlyUtb {
        //@audit-info => The packedInfo is part of the hash that will be used to verify if the signature is correct 
        bytes32 constructedHash = keccak256(
            abi.encodePacked(BANNER, keccak256(packedInfo))
        );
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature);
        
        //@audit-info => As longs as the signer has signed the packedInfo (SwapAndExecuteInstruction & FeeStructure), the fee will be considered valid
        //@audit-issue => not using a nonce as part of the hash allows to replay signatures
        //@audit-issue => not setting an expiry time allows old signatures to be replayed at any point in the future
        address recovered = ecrecover(constructedHash, v, r, s);
        require(recovered == signer, "Wrong signature");

        ...
    }
```

## Tools Used
Manual Audit

## Recommended Mitigation Steps
Include a nonce and an expiry time as part of the signature, in this way signatures can't be replayed.



## Assessed type

Other
Missing deadline parameter to prevent swaps from being 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#L149-L156
https://github.com/code-423n4/2024-01-decent/blob/main/src/swappers/UniSwapper.sol#L129-L135


# Vulnerability details

## Impact
The swap operations from the UniSwapper contract **does not set a deadline**. Not setting a deadline allows pending transactions to be maliciously executed at a later point.

## Proof of Concept
AMMs provide their users with an option to limit the execution of their pending actions, such as swaps. 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) and [Uniswap V3](https://github.com/Uniswap/v3-periphery/blob/6cce88e63e176af1ddb6cc56e029110289622317/contracts/SwapRouter.sol#L119)). If such an option is not present, users can unknowingly perform bad trades:

1. Alice wants to swap 100 tokens for 1 ETH and later sell the 1 ETH for 1000 DAI.
2. The transaction is submitted to the mempool, however, Alice chose a transaction fee that is too low for miners 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.
3. 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 ETH could have drastically changed. She will still get 1 ETH 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, even though Alice sets a slipagge to her transaction, the MEV bot will extract the maximum possible value from Alice's transaction

1. The swap transaction is still pending in the mempool. Average fees are still too high for miners to be interested in it. The price of tokens 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 maximum slippage value (sqrtPriceLimitX96 and minOut in terms of the Papr contracts) is outdated and would allow for significant slippage.
2. A MEV bot detects the pending transaction. Since the outdated maximum slippage value now allows for high slippage, the bot sandwiches Alice, resulting in significant profit for the bot and significant loss for Alice.


Nor in the [`UniSwapper::swapExactIn() function`](https://github.com/code-423n4/2024-01-decent/blob/main/src/swappers/UniSwapper.sol#L123-L141) neither in the [`UniSwapper::swapExactOut() function`](https://github.com/code-423n4/2024-01-decent/blob/main/src/swappers/UniSwapper.sol#L143-L169) it is defined a deadline as the limit time for the swap to be executable.
> Uniswappe
UTBExecutor and Uniswapper allow contracts to accumulate allowance of all the unspent tokens
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/main/src/swappers/UniSwapper.sol#L143-L169
https://github.com/code-423n4/2024-01-decent/blob/main/src/UTBExecutor.sol#L41-L82


# Vulnerability details

## Impact
UniswapRouter will let the Uniswapper contract to accumulate allowance over all the unspent amountIn of tokens that are not transfered when doing a swapExactOutput
The UTBExecutor will let the paymentOperators contracts to accumulate allowance over all the unspent amountOut that is not spent during the call to the target contract

## Proof of Concept
When the UniswapRouter does a swapExactOut() it grants the UniswapRouter to spend all the specified `amountIn` of `tokensIn` to perform the swap. The problem is that a swapExactOut not always spends all the `amountIn` of `tokenIn` that was specified, the [`UniSwapper::swapExactOut() function`](https://github.com/code-423n4/2024-01-decent/blob/main/src/swappers/UniSwapper.sol#L143-L169) already returns the unspent tokenIn, but it misses to reset the allowance to the UniswapRouter when there is any unspent tokenIn.

> UniSwapper.sol
```solidity
    function swapExactOut(
        ...
    ) public payable routerIsSet returns (uint256 amountIn) {
        ...

        //@audit-info => Approves `amountIn` to the UniswapRouter
        IERC20(swapParams.tokenIn).approve(uniswap_router, swapParams.amountIn);
        amountIn = IV3SwapRouter(uniswap_router).exactOutput(params);

        //@audit-info => Refunds unspent `amountIn` to the refundAddress
        // refund sender
        _refundUser(
            refundAddress,
            swapParams.tokenIn,
            params.amountInMaximum - amountIn
        );

        //@audit-issue => Not resetting allowance to the uniswap router
        //@audit-issue => If not all amountIn is spend, approval must be reset to 0!
        //@audit-reference => https://docs.uniswap.org/contracts/v3/guides/swaps/single-swaps#a-complete-single-swap-contract

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

A similar scenario happens in the [`UTBExecutor::execute() function`](https://github.com/code-423n4/2024-01-decent/blob/main/src/UTBExecutor.sol#L41-L82), the executor approves a `paymentOperator` address to spend `amount` the tokenOut, the problem is that if there is left any unspent amount, the allowance to the paymentOperator is not resetted.
> UTBExecutor.sol
```solidity
    function execute(
        ...
    ) public onlyOwner {
        ...

        //@audit-info => Approves the paymentOperator to spend `amount`
        IERC20(token).approve(paymentOperator, amount);

        ...

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

        if (remainingBalance == 0) {
            return;
        }

        //@audit-ok => Refunds any unspent tokens
        IERC20(token).transfer(refund, remainingBalance);
        
        //@audit-issue => Allowance to paymentOper
Outstanding WETH approvals in `DecentBridgeExecutor`
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DecentBridgeExecutor.sol#L31-L44


# Vulnerability details

## Impact

From the contest docs:
> Other than the UTBFeeCollector, and DcntEth, the contracts are not intended to hold on to any funds or unnecessary approvals 

However, in the [`DecentBridgeExecutor._executeWeth()`](https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DecentBridgeExecutor.sol#L24) function, the contract approves a `target` address to withdraw a specified `amount` of WETH and does not clear the approval if the target address does not use it:
https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DecentBridgeExecutor.sol#L31-L44
```solidity
	weth.approve(target, amount);
	
	(bool success, ) = target.call(callPayload);
	
	if (!success) {
		weth.transfer(from, amount);
		return;
	}
	
	uint256 remainingAfterCall = amount -
		(balanceBefore - weth.balanceOf(address(this)));
	
	// refund the sender with excess WETH
	weth.transfer(from, remainingAfterCall);
```

In either of the following two cases, the WETH is transferred to the target address, which does not revoke the approval, and isn't cleared afterwards:
- If the call fails
- If the call succeeds but doesn't pull any (or less) WETH

This means that the `target` address can withdraw WETH from the contract at any time in the future.

## Proof of Concept
1. The `DecentEthRouter` calls the `execute()` function with the received `target` address and `amount` of WETH to be transferred.
2. The `execute()` function calls `_executeWeth()`, which approves the `target` address to withdraw the specified `amount` of WETH.
3. The `target` address receives the call with the `callPayload`, but does not withdraw any WETH.
4. The WETH amount is transferred to the target address.
5. The `target` address nevertheless has approval to withdraw WETH from the contract and can do so at any time.

## Tools Used
Manual review

## Recommended Mitigation Steps
Revoke the approval before returning.


## Assessed type

Other
Signature replay
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/main/src/UTBFeeCollector.sol#L49-L54


# Vulnerability details

## Impact
The signature has some potential problems. It has no expiration and can easily be replayed due to the lack of nonce and protection against malleability. Additionally, there is a potential cross-chain replay attack because the signature does not contain chain ID.

If the user calls `swapAndExecute` or `bridgeAndExecute`, notices a mistake and then calls one of these functions again, an attacker can use the same signature from the first call to pass the signature to his transaction. The `recovered` address will be equal to `signer` and the transaction will be executed successfully. 

## Tools Used
Manual review

## Recommended Mitigation Steps
Add chain ID, expiration, and nonce to the signature. Also, use `ECDSA` from OpenZeppelin instead of `ecrecover`.


## Assessed type

Other
the `.IV3SwapRouter.ExactOutputParams(  swap 
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-decent/blob/07ef78215e3d246d47a410651906287c6acec3ef/src/swappers/UniSwapper.sol#L149-L157


# Vulnerability details

## Impact
the `.IV3SwapRouter.ExactOutputParams(`  doesnt include the deadline value.

no deadline for the trade transaction to be valid which means the trade can be delayed by miners or users congesting the network, as well as being sandwich attacked - ultimately leading to loss of user funds.

as it even commented in uniswap docs that its supper important 
`docs: deadline: the unix time after which a swap will fail, to protect against long-pending transactions and wild swings in prices`
https://docs.uniswap.org/contracts/v3/guides/swaps/single-swaps#swap-input-parameters

and the codebase lack it which is really dangerous
```solidity

 IV3SwapRouter.ExactOutputParams memory params = IV3SwapRouter
            .ExactOutputParams({
                path: swapParams.path,
                recipient: address(this),
                //deadline: block.timestamp,
                amountOut: swapParams.amountOut,
                amountInMaximum: swapParams.amountIn
            });
```
## Proof of Concept
here is instance 

```soldiity 

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

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

        // refund sender
        _refundUser(
            refundAddress,
            swapParams.tokenIn,
            params.amountInMaximum - amountIn
        );

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

```
## Tools Used
vscode
## Recommended Mitigation Steps
- consider enabling deadline in both exact swaps 
```solidity 
  .ExactOutputParams({
                path: swapParams.path,
                recipient: address(this),
 @+               deadline: block.timestamp,
                amountOut: swapParams.amountOut,
                amountInMaximum: swapParams.amountIn
            });

```
you can eighter pass it to user or put value in it directly.


## Assessed type

Uniswap
The signer address is not set appropriately before fee collection in the 'collectFees' function
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Signature verification is performed correctly using `ecrecover` function. However, the `signer` address is not set appropriately before fee collection

## Proof of Concept
The `collectFees` function is not checking whether the `signer` address is set to zero, which may cause a revert to its functionality:

```solidity
    function collectFees(
        FeeStructure calldata fees,
        bytes memory packedInfo,
        bytes memory signature
    ) public payable onlyUtb {
        bytes32 constructedHash = keccak256(
            abi.encodePacked(BANNER, keccak256(packedInfo))
        );
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature);
        address recovered = ecrecover(constructedHash, v, r, s);
        require(recovered == signer, "Wrong signature");
        if (fees.feeToken != address(0)) {
            IERC20(fees.feeToken).transferFrom(
                utb,
                address(this),
                fees.feeAmount
            );
        }
    }
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
To ensure that the `signer` address is set appropriately before fee collection, you can modify the `collectFees` function to include a check for the presence of a valid signer. Here's an updated version of the `collectFees` function:

```solidity
function collectFees(
    FeeStructure calldata fees,
    bytes memory packedInfo,
    bytes memory signature
) public payable onlyUtb nonReentrant {
    require(signer != address(0), "Signer address not set");

    bytes32 constructedHash = keccak256(
        abi.encodePacked(BANNER, keccak256(packedInfo))
    );
    (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature);
    address recovered = ecrecover(constructedHash, v, r, s);
    require(recovered == signer, "Wrong signature");

    // ... 
}
```
In this modification, a require statement is added at the beginning of the function to check if the `signer` address is not set to the zero address. If it is not set, the function will revert, preventing unauthorized fee collection.

Make sure that the `setSigner` function is called to set the signer address appropriately before any fee collection occurs. 


## Assessed type

Access Control
Swaps and execute operation without paying protocol fees
Audit: 2024-01-decent-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
There exist a codepath that allows any users to call the function `swapAndExecute` (which is a wrapper to function `_swapAndExecute`) without paying Decent protocol fees.
To exploit the bug it is as simple as  calling directly the public function `receiveFromBridge` which in turns reach the function `_swapAndExecute`  without passing through the check of the modifier `retrieveAndCollectFees` .

By calling the function`receiveFromBridge` it is possible to  achieve a swap and execute operation without paying for the fees, as you can control all the arguments passed in to the function `_swapAndExecute`.
## Proof of Concept

The function `swapAndExecute` is the legit entrypoint for a swap and execute operation 

function `swapAndExecute`
https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L108

function `_swapAndExecute`
https://github.com/code-423n4/2024-01-decent/blob/main/src/UTB.sol#L134

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

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

As shown in Decent protocol test function `testSwapWethToUsdcAndMintAnNft` (file name `UTBExactOutRoutesTestEth2Eth.t.sol`) the UTB function `swapAndExecute` is called as follows:

```solidity
utb.swapAndExecute(instructions, fees, signature);
```

The same logic operation can be achieved by the following code:

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

utb.receiveFromBridge(swapInstructions, address(cat), address(cat), abi.encodeCall(cat.mintWithUsdc, (bob)), refund);
```

which calls the public function with no modifier check.
## Tools Used

Foundry

## Recommended Mitigation Steps

Add the modifier `retrieveAndCollectFees` for the function `receiveFromBridge`


## Assessed type

Other