Categories: 블록체인 글

솔리디티 개발자를 위한 오라클: 제약, 위협 모델, 그리고 패턴

“실전 오라클” 시리즈 1편 — 스마트 컨트랙트 엔지니어를 위한 실습형 연재. 이 시리즈의 모든 컨트랙트는 OpenZeppelin 5.x로 컴파일하고 실제 EVM에서 검증했습니다.

한 줄 요약은 이미 아실 겁니다. 스마트 컨트랙트는 바깥세상을 호출할 수 없다. 이 시리즈는 그 제약이 실제로 여러분에게 무엇을 강제하는지, 함정이 어디 묻혀 있는지, 그리고 데모가 아니라 공격자를 상대로 살아남는 오라클 소비 코드를 어떻게 짜는지에 관한 이야기입니다.

제약을 정확히 진술하기

합의는 결정론적 재실행을 요구합니다. 모든 검증자가 여러분의 트랜잭션을 다시 계산해 동일한 상태에 도달해야 합니다. 외부 HTTP 호출은 이 모델에 자리가 없습니다. 1초 차이로 API를 호출한 두 노드가 다른 값을 볼 수 있고, 그러면 상태가 갈라지니까요. 그래서 EVM은 네트워크·파일시스템·시계(block.timestamp 외)에 대한 시스템 콜을 주지 않습니다. 컨트랙트가 “아는” 모든 것은 자기 스토리지에 쓰였거나 calldata로 전달된 것뿐입니다.

이것이 엔지니어링 언어로 표현한 오라클 문제의 전부입니다. 모든 외부 사실은 온체인 트랜잭션으로 도착해야 하고, 이는 어떤 계정이 그것에 서명하고 비용을 냈다는 뜻이며, 결국 여러분은 그 계정을 신뢰하는 것입니다. 여러분의 일은 그 신뢰를 없애는 게 — 불가능하니까요 — 아니라, 어떤 단일 행위자도 이득을 보며 거짓말할 수 없도록 신뢰를 구조화하는 것입니다.

세 가지 소싱 패턴 (그리고 각각의 적합한 상황)

실제로 손이 가는 갈래는 셋입니다. 이 시리즈는 각각에 한 편씩 할애합니다.

1. 푸시 피드 (Chainlink 등) — 탈중앙 오퍼레이터 집합이 오프체인에서 보고하고, median으로 집계해, 여러분이 AggregatorV3Interface.latestRoundData()로 읽는 aggregator에 씁니다. 직접 모을 수 없는 폭넓은 출처가 필요한 거래소 간 실물 가격(FX, 원자재, 대형 암호자산)에 씁니다. 함정은 신선도입니다. 피드는 블록마다가 아니라 편차나 heartbeat에 따라 갱신됩니다.

2. 온체인 TWAP (Uniswap V3) — DEX 풀에서 가격을 직접 읽되, 단일 블록의 밀치기가 값을 못 움직이도록 시간 평균을 냅니다. 깊은 V3 풀이 있는 자산이면서 외부 오퍼레이터 없는 순수 온체인 소스를 원할 때 씁니다. 함정은 cardinality윈도우 길이입니다.

3. 서명 쿼럼 — m-of-n 인가 서명자 집합이 같은 값에 EIP-712 서명으로 각자 보증하고, 컨트랙트는 임계치 이상일 때만 받아들입니다. 평균 낼 시장도 공개 피드도 없는 오프체인 사실에 쓰며, AI 모델 출력에도 점점 쓰입니다. 함정은 서명자 집합 거버넌스입니다.

절대적으로 최선인 건 없습니다. 진지한 프로토콜은 주 소스와 보조 소스를 조합하고, 일정 밴드를 넘는 괴리에서 revert합니다.

실제로 방어해야 하는 위협 모델

코드에 앞서, 프로덕션에서 오라클 소비자를 무너뜨리는 것들을 체화하세요.

  • 플래시론 스팟 조작. AMM의 순간 가격을 읽으면, 공격자가 한 트랜잭션에서 빌리고, 풀을 왜곡시키고, 여러분의 읽기를 악용할 수 있습니다. bZx/Harvest/Mango 계열이죠. 방어: 스팟을 절대 읽지 말고 TWAP이나 집계 피드를 쓰세요.
  • 낡은 데이터. 갱신이 멈춘 푸시 피드(오퍼레이터 장애, 체인 혼잡, L2 시퀀서 재시작)는 태연히 옛 값을 내줍니다. 방어: 모든 읽기에서 예외 없이 updatedAt을 피드 heartbeat와 대조하세요.
  • 결함/음수 답. aggregator는 int256을 반환하고, 결함 시 <= 0이 나올 수 있습니다. 방어: uint256으로 캐스팅 전에 require(answer > 0).
  • 범위 밖 값. ETH를 $0.01이나 $1M로 보고하는 망가진 피드가 청산을 몰면 안 됩니다. 방어: sanity bound.
  • 리플레이와 재정렬. 서명 데이터에서는 오래됐지만 유효한 메시지가 재제출될 수 있습니다. 방어: 단조 증가 round ID와 타임스탬프.
  • decimals 혼동. ETH/USD는 8자리, ETH/BTC는 18자리. 상수로 가정하면 실제 버그입니다. 방어: decimals()를 읽고 정규화하세요.

한 가지 주제가 보입니다. 익스플로잇은 대개 여러분 로직의 버그가 아닙니다. 여러분 코드는 짠 그대로 동작합니다. 애초에 믿지 말았어야 할 값 위에서요. 오라클 보안은 돈이 걸린 입력 검증입니다.

소비자 측 보안 체크리스트

소스가 무엇이든, 가치를 다루는 경로의 오라클 읽기는 이래야 합니다.

  1. 값이 양수이고 sane bound 안인지 검증한다.
  2. 신선도(예상 갱신 주기 대비 나이)를 검증한다.
  3. decimals를 알려진 내부 단위(관례상 18)로 정규화한다.
  4. fail closed — 어떤 검사든 실패하면 기본값을 반환하지 말고 revert한다.
  5. 폭발 반경을 제한한다 — 검사를 통과했지만 값이 여전히 틀린 경우를 위한 업데이트당 편차 캡이나 서킷 브레이커.
  6. L2에서는 시퀀서 가동 + grace period로 읽기를 게이트한다.

이 시리즈의 모든 컨트랙트는 자기 소스에 맞게 이 체크리스트를 구현하며, 실패 모드가 온체인에서 읽히도록 custom error를 씁니다.

다음 편

  • 2편 — 안전한 Chainlink. 순진한 3줄 읽기, 그것이 부르는 익스플로잇, 그리고 그걸 프로덕션급으로 만드는 열 줄. 모두가 잊는 L2 시퀀서 게이트 포함.
  • 3편 — Uniswap V3 TWAP. observe(), arithmetic mean tick 수학, 음의 무한대 방향 반올림 규칙, cardinality, 그리고 최소 윈도우를 초가 아니라 분 단위로 잡아야 하는 이유.
  • 4편 — AI·오프체인 데이터를 위한 서명 쿼럼. EIP-712 리포트, dedup을 공짜로 주는 엄격 오름차순 서명자 트릭, 리플레이 방어, 그리고 서명 쿼럼이 누가/어떤 모델/어떤 입력은 증명해도 그게 옳았는가는 결코 증명하지 못하는 이유.

딱 한 문장만 기억한다면: 모든 오라클 값을 검사를 통과하기 전까지는 적대적 calldata로 취급하라. 나머지는 디테일이고, 돈은 그 디테일에서 잃습니다.


Tags: Solidity, Ethereum, SmartContracts, Blockchain, Web3

Recent Posts

블록체인이 진짜 파는 것: “나는 못 고친다”

체인 위의 데이터 — 3부작 중 3편 · ChainLab 앞의 두 편은 배관에 관한 것이었습니다…

2개월 ago

데이터를 소비하는 것이 스마트컨트랙트일 때

체인 위의 데이터 — 3부작 중 2편 · ChainLab 1편에서 우리는 하나의 재정의에 도달했습니다. 블록체인은…

2개월 ago

무료 데이터를 블록체인에 얹어 판다는 것

체인 위의 데이터 — 3부작 중 1편 · ChainLab 솔깃한 아이디어가 하나 있습니다. 부동산 실거래가…

2개월 ago

한국에선 왜 아직? — 규제 이야기, 쉽게 (5/5)

기술은 세계 최고 수준인데, 왜 우리나라에선 AI 결제가 아직 잘 안 굴러갈까요? 답은 '실력'이 아니라…

2개월 ago

에스크로 — 돈을 안전하게 맡기는 금고, 그런데 왜 어려울까 (4/5)

중고거래 안전결제, 다들 써보셨죠? AI 결제에도 이게 필요합니다. 문제는, 생각보다 훨씬 까다롭다는 거예요. 중고거래를 떠올려…

2개월 ago

AP2 & MPP — ‘미리 허락받는’ 결제 vs ‘탭 열어두는’ 결제 (3/5)

앞 편의 x402가 자판기였다면, 이번 두 방식은 '용돈 한도'와 '술집 외상장부'에 가깝습니다. 앞 편 복습:…

2개월 ago