Quantstamp reviewed Zero Staking on 19 Jun 2024: 10 findings, 0 critical; 6 marked fixed, 4 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 | 2 |
| Medium | 0 | 0 | 2 |
| Low | 0 | 4 | 2 |
A user who unstakes his full staked balance with the flag `exit = true` can lose all his potential rewards because of the following lines: ``` // In StakingERC20.sol if (staker.amountStaked - amount == 0) { delete stakers[msg.sender]; } ``` This effectively deletes the accounting of the rewards owed to the staker, causing the user to lose all his rewards even when `staker.owedRewards` is non-zero. A similar vulnerability exists in `StakingERC721.sol`.
The `StakingERC721.stake()` function allows a malicious staker to exploit the stale `staker.lastUpdatedTimestamp` due to a reentrancy vulnerability. During the staking process, the `_stake()` function calls `_safeMint()`, which calls `checkOnERC721Received()`. This allows the staker to re-enter the staking contract during the minting process. Since the `staker.lastUpdatedTimestamp` is only updated at the end of the `stake()` function, the staker can re-enter the `stake()` or `unstake()` function to trigger multiple `_checkRewards` calculations, thereby unfairly increasing their pending rewards based on the `staker.lastUpdatedTimestamp`. Similar reentrancy risks exist in the `StakingERC20.stake()` function if ERC777 tokens are accepted as staking tokens. The ERC777 token standard was created to extend the capabilities available in ERC20 tokens and one of the features allows the ERC777 token contract to notify the sender and recipient when ERC777 tokens are sent or received, which a malicious staker can use to re-enter the staking function.
If the staked token and reward token are identical, the staked tokens may be incorrectly used to pay out rewards to other users. This occurs because there is no separation between the funds that are staked by users and the tokens that are sent to the contract to distribute as rewards. This lack of separation can lead to a situation where users' staked tokens are depleted to fulfill reward claims. Furthermore, if the staked token and reward tokens are identical, the function `withdrawLeftoverRewards()` can be used to withdraw staked funds.
If a user calls `claim()` too frequently, he may lose out on rewards due to rounding calculation in `_getPendingRewards()`. In particular, when a user claims at times `A` and `B`, where the `B - A` is less than `periodLength`, then the rewards that should have been accumulated (i.e. `(rewardsPerPeriod * staker.amountStaked * ((B - A) / periodLength))` is lost simply due to rounding. Similarly, when a user claims and then stakes within a period's length, the partially accumulated rewards will be lost due to rounding.
The contracts are not designed to accommodate deflationary tokens when accepting token transfers. If a user transfers funds that incur a fee on transfer during order submission, the contract will store that the user has deposited more funds than they sent. This can cause issues during withdrawal or vault deposit.
It is important to validate inputs, even if they only come from trusted addresses, to avoid human error. Specifically, in the following functions arguments of type `address` may be initialized with value `0x0`: Following is the list of places that can potentially benefit from stricter input validation: 1. `StakingERC20`: 1. the pending `rewards` of the `_baseClaim()` should not be zero. 2. the `amount` of the `unstake()` should not be zero. 2. `StakingERC721`: 1. the `tokenIds` length should be greater than zero and the `tokenIds` and `tokenUris` length should match in `stake()` function. 2. the `tokenIds` length should be greater than zero in the `unstake()` function. 3. `Match`: 1. the `matchFee` should be greater than zero in the `startMatch()` function; otherwise the `lockedFunds` will be zero and it will not be possible to end the match. 2. the list of `players` addresses should not contain duplicates; otherwise, a player can be charged `matchFee` more than once. 3. when ending a match via the `endMatch()` function, the authorized caller can specify any amount in `gameFee`. A compromised authorized party can set this value to be as much as 100%, stealing the users’ payouts. Consider adding either a state variable with a fixed and known game fee or add an upper bound validation check. 4. `OwnableOperable`: 1. when adding a new operator, the operator boolean should be previously `false` in the function `addOperator()`. 2. when removing an existing operator, the operator boolean should be previously `true` in the function `removeOperator()`.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (12)
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