During initialization, `ThunderLoan.initialize()` receives `tswapAddress` as a parameter and passes it directly to `OracleUpgradeable.__Oracle_init()`, which stores it as `s_poolFactory`. This address is then used by `getPriceInWeth()` to fetch token prices, which is called by `getCalculatedFee()` on every `flashloan()` and `deposit()` call.
However, `initialize()` performs no validation on `tswapAddress` — it does not verify that the address implements `IPoolFactory` or that it is capable of returning a valid pool address. Since `initializer` prevents re-initialization, passing an incompatible or zero address permanently writes an invalid `s_poolFactory` to storage, causing every call to `flashloan()` and `deposit()` to revert when attempting to fetch the token price, with no recovery path other than a full protocol upgrade.
Likelihood:
This issue occurs any time the deployer passes an incorrect, uninitialized, or incompatible address as `tswapAddress` during the one-time initialization call, whether by human error, a misconfigured deploy script, or an untested deployment pipeline.
Impact:
Any invalid `tswapAddress` permanently breaks `flashloan()` and `deposit()` since both rely on `getCalculatedFee()` → `getPriceInWeth()` → `IPoolFactory(s_poolFactory).getPool()`, which will revert on every call with no way to recover without a full protocol upgrade.
Liquidity providers that already deposited funds before the misconfiguration is discovered cannot call `redeem()` to recover their underlying tokens if the issue also affects the exchange rate calculation, resulting in potential permanent loss of funds.
The test demonstrates that passing address(0) as tswapAddress during initialization
permanently bricks core protocol functionality with no recovery path. First, it deploys
a fresh proxy and initializes it with address(0), then proves that both deposit() and
flashloan() revert because every fee calculation chains through getPriceInWeth(), which
calls IPoolFactory(address(0)).getPool() — a call to a non-contract address. Step 3
confirms that the misconfiguration is irreversible since the initializer modifier prevents
any re-initialization attempt. Step 4 shows that even upgrading to ThunderLoanUpgraded
does not fully recover the protocol — while deposit() survives because it no longer calls getCalculatedFee(), flashloan() remains permanently broken since it still depends on
s_poolFactory, which is forever locked to address(0) in storage.
Consider validating `tswapAddress` at the beginning of `initialize()` before writing it to storage, by attempting a call to `IPoolFactory.getPool()` wrapped in a `try/catch`. If the call reverts — whether because the address is `address(0)`, an EOA, or an incompatible contract — the initialization fails immediately with a descriptive custom error, preventing an invalid `s_poolFactory` from ever being committed to storage.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.