Defi의 Staking 개요 & AAVE의 Staking Logic

jeff lee
Decipher Media |디사이퍼 미디어
18 min readFeb 14, 2022

이번 글을 통해, Defi 에 등장하는 Staking에 대해 소개하고, 실제로 Staking이 코드 상에서 어떻게 구현되는 지에 대해 살펴보는 시간을 갖겠습니다. 작성자는 본 시리즈에 등장하는 Protocol과 아무런 연관이 없음을 미리 알립니다.

Author
이재복 of Decipher
Seoul Nat’l Univ. Blockchain Academy Decipher(@decipher-media)
Reviewed By 정재환

목차

Staking 개요
1. Staking이란?
2. Staking의 목적
3. Staking의 기본적인 원리 소개
4. Staking 구현의 어려움

개요

다양한 블록체인 서비스에서 Staking이라는 단어를 흔하게 접할 수 있습니다. Proof of Staking, LP Staking 등 다양한 분야에서 ‘Staking’이라는 단어가 등장합니다.

Staking은 여러 서비스에서 등장하는 개념이지만, 이 글에서는 Defi에서 등장하는 Staking에 대해 알아보고자 합니다. 특히 Ethereum 네트워크의 Defi에서 등장하는 Staking에 대해 알아보고, 구현에 어떤 어려움이 있는지, 그리고 이를 해결하기 위해 어떤 방법을 사용했는지 깊게 분석해보도록 하겠습니다.

Staking 이란

Defi에서 말하는 Staking이란, 특정 토큰을 스마트 컨트랙트에 예치하는 행위를 의미합니다. 유저가 Staking을 하면 컨트랙트는 해당 Defi의 정책에 따라 유저가 예치한 토큰의 양이 비례하여 보상을 지급합니다. 이 때 예치되는 토큰의 종류, 혹은 Staking의 목적에 따라 LP Farming, Yield Farming, Liquidity Mining 등 다양한 이름으로 불립니다.

Staking의 목적

유저의 Staking을 유도하고, Staking 된 양에 따라 보상을 지급하는 이유는, 유저가 Staking하는 행위가 해당 서비스에 도움을 주는 행위이기 때문입니다.

가령 Swap의 경우, AMM이라는 본래의 목적을 원활하게 수행할 수 있기 위해서는 유동성 풀에 충분한 양의 유동성이 존재해야 합니다. 따라서 프로토콜의 입장에서는 유저가 유동성을 제공하는 행위를 ‘서비스에 도움이 되는 행위’라고 간주할 수 있으며, 이를 유지할 수 있는 유인책을 마련할 필요가 있습니다.

이 경우, 해당 Swap 서비스는 유저가 유동성을 제공하고 받은 Liquidity Provider 토큰을 특정 컨트랙트에 예치하도록 하여 보상을 제공합니다.

이는 비단 Swap만 해당하는 것은 아닙니다. 어떤 유틸리티 토큰이 있을 때, 이 유틸리티 토큰이 시장에서 활발하게 거래되기 위해서는 AMM(DEX)에 충분한 유동성이 필요합니다. 따라서 이 경우에도 유동성 제공자에게 보상을 제공함으로써 프로토콜 참여자들의 토큰 거래를 촉진시킬 수 있습니다.

Staking의 목적이 유동성 제공에만 있는 것은 아닙니다. 유저의 Staking을 유도하고, Staking 된 자산을 통해 프로토콜의 리스크를 분담하고 이에 대한 대가로 보상을 제공할 수도 있습니다. 가령 랜딩 프로토콜의 경우, 시장의 급격한 변동성에 의해 유저가 공급한 유동성을 찾아갈 수 없는 뱅크런과 같은 사태가 발생할 수 있습니다. 이러한 경우, 랜딩 프로토콜은 유저가 Staking한 자산을 판매하여 유동성을 공급하는 방식으로 문제를 해결할 수 있습니다.

Staking의 기본적인 과정

많은 Defi에서 Staking 보상을 자체적인 거버넌스 토큰으로 지급합니다. Sushiswap의 경우, Uniswap의 LP token을 예치하는 유저에게 sushi token을 지급한 바 있습니다. 또한 Compound의 경우 유저가 제공한 유동성의 양에 비례하여 Comp token을 지급합니다.

Staking에 대한 보상은 블록, 혹은 초 단위로 기록됩니다. 이 때 대부분의 서비스에서 단위 시간당 지급되는 보상의 양은 일정합니다. 따라서 많은 유저가 참여하고 예치된 토큰이 많아질수록 유저가 받을 수 있는 보상의 양은 줄어들 수 밖에 없습니다. 같은 양의 보상을 여러 유저가 나누어 받아야 하기 때문입니다.

앞으로 유저가 토큰을 Staking하는 컨트랙트를 ‘StakingPool’이라고 부르고, 유저가 토큰을 풀에 Staking 하는 행위를 ‘stake’라는 용어로 설명하도록 하겠습니다.

아래는 일반적인 Staking 서비스의 예시입니다.

초당 1000개의 보상을 지급하는 StakingPool이 있습니다. 이 풀에 3명의 유저가 각각 10개, 20개, 70개를 stake하고 있는 상황을 가정하겠습니다.

그림1. Pool에 Asset을 Staking하는 상황, 설명을 위해 제작된 이미지입니다

각 유저에게 매 초 마다 지급되는 보상의 양은 다음과 같습니다.

그림2. Pool에서 예치한 지분에 해당하는 보상이 지급되는 상황. 설명을 위해 제작된 이미지입니다

이 때 새로운 유저 D가 토큰 100개를 stake합니다. 이 순간, 총 예치량이 달라지며, 각각의 유저에게 지급되는 보상의 양도 달라집니다.

그림3. 새로운 Staking이 발생했을 때, 전체적인 보상이 변경되는 상황. 설명을 위해 제작된 이미지입니다

Staking 구현의 어려움

그러나 Ethereum 네트워크에서는 StakingPool가 위에서 설명한 방식대로 구현될 수 없습니다. 그 이유는 Ethereum 네트워크의 가스비 때문입니다.

Ethereum 네트워크의 상태를 변경하는 모든 작업은 가스를 사용합니다. Ethereum 네트워크에서는 ‘가스’라는 개념을 통해 수행될 수 있는 연산의 양을 제한하고 있습니다. 가스가 없다면, 무한 루프와 같이 Ethereum 네트워크에 부하를 줄 수 있는 방식으로 네트워크에 악영향을 미칠 수 있기 때문입니다.

표1. Ethereum Gas Fee Schedule, 출처: Ethereum Yellowpaper 27p, Appendix G.

Ethereum의 황서(Yellow paper)에는 각 연산마다 소모되는 가스의 양이 책정되어 있습니다. 이 때, 사용되는 가스의 양은 연산에 따라 다릅니다. 예를 들어, 가장 단순한 연산인 덧셈의 경우, 소모되는 가스의 양이 3입니다. 반면, 그보다 살짝 복잡한 연산인 곱셈의 경우, 소모되는 가스의 양이 5입니다.

덧셈, 곱셈과 같은 사칙 연산 뿐 아니라, 해싱 작업, 블록에 로그를 기록하는 작업(event emission) 등 Ethereum의 상태를 변경하는 모든 작업에 가스가 사용됩니다.

이 때, 가장 많은 가스를 소모하는 작업은 Ethereum 네트워크에 기록된 스토리지의 변경입니다. 스토리지의 변경이란, Ethereum 가상머신의 스토리지에 기록되어 있는 변수를 변경하는 작업을 의미합니다.

스토리지의 변경 작업은 사칙연산이나 단순한 읽기 작업과 비교했을 때, 훨씬 많은 비용을 소모합니다. 새로운 변수를 할당하는데 소모되는 가스는 20000이며, 기록되어 있는 변수를 변경하는데 소모되는 가스의 양은 5000입니다. 사칙 연산이 한 자리수, 로깅과 해싱이 세자리수의 가스를 소모하는데 비해 스토리지의 변경이 매우 큰 비용이 소모된다는 것을 알 수 있습니다. 따라서 Ethereum 네트워크상에서 서비스를 만들기 위해 스마트 컨트랙트를 작성할 때에는 스토리지의 변경을 최소화 하는 것이 중요합니다.

스토리지의 사용을 최소화 할 필요가 있다는 사실은 앞서 설명드린 StakingPool 컨트랙트를 구현하는데 큰 장애물이 됩니다. 총 보상이 고정되어 있는 상태에서, 유저가 신규로 유입되거나 이탈하는 순간 모든 유저들에게 지급되어야 하는 보상의 양이 자동적으로 줄어들어야 하기 때문입니다.

이는 사실 비단 Staking 서비스에만 해당하는 문제는 아닙니다. 예를 들어, AAVE, Compound와 같은 랜딩 프로토콜은 차입자로부터 발생하는 이자를 대출자에게 분배합니다. 이 때, 대출자가 받을 수 있는 이자의 양은 예치한 양에 비례하는데요, 대출자에게 분배되어야 하는 총 이자의 양이 고정되어 있는 상황에서 새로운 예치가 발생하게 되면, 각 유저들이 받을 수 있는 보상이 일괄적으로 변경되게 됩니다.

결국 Staking 컨트랙트를 구현하기 위해 가스비라는 제약 하에서 최소한의 변수로, 유저가 받아야 할 보상이 변경되어야 하는 문제를 풀어야 합니다. 그리고 개발자들은 창의적인 아이디어로 이 문제를 풀어냅니다. 많은 디파이가 Staking 서비스를 제공하고 있지만, 이 중 대표적으로 3개의 서비스(AAVE, Sushiswap, Synthetix)의 Staking 컨트랙트를 구현할 수 있는 이론적인 배경과 실제 코드를 살펴보도록 하겠습니다.

AAVE

index 아이디어는 2가지 사실에서 출발합니다

1. 유저에게 지급되는 보상 토큰의 양은 시간에 비례한다 :
시간이 흐름에 따라 유저에게 지급되는 보상의 양이 증가합니다.

2. 유저에게 지급되는 보상 토큰의 양은 총 예치량에 반비례함 :
유저에게 지급되는 보상의 양은 전체 예치량이 늘어날수록 줄어듭니다.

두 가지 사실을 바탕으로 초당 지급되는 거버넌스 토큰의 양을 K 라고 할 때, 일정 시간(ΔT= t2-t1) 동안 토큰 1개 당 주어지는 보상(ΔI)은 다음과 같이 쓸 수 있습니다. 단, 총 예치량(D)가 변하지 않았다고 가정합니다.

그림4. Basic Concepts of Index, 설명을 위해 작성된 이미지입니다.

ΔT동안 유저가 추가로 예치하거나 예치된 토큰을 회수하지 않는 한, 총 예치량 D 는 일정합니다.

위 식을 설명하기 위한 예를 들면 다음과 같습니다.

초 당 보상이 1000개인 Staking 풀이 있습니다. (K=100)

특정 시점(t)에 유저가 예치한 뒤 10초동안 Staking 풀에 추가적인 액션이 발생하지 않습니다. 즉 임의의 유저가 풀에 토큰을 추가로 예치하거나 예치된 토큰을 회수하지 않습니다.

이 경우, t 부터 다음 액션이 발생하는 t+10 까지 총 예치량 D는 일정합니다. 따라서 Δt = 10이며, 이 구간동안 총 예치량이 토큰 5개라고 가정하겠습니다. 즉 t~t+10에서 D=5입니다.

이 때 [t, t+10)의 구간에서 토큰 1개 당 주어지는 보상은 다음과 같이 계산할 수 있습니다

그림5. Basic Concepts of Index Example, 설명을 위해 작성된 이미지입니다.

일정 시간 ΔT 동안의 ΔI 와 ΔT 는 일차 비례 관계에 있습니다.

따라서 특정 구간에 대해 위 식을 T에 대해서 적분하면

그림6. Integral Calculation of Index, 설명을 위해 제작된 이미지입니다.

이며, 위 일차함수는 총 예치량 D가 변하지 않는 특정 구간(ΔT) 내에서 성립하게 됩니다. 따라서 어떤 변수 I와 T의 관계에 대한 그래프를 그리면 아래와 같습니다.

그림7. Graph of function of t, 설명을 위해 제작된 이미지입니다.

총 예치량이 달라지는 지점, 즉 ΔT 구간 이후의 지점에서는 그래프를 새롭게 그릴 수 있습니다. 만약 신규 유저가 예치를 하거나, 혹은 기존 유저가 추가로 예치하게 된다면, 총 예치량 D가 증가합니다. 이 때 새로운 총 예치량 D를 D’라고 쓸 수 있습니다.(D<D’)

마찬가지로 그 다음 유저의 액션이 발생하기 전까지의구간을 ΔT’라고 정의한다면, 다음과 같은 식을 얻을 수 있습니다.

그림8. Integral Calculation of Index, 설명을 위해 제작된 이미지입니다.

D’은 기울기의 역수이므로, ΔT’ 에서는 기울기가 감소한 그래프가 그려지게 됩니다.

그림9. Extended Graph of function of t, 설명을 위해 제작된 이미지입니다.

Distribution Index

앞서 ΔI를 일정 시간 동안 토큰 1개 당 추가로 주어지는 보상이라고 정의했고, 이를 바탕으로 I 에 대한 그래프를 도출할 수 있었습니다.

이 때 정의한 I를 Distribution Index라고 하며, 다음과 같은 특징을 지니고 있습니다.

1. index는 시간에 비례하여 단조증가하는 값입니다.

2. index는 총 예치량이 증가할수록 ‘증가량’이 감소합니다.
즉 총 예치량이 적을수록 index는 빠르게 증가하며, 총 예치량이 많을수록 천천히 증가합니다.

이렇게 정의된 index는 뜻 그대로 ‘색인’입니다. 유저가 예치한 시점의 index를 저장함으로써, 해당 유저가 어느 시점에 자산을 예치했는지 기록할 수 있습니다. index는 시간이 지남에 따라 자동적으로 증가하는 반면, 유저의 index는 해당 유저가 액션을 수행한 시점으로 고정됩니다.

앞서 설명했듯 index는 단순히 시간에만 의존하는 변수가 아닙니다. index는 시간과 총 예치량 두 가지 변수를 모두를 반영하고 있습니다.

만약 어떤 유저가 총 예치량이 적은, 초기에 진입했다면 유저가 예치한 이후로 index는 빠르게 증가할 것입니다. 그러나 시간이 지남에 따라 여러 유저가 유입되고, 총 예치량이 많아질수록 index가 증가하는 기울기는 감소합니다.

Reward Calculation

유저가 예치한 보상은 현재의 index와 유저가 예치한 시점의 index의 차이를 통해 계산할 수 있습니다. ΔI가 단위 예치량(토큰 1개) 당 주어지는 보상이이기 때문입니다. 따라서 유저의 보상은 아래와 같이 계산할 수 있습니다.

그림 10. Reward Calculation, 설명을 위해 제작된 이미지입니다.

예시

구체적인 예시를 들어 설명드리도록 하겠습니다.

유저 A와 B가 Staking 풀에 예치를 하는 상황을 가정해보겠습니다. Staking 풀에서 지급되는 보상은 초당 1개입니다.

Staking 풀 런칭 직후 A가 토큰 10개를 예치했으며, A가 예치하고 100초 뒤에 B가 뒤이어 토큰 30개를 예치했습니다. 그 뒤에 200초가 된 시점에서 각 유저의 예치 상황을 표로 나타내면 다음과 같습니다.

표1. Situation When A and B stakied, 설명을 위해 제작된 이미지입니다.

유저 A의 경우, 100초까지 지급되는 모든 보상을 혼자 독차지하고, 이후에 200초까지 지급되는 보상은 전체 보상에 대해 B와 1:3으로 나누어가집니다. 따라서 200초가 된 시점에서 유저 A와 B가 받을 수 있는 보상을 각각 계산하면 다음과 같습니다.

그림11. Calculated Rewards for A and B, 설명을 위해 제작된 이미지입니다.

Calculate with Distribution Index

위 상황을 Distribution Index를 통해 구해보겠습니다.

index의 초기값을 I(0)이라고 설정했을 때, 첫번째로 10개의 토큰을 예치한 유저 A의 index는 I(0)으로 기록됩니다.

100초가 지난 후 유저 B가 30개의 토큰을 예치합니다.

이 때 0초부터 100초까지 증가한 index, 즉 ΔI는 다음과 같이 계산할 수 있습니다.

0~100초 동안 증가한 index :ΔI = K * ΔT / D = 100 / 10 = 10

유저 B가 액션을 수행한 시점의 index가 I(0) + 10이기 때문에 유저 B의 index는 I(0)+10으로 기록됩니다.

이러한 상황을 나타내는 index의 그래프는 다음과 나타낼 수 있습니다.

그림12. Index of A and B with Graph, 설명을 위해 제작된 이미지입니다.

이후 100초의 시간이 흐르고, 200초가 되었을 때, 증가한 index의 양을 계산하면 다음과 같습니다.

100~200초 동안 증가한 index :ΔI =K * ΔT / D = 1 * 100 / 40 = 2.5

마찬가지로 이어서 그려진 index의 그래프는 다음과 같습니다.

그림13. Index of A and B with Graph Extension, 설명을 위해 제작된 이미지입니다.

위에서 이야기 한 모든 상황을 표로 나타내면 다음과 같습니다.

표2. Index table of A and B after time passes, 설명을 위해 제작된 이미지입니다.

이 상황에서, 앞 내용의 공식대로 index의 차이를 이용하여 time = 200 인 시점에 유저가 받을 수 있는 보상을 계산하면 다음과 같습니다.

그림14. Accrued Rewards of A and B, 설명을 위해 제작된 이미지입니다.

이러한 계산과정을 통해 Distribution Index를 사용하여 각 유저의 Staking 보상을 계산할 수 있습니다.

Reward Accumulation

이 때, 유저 A가 200초인 시점에 10개의 토큰을 추가로 예치하면 어떻게 될까요? 이 때는 유저 A의 index를 새로 기록해야 합니다. 유저의 예치량이 달라졌기 때문입니다.

유저의 예치량이 업데이트 되는 시점에는 누적되어 있던 유저의 보상을 기록해줘야 합니다. 보상을 기록하지 않으면 index가 새로 초기화됨에 따라 이전의 보상에 대한 기록이 누락되기 때문입니다.

상황을 표로 살펴보도록 하겠습니다. index는 위에서 계산한 방식과 동일하게 계산하며, 계산식은 생략합니다.

표3. Index table of A and B after A’s additional staking, 설명을 위해 제작된 이미지입니다.

time = 300s가 되었을 때, 위 식에 의해 계신된 유저 A의 보상은 다음과 같습니다. 아래는 이전의 보상에 대한 기록이 누락된 계산식입니다.

그림15. Accrued rewards of A without recording previous reward, 설명을 위해 제작된 이미지입니다.

A의 index가 추가로 예치한 시점의 index인 I(0)+12.5로 업데이트 되었기 때문에, 200s 까지 누적된 보상이 따로 기록되지 않으면 이전의 보상은 index의 차이(ΔI)만으로는 구할 수 없게 됩니다.

따라서 유저가 추가로 예치를 하거나, 혹은 회수를 할 때 해당 시점까지 누적된 유저의 보상을 기록해주는 로직이 필요합니다. 그리고 유저가 받을 수 있는 보상의 양을 계산할 때는 기존에 누적된 보상의 양을 더해주어야 하죠.

표4. Calculation of A’s reward after additional staking, 설명을 위해 제작된 이미지입니다.

이에 따라 100초가 더 흐른 상황, 즉 300s가 되었을 때, A의 보상은 다음과 같이 계산할 수 있습니다.

그림15. Accrued rewards of A with recording previous reward, 설명을 위해 제작된 이미지입니다.

Conclusion

이번 글에서는 이더리움 네트워크에서 Staking 을 구현할 때 마주하는 가스에 대한 장벽을 살펴보았습니다. 또한 이에 대한 해결책으로 AAVE의 Distribution Index에 대해서도 알아보았는데요,

Distribution Index를 사용하면, 아래의 5개의 변수만 변경함으로써 Staking에 모든 유저의 누적 보상을 기록할 수 있다는 사실을 확인했습니다.

1. 유저의 deposit amount의 변경2. total deposit amount의 변경3. last updated index를 current index로 변경4. user index를 current index로 변경5. user accrued reward를 기록

다음 글에서는 AAVE의 Distribution Index가 코드로 어떻게 구현되어 있는지 살펴보고, 다른 Defi의 staking에 담겨있는 로직을 살펴보도록 하겠습니다.

Reference

https://docs.aave.com/aavenomics/safety-module

https://docs.sushi.com/products/yield-farming

https://ethereum.github.io/yellowpaper/paper.pdf

--

--