CertiK reviewed Cadabra (Cadabra Finance) on 22 Aug 2023: 20 findings, 0 critical; 14 marked fixed, 6 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 | 0 |
| High | 3 | 0 | 0 |
| Medium | 0 | 0 | 2 |
| Low | 3 | 0 | 12 |
If the `sqrtPriceX96` is greater than the maximum `uint128`, that is `2^128 - 1`, then when the following logic is reached it will revert due to overflow: ```solidity uint256 priceX192 = uint256(sqrtPriceX96) * uint256(sqrtPriceX96); ``` Thus in this scenario when the function `value()` is called on the `ThenaGammaToken0TWAPAdapter` it will revert, which will make it impossible for users to invest or redeem.
The `SUPPLY_OFFSET` and `ASSEST_OFFSET` are set to be `1e18`, which creates `1e18` virtual shares and `1e18` virtual assets. As the amount of virtual assets is greater than 1, it allows for the possibility that a user will redeem for more assets than they should be entitled to, or it may revert as it may overestimate the actual amount of assets held by the contract. This is because when shares are redeemed for assets the following equation is used: $$ \frac{shares * (nav + 1e18)}{totalSupply() + 1e18} $$ rearranging this gives $$ \frac{shares}{totalSupply() + 1e18} * (nav + 1e18) $$ If the total supply is redeemed at once this equation becomes $$\frac{totalSupply}{totalSupply() + 1e18} * (nav + 1e18) < nav + 1e18$$ However as the fraction $\frac{totalSupply}{totalSupply() + 1e18}$ converges to 1 as the total supply increases, this value may exceed the value of `nav`. Thus allowing for some of the locked profit or fees to be given when redeeming or if the entire total supply is redeemed and there are no locked fees it will cause it to revert due to a lack of funds.
In the contract `TwapUtils`, the function `getSqrtTwapX96()` has the following logic: ```solidity sqrtPriceX96 = TickMath.getSqrtRatioAtTick( int24((tickCumulatives[1] - tickCumulatives[0]) / SafeCast.toInt56(SafeCast.toInt256(uint256(twapInterval)))) ); ``` However, if the difference `tickCumulatives[1] - tickCumulatives[0]` is negative, then when dividing by the `twapInterval` it will not round towards negative infinity. (For example -10/3 = -3 as opposed to -4) This means that the tick with the slightly higher price will be chosen in this scenario.
In the contact `ChainLinkLib`, there a `TODO` comment as follows: ```solidity // TODO: L2 https://twitter.com/bytes032/status/1653943092427325448 // https://docs.chain.link/data-feeds/l2-sequencer-feeds ``` The first link addresses the potential problem of a sequencer going offline, while the second provides guidance on properly handling this issue for Arbitrum, Optimism, and Metis L2 chains.
The locked profits and fees are fixed and will not change in value as the invested tokens may. Thus if the invested tokens decrease dramatically in value, then the locked profits may exceed the total value of the adapters and cause calls to `_totalNAV()` to revert when the operation `value - lockedProfit` is performed.
There should always be events emitted in the sensitive functions that update important states.
If a chainlink feed uses more than 18 decimals, when `scaleFactor` is computed it will revert due to underflow. While most chainlink feeds use 8 or 18 decimals, it is possible for a feed with more than 18 decimals to be created and used in the future.
In the contract `LiquidityUtils` if the `reserve0` or `reserve1` is 0, then the function `ratio2` may unexpectedly revert due to a division by 0.
There is no method to recover tokens other than the `depositTokens()` that are accidentally sent to an adapter, causing them to be lost forever. As the swaps are input manually, it is possible that an incorrect token will be swapped for and sent to the adapter or users may accidentally send the wrong token directly to the adapter. In addition, in the contract `BalancerUpgradeable` in the function `rebalance()`, when `redeem()` is called the redeemed tokens are sent to `address(this)` and when `invest()` is called the dust is sent to `address(this)`. This allows for tokens to potentially be left in the `BalancerUpgradeable` contract.
The following comment is not correct in all possible cases ```solidity // shouldn't be susceptible to inflation attacks, since `valuePrior` can only increase through adding shares (???) ``` The `valuePrior` is determined by the `value()` returned by the adapter, which may depend on the value returned from an oracle, its balances stored in other contracts, or the balance of a certain token. These values may be able to be manipulated by an attacker. In general, the `value()` heavily depends on the adapter implementation.
Many of the for loops execute functions that take a significant amount of gas, if enough adapters, swaps, or transfers are used the transaction may exceed the block gas limit.
In the contract `BalancerUpgradeable`: - The comment for the constant `REBALANCE_COOLDOWN` says "1 hour", when it is set to 6 hours. In the contract `ThenaGammaAdapter`: - The comment "// doesn't really mater what is `to` argument, shares are always issued to msg.sender" is not consistent with the functionality. The `to` argument indicates who the shares will be minted to and is not necessarily minted to the message sender.
This contract does not contain any method to remove native tokens from the contract. This will cause any native token sent to the contract through the `receive()` function to be permanently lost.
In the contract `TwapUtils`, the function `getSqrtTwapX96` performs the following logic: ```solidity if (twapInterval == 0) { uint32[] memory secondsAgos = new uint32[](1); secondsAgos[0] = 0; (sqrtPriceX96,,,,,,) = IPool(uniswapV3Pool).globalState(); } ``` However, the array `secondsAgos` is defined and initialized, but never used. If the `twapInterval` is 0, then it should fetch the current price, which can be done by reading it from the global state without the need for the `secondsAgos` array.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (9)
1,044 projects reviewed · 1,393 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