[Compound Series] 3. 컴파운드 로직 분석 — 대출, 상환

Tariz
Decipher Media |디사이퍼 미디어
15 min readJun 28, 2021

시작하며

본 게시글에서는 앞 게시글 [Compound series] 1. 컴파운드 톺아보기에 이어 Compound의 로직을 컨트랙트 코드를 기반으로 분석합니다. Compound는 암호화폐 과담보 대출 서비스로 특정 암호화폐를 예금하고, 이를 담보로 원하는 암호화폐를 대출할 수 있는 서비스입니다. 오늘 게시글에서는 예금과 출금의 동작 방법에 대하여 구체적인 내용을 정리하며, 이를 위해 각 로직에 대한 Compound의 컨트랙트 코드를 분석합니다.

[Compound series]

  1. 컴파운드 톺아보기
  2. 컴파운드 로직 분석 (예금, 출금)
  3. 컴파운드 로직 분석 (대출, 상환)
  4. 컴파운드 로직 분석 (청산)
  5. 컴파운드 이자율 계산 분석
  6. 컴파운드의 미래 — Compound Gateway

Authors

부현식, 정현 of Decipher DeFi Research Team DeFinite
Seoul Nat’l Univ. Blockchain Academy Decipher(@decipher-media)

용어 정리

  • 사용자 : Compound 프로토콜에서 자산을 공급, 차입하는 모든 참여자. 공급자가 될 수도 있고 차입자가 될 수도 있다.
  • 공급자 : Compound에 자산을 예치만 한 사용자. ‘대출’ 이자를 수령할 수 있다.
  • 차입자 : Compound를 통해 자산을 예치 후 다른 자산을 빌려 가는 사용자. ‘차입’ 이자를 지불해야 한다.
  • 대출 서비스 : Compound 프로토콜 자체를 일컫는 말. Compound에서 제공되는 서비스를 일컬을 때는 ‘차입’ 서비스가 아닌 ‘대출’ 서비스라고 한다.
  • 대출금 : 공급자 입장에서 대출 서비스의 대상이 되는 자산. 예) 대출금 회수
  • 차입금 : 차입자 입장에서 대출 서비스의 대상이 되는 자산. 예) 차입금 상환
  • 기초 암호화폐(underlying cryptocurrency): Compound에서 cToken으로 변환되기 전 암호화폐를 의미

cToken

[Compound series] 1. 컴파운드 톺아보기 편에서 살펴본 것처럼 사용자는 Compound에서 원하는 암호화폐를 예치하고 이를 담보로 암호화폐를 차입할 수 있습니다. Compound의 운용 로직은 총 5가지로 나눌 수 있으며 예금, 출금, 대출, 상환, 청산이 이에 해당됩니다. Compound는 이 과정에서 사용되는 모든 자산을 cToken으로 변환하여 운용합니다.

Compound는 과담보 대출을 지원하는 모든 기초 암호화폐에 대한 cToken 컨트랙트를 가지고 있습니다. 즉, ETH에 대한 cETH 컨트랙트, DAI에 대한 cDAI 컨트랙트가 각각 존재합니다. 모든 자산의 컨트랙트는 Compound의 5가지 운용 로직을 모두 포함하고 있으며, 사용자의 거래 자산에 따라 선택되어 동작합니다. 쉽게 말해, 사용자가 ETH를 예치한다면 cETH 컨트랙트의 예치 기능이 동작하고, DAI를 예치한다면 cDAI 컨트랙트의 예치 기능이 동작하게 됩니다.

각 상황에 따른 cToken 컨트랙트의 동작 방법을 간단히 살펴봄으로써 cToken의 역할을 이해해보도록 하겠습니다.

상황 1. 공급자가 ETH를 Compound에 예치하는 경우

[Mint 함수 실행]1. 공급자의 월렛에서 ETH를 Compound의 cETH 컨트랙트에 전송2. Compound가 특정 교환 비율을 사용하여 전송한 ETH를 cToken인 cETH로 변환 후 사용자의 월렛에 전송

기초 암호화폐인 ETH에 대해 교환되는 cToken인 cETH의 개수는 Compound의 시장 상황에 따라 결정되는 ‘교환 비율’에 의해 결정됩니다.

상황 2. 공급자가 예치한 ETH를 Compound에서 출금하는 경우

[Redeem 함수 실행]1. ETH 출금 요청2. Compound가 특정 교환 비율을 사용하여 사용자가 요청한 ETH를 받기 위해 지급해야 할 cETH의 개수 계산3. 사용자가 계산된 cETH를 Compound의 cETH 컨트랙트에 전송4. Compound가 1번에서 요청한 ETH를 공급자의 월렛에 전송

상황 3. 차입자가 예치한 ETH를 담보로 Compound에서 DAI를 차입하는 경우

[Borrow 함수 실행]1. 사용자가 DAI 차입 요청2. Compound가 사용자가 요청한 DAI가 해당 사용자에게 대출 가능한지 확인3. Compound가 사용자가 요청한 DAI를 사용자 월렛에 전송

상황 4. 차입자가 차입한 DAI를 Compound에 상환하는 경우

[Repay 함수 실행]1. 차입자가 DAI 상환 요청2. Compound가 차입자의 DAI 상환 가능 여부 확인3. 차입자가 Compound의 cDAI 컨트랙트에 차입한 DAI를 전송

대출 [Borrow]

요약: 차입자가 차입하고자 요청한 기초 암호화폐의 대출 가능 여부를 확인 후 차입자의 월렛에 전송

borrow는 차입자가 Compound에 예치한 기초 암호화폐를 담보로 다른 암호화폐의 차입을 요청할 때 사용되는 기능입니다.

  1. Compound 시장의 대출량과 이자율 검사
borrowInternal

borrowInternal 함수는 borrowFresh 실행 전 간단한 에러를 확인하는 기능입니다. 위 함수에서 확인하는 에러는 accrueInterest 함수로 Compound 시장의 암호화폐의 대출량과 이자율을 계산하는 로직에 이상이 없다는 것을 확인합니다. 이후 borrowInternal 함수는 borrowFresh 함수를 실행합니다.

한편, accrueInterest 함수는 예금을 위해 실행되는 mint 함수뿐만 아니라 출금, 대출, 상환을 위해 실행되는 모든 함수에서 실행됩니다. 이 함수의 핵심 역할은 각 함수의 실행마다 이자를 계산하는 것입니다.

accrueInterest 함수에 대한 자세한 내용은 다음 게시글인 [Compound series] 3. 컴파운드 이자율 분석에서 다룰 예정입니다.

2. 차입금에 대한 대출 가능여부 확인

대출 기능의 핵심 로직은 borrowFresh 함수를 통해 실행됩니다.

borrowFresh
borrower: 차입자의 월렛 주소 
borrowAmount: 차입자의 담보를 통해 대출할 수 있는 차입금(기초 암호화폐)

borrowFresh의 입력값은 차입자의 월렛주소와 차입자가 Compound에 요청한 차입금입니다. 사용자는 계좌의 상태와 Compound의 시장 상황에 따라서 요청한 차입금을 차입할 수도 혹은 하지 못할수도 있습니다.

이에 borrowFresh 함수는 사용자가 요청한 차입금을 우선 입력값으로 받은 후 이를 Compound가 대출해줄 수 있는지 확인하기 위해 comptroller 컨트랙트의 borrowAllowed를 통해 몇 가지 조건을 확인합니다.

1) 요청한 cToken이 compound 시장에 있는 토큰인지 확인

borrowAllowed — isListed

Compound는 모든 기능을 운용하는 데 있어서 공급자의 기초 암호화폐를 모두 cToken으로 변환합니다. 이에 Compound는 (1) 사용자의 대출 자산이 Compound 시장에서 이미 운용되고 있는 자산이어야 하며, (2) 해당 기초 암호화폐의 cToken이 발행 가능한 상태여야 합니다. 위 과정에서는 이 두 가지를 검사한 후 다음 동작을 실행합니다.

2) 오라클 가격 설정 여부 확인

borrowAllowed — oracle

차입을 요청한 기초 암호화폐에 대한 가격을 불러올 수 있는 오라클 시스템이 설정되어있는지 확인합니다. 만약 이 오라클을 통해 가져온 기초 암호화폐의 가격이 0이라면 borrowAllowed는 실패하게 됩니다.

3) 대출의 임계값 확인

borrowAllowed — borrowCap

각 자산에 대한 대출 한도가 설정되어 있습니다. 이 과정에서는 사용자가 요청한 차입금을 Compound가 대출해 줄 경우 해당 자산의 대출 한도를 넘는지 확인합니다. 자산의 대출금을 계산하는 방법은 아래와 같습니다.

borrowCap: 자산의 대출 한도 
totalBorrows: 컨트랙트의 전체 대출량 (기초 암호화폐)
borrowAmount: 차입자의 담보를 통해 대출할 수 있는 차입금(기초 암호화폐)
nextTotalBorrows = totalBorrows + borrowAmount

이처럼 계산된 nextTotalBorrows 가 미리 설정된 대출한도인 borrowCap을 넘을 경우 로직은 실패하게 됩니다. 예를 들어, ETH의 대출한도가 1000이고 현재 전체 대출량이 900이라 할 때, 사용자가 110 ETH의 대출을 요청하면 이 요청은 실패로 처리됩니다. 이 사용자가 성공적인 대출을 하기 위해서는 1000–900 = 100 이하의 ETH를 차입해야합니다.

4) 사용자의 계좌 유동성 확인

borrowAllowed — getHypotheticalAccountLiquidityInternal

대출 시 사용자의 계좌 유동성 확인을 위한 과정은 링크된 게시글을 확인해주세요.

5) distributeSupplierComp 함수를 통해 공급자에게 제공해야 할 COMP 토큰의 개수 계산

distributeSupplierComp

Compound는 모든 사용자(공급자, 차입자)에게 서비스 사용에 대한 특수한 리워드인 Compound의 거버넌스 토큰인 COMP를 제공합니다. COMP 토큰의 제공량은 블록마다 정해져 있으며, 제공 비율은 각 Compound 내의 토큰 마켓의 상황에 따라 변화합니다. COMP 토큰의 자세한 내용은 본 Compound의 docs에서 확인할 수 있습니다.

3. 이자율을 가장 최신 블록 시점으로 업데이트했는지 확인

borrowFresh — accrualBlockNumber

다시 borrowFresh로 돌아오면, borrowAllowed 함수에 에러가 없는 경우 이자율 업데이트 여부를 확인합니다. Compound의 이자율은 블록마다 변경될 수 있지만, 연산의 효율을 위해 ‘블록마다’ 계산하여 업데이트하지 않고 특정 기능이 호출될 때 밀린 블록만큼의 연산을 누적하여 한 번에 실시합니다. borrowFresh 함수는 borrow 함수를 호출한 시점의 블록 넘버에서 이자율이 다른 사용자의 거래에 의해 업데이트되었는지 확인하는 과정을 거칩니다.

4. Compound 내의 유동성(cash) 확인

borrowFresh — getCashPrior
getCashPrior: 시장이 유동할 수 있는 기초 암호화폐의 개수

borrow 기능 또한 redeem 기능과 마찬가지로 컨트랙트의 기초 암호화폐의 자금을 사용자의 월렛에 전송하기 때문에, 해당 컨트랙트가 유동할 수 있는 기초 암호화폐 개수가 사용자에게 지급해야하는 개수보다 많은지 확인해야합니다.

getCashPrior 함수를 통해 가져오는 값은 cToken 컨트랙트가 보유한 전체 기초 암호화폐 중에서 유동할 수 있는 기초 암호화폐의 개수입니다. 즉, Compound는 공급자가 요청한 기초 암호화폐를 전송하기 전에 지급 개수가 cToken 컨트랙트가 유동할 수 있는 기초 암호화폐 개수보다 작은지 확인하고, 큰 경우 에러를 반환합니다.

5. 사용자의 cToken 보유량과 컨트랙트의 cToken 발행량 업데이트

borrowFresh — addUInt
accountBorrows: 차입자가 과거에 차입한 기초 암호화폐의 전체 개수 
totalBorrows: 컨트랙트의 전체 대출량 (기초 암호화폐)
borrowAmount: 차입자의 담보를 통해 대출할 수 있는 차입금 (기초 암호화폐)

이제 borrowFresh함수는 차입자가 차입금으로 요청한 기초 암호화폐의 개수(borrowAmount)가 대출 가능하다고 판단되면, Compound 프로토콜은 컨트랙트의 전체 대출량과 사용자의 대출량을 업데이트 합니다. 컨트랙트가 대출 기능을 통해 사용자에게 기초 암호화폐를 제공하면 컨트랙트가 전송한 기초 암호화폐의 개수와 사용자가 차입한 기초 암호화폐의 개수가 증가하므로 다음과 같이 계산합니다.

6. 토큰 양 업데이트 후 이벤트 기록

borrowFresh — storaged

이제 Compound 프로토콜은 5번 과정에서 계산한 값인 전체 cToken 발행량과 사용자의 cToken 보유량을 업데이트 합니다.

7. 전송 결과 확인

위 과정이 모두 정상적으로 동작했다면 컨트랙트는 컨트랙트는 사용자에게 차입금을 전송합니다.

위 사진을 통해 사용자가 미리 입력한 1.09개의 DAI를 전송한 후 사용자 계좌의 전체 차입량과 컨트랙트의 전체 차입량을 모두 확인할 수 있습니다.

  • 사용자의 대출량(borrowAmount): 1.09 DAI
  • 사용자의 전체 대출량(accountBorrows): 1.11 DAI
  • cDAI 컨트랙트의 전체 대출량(totalBorrows): 43029235 DAI

상환 [Repay]

요약: 차입한 금액을 Compound에 전송하여 차입금 상환

repayBorrow 기능은 차입자가 차입한 자금 중 원하는 기초 암호화폐를 cToken 컨트랙트에 전송하여 상환할 때 사용되는 기능입니다. 다음 3가지 로직은 예금 함수의 과정과 동일하므로 설명에서 제외합니다.

  1. Compound 시장의 대출량과 이자율 검사
  2. 사용자에게 제공해야 할 cToken 발행 여부 확인과 COMP 토큰 계산
  3. 이자율을 가장 최신 블록 시점으로 업데이트했는지 확인

4. 사용자의 cToken 보유량과 컨트랙트의 cToken 발행량 업데이트

borrowBalanceStoredInternal
accountBorrows: 차입자가 과거에 차입한 기초 암호화폐의 전체 개수

차입자의 상환금이 상환 가능하다고 판단되었다면, 컨트랙트는 사용자의 차입 현황을 업데이트 해야합니다.

doTransferIn
actualRepayAmount: 차입자가 상환하고자 하는 기초 암호화폐의 개수 
accountBorrows: 차입자가 과거에 차입한 기초 암호화폐의 전체 개수
totalBorrows: 컨트랙트의 전체 대출량 (기초 암호화폐)

컨트랙트는 doTransferIn을 통해 차입자가 상환하고자 하는 기초 암호화폐의 개수를 불러옵니다. 상환 기능은 사용자가 가진 기초 암호화폐를 컨트랙트에 전송하는 것입니다. 이를 통해 사용자가 과거 차입한 기초 암호화폐의 개수와 컨트랙트가 과거 차입해주었던 기초 암호화폐가 줄어들기 때문에 전체 차입금에 대하여 다음과 같이 계산합니다.

accountBorrowsNew = accountBorrows + actualRepayAmount 
totalBorrowsNew = totalborrows + actualRepayAmount

5. 토큰 양 업데이트 후 이벤트 기록

storage

이제 Compound 프로토콜은 4번 과정에서 계산한 값인 전체 차입량을 업데이트 합니다.

6. 전송 결과 확인

위 과정이 모두 정상적으로 동작했다면 컨트랙트는 사용자에게 상환금을 전송하도록 합니다.

위 사진을 통해 사용자가 컨트랙트에 상환금으로 11 DAI를 전송한 것을 알 수 있습니다. 또한 사용자의 사용자 계좌의 전체 차입량과 컨트랙트의 전체 차입량을 모두 확인할 수 있습니다.

  • 사용자의 대출량(borrowAmount): 11 DAI
  • 사용자의 전체 대출량(accountBorrows): 0.05 DAI
  • cDAI 컨트랙트의 전체 대출량(totalBorrows): 43029141 DAI

마치며

[Compound series] 2. 컴파운드 로직 분석은 Compound 서비스의 주요 로직중 예치, 출금, 차입, 상환 시 실행되는 함수의 기능과 동작을 cToken 컨트랙트를 기반으로 자세하게 분석하였습니다.

Compound는 다양한 암호화폐들을 예치하고, 이를 담보로 다른 암호화폐를 차입할 수 있는 과담보 대출 서비스를 제공합니다. 그 과정에서 예치한 암호화폐들은 Compound에서 운용되는 자산인 cToken으로 변환되며 Compound에서 지정된 로직에 의해 그 양과 금리가 결정됩니다.

본 게시글에서는 [Compound Series] 2.1. 컴파운드 로직 분석 — 예금, 출금편에 이어 대출과 상환 로직을 살펴보았으며, 다음 게시글에서는 컴파운드의 이자율 결정 방법에 대해서 알아봅니다.

디사이퍼 — DeFinite팀 소개

이병헌 — Decipher Senior Researcher, President of Decipher

부현식 — Decipher Mentor

이우진 — Decipher Junior Researcher

정재영 — Decipher Senior Researcher

정현 — Decipher mentor

--

--