The implementation contract does not follow best practices for upgradeable contracts: the initialize
function is declared public
, and no constructor disables initialisers on the implementation contract. As a result, anyone can call initialize
directly on the implementation contract.
According to OpenZeppelin’s recommendations, the LevelOne
contract should include a constructor with _disableInitializers()
to prevent unauthorised initialisation of the implementation contract.
A key risk in upgradeable proxy patterns is that the implementation contract is deployed independently and accessible on-chain. While the initialize
function is intended to be called via the proxy, it is also publicly accessible on the implementation contract itself.
Because initialize
is public
, any address can call it directly on the implementation contract. If initialisation has not yet occurred on the implementation contract, the caller can assign themselves as principal
. This creates a scenario where there are effectively two owners:
The principal
stored in the proxy contract’s storage
The principal
stored in the implementation contract’s storage
Functions protected by onlyPrincipal
modifiers may check ownership against the contract’s own storage, resulting in unexpected access control behaviour if called on the implementation contract directly.
While this issue does not affect the proxy storage or proxy-based interactions, it leaves the implementation contract unnecessarily exposed and potentially abusable in future deployments, integrations, or tooling relying on the implementation contract.
OpenZeppelin recommends including the following constructor in upgradeable contracts to disable initializers on the implementation contract:
This prevents any future call to initialize()
or any reinitializer()
function on the implementation contract itself.
Impact Classification: Medium - reduces risk of accidental or malicious use of implementation contract, which could result in unintended access control the implementation contract.
Likelihood Classification: Low - requires someone to intentionally call implementation contract
Manual code review of the initialize function
Reference to OpenZeppelin documentation and upgradeable contract best practices
Add a constructor with the _disableInitializers()
function to the implementation 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.