/Reach - Diff 1

QuantstampRepository at commit e7306b8

Quantstamp reviewed Reach (/Reach - Diff 1) on 15 Dec 2023: 20 findings, 0 critical; 15 marked fixed, 3 acknowledged, 2 open.

No open critical findings
Open the reportOpen Reach’s security record

Report hosted on certificate.quantstamp.com.

Trustblock does not review code.

Findings20

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

SeverityOpenAcknowledgedFixed
Critical000
High103
Medium121
Low0111
High`ReachDistributionv2` Is Effectively OwnerlessFixed

`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`.

HighFunds Will Get Locked in the `ReachFactory`Fixed

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.

HighSignature Replay Can Lead to Loss of FundsFixed

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.

MediumMissing/limited Test SuiteFixed

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.

LowConfusing Function NameFixed

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

LowCritical Roles Not Transferred Following a Two-Step PatternFixed

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.

LowDead CodeFixed

"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.

LowDistributions Merkle Roots Can Be OverwrittenFixed

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()`.

LowERC-20 Transfer Return Value Is Not ValidatedFixed

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.

LowMissing Input ValidationFixed

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.

Show 5 more
LowOwnership Can Be RenouncedFixed

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.

LowUnable to Compile CodebaseFixed

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.

LowUnlocked PragmaFixed

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`.

LowUnnecessary Public FunctionFixed

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.

LowUsers Cannot Claim Rewards After Accidentally Submitting an Incorrect ProofFixed

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.

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

  • https://github.com/get-reach-dev/smart-contracts/contracts/ReachDistribution.sol at commit e7306b8

Part of Reach’s security record

Reach
Audits2 audits by 1 firmLast audit Jan 2024, by Quantstamp.
Critical findingsNo open critical findingsAcross 2 reports.
Open Reach’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