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

Analysis Run

4b6d808e-caa6-4fa9-9cd9-5efbcbdeeda0

complete
Chain ID
1
Address
Block Number
Created
Tue, Dec 30, 2025 8:51 PM
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 direct contract
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
30764 report.consolidate complete 1 / 1 3 months ago
75580 artifact.fetch complete 1 / 3 1 week ago
75581 tool.cast_disasm complete 1 / 3 1 week ago
75582 analysis.bundle complete 1 / 3 1 week ago
75583 capability.graph complete 1 / 3 1 week ago
75584 detector.run complete 1 / 3 1 week ago
75585 validation.fork complete 1 / 3 1 week ago

Artifact

Runtime bytecode: available
Creation TX:
Deployer:
Proxy: no
Implementation:
Verified Source: yes

Slither

tool.slither

No slither job recorded yet.

Codex

report.consolidate
Job Status
complete
Attempts
1 / 1
Findings
2
No findings?
no
Summary
Found 2 access-control issues that allow anyone to initialize the distributor or set the token, enabling takeover or permanent distribution breakage.
View stderr
[codex:984899] OpenAI Codex v0.77.0 (research preview)
[codex:984899] --------
[codex:984899] workdir: /tmp/codex-multi-y_4w6g6_
[codex:984899] model: gpt-5.2-codex
[codex:984899] provider: openai
[codex:984899] approval: never
[codex:984899] sandbox: read-only
[codex:984899] reasoning effort: xhigh
[codex:984899] reasoning summaries: auto
[codex:984899] session id: 019b7106-1084-7f51-b87c-e4322c84fcb1
[codex:984899] --------
[codex:984899] user
[codex:984899] You are a security auditor. Audit this Solidity codebase for exploitable vulnerabilities.
[codex:984899] Focus on: reentrancy, access control, upgradeability/proxy issues, signature validation, oracle manipulation,
[codex:984899] accounting/invariant breaks, external call patterns, unchecked return values, and dangerous delegatecall.
[codex:984899] 
[codex:984899] Scope rules:
[codex:984899] - You MUST fully read `AUDIT_BUNDLE.txt` before concluding. It contains the complete contents of all first-party `contracts/**/*.sol` files.
[codex:984899] - Treat `lib/` and `@openzeppelin/` as third-party dependencies; skip them unless there is a concrete misuse angle in first-party code.
[codex:984899] 
[codex:984899] Output MUST be valid JSON matching the provided schema.
[codex:984899] - If you find nothing meaningful: set no_findings=true and findings=[].
[codex:984899] - Otherwise: set no_findings=false and include findings with severity/category/title and file/line locations when possible.
[codex:984899] 
[codex:984899] Output constraints (IMPORTANT):
[codex:984899] - severity MUST be exactly one of: info, low, medium, high, critical (lowercase).
[codex:984899] - category MUST be exactly one of: access_control, reentrancy, math, oracle, upgradeability, auth, other.
[codex:984899] mcp startup: no servers
[codex:984899] 
[codex:984899] thinking
[codex:984899] **Preparing to investigate audit request**
[codex:984899] exec
[codex:984899] /bin/bash -lc ls in /tmp/codex-multi-y_4w6g6_ succeeded in 5ms:
[codex:984899] AUDIT_BUNDLE.txt
[codex:984899] codex-output-schema.json
[codex:984899] contracts
[codex:984899] 
[codex:984899] thinking
[codex:984899] **Reading AUDIT_BUNDLE.txt file**
[codex:984899] exec
[codex:984899] /bin/bash -lc 'cat AUDIT_BUNDLE.txt' in /tmp/codex-multi-y_4w6g6_ succeeded in 5ms:
[codex:984899] This file concatenates the full contents of first-party Solidity sources for auditing.
[codex:984899] 
[codex:984899] ================================================================================
[codex:984899] FILE: contracts/@openzeppelin/contracts/token/ERC20/IERC20.sol
[codex:984899] ================================================================================
[codex:984899] // SPDX-License-Identifier: MIT
[codex:984899] 
[codex:984899] pragma solidity ^0.8.0;
[codex:984899] 
[codex:984899] /**
[codex:984899]  * @dev Interface of the ERC20 standard as defined in the EIP.
[codex:984899]  */
[codex:984899] interface IERC20 {
[codex:984899]     /**
[codex:984899]      * @dev Returns the amount of tokens in existence.
[codex:984899]      */
[codex:984899]     function totalSupply() external view returns (uint256);
[codex:984899] 
[codex:984899]     /**
[codex:984899]      * @dev Returns the amount of tokens owned by `account`.
[codex:984899] 
[codex:984899] thinking
[codex:984899] **Analyzing DistributorV2 for vulnerabilities**
[codex:984899] 
[codex:984899] I’m carefully auditing DistributorV2 focusing on first-party code only, checking access control, initialization, reentrancy, and external calls. The main concerns are unprotected initializer and open setToken allowing unauthorized token or address setting, which could lead to high-risk hijacking or medium-risk denial-of-service/griefing. Reentrancy risks appear limited due to state updates before external calls. I’m noting that some expected sanity checks (like zero address validation) and use of SafeERC20 wrappers are missing but are lower risk. Next, I want to correlate these findings with precise file and line locations.
[codex:984899] exec
[codex:984899] /bin/bash -lc 'nl -ba contracts/contracts/distributor/DistributorV2.sol' in /tmp/codex-multi-y_4w6g6_ succeeded in 5ms:
[codex:984899]      1	// SPDX-License-Identifier: UNLICENSED
[codex:984899]      2	pragma solidity ^0.8.19;
[codex:984899]      3	
[codex:984899]      4	/*
[codex:984899]      5	The DistributorV2 is a clone contract version of the Uniswap Distributor. 
[codex:984899]      6	It expects a merkleRoot and a manifest, which is a public IPFS CID where to find
[codex:984899]      7	the full merkle tree.
[codex:984899]      8	*/
[codex:984899]      9	
[codex:984899]     10	import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
[codex:984899]     11	import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
[codex:984899]     12	import "../initializable/Initializable.sol";
[codex:984899]     13	import "../interfaces/IDistributorV2.sol";
[codex:984899]     14	
[codex:984899]     15	contract DistributorV2 is Initializable, IDistributorV2 {
[codex:984899]     16	    address public override token;
[codex:984899]     17	    bytes32 public override merkleRoot;
[codex:984899]     18	    string public manifest;
[codex:984899]     19	    // This is a packed array of booleans.
[codex:984899]     20	    mapping(uint256 => uint256) private claimedBitMap;
[codex:984899] 
[codex:984899] thinking
[codex:984899] **Preparing JSON report for findings**
[codex:984899] codex
[codex:984899] {"no_findings":false,"summary":"Found 2 access-control issues that allow anyone to initialize the distributor or set the token, enabling takeover or permanent distribution breakage.","findings":[{"severity":"high","category":"access_control","title":"Unprotected initializer lets any caller take ownership of distribution parameters","exploit_scenario":"If a clone is deployed without calling initialize in the same transaction, an attacker can call initialize first, set researchPortfolioGnosis/returnTokenAddress to themselves, then wait for the funder to transfer tokens and drain them via withdraw/withdrawAmount, or set a malicious merkleRoot to control claims.","recommendation":"Restrict initialize to a trusted factory/owner (e.g., onlyFactory/onlyOwner) or enforce atomic initialization in the factory deployment; alternatively set critical params in the constructor of the implementation and make clones immutable.","evidence":"initialize is public and only guarded by initializer; it sets merkleRoot, returnTokenAddress, and researchPortfolioGnosis with no caller checks.","locations":["contracts/contracts/distributor/DistributorV2.sol:44","contracts/contracts/distributor/DistributorV2.sol:71"]},{"severity":"medium","category":"access_control","title":"setToken is unrestricted and can be front‑run to brick or redirect the distribution","exploit_scenario":"After deployment/initialization but before the intended operator sets the token, any address can call setToken once. An attacker can set a bogus token address, permanently preventing the correct token from being set and causing claims/withdrawals to fail or distribute the wrong asset.","recommendation":"Add access control to setToken (e.g., onlyOwner/researchPortfolioGnosis) or set the token during initialization so there is no public window to front‑run.","evidence":"setToken is external with no access control and can be called by any address as long as token is unset.","locations":["contracts/contracts/distributor/DistributorV2.sol:83"]}]}
[codex:984899] tokens used
[codex:984899] 19,495
[codex:984899] {"no_findings":false,"summary":"Found 2 access-control issues that allow anyone to initialize the distributor or set the token, enabling takeover or permanent distribution breakage.","findings":[{"severity":"high","category":"access_control","title":"Unprotected initializer lets any caller take ownership of distribution parameters","exploit_scenario":"If a clone is deployed without calling initialize in the same transaction, an attacker can call initialize first, set researchPortfolioGnosis/returnTokenAddress to themselves, then wait for the funder to transfer tokens and drain them via withdraw/withdrawAmount, or set a malicious merkleRoot to control claims.","recommendation":"Restrict initialize to a trusted factory/owner (e.g., onlyFactory/onlyOwner) or enforce atomic initialization in the factory deployment; alternatively set critical params in the constructor of the implementation and make clones immutable.","evidence":"initialize is public and only guarded by initializer; it sets merkleRoot, returnTokenAddress, and researchPortfolioGnosis with no caller checks.","locations":["contracts/contracts/distributor/DistributorV2.sol:44","contracts/contracts/distributor/DistributorV2.sol:71"]},{"severity":"medium","category":"access_control","title":"setToken is unrestricted and can be front‑run to brick or redirect the distribution","exploit_scenario":"After deployment/initialization but before the intended operator sets the token, any address can call setToken once. An attacker can set a bogus token address, permanently preventing the correct token from being set and causing claims/withdrawals to fail or distribute the wrong asset.","recommendation":"Add access control to setToken (e.g., onlyOwner/researchPortfolioGnosis) or set the token during initialization so there is no public window to front‑run.","evidence":"setToken is external with no access control and can be called by any address as long as token is unset.","locations":["contracts/contracts/distributor/DistributorV2.sol:83"]}]}
Metadata
{
    "codex": {
        "ran_at": "2025-12-30T20:51:17.512038+00:00",
        "returncode": 0
    },
    "summary": "Found 2 access-control issues that allow anyone to initialize the distributor or set the token, enabling takeover or permanent distribution breakage.",
    "raw_final": {
        "summary": "Found 2 access-control issues that allow anyone to initialize the distributor or set the token, enabling takeover or permanent distribution breakage.",
        "findings": [
            {
                "title": "Unprotected initializer lets any caller take ownership of distribution parameters",
                "category": "access_control",
                "evidence": "initialize is public and only guarded by initializer; it sets merkleRoot, returnTokenAddress, and researchPortfolioGnosis with no caller checks.",
                "severity": "high",
                "locations": [
                    "contracts/contracts/distributor/DistributorV2.sol:44",
                    "contracts/contracts/distributor/DistributorV2.sol:71"
                ],
                "recommendation": "Restrict initialize to a trusted factory/owner (e.g., onlyFactory/onlyOwner) or enforce atomic initialization in the factory deployment; alternatively set critical params in the constructor of the implementation and make clones immutable.",
                "exploit_scenario": "If a clone is deployed without calling initialize in the same transaction, an attacker can call initialize first, set researchPortfolioGnosis/returnTokenAddress to themselves, then wait for the funder to transfer tokens and drain them via withdraw/withdrawAmount, or set a malicious merkleRoot to control claims."
            },
            {
                "title": "setToken is unrestricted and can be front\u2011run to brick or redirect the distribution",
                "category": "access_control",
                "evidence": "setToken is external with no access control and can be called by any address as long as token is unset.",
                "severity": "medium",
                "locations": [
                    "contracts/contracts/distributor/DistributorV2.sol:83"
                ],
                "recommendation": "Add access control to setToken (e.g., onlyOwner/researchPortfolioGnosis) or set the token during initialization so there is no public window to front\u2011run.",
                "exploit_scenario": "After deployment/initialization but before the intended operator sets the token, any address can call setToken once. An attacker can set a bogus token address, permanently preventing the correct token from being set and causing claims/withdrawals to fail or distribute the wrong asset."
            }
        ],
        "no_findings": false
    },
    "no_findings": false,
    "schema_version": 1
}

Opcode Heuristics

tool.cast_disasm
Delegatecall
1
Selfdestruct
0
CREATE2
0
CALL-family (heavy)
0
EXT*/BALANCE
0
Total opcodes
23
Flags
delegatecall_present
View cast disassembly output
00000000: RETURNDATASIZE
00000001: RETURNDATASIZE
00000002: RETURNDATASIZE
00000003: RETURNDATASIZE
00000004: CALLDATASIZE
00000005: RETURNDATASIZE
00000006: RETURNDATASIZE
00000007: CALLDATACOPY
00000008: CALLDATASIZE
00000009: RETURNDATASIZE
0000000a: PUSH20 0x3e742c01989081d30ba606856df9f92503650d81
0000001f: GAS
00000020: DELEGATECALL
00000021: RETURNDATASIZE
00000022: RETURNDATASIZE
00000023: SWAP4
00000024: DUP1
00000025: RETURNDATACOPY
00000026: PUSH1 0x2a
00000028: JUMPI
00000029: REVERT
0000002a: JUMPDEST
0000002b: RETURN

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: 0x838509aaa60aee73491666e8b72405c1e852ccd91f545cabfad3052e7b3a259c
call_targets: 0x3e742c01989081d30ba606856df9f92503650d81
target_varies: no
classification: constant_target
validation_json
{
    "sink": "DELEGATECALL",
    "errors": 0,
    "status": "sink_reached",
    "attempts": 1,
    "trace_id": "0x838509aaa60aee73491666e8b72405c1e852ccd91f545cabfad3052e7b3a259c",
    "confirmed": false,
    "trace_mode": "callTracer",
    "call_targets": [
        "0x3e742c01989081d30ba606856df9f92503650d81"
    ],
    "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": 32,
            "opcode": "DELEGATECALL",
            "returndata_checked": null,
            "value_expression_category": "n/a",
            "target_expression_category": "computed"
        }
    ],
    "guards_detected": [],
    "delegatecall_count": 1,
    "untrusted_delegatecall_count": 1
}
high access_control codex Unprotected initializer lets any caller take ownership of distribution parameters 85 no
view
evidence_json
{
    "evidence": "initialize is public and only guarded by initializer; it sets merkleRoot, returnTokenAddress, and researchPortfolioGnosis with no caller checks.",
    "locations": [
        "contracts/contracts/distributor/DistributorV2.sol:44",
        "contracts/contracts/distributor/DistributorV2.sol:71"
    ],
    "recommendation": "Restrict initialize to a trusted factory/owner (e.g., onlyFactory/onlyOwner) or enforce atomic initialization in the factory deployment; alternatively set critical params in the constructor of the implementation and make clones immutable.",
    "exploit_scenario": "If a clone is deployed without calling initialize in the same transaction, an attacker can call initialize first, set researchPortfolioGnosis/returnTokenAddress to themselves, then wait for the funder to transfer tokens and drain them via withdraw/withdrawAmount, or set a malicious merkleRoot to control claims."
}
medium access_control codex setToken is unrestricted and can be front‑run to brick or redirect the distribution 75 no
view
evidence_json
{
    "evidence": "setToken is external with no access control and can be called by any address as long as token is unset.",
    "locations": [
        "contracts/contracts/distributor/DistributorV2.sol:83"
    ],
    "recommendation": "Add access control to setToken (e.g., onlyOwner/researchPortfolioGnosis) or set the token during initialization so there is no public window to front\u2011run.",
    "exploit_scenario": "After deployment/initialization but before the intended operator sets the token, any address can call setToken once. An attacker can set a bogus token address, permanently preventing the correct token from being set and causing claims/withdrawals to fail or distribute the wrong asset."
}
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
}