The LevelOne contract follows OpenZeppelin’s ERC1967Proxy upgradeability pattern. When the proxy is deployed without passing the encoded initialize() call as data to the proxy constructor, a critical time window is created where any actor can initialize the contract before the legitimate owner does.
This call to initialize() is executed via delegatecall, writing directly to the proxy’s storage. OpenZeppelin’s initializer modifier ensures that this function can only be called once. As a result, any subsequent initialization attempt — including one from the legitimate principal — will revert with InvalidInitialization().
This enables an attacker to:
Initialize the system with controlled values (e.g., setting themselves as the principal),
Block the legitimate initialization,
Permanently disable the contract by locking the proxy in an unusable state.
Although deployment scripts are not formally within the scope of this audit, this risk is structural, as the contract does not enforce its own secure initialization internally, relying instead on external deployment logic.
Therefore, this is a design limitation, not merely an implementation oversight.
Proxy initialization by an external actor before the legitimate owner.
Permanent loss of control over the system.
Complete Denial of Service (DoS) of the upgradeable contract.
Possibility for an attacker to set critical parameters or roles maliciously.
Deployment of LevelOne and ERC1967Proxy.
Between the proxy deployment and the contract initialization, there is a time window that an attacker exploits to initialize LevelOne
.
The legitimate owner attempts to initialize the contract, but the function is protected by the initializer()
modifier and cannot be re-initialized, causing it to revert.
Revert when the owner attempts to initialize the contract:
Result:
Shows that the principal is the attacker:
Manual Review, Foundry
The initialize() call should be passed as an encoded parameter at the time of proxy deployment, ensuring atomic initialization:
To further protect against unauthorized or accidental direct calls to the implementation contract, consider adding the onlyProxy modifier to the initialize() function. This ensures that initialization can only occur through the proxy, preventing any direct interaction with the logic contract:
The system can be re-initialized by an attacker and its integrity tampered with due to lack of `disableInitializer()`
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.