Nex Labs Cross Chain

QuillAuditsFiles named in the report

Audit · firm’s term: audit report

QuillAudits reviewed Nex Labs Cross Chain on 15 Jan 2025: 16 findings, 0 critical; all marked fixed.

No open critical findings
Open the reportOpen Nex Labs Cross Chain’s security record

Report hosted on quillaudits.com.

Trustblock does not review code.

Findings16

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High003
Medium007
Low006
HighExcess tokens will be lost to CCIP due to high gas limit setFixed

The gasLimit specifies the maximum amount of gas CCIP can consume to execute ccipReceive() on the contract located on the destination blockchain. It is the main factor in determining the fee to send a message. Unspent gas is not refunded.

HighIncorrect BPS fee calculation in redemption leads to 30% fee Instead of 0.3%Fixed

The IndexFactory contract implements a fee system for token redemptions using basis points (BPS). The fee calculation is performed in the _handleReceivedRedemption() function when processing cross-chain redemption requests. However, there is a critical error in how the swap fee is handled. In the contract, the fee rate is documented and used elsewhere as: uint8 public feeRate; // 10/10000 = 0.1% However, in _handleReceivedRedemption(), the swap fee is hardcoded to 3000 This leads to a fee of 3000 basis points (30%) being charged instead of the expected 0.3% (30 basis points), representing a 100x increase in the fee rate compared to what users would expect based on the documentation and other parts of the contract.

HighSwapHelpers::swap() always uses UniswapV3 because of incorrectly checked _swapFee parameterFixed

The swap() function in IndexFactory.sol is intended to support both Uniswap V3 and V2 swaps based on the _swapFee parameter. However, due to input validation requirements in multiple functions, the code always executes the V3 swap path, making the V2 swap functionality unreachable. The issue stems from several functions requiring _swapFee > 0 in their validation:<br>1. issuanceIndexTokens() requires _tokenInSwapFee > 0<br>2. redemption() requires _tokenOutSwapFee > 0<br>3. _handleReceivedRedemption() hardcodes fee as 3000<br>These requirements force the swap() function to always take the V3 path since the SwapHelpers.swap() implementation using logic in the image This means the V2 fallback path is never taken, potentially leading to failed swaps if:<br>1. The token pair doesn't exist on Uniswap V3<br>2. The V3 pool has insufficient liquidity<br>3. The specified fee tier doesn't exist for the pair<br>

MediumIndexFactoryStorage truncates uint256 to uint128 in getAmountOut() leading to potential value lossFixed

getAmountOut() accepts a uint256 parameter amountIn but silently truncates it to uint128 when calling estimateAmountOut(), which could lead to incorrect calculations for large token amounts. In IndexFactoryStorage.sol, the getAmountOut() function is used to calculate expected output amounts for token swaps and is a critical component for determining portfolio balances. This truncation could lead to incorrect calculations if the input amount exceeds type(uint128).max, particularly when calculating large portfolio balances through getPortfolioBalance().

MediumMissing ‘disableInitializers` in Proxy Upgradable Contract ConstructorsFixed

The failure to disable initializers at the constructor level of the proxy upgradeable contract raises concern as it is susceptible to exploit. This oversight presents the potential of attackers initializing the implementation contract itself.

MediumMissing whenNotPaused Modifier on critical entry pointsFixed

These contracts implement the pausable functionality that prevents users from calling important en try points in time of emergency. In the IndexFactory, the internal functions (_pause and _unpause) of the Pausable contract were appropriately invoked and the external functions had onlyOwner modifier. However, the implementation is incomplete as the “whenNotPaused” modifier was not attached to any critical functions. This implies that in time of emergency when the admin calls the pause function, the activity of the contract does not exactly pause. Users will proceed with their activity

MediumSandwich attack possibilityFixed

It was noticed that the minAmountOut is set to zero during interactions with the AMM router (Uniswap) for swaps. These parameters are intended to protect against slippage; setting them to zero exposes transactions to slippage, potentially resulting in receiving or adding smaller amounts than intended.

MediumswapV2 will always revert for ordinary tokens that have no fee-on-transfer.Fixed

Identical to swapExactTokensForTokens, but succeeds for tokens that take a fee on transfer <a href="https://docs.uniswap.org/contracts/v2/reference/smart-contracts/router-02#swapexacttokensfor" target="_blank">https://docs.uniswap.org/contracts/v2/reference/smart-contracts/router-02#swapexacttokensfor</a> tokenssupportingfeeontransfertokens

MediumValidate that incoming Chainlink price data is not staleFixed

The Index smart contract integrates Chainlink oracle for price derivation. However, when it fetches the price of a token, it fails to check that the price value is not stale as it is possible for an attacker to manipulate this.

Mediumwner can mint tokens above the supply ceiling as this check does not exist in mintToFeeReceiver()Fixed

The mint function of the IndexToken contract has 5 require statements to check for address validity and the supply cap being less or equal to the supplyCeiling, however mintToFeeReceiver() function does not have a similar check for token supply minted. This would make the owner able to mint new tokens above the supplyCeiling set and also cause a DOS the next time an address with MINTER role calls mint() because totalSupply() + amount would already have exceeded the supplyCeiling. require(totalSupply() + amount <= supplyCeiling, "will exceed supply ceiling"); If the owner address gets hijacked at any point, the compromised account would be able to mint new tokens without any restrictions leading to economic loss for legitimate users of the protocol.

Show 6 more
LowCrosschain fee parameter is unused in issuance and redemption functionsFixed

The _crossChainFee parameter in issuanceIndexTokens() and redemption() functions is accepted but never used in the actual fee calculations or token transfers, making it a redundant parameter that could mislead users and integrators. Although it doesn't affect the security or functionality of the contract, it creates confusion for users who may expect cross-chain fees to be charged when they are not. This could lead to integration issues or user confusion.

LowExcess ETH will be stuckFixed

Users are allowed to pass in any amount of ETH via msg.value which could be much greater than the needed input amount, and when this happens there would be no way to process a refund

LowMinting tokens to the feeReceiver becomes very expensive if not done frequentlyFixed

The current arithmetic implementation for minting tokens to the fee receiver is heavily gas intensive because it performs the arithmetic for compounding with a loop. This would become considerably expensive when the number of days grows to a large number. With current tests, it costs ~500k gas units to call this function when 300 days have passed from the initial feeTimestamp value. Conversely, using a logarithmic approximation for compounding is just as precise with ~80% gas savings

Lowreceive() function causes inconsistency across multiple contractsFixed

The in-line documentation of the IndexFactory states that users would not be able to pass in external funds via the receive() function but the code implementation in receive() allows for ETH to be sent into the contract without interacting with a payable function. Whereas, the IndexFactoryBalancer contract cannot receive ether which renders the withdraw func tion useless as no other function is marked payable to allow ETH deposits. The withdraw function cannot be called since no ether enters the contract.

LowsetFeeRate requires more time than 12 hours to reset fee rateFixed

The setFeeRate is conditioned to be called after 12 hours from the last time of update. After 12 hours and 59 minutes, it’s impossible to reset the fee rate until 13 hours.

LowUnused state variables and functionsFixed

In the codebase, Some Variables,functions and Imports are not used<br>

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (26)

  • SwapHelpers.sol
  • factory/IndexFactoryStorage.sol
  • librares/MessageSender.sol
  • chainlink/ConfirmedOwner.sol
  • Sender.sol
  • IndexToken.sol
  • IndexFactoryStorage.sol
  • IndexFactory.sol
  • proposable/ProposableOwnableUpgradeable.sol
  • chainlink/FunctionsClient.sol
  • factory/IPriceOracle.sol
  • vault/CrossChainFactory.sol
  • factory/IndexFactoryBalancer.sol
  • factory/OrderManager.sol
  • vault/Vault.sol
  • IndexfactoryStorage.sol
  • chainlink/ConfirmedOwnerWithProposal.sol
  • proposable/ProposableOwnable.sol
  • factory/PriceOracle.sol
  • factory/IndexFactory.sol
  • token/IndexToken.sol
  • libraries/SwapHelpers.sol
  • tory.sol
  • ccip/CCIPReceiver.sol
  • libraries/FeeCalculation.sol
  • IndexFactoryBalancer.sol

Part of Nex Labs Cross Chain’s security record

Nex Labs Cross Chain
Audits3 audits by 1 firmLast audit Jan 2025, by QuillAudits.
Critical findingsNo open critical findingsAcross 3 reports.
Open Nex Labs Cross Chain’s security record

Security firm

QuillAudits

350 projects reviewed · 459 reports · latest Apr 2025

See QuillAudits’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us