# PDAIHolderAirdrop Review Notes ## Gas Review - Merkle proofs keep claims O(log n), so the contract does not store the pDAI holder list on-chain. - `pFLY`, `pDAI`, and `snapshotBlock` are immutable, reducing repeated storage reads. - Claim proofs use calldata and a single `hasClaimed` mapping write per wallet. - The contract enforces one claim per address, a global `630,000 pFLY` allocation cap, a per-wallet cap, a minimum claim amount, and a minimum pDAI snapshot balance before token transfer. - Merkle leaves include `account`, `snapshotPdaiBalance`, and `amount`, so a proof binds the claim to a specific pDAI holder balance at the published snapshot. - `stakeAndClaim()` uses exact temporary approval to the staking contract, then resets allowance to zero after the stake call. - Revert strings are used to match the repo's current testing style. If final mainnet bytecode needs further squeezing, custom errors are the next obvious gas reduction. ## Security Review Comments - The Merkle snapshot is intentionally off-chain. The published snapshot package must include the pDAI snapshot block, excluded addresses, claim JSON, Merkle root, and generation settings. - On PulseChain mainnet, constructor deployment rejects any pDAI token other than `0x6B175474E89094C44Da98b954EedeAC495271d0F`. - Required exclusions are handled in `scripts/generatePDAIHolderAirdropSnapshot.js`: dead wallet, LP pair contracts, treasury wallets, team wallets, staking contracts, and any extra excluded addresses. - Unknown multi-wallet ownership cannot be proven on-chain. The snapshot tool therefore supports `PDAI_SYBIL_CLUSTER_FILE`, which groups known linked wallets, computes `sqrt(totalClusterPdaiBalance)`, applies the cap per cluster, then splits that cluster allocation across its wallets by pDAI balance. - Fairness still depends on snapshot timing and review quality. Use an unannounced snapshot block, meaningful `MIN_PDAI_BALANCE`, a conservative `PDAI_MAX_CLAIM_PER_WALLET`, and a public exclusion/cluster list before publishing the root. - The Merkle root can only be changed before the first claim, which avoids changing eligibility after users begin claiming. - The owner can extend the claim deadline after claims start, but cannot shorten it. - `recoverNonPflyToken()` cannot recover pFLY, so the airdrop allocation cannot be pulled by generic owner recovery. - `recoverExcessPfly()` can only recover pFLY above `AIRDROP_ALLOCATION - totalClaimed`, which protects the remaining claim reserve while allowing accidental overfunds to be returned. - Expired unclaimed pFLY is split 50/50: half to the burn address and half to the staking reserve. This can only run once after the claim deadline. - `stakeAndClaim()` requires a staking contract with `stakeFor(address,uint256,uint16)`. The local `PMINTFlywheelStaking` includes this so the position belongs to the claimant, not the airdrop contract. - Supported stake locks are exactly 180, 555, and 5,555 days. - This is an internal scoped review and test pass, not a formal third-party audit. Before mainnet promotion, review the final snapshot file, excluded wallets, owner address, staking reserve, burn address, and funded pFLY transaction.