Back to Blog

Derinlemesine Analiz: Balancer V2 Açığı

Code Auditing
November 5, 2025
7 min read

6 Kasım 2025'te güncellendi: Balancer, resmi ön raporunu yayımladı [6]; bu rapor, analizimizde tespit edilen temel nedeni doğrulamaktadır.

3 Kasım 2025'te Balancer V2'nin Composable Stable Pool'ları, birden fazla zincirdeki çeşitli çatallı projelerle birlikte, toplam 125 milyon doları aşan kayıpla sonuçlanan koordineli bir saldırıya maruz kaldı. BlockSec en erken aşamada bir uyarı yayımladı [1] ve ardından ilk analizini yayınladı [2].

Bu son derece sofistike bir saldırıydı. Araştırmamız, temel nedenin değişmez (invariant) hesaplamasındaki hassasiyet kaybından kaynaklanan fiyat manipülasyonu olduğunu ve bunun BPT (Balancer Pool Token) fiyat hesaplamasını bozduğunu ortaya koymaktadır. Bu değişmez manipülasyonu, saldırganın tek bir toplu takas aracılığıyla belirli bir stabil havuzdan kâr elde etmesine olanak tanıdı. Bazı araştırmacılar değerli analizler sunmuş olsa da belirli yorumlar yanıltıcıdır ve temel neden ile saldırı süreci henüz tam olarak aydınlatılmamıştır. Bu blog, olayın kapsamlı ve doğru bir teknik analizini sunmayı amaçlamaktadır.

Temel Çıkarımlar (TL;DR)

Temel neden: yuvarlama tutarsızlığı ve hassasiyet kaybı

  • Yukarı ölçekleme işlemi tek yönlü yuvarlama (aşağı yuvarlama) kullanırken, aşağı ölçekleme işlemi çift yönlü yuvarlama (yukarı ve aşağı yuvarlama) kullanmaktadır.
  • Bu tutarsızlık, dikkatli bir şekilde oluşturulmuş bir takas yolu aracılığıyla istismar edildiğinde, yuvarlamanın her zaman protokol lehine olması gerektiği standart ilkesini ihlal eden bir hassasiyet kaybı yaratmaktadır.

Saldırının icrasıı

  • Saldırgan, hassasiyet kaybının etkisini en üst düzeye çıkarmak için yineleme sayısı ve giriş değerleri dahil olmak üzere parametreleri kasıtlı olarak hazırladı.
  • Saldırgan, tespiti atlatmak için iki aşamalı bir yaklaşım benimsedi: önce temel istismarı anlık kâr elde etmeksizin tek bir işlem içinde gerçekleştirdi, ardından varlıkları ayrı bir işlemde çekerek kârı realize etti.

Operasyonel etki ve büyütme

  • Protokol, belirli kısıtlamalar nedeniyle duraklatılamadı [3]. Operasyonların durdurulamaması, saldırının etkisini artırdı ve çok sayıda sonraki veya kopya saldırıya zemin hazırladı.

Aşağıdaki bölümlerde önce Balancer V2 hakkında temel arka plan bilgileri sunacak, ardından tespit edilen sorunları ve ilgili saldırıyı derinlemesine inceleyeceğiz.

0x1 Arka Plan

Balancer V2'nin Composable Stable Pool'u

Bu saldırıda etkilenen bileşen, Balancer V2 protokolünün Composable Stable Pool'uydu [4]. Bu havuzlar, yaklaşık 1:1 paritesini koruması beklenen (veya bilinen bir döviz kurunda işlem gören) varlıklar için tasarlanmış olup minimal fiyat etkisiyle büyük takaslar yapılmasına olanak tanıyarak benzer veya ilişkili varlıklar arasındaki sermaye verimliliğini önemli ölçüde artırır. Her havuzun, likidite sağlayıcısının havuzdaki payını temsil eden kendi Balancer Pool Token'ı (BPT) ve buna karşılık gelen temel varlıkları bulunmaktadır.

  • Bu havuz, değişmezcinin D'nin havuzun sanal toplam değerini temsil ettiği Stable Math'i (Curve'ün StableSwap modeline dayalı) benimser.
  • BPT fiyatı yaklaşık olarak şu şekilde ifade edilebilir:

Yukarıdaki formülden, D'nin kâğıt üzerinde daha küçük gösterilebilmesi durumunda (gerçek bir fon kaybı olmaksızın dahi), BPT fiyatının daha ucuz görüneceği anlaşılmaktadır.

batchSwap() ve onSwap()

Balancer V2, Vault içinde çok adımlı takaslar yapmayı mümkün kılan batchSwap() fonksiyonunu sunmaktadır [5]. Bu fonksiyona iletilen bir parametreyle belirlenen iki takas türü mevcuttur:

  • GIVEN_IN ("Verilen Giriş"): Çağıran, giriş tokenının tam miktarını belirtir ve havuz buna karşılık gelen çıkış miktarını hesaplar.
  • GIVEN_OUT ("Verilen Çıkış"): Çağıran, istenen çıkış miktarını belirtir ve havuz gereken giriş miktarını hesaplar.

Tipik olarak, bir batchSwap() işlemi onSwap() fonksiyonu aracılığıyla gerçekleştirilen birden fazla token-to-token takasından oluşur. Aşağıda, bir SwapRequest'e GIVEN_OUT takas türü atandığında yürütme yolu özetlenmektedir (ComposableStablePool'un BaseGeneralPool'dan miras aldığına dikkat ediniz):

Aşağıda, D değişmezini içeren GIVEN_OUT takas türü için amount_in hesaplaması gösterilmektedir.

Ölçekleme ve Yuvarlama

Farklı token bakiyeleri arasındaki hesaplamaları normalleştirmek için Balancer aşağıdaki iki işlemi gerçekleştirir:

  • Yukarı ölçekleme: Hesaplamalar gerçekleştirilmeden önce bakiyeleri ve miktarları birleşik bir iç hassasiyete yükseltir.
  • Aşağı ölçekleme: Sonuçları yerel hassasiyetlerine geri dönüştürür ve yönlü yuvarlama uygular (örneğin, giriş miktarları genellikle havuzun eksik ücretlendirme yapmaması için yukarı yuvarlanır; çıkış miktarları ise genellikle aşağı yuvarlanır).
Açıkça görüldüğü üzere, yukarı ölçekleme ve aşağı ölçekleme teorik olarak eşleştirilmiş işlemlerdir; sırasıyla çarpma ve bölme. Ancak bu iki işlemin uygulanmasında bir tutarsızlık mevcuttur. Özellikle, aşağı ölçekleme işleminin iki varyantı ya da yönü bulunmaktadır: divUp ve divDown. Buna karşın, yukarı ölçekleme işleminin yalnızca tek bir yönü vardır: mulDown.

Bu tutarsızlığın nedeni belirsizdir. _upscale() fonksiyonundaki yoruma göre geliştiriciler, tek yönlü yuvarlamanın etkisinin minimum düzeyde olduğunu değerlendirmektedir.

// Yukarı ölçekleme yuvarlaması her zaman aynı yönde gitmez: örneğin bir takasta giriş
// tokenının bakiyesi yukarı yuvarlanmalı, çıkış tokenının bakiyesi ise aşağı yuvarlanmalıdır. Bu,
// tüm miktarlar için aynı yönde yuvarladığımız tek yerdir; zira bu yuvarlamanın etkisinin
// minimum olması beklenmektedir (ve _scalingFactor() geçersiz kılınmadıkça yuvarlama hatası oluşmaz).

0x2 Güvenlik Açığı Analizi

Altta yatan sorun, BaseGeneralPool._swapGivenOut() fonksiyonunda yukarı ölçekleme sırasında gerçekleştirilen aşağı yuvarlama işleminden kaynaklanmaktadır. Özellikle, _swapGivenOut() fonksiyonu swapRequest.amount değerini _upscale() fonksiyonu aracılığıyla hatalı biçimde aşağı yuvarlamaktadır. Elde edilen yuvarlama değeri daha sonra _onSwapGivenOut() üzerinden amountIn hesaplanırken amountOut olarak kullanılmaktadır. Bu davranış, yuvarlamanın protokol yararına uygulanması gerektiği standart pratiğiyle çelişmektedir.

Bu nedenle, belirli bir havuz (wstETH/rETH/cbETH) için hesaplanan amountIn, gerçekte gerekli olan girişi olduğundan düşük tahmin etmektedir. Bu durum, bir kullanıcının bir temel varlığın (örn. wstETH) daha küçük bir miktarını başka bir varlıkla (örn. cbETH) takas etmesine olanak tanır; böylece azalan etkin likidite sonucunda değişmez D küçülür. Sonuç olarak, BPT fiyatı = D / totalSupply olduğundan, ilgili BPT'nin (wstETH/rETH/cbETH) fiyatı yapay biçimde düşer.

0x3 Saldırı Analizi

Saldırgan, muhtemelen tespit riskini en aza indirmek amacıyla iki aşamalı bir saldırı gerçekleştirdi:

  • Birinci aşamada, temel istismar tek bir işlem içinde gerçekleştirildi ve anlık herhangi bir kâr elde edilmedi.
  • İkinci aşamada, saldırgan varlıkları ayrı bir işlemde çekerek kârı realize etti.

Birinci aşama, parametre hesaplama ve toplu takas olmak üzere iki evreye ayrılabilir. Aşağıda bu evreler, Arbitrum üzerindeki örnek bir saldırı işlemi (TX) kullanılarak açıklanmaktadır.

Parametre Hesaplama Aşaması

Bu aşamada saldırgan, Composable Stable Pool'un mevcut durumuna (ölçekleme faktörleri, amplifikasyon katsayısı, BPT oranı, takas ücretleri ve diğer parametreler dahil) dayanarak bir sonraki (toplu takas) aşamasındaki her adımın parametrelerini hassas biçimde ayarlamak amacıyla zincir dışı hesaplamaları zincir üstü simülasyonlarla birleştirdi. İlginç biçimde, saldırgan bu hesaplamalara yardımcı olmak için bir yardımcı sözleşme de devreye aldı; bu durum, öne geçme (front-running) riskine maruz kalmayı azaltma amacı taşıyor olabilir.

Başlangıçta saldırgan, hedef havuz hakkında her tokenın ölçekleme faktörleri, amplifikasyon parametresi, BPT oranı ve takas ücreti yüzdesi dahil temel bilgileri toplar. Ardından, hassasiyet kaybına yol açmak amacıyla kullanılan hedef tokenın manipüle edilmiş miktarı olan trickAmt adlı kritik bir değeri hesaplar.

Hedef tokenın ölçekleme faktörü sF olarak gösterildiğinde, hesaplama şu şekildedir:

Bir sonraki (toplu takas) aşamasının 2. adımında kullanılan parametreleri belirlemek için saldırgan, aşağıdaki çağrı verileriyle yardımcı sözleşmenin 0x524c9e20 fonksiyonuna ardışık simülasyon çağrıları yaptı:

uint256[] balances; // Havuz tokenlarının bakiyeleri (BPT hariç)
uint256[] scalingFactors; // Her havuz tokenı için ölçekleme faktörleri
uint tokenIn; // Bu adımın simülasyonu için giriş tokenının indeksi
uint tokenOut; // Bu adımın simülasyonu için çıkış tokenının indeksi
uint256 amountOut; // İstenen çıkış token miktarı
uint256 amp; // Havuzun amplifikasyon parametresi
uint256 fee; // Havuz takas ücreti yüzdesi

Döndürülen veriler ise şunlardır:

uint256[] balances; // Takasın ardından havuz token bakiyeleri (BPT hariç)

Özellikle, başlangıç bakiyesi ve yineleme döngüsü sayısı zincir dışında hesaplanarak saldırganın sözleşmesine parametre olarak iletildi (sırasıyla 100.000.000.000 ve 25 olarak bildirildi). Her yineleme üç takas gerçekleştirir:

  • Takas 1: Takas yönünün 0 → 1 olduğu varsayılarak hedef tokenın miktarını trickAmt + 1'e çıkarır.
  • Takas 2: _upscale() çağrısında aşağı yuvarlamayı tetikleyen trickAmt miktarıyla hedef tokenu takas etmeye devam eder.
  • Takas 3: Takas edilecek miktar, havuzdaki mevcut token bakiyesinden en anlamlı iki ondalık basamak kesilerek, yani 10d210^{d-2}'nin en yakın katına aşağı yuvarlanarak türetilen bir geri takas işlemi (1 → 0) gerçekleştirir; burada d ondalık basamak sayısıdır. Örneğin, 324.816 -> 320.000.
    • Bu adımın, StableMath hesaplamasında kullanılan Newton–Raphson yöntemi nedeniyle zaman zaman başarısız olabileceğine dikkat edilmelidir. Bunu azaltmak için saldırgan, her biri orijinal değerin 9/10'unu kullanan iki yeniden deneme girişimi uygulamaktadır. Saldırganın yardımcı sözleşmesi, "BAL" tarzı özel hata mesajlarının dahil edilmesinden anlaşıldığı üzere Balancer V2'nin StableMath kütüphanesinden türetilmiştir.

Toplu Takas Aşaması

Ardından, batchSwap() işlemi üç adıma ayrılabilir:

  • Adım 1: Saldırgan, bir tokenın (cbETH) bakiyesini hassas biçimde bir yuvarlama sınırının kenarına (miktar = 9) ayarlamak için BPT'yi (wstETH/rETH/cbETH) temel varlıklarla takas eder. Bu, bir sonraki adımda hassasiyet kaybı için koşulları oluşturur.

  • Adım 2: Saldırgan daha sonra hazırlanmış bir miktar (= 8) kullanarak başka bir temel varlık (wstETH) ile cbETH arasında takas gerçekleştirir. Token miktarları ölçeklenirken aşağı yuvarlama yapılması nedeniyle hesaplanan Δx biraz küçülür (8,918'den 8'e), bu da Δy'nin olduğundan düşük tahmin edilmesine ve dolayısıyla değişmezin (Curve'ün StableSwap modelindeki D) küçülmesine yol açar. BPT fiyatı = D / totalSupply olduğundan, BPT fiyatı yapay biçimde düşürülmüş olur.

  • Adım 3: Saldırgan, düşürülmüş BPT fiyatından kâr ederek dengeyi yeniden sağlarken temel varlıkları tekrar BPT ile takas eder.

0x4 Saldırılar ve Kayıplar

Saldırıları ve buna karşılık gelen kayıpları aşağıdaki tabloda özetledik; toplam kayıplar 125 milyon doları aşmaktadır.

0x5 Sonuç

Bu olay, Balancer V2 protokolünü ve çatallı projelerini hedef alan ve önemli mali kayıplara yol açan bir dizi saldırı işlemini kapsamaktadır. İlk saldırının ardından birden fazla zincirde çok sayıda sonraki ve kopya işlem gözlemlenmiştir. Bu olay, DeFi protokollerinin tasarımı ve güvenliği açısından birkaç kritik dersi gün yüzüne çıkarmaktadır:

  • Yuvarlama Davranışı ve Hassasiyet Kaybı: Yukarı ölçekleme işleminde kullanılan tek yönlü yuvarlama (aşağı yuvarlama), aşağı ölçekleme işleminde kullanılan çift yönlü yuvarlamadan (yukarı ve aşağı yuvarlama) farklıdır. Benzer güvenlik açıklarını önlemek için protokoller daha yüksek hassasiyetli aritmetik kullanmalı ve sağlam doğrulama kontrolleri uygulamalıdır. Yuvarlamanın her zaman protokol lehine olması gerektiği standart ilkesine uymak büyük önem taşımaktadır.

  • İstismarın Evrimi: Saldırgan, tespiti atlatmak için tasarlanmış sofistike iki aşamalı bir istismar gerçekleştirdi. Birinci aşamada saldırgan, temel istismarı anlık kâr elde etmeksizin tek bir işlem içinde gerçekleştirdi. İkinci aşamada ise saldırgan, varlıkları ayrı bir işlemde çekerek kârı realize etti. Bu olay, güvenlik araştırmacıları ile saldırganlar arasındaki süregelen silahlanma yarışını bir kez daha gözler önüne sermektedir.

  • Operasyonel Farkındalık ve Tehdit Müdahalesi: Bu olay, başlatma ve operasyonel durum hakkında zamanında uyarıların önemini ve devam eden ya da kopya saldırılardan kaynaklanabilecek olası kayıpları azaltmaya yönelik proaktif tehdit tespit ve önleme mekanizmalarının gerekliliğini vurgulamaktadır.

Operasyonel ve iş sürekliliğini korurken, sektör katılımcıları varlıklarını korumak için son savunma hattı olarak BlockSec Phalcon'dan yararlanabilir. BlockSec uzman ekibi, projeniz için kapsamlı bir güvenlik değerlendirmesi yapmaya hazırdır.

Referans

[1] https://x.com/Phalcon_xyz/status/1985262010347696312

[2] https://x.com/Phalcon_xyz/status/1985302779263643915

[3] https://x.com/Balancer/status/1985390307245244573

[4] https://docs-v2.balancer.fi/concepts/pools/composable-stable.html

[5] https://docs-v2.balancer.fi/reference/swaps/batch-swaps.html

[6] https://x.com/balancer/status/1986104426667401241

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit