As of 30 Oct 2026, Decentric has 1 audit report on Trustblock, by Resonance Security, dated 26 Jun 2024. That report lists no open critical findings.
Record last updated . It holds audits and findings, contracts and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Decentric | Security firm: Resonance Security | Date: | Scope: Not recorded (see the report) | 1 critical finding, fixed | Open · IPFS |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 1 |
| High | 0 | 0 | 3 |
| Medium | 0 | 0 | 5 |
| Low | 0 | 0 | 8 |
The function runLottery() is used to make a predetermined amount of winners out of an array of contestants. After performing necessary validation checks, it uses prefetched random numbers from Chainlink VRF to determine whether each individual contestant is a winner of the lottery for the specific tier. In essence, the function performs two important loops:On the first loop iterating through the contestants array, the function calculateResult() is called multiple times for the array's length. This function randomly determines whether the contestant is a winner. The variable numberOfWinnersChosen is incremented every time a winner is selected.On the second loop, the array of contestants is traversed in reverse order from the first loop. This loop only occurs if there were not enough winners selected on the first loop. Instead of calling the function calculateResult() again, contestants are automatically assigned a win if they were not winners yet. This guarantees that the function runLottery() will always yield the necessary amount of winners in order to return successfullyWhile not immediately perceivable, the previous logic introduces two major flaws that hamper the fairness of the lottery process:The result of the call to the function calculateResult() on the first loop is random and is an independent probabilistic event. This means that with an array of contestants of the same size as the number of expected winners, it is only natural that not all contestants will be selected as winners on the first loop. However, on the opposite side, if the array of contestants is very large in comparison to the number of expected winners, only the initial portion of the array will most likely yield winners, since once the expected number of winners is reached, the loop will break. Added to the fact that the addresses of contestants are numerically sorted, contestants with lower addresses will have a higher change of winning than their peers with higher addresses.Circling back to the scenario where the array of contestants is nearly the same size as the number of expected winners, when not enough winners are chosen on the first loop, the second loop will not calculate winners based on randomness. It will simply assign winners to contestants who are not winners yet, and it does so in reverse order. This means that higher addresses are more likely to simply win the lottery where lower addresses had to go through the random lottery process.Essentially, whenever the size of the array of contestants is smaller, more winning probability is given to higher addresses, whereas whenever the size of the array of contestants is bigger, lower addresses have more probability of winning.It should also be noted that, users may somewhat influence their own addresses prior to be assigned as contestants, e.g. keep creating new EOAs or contract addresses to serve their specific purpose as contestants.
Open the reportThe function calculateResult() is a helper function that computes whether an individual contestant should be a winner or a loser on the lottery process. This function uses a random number prefetched from Chainlink VRF and encodes it into a keccak256 hash along with other input parameters. One of these parameters is block.timestamp, which can be manipulated by either, the owner or manager of the protocol that is able to call the runLottery() function whenever he wants, or miners that validate the transaction.Malicious contestants may collude with the owner or manager of the protocol, or miners, to fix the lottery process and indirectly assign themselves as winners.It should also be noted that, while not as easy, other parameters such as index, contestant and IDO_ID may also be indirectly manipulated by malicious actors.
Open the reportThe function payForUserBonus() can be called by users to pay for bonus allocation and further claim the respective allocation in reward tokens. This function allows users to pay on behalf of other users. While this functionality is relevant to support account abstraction mechanisms, it should be implemented with care.The function performs a validation that checks whether the specific user has already paid for allocation, and if they have, it reverts. Since there are maxPayment and minPaymentPercentage variables that limit the input parameter paymentAmount, it is possible for malicious actors to frontrun transactions that call this function and grief legitimate users of paying for their allocation and receive reward tokens.While most of the times, it is expected legitimate users make use of the maxPayment available to get the maximum amount of rewards, malicious users may perform the same transaction first and on their behalf with minimal cost. The impact of this vulnerable scenario directly depends on the cost of the exploit and the relevant variables maxPayment and minPaymentPercentage.
Open the reportThe function calculateClaimAmount() performs vesting calculations on ERC20 tokens and returns the claimable amount to distributor contracts that can be claimed by users. An auto-unlock feature is also implemented that lets users claim tokens before the vesting process, and the amount of tokens that can be claimed at the start is controlled by the variable percentageAvailableForClaim.The function performs a validation that checks if percentageAvailableForClaim is 100% and allows users to claim the auto-unlocked tokens. However, if percentageAvailableForClaim is anything different than 100%, the auto-unlocked tokens cannot be called on the start but only after the cliff period has ended.
Open the reportThe function calculateClaimAmount() implements logic that hinders legitimate users from claiming tokens on a specific scenario. While it allows users to claim auto-unlocked and already vested tokens, in the case where the amount of leftover tokens to claim is less than what was already claimed minus the amount of auto-unlocked tokens, the function call reverts. This is due to calculations over the adjustedClaimed variable that will yield an underflow condition on the return statement of the function.As a Proof of Concept example, suppose:allocation = 100 tokenspercentageAvailableForClaim = 50 %claimed = 75 tokensThen:vesting will be 50 tokensunlocked will be 12.5 tokensadjustedClaimed will be 25 tokensunlocked - adjustedClaimed = -12.5 (Underflow)In order to be able to retrieve the leftover tokens, the user will have to wait for the end of the vesting to claim all tokens, and can no longer claim tokens as the vesting process matures over time.
Open the reportThe function runLottery() does not properly validate duplicate entries on the contestants array. While the array is assumed to be sorted numerically in ascending order, the validation does not check for equality with the previous entry.
Open the reportThe functions updateMinimumPaymentPercentage(), updateMinimumPaymentPercentageForIDO(), updateMinimumBonusPaymentPercentage(), and updateMinimumBonusPaymentPercentageForIDO() do not perform the same validations of the input parameter minimumPaymentPercentage_ as the constructor of the smart contract does, leaving the possibility of undefined behaviour across the protocol, should these variable be set incorrectly.
Open the reportThe function depositFor() is used to perform a lock of ERC20 tokens on behalf of a user that can be unstaked and withdrawn at a later date. While this functionality is relevant to support account abstraction mechanisms, it should be implemented with care.The function also implements two protection mechanisms, the cancelation of the withdrawal request and the reset of the staking duration of the total amount of tokens.Combining the previous two facts about the functionalities of this function, it enables the possibility for malicious actors to frontrun legitimate transactions that call this function and grief users on two possible accounts:Legitimate user stakes tokens. Stake duration has elapsed and legitimate user calls the function requestWithdrawal(). During the cooldown period, a malicious user deposits a minimal amount of tokens using depositFor(), causing the withdraw request to be cancelled. The malicious user may do this multiple times.Legitimate user stakes tokens. Stake duration is almost at the end or in withdraw cooldown period when a malicious user deposits a minimal amount of tokens using depositFor() with a duration equal to maximumStakingDuration, causing the user to have to wait further more to withdraw their tokens. The malicious user may do this multiple times.While most of the times, it is expected legitimate users make use of the minimumStakingDuration to stake the tokens, malicious users may perform the same transaction first and on their behalf with minimal cost. The impact of this vulnerable scenario directly depends on the cost of the exploit and the relevant variables minimumStakeAmount and maximumStakingDuration.
Open the reportThe function payForUserBonus() performs a check that validates whether the provided paymentAmount is at least the minPaymentPercentage allowed. Furthermore, it does not allow the same user to pay for bonus allocation more than once. This effectively means that a user that provides incorrect input parameters by mistake may not rectify and increase their payment, losing on potential bonus rewards.
Open the reportThe variable subscriptionId is not capable of being modified after being initialized in the constructor. Since this variable relates to Chainlink's subscription to request random numbers, if the subscription account is compromised, the protocol may become compromised as well.
Open the reportThe function updateStakingDuration() does not properly validate the input parameter newStakingDuration. Apart from other validations, the variable newStakingDuration is being compared to stakingEndTimestamp, which means that a duration is being incorrectly compared to a timestamp.This fact results in extreme modifications to the duration of the token staking.
Open the reportThe function runLottery() does not properly protect against an integer underflow condition of the variable uint256 i on the second for loop. As the variable i iterates through the contestants array, it is decremented at the end of each iteration. On the last iteration, it will again attempt to decrement i below 0 which for a uint256 variable is not possible, thus resulting in a integer underflow.It should be noted that this scenario is only possible when not enough winners are selected, which would revert the transaction all the same.
Open the reportThe function validateVestingTerms() does not validate the variable idoToken against the Zero Address, allowing for undefined behavior within the protocol.It should be noted that this fact becomes specially relevant since this variable is only interacted with much further down the code workflow, when it becomes very difficult to justify a genuine update of vesting terms.
Open the reportThroughout the Launchpad smart contract the lottery state variable is used to perform external calls. However, this variable is not validated against the Zero Address, allowing for undefined behavior within the protocol.
Open the reportThe functions updateLeaderboardSnapshot() and resyncLeaderboardSnapshotWithIDO() do not validate the variable latestLeaderboardSnapshot against the Zero Value, allowing for undefined behavior within the protocol.It should be noted that this fact becomes specially relevant since these functions can be called after the successful execution of the function runLottery(), where further validation checks will be made to this variable.
Open the reportThe function getRequestStatus() does not validate the variable requestId against the Zero Value, allowing for incorrect information to be presented by the protocol. Even though a requestId of 0 would most likely be invalid, the protocol can still confirm it exists even when it does not.
Open the reportThe function isWinnerInTierInIDO() does not properly validate whether a contestant is actually a winner in the specific case of providing TierOne as the tier input parameter, yielding a possibly incorrect result of true.It should be noted that while this function is being used internally by the protocol to perform relevant checks, it still provides erroneous information should it be called directly by a legitimate user of the protocol.
Open the reportThe reports name a repository or files. Open a report to see its scope.
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.
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.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="decentric"></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