As of 3 Nov 2026, 31Third has 2 audit reports by 1 security firm on Trustblock; the latest is by Quantstamp, dated 4 Apr 2024. Those reports list no open critical findings.
Record last updated . It holds audits and findings, contracts and incidents up to Nov 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| 31Third - Diff - Update | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
| 31Third | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 1 |
| Medium | 0 | 0 | 2 |
| Low | 2 | 5 | 8 |
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.
Open the reportThe 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.
Open the reportThe `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.
Open the reportApplication monitoring can be improved by indexing specific parameters emitted in an event. By indexing event parameters, off-chain systems can more easily filter contract logs by the indexed data. The following event parameters could benefit from indexing: 1. `GenericDifferentSpenderAdapter`: 1. `name`, `handlerAddress`, and `spenderAddress` in the event `GenericDifferentSpenderAdapterDeployed`. 2. `GenericBasicAdapter`: 1. `name` and `handlerAndSpenderAddress` in the event `GenericBasicAdapterDeployed`.
Open the reportCertain changes can be made in order to improve the gas-efficiency of the contracts: 1. Use custom errors instead of `require()`. 2. The two contracts in scope are duplicates. Code duplication is detrimental as it leads to the maintenance of the same issues in different locations, and is therefore, error-prone. In the case of Solidity, it might also lead to larger bytecode which might hinder the deployment of the system. Consider using inheritance or only the `GenericDifferentSpenderAdapter` contract and slightly modify the constructor to manage the case of the `GenericBasicAdapter`.
Open the reportA 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`.
Open the reportWhen 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()`
Open the reportThe `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.
Open the reportIf 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.
Open the report31Third 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.
Open the report`BatchTrade.receiveFees()` does not check if the accrued fees for that asset are not zero. This can lead to zero-amount transfers.
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="31third-1"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
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