Ochrana akumulácie poplatkov keď sa pripoja noví LP
Dátum: 2025-10-26, revidované: 2026-09-26
Repozitár: BiatecCLAMM (projects/BiatecCLAMM)
Primárny súbor: contracts/BiatecClammPool.algo.ts
Pozadie
Keď pool nazbiera swap poplatky, on-chain stav ich sleduje ako dodatočnú likviditu (LiquidityUsersFromFees, Lu) bez mintovania extra LP tokenov. Pôvodný add-liquidity tok mintoval nové LP tokeny zo surového prírastku likvidity (newLiquidity - oldLiquidity). Výsledkom bolo, že nový účastník mohol pridať likviditu a okamžite ju vybrať, čím získal pomerný (pro-rata) podiel z historických poplatkov, ktoré mali patriť pôvodným poskytovateľom likvidity (LP).
Regresia sa objavila v pool teste „new liquidity provider does not scoop pre-existing fees“, kde účet C pridá likviditu po scenári so swap poplatkami a okamžite ju vyberie. Očakávané správanie je, že účet dostane presne to, čo vložil (čistý nulový zisk).
Účtovný invariant
Každá jednotka likvidity poolu musí niekomu patriť:
Liquidity == distributedLp + LiquidityUsersFromFees + LiquidityBiatecFromFees
distributedLp(D) je ponuka LP v obehu mimo účtu poolu, preškálovaná na základnú presnosť,Luje poplatková likvidita vlastnená pomerne (pro rata) všetkými držiteľmi LP tokenov (vyplácaná cezremoveLiquidity),Lbje poplatková likvidita vlastnená spoločnosťou Biatec (vyplácaná cezremoveLiquidityAdmin).
Metóda swap zachováva tento invariant, pretože celý prírastok likvidity sa rozdelí medzi Lu a Lb.
Súhrn opravy
Každý LP token v obehu je krytý (D + Lu) / D jednotkami likvidity, pretože používateľská poplatková likvidita Lu je zdieľaná pomerne všetkými LP tokenmi pri removeLiquidity. Vkladateľ, ktorý pridá likviditu delta, preto dostane
X = delta * D / (D + Lu)
LP tokenov (zaokrúhlených nadol na desatinné miesta LP tokenu) a podiel riedenia poplatkov delta - X sa zaúčtuje do Lu (je presne 0, kým pool nevyzbieral žiadny poplatok). S hodnotou Lu' = Lu + delta - X:
- nárok nového účastníka pri výstupe:
X * (D + Lu + delta) / (D + X) == delta— presne to, čo bolo vložené, žiadne historické poplatky, - pôvodní LP:
D * (D + Lu + delta) / (D + X) == D + Lu— ponechajú si všetky historické poplatky, - invariant:
D + X + Lu' == D + Lu + delta— žiadna likvidita nezostane bez vlastníka.
Keď pool nemá žiadne nahromadené poplatky, vzorec sa zredukuje na pôvodné správanie „namintuj prírastok likvidity“.
Prečo bol kvadratický variant nahradený
Medzi 2025-10-26 a 2026-09-26 kontrakt mintoval min(delta, root), kde root riešil rovnicu X^2 + X(D + Lu - Q) - Q * D = 0 a Q = depositA * newLiquidity / assetABalance (alebo podiel aktíva B, keď sa nevložilo žiadne aktívum A). Malo to dva problémy:
Qje jednostranný odhad. Pre vklad, ktorý nie je v aktuálnom pomere poolu (čo je povolené a posúva cenu poolu), jeQnižšie ako skutočne pridaná likvidita, takže vkladateľovi bolo namintovaných menej LP tokenov, než bola likvidita, ktorou prispel.- Rozdiel
delta - Xnebol nikde zaúčtovaný. ZvýšilLiquidity, ale nepatril ani držiteľom LP, ani spoločnosti Biatec, takže ho nikdy nebolo možné vybrať.
Mainnet pool 3720188642 (Gold/GoldDAO, rozsah 128–256, LP poplatok 0,1 %, podiel Biatec 20 %) to demonštruje: tri vklady, 238 swapov a výber 100 % LP tokenov zanechali v poole L = 57.3 (približne 1.04 Gold a 61.6 GoldDAO), zatiaľ čo podiel Biatec na poplatkoch bol iba Lb = 0.045. Kompletná história je prehratá v __test__/pool/mainnet-replay-3720188642.test.ts zo súboru __test__/test-data/mainnet-pool-3720188642.json; s opraveným vzorcom výber vráti celú pozíciu a v poole zostane iba Lb.
Ďalšie zmeny vo verzii 01-06-05
- Vklad, ktorý nedosiahne jednu mikrojednotku LP (
1e-6LP), sa odmietne s chybouLP-ZERO-ERR. Predtým sa zaokrúhľoval nahor na jeden LP token, čo umožňovalo vkladateľovi 1 základnej jednotky vybrať viac, ako vložil (audit 2026-09-07, H-01). - Metóda
reconcileLiquidity(iba pre exekútora poplatkov) zaúčtuje likviditu, ktorú staršie pooly ponechali bez vlastníka; pozri Oprava živých poolov. - Metódy
removeLiquidityaremoveLiquidityAdminzdieľajú jednu výplatnú rutinu (payOutLiquidity); správanie sa nezmenilo.
Očakávania zaokrúhľovania
- LP tokeny sa zaokrúhľujú nadol na 6 desatinných miest; zvyšok pod úrovňou mikro-LP (
< 1e-6LP na vklad) zostáva v poole ako zaokrúhlenie v prospech poolu. Zámerne sa neúčtuje ako výnos z poplatkov, takželiquidityUsersFromFeeszostáva 0 až do prvého swapu;reconcileLiquiditydokáže nahromadené zaokrúhlenia pripísať držiteľom LP. - Metóda
removeLiquiditynaďalej zaokrúhľuje nadol vyplácané sumy aktív, takže výbery môžu zaostávať za vkladmi o niekoľko základných jednotiek, ktoré zostávajú v poole. - Testovacia sada Jest kontroluje, že zostatok nového účastníka sa nikdy nezvýši a toleruje iba nepatrný deficit, a prehratie mainnet histórie overuje invariant po každej likviditnej operácii s toleranciou jednej mikrojednotky LP na operáciu.
Prevádzkové poznámky
- Akákoľvek zmena kontraktu vyžaduje prepočítanie TEAL artefaktov (
npm run compile-contract) a pregenerovanie klientov (npm run generate-clientalebonpm run build) pred publikovaním balíkov. - Off-chain pomocné funkcie alebo simulácie, ktoré odhadujú namintované LP tokeny, musia používať vzorec
delta * D / (D + Lu), aby zostali v súlade s reťazcom. - Pooly nasadené s verziou
BIATEC-CLAMM-01-06-04alebo staršou by mali byť aktualizované; likvidita už osirelá v takýchto pooloch sa samotnou aktualizáciou automaticky neobnoví. - Pre kompletný viacepochový matematický dôkaz, konkrétne číselné príklady a pokrytie v testovacej sade pre viacero strán (
__test__/pool/multi-party-fee-aggregation.test.ts) pozri Agregácia likvidity viacerých strán a dôkaz spravodlivosti poplatkov.