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.