FireStarter security record

Polygon (POS)firestarter.fi

As of 3 Nov 2026, FireStarter has 1 audit report on Trustblock, by Quantstamp, dated 11 Sep 2021. That report lists no open critical findings.

Critical findings
No open critical findings
In 1 report.
Open the latest report
Your label, live
Get the label for your site

Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.

Audits1

Newest first

AuditSecurity firmDateScopeCritical findingsReport
FirestarterSecurity firm: QuantstampDate: Scope: Files named in the reportNo open critical findingsOpen · certificate.quantstamp.com

Findings23

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

SeverityOpenAcknowledgedFixed
Critical000
High003
Medium004
Low1015
HighInitialization with multiple owners can be front-runFirestarter · Quantstamp · 11 Sep 2021Fixed

The `initialize()` function takes an array of `owners` as an input parameter and gives all these addresses the `DEFAULT_ADMIN_ROLE`, same role given to the `msg.sender` in the same function. Given that the `initialize()` function does not return a particular value to indicate its successful execution and given that it might be executed by a script together with several subsequent contract calls, the legitimate owner who is making the call might be front-run by a malicious entity who inserts itself as one of the owners, alongside all other owners.

Open the report
HighKYC is not checked in any of the contractsFirestarter · Quantstamp · 11 Sep 2021Fixed

The `UserData` structure has a `isKycPassed` attribute, which is never used in the code. The documentation indicates that: "it should always be true but we have some exceptions for the Firestarter campaign". Therefore, we assume that it should be checked for all depositors in both the private and the public presales.

Open the report
HighUnsafe casts potentially leading to overflowsFirestarter · Quantstamp · 11 Sep 2021Fixed

In L154, L158, L178, L202, L244, L259, L263, L288 of `Staking.sol` unsigned integers are cast/converted to signed integers, potentially leading to overflows, where very large unsigned values may become negative values after the conversion.

Open the report
MediumMultiple owners increases the risk of a hackFirestarter · Quantstamp · 11 Sep 2021Fixed

The `Presale` and `Whitelist` contracts have multiple addresses which may perform privileged function calls. This design decision increases the likelihood of a hack because if ANY of the addresses is compromised, then an attacker could tamper with the presale or whitelist.

Open the report
MediumPublic presale period can be shortenedFirestarter · Quantstamp · 11 Sep 2021Fixed

The intention behind the `presalePeriod` state variable is to have a fixed size period for the public sale, which cannot be shortened even if the presale is paused. However, it is still possible to shorten the public presale period in case the private sale is not ended (by calling `endPrivateSale()`) before the `block.timestamp` passes the value of `startTime`. If this happens the `setStartTime()` function will no longer work and the public presale period would have effectively started, but the `deposit()` function cannot be called for the entire duration of the public presale. It would only be callable after `endPrivateSale()` is called.

Open the report
MediumReentrancy possible when (un)locking tokensFirestarter · Quantstamp · 11 Sep 2021Fixed

The `TokenLock.unlock()` function writes to the `lockInfo.amount` state variable after performing two ERC20 `transfer()` calls on L110 and L117. In the event that the `token` contract would be malicious, the `unlock()` function could be exploited through a reentrancy attack. A similar issues occurs in the `TokenLock.lock()` function where `lockInfo` and `totalLocked` are changed after the call to the `transferFrom()` function of `token`. A less severe issue occurs in the following functions because only events are emitted after external contract calls and no state variables are modified: * `Staking.deposit()` emits `Deposit` after the call to `lpToken.safeTransferFrom()`. * `Staking.emergencyWithdraw()` emits `EmergencyWithdraw` after the call to `lpToken.safeTransfer`. * `Staking.harvest()` emits `Harvest` after the calls to the external `FLAME` contract. * `Staking.withdraw()` emits `Harvest` and `Withdraw` after the calls to the external `FLAME` and `lpToken` contracts. * `Vesting.withdraw()` emits `Withdraw` after the call to `rewardToken.transfer()`. * `Presale.withdrawFunds` emits `WithdrawFunds` after the calls to `fundToken`. * `Presale.withdrawUnsoldToken()` emits `WithdrawUnsoldToken` after the call to `rewardToken.trasferFrom()`.

Open the report
MediumVesting may start before unsold tokens are withdrawnFirestarter · Quantstamp · 11 Sep 2021Fixed

According to the "Campaign Timeline" in the documentation, vesting should only start after the unsold tokens are withdrawn. Otherwise, one of the vesting users could call the `Vesting.withdraw()` function before one of the owners calls `Presale.withdrawUnsoldToken()`. If this were to happen, the `unsoldAmount` computed by the `Presale.withdrawUnsoldToken()` function would be lower than the actual unsold amount and the `projectOwner` would only receive this lower amount. The token difference would be stuck in the `Vesting` contract.

Open the report
LowContract Could Be Left Without OwnershipFirestarter · Quantstamp · 11 Sep 2021Fixed

[`renounceOwnership()`](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/access/OwnableUpgradeable.sol#L59) from OpzenZeppelin allows an actor with the `onlyOwner` role to rennounce the ownership of the contract by setting the owner to the zero address. This would prevent any further functions with `onlyOwner` to be callable.

Open the report
LowDenial-of-service due to integer underflowFirestarter · Quantstamp · 11 Sep 2021Fixed

There is a potential integer overflow on L178 in `Staking.totalRewards()`, in case `totalRewardDebt > flamePerSecond * stakingPeriod`, because the result is cast to `unit256`. This underflow is caught by the `SafeCast.toUint256()` function. However, it would cause an effective denial-of-service to all the functions of the `Staking` contract, which are using this function.

Open the report
LowEvents not emitted on state changeFirestarter · Quantstamp · 11 Sep 2021Fixed

An event should always be emitted when a state change is performed in order to facilitate smart contract monitoring by other systems which want to integrate with the smart contract. This is not the case for the `Vesting.init()` function, which does not emit any event upon a successful change of the `owner` state variable.

Open the report
Show 10 more
LowGas concerns / for-loopsFirestarter · Quantstamp · 11 Sep 2021Fixed

In L54 of `Whitelist.sol` and in L198 of `Presale.sol` for-loops are used to iterate over the length of the user-supplied arrays `owners`. Since the corresponding functions have the `initializer` modifier they can not be called multiple times with smaller sized arrays, effectively denying their service for sufficiently large `owners` arrays. <br><br> Functions `addToWhitelist` and `removeFromWhitelist` contain for-loops which could run out of gas in case the input parameter array length is too large.

Open the report
LowIgnored return valueFirestarter · Quantstamp · 11 Sep 2021Fixed

The following instance of this issue was observed: the call to `IERC20(rewardToken).approve()` is ignored on L140 in `Vesting.init()`. This could lead to unexpected errors when calling other functions that are expecting the `presale` contract to be approved for transferring the `rewardToken`.

Open the report
LowIncorrect accounting for `totalUsers`Firestarter · Quantstamp · 11 Sep 2021Fixed

In L91 of `Whitelist.sol`, if the provided `users` array contains already whitelisted users, the update of `totalUsers` will be incorrect, in that it will incorrectly count them as also being new, resulting in `totalUsers` being larger than the actual number of whitelisted users.

Open the report
LowIncreased loss of precisionFirestarter · Quantstamp · 11 Sep 2021Fixed

Integer division truncates the result, which leads to a loss of precision. When division is followed by a multiplication, this loss of precision is further increased. Two instances of this issue were observed in the `Vesting.vested()` function: 1. The division on L260: `.div(accuracy)` is performed before the multiplication of the result (i.e. `unlockAmountPerInterval`) on L262. 2. The division on L262: `.div(releaseInterval)` is performed before the multiplication on L262: `.mul(unlockAmountPerInterval)`.

Open the report
LowLocal variable shadowingFirestarter · Quantstamp · 11 Sep 2021Fixed

The `name` and `symbol` input parameters of the `CustomToken.constructor` are shadowing the state variables of the `ERC20` contract that `CustomToken` inherits from. As a result the use of these local variables might be incorrect.

Open the report
LowObsolete checkFirestarter · Quantstamp · 11 Sep 2021Fixed

The `startPresale()` function uses the `whileDeposited` modifier which checks: ``` modifier whileDeposited() { require( _getDepositedRewardTokenAmount() >= initialRewardAmount, "Deposit enough rewardToken tokens to the vesting contract first!" ); _; } ``` Therefore, the check on L276-279 is obsolete (superseded): ``` require( _getDepositedRewardTokenAmount() != 0, "startPresale: Please deposit rewardToken tokens to vesting contract first!" ); ```

Open the report
LowPrivileged roles and ownershipFirestarter · Quantstamp · 11 Sep 2021Fixed

Certain contracts have state variables, e.g. `owner`, which provide certain addresses with privileged roles. Such roles may pose a risk to end-users. Any of the owners of the `Presale` contract may perform the following privileged actions: 1. End the private sale at any point in time. 2. Set the start time of the public sale to any point in the future as long as the public sale has not started yet. 3. Start the public sale at any point in time as long as it has not started yet AND the private sale has ended AND there are sufficient reward tokens deposited in the vesting contract. 4. Pause the public sale at any point in time. 5. Resume the public sale if it is paused. 6. Withdraw the service fee amount, after the public sale has ended, to any address they feed in as the `treasury` parameter. 7. Withdraw the fund tokens and the unsold reward tokens to the `projectOwner` address after the public sale has ended. The owner of the `Vesting` contract may perform the following privileged actions: 1. Add/update vesting recipients to any amount greater than zero at any time, as long as the `totalVestingAmount <= depositedAmount`. NOTE that this may be done before `Vesting.init()` is called. 2. Set the presale contract to any address at any point in time. 3. Set the start time to any point in the future as long as vesting has not already started and `Vesting.init()` has not yet been called. The owner of the `Staking` contract may perform the following privileged actions: 1. Set the early withdrawal time to any amount, at any point in time. 2. Set the staking info before staking starts. 3. Set the flame per second to be distributed at any point in time.

Open the report
LowTwo different versions of `IERC20.sol`Firestarter · Quantstamp · 11 Sep 2021Fixed

The installed project contains 2 different instances of `IERC20.sol`: * One from the `openzeppelin` contracts package * One custom version inside the `interfaces/` sub-directory. The only difference between these 2 versions is that the latter version contains 4 additional functions w.r.t. the former version: * `function decimals() external view returns (uint8);` * `function symbol() external view returns (string memory);` * `function name() external view returns (string memory);` * `function getOwner() external view returns (address);` It is not recommended to have any contract or interface with the same name in the same build, because it creates confusion since some of the contracts import one file and some the other. Moreover, the last 3 functions in the enumeration above do not seem to be called by any function in the repository.

Open the report
LowUnclear maximum allocationFirestarter · Quantstamp · 11 Sep 2021Fixed

The following check in `Presale.deposit` is unclear: ``` require( maxAlloc + privateSoldFunds[user] >= newFundBalance, "Deposit: Can't exceed the maxAlloc!" ); ``` The comment above the `maxAlloc` declaration in `Whitelist.sol` indicates that it represents: "Max allocation for this user". However, according to the check above it only represents the maximum allocation for the public sale. If it were the absolute maximum allocation, then the check inside `Presale.deposit` should be: `maxAlloc >= newFundBalance`.

Open the report
LowUninitialized state variablesFirestarter · Quantstamp · 11 Sep 2021Fixed

State variables should always be initialized before they are used, unless they are explicitly stated (in the comments) to be initialized to the default value, e.g. `0x0` for `address`. The following instances of this issue have been observed: 1. `Presale.privateSoldAmount` is never initialized. It is used in `Presale.withdrawUnsoldToken()` 2. `Presale.publicSoldAmount` is never initialized. It is used in `Presale.withdrawUnsoldToken()` 3. `Presale.privateSoldFunds` is never initialized. It is used in `Presale.deposit(uint256)` 4. `Presale.recipients` is never initialized. It is used in `Presale.deposit(uint256)` and `ProjectPresale.depositPrivateSale(uint256)` 5. `Presale.participants` is never initialized. It is used in `Presale.participantCount()` and `Presale.getParticipants(uint256,uint256)`

Open the report
Show 2 more
LowUnlocked pragmaFirestarter · Quantstamp · 11 Sep 2021Fixed

The version of the Solidity defined in the code is not fixed. This can lead to unintended or unexpected behaviours in the implementation due to different compiler versions between what is intented in the code and the output at compile time.

Open the report
LowWrong event emitted in `setEarlyWithdrawal`Firestarter · Quantstamp · 11 Sep 2021Fixed

In L107 of `Staking.sol` of function `setEarlyWithdrawal` the event `LogFlamePerSecond` is emitted, however event `LogEarlyWithdrawal` exists and seems should've been used instead.

Open the report

Contracts

No deployed address is recorded for these audits.

The reports name a repository or files. Open a report to see its scope.

The repositories and files the reports name

Show this on your site

Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.

Content

Facts

Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.

Fixed as
Language

Style

Layout
Theme
Accent
Corners

Format

Size
Date
Embed as
HTML
<head>
<script src="https://widget.trustblock.run/project-widget.min.js" defer></script>
</head>
<body>
<div
data-tb-project-widget
data-tb-project-slug="firestarter"
></div>
</body>

Paste the code where the label should appear. It uses no API credit and updates by itself.

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