Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: high
Likelihood: low
Invalid

Single-step collector transfer risks permanent loss of fee collection rights

Description:

The changeCollector function in Snow.sol implements a dangerous single-step ownership transfer pattern that can permanently lock out fee collection if the current collector makes an error when specifying the new collector address.

In Snow.sol:

function changeCollector(address _newCollector) external onlyCollector {
if (_newCollector == address(0)) {
revert S__ZeroAddress();
}
s_collector = _newCollector; // @audit immediate transfer, no confirmation
emit NewCollector(_newCollector);
}

The function immediately transfers collector privileges to the new address without any confirmation mechanism. If the current collector accidentally provides an incorrect address (typo, wrong contract address, or inaccessible address), the collector role becomes permanently locked, making fee collection impossible.

Attack path:

Accidental input error:

  • Current collector intends to transfer rights to 0x1234...ABCD

  • Due to copy-paste error, enters 0x1234...ABCE (single character difference)

  • Transaction executes successfully, transferring rights to uncontrolled address

  • Original collector loses access, new address doesn't respond

  • Fee collection becomes permanently impossible


Impact:

If collector rights are transferred to an inaccessible address, all accumulated fees become permanently locked

The protocol loses its primary revenue mechanism permanently

There's no way to recover from this error

Recommended Mitigation:

Implement a two-step ownership transfer pattern with confirmation mechanism:

address private s_pendingCollector;
function proposeNewCollector(address _newCollector) external onlyCollector {
if (_newCollector == address(0)) {
revert S__ZeroAddress();
}
s_pendingCollector = _newCollector;
emit CollectorProposed(_newCollector);
}
function acceptCollector() external {
require(msg.sender == s_pendingCollector, "Not pending collector");
address oldCollector = s_collector;
s_collector = s_pendingCollector;
s_pendingCollector = address(0);
emit NewCollector(oldCollector, s_collector);
}
function cancelCollectorTransfer() external onlyCollector {
s_pendingCollector = address(0);
emit CollectorTransferCancelled();
}
function getPendingCollector() external view returns (address) {
return s_pendingCollector;
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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