Quantstamp reviewed Volatility Technologies Ltd on 22 Nov 2023: 13 findings, 0 critical; 7 marked fixed, 6 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 | 0 |
| Medium | 0 | 0 | 0 |
| Low | 6 | 0 | 7 |
There are various locations where integer overflow can occur in the codebase, which can lead to inaccurate rewards accounting and unintentional reverts. In particular, we have the following snippet from `_resolveLot()`: ```solidity! if (startPriceTokenA == 0 || d2 > d1) { lot.totalClaimPoolB = (2 * (size - feeAmountPerSide)).toUint128(); winningToken = lot.tokenB; } // If token B is invalid or if token A had a greater increase in price... else if (startPriceTokenB == 0 || d1 > d2) { lot.totalClaimPoolA = (2 * (size - feeAmountPerSide)).toUint128(); winningToken = lot.tokenA; } // Otherwise, in the case of a tie... else { uint128 claim = (size - feeAmountPerSide).toUint128(); lot.totalClaimPoolA = claim; lot.totalClaimPoolB = claim; } ``` It is possible for `(2 * (size - feeAmountPerSide))` to be greater than `2^128`. In which case, casting it back to the `uint128` will lead to an inaccurate accounting of how much the users can claim. This happens when `size` is on the order of `2^128`. However, this is unlikely to happen because `size` equals how many collateral tokens are in the lot, and per discussion with the team, these tokens will likely be stablecoins. Furthermore, it is also possible for the following snippet in `_resolveLot()` to revert: ```solidity! if (!priceFeed.isInvalid(lot.tokenA)) { startPriceTokenA = priceFeed.getHistoricalPrice(lot.tokenA, lot.startTimestamp); resolvePriceTokenA = priceFeed.getHistoricalPrice(lot.tokenA, endTimestamp); } if (!priceFeed.isInvalid(lot.tokenB)) { startPriceTokenB = priceFeed.getHistoricalPrice(lot.tokenB, lot.startTimestamp); resolvePriceTokenB = priceFeed.getHistoricalPrice(lot.tokenB, endTimestamp); } uint256 d1 = resolvePriceTokenA * startPriceTokenB; uint256 d2 = resolvePriceTokenB * startPriceTokenA; ``` Since `priceFeed.getHistoricalPrice()` returns a type of `uint256`, it is possible to overflow in the calculation of `d1` and `d2`, causing unintentional reverts. The likelihood of this happening depends on the results of the price feed and how the Relative Finance team chooses to represent the price in the price feed backend.
Smart contracts will often have `owner` variables to designate the person with special privileges to make modifications to the smart contract. We list the areas where owners have special privileges and the corresponding actions they can take. 1. `owner`: 1. `MultiLot.setIsValidCollateralToken()` 2. `MultiLot.setPriceFeed()` 3. `MultiLot.withdrawFee()` 4. `MultiLot.setFeePercentage()` 5. `MultiLot.pauseContract()` 6. `MultiLot.unpauseContract()` 2. `ORACLE_ROLE` 1. `PriceFeed.store()` 2. `PriceFeed.setInvalid()` 3. `DEFAULT_ADMIN_ROLE` 1. Any role-related modifications in `AccessControl`, inherited by `PriceFeed`.
The `price` mapping maps token strings to timestamp slots that contain the token prices at a given time. The mapping doesn't differentiate between strings like `ETH` and `eth`, allowing for the possibility of having two different prices for the same token.
The correctness of the whole system depends on the data correctness from the price feed, which is maintained by the Relative Finance team off-chain. In particular, the rewards accounting will be most impacted by inaccurate data from the price feed. Furthermore, if, at any point in time, price data is not submitted to the price feed contract (due to benign or malicious reasons), any lot that depends on the price at that timeslot will revert upon calling `_resolveLot()`, which means users will not be able to withdraw their funds from the system. The availability and correctness of the price feed are critical to the health and functionality of the system. While the off-chain part is not in the scope of this audit, we simply want to raise awareness of the reliance on off-chain components and their associated risks.
A "lot" in general supports betting across arbitrary asset classes, as long as the token is supported by the price feed. However, an asset (e.g. NFT) can potentially have a price of `0 ETH` (e.g. starts from `0 ETH` or drops to `0 ETH`). The current code will then revert whenever `PriceFeed.getHistoricalPrice()` is called, which means it will revert whenever a user tries to resolve the lot.
Currently, there are no checks for whether the tokens involved in a "lot" are supported by the price feed. If a user creates a "lot" with invalid tokens (e.g. the tokens `_tokenA` and `_tokenBChoices` passed as parameters to `createLot()` are not supported by the price feed), then, while the players can still withdraw their funds, they will lose a portion of their funds to the fees collected in `_resolveLot()`.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (3)
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