As of 21 Oct 2026, Sturdy has 4 audit reports by 3 security firms on Trustblock; the latest is by ChainSecurity, dated 13 Oct 2023. Those reports list no open critical findings.
Record last updated . It holds audits and findings, contracts and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Sturdy Aggregator | Security firm: ChainSecurity | Date: | Scope: Files named in the report | No open critical findings | Open · chainsecurity.com |
| Sturdy security assessment | Security firm: Zellic | Date: | Scope: Not recorded (see the report) | No open critical findings | Open · GitHub |
| Sturdy (Aura Integration And Leverage) | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
| Sturdy | 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 | 1 |
| Medium | 3 | 2 | 10 |
| Low | 1 | 25 | 22 |
The report gives no description here.
Open the reportThe approach taken to calculate the Balancer LP Token value involves multiplying the price of the cheaper of the two tokens (`WETH` and `STETH`) by the value returned by `BALWSTETHWETH.getRate()`. This price calculation would be most accurate when the price of `WETH` is the same as `STETH`. However, the `WETH/STETH` price can fluctuate significantly, and the further they diverge, the less accurate the price calculation will be. While the calculation provides a reliable lower bound for the price, it is unclear how different this lower bound will be from the actual price. If the price calculation is too low, it could result in premature liquidation of users who have deposited the Balancer LP token as collateral.
Open the reportThe report gives no description here.
Open the reportThe function `enterPositionWithFlashloan()` assumes the maximum amount of slippage when calculating the amount to borrow for the flash loan. This can be seen in the code below from the helper function `_leverageWithFlashloan()` ``` amounts[0] = ((((_principal * _getAssetPrice(COLLATERAL)) / 10**DECIMALS) * 10**borrowAssetDecimals) / _getAssetPrice(_borrowAsset)).percentMul(_leverage).percentMul( PercentageMath.PERCENTAGE_FACTOR + _slippage ); ``` This borrowed amount is then completely swapped to collateral tokens and deposited on behalf of the user as shown in the code below from`_enterPositionWithFlashloan()`: ``` //swap borrowing asset to collateral uint256 collateralAmount = _swapTo(_borrowingAsset, _borrowedAmount); require(collateralAmount >= _minAmount, Errors.LS_SUPPLY_FAILED); //deposit collateral _supply(collateralAmount, _user); //borrow borrowing asset _borrow(_borrowingAsset, _borrowedAmount + _fee, _user); ``` A user may expect that they will only deposit `_principal + _principal*_leverage` amount of collateral if the swap slippage is zero. They may be surprised to see that they have a more leveraged position than they expected. The `zapLeverageWithFlashloan()` suffers from the same issue.
Open the reportThe `BALWSTETHWETHOracle` contract's primary purpose is to provide a price for the `B-stETH-STABLE ` token. However, the correct calculation of the price is highly dependent on having the correct `STETH` price. Currently, the only source of this data is the Chainlink price feed. In the event of any large-scale attack/disruption of the Chainlink network, the protocol could be severely impacted.
Open the reportAs the `_swapTo()` and `_swapFrom()` functions in the `AURAWSTETHWETHLevSwap` contract return the whole contract balance of Balancer LP Tokens and WETH, respectively, such tokens, which were sent directly to the contract by accident, will be returned to the next user swapping to that asset. So, under such conditions, users will unexpectedly receive more tokens than perhaps anticipated from swaps. This of course does not seem problematic but could lead to unexpected behavior. The inflated amount of collateral would be used to create leverage, possibly resulting in a much larger position than desired. Of course, a user could immediately withdraw again and make a profit, but the user may also be confused by the protocol's behavior.
Open the reportThe `registerLevSwapper()` function represents a critical state change for the protocol, as it changes or disables the leverage swap functionality for a particular collateral asset. The function should emit an event, so this key state transition can be monitored and responded to.
Open the reportThe functions `setConfiguration()` and `processExtraYield()` in the `AuraBalancerLPVault` contract are marked as `payable`, yet we were unable to find a reason for why that should be the case. It seems like there would be no use for ETH transferred via these methods and the funds would become stuck.
Open the reportThe definition of leverage the protocol uses might differ from a user's expectations. For example, most users would expect when using "2X" leverage that an additional 100% of the provided collateral would be borrowed. Yet in this protocol, a leverage of `2` will result in an additional `200%` being borrowed, which for some users might lead to unexpectedly creating higher leverage than desired. While the used definition differs, the documentation properly describes how the leverage is set and calculated in the protocol.
Open the reportThe `LeverageSwapManager` possesses a function `registerLevSwapper()` which is callable by the administrator role of the contract. This function allows the admin to change the official implementation of the `*LevSwap` contract for a particular collateral to an entirely different one. They may also disable the leveraged swap functionality for the collateral by calling `registerLevSwapper()` with the zero address for the `swapper` parameter. Users should be made aware of the consequences of this privileged role and its consequences. The team should also warn users in advance if the leveraged swapped functionality is being disabled or changed for a collateral asset.
Open the reportTo 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: * `GeneralVault`/`AuraBalancerLPVault.sol` * `depositCollateral()` * `depositCollateralFrom()` * `withdrawCollateral()` * `processYield()`
Open the reportThe platform generates yields by sending the collateral tokens stored by borrowers to other protocols such as Aura. If any of these yield-generating protocols got hacked, the protocol users would also lose their funds.
Open the reportThe exploit scenario described in QSP-2 demonstrates that the effect of swap slippage can be greatly amplified. In that scenario, the swap slippage was only `10%,` and yet the user lost `45%` of their position's value. The amplification of slippage is primarily caused by the need to pay back the exact amount of borrowed tokens (plus any flash loan fees) to the flash loan provider. The flash loan provider absorbs no fraction of the loss of value due to slippage, so the entire cost is absorbed by the user. It follows that using higher leverage, and thus a higher borrow amount, leads to a higher amplification of the swap slippage rate.
Open the reportEvery 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".
Open the reportThere are multiple instances of unsafe casts within the contracts. These could lead to overflow or underflow of values and have unpredictable results. We have listed all instances of unsafe casts below: 1. `BALWSTETHWETHOracle`` 1. ``L25:uint256 minValue = Math.min(**uint256**(wstETHPrice), 1e18);` 2. `L39:return (true, int256(_get()));` 3. ``L45:return int256(_get());` 2. AURAWSTETHWETHLevSwap` 1. `L45:uint256 joinKind = **uint256**(IBalancerVault.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT);` 2. `L75:uint256 exitKind = **uint256**(IBalancerVault.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT);`
Open the reportThe `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; } ```
Open the reportThe `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.
Open the reportThe 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.
Open the reportThe `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.
Open the reportIf 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.
Open the reportTo 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()`
Open the reportThe 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.
Open the reportThe `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.
Open the reportAlthough 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`
Open the reportIt 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`
Open the reportEvery 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".
Open the reportSome 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.
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="sturdy"></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