Download audit report
This document presents the results of the security review conducted by Nethermind Security for ETH Strat's ERC4626 Vault contracts. The system is a yield-generation strategy designed to offer stablecoin-denominated returns. The architecture is centered around two interconnected ERC4626 vaults and a trusted, off-chain manager.
The primary vault, StratPerpetualBond, accepts stablecoin deposits from users. These funds are then controlled by the manager, who manually executes a discretionary options strategy to generate yield. The second vault, StakedStratPerpetualBondLP, is designed to bootstrap liquidity for the primary vault's share token (SPB) by rewarding liquidity providers on a decentralized exchange.
A key architectural characteristic is that direct redemptions from the primary vault are disabled. The sole exit mechanism for users is to sell their SPB tokens on the secondary market. The system's viability is therefore dependent on the market's confidence in the manager and the DEX liquidity incentivized by the second vault.
The audit comprises 105 lines of Solidity code. The audit was performed using (a) manual analysis of the codebase, and (b) automated analysis tools.
| Audit assessment | |
| Audit Type | Security Review |
| Initial Report | August 13, 2025 |
| Final Report | August 19, 2025 |
| Initial SHA-256 Hash | 3cfd632660 |
| Final SHA-256 Hash | 5266e902fe |
| Documentation Assessment | High |
| Test Suite Assessment | High |
Findings by severity
| # | Contract | LoC | Comments | Ratio | Blank | Total |
| 1 | src/StratPerpetualBond.sol | 96 | 70 | 72.9% | 26 | 192 |
| 2 | src/StratPerpetualBondLPVault.sol | 9 | 9 | 100.0% | 2 | 20 |
| Total | 105 | 79 | 75.2% | 28 | 212 |
| # | Finding | Severity | Update |
| 1 | The _totalAssets cannot be written down to reflect realized losses | Info | Acknowledged |
| 2 | Vault design does not account for fee-on-transfer tokens | Info | Acknowledged |
| 3 | Incorrect comment for depositCap | Best Practices | Fixed |
| 4 | The increaseAssetsPerShare(...) function could emit an event | Best Practices | Fixed |
The protocol is a yield-generation strategy designed to offer stablecoin-denominated returns. The architecture is centered around two interconnected ERC-4626 vaults that work in tandem with a trusted, off-chain manager. Users deposit stablecoins into a primary vault, and the manager deploys these assets into a discretionary options-based strategy. The resulting yield is then used to reward liquidity providers in a secondary vault, creating an economic flywheel intended to drive value for the protocol's share token.
The primary entry point for users is the StratPerpetualBond vault. Users deposit a whitelisted stablecoin as the underlying asset and, in return, receive SPB tokens, which represent their share of the vault's total assets under management. Upon deposit, the full amount of the stablecoin assets is immediately transferred from the vault to the control of a designated manager.
This manager, a multi-signature wallet, is responsible for executing the entirety of the protocol's yield strategy. At launch, this involves a manual, off-chain process where the manager uses the deposited funds to purchase ETH, which is then used as collateral to sell covered call options on an external derivatives platform. The premium generated from selling these options constitutes the system's yield. A portion of this yield is periodically sent back to the StratPerpetualBond vault by calling the increaseAssetsPerShare(...) function. This action increases the vault's totalAssets, causing the value of each SPB share to appreciate.
The second core component of the system is the StakedStratPerpetualBondLP vault. This vault is designed to incentivize liquidity for the SPB token on a secondary market, such as a decentralized exchange (DEX). To participate, a user first provides liquidity by pairing their SPB tokens with a stablecoin on a DEX, receiving LP (liquidity provider) tokens in return.
These LP tokens can then be staked in the StakedStratPerpetualBondLP vault to earn yield. A significant portion of the premium generated by the manager is directed to this vault to be paid out as staking rewards. The exact split of yield between compounding in the main vault and rewarding LPs is at the manager's discretion. This flexibility allows the protocol to dynamically adjust its incentive structure based on prevailing market conditions and is a key reason for the manual, discretionary approach at launch. The rewards are distributed in the form of more LP tokens, creating a compounding return for stakers.
The SPB token is an ERC-4626 share token that represents a proportional claim on the assets being managed by the protocol. Its value is directly tied to the performance of the manager's options strategy. As yield is compounded back into the StratPerpetualBond vault, the underlying asset value per SPB token increases.
A key design feature of the protocol is that direct redemptions from the StratPerpetualBond vault are disabled by default. Consequently, the sole exit path for an SPB holder is to sell their tokens on the secondary market. The sustainability of this model relies on the liquidity incentivized by the StakedStratPerpetualBondLP vault. The price and tradability of SPB are therefore dependent on the market's confidence in the manager's ability to consistently generate yield and the effectiveness of the staking reward program in maintaining sufficient DEX liquidity.
The risk rating methodology used by Nethermind Security follows the principles established by the OWASP Foundation. The severity of each finding is determined by two factors: Likelihood and Impact.
Likelihood measures how likely the finding is to be uncovered and exploited by an attacker. This factor will be one of the following values:
When defining the likelihood of a finding, other factors are also considered. These can include but are not limited to motive, opportunity, exploit accessibility, ease of discovery, and ease of exploit.
Impact is a measure of the damage that may be caused if an attacker exploits the finding. This factor will be one of the following values:
When defining the impact of a finding, other factors are also considered. These can include but are not limited to data and state integrity, loss of availability, financial loss, and reputation damage. After defining the likelihood and impact of an issue, the severity is determined according to the table below.
| Impact / Likelihood | Low | Medium | High |
| High | Medium | High | Critical |
| Medium | Low | Medium | High |
| Low | Info / Best Practices | Low | Medium |
| Undetermined | Undetermined | Undetermined | Undetermined |
To address issues that do not fit a High, Medium or Low severity, Nethermind Security also uses three more finding severities: Informational, Best Practices, and Undetermined.
_totalAssets cannot be written down to reflect realized losses| File | Fix commit | Type | Status |
src/StratPerpetualBond.sol | — | Info | Acknowledged |
The StratPerpetualBond vault is designed as an ERC-4626 vault where deposited assets are immediately transferred to a manager. This manager is responsible for deploying the assets into a yield-generating strategy, such as selling ETH call options. The vault itself does not hold the underlying assets; its value is tracked by the _totalAssets state variable, which is increased when the manager directs yield back into the vault via the increaseAssetsPerShare(...) function. By design, withdrawals are disabled, and the primary exit path for users is to sell their PBS (vault share) tokens on a secondary market like a DEX.
The potential issue arises if the project decides to enable withdrawals, for instance, to sunset the vault. The _withdraw(...) function calls super._withdraw(...), which in turn attempts to transfer the underlying asset from the vault contract to the user. This transaction will revert if the vault's actual balance of the asset is insufficient to cover the withdrawal amount. Since the vault sends all assets to the manager upon deposit, its balance is typically zero.
function _withdraw(
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
) internal virtual override nonReentrant {
// @audit Withdrawals are disabled by default.
if (withdrawalsDisabled) revert WithdrawalsDisabled();
// @audit-issue This call will attempt an asset transfer and revert if the contract has an insufficient balance.
super._withdraw(caller, receiver, owner, assets, shares);
// @audit The total assets are reduced only after a successful withdrawal.
_totalAssets -= assets;
}If the manager becomes insolvent or its strategy underperforms, it may be unable to return enough assets to the vault to cover all outstanding _totalAssets. In this scenario, enabling withdrawals would create a race condition where the realized losses are not shared proportionally among all shareholders. The first users to withdraw will successfully redeem their shares for assets at a value calculated from the artificially high _totalAssets. These initial withdrawals will deplete the limited asset balance held within the contract. Once this balance is exhausted, subsequent withdrawal attempts will revert, leaving the remaining shareholders unable to recover any funds, as the contract lacks a mechanism to write down the value of _totalAssets to account for the manager's losses.
Recommendation
This design places a high degree of trust in the manager. It is recommended to clearly document these risks for all users, emphasizing that the value of PBS tokens and the viability of an exit are entirely dependent on the manager's performance and solvency.
Adding a function to write down the _totalAssets value would allow for the fair socialization of losses, preventing a "race to the exit." However, such a function introduces a significant centralization risk. If the owner's private key were compromised, an attacker could call this function to maliciously reduce or erase the value of all shares, which would have a severe negative impact on the PBS token's economics.
This finding serves an informational purpose for all stakeholders to understand the design trade-offs. The decision of whether to introduce a loss-socialization mechanism at the cost of increased centralization risk is left to the team.
Resolution
Noted, on balance will keep as is, if we need to socialise losses, we can always do so by creating a new proportional withdraw in a different smart contract as opposed to using withdraw directly
Withdraw is left for completeness to the standard, and to ensure flexibility in the event we want to sunset and return capital easily without using the intended method of users selling shares in the LP pool.
| File | Fix commit | Type | Status |
src/StratPerpetualBond.sol | — | Info | Acknowledged |
The vault's accounting mechanism in the _deposit(...) function is incompatible with fee-on-transfer tokens. It credits the vault's _totalAssets based on the user-specified deposit amount before the actual token transfer occurs. If the vault were deployed with a fee-on-transfer stablecoin, the amount received by the manager would be less than the amount recorded, causing the vault's total asset value to become inflated over time. This would lead to shares being backed by fewer assets than the accounting suggests. While uncommon, some stablecoins like USDT have a dormant fee mechanism, which presents a consideration for future deployments.
Recommendation
When deploying new instances of the vault with different stablecoins, care must be taken to ensure the chosen asset does not have a fee-on-transfer mechanism to avoid accounting inaccuracies.
Resolution
We'll be using USDS (sky.money) - given the probability sky/maker will add a fee is low, we think this is acceptable risk.
depositCap| File | Fix commit | Type | Status |
src/StratPerpetualBond.sol | — | Best Practices | Fixed |
The StratPerpetualBond contract uses a depositCap state variable to enforce a limit on the total assets that can be deposited. The NatSpec comment associated with this variable states that a value of 0 signifies no deposit cap.
// ...
/// @dev Maximum total assets that can be deposited (0 = no cap)
// @audit-issue This comment is incorrect. A value of 0 effectively blocks all deposits.
uint256 public depositCap;
// ...However, the implementation within the _deposit(...) function contradicts this comment. The function checks if the new total assets would exceed the cap.
function _deposit(
address caller,
address receiver,
uint256 assets,
uint256 shares
) internal virtual override {
// @audit If depositCap is 0, this check will revert for any deposit with assets > 0.
if (_totalAssets + assets > depositCap) {
revert DepositCapExceeded();
}
super._deposit(caller, receiver, assets, shares);
// ...
}If depositCap is set to 0, the condition _totalAssets + assets > depositCap will be true for any deposit where assets is greater than 0. This will cause the transaction to revert, effectively disabling all deposits.
This discrepancy between the comment and the code's behavior can mislead the contract owner or any developer interacting with the system. An owner attempting to remove the deposit limit by setting depositCap to 0 would unintentionally halt all deposits to the vault.
Recommendation
Consider correcting the comment for depositCap to accurately reflect its functionality.
Resolution
Resolved in commit dad2bf03a36d41076d211c02954d67f5c426827d.
increaseAssetsPerShare(...) function could emit an event| File | Fix commit | Type | Status |
src/StratPerpetualBond.sol | — | Best Practices | Fixed |
The StratPerpetualBond contract allows a manager to add yield to the vault by calling the increaseAssetsPerShare(...) function. This function increases the total assets under management (_totalAssets), effectively boosting the value of shares held by depositors.
However, the increaseAssetsPerShare(...) function does not emit an event after successfully adding assets.
function increaseAssetsPerShare(uint256 assets) external {
// @audit-issue No event is emitted to signal the addition of assets.
SafeERC20.safeTransferFrom(IERC20(asset()), msg.sender, address(this), assets);
SafeERC20.safeTransfer(IERC20(asset()), manager, assets);
_totalAssets += assets;
}The absence of an event makes it difficult for off-chain services, monitoring tools, and users to track the inflow of yield into the vault. This lack of transparency complicates the analysis of the vault's performance over time.
Recommendation
Consider defining and emitting an event within the increaseAssetsPerShare(...) function.
Resolution
Resolved in commit 6ccf6ff559e7a23f3c71f673ffaf273e7ca1567f.
Software documentation refers to the written or visual information that describes the functionality, architecture, design, and implementation of software. It provides a comprehensive overview of the software system and helps users, developers, and stakeholders understand how the software works, how to use it, and how to maintain it. Software documentation can take different forms, such as user manuals, system manuals, technical specifications, requirements documents, design documents, and code comments. Software documentation is critical in software development, enabling effective communication between developers, testers, users, and other stakeholders. It helps to ensure that everyone involved in the development process has a shared understanding of the software system and its functionality. Moreover, software documentation can improve software maintenance by providing a clear and complete understanding of the software system, making it easier for developers to maintain, modify, and update the software over time. Smart contracts can use various types of software documentation. Some of the most common types include:
These types of documentation are essential for smart contract development and maintenance. They help ensure that the contract is properly designed, implemented, and tested, and they provide a reference for developers who need to modify or maintain the contract in the future.
ETH Strat's team provided an overview of the main system components during the kick-off call with a detailed explanation of the intended functionalities along with a written specification for the different contracts. Moreover, the team addressed all questions and concerns raised by the Nethermind Security team, providing valuable insights and a comprehensive understanding of the project's technical aspects.
| 97Tests passed | 0Failed |
The complete test output is in the full report.
As auditors, we recommend that any changes or updates made to the audited codebase undergo a re-audit or security review to address potential vulnerabilities or risks introduced by the modifications. By conducting a re-audit or security review of the modified codebase, you can significantly enhance the overall security of your system and reduce the likelihood of exploitation. However, we do not possess the authority or right to impose obligations or restrictions on our clients regarding codebase updates, modifications, or subsequent audits. Accordingly, the decision to seek a re-audit or security review lies solely with you.
This report is based on the scope of materials and documentation provided by you to Nethermind in order that Nethermind could conduct the security review outlined in 1. Executive Summary and 2. Audited Files. The results set out in this report may not be complete nor inclusive of all vulnerabilities. Nethermind has provided the review and this report on an as-is, where-is, and as-available basis. You agree that your access and/or use, including but not limited to any associated services, products, protocols, platforms, content, and materials, will be at your sole risk. Blockchain technology remains under development and is subject to unknown risks and flaws. The review does not extend to the compiler layer, or any other areas beyond the programming language, or other programming aspects that could present security risks. This report does not indicate the endorsement of any particular project or team, nor guarantee its security. No third party should rely on this report in any way, including for the purpose of making any decisions to buy or sell a product, service or any other asset. To the fullest extent permitted by law, Nethermind disclaims any liability in connection with this report, its content, and any related services and products and your use thereof, including, without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement. Nethermind does not warrant, endorse, guarantee, or assume responsibility for any product or service advertised or offered by a third party through the product, any open source or third-party software, code, libraries, materials, or information linked to, called by, referenced by or accessible through the report, its content, and the related services and products, any hyperlinked websites, any websites or mobile applications appearing on any advertising, and Nethermind will not be a party to or in any way be responsible for monitoring any transaction between you and any third-party providers of products or services. As with the purchase or use of a product or service through any medium or in any environment, you should use your best judgment and exercise caution where appropriate. FOR AVOIDANCE OF DOUBT, THE REPORT, ITS CONTENT, ACCESS, AND/OR USAGE THEREOF, INCLUDING ANY ASSOCIATED SERVICES OR MATERIALS, SHALL NOT BE CONSIDERED OR RELIED UPON AS ANY FORM OF FINANCIAL, INVESTMENT, TAX, LEGAL, REGULATORY, OR OTHER ADVICE.
The Leading Engineers of Blockchain Infrastructure