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

Analysis Run

006ad5b9-da71-47b0-b31d-5bb56d063f46

complete
Chain ID
1
Address
Block Number
Created
Tue, Dec 30, 2025 9:41 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
Unprotected initialize lets anyone set pool interest rate
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
30783 report.consolidate complete 1 / 1 3 months ago
75694 artifact.fetch complete 1 / 3 1 week ago
75695 tool.cast_disasm complete 1 / 3 1 week ago
75696 analysis.bundle complete 1 / 3 1 week ago
75697 capability.graph complete 1 / 3 1 week ago
75698 detector.run complete 1 / 3 1 week ago
75699 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 issues: unprotected initializer enabling hostile pool configuration, and potential interest-accrual DoS when the rate/elapsed product exceeds PRBMath’s exp range.
View stderr
[codex:913844] OpenAI Codex v0.77.0 (research preview)
[codex:913844] --------
[codex:913844] workdir: /tmp/codex-multi-9mui361y
[codex:913844] model: gpt-5.2-codex
[codex:913844] provider: openai
[codex:913844] approval: never
[codex:913844] sandbox: read-only
[codex:913844] reasoning effort: xhigh
[codex:913844] reasoning summaries: auto
[codex:913844] session id: 019b712e-198b-7ee3-908b-a2df34fee246
[codex:913844] --------
[codex:913844] user
[codex:913844] You are a security auditor. Audit this Solidity codebase for exploitable vulnerabilities.
[codex:913844] Focus on: reentrancy, access control, upgradeability/proxy issues, signature validation, oracle manipulation,
[codex:913844] accounting/invariant breaks, external call patterns, unchecked return values, and dangerous delegatecall.
[codex:913844] 
[codex:913844] Scope rules:
[codex:913844] - You MUST fully read `AUDIT_BUNDLE.txt` before concluding. It contains the complete contents of all first-party `contracts/**/*.sol` files.
[codex:913844] - Treat `lib/` and `@openzeppelin/` as third-party dependencies; skip them unless there is a concrete misuse angle in first-party code.
[codex:913844] 
[codex:913844] Output MUST be valid JSON matching the provided schema.
[codex:913844] - If you find nothing meaningful: set no_findings=true and findings=[].
[codex:913844] - Otherwise: set no_findings=false and include findings with severity/category/title and file/line locations when possible.
[codex:913844] 
[codex:913844] Output constraints (IMPORTANT):
[codex:913844] - severity MUST be exactly one of: info, low, medium, high, critical (lowercase).
[codex:913844] - category MUST be exactly one of: access_control, reentrancy, math, oracle, upgradeability, auth, other.
[codex:913844] mcp startup: no servers
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Preparing to read AUDIT_BUNDLE.txt**
[codex:913844] exec
[codex:913844] /bin/bash -lc ls in /tmp/codex-multi-9mui361y succeeded in 5ms:
[codex:913844] AUDIT_BUNDLE.txt
[codex:913844] codex-output-schema.json
[codex:913844] contracts
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '1,200p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844] This file concatenates the full contents of first-party Solidity sources for auditing.
[codex:913844] 
[codex:913844] ================================================================================
[codex:913844] FILE: contracts/lib/clones-with-immutable-args/src/Clone.sol
[codex:913844] ================================================================================
[codex:913844] // SPDX-License-Identifier: BSD
[codex:913844] pragma solidity ^0.8.4;
[codex:913844] 
[codex:913844] /// @title Clone
[codex:913844] /// @author zefram.eth
[codex:913844] /// @notice Provides helper functions for reading immutable args from calldata
[codex:913844] contract Clone {
[codex:913844]     /// @notice Reads an immutable arg with type address
[codex:913844]     /// @param argOffset The offset of the arg in the packed data
[codex:913844]     /// @return arg The arg value
[codex:913844]     function _getArgAddress(uint256 argOffset)
[codex:913844]         internal
[codex:913844]         pure
[codex:913844]         returns (address arg)
[codex:913844]     {
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Scanning full AUDIT_BUNDLE.txt**
[codex:913844] exec
[codex:913844] /bin/bash -lc 'wc -l AUDIT_BUNDLE.txt' in /tmp/codex-multi-9mui361y succeeded in 5ms:
[codex:913844] 13037 AUDIT_BUNDLE.txt
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Planning full file reading approach**
[codex:913844] exec
[codex:913844] /bin/bash -lc 'rg -n "''^FILE:" AUDIT_BUNDLE.txt' in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844] 4:FILE: contracts/lib/clones-with-immutable-args/src/Clone.sol
[codex:913844] 104:FILE: contracts/lib/openzeppelin-contracts/contracts/security/ReentrancyGuard.sol
[codex:913844] 177:FILE: contracts/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol
[codex:913844] 263:FILE: contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/draft-IERC20Permit.sol
[codex:913844] 327:FILE: contracts/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol
[codex:913844] 447:FILE: contracts/lib/openzeppelin-contracts/contracts/utils/Address.sol
[codex:913844] 695:FILE: contracts/lib/openzeppelin-contracts/contracts/utils/Multicall.sol
[codex:913844] 723:FILE: contracts/lib/openzeppelin-contracts/contracts/utils/math/Math.sol
[codex:913844] 1072:FILE: contracts/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol
[codex:913844] 2212:FILE: contracts/lib/prb-math/contracts/PRBMath.sol
[codex:913844] 2864:FILE: contracts/lib/prb-math/contracts/PRBMathSD59x18.sol
[codex:913844] 3552:FILE: contracts/lib/prb-math/contracts/PRBMathUD60x18.sol
[codex:913844] 4058:FILE: contracts/src/ERC20Pool.sol
[codex:913844] 4577:FILE: contracts/src/base/FlashloanablePool.sol
[codex:913844] 4675:FILE: contracts/src/base/Pool.sol
[codex:913844] 5672:FILE: contracts/src/interfaces/pool/IERC3156FlashBorrower.sol
[codex:913844] 5699:FILE: contracts/src/interfaces/pool/IERC3156FlashLender.sol
[codex:913844] 5746:FILE: contracts/src/interfaces/pool/IPool.sol
[codex:913844] 5812:FILE: contracts/src/interfaces/pool/commons/IPoolBorrowerActions.sol
[codex:913844] 5833:FILE: contracts/src/interfaces/pool/commons/IPoolDerivedState.sol
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Planning file reading strategy**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '1,400p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844] This file concatenates the full contents of first-party Solidity sources for auditing.
[codex:913844] 
[codex:913844] ================================================================================
[codex:913844] FILE: contracts/lib/clones-with-immutable-args/src/Clone.sol
[codex:913844] ================================================================================
[codex:913844] // SPDX-License-Identifier: BSD
[codex:913844] pragma solidity ^0.8.4;
[codex:913844] 
[codex:913844] /// @title Clone
[codex:913844] /// @author zefram.eth
[codex:913844] /// @notice Provides helper functions for reading immutable args from calldata
[codex:913844] contract Clone {
[codex:913844]     /// @notice Reads an immutable arg with type address
[codex:913844]     /// @param argOffset The offset of the arg in the packed data
[codex:913844]     /// @return arg The arg value
[codex:913844]     function _getArgAddress(uint256 argOffset)
[codex:913844]         internal
[codex:913844]         pure
[codex:913844]         returns (address arg)
[codex:913844]     {
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Scrolling further**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '400,800p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844]         address spender,
[codex:913844]         uint256 value
[codex:913844]     ) internal {
[codex:913844]         unchecked {
[codex:913844]             uint256 oldAllowance = token.allowance(address(this), spender);
[codex:913844]             require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
[codex:913844]             uint256 newAllowance = oldAllowance - value;
[codex:913844]             _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
[codex:913844]         }
[codex:913844]     }
[codex:913844] 
[codex:913844]     function safePermit(
[codex:913844]         IERC20Permit token,
[codex:913844]         address owner,
[codex:913844]         address spender,
[codex:913844]         uint256 value,
[codex:913844]         uint256 deadline,
[codex:913844]         uint8 v,
[codex:913844]         bytes32 r,
[codex:913844]         bytes32 s
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Planning to continue reading later**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '800,1200p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844] 
[codex:913844]             // Make sure the result is less than 2^256. Also prevents denominator == 0.
[codex:913844]             require(denominator > prod1);
[codex:913844] 
[codex:913844]             ///////////////////////////////////////////////
[codex:913844]             // 512 by 256 division.
[codex:913844]             ///////////////////////////////////////////////
[codex:913844] 
[codex:913844]             // Make division exact by subtracting the remainder from [prod1 prod0].
[codex:913844]             uint256 remainder;
[codex:913844]             assembly {
[codex:913844]                 // Compute remainder using mulmod.
[codex:913844]                 remainder := mulmod(x, y, denominator)
[codex:913844] 
[codex:913844]                 // Subtract 256 bit number from 512 bit number.
[codex:913844]                 prod1 := sub(prod1, gt(remainder, prod0))
[codex:913844]                 prod0 := sub(prod0, remainder)
[codex:913844]             }
[codex:913844] 
[codex:913844]             // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Continuing to process large dependency files**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '1200,2400p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844]      * overflow (when the input is greater than largest uint200).
[codex:913844]      *
[codex:913844]      * Counterpart to Solidity's `uint200` operator.
[codex:913844]      *
[codex:913844]      * Requirements:
[codex:913844]      *
[codex:913844]      * - input must fit into 200 bits
[codex:913844]      *
[codex:913844]      * _Available since v4.7._
[codex:913844]      */
[codex:913844]     function toUint200(uint256 value) internal pure returns (uint200) {
[codex:913844]         require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits");
[codex:913844]         return uint200(value);
[codex:913844]     }
[codex:913844] 
[codex:913844]     /**
[codex:913844]      * @dev Returns the downcasted uint192 from uint256, reverting on
[codex:913844]      * overflow (when the input is greater than largest uint192).
[codex:913844]      *
[codex:913844]      * Counterpart to Solidity's `uint192` operator.
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Continuing sequential file reading**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '2400,3600p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844]             }
[codex:913844]             if (x & 0x200000000000 > 0) {
[codex:913844]                 result = (result * 0x10000162E43F4F831) >> 64;
[codex:913844]             }
[codex:913844]             if (x & 0x100000000000 > 0) {
[codex:913844]                 result = (result * 0x100000B1721BCFC9A) >> 64;
[codex:913844]             }
[codex:913844]             if (x & 0x80000000000 > 0) {
[codex:913844]                 result = (result * 0x10000058B90CF1E6E) >> 64;
[codex:913844]             }
[codex:913844]             if (x & 0x40000000000 > 0) {
[codex:913844]                 result = (result * 0x1000002C5C863B73F) >> 64;
[codex:913844]             }
[codex:913844]             if (x & 0x20000000000 > 0) {
[codex:913844]                 result = (result * 0x100000162E430E5A2) >> 64;
[codex:913844]             }
[codex:913844]             if (x & 0x10000000000 > 0) {
[codex:913844]                 result = (result * 0x1000000B172183551) >> 64;
[codex:913844]             }
[codex:913844]             if (x & 0x8000000000 > 0) {
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Continuing to read PRBMathUD60x18 section**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '3600,4200p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844]     ///
[codex:913844]     /// Requirements:
[codex:913844]     /// - x must be less than or equal to MAX_WHOLE_UD60x18.
[codex:913844]     ///
[codex:913844]     /// @param x The unsigned 60.18-decimal fixed-point number to ceil.
[codex:913844]     /// @param result The least integer greater than or equal to x, as an unsigned 60.18-decimal fixed-point number.
[codex:913844]     function ceil(uint256 x) internal pure returns (uint256 result) {
[codex:913844]         if (x > MAX_WHOLE_UD60x18) {
[codex:913844]             revert PRBMathUD60x18__CeilOverflow(x);
[codex:913844]         }
[codex:913844]         assembly {
[codex:913844]             // Equivalent to "x % SCALE" but faster.
[codex:913844]             let remainder := mod(x, SCALE)
[codex:913844] 
[codex:913844]             // Equivalent to "SCALE - remainder" but faster.
[codex:913844]             let delta := sub(SCALE, remainder)
[codex:913844] 
[codex:913844]             // Equivalent to "x + delta * (remainder > 0 ? 1 : 0)" but faster.
[codex:913844]             result := add(x, mul(delta, gt(remainder, 0)))
[codex:913844]         }
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Continuing file review from line 4200**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '4200,5200p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 8ms:
[codex:913844]         // ensure the borrower is not credited with a fractional amount of collateral smaller than the token scale
[codex:913844]         collateralToPledge_ = _roundToScale(collateralToPledge_, _getArgUint256(COLLATERAL_SCALE));
[codex:913844] 
[codex:913844]         DrawDebtResult memory result = BorrowerActions.drawDebt(
[codex:913844]             auctions,
[codex:913844]             buckets,
[codex:913844]             deposits,
[codex:913844]             loans,
[codex:913844]             poolState,
[codex:913844]             _availableQuoteToken(),
[codex:913844]             borrowerAddress_,
[codex:913844]             amountToBorrow_,
[codex:913844]             limitIndex_,
[codex:913844]             collateralToPledge_
[codex:913844]         );
[codex:913844] 
[codex:913844]         emit DrawDebt(borrowerAddress_, amountToBorrow_, collateralToPledge_, result.newLup);
[codex:913844] 
[codex:913844]         // update in memory pool state struct
[codex:913844]         poolState.debt       = result.poolDebt;
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Continuing work**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '5200,6400p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844]     /// @inheritdoc IPoolLPActions
[codex:913844]     function transferLP(
[codex:913844]         address owner_,
[codex:913844]         address newOwner_,
[codex:913844]         uint256[] calldata indexes_
[codex:913844]     ) external override nonReentrant {
[codex:913844]         LPActions.transferLP(
[codex:913844]             buckets,
[codex:913844]             _lpAllowances,
[codex:913844]             approvedTransferors,
[codex:913844]             owner_,
[codex:913844]             newOwner_,
[codex:913844]             indexes_
[codex:913844]         );
[codex:913844]     }
[codex:913844] 
[codex:913844]     /*****************************/
[codex:913844]     /*** Pool Helper Functions ***/
[codex:913844]     /*****************************/
[codex:913844] 
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Planning careful file rereading to avoid truncation**
[codex:913844] 
[codex:913844] I’m figuring out how to read a huge file without tool truncation messing things up. The output got cut off around line 5200–6400, so I’ll break the file into smaller chunks—around 300–600 lines each—and reread from about line 5400 onward to ensure I fully capture all interface definitions and contracts. This way, I can review everything reliably without losing parts to output limits.
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '5400,5800p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844]      */
[codex:913844]     function _transferQuoteToken(address to_, uint256 amount_) internal {
[codex:913844]         IERC20(_getArgAddress(QUOTE_ADDRESS)).safeTransfer(to_, amount_ / _getArgUint256(QUOTE_SCALE));
[codex:913844]     }
[codex:913844] 
[codex:913844]     /**
[codex:913844]      *  @notice Returns the quote token amount available to take loans or to be removed from pool.
[codex:913844]      *          Ensures claimable reserves and auction bonds are not used when taking loans.
[codex:913844]      */
[codex:913844]     function _availableQuoteToken() internal view returns (uint256 quoteAvailable_) {
[codex:913844]         uint256 poolBalance     = _getNormalizedPoolQuoteTokenBalance();
[codex:913844]         uint256 escrowedAmounts = auctions.totalBondEscrowed + reserveAuction.unclaimed;
[codex:913844] 
[codex:913844]         if (poolBalance > escrowedAmounts) quoteAvailable_ = poolBalance - escrowedAmounts;
[codex:913844]     }
[codex:913844] 
[codex:913844]     /**
[codex:913844]      *  @notice Returns the pool quote token balance normalized to `WAD` to be used for calculating pool reserves.
[codex:913844]      */
[codex:913844]     function _getNormalizedPoolQuoteTokenBalance() internal view returns (uint256) {
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Continuing interface review**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '5800,6600p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844] }
[codex:913844] 
[codex:913844] /// @dev `ERC721` token interface.
[codex:913844] interface IERC721Token {
[codex:913844]     function transferFrom(
[codex:913844]         address from,
[codex:913844]         address to,
[codex:913844]         uint256 tokenId
[codex:913844]     ) external;
[codex:913844] }
[codex:913844] 
[codex:913844] ================================================================================
[codex:913844] FILE: contracts/src/interfaces/pool/commons/IPoolBorrowerActions.sol
[codex:913844] ================================================================================
[codex:913844] // SPDX-License-Identifier: MIT
[codex:913844] 
[codex:913844] pragma solidity 0.8.18;
[codex:913844] 
[codex:913844] /**
[codex:913844]  * @title Pool Borrower Actions
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Extending code review range**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '6600,7400p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844] /******************************************/
[codex:913844] /*** Liquidity Management Param Structs ***/
[codex:913844] /******************************************/
[codex:913844] 
[codex:913844] /// @dev Struct used to hold parameters for `LenderAction.addQuoteToken` action.
[codex:913844] struct AddQuoteParams {
[codex:913844]     uint256 amount;           // [WAD] amount to be added
[codex:913844]     uint256 index;            // the index in which to deposit
[codex:913844]     bool    revertIfBelowLup; // revert tx if index in which to deposit is below LUP
[codex:913844] }
[codex:913844] 
[codex:913844] /// @dev Struct used to hold parameters for `LenderAction.moveQuoteToken` action.
[codex:913844] struct MoveQuoteParams {
[codex:913844]     uint256 fromIndex;        // the deposit index from where amount is moved
[codex:913844]     uint256 maxAmountToMove;  // [WAD] max amount to move between deposits
[codex:913844]     uint256 toIndex;          // the deposit index where amount is moved to
[codex:913844]     uint256 thresholdPrice;   // [WAD] max threshold price in pool
[codex:913844]     bool    revertIfBelowLup; // revert tx if quote token is moved from above the LUP to below the LUP
[codex:913844] }
[codex:913844] 
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Continuing code exploration from line 7400**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '7400,8200p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844]     /***********************/
[codex:913844]     /*** Reserve Auction ***/
[codex:913844]     /***********************/
[codex:913844] 
[codex:913844]     /**
[codex:913844]      *  @notice Purchases claimable reserves during a `CRA` using `Ajna` token.
[codex:913844]      *  @param  maxAmount_ Maximum amount of quote token to purchase at the current auction price (`WAD` precision).
[codex:913844]      *  @return amount_    Actual amount of reserves taken (`WAD` precision).
[codex:913844]      */
[codex:913844]     function takeReserves(
[codex:913844]         uint256 maxAmount_
[codex:913844]     ) external returns (uint256 amount_);
[codex:913844] 
[codex:913844] }
[codex:913844] 
[codex:913844] ================================================================================
[codex:913844] FILE: contracts/src/interfaces/pool/erc20/IERC20Pool.sol
[codex:913844] ================================================================================
[codex:913844] // SPDX-License-Identifier: MIT
[codex:913844] 
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Continuing code exploration in segments**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '8200,9000p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844]     error InvalidAmount();
[codex:913844]     error NoReserves();
[codex:913844]     error PriceBelowLUP();
[codex:913844]     error ReserveAuctionTooSoon();
[codex:913844] 
[codex:913844]     /***************************/
[codex:913844]     /***  External Functions ***/
[codex:913844]     /***************************/
[codex:913844] 
[codex:913844]     /**
[codex:913844]      *  @notice See `IPoolKickerActions` for descriptions.
[codex:913844]      *  @return The `KickResult` struct result of the kick action.
[codex:913844]      */
[codex:913844]     function kick(
[codex:913844]         AuctionsState storage auctions_,
[codex:913844]         DepositsState storage deposits_,
[codex:913844]         LoansState    storage loans_,
[codex:913844]         PoolState calldata poolState_,
[codex:913844]         address borrowerAddress_,
[codex:913844]         uint256 limitIndex_
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Continuing to review LenderActions and PoolCommons**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '9000,9800p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844]         DepositsState storage deposits_,
[codex:913844]         PoolState calldata poolState_,
[codex:913844]         AddQuoteParams calldata params_
[codex:913844]     ) external returns (uint256 bucketLP_, uint256 lup_) {
[codex:913844]         // revert if no amount to be added
[codex:913844]         if (params_.amount == 0) revert InvalidAmount();
[codex:913844]         // revert if adding to an invalid index
[codex:913844]         if (params_.index == 0 || params_.index > MAX_FENWICK_INDEX) revert InvalidIndex();
[codex:913844] 
[codex:913844]         Bucket storage bucket = buckets_[params_.index];
[codex:913844] 
[codex:913844]         uint256 bankruptcyTime = bucket.bankruptcyTime;
[codex:913844] 
[codex:913844]         // cannot deposit in the same block when bucket becomes insolvent
[codex:913844]         if (bankruptcyTime == block.timestamp) revert BucketBankruptcyBlock();
[codex:913844] 
[codex:913844]         uint256 unscaledBucketDeposit = Deposits.unscaledValueAt(deposits_, params_.index);
[codex:913844]         uint256 bucketScale           = Deposits.scale(deposits_, params_.index);
[codex:913844]         uint256 bucketDeposit         = Maths.wmul(bucketScale, unscaledBucketDeposit);
[codex:913844]         uint256 bucketPrice           = _priceAt(params_.index);
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Continuing to review core library files**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '9800,10600p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 8ms:
[codex:913844]         vars.newMeaningfulDeposit = Maths.max(
[codex:913844]             _meaningfulDeposit(
[codex:913844]                 deposits_,
[codex:913844]                 poolState_.t0DebtInAuction,
[codex:913844]                 vars.nonAuctionedT0Debt,
[codex:913844]                 poolState_.inflator,
[codex:913844]                 vars.t0Debt2ToCollateral
[codex:913844]             ),
[codex:913844]             vars.newDebt
[codex:913844]         );
[codex:913844]         vars.newDebtCol   = Maths.wmul(poolState_.inflator, vars.t0Debt2ToCollateral);
[codex:913844]         vars.newLupt0Debt = Maths.wmul(lup_, vars.nonAuctionedT0Debt);
[codex:913844] 
[codex:913844]         // update EMAs only once per block
[codex:913844]         if (vars.lastEmaUpdate != block.timestamp) {
[codex:913844] 
[codex:913844]             // first time EMAs are updated, initialize EMAs
[codex:913844]             if (vars.lastEmaUpdate == 0) {
[codex:913844]                 vars.debtEma      = vars.newDebt;
[codex:913844]                 vars.depositEma   = vars.newMeaningfulDeposit;
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Continuing file review**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '10600,11400p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844]                 // Remove deposit used to forgive bad debt from bucket
[codex:913844]                 Deposits.unscaledRemove(deposits_, index, depositUsed);
[codex:913844] 
[codex:913844]             // 2) loan debt to settle exceeds bucket deposit, bucket deposit is the constraint
[codex:913844]             } else {
[codex:913844]                 // subtract from remaining debt the corresponding t0 amount of deposit
[codex:913844]                 remainingt0Debt_ -= Maths.floorWdiv(depositToRemove, inflator_);
[codex:913844] 
[codex:913844]                 // Remove all deposit from bucket
[codex:913844]                 Deposits.unscaledRemove(deposits_, index, unscaledDeposit);
[codex:913844]             }
[codex:913844] 
[codex:913844]             Bucket storage hpbBucket = buckets_[index];
[codex:913844]             uint256 bucketLP = hpbBucket.lps;
[codex:913844]             // If the remaining deposit and resulting bucket collateral is so small that the exchange rate
[codex:913844]             // rounds to 0, then bankrupt the bucket.  Note that lhs are WADs, so the
[codex:913844]             // quantity is naturally 1e18 times larger than the actual product
[codex:913844]             if (depositRemaining * Maths.WAD + hpbBucket.collateral * _priceAt(index) <= bucketLP) {
[codex:913844]                 // existing LP for the bucket shall become unclaimable
[codex:913844]                 hpbBucket.lps            = 0;
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Continuing through TakerActions and helpers**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '11400,12200p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844]             // borrower debt is constraining factor
[codex:913844]             vars.collateralAmount         = _roundToScale(Maths.wdiv(vars.borrowerDebt, borrowerPrice), collateralScale_);
[codex:913844]             vars.t0RepayAmount            = vars.t0BorrowerDebt;
[codex:913844]             vars.unscaledQuoteTokenAmount = Maths.wdiv(vars.borrowerDebt, vars.bucketScale);
[codex:913844] 
[codex:913844]             vars.quoteTokenAmount         = (vars.isRewarded) ? Maths.wdiv(vars.borrowerDebt, borrowerPayoffFactor) : vars.borrowerDebt;
[codex:913844] 
[codex:913844]         } else {
[codex:913844]             // collateral available is constraint
[codex:913844]             vars.collateralAmount         = totalCollateral_;
[codex:913844]             vars.t0RepayAmount            = Math.mulDiv(totalCollateral_, borrowerPrice, inflator_);
[codex:913844]             vars.unscaledQuoteTokenAmount = Math.mulDiv(totalCollateral_, borrowerPrice, vars.bucketScale);
[codex:913844] 
[codex:913844]             vars.quoteTokenAmount         = Maths.wmul(vars.collateralAmount, vars.auctionPrice);
[codex:913844]         }
[codex:913844] 
[codex:913844]         if (vars.isRewarded) {
[codex:913844]             // take is below neutralPrice, Kicker is rewarded
[codex:913844]             vars.bondChange = Maths.wmul(vars.quoteTokenAmount, uint256(vars.bpf));
[codex:913844]         } else {
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Continuing internal libraries work**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '12200,13037p' AUDIT_BUNDLE.txt" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844]     function lpToQuoteTokens(
[codex:913844]         uint256 bucketCollateral_,
[codex:913844]         uint256 bucketLP_,
[codex:913844]         uint256 deposit_,
[codex:913844]         uint256 lp_,
[codex:913844]         uint256 bucketPrice_,
[codex:913844]         Math.Rounding rounding_
[codex:913844]     ) internal pure returns (uint256) {
[codex:913844]         // case when there's no deposit nor collateral in bucket
[codex:913844]         if (deposit_ == 0 && bucketCollateral_ == 0) return lp_;
[codex:913844] 
[codex:913844]         // case when there's deposit or collateral in bucket but no LP to cover
[codex:913844]         if (bucketLP_ == 0) return lp_;
[codex:913844] 
[codex:913844]         // case when there's deposit or collateral and bucket has LP balance
[codex:913844]         return Math.mulDiv(
[codex:913844]             deposit_ * Maths.WAD + bucketCollateral_ * bucketPrice_,
[codex:913844]             lp_,
[codex:913844]             bucketLP_ * Maths.WAD,
[codex:913844]             rounding_
[codex:913844] 
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Analyzing security risks and patterns**
[codex:913844] thinking
[codex:913844] **Investigating arithmetic edge cases and token handling**
[codex:913844] 
[codex:913844] There is a lot here! I’m considering how extremely high interest rates set during initialization could cause the accrual functions to revert after some elapsed time due to math limits, possibly freezing the pool. I’m also thinking through rounding behaviors during token transfers, potential overflows in debt calculations, and subtle casting issues when converting between signed and unsigned integers that might affect bonding calculations. Overall, I’m checking for scenarios where edge cases in math or token handling could cause unexpected behaviors or minor inconsistencies.
[codex:913844] exec
[codex:913844] /bin/bash -lc 'rg -n "factor" -n contracts/src/libraries/external/TakerActions.sol' in /tmp/codex-multi-9mui361y succeeded in 7ms:
[codex:913844] 80:        int256  bpf;                      // The bond penalty factor.
[codex:913844] 85:        uint256 factor;                   // The take factor, calculated based on bond penalty factor.
[codex:913844] 88:        uint256 quoteTokenAmount;         // [WAD] Scaled quantity in Fenwick tree and before 1-bpf factor, paid for collateral
[codex:913844] 723:        vars.factor       = uint256(1e18 - Maths.maxInt(0, vars.bpf));
[codex:913844] 754:            // quote token used to purchase is constraining factor
[codex:913844] 764:            // borrower debt is constraining factor
[codex:913844] 
[codex:913844] 
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Analyzing potential vulnerabilities and edge cases**
[codex:913844] 
[codex:913844] I’m carefully reviewing the contract’s arithmetic, access control, and reentrancy patterns to spot real security risks. Most calculations and conversions seem safe within expected ranges, though the unchecked interest rate during initialization and manipulation via multicall stand out. I also see rounding and scaling quirks that might cause minor user unfairness but not exploitable vulnerabilities. The biggest concerns appear to be unrestricted initialization allowing malicious rate setting, and possible callback state changes during flashloans due to missing nonReentrant guards on some functions. Let's explore if these could be problematic in practice!
[codex:913844] thinking
[codex:913844] **Evaluating rounding, initialization, and state update nuances**
[codex:913844] 
[codex:913844] I’m checking subtle rounding behaviors, collateral transfers, and auction bond accounting to find edge cases. Most rounding seems safe due to scale checks, and bond escrow accounting matches expected logic. The main confirmed vulnerability remains unrestricted initialization allowing arbitrary interest rate causing potential disruptions. Other candidate issues like division by zero, reentrancy in flashLoan, or state inconsistencies seem mitigated by internal checks. I’m keeping an eye on scaling parameters and auction bookkeeping but so far, no critical gaps beyond initialization and rate validation.
[codex:913844] thinking
[codex:913844] **Examining exponent usage and math edge cases**
[codex:913844] exec
[codex:913844] /bin/bash -lc 'rg -n "function initialize" -n contracts/src/ERC20Pool.sol' in /tmp/codex-multi-9mui361y succeeded in 8ms:
[codex:913844] 86:    function initialize(
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Checking rate validation details**
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '80,120p' contracts/src/ERC20Pool.sol" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844] 
[codex:913844]     /****************************/
[codex:913844]     /*** Initialize Functions ***/
[codex:913844]     /****************************/
[codex:913844] 
[codex:913844]     /// @inheritdoc IERC20Pool
[codex:913844]     function initialize(
[codex:913844]         uint256 rate_
[codex:913844]     ) external override {
[codex:913844]         if (isPoolInitialized) revert AlreadyInitialized();
[codex:913844] 
[codex:913844]         inflatorState.inflator       = uint208(1e18);
[codex:913844]         inflatorState.inflatorUpdate = uint48(block.timestamp);
[codex:913844] 
[codex:913844]         interestState.interestRate       = uint208(rate_);
[codex:913844]         interestState.interestRateUpdate = uint48(block.timestamp);
[codex:913844] 
[codex:913844]         Loans.init(loans);
[codex:913844] 
[codex:913844]         // increment initializations count to ensure these values can't be updated
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Investigating potential overflow in PRBMath exp usage**
[codex:913844] exec
[codex:913844] /bin/bash -lc 'rg -n "function accrueInterest" -n contracts/src/libraries/external/PoolCommons.sol' in /tmp/codex-multi-9mui361y succeeded in 7ms:
[codex:913844] 220:    function accrueInterest(
[codex:913844] exec
[codex:913844] /bin/bash -lc "sed -n '220,260p' contracts/src/libraries/external/PoolCommons.sol" in /tmp/codex-multi-9mui361y succeeded in 6ms:
[codex:913844]     function accrueInterest(
[codex:913844]         EmaState      storage emaParams_,
[codex:913844]         DepositsState storage deposits_,
[codex:913844]         PoolState calldata poolState_,
[codex:913844]         uint256 thresholdPrice_,
[codex:913844]         uint256 elapsed_
[codex:913844]     ) external returns (uint256 newInflator_, uint256 newInterest_) {
[codex:913844]         // Scale the borrower inflator to update amount of interest owed by borrowers
[codex:913844]         uint256 pendingFactor = PRBMathUD60x18.exp((poolState_.rate * elapsed_) / 365 days);
[codex:913844] 
[codex:913844]         // calculate the highest threshold price
[codex:913844]         newInflator_ = Maths.wmul(poolState_.inflator, pendingFactor);
[codex:913844]         uint256 htp = Maths.wmul(thresholdPrice_, poolState_.inflator);
[codex:913844] 
[codex:913844]         uint256 accrualIndex;
[codex:913844]         if (htp > MAX_PRICE)      accrualIndex = 1;                 // if HTP is over the highest price bucket then no buckets earn interest
[codex:913844]         else if (htp < MIN_PRICE) accrualIndex = MAX_FENWICK_INDEX; // if HTP is under the lowest price bucket then all buckets earn interest
[codex:913844]         else                      accrualIndex = _indexOf(htp);     // else HTP bucket earn interest
[codex:913844] 
[codex:913844]         uint256 lupIndex = Deposits.findIndexOfSum(deposits_, poolState_.debt);
[codex:913844] 
[codex:913844] thinking
[codex:913844] **Summarizing vulnerability findings**
[codex:913844] codex
[codex:913844] {"no_findings":false,"summary":"Found 2 issues: unprotected initializer enabling hostile pool configuration, and potential interest-accrual DoS when the rate/elapsed product exceeds PRBMath’s exp range.","findings":[{"severity":"high","category":"access_control","title":"Unprotected initialize lets anyone set pool interest rate","exploit_scenario":"An attacker monitors for new pool clones and calls `initialize` before the factory/deployer. They set `rate_` to an extreme value (e.g., very high to make borrowing unsafe or to later trigger interest-accrual reverts), permanently locking in hostile parameters for all users.","recommendation":"Restrict `initialize` to the trusted factory/deployer (e.g., `onlyFactory`) and enforce bounds on `rate_` (per interface docs: 1%–10%). Consider using OpenZeppelin’s `initializer` pattern or a constructor-like deploy-and-init flow to avoid exposure.","evidence":"`initialize` is `external` and only checks `isPoolInitialized`, then sets `interestState.interestRate = uint208(rate_)` with no access control or bounds check.","locations":["contracts/src/ERC20Pool.sol:86"]},{"severity":"medium","category":"math","title":"Interest accrual can revert for high rate * elapsed, freezing the pool","exploit_scenario":"If the interest rate reaches high values (the algorithm allows up to 500 * 1e18) and the pool is idle long enough (~97+ days at max), `accrueInterest` calls `PRBMathUD60x18.exp` with an input >= 133e18, which reverts. Since most state-changing actions call `_accruePoolInterest`, the pool becomes permanently unusable until a code change. An attacker could push utilization to raise rates, then wait for enough time to pass to trigger the revert and DoS the pool.","recommendation":"Clamp the `rate_ * elapsed_ / 365 days` input to the PRBMath exp range or accrue interest in smaller chunks. Alternatively cap the rate so that `rate * maxElapsed` never exceeds the exp limit, or switch to a math routine that supports larger exponents.","evidence":"`accrueInterest` directly calls `PRBMathUD60x18.exp((poolState_.rate * elapsed_) / 365 days)` with no guard against exp range limits.","locations":["contracts/src/libraries/external/PoolCommons.sol:220"]}]}
[codex:913844] tokens used
[codex:913844] 167,024
[codex:913844] {"no_findings":false,"summary":"Found 2 issues: unprotected initializer enabling hostile pool configuration, and potential interest-accrual DoS when the rate/elapsed product exceeds PRBMath’s exp range.","findings":[{"severity":"high","category":"access_control","title":"Unprotected initialize lets anyone set pool interest rate","exploit_scenario":"An attacker monitors for new pool clones and calls `initialize` before the factory/deployer. They set `rate_` to an extreme value (e.g., very high to make borrowing unsafe or to later trigger interest-accrual reverts), permanently locking in hostile parameters for all users.","recommendation":"Restrict `initialize` to the trusted factory/deployer (e.g., `onlyFactory`) and enforce bounds on `rate_` (per interface docs: 1%–10%). Consider using OpenZeppelin’s `initializer` pattern or a constructor-like deploy-and-init flow to avoid exposure.","evidence":"`initialize` is `external` and only checks `isPoolInitialized`, then sets `interestState.interestRate = uint208(rate_)` with no access control or bounds check.","locations":["contracts/src/ERC20Pool.sol:86"]},{"severity":"medium","category":"math","title":"Interest accrual can revert for high rate * elapsed, freezing the pool","exploit_scenario":"If the interest rate reaches high values (the algorithm allows up to 500 * 1e18) and the pool is idle long enough (~97+ days at max), `accrueInterest` calls `PRBMathUD60x18.exp` with an input >= 133e18, which reverts. Since most state-changing actions call `_accruePoolInterest`, the pool becomes permanently unusable until a code change. An attacker could push utilization to raise rates, then wait for enough time to pass to trigger the revert and DoS the pool.","recommendation":"Clamp the `rate_ * elapsed_ / 365 days` input to the PRBMath exp range or accrue interest in smaller chunks. Alternatively cap the rate so that `rate * maxElapsed` never exceeds the exp limit, or switch to a math routine that supports larger exponents.","evidence":"`accrueInterest` directly calls `PRBMathUD60x18.exp((poolState_.rate * elapsed_) / 365 days)` with no guard against exp range limits.","locations":["contracts/src/libraries/external/PoolCommons.sol:220"]}]}
Metadata
{
    "codex": {
        "ran_at": "2025-12-30T21:41:34.699053+00:00",
        "returncode": 0
    },
    "summary": "Found 2 issues: unprotected initializer enabling hostile pool configuration, and potential interest-accrual DoS when the rate/elapsed product exceeds PRBMath\u2019s exp range.",
    "raw_final": {
        "summary": "Found 2 issues: unprotected initializer enabling hostile pool configuration, and potential interest-accrual DoS when the rate/elapsed product exceeds PRBMath\u2019s exp range.",
        "findings": [
            {
                "title": "Unprotected initialize lets anyone set pool interest rate",
                "category": "access_control",
                "evidence": "`initialize` is `external` and only checks `isPoolInitialized`, then sets `interestState.interestRate = uint208(rate_)` with no access control or bounds check.",
                "severity": "high",
                "locations": [
                    "contracts/src/ERC20Pool.sol:86"
                ],
                "recommendation": "Restrict `initialize` to the trusted factory/deployer (e.g., `onlyFactory`) and enforce bounds on `rate_` (per interface docs: 1%\u201310%). Consider using OpenZeppelin\u2019s `initializer` pattern or a constructor-like deploy-and-init flow to avoid exposure.",
                "exploit_scenario": "An attacker monitors for new pool clones and calls `initialize` before the factory/deployer. They set `rate_` to an extreme value (e.g., very high to make borrowing unsafe or to later trigger interest-accrual reverts), permanently locking in hostile parameters for all users."
            },
            {
                "title": "Interest accrual can revert for high rate * elapsed, freezing the pool",
                "category": "math",
                "evidence": "`accrueInterest` directly calls `PRBMathUD60x18.exp((poolState_.rate * elapsed_) / 365 days)` with no guard against exp range limits.",
                "severity": "medium",
                "locations": [
                    "contracts/src/libraries/external/PoolCommons.sol:220"
                ],
                "recommendation": "Clamp the `rate_ * elapsed_ / 365 days` input to the PRBMath exp range or accrue interest in smaller chunks. Alternatively cap the rate so that `rate * maxElapsed` never exceeds the exp limit, or switch to a math routine that supports larger exponents.",
                "exploit_scenario": "If the interest rate reaches high values (the algorithm allows up to 500 * 1e18) and the pool is idle long enough (~97+ days at max), `accrueInterest` calls `PRBMathUD60x18.exp` with an input >= 133e18, which reverts. Since most state-changing actions call `_accruePoolInterest`, the pool becomes permanently unusable until a code change. An attacker could push utilization to raise rates, then wait for enough time to pass to trigger the revert and DoS the pool."
            }
        ],
        "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
1
Total opcodes
98
Flags
delegatecall_present
View cast disassembly output
00000000: RETURNDATASIZE
00000001: RETURNDATASIZE
00000002: RETURNDATASIZE
00000003: RETURNDATASIZE
00000004: CALLDATASIZE
00000005: RETURNDATASIZE
00000006: RETURNDATASIZE
00000007: CALLDATACOPY
00000008: PUSH2 0x007f
0000000b: PUSH1 0x37
0000000d: CALLDATASIZE
0000000e: CODECOPY
0000000f: CALLDATASIZE
00000010: PUSH2 0x007f
00000013: ADD
00000014: RETURNDATASIZE
00000015: PUSH20 0x05bb4f6362b02f17c1a3f2b047a8b23368269a21
0000002a: GAS
0000002b: DELEGATECALL
0000002c: RETURNDATASIZE
0000002d: RETURNDATASIZE
0000002e: SWAP4
0000002f: DUP1
00000030: RETURNDATACOPY
00000031: PUSH1 0x35
00000033: JUMPI
00000034: REVERT
00000035: JUMPDEST
00000036: RETURN
00000037: STOP
00000038: SWAP11
00000039: SWAP7
0000003a: UNKNOWN(0xEC)
0000003b: SWAP12
0000003c: JUMPI
0000003d: UNKNOWN(0xFB)
0000003e: PUSH5 0xfbc60b423d
00000044: UNKNOWN(0x1F)
00000045: UNKNOWN(0x4D)
00000046: UNKNOWN(0xA7)
00000047: PUSH10 0x1bd35079ae78736cd615
00000052: RETURN
00000053: PUSH21 0xd3085123a210448e74fc6393c02aaa39b223fe8d0a
00000069: UNKNOWN(0x0E)
0000006a: TLOAD
0000006b: UNKNOWN(0x4F)
0000006c: UNKNOWN(0x27)
0000006d: UNKNOWN(0xEA)
0000006e: UNKNOWN(0xD9)
0000006f: ADDMOD
00000070: EXTCODECOPY
00000071: PUSH22 0x6cc20000000000000000000000000000000000000000
00000088: STOP
00000089: STOP
0000008a: STOP
0000008b: STOP
0000008c: STOP
0000008d: STOP
0000008e: STOP
0000008f: STOP
00000090: STOP
00000091: STOP
00000092: STOP
00000093: ADD
00000094: STOP
00000095: STOP
00000096: STOP
00000097: STOP
00000098: STOP
00000099: STOP
0000009a: STOP
0000009b: STOP
0000009c: STOP
0000009d: STOP
0000009e: STOP
0000009f: STOP
000000a0: STOP
000000a1: STOP
000000a2: STOP
000000a3: STOP
000000a4: STOP
000000a5: STOP
000000a6: STOP
000000a7: STOP
000000a8: STOP
000000a9: STOP
000000aa: STOP
000000ab: STOP
000000ac: STOP
000000ad: STOP
000000ae: STOP
000000af: STOP
000000b0: STOP
000000b1: STOP
000000b2: STOP
000000b3: ADD
000000b4: STOP
000000b5: PUSH30

Findings

validated = confirmed for call sinks; sink observed for others
Severity Category Tool Title Confidence Validated Confirmed Details
high access_control codex Unprotected initialize lets anyone set pool interest rate 85 no
view
evidence_json
{
    "evidence": "`initialize` is `external` and only checks `isPoolInitialized`, then sets `interestState.interestRate = uint208(rate_)` with no access control or bounds check.",
    "locations": [
        "contracts/src/ERC20Pool.sol:86"
    ],
    "recommendation": "Restrict `initialize` to the trusted factory/deployer (e.g., `onlyFactory`) and enforce bounds on `rate_` (per interface docs: 1%\u201310%). Consider using OpenZeppelin\u2019s `initializer` pattern or a constructor-like deploy-and-init flow to avoid exposure.",
    "exploit_scenario": "An attacker monitors for new pool clones and calls `initialize` before the factory/deployer. They set `rate_` to an extreme value (e.g., very high to make borrowing unsafe or to later trigger interest-accrual reverts), permanently locking in hostile parameters for all users."
}
medium math codex Interest accrual can revert for high rate * elapsed, freezing the pool 75 no
view
evidence_json
{
    "evidence": "`accrueInterest` directly calls `PRBMathUD60x18.exp((poolState_.rate * elapsed_) / 365 days)` with no guard against exp range limits.",
    "locations": [
        "contracts/src/libraries/external/PoolCommons.sol:220"
    ],
    "recommendation": "Clamp the `rate_ * elapsed_ / 365 days` input to the PRBMath exp range or accrue interest in smaller chunks. Alternatively cap the rate so that `rate * maxElapsed` never exceeds the exp limit, or switch to a math routine that supports larger exponents.",
    "exploit_scenario": "If the interest rate reaches high values (the algorithm allows up to 500 * 1e18) and the pool is idle long enough (~97+ days at max), `accrueInterest` calls `PRBMathUD60x18.exp` with an input >= 133e18, which reverts. Since most state-changing actions call `_accruePoolInterest`, the pool becomes permanently unusable until a code change. An attacker could push utilization to raise rates, then wait for enough time to pass to trigger the revert and DoS the 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
}