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

Analysis Run

019bab3b-aaee-7307-8a62-f0c903f3c8f8

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
38429 artifact.fetch complete 1 / 3 3 months ago
38430 proxy.resolve complete 1 / 3 3 months ago
38431 tool.cast_disasm complete 1 / 3 3 months ago
38432 analysis.bundle complete 1 / 3 3 months ago
38433 capability.graph complete 1 / 3 3 months ago
38434 detector.run complete 1 / 3 3 months ago
38435 validation.fork complete 1 / 3 3 months ago
38996 report.consolidate complete 1 / 6 3 months ago
38997 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": "0x68fe80c6e97e0c8613e2fed344358c6635ba5366"
        }
    },
    "implementation": "0x68fe80c6e97e0c8613e2fed344358c6635ba5366",
    "onchain_method": "eip1967"
}

Slither

tool.slither
Status
failed slither failed (exit 1): 'forge clean' running (wd: /tmp/slither-_c1aj8zf) 'forge config --json' running 'forge build --build-info --skip ./test/** ./script/** --force' running (wd: /tmp/slither-_c1aj8zf) 'forge' returned non-zero exit code 1 Error: Encountered invalid solc version in src/UUPSProxy.sol: No solc version exists that matches the version requirement: =0.8.13 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-_c1aj8zf/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-_c1aj8zf)
'forge config --json' running
'forge build --build-info --skip ./test/** ./script/** --force' running (wd: /tmp/slither-_c1aj8zf)
'forge' returned non-zero exit code 1
Error: Encountered invalid solc version in src/UUPSProxy.sol: No solc version exists that matches the version requirement: =0.8.13
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-_c1aj8zf/out/build-info is not a directory.

Codex

report.consolidate
Job Status
complete
Attempts
1 / 6
Findings
2
No findings?
no
Summary
Found 2 issues in the LiquidityPool implementation: missing BNFT-holder authorization and a division-by-zero DoS in fund source allocation.
View stdout
{"no_findings":false,"summary":"Found 2 issues in the LiquidityPool implementation: missing BNFT-holder authorization and a division-by-zero DoS in fund source allocation.","findings":[{"severity":"high","category":"access_control","title":"Unregistered address can execute BNFT deposit flow","exploit_scenario":"Any address with no BNFT registration has `bnftHoldersIndexes[msg.sender].index == 0` by default. If the current scheduling window includes index 0, that address can call `batchDepositAsBnftHolder` and pass the assignment/timestamp checks, forcing the pool to supply 30 ETH per validator while only posting 2 ETH, and can lock out the legitimate holder at index 0 by updating its timestamp.","recommendation":"Require `bnftHoldersIndexes[msg.sender].registered` and/or check that `bnftHolders[index].holder == msg.sender` before allowing BNFT deposits.","evidence":"`batchDepositAsBnftHolder` uses `bnftHoldersIndexes[msg.sender].index` and performs assignment/timestamp checks but never validates `registered` or holder identity.","locations":["src/LiquidityPool.sol:257","src/LiquidityPool.sol:260","src/LiquidityPool.sol:264","src/LiquidityPool.sol:265"]},{"severity":"low","category":"math","title":"Division-by-zero DoS in fund source allocation","exploit_scenario":"If `fundStatistics[ETHER_FAN].numberOfValidators` or `fundStatistics[ETHER_FAN].targetWeight` is zero (initially or after decreases), `allocateSourceOfFunds()` reverts. This blocks `batchDepositAsBnftHolder`, halting BNFT deposits until an admin fixes the values.","recommendation":"Guard against zero denominators (e.g., if either is zero, default to the other source or require initialization before use).","evidence":"`allocateSourceOfFunds()` divides by `fundStatistics[SourceOfFunds.ETHER_FAN].numberOfValidators` and `.targetWeight` with no zero checks.","locations":["src/LiquidityPool.sol:639","src/LiquidityPool.sol:640"]}]}
View stderr
OpenAI Codex v0.77.0 (research preview)
--------
workdir: /tmp/codex-hcn7ma47
model: gpt-5.2-codex
provider: openai
approval: never
sandbox: read-only
reasoning effort: high
reasoning summaries: auto
session id: 019bab7d-dd16-73b3-8fd6-6bec968cb6c2
--------
user
Context: The user requested the proxy at 0x00c452affee3a17d9cecc1bcd2b8d5c7635c4cb9.
Audit the implementation at 0x68fe80c6e97e0c8613e2fed344358c6635ba5366.
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 AUDIT_BUNDLE file**
exec
/bin/bash -lc 'cat /tmp/codex-hcn7ma47/AUDIT_BUNDLE.txt' in /tmp/codex-hcn7ma47 succeeded in 6ms:
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: src/AuctionManager.sol
================================================================================
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "./interfaces/IAuctionManager.sol";
import "./interfaces/INodeOperatorManager.sol";
import "./interfaces/IProtocolRevenueManager.sol";
import "@openzeppelin-upgradeable/contracts/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin-upgradeable/contracts/security/PausableUpgradeable.sol";
import "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol";
import "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";

contract AuctionManager is
    Initializable,

thinking
**Investigating full contract files**
exec
/bin/bash -lc 'rg -n "''^FILE:" /tmp/codex-hcn7ma47/AUDIT_BUNDLE.txt' in /tmp/codex-hcn7ma47 succeeded in 7ms:
5:FILE: src/AuctionManager.sol
377:FILE: src/LiquidityPool.sol
1111:FILE: src/interfaces/IAuctionManager.sol
1161:FILE: src/interfaces/IEtherFiAdmin.sol
1173:FILE: src/interfaces/IEtherFiNode.sol
1269:FILE: src/interfaces/IEtherFiNodesManager.sol
1351:FILE: src/interfaces/ILiquidityPool.sol
1434:FILE: src/interfaces/IMembershipManager.sol
1528:FILE: src/interfaces/INodeOperatorManager.sol
1579:FILE: src/interfaces/IProtocolRevenueManager.sol
1597:FILE: src/interfaces/IRegulationsManager.sol
1618:FILE: src/interfaces/IStakingManager.sol
1664:FILE: src/interfaces/ITNFT.sol
1682:FILE: src/interfaces/IWithdrawRequestNFT.sol
1708:FILE: src/interfaces/IeETH.sol

thinking
**Examining implementation contract code**
exec
/bin/bash -lc "sed -n '1,260p' /tmp/codex-hcn7ma47/src/LiquidityPool.sol" in /tmp/codex-hcn7ma47 succeeded in 6ms:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "@openzeppelin-upgradeable/contracts/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin-upgradeable/contracts/token/ERC721/IERC721ReceiverUpgradeable.sol";
import "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol";

import "./interfaces/IRegulationsManager.sol";
import "./interfaces/IStakingManager.sol";
import "./interfaces/IEtherFiNodesManager.sol";
import "./interfaces/IeETH.sol";
import "./interfaces/IStakingManager.sol";
import "./interfaces/IMembershipManager.sol";
import "./interfaces/ITNFT.sol";
import "./interfaces/IWithdrawRequestNFT.sol";
import "./interfaces/ILiquidityPool.sol";
import "./interfaces/IEtherFiAdmin.sol";


thinking
**Requesting more file content**
exec
/bin/bash -lc "sed -n '260,620p' /tmp/codex-hcn7ma47/src/LiquidityPool.sol" in /tmp/codex-hcn7ma47 succeeded in 14ms:
        uint32 index = bnftHoldersIndexes[msg.sender].index;

        //Need to make sure the BNFT player is assigned for the current period
        //See function for details
        require(isAssigned(firstIndex, lastIndex, index), "Not assigned");
        require(bnftHolders[index].timestamp < uint32(getCurrentSchedulingStartTimestamp()), "Already deposited");
        require(msg.value == _numberOfValidators * 2 ether, "Deposit 2 ETH per validator");
        require(totalValueInLp + msg.value >= 32 ether * _numberOfValidators, "Not enough balance");

        //BNFT players are eligible to spin up anything up to the max amount of validators allowed (maxValidatorsPerOwner),
        if(_numberOfValidators > maxValidatorsPerOwner) revert AboveMaxAllocation();
    
        //Funds in the LP can come from our membership strategy or the eEth staking strategy. We select which source of funds will
        //be used for spinning up these deposited ids. See the function for more detail on how we do this.
        SourceOfFunds _source = allocateSourceOfFunds();
        fundStatistics[_source].numberOfValidators += uint32(_numberOfValidators);

        uint256 amountFromLp = 30 ether * _numberOfValidators;
        if (amountFromLp > type(uint128).max) revert InvalidAmount();

exec
/bin/bash -lc "sed -n '620,980p' /tmp/codex-hcn7ma47/src/LiquidityPool.sol" in /tmp/codex-hcn7ma47 succeeded in 7ms:
        if (totalPooledEther == 0) {
            return _depositAmount;
        }
        return (_depositAmount * eETH.totalShares()) / totalPooledEther;
    }

    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

    //--------------------------------------------------------------------------------------
    //------------------------------------  GETTERS  ---------------------------------------
    //--------------------------------------------------------------------------------------

    /// @notice Selects a source of funds to be used for the deposits
    /// @dev The LP has two ways of accumulating funds, through eEth staking and through the ether fan page (membership manager).
    ///         We want to manipulate which funds we use per deposit. Example, if someone is making 2 deposits, we want to select where the 60 ETH
    ///         should come from. The funds will all be held in the LP but we are storing how many validators are spun up per source on the contract.
    ///         We simply check which of the sources is below their target allocation and allocate the deposits to it.
    /// @return The chosen source of funds (EETH or ETHER_FAN)
    function allocateSourceOfFunds() public view returns (SourceOfFunds) {
        uint256 validatorRatio = (fundStatistics[SourceOfFunds.EETH].numberOfValidators * 10_000) / fundStatistics[SourceOfFunds.ETHER_FAN].numberOfValidators;




thinking
**Analyzing contracts for vulnerabilities**

I'm examining AuctionManager and LiquidityPool for potential issues like reentrancy, access control, division by zero, and asset accounting quirks. I'll pay close attention to external calls, permission checks, numeric edge cases, and trust assumptions to highlight possible risks or misconfigurations.
thinking
**Distinguishing implementation focus and key risks**

I'm confirming that the address corresponds to LiquidityPool as the implementation to audit, setting aside AuctionManager unless otherwise specified. My focus is on identifying critical issues like missing access control in batchDepositAsBnftHolder allowing unauthorized deposits, DoS risks from division by zero, potential oracle manipulation via timestamp-based scheduling, and pool accounting nuances around ETH deposits and withdrawals.
thinking
**Analyzing fund refund and access control nuances**

I'm untangling how user refunds for failed validators affect pool accounting and spotting potential misalignments in totalValueInLp and totalValueOutOfLp updates. I’m also reviewing access controls in deposit cancellation and batch deposit functions, noting missing checks that could let unauthorized actors interact improperly, plus subtle DoS and state sync concerns tied to ETH handling and whitelist logic.
thinking
**Summarizing primary BNFT deposit vulnerability**

I'm narrowing down to the main issue in LiquidityPool: a missing registration check that lets unregistered addresses call batchDepositAsBnftHolder and misuse BNFT slots, risking LP funds. I plan to reference specific source lines to clarify this critical access control flaw.
exec
/bin/bash -lc "nl -ba /tmp/codex-hcn7ma47/src/LiquidityPool.sol | sed -n '250,340p'" in /tmp/codex-hcn7ma47 succeeded in 8ms:
   250	
   251	    /// @notice Allows a BNFT player to deposit their 2 ETH and pair with 30 ETH from the LP
   252	    /// @dev This function has multiple dependencies that need to be followed before this function will succeed. 
   253	    /// @param _candidateBidIds validator IDs that have been matched with the BNFT holder on the FE
   254	    /// @param _numberOfValidators how many validators the user wants to spin up. This can be less than the candidateBidIds length. 
   255	    ///         we may have more Ids sent in than needed to spin up incase some ids fail.
   256	    /// @return Array of bids that were successfully processed.
   257	    function batchDepositAsBnftHolder(uint256[] calldata _candidateBidIds, uint256 _numberOfValidators) external payable whenNotPaused returns (uint256[] memory){
   258	        //Checking which indexes form the schedule for the current scheduling period.
   259	        (uint256 firstIndex, uint128 lastIndex) = dutyForWeek();
   260	        uint32 index = bnftHoldersIndexes[msg.sender].index;
   261	
   262	        //Need to make sure the BNFT player is assigned for the current period
   263	        //See function for details
   264	        require(isAssigned(firstIndex, lastIndex, index), "Not assigned");
   265	        require(bnftHolders[index].timestamp < uint32(getCurrentSchedulingStartTimestamp()), "Already deposited");
   266	        require(msg.value == _numberOfValidators * 2 ether, "Deposit 2 ETH per validator");
   267	        require(totalValueInLp + msg.value >= 32 ether * _numberOfValidators, "Not enough balance");
   268	
   269	        //BNFT players are eligible to spin up anything up to the max amount of validators allowed (maxValidatorsPerOwner),

thinking
**Identifying potential issues in code**
exec
/bin/bash -lc "nl -ba /tmp/codex-hcn7ma47/src/LiquidityPool.sol | sed -n '640,720p'" in /tmp/codex-hcn7ma47 succeeded in 15ms:
   640	        uint256 weightRatio = (fundStatistics[SourceOfFunds.EETH].targetWeight * 10_000) / fundStatistics[SourceOfFunds.ETHER_FAN].targetWeight;
   641	
   642	        return validatorRatio > weightRatio ? SourceOfFunds.ETHER_FAN : SourceOfFunds.EETH;
   643	    }
   644	
   645	    /// @notice Fetching the starting timestamp of the current scheduling period
   646	    /// @return The timestamp of the begging of the current scheduling period
   647	    function getCurrentSchedulingStartTimestamp() public view returns (uint256) {
   648	        return block.timestamp - (block.timestamp % schedulingPeriodInSeconds);
   649	    }
   650	
   651	    /// @notice Checks whether the BNFT player with _index is assigned
   652	    /// @dev Because we allow a sliding window type selection, we use strict conditions to check whether the provided index is 
   653	    ///         inside the first and last index.
   654	    /// @param _firstIndex The index of the first selected BNFT holder
   655	    /// @param _lastIndex The index of the last selected BNFT holder
   656	    /// @param _index The index of the BNFT we are checking
   657	    /// @return Bool value if the BNFT player is assigned or not
   658	    function isAssigned(uint256 _firstIndex, uint128 _lastIndex, uint256 _index) public view returns (bool) {
   659	        if(_lastIndex < _firstIndex) {

thinking
**Summarizing audit findings and assumptions**

I'm gathering and organizing audit findings on LiquidityPool, focusing on potential high-severity access control flaws around unregistered BNFT holders, and low-severity division-by-zero DoS risks in fund allocation. I'm also clarifying assumptions on contract addresses and preparing a concise JSON report with categorized findings and evidence.
codex
{"no_findings":false,"summary":"Found 2 issues in the LiquidityPool implementation: missing BNFT-holder authorization and a division-by-zero DoS in fund source allocation.","findings":[{"severity":"high","category":"access_control","title":"Unregistered address can execute BNFT deposit flow","exploit_scenario":"Any address with no BNFT registration has `bnftHoldersIndexes[msg.sender].index == 0` by default. If the current scheduling window includes index 0, that address can call `batchDepositAsBnftHolder` and pass the assignment/timestamp checks, forcing the pool to supply 30 ETH per validator while only posting 2 ETH, and can lock out the legitimate holder at index 0 by updating its timestamp.","recommendation":"Require `bnftHoldersIndexes[msg.sender].registered` and/or check that `bnftHolders[index].holder == msg.sender` before allowing BNFT deposits.","evidence":"`batchDepositAsBnftHolder` uses `bnftHoldersIndexes[msg.sender].index` and performs assignment/timestamp checks but never validates `registered` or holder identity.","locations":["src/LiquidityPool.sol:257","src/LiquidityPool.sol:260","src/LiquidityPool.sol:264","src/LiquidityPool.sol:265"]},{"severity":"low","category":"math","title":"Division-by-zero DoS in fund source allocation","exploit_scenario":"If `fundStatistics[ETHER_FAN].numberOfValidators` or `fundStatistics[ETHER_FAN].targetWeight` is zero (initially or after decreases), `allocateSourceOfFunds()` reverts. This blocks `batchDepositAsBnftHolder`, halting BNFT deposits until an admin fixes the values.","recommendation":"Guard against zero denominators (e.g., if either is zero, default to the other source or require initialization before use).","evidence":"`allocateSourceOfFunds()` divides by `fundStatistics[SourceOfFunds.ETHER_FAN].numberOfValidators` and `.targetWeight` with no zero checks.","locations":["src/LiquidityPool.sol:639","src/LiquidityPool.sol:640"]}]}
tokens used
75,198
Metadata
{
    "codex": {
        "ran_at": "2026-01-11T05:22:50.709297+00:00",
        "returncode": 0
    },
    "project": {
        "mode": "source",
        "file_count": 46,
        "import_count": 52,
        "first_party_contracts_count": 15
    },
    "summary": "Found 2 issues in the LiquidityPool implementation: missing BNFT-holder authorization and a division-by-zero DoS in fund source allocation.",
    "raw_final": {
        "summary": "Found 2 issues in the LiquidityPool implementation: missing BNFT-holder authorization and a division-by-zero DoS in fund source allocation.",
        "findings": [
            {
                "title": "Unregistered address can execute BNFT deposit flow",
                "category": "access_control",
                "evidence": "`batchDepositAsBnftHolder` uses `bnftHoldersIndexes[msg.sender].index` and performs assignment/timestamp checks but never validates `registered` or holder identity.",
                "severity": "high",
                "locations": [
                    "src/LiquidityPool.sol:257",
                    "src/LiquidityPool.sol:260",
                    "src/LiquidityPool.sol:264",
                    "src/LiquidityPool.sol:265"
                ],
                "recommendation": "Require `bnftHoldersIndexes[msg.sender].registered` and/or check that `bnftHolders[index].holder == msg.sender` before allowing BNFT deposits.",
                "exploit_scenario": "Any address with no BNFT registration has `bnftHoldersIndexes[msg.sender].index == 0` by default. If the current scheduling window includes index 0, that address can call `batchDepositAsBnftHolder` and pass the assignment/timestamp checks, forcing the pool to supply 30 ETH per validator while only posting 2 ETH, and can lock out the legitimate holder at index 0 by updating its timestamp."
            },
            {
                "title": "Division-by-zero DoS in fund source allocation",
                "category": "math",
                "evidence": "`allocateSourceOfFunds()` divides by `fundStatistics[SourceOfFunds.ETHER_FAN].numberOfValidators` and `.targetWeight` with no zero checks.",
                "severity": "low",
                "locations": [
                    "src/LiquidityPool.sol:639",
                    "src/LiquidityPool.sol:640"
                ],
                "recommendation": "Guard against zero denominators (e.g., if either is zero, default to the other source or require initialization before use).",
                "exploit_scenario": "If `fundStatistics[ETHER_FAN].numberOfValidators` or `fundStatistics[ETHER_FAN].targetWeight` is zero (initially or after decreases), `allocateSourceOfFunds()` reverts. This blocks `batchDepositAsBnftHolder`, halting BNFT deposits until an admin fixes the values."
            }
        ],
        "no_findings": false
    },
    "no_findings": false,
    "proxy_context": {
        "proxy_run_id": "019bab3b-aaee-7307-8a62-f0c903f3c8f8",
        "proxy_address": "0x00c452affee3a17d9cecc1bcd2b8d5c7635c4cb9",
        "implementation_run_id": "019bab3c-a6af-702e-81df-bc4bc14e96dc",
        "implementation_address": "0x68fe80c6e97e0c8613e2fed344358c6635ba5366"
    },
    "schema_version": 1
}

Opcode Heuristics

tool.cast_disasm
Delegatecall
2
Selfdestruct
0
CREATE2
0
CALL-family (heavy)
0
EXT*/BALANCE
2
Total opcodes
409
Flags
delegatecall_present
View cast disassembly output
00000000: PUSH1 0x80
00000002: PUSH1 0x40
00000004: MSTORE
00000005: CALLDATASIZE
00000006: PUSH2 0x0013
00000009: JUMPI
0000000a: PUSH2 0x0011
0000000d: PUSH2 0x0017
00000010: JUMP
00000011: JUMPDEST
00000012: STOP
00000013: JUMPDEST
00000014: PUSH2 0x0011
00000017: JUMPDEST
00000018: PUSH2 0x0027
0000001b: PUSH2 0x0022
0000001e: PUSH2 0x0074
00000021: JUMP
00000022: JUMPDEST
00000023: PUSH2 0x00b9
00000026: JUMP
00000027: JUMPDEST
00000028: JUMP
00000029: JUMPDEST
0000002a: PUSH1 0x60
0000002c: PUSH2 0x004e
0000002f: DUP4
00000030: DUP4
00000031: PUSH1 0x40
00000033: MLOAD
00000034: DUP1
00000035: PUSH1 0x60
00000037: ADD
00000038: PUSH1 0x40
0000003a: MSTORE
0000003b: DUP1
0000003c: PUSH1 0x27
0000003e: DUP2
0000003f: MSTORE
00000040: PUSH1 0x20
00000042: ADD
00000043: PUSH2 0x02f1
00000046: PUSH1 0x27
00000048: SWAP2
00000049: CODECOPY
0000004a: PUSH2 0x00dd
0000004d: JUMP
0000004e: JUMPDEST
0000004f: SWAP4
00000050: SWAP3
00000051: POP
00000052: POP
00000053: POP
00000054: JUMP
00000055: JUMPDEST
00000056: PUSH20 0xffffffffffffffffffffffffffffffffffffffff
0000006b: AND
0000006c: EXTCODESIZE
0000006d: ISZERO
0000006e: ISZERO
0000006f: SWAP1
00000070: JUMP
00000071: JUMPDEST
00000072: SWAP1
00000073: JUMP
00000074: JUMPDEST
00000075: PUSH1 0x00
00000077: PUSH2 0x00b4
0000007a: PUSH32 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
0000009b: SLOAD
0000009c: PUSH20 0xffffffffffffffffffffffffffffffffffffffff
000000b1: AND
000000b2: SWAP1
000000b3: JUMP
000000b4: JUMPDEST
000000b5: SWAP1
000000b6: POP
000000b7: SWAP1
000000b8: JUMP
000000b9: JUMPDEST
000000ba: CALLDATASIZE
000000bb: PUSH1 0x00
000000bd: DUP1
000000be: CALLDATACOPY
000000bf: PUSH1 0x00
000000c1: DUP1
000000c2: CALLDATASIZE
000000c3: PUSH1 0x00
000000c5: DUP5
000000c6: GAS
000000c7: DELEGATECALL
000000c8: RETURNDATASIZE
000000c9: PUSH1 0x00
000000cb: DUP1
000000cc: RETURNDATACOPY
000000cd: DUP1
000000ce: DUP1
000000cf: ISZERO
000000d0: PUSH2 0x00d8
000000d3: JUMPI
000000d4: RETURNDATASIZE
000000d5: PUSH1 0x00
000000d7: RETURN
000000d8: JUMPDEST
000000d9: RETURNDATASIZE
000000da: PUSH1 0x00
000000dc: REVERT
000000dd: JUMPDEST
000000de: PUSH1 0x60
000000e0: PUSH1 0x00
000000e2: DUP1
000000e3: DUP6
000000e4: PUSH20 0xffffffffffffffffffffffffffffffffffffffff
000000f9: AND
000000fa: DUP6
000000fb: PUSH1 0x40
000000fd: MLOAD
000000fe: PUSH2 0x0107
00000101: SWAP2
00000102: SWAP1
00000103: PUSH2 0x0283
00000106: JUMP
00000107: JUMPDEST
00000108: PUSH1 0x00
0000010a: PUSH1 0x40
0000010c: MLOAD
0000010d: DUP1
0000010e: DUP4
0000010f: SUB
00000110: DUP2
00000111: DUP6
00000112: GAS
00000113: DELEGATECALL
00000114: SWAP2
00000115: POP
00000116: POP
00000117: RETURNDATASIZE
00000118: DUP1
00000119: PUSH1 0x00
0000011b: DUP2
0000011c: EQ
0000011d: PUSH2 0x0142
00000120: JUMPI
00000121: PUSH1 0x40
00000123: MLOAD
00000124: SWAP2
00000125: POP
00000126: PUSH1 0x1f
00000128: NOT
00000129: PUSH1 0x3f
0000012b: RETURNDATASIZE
0000012c: ADD
0000012d: AND
0000012e: DUP3
0000012f: ADD
00000130: PUSH1 0x40
00000132: MSTORE
00000133: RETURNDATASIZE
00000134: DUP3
00000135: MSTORE
00000136: RETURNDATASIZE
00000137: PUSH1 0x00
00000139: PUSH1 0x20
0000013b: DUP5
0000013c: ADD
0000013d: RETURNDATACOPY
0000013e: PUSH2 0x0147
00000141: JUMP
00000142: JUMPDEST
00000143: PUSH1 0x60
00000145: SWAP2
00000146: POP
00000147: JUMPDEST
00000148: POP
00000149: SWAP2
0000014a: POP
0000014b: SWAP2
0000014c: POP
0000014d: PUSH2 0x0158
00000150: DUP7
00000151: DUP4
00000152: DUP4
00000153: DUP8
00000154: PUSH2 0x0162
00000157: JUMP
00000158: JUMPDEST
00000159: SWAP7
0000015a: SWAP6
0000015b: POP
0000015c: POP
0000015d: POP
0000015e: POP
0000015f: POP
00000160: POP
00000161: JUMP
00000162: JUMPDEST
00000163: PUSH1 0x60
00000165: DUP4
00000166: ISZERO
00000167: PUSH2 0x01fd
0000016a: JUMPI
0000016b: DUP3
0000016c: MLOAD
0000016d: PUSH1 0x00
0000016f: SUB
00000170: PUSH2 0x01f6
00000173: JUMPI
00000174: PUSH20 0xffffffffffffffffffffffffffffffffffffffff
00000189: DUP6
0000018a: AND
0000018b: EXTCODESIZE
0000018c: PUSH2 0x01f6
0000018f: JUMPI
00000190: PUSH1 0x40
00000192: MLOAD
00000193: PUSH32 0x08c379a000000000000000000000000000000000000000000000000000000000
000001b4: DUP2
000001b5: MSTORE
000001b6: PUSH1 0x20
000001b8: PUSH1 0x04
000001ba: DUP3
000001bb: ADD
000001bc: MSTORE
000001bd: PUSH1 0x1d
000001bf: PUSH1 0x24
000001c1: DUP3
000001c2: ADD
000001c3: MSTORE
000001c4: PUSH32 0x416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000
000001e5: PUSH1 0x44
000001e7: DUP3
000001e8: ADD
000001e9: MSTORE
000001ea: PUSH1 0x64
000001ec: ADD
000001ed: JUMPDEST
000001ee: PUSH1 0x40
000001f0: MLOAD
000001f1: DUP1
000001f2: SWAP2
000001f3: SUB
000001f4: SWAP1
000001f5: REVERT
000001f6: JUMPDEST
000001f7: POP
000001f8: DUP2
000001f9: PUSH2 0x0207
000001fc: JUMP
000001fd: JUMPDEST
000001fe: PUSH2 0x0207
00000201: DUP4
00000202: DUP4
00000203: PUSH2 0x020f
00000206: JUMP
00000207: JUMPDEST
00000208: SWAP5
00000209: SWAP4
0000020a: POP
0000020b: POP
0000020c: POP
0000020d: POP
0000020e: JUMP
0000020f: JUMPDEST
00000210: DUP2
00000211: MLOAD
00000212: ISZERO
00000213: PUSH2 0x021f
00000216: JUMPI
00000217: DUP2
00000218: MLOAD
00000219: DUP1
0000021a: DUP4
0000021b: PUSH1 0x20
0000021d: ADD
0000021e: REVERT
0000021f: JUMPDEST
00000220: DUP1
00000221: PUSH1 0x40
00000223: MLOAD
00000224: PUSH32 0x08c379a000000000000000000000000000000000000000000000000000000000
00000245: DUP2
00000246: MSTORE
00000247: PUSH1 0x04
00000249: ADD
0000024a: PUSH2 0x01ed
0000024d: SWAP2
0000024e: SWAP1
0000024f: PUSH2 0x029f
00000252: JUMP
00000253: JUMPDEST
00000254: PUSH1 0x00
00000256: JUMPDEST
00000257: DUP4
00000258: DUP2
00000259: LT
0000025a: ISZERO
0000025b: PUSH2 0x026e
0000025e: JUMPI
0000025f: DUP2
00000260: DUP2
00000261: ADD
00000262: MLOAD
00000263: DUP4
00000264: DUP3
00000265: ADD
00000266: MSTORE
00000267: PUSH1 0x20
00000269: ADD
0000026a: PUSH2 0x0256
0000026d: JUMP
0000026e: JUMPDEST
0000026f: DUP4
00000270: DUP2
00000271: GT
00000272: ISZERO
00000273: PUSH2 0x027d
00000276: JUMPI
00000277: PUSH1 0x00
00000279: DUP5
0000027a: DUP5
0000027b: ADD
0000027c: MSTORE
0000027d: JUMPDEST
0000027e: POP
0000027f: POP
00000280: POP
00000281: POP
00000282: JUMP
00000283: JUMPDEST
00000284: PUSH1 0x00
00000286: DUP3
00000287: MLOAD
00000288: PUSH2 0x0295
0000028b: DUP2
0000028c: DUP5
0000028d: PUSH1 0x20
0000028f: DUP8
00000290: ADD
00000291: PUSH2 0x0253
00000294: JUMP
00000295: JUMPDEST
00000296: SWAP2
00000297: SWAP1
00000298: SWAP2
00000299: ADD
0000029a: SWAP3
0000029b: SWAP2
0000029c: POP
0000029d: POP
0000029e: JUMP
0000029f: JUMPDEST
000002a0: PUSH1 0x20
000002a2: DUP2
000002a3: MSTORE
000002a4: PUSH1 0x00
000002a6: DUP3
000002a7: MLOAD
000002a8: DUP1
000002a9: PUSH1 0x20
000002ab: DUP5
000002ac: ADD
000002ad: MSTORE
000002ae: PUSH2 0x02be
000002b1: DUP2
000002b2: PUSH1 0x40
000002b4: DUP6
000002b5: ADD
000002b6: PUSH1 0x20
000002b8: DUP8
000002b9: ADD
000002ba: PUSH2 0x0253
000002bd: JUMP
000002be: JUMPDEST
000002bf: PUSH1 0x1f
000002c1: ADD
000002c2: PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0
000002e3: AND
000002e4: SWAP2
000002e5: SWAP1
000002e6: SWAP2
000002e7: ADD
000002e8: PUSH1 0x40
000002ea: ADD
000002eb: SWAP3
000002ec: SWAP2
000002ed: POP
000002ee: POP
000002ef: JUMP
000002f0: INVALID
000002f1: COINBASE
000002f2: PUSH5 0x6472657373
000002f8: GASPRICE
000002f9: KECCAK256
000002fa: PUSH13 0x6f772d6c6576656c2064656c65
00000308: PUSH8 0x6174652063616c6c
00000311: KECCAK256
00000312: PUSH7 0x61696c6564a264
0000031a: PUSH10 0x706673582212209a37d8
00000325: DIV
00000326: UNKNOWN(0xD4)
00000327: UNKNOWN(0x0F)
00000328: SWAP14
00000329: SGT
0000032a: DUP13
0000032b: UNKNOWN(0x0F)
0000032c: SUB
0000032d: UNKNOWN(0xCA)
0000032e: PUSH29 0xd03e999e9f9e1d34d86db16c353d9b56c900ab64736f6c634300080d00
0000034c: CALLER

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: 0x6eab753f50fd2457a5e7aa2bb7a16fdcf4224a9455246a9c77078a64f73e9b70
call_targets: 0x68fe80c6e97e0c8613e2fed344358c6635ba5366
target_varies: no
classification: constant_target
validation_json
{
    "sink": "DELEGATECALL",
    "errors": 0,
    "status": "sink_reached",
    "attempts": 1,
    "trace_id": "0x6eab753f50fd2457a5e7aa2bb7a16fdcf4224a9455246a9c77078a64f73e9b70",
    "confirmed": false,
    "trace_mode": "callTracer",
    "call_targets": [
        "0x68fe80c6e97e0c8613e2fed344358c6635ba5366"
    ],
    "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": 199,
            "opcode": "DELEGATECALL",
            "returndata_checked": null,
            "value_expression_category": "n/a",
            "target_expression_category": "computed"
        },
        {
            "pc": 275,
            "opcode": "DELEGATECALL",
            "returndata_checked": null,
            "value_expression_category": "n/a",
            "target_expression_category": "computed"
        }
    ],
    "guards_detected": [],
    "delegatecall_count": 2,
    "untrusted_delegatecall_count": 2
}
high access_control codex Unregistered address can execute BNFT deposit flow 85 no
view
evidence_json
{
    "evidence": "`batchDepositAsBnftHolder` uses `bnftHoldersIndexes[msg.sender].index` and performs assignment/timestamp checks but never validates `registered` or holder identity.",
    "locations": [
        "src/LiquidityPool.sol:257",
        "src/LiquidityPool.sol:260",
        "src/LiquidityPool.sol:264",
        "src/LiquidityPool.sol:265"
    ],
    "recommendation": "Require `bnftHoldersIndexes[msg.sender].registered` and/or check that `bnftHolders[index].holder == msg.sender` before allowing BNFT deposits.",
    "exploit_scenario": "Any address with no BNFT registration has `bnftHoldersIndexes[msg.sender].index == 0` by default. If the current scheduling window includes index 0, that address can call `batchDepositAsBnftHolder` and pass the assignment/timestamp checks, forcing the pool to supply 30 ETH per validator while only posting 2 ETH, and can lock out the legitimate holder at index 0 by updating its timestamp."
}
medium upgradeability cast DELEGATECALL present 80 no
view
evidence_json
{
    "notes": "delegatecall enables execution in caller context; common in proxies/upgradeable patterns",
    "delegatecall_count": 2
}
low math codex Division-by-zero DoS in fund source allocation 65 no
view
evidence_json
{
    "evidence": "`allocateSourceOfFunds()` divides by `fundStatistics[SourceOfFunds.ETHER_FAN].numberOfValidators` and `.targetWeight` with no zero checks.",
    "locations": [
        "src/LiquidityPool.sol:639",
        "src/LiquidityPool.sol:640"
    ],
    "recommendation": "Guard against zero denominators (e.g., if either is zero, default to the other source or require initialization before use).",
    "exploit_scenario": "If `fundStatistics[ETHER_FAN].numberOfValidators` or `fundStatistics[ETHER_FAN].targetWeight` is zero (initially or after decreases), `allocateSourceOfFunds()` reverts. This blocks `batchDepositAsBnftHolder`, halting BNFT deposits until an admin fixes the values."
}