Quantstamp reviewed Rari Capital (RariCapital V2) on 20 Aug 2020: 41 findings, 0 critical; 18 marked fixed, 18 acknowledged, 5 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 | 0 | 0 | 3 |
| Medium | 1 | 1 | 4 |
| Low | 4 | 17 | 11 |
The `getRawFundBalance()` function should return the total balance of all RFT holders’ funds and all unclaimed fees of all currencies, in USD. However, the computation on L503 assumes that all currencies are worth 1 USD. This has significant impact on the entire system, including accrued interest, fees, deposits and withdrawals.
There is a typo on L27 of `RariGovernanceToken.sol`, namely 8570000 should be 8750000 according to the comment on L23: "Initializer that reserves 8.75 million RGT for liquidity mining and 1.25 million RGT to the team.". This will conflict with L157 of `RariGovernanceTokenDistributor.sol`: `finalRgtDistribution = 8750000e18`.
The `AggregatorV3Interface private _ethUsdPriceFeed` state variable defined on L234 in `RariGovernanceTokenDistributor.sol` is never initialized (assigned a value). However, it is used in the `getEthUsdPrice` function. This means that the `getEthUsdPrice` will always return `0`, which will affect the Ethereum fund pool of Rari.
The issue is visible in the `rari-yield-pool-contracts` repo, in the `_withdrawFrom` function in `RariFundManager.sol`: 1. L666 computes: `uint256 contractBalance = token.balanceOf(_rariFundControllerContract);` 1. L668-683 iterate over all pools in order to withdraw the remaining balance and add it to `contractBalance` 1. L685 checks: `require(amount <= contractBalance, "Available balance not enough to cover amount even after withdrawing from pools.");` 1. L686 recomputes the same value as on L666 into another variable: `uint256 realContractBalance = token.balanceOf(_rariFundControllerContract);` 1. L709 checks if `realContractBalance < amount ? realContractBalance : amount` and transfers the resulting value. This clearly shows that the following condition is possible: `realContractBalance < amount <= contractBalance`, which would indicate that the amounts withdrawn from the pools in the `for`-loop on L668-683 is discarded.
The array index of the left-hand side member of the assignment in the following code snippet located in `RariFundManager.sol` does not change for any loop iteration and it is out of bounds for the `acceptedCurrencies` array: ``` for (uint256 i = 0; i < _supportedCurrencies.length; i++) if (_acceptedCurrencies[_supportedCurrencies[i]]) acceptedCurrencies[acceptedCurrencies.length] = _supportedCurrencies[i]; ``` Therefore this loop will not fill in all the supported currencies as the function is expected to do and the return values will be incorrect.
Most functions will return a value indicating success or failure. It's important to ensure that every necessary function is checked. Otherwise, the caller just assumes that the function call was successful and continues execution. This is the case for the function call `cErc20.accrueInterest() ` on L49 in `CompoundPoolController.sol`, whose return value is not checked.
If a user upgrades, but is then sent old fund tokens (which seems possible since it's an ERC20), that user cannot upgrade the received tokens. Further, if token transfers from an already updated account occur, the conditional on L69 will never hold, because there will be old tokens in an account that cannot be upgraded (since it was already upgraded). Therefore, `finished` will never be set to true.
The Checks-Effects-Interactions coding pattern is meant to mitigate any chance of other contracts manipulating the state of the blockchain in unexpected and possibly malicious ways before control is returned to the original contract. As the name implied, only after checking whether appropriate conditions are met and acting internally on those conditions should any external calls to, or interactions with, other contracts be done.
There is a cast to `int256` on L515 in the `RariFundManager`, which would cause a large enough unsigned value to be converted to a negative value. However, this is highly unlikely to occur.
The duration of the distribution period is set to `345600` blocks on L152 in `RariGovernanceTokenDistributor.sol`. Assuming that the average block duration over a 60 day period is 15 seconds results in 60 days. However, according to the latest [statistics on Etherscan](https://etherscan.io/chart/blocktime) we foresee an average block duration of 13 seconds, which would reduce the distribution period to 52 days. However, this is also an approximate estimate as the actual duration could be even lower.
The fallback function is meant to only be “called by 0x exchange to refund unspent protocol fee.” However, there are no restrictions/checks in place to guarantee this. This means that anyone could send funds to this contract by mistake.
As described in this [forum update] (https://forum.openzeppelin.com/t/problem-in-upgrades-plugins-for-users-of-unsafeallowcustomtypes/5066), `truffle-upgrades` supports the `unsafeAllowCustomTypes` flag. Due to an implementation error of this flag, older versions might not display some storage layout errors for enum and struct.
The comment on L23 says 20 million tokens will be minted, but on L27 only 10 million are minted.
`distributionEndBlock` is a critical parameter used on the interface for governance distribution `IRariGovernanceTokenDistributor`. This method is only called in `RariFundManager` when the block number is below `distributionEndBlock`. No example implementation of `IRariGovernanceTokenDistributor` is presented, and `distributionEndBlock` must be used there to make this parameter effective.
Smart contracts will often have `owner` variables to designate the person with special privileges to make modifications to the smart contract. In particular: 1. In `RariFundController.sol`, the owner can invoke `setEnzymeComptroller` at any point. 1. In `RariGovernanceTokenUniswapDistributor.sol`, if the owner changes the Uniswap pair address using `setRgtEthUniswapV2Pair`, users that have already deposited may not be able to withdraw.
In the function `withdraw()`, the amount of withdrawal credits is rounded up on L81. It seems that if all users would choose to redeem credits and some would get rounded-up, then the last user to withdraw would fail due to lack of credits.
In `initialize`, it is unclear why `addPool(5)`, which would correspond to Enzyme, is not invoked.
Every Solidity file specifies in the header a version number of the format `pragma solidity (^)0.5.*`. The caret (`^`) before the version number implies an unlocked pragma, meaning that the compiler will use the specified version _and above_, hence the term "unlocked."
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (63)
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