Reach security record

Ethereumgetreach.xyz

As of 3 Nov 2026, Reach has 2 audit reports by 1 security firm on Trustblock; the latest is by Quantstamp, dated 31 Jan 2024. Those reports list no open critical findings.

Critical findings
No open critical findings
Across 2 reports.
Open the latest report
Your label, live
Get the label for your site

Record last updated . It holds audits and findings, contracts and incidents up to Nov 2026. Trustblock does not review code.

Audits2

Newest first

AuditSecurity firmDateScopeCritical findingsReport
/Reach - Diff 2Security firm: QuantstampDate: Scope: Files named in the reportNo open critical findingsOpen · certificate.quantstamp.com
/Reach - Diff 1Security firm: QuantstampDate: Scope: Repository at commit e7306b8No open critical findingsOpen · certificate.quantstamp.com

Findings39

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

SeverityOpenAcknowledgedFixed
Critical000
High104
Medium152
Low0323
HighFront Running Allows Claiming Rewards Twice/Reach - Diff 2 · Quantstamp · 31 Jan 2024Fixed

The off-chain system generating the Merkle trees and distributions is out of the scope of this audit, but as stated by the Reach team, non-claimed rewards will be included in the next distribution. An exploitable behavior has been identified where a user can intentionally delay claiming rewards until observing a new `createDistribution()` transaction by the system owner in the mempool. Subsequently, the user can claim their un-claimed rewards before the execution of `createDistribution()` and then claim them again after the distribution creation process.

Open the report
High`ReachDistributionv2` Is Effectively Ownerless/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

`ReachDistributionV2` is deployed by `ReachFactory`, making the `ReachFactory` contract the owner. However, the `ReachDistributionV2` contract is intended to be owned by the user who is creating the Reach server. Furthermore, the `ReachFactory` is not configured to call the guarded `onlyOwner` functions in `ReachDistributionV2`.

Open the report
HighFunds Will Get Locked in the `ReachFactory`/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

The `ReachFactory` implements the `topUp()` and `reserveEthAllocations()`functions, which will transfer user funds to the contract. However, the expected behavior is not defined in the documentation, and there is no way of withdrawing the funds from this contract, thereby locking them forever.

Open the report
HighSignature Replay Can Lead to Loss of Funds/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

The `onlySigned` modifier validates the `_signature` and `_data` has been correctly signed by the `msg.sender`. However, the signatures are not marked as used, allowing them to be replayed at a later date. Additionally, the chain ID is not included either, allowing signatures to be used across chains. This vulnerability allows users to call `topUp()` and `reserveEthAllocation()` with the same signature, allowing them to reset their balances to a higher previous balance which might lead to a loss of funds.

Open the report
MediumDouble Accounting of `commissionAmount` in `swapEth()`/Reach - Diff 2 · Quantstamp · 31 Jan 2024Fixed

In `ReachAffiliateDistribution.swapEth()`, part of the `_ethAmount` is transferred to the main distribution contract. Specifically, `uint256 commissionAmount = (_ethAmount * commission) / 100;` is transferred to the `mainDistribution` contract. However, in the lines that follow, it swaps `_ethAmount` of ETH for `REACH` tokens, without accounting for the `commissionAmount` that was already transferred out, which can lead to serious accounting issues because the contract might not have enough ETH for the swap. ``` router.swapExactETHForTokensSupportingFeeOnTransferTokens{ value: _ethAmount }(_outputAmount, path, address(this), block.timestamp + 5); ``` Furthermore, if the extra ETH amount that is being swapped is part of the user’s distribution, this could block some users from claiming their rewards.

Open the report
MediumMissing/limited Test Suite/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

The project does not implement tests for the new functionalities related to the `ReachDistribution` and `ReachFactory` contracts. Additionally, the test for the `Reach` token contract is very limited, only testing its deployment. Tests may express requirements and are necessary to validate the software's intent.

Open the report
Low'Dead' Code/Reach - Diff 2 · Quantstamp · 31 Jan 2024Fixed

Dead code should be avoided since it might consume more resources during development, testing, and compilation without contributing to the software's functionality. It makes the codebase harder to understand and may mislead developers, leading to potential bugs or wasted time. The reader might also believe that the code is incomplete. In the case of solidity, it might also increase the bytecode size increasing deployment costs. The variable `transferThreshold`, the import `EnumerableSet`, and the data structure `Config` are not used. Additionally, both distribution contracts have the variable `totalEthAllocated` whose goal and functionality are unclear.

Open the report
Low`_ethAmount` Is Not Verified in `createDistribution()`/Reach - Diff 2 · Quantstamp · 31 Jan 2024Fixed

It is unclear why `_ethAmount` is not used in the verification in `createDistribution`. It is likely that `if (address(this).balance < totalEthAllocated)` should be replaced with `if (address(this).balance < _ethAmount)` to ensure that the distributed amount is less than the available balance in the contract. This can potentially lead to an accounting issue. ``` function createDistribution( bytes32 _merkleRoot, uint256 _ethAmount, uint256 _reachAmount ) public onlyOwner { ... if (address(this).balance < totalEthAllocated) revert UnsufficientEthBalance(); ... } ```

Open the report
Low`totalEthAllocated` Logic Is Unclear and Incorrect/Reach - Diff 2 · Quantstamp · 31 Jan 2024Fixed

As discussed with the Reach team, the goal `totalEthAllocated` logic in `ReachAffiliateDistribution` is to avoid allocating funds in distributions that will be used to buy back Reach tokens using `swapEth()`. However, as shown in several issues above, the variable introduced various issues. We concluded that this logic is not designed properly to achieve that goal, and should be revised.

Open the report
LowCode Duplication Hinders Readability and Maintainability of the Codebase/Reach - Diff 2 · Quantstamp · 31 Jan 2024Fixed

Code duplication is detrimental as it leads to the maintenance of the same issues in different locations, and is therefore, error-prone. It makes code harder to understand and increases development time. In the case of solidity, it might also lead to larger bytecode which might hinder the deployment of the system. The contracts `ReachAffliateDistribution.sol` and `ReachMainDistribution.sol` share a lot of the same code. It is advisable to utilize inheritance to facilitate the reuse of code, which will make the codebase easier to maintain in the future.

Open the report
Show 10 more
LowDouble Accounting of Balance in `totalEthAllocated` in `swapEth()`/Reach - Diff 2 · Quantstamp · 31 Jan 2024Fixed

In `swapEth()`, there is this line `totalEthAllocated += address(this).balance - _ethAmount;`. Note that if `_ethAmount` is not equal to `address(this).balance`, the value `totalEthAllocated` will be double counting the contract's balance when `swapEth()` is called multiple times. For example, suppose the contract has a balance of `10 ETH` and `totalEthAllocated = 0`. The owner calls `swapEth()` with `_ethAmount = 1`. This updates `totalEthAllocated` to be `10 - 1 = 9`. The owner again calls `swapEth()` with `_ethAmount = 1`, this updates `totalEthAllocated` to be `9 + (9 -1) = 17`, which is double counting the `8 ETH` balance in the contract across the two `swapETH()` calls.

Open the report
LowMission Credit Price Is Inconsistent with Documentation/Reach - Diff 2 · Quantstamp · 31 Jan 2024Fixed

In the documentation, it mentioned that each mission credit costs `100 REACH` tokens (see [here](https://docs.getreach.xyz/lang/overview/mission-types)). However, in `ReachMainDistribution.sol`, we have `uint256 public creditPrice = 50 ether;`, meaning that each credit costs `50 REACH` tokens.

Open the report
LowOwner Can Set `commission` to Be over `100%`/Reach - Diff 2 · Quantstamp · 31 Jan 2024Fixed

Based on the context of how the commission is used in `ReachAffliateDistribution.sol`, `commission` represents a percentage, which is denominated by `100`. However, in `setCommission()`, we can set the commission to be over `100`, which can lead to unexpected behavior, such as potentially reverting on this line ` payable(mainDistribution).transfer(commissionAmount);` in `swapEth()`.

Open the report
LowOwnership Can Be Renounced/Reach - Diff 2 · Quantstamp · 31 Jan 2024Fixed

If the owner renounces their ownership, all ownable contracts will be left without an owner. Consequently, any function guarded by the `onlyOwner` modifier will no longer be able to be executed.

Open the report
LowPossible Overwrite of `affiliates`/Reach - Diff 2 · Quantstamp · 31 Jan 2024Fixed

The function `deployAffiliateDistribution()` deploys a new `ReachAffiliateDistribution` for the caller, and stores the address in `affiliates[_name]`. However, there is no check that the given name was already used, which would result in the possible overwrite of said mapping. Additionally, since the mapping cannot be edited manually, the initial association cannot be reestablished anymore.

Open the report
LowUnable to Execute Test Suite As-Is/Reach - Diff 2 · Quantstamp · 31 Jan 2024Fixed

The project’s configuration and dependency management are not maintained properly. Specifically, our team is not able to install all the dependencies necessary to compile the project, and therefore, it is not possible to compile and run the tests as-is in a fresh development environment. A proper development framework streamlines setup, ensures consistency, facilitates easier testing, accelerates onboarding, and enhances productivity, significantly improving software quality and project collaboration efficiency.

Open the report
LowUnclear Intention in Uniswap Router Deadline/Reach - Diff 2 · Quantstamp · 31 Jan 2024Fixed

`ReachAffiliateDistribution.swapEth()` calls Uniswap V2 router to swap ETH for REACH tokens. `router.swapExactETHForTokensSupportingFeeOnTransferTokens()` receives a `deadline` parameter that will be verified when the transaction is mined. However, passing a time relative to `block.timestamp` (i.e. `block.timestamp + 5`) does not make sense as `block.timestamp` is set when the transaction is mined, so the deadline will never expire.

Open the report
LowUsing `transfer()` for Eth May Fail/Reach - Diff 2 · Quantstamp · 31 Jan 2024Fixed

The functions `address.transfer()` and `address.send()` assume a fixed amount of gas to execute the call (2300). The use of these functions protects against reentrancy attacks. However, if the recipient of the transfer is a smart contract implementing some logic in the receiving function (`fallback()`, `receive()`), the transfer will fail.

Open the report
LowConfusing Function Name/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

The function `Reach.rescueETH20Tokens()` suggests it rescues `ETH` but it rescues `ERC20` tokens that are accidentally sent to the contract.

Open the report
LowCritical Roles Not Transferred Following a Two-Step Pattern/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

The owner of the contracts inheriting from OpenZeppelin's Ownable can call `transferOwnership()` to transfer the ownership to a new address. If an uncontrollable address is accidentally provided as the new owner address then the contract will no longer have an active owner, and functions with the `onlyOwner` modifier can no longer be executed. While the implementation of `ReachDistributionV2.transferOwnership()` is a good practice (time lock), the risk of transferring it to the wrong address is still possible.

Open the report
Show 9 more
LowDead Code/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

"Dead" code refers to code that is never executed and hence makes no impact on the final result of running a program. Dead code raises a concern, since either the code is unnecessary or the necessary code's results were ignored. Here are some snippets of dead code found in the codebase: 1. `addLiquidity()` is a private function not called in the contract. 2. In `swapAndLiquify()` there is commented-out code. 3. `claimEnabled` is not used which would make its setter function `setClaimEnabled()` also unnecessary. 4. `devWallet` is not used which would make its setter function `setDevWallet()` also unnecessary. 5. `airdropTokens()` can be removed as it is not used and acknowledged by the Reach team. 6. `EnumerableSet` import can be removed from `ReachDistributionV2.sol`. 7. Taxes in `Reach` have two fields: `treasury` and `liquidity`. However, in the protocol, only the total fee (sum of both) is used.

Open the report
LowDistributions Merkle Roots Can Be Overwritten/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

In `ReachDistributionV2`, `setEthDistribution()` and `setReachDistribution()` are public functions protected by the `onlyOwner` modifier. These allow the owner to change the Merkle roots for both `ETH` and `REACH` allocations, breaking the flow of distribution version creation that should be modified only by `createDistribution()`.

Open the report
LowERC-20 Transfer Return Value Is Not Validated/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

Although `Reach.rescueETH20Tokens()` is intended to just rescue ERC-20 tokens sent to the contract by error, it does not validate the return value of the transaction. Most of the ERC-20 implementations return `true` if the transfer was successful, following the ERC-20 standard. However, some tokens do not follow this recommendation (e.g. USDT), causing DoS and locked tokens in some smart contracts.

Open the report
LowMissing Input Validation/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

It is important to validate inputs, even if they only come from trusted addresses, to avoid human error. The following is a non-exhaustive list of functions that should validate their inputs: 1. `ReachFactory.constructor()`: Validate that the `reachToken` is a non-zero address. Since there is no setter for the token address, an incorrect provided value would require a redeployment of the factory. 2. `ReachFactory.setPrices()`: Validate that `_credits` and `_minEthAllocation` are non-zero. 3. `Reach.setSwapTokensAtAmount()`: Validate that the input `amount` lies within a given range with lower and upper values.

Open the report
LowOwnership Can Be Renounced/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

If the owner renounces their ownership, all ownable contracts will be left without an owner. Consequently, any function guarded by the `onlyOwner` modifier will no longer be able to be executed.

Open the report
LowUnable to Compile Codebase/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

The codebase at the confirmed commit hash `9d3b7f1` does not compile. There are missing dependencies in the `package-lock.json` and the `yarn.lock` files.

Open the report
LowUnlocked Pragma/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

Every Solidity file specifies in the header a version number of the format `pragma solidity (^)0.8.*` or `>=0.8.*`. The caret (`^`) or the `>=` before the version number implies an unlocked pragma, meaning that the compiler will use the specified version _and above_, hence the term "unlocked". There are also inconsistencies in the version to be used between files. The files `ReachDistributionV2.sol`, `ReachFactory.sol`, and `Reach` use `>=0.8.19`. The file `IDex.sol` uses a locked pragma but with a different solidity version `0.8.20`.

Open the report
LowUnnecessary Public Function/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

The function `getDeployedDistributions()` returns the state variable`deployedDistributions`. However, said variable is already public and solidity always provides getter functions for all public state variables.

Open the report
LowUsers Cannot Claim Rewards After Accidentally Submitting an Incorrect Proof/Reach - Diff 1 · Quantstamp · 15 Dec 2023Fixed

The function `ReachDistribution.createDistribution()` allows to create a distribution of both `ETH` and `REACH` at the same time. The function `claimRewards()` allows a user to claim rewards only once with the require statement `require(lastClaimedVersion[msg.sender] < currentVersion, "Already claimed for this version")`. However, if a user is supposed to get rewards in both currencies and for some reason, one Merkle proof was provided incorrectly, the user will get one type of reward and will not be able to claim a second time.

Open the report

Contracts

No deployed address is recorded for these audits.

The reports name a repository or files. Open a report to see its scope.

The repositories and files the reports name

Show this on your site

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.

Content

Facts

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.

Fixed as
Language

Style

Layout
Theme
Accent
Corners

Format

Size
Date
Embed as
HTML
<head>
<script src="https://widget.trustblock.run/project-widget.min.js" defer></script>
</head>
<body>
<div
data-tb-project-widget
data-tb-project-slug="reach"
></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