Le hook et l'anti-snipe
Un hook Uniswap v4 est un contrat que le PoolManager appelle à des moments précis d'un
swap. L'implémentation actuelle de StockFunHook utilise six permissions :
beforeInitialize, beforeAddLiquidity, beforeSwap, afterSwap, et les deux permissions
de delta qui lui permettent de prélever. Depuis le 2026-10-02, son adresse porte les 14
permissions v4 ; les callbacks qu'elle n'utilise pas laissent passer.
L'adresse encode les permissions
v4 lit les bits de poids faible de l'adresse d'un hook pour savoir quand l'appeler. L'adresse n'est donc pas choisie : elle est minée en CREATE2 jusqu'à tomber sur une valeur dont les bits correspondent aux permissions déclarées.
Depuis le 2026-10-02, l'adresse minée est celle d'un proxy, avec les 14 bits de permission à un. Un upgrade remplace l'implémentation derrière cette adresse sans la déplacer : il n'y a jamais à re-miner, et une implémentation future peut utiliser n'importe quel callback. L'actuelle laisse passer les callbacks qu'elle n'utilise pas : ils renvoient leur sélecteur et, là où un delta est attendu, zéro. L'initialisation du proxy vérifie que son adresse porte chaque bit.
Conséquence pratique : l'adresse minée dépend du bytecode du proxy et des arguments de
son constructeur, qui portent l'adresse de l'implémentation. Un nouveau déploiement doit
être re-miné. C'est aussi pourquoi foundry.toml fixe bytecode_hash = "none" — sans ça,
l'adresse minée ne correspond plus au contrat réellement déployé.
La permission beforeAddLiquidity, ajoutée le 2026-10-01, a changé les bits eux-mêmes :
l'adresse a été re-minée, et tout déploiement antérieur à cette date est périmé. Le proxy du
2026-10-02 les a changés à nouveau : tout déploiement antérieur à cette date est périmé
aussi.
Le prélèvement
Sur un achat, la taxe est prise dans beforeSwap, sur l'ETH entrant, avant que le swap
ait lieu. Le hook retire sa part du PoolManager et retourne un delta qui la débite de
l'acheteur. Le routeur StockFun et le lock de liquidité versent l'ETH de l'acheteur au
PoolManager avant le swap : leurs achats ne puisent jamais dans l'ETH que le
PoolManager détient déjà.
Tout autre routeur v4 paie le même taux de taxe. Mais un routeur qui règle l'ETH de
l'acheteur après le swap, comme le V4Router de v4-periphery avec son encodage par défaut,
voit la taxe prise sur l'ETH que le PoolManager détient déjà, et son achat échoue quand la
taxe est plus grande. Cela peut arriver sur un PoolManager qui détient peu d'ETH, celui
d'un testnet par exemple. Un intégrateur doit verser l'ETH de l'acheteur avant le swap,
comme le fait le routeur officiel. Les ventes ne sont pas concernées. Le pipeline de
sécurité du 2026-10-01 a documenté cette limite. Prendre la taxe en claims du PoolManager
la lèverait pour tout routeur, mais ferait passer les 2 % de la trésorerie de payés pendant
le trade à crédités puis payés plus tard : cette option a été écartée le 2026-10-05, et la
limite reste telle quelle (R2F-1).
Sur une vente, elle est prise dans afterSwap, sur l'ETH sortant, une fois le montant
connu.
Dans les deux cas le hook répartit immédiatement : trésorerie, créateur, équipe, buyback. Seule la ligne trésorerie part pendant le trade, envoyée au vault du marché. Les trois autres lignes sont créditées sur le hook et payées en dehors de tout trade.
Depuis le 2026-10-05, un vault qui refuse cet envoi, après un upgrade raté par exemple,
n'arrête plus son marché : le hook lui doit le montant (treasuryOwed, événement
TreasuryOwed), et n'importe qui le paie à ce vault, et à lui seul, par payTreasury ;
l'appel échoue et garde la dette tant que le vault refuse. Les achats et les ventes
continuent pendant ce temps, et le keeper retente le paiement à chaque passage. Jusque-là,
l'envoi échouait bruyamment : un vault qui ne pouvait pas recevoir faisait échouer chaque
trade de son marché, ventes comprises, et l'erreur TreasuryTransferFailed a disparu avec
ce comportement.
La part créateur est en pull : elle s'accumule sur le hook, marché par marché, et le
créateur la réclame quand il veut, en une transaction par marché (claimCreatorFees).
Depuis le 2026-10-05, il peut aussi la réclamer vers une autre adresse
(claimCreatorFeesTo), si lui-même ne peut pas recevoir d'ETH, un contrat sans receive
par exemple ; seul le créateur l'appelle, et l'adresse ne peut pas être nulle.
Depuis le 2026-10-01, les parts équipe et buyback sont créditées de la même façon, un solde
chacune, et payées par claimTeamFees() et claimBuybackFees(). N'importe qui peut les
appeler ; elles ne paient que les wallets équipe et buyback que la factory désigne au
moment de la réclamation. Le BuybackBurner prélève lui-même son solde au début de chaque
burn. Un wallet qui refuse l'ETH ne retarde que son propre paiement : sa réclamation échoue
et le solde reste sur le hook.
Jusque-là, ces deux parts étaient envoyées pendant le trade, avec un repli sur un séquestre
quand l'envoi échouait. L'audit de sécurité du 2026-09-29 a montré qu'un wallet qui
acceptait l'envoi puis appelait le PoolManager pouvait bloquer le trading sur tous les
pools. Le séquestre n'existe plus.
Ce que détient le hook
Entre deux transactions, le hook détient ce qu'il doit : les frais des créateurs, les soldes
de l'équipe et du buyback, et, depuis le 2026-10-05, ses dettes envers les vaults. Le reste
y a été envoyé par erreur. L'ETH qu'il reçoit d'un autre que le PoolManager est compté à
part, comme égaré (strayEth). L'owner du protocole peut le sortir par rescue, jusqu'à
ce montant, ainsi que tout token, le hook n'en détenant jamais : rien de ce qu'il doit ne
sort par là. L'ETH qui arrive sans être compté — forcé sans appel, par un self-destruct, ou
versé par le PoolManager hors de la taxe — attend un upgrade.
Seul le lock ajoute de la liquidité
Depuis le 2026-10-01, beforeAddLiquidity refuse tout ajout de liquidité dans un pool
StockFun, sauf celui du lock de liquidité.
Une position placée juste à côté du prix courant agit comme un ordre limite : le swap d'un trader la traverse et la convertit, et le trader paie la taxe, tandis que le propriétaire de la position l'ajoute et la retire sans jamais payer les 5 %, ni, pendant les dix premiers blocs, la taxe anti-snipe. L'audit de sécurité du 2026-09-29 l'a reproduit. Les pools ne prélèvent aucune commission de LP par défaut : aucun usage légitime n'a besoin d'une position tierce.
Les encaissements de frais du lock lui-même, à delta de liquidité nul, passent par le chemin du retrait de liquidité, que l'implémentation actuelle laisse passer : ils ne sont pas touchés. La récupération du mode fin non plus, qui retire les positions par le même chemin : voir Le lancement en deux positions.
L'anti-snipe dégressif
Un pool v4 est vivant dès son initialisation. Sans protection, les premiers blocs suivant la création seraient raflés par des bots. Depuis le 2026-09-28, ces blocs paient une taxe plus forte, qui baisse à chaque bloc, à l'achat comme à la vente. Avec les réglages par défaut :
| Bloc depuis le lancement | Taxe |
|---|---|
| 1 | 80 % |
| 2 à 10 | 72, 64, 56, 48, 40, 32, 24, 16, 8 % |
| 11+ | Régime normal, 5 % |
Un bot qui achète au bloc d'ouverture paie 80 % d'emblée : le snipe est perdant.
Trois choses méritent d'être expliquées.
L'achat du créateur au lancement n'a besoin d'aucune vérification d'identité. Le seul
swap que le LiquidityLock exécute est l'achat optionnel du créateur, à l'intérieur de son
propre callback de création. Donc « l'appelant est le lock » implique « on est dans la
transaction de création », et cet achat paie les 5 % normaux.
Le surplus a sa propre destination. Les 5 % normaux gardent leur répartition
habituelle. La part au-dessus va à la trésorerie du marché, donc aux holders à l'airdrop
suivant ; un vault qui la refuse en devient créancier, comme pour la ligne trésorerie. Sur
le marché $STOCKFUN, elle va au solde de l'équipe sur le hook, payé par
claimTeamFees().
La whitelist a besoin d'identité, et c'est le point subtil. Le hook voit le routeur, pas
l'acheteur. Le routeur StockFun transporte donc l'adresse de son appelant dans les données
du hook, et le hook ne fait confiance à ce champ que si l'appelant est bien le routeur
officiel, celui qu'enregistre la factory et que l'owner peut changer à tout moment
(accepté le 2026-09-28). Aucun tx.origin n'est utilisé nulle part dans ce protocole.
Limite documentée : une adresse whitelistée qui passe par un agrégateur tiers pendant les dix premiers blocs n'est pas exemptée.
La whitelist
Fixée par le créateur du marché. Les adresses qui y figurent paient les 5 % normaux pendant les blocs anti-snipe. Jusqu'au 2026-09-28, la liste était tenue par l'owner et commune à tous les marchés, précisément pour qu'elle ne puisse pas servir d'avantage d'initié ; un créateur peut désormais exempter ses propres wallets. Validé le 2026-09-28 : la liste est fixée dans la transaction de création, publique, immuable, et plafonnée à 20 adresses par défaut.
Le marché $STOCKFUN
La taxe dégressive s'applique aussi. $STOCKFUN n'a pas de créateur externe : son surplus
va aux frais réclamables de l'équipe, et sa whitelist est fixée par l'owner au
lancement.
Les réglages
Depuis le 2026-10-05, chaque chiffre de cette page est un réglage de l'owner de StockFun,
changé sur le hook avec setTaxSettings : la taxe, 5 % par défaut ; ses lignes, 2 / 2 / 0,5
sur un marché lancé et 2 / 2,5 sur $STOCKFUN, le buyback prenant le reste ; la taxe
anti-snipe au bloc d'ouverture, 80 % ; sa baisse par bloc, 8 points ; le nombre de blocs
qu'elle dure, bloc de création compris, 10 ; et la plus grande whitelist, 20 adresses.
Un changement vaut dès le swap suivant, sur chaque pool, y compris un pool encore dans ses blocs anti-snipe : la baisse se calcule avec les réglages en vigueur, depuis le bloc de lancement du pool. Quels que soient les réglages, l'anti-snipe ne prélève jamais moins que la taxe. Le setter refuse un taux au-dessus de 100 % et un barème dont les lignes dépassent la taxe. Le plafond de la whitelist est vérifié à la création d'un marché : une liste déjà fixée reste telle quelle.
Les upgrades
Depuis le 2026-10-02, l'owner de StockFun peut upgrader le hook, avec effet immédiat. La
taxe, sa répartition, l'anti-snipe et les whitelists décrits sur cette page sont ceux de
l'implémentation actuelle. Un upgrade garde l'adresse du hook et doit garder le même
PoolManager ; la factory, que le hook lit et qui décide qui peut l'upgrader, est fixée
dans l'implémentation.