As of 3 Nov 2026, UnibotX has 1 audit report on Trustblock, by Quantstamp, dated 13 Nov 2022. 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 |
|---|---|---|---|---|---|
| UniBot (Diamond Protocol) | 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 | 0 | 0 | 2 |
| Medium | 1 | 0 | 8 |
| Low | 1 | 7 | 15 |
`DiamondFactoryV3._closePosition()` Liquidation fees are applied to the amounts returned by `DiamondFactoryV3._removeUniLiquidity()` which are equivalent to the initial want token deposit minus the reserve and the amount borrowed with leverage. For example, assuming a liquidation fee of 5% and a 4x leverage with zero reserve ratio, the final fees applied on the original deposited amount turn out to be close to 20%, which is a high value. The issue described can lead users if liquidation is allowed for non-keeper addresses (owners of the position e.g) or keepers to get back a value even higher than the initial deposit (worst case scenario).
Open the report`DiamondFactoryV3._trySwapExactOutput()` uses the average price tick (`oracleTimeWeightedSec = 900`) to estimate the maximum `quoteAmount` and can be the reason why the swap will possibly throw leading `DiamondFactoryV3._trySwapExactOutput()` to revert due to the requirement stated below: ``` require(_amountIn < amountInWithSlippage, "Slippage protect"); ``` This will cause healthy positions to revert when closing them and possibly turn to unhealthy or even to bad positions. Please note that this issue depends on a random factor (the price volatility during the last `oracleTimeWeightedSec`), and it is hard to predict when a user will be able to claim his position again.
Open the reportThe lending pool used by the contract can be set and updated with the function `setLendingPoolAddress()`. Only the owner of the contract has the right to execute that function. However, if the lending pool is malicious or if it gets exploited, it gets the right to drain all the `borrowToken` owned by the contract.
Open the report`LendingPoolV3.repayBorrow()` checks if the caller (which should be factory) is in the whitelist. However, if a factory borrows from a lending pool, and then is removed from the whitelist, it will not be able to call `LendinPool.repayBorrow()`.
Open the reportThe contracts `DiamondFactoryV3` and `BalanceVaultV3` have no constructor but have a function `init()`. As a result, once the contracts are deployed, everyone can call the function `init()` and become the owner of these contracts.
Open the reportThe function `_findPositionIndex()` checks with a for loop if a user owns a given position based on its `_positionId`. However, the loop does not use the library `SafeMath` and uses an `uint8` variable which can store a maximum of 256 values. As a result, an overflow can happen on the variable `i`, which would lead to an infinite loop. Such a situation can be mitigated with the state variable `maxPositionNumber` of the contract `DiamondFactoryController`, but there is no upper limit for its value. If `maxPositionNumber` is not set properly, closing a position that has an id higher than 255 will cause the transaction to throw.
Open the reportIf a particular position reaches a `healthFactor < 1` it can be liquidated through a call to `DiamondFactoryV3.liquidate()`. This function checks that a caller is registered as a keeper in `DiamondFactoryController` when `requireKeeper` flag is set. However, a user can call `DiamondFactoryV3.closePosition()` on an unhealthy position he owns and bypass the keeper requirement imposed in `liquidate()`.
Open the reportA reentrancy vulnerability is a scenario where an attacker can repeatedly call a function from itself, unexpectedly leading to potentially disastrous results. This is done through a function that does not respect Checks-Effects-Interactions. The [Checks-Effects-Interactions](https://solidity.readthedocs.io/en/latest/security-considerations.html#use-the-checks-effects-interactions-pattern) coding pattern is meant to mitigate any chance of other contracts manipulating the state of the blockchain in unexpected and possibly malicious ways before control is returned to the original contract. As the name implied, only after checking whether appropriate conditions are met and acting internally on those conditions should any external calls to, or interactions with, other contracts be done. All the functions listed below need to have a re-entrancy guard set: - `DiamondFactoryV3.liquidate()` - `DiamondFactoryV3.stopLoss()` - `DiamondFactoryV3.openPosition()` - `DiamondFactoryV3.closePosition()` - `DiamondFactoryV3.addCollateral()` - `DiamondFactoryV3.decreaseCollateral()` - `DiamondFactoryV3.collectFee()` - `DiamondFactoryV3.updateStopLossPrice()` - `BalanceVaultV3.deposit()` - `BalanceVaultV3.withdraw()` - `BalanceVaultV3.transferBalanceFromVault()` - `LendingPoolV3.deposit()` - `LendingPoolV3.withdraw()` Even if a function seems re-entrancy safe due to its interaction with a trusted third-party contract (known token contract, e.g.), it should not be trusted.
Open the reportA common attack in DeFi is the sandwich attack. Upon observing a position opening or closing, an attacker can front-run the victim transaction by buying one of the assets, letting the victim execute the transaction, and then executing another trade after the victim by trading back the amount gained in the first trade. In both `DiamondFactoryV3._provideUniLiquidity()` and `DiamondFactoryV3._removeUniLiquidity()` the following parameters are not set: - When minting liquidity: - `MintParams.amount0Min` - `MintParams.amount1Min` - When decreasing liquidity (burning): - `DecreaseLiquidityParams.amount0Min` - `DecreaseLiquidityParams.amount1Min` As a slippage prevention mechanism, the official documentation of Uniswap V3 recommends that non-zero values should be used for the parameters `amount0Min` and `amount1Min` when using in production the functions: - `NonfungiblePositionManager.mint()` https://docs.uniswap.org/protocol/guides/providing-liquidity/mint-a-position#calling-mint ; - `NonfungiblePositionManager.decreaseLiquiditymint()` https://docs.uniswap.org/protocol/guides/providing-liquidity/decrease-liquidity#decrease-liquidity ; This best practice is not respected when the functions are called by the functions `_provideUniLiquidity()` and `_removeUniLiquidity()`.
Open the reportKeepers play an essential role in the protocol when enabled. They liquidate insolvent positions and activate stop losses when a price tick goes below or above stop loss limits defined by the owners of positions. It implies that the protocol solvency highly depends on a limited set of addresses which could easily become single points of failure. On top of that, the function `removeKeeper()` is used to remove a keeper. However, the fact that the last allowed keeper can be removed is not checked. As a result, the protocol can remain without any keeper.
Open the reportThe state variable `openPositionMinimumAmount` acts as a lower limit for the number of tokens for a position. It is set in the function `init()`. However, there is no function to update it.
Open the reportThe function `Oracle.consult()` is used to fetch time-weighted average ticks using Uniswap V3 oracle. It uses the state variable `oracleTimeWeightedSec` as a parameter. However, the call will revert if `oracleTimeWeightedSec` is equal to `0` or if it goes beyond the oldest observation recorded in the pool.
Open the report`DiamondFactoryV3.setLendingPoolAddress()` approves the borrow token to be spent by the new pool but does not remove the allowance for the previous address. This represents a risk since a compromised pool will still be able to access possible funds on `DiamondFactoryV3` contract address even after removing them.
Open the report`DiamondFactoryV3.setLendingPoolAddress()` allows changing the lending pool address while the contract is deployed. This seems to introduce unnecessary risk since the new address state might not be the same as the previous one. As a consequence, users will likely not be able to withdraw their assets.
Open the reportPerformance fees are collected in the function `collectFee()`. No event is emitted with the number of performance fees that have been collected, which could negatively impact the off-chain monitoring of the protocol.
Open the reportThe names used for the local variables in the function `openPosition()` are close and also mix expected and actual amounts of both tokens. For example, the variables `_strategyParams.wantTokenAmount` and `wantTokenAmount` are very similar. Also, the variables `usdcLiquidityAmount` and `wethLiquidityAmount` have token names in their names, which is not recommended if the contract is meant to be used for other tokens than USDC and WETH. The impact is that it is hard to check that the function behaves as expected.
Open the reportFor several functions, there are differences between the documentation in the interface contract and the implementation contract. The description is sometimes more detailed in the interface and sometimes it is the inverse. The impact is that some integrity constraints could be added or ignored, which could ultimately break the interoperability expected from different contracts implementing the same interface. Also, there is no documentation in the interface `IInterestModel`.
Open the reportThe state variable `feeReceipt` stores the address that will get the fees generated by the contract. Its value is set in the constructor. However, there is no function to update its value. As a result, if the address `feeReceipt` is compromised or blacklisted for the token want, the only way to react would be to set the variable `performanceFee` to 0.
Open the report1. The function `find()` of the library `Positions`: - returns `true` if a position exists; - reverts if a position doesn't exist; As a result, it never returns `false` and reverts instead. 2. The function `get()` of the library `Positions` includes an optional check of ownership. However, it does not check if the position does exist. As a result, if the position does not exist, it is possible to: - return an empty position, if `checkOwner == false`; - revert with the incorrect reason `"Invalid position owner"`, if `checkOwner == true`; In both cases, a developer could be misled by the name of these functions if the library is reused in future versions.
Open the reportIn the contract `BalanceVaultV3`, it is possible to manage whitelisted addresses with the functions `addWhiteFactory()` and `removeWhiteFactory()`. However, the documentation of these functions states that an address is whitelisted for a given token and the function expects a parameter `_token`, which is not used in the code. Also, the state variable `whiteFactories` does not record which token should be whitelisted for a given factory.
Open the reportThe following comments in `DiamondFactoryV3._removeUniLiquidity()` seems incorrect: ``` // We charge performance fee based on uniswap fee, but when you removed liquidity // uniswap will not tell you how much fee do you earned. So we have to calculate // how much uniswap fee should we have first. ``` The whole ` IDiamondFactoryHelper(_helper()).getPositionTokenAmount()` is executed to get the fees when only a single call to the line stated below will return the accumulated fees values: ``` (uint256 fee0, uint256 fee1) = PositionValue.fees( INonfungiblePositionManager(UNI_POSITION_MANAGER), _positionId ); ``` The comments are misleading since no real calculations are done to get the fee values.
Open the reportThe contracts do not have specific functions to pull tokens or ETH sent on purpose or by mistake.
Open the report`InterestModal` execute multiple arithmetic operations without the use of any safe math library or a solidity version greater than `0.8.0`. Unchecked arithmetic operations can lead to overflows and possible exploits.
Open the reportIn the contract `BalanceVaultV3`, four functions are used by the contract to receive or transfer funds: `deposit()`, `withdraw()`, `transferBalanceFromVault()`, `transferBalanceToVault()`. Tokens received are owned by the contract and the internal accounting of which address owns which amount of token is stored in the state variable `balances`. However, that state variable is updated based on the amount that is expected to be transferred, instead of based on what has been received. As a result, if the contract used an ERC20 where the amount to transfer is different than the amount received (greater or lower), the internal accounting of the contract would be broken and users could claim more or less than the actual amount of tokens owned by the contract.
Open the reportThe state variable `tokenDecimalDiff` is set in the function `init()`. However, it is not used in the contract.
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="unibot"></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