Protocol's Promise & Invariant:
The project documentation establishes a clear separation of roles. The Claimer is responsible for withdrawing tokens, while the Owner has administrative duties. A key limitation for the owner is explicitly stated: they "cannot claim faucet tokens." This implies a core invariant: tokens held in the faucet contract are exclusively reserved for claimers and are inaccessible to the owner for personal withdrawal.
The Issue:
This core invariant is broken by a logical flaw in the burnFaucetTokens() function. Instead of burning tokens directly from the contract's balance, the function first transfers the entire token balance of the faucet to the owner (msg.sender) and then burns the specified amountToBurn from the owner's now-inflated balance.
This creates a backdoor for the owner to bypass the "cannot claim" restriction and drain the entire token supply intended for the community. A call to burnFaucetTokens(1) would result in all tokens being rugged from the faucet.
This behavior directly contradicts the protocol's stated limitations and breaks the trust model where the faucet's funds are supposed to be segregated from the owner's control.
Likelihood: High
This flawed logic is triggered every time the burnFaucetTokens() function is called, regardless of the owner's intent. The function's design does not provide a way for the owner to burn a small amount without first sweeping the entire contract balance. Therefore, the likelihood of the dangerous state change (full fund transfer) occurring is high, as it's the only way the function can be used.
Impact: High
The entire supply of faucet tokens can be drained from the contract, making it impossible for any user to claim them. This completely defeats the purpose of the faucet. Even if the owner's intention is benign (e.g., to burn a small number of tokens), the function forces a full withdrawal of all funds designated for the community, breaking the protocol's core promise and trust model.
The following Foundry test demonstrates that the owner can drain all faucet tokens by calling burnFaucetTokens() with a minimal amount of 1, thereby breaking the "owner cannot claim" invariant.
PoC Test Code (test/TokenRugPull.t.sol):
Test Execution and Results:
The test will pass. The logs show the faucet's balance dropping to zero, while the owner's balance increases by the full amount, proving the rug pull capability and the violation of the protocol's core promise.
The burnFaucetTokens function should not involve transferring tokens to the owner. It should burn tokens directly from the faucet contract's own balance. The standard _burn function in OpenZeppelin's ERC20 implementation accepts the account from which to burn as a parameter.
The corrected implementation should burn from address(this).
This change aligns the function's behavior with its name and purpose, prevents the owner from draining the faucet's funds, and restores the protocol's trust model.
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.