오라클, 플래시론, 청산 엔진
도입
Liquidation은 벌칙 기능이 아니라 lending protocol의 지급능력을 지키는 엔진이다. 담보 가치가 부채를 충분히 덮지 못하면 liquidator가 일부 부채를 갚고 담보를 할인받아 가져간다. 이 과정이 빠르고 정확히 동작해야 bad debt가 커지지 않는다.
하지만 liquidation engine의 품질은 oracle에 크게 의존한다. 가격이 stale이거나 decimals가 잘못 해석되거나 얇은 market 가격이 조작되면 정상 position이 청산되거나 부실 position이 방치될 수 있다. flash loan은 liquidator capital을 낮춰 청산을 원활하게 만들 수 있지만, 같은 atomic execution이 공격 경로에 쓰일 수도 있다.
학습 목표
- oracle freshness, deviation, decimals, confidence interval을 liquidation engine 입력으로 검증한다.
- liquidator가 repay amount, collateral seized, gas, bonus를 계산하는 흐름을 설명한다.
- flash loan과 liquidation bot이 protocol solvency에 주는 장단점을 구분한다.
개념 설명
freshness and deviation
repay candidate
bonus and gas profit
user notice and bad debt report
| 입력 | 검증해야 할 것 | 실패 시 처리 |
|---|---|---|
| Oracle answer | 양수, decimals, round freshness | borrow 제한 또는 fallback |
| TWAP | observation length, liquidity | thin pool 가격 배제 |
| Account debt | debt index 최신성 | snapshot 재계산 |
| Collateral config | threshold, bonus, cap | governance config diff |
| Gas and liquidity | liquidator profit | queue priority 조정 |
코드로 확인하기
type OracleRead = { answer: bigint; decimals: number; updatedAt: number };export function normalizeOraclePrice(read: OracleRead, now: number, maxAgeSec: number) { if (read.answer <= 0n) throw new Error("invalid oracle answer"); if (read.decimals < 6 || read.decimals > 18) throw new Error("unexpected decimals"); if (now - read.updatedAt > maxAgeSec) throw new Error("stale oracle answer"); return Number(read.answer) / 10 ** read.decimals;}export function liquidationQuote({ debtToCoverUsd, collateralPriceUsd, liquidationBonus}: { debtToCoverUsd: number; collateralPriceUsd: number; liquidationBonus: number;}) { const seizedCollateralUsd = debtToCoverUsd * (1 + liquidationBonus); return { debtToCoverUsd, seizedCollateralAmount: seizedCollateralUsd / collateralPriceUsd, seizedCollateralUsd };}첫 함수는 oracle adapter의 최소 방어선을 보여준다. 두 번째 함수는 liquidation bonus가 liquidator incentive로 어떻게 담보 수량을 바꾸는지 보여준다. 실제 구현에서는 close factor, protocol fee, slippage, gas, flash loan premium이 추가된다.
강의 포인트
| 관점 | 확인할 질문 | 증거로 남길 것 |
|---|---|---|
| Oracle | 가격이 신선하고 신뢰 가능한가 | updatedAt, decimals, deviation |
| Eligibility | 어떤 계정이 liquidatable인가 | health factor snapshot |
| Execution | liquidator가 실제로 이익을 낼 수 있는가 | repay, bonus, gas, slippage |
| Aftermath | 사용자와 protocol 상태가 맞게 바뀌었는가 | event diff와 notice |
실무 예시
인덱서[BACKEND] oracle answer가 8 decimals인데 backend가 18 decimals로 처리하면 health factor가 완전히 틀어진다. 이런 오류는 테스트넷에서는 작게 보일 수 있지만, 실제 market에서는 정상 사용자를 청산하거나 bad debt를 키운다. 모든 feed는 decimals, heartbeat, deviation threshold를 config로 저장하고 release 때 확인해야 한다.
운영flash loan 기반 liquidator는 자본 효율을 높인다. 그러나 thin DEX에서 collateral을 바로 팔아야 이익이 나는 구조라면 liquidation이 market impact를 만들 수 있다. liquidation bot은 "청산 가능"만 보지 않고 seized collateral을 어디서 얼마나 slippage로 처분할 수 있는지까지 본다.
흔한 오해와 실패 시나리오
| 오해 | 실패 시나리오 | 교정 방식 |
|---|---|---|
| oracle answer가 있으면 충분하다 | stale price로 잘못된 청산이 발생한다 | freshness와 round validation을 둔다 |
| liquidation은 자동으로 시장을 안정화한다 | keeper 부족과 gas spike로 bad debt가 커진다 | queue와 profitability를 모니터링한다 |
| flash loan은 공격 도구다 | 정상 liquidation의 자본 장벽도 낮춘다 | 사용 목적과 invariant를 분리한다 |
| bonus가 높을수록 좋다 | 사용자 손실과 toxic liquidation incentive가 커진다 | volatility와 liquidity에 맞게 parameter를 검토한다 |
실습 과제
- Safe oracle adapter 작성하기: price answer, updatedAt, decimals, maxAgeSec, maxDeviationBps를 검증하는 adapter 함수를 작성한다.
- Liquidation bot 시뮬레이터 만들기: account snapshots를 스캔해 liquidatable account, repay amount, collateral seized, expected profit을 계산한다.
완료 기준
- oracle adapter에서 stale price, invalid decimals, excessive deviation을 차단했다.
- liquidation 후보 스캔부터 repay amount와 seized collateral 계산까지 흐름을 작성했다.
근거 자료
- 02 Lending
- 09 Risk and Security
- Chainlink Data Feeds API Reference