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

Analysis Run

019bb004-7b45-70c7-adb3-d1c5cb2846d2

failed
Chain ID
1
Address
Block Number
Created
Mon, Jan 12, 2026 2:24 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
39416 artifact.fetch complete 1 / 3 3 months ago
39417 proxy.resolve complete 1 / 3 3 months ago
39418 tool.cast_disasm complete 1 / 3 3 months ago
39419 analysis.bundle complete 1 / 3 3 months ago
39420 capability.graph complete 1 / 3 3 months ago
39421 detector.run complete 1 / 3 3 months ago
39422 validation.fork complete 1 / 3 3 months ago
39517 report.consolidate complete 1 / 6 3 months ago
39518 tool.slither failed 2 / 2 3 months ago

Artifact

Runtime bytecode: available
Creation TX:
Deployer:
Proxy: yes onchain_only
Verified Source: yes
Proxy evidence
{
    "method": "minimal_proxy",
    "status": "onchain_only",
    "implementation": "0x24d937143d3f5cf04c72ba112735151a8cae2262",
    "onchain_method": "minimal_proxy"
}

Slither

tool.slither
Status
failed solc version 0.2.15 is too old for supported toolchain
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 1056, in run
    solc_path, solc_meta = _prepare_solc(chosen_version, workspace)
                           ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/worker/jobs/slither_run.py", line 190, in _prepare_solc
    raise RuntimeError(f"solc version {default_version} is too old for supported toolchain")
RuntimeError: solc version 0.2.15 is too old for supported toolchain

Codex

report.consolidate
Job Status
complete
Attempts
1 / 6
Findings
3
No findings?
no
Summary
Found 3 issues: initializer lacks access control (proxy takeover risk if not initialized atomically), and `withdraw_admin_fees` is reentrancy-prone and ignores ERC20 return values.
View stdout
{"no_findings":false,"summary":"Found 3 issues: initializer lacks access control (proxy takeover risk if not initialized atomically), and `withdraw_admin_fees` is reentrancy-prone and ignores ERC20 return values.","findings":[{"severity":"high","category":"access_control","title":"Unprotected initializer allows proxy takeover if not initialized atomically","exploit_scenario":"If the proxy at 0x828b… is deployed without an atomic initializer call, any user can call `initialize` first, set `factory` to their address, choose arbitrary coins/fees/A, and then control admin-only actions (e.g., `ramp_A`, `stop_ramp_A`, fee receiver) for that pool.","recommendation":"Restrict `initialize` to an expected factory/admin address (e.g., pass factory in constructor or add an `onlyFactory` check), or ensure the proxy is initialized atomically in the deployment transaction.","evidence":"`initialize` only checks `self.fee == 0` and sets `self.factory = msg.sender` without access control.","locations":["contracts/Contract.sol:111","contracts/Contract.sol:130","contracts/Contract.sol:143"]},{"severity":"medium","category":"reentrancy","title":"`withdraw_admin_fees` can be reentered via token callback before state is updated","exploit_scenario":"A malicious pool coin can reenter `withdraw_admin_fees` during `transfer`, before `admin_balances[i]` is zeroed. Reentrancy can trigger repeated transfers of the same amount, draining pool liquidity beyond the accrued admin fees.","recommendation":"Apply the reentrancy guard to `withdraw_admin_fees` and/or set `admin_balances[i] = 0` before the external call (checks-effects-interactions).","evidence":"`withdraw_admin_fees` performs an external token transfer and only zeroes `admin_balances[i]` afterward, with no nonreentrant guard.","locations":["contracts/Contract.sol:903","contracts/Contract.sol:911","contracts/Contract.sol:919"]},{"severity":"low","category":"other","title":"Admin fee withdrawal ignores ERC20 return values and can zero fees on failed transfer","exploit_scenario":"If a non-standard ERC20 returns `false` (but does not revert) on transfer, `withdraw_admin_fees` will still clear `admin_balances[i]`, effectively losing the fees and creating accounting drift.","recommendation":"Mirror the return-value checks used elsewhere: capture `raw_call` response, and `assert convert(response, bool)` when data is returned.","evidence":"`withdraw_admin_fees` uses `raw_call` without `max_outsize` and does not validate returned data.","locations":["contracts/Contract.sol:911"]}]}
View stderr
OpenAI Codex v0.77.0 (research preview)
--------
workdir: /tmp/codex-rqsvid5u
model: gpt-5.2-codex
provider: openai
approval: never
sandbox: read-only
reasoning effort: high
reasoning summaries: auto
session id: 019bb03a-b683-7cb2-9fc7-ccbadecc6b32
--------
user
Context: The user requested the proxy at 0x828b154032950c8ff7cf8085d841723db2696056.
Audit the implementation at 0x24d937143d3f5cf04c72ba112735151a8cae2262.
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 ls in /tmp/codex-rqsvid5u succeeded in 6ms:
AUDIT_BUNDLE.txt
codex-output-schema.json
contracts
exec
/bin/bash -lc 'cat AUDIT_BUNDLE.txt' in /tmp/codex-rqsvid5u succeeded in 18ms:
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/Contract.sol
================================================================================
# @version 0.2.15
"""
@title StableSwap
@author Curve.Fi
@license Copyright (c) Curve.Fi, 2020-2021 - all rights reserved
@notice 2 coin pool implementation with no lending
@dev ERC20 support for return True/revert, return True/False, return None
     Support for positive-rebasing and fee-on-transfer tokens
"""

from vyper.interfaces import ERC20

interface Factory:
    def convert_fees() -> bool: nonpayable

thinking
**Analyzing security of proxy implementation**
exec
/bin/bash -lc 'nl -ba contracts/Contract.sol' in /tmp/codex-rqsvid5u succeeded in 8ms:
     1	# @version 0.2.15
     2	"""
     3	@title StableSwap
     4	@author Curve.Fi
     5	@license Copyright (c) Curve.Fi, 2020-2021 - all rights reserved
     6	@notice 2 coin pool implementation with no lending
     7	@dev ERC20 support for return True/revert, return True/False, return None
     8	     Support for positive-rebasing and fee-on-transfer tokens
     9	"""
    10	
    11	from vyper.interfaces import ERC20
    12	
    13	interface Factory:
    14	    def convert_fees() -> bool: nonpayable
    15	    def get_fee_receiver(_pool: address) -> address: view
    16	    def admin() -> address: view
    17	
    18	
    19	event Transfer:
    20	    sender: indexed(address)

thinking
**Compiling detailed security findings JSON**

I’m putting together a concise JSON output describing three key findings: a high-severity unprotected initializer that risks proxy takeover, a medium-severity reentrancy vulnerability in admin fee withdrawal, and a low-severity unchecked return value issue causing fee loss. Each includes category, severity, evidence with line numbers, exploit scenarios, and recommendations—aiming for a clear and actionable security summary.
codex
{"no_findings":false,"summary":"Found 3 issues: initializer lacks access control (proxy takeover risk if not initialized atomically), and `withdraw_admin_fees` is reentrancy-prone and ignores ERC20 return values.","findings":[{"severity":"high","category":"access_control","title":"Unprotected initializer allows proxy takeover if not initialized atomically","exploit_scenario":"If the proxy at 0x828b… is deployed without an atomic initializer call, any user can call `initialize` first, set `factory` to their address, choose arbitrary coins/fees/A, and then control admin-only actions (e.g., `ramp_A`, `stop_ramp_A`, fee receiver) for that pool.","recommendation":"Restrict `initialize` to an expected factory/admin address (e.g., pass factory in constructor or add an `onlyFactory` check), or ensure the proxy is initialized atomically in the deployment transaction.","evidence":"`initialize` only checks `self.fee == 0` and sets `self.factory = msg.sender` without access control.","locations":["contracts/Contract.sol:111","contracts/Contract.sol:130","contracts/Contract.sol:143"]},{"severity":"medium","category":"reentrancy","title":"`withdraw_admin_fees` can be reentered via token callback before state is updated","exploit_scenario":"A malicious pool coin can reenter `withdraw_admin_fees` during `transfer`, before `admin_balances[i]` is zeroed. Reentrancy can trigger repeated transfers of the same amount, draining pool liquidity beyond the accrued admin fees.","recommendation":"Apply the reentrancy guard to `withdraw_admin_fees` and/or set `admin_balances[i] = 0` before the external call (checks-effects-interactions).","evidence":"`withdraw_admin_fees` performs an external token transfer and only zeroes `admin_balances[i]` afterward, with no nonreentrant guard.","locations":["contracts/Contract.sol:903","contracts/Contract.sol:911","contracts/Contract.sol:919"]},{"severity":"low","category":"other","title":"Admin fee withdrawal ignores ERC20 return values and can zero fees on failed transfer","exploit_scenario":"If a non-standard ERC20 returns `false` (but does not revert) on transfer, `withdraw_admin_fees` will still clear `admin_balances[i]`, effectively losing the fees and creating accounting drift.","recommendation":"Mirror the return-value checks used elsewhere: capture `raw_call` response, and `assert convert(response, bool)` when data is returned.","evidence":"`withdraw_admin_fees` uses `raw_call` without `max_outsize` and does not validate returned data.","locations":["contracts/Contract.sol:911"]}]}
tokens used
33,896
Metadata
{
    "codex": {
        "ran_at": "2026-01-12T03:25:18.654599+00:00",
        "returncode": 0
    },
    "project": {
        "mode": "source",
        "file_count": 1,
        "import_count": 0,
        "first_party_contracts_count": 1
    },
    "summary": "Found 3 issues: initializer lacks access control (proxy takeover risk if not initialized atomically), and `withdraw_admin_fees` is reentrancy-prone and ignores ERC20 return values.",
    "raw_final": {
        "summary": "Found 3 issues: initializer lacks access control (proxy takeover risk if not initialized atomically), and `withdraw_admin_fees` is reentrancy-prone and ignores ERC20 return values.",
        "findings": [
            {
                "title": "Unprotected initializer allows proxy takeover if not initialized atomically",
                "category": "access_control",
                "evidence": "`initialize` only checks `self.fee == 0` and sets `self.factory = msg.sender` without access control.",
                "severity": "high",
                "locations": [
                    "contracts/Contract.sol:111",
                    "contracts/Contract.sol:130",
                    "contracts/Contract.sol:143"
                ],
                "recommendation": "Restrict `initialize` to an expected factory/admin address (e.g., pass factory in constructor or add an `onlyFactory` check), or ensure the proxy is initialized atomically in the deployment transaction.",
                "exploit_scenario": "If the proxy at 0x828b\u2026 is deployed without an atomic initializer call, any user can call `initialize` first, set `factory` to their address, choose arbitrary coins/fees/A, and then control admin-only actions (e.g., `ramp_A`, `stop_ramp_A`, fee receiver) for that pool."
            },
            {
                "title": "`withdraw_admin_fees` can be reentered via token callback before state is updated",
                "category": "reentrancy",
                "evidence": "`withdraw_admin_fees` performs an external token transfer and only zeroes `admin_balances[i]` afterward, with no nonreentrant guard.",
                "severity": "medium",
                "locations": [
                    "contracts/Contract.sol:903",
                    "contracts/Contract.sol:911",
                    "contracts/Contract.sol:919"
                ],
                "recommendation": "Apply the reentrancy guard to `withdraw_admin_fees` and/or set `admin_balances[i] = 0` before the external call (checks-effects-interactions).",
                "exploit_scenario": "A malicious pool coin can reenter `withdraw_admin_fees` during `transfer`, before `admin_balances[i]` is zeroed. Reentrancy can trigger repeated transfers of the same amount, draining pool liquidity beyond the accrued admin fees."
            },
            {
                "title": "Admin fee withdrawal ignores ERC20 return values and can zero fees on failed transfer",
                "category": "other",
                "evidence": "`withdraw_admin_fees` uses `raw_call` without `max_outsize` and does not validate returned data.",
                "severity": "low",
                "locations": [
                    "contracts/Contract.sol:911"
                ],
                "recommendation": "Mirror the return-value checks used elsewhere: capture `raw_call` response, and `assert convert(response, bool)` when data is returned.",
                "exploit_scenario": "If a non-standard ERC20 returns `false` (but does not revert) on transfer, `withdraw_admin_fees` will still clear `admin_balances[i]`, effectively losing the fees and creating accounting drift."
            }
        ],
        "no_findings": false
    },
    "no_findings": false,
    "proxy_context": {
        "proxy_run_id": "019bb004-7b45-70c7-adb3-d1c5cb2846d2",
        "proxy_address": "0x828b154032950c8ff7cf8085d841723db2696056",
        "implementation_run_id": "019bb005-2254-73e9-85d1-87e7597e1544",
        "implementation_address": "0x24d937143d3f5cf04c72ba112735151a8cae2262"
    },
    "schema_version": 1
}

Opcode Heuristics

tool.cast_disasm
Delegatecall
1
Selfdestruct
0
CREATE2
0
CALL-family (heavy)
0
EXT*/BALANCE
0
Total opcodes
24
Flags
delegatecall_present
View cast disassembly output
00000000: CALLDATASIZE
00000001: RETURNDATASIZE
00000002: RETURNDATASIZE
00000003: CALLDATACOPY
00000004: RETURNDATASIZE
00000005: RETURNDATASIZE
00000006: RETURNDATASIZE
00000007: CALLDATASIZE
00000008: RETURNDATASIZE
00000009: PUSH20 0x24d937143d3f5cf04c72ba112735151a8cae2262
0000001e: GAS
0000001f: DELEGATECALL
00000020: RETURNDATASIZE
00000021: DUP3
00000022: DUP1
00000023: RETURNDATACOPY
00000024: SWAP1
00000025: RETURNDATASIZE
00000026: SWAP2
00000027: PUSH1 0x2b
00000029: JUMPI
0000002a: REVERT
0000002b: JUMPDEST
0000002c: 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: 0x55a8fe50160db5a29de0bad8febcb99ec85cfc8a15f955ab4ec3c442683ee8bd
call_targets: 0x24d937143d3f5cf04c72ba112735151a8cae2262
target_varies: no
classification: constant_target
validation_json
{
    "sink": "DELEGATECALL",
    "errors": 0,
    "status": "sink_reached",
    "attempts": 1,
    "trace_id": "0x55a8fe50160db5a29de0bad8febcb99ec85cfc8a15f955ab4ec3c442683ee8bd",
    "confirmed": false,
    "trace_mode": "callTracer",
    "call_targets": [
        "0x24d937143d3f5cf04c72ba112735151a8cae2262"
    ],
    "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": 31,
            "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 allows proxy takeover if not initialized atomically 85 no
view
evidence_json
{
    "evidence": "`initialize` only checks `self.fee == 0` and sets `self.factory = msg.sender` without access control.",
    "locations": [
        "contracts/Contract.sol:111",
        "contracts/Contract.sol:130",
        "contracts/Contract.sol:143"
    ],
    "recommendation": "Restrict `initialize` to an expected factory/admin address (e.g., pass factory in constructor or add an `onlyFactory` check), or ensure the proxy is initialized atomically in the deployment transaction.",
    "exploit_scenario": "If the proxy at 0x828b\u2026 is deployed without an atomic initializer call, any user can call `initialize` first, set `factory` to their address, choose arbitrary coins/fees/A, and then control admin-only actions (e.g., `ramp_A`, `stop_ramp_A`, fee receiver) for that pool."
}
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
}
medium reentrancy codex `withdraw_admin_fees` can be reentered via token callback before state is updated 75 no
view
evidence_json
{
    "evidence": "`withdraw_admin_fees` performs an external token transfer and only zeroes `admin_balances[i]` afterward, with no nonreentrant guard.",
    "locations": [
        "contracts/Contract.sol:903",
        "contracts/Contract.sol:911",
        "contracts/Contract.sol:919"
    ],
    "recommendation": "Apply the reentrancy guard to `withdraw_admin_fees` and/or set `admin_balances[i] = 0` before the external call (checks-effects-interactions).",
    "exploit_scenario": "A malicious pool coin can reenter `withdraw_admin_fees` during `transfer`, before `admin_balances[i]` is zeroed. Reentrancy can trigger repeated transfers of the same amount, draining pool liquidity beyond the accrued admin fees."
}
low other codex Admin fee withdrawal ignores ERC20 return values and can zero fees on failed transfer 65 no
view
evidence_json
{
    "evidence": "`withdraw_admin_fees` uses `raw_call` without `max_outsize` and does not validate returned data.",
    "locations": [
        "contracts/Contract.sol:911"
    ],
    "recommendation": "Mirror the return-value checks used elsewhere: capture `raw_call` response, and `assert convert(response, bool)` when data is returned.",
    "exploit_scenario": "If a non-standard ERC20 returns `false` (but does not revert) on transfer, `withdraw_admin_fees` will still clear `admin_balances[i]`, effectively losing the fees and creating accounting drift."
}