Quantstamp reviewed Levva Protocol on 11 Oct 2023: 24 findings, 0 critical; 16 marked fixed, 8 acknowledged.
Report hosted on certificate.quantstamp.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 | 0 | 5 |
| Medium | 0 | 0 | 3 |
| Low | 0 | 8 | 8 |
The `MarginlyParams.quoteLimit` parameter indicates the maximum amount of assets denominated in the quote asset price that the protocol can hold separately for both the quote and base asset. It is checked for before performing asset deposits and opening (or adding to) a long or short position. We want to point out that this is more of a soft limit. It can be exceeded in natural protocol conditions, e.g., when positions are closed, and in case of direct transfers to the protocol followed by a balance syncing.
With the `receivePosition()` function, any user can take over a liquidatable position from another user, given that they provide at least enough collateral to keep the position healthy. According to the documentation, the function seems to have mainly been designed for illiquid collateral situations, but it is worth noting that it can be called at any time. In most market conditions, receiving positions will be very lucrative for bots to monitor and execute, as they could immediately realize the 5% liquidation bonus. Therefore, we do not think the pool will receive the 5% liquidation bonus regularly. The bots will not call the `receivePosition()` function for insolvent positions, so only the unprofitable liquidations will be left to the pool or our client's deployed instance of the monitor bot. While these bots will increase the system's stability with timely liquidations, they will most likely receive most of the liquidation profits. This has been acknowledged by the client, who stated that ensuring several means of protection from liquidations is crucial and that the primary source of revenue for LPs will most likely come from the borrowing activity and not the liquidations.
In the current design of the Marginly pool, the `reinit()` function only checks whether the position at the top of the heap (i.e., the riskiest position) is unhealthy. Even though other positions in the heap are unhealthy, they will not get liquidated since the `reinit()` function is executed only once per block. As a result, the `reinit()` function can liquidate at most one unhealthy position in each block, even if multiple positions within the heap are unhealthy. Although liquidators can directly trigger the liquidation of any unhealthy position in the system by utilizing `receivePosition()`, this design presents potential challenges in scenarios where liquidators are absent and price changes rapidly. If the unhealthy position fails to be liquidated timely, the system may incur bad debt.
The design of all the adapters does not support fee-on-transfer tokens. Furthermore, the pool's accounting would not work well using rebasing tokens as the quote or base asset. Therefore, such tokens shall not be used for the protocol.
The Marginly pool maintains three system coefficients to track the debt and collateral amount in users' positions. These coefficients include the debt coefficient, collateral coefficient, and deleverage coefficient. Readers can find detailed definitions in the [official documentation](https://docs.marginly.com/protocol-architecture/pool-variables). The coefficients used are of type `FP96`, representing fixed-point numbers with 96 bits of precision. The smallest possible value in this representation is `1 / (1 << 96)`, approximately 1.26E-29. In normal circumstances, the debt and collateral coefficients increase as the pool accrues interest from borrowers. However, two coefficients may decrease in the following situations: - Debt coefficients may decrease when deleveraging occurs. - Collateral coefficients may decrease when the liquidated position has a negative net difference. Debt coefficients may decrease significantly when a whale position is liquidated through deleveraging. The size of the liquidated position correlates with the reduction in debt coefficients. In the most extreme case, where the whale's collateral exceeds the total debt of all positions on the opposite side, the debt coefficient can be reduced to only represent the remaining interest portion. As the interest portions are generally much smaller than the total debt in the system, this significantly reduces the debt coefficient. If whale positions are liquidated through deleveraging multiple times, the updated debt coefficients may eventually fall below the minimum representable value of the FP96 type. However, due to precision loss during the calculations, they will be rounded up to the minimum value. This incorrect updating of debt coefficients can lead to inaccurate internal accounting, potentially impacting user positions.
Smart contracts often have `owner` variables to designate the person with special privileges to modify the smart contract. If a malicious user compromises or controls the contract owner, sensitive functionality can be exploited. The following are all instances of functionality restricted to the contract owner or a privileged role: * Only the `owner` address of the `MarginlyFactory` contract can: * call `MarginlyFactory.createPool()`, deploying new pools with specified `MarginlyParams` and linked with a specific Uniswap pool. * call `MarginlyFactory.changeSwapRouter()`, replacing the existing swap router. A replacement with a malicious swap router could drain all input tokens from the protocol users' requested swaps. * call `MarginlyFactory.setOwner()`, transferring the contract ownership to any address, including the zero address. * Only the factory `owner` can * call `MarginlyPool.setParameters()` at all times, which can potentially severely misconfigure the pool. Updated values include the maximum leverage, the TWAP configuration, the basic interest rate, the fee ("close debt fee") and swap fee, the slippage thresholds, the minimum size of a position, and the discussed `quoteLimit`. * call `MarginlyPool.shutdown()`, shutting down the pool and setting it to the proper emergency mode when either long's or short's collateral is exceeded by its respective debt. * call `MarginlyPool.sweepETH()`, receiving all the protocol's ETH. It should be noted that the internal base asset is not expected to be ETH but WETH, so it does not enable the withdrawal of core protocol pool assets. * Only the `owner` role of the `MarginlyRouter` contract can: * call `RouterStorage.addDexAdapters()`, enabling the addition and overwriting of swap adapters, i.e., intermediary contracts that settle the swap requests. A compromised adapter could forward to-be-swapped funds to arbitrary addresses. * Only the `owner` role of the specific adapters in use can: * call `AdapterStorage.addPools()`, where new pools can be added or existing pools overridden for a certain DEX adapter. A malicious address added as a pool could forward to-be-swapped funds to arbitrary addresses. Furthermore, the Marginly team is expected to take over slippage-exceeding liquidations in turbulent market conditions. If this would not happen, the pool could become insolvent.
Marginly pools rely on Uniswap V3 pools as TWAP oracles to perform calculations related to position health, system leverage, pool balance limits, etc. As a result, the resistance of TWAP oracles to manipulation is critical to the system, as manipulated prices can have significant consequences for both the system and its users. This issue highlights the potential risks associated with utilizing Uniswap V3 TWAP oracles: 1. Uniswap V3 pools with low liquidity are more susceptible to manipulation, as attackers require less capital to influence prices. Since protocol admins can create Marginly pools using Uniswap V3 pools of different fee tiers, they should be cautious when selecting fee tiers associated with pools with less liquidity. 2. The design of concentrated liquidity of Uniswap V3 pools can make TWAP manipulations more cost-efficient as liquidity concentrated within specific price ranges may reduce the costs caused by price slippage. 3. The presence of multi-block MEV introduces risks of TWAP manipulation. As block producers, attackers can leverage control over consecutive blocks, thereby increasing the ease of price manipulation and avoiding the risk of being arbitrated by external actors. 4. An insufficient TWAP period may make the calculated prices more susceptible to manipulation.
Liquidations have a slippage threshold based on the `params.mcSlippage` value applied to the `slot0()` price in the respective pool. The liquidation call reverts if the slippage is exceeded. The Marginly team is expected to overtake illiquid positions in such situations. We want to raise awareness that the slippage checks may delay or entirely prevent the admins from liquidating these illiquid positions when slippage is too high, and the swaps fail. Thus, the illiquid positions could become more unhealthy and impact overall system health.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (40)
236 projects reviewed · 332 reports · latest Oct 2026
See Quantstamp’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