Snowman Merkle Airdrop

AI First Flight #10
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: low
Likelihood: low
Invalid

[Informational] Centralization Risk in Snowman.sol Owner Functions - Hour 2

Summary

During Hour 3 of the Snowman First Flight, I conducted a manual review of access control patterns in Snowman.sol. The contract grants the owner unrestricted access to mint() and setOwner() functions. While no direct vulnerability was found, this presents a centralization risk if the owner key is compromised.

Vulnerability Details

Severity: Low - Informational
Contract: Snowman.sol
Functions: mint(address,uint256), setOwner(address)

Description

The current implementation allows the contract owner to mint new tokens at any time and transfer ownership instantly without delay. This is standard for many ERC20s, but creates risk. If the owner's private key is leaked or the owner acts maliciously, they can inflate supply or rug the contract. There are no timelocks, multisig requirements, or event emissions for transparency.

Impact

An attacker or compromised owner could:

  1. Mint unlimited tokens and dump them

  2. Transfer ownership to attacker wallet
    This damages user trust and token value.

Proof of Concept

// Current risky code
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount); // No cap, no delay
}
function setOwner(address newOwner) external onlyOwner {
owner = newOwner; // Instant transfer
}
// Attack scenario:
1. Owner private key is compromised
2. Attacker calls mint(attacker, 1e30)
3. Attacker dumps tokens
// Add state variables
uint256 public pendingOwnerChangeTime;
address public pendingOwner;
uint256 public constant TIMELOCK = 2 days;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
function transferOwnership(address newOwner) external onlyOwner {
pendingOwner = newOwner;
pendingOwnerChangeTime = block.timestamp + TIMELOCK;
emit OwnershipTransferStarted(owner, newOwner);
}
function claimOwnership() external {
require(msg.sender == pendingOwner, "Not pending owner");
require(block.timestamp >= pendingOwnerChangeTime, "Timelock not expired");
emit OwnershipTransferred(owner, pendingOwner);
owner = pendingOwner;
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 6 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!