CertiK reviewed DeXe Protocol (DeXe Audit) on 4 May 2023: 27 findings, 0 critical; 15 marked fixed, 1 acknowledged, 11 open.
Report hosted on skynet.certik.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 1 | 1 |
| Medium | 0 | 0 | 0 |
| Low | 11 | 0 | 14 |
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 ## PriceFeed.sol In contract `PriceFeed`, below are the involved code snippets: ```solidity=96 _grabTokens(inToken, amountIn); ``` - Transferring tokens by `amountIn`. - This function call executes the following operation. ```solidity=98 uint256[] memory outs = uniswapV2Router.swapExactTokensForTokens( amountIn, minAmountOut, foundPath.path, msg.sender, block.timestamp ); ``` - The `amountIn` appears to be used for bookkeeping purposes without compensating the potential transfer fees. ```solidity=141 _grabTokens(inToken, maxAmountIn); ``` - Transferring tokens by `maxAmountIn`. - This function call executes the following operation. ```solidity=151 IERC20(inToken).safeTransfer(msg.sender, maxAmountIn - ins[0]); ``` - The `maxAmountIn` appears to be used for bookkeeping purposes without compensating the potential transfer fees. --- ## Insurance.sol In contract `Insurance`, below are the involved code snippets: ```solidity=65 _dexe.transferFrom(msg.sender, address(this), deposit); ``` - Transferring tokens by `deposit`. ```solidity=62 userInfos[msg.sender].stake += deposit; ``` - The `deposit` appears to be used for bookkeeping purposes without compensating the potential transfer fees. --- ## DistributionProposal.sol In contract `DistributionProposal`, below are the involved code snippets: ```solidity=75 rewardToken.safeTransferFrom(govAddress, address(this), reward - balance); ``` - Transferring tokens by `reward-balance`. - The `reward-balance` appears to be used for bookkeeping purposes without compensating the potential transfer fees. --- ## TraderPoolProposal.sol In contract `TraderPoolProposal`, below are the involved code snippets: ```solidity=123 IERC20(_parentTraderPoolInfo.baseToken).safeTransferFrom( _parentTraderPoolInfo.parentPoolAddress, address(this), baseInvestment.from18(_parentTraderPoolInfo.baseTokenDecimals) ); ``` - Transferring tokens by `baseInvestment.from18(_parentTraderPoolInfo.baseTokenDecimals)`. - The `baseInvestment` appears to be used for bookkeeping purposes without compensating the potential transfer fees.
According to the design of the `GovPoolStaking` contract, the `unstake()` function will calculate users' rewards from delegatees' micro pools. When sending rewards, the contract sends the minimum between the contract's token balance and the user's entitled rewards. In both cases, the delegator's reward is completely cleared. ```solidity=126 delegatorInfo.pendingRewards = 0; rewardToken.sendFunds(msg.sender, rewards.min(rewardToken.normThisBalance())); ``` A more reasonable approach would be to reduce `delegatorInfo.pendingRewards` by `rewards.min(rewardToken.normThisBalance())`, enabling users to receive the remainder of their reward at a later time once the contract has accrued more rewards.
Functions that update state variables should emit relevant events as notifications.
## CoreProperties.sol According to the whitepaper, the listed functions in `CoreProperties` and the corresponding variables should fall within specific ranges, and it is recommended to perform validation checks to ensure their correctness: - `setMaximumPoolInvestors()` - `setMaximumOpenPositions()` - `setTraderLeverageParams()` - `setCommissionInitTimestamp()` - `setCommissionDurations()` - `setDEXECommissionPercentages()` - `setTraderCommissionPercentages()` - `setDelayForRiskyPool()` - `setInsuranceParameters()` - `setGovVotesLimit()` --- ## ERC721Multiplier.sol In contract `ERC721Multiplier`, the function `mint()` is missing an input validation for `multiplier`. The field `multiplier` of `NftInfo` will be used to calculate the reward in L67: ```solidity=67 ? rewards.ratio(_tokens[latestLockedTokenId].multiplier, PRECISION) ``` The aforementioned code snippet implements the following calculation: $\text{rewards} \times \frac{\text{\_tokens[latestLockedTokenId].multiplier}}{\text{PRECISION}}$. It is unclear if `multiplier` should be less than `PRECISION`. If `multiplier` is greater than `PRECISION`, the function `getExtraRewards()` will return a greater value than `rewards`.
When creating a new instance of a contract from upgradeable contract’s code, these creations are handled directly by Solidity and not by OpenZeppelin Upgrades, which means that these contracts will not be upgradeable.
The state variable `deployerBABTid` is set by the contract initializer and cannot be modified after the contract is deployed. According to the contract `PoolFactory`, `babtId` is defined as follows: ```solidity=277 if (_babt.balanceOf(msg.sender) > 0) { babtId = _babt.tokenIdOf(msg.sender); } ``` However, the state variable `babt` is set in function `setDependencies()` and can be modified more than once. There may exist inconsistency between `deployerBABTid` and `babt` when invoking the function `setDependencies()` multiple times, that is, the `deployerBABTid` may be for a different address if `babt` changes.
According to the design of the `GovUserKeeper` contract, the `_setERC721Address()` function is utilized to initialize the information of NFT contract and store it in `_nftInfo`. There is no relationship between the values of `_nftInfo.totalPowerInTokens` and `_nftInfo.totalSupply`. ```solidity=650 require(totalPowerInTokens > 0, "GovUK: the equivalent is zero"); _nftInfo.totalPowerInTokens = totalPowerInTokens; if (!IERC165(_nftAddress).supportsInterface(type(IERC721Power).interfaceId)) { if (!IERC165(_nftAddress).supportsInterface(type(IERC721Enumerable).interfaceId)) { require(nftsTotalSupply > 0, "GovUK: total supply is zero"); _nftInfo.totalSupply = uint128(nftsTotalSupply); } } else ``` In the `nftVotingPower()` function of the `GovUserKeeperView` contract, if the NFT contract does not support power (`nftInfo.isSupport = true`), the nftPower is calculated by $\frac{\text{\_nftInfo.totalPowerInTokens}}{\text{\_nftContract.totalSupply()}}$ or $\frac{\text{\_nftInfo.totalPowerInTokens}}{\text{\_nftInfo.totalSupply}}$. However, if `_nftInfo.totalPowerInTokens` is less than `nftContract.totalSupply()` or `_nftInfo.totalSupply`, the `nftPower` will be zero for each `nftId`, which is inconsistent with the fact that `totalPowerInTokens` is positive. ```solidity=72 if (!nftInfo.isSupportPower) { uint256 totalSupply = nftInfo.totalSupply == 0 ? nftContract.totalSupply() : nftInfo.totalSupply; if (totalSupply > 0) { uint256 totalPower = nftInfo.totalPowerInTokens; if (calculatePowerArray) { for (uint256 i; i < nftIds.length; i++) { perNftPower[i] = totalPower / totalSupply; } } nftPower = nftIds.length.ratio(totalPower, totalSupply); } ```
The DeXe network only considers the Uniswap V2 pool as the protocol's price feed. Attackers with enough funds or using flashloan operations could temporarily manipulate the price of the underlying assets.
A reentrancy attack can occur when the contract creates a function that makes an external call to another untrusted contract before resolving any effects. If the attacker can control the untrusted contract, they can make a recursive call back to the original function, repeating interactions that would have otherwise not run after the external call resolved the effects. ## TraderPoolInvestProposal.sol ### External call(s) ```solidity=87 _transferAndMintLP(proposalId, trader, lpInvestment, baseInvestment); ``` * This function call executes the following external call(s). * In `ERC1155._doSafeTransferAcceptanceCheck`, * `IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data)` ### State variables written after the call(s) ```solidity=89 _proposalInfos[proposalId].descriptionURL = descriptionURL; _proposalInfos[proposalId].lpLocked = lpInvestment; _proposalInfos[proposalId].investedBase = baseInvestment; _proposalInfos[proposalId].newInvestedBase = baseInvestment; ``` --- ### External call(s) ```solidity=117 _transferAndMintLP(proposalId, trader, lpInvestment, baseInvestment); ``` * This function call executes the following external call(s). * In `ERC1155._doSafeTransferAcceptanceCheck`, * `IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data)` ### State variables written after the call(s) ```solidity=119 _proposalInfos[proposalId].descriptionURL = descriptionURL; _proposalInfos[proposalId].lpLocked = lpInvestment; _proposalInfos[proposalId].investedBase = baseInvestment; _proposalInfos[proposalId].newInvestedBase = baseInvestment; ``` --- ## ERC721Power.sol ### External call(s) ```solidity=93 _safeMint(to, tokenId, ""); ``` * This function call executes the following external call(s). * In `ERC721._checkOnERC721Received`, * `(retval) = IERC721Receiver(to).onERC721Received(_msgSender(),from,tokenId,data)` ### State variables written after the call(s) ```solidity=95 totalPower += getMaxPowerForNft(tokenId); ``` --- ## TokenSaleProposal.sol ### External call(s) ```solidity=103 ERC20(tierView.saleTokenAddress).safeTransfer( msg.sender, saleTokenAmount.percentage(PERCENTAGE_100 - tierView.vestingSettings.vestingPercentage) ); ``` ### State variables written after the call(s) ```solidity=108 tierInfo.tierInfoView.totalSold += saleTokenAmount; ``` ```solidity=110 tierInfo.customers[msg.sender] = Purchase({ purchaseTime: uint64(block.timestamp), vestingTotalAmount: saleTokenAmount.percentage( tierView.vestingSettings.vestingPercentage ), vestingWithdrawnAmount: 0, latestVestingWithdraw: 0 }); ``` --- ## GovPoolUnlock.sol ### External call(s) ```solidity=45 maxUnlocked = IGovUserKeeper(userKeeper) .unlockTokens(proposalId, user, isMicropool) .max(maxUnlocked); ``` ```solidity=48 IGovUserKeeper(userKeeper).unlockNfts( voteInfos[proposalId][user][isMicropool].nftsVoted.values() ); ``` ### State variables written after the call(s) ```solidity=52 userProposals.remove(proposalId); ``` * This function call executes the following assignment(s). * In `EnumerableSet.remove`, * `_remove(set._inner,bytes32(value))` * In `EnumerableSet._remove`, * `set._values[toDeleteIndex] = lastValue` * In `EnumerableSet._remove`, * `set._indexes[lastValue] = valueIndex` * In `EnumerableSet._remove`, * `set._values.pop()` * In `EnumerableSet._remove`, * `delete set._indexes[value]`
The linked function `_payout()` calculates the total insurance payout for the `user`. If `insurancePayout / _coreProperties.getInsuranceFactor() > userInfo.stake`, we have `stakePayout = userInfo.stake` and `payout = stakePayout + insurancePayout > userInfo.stake`. But `payout` may be less than `userInfo.stake` if `insurancePayout / _coreProperties.getInsuranceFactor() < userInfo.stake`. If the insurance premium is to be paid only once, it is not rational for a user to buy insurance if the payout will be less than the premium.
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 (53)
1,044 projects reviewed · 1,375 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