As of 3 Nov 2026, Level has 1 audit report on Trustblock, by Quantstamp, dated 29 Mar 2023. That report lists 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 |
|---|---|---|---|---|---|
| Level Finance | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 1 | 0 | 4 |
| Medium | 2 | 1 | 2 |
| Low | 0 | 14 | 8 |
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.
Open the report`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.
Open the reportThe `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.
Open the reportWhen 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)`.
Open the reportWhen `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.
Open the reportIn `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.
Open the reportDivision 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`
Open the report`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.
Open the reportIn `_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.
Open the report`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.
Open the reportSmart 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`
Open the report`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.
Open the reportIn`_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.
Open the report`LevelReferralController.setEnableStake()` will always emit an `EnableStakeSet` event, even if the new value is equal to the old value of `enableStaking`.
Open the reportWhen `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.
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="level-1"></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