Captain Pickhook (PHOOK) security report
Radar by SecureChain AI scanned the verified Captain Pickhook contract on BNB Smart Chain. Material findings include creator-specific buy-rule bypasses, creator control over the buyer-rewards reserve, external payroll transfer dependencies, and V2 pool sell-accounting risks. Slither was not run.
Captain Pickhook does not expose an owner upgrade path or post-launch mint function in the reviewed source, but it contains meaningful centralized and configuration-dependent risks. The creator has privileged buy behavior, can withdraw funds reserved for buyer rewards, and an enabled external payroll module can cause transfer failures. Several trading restrictions can also materially limit transfers if enabled at launch.
Trust score: 58/100. Source verified: Yes. Chain: bsc. Contract: 0x7c274b22fe7e051232b75d7b49f712276e98e519.
This report was last modified on 2026-10-08 and contains 5 material findings.
medium finding: Creator bypasses most buy restrictions when receiving tokens
When a buy sends tokens to the immutable creator address, _buy sets dev to true and skips the principal non-developer checks. This bypass includes market-hour restrictions, entanglement gates, wallet and trade caps, anti-dump buy limits, gas caps, minimum buys, slow mode, repeat-buyer checks, buy limits, sniper-jail assignment, and dip-only validation.
Evidence: In _buy(address to, uint256 amount), the code assigns bool dev = to == creator and wraps the restrictions in if (!dev). The creator address is immutable and set in the constructor. The same function still records the transaction as a buy after the bypass.
Recommendation: Treat the creator exception as a privileged trading capability. If uniform market rules are intended, remove the bypass or restrict it to an explicit launch phase with auditable limits. Publish the configured rule mask and test creator-directed buys against every enabled restriction.
medium finding: Creator can withdraw the buyer-rewards reserve
The creator can withdraw any amount from rewardsPot without checking whether the funds are needed for configured buyer rewards. This can make future reward obligations unfunded, although amounts already moved into owed balances are tracked separately.
Evidence: withdrawRewards(uint256 amount) only checks msg.sender == creator, subtracts amount from rewardsPot, and pays the creator. It does not compare the withdrawal with pending reward commitments or enforce a reserve requirement. Buyer rewards are later deducted from rewardsPot in _buy when the reward rule is active.
Recommendation: Separate committed rewards from withdrawable surplus, enforce a reserve equal to outstanding obligations, or remove creator withdrawal authority after funding. Add tests covering withdrawals before and after qualifying buys.
medium finding: External payroll integration can block token transfers
If the PAYROLL rule is enabled at deployment, every token movement calls the immutable payroll companion twice through sync. A revert, gas failure, or incompatible behavior in that external contract causes the entire token transfer to revert, creating a contract-level transfer availability dependency.
Evidence: In _move, when _has(RuleIdV2.PAYROLL) is true, the token calls IPayrollBox(payroll).sync(from, _onPayroll(from), isSell) and then sync(to, _onPayroll(to), false) before trade processing completes. The payroll address is supplied as an immutable constructor companion.
Recommendation: Use a narrowly specified and independently reviewed payroll contract, minimize synchronous cross-contract calls, and provide a tested failure-tolerant design where appropriate. Verify the deployed companion implementation and test transfers for all sender and recipient classes.
medium finding: Direct transfers into the V2 pool are treated as sells
The sell-detection path cannot distinguish a real V2 swap from a donation or liquidity transfer into the pair. Any transfer whose recipient is pool2 returns true from _swapped because the function immediately returns true when out is false. Such transfers update sell counters and apply sell restrictions, which can distort rule state or cause otherwise valid pool-directed transfers to revert.
Evidence: In _move, isSell is set when to == p2. _swapped(to, false) is then called, and its V2 branch executes if (!out) return true. _sell subsequently updates stats.sells, resets buysSinceSell, and applies configured sell-window, anti-dump, sell-curve, taper, cooldown, jail, and other checks.
Recommendation: Use pair reserve and invariant checks, router context, or an explicit liquidity-operation path to distinguish swaps from donations and liquidity changes. Add tests for direct transfers, liquidity additions, skims, and router sells.
medium finding: Certain enabled sell rules can become permanently unusable with zero divisors
The sell-curve and taper calculations divide by configuration values without validating them. If either rule is enabled with a zero denominator, sell execution reverts instead of applying a bounded restriction, potentially locking sells until the rule is lifted or expires.
Evidence: In _sell, the SELL_CURVE branch divides by big, derived from r.curveBigBagBps, and the TAPER branch divides by r.taperSoldBps. These values are constructor-supplied and there is no explicit nonzero validation in HooksTokenV2.
Recommendation: Validate all denominator parameters in the factory before deployment and add defensive nonzero checks in the token. Reject invalid rule configurations rather than deploying a token whose sell path can revert for every attempted sell.