31Third

QuantstampFiles named in the report

Quantstamp reviewed 31Third on 1 Mar 2023: 15 findings, 0 critical; 9 marked fixed, 4 acknowledged, 2 open.

No open critical findings
Open the reportOpen 31Third’s security record

Report hosted on certificate.quantstamp.com.

Trustblock does not review code.

Findings15

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

SeverityOpenAcknowledgedFixed
Critical000
High001
Medium002
Low246
HighUser Can Pass Any CalldataFixed

31Third is highly dependant on external exchanges. As external exchanges can add or remove functionalities, the 31Third team should be very careful when adding new exchanges and stay updated about possible vulnerabilities in these systems. <br><br> When users call `BatchTrade.batchTrade()`, they can pass call data for a swap that differs from what is specified in the `Trade` struct. For example, if a user is trading using the 0x adapter, the actual call data sent to the 0x contract could target any available function. This could result in the user being able to call unintended functions in external contracts that could in turn drain `BatchTrade` of any funds held within.

MediumArbitrary Call Data to Exchange Can Circumvent Fee CollectionFixed

The batch trade functionality builds upon the 0x exchange in order to perform each trade in the batch. The 0x protocol supports the submitter of the order to be different from the actual taker in the case of both [limit orders](https://protocol.0x.org/en/latest/basics/orders.html#limit-orders) and [RFQ orders](https://protocol.0x.org/en/latest/basics/orders.html#rfq-orders). Since the `batchTrade` function allows the user to pass arbitrary call data for the 0x swap, the user can easily specify within the order that the `taker` address which receives the result from the trade is different from the `spender`. Since the attacker can specify their own address as the `taker` they can receive the result of the trade directly and avoid paying the fees. In order to subvert the token balance checks within the `BatchTrade` contract, the attacker can use their own custom attack tokens as described in the attack scenario.

MediumInflation Of Approvals Through Failed TradesFixed

The `BatchTrade.batchTrade()` function might not revert on a failed trade if `_batchTradeConfig.revertOnError ` is set to `false`. This lack of reversion may result in some undesirable state changes persisting. Of particular concern is that token approvals to the exchange spender contract are not reset. During a trade, the function `_claimAndApproveFromToken()` is called, which transfers the input tokens for the swap, as well as approves the exchange spender to spend these tokens as shown below: ``` IERC20(_trade.from).safeIncreaseAllowance( _exchangeAdapter.getSpender(), _trade.fromAmount ); ``` The approval above is not reversed if the trade fails and could potentially be exploited in subsequent transactions. Furthermore, failed trades could be repeatedly executed in order to repeatedly increase the allowance above without resetting it.

LowAmount Received Is Lower than `Trade.minToReceive`Fixed

A trader might reasonably expect that they would receive at least `Trade.minToReceive` from a trade. However, the actual minimum amount received by the user is actually `Trade.minToReceive - fees`. This is because the check involving `Trade.minToReceive` is performed immediately after the trade, as shown below: ``` if (toBalanceAfterTrade < toBalanceBeforeTrade + _trade.minToReceive) { revert NotEnoughReceived(_trade, _trade.minToReceive, toBalanceAfterTrade - toBalanceBeforeTrade); } ``` The fees are then subtracted from the amount received from the swap, leaving the resulting amount to be potentially less than `Trade.minToReceive`.

LowIncorrect Use of ERC-20 ApprovalsFixed

When transferring ERC20 tokens, approvals are only needed when the caller of the transfer is **not** the source address of the transfer. The following instances incorrectly use `IERC20.safeIncreaseAllowance()` to approve `BatchTrade` for transferring tokens out of `BatchTrade`: 1. `receiveFees()` 2. `_approveAndReturnToken()` 3. `_returnClaimedOnError()`

LowInflationary/Yield Bearing Tokens Are Locked ForeverFixed

The `BatchToken` contract collects fees as a portion of the tokens received for each trade. The contract imposes no restrictions as to which tokens can be traded. Thus it is quite possible that inflationary or yield-bearing tokens could be collected as fees. One example of an inflationary token is `stETH`. If `stETH` is collected as fees for the contract, the contract's balance of `stETH` will grow over time, even if no more fees in `stETH` are collected. However, the increased balance will not be extractible since the fee amount is recorded at the time the fee was collected and stored in the contract, as shown below: ``` feeAmount = (_receivedAmount * feeBasisPoints) / 10000; if (feeAmount > 0) { accruedFees[_token] += feeAmount; } ``` Thus the `stETH` yield will remain locked in the contract forever.

LowOwnership Can Be RenouncedFixed

If the owner renounces their ownership, all contracts inheriting from `Ownable` will be left without an owner. Consequently, any function guarded by the `onlyOwner` modifier will no longer be able to be executed. <br><br> Additionally, if ownership were to be revoked, the contracts would lose crucial functionality such as activating or deactivating the batch trading, modifying fees, feeless wallets, and fee recipients, as well as modifying the adapters used in the batch trading.

LowTrades Can Be Executed with a Zero Value for `_minToReceive`Fixed

31Third relies on the 0x protocol for zero slippage. However, in the instance where an erroneous `Trade` struct is passed to `batchTrade()`, with a `_minToReceive` specified as zero, the trade will still execute normally. This could potentially lead to a loss of user funds, as `from` tokens will be received, but not `to` tokens.

LowZero Amount Transfers when Claiming FeesFixed

`BatchTrade.receiveFees()` does not check if the accrued fees for that asset are not zero. This can lead to zero-amount transfers.

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 (5)

  • contracts/trading/ExchangeAdapterRegistry.sol
  • contracts/trading/IExchangeAdapter.sol
  • contracts/trading/adapter/WETHAdapter.sol
  • contracts/trading/BatchTrade.sol
  • contracts/trading/adapter/ZeroExExchangeAdapter.sol

Part of 31Third’s security record

31Third
Audits2 audits by 1 firmLast audit Apr 2024, by Quantstamp.
Critical findingsNo open critical findingsAcross 2 reports.
Open 31Third’s security record

Security firm

Quantstamp

236 projects reviewed · 332 reports · latest Oct 2026

See Quantstamp’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