CertiK reviewed LendeXe Finance (LendeXe) on 28 Apr 2023: 28 findings, 1 critical; 25 marked fixed, 3 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 | 1 |
| High | 3 | 0 | 5 |
| Medium | 0 | 0 | 4 |
| Low | 0 | 0 | 15 |
The visibility of the function `transfer()` is public, which allows anyone to transfer tokens from the `from` address to the `to` address. The visibility of the function `approve()` is public, which allows anyone to set allowance of `spender` over the `owner's` token. The visibility of the function `spendAllowance()` is public, which allows anyone to update the `owner's` allowance for `spender` based on the spent `amount`.
In the history of the compound protocol, the calculation of the Comp rewards becomes incorrect after proposal 062(`Split COMP rewards distribution and bug fixes`) is executed. In response to this problem, the function `fixBadAccruals()` has been temporarily added in proposal 065(`Correct Over-Accrued COMP`) and removed later. The function `fixBadAccruals()` is a centralized function. It will update the users' unclaimed Comp rewards to decrease the incorrect rewards, and record the COMP debt in the variable `compReceivable` for the users whose unclaimed Comp rewards are not enough to decrease. The function `fixBadAccruals()` is only used to handle the error caused by this upgrade migration in the Compound protocol, which is useless in the newly deployed Compound forks. Refer to: - https://compound.finance/governance/proposals/62 - https://compound.finance/governance/proposals/65
The function `_setSwapHelperAddress()` is `external` and can be called by anyone as long as the contract is deployed.
The function `_setSwapRouter()` is `external` and can be called by anyone as long as the contract is deployed.
The input variable `nftAmount` is not validated in the function `supply()`, so the user can input the arbitrary `nft` amount to mint the maximum allowed XSD tokens.
The user can provide assets by calling the function provide() in the contract `UltimateLoan` and then directly call the function `burn()`/`burnShares()` in the contract `XSDStabilizer` to perform an arbitrage attack. The steps can be as follows, 1. call function `supply()` to lock xLEXE tokens as collateral, then receive XSD tokens, which is 150% of locked XLEXE tokens in value. 2. call the function `burn()`/`burnShares()` in the contract `XSDStabilizer` instead of the function `repay()` to burn XSD tokens will eventually receive 50% of the locked XLEXE tokens value in profit. ```solidity=240 (uint256 mintAmount, ) = stabilizer.mintShares(xsdAmount, supplyTokens); //UL will gain double the xsd amount that was set as input because it locks an equal amount of LEXE uint256 portionOfOwnerMint = mul_(mintAmount, Exp({mantissa: 0.75e18})); ``` In an extreme case, the user can give up repaying the loan, and receive 50% of the locked `xLEXE` tokens value in profit.
The contract deployer can input himself as the admin to pass the validation. ```solidity=349 constructor( XSDStabilizer XSDStabilizer_, address lexeVault_, address payable admin_, Lexe lexe_, XToken xLEXE_, XToken[] memory supplyTokens_, uint256[] memory supplyTokenShares_, XSDInterface xsd_, PriceOracle oracle_ ) public { // Creator of the contract is admin during initialization admin = msg.sender; require(msg.sender == admin, "only admin may initialize the UL"); // Set initial exchange rate ULPercentage = uint256(50); stabilizer = XSDStabilizer(XSDStabilizer_); ... ```
There is no validation to ensure the `lockEndBlocks[i]` is larger than the `_lockingPeriodEndBlock`.
There's no sanity check to validate if a `lockTime` is existing. If the `lockTime` does not exist, the user who locked assets will not get the rewards.
The user has locked their `xLEXE` tokens in the contract `UltimateLoanLock` for at least 3 months, with the expectation of minting `XDS` tokens through the function `supply()` in the contract `UltimateLoan` at the end of the lock period. However, if XDS's market share exceeds a certain threshold after three months, they may be unable to mint additional `XDS` tokens. - Contract `XSDStabilizer.sol` ```solidity=242 require( getHypotheticalLendexeShare(amount) < 0.6e18, "Market share of XSD is too high!" );
The `liquidate()` function calls the `xLEXE.transferFrom()` function with the `to` address specified as `lexeVault` for acquiring the `xLEXE` tokens. As a result, over time the `lexeVault` address will accumulate a significant portion of `xLEXE` tokens. If the `lexeVault` is an EOA (Externally Owned Account), the mishandling of its private key can have devastating consequences for the project as a whole.
In the function `liquidateStabilizerPool`, the `poolShares` is possibly deleted if the length of the array `_poolShares` is 1, and there is no function provided in the contract to reset the `poolShares`. Therefore, the function `liquidateStabilizerPool` will execute fail in the next call. We would like to confirm with the customer whether it is as intended.
`vt.lockEndBlock` is assigned the same value twice. ```solidity=111 for (uint256 i = 0; i < _lockTimes.length; i++) { uint8 distributionTime = uint8(totalSupply[i] / tokensPerDay); VestingModel storage vt = vestingModels[_lockTimes[i]]; vt.lockEndBlock = lockEndBlocks[i]; vt.totalSupply = totalSupply[i]; vt.lockEndBlock = lockEndBlocks[i]; vt.distributionTime = distributionTime; } ```
The array `supplyTokens` is not reset before new supply tokens are pushed, so it is impossible to update the supply tokens and shares if the function is called again. ```solidity=87 supplyTokens.push( supplyToken({ token: supplyTokens_[i], share: Exp({mantissa: supplyTokenShares_[i]}) }) ); ```
The function `calculateTokenPortions()` in the contract `LockDrop` should be called successfully daily. Otherwise, users who locked assets will potentially lose their rewards. We want to check with the team for more detail about the mechanism that can ensure the function `calculateTokenPortions()` run successfully daily.
The **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.
`EIP20NonStandardInterface` and `EIP20Interface` are used in the contract `XSDStabilizer`, but the import is missing.
There is no validation between `_lockTimes.length` , `lockEndBlocks.length` ,`totalSupply.length` and `_xTokenAddresses.length` in constructor. And there is no validation between `amounts.length` and `_xTokenAddresses.length` in function `lockAssets()`.
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 backrunning (executing after the target) 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: - `swapRouter.exactInputSingle(ISwapRouter.ExactInputSingleParams()`
A 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 is attacked) a transaction to purchase one of the assets and make profits by backrunning (after the transaction is 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: - `swapRouter.exactInputSingle()` - `swapRouter.exactInput()`
If the supply token status is marked as `BLACKLISTED` or `BANNED`, users are possibly unable to burn the XDS tokens to get the supply tokens.
These expressions in the contracts do not check arithmetic overflow. Such unsafe math operations may cause unexpected behavior if unusual parameters are given.
The transfer amount of `xwbtc`/`supplyToken` from the `XSDStabilizer` contract is not properly calculated, this will transfer all `xwbtc`/`supplyToken` left in the current contract to the recipient. The token balance should be checked before and after the transfer. ```solidity=665 xwbtc.mint(underlyingSupplyTokenBalance); xwbtc.transfer( address(xsdInterface), xwbtc.balanceOf(address(this)) ); ``` ```solidity=682 supplyToken.mint(underlyingSupplyTokenBalance); supplyToken.transfer( address(xsdInterface), supplyToken.balanceOf(address(this)) ); ```
The linked functions invocations do not check the return value of the function call which returns a value in case of a proper call.
As per the fee calculation logic, if the amount is less than 1, all the locked assets amount will be charged as fees. ```solidity=157 uint256 actualAmount = (amount * 99) / 100; uint256 fee = amount - actualAmount; ```
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (8)
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