Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

(HIGH) Tuition fees paid by students are permanently locked within the contract.

Summary

(HIGH) Tuition fees paid by students are permanently locked within the contract.

Vulnerability Details

Affected Assets

https://github.com/CodeHawks-Contests/2025-05-hawk-high/blob/main/src/LevelOne.sol#L295-L312

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
// @audit-issue Funds transferred during upgrade are locked in LevelTwo
// The UUPS upgrade mechanism transfers the contract's balance to the new implementation's address (which is the proxy address).
// However, the LevelTwo contract has no mechanism to withdraw these funds.
// The reinitializer `graduate()` in LevelTwo is empty.
// This results in permanent loss of all collected school fees.
}

https://github.com/CodeHawks-Contests/2025-05-hawk-high/blob/main/src/LevelTwo.sol#L28

contract LevelTwo is Initializable {
// @audit-issue Empty reinitializer and no withdrawal function
// The graduate() function is the reinitializer called during upgrade from LevelOne.
// It is empty and performs no logic, including fund distribution or withdrawal.
// There are no other functions in LevelTwo to access funds transferred during the upgrade.
function graduate() public reinitializer(2) {}

Impact

The school contract (LevelOne) collects school fees in USDC from students. When the principal decides to "graduate" the school to the next level (LevelTwo), they call a function that upgrades the contract logic to the LevelTwo code. During this upgrade process, all the USDC held by the LevelOne contract (which is actually held by the proxy contract) is automatically transferred to the new LevelTwo contract's address (which is still the same proxy address, but now executing the LevelTwo logic).

The problem is that the LevelTwo contract code has no way to access or withdraw this USDC. The special function that is supposed to run right after the upgrade (graduate()) is completely empty. There are no other functions in LevelTwo that allow anyone, including the principal, to get the money out. This means all the collected school fees are permanently stuck and lost inside the contract.

Tools Used

  1. Manual Review

  2. Report generation with AI assistance

Recommendations

The core issue is the lack of fund management logic in the LevelTwo contract after the upgrade.

Implement Fund Management in LevelTwo:

  • Add a function to the LevelTwo contract that allows the principal (or another authorized address) to withdraw the collected school fees (USDC). This function should likely be part of the reinitializer or a separate function callable only by the principal.

Likelihood of Exploitation: High. While not an "exploit" in the sense of malicious gain by an external attacker, the vulnerability is triggered by the intended and necessary action of upgrading the contract. The principal will eventually call graduateAndUpgrade to move to the next phase of the project. When they do, the funds will be locked. This is a guaranteed outcome of using the contract as designed with the current code.

Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Validated
Assigned finding tags:

stuck funds in system

Funds are stuck in `LevelOne()` contract after upgrade.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.