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.
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.
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
function setOwner(address newOwner) external onlyOwner {
owner = newOwner;
}
1. Owner private key is compromised
2. Attacker calls mint(attacker, 1e30)
3. Attacker dumps tokens
## Summary
Performed a 1-hour manual security review of the `Snowman.sol` contract during CodeHawks First Flight. Focus areas were access control, token minting logic, and owner privileges. No critical vulnerabilities were found. This submission documents a centralization risk.
## Vulnerability Details
**Severity:** Low - Informational
**Contract:** Snowman.sol
**Functions:** `mint(address,uint256)`, `setOwner(address)`
## Description
The contract allows the owner to call `mint()` with no cap and `setOwner()` with no delay. While `onlyOwner` is present, there is no timelock, multisig, or event emission. If the owner private key is compromised, an attacker could mint unlimited tokens or transfer ownership instantly. This is a common pattern but considered a best practice issue.
## Impact
If exploited:
1. Attacker mints unlimited supply and dumps
2. Attacker takes ownership and rugs the contract
This leads to loss of user funds and trust.
## Proof of Concept
```solidity
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
function setOwner(address newOwner) external onlyOwner {
owner = newOwner;
}
import "@openzeppelin/contracts/access/Ownable2Step.sol";
contract Snowman is Ownable2Step {
uint256 public constant TIMELOCK = 2 days;
uint256 public pendingOwnerTime;
address public pendingOwner;
event TokensMinted(address indexed to, uint256 amount);
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
emit TokensMinted(to, amount);
}
function transferOwnership(address newOwner) public override onlyOwner {
pendingOwner = newOwner;
pendingOwnerTime = block.timestamp + TIMELOCK;
}
}