Level Finance

QuantstampFiles named in the report

Quantstamp reviewed Level (Level Finance) on 29 Mar 2023: 32 findings, 0 critical; 14 marked fixed, 15 acknowledged, 3 open.

No open critical findings
Open the reportOpen Level’s security record

Report hosted on certificate.quantstamp.com.

Trustblock does not review code.

Findings32

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

SeverityOpenAcknowledgedFixed
Critical000
High104
Medium212
Low0148
MediumOwnership Can Be RenouncedAcknowledged

Contracts inheriting from `Ownable` contain a function called `renounceOwnership()` which allows the `owner` to leave the contract without an `owner`. This would leave every function guarded by the `onlyOwner` modifier unable to be executed. In the case of `Erc20Reserve`, the `transfer` function, guarded by `onlyOwner`, is the only way of retrieving funds from the contract. If ownership is renounced in this context, the funds in the contract would be lost forever.

Low`LevelDevFund` Improper InitializationAcknowledged

`LevelDevFund` inherits `Initializable`, a base contract used to "aid in writing upgradeable contracts, or any kind of contract that will be deployed behind a proxy". However, it is not written as an upgradeable contract and inherits from `Ownable`, which is not upgradeable. Therefore, when deployed, the contract will run the constructor from `Ownable`. At this point, it is possible for a malicious user to call `initialize`, setting `LEVEL_STAKE` to any desired value. Since the `owner` has no recourse to reset `LEVEL_STAKE`, the deployment would be a failure.

Low`LyLevel` ERC-20 DiscrepancyAcknowledged

The `LyLevel` contract implements a token by attempting to follow ERC-20. However, the constant `decimals` is declared as `uint256` instead of `uint8` (see [EIP-20](https://eips.ethereum.org/EIPS/eip-20#token)). This is potentially a result of trying to clone-and-own the OpenZeppelin ERC20 code. This is generally considered poor practice, as it obfuscates the intentions of the developer, and neglects the benefits of using time-tested libraries such as OpenZeppelin's resulting in errors like the above.

LowAccounting Errors when Dealing with Deflationary TokensAcknowledged

When placing a swap order via `OrderManager` with a deflationary token, the `executeSwapOrder()` function attempts to use the same amount used when calling the `transfer()` function of the token. This will lead to the `OrderManager` attempting to swap more tokens than actually received during placement of the order. If there are not enough tokens in `OrderManager`, then the transaction will revert. Moreover, returning funds via `cancelOrder()` may fail due to insufficient token balance in `OrderManager`. In `LevelMasterV2`, `addLiquidity()` calculates ``` IERC20(assetToken).safeTransferFrom(msg.sender, address(this), assetAmount); IERC20(assetToken).safeIncreaseAllowance(address(levelPool), assetAmount); levelPool.addLiquidity(tranche, assetToken, assetAmount, minLpAmount, address(this)); ``` After the transfer from `msg.sender` to the `LevelMasterV2` contract, the actual transferred amount might be lower than the suppose `assetAmount`. This means we are potentially giving more allowance to the `levelPool` contract than it should have access to. However, this is not an issue in `levelPool.addLiquidity()` as it uses the actual balance changed through the call `_transferIn(_token, _amountIn)`.

LowAssets Cannot Be Removed From the PoolAcknowledged

When `addToken()` is called, the token's address is added to the `allAssets` array. However, there is no way for the protocol to remove added tokens, which means that once the `MAX_ASSETS` threshold is reached (currently set at 10 total assets), the protocol will not be able to add any new tokens or remove existing ones.

LowLax Assumptions for `LyLevel.sol`Acknowledged

In `claimable()`, it is required that `_batchId <= currentBatchId`. However, it doesn't make sense to allow claiming at `currentBatchId` since `rewardPerShare` for the current batch would not have been set yet. This is only set in `allocateReward()`, but by calling it, `currentBatchId` will be incremented.

LowLoss of Precision Due to Division before MultiplicationAcknowledged

Division before multiplication may result in a loss of precision when the operations are carried over integer numbers. This was noticed in the following instances: * `LevelOracle` * #194 -- `lowerBound` * #195 -- `minLowerBound` * #196 -- `upperBound` * #197 -- `maxUpperBound` * `Pool` * #828 -- `tokenInfo.lastAccrualTimestamp` * #835 -- `tokenInfo.borrowIndex` * #836 -- `tokenInfo.lastAccrualTimestamp` * #888 -- `feeAdjust_scope_0`

LowMissing Interface Inheritance for `OrderManager`Acknowledged

`OrderManager` is currently not inheriting from the `IOrderManager` interface. It is generally recommended for Solidity contracts to inherit interfaces in order to define an abstract behavior for the extending contract. This helps to ensure that the contract implements the required functions and adheres to the specified behavior.

LowPool Asset Amount Increase Is Not Reflected in Check in `_reservePoolAsset()`Acknowledged

In `_reservePoolAsset()`, the `if` statement checks whether the asset's new reserved amount is greater than the pool amount. If this is not true, the logic is allowed to continue and `_reserveTrancheAsset()` is called. This check is being made because there should never be a situation where the pool amount is less than the reserved amount. However, `_reserveTrancheAsset()` increases not just the reserved amount, but also the pool amount. Therefore, the check is validating the wrong amounts. This may occasionally cause certain position increases to be blocked.

LowPotential User Error Using `setRiskFactor()`Acknowledged

`setRiskFactor()` loops through an array of `RiskConfig` objects, one for each tranche with a risk factor to be modified. However, this leaves room for the user to mistakenly input a tranche twice, leading to an incorrect risk configuration.

Show 5 more
LowPrivileged Roles and OwnershipAcknowledged

Smart contracts will often have variables designating users with special privileges. This phenomenon occurs in numerous contracts throughout the Level Finance codebase. * `Pool.sol` * Functions using `onlyOrderManager` * Functions using `onlyOwner` * Functions using `_validateFeeDistributor` * `OrderManager.sol` * Functions using `onlyExecutor` * Functions using `onlyOwner` * `LPToken.sol` * `_mint()` requires the caller to be `minter` * `LevelOracle.sol` * Functions using `onlyOwner` * `LevelStake.sol` * `setRewardPerSecond()` uses `onlyOwner` * `LevelZap.sol` * `addZap()` and `removeZap()` use `onlyOwner` * `LevelMasterV2.sol` * Functions using `onlyOwner` * `Erc20Reserve.sol` * `transfer()` uses `onlyOwner` * `LevelDevFund.sol` * Functions using `onlyOwner` * `LVLOracle.sol` * `update()` requires `msg.sender` to be `updater` * `LevelReferralController.sol` * Functions using `onlyOwner` * `LevelReferralRegistry.sol` * `setController()` uses `onlyOwner` * `LyLevel.sol` * Functions using `onlyOwner`

LowStaking Rewards AccountingAcknowledged

`LevelStake` gives `LGO` rewards to users that stake `LVL` tokens in it. It transfers the reward (if any) when staking, unstaking, or when the user claims it (`claimRewards()`). `LevelStake._safeTransferLGO()` sanitizes the number of tokens to not send more than the contract balance. In case there are not enough tokens, the user's `rewardDebt` is updated as if the full amount was transferred.

LowUnderconstrained Assumptions for OracleAcknowledged

In`_getReferencePrice()`, we calculate the following formula: `minLowerBound = refPrice * (PRECISION - 3 * config.chainlinkDeviation) / PRECISION`. However, in `configToken()`, it is only required that `chainlinkDeviation != 0 && chainlinkDeviation < PRECISION / 2`. Consequently, if the owner misconfigures the token by setting `chainlinkDeviation` to somewhere between `PRECISION / 3` and `PRECISION / 2`, calls to `_getReferencePrice()` will revert due to underflow. This means any call to get the price of the token from the oracle will fail. However, this can be fixed by having the `owner` call `configToken()` again with the correct parameters.

LowUnexpected EventAcknowledged

`LevelReferralController.setEnableStake()` will always emit an `EnableStakeSet` event, even if the new value is equal to the old value of `enableStaking`.

LowWhitelisted Tokens Cannot Be RemovedAcknowledged

When `configToken()` is called, the token address is pushed to `whitelistedTokens` if the token's `baseUnits` is equal to zero, and the token's properties are configured. The function doesn't allow the owner to remove a whitelisted token since the `baseUnits` cannot be reset back to zero as passing `tokenDecimals` as a zero value would make the function revert.

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

  • src/interfaces/IRewarder.sol
  • src/lib/FixedPoint.sol
  • src/interfaces/ILevelMaster.sol
  • src/timelock/Timelock.sol
  • src/farm/LevelMasterV2.sol
  • src/interfaces/ILyLevelToken.sol
  • MathUtils.sol
  • src/lib/PairOracleTWAP.sol
  • src/interfaces/ILevelStake.sol
  • PoolErrors.sol
  • src/lockdrop/LockDrop.sol
  • PoolHook.sol
  • src/treasury/Treasury.sol
  • src/farm/LevelStake.sol
  • src/interfaces/IUniswapV2Pair.sol
  • ETHUnwrapper.sol
  • src/interfaces/ILPToken.sol
  • Pool.sol
  • src/fund/LevelDevFund.sol
  • src/lib/UniswapV2OracleLibrary.sol
  • src/interfaces/IWETH.sol
  • PositionUtils.sol
  • LPToken.sol
  • IOrderManager.sol
  • LiquidityRouter.sol
  • src/fund/Erc20Reserve.sol
  • src/lib/UQ112x112.sol
  • IPool.sol
  • ILPToken.sol
  • src/tokens/LyLevel.sol
  • src/referral/LevelReferralRegistry.sol
  • src/tokens/LevelGovernance.sol
  • IOracle.sol
  • PoolStorage.sol
  • src/referral/LevelReferralController.sol
  • IWETH.sol
  • IPoolHook.sol
  • IETHUnwrapper.sol
  • src/tokens/LevelToken.sol
  • AggregatorV3Interface.sol
  • OrderManager.sol
  • src/interfaces/ILVLOracle.sol
  • ILevelOracle.sol
  • src/lib/Babylonian.sol
  • SignedInt.sol
  • src/farm/LevelZap.sol
  • src/interfaces/IPool.sol
  • IReferralController.sol
  • src/fund/DaoVesting.sol
  • src/oracle/LVLOracle.sol
  • IOrderHook.sol
  • OrderHook.sol
  • IMintableErc20.sol
  • src/lib/GovernancePowerDelegationERC20.sol
  • LevelOracle.sol
  • src/farm/LevelMaster.sol
  • src/interfaces/IGovernancePowerDelegationToken.sol
  • src/interfaces/ILevelReferralRegistry.sol

Part of Level’s security record

Level
Audits1 audit by 1 firmLast audit Mar 2023, by Quantstamp.
Critical findingsNo open critical findingsIn 1 report.
Open Level’s security record

Security firm

Quantstamp

236 projects reviewed · 332 reports · latest Oct 2026

See Quantstamp’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