As of 30 Oct 2026, StakeStone has 4 audit reports by 1 security firm on Trustblock; the latest is by Quantstamp, dated 14 Oct 2025. 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 |
|---|---|---|---|---|---|
| StakeStone Tokenized Vault | Security firm: Quantstamp | Date: | Scope: Not recorded (see the report) | No open critical findings | Open · certificate.quantstamp.com |
| StakeStone Vault | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
| StakeStone StoneBTC and Vault | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
| StakeStone | Security firm: Quantstamp | Date: | Scope: Repository at commit d34a1ec | 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 | 0 |
| Medium | 1 | 2 | 5 |
| Low | 1 | 14 | 12 |
Chainlink oracles can return stale pricing data due to network delays or other issues. If stale data is used, transactions may execute with outdated prices, leading to incorrect LP valuations or withdrawals, potentially resulting in losses for users or the protocol. The current implementation does not check if the price data is stale.
Open the reportBoth `AssetVault.processWithdrawal(uint256 _index)` and `AssetVault.cancelWithdrawal(uint256 _index)` can pop elements from the `withdrawalRequests[]` array. If two calls are in the mempool at the same time, there is no guarantee that the respective `_index` will point to the intended `WithdrawalRequest`, as the opposing call may have modified the array. This can result in the incorrect withdrawal being processed/canceled.
Open the reportWhen a user makes a withdrawal request, they specify a `_minReceiveAmount` of LP tokens to receive. When `WithdrawalQueue.processWithdrawals()` calls the `WithdrawalProcessor.callback()` function, `callback()` will call `AssetVault.requestWithdraw()` and `AssetVault.processWithdrawal()` to burn the LP tokens and send back the underlying token, after which the tokens are sent to the user. However, `AssetVault.processWithdrawal()` is not guaranteed to burn the LP tokens and transfer the underlying tokens. If `finalizedAmount < minReceiveAmount`, it cancels the withdrawal and returns the LP tokens to the `WithdrawalProcessor`. The `WithdrawalProcessor` falsely assumes that the call succeeded and approves the `WithdrawalQueue` to transfer `_minReceiveAmount` tokens to the user. If the `WithdrawalProcessor` has sufficient funds, then the withdrawal of `_minReceiveAmount` will be honored, irrespective of how large it is or what token prices currently are.
Open the reportThe strategy manager may perform arbitrary calls on the `tokenAddr` contract by calling `invoke()`. This could be abused to request withdrawals and/or claim any queued withdrawals to oneself.
Open the reportSome contracts contain setter functions that are only callable by the `StoneVault` or `StrategyController`. However, these setter functions are never called in the `StoneVault` or `StrategyController` contracts. If the configuration of the protocol changes, these contracts may not be updated accordingly. The following is a list of found functions: - `AssetVault.setNewVault()` - `Minter.setNewVault()` - `StrategyController.setNewVault()` - `SwappingAggregator.setSlippage()` - `SwappingAggregator.setUniRouter()` - `SwappingAggregator.setCurveRouter()` - `SwappingAggregator.setNewGovernance()`
Open the reportThe `removeUnderlyingAsset` function iterates through the entire `underlyingAssets` array without bounds checking, creating potential denial-of-service conditions. ```solidity uint256 length = underlyingAssets.length; uint256 i; for (i; i < length; i++) { if (underlyingAssets[i] == _asset) { underlyingAssets[i] = underlyingAssets[length - 1]; underlyingAssets.pop(); break; } } ``` If the array becomes very large, this function will run out of gas and become unusable.
Open the reportIn the `OracleRegistry` contract, the functions `getCutOffPrice()`, `getCutOffPrices()`, and `onlyGetCutOffPrices()` calculate a timestamp from `cutOffPriceUpdateAt` and then return `prices[_token][updatedAt]`. However, if no price was ever recorded for that (token, updatedAt) pair, for example, if the token was added after the cut-off update or removed before it then the mapping will return the default zero value.
Open the reportIn the `ParamRegistry` contract, the `setPriceUpdateInterval()` function enforces only a minimum bound but no maximum bound. Therefore, a privileged actor with the `D0_TIMELOCK_ROLE` can set the `_priceUpdateInterval` to an arbitrarily high value. This results in a denial-of-service scenario where all calls to `OracleRegistry.updatePrices` revert with `Errors.PriceUpdateTooFrequent()`. Additionally, consumers of prices that enforce freshness checks will eventually revert with `Errors.PriceValidityExpired()` since the maximum validity duration is capped at 7 days.
Open the reportIn the `Token` contract constructor, the `_admin` parameter is directly assigned the `DEFAULT_ADMIN_ROLE` via `_grantRole`. However, there is no validation to prevent `_admin` from being the zero address. If the contract is deployed with a zero address for the admin then all administrative access is lost permanently. This misconfiguration would prevent assignment of the `MINTER_ROLE` and block all future role management actions, effectively bricking minting functionality and governance control.
Open the reportDuring creation of the `OracleRegistry` contract, the initial token oracles are checked against the stablecoin tolerance recorded in the `ParamRegistry` contract. However, this check is missing for oracles that are added or updated after contract initialization using the `updateTokenOracle()` and `addTokenOracle()` functions.
Open the reportIn the `WithdrawController` contract, the `addWithdrawalAsset()` function allows new assets to be added as withdrawal options. However, there is no validation to prevent the vault share token from being added as a withdrawal asset. If this occurs, users could attempt to redeem shares into the vault token itself, which undermines the intended design of the system and may cause incorrect accounting or unexpected behavior.
Open the reportIn the `OracleRegistry` contract the `addTokenOracle()` function (and also in the constructor) do not prevent the `vaultToken` from being added to the list of registered tokens. In `updatePrices()`, the contract first updates the vault token price using `_vaultTokenPrice` and enforces exchange-rate deviation checks. However, since it then iterates over `tokens` and calls `peek(token)` for each, this would overwrite the vault token’s price if it appears in the list. Since `peek()` only enforces stablecoin tolerance checks, the stricter exchange-rate validations for the vault token can be bypassed. This introduces a risk of misconfiguration where the vault token’s price can be set without proper safety checks, undermining the integrity of the system’s pricing logic.
Open the reportThe `processWithdrawals()` function may be vulnerable to reentrancy when interacting with tokens that have callback functionality, such as ERC777 tokens. If a user crafts a withdrawal request with such a token, the request could be processed multiple times, allowing the fulfilling user to process the request multiple times. While this does not necessarily result in a loss for the protocol, it allows the fulfilling user to exploit the withdrawal process to their advantage by processing the withdrawal request multiple times.
Open the reportIn the `cancelWithdrawals()` function, it is possible to cancel a withdrawal receipt even after the withdrawal has been processed. The function only checks if the `receipt.deadline` has passed and if the caller is the requester, but it does not verify whether the withdrawal has already been completed (`isWithdrawn`). This can allow users to cancel a processed withdrawal receipt and emit the event `WithdrawalCancelled` which may impact off-chain components.
Open the reportThe constructor in the `Account` contract has the following line: ``` controller = EigenStrategy(address(this)).controller(); ``` Rather than referring to an outside contract, `EigenStrategy` wraps `address(this)`. The `controller` variable is effectively assigned to itself and will remain zero, leading all calls to the controller in `invoke()` to revert. Additionally, `invoke()` may arbitrarily execute external contract calls when `value` equals zero.
Open the reportThe `EigenLSTRestaking.swapToEther()` and `SymbioticDepositWstETHStrategy.requestToEther()` functions submit withdrawal requests to the `LidoWithdrawalQueue`. The functions return `etherAmount = amount` to indicate how much ETH was requested to be withdrawn. However, when `remainder < minAmountPerRequest`, this value will incorrectly include the `remainder`.
Open the reportVoters stake their STONE into the `Proposal` contract under the pretense that they can withdraw their STONE after the proposal has completed. Executing a proposal involves calling `Proposal.invoke()`, which calls `stoneVault.call{value: 0}(data)`. If a proposal passes that calls `StoneVault.requestWithdraw()`, it will use the STONE that was staked by voters into the `Proposal` contract. This will make it impossible to return STONE to all voters. Given this exploit scenario would have to be approved by voters, the severity has been left as Informational.
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="stonebtc"></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