Solidity 튜토리얼 6: truffle + zeppelin-solidity을 이용한 크라우드 세일 계약 V

Yoonjae Yoo
DNEXT
Published in
13 min readMay 14, 2018

Solidity 튜토리얼 5: truffle + zeppelin-solidity을 이용한 크라우드 세일 계약 IV에서 이어집니다.

여기서 쓰인 예제 코드들은 실제로 작동하는 코드이지만, 취약점이 존재할 가능성이 있습니다. 따라서 실제 크라우드 세일에서 사용하지 말아야 합니다. 사용했을 때 발생하는 문제에 대해서는 책임을 지지 않습니다. 계약의 보안성을 높이기 위해서는 스마트 계약 전문 Audit 업체들에게 서비스를 받는 것을 추천합니다.

Ganache 설치

Ganache란 테스트 목적을 위해 여러분의 PC에 설치해서 사용할 수 있는 일종의 간이 블록체인입니다. 간이 블록체인이기 때문에 네트워크와의 연결이 필요없이 로컬에서 작동하며 우리가 작성한 계약을 손쉽게 배포, 테스트해볼 수 있습니다.

여기에서 운영체제별로 적절한 Ganache를 다운받을 수 있습니다. 설치 후 Ganache를 실행하면 아래와 같은 화면이 나타납니다.

최상단에는 네 개의 탭이 있는데, 각각 Accounts, Blocks, Transactions, Logs 입니다.

  • Accounts(계정) : 총 10개의 계정이 생성되어 있고 각각 100 ETH를 보유하고 있습니다. 이는 물론 로컬 Ganache에서만 거래 가능한 금액입니다.
  • Blocks(블록) : 생성된 블록의 내역과 각 블록에서 사용된 가스, 포함된 거래르 나타냅니다.
  • Transactions(거래) : 생성된 거래의 전체 내역을 나타냅니다.
  • Logs(로그) : 계약의 배포 및 실행 과정에서 생성되는 디버깅을 위한 로그가 표시됩니다.

우리가 작성한 계약을 이 Ganache 블록체인에 배포하고 실행하기 위해서는 truffle 프레임워크가 Ganache를 인식할 수 있도록 해 줘야합니다. 이를 위해서는 프로젝트 최상위 경로의 truffle.js을 열어 아래와 같이 내용을 작성합니다.

module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 7545,
network_id: "*"
}
}
};

networks에는 여러개의 다른 이름의 블록체인 네트워크가 등록될 수 있고 우리는 Ganache 네트워크를 development라는 이름으로 등록할 것입니다. host: “127.0.0.1”은 로컬 네트워크를 의미하고 port: 7545는 Ganache의 포트 번호를 의미합니다. network_id: “*”은 해당 네트워크의 ID를 특별히 명시하지 않음을 의미하는데, 이와 반대로 로컬이 아닌 이더리움 메인넷, 테스트넷 등 live 네트워크를 등록할 때에는 network_id: “1”, network_id: “2” 등 각각 다른 ID를 부여해줍니다.

배포 마이그레이션(deploy migration) 작성

우리의 계약을 truffle을 이용해서 배포하기 위해서는 우선 마이그레이션을 작성해야 합니다. 마이그레이션이란, 계약을 배포하고 변경하는 일련의 과정을 프로그래밍 언어 중 하나인 자바스크립트(js)로 기술한 것을 의미합니다.

이를 위해서는 아래와 같은 명령어를 이용합니다. (이 명령어는 프로젝트 최상위 경로에서 실행되어야 합니다.)

truffle create migration deploy

여기서 deploy는 마이그레이션의 이름이라 원하는 이름을 명시해주면 되지만 우리는 배포를 위해 위와 같이 명시해줍니다. 그러면 migrations 디렉토리 내에 1522683134_deploy.js와 같은 파일이 생성됩니다. _deploy 앞에 붙어있는 숫자는 마이그레이션이 생성된 시점의 타임스탬프입니다. 따라서 같은 이름의 마이그레이션을 여러개 생성해주더라도 다른 것으로 인식되고 또한 타임스탬프의 순서에 따라 차례대로 마이그레이션이 실행되는 것이 보장됩니다. 즉, 나중에 생성한 마이그레이션일수록 나중에 수행됩니다.

위의 명령어를 통해 생성한 마이그레이션은 아래와 같이 비어있습니다.

module.exports = function(deployer) {
// Use deployer to state migration tasks.
};

먼저 DNextToken.sol 계약을 배포하는 마이그레이션을 작성해봅시다. 계약을 배포하기 위해서는 artifacts.require()을 사용해 계약 정보(artifact)를 읽어와야 합니다. 코드로는 아래와 같습니다.

const DNextToken = artifacts.require("./DNextToken");

위 줄을 마이그레이션 파일의 최상단에 위치시킵니다. 그리고 기본으로 생성되어 있던 함수(function) 내부에 아래와 같이 작성해줍니다.

deployer.deploy(DNextToken);

deployer는 truffle이 제공해주는 배포를 위한 툴입니다. 우리는 이 deployerdeploy() 함수를 호출하되, 그 인자로 위에서 읽어온 계약 정보인 DNextToken을 넘겨줍니다. 만약 DNextToken.sol의 생성자에도 인자가 필요했다면 우리는 deploy() 함수를 호출할 때 추가적인 인자를 넘겨줬어야할 것입니다. 이에 대해서는 이후에 살펴볼 것입니다. DNextToken 계약의 배포 마이그레이션은 아래와 같습니다.

const DNextToken = artifacts.require("./DNextToken");

module.exports = function (deployer) {
deployer.deploy(DNextToken);
};

하지만 이는 DNextToken.sol 하나의 계약을 배포하는 마이그레이션입니다. 우리는 DNextToken.solDNextTokenWhitelist.sol 계약도 배포해야 합니다. 이를 위해서는 물론 각각의 계약을 배포하는 마이그레이션을 따로 작성할 수도 있지만 우리는 위에서 생성한 마이그레이션에서 모든 계약을 배포하도록 변경합시다.

const DNextToken = artifacts.require("./DNextToken");
const DNextTokenSale = artifacts.require("./DNextTokenSale");
const DNextTokenWhitelist = artifacts.require("./DNextTokenWhitelist");
module.exports = function (deployer) {
deployer.deploy(DNextToken);
deployer.deploy(DNextTokenSale);
deployer.deploy(DNextTokenWhitelist);
};

위와 같이 작성하면 될 것 같지만 이는 잘못된 마이그레이션입니다. 왜냐하면 DNextTokenSale.sol 계약은 생성자에서 두개의 인자로 DNextToken _tokenDNextTokenWhitelist _whitelist를 필요로 하고, 이를 배포 시점에서 명시해줘야하기 때문입니다. DNextToken _tokenDNextToken.sol 계약의 주소, DNextTokenWhitelist _whitelistDNextTokenwhitelist.sol 계약의 주소를 의미합니다.

그렇다면 DNextToken 계약의 주소는 어떻게 알 수 있을까요? 이를 위해서는 .then()이 필요합니다. 이를 활용한 실제 배포 코드는 아래와 같습니다.

const DNextToken = artifacts.require("./DNextToken");
const DNextTokenSale = artifacts.require("./DNextTokenSale");
const DNextTokenWhitelist = artifacts.require("./DNextTokenWhitelist");

module.exports = function (deployer) {
deployer.deploy(DNextToken).then(function () {
deployer.deploy(DNextTokenWhitelist).then(function() {
deployer.deploy(DNextTokenSale, DNextToken.address, DNextTokenWhitelist.address);
});
});
};

deployer.deploy(DNextToken)으로 배포하는 과정은 동일하지만 이어서 나오는 .then()을 통해 배포 직후 수행할 다른 작업을 명시해줍니다.DNextToken의 배포가 완료되면 DNextTokenWhitelist의 배포를 수행하고이 또한 완료되면DNextToken.addressDNextTokenWhitelist.address를 통해 배포된 계약 계정의 주소를 가져올 수 있습니다. 이렇게 가져온 두개의 주소를 DNextTokenSale을 배포할 때 사용합니다. deployer.deploy() 함수의 첫번째 인자로 계약의 정보(artifact)를 입력해줬다면, 두번째 인자부터는 해당 계약 생성자의 인자를 순서대로 입력해줍니다. 만약 생성자의 인자가 두개라면deployer.deploy() 함수의 인자는 세개가 되는 식입니다.

계약 배포

계약 배포를 위해서는 1) 컴파일 2) 마이그레이트 두 과정이 필요합니다. 우선 컴파일은 솔리디티로 작성된 계약을 EVM 바이트 코드로 변환하는 과정이고, 마이그레이트는 우리가 작성한 마이그레이션 실행을 통해 실제 배포를 하는 과정입니다.

컴파일을 위해서는 프로젝트 최상위 경로에서 아래 명령어를 입력합니다.

truffle compile

컴파일의 결과로 최상위 경로의 build/contracts 디렉토리 내에 각 계약의 이름에 해당하는 json 파일이 생성됩니다. 이 파일은 배포를 위한 모든 정보를 담고 있습니다.

배포를 위해서는 아래 명령어를 입력합니다. (단, 이 때 위에서 설치한 Ganache가 실행중인 상태여야 합니다.)

truffle migrate

아래와 같이 메시지가 출력된다면 성공적으로 마이그레이션이 수행된 것입니다.

Using network 'development'.Running migration: 1_initial_migration.js
Deploying Migrations...
... 0xa71ac5f70f119b43dc9418b2901004037b28207989577109f8438f2bc79787ff
Migrations: 0x6761219a3a62809bbc1a0928495336022fa13db2
Saving successful migration to network...
... 0xbaa9b40f737aba4cb2e31eb8695c40a46b4b7d830792afbe85d1c3a3d66ab16f
Saving artifacts...
Running migration: 1522683134_deploy.js
Deploying DNextToken...
... 0x629486238c7d415fad6bac6550c3cc1716658daa55bd1cbf7e596b635de935f5
DNextToken: 0xd8d22f92e8ae6b17018f15eef5f1c5d8854bd738
Saving successful migration to network...
Deploying DNextTokenSale...
Deploying DNextTokenWhitelist...
... 0x68dea4a0b5fb0162b06331cc29ccc8c377044529522c42358f640e36d74cd575
Saving artifacts...
... 0xd0df50a58e6103766928e5bf5f5eeaa830f542afb6efb8cf0cbb55ad069ea368
... 0x137123ac3bf40b5f8df52678e4bd4bae6dde79e9dcf21100d1dddb18d43a4967
DNextTokenSale: 0xe8f525e1a09d99ff5bf9056ac726d3076967aeb1
DNextTokenWhitelist: 0x81c287752e65d48a0a99f0438e4a01c1d582ee40

그리고 Ganache를 확인해보면 Transactions 탭에 다음 그림과 같은 거래 내역이 생성되어 있을 것입니다.

이로써 배포가 완료되었다는 것을 확인할 수 있습니다.

크라우드 세일 절차

DNextToken, DNextTokenSale, DNextTokenWhitelist 세가지 계약이 배포된 상황에서 크라우드 세일 전략은 순서대로 아래와 같습니다.

  1. 토큰 공개 : DNextToken 주소와 솔리디티로 작성된 계약 소스 코드를 공개해서 투자자들에게 토큰의 투명성(발행자가 추후에 본인의 이익을 위해 토큰 발행을 추가 발행하는 등의 행위를 하지 않는지)을 보장합니다.
  2. 사전 등록 : DNextTokenWhitelist 주소와 abi 정보를 공개해 DNextToken 구매를 원하는 투자자들이 사전 등록을 할 수 있도록 합니다.
  3. 토큰 판매 : DNextTokenSale 주소와 abi 정보를 공개해 사전 등록을 한 투자자들이 토큰을 구매할 수 있도록 합니다.

이렇게 스마트 계약의 배포와 크라우드 세일 절차에 대해 알아보았습니다. 막연히 복잡해 보이던 크라우드 세일을 이렇게 하나씩 찬찬히 살펴보면 결국 코드의 집합일 뿐이고 고난이도의 프로그래밍 기법이나 스킬이 필요하지는 않다는 것을 알 수 있습니다.

결국 Dapp 개발에서 더 중요한 것은 프로그래밍 지식이라기보다 토큰 경제(token economy)를 설계하고 이를 솔리디티로 구현할 때 발생 가능한 허점(loophole)이나 취약점(exploit)이 존재하는지를 점검하는 것이라고 할 수 있습니다. 이에 대해서는 앞으로의 튜토리얼에서 자세히 다루려고 합니다.

스마트 계약과 관련한 도움이 필요하다면 DNext에서 운영하고 있는 블록체인 전문 교육 기관인 DNext Campus를 언제든 방문해주시기 바랍니다. 블록체인, 이더리움, 스마트 계약에 관련된 주제로 정규 교육 과정을 운영중에 있습니다.

또한 저희 DNext에서는 토큰 발행 및 판매와 관련한 전문 컨설팅 서비스를 제공하고 있습니다. 도움이 필요하신 분들은 support@dnext.co로 문의주시기 바랍니다.

--

--