As of 3 Nov 2026, FlokiFi has 3 audit reports by 1 security firm on Trustblock; the latest is by CertiK, dated 6 Nov 2023. Those reports list no open critical findings.
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 |
|---|---|---|---|---|---|
| Flokifi Staking | Security firm: CertiK | Date: | Scope: Files named in the report | No open critical findings | Open · skynet.certik.com |
| Flokifi II | Security firm: CertiK | Date: | Scope: Files named in the report | No open critical findings | Open · skynet.certik.com |
| Flokifi | Security firm: CertiK | Date: | Scope: Files named in the report | No open critical findings | Open · skynet.certik.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 6 | 0 | 5 |
| Medium | 0 | 0 | 5 |
| Low | 6 | 0 | 26 |
In the contract `FlokiRewardToken` the role `DEFAULT_ADMIN_ROLE` has authority over the functions shown in the diagram below. Any compromise to the `DEFAULT_ADMIN_ROLE` account may allow the hacker to take advantage of this authority and mint arbitrary amounts of tokens. 
Open the reportThe private variable `_lastAdded` has not been updated anywhere, hence `_lastAdded[lpToken]` will always be zero, as a result, the `_sellDelay` check will be easily passed.
Open the reportThe common UniswapV2 functionality allows contracts to swap tokens for a native token such as ETH. In order to receive the native token, the contract must implement either a `receive()` or `payable fallback` function. If the contract's Solidity version is below 0.6.x, the contract must implement an unnamed payable function.
Open the reportAccording to the following code: ```solidity function checkUpkeep( bytes memory /* checkData */ ) external view override returns (bool upkeepNeeded, bytes memory performData) { ... uint256 loopLimit = _lpTokens.length(); if (loopLimit > _maxSellBatchSize) { loopLimit = _maxSellBatchSize; } address[] memory tokenBatch = new address[](loopLimit); uint256 tokenBatchCounter = 0; for (uint256 i = 0; i < loopLimit; i++) { address lpToken = _lpTokens.at(i); if ((_lastAdded[lpToken] + _sellDelay) < block.timestamp) { tokenBatch[tokenBatchCounter] = lpToken; } } ... } ``` , if the length of `_lpTokens` is 30, while the `_maxSellBatchSize` is 20, then the `loopLimit` will be 20. In this case, the number from 20 to 29 of `_lpTokens` will never be added to the `tokenBatch` no matter how many times the `checkUpkeep()` is called.
Open the reportThe function `performUpkeep()` should perform the _removeLiquidity() for the LP tokens in the LPTokenProcessorV1 contract. However, this function takes the parameter `performData` as the target. The truth is the LP token in the parameter `performData` may not exist in the LPTokenProcessorV1 contract.
Open the reportThe function `stake()` does not check the `_minimumStakeTimestamp` not greater than the `endsAt`. Then there will be a period after `endsAt` with no rewards. However, when the user unstakes before `_minimumStakeTimestamp` the user will pay a penalty fee.
Open the reportWhen the user calls the extendStake function, the duration will be changed, which means the duration multiplier should be changed as well. However, there is no update of unstakeTimestamp in this function.
Open the reportThe `createVaultWithoutKey()` function can be called by someone in the whitelist to create a vault, but does not mint nft, so `IVault(vault).vaultKeyId()` always returns 0 when called within the function `createVaultWithoutKey`, and the variable `vaultByKey[keyId]` will be overwritten.
Open the reportAccording to the following code: ```solidity function checkUpkeep( ... address[] memory tokenBatch = new address[](loopLimit); uint256 tokenBatchCounter = 0; for (uint256 i = 0; i < loopLimit; i++) { address lpToken = _lpTokens.at(i); if ((_lastAdded[lpToken] + _sellDelay) < block.timestamp) { tokenBatch[tokenBatchCounter] = lpToken; } } ``` , the `tokenBatchCounter` should be added after `tokenBatch[tokenBatchCounter] = lpToken`.
Open the reportAccording to the following code, ```solidity function getAvailableTokens() public view returns (address[] memory, uint256[] memory) { ... // Send over all remaining tokens in the case where the unlock // timestamp has already expired. Otherwise vest linearly. if (block.timestamp >= unlockTimestamp) { amounts[i] = IERC20(tokens[i]).balanceOf(address(this)); } else { amounts[i] = (tokenAmounts[i] * (block.timestamp - lastVestingTimestamp)) / (unlockTimestamp - lockTimestamp); ... } ``` , when `block.timestamp < unlockTimestamp`, the `amounts[i]` will be calculated linearly in terms of the `block.timestamp - lastVestingTimestamp`, however, according to the contract `PaymentModuleV1`, when the token is a LP token, the amount of tokens deposited in the contract should be `fungibleTokenDeposits[i].amount - lpAmounts[j]` instead of the `tokenAmounts[i]`.
Open the reportThe function pauseStaking incorrectly change the state of `isPaused`.
Open the reportIn the constructor of the PenaltyFee contract, the `penaltyBasis` will be set by the parameter and it can be close to the MULTIPLIER_BASIS. Hence the `penaltyFee` can be bigger than the stake amount which is unreasonable.
Open the reportIn the function `extendRewardsDuration()`, the extended duration will be added to the old duration, the endsAt will be delayed for the extended duration as well. So this function needs to be called immediately after the old duration is over.
Open the reportIn the functions `getUserRewards()` and `rewardPerToken()`, the calculation uses the decimal to avoid the divide truncation issue. However, the base digit of the multiplication is missing in the calculation.
Open the reportWhen the function `setStakingDuration` is called, no previous reward tokens are left since the previous duration is over. However, there is no new reward tokens transferred in the contract. The similar issue is existing in the function `extendRewardsDuration`.
Open the reportIn the function `lastTimeRewardApplicableForUser`, the code logic will check whether the `block.timestamp` is greater than the unstakeTimestamp[_user]. If yes, it will directly return the unstakeTimestamp[_user]. If not, it means `block.timestamp` is not greater than the unstakeTimestamp[_user]. Since the _unstakeTimestamp must be not greater than endsAt which is restricted when staking, so the `block.timestamp` must be smaller than endsAt. ```solidity function lastTimeRewardApplicableForUser(address _user) public view returns (uint256) { if (block.timestamp > unstakeTimestamp[_user]) { return unstakeTimestamp[_user]; } return block.timestamp < endsAt ? block.timestamp : endsAt; } ```
Open the reportThe contract is serving as the underlying entity to interact with third-party `stakingToken`. 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. ```solidity=127 stakingToken.safeTransferFrom(msg.sender, address(this), _amount); ```
Open the reportWhen 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=241 USDT.safeTransferFrom(user, address(this), usdtAmount); ``` - Transferring tokens by `usdtAmount`. ```solidity=263 uint256 treasuryUSDTShare = usdtAmount - referrerUSDTShare - burnShare; ``` - The `usdtAmount` appears to be used for bookkeeping purposes without compensating the potential transfer fees.
Open the reportThe `_hasDiscount` function is called to check if it owns a certain nft that can be discounted, so there may be some sort of nft supported by this contract that can be used for borrowing, and therefore the user can be exempted from the charge by borrowing the nft.
Open the reportThe function `isV2LiquidityPoolToken()` is designed to check if the passed-in address token is an uniswap v2 pair, the function's checking is lax, and a malicious user can build a contract to pass the check, as is the case with the `isV3LiquidityPoolToken` function.
Open the reportThe `removeTokensFromSwappingByIndexes()` function is called to remove the addresses of the tokens at the specified locations. Since the removal is cyclic, it is possible that the incoming locations have already been removed and the function call will fail. ```solidity=141 address tokenAddress = _tokens[index].tokenAddress; _tokens[index] = _tokens[_tokens.length - 1]; _tokens.pop(); ```
Open the reportA sandwich attack might 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 (before the transaction being attacked) a transaction to purchase one of the assets and make profits by backrunning (after the transaction being attacked) 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: - `router.removeLiquidity()` - `router.swapExactTokensForTokens()`
Open the reportThe `reinvestV3PositionFees()` can be called by the owner of the vault to collect the LP fee and transfer the fee tokens to the current vault, then this part of the token to increase liquidity. ```solidity IERC20(token0).safeApprove(address(tokenAddress), earnedToken0); IERC20(token1).safeApprove(address(tokenAddress), earnedToken1); INonfungiblePositionManager.IncreaseLiquidityParams memory params = INonfungiblePositionManager.IncreaseLiquidityParams({ tokenId: tokenId, amount0Desired: earnedToken0, amount1Desired: earnedToken1, amount0Min: amount0Min, amount1Min: amount1Min, deadline: block.timestamp }); INonfungiblePositionManager(tokenAddress).increaseLiquidity(params); ``` The function `increaseLiquidity()` returns the amount of tokens used to add liquidity, and the parameter `earnedToken` may not necessarily be equal. As a result, there is a possibility that some tokens will remain in the contract. If these tokens are not present in `fungibleTokenDeposits`, then this portion of tokens will be locked in the contract.
Open the reportThe function `_processNfts` is called to remove liquidity and collect fees and then transfer the asset to the `lpTokenProcessor` for processing. The token accepted by the `lpTokenProcessor` should be equal to the difference between before and after the transfer. ```solidity=161 IERC20(price.v3LpTokens[i].token0).safeTransfer(address(lpTokenProcessor), balance0); IERC20(price.v3LpTokens[i].token1).safeTransfer(address(lpTokenProcessor), balance1); uint256 amount0Received = initialToken0Balance - IERC20(price.v3LpTokens[i].token0).balanceOf(address(this)); uint256 amount1Received = initialToken1Balance - IERC20(price.v3LpTokens[i].token1).balanceOf(address(this)); ```
Open the reportThe smart contract contains one or more values that are not used, which can lead to unnecessary complexity and reduced maintainability. ```solidity=105 uint256 referrerLPShare = 0; ``` - The variable `referrerLPShare` is never used after this assignment. --- ```solidity=107 referrerLPShare = (price.v2LpAmounts[i] * referrerBasisPoints) / 10000; ``` - The variable `referrerLPShare` is never used after this assignment. --- ```solidity=226 uint256 usdBalance = usdtAmount; ``` - The variable `usdBalance` is never used after this assignment. --- ```solidity=211 try discountNFTs[i].tokenOfOwnerByIndex(user, 0) returns (uint256) { return true; } catch ( bytes memory /* lowLevelData */ ) {} ``` - The variable `` is never used after this assignment.
Open the report```solidity=161 assembly { mstore(paymentTokens, sub(mload(paymentTokens), sub(totalTokens, paymentCounter))) mstore(paymentAmounts, sub(mload(paymentAmounts), sub(totalTokens, paymentCounter))) } ```
Open the reportThere should always be events emitted in the sensitive functions that are controlled by centralization roles.
Open the reportThe **require** can be used to check for conditions and throw an exception if the condition is not met. It is better to provide a string message containing details about the error that will be passed back to the caller.
Open the reportAddresses should be checked before assignment or external call to make sure they are not zero addresses. ```solidity=15 vaultFactory = vaultFactoryAddress; ``` - `vaultFactoryAddress` is not zero-checked before being used. --- ```solidity=43 USDT = usdtAddress; ``` - `usdtAddress` is not zero-checked before being used. --- ```solidity=38 FLOKI = flokiAddress; ``` - `flokiAddress` is not zero-checked before being used. --- ```solidity=42 treasury = treasuryAddress; ``` - `treasuryAddress` is not zero-checked before being used. --- ```solidity=169 treasury = newTreasury; ``` - `newTreasury` is not zero-checked before being used.
Open the reportThe role 'KeyHolder' has lots of privileges to control the corresponding vault, we would like the client to provide with us more information about the 'KeyHolder' role.
Open the reportThe local variable `oldPrice` and `oldBasisPoints` are defined but not used anywhere in the function, hence they are redundant.
Open the reportThe function call that sends Ether to an arbitrary address is unprotected. ```solidity=264 try router.swapExactETHForTokens{ value: ETHGained }(0, path, receiver, block.timestamp) returns ( uint256[] memory /* amounts */ ) {} catch ( bytes memory /* lowLevelData */ ) { return false; } ```
Open the reportThe return value of the transfer()/transferFrom() call is not checked. ```solidity=27 token.transferFrom(address(this), msg.sender, token.balanceOf(address(this))); ``` --- ```solidity=34 IERC20(_tokenAddress).transferFrom(address(this), msg.sender, _tokenAmount); ``` --- ```solidity=40 token.transferFrom(address(this), msg.sender, token.balanceOf(address(this))); ``` --- ```solidity=69 IERC20(_tokenAddress).transferFrom(address(this), msg.sender, _tokenAmount); ```
Open the reportThe low-level `call` function returns the status of the call as the first variable in the returned tuple. The status of the `call` is not asserted to be `true`, which would treat the low-level call as a success even when it reverted. ```solidity=201 payable(msg.sender).call{value: amount}(""); ```
Open the report```solidity=31 event LPTokenCleared(address indexed tokenAddress); ``` - `LPTokenCleared` is declared in `LPTokenProcessorV1` but never emitted.
Open the reportThe 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 reportThe reports name a repository or files. Open a report to see its scope.
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="flokifi"></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