Solidity : 상속받은 계약에서 base 계약의 Constructor 접근하기

참고 : http://solidity.readthedocs.io/en/develop/contracts.html#arguments-for-base-constructors

이번에 다뤄볼 주제는, 계약의 OOP를 위해서 반드시 필요한 개념입니다. 예를 들면 어떤 계약 A가 있다고 가정하고, 계약 B가 A를 상속받았다고 가정합시다. 그러면 코드는 아래와 같이 될 것입니다.

contract A { ... }
contract B is A { ... }

그렇다면 이제 B의 계약을 만들면서 A의 생성자를 마찬가지로 call하려면 어떻게 해야 할까요?
아래의 계약을 예로 생각해봅시다.

pragma solidity ^0.4.22;

contract Base {
    uint x;
    constructor(uint _x) public { x = _x; }

    function getX() public view returns (uint256) { return x; }
}

계약은 간단합니다. Base 계약에서는 x라는 멤버 변수가 있는데, 생성자에서 그 x값을 세팅하고, 멤버 함수로는 세팅된 x의 값을 리턴하는 getX라는 함수를 갖습니다.

이제 이 계약을 상속받는 계약을 생각해 봅시다.

contract Derived is Base {
    constructor(uint _y) Base(_y * _y) public { }
}

여기서 Derived라는 계약은 생성시에 argument를 받아서 이를 제곱한 값을 Base의 constructor의 argument로 넣고 있습니다. 보통 자바에서는 생성자 함수 내에서 super(_y * _y);식으로 콜 했을텐데, solidity에서는 위와 같이 modifier위치에서 세팅하는 것이 문법입니다.

그렇다면 마지막 테스트로, Derived 계약을 만들고 argument로 7을 넣어줍시다. 그렇다면 Base계약의 생성자에 7*7=49를 넣을 것이기 때문에 결국 getX함수에 의해서 49가 나와야 할 것입니다.

Screen Shot 2018-05-05 at 7.26.08 AM.pngScreen Shot 2018-05-05 at 7.26.14 AM.png

잘 동작함을 확인할 수 있습니다. 이 부분이 복잡한 계약을 만듦에 있어서 굉장히 중요한 문법을 차지하기 때문에 포스팅 해 봤습니다 🙂

ERC20 기반 코인 만들기

이제 어느정도 Solidity나 Smart contract에 대해서 익숙해진 상황이기 때문에, 실질적으로 ICO를 준비하면서 정말로 활용할 수 있는, 그리고 시중에 유통되고 있는 ERC20 코인들을 만들어보고 싶었습니다.

Screen Shot 2018-04-22 at 1.18.16 PM.png

이런 ERC20 코인들의 특징 중의 하나는 https://etherscan.io/tokens와 같은 공식 사이트에서 공개적으로 거래 내역 및 잔액을 확인할 수 있고, 또 코인을 사고 파는 것을 연계시킬 수 있다는 점입니다! 그리고 만약 제가 ICO를 하게 된다면 그 코인 또한 이렇게 공개적으로 등록이 되어야 하는 것이겠죠.

그렇기 위해서 필요한 것이 바로 ERC20Interface를 구현하는 것입니다. Java로 치면 Interface를 구현하는 것과 같은 것인데요, 그 골격을 살펴보면 아래와 같습니다.

contract ERC20Interface {
    function totalSupply() public constant returns (uint);
    function balanceOf(address tokenOwner) public constant returns (uint balance);
    function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
    function transfer(address to, uint tokens) public returns (bool success);
    function approve(address spender, uint tokens) public returns (bool success);
    function transferFrom(address from, address to, uint tokens) public returns (bool success);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

ERC20Interface는 총 6개의 함수와 2개의 event로 구성되어 있습니다. 각 함수는 customizable합니다. 따라서 여기에서 제시된 것을 참고하시어 맞는 코드를 구성하시면 되겠습니다. 크게 고칠 것은 없지만, 저는 유효 거래를 필터링 하는 부분이 제 기준에 맞지 않아 좀 고쳤습니다.
https://theethereum.wiki/w/index.php/ERC20_Token_Standard#Sample_Fixed_Supply_Token_Contract

어쨌든 이렇게 ERC20Interface가 구현된 코인을 만들고 그것을 Ropsten 테스트넷에 deploy시켜보겠습니다.

contract ZipToken is ERC20Interface, Owned {
  ...

우리가 만들 코인 계약은 ERC20InterfaceOwned계약을 상속받아 만들어졌습니다. ERC20Interface에서 선언되어 있는 함수들은 ZipToken 계약 안에서 구체적으로 구현됩니다.

가장 중요한 부분이 계약의 생성자 부분인데요,

function ZipToken() public {
    symbol = "ZIP";
    name = "ZIP Token from Z-BSI";
    decimals = 0;
    _totalSupply = 1000000 * 10**uint(decimals);
    balances[owner] = _totalSupply;
    Transfer(address(0), owner, _totalSupply);
}

일단 연습으로 만들어 볼 코인은 깔끔하게 100만 코인만 만들고 소수점은 없습니다. 코인의 심볼은 “ZIP”이고 코인의 이름은 일단 “ZIP Token from Z-BSI”로 정했습니다. 이 계약을 deploy하게 되면 Ropsten Etherscan에서 해당 코인을 검색하실 수 있게됩니다! https://ropsten.etherscan.io/token/0x9179d7c197fa5a7500a4740ca96b1dde73baf8b2

Screen Shot 2018-04-22 at 9.01.20 AM.png

드디어 테스트넷이긴 하지만 퍼블릭한 최초의 코인을 만들었습니다!! 🙂 이제 이 코인을 실제로 유통할 수 있는지 확인해 보겠습니다. 여기서 제 TREZOR지갑을 사용해보려고 하는데요, 가장 검증된 안전한 하드웨어 지갑입니다 🙂 http://mysafecoin.net/mysafecoin.php

Trezor지갑의 한 주소로 1000코인을 보냅니다. ERC20Interface를 구현한 덕분에 모든 transaction을 모니터할 수 있습니다!
Screen Shot 2018-04-22 at 9.21.43 AM.png

그리고 그 결과 Trezor지갑에서 토큰을 받았음을 확인할 수 있습니다! 여기서는 이더리움 공식 월렛인 https://www.myetherwallet.com를 통해 확인하실 수 있습니다.

Screen Shot 2018-04-22 at 9.24.44 AM.png

왼쪽 아래에 1000 ZIP 코인을 받았음을 확인하실 수 있죠? 🙂
마지막으로 Trezor지갑을 사용하여 이 코인을 다른사람한테 보낼 수 있는지 체크해 보겠습니다.
MyEtherWallet에서 손쉽게 ERC20 코인을 주고받을 수 있습니다.
Screen Shot 2018-04-22 at 9.26.20 AM.png
Screen Shot 2018-04-22 at 9.26.28 AM.png

사이트에서 송금을 신청할 수 있고, (물론 충분한 가스비를 해당 지갑이 가지고 있어야 합니다!) 그것을 하드웨어 지갑인 Trezor에서 최종 승인할 수 있습니다.
IMG_3187.jpg

이로써 테스트넷이긴 하지만 완벽하게 실제 사용 가능한 코인을 만들었습니다!! 🙂

Truffle을 이용한 이더리움 기반 DApp개발 – Unit Test

참고 : http://truffleframework.com/tutorials/pet-shop

얼마전에 Truffle이라는 이더리움 기반 dapp 개발 툴이 있다는 것을 알게 되었습니다. 제가 받은 인상은, dapp개발쪽의 maven같은 느낌이라고 해야할까요? 단순히 solidity코드만 짜는 것이 아니라 testing과 deployment까지 한꺼번에 프로젝트로 관리할 수 있게 해주는 것이 Truffle이라고 할 수 있습니다. 저도 이제 막 쓰기 시작해서 배워가는 마당이고, 그 과정에서 느낀 것들을 적어보려고 합니다.

일단 제가 처음으로 깊은 인상을 받았던 것은 스마트 계약에 대해 unit test를 작성할 수 있다는 것이었습니다!
그동안 일일이 클릭해가고 스크린샷으로 확인하고.. 물론 그것도 꼭 나쁜 방법이라고 볼 수는 없지만, 실제로 대형 프로젝트나 계약이 여러개 걸린 복잡한 계약들에 대해 수작업으로 매번 테스트 하는것은 거의 불가능에 가까울 것입니다. Reproducible하고 간편한 unit test를 어떻게 하는지에 대해 좀 더 살펴보겠습니다. (기본적인 Truffle 사용법은 생략하겠습니다.)

일단 Adoption이란 계약을 살펴보죠.

pragma solidity ^0.4.21;

contract Adoption {
    address[16] public adopters;

    function adopt(uint petId) public returns (uint) {
        require(petId >= 0 && petId <= 15);

        adopters[petId] = msg.sender;

        return petId;
    }

    function getAdopters() public view returns (address[16]) {
        return adopters;
    }
}

계약 자체는 간단합니다. 16명의 adopter들이 있는데, adopt라는 함수를 0에서 15까지의 id로 콜하게 되면 해당 id의 펫이 입양되는 컨셉입니다. getAdopters라는 함수는 현재 상태의 adopters를 리턴하는 역할을 합니다.

이제 unit test 코드를 살펴볼까요?

pragma solidity ^0.4.21;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";

contract TestAdoption {
	Adoption adoption = Adoption(DeployedAddresses.Adoption());

	function testUserCanAdoptPet() public {
		uint returnedId = adoption.adopt(8);

		uint expected = 8;

		Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
	}

	function testGetAdopterAddressByPetId() public {
		address expected = this;
		address adopter = adoption.adopters(8);
		Assert.equal(adopter, expected, "Owner of pet ID 8 should be recorded.");
	}

	function testGetAdopterAddressByPetIdInArray() public {
		address expected = this;

		address[16] memory adopters = adoption.getAdopters();

		Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
	}
}

기본적으로 세 개의 sol파일을 import하는데, truffle/Assert.sol은 Java unit test에서처럼 어떤 특정 조건을 충족하는지에 대한 다양한 테스트 함수들을 포함하고 있습니다. truffle/DeployedAddresses.sol은 deployed된 계약을 access하기 위해 필요한 주소를 가져올 수 있는 라이브러리입니다. 마지막으로 우리가 작성한 계약 Adoption.sol을 불러옵니다.
아마 제 생각에는 migration기능을 통해 순서대로 build해가면서 생성된 계약 주소들을 저장하고 불러오고 하는 것 같습니다.
아무튼 중요한건
Adoption adoption = Adoption(DeployedAddresses.Adoption());
DeployedAddresses.${계약이름}()을 통해서 deploy된 계약 주소를 가져올 수 있습니다. 즉, 계약을 테스트 하기 위해서 먼저 원본 계약을 생성하고 그 주소를 가져와서 테스트 하는 것이죠.

그리고 나서 testUserCanAdoptPet함수에서는 adopt(8)을 호출하게 됩니다. 그렇게 되면 리턴되는 id도 8이어야 합니다. Assert.equal을 통해 그게 맞는지 테스트합니다.
제 생각엔 테스트들은 순차적으로 실행되는 것 같습니다. testGetAdopterAddressByPetId에서는 이미 8번 pet의 입양자가 지금 테스트계약의 주소인지를 테스트합니다.
마지막 testGetAdopterAddressByPetIdInArray함수는 getAdopters함수로 전체 주소 리스트를 잘 긁어오는지 확인하고, 마찬가지로 8번 pet 입양자의 정보를 테스트합니다.

truffle test
를 통해서 실행하면 다음과 같은 결과를 얻을 수 있습니다.

Using network 'development'.

Compiling ./contracts/Adoption.sol...
Compiling ./test/TestAdoption.sol...
Compiling truffle/Assert.sol...
Compiling truffle/DeployedAddresses.sol...

 TestAdoption
   ✓ testUserCanAdoptPet (91ms)
   ✓ testGetAdopterAddressByPetId (70ms)
   ✓ testGetAdopterAddressByPetIdInArray (89ms)


 3 passing (670ms)