Stratax Contracts

First Flight #57
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: high
Likelihood: low

`initialize()` Can Be Front-Run on Proxy Deployment

Author Revealed upon completion

initialize() Can Be Front-Run on Proxy Deployment

Description

  • If the proxy deployment and initialize() call are not performed atomically (e.g., not in a factory contract or deployProxy helper), an attacker monitoring the mempool can front-run the initialize() transaction and become the owner of the proxy.

function initialize(
address _aavePool,
address _aaveDataProvider,
address _oneInchRouter,
address _usdc,
address _strataxOracle
) external initializer {
// ...
owner = msg.sender; // @audit Whoever calls initialize() first becomes owner
}
  • The initializer modifier only prevents calling initialize() a second time — it does not restrict who can call it the first time.

Risk

Likelihood:

  • Low — most modern deployment scripts use atomic proxy deployment (e.g., OpenZeppelin's deployProxy or Foundry scripts that deploy + initialize in one transaction)

  • Only exploitable if the deployment and initialization are separate transactions

Impact:

  • If exploited, the attacker becomes the full owner of the proxy contract

  • As owner, the attacker controls all leveraged positions, can drain funds via recoverTokens(), and can change the oracle

Proof of Concept

How the attack works:

  1. Deployer sends transaction 1: deploy proxy pointing to Stratax implementation

  2. Deployer sends transaction 2: call initialize(aavePool, dataProvider, router, usdc, oracle) on the proxy

  3. Attacker observes transaction 2 in the mempool and front-runs it with higher gas

  4. Attacker's initialize() call is mined first — attacker becomes owner

  5. Deployer's initialize() call reverts because the initializer modifier detects it has already been called

Expected outcome: The attacker takes ownership of the proxy contract before the legitimate deployer.

Recommended Mitigation

The root cause is that initialize() is permissionless — anyone can call it first. The fix is to ensure deployment and initialization are atomic.

Primary fix — Deploy and initialize atomically:

// In deployment script — single transaction
address proxy = Upgrades.deployUUPSProxy(
"Stratax.sol",
abi.encodeCall(Stratax.initialize, (aavePool, dataProvider, oneInchRouter, usdc, oracle))
);

Why this works: The proxy is deployed and initialized in a single transaction, leaving no window for front-running. By the time the transaction is mined, the proxy is already initialized with the correct owner.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!