The LevelTwo
contract does not define any meaningful initialization or reinitialization logic, despite being a replacement implementation for LevelOne
in a UUPS upgradeable system. It uses a reinitializer(2)
decorator on an empty and publicly callable graduate()
function, which does nothing and can be called by anyone, potentially locking version 2 initialization permanently.
In upgradeable contracts using OpenZeppelin’s UUPS pattern:
The original contract (LevelOne
) uses initializer()
during deployment via proxy.
Any upgraded version (LevelTwo
) must define its own reinitializer(x)
function to safely initialize new variables or verify existing state.
However, in LevelTwo
:
There is no initializeV2()
or equivalent reinitializer function.
The only function marked reinitializer(2)
is an empty and unprotected graduate()
function:
If this function is called once (by anyone), it will lock version 2 initialization forever, preventing proper setup later.
This makes it impossible to safely:
Set new state variables (principal
, usdc
, etc.)
Perform security checks on upgrade
Ensure upgrade flow completes as intended
Upgrade will appear successful, but critical variables may remain unset
All future upgrade logic relying on version reinitializer(2)
will revert
Contract logic may break or revert silently due to uninitialized state
Introduces undefined behavior and major upgrade risks
Manual review
OpenZeppelin UUPSUpgradeable documentation : https://docs.openzeppelin.com/contracts/5.x/api/proxy#Initializable
Testing on Remix with ERC1967Proxy
Remove the placeholder graduate()
function or move real logic into a properly secured and descriptive initializer:
Ensure any new logic in the upgrade path is protected with onlyPrincipal
where appropriate.
The system doesn't implement UUPS properly.
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.