← Dashboard
BNB Smart Chain Chain ID: 56
PMA Case #—

Spartan Protocol

calcLiquidityShare()가 현재 잔고(balanceOf)를 참조하여, 직접 전송으로 풀 잔고를 부풀린 뒤 LP 소각 시 초과 인출이 가능한 취약점을 이용, ~$30.5M 탈취
LP Share Inflation Flash Loan Single Tx Protocol Logic Flaw

1. 프로토콜 의존성 구조도

전체 공격 흐름: 공격자는 PancakeSwap에서 100,000 WBNB를 플래시론으로 차입한 뒤, Spartan Pool(SPARTA/WBNB)에서 반복 스왑 → LP 획득 → 잔고 인플레이션 → LP 소각 → 초과 인출 사이클을 반복하여 풀을 고갈시켰다.
Single atomic transaction — Block #7,048,833 Attacker0x3b6e...3671 PancakeSwapFlash Loan Spartan PoolSPARTA/WBNB0x3de6...426f Utils ContractcalcLiquidityShare() Attack Contract0x2883...6A51 SPT1-WBNB LPPool Token 100K WBNB 1 Swap WBNB→SPARTA 2 Add Liq → Mint LP 3 Direct Transfer (Inflate) 4 Burn LP → Overclaim 5 balanceOf() ⚠ ! 100,260 WBNB 7

2. 종합 대시보드

2-1. 트랜잭션 정보

Date / Time2021-05-01 16:38:39 UTC
Attack TypeLP Share Inflation (Protocol Logic Flaw)
Total Loss~$30,500,000
Flash Loan100,000 WBNB (~$62.42M)
Flash Loan Fee260 WBNB (~$162,286)
Attack Cycles8 cycles (460 events)
Gas Fee~$66

2-2. 공격 실행 흐름

Step 1 — Flash Loan
PancakeSwap에서 100,000 WBNB 플래시론 차입
PancakeSwap의 CAKE-WBNB LP 풀에서 100,000 WBNB를 플래시론으로 차입. 트랜잭션 종료 시 260 WBNB(0.26%) 수수료 포함 상환 필요.
100,000 WBNB (~$62.42M)
Step 2 — Swap WBNB → SPARTA (5회 분할)
슬리피지 보호 우회를 위한 분할 스왑
Spartan Pool에서 1,913 WBNB씩 5회 반복 스왑. 슬리피지 기반 수수료 체계를 우회하기 위해 분할 실행. 총 9,566 WBNB → 2,536,613 SPARTA 획득.
9,566 WBNB → 2,536,613 SPARTA
Step 3 — Add Liquidity & Mint LP
LP 토큰 획득을 위한 유동성 추가
2,536,613 SPARTA + 11,853 WBNB를 풀에 유동성으로 추가하여 933,351 SPT1-WBNB LP 토큰 발행(mint).
933,351 LP tokens minted
Step 4 — Swap WBNB → SPARTA (10회 분할)
풀 인플레이션용 SPARTA 매집
1,674 WBNB씩 10회 반복 스왑으로 2,639,122 SPARTA 추가 확보. 이 자금은 다음 단계에서 풀 잔고 인플레이션에 사용됨.
16,740 WBNB → 2,639,122 SPARTA
Step 5 — 풀 잔고 인플레이션 ⚠
addLiquidity()를 거치지 않고 직접 전송으로 풀 잔고 부풀리기
21,632 WBNB + 2,639,122 SPARTA를 풀 컨트랙트에 직접 전송(ERC20 transfer). addLiquidity()를 통하지 않았으므로 풀의 내부 추적 변수(baseAmountPooled/tokenAmountPooled)는 갱신되지 않지만, 실제 토큰 잔고(balanceOf)는 증가. 이것이 취약점의 핵심 트리거.
21,632 WBNB + 2,639,122 SPARTA → Pool에 직접 전송
Step 6 — LP 소각 → 초과 인출
부풀린 잔고 기준으로 LP 소각하여 초과 자산 인출
933,351 LP 토큰을 소각(burn). calcLiquidityShare()가 인플레이션된 balanceOf()를 참조하여 LP 지분을 계산. 결과적으로 Step 3에서 예치한 것보다 훨씬 많은 20,694 WBNB + 2,538,199 SPARTA 인출. 11,853 WBNB만 예치했으므로 ~9K WBNB 순이익.
LP 소각 → 20,694 WBNB + 2,538,199 SPARTA (순이익 ~9K WBNB)
Step 7 — 잔여 자산 회수
인플레이션 자산을 유동성으로 추가 후 즉시 인출
Step 5에서 전송한 자산을 addLiquidity()로 추가 → 1,414,010 LP 발행 → 즉시 소각 → 2,643,882 SPARTA + 21,556 WBNB 회수.
21,556 WBNB + 2,643,882 SPARTA 회수
Step 8 — 반복 & 추가 드레인
동일 사이클 반복으로 풀 고갈
위 사이클(Step 2~7)을 총 8회 반복. 매 사이클마다 SPARTA를 풀에 스왑백하여 추가 WBNB를 확보. 반복할수록 풀 유동성이 감소하여 드레인 가능 WBNB가 급감 (사이클 1: 21,284 WBNB → 사이클 8: 1,111 WBNB).
Step 9 — Flash Loan 상환 & 이익 확정
100,260 WBNB 상환 후 순이익 확보
PancakeSwap에 100,000 + 260(수수료) = 100,260 WBNB 상환. 잔여 자금이 공격자 지갑(0x3b6e)으로 이전.
순이익: ~$30.5M (30.7K WBNB ≈ $19.16M + 4.6M SPARTA ≈ $11.56M + 기타)

2-3. 관련 엔티티 & PnL

Attacker (EOA) ATTACKER
PnL: +$30,500,000
Attack Contract ATTACKER
공격 로직 실행 컨트랙트
Spartan Pool (V1) VICTIM
PnL: −$30,500,000
PancakeSwap FLASH LOAN
PnL: +260 WBNB ($162,287)

2-4. 취약점 상세 — calcLiquidityShare()

정상 동작 (Uniswap 방식)
잔고 참조캐시된 변수
baseAmountPooled내부 추적값
인출 비율LP/totalSupply
조작 가능성없음
⚠ Spartan 구현 (취약)
잔고 참조IERC20.balanceOf()
실시간 잔고조작 가능
인출 비율LP × (현재잔고/totalLP)
조작 가능성직접 전송으로 부풀리기
LP 예치 (정상 경로)
WBNB 예치11,853
SPARTA 예치2,536,613
LP 발행933,351
기대 인출~11,853 WBNB
⚠ 인플레이션 후 실제 인출
인플레이션 전송21,632 WBNB
LP 소각933,351
실제 인출20,694 WBNB
1회 순이익~8,841 WBNB

2-5. Key Events (전체 8 사이클, 온체인 이벤트 로그 기반)

#EventFromToAmountDeviation
Flash Loan (event #819)
1Swap (Flashloan borrow)PancakeSwap LPAttack Contract100,000 WBNB
Cycle 1 (events #81–#171)
2Swapped (×5)Attack CASpartan Pool9,565.86 WBNB → 2,536,613.21 SPARTA $666 avg
($817→$538) +6.6%
3AddLiquidityAttack CASpartan Pool2,536,613 SPARTA + 11,853.33 WBNB
4Swapped (×10)Attack CASpartan Pool16,740.26 WBNB → 2,639,121.98 SPARTA $396 avg
($505→$308) −36.6%
5Direct Transfer ⚠Attack CASpartan Pool21,632.15 WBNB + 2,639,121.98 SPARTA
6RemoveLiquidity (burn)Spartan PoolAttack CA933,351 LP → 20,694.06 WBNB + 2,538,199.15 SPARTA
7AddLiquidity (잔여)Attack CASpartan Pool2,639,121.98 SPARTA + 21,632.15 WBNB
8RemoveLiquidity (회수)Spartan PoolAttack CA1,414,010 LP → 21,555.70 WBNB + 2,643,882.07 SPARTA
9Swapped (×10, drain)Attack CASpartan Pool5,182,081 SPARTA → 21,283.57 WBNB $2,563 avg
(3,562→1,230) WBNB 점감
Cycle 2 (events #174–#264)
10Swapped (×5)Attack CASpartan Pool9,565.86 WBNB → 2,786,314.99 SPARTA $732 avg
($918→$576) +17.3%
11AddLiquidity → Swapped(×10) → Inflate → Burn → Recover동일 패턴RemoveLiquidity 21,928.88 + 21,827.80 WBNB
12Swapped (×10, drain)Attack CASpartan Pool5,565,507 SPARTA → 20,499.44 WBNB $2,300 avg
(3,600→1,122) WBNB 점감
Cycle 3 (events #267–#357)
13Swapped (×5) + Exploit cycleAttack CASpartan PoolRemoveLiquidity: 23,538.47 + 22,205.56 WBNB
14Swapped (×10, drain)Attack CASpartan Pool6,041,924 SPARTA → 19,452.16 WBNB $2,010 avg
(3,635→990) WBNB 점감
Cycle 4 (events #360–#450)
15Swapped (×5) + Exploit cycleAttack CASpartan PoolRemoveLiquidity: 25,821.03 + 22,660.51 WBNB
16Swapped (×11, drain)Attack CASpartan Pool7,312,798 SPARTA → 19,323.36 WBNB $1,650 avg
(3,648→542) WBNB 점감
Cycle 5 (events #453–#543)
17Swapped (×5) + Exploit cycleAttack CASpartan PoolRemoveLiquidity: 29,059.50 + 23,188.18 WBNB
18Swapped (×11, drain)Attack CASpartan Pool8,174,506 SPARTA → 15,735.46 WBNB $1,201 avg
(3,579→629) WBNB 점감
Cycle 6 (events #546–#636)
19Swapped (×5) + Exploit cycleAttack CASpartan PoolRemoveLiquidity: 33,971.10 + 23,720.00 WBNB
20Swapped (×11, drain)Attack CASpartan Pool9,297,240 SPARTA → 12,223.30 WBNB $821 avg
(3,253→395) WBNB 점감
Cycle 7 (events #639–#729)
21Swapped (×5) + Exploit cycleAttack CASpartan PoolRemoveLiquidity: 41,285.70 + 23,971.79 WBNB
22Swapped (×10, drain)Attack CASpartan Pool9,691,157 SPARTA → 6,898.39 WBNB $444 avg
(2,155→167) WBNB 점감
Cycle 8 (events #732–#813)
23Swapped (×5) + Exploit cycleAttack CASpartan PoolRemoveLiquidity: 48,766.72 + 23,902.15 WBNB
24Swapped (×7, drain)Attack CASpartan Pool7,542,829 SPARTA → 1,111.12 WBNB $92 avg
(216→55) WBNB 급감
Flash Loan 상환 (event #819)
FSwap (Flashloan repayment)Attack ContractPancakeSwap LP100,260 WBNB → 100,000 WBNB
📊 사이클별 드레인 효율 감소: Cycle 1에서 21,284 WBNB를 드레인했으나, Cycle 8에서는 단 1,111 WBNB만 가능했다. 풀 유동성이 고갈되면서 매 사이클마다 드레인 가능 금액이 급감. 이는 공격자가 8사이클 이후 자동 중단한 이유이다.

2-6. 유사 공격 비교표

공격날짜체인취약점손실
Spartan Protocol2021-05-01BSCLP Share Inflation (balanceOf)~$30.5M
Uranium Finance2021-04-28BSCBalance Calculation Bug~$57.2M
Warp Finance2020-12-18ETHLP Token Oracle Manipulation~$7.7M
Cheese Bank2020-11-06ETHLP Token Price Manipulation~$3.3M
PancakeBunny2021-05-20BSCPrice Manipulation + Flash Loan~$45M

2-7. Root Causes & Lessons

취약점
calcLiquidityShare()가 캐시된 내부 추적 변수 대신 IERC20(token).balanceOf(pool)을 실시간 조회하여 LP 지분을 계산
슬리피지 보호 메커니즘이 분할 호출(여러 건의 소액 스왑)로 쉽게 우회 가능
addLiquidity() 없이 직접 전송된 토큰이 잔고에 반영되지만 내부 추적에는 미반영 — 이 불일치가 공격의 핵심
CertiK 감사를 받았으나 이 취약점이 발견되지 않음
교훈
유동성 계산 시 반드시 캐시된 내부 변수(baseAmountPooled/tokenAmountPooled)를 사용해야 함 — Uniswap V2의 reserve0/reserve1 패턴 참조
프로토콜 레벨에서 분할 호출 우회에 대한 추가 방어 필요 (단일 트랜잭션 내 호출 횟수 제한 등)
"독자적으로 작성한 코드"는 검증된 구현 대비 더 높은 감사 기준이 필요 — Uniswap 포크가 아닌 커스텀 AMM은 특히 주의
직접 전송(transfer)과 프로토콜 함수 호출의 차이를 항상 고려 — 잔고 불일치 공격에 대한 불변 검증(invariant check) 필수

2-8. 공격 당시 토큰 가격 (tokenPriceCache 기준)

WBNB
$624.18
온체인 검증 시세
SPARTA
$2.5135
공격 시점 시가
SPARTA (공격 후)
~$1.00
공격 후 −60% 급락
공정 교환비
248.68
SPARTA/WBNB (공정가 기준)
💡 핵심: 이 공격은 가격 오라클 조작이 아닌 프로토콜 로직 결함이다. calcLiquidityShare()가 실시간 balanceOf()를 참조하기 때문에, 직접 전송으로 풀 잔고를 부풀린 뒤 LP를 소각하면 예치보다 많은 자산을 인출할 수 있었다.

3. 자금 흐름 (Fund Flow)

단계별 버튼을 클릭하여 공격 자금 흐름을 추적하세요.
WBNB Balance
0
SPARTA Balance
0
LP Tokens
0
Status
대기
PancakeSwap Flash Loan Attacker CA 0x2883...6A51 Spartan Pool SPARTA/WBNB Attacker EOA 0x3b6e...3671 100K WBNB 9,566 WBNB 2.5M SPARTA Add Liq 933K LP 21.6K WBNB + 2.6M SPARTA (Direct Transfer ⚠) 20.7K WBNB + 2.5M SPARTA Burn LP → Overclaim Repeat drain ×N 100,260 WBNB 30.7K WBNB + 4.6M SPARTA WBNB flow SPARTA / LP flow Repay Exploit (inflate/drain)

4. WBNB 잔고 변화 (Waterfall)

Flash Loan 유입 사이클별 순이익 Flash Loan 상환 Net profit (30,842 WBNB)