Quantstamp reviewed Sturdy on 13 Oct 2022: 26 findings, 0 critical; 12 marked fixed, 12 acknowledged, 2 open.
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 | 0 |
| Medium | 2 | 0 | 5 |
| Low | 0 | 12 | 7 |
The `GeneralVault.vaultYieldInPrice()` function always returns `0` and is not overridden by any vault contracts. As such, this function always returns the incorrect value. This can be seen in the function definition below: ``` function vaultYieldInPrice() external view virtual returns (uint256) { return 0; } ```
The `YearnVault.processYield()` function iterates over all debt assets multiple times and performs a swap for each asset. If the number of debt assets is sufficiently large, the function could easily run out of gas and fail. The function first iterates over all reserves when it calls `LendingPool.getBorrowingAssetAndVolumes()` through the `YearnVault._getAssetYields()` function. Then the `YearnVault._getAssetYields()` function iterates over every debt asset as shown from `L274-L286` below: ``` for (uint256 i; i < length; ++i) { assetYields[i].asset = assets[i]; if (i == length - 1) { // without calculation, set remained extra amount assetYields[i].amount = extraWETHAmount; } else { // Distribute wethAmount based on percent of asset volume assetYields[i].amount = _amount.percentMul( (volumes[i] * PercentageMath.PERCENTAGE_FACTOR) / totalVolume ); extraWETHAmount -= assetYields[i].amount; } } ``` The `YearnVault.processYield()` function then performs another iteration over every debt asset as shown from `L57-L62` below: ``` for (uint256 i; i < length; ++i) { // FTM -> Asset and Deposit to pool if (assetYields[i].amount != 0) { _convertAndDepositYield(assetYields[i].asset, assetYields[i].amount); } } ``` Since `YearnVault._convertAndDepositYield()` will perform a swap, this will result in significantly increased gas usage, possibly enough to make the entire transaction fail.
The clone-and-own approach involves copying and adjusting open source code at one's own discretion. From the development perspective, it is initially beneficial as it reduces the amount of effort. However, from the security perspective, it involves some risks as the code may not follow the best practices, may contain a security vulnerability, or may include intentionally or unintentionally modified upstream libraries. Many contracts reference a locally cloned copy of OpenZeppelin contracts stored in the `protocol/dependencies/openzeppelin/*` folder.
The `LidoVault` contract implements the `setSlippage()` function, allowing an administrator to change the slippage value when swapping stETH for ETH in Curve. 1. `YearnVault` uses a fixed one-percent slippage when swapping tokens in Uniswap. 2. `YearnRETHWstETHVault` uses a fixed two-percent slippage when swapping tokens in Curve.
If the owner renounces their ownership, all ownable contracts will be left without an owner. Consequently, any function guarded by the `onlyOwner` modifier will no longer be able to be executed. Many of the contracts have key administrative functions which are only accessible by the owner.
To avoid reentrancy risks and reduce the attack surface, it is highly recommended to inherit from OpenZeppelin's `ReentrancyGuard` contract and add the `nonReentrant` modifier to all functions that: 1. Involve asset manipulation. 2. Could be accessed by any external address. 3. Are not expected to be reentered The following is a non-exhaustive list of functions we recommend adding a `nonReentrant` modifier to: * `LendingPool` * `deposit()` * `withdraw()` * `borrow()` * `repay()` * `liquidationCall()` * `setUserUseReserveAsCollateral()` * `GeneralVault` * `depositCollateral()` * `depositCollateralFrom()` * `withdrawCollateral()` * `processYield()`
The platform generates yields by sending the collateral tokens stored by borrowers to other protocols such as Lido and Convex. If any of these yield-generating protocols got hacked, the protocol users would also lose their funds.
The `SturdyOracle` and lending rate oracle contracts are heavily used among the vault, lending pool, and interest rate contracts. However, they are not within the scope of the current audit. So it is worth noting here that the security of the platform relies on the security of these oracles. If the oracles are breached, the platform could behave unexpectedly, for instance, liquidating assets. And users could suffer from a great loss of their funds.
Although the stable debt functionality is not currently used by the Sturdy Protocol, the original AAVE code for the stable debt functionality has not been removed. Since the Sturdy Protocol does not currently utilize the stable debt functionality, it is unlikely to have been carefully tested and could thus result in unexpected bugs. The additional code also results in unnecessary additional attack surface for the protocol. There are many areas within the code where stable debt functionality still exists, including the following: * `DefaultReserveInterestRateStrategy` * `LendingPool` * `LendingPoolCollateralManager` * `LendingPoolConfigurator` * `GenericLogic` * `ReserveLogic` * `ValidationLogic`
It is highly recommended to check the changes in the balance after the token transfer is expected to avoid most of the non-standard behavior of external token contracts (e.g. deflationary tokens) from being used by a hacker as a tool to manipulate the contract. Specifically, we recommend verifying the token balances at the following locations in the code: 1. `LendingPool.sol#L146` 2. `LendingPool.sol#L186` 3. `LendingPool.sol#L480` 4. `LendingPoolCollateralManager.sol#L250` 5. `ConvexCurveLPVault.sol#L192` 6. `LidoVault.sol#L133` 7. `YearnRETHWstETHVault.sol#L167` 8. `YearnVault.sol#L182`
Every Solidity file specifies in the header a version number of the format `pragma solidity ^0.8.0`. The `^` before the version number implies an unlocked pragma, meaning that the compiler will use the specified version and above, hence the term "unlocked".
Some vault contracts implement the `receive()` function to receive ETH or FTM. However, it also opens the possibility for a user to send these native tokens directly to the contract by mistake.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (24)
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