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 |
Suppose `A` is an address controlled by an owner (or operator). An owner (or operator) can create a match with high match fees and have one of the players being `A` and other players as victims. The owner (or operator) can then call `endMatch()` where the payout to `A` is the total of all the match fees from each player and every other player gets zero payouts, effectively stealing funds from all the other players. Per communication with the team, the operators are addresses owned by the team, not some third parties, so the risk here is considered acceptable.
In `StakingBase.sol`: `Owner`: - The owner can steal all the rewards (not just the leftovers) by using `withdrawLeftoverRewards()`. In `Escrow.sol`: `Owner and Operators`: - Can force sending funds back to users by calling `releaseFunds()`. In `Match.sol`: `Owner and Operators`: - Can start and end a match by calling `startMatch()` and `endMatch()`. Note that ZS-5 allows an owner or operator to steal all users' funds in the Escrow contract. In `OwnableOperable.sol`: `Owner`: - Can add and remove operators by calling `addOperator()`, `addOperators()`, and `removeOperator()`. - Can change and revoke ownership through `Ownable`. In `StakingERC721.sol`: `Owner`: - Can set the tokenURI and base tokenURI by calling `setTokenURI` and `setBaseURI` respectively.
A user can only claim rewards from the contract if there are sufficient reward tokens in the staking contract. However, this heavily relies on the owner to provide funds to the contract to cover any user's claims. There may be times when there won't be sufficient funds in the contract to cover all user's claims.
Since the unlock timestamp is never updated after the initial staking (see below in `_checkRewards()`), a user can game the system by staking an initial small amount (e.g. 1 token) to get an early `unlockTimestamp` and stake more as needed in the future. ``` function _checkRewards(Staker storage staker) internal { if (staker.amountStaked > 0) { // It isn't their first stake, snapshot pending rewards staker.owedRewards = _getPendingRewards(staker); } else { // Log the time at which this stake becomes claimable or unstakable // This is only done once per user staker.unlockTimestamp = block.timestamp + timeLockPeriod; } } ``` Along the same line of reasoning, users could unstake all but 1 `wei` to bypass any unlock period in their next staking operation since the corresponding entry in the `stakers` mapping is not deleted.
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