Quantstamp reviewed Subscription Token Protocol V2 (Subscription Token - Fabric) on 25 Oct 2023: 9 findings, 0 critical; 5 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 | 1 |
| Medium | 0 | 2 | 2 |
| Low | 0 | 2 | 2 |
A security issue has been identified within the reward redemption and total reward points management system of the platform. This issue is critical because it can result in unfair reward distribution, where users who have accumulated reward points may not receive the correct amount of tokens from the reward pool. The issue arises from the formula used for redemption and the dynamics of total reward points. Analysis: 1. Reward Redemption Formula: - The platform uses a redemption formula to calculate the number of tokens a user receives from the reward pool when redeeming their reward points. - The formula is: `rewardPoolTotal * (userRewardPoints / totalRewardPoints)`. - `rewardPoolTotal` represents the total amount of tokens allocated for rewards (not the current reward pool balance). - `totalRewardPoints` is the sum of reward points in circulation. 2. Total Reward Points Dynamics: - `totalRewardPoints` increases when users purchase subscription time. - `totalRewardPoints` decreases when a user's subscription expires, and their reward points are slashed by other users. - The formula for reward points redemption can potentially fail to accurately distribute rewards when `totalRewardPoints` decreases, leading to funds blocked in the contract. When a user subscription expires, other users can slash and burn those reward points, so `totalRewardPoints` will decrease and users will get a higher share per reward token on the reward pool. `rewardPoolBalance` will not have enough funds for the users, and some withdrawals will revert, locking the funds in the contract.
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 following instance(s) have been identified: <br> * `_refund()` <br> ``` uint256 tokens = balance * _tokensPerSecond; if (balance > 0) { sub.secondsPurchased -= balance; _transferOut(account, tokens); <== External Call } _subscriptions[account] = sub; <== State Change ``` <br> * `_fetchSubscription()` <br> ``` if (sub.tokenId == 0) { require(_supplyCap == 0 || _tokenCounter < _supplyCap, "Supply cap reached"); _tokenCounter += 1; sub = Subscription(_tokenCounter, 0, 0, block.timestamp, block.timestamp, 0, 0, 0); <== This state gets updated in `purchaseTime()` _safeMint(account, sub.tokenId); <== External Call } ```
A scenario where a content creator has lost access to their private keys, and did not set a designated transfer recipient (`_transferRecipient`) will lead to funds locked in the contract. This oversight results in rewards (if activated) and fees not being correctly allocated, causing these funds to remain locked within the contract indefinitely. Rewards and fees are allocated when `withdrawTo()` is executed. Note that this function can only be called by the contract creator. Users will not be able to claim any reward tokens by redeeming their reward points. Although this can be seen as a contract creator's responsibility, final user funds will be locked, potentially harming the protocol's reputation.
Funds can be sent to the contract using the function `selfDestruct`, funds sent this way will not invoke the code inside the `receive()` function.
It is essential to validate inputs, even if they only come from trusted addresses, to avoid human error. The following missing input validations have been identified: * `initialize()` * `params.name`: validate to be a non-empty string. * `params.symbol`: validate to be a non-empty string. * `params.contractUri`: validate to be a non-empty string. * `params.tokenUri`: validate to be a non-empty string. * `mintWithReferralFor()` * `account`: validate to be a non-zero address. * `referrer`: validate to be a non-zero address. * `withdrawTo()` * `account`: validate to be a non-zero address. * `refund()` * `accounts`: validate to be a non-empty list. * `updateMetadata()` * `_contractURI`: validate to be a non-empty string. * `_tokenURI`: validate to be a non-empty string. * `_grantTime()` * `numSeconds`: validate to be a non-zero value. * `createReferralCode()` relies on verifying that `_referralCodes[code].bps` is zero to know if a referral code exists or not. However, it does not revert if `bps` input parameter is zero. This can confuse external systems listening to `ReferralCreated` event. Validate `bps` to be a non-zero value.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (8)
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