This report identifies a critical design flaw in the LevelTwo.sol contract. While the LevelOne.sol contract is correctly configured for UUPS (Universal Upgradeable Proxy Standard) upgrades, the LevelTwo.sol contract, intended as its upgrade target, does not implement the necessary UUPS mechanisms. This oversight means that once the system is upgraded to LevelTwo.sol, no further UUPS-based upgrades to subsequent versions (e.g., a hypothetical LevelThree) will be possible through the standard UUPS proxy upgradeToAndCall mechanism.
The UUPS upgrade pattern requires the upgrade authorization logic (typically an _authorizeUpgrade function) to reside within the implementation contract itself. The proxy contract delegates the authorization check to the current implementation before switching to a new one.
LevelOne.sol Implementation:
This contract correctly implements UUPS upgradeability by:
Inheriting from UUPSUpgradeable.
Overriding the _authorizeUpgrade(address newImplementation) internal override onlyPrincipal {} function, restricting upgrade authorization to the principal.
LevelTwo.sol Implementation:
This contract, as provided, fails to maintain UUPS compliance:
It only inherits from Initializable and does not inherit from UUPSUpgradeable.
Consequently, it does not implement or override the _authorizeUpgrade function.
This omission breaks the chain of UUPS upgradeability.
Initial State: The proxy contract points to LevelOne.sol as its implementation.
Upgrade to LevelTwo: The principal calls graduateAndUpgrade(address _levelTwo, bytes memory) on LevelOne.sol.
Inside graduateAndUpgrade, LevelOne.sol's _authorizeUpgrade function is called, which succeeds because msg.sender is the principal.
The proxy's upgradeToAndCall function (called externally or by the principal after _authorizeUpgrade sets the stage) successfully changes the implementation pointer to LevelTwo.sol.
Attempted Upgrade from LevelTwo to LevelThree (Hypothetical):
Sometime later, a LevelThree.sol contract is developed.
The principal attempts to upgrade the proxy from LevelTwo.sol to LevelThree.sol by calling the proxy's upgradeToAndCall function.
The proxy will attempt to call the _authorizeUpgrade function on its current implementation, which is now LevelTwo.sol.
Since LevelTwo.sol does not define an _authorizeUpgrade function (nor does it inherit UUPSUpgradeable which provides a base for it), the call will fail (e.g., function not found, or revert if a non-matching selector is somehow hit).
The upgrade to LevelThree.sol is thus prevented.
The impact of this vulnerability is High:
Loss of Future Upgradeability: The primary purpose of using an upgradeable contract system is to allow for future modifications and bug fixes. This vulnerability negates that benefit beyond LevelTwo.sol.
Contradicts Project Intent: The project documentation states, "At the end of the school session (4 weeks), the system is upgraded to a new one," implying a potentially ongoing cycle of upgrades. This vulnerability breaks that cycle.
Increased Migration Costs: If future changes are needed after upgrading to LevelTwo.sol, a full data migration to a new proxy system would be required, which is significantly more complex, costly, and risky than a standard UUPS upgrade.
Manual Code Review
Analysis of OpenZeppelin UUPSUpgradeable contract patterns
To ensure continued upgradeability, the LevelTwo.sol contract must be made UUPS-compliant.
Inherit UUPSUpgradeable: Modify LevelTwo.sol to inherit from UUPSUpgradeable in addition to Initializable.
Implement _authorizeUpgrade: Add an _authorizeUpgrade function to LevelTwo.sol. This function should contain the logic to authorize subsequent upgrades, typically restricting it to the principal (or another designated upgrade admin role).
Call __UUPSUpgradeable_init: Ensure that the reinitializer function (or a dedicated initializer for LevelTwo if it were the first deployment) calls __UUPSUpgradeable_init() if it's not already implicitly handled by the Initializable chain and UUPS versioning. However, since graduate() is a reinitializer(2), the UUPS state is already initialized by LevelOne.sol. The key is the presence of _authorizeUpgrade in LevelTwo.sol.
Proposed Code Change for LevelTwo.sol:
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.