Quantstamp reviewed Impossible Finance on 7 Mar 2024: 13 findings, 0 critical; 8 marked fixed, 5 acknowledged.
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 | 1 |
| Medium | 0 | 0 | 1 |
| Low | 0 | 5 | 6 |
The [Checks-Effects-Interactions](https://solidity.readthedocs.io/en/latest/security-considerations.html#use-the-checks-effects-interactions-pattern) 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. The CEI pattern should be followed in `IFFundable.cash()`.
There are various instances of variable and function naming that can lead to confusion. Here is a non-exhaustive list: - `salePrice`: This variable indicates how many purchase tokens one needs to buy a sale token. Specifically, `salePrice/SALE_PRICE_DECIMALS` is the amount of purchase token needed to buy one sale token. - `saleTokenPurchased`: This variable doesn't keep track of the number of sale tokens purchased, but the number of purchase tokens spent to buy sale tokens. It may be better to name this `totalPaymentReceived`, like in `IFSale.sol`. Note that `totalPaymentReceived` in `IFSale.sol` is tracking the same thing as `saleTokenPurchased` in `IFPurchasable.sol`. - `minTotalPayment`: This variable represents the minimum amount of payment amount one needs to pay to purchase sale tokens (per purchase). It may be more reasonable to name it `minPaymentPerPurchase`. - `maxTotalPurchasable`: Rather than the maximum amount of sale tokens one can purchase, this variable is denominated in payment tokens, representing the number of maximum payment tokens one can use to purchase sale tokens. For example, it may be more reasonable to rename this to `maxTotalPurchasableInPaymentToken`. In general, when dealing with a sell token and purchase token, as well as the relative price between the pair of tokens, having variable names that are consistent and indicative of the token denomination will help improve readability and maintainability. For example, it will be much clearer if we rename `allocation` to `allocationOfSaleToken`.
We want to raise awareness of the fact that in case `IFFixedSale.publicAllocation` is set to a non-zero amount there is no sybil protection, effectively circumventing the whitelist mechanism. When a fixed allocation of the token is purchasable by individual addresses that are not whitelisted via `IFFixesale.whitelistPurchase()` or `IFFixedSale.whitelistPurchaseWithCode()`. So, a savvy user could quickly sweep the funded tokens by calling these functions from many different addresses, getting the fixed allocation for each. Furthermore, an arbitrary amount of codes can be used that way, circumventing the unique code limit for each user set in `maxPromoCodePerUser`.
The `IFFundable.cash()` function uses the following logic to calculate the `principal` variable: ``` // Get the amount of sale token on the contract uint256 saleTokenBal = saleToken.balanceOf(address(this)); // Get the amount of tokens sold uint256 totalTokensSold = getSaleTokensSold(); // Determine principal (the greater of sale amount or amount on contract) uint256 principal = saleAmount < saleTokenBal ? saleTokenBal : saleAmount; ``` The intent, as discussed with the team, is to enable the project team to retrieve whatever is on the contract, excluding the tokens already sold. The `saleTokenBal` accounts for scenarios where there are unexpected transfers of the sale token to the contract, making `saleAmount` an inaccurate reflection of the contract's total sale token balance. A concern arises because `saleToken.balanceOf(address(this))` might not accurately represent the tokens available for withdrawal after users have made withdrawals. Once a withdrawal occurs, the accounting may become inaccurate. We consider the severity of this issue to be relatively low because the locked funds are funds that are not meant to be in the contract in the first place (e.g. accidental transfer).
All privileged permissions should be clearly documented for users. The privileged permissions of the audited contracts are documented below: **IFFixedSale.sol** * Owner * Via `setVestedGiveaway()`, the owner toggle between can making the giveaway a standard fixed payout-giveaway or making it a vested-giveaway, activating the vesting logic for the giveaway. * Via `setPublicAllocation()`, the owner can set the public allocation granted to all users in case users are not on the white list. **IFSale.sol** * Owner * Via `setWithdrawDelay()`, the owner can set an additional withdraw delay after the sale period. * Via `setLinearVestingEndTime()`, the owner can activate and define the terms of the vesting schedule. This can deactivate a previous cliff schedule. * Via `setCliffPeriod()`, the owner can activate and define the terms of the cliff schedule. This can deactivate a previous vesting schedule. **IFVestable.sol** * Owner * Via `setVestingEditable()`, the owner can toggle a value that can enable continuing to switch between the cliff and vesting schedule, even if the withdrawal period has already started. * Via `setLinearVestingEndTime()`, the owner can change and adjust the end time of the vesting schedule, before the withdrawal period or if `vestingEditable` is enabled. * Via `setCliffPeriod()`, the owner can change to and adjust the end time of the cliff schedule, before the withdrawal period or if `vestingEditable` is enabled. **IFFundable.sol** * Owner * Via `setCasher()`, the owner can update the casher address at all times. * Via `setFunder()`, the owner can update the funder address at all times. * Via `setWithdrawDelay()`, before the sale period, the owner can add and adjust an additional withdraw delay, which can not exceed five years. * Via `emergencyTokenRetrieve()`, after the sale period, the owner can withdraw funds from arbitrary tokens which are not the saleToken. * The owner shares all privileges of Casher. This is notable as `cash()` and `cashPaymentToken()` send the funds to `msg.sender`, not the funder address. So the owner could technically front-run the Casher address. * Casher * Via `cash()`, after the sale period, the casher can withdraw all payment token and unsold saleToken. * Via `cashPaymentToken()`, the casher can withdraw up to the whole balance of payment token the contract has received. * Funder * Via `fund()`, the funder can add additional tokens for sale up until the start time of sale. **IFPurchasable.sol** * Owner * Via `setIsPurchaseHalted()`, the owner can pause all purchasing activities. * Via `setMinTotalPayment()`, the owner can set an optional minimum payment a user has to make in order to purchase saleTokens. * Via `setMaxTotalPurchasable()`, the owner can set an optional maximum payment a user can make to limit the amount of purchasable tokens. **IFWhitelistable.sol** * Owner * Via `setWhitelistSetter()`, the owner can set an address to hold the WhitelistSetter-role. * The owner shares all privileges with the WhitelistSetter. * WhitelistSetter * Via `setWhitelist()`, the WhitelistSetter can set and unset the root of the Merkle tree used for the whitelisting feature at all times.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (6)
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