[BokLee series] 3. Yield Space 분석

서울대학교 블록체인 학회 디사이퍼(Decipher) BokLee팀 에서 Fixed Rate Lending 프로토콜에 대한 글을 시리즈로 연재합니다. 본 시리즈는 Yield Protocol 및 자동 청산의 작동 원리를 자세하게 설명한 글이며, 따라서 해당 주제를 처음 접하시는 분의 경우 이해가 어려울 수 있습니다. 이해가 어려울 것 같은 분들은 본 글을 읽기 전에 관련 Youtube 영상을 먼저 시청하시는 것을 추천합니다. 해당 동영상은 BokLee 팀이 2022년 DE-FERENCE 에서 진행한 본 글 시리즈에 대한 쉬운 설명입니다.

[BokLee: Road to an Ultimate Lending Protocol]

  1. 랜딩 프로토콜에 필요한 두가지 요소
  2. Yield Protocol 분석
  3. Yield Space 분석
  4. 랜딩 프로토콜 개선 — Partial Liquidation

Authors
이우진 of Decipher DeFi Research Team BokLee
최유진
Seoul Nat’l Univ. Blockchain Academy Decipher(@decipher-media)
Reviewed by: 이재복

1. What is Yield Space?

Yield Space는 Yield protocol을 통해 만들어지는 fyToken(e.g. fyDAI, 무이자 채권에 해당)과 fyToken의 타겟 자산(e.g. DAI)을 교환하기 위해 특별히 고안된 DEX입니다. 다만 Yield Protocol 분석 글에서 볼 수 있듯, fyToken을 판매하는 것은 곧 돈을 빌리는 것과 같고, 반대로 구매하는 것은 돈을 빌려 주는 것과 동일합니다. 이러한 특성에 따라 일반 사용자는 익숙한 ‘Swap’ 이 아닌 ‘Borrow’ 그리고 ‘Lend’ 라는 탭을 통해 Yield Space에 접근하게 됩니다.

2. Why Yield Space? (Why not just take on existing AMMs?)

우리가 일반적으로 사용하는 DEX들에 다양한 변형된 형태가 있을 수 있지만, 주로 접하는 것은 크게 두가지 형태입니다:

  1. Token swap(e.g. Uniswap): 대부분의 가격이 변동되는 토큰들을 교환하는데 사용합니다. Swap 공식으로는 Constant product formula* 혹은 이를 약간 변형한 형태(e.g. Uniswap v3) 를 사용하게 됩니다.
  2. Stablecoin swap(e.g. Curve): 주로 스테이블코인들끼리 교환하거나, 혹은 stETH(Staking된 ETH) 및 ETH와 같이 가격이 비슷한 토큰들을 교환하는데 사용합니다. 가격이 비슷한 토큰들을 서로 교환하기 때문에, 슬리피지가 최대한 낮을 수 있도록 고안된 공식(Constant sum formula** + constant product formula*)을 사용합니다.

*Constant product formula: x * y = k

**Constant sum formula: x + y = k

Constant product formula를 사용하지 않는 이유

다만 위의 두가지 형태 DEX는 fyToken과 타겟 자산을 교환하는데 있어서는 단점들이 존재하는데, 이는 fyToken이 일종의 zero-coupon bond로써 기능하기 때문입니다.

fyToken이 발행된 초기에는 타겟 자산과의 가격이 차이가 있지만, 만기가 도래할수록 타겟 자산과의 가격이 거의 동일해 집니다. 우리가 2년 후 100달러어치를 받을 수 있는 채권을 지금 구매한다고 했을 때, 현재가는 100달러 미만에 거래되겠지만 2년 후 만기가 다가올수록 100달러에 가깝게 거래되는 것을 직관적으로도 이해할 수 있습니다. 이 때, 만약 constant product formula를 사용한다면 특히나 만기에 도래한 가격이 거의 같은 두 자산을 교환하는데 있어 너무 큰 슬리피지가 발생합니다.

예를 들어 설명해보겠습니다. 만기까지 한달 밖에 남지 않은 연이자율이 5%인 무이자 채권을 거래 수수료가 0.3% DEX에서 거래할 경우, 0.3%의 수수료는 매우 큰 슬리피지를 발생시킵니다.

  1. 차입자: 10.950.99712–1 = 9.1% (연이율)
  1. 대출자: 0.997120.95–1 = 1.01% (연이율)

그리고 이러한 단점들이 발생하는 이유를 요약하자면, 결국 constant product formula를 가지고는 자산의 time to maturity(만기가 도래하기까지 남은 시간)를 고려할 수 없기 때문입니다.

Curve의 공식을 사용하지 않는 이유

Curve 공식은 아까 언급한 constant product formula와 constant sum formula를 융합시킨 버전이라고 볼 수 있습니다. 아래 식을 보시면 증폭계수가 0일때는 constant product formula를, 무한대로 수렴할 때는 constant sum formula의 형태를 띄게 됩니다. 사용 가능할 수 있으나, 증폭계수(amplification coefficient)를 통해 time to maturity 변수를 식에 적절하게 포함시키는 것이 매우 힘들었던 것으로 보입니다.

Curve 식 | 출처: Curve StableSwap

2 토큰에 대한 Curve 식 (n = 2) | 출처: Yield Space

이러한 단점들을 보완하기 위해 Yield protocol은 Yield space라는 새로운 DEX를 고안해 냅니다.

3. Yield Space Derivation

Constant product formula

Yield Space의 수식을 직접 유도해보기 전에, 비교를 위해 constant product formula 를 유도해 보겠습니다.

일단 수식에 등장하는 기본적인 요소들의 정의를 내리고 시작하겠습니다:

y = 풀에 있는 자산 y의 리저브x = 풀에 있는 자산 x의 리저브dx: 유저가 풀에 자산 x를 팔면 풀에 자산 x는 dx만큼 증가-dy: 유저가 풀에 자산 x를 팔면 풀에 자산 y는 dy만큼 하락dy/dx: 유저가 풀에 자산 x를 판 가격

이때 자산 x의 가격은 아래 식으로 표현 할 수 있습니다:

p = -dy/dx

Constant sum formula는 이 가격 p가 1로 고정된다고 가정했을 때이므로 1 = -dy/dx 을 사용해 도출 할 수 있습니다. 반면 Constant product formula의 경우에는 가격이 -dy/dx인 시점에 풀에 있는 자산 x와 자산 y의 양이 같다라는 나래 조건을 만족시킬 때 도출할 수 있습니다:

p = y/x (p는 x의 가격을 y에 대해 표현한 식이기 때문에 p * x = y 관계가 성립 가능합니다.)
p = -dy/dx
=> -dy/dx = y/x

양 변을 x에 대해 적분해 보도록 하겠습니다:

우변을 상수 k로 대체하면 이처럼 constant product formula를 도출할 수 있게 됩니다:

Yield Space AMM: Constant power sum formula

위에서 도출한 constant product formula는 자산 x와 y의 리저브 값을 사용해 자산의 가격을 계산하지만 constant power sum formula는 자산 x와 y의 리저브 값을 사용해 가격이 아닌 이자율을 계산하게 됩니다. Time to maturity를 고려하기 때문에 constant product formula에서는 쉽게 발생할 수 있는 비영구적 손실이 일어날 확률을 대폭 줄여줄 수 있습니다.

이를 구현하기 위해서는 이자율(r)과 풀에 두 자산(x 와 y)의 리저브 비율이 동일해야 합니다:

r = y/x - 1: 이자율은 자산 xy의 비율에 1을 뺀 값 (e.g. Dai(x)와 fyDai(y) 리저브)r = p^(1/t) - 1: [BokLee series] 2. Yield Protocol 분석 에 등장하는 식을 p에 대한 r 함수로 변경한 식p: fyDai에 대한 Dai의 현재 가격

위의 식 때문에 1) fyDai 리저브가 증가할 때마다 이자율은 같이 인상되고, 반대로 2) Dai 리저브가 증가할 때마다 이자율은 인하됩니다.

아까 설명했듯이 p = -dy/dx이기 때문에 아래 수식이 성립합니다:

이제 양 변을 x에 대해 적분해주면:

양 변에 (1-t)를 곱해주고 (1-t)*(c1-c2)를 상수 k로 둠으로써 아래 constant power sum formula를 도출합니다:

자산 x와 자산 y 모두 풀의 초기 자산 값이기 때문에 아래 식 처럼 x start와 y start를 대입해주도록 하겠습니다:

Constant power sum formula는 t = 0 일때는 constant sum formula의 형태를 띄고 t = 1에 근방에서는 constant product formula 로 수렴합니다. 이를 증명해 보겠습니다:

t가 1에 가까워질 때 부정형(아래 식의 우변)의 극한값을 구하기 위해 로피탈의 정리를 적용하면:

결과적으로 constant product formula로 수렴하는 것을 볼 수 있습니다:

아래에는 이자율이 0%라고 가정했을 때 t에 따라 변화하는 constant power sum formula를 도표화한 것입니다. 아래 곡선이 꺽이는 지점으로 볼 수 있듯이 현재 t값, 풀에 있는 자산 x의 리저브 양, 그리고 자산 y의 리저브 양에 따라 매 거래마다 재산출 됩니다.

이자율이 0%라고 가정했을 때 Constant Power Sum 풀의 리저브 계산식 | 출처: Yield Space

아래에는 constant power sum formula를 사용했을 때 자산 x(e.g. DAI) 리저브 양에 따라 변하는 자산 x의 가격을 도표화한 그래프입니다. 만기가 도래할수록(t가 줄어들수록) 가격 곡선은 평평해지는 것을 확인하실 수 있습니다:

Constant Power Sum 풀의 Dai 가격 | 출처: Yield Space

Constant power sum formula를 사용해 이자율을 산출하면 아래 도표와 같이 만기까지의 시간과 크게 관계없이 일정한 이자율(y) 풀에 유동성을 공급해줄 수 있다는 것을 확인하실 수 있습니다:

Constant Power Sum 풀의 이자율 | 출처: Yield Space

Fees for Liquidity Providers

잘 알고 있으실 Uniswap v2의 경우에는 유저로부터 컨트랙트로 들어온 토큰의 0.3%를 수수료로 미리 빼고, 그렇게 모인 수수료를 유저에게 지급할 토큰 값을 산출해서 분배합니다.

다른 AMM(e.g. constant sum formula 혹은 constant product formula)은 x 혹은 y 중 적어도 하나의 자산은 낮은 이자 수익율을 얻을 수 밖에 없기 때문에 이를 상쇄시키기는 용도로 거래 수수료를 지급해야만 합니다. Constant power sum 로직이 적용된 Yield에서는 이자를 더 많이 받기 위해서 이자율이 높을 때 자산 y(e.g. fyDAI)를 구매하고, 이자율이 낮을 때 자산 y를 팔게 됩니다. 이 때문에 자산 x와 y 두 자산에 대한 이자 수익율이 어느정도 보장되어 있어 낮은 수익율을 상쇄시키는 목적보다는 유동성 공급자들을 위한 인센티브를 극대화시키는 목적으로 거래 수수료를 지급하게 됩니다.

Yield Space에서는 이 거래 수수료도 이자율에 따라 지급될 수 있도록 했는데 이는 가격에 따라 수수료를 매기는 다른 프로토콜과 확연한 차이를 보입니다. 이자율에 따라 수수료를 매기기 위해서는 유저가 y를 구매할 때 이자율을 내려 유저가 수수료가 없을 때 보다 더 높은 y 가격을 지불하게끔 합니다. 높은 가격을 지불하게끔 하고, 그 차액을 수수료로 유동성 공급자에게 제공하는 방식입니다. 이를 이자율 함수로 구현하기 위해 1보다 작은 임의의 수 g를 가격(y/x)의 지수로 지정해줍니다.

예를 들어 풀에 fyDai(y)가 110, Dai(x)가 100, 그리고 수수료가 0.05%일때(= g가 0.95)일때, 이자율은 약 5% 정도 변하게 됩니다:

수수료 미포함 이자율은:r = (110/100) – 1 = 10%수수료를 포함한 이자율은:r = (110/100)^(0.95) - 1 = 9.47%입니다.

반대로, 유저가 fyDAI를 팔때는 이자율는 높여 fyDAI 가격을 최소화 시키게 됩니다. 이때는 g가 아닌 1/g를 지수로 두게 됩니다.

구매할 때:

판매할 때:

위의 두 경우에 따른 수식을 미분하면 아래 식을 도출할 수 있습니다:

구매할 때:

판매할 때 (분모가 0이 되는 것을 막기 위해 기존의 조건인 t < 1이 아니라 t/g < 1로 변경해줍니다):

Providing Liquidity

풀이 처음에 초기화(initialize)될 때는 같은 양의 DAI(x) 리저브와 fyDAI(y) 리저브가 공급되어야 합니다. 즉, 이때 가격(y/x)은 1이고 이자율은 0%인 상태입니다. 해당 조건이 지켜졌을 경우에만 컨트랙트에 어떠한 변화(1. mint & burn, 2. trading, 3. passing of time)가 일어나도 풀의 총 공급량을 나타내는 아래의 수식 값은 줄어들지 않게 됩니다. 아래 식에서는 마이너스 이자율 값을 사용하는 상황을 방지하기 위해(유저가 fyDai를 1보다 높은 가격에 팔아 풀에 지급되어야할 수수료가 거래자에게 지급되는 경우 e.g. donation) 1-t 가 아닌 1-t/g를 사용합니다. 거래가 발생하기 시작하면서 풀에 수수료와 fyDAI로 얻어지는 이자가 쌓이면 아래 수식의 값은 증가하게됩니다:

*s 는 유동성 토큰의 총 발행량으로서, 상수로 취급됩니다. 글의 뒷부분에서 자세히 다루어집니다.

  1. 유동성 공급(mint)과 회수(burn)
  • 유동성 공급자는 커트랙트에 들어있는 DAI와 fyDAI 리저브 양과 동일한 비율의 DAI와 fyDAI를 풀에 공급하게 됩니다. 공급자는 그 대가로 풀의 총 공급량에 비례하는 유동성 토큰을 발급받습니다.
  • minting 혹은 burn을 실행하게되면 비율이 같기 때문에 식의 x, y, s가 각각 동일한 계수로 곱해집니다. 계수는 공통인수로 이를 상쇄함으로써 결과적으로는 아무런 변화도 일어나지 않습니다.

2. 거래(trading)

  • 거래가 일어나도 s와 1/(1-t/g) 변수는 영향을 받지 않습니다. 증명의 편의를 위해 방금 언급한 변수를 제외시킨 식을 도출해보면 위에서 fyDAI를 판매할 때 구했던 수식과 동일해지는 것을 보실 수 있습니다.
  • fyDAI를 구매할 때도 동일하게 식이 감소하지 않고 증가한다는 것을 증명하면 거래가 일어나도 값이 감소하지 않는다는 것을 증명할 수 있습니다.

위 식의 도함수를 구해보면:

fyDAI를 판매하는 입장에서는 1-t/g가 아닌 1-gt가 지수가 된다는 것을 위에서 살펴봤습니다. 이제 판매하는 입장에서의 도함수를 구해보면:

유저가 fyDai를 구매하면 풀에 있는 자산 x가 증가하고 자산 y는 줄어듭니다. 이때 fyDAI룰 구매할 때 사용하는 수식을 사용해 도출한 줄어든 y의 양이 fyDAI 판매할 때 사용하는 수식을 사용했을 때 도출한 줄어든 y의 양보다 무조건 같거나 적다는 것을 증명할 수 있습니다:

양변을 1/t 로 거듭제곱 해주고 로그를 씌워 계산해보면:

구매는 y >x 조건이 지켜져야 가능하기 때문에 자동으로 ln(y/x)>0의 조건이 성립됩니다. ln(y/x)로 양 변을 나눠주게되면 g는 1보다 항상 작기 때문에 위 부등식은 참이라는 것을 증명하게 됩니다.

3. 시간(passing of time)

  • 만기가 도래할수록(time to maturity 값이 점점 줄어들수록) 식의 분모 부분는 상수 s이므로 변하지 않고 분자 부분만 계속 증가하게 됩니다. 이를 증명하기 위해서 t에 대해 편미분하여 0보다 작거나 같다는 것을 증명할 수 있습니다.

각각 x^(1-t)와 y^(1-t)를 a와 b로 대체시키고 이 두 값이 같은 경우에만 0인 될 수 있다는 것을 증명하면 됩니다:

2차 미분을 진행해보면:

2차 미분값이 0보다 작으므로 이 점이 최대값임을 증명 수 있습니다. 즉, 이 전의 도함수는 모든 점에서 0보다 작거나 같고 결과적으로 식은 만기가 도래할수록 값이 일정하게 유지되거나 증가한다는 것을 알 수 있습니다.

4. Virtual Reserve (s)

Constant power sum formula를 소개 할 때 등장했던 도표로 다시 돌아가 봅시다. 이자율이 0%인 시점을 보면 DAI 유동성의 적어도 50%는 이자율이 100% 이상일 때 공급하기 위해 따로 저장되어 있는 것을 알 수 있습니다. Yield Space는 최대한 유동성을 높이기 위해서 정규화를 통해 t의 단위를 t = 1이 1년이 아닌 4년을 의미하도록 재조정 시켜줍니다. 즉, 이는 1년 단위가 아닌 4년 단위의 유동성을 상징합니다. 이자율이 0%일 때, 풀의 모든 fyDAI는 마이너스 이자율이 될 때를 대비해 리저브에 저장되는데 fyDAI는 항상 1보다 작기 때문에 낭비되는 유동성이 생기게 됩니다. Yield Space는 자본효율성을 높여 이 문제를 극복하고자 하는데 이를 위에 도입된 개념이 가상의 리저브(virtual reserve)입니다.

인위적인 개입 없이는 1 fyDAI가 1 DAI보다 높은 가격으로 거래되지 않습니다(마이너스 금리). 1 DAI를 담보로 예치하면 1 fyDAI를 청산 리스크 없이 민팅할 수 있게 해주기 때문에 fyDAI를 1DAI 이상의 가격을 내고 풀에서 살 이유가 없게끔 설계되어 있습니다. 거래가 일어날 때마다 fyDAI 가격이 1DAI를 넘어가지 않는지 확인하는 절차를 거치게 됩니다. fyDAI 리저브가 항상 DAI 리저브보다 높아야한다는 조건을 지키기 위해서 fyDAI 리저브 중 접근이 제한된 영역을 정해둡니다.

예를 들어 초기화 부분에서 설명했듯이 풀이 초기화 단계에서는 fyDAI와 DAI 리저브는 동일하기 때문에(x start = y start = s) 풀에 남아있는 잉여 fyDAI는 팔 수 없게 됩니다(아래 도표에서 회색으로 칠해진 영역). 아래 수식을 사용하면 컨트랙트 남아있는 잉여 fyDAI 값을 추산 할 수 있는데 이는 fyDAI를 구매할 경우에만 해당됩니다. 유저가 풀에 DAI를 기부하게되면 판매 액션을 취할 수 있지만 그럴 경우 fyDAI 구매는 금지되기 때문에 fyDAI에는 접근 할 수 없어 따로 고려할 필요 없습니다.

Constant Power Sum 풀의 잉여 fyDai 리저브 | 출처: Yield Space

사용되지 않은 잉여 리저브(excess reserve)를 가상의(virtual)의 상태로 만들고 유동성 공급자가 유동성을 추가로 공급할 필요 없게끔 만듬으로써 자본 효율성을 높이고 있습니다. 이 값은 항상 총 발행량 보다 크거나 같기 때문에 “가상의 fyDAI 리저브는 = s(유동성 토큰의 총 발행량)”가 성립하게 됩니다. 거래가 일어나면 실제 fyDAI에 가상의 fyDAI 값이더해진 상태의 리저브 값을 바탕으로 거래의 결과값들이 산출됩니다. 유동성 토큰이 민팅되면 유동성 토큰 민터는 가상의 fyDAI 리저브가 더해지기 전의 실제 fyDAI 리저브만큼만의 fyDAI만 공급하면 되는 구조입니다. 민팅된 유동성 토큰으로 s와 가상의 리저브도 자동으로 증가합니다.

예를 들어 유저 A가 100 DAI로 풀을 초기화 할 때, 해당 유저가 유동성 토큰 100개를 받음과 동시에 이 풀에 있는 유동성 토큰의 총 공급량 s는 똑같이 100으로 증가합니다. 만약 t = 0.5일때 A가 100 fyDAI를 팔면:

X start: actual DAI 리저브 100Y start: virtual fyDAI 리저브(= s = 100) + actual fyDAI 리저브(= 0)를 더함 = 100Y end: 100 fyDAI + y start = y end = 200

위에 도출해낸 값들로 X end 를 구하게 되면:

t: 0.5g: 0.95X = 35.39100 - x = 64.61

총 64.61 DAI가 컨트랙트로부터 발급됩니다.

*해당 예시는 Yield Space 백서에서 잘못 계산되어 필자가 맞게 수정한 값이기 때문에 백서의 산출값과 상이할 수 있습니다. 백서의 저자이자 Yield의 파운더인 Allan Niemerg 께 여쭤보니 새로 도출된 산출 값이 맞다고 확인해주셨습니다.

이제 유저 B가 유동성을 공급함으로써 10개의 유동성 토큰(10% * s)을 민팅했다고 해봅시다. 풀에는 35.39 DAI가 있기 때문에 B는 3.54 DAI를 예치하게 됩니다. 하지만 풀에 있는 실제 fyDAI는 100이기 때문에 B는 실제로는 10 fyDAI만 예치합니다. 유동성 토큰 10개가 해당 유저에게 민팅되면, 가상의 fyDAI 리저브(= s)는 110으로 증가합니다.

즉, 다음 거래에서 y start 값은: 100 + 110 = 210 이 됩니다. 거래가 발생하기 시작하면서 풀에 수수료와 fyDAI로 얻어지는 이자가 쌓이면 수식 값은 증가하게됩니다. 이말인즉슨 이 시점 이후에 풀에 쌓이게 되는 fyDAI 는 접근 불가능하고 거래되지 못하는 상태로 저장되게 됩니다.

4. Yield Space Implementation

이 섹션에서는 백서에 쓰여진 Yieldspace의 로직이 실제 코드로 어떻게 구현되었는지 살펴봅니다. 다만 대상이 되는 모든 코드를 살펴볼 수는 없으므로, Yieldspace를 구현하는데 있어 가장 중요한 로직들을 중심으로 살펴 보겠습니다. 원래 코드의 주석 중 코드를 이해하는데 필요한 부분들은 그대로 두었고, 상세 설명이 필요한 부분들은 한글로 추가 커멘트를 달아 놓았습니다.

Yieldspace는 크게 Pool, PoolFactory, PoolExtention이라는 컨트랙트들로 이루어 집니다. 이 중 대부분의 거래 및 유동성 관련 로직은 Pool 컨트랙트를 통해 일어나며, 크게 (1) 유동성 공급(Mint) (2) 유동성 회수(Burn) (3) 거래 세가지의 기능을 제공합니다. 다만 앞서 서술하였듯 다른 AMM과 다른 점은 교환 및 유동성 공급의 대상이 되는 토큰이 fyToken(e.g. fyDAI) 과 해당 fyToken의 Base 토큰(e.g. DAI) 이라는 점입니다.

*이 분석 글의 코드는 Yieldspace-v2 RC11를 기준으로 합니다.

4.1. 유동성 공급(Mint)

유동성 공급의 경우 (1) base token(e.g. DAI)만을 사용해 공급하는 방법과, (2) base token과 fyToken을 반반씩 준비해 바로 유동성을 공급하는 방법이 존재합니다. 다만 base token만으로 유동성을 공급했을 경우, 컨트랙트 안에서 ½ 에 해당하는 base token은 fyToken으로 자동 변환되어 유동성을 공급하게 됩니다.

이 글에서는 설명의 간결함을 위해, (2) base token과 fyToken을 미리 반반씩 준비해 유동성을 공급하는 방법을 기준으로 설명합니다.

유동성 공급은 Pool Contract의 mint 함수를 통해 실행 됩니다. 다만, 실제 Yield Protocol에서는 batch 함수를 통해 mint 함수가 트리거 되기 전에 유동성 공급에 필요한 토큰들을 해당 컨트랙트로 먼저 이동시킵니다. 따라서 앞으로 설명할 코드 예시에서 따로 유동성 공급에 필요한 토큰을 transfer 하는 과정은 없습니다(이미 이동되었기 때문에). 예시 transactions: 링크 1, 링크 2

mint() 함수. https://github.com/yieldprotocol/yieldspace-v2/blob/rc11/contracts/Pool.sol#L176

_mintInternal 함수의 remainder(남는 surplus base를 다시 되돌려 받는 주소)가 따로 존재하는 이유는, 유동성 공급시 내는 fyToken의 갯수에 따라 LP token의 양이 계산되고, 추후 해당 LP token을 얻기 위해 납입해야 하는 base token의 양이 제일 마지막으로 계산되기 때문입니다. 이는 _mintInternal 함수에서 더 자세하게 알아봅니다.

여기서 minRatio와 maxRatio는 본 코드의 설명처럼, 유동성 제공시 일종의 슬리피지에 대한 제한을 두려는 목적으로 입력됩니다. 이 케이스는 base Token과 fyToken을 미리 반반씩 준비해 유동성을 공급하므로, 앞으로 볼 _mintInternal 함수의 ‘fyTokenToBuy’에 해당하는 값은 0을 입력해 줍니다.

_minInternal() 함수. https://github.com/yieldprotocol/yieldspace-v2/blob/rc11/contracts/Pool.sol#L209

Update 함수: Time Weighted Average Rate 업데이트

_update() 함수: https://github.com/yieldprotocol/yieldspace-v2/blob/rc11/contracts/Pool.sol#L152

Time Weighted Average Rate(TWAR)을 간단히 설명하면, AMM DEX에서 가격 데이터를 의도적으로 조작하는 것을 힘들게 만들기 위해 Uniswap V2 부터 제안되어 사용되고 있는 온체인 가격 산정 방법입니다. 한 블록 안에서 연산된 마지막 가격에 다음 블록이 만들어지는 시점 까지 흐른 시간을 weight로 더해주는 형식을 가지고 있습니다. Yield Space의 경우 이러한 방식을 Pool balance와 cumulative balance ratio를 업데이트 해 주는데 이용하고 있으며, base와 fyToken간의 비율이 해당 시점 base asset의 시간 대비 이자율을 나타내기 때문에 유용합니다. TWAR 에 대한 더 자세한 설명들은 유니스왑 문서 Oracles | Uniswap 에서 찾을 수 있으며, 본 글의 범위에서 벗어나므로 생략합니다.

4.2. 유동성 회수(Burn)

유동성 회수(Burn) 또한 유동성 공급(Mint)와 비슷하게, LP token을 burn 한 댓가로 base token만 수령하거나 base token + fyToken을 함께 수령할 수 있습니다. 이 글에서는 간단한 설명을 위해, base token + fyToken을 함께 수령함을 가정하겠습니다.

Burn tx 예시: https://etherscan.io/tx/0xc4dcb658dcafd9e84ff8f696ac828b03bcfe54e9b5c700f3d98541ddc93d78f2#eventlog

burn() 함수: https://github.com/yieldprotocol/yieldspace-v2/blob/rc11/contracts/Pool.sol#L280

mint 함수와 마찬가지로, burn 함수를 호출하면 차례로 _burnInternal 함수가 호출됩니다. 이 때, _burnInternal에 parameter로 들어가 있는 false는 tradeToBase가 false임을 나타내며, LP token burning시 fyToken과 base의 쌍으로 유동성을 회수함을 의미합니다. minRatio 및 maxRatio는 mint와 마찬가지로, 최대 슬리피지를 표현합니다.

burnInternal() 함수: https://github.com/yieldprotocol/yieldspace-v2/blob/rc11/contracts/Pool.sol#L312
ERC20.sol burn() 함수

burn의 구체적인 동작은 기본적인 ERC20 토큰의 burn 함수와 동일합니다.

4.3. 거래(Trading)

거래에 대한 함수는 총 4가지가 존재합니다.

  1. sellBase: 파는 양(base)을 미리 정하고 해당 양에 맞추어 fyToken을 수령
  2. buyBase: 받는 양(base)를 미리 정하고 해당 양에 맞추어 fyToken을 판매
  3. sellFYToken: 파는 양(fyToken)을 미리 정하고 해당 양에 맞추어 base를 수령
  4. buyFYToken: 받는 양(fyToken)를 미리 정하고 해당 양에 맞추어 base를 판매

이렇게 다양한 함수가 존재하는 이유는, AMM의 작동 원리상 모든 거래는 다음 식을 만족해야 하기 때문입니다. 앞에서 보았듯, Yieldspace는 항상 다음 식을 만족하여야 합니다.

‘Buy’ case: x_start^(1-gt)+ y_start^(1-gt) = x_end^(1-gt)+ y_end^(1-gt)

‘Sell’ case: x_start^(1–t/g)+ y_start^(1-t/g) = x_end^(1-t/g)+ y_end^(1-t/g)

여기서 x_start 변수는 x 토큰의 거래 전 개수, x_end는 거래 후의 개수입니다. y 변수도 마찬가지입니다.

예를 들어 x변수가 가 base Token, y변수가 fyToken이라면, 어떤 거래나 이전 거래에 의해 x_starty_start 는 이미 정해져 있습니다.

Buy를 하는 거래일 경우, buyBase 함수는 x_end 변수를 미리 정하고 그에 따라 y_end값을 구하는 과정입니다. 반대로, buyFYToken 함수는 y_end 변수를 미리 정하고 그에 따라 x_end 값을 구하게 됩니다.

반대로 sell 의 경우에도 마찬가지로 sellBase 및 buyFYToken 함수가 위와 동일한 원리로 위의 ‘sell’ case 식을 만족해야 하기 때문에, Yieldspace에는 총 4가지의 거래 함수가 존재하게 됩니다.

분량상 이 글에서 모든 케이스를 다루기는 힘들기 때문에, 여기에서는 buyBase 함수만 살펴 보겠습니다.

burnBase() 함수: https://github.com/yieldprotocol/yieldspace-v2/blob/rc11/contracts/Pool.sol#L454
buyBasePreview() 함수: https://github.com/yieldprotocol/yieldspace-v2/blob/rc11/contracts/Pool.sol#L506
fyTokenInForBaseOut() 함수: https://github.com/yieldprotocol/yieldspace-v2/blob/rc11/contracts/YieldMath.sol#L464

5. Summary

지금까지 Yield Space에 대해 알아 보았습니다. Yieldspace의 가장 큰 의의는 기존 AMM 공식에 시간이라는 변수를 포함시켜 시간의 흐름에 따라 가치가 변하는 토큰들을 위한 새로운 AMM을 발명했다는 점인 것 같습니다. 이러한 Yield Space는 시간에 따라 가치가 변하는 자산들을 교환하기에 효과적일 뿐더러, Lending & Borrowing rate의 효과적 가격 발견, 그리고 타겟 자산(e.g. DAI)에 대한 yield curve 생성 등 많은 쓰임새를 가지고 있습니다. 많은 담보대출 프로토콜들에서의 초기 lending & borrowing rate은 해당 프로토콜들의 (임의적인) utilization ratio를 통해 정해지지만, Yield Protocol의 경우 Yield Space를 통해 순수한 market-based rate을 발견할 수 있습니다. 게다가 채권의 성질을 이용해 yield curve 등을 간접적으로 생성할 수 있어, 블록체인 위의 타 자산가격을 가치평가 하거나 파생상품 등의 새로운 금융시장에 오라클로도 기여할 수 있는 가능성 또한 보입니다.

Source: https://upload.wikimedia.org/wikipedia/commons/a/a7/Yield_curve_20180513.png

최근 DeFi 시장이 각종 거버넌스 토큰들의 에어드랍이나 레버리지 파밍 등으로 높은 APR를 추구하고 있어 아직 Yield Protocol 및 Yield Space가 크게 주목받지는 못하였지만, 크립토 시장이 점점 커지며 기관 및 크립토에 아직 익숙하지 않은 사람들이 더 들어올수록 fixed rate lending protocol은 큰 위력을 발휘할 수 있을것 같습니다.

다만 Yield Protocol과 Yield Space의 fyToken도 과담보의 형태로 채권을 만드는 것이기 때문에, 청산 리스크에서 자유롭지 않습니다. 특히나 24시간 큰 가격 변동성을 보이는 암호화폐를 담보로 두기 때문에, 이는 더욱 더 큰 문제입니다. 시리즈의 다음 편에서는 이러한 청산 리스크를 어떻게 감소시킬 수 있을지 알아 보겠습니다.

6. Reference

디사이퍼 — Bok Lee팀 소개

김한 — Decipher Junior Researcher, Decipher Media Lead
이병헌 — Decipher alumni
이우진 — Decipher Senior Researcher
이재복 — Decipher Junior Researcher
이재승 — Decipher alumni
최유진
홍성수 — Decipher Junior Researcher

--

--