Sabai Protocol security record

As of 3 Nov 2026, Sabai Protocol has 1 audit report on Trustblock, by CertiK, dated 21 Jul 2023. 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
Sabai Ecoverse AuditSecurity firm: CertiKDate: Scope: Files named in the reportNo open critical findingsOpen · skynet.certik.com

Findings19

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

SeverityOpenAcknowledgedFixed
Critical000
High022
Medium003
Low0012
HighExecution of `getRestOfDeposits()` can potentiallSabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

In the `getRestOfDeposits()` function, the owner can transfer out `_token.balanceOf(address(this)) - totalActualDepositsAmount` tokens after 30 days of the last deposit's `endTS` date. However, if some users do not call the `getReward()` in time after the `planExpired` date, the owner can run `getRestOfDeposits()` to withdraw all contract funds in excess of user deposits. ```solidity=167 require(_token.transfer(msg.sender, _token.balanceOf(address(this)) - totalActualDepositsAmount)); ``` Since the amount `totalActualDepositsAmount` does not incorporate rewards, the execution of `getRestOfDeposits()` can potentially remove all remaining user rewards. Therefore there might be group of users who will not be able to withdraw both their deposits and rewards.

Open the report
HighPotential transfer tokens in other addresses withSabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

If `isAntisnipe` is set to true, antisnipe contract can transfer tokens in other addresses without approval, which is a extremely dangerous design.

Open the report
MediumInconsistent usage of the `depositsCounter`Sabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

In the `createDeposit()` function, when creating a deposit, the contract saves the generated `deposit_id` into the `depositsCounter` `totalDepositsCount+1` position. In the case of the very first deposit, this would mean that the **0** index is never used. ```solidity depositsCounter[totalDepositsCount+1] = deposit_id; ``` When iterating `depositsCounter` to get the deposit info in the `getAmountToCloseactualDiposits()` and `getRestOfDeposits()` functions, the starting index is **0** and the last index of `depositsCounter` will never be reached. ```solidity for(uint8 i = 0 ; i<totalDepositsCount; i++) ```

Open the report
MediumIncorrect amount deducted from `totalActualDeposiSabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

The current implementation deducts the amount `deposits[_depositId].claimed` from amount `totalActualDepositsAmount`. This could result in users not being able to retrieve their deposit and rewards. The variable `totalActualDepositsAmount` is updated by adding on the deposit amount each time a new deposit is created. ```solidity function createDeposit(uint256 count) public payable { // ... Omitted totalDepositsAmount += count; totalDepositsCount ++; totalActualDepositsAmount += count; ``` When deposits and rewards are withdrawn through function `getReward()`, the amount of `deposits[_depositId].claimed`, which is `deposits[_depositId].amount` + rewards, is deducted from the amount `totalActualDepositsAmount` which only keeps track of total deposit amounts. ```solidity function getReward(bytes32 _depositId) public onlyOwner { // ... Omitted // If the deposit is completed if (getCurrentTime() > deposits[_depositId].endTS) { deposits[_depositId].claimed = deposits[_depositId].amount + ((deposits[_depositId].amount / 100) * percentageOfEarnings); } else { // If the deposit is closed ahead of schedule (penalty) deposits[_depositId].claimed = deposits[_depositId].amount - ((deposits[_depositId].amount / 100) * penaltyPercentage); } require(_token.transfer(msg.sender, deposits[_depositId].claimed)); totalActualDepositsAmount -= deposits[_depositId].claimed; ``` The issue with this treatment is that when multiple users withdraw deposits and rewards, there is a chance that the last user(s) to claim rewards will be unable to retrieve the full amount since the value of `totalActualDepositsAmount` since reward withdrawals are not accounted for in the amount `totalActualDepositsAmount`.

Open the report
MediumIncorrect modifier restricts deposit retrievalsSabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

There is a `onlyOwner` modifier in the public function `getReward()`, which restricts only the owner to call it. However, from its implementation, it checks if the msg.sender matches the `_depositId`, which means it should allow common users to call it.

Open the report
LowDiscussion for `getAmountToCloseactualDiposits()`Sabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

The `getAmountToCloseactualDiposits()` function is used to calculate how many tokens are required to provide rewards for all active deposits. However, the calculation makes use of the `penaltyPercentage` instead, and it also does not consider completed cases, where `getCurrentTime() > deposits[_depositId].endTS`. The business logic of this function is inconsistent with the `getReward()` function. ```solidity=64 // If the deposit is completed if (getCurrentTime() > deposits[_depositId].endTS) { deposits[_depositId].claimed = deposits[_depositId].amount + ((deposits[_depositId].amount / 100) * percentageOfEarnings); } else { // If the deposit is closed ahead of schedule (penalty) deposits[_depositId].claimed = deposits[_depositId].amount - ((deposits[_depositId].amount / 100) * penaltyPercentage); } ``` The audit team would like to check if this implementation matches the intended design.

Open the report
LowDiscussion on ensuring contract contains sufficieSabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

The audit team would like to understand how the contract maintains sufficient funds to transfer deposit rewards to beneficiaries and to ensure that transfers of rewards do not fail under normal conditions.

Open the report
LowDiscussion on reward calculationSabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

The current implementation deals a penalty deducting the amount directly from the user's deposit amount. In most cases, the penalties usually impact the amount of rewards the user receives rather than reducing the user's deposit amount. The audit team would like to confirm whether this matches the intended design.

Open the report
LowDivide Before MultiplySabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

Performing integer division before multiplication truncates the low bits, losing the precision of calculation. ### SabaiStaking.sol ```solidity=66 deposits[_depositId].claimed = deposits[_depositId].amount + ((deposits[_depositId].amount / 100) * percentageOfEarnings); ``` --- ```solidity=68 deposits[_depositId].claimed = deposits[_depositId].amount - ((deposits[_depositId].amount / 100) * penaltyPercentage); ``` --- ```solidity=147 totalAmount+= ((deposits[depositsCounter[i]].amount / 100) * penaltyPercentage); ``` --- ```solidity=397 uint256 vestedSlicePeriods = timeFromStart.div(secondsPerSlice); ``` ### Sabai Vesting.sol ```solidity=398 uint256 vestedSeconds = vestedSlicePeriods.mul(secondsPerSlice); ```

Open the report
LowIncompatibility with Deflationary TokensSabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

When transferring deflationary ERC20 tokens, the input amount may not be equal to the received amount due to the charged transaction fee. For example, if a user sends 100 deflationary tokens (with a 10% transaction fee), only 90 tokens actually arrived to the contract. However, a failure to discount such fees may allow the same user to withdraw 100 tokens from the contract, which causes the contract to lose 10 tokens in such a transaction. Reference: https://thoreum-finance.medium.com/what-exploit-happened-today-for-gocerberus-and-garuda-also-for-lokum-ybear-piggy-caramelswap-3943ee23a39f ### SabaiStaking ```solidity=97 require(_token.transferFrom(_msgSender(), address(this), count)); ``` - Transferring tokens by `count`. ```solidity=103 totalActualDepositsAmount += count; ``` - The `count` appears to be used for bookkeeping purposes without compensating the potential transfer fees. ### SabaiVesting ```solidity=302 _token.safeTransfer(beneficiaryPayable, amount); ``` - Transferring tokens by `amount`. ```solidity=301 vestingSchedulesTotalAmount = vestingSchedulesTotalAmount.sub(amount); ``` - This function call executes the following operation. - In `SafeMath.sub`, - `return a - b;` - The `amount` appears to be used for bookkeeping purposes without compensating the potential transfer fees.

Open the report
Show 7 more
LowIncorrect check in `createVestingScheduleMany()`Sabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

In the `createVestingScheduleMany()` function, the contract checks if there are sufficient tokens to create new vesting schedules. However, the current logic only compares the withdrawable amount to a single schedule amount, which can potentially lead to the function failing because there may be sufficient funds to create a certain vesting schedule, but not all the vesting schedules contained in array `_schedules`. ```solidity=216 for (uint256 i=0; i<_schedules.length; i++) { require( this.getWithdrawableAmount() >= _schedules[i].amount, "TokenVesting: cannot create vesting schedule because not sufficient tokens" ); require(_schedules[i].duration > 0, "TokenVesting: duration must be > 0"); require(_schedules[i].amount > 0, "TokenVesting: amount must be > 0"); require(_schedules[i].slicePeriodSeconds >= 1, "TokenVesting: slicePeriodSeconds must be >= 1"); } ```

Open the report
LowIncorrect logic in function `_setIsAntisnipe()`Sabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

From the function name and its parameters, the function `_setIsAntisnipe()` seems to be able to set the `isAntisnipe` flag to `true` or `false`. However, this function actually always sets `isAntisnipe` to `false` without making use of the inputted `value`.

Open the report
LowLogic for `vestedAmount` calculationSabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

In function `_computeReleasableAmount()` the calculation for `vestedAmount` is as follows: ```solidity=396 uint secondsPerSlice = vestingSchedule.slicePeriodSeconds; uint256 vestedSlicePeriods = timeFromStart.div(secondsPerSlice); uint256 vestedSeconds = vestedSlicePeriods.mul(secondsPerSlice); uint256 vestedAmount = vestingSchedule.amountTotal.mul(vestedSeconds).div(vestingSchedule.duration); ``` The calculation for `vestedAmount` seems to be similar to the original amount for `secondsPerSlice` except that there will be division truncation as a result of the calculation using integers (in the calculation of `vestedSlicePeriods`). There is a risk that if the total `vestingSchedule.duration` is not made of complete `vestedSlicePeriods`, there will be leftover releasable amounts that cannot be claimed. It might be beneficial to include checks in the creation of the vesting schedule to ensure that this issue does not occur. The audit team would like to confirm if this aligns with the intended design.

Open the report
LowPotential locked native tokensSabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

The `SabaiVesting.fallback()`/`SabaiVesting.receive()`/`SabaiStaking.createDeposit()` functions allow the contract to accept the native tokens, such as ETH, transferred into the contract. However, the corresponding contract seems to have no functions on withdraw native tokens, which can potentially cause the native tokens to be stuck in the contract forever. On a side note, in the case of the `SabaiStaking.createDeposit()` function, the function is marked as `payable` which means that the function can accept native tokens, however there is no logic to handle these native tokens since the function only handles transfers of the specified token contract for valid deposits.

Open the report
LowUnused EventSabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

Some events are never emitted, which can lead to confusion and code maintainability issues. ```solidity=47 event Released(uint256 amount); ``` - `Released` is declared in `SabaiVesting` but never emitted. --- ```solidity=48 event Revoked(); ``` - `Revoked` is declared in `SabaiVesting` but never emitted.

Open the report
LowUnused State VariableSabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

Some state variables are not used in the codebase. This can lead to incomplete functionality or potential vulnerabilities if these variables are expected to be utilized. Variable `users` in `SabaiStaking` is never used in `SabaiStaking`. ```solidity=35 mapping(uint256 => address) users; ``` ```solidity=8 contract SabaiStaking is Ownable { ```

Open the report
LowVariables That Could Be Declared as ImmutableSabai Ecoverse Audit · CertiK · 21 Jul 2023Fixed

The linked variables assigned in the constructor can be declared as `immutable`. Immutable state variables can be assigned during contract creation but will remain constant throughout the lifetime of a deployed contract. A big advantage of immutable variables is that reading them is significantly cheaper than reading from regular state variables since they will not be stored in storage.

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="sabai-protocol"
></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