[Olympus DAO를 분석해보자 ] — 5. 컨트랙트 배포

Ivan Kim
BerryFi
Published in
20 min readApr 23, 2022

BerryFi의 첫 번째 연재 시리즈였던 올림푸스 다오의 스마트 컨트랙트를 Rinkeby 테스트넷 환경에 배포하는 과정을 설명하는 글입니다. 본 글은 올림푸스 다오 깃헙 레포지토리 메인 브랜치에 2022년 4월 2일에 업데이트 된 efdd1e2415 커밋을 기준으로 합니다.

Author: 김홍욱

Audited By: 이태헌, 표정우, 이동헌

로컬 개발 환경 설정하기

올림푸스 다오는 ‘분산화된 자율 조직’ 형태로 운영되고 있습니다. 그러나 스마트 컨트랙트 깃헙에 한해서만 올해 약 40회가 넘는 커밋이 발생했을 정도로 개발 진행 속도가 상당히 빠른 편입니다. 이에 최대한 동일한 환경에서 스마트 컨트랙트를 배포할 수 있도록 다음과 같이 git clone을 진행합니다.

$git clone https://github.com/OlympusDAO/olympus-contracts.git
$cd olympus-contracts
$git log
$git reset --hard efdd1e2415106ae03e64b06f4d73796747fef6c8
  • git log를 통해 커밋 아이디 확인
  • git reset --hard를 통해 원하는 커밋 아이디의 깃헙 복구

git clone을 진행 후 스마트 컨트랙트 배포에 필요한 패키지를 설치합니다. 다만, 올림푸스 다오는 기본적으로 ‘node v14’를 지원하기 때문에 NVM(Node Version Manager)를 이용하여 노드 버전을 맞추도록 합니다. 참고로 본 글은 스마트 컨트랙트 배포에 초점을 맞추고 있어 node, nvm, yarn 등과 같은 기본적인 개발 프로그램에 대한 설명은 생략합니다.

$nvm ls
$nvm install 14.17.3
$nvm use 14.17.3
  • nvm ls를 통해 설치한 버전과 설치 가능한 버전 확인
  • nvm install를 통해 특정 버전의 노드 설치
  • nvm use를 통해 특정 버전으로 사용 전환

스마트 컨트랙트 배포에 필요한 패키지를 설치합니다. 주요 패키지 내용도 함께 확인합니다. (package.json)

$yarn install
  • hardhat : 이더리움 개발 프레임워크
  • openzeppelin/contracts : 컨트랙트 구현체와 라이브러리 모음
  • dotenv : 중요한 환경변수를 .env 파일에 저장 및 관리할 수 있게 해주는 도구

패키지 설치 후 환경변수를 설정합니다. 설정이 필요한 환경변수는 .env.sample 파일에서 확인 가능합니다. 해당 파일을 참고하여 .env 파일을 생성한 후 환경변수 설정을 완료합니다.

$cp .env.example .env
$vi .env
  • PRIVATE_KEY : 컨트랙트 배포 및 가스 수수료를 부담하는 계정의 프라이빗 키
  • ALCHEMY_API_KEY : 블록체인 노드와 연결할 수 있는 API 제공 (API 발급 방법)
  • ETHERSCAN_API_KEY : 이더스캔을 연결할 수 있는 API 제공 (API 발급 방법)

ALCHEMY와 ETHERSCAN의 API를 발급 받는 방법은 링크로 대체합니다. 위 3가지 환경변수는 반드시 설정해 주어야 합니다. 해당 환경변수들은 컨트랙트 배포 계정의 프라이빗 키 등을 포함하고 있음으로 보안에 각별히 신경 써야 합니다. 특히, 공개된 깃헙에 .env파일이 노출되지 않도록 .gitignore.env를 추가합니다.

아래 명령어를 통해 컨트랙트가 정상적으로 컴파일 되는 지 확인합니다. ‘Compilation finished successfully’ 문구가 나타나면 정상적으로 컴파일 된 것이며, olmpus-contracts 폴더 내에 artifactscache 폴더가 생성된 것을 확인할 수 있습니다.

$yarn compile 

‘artifacts’ 폴더는 컴파일의 결과물을 저장합니다.
여기서 말하는 결과물이란, 컨트랙트 기능을 구조화한 것으로 일종의 컨트랙트 사용 설명서 역할을 합니다. 컴퓨터는 컨트랙트 주소만으로 컨트랙트가 어떤 기능을 보유하고 있는 지 알 수 없습니다. 따라서 컨트랙트 기능 수행에 대한 명령을 내리기 위해서는 컨트랙트 주소와 함께 컨트랙트 사용 설명서가 필요합니다. 이를 ABI(Application Binary Interface)라고 합니다. ‘artifacts’ 폴더에 저장되어 있는 컨트랙트 json 파일을 살펴 보면, 각각 abi라는 항목을 통해 컨트랙트 기능이 정의되어 있음을 확인할 수 있습니다.

컨트랙트 폴더 구성 살펴보기

contracts 폴더 내부를 살펴보면 상당히 많은 컨트랙트들이 존재합니다. 이는 올림푸스 다오가 V1에서 V2로 마이그레이션 하고, 외부 디파이 전략으로 Allocator를 확장하면서 기능 세분화가 이루어진 것으로 보입니다. 본 글에서는 올림푸스 다오 V2의 핵심 요소인 Bond, Stake, Wrap 3가지 기능에 집중 할 예정입니다. 이에, 컨트랙트 폴더 구성을 살펴보며 우리가 집중해야 할 컨트랙트들은 무엇인지 선별하는 작업을 진행합니다.

contracts

allocators : 외부 디파이와 연계하여 자산 운용하는 컨트랙트 (불필요)
cryptography : 서명 및 암호화 작업에 필요한 라이브러리
governance : 올림푸스 다오 V2에서 처음 소개된 gOHM 컨트랙트
interfaces : 컨트랙트 인터페이스 제공
libraries : 컨트랙트 라이브러리 제공
migration : 올림푸스 다오 V1에서 V2로의 마이그레이션 컨트랙트 (불필요)
mocks : 컨트랙트 기능 및 작동 테스트에 필요한 기능 제공 (불필요)
OLD : 올림푸스 다오 V1에서 사용한 wsOHM 컨트랙트 (불필요)
peripheral : 기부(donation) 컨트랙트 (불필요)
testnet : 테스트넷 컨트랙트 (불필요)
types : 핵심 부가 기능 제공
vesting : 프로젝트 초기 투자 유치 및 토큰 분배 관련 컨트랙트 (불필요)
BondDepository.sol : 본드 판매 기능 제공
OlympusAuthority.sol : 프로젝트 권한 설정 기능 제공
OlympusERC20.sol : OHM 토큰 컨트랙트
sOlympusERC20.sol : sOHM 토큰 컨트랙트
Staking.sol : OHM 스테이킹 및 랩핑 기능 제공
StakingDistributor.sol : 스테이킹 보상 분배 관련 컨트랙트
StandardBondingCalculator.sol : Liquidity 토큰 가치 산정 기능 제공
Treasury.sol : 금고 및 자산 운용 기능 제공
TreasuryExtender.sol : Allocator 관련 금고 확장 기능 제공 (불필요)

기본 기능과 상관 없는 불필요한 폴더 및 파일은 삭제합니다. 삭제 후 yarn compile 명령어를 다시 입력하여 컴파일이 잘 되는 지 확인 합니다. 상속에 필요한 파일 등을 잘못 삭제하는 경우에는 컴파일 시 에러가 발생합니다. 컴파일 에러가 발생하지 않았다면 컨트랙트 배포에 필요한 하드햇(Hardhat) 설정 단계로 넘어갑니다.

불필요한 폴더 및 파일 삭제 후 컨트랙트 목록

하드햇 설정하기

하드햇(Hardhat)은 트러플(Truffle)과 함께 대표적인 스마트 컨트랙트 개발 도구입니다. 과거에는 트러플을 채택하는 프로젝트가 많은 편이었지만, 최근에는 각종 편의 기능과 커스터마이징 기능을 제공하는 하드햇으로 옮겨가는 추세입니다. 하드햇이 제공해주는 가장 대표적인 예로는 솔리디티 파일에서 console.log 사용하는 것이 가능하다는 것이 있으며, 이 외에도 타입스크립트 및 Upgrade Proxy 지원, 손쉬운 테스트 환경 구축 등이 있습니다.

올림푸스 다오는 하드햇을 채택하고 있습니다. 아마도 ‘분산화된 자율 조직’에서 발생할 수 있는 비효율을 최소화 하고자 타입스크립트를 지원하는 하드햇을 선택한 것으로 판단됩니다. 또한, 올림푸스 다오는 컨트랙트를 다양한 체인에서 배포할 수 있도록 하드햇 설정(hardhat.config.js)에 대한 기본 커스터마이징을 잘 해놓았습니다. 우리는 이더리움 테스트넷인 Rinkeby에 배포 할 예정임으로 불필요한 내용들은 다음과 같이 주석 처리합니다.

  • dotenvConfig : .env에 저장한 환경변수를 사용하기 위한 기본 설정
  • chainIds : 배포할 체인의 아이디 저장 (Ethereum — 1, Rinkeby — 4)
  • getChainConfig : 하드햇 네트워크 설정 함수
  • config > defaultNetwork : 기본으로 자동 선택되는 네트워크
  • config > networks : 네트워크 정보 설정
  • config > solidity : 솔리디티 버전에 따른 컴파일러 정보 설정
  • config > nameAccount, typechain : 타입스크립트 편의 기능 설정
  • config > mocha : 테스트 프레임워크인 모카 기능 설정

컨트랙트 배포하기

1) 배포 전 준비물
배포 스크립트를 작성하기 전에 Rinkeby 테스트넷 전용 이더리움과 올림푸스 다오의 DAI를 대체 할 리저브 토큰이 필요합니다. 본 글에서는 테스트넷 전용 이더리움을 제공해 주는 여러 Faucets 사이트 중 관리가 잘 되고 있는 Chainlink Faucets를 이용 할 예정입니다. 해당 사이트는 Rinkeby 테스트넷 전용 LINK 토큰도 함께 지급해 주고 있어, LINK 토큰을 DAI를 대체 할 리저브 토큰으로 선정합니다. 토큰을 받을 계정은 .env 파일에서 설정한 배포 계정과 동일해야 합니다.

2) 스크립트 작성
하드햇은 스크립트를 이용한 컨트랙트 배포 및 실행 기능을 지원합니다. ‘scripts’ 폴더를 열어 보시면 올림푸스 다오에서 만든 다양한 스크립트 샘플을 확인할 수 있습니다. 우리가 주의 깊게 봐야 할 파일은 ‘deployAll.js’ 파일입니다. 그러나 해당 파일을 실행해 보면 우리가 원하는 값을 얻을 수 없습니다. 올림푸스 다오 V1에서 V2로 마이그레이션 할 때 작성된 스크립트이기 때문에 초기 설정 값이 상이하며, 무엇보다 불필요한 마이그레이션 프로세스가 포함되어 있습니다. 또한, 본드를 생성할 수 있는 BondDepository 컨트랙트에 대한 배포 내용은 포함되어 있지 않습니다. (‘deployBondDepo.js’ 파일이 별도로 있지만 배포 과정만 있고 초기 세팅 작업은 생략되어 있습니다.)

이에 다음과 같이 ‘deployAll.js’ 스크립트 파일을 재구성하였습니다.

  • firstIndexOfsOHM : sOHM과 gOHM 교환 비율을 1로 세팅합니다. (‘1000000000’ = 1 )
  • epochLength : Epoch 주기로 8시간으로 세팅합니다. (28800 = 8시간)
  • firstEpochNumber : 최초 Epoch 넘버로 0으로 세팅합니다.
  • firstEpochEndTime : 최초 Epoch 종료 시점만 임의로 정할 수 있습니다. (1650340800 = 2022–04–19 13:00 KST)
  • rewardRate : Staker들에게 돌아가는 보상 비율입니다. (4580 = 0.4580%)
  • refReward : 프론트를 제공하는 Referral에게 돌아가는 보상 비율입니다.
  • daoReward : DAO에게 돌아가는 보상 비율입니다. (10000 = 100%)
  • refRewarddaoReward의 합은 10000, 즉 100%가 되도록 설정합니다.
  • OlympusStaking : Staking 컨트랙트 배포 시 6개의 파라미터가 필요합니다. 스크립트 파일 원본에는 firstBlockNumber로 되어 있으나 실제 constructor를 보면 firstEpochEndTime이 맞습니다.
  • olympusTreasury.enable : Treasury 컨트랙트는 자금 운용과 관련된 11가지의 Permission Status가 존재합니다. 원본에서는 queueTimelockexecute를 이용하여 status를 설정하고 있습니다. 이는 DAO에서 많이 채택하는 방식으로 특정 작업에 대한 실행 유예 기간을 두어 누군가의 독단적인 행위를 방지합니다. 우리는 동일한 기능을 수행하는 enable함수를 사용하여 status 설정이 즉시 적용될 수 있도록 합니다.
  • INITIAL_RESERVES, INITIAL_PROFIT : 올림푸스 다오의 OHM 토큰은 1달러에 지지(backing)됩니다. 이에 1달러 지지선이 무너지면 OHM 토큰을 발행하지 못하도록 하는 excessReserves 함수에 의해 제동이 걸립니다. 최초 컨트랙트 배포 시에는 해당 함수가 작동되지 않도록 일정량의 Reserve 토큰을 Treasury에 예치해야 합니다. 이 때, INITIAL_PROFIT을 통해 예치된 Reserve 토큰 중 얼마 만큼을 신규 발행 할 OHM 토큰을 지지하게 할 지 조정할 수 있습니다. Treasury에 Reserve 토큰을 예치하는 경우, 해당 수량 만큼 OHM이 발행되도록 설계 되어 있는데 PROFIT 값을 통해 발행되는 OHM의 개수를 조절할 수 있습니다. PROFIT 만큼 발행되지 않은 OHM의 개수는 말 그대로 잉여 자금이 되기 때문에 excessReserves 함수의 감시를 피할 수 있습니다.
  • addRecipient : Staker들을 위한 rewardRate를 적용합니다.
  • setRewards : Referral과 DAO를 위한 reward를 적용합니다.
  • gOHM.migrate : V1에서 V2로 마이그레이션 할 때 gOHM에 대한 발행 권한을 일시적으로 Migrator에 위임한 후 sOHM 컨트랙트로 이관합니다. 우리는 Migrator 컨트랙트를 별도로 배포하지 않았기 때문에 Deployer 계정이 잠시 gOHM에 대한 발행 권한을 가지고 있다 sOHM 컨트랙트에 위임합니다.

3) 스크립트 실행
다음의 명령어를 통해 스크립트를 실행합니다. 실행 결과도 함께 첨부합니다. 배포 후에는 이더스캔(Rinkeby)에서 리버트(revert)가 발생한 트랜잭션이 있는 지 반드시 확인합니다. 모든 스크립트 명령문이 정상적으로 실행 되었다면, 올림푸스 다오 컨트랙트 배포 작업이 성공적으로 마무리 되었음을 의미합니다.

$npx hardhat run ./scripts/deployAll.js --network rinkeby

본드 생성 및 구매하기

1) Remix 설정하기
컨트랙트 배포가 잘 되었는 지 확인하기 위해 이더리움 IDE 프로그램인 Remix를 사용하여 본드를 직접 생성해 보고 구매해 봅니다. Remix는 사용자의 로컬 파일 시스템에 접근할 수 있는 편의 기능을 제공합니다. 해당 기능을 사용하기 위해 우선 remixd를 설치합니다.

$npm install -g @remix-project/remixd

설치 후 프로젝트 디렉토리로 이동하여 다음의 명령어를 입력합니다.

$remixd -s . --remix-ide <https://remix.ethereum.org>

그리고 Remix IDE에 접속합니다. 워크스페이스 메뉴를 클릭하여 connect to localhost라는 항목을 선택합니다. 그러면 다음과 같이 Remix IDE에서 로컬 파일 시스템에 저장되어 있는 컨트랙트 목록을 확인할 수 있습니다. 더 자세한 설치 및 사용법은 공식 문서를 참고해 주시기 바랍니다.

2) 본드 생성하기
컨트랙트 폴더에서 BondDepository.sol 파일을 열고 왼쪽의 3번째 메뉴에서 컴파일을 진행합니다. 컴파일이 끝나면, 4번째 메뉴에서 Injected Web3 환경을 설정하여 메타마스크와 연결합니다. 메타마스크는 Rinkeby 테스트넷으로 설정되어 있어야 하며 연결 계정 또한 컨트랙트 배포 계정과 동일해야 합니다. 그리고 At Address 입력란에 우리가 배포한 BondDepository 컨트랙트 주소를 입력 및 클릭하면, 해당 컨트랙트의 읽기(파랑), 쓰기(주황) 함수가 표시됩니다. 우리가 배포한 컨트랙트 주소를 잘 가져 왔는지 확인하기 위해 읽기 함수 중에 Authority를 클릭해 봅니다. 이 때, 우리가 배포한 Authority 컨트랙트 주소가 출력이 되어야 합니다.

BondDepositoryl 파일 열기
BondDepository 컴파일 진행
BondDepository의 읽기 및 쓰기 함수

create 함수를 펼치면 5개의 입력란이 나타납니다. 각 항목에 대한 설명은 컨트랙트 주석 또는 올림푸스 다오 연재 시리즈 — Bonding 컨트랙트 코드 리뷰 편을 참고해 주시기 바랍니다. 매우 큰 숫자는 오버플로우가 발생할 수 있어 string으로 표시합니다.

  • quoteToken : 0x01BE23585060835E02B77ef475b0Cc51aA1e0709 — LINK 토큰 주소
  • market : [“20000000000000000000”, “1000000000”, “100000”] — [capacity, price, buffer]
  • booleans : [true, true] — [capacity in quote, fixed term]
  • terms : [60, 1650844800] — [vesting length, conclusion]
  • intervals : [14400, 3600] — [deposit interval, tune interval]

우리가 생성할 본드에 대해 간략하게 설명해 드리자면, LINK 토큰으로만 결제할 수 있으며 총 20개의 LINK 토큰이 모일 때까지 판매가 유지됩니다. 판매 가격은 1달러이고 1 OHM=1 LINK=1 USD로 매칭되어 있습니다. 베스팅 기간은 60초로 짧게 설정하였으며, 본드 판매 종료 시점은 2022년 4월 25일입니다. 본드 정보 입력 후, transact 버튼을 클릭하면, 메타마스크 승인 과정을 통해 본드가 성공적으로 생성됩니다. 본드 생성 후 BondDepository의 읽기 함수 liveMarkets를 클릭하면 marketId 0을 출력합니다.

create 쓰기 함수와 liveMarkets 읽기 함수

3) 본드 구매하기
deposit 쓰기 함수를 통해 방금 생성한 bond를 직접 구매할 수 있습니다. 그러나 그 전에 Bond 컨트랙트가 사용자가 보유한 LINK 토큰을 전송할 수 있도록 approve작업을 해줘야 합니다. 이에 로컬 파일시스템 목록으로 돌아가 type폴더에 있는 ERC20컨트랙트를 BondDepository컨트랙트와 같은 방법으로 컴파일 및 불러오기를 진행합니다. 컴파일러 버전은 0.7.5로 설정해야 하며, 불러올 때는 At Address에 LINK 토큰 주소를 입력합니다. approve 쓰기 함수에 파라미터는 다음과 같이 입력합니다.

  • spender : 0xB7548a11c26D0a35b613Cb298C7A42269EA813A0 — Bond 컨트랙트 주소
  • amount : “100000000000000000000000000000000” — Large approval
컴파일 버전 재설정 — 0.7.5
approve 쓰기 함수

BondDepository 컨트랙트로 다시 돌아와 deposit함수를 펼치면 마찬가지로 5개의 입력란이 나타납니다. amount 항목은 구매자가 지불할 LINK 토큰의 개수입니다. 우리가 생성한 본드의 capacity는 LINK 토큰 20개입니다. 그러나 해당 항목에 20개, 아니 5개만 입력해도 오류가 발생합니다. 그 원인은 maxPayout에 있습니다. 올림푸스 다오는 본드가 선형적으로 판매되는 것을 가장 이상적인 형태로 가정합니다. 이에 capacity와 본드 종료 시점을 고려하여 한 번에 구매할 수 있는 최대 수량인 maxPayout이 존재합니다. 따라서 해당 값을 초과하는 amount 값을 입력하게 되면 오류가 발생하게 됩니다. (maxPayout 만큼 여러 번 구매는 가능합니다.)

  • id : “0” — market id
  • amount : “500000000000000000” — 지불 할 LINK 토큰 개수 (0.5개)
  • maxPrice : “12000000000” — 슬리피지 고려한 최대 가격 설정 (20%)
  • user : “0x7D20307D274757192FB6Aa745fC285b59c61f8cc” — sOHM 받을 계정 주소
  • referral : “0x0000000000000000000000000000000000000000” — 프론트 공급자 주소

본드 생성 시 베스팅 기간을 60초로 짧게 설정했기 때문에 60초가 지나면 클레임(=redeem)이 가능합니다. 우선 클레임을 하기 전, 현재 계정이 보유한 OHM 토큰 개수와 sOHM 토큰 개수를 확인합니다. 컨트랙트 배포 시 INITIAL_RESERVES 예치에 따라 OHM은 10개, sOHM은 0개를 보유하게 됩니다. 만약 본드가 정상적으로 구매 되었다면, 클레임 후 sOHM의 보유 수량은 약 0.5개로 늘어나야 합니다. redeem함수를 실행하기 위해서는 총 3개의 값을 입력해야 합니다.

  • user : 0x7D20307D274757192FB6Aa745fC285b59c61f8cc — sOHM 받을 계정 주소
  • indexesindexes : [0] — 본드 구매 정보
  • sendgOHM : false — gOHM으로 수령 여부
OHM / sOHM 토큰 보유 수량
redeem 함수 트랜잭션 내역

마치며

트랜잭션 내역을 살펴보면 gOHM을 소각하고 sOHM을 전송하는 것을 확인할 수 있습니다. 올림푸스 다오 V2에서는 대부분의 토큰 전송이 gOHM을 default 값으로 설정하고 있습니다. 이는 sOHM 토큰의 유동성 제약 때문도 있지만, gOHM이 외부 디파이 프로토콜과 연계가 쉽다는 점에서 기능 확장에 대한 의지가 느껴지는 부분이기도 합니다. 사실 올림푸스 다오는 최근 몇 개월 동안 OHM 토큰의 가치가 지속적으로 하락하면서 고전을 면치 못하고 있습니다. ‘인버스 본드(Inverse Bond)’까지 고안해 냈지만 아직까지 상승세로의 전환은 뚜렷해 보이지 않습니다.

올림푸스 다오의 스마트 컨트랙트를 배포하면서 느꼈던 것은 ‘생각보다 정교하다’는 점이었습니다. 기능 별로 세분화 되어 있지만, 또한 기능에 따라 유기적으로 연결됩니다. 분산화된 자율 조직(DAO)의 비효율성에 대해 항상 가지고 있던 의구심을 한꺼번에 날려버릴 정도로 시스템 설계가 매우 잘 되어 있습니다. POL(Protocol Owned Liquidity)의 개념을 확립하여 디파이의 고질적인 문제인 유동성 관리 실패를 해결했다고 평가 받는 올림푸스 다오가, 지금의 위기를 앞으로 어떻게 헤쳐나갈 지 기대가 되는 이유입니다.

--

--