Orijinal başlık: "Teklif %0, Uygulama %12,8: Uniswap v4 'Zehirli Havuz' Dinamik Ücret Tuzağı İncelemesi"
Orijinal yazar: Wang Zihao, BitsLab
22-23 Ağustos 2026 tarihlerinde, BNB Chain üzerindeki bir Uniswap v4 USDC/WBNB dinamik ücret havuzu, toplayıcı tekliflerinde %0 ücret gösterirken, kullanıcıların gerçek işlemlerinden %12,8 LP ücreti kesildi. Yaklaşık 29 saat boyunca bu havuzda 21.086 işlem gerçekleşti, toplam 131.888 dolar ücret birikti ve ardından havuz kapatıldı. Tüm mağdur işlemler başarılıydı: sözleşme çalınmadı, çıktı kullanıcının belirlediği alt sınırın üzerindeydi, hiçbir anormal sinyal yoktu — fark sessizce ücrete dönüştü.
Bu makale, örnek bir işlemi başlangıç noktası olarak alıyor:
(0x7615dec2ce80506ec461a47739eb8533ac2bc7c605ca56c255964481b76363d0: kullanıcı 5.926,90 USDT yatırdı, beklenenden 757,43 USDT daha az aldı), bu havuzun "teklif %0, uygulama %12,8" işlemini nasıl başardığını analiz ediyor: ücret fonksiyonunun üç katmanlı değerlendirmesi, etkisiz kılınan kayma koruması, diğer LP'leri kapı dışında tutan beyaz liste. Tüm sonuçlar zincir üstü verilere ve yerel kontrollü doğrulamaya dayanmaktadır.
Kullanıcı toplayıcı arayüzünden takas başlattıktan sonra, tüm zincir şu şekilde işliyor:

Bu zincirde iki arka plan gerçeği var; sonraki her değerlendirme katmanı bu iki nokta üzerine kuruludur.
Önce v4 tarafına bakalım: Hook'un işlem bazında ücreti geçersiz kılmasına izin veriyor. PoolKey.fee = 0x800000 dinamik ücret havuzunu bildirir; Hook, beforeSwap'ın üçüncü dönüş değerinde 0x400000 | fee taşır. v4-core'da bu dönüş değerini alan kod (Pool.sol:303-305):

isOverride() geçersiz kılma bayrağını kontrol eder, removeOverrideFlagAndValidate() 0x400000'ı çıkarır ve MAX_LP_FEE = 1.000.000'u (%100) aşmadığını doğrular, ardından bu ücret oranıyla mevcut swap'ı gerçekleştirir ve Swap olayına yazar. Yani Hook ne döndürürse, kullanıcı onu öder — çıktı amountOutMin'in üzerinde kaldığı sürece. Bu resmi bir özelliktir; volatiliteye ve envantere göre ücret ayarlamak meşru kullanımlardır.
Şimdi bu Hook'un kendisine bakalım: yalnızca iki yetki bildiriyor. v4, yetkileri sözleşme adresinin düşük 14 bitine kodluyor (Hooks.sol:29-47), dağıtım sırasında doğrulanıyor ve sonradan değiştirilemiyor. v4-core tarafındaki bayrak tanımları ile adres çözümlemesi karşılıklı okunuyor:

Zincir üzerinde ölçülen getHookPermissions() dönüşü bitmap ile tutarlı: yalnızca bu iki bit doğru.
Hook'un kaynak kodu yok. Bu makale, bayt kodundan ücret fonksiyonunun tam metnini derleme dışı bırakıyor—aşağıda halihazırda zincir üzerinde çalışmış bu mantığın davranış analizi yer alıyor:

Şimdi katman katman inceleyelim. İlk iki katman "bunun bir simülasyon olup olmadığı" sorusunu yanıtlıyor, üçüncü katman ise gerçek işlemin ne kadar ödeyeceğini belirliyor.
Ücret fonksiyonu çağrıldıktan sonra önce tx.origin karşılaştırılır: üç sabit adresten herhangi birine eşitse doğrudan geri çekilme kademesini döndürür. Geri çekilme kademesi şu anda %0 olarak yapılandırılmıştır.
Üç adres rastgele seçilmemiştir. eth_call'da from belirtilmediğinde, işlemin origin'i sıfır adresine düşer—toplayıcı toplu fiyat sorgulaması genellikle belirtmez. Yerel fork'ta doğrudan doğrulama:

İlk katman yalnızca bu üç adresi tanır. Sonraki katman, normal bir adres belirtildikten sonra Hook'un nasıl ayırt edebileceğine bakıyor.
tx.origin'i normal bir adresle değiştirip yeniden denendiğinde, dönüş hâlâ %0—ilk katman atlatıldı, ancak ücret değişmedi. Bu, ilk katmanın tek değerlendirme olmadığını gösteriyor; ikinci katmana konumlanıyoruz: gasleft() * 10100 / 10000 >= gate.
Toplayıcı fiyat teklifi alışkanlığı çok yüksek gas verir, aday rotaların simülasyonda gas yetersizliğinden başarısız olmasını önlemek için; gerçek işlem Hook'a girmeden önce gas, Router, yetkilendirme ve ön atlamalar tarafından tüketilmiştir. Aynı kod, üç tür gas:

Ayrım çizgisi ölçülen değere göre 16.85M–16.9M gas arasında yer alıyor. 30M ile 2.59M arasındaki fark, "fiyat teklifi" ile "yürütme" arasındaki farktır.
İlk iki katmanı geçen gerçek işlemler üçüncü katmana girer: blok ortamı parmak izi hashlenir ve 10.000'e göre mod alınır, sonucun düştüğü aralığa göre ilgili ücret kademesi döndürülür. Mevcut yapılandırmada üç kademe 8% / 10% / 10% olup, boşta kalırsa 0%'a geri döner.
Bu katmanda dikkate değer iki tasarım var. Parmak izine calldata'nın üç alanı karıştırılmıştır—agregatör teklifinin calldata'sı ile gerçek yönlendirmenin calldata'sı zaten farklıdır, dolayısıyla parmak izi de doğal olarak farklıdır. Ayrıca, işlem bazında sözde rastgelelik ücret oranına dağılım kazandırır; birkaç işleme bakarak düzeni fark etmek zordur, basit kural numaralandırması bunu engelleyemez.
Zincir üstü toplam 21.086 olayda 19 farklı tarihsel ücret kademesi (6,8%–28%) görülmüştür; bu, ücret kademelerinin yönetici fonksiyonu tarafından ihtiyaca göre yeniden yapılandırıldığını gösterir; örnek işlem gerçekleştiğinde yürürlükte olan oran 12,8%'dir.

Bu noktada "zehirli havuz" için bir tanım yapılabilir—üç koşulu aynı anda karşılayan bir v4 havuzu: PoolKey.fee dinamik ücret bayrağı taşır; ücret kararı, kamuya açık piyasa durumunu değil, yürütme ortamı sinyallerini (gas, origin, blok ortamı) okur; teklif simülasyonu ile zincir üstü yürütmedeki ücret sistematik olarak ayrışır ve bundan fayda sağlayan taraf zehirli havuz dağıtıcısıdır. Ayrım noktası ücretin neyi okuduğudur: herhangi bir çağırana aynı ücreti döndüren şey programlanabilir piyasa tasarımıdır; "kim fiyat soruyor"a göre farklı ücret döndüren şey ise yönlendirme sistemine karşı bir aldatmacadır.
Buraya kadar ücret ayrışmasının mekanizması tamamlanmıştır. Ancak ücret alındıktan sonra işlemin neden hâlâ başarılı olduğu bir sonraki sorudur.
Önce bu işlemin tam yolunu ve tutarlarını görelim:

İlgili Swap olayı yürürlükteki ücreti fee alanına kaydeder: 128000. v4'te ücret birimi 1.000.000'u 100% olarak alır; 128000 ise 12,8% demektir. Bu işlemin kaybı iki açıdan ölçülebilir: havuz açısından ücret WBNB girdisi üzerinden alınır; tüm rota açısından bakıldığında stabilcoin giriş-çıkış farkı 757,43 USDT olup %12,7794 kayıp vardır; buna ön atlama ücretleri ve peg sapması da dahildir. İki rakam arasındaki fark yalnızca 0,02 puandır; bu da kaybın ana kaynağının bu havuzun LP ücreti olduğunu gösterir.
amountOutMin yalnızca nihai çıktı alt sınırını doğrular, ara adımların her birinde ne kadar ücret alındığına bakmaz:

%12,8'lik ücret oranı tamamen %20,32'lik tamponun içinde kalıyor, çıktı hâlâ alt sınırın üzerinde—işlem başarılı, geri dönüş yok. Ana akım stablecoin rotalarında olağan varsayılan %0,1–%1'dir; bu rota 20 katından fazla alan tanıyor. Kullanıcı geniş tamponu "daha güvenli" sanıyor, ama gerçekte rotadaki her sözleşmeyi karşı tarafın özdenetimine bırakıyor. Bu hesabı grafiğe dökelim:

Beyaz liste mantığı beforeAddLiquidity içinde, tersine derleme sonucu şöyle:

Likidite eklemek üç kapıdan geçmeli: önce her v4 Hook'ta bulunan PoolManager çağrı kontrolü, sonra mutlaka resmi PositionManager üzerinden geçmeli, son olarak pozisyon sahibinin beyaz listesi doğrulanmalı. Listede olmayan adresler burada sözleşme tarafından geri çevrilir, diğer LP'ler giremez, ücret geliri seyreltilmez, tamamı dağıtıcıya kalır. Liste, bir yönetici fonksiyonu (seçici 0xc4452e52) tarafından adres bazında yönetilir.
21.086 işlemi bir arada incelediğimizde dağılım net bir örüntü sergiliyor: %0 kademesinde 6.946 işlem, 12.947.751 dolar hacim, işlem başına 1.864 dolar; ücretli kademede 14.140 işlem, 1.120.106 dolar hacim, işlem başına 79 dolar. Büyük hacimler %0 kademesinde yoğunlaşıyor, ücretler küçük işlemlerde toplanıyor. Büyük tutarlı %0 işlemlerin en mantıklı açıklaması dağıtıcının yüksek gas'la kendi kendine işlem yapması: yüksek gas ile agregatör kotasyonunu tutturma aynı karara dayanıyor, kendi kendine işlemin ücret maliyeti sıfıra yakın. Şişirilen hacim havuzu piyasa sitelerinin sıralamasına taşıyor (anlık görüntü 24 saatlik hacmi yaklaşık 10,82 milyon dolar, 16.671 işlem gösteriyor), agregatörün gözünde bu, derinliği iyi ve ücreti düşük bir havuz.
Üçüncü değerlendirmede 19 adet geçmiş ücret kademesinden bahsedilmişti; bunlar tersine derlenen ücret yapılandırma fonksiyonundan geliyor (seçici 0x4d909a45, kamuya açık imza kaydı yok):

Dört ücret kademesi ve eşik değerleri bir bütün olarak paketlenip keccak(poolId, 2) depolama yuvasına yazılıyor. Zincir üzerinde ölçülen bu yuvanın okuması 0x0138800186a00186a0 olup fonksiyonun paketleme formatıyla bölüm bölüm örtüşüyor. Ücret kademeleri burada her an ayarlanabilen parametreler, dağıtım anında sabitlenmiş değil.
Bu havuz 08-22 06:57'de oluşturuldu, 07:13'te ilk swap gerçekleşti, 21:39 ise bu makalenin örnek işlemiydi; 08-23 12:25'te son işlem yapıldı ve ardından kaydı silindi. Toplam aktif süre yaklaşık 29 saat olup toplam işlem hacmi 14.067.857 dolar, komisyon geliri ise 131.888 dolar (dağıtıcı maliyeti düşülmemiş). İnceleme sırasında kayıt işareti yeniden etkinleştirilmiş durumdaydı—havuz hâlâ orada ve her an yeniden etkinleştirilebilir.
Tüm saldırı zincirini birleştirelim:

Tüm zincirin nedenselliği yukarıdaki grafikte olup üç koşulun hepsi gereklidir: simülasyonun ilk iki katmanı vurması, gerçek işlemin üçüncü katmana düşmesi ve tamponun ücret oranından büyük olması. Herhangi biri geçerli olmazsa bu yöntem işe yaramaz.
İlk nokta, toplayıcının fiyat teklifi yöntemindedir. Kök neden, simülasyon ile yürütmenin aynı yoldan gitmemesidir: fiyat teklifi idealize edilmiş havuz düzeyinde sorgulama ile sıfır adres ve yüksek gas kullanırken, gerçek işlem gerçek kimlik ve tüketilmiş gas ile gelir. Hook tam olarak bu farkları okuyabildiği için ücret oranı çatallanır. Çözüm, simülasyonu yürütmeye yaklaştırmaktır—gerçekte gönderilecek Router calldata'sı, gerçek from/to ve zincir üstü işleme yakın gas ile simülasyon yapın; işlem sonrası Swap olayını çözerek fee'yi doğrulayın, teklifle uyuşmuyorsa ağırlığı düşürün veya listeden çıkarın.
İkinci nokta, kullanıcının kayma ayarındadır. Kök neden, min_out'un çok gevşek verilmesidir: %20,32'lik tampon %12,8'lik ücret oranını tamamen yutmuş ve işlem her zamanki gibi başarılı olmuştur. Ana akım stablecoin rotaları için günlük kullanımda %0,1–%1 yeterlidir; işlem başlatmadan önce bu sayıya bir bakın; cüzdanlar ve ön uçlar varsayılan değerleri sıkılaştırarak kullanıcıların çoğu riskini engelleyebilir.
Üçüncü nokta, rota kabulündedir. Kök neden, herhangi bir dinamik ücretli havuzun doğrudan fiyat teklifi rekabetine katılabilmesidir: açık kaynağı ve denetim kaydı olmayan bir Hook bile şişirilmiş işlem hacmiyle önerilen rotalara girebilir. Güvenilir geçmişi olmayan dinamik ücretli havuzlar için varsayılan olarak rotaya almamak veya belirgin şekilde ağırlık düşürmek en güvenli yaklaşımdır.
·Örnek işlem:
https://bscscan.com/tx/0x7615dec2ce80506ec461a47739eb8533ac2bc7c605ca56c255964481b76363d0 (blok 117497524)
·Havuz başlatma işlemi:
https://bscscan.com/tx/0x8fa72ef72d77b61f715dc9ce90548249ab045c6d78716078cd5ab425bbe69a05
·PoolManager:
0x28e2ea090877bf75740558f6bfb36a5ffee9e9df
·Havuz Kimliği:
0x36e5540e9dedc02229fe8a82aa5b10c0bf07d1fa74e4f2ffe0efd00fa1a36aea
·Hook:
0xd111b3ddd92e627f1864520c770e913ec04e0880 (kaynak kodu yok, sözde kodu bu makale için bağımsız olarak derlenmiştir; yönetici 0x08b03e1a5444d469f4dc954e74d3f662c94a6b13)
·İşlemler ve ücretler: Bu havuzdaki toplam 21.086 Swap olayı işlem bazında kümülatif olarak toplanmıştır (eth_getLogs), WBNB aynı işlemin yürütme fiyatından dönüştürülmüştür; gelir brüt değerdir, dağıtıcı maliyetleri düşülmemiştir
BlockBeats Resmi Topluluğuna Katılın:
Telegram Abonelik Grubu: https://t.me/theblockbeats
Telegram Sohbet Grubu: https://t.me/BlockBeats_App
Twitter Resmi Hesabı: https://twitter.com/BlockBeatsAsia