As of 3 Nov 2026, Sakai Vault has 4 audit reports by 2 security firms on Trustblock; the latest is by CertiK, dated 15 Jul 2024. Those reports list no open critical findings. It also holds 8 deployed contracts on 1 chain.
Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| SakaiDAO - Audit | Security firm: CertiK | Date: | Scope: 1 address on BNB Chain | 2 critical findings, fixed | Open · skynet.certik.com |
| Sakaivault - Audit | Security firm: CertiK | Date: | Scope: 4 addresses on BNB Chain | No open critical findings | Open · skynet.certik.com |
| SakaiDAO | Security firm: SolidProof | Date: | Scope: 1 address on BNB Chain | No open critical findings | Open · IPFS |
| Sakai Vault | Security firm: SolidProof | Date: | Scope: 2 addresses on BNB Chain | No open critical findings | Open · IPFS |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 2 |
| High | 0 | 6 | 0 |
| Medium | 1 | 3 | 6 |
| Low | 17 | 0 | 6 |
The report gives no description here.
Open the reportAccording to the logic, the initial value of `voteAddress` is zero address, after the first epoch, any user who stakes `minimumStakeForVote` tokens can vote. When `voteAddress` is not zero address, the contract will get current epoch from vote contract for prevent user voting on running epoch. ```solidity shares[account].lastEpochNumberWhenDeposit = voteAddress != address(0) ? ITokenVote(voteAddress).getCurrentEpoch() : 0; ``` However, once the `voteAddress` is set, it cannot be changed back to the zero address. Whether the user can vote will be affected by the `voteAddress` contract. ```solidity function updateVoteAddress(address _voteAddress) external onlyOwner { require(_voteAddress != address(0), "SakaiDAO: Invalid address"); require(voteAddress != _voteAddress, "SakaiDAO: Vote address is already that address"); voteAddress = _voteAddress; emit UpdateVoteAddress(_voteAddress); } ```
Open the reportIf it is intended to prohibit token transfers, it is better to use `revert` to terminate the transaction and throw an error. This is because: 1. Clarity: `revert` clearly indicates that the transaction has failed and provides a reason for the failure. This is helpful for both users and developers as it provides clear feedback on why the operation is not allowed. 2. Gas Savings: When a transaction is reverted, the unused Gas is refunded to the user. Executing empty logic, although it does not change the state, still incurs Gas costs for these invalid computations. 3. Security: Executing empty logic may mislead users into thinking the transaction was successful. Using `revert` can avoid such misunderstandings and ensure that the contract behaves in accordance with its design intentions, enhancing contract security. Therefore, for the sake of transparency, Gas savings, and ensuring security, it is recommended to use `revert` when prohibiting token transfers.
Open the reportAccording to the design, when a user unstakes, the contract estimates the fee based on the duration of the staking. The longer the staking period, the lower the fee, and if the staking period exceeds 365 days, no fee is charged when the user unstakes. However, the contract uses `shares[account].lastDepositTimestamp` as the staking start time, and this state variable will be updated both when the user stakes and unstakes. This means that if a user unstakes (possibly only a portion), the staking duration for the user will be recalculated. ```solidity function _estimateTaxPercentEarlyUnstake(address account) internal view returns(uint256) { /** this function for estimate tax if user unstake early */ uint256 lastDepositTimestamp = shares[account].lastDepositTimestamp; uint256 totalDaysStake = (block.timestamp - lastDepositTimestamp) / 86400; uint256 tax = 0; if(totalDaysStake >=0 && totalDaysStake <= 90) tax = 250; else if(totalDaysStake > 90 && totalDaysStake <= 120) tax = 223; else if(totalDaysStake > 120 && totalDaysStake <= 150) tax = 196; else if(totalDaysStake > 150 && totalDaysStake <= 180) tax = 169; else if(totalDaysStake > 180 && totalDaysStake <= 210) tax = 142; else if(totalDaysStake > 210 && totalDaysStake <= 240) tax = 115; else if(totalDaysStake > 240 && totalDaysStake <= 270) tax = 88; else if(totalDaysStake > 270 && totalDaysStake <= 300) tax = 61; else if(totalDaysStake > 300 && totalDaysStake <= 330) tax = 34; else if(totalDaysStake > 330 && totalDaysStake <= 365) tax = 7; return tax; } ```
Open the reportThe owner of the **SakaiDAO** contract can claim all tokens in the contract except the **Sakai** token, but the error message indicates that the owner can only claim native tokens, which is inconsistent with the requirements.
Open the reportThe linked code does not affect the functionality of the codebase and appears to be either remnants of test code or older functionality.
Open the reportIt is not recommended to use Solidity's `transfer()` and `send()` functions for transferring native tokens, since some contracts may not be able to receive the funds. Those functions forward only a fixed amount of gas (2300 specifically) and the receiving contracts may run out of gas before finishing the transfer. Also, EVM instructions' gas costs may increase in the future. Thus, some contracts that can receive now may stop working in the future due to the gas limitation. ```solidity=797 payable(msg.sender).transfer(address(this).balance); ``` - `SakaiDAO.claimStuckBNB` uses `transfer()`.
Open the report`SAKAI` token charges 3% buy/sell fee, and the 5/6 of the fee is collected by `treasuryWallet` and `gasWallet`. If these fees don't flow back to the users in some way, eventually all the SAKAI token would end up in the contract owner's wallet. We'd like to confirm if there's a way for the users/token holders to benefit in the tokenomics by design.
Open the reportThe `stakingToken` should not be deflationary token. 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 Specifically in `Sakaivault`, in function `deposit()`, the `safeTransferFrom()` amount is the same as the recorded stake amount. But for deflationary tokens, the actual received amount might be less. ```solidity=1617 ERC20token.safeTransferFrom(msg.sender, address(this), amount); ``` ```solidity=1643 _stakers[msg.sender].balance += amount; ``` On the other hand, function `withdraw()` still transfers out the recorded staked balance. ```solidity=1657 IERC20 ERC20token = IERC20(stakingToken); ERC20token.safeTransfer(msg.sender, balance); totalAddressStaked -= 1; totalStakedToken -= balance; ```
Open the reportA sandwich attack may happen when an attacker observes a transaction swapping tokens or adding liquidity without setting restrictions on slippage or minimum output amount. The attacker can manipulate the exchange rate by frontrunning (executing before the target) a transaction to purchase one of the assets and make profits by backrunning (executing after the target) a transaction to sell the asset. The following functions are called without setting restrictions on slippage or minimum output amount, so transactions triggering these functions are vulnerable to sandwich attacks, especially when the input amount is large: - `swapExactTokensForTokensSupportingFeeOnTransferTokens()` - `swapExactTokensForETHSupportingFeeOnTransferTokens()` This poses as an issue since the `manualSwapBack()` function is callable by anyone. It might be possible for an attacker to manipulate the price of the SAKAI-USDT and/or SAKAI-WETH pool using flashloan, and then call `manualSwapBack()`. This would result in less USDT for `treasuryWallet` and less WETH for `gasWallet`.
Open the report1. Expected total reward for a user is not recorded in the contract. As indicated by the code, the stakers should receive a fixed-rate daily interest of 0.8%. If a user makes two 100 token deposits on different days, the reward amount should be different for the 2 deposits, because of the different stake time. But only the deposited balance and the time of the first deposit are recorded in the `Staker` struct. This means that there would be no way to calculate the accurate amount of expected total rewards for a user, solely from the data in this contract. We'd like to confirm if this is working as designed. 2. The daily interest rate in the contract is 0.8%, and the allocated reward is calculated based on it. But the actual reward amounts handed out to stakers are decided by `backendWallet` when it calls `claimReward()` function without limitation on the amounts. We'd like to confirm if this is working per design. 3. The stakers cannot withdraw token before `endTime`, while the rewards can be handed out before `endTime`. We'd like to confirm if this is intentional. 4. When `claimRewards()` function is called, the reward recipient is not validated against `_stakers`. This means that the `backendWallet` could allocate rewards to any address, regardless of whether it has a valid staking record. This could be volatile. For example, if the `backendWallet` accidentally calls `claimRewards()` twice with the same input, the code may not revert, and the reward could be handed out twice. On the other hand, the current code allows users to withdraw the staked tokens before reward is handed out, and the user's staking record will be deleted after withdrawal. This makes it necessary to allow for reward distribution to addresses without staking records. We'd like to point out the potential risk in the current design and confirm if this is intended. Resolution of this issue may need a change in design.
Open the reportIn contract `SAKAI` and `SakaiStaking`, the following addresses are hardcoded, and some of them cannot be updated after deployment. These addresses appear to be in BSC testnet instead of mainnet. - SAKAI contract ``` solidity=1379 address public treasuryWallet = 0x6b269c07e8f94f0fa1769CBD362879AFeA0206dB; address public stakingContract = 0x7BC537e26478789d3709cD68972C6A6DC6611Cde; address public gasWallet = 0x9Ffd803BD8048Ed59Fccb73d688326d7085A4e54; ``` - SakaiStaking contract ```solidity=1363 address public backendWallet = 0x9Ffd803BD8048Ed59Fccb73d688326d7085A4e54; address public treasuryWallet = 0x6b269c07e8f94f0fa1769CBD362879AFeA0206dB; ```
Open the reportFollowing third-party dependency exists with scope of audit. - Contract `SAKAI` and `SakaiStaking` are serving as the underlying entity to interact with third-party DEX. - The reward distribution in `SakaiStaking` contract is dependent on the external backend wallet to correctly calculate and distribute. The scope of the audit treats third-party entities as black boxes and assumes their functional correctness. However, in the real world, third parties can be compromised and this may lead to lost or stolen assets.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the report| Address | Chain | Source code |
|---|---|---|
| 0x6729…16090x6729324119a210cf493fa7455e581df9245d1609 | Chain: BNB Chain | Source code: Verified source |
| 0xb8de…1abd0xb8de9c3ede83cd4bba7982273e6c1c0852201abd | Chain: BNB Chain | Source code: Verified source |
| 0xc20a…a42a0xc20a079c7962d9fc92173cda349e80d484dfa42a | Chain: BNB Chain | Source code: Verified source |
| 0x43b3…c4af0x43b35e89d15b91162dea1c51133c4c93bdd1c4af | Chain: BNB Chain | Source code: Verified source |
| 0xfa8b…68ce0xfa8b0197219b6cc8f3336039d50bc03220a368ce | Chain: BNB Chain | Source code: Verified source |
| 0x8f2e…cfd70x8f2e2bad6020d8bb5bf947199cae8eb86d24cfd7 | Chain: BNB Chain | Source code: Verified source |
| 0x65d4…33f40x65d4cb29e0edaefb18246b22513483c21a8033f4 | Chain: BNB Chain | Source code: Verified source |
| 0xebb3…da7d0xebb30b32e74ad446b00b6333c372c5a1ccabda7d | Chain: BNB Chain | Source code: Verified source |
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="sakai-vault"></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