DeXe Audit

CertiKFiles named in the report

CertiK reviewed DeXe Protocol (DeXe Audit) on 4 May 2023: 27 findings, 0 critical; 15 marked fixed, 1 acknowledged, 11 open.

No open critical findings
Open the reportOpen DeXe Protocol’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings27

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

SeverityOpenAcknowledgedFixed
Critical000
High011
Medium000
Low11014
LowIncompatibility with Deflationary TokensOpen

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.

LowLogic Issue of Sending Rewards in `GovPoolStakingOpen

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.

LowMissing Emit EventsOpen

Functions that update state variables should emit relevant events as notifications.

LowMissing Input ValidationOpen

## 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`.

LowNew Contract Instance Created in Upgradeable ContOpen

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.

LowPotential Inconsistency between `deployerBABTid`Open

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.

LowPotential Inconsistency between `totalPowerInTokeOpen

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); } ```

LowPotential Price Manipulation RiskOpen

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.

LowPotential Reentrancy AttackOpen

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]`

LowThe insurance `payout` can be less than `userInfoOpen

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.

Show 1 more
LowVariables That Could Be Declared as ImmutableOpen

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.

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 (53)

  • contracts/core/CoreProperties.sol
  • contracts/libs/trader-pool/TraderPoolInvest.sol
  • contracts/libs/price-feed/UniswapV2PathFinder.sol
  • contracts/trader/InvestTraderPool.sol
  • contracts/libs/gov-pool/GovPoolCommission.sol
  • contracts/core/ContractsRegistry.sol
  • contracts/factory/PoolFactory.sol
  • contracts/core/Globals.sol
  • contracts/libs/trader-pool/TraderPoolPrice.sol
  • contracts/libs/trader-pool/TraderPoolView.sol
  • contracts/libs/trader-pool-proposal/TraderPoolRiskyProposalView.sol
  • contracts/libs/trader-pool/TraderPoolDivest.sol
  • contracts/libs/gov-user-keeper/GovUserKeeperLocal.sol
  • contracts/libs/factory/GovTokenSaleDeployer.sol
  • contracts/libs/trader-pool/TraderPoolModify.sol
  • contracts/libs/gov-pool/GovPoolView.sol
  • contracts/gov/validators/GovValidatorsToken.sol
  • contracts/libs/gov-pool/GovPoolExecute.sol
  • contracts/libs/gov-pool/GovPoolRewards.sol
  • contracts/gov/ERC20/ERC20Sale.sol
  • contracts/libs/trader-pool-proposal/TraderPoolInvestProposalView.sol
  • contracts/gov/user-keeper/GovUserKeeper.sol
  • contracts/libs/gov-pool/GovPoolVote.sol
  • contracts/libs/utils/TokenBalance.sol
  • contracts/libs/gov-pool/GovPoolUnlock.sol
  • contracts/libs/trader-pool/TraderPoolExchange.sol
  • contracts/user/UserRegistry.sol
  • contracts/libs/gov-pool/GovPoolOffchain.sol
  • contracts/gov/proposals/TokenSaleProposal.sol
  • contracts/gov/validators/GovValidators.sol
  • contracts/libs/trader-pool/TraderPoolCommission.sol
  • contracts/libs/gov-user-keeper/GovUserKeeperView.sol
  • contracts/factory/PoolRegistry.sol
  • contracts/libs/trader-pool/TraderPoolLeverage.sol
  • contracts/libs/gov-pool/GovPoolStaking.sol
  • contracts/libs/data-structures/ShrinkableArray.sol
  • contracts/libs/math/MathHelper.sol
  • contracts/trader/TraderPoolRiskyProposal.sol
  • contracts/gov/ERC721/ERC721Power.sol
  • contracts/gov/ERC721/ERC721Multiplier.sol
  • contracts/core/PriceFeed.sol
  • contracts/trader/TraderPoolInvestProposal.sol
  • contracts/trader/BasicTraderPool.sol
  • contracts/libs/gov-pool/GovPoolCreate.sol
  • contracts/insurance/Insurance.sol
  • contracts/gov/settings/GovSettings.sol
  • contracts/gov/GovPool.sol
  • contracts/gov/proposals/DistributionProposal.sol
  • contracts/libs/utils/AddressSetHelper.sol
  • contracts/libs/utils/DataHelper.sol
  • contracts/libs/price-feed/PriceFeedLocal.sol
  • contracts/trader/TraderPool.sol
  • contracts/trader/TraderPoolProposal.sol

Part of DeXe Protocol’s security record

DeXe Protocol
Audits3 audits by 2 firmsLast audit Jun 2023, by Hacken.
Critical findings2 open critical findingsReported in “DeXe / DEX” by Hacken, Jun 2023. Not marked fixed in that report.
Open DeXe Protocol’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