Snowman Merkle Airdrop

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

Mutable Deploy Script Causes Non-Deterministic Contract Setup

Root + Impact

Description

  • Normally, a deployment helper contract should perform deterministic, repeatable actions, deploying contracts with known, predictable addresses and ownership configuration.

  • In this case, the Helper contract instantiates a new DeploySnowmanAirdrop contract and calls its deploySnowmanAirdrop() function. If DeploySnowmanAirdrop deploys new contracts every time it's called and uses internal state or msg.sender during deployment, this pattern can lead to inconsistent deployments or incorrect ownership setup.

  • During Airdrop it is expected that Snow exist before we created Merkle Tree and deployed Airdrop and Snowmen contracts.

// In Helper.sol
function helper() public returns (SnowmanAirdrop, Snow, Snowman, MockWETH) {
...
@> deployer = new DeploySnowmanAirdrop();
@> (airdrop, snow, nft) = deployer.deploySnowmanAirdrop();
...
}

Risk

Likelihood:

  • Happens when the deployment script is run multiple times (e.g. during testing or multi-stage deployments)

  • Occurs whenever DeploySnowmanAirdrop uses internal state or msg.sender to configure ownership or other state-sensitive logic

Impact:

  • Deployed contract addresses will be inconsistent across runs, breaking integration tests, frontends, or deployment tracking

  • Ownership and permissions may be misconfigured if msg.sender is incorrectly set (e.g., set to Helper instead of an externally owned account)

Proof of Concept

contract DeploySnowmanAirdrop {
function deploySnowmanAirdrop() external returns (SnowmanAirdrop, Snow, Snowman) {
// msg.sender here will be the Helper contract, NOT the deployer
Snow snow = new Snow(msg.sender); // assigns ownership
Snowman nft = new Snowman();
SnowmanAirdrop airdrop = new SnowmanAirdrop(address(snow), address(nft));
return (airdrop, snow, nft);
}
}

If Snow relies on msg.sender for Ownable permissions, the deployer loses control:

// Inside Snow.sol
constructor(address owner) {
_transferOwnership(owner); // receives address(Helper)
}

Recommended Mitigation

Use inline or stateless deployment logic where possible to ensure clear ownership and deterministic behavior.
If using a deployer contract is necessary, ensure it:

Does not rely on internal state

Accepts all configuration (e.g. owner addresses) as parameters

Does not call functions sensitive to msg.sender

- deployer = new DeploySnowmanAirdrop();
- (airdrop, snow, nft) = deployer.deploySnowmanAirdrop();
+ Snow snow = new Snow(msg.sender);
+ Snowman nft = new Snowman();
+ SnowmanAirdrop airdrop = new SnowmanAirdrop(address(snow), address(nft));
Updates

Lead Judging Commences

yeahchibyke Lead Judge 5 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.