스와프 이전: Uniswap V2 지도
Source: Dev.to
최근에 저는 블로그 포스트를 작성했는데, 그 안에서 AMM이 무엇인지, 특히 Uniswap에 대해 간단히 살펴보고, 기본적인 작동 원리에 대한 직관을 구축했습니다.
원래는 간단한 소개 정도만 할 생각이었지만, 결국 더 기술적인 내용으로 이어졌습니다. 우리는 Constant Product 공식, 그것이 어떻게 탄생했는지, 그리고 거의 전체 DeFi 영역의 초석이 되어 온 이유에 대해 다뤘습니다 (실제 탈중앙화된 공간을 의미하며, 멀티시그와 함께 등장해 DeFi라고 주장하지만 결국 해킹당하고 “프로토콜 자체가 탈중앙화돼 있다”며 우리를 비난하는 그런 프로토콜들을 말합니다—핵심은 키였습니다. 그런 큰 논리를 깨뜨릴 수 있다면 스스로를 DeFi 프로토콜이라 부르는 것을 그만두세요. 네, DeFi이다가 아니게 되면 그게 DeFi가 아닙니다. 지난 몇 년간 이런 악용 사례가 너무 많았지만, 그것이 새로운 프로토콜이 비슷한 구조를 따라 나오지 못하게 하거나, 자금을 못 모아 억대 달러 TVL에 도달하지 못하게 하는 것은 아닙니다. 길을 벗어나서 죄송합니다만, 상황이 이렇습니다).
그래서 이것이 시리즈의 두 번째 포스트입니다. 실제 흐름—스와프가 어떻게 작동하는지, 유동성이 어떻게 추가되는지 등으로 들어가기 전에, Uniswap V2가 어떻게 구성되어 있는지 5분 정도 이해하는 것이 좋습니다. 상세 계약 내용은 (다음 포스트에서 하나씩 다룰 예정) 여기서는 구조만 살펴봅니다. 마치 한 번도 들어본 적 없는 건물의 설계도와 같아서, 모든 방이 어디에 있는지는 몰라도 정문이 어디이고, 계단이 어느 방향으로 연결되는지만 알면 되는 겁니다.
여기서 사용되는 일부 용어나 개념이 낯설게 느껴져도 걱정하지 마세요. 이 시리즈를 모두 마치면 모든 것이 자연스럽게 맞물릴 것입니다. 이 포스트는 단지 맥락—즉, 따라가다 보니 방향을 잃었을 때 언제든지 다시 참고할 수 있는 지도일 뿐입니다.
Source: …
두 개의 레포, 하나의 프로토콜
사람들이 Uniswap V2 GitHub를 처음 열었을 때 가장 먼저 헷갈리는 점은 두 개의 레포지토리가 있다는 것이다: v2-core와 v2-periphery.
이는 단순히 조직적인 선택이라고 생각하기 쉽다(처음에 나도 그렇게 생각했다). 즉, 누군가 코드를 두 폴더로 나누고 멋진 이름을 붙였다고 보는 것이다. 실제로는 의도적인 아키텍처 결정이며, 왜 이렇게 나뉘었는지를 이해하면 전체를 더 쉽게 파악할 수 있다.
| 레포 | 목적 |
|---|---|
| v2-core | 실제로 여러분의 자금을 보관하는 계약들을 담고 있다. 화려한 기능도, 불필요한 요소도 없다. 이 계약들은 실제 자금을 보관하기 때문에 의도적으로 최소화되어 있으며 배포 후에는 완전히 불변이다. 관리자 키도, 업그레이드 메커니즘도, 사후에 패치할 방법도 없다. Core에서 버그가 발견되면 새로운 버전을 완전히 새로 배포하는 수밖에 없다. 처음엔 무섭게 들릴 수 있지만, 반대로 생각해 보면: 이 계약들은 여러분의 자금을 예기치 않게 조작하도록 변경될 수 없다는 뜻이다. 절대. |
| v2-periphery | Core 로직의 주변에 위치한다. Core를 사용할 수 있게 해주는 모든 것을 담고 있다: 라우팅 로직, 슬리피지 체크, 최적 금액 계산, 멀티‑홉 경로 해석 등. 중요한 점은 Periphery는 교체가 가능하다는 것이다. 내일 더 좋은 라우터가 배포되면, 사용자는 풀 자체가 전혀 변하지 않은 채로 라우터만 교체해서 사용할 수 있다. 풀은 누가 호출하든 상관하지 않는다. |
이 분리는 신뢰 경계이다. Core는 여러분이 신뢰해야 하는 부분이고, Periphery는 단지 편의성일 뿐이다.
각 저장소에 실제로 포함된 내용
Core (v2-core)
Core에는 중요한 세 개의 계약이 있습니다:
-
UniswapV2Factory– 레지스트리. 새로운 풀 계약을 배포하고 존재하는 모든 풀을 기록합니다—토큰‑쌍을 풀 주소에 매핑합니다.
흥미로운 세부 사항: 이 계약은 결정론적 배포 방식(CREATE2)을 사용합니다. 이는 풀 주소를 체인에 직접 조회하지 않고도 팩토리 주소와 두 토큰 주소만으로 오프‑체인에서 계산할 수 있음을 의미합니다. 내부적으로CREATE2는 여러 매개변수의 해시이며, 그 중 하나가 salt입니다. 동일한 매개변수를 사용하면 매번 같은 계약 주소가 생성됩니다. 다시 배포하고 싶다면 salt만 바꾸면 새로운 주소를 얻을 수 있습니다.
결정론적 배포에 대한 별도 포스트를 작성해 보겠습니다: 종류, 언제‑왜 사용하는지, 그리고 언제 완전히 피해야 하는지. -
UniswapV2Pair– 풀 자체. 배포된 각 페어 계약은 정확히 두 개의 토큰을 보유하고 모든 작업을 처리합니다:- 유동성을 추가할 때 LP 토큰을 발행,
- 유동성을 제거할 때 LP 토큰을 소각,
- 스와프 실행,
- 플래시 대출,
- Uniswap 내장 TWAP 오라클을 가능하게 하는 가격 누적값 저장.
주목할 점:
UniswapV2Pair그 자체가 ERC‑20이라는 점입니다. LP 토큰은 해당 페어 계약이 발행하는 토큰이며, 풀에 대한 LP 토큰을 보유한다는 것은 곧 그 계약의 지분을 보유한다는 의미입니다. -
UniswapV2ERC20–Pair가 상속받는 기본 ERC‑20. 표준 토큰 기능에 EIP‑2612 permit 지원을 추가해, 두 번이 아닌 한 번의 트랜잭션으로 승인 및 실행이 가능합니다.
Core에는 또한 몇 가지 유틸리티 라이브러리가 포함되어 있습니다:
| Library | Purpose |
|---|---|
Math.sol | 초기 LP‑토큰 계산에 사용되는 정수 제곱근 |
UQ112x112.sol | 가격 누적에 사용되는 고정 소수점 연산 |
지금 당장은 이들에 대해 깊게 생각할 필요는 없습니다.
Periphery (v2-periphery)
Periphery는 **UniswapV2Router02**를 중심으로 구성됩니다 – 사용자가 실제로 상호작용하는 계약입니다. DEX 프론트엔드(또는 기타 통합자)를 사용할 때 호출하는 모든 함수가 여기 있습니다. 이 계약은:
- 네 가지 핵심 작업(유동성 추가/제거, 스와프 등)을 모두 처리
- ETH가 포함된 스와프를 위해 ETH를 래핑·언래핑
- 마감 시간(데드라인) 강제
- Core와 상호작용하기 전 최적의 금액을 계산
레포에는 Router01도 존재하지만 사실상 폐기되었습니다. Router02는 수수료‑전송 토큰 및 기타 엣지 케이스에 대한 지원을 개선하면서 이를 대체했습니다.
TL;DR
v2-core= 실제로 자금을 보유하고 있는 불변이며 신뢰가 중요한 계약.v2-periphery= Core 위에 위치하며 업그레이드하거나 교체할 수 있는 가변적인 편의 계약.
이 구분을 이해하면 시리즈의 나머지 부분(스왑 흐름, 유동성 메커니즘, 플래시 론 등)이 훨씬 명확해집니다. 스왑이 실제로 어떻게 작동하는지에 대해 다음 포스트를 기대해 주세요.
Supporting the Router
UniswapV2Library는 상태 비저장 유틸리티 라이브러리로서 모든 계산 작업을 처리합니다:
- 페어 주소 조회
- 리저브 가져오기
- 스와프 출력 계산
- 다중 홉 경로 탐색
이 작업들은 상태를 건드리지 않기 때문에 라이브러리는 별도 컨트랙트로 배포되지 않고 인라인으로 컴파일됩니다.
UniswapV2OracleLibrary는 페어의 가격 누적값을 기반으로 TWAP 오라클을 구축하기 위한 헬퍼를 제공합니다. 프로토콜이 어떻게 조작에 강인한 온‑체인 가격 피드를 얻는지 궁금했다면, 이것이 그 이야기의 일부입니다.
두 리포에 포함되지 않은 헬퍼들
두 개의 외부 유틸리티 패키지가 V2에 포함되어 있으며, 계약서에서 임포트되는 것을 볼 수 있기 때문에 알아두면 좋습니다.
TransferHelper (@uniswap/solidity-lib)
ERC‑20의 transfer()는 성공 여부를 나타내는 boolean을 반환하도록 되어 있지만, 많은 토큰(가장 악명 높은 경우는 USDT)은 아무것도 반환하지 않습니다. 다음과 같은 순진한 코드는
require(token.transfer(...));
이러한 토큰에서는 전송이 성공했음에도 불구하고 리버트됩니다. TransferHelper는 저수준 호출을 사용해 두 경우를 모두 부드럽게 처리하도록 전송을 래핑합니다. 작은 차이지만, 이것이 없으면 라우터는 실제로 사람들이 사용하는 많은 토큰에서 조용히 실패하게 됩니다.
FixedPoint (@uniswap/lib)
Solidity에는 기본 부동소수점 숫자가 없기 때문에, 누적 가격 값은 시간에 따라 분수 비율을 손실 없이 누적할 방법이 필요합니다. FixedPoint는 오라클 라이브러리를 위한 고정소수점 숫자 타입을 제공하여 바로 그 문제를 해결합니다.
한 줄로 보는 호출 흐름
모든 작업에 대한 경로는 다음과 같습니다:
User → Router → Pair → User
Factory는 옆에 위치하여 Router가 필요할 때 쌍 주소를 찾거나 배포하도록 참조하지만, 매 거래의 핵심 경로에는 포함되지 않습니다. 라이브러리는 조용한 인프라로, Router에 컴파일되어 직접 호출되지 않습니다.
이것이 전체 지도입니다— 흐름을 살펴볼 때 기억하세요. Router가 Pair에 무언가를 호출하거나, Pair가 Library를 통해 무언가를 확인하는 경우, 정확히 어디에 있는지 알 수 있을 것입니다.
References
| 구성 요소 | 레포지토리 |
|---|---|
| Router | |
| Pair | |
| Factory | |
| TransferHelper | |
| FixedPoint |
다음 단계
풀에 유동성을 추가합니다. 여기서 상수‑곱 공식이 이론에 머무르지 않고 실제로 손을 더럽히기 시작합니다.
소개를 놓쳤다면:
x * y = k, 그리고 내가 더 일찍 배워야 했던 다른 것들