2025년 11월 6일 업데이트: Balancer가 공식 예비 보고서 [6]를 발표했으며, 이는 본 분석에서 식별한 근본 원인을 확인해줍니다.
2025년 11월 3일, Balancer V2의 Composable Stable Pool과 여러 체인에 걸친 다수의 포크 프로젝트가 조직적인 공격을 받아 총 1억 2,500만 달러 이상의 손실이 발생했습니다. BlockSec은 가장 빠른 시점에 경보를 발령했으며 [1], 이후 초기 분석을 공개했습니다 [2].
이번 사건은 매우 정교한 공격이었습니다. 조사 결과, 근본 원인은 불변량(invariant) 계산 과정에서 발생한 정밀도 손실로 인한 가격 조작이었으며, 이로 인해 BPT(Balancer Pool Token) 가격 계산이 왜곡되었습니다. 이 불변량 조작을 통해 공격자는 단일 배치 스왑으로 특정 스테이블 풀에서 이익을 취할 수 있었습니다. 일부 연구자들이 유익한 분석을 제공했지만, 특정 해석들은 오해를 불러일으키며 근본 원인과 공격 과정이 아직 충분히 명확하게 밝혀지지 않았습니다. 이 블로그는 해당 사건에 대한 포괄적이고 정확한 기술적 분석을 제시하고자 합니다.
핵심 요약 (TL;DR)
근본 원인: 반올림 불일치 및 정밀도 손실
- 업스케일링 연산은 단방향 반올림(내림)을 사용하는 반면, 다운스케일링 연산은 양방향 반올림(올림 및 내림)을 사용합니다.
- 이 불일치는 정밀도 손실을 초래하며, 정교하게 설계된 스왑 경로를 통해 악용될 경우 반올림이 항상 프로토콜에 유리하게 적용되어야 한다는 표준 원칙을 위반합니다.
공격 실행
- 공격자는 정밀도 손실의 효과를 극대화하기 위해 반복 횟수 및 입력값을 포함한 파라미터를 의도적으로 설계했습니다.
- 공격자는 탐지를 회피하기 위해 2단계 접근 방식을 사용했습니다: 즉각적인 이익 없이 단일 트랜잭션 내에서 핵심 공격을 실행한 후, 별도의 트랜잭션에서 자산을 인출하여 이익을 실현했습니다.
운영 영향 및 증폭
- 특정 제약으로 인해 프로토콜을 일시 중지할 수 없었습니다 [3]. 이러한 운영 중단 불가능성은 공격의 영향을 악화시켰으며, 이후 다수의 후속 또는 모방 공격을 가능하게 했습니다.
이후 섹션에서는 먼저 Balancer V2에 대한 핵심 배경 정보를 제공한 후, 식별된 문제점과 관련 공격에 대한 심층 분석을 제시합니다.
0x1 배경
Balancer V2의 Composable Stable Pool
이번 공격에서 영향을 받은 구성 요소는 Balancer V2 프로토콜의 Composable Stable Pool [4]이었습니다. 이 풀은 1:1 등가(또는 알려진 환율로 거래)를 유지할 것으로 예상되는 자산을 위해 설계되었으며, 최소한의 가격 영향으로 대규모 스왑을 허용함으로써 유사하거나 상관된 자산 간의 자본 효율성을 크게 향상시킵니다. 각 풀은 유동성 공급자의 풀 지분을 나타내는 자체 BPT(Balancer Pool Token)와 해당 기초 자산을 보유합니다.
- 이 풀은 불변량 D가 풀의 가상 총 가치를 나타내는 Stable Math(Curve의 StableSwap 모델 기반)를 채택합니다.
- BPT 가격은 다음과 같이 근사할 수 있습니다:
위 공식에서, D를 실질적인 자금 손실 없이도 장부상 더 작게 만들 수 있다면 BPT 가격이 더 저렴하게 보일 것임을 알 수 있습니다.
batchSwap()과 onSwap()
Balancer V2는 Vault 내에서 멀티홉 스왑을 가능하게 하는 batchSwap() 함수를 제공합니다 [5]. 이 함수에 전달되는 파라미터에 의해 결정되는 두 가지 스왑 유형이 있습니다:
- GIVEN_IN ("Given In"): 호출자가 입력 토큰의 정확한 양을 지정하면, 풀이 해당 출력 양을 계산합니다.
- GIVEN_OUT ("Given Out"): 호출자가 원하는 출력 양을 지정하면, 풀이 필요한 입력 양을 계산합니다.
일반적으로 batchSwap()은 onSwap() 함수를 통해 실행되는 여러 토큰 간 스왑으로 구성됩니다. 다음은 SwapRequest에 GIVEN_OUT 스왑 유형이 할당된 경우의 실행 경로를 설명합니다(ComposableStablePool은 BaseGeneralPool을 상속함):
다음은 불변량 D를 포함하는 GIVEN_OUT 스왑 유형에 대한 amount_in 계산을 보여줍니다.
스케일링과 반올림
서로 다른 토큰 잔액에 걸쳐 계산을 정규화하기 위해, Balancer는 다음 두 가지 연산을 수행합니다:
- 업스케일링: 계산을 수행하기 전에 잔액과 금액을 통합된 내부 정밀도로 스케일 업합니다.
- 다운스케일링: 결과를 고유 정밀도로 다시 변환하며, 방향성 반올림을 적용합니다(예: 입력 금액은 풀이 과소 청구하지 않도록 올림 처리하고, 출력 금액은 내림 처리합니다).
이 불일치의 이유는 불분명합니다. _upscale() 함수의 주석에 따르면, 개발자들은 단방향 반올림의 영향이 미미할 것으로 판단했습니다.
// Upscale rounding wouldn't necessarily always go in the same direction: in a swap for example the balance of
// token in should be rounded up, and that of token out rounded down. This is the only place where we round in
// the same direction for all amounts, as the impact of this rounding is expected to be minimal (and there's no
// rounding error unless_scalingFactor()is overriden).
0x2 취약점 분석
근본적인 문제는 BaseGeneralPool._swapGivenOut() 함수에서 업스케일링 시 수행되는 내림 반올림 연산에서 발생합니다. 특히, _swapGivenOut()은 _upscale() 함수를 통해 swapRequest.amount를 잘못 내림 처리합니다. 이렇게 반올림된 값은 이후 _onSwapGivenOut()을 통해 amountIn을 계산할 때 amountOut으로 사용됩니다. 이 동작은 반올림이 프로토콜에 유리한 방식으로 적용되어야 한다는 표준 관행에 위배됩니다.
따라서 특정 풀(wstETH/rETH/cbETH)에 대해, 계산된 amountIn은 실제 필요한 입력량을 과소평가합니다. 이를 통해 사용자는 더 적은 양의 한 기초 자산(예: wstETH)을 다른 자산(예: cbETH)으로 교환할 수 있으며, 이는 유효 유동성 감소로 인해 불변량 D를 감소시킵니다. 결과적으로 BPT 가격 = D / 총 공급량이므로, 해당 BPT(wstETH/rETH/cbETH)의 가격이 인위적으로 낮아집니다.
0x3 공격 분석
공격자는 탐지 위험을 최소화하기 위해 2단계 공격을 실행했을 가능성이 높습니다:
- 1단계에서는 단일 트랜잭션 내에서 핵심 공격이 수행되었으나, 즉각적인 이익은 발생하지 않았습니다.
- 2단계에서는 공격자가 별도의 트랜잭션에서 자산을 인출하여 이익을 실현했습니다.
1단계는 파라미터 계산과 배치 스왑의 두 단계로 더 나눌 수 있습니다. 아래에서는 Arbitrum의 공격 트랜잭션(TX) 예시를 사용하여 이 단계들을 설명합니다.
파라미터 계산 단계
이 단계에서 공격자는 오프체인 계산과 온체인 시뮬레이션을 결합하여, Composable Stable Pool의 현재 상태(스케일링 팩터, 증폭 계수, BPT 비율, 스왑 수수료 및 기타 파라미터 포함)를 기반으로 다음 단계(배치 스왑)에서 각 홉의 파라미터를 정밀하게 조정했습니다. 흥미롭게도, 공격자는 이 계산을 보조하기 위해 보조 컨트랙트를 배포했으며, 이는 프론트러닝 노출을 줄이기 위한 것이었을 수 있습니다.
처음에 공격자는 각 토큰의 스케일링 팩터, 증폭 파라미터, BPT 비율, 스왑 수수료 비율 등 대상 풀에 대한 기본 정보를 수집합니다. 그런 다음 trickAmt라는 핵심 값을 계산하는데, 이는 정밀도 손실을 유도하기 위해 사용되는 대상 토큰의 조작된 양입니다.
대상 토큰의 스케일링 팩터를 sF로 표시하면, 계산은 다음과 같습니다:
다음 단계(배치 스왑)의 2번 단계에서 사용되는 파라미터를 결정하기 위해, 공격자는 다음 calldata와 함께 보조 컨트랙트의 0x524c9e20 함수에 후속 시뮬레이션 호출을 수행했습니다:
uint256[] balances; // 풀 토큰 잔액 (BPT 제외)
uint256[] scalingFactors; // 각 풀 토큰의 스케일링 팩터
uint tokenIn; // 이 홉 시뮬레이션의 입력 토큰 인덱스
uint tokenOut; // 이 홉 시뮬레이션의 출력 토큰 인덱스
uint256 amountOut; // 원하는 출력 토큰 양
uint256 amp; // 풀의 증폭 파라미터
uint256 fee; // 풀 스왑 수수료 비율
반환 데이터는 다음과 같습니다:
uint256[] balances; // 스왑 후 풀 토큰 잔액 (BPT 제외)
구체적으로, 초기 잔액과 반복 횟수는 오프체인에서 계산되어 공격자의 컨트랙트에 파라미터로 전달되었습니다(각각 100,000,000,000 및 25로 보고됨). 각 반복은 세 번의 스왑을 수행합니다:
- 스왑 1: 스왑 방향이 0 → 1이라고 가정할 때, 대상 토큰의 양을 trickAmt + 1로 조정합니다.
- 스왑 2: trickAmt로 대상 토큰을 계속 스왑하며, 이는 _upscale() 호출에서 내림 반올림을 유발합니다.
- 스왑 3: 역스왑 작업(1 → 0)을 실행하며, 스왑할 양은 소수점 자릿수를 d라 할 때 가장 높은 두 자리 십진수를 잘라내어, 즉 의 가장 가까운 배수로 내림하여 풀의 현재 토큰 잔액에서 도출됩니다. 예: 324,816 -> 320,000.
- 이 단계는 StableMath 계산에 사용되는 Newton-Raphson 방법으로 인해 가끔 실패할 수 있습니다. 이를 완화하기 위해, 공격자는 원래 값의 9/10 폴백을 사용하는 두 번의 재시도를 구현합니다. 공격자의 보조 컨트랙트는 "BAL" 스타일의 커스텀 오류 메시지가 포함되어 있다는 점에서 알 수 있듯이, Balancer V2의 StableMath 라이브러리에서 파생되었습니다.
배치 스왑 단계
그런 다음, batchSwap() 작업은 세 단계로 나눌 수 있습니다:
-
1단계: 공격자는 BPT(wstETH/rETH/cbETH)를 기초 자산으로 스왑하여 한 토큰(cbETH)의 잔액을 반올림 경계의 가장자리(amount = 9)로 정밀하게 조정합니다. 이는 다음 단계에서 정밀도 손실을 위한 조건을 설정합니다.
-
2단계: 공격자는 조작된 양(= 8)을 사용하여 다른 기초 자산(wstETH)과 cbETH 사이를 스왑합니다. 토큰 양을 스케일링할 때 내림 반올림으로 인해, 계산된 Δx가 약간 작아지고(8.918에서 8로), 이로 인해 Δy가 과소평가되어 불변량이 작아집니다(Curve의 StableSwap 모델에서 D). BPT 가격 = D / 총 공급량이므로, BPT 가격이 인위적으로 낮아집니다.
- 3단계: 공격자는 기초 자산을 BPT로 역스왑하여 잔액을 회복하면서 낮아진 BPT 가격으로부터 이익을 취합니다.
0x4 공격 및 손실
아래 표에 공격과 그에 따른 손실을 요약했으며, 총 손실액은 1억 2,500만 달러를 초과합니다.
0x5 결론
이번 사건은 Balancer V2 프로토콜과 포크된 프로젝트들을 대상으로 한 일련의 공격 트랜잭션을 포함하며, 상당한 재정적 손실을 초래했습니다. 초기 공격 이후, 여러 체인에 걸쳐 수많은 후속 및 모방 트랜잭션이 관찰되었습니다. 이 사건은 DeFi 프로토콜의 설계 및 보안에 대한 몇 가지 중요한 교훈을 강조합니다:
-
반올림 동작과 정밀도 손실: 업스케일링 연산에 사용되는 단방향 반올림(내림)은 다운스케일링 연산에 사용되는 양방향 반올림(올림 및 내림)과 다릅니다. 유사한 취약점을 방지하기 위해, 프로토콜은 더 높은 정밀도의 산술을 사용하고 강력한 유효성 검사를 구현해야 합니다. 반올림이 항상 프로토콜에 유리하게 적용되어야 한다는 표준 원칙을 준수하는 것이 필수적입니다.
-
공격 기법의 진화: 공격자는 탐지를 회피하도록 설계된 정교한 2단계 공격을 실행했습니다. 1단계에서 공격자는 즉각적인 이익 없이 단일 트랜잭션 내에서 핵심 공격을 실행했습니다. 2단계에서 공격자는 별도의 트랜잭션에서 자산을 인출하여 이익을 실현했습니다. 이 사건은 보안 연구자와 공격자 사이의 지속적인 군비 경쟁을 다시 한번 부각시킵니다.
-
운영 인식과 위협 대응: 이 사건은 초기화 및 운영 상태에 관한 적시 경보의 중요성과 함께, 진행 중이거나 모방 공격으로 인한 잠재적 손실을 완화하기 위한 선제적 위협 탐지 및 방지 메커니즘의 필요성을 강조합니다.
운영 및 비즈니스 연속성을 유지하면서, 업계 참여자들은 BlockSec Phalcon을 최후의 방어선으로 활용하여 자산을 보호할 수 있습니다. BlockSec 전문가 팀은 귀하의 프로젝트에 대한 포괄적인 보안 평가를 수행할 준비가 되어 있습니다.
참고 문헌
[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



