CYBRO

CertiKFiles named in the report

CertiK reviewed CYBRO on 9 Apr 2024: 14 findings, 0 critical; none marked fixed.

No open critical findings
Open the reportOpen CYBRO’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings14

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

SeverityOpenAcknowledgedFixed
Critical000
High100
Medium400
Low900
HighCentralization Related RisksOpen

In the contract `Presale` the role `DEFAULT_ADMIN_ROLE` has authority over the functions shown in the diagram below. ![](https://accelerator-tasks-prod.acc.corp.certik.com/11ee-f283-c58a8050-8335-8b8b7b279897/diagrams/centralization_Presale-Presale-DEFAULT_ADMIN_ROLE.svg) - `updateProtocolWallet(address wallet)`: Updates the address of the protocol wallet, accessible only by the role with `DEFAULT_ADMIN_ROLE`. - `rescueFunds(IERC20 token, uint256 amount)`: Allows the rescue of funds (both native currency and ERC20 tokens) from the contract, accessible only by the role with `DEFAULT_ADMIN_ROLE`. Any compromise to the `DEFAULT_ADMIN_ROLE` account may allow the hacker to take advantage of this authority and update the sensitive settings and execute sensitive functions of the project In the contract `Presale` the role `OPERATOR_ROLE` has authority over the functions shown in the diagram below. ![](https://accelerator-tasks-prod.acc.corp.certik.com/11ee-f283-c58a8050-8335-8b8b7b279897/diagrams/centralization_Presale-Presale-OPERATOR_ROLE.svg) - `setStage(uint256 stageIterator_)`: Sets the current stage of the presale, accessible only by the role with `OPERATOR_ROLE`. - `updateTotalSold(uint256 amount)`: Updates the total amount of tokens sold, accessible only by the role with `OPERATOR_ROLE`. - `pause()`: Pauses the contract, halting new deposits, accessible only by the role with `OPERATOR_ROLE`. - `unpause()`: Unpauses the contract, allowing new deposits, accessible only by the role with `OPERATOR_ROLE`. Any compromise to the `OPERATOR_ROLE` account may allow the hacker to take advantage of this authority and update the sensitive settings and execute sensitive functions of the project `PresaleBSC` and `PresaleBlast` contracts inherit the `Presale` contract, which also inherited the privileged functions listed above.

MediumIncorrect Assumption of Token Price and PrecisionOpen

The `_calculateAmount()` in the `Presale` and `PresaleBSC` contracts assumes that the price of tokens (`coinPrice`) is always 1 USD when `isStableToken` flag is true and that the precision (`PRECISION`) is always 6 or 18, depending on whether `STABLE_TOKEN_DECIMALS` or `TOKEN_PRECISION` is used. This assumption can lead to incorrect calculations and potential financial losses. If the price of the stable token deviates from 1 USD or if the precision is different from the assumed values, the function will calculate incorrect amounts for token transfers. This can result in users receiving more or fewer tokens than they should, potentially leading to financial discrepancies and loss of funds.

MediumPotential Reentrancy IssueOpen

The smart contract contains functions that transfer native coins (e.g., Ether) to external addresses. These transfers are performed using the low-level `call` function, which can potentially allow reentrancy issues if the recipient is a contract that has a fallback function. An attacker could exploit this vulnerability by creating a malicious contract that has a fallback function. When the `rescueFunds` or `depositCoinTo` function transfers native coins to the malicious contract, the fallback function could be triggered, allowing the attacker to re-enter the Presale contract and potentially withdraw more funds or disrupt the contract's state. In the `rescueFunds`, `depositCoinTo`, and `depositCoin` functions, lines like `(bool success, ) = payable(msg.sender).call{value: amount}("");` transfer native coins to the sender. If the sender is a malicious contract, it could exploit this transfer to re-enter the Presale contract.

MediumRounding Down Issues in Division OperationsOpen

The `Presale.sol` and `PresaleBSC.sol` smart contracts contain division operations that may lead to rounding down errors. Solidity's integer division rounds towards zero, which can result in loss of precision and unintended behavior, especially in financial calculations. In functions such as `_calculateAmount` and `_purchase`, division operations are used to calculate token amounts and values. If the division results in a non-integer value, Solidity will round down the result, potentially leading to discrepancies in token allocation and financial calculations.

MediumTimelock mechanism is flawedOpen

The timelock mechanism works this way: 1. The privilege role member (like `OPERATOR_ROLE`) calls a privileged function (like `updateProtocolWallet()`) for the first time. 2. `_timestamps` for that function is set to `block.timestamp + DEFAULT_DELAY`. 3. The privilege role member calls the function again after `DEFAULT_DELAY`. 4. The function updates the contract state and drops `_timestamps` for that function. This approach has several drawbacks: 1. The function arguments are not saved. As a result, the operator can "charge" all the timelocked functions **beforehand** with random values and bypass the waiting when execution is required. 2. Since the function arguments are not saved, the operator can demonstrate some intention, but after 12 hours execute **another** action. 3. If the function is executed before the timer passes, it refreshes the timer. This can be undesired. 4. Each timelocked function implements the same timelock logic. It is recommended to separate the timelock functionality from contract business logic. 5. `rescueFunds()` is re-enterable, so several currencies can be extracted before the `_timestamps` is zeroed.

LowDependency on Third-Party Oracle for Coin Price FOpen

The smart contract uses a third-party oracle (`COIN_PRICE_FEED`) to fetch the current coin price for calculating the amount of tokens to be transferred during deposits. This reliance on an external data source introduces a point of failure and potential security risks, such as manipulation of the oracle data. For example: 1. An attacker gains control over the oracle or manipulates the data feed. 2. The attacker alters the coin price data returned by the oracle. 3. Users of the contract receive an incorrect amount of tokens for their deposits, leading to financial losses. This will impact sensitive variable values in the contract, for example, in `_purchase` function, according to `uint256 usdInEth = 1 ether / coinPrice;`, the value of `usdInEth` will be directly related to the value of `coinPrice`, which is fetched from the oracle

LowDifferent contracts used for different chainsOpen

`PresaleBSC` is supposed to be deployed in BSC chain. It overrides `_depositChecksAndEffects()` and `_depositInteractions()`, however, the code is identical and can be omitted. The code can use `token.decimals()` and `COIN_PRICE_FEED.decimals()` to make it chain independent and ensure the calculations will be correct. In case the chain dependence can't be avoided `block.chainId` can be used to branch the execution. The stablecoin address can be provided as an argument to the deposit function and checked in the code that it is supported. Deploying different versions to different chains can lead to accidental mistakes.

LowMissing Zero Address Validation in Constructor fOpen

The smart contract constructors for `Presale` and `PresaleBlast` do not validate that the addresses provided for the token contracts (`usdcToken`, `usdtToken`, `usdbToken`, and `wethToken`) are not the zero address. This lack of validation could lead to undefined behavior if any of these addresses are set to the zero address, as the zero address is not a valid contract address.

LowNumber representationOpen

```solidity=54 stages.push(StageData(1e6, 75e5)); stages.push(StageData(15e5, 1e7)); stages.push(StageData(2e6, 175e5)); stages.push(StageData(25e5, 28e6)); ``` Numbers with different decimals are hard to read. Consider using the same, like: ```solidity=54 stages.push(StageData(1e6, 7.5e6)); stages.push(StageData(1.5e6, 10e6)); stages.push(StageData(2e6, 17.5e6)); stages.push(StageData(2.5e6, 28e6)); ``` ```solidity=77 _timestamps[0xe308a099] = 0; ``` `this.updateProtocolWallet.selector` can be used instead of the magic number.

LowPrecision Issue in Emitted Event `AmountAndUSD`Open

The emitted event `AmountAndUSD` in the `_calculateAmount` function does not consider the precision of the calculated amount. This can lead to misunderstandings when interpreting the event data, as the values might not accurately represent the intended amounts due to the lack of precision adjustment. When the `_calculateAmount` function is called, it emits the `AmountAndUSD` event with `expectedAmount` and `coinPrice`. However, the `expectedAmount` is not adjusted for precision before being emitted. This can result in the event displaying a value that is not correctly scaled according to the token's decimals.

Show 4 more
LowRedundant codeOpen

```solidity=211 token.safeTransferFrom(msg.sender, address(this), amount); token.safeTransfer(protocolWallet, spendedValue); if(chargeBack > 0) token.safeTransfer(msg.sender, chargeBack); ``` The code can be simplified to ```solidity=211 token.safeTransferFrom(msg.sender, protocolWallet, amount - chargeBack); ``` `spendedValue` is assumed to be equal to `amount - chargeBack` since `tokensToChargeBack = value - spendedValue`. `_purchase()` can be simplified to: ```solidity=235 if (amount > uint(crtStage.amount)) { // lowering the amount and closing the stage amount = uint(crtStage.amount); stageIterator++; emit StageUpdated(stageIterator); } totalSoldInUSD += amount * crtStage.cost; // with 8 decimals balances[to] += amount; totalTokensSold += amount; // returning actualSpent with original decimals uint decimals = address(token) == address(0) ? TOKEN_PRECISION : token.decimals(); return amount * crtStage.cost * 10 ** decimals / coinPrice; ``` The function should return the `actualSpent`.

LowReferrer Restriction Issue in Deposit FunctionsOpen

The deposit functions allow a user to specify a referrer address that can receive benefits from the deposit. However, there is no restriction in place to prevent the `referrer` from being the same as the `msg.sender` or the `to` address. This could potentially be exploited by users to claim referral benefits for their own deposits, which might not be the intended behavior. For example: 1. A user calls the `depositUSDC` function with their own address as the `referrer`. 2. The transaction succeeds, and the user receives referral benefits for their own deposit.

LowTypos in variable nameOpen

The smart contract contains a typo in the variable `spendedValue`, which should be `spentValue`. This does not affect the functionality of the contract but can lead to confusion and potential errors in future maintenance or updates.

LowWrong destination in `rescueFunds()`Open

The contract always sends the funds to `protocolWallet`, however, in case of native tokens, `rescueFunds()` sends them to `msg.sender`.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (3)

  • contracts/PresaleBSC.sol
  • contracts/PresaleBlast.sol
  • contracts/Presale.sol

Part of CYBRO’s security record

CYBRO
Audits4 audits by 2 firmsLast audit Feb 2025, by CertiK.
Critical findingsNo open critical findingsAcross 4 reports.
Open CYBRO’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