Docs

Security Model

What's actually enforced in code: caps, gates, guards, and what they don't protect against.

What's actually enforced in code

5% max, ever
Fee hard cap
on swapAndBurn
Reentrancy guard
owner-only until enabled
Pre-launch gate
3% default, 20% ceiling
Slippage bound
owner/keeper by default
Caller gate

Trading-enabled gate

Before enableTrading() is called, only fee-excluded addresses (the owner, the token contract itself) can send or receive $SNOWBALL. That's the window for seeding PancakeSwap liquidity without a bot front-running the first block of public trading. The switch is one-way: it cannot be turned back off.

Reentrancy guard, proven with a malicious mock

swapAndBurn is wrapped in OpenZeppelin's ReentrancyGuard. The test suite includes a token whose burn() tries to call feeRouter.swapAndBurn() again mid-call (test/mocks/MaliciousToken.sol). The outer call reverts, proving the guard actually holds rather than just trusting the library.

Slippage bound: what it protects against, and what it doesn't

Every swap leg fetches a live quote via router.getAmountsOut() and requires the actual output land within maxSlippageBps (3% default, owner-capped at 20%) of that quote.

This is not full sandwich-attack protection

A quote fetched inside the same transaction still reflects whatever a front-running transaction already did to reserves earlier in the same block. The bound protects against gross manipulation, bad routing, and fat-finger errors. It is not protection against a determined same-block sandwich. That's why swapAndBurn is also gated (below): keep it owner/keeper-only until the pool has real depth, and submit calls through a private/MEV-protected RPC once it's opened up.

Why swapAndBurn isn't permissionless from day one

onlyKeeperOrOwner restricts swapAndBurn to the owner or a designated keeper address while permissionlessSwaps is false (the default). The owner opens it up later via setPermissionlessSwaps(true) once the pool has enough depth that the slippage bound above is a meaningful protection rather than a formality.

Ownership

All three contracts use OpenZeppelin's Ownable2Step: transferring ownership requires the new owner to explicitly accept, so a mistyped address can't accidentally lock the contracts forever.