누구나 만들 수 있는 이더리움 ERC20 코인/토큰 실전 개발 (3편)

View this thread on: d.buzz | hive.blog | peakd.com | ecency.com
·@nida-io·
0.000 HBD
누구나 만들 수 있는 이더리움 ERC20 코인/토큰 실전 개발 (3편)
# "누구나 만들 수 있는 이더리움 ERC20 코인/토큰 실전 개발" (3편)
<p></p>

가끔씩 코인을 개발해줄 수 있냐는 요청을 받는데요, 코인 개발에 중요한 것은 개발 그 자체보다도 *"세상의 어떤 부분을 어떻게 변화시킬 것이다"* 라는 그 코인만의 세계관인 것 같습니다. 제 개인적인 생각으로는, **코인의 존재를 통해 특정 산업군을 구성하는 사람들과 회사들의 참여가 독려되고 그로 인해 빠른 시간 내에 기존의 산업구조가 새롭게 재편성될 수 있도록** 설계하는 것이 중요한 것 같습니다. 다음 링크를 한번 읽어보시면 도움이 되실 것 같습니다.
 - [http://토큰이코노미.선언문입.니다.com](토큰이코노미.선언문입.니다.com)
<p></p>

또한 이더리움 ERC20 토큰의 경우에는 이러한 세계관과 그에 따른 사업 플랜을 **초기에 엄밀하게 설계**하는 것이 아주 아주 중요합니다. 왜냐하면 토큰을 한번 발행하고 나면, 그 소스가 이더리움 블록체인에 영구적으로 올라가면서 이후 **소스를 업데이트 하지 못하기 때문입니다**. 기존 소스를 개선할 수 있는 유일한 방법은, 새로운 주소로 소스를 등록한 후 예전에 사용하던 토큰을 새롭게 만든 토큰으로 이전시켜주는 것입니다. 하지만 이런 작업은 토큰의 신뢰도를 떨어트릴 수 있으므로 가급적 하면 안 되겠죠?

따라서 다음을 기억하세요.
1. 코인 개발을 시작하기 전에 토큰의 백서를 미리 완성시키세요.
2. 개발자는 멤버들과 많은 대화를 나누고 백서를 철저히 분석하여 향후 가능한 예외상황과 사업의 확장성을 첫 소스에 모두 포함시켜야 합니다.
3. 그러면서도 가스 비용을 아끼기 위해 불필요한 부분은 최대한 제거해야 합니다.
<p></p>

코인 개발 자체는 쉽습니다. 이 문서만 잘 숙지하셔도 누구나 코인을 개발하실 수 있습니다. 하지만 코인을 잘 만들기(2번과 3번을 동시에 만족시키기)는 어려운 것 같습니다. 상용화시키기 위해서는 경험 많은 조언자가 필요하다고 생각합니다.

참고로, 제가 개발에 참여한 코인인 KStarCoin(이하 KSC) 의 경우에는 다음과 같은 목적을 가지고 만들었습니다.

- KStarLive 커뮤니티에 참여하여 활동하는 것만으로도 KSC 를 얻을 수 있다.
- 팬들은 활동하면서 얻은 KSC 를 다양한 팬클럽 활동 및 스타를 위한 조공 등에 활용할 수 있다.
- KSC 생태계에 참여하는 기업은 KStarLive 의 830만 한류 팬 유저들에게 자동으로 홍보가 된다.
- 그 결과, KSC 는 다양한 한류 콘텐츠(음악, 영상, 상품, 관광 등)에서 실제 사용할 수 있는 재화가 된다.
<p></p>

이보다 더 자세한 내용이 궁금하신 분은 [KSC 홈페이지](http://kstarcoin.com) 및 [KSC 백서](http://kstarlive.com/kstarcoinico/whitepaper/KStarLive_KStarCoin_ICO_Whitepaper.pdf) 를 참고해주세요. 또한 소스는 [http://케이스타코인.소스.보기.cc](http://케이스타코인.소스.보기.cc) 에서 확인하실 수 있습니다.

서론이 길었네요. 오늘은 ERC20 규약에 따른 토큰을 완성시킬 생각입니다. 지난 연재를 못 보신 분은 보고 다음 글을 확인하세요.

- [누구나 만들 수 있는 이더리움 ERC20 코인/토큰 실전 개발 (1편)](https://steemit.com/kr-dev/@nida-io/2oduk2-erc20-1)
- [누구나 만들 수 있는 이더리움 ERC20 코인/토큰 실전 개발 (2편)](https://steemit.com/kr-dev/@nida-io/erc20-2)

---

# ERC20 Interface 를 모두 표현 - ERC20.sol
https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC20/ERC20.sol
```
pragma solidity ^0.4.18;

import "./ERC20Basic.sol";

/**
 * @title ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/20
 */
contract ERC20 is ERC20Basic {
  function allowance(address owner, address spender) public view returns (uint256);
  function transferFrom(address from, address to, uint256 value) public returns (bool);
  function approve(address spender, uint256 value) public returns (bool);
  event Approval(address indexed owner, address indexed spender, uint256 value);
}
```
<p></p>

지난 연재 1편에서 `ERC20Basic` 콘트랙트는 ERC20 규약에서 *'제3자 송금 기능'* 을 빼놓고 선언했다고 말씀드린 적 있습니다. 오늘 소개해드리는 `ERC20` 은 드디어 ERC20 규약을 온전히 모두 선언하는 *가상 콘트랙트* 입니다.  가상 콘트랙트가 무엇인지 잘 모르겠다면 연재 1편에서 해당 부분을 찾아서 읽어보시기 바랍니다.

`ERC20` 콘트랙트는 `is` 키워드를 통해 `ERC20Basic` 의 모든 것을 상속 받기 때문에 `totalSupply`, `balanceOf`, `transfer` 함수와 `Transfer` 이벤트 함수는 이미 가지고 있는 상태입니다. 위 소스에서는 ERC20 규약의 나머지 함수들 `allowance`, `transferFrom`, `approve` 과 이벤트 함수 `Approval` 을 추가로 선언하고 있네요. ERC20 규약에 따른 각 함수의 의미는 다음과 같습니다.

함수명 | 내용 | 리턴값
-|-|-
approve | *spender* 에게 *value* 만큼의 토큰을 인출할 권리를 부여한다. 이 함수를 이용할 때는 반드시 *Approval* 이벤트 함수를 호출해야 한다. | 성공 / 실패
allowance | *owner* 가 *spender* 에게 인출을 허락한 토큰의 개수는 몇개인가? | 허용된 토큰의 개수
transferFrom | *from* 의 계좌에서 *value* 개의 토큰을 *to* 에게 보내라. 단, 이 함수는 *approve* 함수를 통해 인출할 권리를 받은 *spender* 만 실행할 수 있다. | 성공 / 실패
<p></p>

`ERC20Basic` 콘트랙트와 마찬가지로 `ERC20` 콘트랙트는 ERC20 인터페이스 규약을 그대로 쓴 것 밖에 되지 않습니다. 여기서는 굳이 이해하려고 하지 않고 넘어가신 후 아래 `StandardToken` 콘트랙트 부분을 보시면 됩니다.

참고로 solidity 에는 가상 콘트랙트와 역할이 비슷하면서 더 명시적인 표현인 `interface` 키워드도 존재합니다. 하지만 `interface` 키워드는 다른 인터페이스를 상속 받지를 못합니다. 이건 너무나 큰 단점이라 현재는 굳이 `interface` 키워드를 사용할 필요가 전혀 없습니다. (참고 : 공식 문서에서 *'Some of these restrictions might be lifted in the future.'* 라고 표현했기 때문에 차후 개선될 것 같음).

---

# 드디어 ERC20 규약을 완성! - StandardToken.sol
https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC20/StandardToken.sol
```
pragma solidity ^0.4.18;

import "./BasicToken.sol";
import "./ERC20.sol";

/**
 * @title Standard ERC20 token
 *
 * @dev Implementation of the basic standard token.
 * @dev https://github.com/ethereum/EIPs/issues/20
 * @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
 */
contract StandardToken is ERC20, BasicToken {

  mapping (address => mapping (address => uint256)) internal allowed;

  /**
   * @dev Transfer tokens from one address to another
   * @param _from address The address which you want to send tokens from
   * @param _to address The address which you want to transfer to
   * @param _value uint256 the amount of tokens to be transferred
   */
  function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
    require(_to != address(0));
    require(_value <= balances[_from]);
    require(_value <= allowed[_from][msg.sender]);

    balances[_from] = balances[_from].sub(_value);
    balances[_to] = balances[_to].add(_value);
    allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
    Transfer(_from, _to, _value);
    return true;
  }

  /**
   * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
   *
   * Beware that changing an allowance with this method brings the risk that someone may use both the old
   * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
   * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
   * @param _spender The address which will spend the funds.
   * @param _value The amount of tokens to be spent.
   */
  function approve(address _spender, uint256 _value) public returns (bool) {
    allowed[msg.sender][_spender] = _value;
    Approval(msg.sender, _spender, _value);
    return true;
  }

  /**
   * @dev Function to check the amount of tokens that an owner allowed to a spender.
   * @param _owner address The address which owns the funds.
   * @param _spender address The address which will spend the funds.
   * @return A uint256 specifying the amount of tokens still available for the spender.
   */
  function allowance(address _owner, address _spender) public view returns (uint256) {
    return allowed[_owner][_spender];
  }

  /**
   * @dev Increase the amount of tokens that an owner allowed to a spender.
   *
   * approve should be called when allowed[_spender] == 0. To increment
   * allowed value is better to use this function to avoid 2 calls (and wait until
   * the first transaction is mined)
   * From MonolithDAO Token.sol
   * @param _spender The address which will spend the funds.
   * @param _addedValue The amount of tokens to increase the allowance by.
   */
  function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
    allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
    Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
    return true;
  }

  /**
   * @dev Decrease the amount of tokens that an owner allowed to a spender.
   *
   * approve should be called when allowed[_spender] == 0. To decrement
   * allowed value is better to use this function to avoid 2 calls (and wait until
   * the first transaction is mined)
   * From MonolithDAO Token.sol
   * @param _spender The address which will spend the funds.
   * @param _subtractedValue The amount of tokens to decrease the allowance by.
   */
  function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
    uint oldValue = allowed[msg.sender][_spender];
    if (_subtractedValue > oldValue) {
      allowed[msg.sender][_spender] = 0;
    } else {
      allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
    }
    Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
    return true;
  }
}
```
<p></p>

이번 소스는 조금 기네요.

일단 `StandardToken` 콘트랙트는 `ERC20` 콘트랙트와 `BasicToken` 콘트랙트를 상속받고 있습니다. `BasicToken` 에서 `totalSupply`, `balanceOf`, `transfer` 함수를 이미 구현했기 때문에 해당 내용은 구현하지 않아도 됩니다. `ERC20` 에서 새롭게 선언한 `allowance`, `transferFrom`, `approve`함수는 아직 선언만 된 상태이므로 이 부분을 구현하면 되겠군요. 

자, 그럼 이제 소스의 각 부분을 분석해보도록 하겠습니다.

## 변수 allowed
<p></p>

```
  mapping (address => mapping (address => uint256)) internal allowed;
```
<p></p>

`allowed` 변수는 `approve` 함수를 통해 *'누가'*, *'누구에게'*, *'얼마의'* 인출 권한을 줄지를 저장합니다. *'누가'*, *'누구에게'* 두 부분을 map 의 *key* 로 사용하고, *'얼마'* 를 *value* 로 저장합니다. 예를 들면 다음과 같습니다.
```
allowed[누가][누구에게]= 얼마;
```
<p></p>

## 함수 transferFrom
<p></p>

```
  function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
    require(_to != address(0));
    require(_value <= balances[_from]);
    require(_value <= allowed[_from][msg.sender]);

    balances[_from] = balances[_from].sub(_value);
    balances[_to] = balances[_to].add(_value);
    allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
    Transfer(_from, _to, _value);
    return true;
  }
```
<p></p>

- 위의 `require` 세 줄은
  - 받는 계좌가 0 이 아닌지, 
  - 보내려는 값이 잔고 이내인지,
  - 보내려는 값이 계좌 주인 `_from` 이 돈을 빼려는 `msg.sender` 에게 허용한 권한 이내인지를 체크합니다.
<p></p>

일반적으로 `msg.sender` 는 가스비를 소모하여 이 함수를 호출한 계정입니다. 즉, **`_from` 에게 인출 권한을 받은 계정이 `transferFrom` 함수를 호출**해야 동작합니다.


- 그 다음 세 줄도 간단합니다.
  - `_from` 의 계좌에서 `_value` 만큼을 빼라(인출하라).
  - `_to` 의 계좌에 `_value` 만큼 더해라(입금하라).
  - `msg.sender` 의 인출 권한에서 `_value` 만큼을 제하라.
<p></p>

- 토큰의 이동이 일어났으므로, ERC20 규약의 가이드에 따라 `Transfer` 이벤트를 발생시킵니다.
  - 이벤트는 이더리움 블록체인에 영구적으로 기록되며, `indexed` 된 값은 차후 언제든 검색 가능합니다.
<p></p>

`allowed` 맵 변수를 잘 보면 `_to` 값은 저장이 되어 있지 않습니다. 다시 말해 `_from` 계좌의 주인이 `approve` 함수에서 `spender` 에게 인출 권한을 주면, 그 `spender` 는 이 `transferFrom` 함수를 호출하여 `_from` 계좌에서 누구에게라도 돈을 보낼 수 있습니다.

## 함수 approve
<p></p>

```
  function approve(address _spender, uint256 _value) public returns (bool) {
    allowed[msg.sender][_spender] = _value;
    Approval(msg.sender, _spender, _value);
    return true;
  }
```
<p></p>

소스는 아주 간단합니다.
- `allowed[msg.sender][_spender] = _value;` 함수를 호출한 본인(`msg.sender`)의 계좌에서 `value` 만큼 인출해 갈 수 있는 권리를 `spender` 에게  부여한다.
- `Approval(msg.sender, _spender, _value);` ERC20 규약에 따라 `Approval` 이벤트를 발생시킨다. 이벤트 함수를 통해 세가지 값(`msg.sender`, `_spender`, `_value`)는 영구적으로 블록체인에 기록되며 `indexed` 된 값은 검색될 수 있다.
<p></p>

그런데 이 부분은 주석이 오히려 중요합니다. approve 함수 위에 다음과 같은 주석이 붙어 있었습니다.
```
   * Beware that changing an allowance with this method brings the risk that someone may use both the old
   * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
   * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
```
<p></p>

위 내용인 즉슨, 이더리움의 경우에는 채굴이 되기 전까지는 실행이 되지 않기 때문에, 간혹 늦게 실행한 코드가 먼저 동작하는 경우가 생깁니다. 따라서 다음과 같은 경우가 생길 수 있습니다.

1. A 가 B 에게 인출할 수 있는 권리 N 을 부여함.
2. A 가 생각이 바뀌어 B 에게 인출할 수 있는 권리 M 을 새롭게 부여함.
3. B 가 A 가 생각이 바뀌었음(2번)을 알아차리고 재빠르게 N 을 인출 시도함.
4. 채굴 구조의 특성상 3번이 2번보다 먼저 실행될 수도 있음. 그러면 B 는 N 을 인출한 후, 추가로 M 을 인출할 수 있는 권리를 얻음.
5. A 가 잘못된 것을 알아차리기 전에 B 가 M 을 인출해 가면, B 는 (N+M) 을 인출해가게 됨.

이런 문제를 해결하기 위해 A 는 B 에게 인출할 권리를 N 에서 0 으로 먼저 바꾸고, 문제 없이 정상적으로 변경된 것을 확인한 후 다시 M 으로 변경하라는 뜻입니다.

**채굴 순서에 의해 코드의 실행 순서가 뒤바뀔 수 있다는 부분을 간과하면, 이 외에도 다양한 보안의 허점이 생길 수 있습니다.** 항상 이 부분을 염두에 두시기 바랍니다.

## 함수 allowance
<p></p>

```
  function allowance(address _owner, address _spender) public view returns (uint256) {
    return allowed[_owner][_spender];
  }
```
<p></p>

`_owner` 가 `_spender` 에게 얼마만큼의 인출 권한을 부여했는지를 *return* 합니다. *public* 함수이기 때문에 누구나 계좌 주소만 알면 확인할 수 있습니다.

## 추가적인 함수 increaseApproval,  decreaseApproval
<p></p>

```
  function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
    allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
    Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
    return true;
  }

  function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
    uint oldValue = allowed[msg.sender][_spender];
    if (_subtractedValue > oldValue) {
      allowed[msg.sender][_spender] = 0;
    } else {
      allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
    }
    Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
    return true;
  }
```
<p></p>

이 함수들은 ERC20 규약에 있는 함수는 아닙니다. 위 `approve` 함수에서 값을 바꿀 때 0으로 변경한 후 다시 원하는 값으로 바꾸는 것은 많이 불편한 일이기 때문에 (참고 : 0 으로 변경하는 명령이 채굴되기를 기다렸다가 다시 원하는 값으로 변경하도록 실행해야 함), 차액만큼을 더하거나 빼는 함수를 추가로 제공하고 있습니다. 이 함수를 이용하면 N+M 만큼 인출해가는 공격을 피할 수 있습니다.

소스가 어렵지 않고, 표준 규약이 아니기 때문에 소스를 굳이 설명드리진 않겠습니다.

# 진짜 ERC20 토큰 만들기에 써도 되는 예제 - SimpleToken
https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/examples/SimpleToken.sol
```
pragma solidity ^0.4.18;

import "../token/ERC20/StandardToken.sol";

/**
 * @title SimpleToken
 * @dev Very simple ERC20 Token example, where all tokens are pre-assigned to the creator.
 * Note they can later distribute these tokens as they wish using `transfer` and other
 * `StandardToken` functions.
 */
contract SimpleToken is StandardToken {

  string public constant name = "SimpleToken"; // solium-disable-line uppercase
  string public constant symbol = "SIM"; // solium-disable-line uppercase
  uint8 public constant decimals = 18; // solium-disable-line uppercase

  uint256 public constant INITIAL_SUPPLY = 10000 * (10 ** uint256(decimals));

  /**
   * @dev Constructor that gives msg.sender all of existing tokens.
   */
  function SimpleToken() public {
    totalSupply_ = INITIAL_SUPPLY;
    balances[msg.sender] = INITIAL_SUPPLY;
    Transfer(0x0, msg.sender, INITIAL_SUPPLY);
  }
}
```
<p></p>

아주 간단한 소스이지만 ERC20 표준 규약을 따르는 토큰을 완벽하게 만들 수 있습니다. 소스 설명은 연재 2편에서의 `MyBasicToken` 과 거의 유사하므로 굳이 하진 않겠습니다.

대신 나만의 커스텀 토큰을 만들기 위한 방법을 가르쳐드리겠습니다. 너무나 너무나 쉽습니다. 위 소스에서 다음 **한글 부분만 수정하여 사용**하시면 됩니다.


```
  string public constant name = "토큰_이름";
  string public constant symbol = "토큰_심볼"; // solium-disable-line uppercase
  uint8 public constant decimals = 18; // solium-disable-line uppercase

  uint256 public constant INITIAL_SUPPLY = 토큰_발행량 * (10 ** uint256(decimals));
```
<p></p>

`decimals` 값 18 은 가급적 그냥 두시는게 좋습니다. 이 값을 변경하면 만들려는 토큰과 ether 와의 상호 계산이 복잡해질 수 있습니다.

zeppelin 의 소스 덕에 우리는 달랑 세 부분만 변경하고 아주 잘 동작하는 ERC20 토큰을 만들 수 있었습니다! 야호! ~~얘들아, 나 토큰 만들었어! 빨리 투자해!~~

하지만 아직 실제 사용할 토큰으로는 부족한 점이 많습니다. 다음 시간에는 zeppelin 의 다른 소스들을 이용하여 몇가지 기능을 추가하도록 하겠습니다.

----------------
TaeKim(@nida-io) 의 프로젝트를 구경하세요.
- [니다닷컴](http://니다.com) : 쉽게 읽히는 "한글 멘트 그대로의 링크" 를 만들어드립니다. 마케팅 콘텐츠, 홈페이지, 쇼핑몰, 블로그, 청첩장, 포트폴리오 등의 링크에 사용하여 가독성과 클릭율을 높여보세요.
- [케이스타코인](http://kstarcoin.com) : 830만 팔로어 전세계 1위 한류 미디어 KStarLive 와 함께 만든 한류 플랫폼에 사용되는 코인입니다. 스팀잇처럼 커뮤니티 활동을 하면서 코인을 얻을 수 있으며, 한류 콘텐츠 구매, 공연 예매, 한국 관광 관련, 기부 및 팬클럽 활동 등에 사용될 계획입니다.
👍 , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,