CertiK reviewed Froggies (Froggies Token) on 4 Aug 2023: 19 findings, 1 critical; 9 marked fixed, 10 open.
Report hosted on skynet.certik.com.
Statuses are the security firm’s own, from this report. Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 1 |
| High | 3 | 0 | 2 |
| Medium | 0 | 0 | 1 |
| Low | 7 | 0 | 5 |
In the contract `StakingContract` the role `_owner` has authority over the functions shown in the diagram below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority. - Function `setPeriodRates()`: Sets the reward rate for the specified `period`. - Function `addToStakingPool()`: Provides tokens for distributing rewards. - Function `setEmergencyWithdrawalRates()`: Set the following state variables - `emergencyWithdrawalPenalty`: Records the rate of penalty when a user uses emergency withdrawal. - `burnRateEmergency`: Records the percentage of tokens which will be burned from penalty. - `poolRateEmergency`: Never used state variable. 
In the contract `Token` the role `_owner` has authority over the functions shown in the diagram below. - `setPair()` and `_setPair()`: Manage the usage status of pairs in this contract. The owner can add new pairs, or disable an active pair. - `setBuyTaxes()`: Set the fee rate when users get the `LLFR2` token from a pair. - `setSellTaxes()`: Set the fee rate when users use the `LLFR2` token to get other tokens from a pair. - `setLPThreshold()`: Set the value of the state variable `lpThreshold`. Transactions with the pair will accumulate `lp` fee, once the `lp` fee meets the `lpThreshold`, the contract will use all of the `lp` fees to add liquidity to the `LLFR2-WBNB` pair - `setMarketingThreshold()`: Set the value of the state variable `marketingThreshold`. - `setMarketingSwap()`: Set the value of the state variable `isSwap`. When the `isSwap` is true, the contract will swap all of the market fees to BNB. - `excludeFromFees()`: Manage the addresses that don't need to pay transaction fees. - `withdrawBNB()`: Withdraw the BNBs swapped from the market fees. - `withdrawMarketingTokens()`: Withdraw the `LLFR2`s that earned from the market fees. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority. 
In the contract `Token`, the EOA address `owner` has `100000000000000 * 10 ** decimals()` tokens minted at the time of the contract deployment, which has severe centralization risk and can manipulate the tokenomics of the project. ```solditiy=53 constructor(address _router) ERC20("LLFR2", "LLFR2") { _mint(msg.sender, 100000000000000 * 10 ** decimals()); ``` Also, the contract will use a part of the transaction fee to add liquidity, but the LP tokens are transferred to the EOA address `owner`, this contract logic results in a centralized token holding position over time. ```solidity=94 function addLiquidity(uint256 tokenAmount, uint256 bnbAmount) private { _approve(address(this), address(uniswapV2Router), tokenAmount); uniswapV2Router.addLiquidityETH{value: bnbAmount}( address(this), tokenAmount, 0, 0, owner(), block.timestamp ); } ``` Any compromise to the private keys of the `owner` EOA address may allow a hacker to take advantage of the EOA address authority and sell tokens on the market and/or remove the pool liquidity, causing a severe price drop.
Performing integer division before multiplication truncates the low bits, losing the precision of calculation. ```solidity=77 uint256 totalAllocation = _amount.mul(totalRate).div(100); ``` ```solidity=83 uint256 burnAmount = totalAllocation.mul(_burnRate).div(100); ``` --- ```solidity=104 uint256 rewardPerSecond = stakers[_staker].totalRewardAllocation.div(stakers[_staker].stakePeriod); ``` ```solidity=105 uint256 newReward = rewardPerSecond.mul(timeElapsed); ``` --- ```solidity=156 uint256 penalty = total.mul(emergencyWithdrawalPenalty).div(100); ``` ```solidity=157 uint256 burnAmount = penalty.mul(burnRateEmergency).div(100); ``` --- ```solidity=209 uint256 rewardPerSecond = stakers[_staker].totalRewardAllocation.div(stakers[_staker].stakePeriod); ``` ```solidity=210 uint256 accumulatedReward = rewardPerSecond.mul(timeElapsed); ```
The field `stakeTime` in the struct `Staker` is not updated in the function `unstake()`. --- The field `stakePeriod` in the struct `Staker` is not updated in the functions `unstake()` and `emergencyUnstake()`. --- The reward amount has been mistakenly added 2 times to the state variable `totalAllocated`. The total reward amount `totalAllocation` a user can receive is added to the state variable `totalAllocated` in the `stake()` function. ```solidity=95 totalAllocated = totalAllocated.add(totalAllocation); ``` Furthermore, in the `calculateReward()` function, the newly generated rewards are added to the state variable `totalAllocated`. This portion of the rewards is already included in the total rewards, so the code at line 110 duplicates the addition. ```solidity=110 totalAllocated = totalAllocated.add(newReward); ``` In addition, the state variable `totalAllocated` is also updated in the functions `unstake()` and `emergencyUnstake()`. ```solidity=138 totalAllocated = totalAllocated.sub(reward); ``` ```solidity=175 totalAllocated = totalAllocated.sub(uncollectedReward).add(poolAmount); ``` --- The amount of burned tokens has been mistakenly added 3 times to the state variable `totalBurned`. The amount of tokens that will be burned is added to the state variable `totalBurned` in the `stake()` function. ```solidity=96 totalBurned = totalBurned.add(burnAmount); ``` And this amount is also added to the state variable `totalBurned` in the `unstake()` function before executing the `burn` function. ```solidity=139 totalBurned = totalBurned.add(burnAmount); burn(burnAmount); ``` Finally, when the function `burn()` executes, this amount is added to the state variable `totalBurned` again. ```solidity=203 totalBurned = totalBurned.add(amount); ```
Based on our understanding, when the period has not ended, and a user wants to withdraw their staked tokens, they need to call the `emergencyUnstake()` function and accept the associated penalty. Therefore, it is necessary to validate the current time to prevent users from mistakenly calling the function and incurring financial losses.
The smart contract contains low-level calls such as `.call()`, `.delegatecall()`, and `.staticcall()`. Since these calls bypass some of the automatic checks that Solidity provides, like function type checks, they can introduce vulnerabilities, logic errors, or unexpected behavior. ```solidity=201 (bool success, ) = address(token).call(abi.encodeWithSignature("publicBurn(uint256)", amount)); ```
There should always be events emitted in the sensitive functions that are controlled by centralization roles.
A 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 back running (executing after the target) a transaction to sell the asset. The following functions are called without setting restrictions on slippage or minimum output amount. Hence, transactions triggering these functions are vulnerable to sandwich attacks, especially when the input amount is large : - `addLiquidity()` - `_swapTokensForBNB()` - `_swapTokensForBNBSimple()`
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.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (2)
1,044 projects reviewed · 1,393 reports · latest May 2025
See CertiK’s auditsSomething 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