Decent - slvDev's results

Decent enables one-click transactions using any token across chains.

General Information

Platform: Code4rena

Start Date: 19/01/2024

Pot Size: $36,500 USDC

Total HM: 9

Participants: 113

Period: 3 days

Judge: 0xsomeone

Id: 322

League: ETH

Decent

Findings Distribution

Researcher Performance

Rank: 25/113

Findings: 2

Award: $204.24

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Labels

bug
grade-b
QA (Quality Assurance)
sufficient quality report
Q-06

Awards

12.2818 USDC - $12.28

External Links

Low Findings

IssueInstances
[L-01]Potential Gas Griefing Due to Non-Handling of Return Data in External Calls2
[L-02].call bypasses function existence check, type checking and argument packing5
[L-03]Unbounded Gas Consumption on External Calls5
[L-04]Use of ecrecover is susceptible to signature malleability1
[L-05]Upgradable contracts not taken into account20
[L-06]Risk of Permanently Locked Ether12
[L-07]Missing Contract-Existence Checks Before Low-Level Calls7
[L-08]Loss of precision2
[L-09]Missing address(0) Check in Constructor3

NonCritical Findings

IssueInstances
[N-01]Variables need not be initialized to zero5
[N-02]require()/revert() statements without reason strings1
[N-03]Consider using descriptive constants when passing zero as a function argument1
[N-04]Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, for readability6
[N-05]Remove Unused private Functions1
[N-06]High cyclomatic complexity2
[N-07]State-Altering Functions Should Emit Events12
[N-08]Contract/Libraries Names Do Not Match Their Filenames1
[N-09]Function/Constructor Argument Names Not in mixedCase26
[N-10]Contract/Library Names Not in CapWords Style (CamelCase)1
[N-11]Leverage Recent Solidity Features with 0.8.2311

Low Findings Details

[L-01] Potential Gas Griefing Due to Non-Handling of Return Data in External Calls

Due to the EVM architecture, return data (bool success,) has to be stored. However, when 'out' and 'outsize' values are given (0,0), this storage disappears. This can lead to potential gas griefing/theft issues, especially when dealing with external contracts.

assembly {
    success: = call(gas(), dest, amount, 0, 0)
}
require(success, "transfer failed");

Consider using a safe call pattern above to avoid these issues. The following instances show the unsafe external call patterns found in the code.

<details> <summary><i>2 issue instances in 1 files:</i></summary>
File: lib/decent-bridge/src/DecentBridgeExecutor.sol

33: (bool success, ) = target.call(callPayload);
61: (bool success, ) = target.call{value: amount}(callPayload);

33 | 61

</details>

[L-02] .call bypasses function existence check, type checking and argument packing

Using the .call method in Solidity enables direct communication with an address, bypassing function existence checks, type checking, and argument packing. While this can save gas and provide flexibility, it can also introduce security risks and potential errors. The absence of these checks can lead to unexpected behavior if the callee contract's interface changes or if the input parameters are not crafted with care. The resolution to these issues is to use Solidity's high-level interface for calling functions when possible, as it automatically manages these aspects. If using .call is necessary, ensure that the inputs are carefully validated and that awareness of the called contract's behavior is maintained.

<details> <summary><i>5 issue instances in 2 files:</i></summary>
File: src/UTBExecutor.sol

52: target.call{value: amount}(payload)
65: target.call{value: extraNative}(payload)
70: target.call(payload)

52 | 65 | 70

File: lib/decent-bridge/src/DecentBridgeExecutor.sol

33: target.call(callPayload)
61: target.call{value: amount}(callPayload)

33 | 61

</details>

[L-03] Unbounded Gas Consumption on External Calls

External calls in your code don't specify a gas limit, which can lead to scenarios where the recipient consumes all transaction's gas causing it to revert. Consider using addr.call{gas: <amount>}("") to set a gas limit and prevent potential reversion due to gas consumption.

<details> <summary><i>5 issue instances in 2 files:</i></summary>
File: src/UTBExecutor.sol

52: (success, ) = target.call{value: amount}(payload);
65: (success, ) = target.call{value: extraNative}(payload);
70: (success, ) = target.call(payload);

52 | 65 | 70

File: lib/decent-bridge/src/DecentBridgeExecutor.sol

33: (bool success, ) = target.call(callPayload);
61: (bool success, ) = target.call{value: amount}(callPayload);

33 | 61

</details>

[L-04] Use of ecrecover is susceptible to signature malleability

The built-in EVM precompile ecrecover is susceptible to signature malleability, which could lead to replay attacks. References: https://swcregistry.io/docs/SWC-117, https://swcregistry.io/docs/SWC-121. While this is not immediately exploitable, this may become a vulnerability if used elsewhere. Consider using OpenZeppelin’s ECDSA library (which prevents this malleability) instead of the built-in function.

<details> <summary><i>1 issue instances in 1 files:</i></summary>
File: src/UTBFeeCollector.sol

53: ecrecover(constructedHash, v, r, s)

53

</details>

[L-05] Upgradable contracts not taken into account

In the realm of blockchain development, it's crucial to consider the impact of upgradable contracts, especially when handling token addresses through interfaces like IERC20. These contracts can evolve over time, potentially altering their behavior or interface. Such changes may lead to compatibility issues or security vulnerabilities in the protocol that relies on them.

<details> <summary><i>20 issue instances in 6 files:</i></summary>
File: src/UTB.sol

83: IERC20(swapParams.tokenIn).transferFrom(
90: IERC20(swapParams.tokenIn).approve(
152: IERC20(tokenOut).approve(address(executor), amountOut);
236: IERC20(fees.feeToken).transferFrom(
241: IERC20(fees.feeToken).approve(

83 | 90 | 152 | 236 | 241

File: src/UTBExecutor.sol

61: IERC20(token).transferFrom(msg.sender, address(this), amount);
62: IERC20(token).approve(paymentOperator, amount);
80: IERC20(token).transfer(refund, remainingBalance);

61 | 62 | 80

File: src/UTBFeeCollector.sol

56: IERC20(fees.feeToken).transferFrom(
73: IERC20(token).transfer(owner, amount);

56 | 73

File: src/bridge_adapters/DecentBridgeAdapter.sol

139: IERC20(swapParams.tokenIn).transferFrom(
145: IERC20(swapParams.tokenIn).approve(utb, swapParams.amountIn);

109 | 114 | 139 | 145

File: src/bridge_adapters/StargateBridgeAdapter.sol

207: IERC20(swapParams.tokenIn).approve(utb, swapParams.amountIn);

207

File: src/swappers/UniSwapper.sol

44: IERC20(token).transfer(user, amount);
55: IERC20(token).transfer(recipient, amount);
83: IERC20(swapParams.tokenIn).transferFrom(
137: IERC20(swapParams.tokenIn).approve(uniswap_router, swapParams.amountIn);
158: IERC20(swapParams.tokenIn).approve(uniswap_router, swapParams.amountIn);

44 | 55 | 83 | 137 | 158

</details>

[L-06] Risk of Permanently Locked Ether

When Ether is mistakenly sent to a contract without a means of retrieval, it becomes irrevocably locked. Incidents of accidental Ether transfers have been observed even in high-profile projects, potentially leading to significant financial setbacks. To enhance contract resilience, it's recommended to incorporate an "Ether recovery" function to serve as a protective measure against unintended Ether lockups.

<details> <summary><i>12 issue instances in 6 files:</i></summary>
File: src/UTB.sol

339: receive() external payable {}
341: fallback() external payable {}

339 | 341

File: src/UTBFeeCollector.sol

77: receive() external payable {}
79: fallback() external payable {}

77 | 79

File: src/bridge_adapters/DecentBridgeAdapter.sol

156: receive() external payable {}
158: fallback() external payable {}

156 | 158

File: src/bridge_adapters/StargateBridgeAdapter.sol

218: receive() external payable {}
220: fallback() external payable {}

218 | 220

File: src/swappers/UniSwapper.sol

171: receive() external payable {}
173: fallback() external payable {}

171 | 173

File: lib/decent-bridge/src/DecentEthRouter.sol

337: receive() external payable {}
339: fallback() external payable {}

337 | 339

</details>

[L-07] Missing Contract-Existence Checks Before Low-Level Calls

When making low-level calls, it's crucial to ensure the existence of the contract at the specified address. If the contract doesn't exist at the given address, low-level calls will still return success, potentially causing errors in the code execution. Therefore, alongside zero-address checks, adding an additional check to verify that <address>.code.length > 0 before making low-level calls would be recommended.

<details> <summary><i>7 issue instances in 2 files:</i></summary>
File: src/UTBExecutor.sol

52: (success, ) = target.call{value: amount}(payload);
54: (refund.call{value: amount}(""));
65: (success, ) = target.call{value: extraNative}(payload);
67: (refund.call{value: extraNative}(""));
70: (success, ) = target.call(payload);

52 | 54 | 65 | 67 | 70

File: lib/decent-bridge/src/DecentBridgeExecutor.sol

33: (bool success, ) = target.call(callPayload);
61: (bool success, ) = target.call{value: amount}(callPayload);

33 | 61

</details>

[L-08] Loss of precision

Division by large numbers may result in the result being zero, due to Solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator.

<details> <summary><i>2 issue instances in 1 files:</i></summary>
File: src/bridge_adapters/StargateBridgeAdapter.sol

66: return (amt2Bridge * (1e4 - SG_FEE_BPS)) / 1e4;
176: (amt2Bridge * (10000 - SG_FEE_BPS)) / 10000, // the min qty you would accept on the destination, fee is 6 bips

66 | 176

</details>

[L-09] Missing address(0) Check in Constructor

The constructor does not include a check for address(0) when set state variables that hold addresses. Initializing a state variable with address(0) can lead to unintended behavior and vulnerabilities in the contract, such as sending funds to an inaccessible address. It is recommended to include a validation step to ensure that address parameters are not set to address(0).

<details> <summary><i>3 issue instances in 3 files:</i></summary>
File: src/bridge_adapters/DecentBridgeAdapter.sol

/// @audit `_bridgeToken` has lack of `address(0)` check before use
20: constructor(bool _gasIsEth, address _bridgeToken) BaseAdapter() {

20

File: lib/decent-bridge/src/DecentEthRouter.sol

/// @audit `_wethAddress` has lack of `address(0)` check before use
/// @audit `_executor` has lack of `address(0)` check before use
26: constructor(
        address payable _wethAddress,
        bool gasIsEth,
        address _executor
    ) Owned(msg.sender) {

26

File: lib/decent-bridge/src/DecentBridgeExecutor.sol

/// @audit `_weth` has lack of `address(0)` check before use
11: constructor(address _weth, bool gasIsEth) Owned(msg.sender) {

11

</details>

[N-01] Variables need not be initialized to zero

By default, int/uint variables in Solidity are initialized to zero. Explicitly setting variables to zero during their declaration is redundant and might cause confusion. Removing the explicit zero initialization can improve code readability and understanding.

<details> <summary><i>5 issue instances in 5 files:</i></summary>
File: src/UTB.sol

234: uint value = 0;

234

File: src/bridge_adapters/DecentBridgeAdapter.sol

13: uint8 public constant BRIDGE_ID = 0;

13

File: src/swappers/SwapParams.sol

5: uint8 constant EXACT_IN = 0;

5

File: src/swappers/UniSwapper.sol

16: uint8 public constant SWAPPER_ID = 0;

16

File: lib/decent-bridge/src/DecentEthRouter.sol

17: uint8 public constant MT_ETH_TRANSFER = 0;

17

</details>

[N-02] require()/revert() statements without reason strings

In Solidity, require() and revert() functions can include an optional 'reason' string that describes what caused the function to fail. This string can be incredibly helpful during testing and debugging, as it provides more context for what went wrong.

Some require()/revert() statements do not have these descriptive reason strings. For better error handling and debugging, it is strongly recommended to add descriptive reason strings to all require()/revert() statements.

<details> <summary><i>1 issue instances in 1 files:</i></summary>
File: lib/decent-bridge/src/DcntEth.sol

9: require(msg.sender == router)

9

</details>

[N-03] Consider using descriptive constants when passing zero as a function argument

In instances where utilizing a zero parameter is essential, it is recommended to employ descriptive constants or an enum instead of directly integrating zero within function calls. This strategy aids in clearly articulating the caller's intention and minimizes the risk of errors. Emitting zero also not recomended, as it is not clear what the intention is.

<details> <summary><i>1 issue instances in 1 files:</i></summary>
File: src/UTBExecutor.sol

28: execute(target, paymentOperator, payload, token, amount, refund, 0)

28

</details>

[N-04] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, for readability

Combining multiple address/ID mappings into a single mapping to a struct can enhance code clarity and maintainability. Consider refactoring multiple mappings into a single mapping with a struct for cleaner code structure. This arrangement also promotes a more organized contract structure, making it easier for developers to navigate and understand.

<details> <summary><i>6 issue instances in 3 files:</i></summary>
File: src/UTB.sol

21: mapping(uint8 => address) public swappers
22: mapping(uint8 => address) public bridgeAdapters

21 | 22

File: src/bridge_adapters/DecentBridgeAdapter.sol

14: mapping(uint256 => address) public destinationBridgeAdapter
16: mapping(uint256 => uint16) lzIdLookup

14 | 16

File: src/bridge_adapters/StargateBridgeAdapter.sol

25: mapping(uint256 => address) public destinationBridgeAdapter
26: mapping(uint256 => uint16) lzIdLookup

25 | 26

</details>

[N-05] Remove Unused private Functions

private functions that are not called anywhere within the same contract are redundant and should be removed to save deployment gas. Unlike public and internal functions, private functions cannot be called by derived contracts or externally, they can only be called within the contract they are defined.

<details> <summary><i>1 issue instances in 1 files:</i></summary>
File: src/bridge_adapters/StargateBridgeAdapter.sol

100: function getValue(
        bytes calldata additionalArgs,
        uint256 amt2Bridge
    ) private view returns (uint value)

100

</details>

[N-06] High cyclomatic complexity

Functions with high cyclomatic complexity are harder to understand, test, and maintain. Consider breaking down these blocks into more manageable units, by splitting things into utility functions, by reducing nesting, and by using early returns.

Learn More About Cyclomatic Complexity

<details> <summary><i>2 issue instances in 2 files:</i></summary>
File: src/UTBExecutor.sol

/// @audit function `execute` has a cyclomatic complexity of 6
41: function execute(
        address target,
        address paymentOperator,
        bytes memory payload,
        address token,
        uint amount,
        address payable refund,
        uint extraNative
    ) public onlyOwner {

41

File: lib/decent-bridge/src/DecentEthRouter.sol

/// @audit function `onOFTReceived` has a cyclomatic complexity of 6
237: function onOFTReceived(
        uint16 _srcChainId,
        bytes calldata,
        uint64,
        bytes32,
        uint _amount,
        bytes memory _payload
    ) external override onlyLzApp {

237

</details>

[N-07] State-Altering Functions Should Emit Events

Functions that alter state should emit events to inform users of the state change. This is crucial for functions that modify the state and don't return a value. The absence of events in such scenarios could lead to lack of transparency and traceability, undermining the contract's reliability.

<details> <summary><i>12 issue instances in 8 files:</i></summary>
File: src/UTB.sol

29: executor = IUTBExecutor(_executor);
37: wrapped = IWETH(_wrapped);
45: feeCollector = IUTBFeeCollector(_feeCollector);

29 | 37 | 45

File: src/UTBFeeCollector.sol

19: signer = _signer;

19

File: src/bridge_adapters/BaseAdapter.sol

20: bridgeExecutor = _executor;

20

File: src/bridge_adapters/DecentBridgeAdapter.sol

27: router = IDecentEthRouter(payable(_router));

27

File: src/bridge_adapters/StargateBridgeAdapter.sol

34: router = IStargateRouter(_router);
38: stargateEth = _sgEth;

34 | 38

File: src/swappers/UniSwapper.sol

21: uniswap_router = _router;
25: wrapped = _wrapped;

21 | 25

File: lib/decent-bridge/src/DcntEth.sol

21: router = _router;

21

File: lib/decent-bridge/src/DecentEthRouter.sol

69: dcntEth = IDcntEth(_addr);

69

</details>

[N-08] Contract/Libraries Names Do Not Match Their Filenames

According to the Solidity Style Guide, contract names should match their filenames. Mismatching contract names and filenames can lead to confusion and make the code harder to maintain and review.

<details> <summary><i>1 issue instances in 1 files:</i></summary>
File: src/swappers/SwapParams.sol

3: library SwapDirection {

3

</details>

[N-09] Function/Constructor Argument Names Not in mixedCase

Underscore before of after function argument names is a common convention in Solidity NOT a documentation requirement.

Function arguments should use mixedCase for better readability and consistency with Solidity style guidelines. Examples of good practice include: initialSupply, account, recipientAddress, senderAddress, newOwner. More information in Documentation

Rule exceptions

  • Allow constant variable name/symbol/decimals to be lowercase (ERC20).
  • Allow _ at the beginning of the mixedCase match for private variables and unused parameters.
<details> <summary><i>26 issue instances in 9 files:</i></summary>
File: src/UTB.sol

28: function setExecutor(address _executor) public onlyOwner {
36: function setWrapped(address payable _wrapped) public onlyOwner {
44: function setFeeCollector(address payable _feeCollector) public onlyOwner {

28 | 36 | 44

File: src/UTBFeeCollector.sol

18: function setSigner(address _signer) public onlyOwner {

18

File: src/bridge_adapters/BaseAdapter.sol

18: function setBridgeExecutor(address _executor) public onlyOwner {

18

File: src/bridge_adapters/DecentBridgeAdapter.sol

25: function setRouter(address _router) public onlyOwner {
20: constructor(bool _gasIsEth, address _bridgeToken) BaseAdapter() {

25 | 20

File: src/bridge_adapters/StargateBridgeAdapter.sol

32: function setRouter(address _router) public onlyOwner {
36: function setStargateEth(address _sgEth) public onlyOwner {

32 | 36

File: src/swappers/UniSwapper.sol

19: function setRouter(address _router) public onlyOwner {
23: function setWrapped(address payable _wrapped) public onlyOwner {

19 | 23

File: lib/decent-bridge/src/DcntEth.sol

20: function setRouter(address _router) public {
23: function mint(address _to, uint256 _amount) public onlyRouter {
27: function burn(address _from, uint256 _amount) public onlyRouter {
31: function mintByOwner(address _to, uint256 _amount) public onlyOwner {
35: function burnByOwner(address _from, uint256 _amount) public onlyOwner {

20 | 23 | 27 | 31 | 35

File: lib/decent-bridge/src/DecentEthRouter.sol

68: function registerDcntEth(address _addr) public onlyOwner {
73: function addDestinationBridge(
        uint16 _dstChainId,
        address _routerAddress
    ) public onlyOwner {
79: function _getCallParams(
        uint8 msgType,
        address _toAddress,
        uint16 _dstChainId,
        uint64 _dstGasForCall,
        bool deliverEth,
        bytes memory additionalPayload
    )
        private
        view
        returns (
            bytes32 destBridge,
            bytes memory adapterParams,
            bytes memory payload
        )
    {
112: function estimateSendAndCallFee(
        uint8 msgType,
        uint16 _dstChainId,
        address _toAddress,
        uint _amount,
        uint64 _dstGasForCall,
        bool deliverEth,
        bytes memory payload
    ) public view returns (uint nativeFee, uint zroFee) {
147: function _bridgeWithPayload(
        uint8 msgType,
        uint16 _dstChainId,
        address _toAddress,
        uint _amount,
        uint64 _dstGasForCall,
        bytes memory additionalPayload,
        bool deliverEth
    ) internal {
197: function bridgeWithPayload(
        uint16 _dstChainId,
        address _toAddress,
        uint _amount,
        bool deliverEth,
        uint64 _dstGasForCall,
        bytes memory additionalPayload
    ) public payable {
218: function bridge(
        uint16 _dstChainId,
        address _toAddress,
        uint _amount,
        uint64 _dstGasForCall,
        bool deliverEth // if false, delivers WETH
    ) public payable {
237: function onOFTReceived(
        uint16 _srcChainId,
        bytes calldata,
        uint64,
        bytes32,
        uint _amount,
        bytes memory _payload
    ) external override onlyLzApp {
26: constructor(
        address payable _wethAddress,
        bool gasIsEth,
        address _executor
    ) Owned(msg.sender) {

68 | 73 | 79 | 112 | 147 | 197 | 218 | 237 | 26

File: lib/decent-bridge/src/DecentBridgeExecutor.sol

11: constructor(address _weth, bool gasIsEth) Owned(msg.sender) {

11

</details>

[N-10] Contract/Library Names Not in CapWords Style (CamelCase)

Contracts and libraries should be named using the CapWords style for better readability and consistency with Solidity style guidelines. Examples of good practice include: SimpleToken, SmartBank, CertificateHashRepository, Player, Congress, Owned. More information in Documentation

<details> <summary><i>1 issue instances in 1 files:</i></summary>
File: src/UTB.sol

13: contract UTB is Owned {

13

</details>

[N-11] Leverage Recent Solidity Features with 0.8.23

The recent updates in Solidity provide several features and optimizations that, when leveraged appropriately, can significantly improve your contract's code clarity and maintainability. Key enhancements include the use of push0 for placing 0 on the stack for EVM versions starting from "Shanghai", making your code simpler and more straightforward. Moreover, Solidity has extended NatSpec documentation support to enum and struct definitions, facilitating more comprehensive and insightful code documentation.

Additionally, the re-implementation of the UnusedAssignEliminator and UnusedStoreEliminator in the Solidity optimizer provides the ability to remove unused assignments in deeply nested loops. This results in a cleaner, more efficient contract code, reducing clutter and potential points of confusion during code review or debugging. It's recommended to make full use of these features and optimizations to enhance the robustness and readability of your smart contracts.

<details> <summary><i>11 issue instances in 11 files:</i></summary>
File: src/UTB.sol

2: pragma solidity ^0.8.0;

| Line #2 |

File: src/UTBExecutor.sol

2: pragma solidity ^0.8.0;

| Line #2 |

File: src/UTBFeeCollector.sol

2: pragma solidity ^0.8.0;

| Line #2 |

File: src/bridge_adapters/BaseAdapter.sol

2: pragma solidity ^0.8.0;

| Line #2 |

File: src/bridge_adapters/DecentBridgeAdapter.sol

2: pragma solidity ^0.8.0;

| Line #2 |

File: src/bridge_adapters/StargateBridgeAdapter.sol

2: pragma solidity ^0.8.0;

| Line #2 |

File: src/swappers/SwapParams.sol

2: pragma solidity ^0.8.0;

| Line #2 |

File: src/swappers/UniSwapper.sol

2: pragma solidity ^0.8.0;

| Line #2 |

File: lib/decent-bridge/src/DcntEth.sol

2: pragma solidity ^0.8.13;

| Line #2 |

File: lib/decent-bridge/src/DecentEthRouter.sol

2: pragma solidity ^0.8.13;

| Line #2 |

File: lib/decent-bridge/src/DecentBridgeExecutor.sol

2: pragma solidity ^0.8.0;

| Line #2 | </details>

#0 - raymondfam

2024-01-26T05:41:58Z

Generic findings with most of them already known from the bot(s).

#1 - c4-pre-sort

2024-01-26T05:42:03Z

raymondfam marked the issue as sufficient quality report

#2 - alex-ppg

2024-02-04T22:47:29Z

QA Judgment

The Warden's QA report has been graded B based on a score of 29 combined with a manual review per the relevant QA guideline document located here.

The Warden's submission's score was assessed based on the following accepted findings:

Low-Risk

  • L-01
  • L-03

Non-Critical

  • L-02
  • L-04
  • N-05

#3 - c4-judge

2024-02-04T22:47:32Z

alex-ppg marked the issue as grade-b

#4 - alex-ppg

2024-02-04T22:52:22Z

To note, this submission was tied with #616 and #542 for an A grade per the relevant guidelines shared above. I opted to retain #616 as the A grade report due to its more "manual" nature in comparison to #542 (this report) and #604 which contain a lot of bot-related findings and follow the format of statically generated findings.

Findings Information

🌟 Selected for report: c3phas

Also found by: 0x11singh99, Raihan, dharma09, hunter_w3b, slvDev

Labels

bug
G (Gas Optimization)
grade-a
sufficient quality report
G-03

Awards

191.9635 USDC - $191.96

External Links

Gas Findings

IssueInstancesTotal Gas Saved
[G-01]Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct6-
[G-02]Stack variable is only used once548
[G-03]Optimize require/revert Statements with Assembly123600
[G-04]Use Cached Contracts for Multiple External Calls172576
[G-05]Optimize Gas by Using Only Named Returns14616
[G-06]Use bytes32 in place of string20
[G-07]Use Assembly for Hash Calculations22010
[G-08]Avoid Inverting if-else Conditions26
[G-09]Optimize External Calls with Assembly for Memory Efficiency14457
[G-10]Unlimited gas consumption risk due to external call recipients5-
[G-11]Reduce deployment costs by tweaking contracts' metadata11116600
[G-12]Unused private functions should be removed to save deployment gas10
[G-13]Use revert() to gain maximum gas savings12600
[G-14]Optimize Ether Transfers with receive() Function13585
[G-15]Avoid contract existence checks by using low-level calls474700
[G-16]Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead31198

Gas Findings Details

[G-01] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct

For destinationBridgeAdapter and lzIdLookup used dstChainId as a key in registerRemoteBridgeAdapter() function so it can be combined into a single mapping of an address/ID to a struct to save gas.

<details> <summary><i>4 issue instances in 2 files:</i></summary>
File: src/bridge_adapters/DecentBridgeAdapter.sol

14: mapping(uint256 => address) public destinationBridgeAdapter
16: mapping(uint256 => uint16) lzIdLookup

14 | 16

File: src/bridge_adapters/StargateBridgeAdapter.sol

25: mapping(uint256 => address) public destinationBridgeAdapter
26: mapping(uint256 => uint16) lzIdLookup

25 | 26

</details>

[G-02] Stack variable is only used once

If the variable is only accessed once, it's cheaper to use the assigned value directly that one time, and save the 3 gas the extra stack assignment would spend

<details> <summary><i>5 issue instances in 2 files:</i></summary>
File: src/UTB.sol

/// @audit - `native` variable
287: bool native = approveAndCheckIfNative(instructions, amt2Bridge)
/// @audit - `s` variable
326: ISwapper s = ISwapper(swapper)
/// @audit - `b` variable
335: IBridgeAdapter b = IBridgeAdapter(bridge)

287 | 326 | 335

File: lib/decent-bridge/src/DecentEthRouter.sol

/// @audit - `GAS_FOR_RELAY` variable
96: uint256 GAS_FOR_RELAY = 100000
/// @audit - `gasAmount` variable
97: uint256 gasAmount = GAS_FOR_RELAY + _dstGasForCall
/// @audit - `callParams` variable

96 | 97

</details>

[G-03] Optimize require/revert Statements with Assembly

Using inline assembly for revert statements in Solidity can offer gas optimizations. The typical require or revert statements in Solidity perform additional memory operations and type checks which can be avoided by using low-level assembly code.

In certain contracts, particularly those that might revert often or are otherwise sensitive to gas costs, using assembly to handle reversion can offer meaningful savings. These savings primarily come from avoiding memory expansion costs and extra type checks that the Solidity compiler performs.

Example:

/// calling restrictedAction(2) with a non-owner address: 24042
function restrictedAction(uint256 num)  external {
    require(owner == msg.sender, "caller is not owner");
    specialNumber = num;
}

// calling restrictedAction(2) with a non-owner address: 23734
function restrictedAction(uint256 num)  external {
    assembly {
        if sub(caller(), sload(owner.slot)) {
            mstore(0x00, 0x20) // store offset to where length of revert message is stored
            mstore(0x20, 0x13) // store length (19)
            mstore(0x40, 0x63616c6c6572206973206e6f74206f776e657200000000000000000000000000) // store hex representation of message
            revert(0x00, 0x60) // revert with data
        }
    }
    specialNumber = num;
}
<details> <summary><i>12 issue instances in 8 files:</i></summary>
File: src/UTB.sol

75: require(msg.value >= swapParams.amountIn, "not enough native");

75

File: src/UTBFeeCollector.sol

29: require(signature.length == 65, "Invalid signature length");
54: require(recovered == signer, "Wrong signature");

29 | 54

File: src/bridge_adapters/BaseAdapter.sol

12: require(
            msg.sender == address(bridgeExecutor),
            "Only bridge executor can call this"
        );

12

File: src/bridge_adapters/DecentBridgeAdapter.sol

91: require(
            destinationBridgeAdapter[dstChainId] != address(0),
            string.concat("dst chain address not set ")
        );

91

File: src/bridge_adapters/StargateBridgeAdapter.sol

157: require(
            dstAddr != address(0),
            string.concat("dst chain address not set ")
        );

157

File: src/swappers/UniSwapper.sol

96: require(uniswap_router != address(0), "router not set");

96

File: lib/decent-bridge/src/DcntEth.sol

9: require(msg.sender == router);

9

File: lib/decent-bridge/src/DecentEthRouter.sol

38: require(gasCurrencyIsEth, "Gas currency is not ETH");
43: require(
            address(dcntEth) == msg.sender,
            "DecentEthRouter: only lz App can call"
        );
51: require(weth.balanceOf(address(this)) > amount, "not enough reserves");
62: require(balance >= amount, "not enough balance");

38 | 43 | 51 | 62

</details>

[G-04] Use Cached Contracts for Multiple External Calls

When function makes multiple calls to the same external contract, it is more gas-efficient to use a local copy of the contract. This is because the EVM will cache the contract in memory, and subsequent calls will be cheaper. It's especially true for contracts that are large and/or have many functions.

    // local cache -> 6561 gas
    IToken localCache = storageContract;
    localCache.externalCall();
    localCache.externalCall();

    // direct call 6683 gas
    storageContract.externalCall();
    storageContract.externalCall();
<details> <summary><i>17 issue instances in 4 files:</i></summary>
File: src/UTB.sol

/// @audit function performSwap() make external call of `wrapped` - 2 times
76: wrapped.deposit{value: swapParams.amountIn}();
98: wrapped.withdraw(amountOut);
/// @audit function _swapAndExecute() make external call of `executor` - 2 times
143: executor.execute{value: amountOut}(
153: executor.execute(

76 | 98 | 143 | 153

File: src/bridge_adapters/DecentBridgeAdapter.sol

/// @audit function estimateFees() make external call of `router` - 2 times
56: router.estimateSendAndCallFee(
57: router.MT_ETH_TRANSFER_WITH_PAYLOAD(),

56 | 57

File: lib/decent-bridge/src/DecentEthRouter.sol

/// @audit function _bridgeWithPayload() make external call of `weth` - 2 times
178: weth.deposit{value: _amount}();
181: weth.transferFrom(msg.sender, address(this), _amount);
/// @audit function onOFTReceived() make external call of `weth` - 4 times
266: if (weth.balanceOf(address(this)) < _amount) {
273: weth.transfer(_to, _amount);
275: weth.withdraw(_amount);
279: weth.approve(address(executor), _amount);

178 | 181 | 266 | 273 | 275 | 279

File: lib/decent-bridge/src/DecentBridgeExecutor.sol

/// @audit function _executeWeth() make external call of `weth` - 5 times
30: uint256 balanceBefore = weth.balanceOf(address(this));
31: weth.approve(target, amount);
36: weth.transfer(from, amount);
41: (balanceBefore - weth.balanceOf(address(this)));
44: weth.transfer(from, remainingAfterCall);

30 | 31 | 36 | 41 | 44

</details>

[G-05] Optimize Gas by Using Only Named Returns

The Solidity compiler can generate more efficient bytecode when using named returns. It's recommended to replace anonymous returns with named returns for potential gas savings.

Example:

/// 985 gas cost
function add(uint256 x, uint256 y) public pure returns (uint256) {
    return x + y;
}
/// 941 gas cost
function addNamed(uint256 x, uint256 y) public pure returns (uint256 res) {
    res = x + y;
}
<details> <summary><i>14 issue instances in 4 files:</i></summary>
File: src/UTB.sol

207: function approveAndCheckIfNative(
        BridgeInstructions memory instructions,
        uint256 amt2Bridge
    ) private returns (bool) {
259: function bridgeAndExecute(
        BridgeInstructions calldata instructions,
        FeeStructure calldata fees,
        bytes calldata signature
    )
        public
        payable
        retrieveAndCollectFees(fees, abi.encode(instructions, fees), signature)
        returns (bytes memory)
    {
282: function callBridge(
        uint256 amt2Bridge,
        uint bridgeFee,
        BridgeInstructions memory instructions
    ) private returns (bytes memory) {

207 | 259 | 282

File: src/bridge_adapters/DecentBridgeAdapter.sol

29: function getId() public pure returns (uint8) {
66: function getBridgeToken(
        bytes calldata /*additionalArgs*/
    ) external view returns (address) {
72: function getBridgedAmount(
        uint256 amt2Bridge,
        address /*tokenIn*/,
        address /*tokenOut*/
    ) external pure returns (uint256) {

29 | 66 | 72

File: src/bridge_adapters/StargateBridgeAdapter.sol

40: function getId() public pure returns (uint8) {
60: function getBridgedAmount(
        uint256 amt2Bridge,
        address /*tokenIn*/,
        address /*tokenOut*/
    ) external pure returns (uint256) {
113: function getLzTxObj(
        bytes calldata additionalArgs
    ) private pure returns (IStargateRouter.lzTxObj memory) {
123: function getDstChainId(
        bytes calldata additionalArgs
    ) private pure returns (uint16) {
133: function getSrcPoolId(
        bytes calldata additionalArgs
    ) private pure returns (uint120) {
143: function getDstPoolId(
        bytes calldata additionalArgs
    ) private pure returns (uint120) {

40 | 60 | 113 | 123 | 133 | 143

File: src/swappers/UniSwapper.sol

27: function getId() public pure returns (uint8) {
31: function updateSwapParams(
        SwapParams memory newSwapParams,
        bytes memory payload
    ) external pure returns (bytes memory) {

27 | 31

</details>

[G-06] Use bytes32 in place of string

For strings of 32 char strings and below you can use bytes32 instead as it's more gas efficient

<details> <summary><i>2 issue instances in 2 files:</i></summary>
File: src/bridge_adapters/DecentBridgeAdapter.sol

93: string.concat("dst chain address not set ")

93

File: src/bridge_adapters/StargateBridgeAdapter.sol

159: string.concat("dst chain address not set ")

159

</details>

[G-07] Use Assembly for Hash Calculations

In certain cases, using inline assembly to calculate hashes can lead to significant gas savings. Solidity's built-in keccak256 function is convenient but costs more gas than the equivalent assembly code. However, it's important to note that using assembly should be done with care as it's less readable and could increase the risk of introducing errors.

<details> <summary><i>2 issue instances in 1 files:</i></summary>
File: src/UTBFeeCollector.sol

49: bytes32 constructedHash = keccak256(
50: abi.encodePacked(BANNER, keccak256(packedInfo))

49 | 50

</details>

[G-08] Avoid Inverting if-else Conditions

Inverting the condition of an if-else-statement results in extra gas consumption. Consider simplifying the condition to reduce gas costs.

<details> <summary><i>2 issue instances in 2 files:</i></summary>
File: lib/decent-bridge/src/DecentEthRouter.sol

272: if (!gasCurrencyIsEth || !deliverEth) {
                weth.transfer(_to, _amount);
            } else {
                weth.withdraw(_amount);
                payable(_to).transfer(_amount);
            }

272

File: lib/decent-bridge/src/DecentBridgeExecutor.sol

76: if (!gasCurrencyIsEth || !deliverEth) {
            _executeWeth(from, target, amount, callPayload);
        } else {
            _executeEth(from, target, amount, callPayload);
        }

76

</details>

[G-09] Optimize External Calls with Assembly for Memory Efficiency

Using interfaces to make external contract calls in Solidity is convenient but can be inefficient in terms of memory utilization. Each such call involves creating a new memory location to store the data being passed, thus incurring memory expansion costs.

Inline assembly allows for optimized memory usage by re-using already allocated memory spaces or using the scratch space for smaller datasets. This can result in notable gas savings, especially for contracts that make frequent external calls.

Additionally, using inline assembly enables important safety checks like verifying if the target address has code deployed to it using extcodesize(addr) before making the call, mitigating risks associated with contract interactions.

<details> <summary><i>14 issue instances in 5 files:</i></summary>
File: src/UTB.sol

152: IERC20(tokenOut).approve(address(executor), amountOut);
216: IERC20(bridgeToken).approve(address(bridgeAdapter), amt2Bridge);

152 | 216

File: src/UTBExecutor.sol

61: IERC20(token).transferFrom(msg.sender, address(this), amount);
62: IERC20(token).approve(paymentOperator, amount);
73: uint remainingBalance = IERC20(token).balanceOf(address(this)) -
            initBalance;
80: IERC20(token).transfer(refund, remainingBalance);

61 | 62 | 73 | 80

File: src/bridge_adapters/DecentBridgeAdapter.sol

109: IERC20(bridgeToken).transferFrom(
                msg.sender,
                address(this),
                amt2Bridge
            );
114: IERC20(bridgeToken).approve(address(router), amt2Bridge);

109 | 114

File: src/bridge_adapters/StargateBridgeAdapter.sol

88: IERC20(bridgeToken).transferFrom(msg.sender, address(this), amt2Bridge);
89: IERC20(bridgeToken).approve(address(router), amt2Bridge);

88 | 89

File: src/swappers/UniSwapper.sol

44: IERC20(token).transfer(user, amount);
55: IERC20(token).transfer(recipient, amount);
138: amountOut = IV3SwapRouter(uniswap_router).exactInput(params);
159: amountIn = IV3SwapRouter(uniswap_router).exactOutput(params);

44 | 55 | 138 | 159

</details>

[G-10] Unlimited gas consumption risk due to external call recipients

When calling an external function without specifying a gas limit , the called contract may consume all the remaining gas, causing the tx to be reverted. To mitigate this, it is recommended to explicitly set a gas limit when making low level external calls.

<details> <summary><i>5 issue instances in 2 files:</i></summary>
File: src/UTBExecutor.sol

52: (success, ) = target.call{value: amount}(payload);
65: (success, ) = target.call{value: extraNative}(payload);
70: (success, ) = target.call(payload);

52 | 65 | 70

File: lib/decent-bridge/src/DecentBridgeExecutor.sol

33: (bool success, ) = target.call(callPayload);
61: (bool success, ) = target.call{value: amount}(callPayload);

33 | 61

</details>

[G-11] Reduce deployment costs by tweaking contracts' metadata

The Solidity compiler appends 53 bytes of metadata to the smart contract code which translates to an extra 10,600 gas (200 per bytecode) + the calldata cost (16 gas per non-zero bytes, 4 gas per zero-byte). This translates to up to 848 additional gas in calldata cost. One way to reduce this cost is by optimizing the IPFS hash that gets appended to the smart contract code.

Why is this important?

  • The metadata adds an extra 53 bytes, resulting in an additional 10,600 gas cost for deployment.
  • It also incurs up to 848 additional gas in calldata cost.

Options to Reduce Gas:

  1. Use the --no-cbor-metadata compiler option to exclude metadata, but this might affect contract verification.
  2. Mine for code comments that lead to an IPFS hash with more zeros, reducing calldata costs.
<details> <summary><i>11 issue instances in 11 files:</i></summary>
File: src/UTB.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/UTBExecutor.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/UTBFeeCollector.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/bridge_adapters/BaseAdapter.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/bridge_adapters/DecentBridgeAdapter.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/bridge_adapters/StargateBridgeAdapter.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/swappers/SwapParams.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/swappers/UniSwapper.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: lib/decent-bridge/src/DcntEth.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: lib/decent-bridge/src/DecentEthRouter.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: lib/decent-bridge/src/DecentBridgeExecutor.sol

1: Consider optimizing the IPFS hash during deployment.

1

</details>

[G-12] Unused private functions should be removed to save deployment gas

All private functions that are never used can be safely removed or commented out to save gas.

<details> <summary><i>1 issue instances in 1 files:</i></summary>
File: src/bridge_adapters/StargateBridgeAdapter.sol

100: function getValue(
        bytes calldata additionalArgs,
        uint256 amt2Bridge
    ) private view returns (uint value)

100

</details>

[G-13] Use revert() to gain maximum gas savings

If you dont need Error messages, or you want gain maximum gas savings - revert() is a cheapest way to revert transaction in terms of gas.

    revert(); // 117 gas 
    require(false); // 132 gas
    revert CustomError(); // 157 gas
    assert(false); // 164 gas
    revert("Custom Error"); // 406 gas
    require(false, "Custom Error"); // 421 gas
<details> <summary><i>12 issue instances in 8 files:</i></summary>
File: src/UTB.sol

75: require(msg.value >= swapParams.amountIn, "not enough native")

75

File: src/UTBFeeCollector.sol

29: require(signature.length == 65, "Invalid signature length")
54: require(recovered == signer, "Wrong signature")

29 | 54

File: src/bridge_adapters/BaseAdapter.sol

12: require(
            msg.sender == address(bridgeExecutor),
            "Only bridge executor can call this"
        )

12

File: src/bridge_adapters/DecentBridgeAdapter.sol

91: require(
            destinationBridgeAdapter[dstChainId] != address(0),
            string.concat("dst chain address not set ")
        )

91

File: src/bridge_adapters/StargateBridgeAdapter.sol

157: require(
            dstAddr != address(0),
            string.concat("dst chain address not set ")
        )

157

File: src/swappers/UniSwapper.sol

96: require(uniswap_router != address(0), "router not set")

96

File: lib/decent-bridge/src/DcntEth.sol

9: require(msg.sender == router)

9

File: lib/decent-bridge/src/DecentEthRouter.sol

38: require(gasCurrencyIsEth, "Gas currency is not ETH")
43: require(
            address(dcntEth) == msg.sender,
            "DecentEthRouter: only lz App can call"
        )
51: require(weth.balanceOf(address(this)) > amount, "not enough reserves")
62: require(balance >= amount, "not enough balance")

38 | 43 | 51 | 62

</details>

[G-14] Optimize Ether Transfers with receive() Function

Consider using receive() function instead of a specific deposit() (or similar) function. If there are several functions in the contract that can receive Ether, it is recommended to use receive() for the most frequently used function.

function deposit() external payable { // 5401 gas
    // your logic
}

receive() external payable {  // 5356 gas
    // your logic
}

The receive() or fallback() function can handle incoming Ether transfers directly, providing more gas-efficient way to manage deposits.

<details> <summary><i>13 issue instances in 7 files:</i></summary>
File: src/UTB.sol

108: function swapAndExecute(
        SwapAndExecuteInstructions calldata instructions,
        FeeStructure calldata fees,
        bytes calldata signature
    )
        public
        payable
        retrieveAndCollectFees(fees, abi.encode(instructions, fees), signature)
259: function bridgeAndExecute(
        BridgeInstructions calldata instructions,
        FeeStructure calldata fees,
        bytes calldata signature
    )
        public
        payable
        retrieveAndCollectFees(fees, abi.encode(instructions, fees), signature)
        returns (bytes memory)

108 | 259

File: src/UTBExecutor.sol

19: function execute(
        address target,
        address paymentOperator,
        bytes memory payload,
        address token,
        uint amount,
        address payable refund
    ) public payable onlyOwner

19

File: src/UTBFeeCollector.sol

44: function collectFees(
        FeeStructure calldata fees,
        bytes memory packedInfo,
        bytes memory signature
    ) public payable onlyUtb

44

File: src/bridge_adapters/DecentBridgeAdapter.sol

81: function bridge(
        uint256 amt2Bridge,
        SwapInstructions memory postBridge,
        uint256 dstChainId,
        address target,
        address paymentOperator,
        bytes memory payload,
        bytes calldata additionalArgs,
        address payable refund
    ) public payable onlyUtb returns (bytes memory bridgePayload)

81

File: src/bridge_adapters/StargateBridgeAdapter.sol

69: function bridge(
        uint256 amt2Bridge,
        SwapInstructions memory postBridge,
        uint256 dstChainId,
        address target,
        address paymentOperator,
        bytes memory payload,
        bytes calldata additionalArgs,
        address payable refund
    ) public payable onlyUtb returns (bytes memory bridgePayload)

69

File: src/swappers/UniSwapper.sol

100: function swapNoPath(
        SwapParams memory swapParams,
        address receiver,
        address refund
    ) public payable returns (address tokenOut, uint256 amountOut)
123: function swapExactIn(
        SwapParams memory swapParams, // SwapParams is a struct
        address receiver
    ) public payable routerIsSet returns (uint256 amountOut)
143: function swapExactOut(
        SwapParams memory swapParams,
        address receiver,
        address refundAddress
    ) public payable routerIsSet returns (uint256 amountIn)

100 | 123 | 143

File: lib/decent-bridge/src/DecentEthRouter.sol

197: function bridgeWithPayload(
        uint16 _dstChainId,
        address _toAddress,
        uint _amount,
        bool deliverEth,
        uint64 _dstGasForCall,
        bytes memory additionalPayload
    ) public payable
218: function bridge(
        uint16 _dstChainId,
        address _toAddress,
        uint _amount,
        uint64 _dstGasForCall,
        bool deliverEth // if false, delivers WETH
    ) public payable
302: function addLiquidityEth()
        public
        payable
        onlyEthChain
        userDepositing(msg.value)
322: function addLiquidityWeth(
        uint256 amount
    ) public payable userDepositing(amount)

197 | 218 | 302 | 322

</details>

[G-15] Avoid contract existence checks by using low-level calls

Before version 0.8.10, the Solidity compiler would insert extra code, such as EXTCODESIZE (costing 100 gas), to check the existence of a contract for external function calls. Newer versions, starting from 0.8.10, no longer insert these checks if the external call has a return value. You can achieve similar behavior in earlier Solidity versions by using low-level calls like call. This low-level call don't check for contract existence, saving gas costs.

<details> <summary><i>47 issue instances in 7 files:</i></summary>
File: src/UTB.sol

78: swapInstructions.swapPayload = swapper.updateSwapParams(
83: IERC20(swapParams.tokenIn).transferFrom(
90: IERC20(swapParams.tokenIn).approve(
95: (tokenOut, amountOut) = swapper.swap(swapInstructions.swapPayload);
98: wrapped.withdraw(amountOut);
152: IERC20(tokenOut).approve(address(executor), amountOut);
153: executor.execute(
188: ).getBridgedAmount(amountOut, tokenOut, newPostSwapParams.tokenIn);
194: ]).updateSwapParams(
212: address bridgeToken = bridgeAdapter.getBridgeToken(
216: IERC20(bridgeToken).approve(address(bridgeAdapter), amt2Bridge);
236: IERC20(fees.feeToken).transferFrom(
241: IERC20(fees.feeToken).approve(
327: swappers[s.getId()] = swapper;
336: bridgeAdapters[b.getId()] = bridge;

78 | 83 | 90 | 95 | 98 | 152 | 153 | 188 | 194 | 212 | 216 | 236 | 241 | 327 | 336

File: src/UTBExecutor.sol

61: IERC20(token).transferFrom(msg.sender, address(this), amount);
62: IERC20(token).approve(paymentOperator, amount);
80: IERC20(token).transfer(refund, remainingBalance);

61 | 62 | 80

File: src/UTBFeeCollector.sol

56: IERC20(fees.feeToken).transferFrom(
71: payable(owner).transfer(amount);
73: IERC20(token).transfer(owner, amount);

56 | 71 | 73

File: src/bridge_adapters/DecentBridgeAdapter.sol

56: router.estimateSendAndCallFee(
57: router.MT_ETH_TRANSFER_WITH_PAYLOAD(),
109: IERC20(bridgeToken).transferFrom(
114: IERC20(bridgeToken).approve(address(router), amt2Bridge);
139: IERC20(swapParams.tokenIn).transferFrom(
145: IERC20(swapParams.tokenIn).approve(utb, swapParams.amountIn);
147: IUTB(utb).receiveFromBridge(

56 | 57 | 109 | 114 | 139 | 145 | 147

File: src/bridge_adapters/StargateBridgeAdapter.sol

88: IERC20(bridgeToken).transferFrom(msg.sender, address(this), amt2Bridge);
89: IERC20(bridgeToken).approve(address(router), amt2Bridge);
207: IERC20(swapParams.tokenIn).approve(utb, swapParams.amountIn);
209: IUTB(utb).receiveFromBridge(

88 | 89 | 207 | 209

File: src/swappers/UniSwapper.sol

44: IERC20(token).transfer(user, amount);
55: IERC20(token).transfer(recipient, amount);
83: IERC20(swapParams.tokenIn).transferFrom(
130: .ExactInputParams({
137: IERC20(swapParams.tokenIn).approve(uniswap_router, swapParams.amountIn);
138: amountOut = IV3SwapRouter(uniswap_router).exactInput(params);
150: .ExactOutputParams({
158: IERC20(swapParams.tokenIn).approve(uniswap_router, swapParams.amountIn);
159: amountIn = IV3SwapRouter(uniswap_router).exactOutput(params);

44 | 55 | 83 | 130 | 137 | 138 | 150 | 158 | 159

File: lib/decent-bridge/src/DecentBridgeExecutor.sol

31: weth.approve(target, amount);
36: weth.transfer(from, amount);
44: weth.transfer(from, remainingAfterCall);
60: weth.withdraw(amount);
63: payable(from).transfer(amount);
75: weth.transferFrom(msg.sender, address(this), amount);

31 | 36 | 44 | 60 | 63 | 75

</details>

[G-16] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead. The Ethereum Virtual Machine (EVM) operates on 32 bytes at a time. Therefore, if an element is smaller than 32 bytes, the EVM must use more operations to reduce the size of the element from 32 bytes to the desired size.

Operations involving smaller size uints/ints cost extra gas due to the compiler having to clear the higher bits of the memory word before operating on the small size integer. This also includes the associated stack operations of doing so.

It's recommended to use larger sizes and downcast where needed to optimize for gas efficiency.

<details> <summary><i>31 issue instances in 6 files:</i></summary>
File: src/UTB.sol

21: mapping(uint8 => address) public swappers;
22: mapping(uint8 => address) public bridgeAdapters;

21 | 22

File: src/bridge_adapters/DecentBridgeAdapter.sol

13: uint8 public constant BRIDGE_ID = 0;
17: mapping(uint16 => uint256) chainIdLookup;
30: function getId() public pure returns (uint8) {
96: uint64 dstGas = abi.decode(additionalArgs, (uint64));

13 | 17 | 30 | 96

File: src/bridge_adapters/StargateBridgeAdapter.sol

22: uint8 public constant BRIDGE_ID = 1;
23: uint8 public constant SG_FEE_BPS = 6;
27: mapping(uint16 => uint256) chainIdLookup;
41: function getId() public pure returns (uint8) {
126: ) private pure returns (uint16) {
136: ) private pure returns (uint120) {
146: ) private pure returns (uint120) {
184: uint16, // _srcChainid
        bytes memory, // _srcAddress
        uint256, // _nonce
        address, // _token
        uint256, // amountLD
        bytes memory payload
    ) external override onlyExecutor {

22 | 23 | 27 | 41 | 126 | 136 | 146 | 184

File: src/swappers/SwapParams.sol

5: uint8 constant EXACT_IN = 0;
6: uint8 constant EXACT_OUT = 1;

5 | 6

File: src/swappers/UniSwapper.sol

16: uint8 public constant SWAPPER_ID = 0;
28: function getId() public pure returns (uint8) {

16 | 28

File: lib/decent-bridge/src/DecentEthRouter.sol

17: uint8 public constant MT_ETH_TRANSFER = 0;
18: uint8 public constant MT_ETH_TRANSFER_WITH_PAYLOAD = 1;
20: uint16 public constant PT_SEND_AND_CALL = 1;
24: mapping(uint16 => address) public destinationBridges;
74: uint16 _dstChainId,
        address _routerAddress
    ) public onlyOwner {
81: uint8 msgType,
        address _toAddress,
        uint16 _dstChainId,
        uint64 _dstGasForCall,
        bool deliverEth,
        bytes memory additionalPayload
    )
        private
        view
        returns (
            bytes32 destBridge,
            bytes memory adapterParams,
            bytes memory payload
        )
    {
114: uint8 msgType,
        uint16 _dstChainId,
        address _toAddress,
        uint _amount,
        uint64 _dstGasForCall,
        bool deliverEth,
        bytes memory payload
    ) public view returns (uint nativeFee, uint zroFee) {
149: uint8 msgType,
        uint16 _dstChainId,
        address _toAddress,
        uint _amount,
        uint64 _dstGasForCall,
        bytes memory additionalPayload,
        bool deliverEth
    ) internal {
198: uint16 _dstChainId,
        address _toAddress,
        uint _amount,
        bool deliverEth,
        uint64 _dstGasForCall,
        bytes memory additionalPayload
    ) public payable {
219: uint16 _dstChainId,
        address _toAddress,
        uint _amount,
        uint64 _dstGasForCall,
        bool deliverEth // if false, delivers WETH
    ) public payable {
238: uint16 _srcChainId,
        bytes calldata,
        uint64,
        bytes32,
        uint _amount,
        bytes memory _payload
    ) external override onlyLzApp {
245: (uint8 msgType, address _from, address _to, bool deliverEth) = abi
            .decode(_payload, (uint8, address, address, bool));
253: (uint8, address, address, bool, bytes)
            );

17 | 18 | 20 | 24 | 74 | 81 | 114 | 149 | 198 | 219 | 238 | 245 | 253

</details>

#0 - raymondfam

2024-01-26T16:50:12Z

All findings except G-10 are generically known to complement the bot report.

#1 - c4-pre-sort

2024-01-26T16:50:17Z

raymondfam marked the issue as sufficient quality report

#2 - alex-ppg

2024-02-04T17:56:40Z

No penalization was performed, the following is noted for the Warden:

  • G-07: Inapplicable as the optimization pertains to two fixed 32-byte arguments, will not penalize

#3 - c4-judge

2024-02-04T17:56:43Z

alex-ppg marked the issue as grade-a

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter