Quantstamp reviewed Reach (/Reach - Diff 1) on 15 Dec 2023: 20 findings, 0 critical; 15 marked fixed, 3 acknowledged, 2 open.
Report hosted on certificate.quantstamp.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 1 | 0 | 3 |
| Medium | 1 | 2 | 1 |
| Low | 0 | 1 | 11 |
`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`.
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.
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.
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.
The function `Reach.rescueETH20Tokens()` suggests it rescues `ETH` but it rescues `ERC20` tokens that are accidentally sent to the contract.
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.
"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.
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()`.
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.
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.
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.
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.
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`.
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.
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.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (1)
236 projects reviewed · 332 reports · latest Oct 2026
See Quantstamp’s auditsSomething 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