Skip to main content

ZkPadHook

Source: contracts/src/hooks/ZkPadHook.sol, ZkPadHookBase.sol, CLBaseHook.sol · Interface: IZkPadHook · Port of Clanker v4 ClankerHookV2 + ClankerHookStaticFeeV2 to PancakeSwap Infinity CL. GPL-2.0-or-later.

Purpose​

For every zk-pad pool the hook:

  • lets only the factory create pools (the hook calls initialize itself, which Infinity does not call back into; every other initializer hits beforeInitialize and reverts);
  • lets only the pool's locker add liquidity, and not while the MEV module runs;
  • on each swap: records the block's pre-swap tick range, pays out earlier swaps' protocol fees, auto-collects LP fees through the locker, picks the fee (static per direction, raised by the MEV module while it runs) and returns the LP fee as the lpFeeOverride: lpFeeFor(F) = floor(0.75·F) on sells and the slightly higher buyLpFeeFor(F, pcs) on buys (below);
  • charges the protocol fee in the paired token in the same four cases as Clanker (exact in/out × buy/sell), sized at 1/3 of the LP fee the positions actually receive, so protocol : beneficiary = 25 : 75;
  • on sells, donates 75% of the part of the protocol fee above the LP fee's post-swap value back to the positions (ProtocolFeeRebated) and books the sell's reference value (lpFeeValueAccrued) for the locker's protocol top-up;
  • exempts the locker's own fee-conversion swaps, and records the LP fees fee-paying swaps earned (lpFeesAccrued), which caps what that exempt conversion may sell;
  • tells the locker whether a conversion may run now (conversionAllowed): only at a price at least as good for it as every pre-swap price of the block.

Permissions​

beforeInitialize, beforeAddLiquidity, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta. Pools use fee = DYNAMIC_FEE_FLAG (0x800000), tick spacing 200, and the permission bitmap in PoolKey.parameters.

Pool configuration​

struct PoolStaticConfigVars {
uint24 tokenFee; // total fee when the launched token is the input (sells), [1%, 5%]
uint24 pairedFee; // total fee when the paired token is the input (buys), [1%, 5%]
}

Protocol-fee math​

The pool charges swapFee = pcs + lp − pcs·lp/1e6 of its gross input, of which lpReceived = swapFee − pcs accrues to the LPs (PancakeSwap's own protocol fee pcs, read from slot0 per direction, is on top of F).

  • Buys (paired in): the protocol fee is lpReceived / 3 of the pool's paired input and comes off the trader's input first, so the buy LP fee is raised to buyLpFeeFor(F, pcs) = ceil(0.75·F / (1 − 0.25·F·(1 − pcs))). Protocol + LP is then exactly F·(1 − pcs) of the trader's gross input, 25:75.
  • Sells (token in): the LP fee is in the token. At the execution price it is worth lpReceived / (1 − swapFee) of the paired output, and the protocol fee is a third of that (exact-in from the output in afterSwap, exact-out up front in beforeSwap). The part above the LP fee's value at the post-swap price is split 25:75 again and 75% of it is donated to the in-range liquidity, which is only ever the locker's positions. A sell that ends with no in-range liquidity has no post-swap reference and rebates nothing. A seller therefore always pays the execution value; where the price ends only moves part of it between protocol and beneficiary, and the locker's top-up undoes that when the fees convert higher.

Details and tests: contracts/PORTING.md H-2, H-3, H-8 and L-9.

Functions​

FunctionAccessDescription
initializePool(token, pairedToken, tickIfToken0IsToken, tickSpacing, locker, mevModule, poolData) returns (PoolKey)factoryCreate and initialize the pool
initializeMevModule(PoolKey, bytes mevModuleData)factoryArm the MEV module after liquidity and dev buy
mevModuleSetFee(PoolKey, uint24 fee)pool's MEV module, during beforeSwapRaise this swap's total fee (ignored unless higher than normal and ≤ MAX_MEV_FEE)
mevModuleOperational(PoolId) returns (bool)anyoneWhether the module still runs; expires it if older than MAX_MEV_MODULE_DELAY
lowerFees(PoolKey, newTokenFee, newPairedFee)token adminLower the per-direction fees; never raise, never below MIN_FEE
claimProtocolFees(Currency)anyonePay held protocol fees to the factory (normally automatic on the next swap)
lpFeeFor(uint24 fee) pureSell-side LP fee (75%, rounded down) for a total fee
buyLpFeeFor(uint24 fee, uint16 pcsFee) pureBuy-side LP fee for a total fee and PancakeSwap's buy-direction fee
lpFeesAccrued(PoolId) view returns (tokenFees, pairedFees)Cumulative LP fees fee-paying swaps credited to the positions (wrapping counters); caps the locker's fee-exempt conversion
lpFeeValueAccrued(PoolId) viewCumulative paired reference value of the token-side LP fees (for the protocol top-up)
conversionAllowed(PoolId, bool zeroForOne) viewWhether a fee conversion in that direction may run now (same-block price guard)
tokenFee(PoolId), pairedFee(PoolId), tokenIsToken0, locker, mevModule, mevModuleEnabled, poolCreationTimestampviewPer-pool state
factory, MIN_FEE, MAX_FEE, MAX_MEV_FEE, MAX_MEV_MODULE_DELAY, PROTOCOL_SHARE_BPSviewConstants

Events​

EventMeaning
PoolCreatedFactory(pairedToken, token, poolId, tickIfToken0IsToken, tickSpacing, locker, mevModule)Pool created
PoolInitialized(poolId, tokenFee, pairedFee)Static fee config
FeesLowered(poolId, oldTokenFee, oldPairedFee, newTokenFee, newPairedFee)Token admin lowered fees
MevModuleDisabled(poolId)Module disabled: when the module reports its decay is over, or when mevModuleOperational finds it older than MAX_MEV_MODULE_DELAY
MevModuleSetFee(poolId, fee)Module raised a swap's fee
ClaimProtocolFees(token, amount)Protocol fees paid to the factory
ProtocolFeeClaimFailed(token, reason)Payout failed; trading continues
ProtocolFeeRebated(poolId, amount)A sell's rebate (in the paired token) donated back to the positions
LpLockerFeeClaimFailed(poolId, reason)LP fee collection failed; trading continues

Errors​

ETHPoolNotAllowed, OnlyFactory, OnlyThis, OnlyLocker, UnsupportedInitializePath, MevModuleEnabled, Unauthorized, NotInSwap, TokenFeeOutOfBounds(fee), PairedFeeOutOfBounds(fee), FeeNotLowered, UnknownPool.

Notes​

  • Native-currency (address 0) pools are rejected (ETHPoolNotAllowed); BNB pairs use WBNB.
  • Nothing depends on hookData, so any Infinity router can swap.
  • Failed fee payouts or collections emit an event instead of reverting, so a frozen recipient can never block trading.