SakaiDAO - Audit

CertiK1 address on BNB Chain

CertiK reviewed Sakai Vault (SakaiDAO - Audit) on 15 Jul 2024: 21 findings, 2 critical; 10 marked fixed, 5 acknowledged, 6 open.

2 critical findings, fixed
Open the reportOpen Sakai Vault’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings21

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

SeverityOpenAcknowledgedFixed
Critical002
High040
Medium015
Low603
HighCentralization Risks in SakaiDAO.solAcknowledged

In the contract `Ownable` the role `_owner` has authority over the functions shown in the diagram below. ![](https://accelerator-tasks-prod.acc.corp.certik.com/11ee-a14a-6029fb80-b7b0-61a6a2edd4af/diagrams/centralization_SakaiDAO-Ownable-_owner.svg) Any compromise to the `_owner` account may allow the hacker to take advantage of this authority and do the following: - Transfer ownership to address(0). - Transfer ownership to the new owner's address. In the contract `SakaiDAO` the role `_owner` has authority over the functions shown in the diagram below. ![](https://accelerator-tasks-prod.acc.corp.certik.com/11ee-a14a-6029fb80-b7b0-61a6a2edd4af/diagrams/centralization_SakaiDAO-SakaiDAO-_owner.svg) Any compromise to the `_owner` account may allow the hacker to take advantage of this authority and do the following: - claim reward for specific account. - claim `BNB` from the contract. - claim all `erc20` tokens except the token associated with the contract from the contract. - update minimum stake for voting. - update minimum stake for creating proposal. - update minimum stake for getting reward. - update minimum stake. - update tax percentage which is deducted from referral reward. - update distribution ratio for layer1, layer2, layer3 referrer. - update tax percentages for staking pool and vault which are deducted from staking reward. - update vault address which is used to receive reward. - update staking pool's address which is used to receive reward. - update treasury address. - update reset epoch time which will affect the reward distribution. - update voting address. Important Note: Certain identification and KYC procedures were attempted to be applied to the project team in order to better understand the centralization situation and potential risks of the project. The project team refused to cooperate with the investigation efforts, and thus based on the negative signals we concluded that there is potential high risk to the project. We strongly advise end users to conduct further research and exercise due diligence before engaging with the project. It is crucial for end users to independently verify and assess all available information to make informed decisions.

HighFaulty Share Reduction Results in Unstake ProcessAcknowledged

According to the design, when users unstake their staked tokens, if their latest share is less than the minimum threshold eligible for rewards, their share will be deducted from the `totalShare`. ```solidity function _setShare(address account, uint256 amount) internal { /** this function for set shares balance */ if(amount > 0 && shares[account].amount == 0){ // if amount > 0 then add shareholder _addShareholder(account); } else if(amount == 0 && shares[account].amount > 0){ // if amount = 0 then remove shareholder _removeShareholder(account); } /** Update shares balance */ totalShares = totalShares - shares[account].amount + amount; shares[account].amount = amount; shares[account].totalExcluded = _getCumulativeDividend(shares[account].amount); shares[account].lastDepositTimestamp = block.timestamp; shares[account].lastDepositEpoch = currentEpoch; // get current epoch from vote contract for prevent user voting on running epoch shares[account].lastEpochNumberWhenDeposit = voteAddress != address(0) ? ITokenVote(voteAddress).getCurrentEpoch() : 0; if(shares[account].amount >= minimumStakeForGetReward){ shares[account].isReceiveReward = true; } else { shares[account].isReceiveReward = false; totalShares = totalShares - shares[account].amount; } } ``` However, when users unstake again, since the deducted portion is not added back to the `totalShare`, the `totalShare` will have to deduct the user's current share amount once more. Ultimately, this will cause the `totalShare` to become increasingly smaller, and updating the user's share will result in an underflow due to subtraction, leading to a failure when users try to unstake in the future.

HighIncompatibility with Deflationary TokensAcknowledged

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 ```solidity=699 IERC20(tokenAddress).transferFrom(_msgSender(), address(this), amount); ``` - Transferring tokens by `amount`. ```solidity=702 _mint(account,amount); ``` - This function call executes the following operation. - In `ERC20._mint`, - `_balances[account] += amount;` - The `amount` appears to be used for bookkeeping purposes without compensating the potential transfer fees. --- ```solidity=860 IERC20(tokenAddress).transferFrom(_msgSender(), address(this), rewardAmount); ``` - Transferring tokens by `rewardAmount`. ```solidity=861 _calculateReward(rewardAmount); ``` - This function call executes the following operation. - In `SakaiDAO._calculateReward`, - `pendingCalculateReward += rewardAmount;` - The `rewardAmount` appears to be used for bookkeeping purposes without compensating the potential transfer fees. --- ```solidity=873 _stake(amount, referrer); ``` - Transferring tokens by `amount`. - This function call executes the following operation. - In `SakaiDAO._stake`, - `IERC20(tokenAddress).transferFrom(_msgSender(), address(this), amount);` ```solidity=873 _stake(amount, referrer); ``` - This function call executes the following operation. - In `SakaiDAO._stake`, - `_mint(account,amount);` - In `ERC20._mint`, - `_balances[account] += amount;` - The `amount` appears to be used for bookkeeping purposes without compensating the potential transfer fees.

HighUnguaranteed Principal WithdrawalAcknowledged

The deposited token and reward token are the same in `SakaiDAO`. However, there is no guarantee that the `SakaiDAO` has sufficient rewards since the rewards need to be transferred into contracts manually. As a result, when users are trying to withdraw their principal or rewards from the contract, the contract is not guaranteed to have enough balance to pay the users. It is especially important to ensure that the user's principal is able to withdraw. Based on the design of the protocol, the referrer reward is directly transferred from the contract. For example, if a user stakes 1000 tokens, the referrer could gain around 60 tokens as a reward (6% maximum not considering the tax). Those rewards are directly transferred from the contract, not the user who stakes the tokens. Therefore, the contract may lose 60 tokens when the referrer claims the reward. Consequently, the contract may not have enough tokens for users to withdraw their tokens. Important Note: Certain identification and KYC procedures were attempted to be applied to the project team in order to better understand the centralization situation and potential risks of the project. The project team refused to cooperate with the investigation efforts, and thus based on the negative signals we concluded that there is potential high risk to the project. We strongly advise end users to conduct further research and exercise due diligence before engaging with the project. It is crucial for end users to independently verify and assess all available information to make informed decisions.

MediumUsers Can Claim Rewards After Burning `SakaiDAO`Acknowledged

The `SakaiDAO` contract is designed to allow users to stake tokens in exchange for rewards, minting `SakaiDAO` tokens for them. Conversely, when users unstake, their `SakaiDAO` tokens are burned. Throughout these transactions, the contract updates the users' shares, which are used to calculate their rewards. However, should users accidentally burn their `SakaiDAO` tokens using the `burn()` function, their shares remain intact, enabling them to keep claiming rewards. ```solidity function burn(uint256 amount) external virtual { require(_msgSender() != address(0), "ERC20Stake: burn from the zero address"); require(amount > 0, "ERC20Stake: burn amount exceeds balance"); require(_balances[_msgSender()] >= amount, "ERC20Stake: burn amount exceeds balance"); _burn(_msgSender(), amount); } ```

Contracts in scope1

AddressChainSource code
0xfa8b0197219b6cc8f3336039d50bc03220a368ceChain: BNB ChainSource code: Verified source

Part of Sakai Vault’s security record

Sakai Vault
Audits4 audits by 2 firmsLast audit Jul 2024, by CertiK.
Critical findingsNo open critical findingsAcross the 4 reports, 2 critical findings were reported. Each is marked fixed by the firm that reported it.
Open Sakai Vault’s security record

Security firm

CertiK

1,044 projects reviewed · 1,375 reports · latest May 2025

See CertiK’s audits

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