[Lido Finance] 1. 전반적인 작동원리와 StETH 컨트랙트 분석

DongHeon Lee
BerryFi
Published in
14 min readApr 10, 2022
출처: lido.fi

Author: 이동헌

Audited By: 이태헌, 표정우, 김홍욱

Intro

Lido Finance는 일반 투자자들이 이더리움 스테이킹에 보다 쉽게 참여할 수 있도록 만드는 유동화 스테이킹(liquid staking) 서비스이다. 이더리움 스테이킹이란 추후 이더리움 메인넷의 합의 레이어가 될 비콘체인(Beacon chain)의 검증인으로 참여하기 위해 Ethereum Deposit Contract에 이더를 예치하는 것을 말한다. 하지만 이더리움 스테이킹은 32ETH라는 큰 액수 단위로 예치해야 한다는 점, 비콘체인이 메인넷에 merge되기 전까지 자산을 인출할 수 없다는 점, 비콘체인의 상태 합의에 참여할 수 있도록 검증인 인프라를 항상 유지하고 있어야 한다는 점 때문에 일반 투자자들에게 장벽이 높다는 문제가 있다. Lido Finance는 투자자가 최소 예치 수량 없이 이더를 예치하고, 이에 대한 대가로 stETH를 발행하여 예치 자산을 유동화할 수 있게 함으로써 이 문제를 해결하고 있다. 이러한 가치를 인정받은 Lido Finance는 2021년 5월 Paradigm이 주도한 라운드에서 7300만 달러 투자를 유치한 데 이어 2022년 3월 a16z로부터 7000만 달러 규모의 투자를 받았고, 최근 기사에 따르면 이더리움 유동화 스테이킹 시장의 약 86%를 점유하고 있다.

이번 아티클에서는 Lido Finance의 전반적인 작동원리를 소개하고, Lido를 통해 예치된 이더리움의 유동화 토큰인 StETH 컨트랙트를 분석해볼 것이다.

이더리움 스테이킹, 왜 하는 걸까?

Lido Finance에 대해 알아보기 전에 이더리움 스테이킹이 생겨난 배경에 대해 먼저 알아보자.

출처: reddit

이더리움은 메인넷 런칭 이전인 2014년부터 PoW에서 PoS 합의 알고리즘으로의 전환을 준비해 왔다. 그리고 그 초석은 2020년 12월 비콘 체인을 런칭함으로써 마련되었다. 비콘 체인은 이더리움 메인넷과 병렬적으로 실행되는 독립적인 PoS 체인으로, 가상의 검증인인 Validator들의 주소와 상태를 저장하고 관리한다. 블록이 추가될 수 있는 시간 간격인 12초의 Slot마다 의사 랜덤하게(pseudo ramdomly) 선정된 Validator는 블록을 제안하고, 나머지 Validator들의 투표를 거쳐 블록이 확정된다.

이때 Validator를 활성화하는 것이 바로 이더리움 스테이킹이다. 이더리움 메인넷에 deploy된 Ethereum Deposit Contract에 예치된 32ETH 당 하나의 Validator가 활성화되며, 활성화된 Validator들은 Validator Client에서 실행된다. 올바른 블록에 투표하거나 제안한 블록이 확정되었을 경우 Validator는 보상을 받고, 블록을 제안하지 않거나 잘못된 블록을 제안 혹은 투표할 경우 Validator는 예치한 이더에서 슬래싱을 당한다.

현재 비콘 체인은 아무런 트랜잭션도 담고 있지 않은 껍데기 상태이지만, 2022년 2~3Q에 예정된 메인넷과의 “Merge”가 완료된 후에는 현재의 메인넷이 실행(Execution) 레이어, 비콘 체인이 컨센서스 레이어가 되어 PoS 알고리즘으로의 전환이 이루어질 것이다. 나아가 샤딩이 도입되면 비콘 체인은 병렬로 운영되는 모든 샤드 체인에 합의점을 제공하여 이를 처리하는 역할을 수행할 것이다. 현재 Ethereum Deposit Contract는 merge를 위한 충분한 수의 Validator와 이더를 효과적으로 묶어 놓기 위해 예치된 이더리움의 인출이 불가능한 one-way transaction으로 설계되어 있다.

출처: kadeemclarke’s medium

요약하면, 이더리움 스테이킹은 네트워크를 유지하기에 충분한 양의 Validator를 확보하기 위해 보상을 주고 비콘 체인의 Validator를 미리 모집하는 것이다. Lido Finance를 통해 이더를 예치하는 이들이 받아가는 이자의 원천이 바로 이 Validator 보상이므로, Validator 보상에 대해 좀더 알아보자.

비콘 체인의 Validator 보상

이더리움 Validator는 32개의 Slot으로 구성된 매 Epoch(6.4분)마다 비콘체인에서 새로 발행되는 ETH로 보상을 받는다. 각 Validator가 받는 보상은 base_reward 의 배수로 책정된다. base_reward는 적절한 수의 Validator를 유지하기 위해 동적으로 조정되는 값이다. Validator의 수가 너무 적다면 네트워크 안정성이 저해되므로 base_reward가 커지고, Validator의 수가 많다면 인플레이션을 줄이기 위해 base_reward를 낮추는 것이 기본 디자인이다.

Validator가 받는 보상은 크게 투표(attestation)에 대한 보상과 블록 생성에 대한 보상으로 구분된다. 투표에 대한 보상은 다시 정확도에 대한 보상(accuracy reward)과 투표의 블록 포함에 대한 보상(inclusion reward)으로 나뉜다. 투표는 비콘 체인의 head에 대한 투표인 LMD GHOST vote, 각 Epoch의 체크포인트에 대한 투표인 Casper FFG vote, 그리고 아직 활성화되지는 않았지만 비콘 체인 내 샤드 블록에 대한 레퍼런스인 Crosslink에 대한 vote로 나뉜다. (각 투표에 대한 자세한 설명은 본 아티클의 범위를 벗어나므로 이 링크에서 확인하시기 바랍니다.) 정확도에 대한 보상은 최대 3*base_reward 이고, 여기에 동일한 투표를 한 활성화된 Validator의 비율을 곱한 수치가 실제로 받는 정확도 보상이 된다. 즉 99%의 Validator가 동일한 투표를 했다면 정확도 보상은 0.99*3*base_reward가 된다.

투표의 블록 포함에 대한 보상(inclusion reward)은 Validator의 투표가 블록에 포함되었을 때 주어지는 보상으로, 블록에 투표가 늦게 포함될수록 지연된 슬롯의 역수에 해당하는 만큼으로 보상이 줄어든다. 즉 특정 슬롯의 블록 생성자로 지정된 Validator가 offline 상태여서 블록을 생성하지 못하였고, Validator들의 투표가 해당 슬롯을 건너뛰고 다음 슬롯의 블록에 포함되었다면 inclusion reward는 1/2로 줄어든다. 이같은 일이 두번 연속으로 발생하여 다다음 슬롯의 블록에 투표가 포함된다면 inclusion reward는 1/3이 된다.

이처럼 inclusion reward는 블록 생성자로 지정된 Validator가 제때 블록을 생성하는지와 관련이 있다. 그래서 비콘 체인은 블록 생성자에 대한 보상이 블록에 포함된 모든 투표에 대한 inclusion reward의 일부가 되도록 설계되었다. 따라서 Validator는 블록 생성자로 선정되었을 때 블록 생성의 의무를 다하고, 가능한 많은 투표를 블록에 포함시킬 인센티브를 갖게 된다. inclusion reward는 최대 base_reward 이고, 이 중 1/8은 블록 생성자에게, 나머지 7/8은 투표자에게 돌아가게 된다.

비콘 체인의 Validator 슬래싱

올바른 행위를 한 Validator에 대한 보상과 반대로 네트워크에 해를 끼치는 행위를 Validator에게는 슬래싱이 가해진다. 가령 블록 생성자로 선정되었을 때 2개 이상의 블록을 제안하거나, 악의적인 투표를 하거나, 8,192 epoch(약 36일) 동안 오프라인 상태에 있다면 최소 1ETH 상당의 자금을 몰수당하고 Validator의 지위를 상실할 수 있다. 프로토콜은 여러 Validator가 공모하여 악의적인 시도를 하는 것을 방지하기 위해 비슷한 시간대에 슬래싱을 당한 Validator의 수에 따라 추가적인 슬래싱을 가한다. 이는validator_balance*3*fraction_of_validators_slashed 라는 기본 공식으로 표현되며, 만약 전체 Valdiator의 3분의 1이 비슷한 시간대에 슬래싱을 당했다면 이들은 모든 예치된 자산(32ETH)을 몰수당하게 된다.

지금까지 살펴본 Validator 보상과 슬래싱 매커니즘은 Lido Finance가 자산을 예치하고, 이자를 분배하는 매커니즘과 연관을 갖고 있다. 먼저 Lido Finance의 전체적인 구조에 대해 알아본 후 작동방식을 분석해 보자.

Lido Finance의 구조

Lido Finance 프로토콜은 다음과 같이 구성되어 있다.

  1. Staking Pool : 예치금, 예치 보상, 인출을 관리하는 프로토콜이다. 아래와 같은 요소로 구성되어 있다.
  2. stETH : 비콘 체인에 예치된 이더와 1:1로 대응되는 유동성 스테이킹 토큰이다.
  3. DAO: Aragon DAO를 상속하여 각종 파라미터에 대한 거버넌스를 담당하는 프로토콜이다.

Staking Pool

Staking Pool은 이더 예치와 인출, stETH의 발행과 소각, Validator를 실행하는 node operator로의 자금 위임, 수수료와 예치 보상 관리를 담당하며 Lido.sol 컨트랙트에 의해 실행된다. 유저가 Staking Pool 컨트랙트에 전송한 이더는 32ETH 단위로 분할되어 node operator에게 배분되고, Ethereum Deposit Contract에 예치되어 Validator를 활성화하는 데 이용된다. Node operator는 DAO에 의해 선정되고 NodeOperatorsRegistry라는 별도 컨트랙트에 의해 관리된다. Staking pool은 node operators의 주소와 Validation에 사용하는 key, 보상의 분배 로직을 담고 있다.

출처: blog.lido.fi

Oracle은 Lido DAO에 속한 Validator들의 비콘 체인상의 이더 잔고를 추적하여 보고하는 핵심적인 컨트랙트이다. 이더 잔고는 Validator 보상에 의해 증가하고 슬래싱을 통해 감소할 수 있다. 이들의 잔고는 stETH와 1:1로 대응되어야 하므로 Oracle에 의해 이더 잔고가 보고되면 stETH의 발행량이 변화하게 된다.

Oracle의 보고는 frame이라는 시간 단위로 이루어지는데, frame은 DAO에 의해 설정되며 현재 값은 24시간이다. Lido의 컨트랙트에 Oracle이 보고한 데이터가 반영되기 위해서는 복수의 Oracle로부터 정족수(quorum)에 해당하는 만큼의 동일한 보고가 들어와야 한다. 만약 Oracle의 보고가 정족수에 도달하지 못한다면 다음 frame의 첫 번째 epoch가 되어야 새로운 보고를 할 수 있다. 2022년 4월 현재 Oracle로 등록된 주소의 수는 5개이며, 정족수는 3이다.

stETH token

Lido에 예치된 이더에 대응하여 발행되는 토큰으로 StETH.sol에 의해 배포되어 있으며, Lido.solStETH.sol을 상속한다. stETH는 유저가 Lido 프로토콜에 비콘 체인의 검증인 보상으로 예치된 이더 잔고가 증가하면 발행량이 증가하고, 슬래싱으로 인해 이더 잔고가 감소하면 발행량이 감소하는 리베이스 토큰이다. stETH를 redeem하면 대응되는 이더를 받을 수 있어야 하지만, 이더리움의 merge 전까지는 Ethereum Deposit Contract에 예치된 이더를 인출할 수 없으므로 지금은 stETH를 redeem하는 것이 불가능하다. 하지만 stETH를 다른 토큰과 스왑하거나 디파이 프로토콜에 예치하는 등의 방법으로 유저는 예치된 이더를 유동화할 수 있다.

Lido DAO

Lido는 예치된 ETH와 stETH의 1:1 대응 관계를 탈중앙화된 방식으로 유지하기 위해 DAO 구조를 채택했다. Lido DAO는 node operator와 oracle를 선정하고, 이더리움 스테이킹 프로토콜의 변화에 대응하여 Lido를 업데이트하고, slashing에 대비한 insurance provider를 선정하고, 프로토콜 내의 수수료가 모이는 treasury를 통해 Lido 프로토콜의 개발 비용을 충당하는 역할을 수행한다.

아래 컨트랙트들은 DAO Voting을 통해서 업데이트될 수 있다.

  • Lido.sol
  • NodeOperatorsRegistry.sol
  • LidoOracle.sol

이번 글에서는 StETH.sol 컨트랙트 분석을 통해 staked Ether의 유동화 토큰인 stETH의 로직에 대해 알아보고, 이어지는 글에서 Lido.sol 컨트랙트를 분석하도록 할 것이다.

컨트랙트 분석을 통한 stETH 토큰 로직 파악

stETH는 ERC20 토큰과 유사하지만 표준에 완전히 부합하지는 않는 ERC20-like Token으로 분류된다. stETH의 총 발행량은 Oracle이 보고한 Staked Ether 수량에 따라 변동하며, 각 account의 stETH 밸런스는 shares라는 별도 매핑을 통해 계산되기 때문이다.

StETH 는 address별 Pooled Ether에 대한 지분을 나타내는 shares라는 구조체와 approve와 유사한 allowance 함수에 사용되는 allowances 구조체를 갖고 있다.

StETH.sol은 Aragon DAO 컨트랙트에서 import한 UnstructuredStorage 라이브러리를 사용하는데, 이는 Struct 구조체를 활용하지 않고 assembly를 통해 각종 변수의 position을 기록해놓고 그 곳에 저장된 데이터를 읽고 쓰는 방식으로 작동한다. TOTAL_SHARES_POSITION는 이 방식으로 total shares 데이터를 keccak256("lido.StETH.totalShares") 라는 custom pointer가 참조하는 position에 저장하고 있다. UnstructuredStorage는 upgradable contract를 위한 proxy 패턴에서 사용되는 value type인데, Lido Finance가 근간을 두고 있는 Aragon DAO 컨트랙트가 이 패턴을 따르고 있기 때문에 동일한 방식이 사용된 것으로 보인다.

stETH의 총 발행량은 _getTotalPooledEther()과 같다. StETH.sol에는 _getTotalPooledEther() 함수가 추상화되어 있고 Lido.sol에 의해 오버라이드 되어 있다. 다음 편에서 살펴보겠지만 이 함수는 아직 stake되지 않은 이더 밸런스(buffered ether), stake 과정에 있는 이더 밸런스(transient balance), 그리고 비콘 체인에 stake된 밸런스를 합해 프로토콜에 예치된 총 이더 수량을 반환한다. 즉 stETH의 총량은 프로토콜에 예치된 총 이더 수량과 같다.

위 코드에 따르면 특정 account의 stETH balance는 프로토콜에 예치된 이더의 수량에서 지분 비율을 곱한 것과 같다. 이를 식으로 나타내면 다음과 같다.

balanceOf(account) = shares[account] * _getTotalPooledEther() / _getTotalShares()

_mintShares 함수는 특정 account의 shares와 total shares를 증가시킨다. 이는 유저가 이더를 예치할 때와 프로토콜 수수료를 거둘 때 호출된다. _burnShares 함수는 특정 account의 shares와 total shares를 감소시킨다. 아직까지 _burnShares 함수가 호출되는 상황은 구체화되어 있지 않은데, 이는 아직 stETH를 redeem하는 것이 불가능하기 때문으로 추측된다.

정리하면, stETH는 예치되어 있는 이더 balance와의 1:1 대응을 위해 shares라는 구조체를 활용한 리베이스 토큰이다. 다음 글에서는 Lido.sol 컨트랙트 분석을 통해 유저가 예치한 이더가 어떻게 Ethereum Deposit Contract에 예치되는지, 보상은 어떻게 분배되는지, 수수료는 어떻게 거두어지는지 등에 대해 알아볼 것이다.

--

--