TW
Tripwire
Find contracts. Test them. Review real vulns.

Analysis Run

019bab3b-aa91-70e2-a7d3-704d770ca089

failed
Chain ID
1
Address
Block Number
Created
Sun, Jan 11, 2026 4:06 AM
Review Summary

What survived the funnel

Open this run to answer one question: is this real attacker-relevant behavior with real impact, or just normal contract plumbing?

Confirmed
0
Validated
0
Source
Verified
Review Backing
Present
Best current signal
Untrusted DELEGATECALL target reachable
crit 0 high 0 proxy context implementation known
Review Checklist
1. Read the findings table first. Ignore raw jobs until the result looks real.
2. Confirm attacker control, not just the existence of a sink.
3. Confirm money or privilege impact.
4. Keep it if the behavior is real. Kill it if this is normal proxy, admin, or upgrade plumbing.

Processing Jobs

Status & attempts
ID Type Status Attempts Heartbeat
38345 artifact.fetch complete 1 / 3 3 months ago
38346 proxy.resolve complete 1 / 3 3 months ago
38347 tool.cast_disasm complete 1 / 3 3 months ago
38348 analysis.bundle complete 1 / 3 3 months ago
38349 capability.graph complete 1 / 3 3 months ago
38350 detector.run complete 1 / 3 3 months ago
38351 validation.fork complete 1 / 3 3 months ago
38962 report.consolidate complete 1 / 6 3 months ago
38963 tool.slither failed 2 / 2 3 months ago

Artifact

Runtime bytecode: available
Creation TX:
Deployer:
Proxy: yes confirmed
Verified Source: yes
Proxy evidence
{
    "method": "etherscan+onchain",
    "status": "confirmed",
    "etherscan_hint": {
        "source": "etherscan",
        "etherscan": {
            "proxy_flag": true,
            "implementation": "0x927a83c679a5e1a6435d6bfaef7f20d4db23e2cc"
        }
    },
    "implementation": "0x927a83c679a5e1a6435d6bfaef7f20d4db23e2cc",
    "onchain_method": "eip1967"
}

Slither

tool.slither
Status
failed slither failed (exit 1): 'forge clean' running (wd: /tmp/slither-374awzpr) 'forge config --json' running 'forge build --build-info --skip ./test/** ./script/** --force' running (wd: /tmp/slither-374awzpr) Traceback (most recent call last): File "/var/www/tripwire/.venv/bin/slither", line 10, in <module> sys.exit(main()) ~~~~^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 776, in main main_impl(all_detector_classes=detectors, all_printer_classes=printers) ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 882, in main_impl ) = process_all(filename, args, detector_classes, printer_classes) ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 96, in process_all compilations = compile_all(target, **vars(args)) File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 722, in compile_all compilations.append(CryticCompile(target, **kwargs)) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 211, in __init__ self._compile(**kwargs) ~~~~~~~~~~~~~^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 633, in _compile self._platform.compile(self, **kwargs) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/platform/foundry.py", line 102, in compile hardhat_like_parsing( ~~~~~~~~~~~~~~~~~~~~^ crytic_compile, str(self._target), build_directory, str(self._project_root) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ) ^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/platform/hardhat.py", line 52, in hardhat_like_parsing raise InvalidCompilation(txt) crytic_compile.platform.exceptions.InvalidCompilation: Compilation failed. Can you run build command? /tmp/slither-374awzpr/out/build-info is not a directory.
Attempts
2 / 2
Findings
0
Solc
View stderr
Traceback (most recent call last):
  File "/var/www/tripwire/worker/worker.py", line 198, in main
    payload = dispatch_job(job_type, job_id, job_meta, run_data, config, laravel_client)
  File "/var/www/tripwire/worker/worker.py", line 114, in dispatch_job
    return slither_run.run(job_id, job_meta, run_data, config, log, laravel_client)
           ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/worker/jobs/slither_run.py", line 1084, in run
    stdout_text, stderr_text, result_json = _run_slither(
                                            ~~~~~~~~~~~~^
        workspace,
        ^^^^^^^^^^
    ...<4 lines>...
        target_path=target_path,
        ^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/var/www/tripwire/worker/jobs/slither_run.py", line 958, in _run_slither
    raise RuntimeError(f"slither failed (exit {result.returncode}): {stderr_text or stdout_text}")
RuntimeError: slither failed (exit 1): 'forge clean' running (wd: /tmp/slither-374awzpr)
'forge config --json' running
'forge build --build-info --skip ./test/** ./script/** --force' running (wd: /tmp/slither-374awzpr)
Traceback (most recent call last):
  File "/var/www/tripwire/.venv/bin/slither", line 10, in <module>
    sys.exit(main())
             ~~~~^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 776, in main
    main_impl(all_detector_classes=detectors, all_printer_classes=printers)
    ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 882, in main_impl
    ) = process_all(filename, args, detector_classes, printer_classes)
        ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 96, in process_all
    compilations = compile_all(target, **vars(args))
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 722, in compile_all
    compilations.append(CryticCompile(target, **kwargs))
                        ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 211, in __init__
    self._compile(**kwargs)
    ~~~~~~~~~~~~~^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 633, in _compile
    self._platform.compile(self, **kwargs)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/platform/foundry.py", line 102, in compile
    hardhat_like_parsing(
    ~~~~~~~~~~~~~~~~~~~~^
        crytic_compile, str(self._target), build_directory, str(self._project_root)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/platform/hardhat.py", line 52, in hardhat_like_parsing
    raise InvalidCompilation(txt)
crytic_compile.platform.exceptions.InvalidCompilation: Compilation failed. Can you run build command?
/tmp/slither-374awzpr/out/build-info is not a directory.

Codex

report.consolidate
Job Status
complete
Attempts
1 / 6
Findings
1
No findings?
no
Summary
Found 1 low-severity issue: validator-manager signatures for registerValidators are replayable, enabling duplicate validator deposits if approvals are reused.
View stdout
{"no_findings":false,"summary":"Found 1 low-severity issue: validator-manager signatures for registerValidators are replayable, enabling duplicate validator deposits if approvals are reused.","findings":[{"severity":"low","category":"auth","title":"Validator-manager signature can be replayed for registerValidators, allowing duplicate validator deposits","exploit_scenario":"A manager signature used in registerValidators is revealed on-chain. Because the vault does not track used nonces for registerValidators (it uses validatorsRegistryRoot as the nonce) and does not reject already-registered validator pubkeys, an attacker can replay the same manager signature (and any reusable keeper approvals) to call registerValidators again. This can trigger repeated deposits for the same validators, locking additional vault liquidity or bypassing the intended fundValidators path.","recommendation":"Bind registerValidators to a true monotonic nonce (e.g., validatorsManagerNonce) or track used signature/message hashes. Also reject already-registered v2 validator pubkeys (and add duplicate checks for v1) during registration to prevent replayed deposits.","evidence":"registerValidators passes validatorsRegistryRoot as the signature nonce and _isValidatorsManager does not enforce single-use of that nonce; getValidatorDeposits does not prevent re-registration of already tracked validators.","locations":["contracts/vaults/modules/VaultValidators.sol","contracts/libraries/ValidatorUtils.sol"]}]}
View stderr
OpenAI Codex v0.77.0 (research preview)
--------
workdir: /tmp/codex-o6mhmkuz
model: gpt-5.2-codex
provider: openai
approval: never
sandbox: read-only
reasoning effort: high
reasoning summaries: auto
session id: 019bab63-f125-7b21-8ae3-27bca80ec02a
--------
user
Context: The user requested the proxy at 0xb36fc5e542cb4fc562a624912f55da2758998113.
Audit the implementation at 0x927a83c679a5e1a6435d6bfaef7f20d4db23e2cc.
Findings should refer to the implementation unless explicitly about proxy mechanics.

You are a security auditor. Audit this Solidity codebase for exploitable vulnerabilities.
Focus on: reentrancy, access control, upgradeability/proxy issues, signature validation, oracle manipulation,
accounting/invariant breaks, external call patterns, unchecked return values, and dangerous delegatecall.

Scope rules:
- You MUST fully read `AUDIT_BUNDLE.txt` before concluding. It contains the complete contents of all first-party Solidity sources in this workspace.
- Treat `lib/` and `@openzeppelin/` as third-party dependencies; skip them unless there is a concrete misuse angle in first-party code.
- If there are only dependency files and no first-party sources, set no_findings=true and explain in summary.

Output MUST be valid JSON matching the provided schema.
- If you find nothing meaningful: set no_findings=true and findings=[].
- Otherwise: set no_findings=false and include findings with severity/category/title and file/line locations when possible.

Output constraints (IMPORTANT):
- severity MUST be exactly one of: info, low, medium, high, critical (lowercase).
- category MUST be exactly one of: access_control, reentrancy, math, oracle, upgradeability, auth, other.
Keep summary short and concrete.
mcp startup: no servers

thinking
**Reading bundle file**
exec
/bin/bash -lc ls in /tmp/codex-o6mhmkuz succeeded in 8ms:
AUDIT_BUNDLE.txt
codex-output-schema.json
contracts
lib

thinking
**Opening AUDIT_BUNDLE**
exec
/bin/bash -lc 'cat AUDIT_BUNDLE.txt' in /tmp/codex-o6mhmkuz succeeded in 22ms:
This file concatenates the full contents of first-party Solidity sources for auditing.
Do not treat dependency code under lib/ or @openzeppelin/ as first-party.

================================================================================
FILE: contracts/base/Multicall.sol
================================================================================
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.22;

import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import "../interfaces/IMulticall.sol";

/**
 * @title Multicall
 * @author StakeWise
 * @notice Enables calling multiple methods in a single call to the contract
 */
abstract contract Multicall is IMulticall {
    /// @inheritdoc IMulticall

thinking
**Searching for EthVault contract implementation**
exec
/bin/bash -lc 'rg -n "contract EthVault|EthVault" AUDIT_BUNDLE.txt' in /tmp/codex-o6mhmkuz succeeded in 16ms:
219:FILE: contracts/interfaces/IEthVault.sol
238: * @title IEthVault
240: * @notice Defines the interface for the EthVault contract
242:interface IEthVault is
255:     * @dev Struct for deploying the EthVault contract
269:    struct EthVaultConstructorArgs {
285:     * @dev Struct for initializing the EthVault contract
290:    struct EthVaultInitParams {
297:     * @notice Initializes or upgrades the EthVault contract. Must transfer security deposit during the deployment.
298:     * @param params The encoded parameters for initializing the EthVault contract
333:FILE: contracts/interfaces/IEthVaultFactory.sol
340: * @title IEthVaultFactory
344:interface IEthVaultFactory {
2807:FILE: contracts/vaults/ethereum/EthVault.sol
2814:import {IEthVault} from "../../interfaces/IEthVault.sol";
2815:import {IEthVaultFactory} from "../../interfaces/IEthVaultFactory.sol";
2830: * @title EthVault
2834:contract EthVault is
2847:    IEthVault
2855:     * @param args The arguments for initializing the EthVault contract

thinking
**Reviewing EthVault and related modules**
exec
/bin/bash -lc "sed -n '2700,3400p' AUDIT_BUNDLE.txt" in /tmp/codex-o6mhmkuz succeeded in 19ms:

        uint256 startIndex;
        uint256 totalFeeAssets = msg.value;
        for (uint256 i = 0; i < validatorsCount;) {
            bytes calldata validator = validators[startIndex:startIndex + _validatorWithdrawalLength];
            bytes calldata publicKey = validator[:48];

            // convert gwei to wei by multiplying by 1 gwei
            uint256 withdrawnAmount = (uint256(uint64(bytes8(validator[48:56]))) * 1 gwei);
            uint256 feePaid = uint256(bytes32(Address.functionStaticCall(validatorsWithdrawals, "")));

            // submit validator withdrawal
            Address.functionCallWithValue(validatorsWithdrawals, validator, feePaid);
            totalFeeAssets -= feePaid;
            emit IVaultValidators.ValidatorWithdrawalSubmitted(publicKey, withdrawnAmount, feePaid);

            unchecked {
                // cannot realistically overflow
                ++i;
                startIndex += _validatorWithdrawalLength;

thinking
**Inspecting additional modules for vulnerabilities**
exec
/bin/bash -lc "sed -n '3400,4200p' AUDIT_BUNDLE.txt" in /tmp/codex-o6mhmkuz succeeded in 8ms:

    /// @inheritdoc IVaultFee
    uint16 public override feePercent;

    uint64 private _lastUpdateTimestamp;

    /// @inheritdoc IVaultFee
    function setFeeRecipient(address _feeRecipient) external override {
        _checkAdmin();
        _setFeeRecipient(_feeRecipient);
    }

    /// @inheritdoc IVaultFee
    function setFeePercent(uint16 _feePercent) external override {
        _checkAdmin();
        _setFeePercent(_feePercent, false);
    }

    /**
     * @dev Internal function for updating the fee recipient externally or from the initializer

thinking
**Pausing to review state**
exec
/bin/bash -lc "sed -n '4200,5000p' AUDIT_BUNDLE.txt" in /tmp/codex-o6mhmkuz succeeded in 6ms:
        _totalShares -= SafeCast.toUint128(burnedShares);
        _totalAssets -= SafeCast.toUint128(exitedAssets);
    }

    /**
     * @dev Internal function for minting shares
     * @param owner The address of the owner to mint shares to
     * @param shares The number of shares to mint
     */
    function _mintShares(address owner, uint256 shares) internal virtual {
        // update total shares
        _totalShares += SafeCast.toUint128(shares);

        // mint shares
        unchecked {
            // cannot overflow because the sum of all user
            // balances can't exceed the max uint256 value
            _balances[owner] += shares;
        }
    }

thinking
**Analyzing EthVault and related contracts for vulnerabilities**

I'm focusing on EthVault’s implementation within the bundle, checking potential issues like reentrancy, signature validation, and nonce use. I'm scrutinizing deposit and minting flows, external calls in burning tokens, and how validator signatures are managed, especially around nonce handling and possible replay risks. Let's explore related modules like ValidatorUtils and EIP712 for deeper insights.
exec
/bin/bash -lc 'rg -n "library ValidatorUtils|isValidManagerSignature|Validators" AUDIT_BUNDLE.txt' in /tmp/codex-o6mhmkuz succeeded in 8ms:
73:import {IKeeperValidators} from "./IKeeperValidators.sol";
158:        IKeeperValidators.ApprovalParams calldata keeperParams,
170:    function registerValidators(
172:        IKeeperValidators.ApprovalParams calldata keeperParams,
188:FILE: contracts/interfaces/IEthValidatorsRegistry.sol
194:import {IValidatorsRegistry} from "./IValidatorsRegistry.sol";
197: * @title IEthValidatorsRegistry
203:interface IEthValidatorsRegistry is IValidatorsRegistry {
230:import {IVaultValidators} from "./IVaultValidators.sol";
247:    IVaultValidators,
650:FILE: contracts/interfaces/IKeeperValidators.sol
660: * @title IKeeperValidators
664:interface IKeeperValidators is IKeeperOracles, IKeeperRewards {
670:    event ValidatorsApproval(address indexed vault, string exitSignaturesIpfsHash);
687:    event ValidatorsMinOraclesUpdated(uint256 oracles);
722:    function approveValidators(ApprovalParams calldata params) external;
742:    function setValidatorsMinOracles(uint256 _validatorsMinOracles) external;
1309:FILE: contracts/interfaces/IValidatorsRegistry.sol
1316: * @title IValidatorsRegistry
1320:interface IValidatorsRegistry {

thinking
**Reviewing ValidatorUtils for vulnerabilities**
exec
/bin/bash -lc "sed -n '2480,2725p' AUDIT_BUNDLE.txt" in /tmp/codex-o6mhmkuz succeeded in 6ms:
                revert Errors.LiquidationDisabled();
            }
            // check health factor violation in case of liquidation
            if (
                Math.mulDiv(data.depositedAssets * _wad, config.liqThresholdPercent, data.mintedAssets * _maxPercent)
                    >= _hfLiqThreshold
            ) {
                revert Errors.InvalidHealthFactor();
            }
            receivedAssets = Math.mulDiv(
                osTokenVaultController.convertToAssets(data.redeemedOsTokenShares), config.liqBonusPercent, _maxPercent
            );
        } else {
            receivedAssets = osTokenVaultController.convertToAssets(data.redeemedOsTokenShares);
        }

        // check whether received assets are valid
        if (receivedAssets > data.depositedAssets || receivedAssets > data.availableAssets) {
            revert Errors.InvalidReceivedAssets();
        }

thinking
**Analyzing potential replay and reentrancy risks**

I'm examining how `registerValidators` might be vulnerable to signature replay since it uses a root as nonce that may not increment, allowing replays to re-register validators and potentially top up deposits repeatedly. I’m also checking if reentrancy is possible via external calls, but the deposit contract appears trusted and non-reentrant. I want to verify details on keeper approvals and nonce usage to see how these impact security.
exec
/bin/bash -lc "sed -n '620,760p' AUDIT_BUNDLE.txt" in /tmp/codex-o6mhmkuz succeeded in 9ms:
     * @param vault The address of the Vault
     * @return `true` if Vault is collateralized, `false` otherwise
     */
    function isCollateralized(address vault) external view returns (bool);

    /**
     * @notice Update rewards data
     * @param params The struct containing rewards update parameters
     */
    function updateRewards(RewardsUpdateParams calldata params) external;

    /**
     * @notice Harvest rewards. Can be called only by Vault.
     * @param params The struct containing rewards harvesting parameters
     * @return totalAssetsDelta The total reward/penalty accumulated by the Vault since the last sync
     * @return unlockedMevDelta The Vault execution reward that can be withdrawn from shared MEV escrow. Only used by shared MEV Vaults.
     * @return harvested `true` when the rewards were harvested, `false` otherwise
     */
    function harvest(HarvestParams calldata params)
        external

thinking
**Analyzing potential reentrancy and state update risks**

I'm reviewing mint, burn, and transfer functions for vulnerabilities, focusing on external calls that might allow reentrancy before state updates complete. I'll look into trusted assumptions around external contracts like the vault controller and escrow, and plan to investigate related modules such as the deposit data registry next.
exec
/bin/bash -lc 'rg -n "contract DepositDataRegistry|DepositDataRegistry" AUDIT_BUNDLE.txt' in /tmp/codex-o6mhmkuz succeeded in 9ms:
67:FILE: contracts/interfaces/IDepositDataRegistry.sol
78: * @title IDepositDataRegistry
80: * @notice Defines the interface for DepositDataRegistry
82:interface IDepositDataRegistry is IMulticall {
266:     * @param depositDataRegistry The address of the DepositDataRegistry contract
1996:     * @notice Function for updating the validators manager. Can only be called by the admin. Default is the DepositDataRegistry contract.
4334:import {IDepositDataRegistry} from "../../interfaces/IDepositDataRegistry.sol";
4373:    /// deprecated. Deposit data management is moved to DepositDataRegistry contract
4376:    /// deprecated. Deposit data management is moved to DepositDataRegistry contract
4574:        // migrate deposit data variables to DepositDataRegistry contract
4576:            IDepositDataRegistry(_depositDataRegistry).migrate(

thinking
**Investigating contract presence and references**
exec
/bin/bash -lc 'rg -n "''^FILE:" AUDIT_BUNDLE.txt' in /tmp/codex-o6mhmkuz succeeded in 15ms:
5:FILE: contracts/base/Multicall.sol
32:FILE: contracts/interfaces/IConsolidationsChecker.sol
67:FILE: contracts/interfaces/IDepositDataRegistry.sol
188:FILE: contracts/interfaces/IEthValidatorsRegistry.sol
219:FILE: contracts/interfaces/IEthVault.sol
333:FILE: contracts/interfaces/IEthVaultFactory.sol
382:FILE: contracts/interfaces/IKeeperOracles.sol
447:FILE: contracts/interfaces/IKeeperRewards.sol
650:FILE: contracts/interfaces/IKeeperValidators.sol
746:FILE: contracts/interfaces/IMulticall.sol
768:FILE: contracts/interfaces/IOsTokenConfig.sol
835:FILE: contracts/interfaces/IOsTokenVaultController.sol
1018:FILE: contracts/interfaces/IOsTokenVaultEscrow.sol
1232:FILE: contracts/interfaces/IOwnMevEscrow.sol
1273:FILE: contracts/interfaces/ISharedMevEscrow.sol
1309:FILE: contracts/interfaces/IValidatorsRegistry.sol
1330:FILE: contracts/interfaces/IVaultAdmin.sol
1376:FILE: contracts/interfaces/IVaultEnterExit.sol
1489:FILE: contracts/interfaces/IVaultEthStaking.sol
1540:FILE: contracts/interfaces/IVaultFee.sol

thinking
**Analyzing EthVault contract vulnerabilities**
exec
/bin/bash -lc "sed -n '2140,2245p' AUDIT_BUNDLE.txt" in /tmp/codex-o6mhmkuz succeeded in 6ms:
FILE: contracts/libraries/EIP712Utils.sol
================================================================================
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.22;

/**
 * @title EIP712Utils
 * @author StakeWise
 * @notice Includes functionality for calculating EIP712 hashes
 */
library EIP712Utils {
    bytes32 private constant _domainTypeHash =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
    bytes32 private constant _versionHash = keccak256("1");

    /**
     * @notice Computes the hash of the EIP712 typed data
     * @dev This function is used to compute the hash of the EIP712 typed data
     * @param name The name of the domain

thinking
**Analyzing upgrade and exit queue vulnerabilities**
exec
/bin/bash -lc "sed -n '1330,1425p' AUDIT_BUNDLE.txt" in /tmp/codex-o6mhmkuz succeeded in 6ms:
FILE: contracts/interfaces/IVaultAdmin.sol
================================================================================
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.22;

/**
 * @title IVaultState
 * @author StakeWise
 * @notice Defines the interface for the VaultAdmin contract
 */
interface IVaultAdmin {
    /**
     * @notice Event emitted on metadata ipfs hash update
     * @param caller The address of the function caller
     * @param metadataIpfsHash The new metadata IPFS hash
     */
    event MetadataUpdated(address indexed caller, string metadataIpfsHash);

    /**



thinking
**Analyzing potential vulnerabilities and logic consistency**

I'm carefully evaluating various contract functions for possible security issues like signature replay, reentrancy, underflow, and state inconsistencies, especially in validator management, asset accounting, and token redemption flows; I’m noting assumptions about trusted keepers and external calls while highlighting subtle edge cases and where protections may rely on external trust.
thinking
**Evaluating upgrade and signature handling intricacies**

I'm investigating external calls during minting and redeeming tokens, upgrade initialization logic, and potential signature replay issues in validator management; I’m also reviewing reentrancy risks in external calls and subtle nonce increments that could unintentionally disrupt signature workflows while considering contract trust assumptions.
thinking
**Reviewing upgrade paths and queue index handling**
exec
/bin/bash -lc "sed -n '2241,2425p' AUDIT_BUNDLE.txt" in /tmp/codex-o6mhmkuz succeeded in 7ms:
FILE: contracts/libraries/ExitQueue.sol
================================================================================
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.22;

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {Errors} from "./Errors.sol";

/**
 * @title ExitQueue
 * @author StakeWise
 * @notice ExitQueue represent checkpoints of burned shares and exited assets
 */
library ExitQueue {
    /**
     * @notice A struct containing checkpoint data
     * @param totalTickets The cumulative number of tickets (shares) exited
     * @param exitedAssets The number of assets that exited in this checkpoint


thinking
**Analyzing complex contract interactions and potential risks**
thinking
**Reviewing upgradeability and reentrancy considerations**

I'm carefully weighing the upgrade pattern, external calls, and reentrancy guards; after thorough checks, state updates before external calls appear safe, and no concerning upgradeability gaps stand out. It seems acceptable to report no findings while confirming the review scope and careful analysis of key modules.
exec
/bin/bash -lc "sed -n '2423,2506p' AUDIT_BUNDLE.txt" in /tmp/codex-o6mhmkuz succeeded in 31ms:
FILE: contracts/libraries/OsTokenUtils.sol
================================================================================
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.22;

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IOsTokenConfig} from "../interfaces/IOsTokenConfig.sol";
import {IOsTokenVaultController} from "../interfaces/IOsTokenVaultController.sol";
import {Errors} from "./Errors.sol";

/**
 * @title OsTokenUtils
 * @author StakeWise
 * @notice Includes functionality for handling osToken redemptions
 */
library OsTokenUtils {
    uint256 private constant _wad = 1e18;
    uint256 private constant _hfLiqThreshold = 1e18;
    uint256 private constant _maxPercent = 1e18;

thinking
**Finding replay vulnerability in validator registration**
codex
{"no_findings":false,"summary":"Found 1 low-severity issue: validator-manager signatures for registerValidators are replayable, enabling duplicate validator deposits if approvals are reused.","findings":[{"severity":"low","category":"auth","title":"Validator-manager signature can be replayed for registerValidators, allowing duplicate validator deposits","exploit_scenario":"A manager signature used in registerValidators is revealed on-chain. Because the vault does not track used nonces for registerValidators (it uses validatorsRegistryRoot as the nonce) and does not reject already-registered validator pubkeys, an attacker can replay the same manager signature (and any reusable keeper approvals) to call registerValidators again. This can trigger repeated deposits for the same validators, locking additional vault liquidity or bypassing the intended fundValidators path.","recommendation":"Bind registerValidators to a true monotonic nonce (e.g., validatorsManagerNonce) or track used signature/message hashes. Also reject already-registered v2 validator pubkeys (and add duplicate checks for v1) during registration to prevent replayed deposits.","evidence":"registerValidators passes validatorsRegistryRoot as the signature nonce and _isValidatorsManager does not enforce single-use of that nonce; getValidatorDeposits does not prevent re-registration of already tracked validators.","locations":["contracts/vaults/modules/VaultValidators.sol","contracts/libraries/ValidatorUtils.sol"]}]}
tokens used
64,440
Metadata
{
    "codex": {
        "ran_at": "2026-01-11T04:57:57.314760+00:00",
        "returncode": 0
    },
    "project": {
        "mode": "source",
        "file_count": 62,
        "import_count": 89,
        "first_party_contracts_count": 42
    },
    "summary": "Found 1 low-severity issue: validator-manager signatures for registerValidators are replayable, enabling duplicate validator deposits if approvals are reused.",
    "raw_final": {
        "summary": "Found 1 low-severity issue: validator-manager signatures for registerValidators are replayable, enabling duplicate validator deposits if approvals are reused.",
        "findings": [
            {
                "title": "Validator-manager signature can be replayed for registerValidators, allowing duplicate validator deposits",
                "category": "auth",
                "evidence": "registerValidators passes validatorsRegistryRoot as the signature nonce and _isValidatorsManager does not enforce single-use of that nonce; getValidatorDeposits does not prevent re-registration of already tracked validators.",
                "severity": "low",
                "locations": [
                    "contracts/vaults/modules/VaultValidators.sol",
                    "contracts/libraries/ValidatorUtils.sol"
                ],
                "recommendation": "Bind registerValidators to a true monotonic nonce (e.g., validatorsManagerNonce) or track used signature/message hashes. Also reject already-registered v2 validator pubkeys (and add duplicate checks for v1) during registration to prevent replayed deposits.",
                "exploit_scenario": "A manager signature used in registerValidators is revealed on-chain. Because the vault does not track used nonces for registerValidators (it uses validatorsRegistryRoot as the nonce) and does not reject already-registered validator pubkeys, an attacker can replay the same manager signature (and any reusable keeper approvals) to call registerValidators again. This can trigger repeated deposits for the same validators, locking additional vault liquidity or bypassing the intended fundValidators path."
            }
        ],
        "no_findings": false
    },
    "no_findings": false,
    "proxy_context": {
        "proxy_run_id": "019bab3b-aa91-70e2-a7d3-704d770ca089",
        "proxy_address": "0xb36fc5e542cb4fc562a624912f55da2758998113",
        "implementation_run_id": "019bab3c-4a33-7098-9325-ebfbff005bf9",
        "implementation_address": "0x927a83c679a5e1a6435d6bfaef7f20d4db23e2cc"
    },
    "schema_version": 1
}

Opcode Heuristics

tool.cast_disasm
Delegatecall
1
Selfdestruct
0
CREATE2
0
CALL-family (heavy)
1
EXT*/BALANCE
0
Total opcodes
61
Flags
delegatecall_present proxy_like_runtime
View cast disassembly output
00000000: PUSH1 0x80
00000002: PUSH1 0x40
00000004: MSTORE
00000005: PUSH32 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
00000026: SLOAD
00000027: PUSH0
00000028: SWAP1
00000029: DUP2
0000002a: SWAP1
0000002b: PUSH1 0x01
0000002d: PUSH1 0x01
0000002f: PUSH1 0xa0
00000031: SHL
00000032: SUB
00000033: AND
00000034: CALLDATASIZE
00000035: DUP3
00000036: DUP1
00000037: CALLDATACOPY
00000038: DUP2
00000039: CALLDATASIZE
0000003a: SWAP2
0000003b: GAS
0000003c: DELEGATECALL
0000003d: RETURNDATASIZE
0000003e: PUSH0
0000003f: DUP1
00000040: RETURNDATACOPY
00000041: ISZERO
00000042: PUSH1 0x48
00000044: JUMPI
00000045: RETURNDATASIZE
00000046: PUSH0
00000047: RETURN
00000048: JUMPDEST
00000049: RETURNDATASIZE
0000004a: PUSH0
0000004b: REVERT
0000004c: INVALID
0000004d: LOG2
0000004e: PUSH5 0x6970667358
00000054: UNKNOWN(0x22)
00000055: SLT
00000056: KECCAK256
00000057: MSIZE
00000058: UNKNOWN(0x0D)
00000059: UNKNOWN(0xE6)
0000005a: AND
0000005b: UNKNOWN(0x2A)
0000005c: CALL
0000005d: UNKNOWN(0xAC)
0000005e: UNKNOWN(0xA5)
0000005f: UNKNOWN(0xF7)
00000060: UNKNOWN(0xD3)
00000061: DUP1
00000062: UNKNOWN(0xE4)
00000063: MSIZE
00000064: CODECOPY
00000065: UNKNOWN(0x4C)
00000066: UNKNOWN(0xE9)
00000067: PUSH30

Findings

validated = confirmed for call sinks; sink observed for others
Severity Category Tool Title Confidence Validated Confirmed Details
critical upgradeability detector Untrusted DELEGATECALL target reachable 70 no no
view
trace_id: 0x317a5c4c8e4afef6abdd6bde2124ea12aea89f192b90f84187f65a296956ce78
call_targets: 0x927a83c679a5e1a6435d6bfaef7f20d4db23e2cc
target_varies: no
classification: constant_target
validation_json
{
    "sink": "DELEGATECALL",
    "errors": 0,
    "status": "sink_reached",
    "attempts": 1,
    "trace_id": "0x317a5c4c8e4afef6abdd6bde2124ea12aea89f192b90f84187f65a296956ce78",
    "confirmed": false,
    "trace_mode": "callTracer",
    "call_targets": [
        "0x927a83c679a5e1a6435d6bfaef7f20d4db23e2cc"
    ],
    "matched_probe": null,
    "target_varies": false,
    "classification": "constant_target"
}
witness_json
{
    "notes": "heuristic witness; entrypoints are best-effort selectors with zeroed calldata",
    "constraints": [],
    "entrypoints": [
        {
            "calldata": "0x",
            "selector": "fallback",
            "calldata_variants": [
                "0x"
            ]
        }
    ]
}
evidence_json
{
    "examples": [
        {
            "pc": 60,
            "opcode": "DELEGATECALL",
            "returndata_checked": null,
            "value_expression_category": "n/a",
            "target_expression_category": "computed"
        }
    ],
    "guards_detected": [],
    "delegatecall_count": 1,
    "untrusted_delegatecall_count": 1
}
high other detector Untrusted CALL target/value reachable 60 no no
view
trace_id: 0x317a5c4c8e4afef6abdd6bde2124ea12aea89f192b90f84187f65a296956ce78
call_targets: 0xb36fc5e542cb4fc562a624912f55da2758998113
target_varies: no
classification: constant_target
validation_json
{
    "sink": "CALL",
    "errors": 0,
    "status": "sink_reached",
    "attempts": 1,
    "trace_id": "0x317a5c4c8e4afef6abdd6bde2124ea12aea89f192b90f84187f65a296956ce78",
    "confirmed": false,
    "trace_mode": "callTracer",
    "call_targets": [
        "0xb36fc5e542cb4fc562a624912f55da2758998113"
    ],
    "matched_probe": null,
    "target_varies": false,
    "classification": "constant_target"
}
witness_json
{
    "notes": "heuristic witness; entrypoints are best-effort selectors with zeroed calldata",
    "constraints": [],
    "entrypoints": [
        {
            "calldata": "0x",
            "selector": "fallback",
            "calldata_variants": [
                "0x"
            ]
        }
    ]
}
evidence_json
{
    "examples": [
        {
            "pc": 92,
            "opcode": "CALL",
            "returndata_checked": null,
            "value_expression_category": "computed",
            "target_expression_category": "computed"
        }
    ],
    "call_count": 1,
    "guards_detected": [],
    "untrusted_call_count": 1
}
high other detector ETH value transfer possible 55 no no
view
trace_id: 0x317a5c4c8e4afef6abdd6bde2124ea12aea89f192b90f84187f65a296956ce78
call_targets: 0xb36fc5e542cb4fc562a624912f55da2758998113
target_varies: no
classification: constant_target
validation_json
{
    "sink": "CALL",
    "errors": 0,
    "status": "sink_reached",
    "attempts": 1,
    "trace_id": "0x317a5c4c8e4afef6abdd6bde2124ea12aea89f192b90f84187f65a296956ce78",
    "confirmed": false,
    "trace_mode": "callTracer",
    "call_targets": [
        "0xb36fc5e542cb4fc562a624912f55da2758998113"
    ],
    "matched_probe": null,
    "target_varies": false,
    "classification": "constant_target"
}
witness_json
{
    "notes": "heuristic witness; entrypoints are best-effort selectors with zeroed calldata",
    "constraints": [],
    "entrypoints": [
        {
            "calldata": "0x",
            "selector": "fallback",
            "calldata_variants": [
                "0x"
            ]
        }
    ]
}
evidence_json
{
    "examples": [
        {
            "pc": 92,
            "opcode": "CALL",
            "returndata_checked": null,
            "value_expression_category": "computed",
            "target_expression_category": "computed"
        }
    ],
    "eth_value_calls": 1,
    "guards_detected": []
}
medium upgradeability cast DELEGATECALL present 80 no
view
evidence_json
{
    "notes": "delegatecall enables execution in caller context; common in proxies/upgradeable patterns",
    "delegatecall_count": 1
}
low auth codex Validator-manager signature can be replayed for registerValidators, allowing duplicate validator deposits 65 no
view
evidence_json
{
    "evidence": "registerValidators passes validatorsRegistryRoot as the signature nonce and _isValidatorsManager does not enforce single-use of that nonce; getValidatorDeposits does not prevent re-registration of already tracked validators.",
    "locations": [
        "contracts/vaults/modules/VaultValidators.sol",
        "contracts/libraries/ValidatorUtils.sol"
    ],
    "recommendation": "Bind registerValidators to a true monotonic nonce (e.g., validatorsManagerNonce) or track used signature/message hashes. Also reject already-registered v2 validator pubkeys (and add duplicate checks for v1) during registration to prevent replayed deposits.",
    "exploit_scenario": "A manager signature used in registerValidators is revealed on-chain. Because the vault does not track used nonces for registerValidators (it uses validatorsRegistryRoot as the nonce) and does not reject already-registered validator pubkeys, an attacker can replay the same manager signature (and any reusable keeper approvals) to call registerValidators again. This can trigger repeated deposits for the same validators, locking additional vault liquidity or bypassing the intended fundValidators path."
}
info upgradeability cast Likely proxy-like runtime 60 no
view
evidence_json
{
    "notes": "short runtime with SLOAD and DELEGATECALL resembles minimal proxy/upgradeable pattern",
    "sload_count": 1,
    "total_opcodes": 61,
    "delegatecall_count": 1
}