누구나 '복붙하여' 만들 수 있는 이더리움 ERC20 코인/토큰 실전 개발 (번외편)
누구나 '복붙하여' 만들 수 있는 이더리움 ERC20 코인/토큰 실전 개발
오랜만의 포스팅이네요. 드디어 번외편이자 본 연재의 백미인 '복붙하여' 만들기 편입니다. 오늘 내용은 사실 다음의 1편부터 3편까지를 다시 한 번 정리했다고 볼 수 있습니다.
- 누구나 만들 수 있는 이더리움 ERC20 코인/토큰 실전 개발 (1편)
- 누구나 만들 수 있는 이더리움 ERC20 코인/토큰 실전 개발 (2편)
- 누구나 만들 수 있는 이더리움 ERC20 코인/토큰 실전 개발 (3편)
'머리 아프고 복잡한 건 나중에, 일단 쉽게 따라해보고 싶다!' 라고 생각하는 분들은 이번 번외편만 보셔도 됩니다.
한번에 복붙하기 좋게 여러 개의 .sol 파일을 하나로 합쳐두었으므로 다음 코드를 그냥 통째로 복사하여 사용하시면 됩니다.
다음 소스를 복붙하세요.
pragma solidity ^0.4.18;
/**
* @title ERC20Basic
* @dev Simpler version of ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/179
*/
contract ERC20Basic {
function totalSupply() public view returns (uint256);
function balanceOf(address who) public view returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
/**
* @title Basic token
* @dev Basic version of StandardToken, with no allowances.
*/
contract BasicToken is ERC20Basic {
using SafeMath for uint256;
mapping(address => uint256) balances;
uint256 totalSupply_;
/**
* @dev total number of tokens in existence
*/
function totalSupply() public view returns (uint256) {
return totalSupply_;
}
/**
* @dev transfer token for a specified address
* @param _to The address to transfer to.
* @param _value The amount to be transferred.
*/
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender]);
// SafeMath.sub will throw if there is not enough balance.
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
emit Transfer(msg.sender, _to, _value);
return true;
}
/**
* @dev Gets the balance of the specified address.
* @param _owner The address to query the the balance of.
* @return An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address _owner) public view returns (uint256 balance) {
return balances[_owner];
}
}
/**
* @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);
}
/**
* @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);
emit 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;
emit 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);
emit 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);
}
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
}
/**
* @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 = "TaeKimCoin"; // solium-disable-line uppercase
string public constant symbol = "TKC"; // solium-disable-line uppercase
uint8 public constant decimals = 18; // solium-disable-line uppercase
uint256 public constant INITIAL_SUPPLY = 20180326 * (10 ** uint256(decimals));
/**
* @dev Constructor that gives msg.sender all of existing tokens.
*/
function SimpleToken() public {
totalSupply_ = INITIAL_SUPPLY;
balances[msg.sender] = INITIAL_SUPPLY;
emit Transfer(0x0, msg.sender, INITIAL_SUPPLY);
}
}
( 참고 : zeppelin 소스에서 수정된 내용이 있습니다. 솔리디티 컴파일러 버전 0.4.21 업그레이드의 내용 에 따라 이벤트 함수를 호출할 때 앞에 emit
을 붙여주도록 추가하였습니다. zeppelin 소스에도 아마 곧 업데이트 될 것입니다. )
여기서 잠깐~! 수정하고 가실게요.
제가 이전 포스팅에서 3군데는 수정하여 사용해야 한다고 말씀드렸죠?
해당 부분에 대해 설명드리겠습니다.
string public constant name = "TaeKimCoin";
name
변수에 저장되는 값이 코인의 이름이 됩니다. zeppelin 의 원래 소스는"SimpleToken"
이었는데 제 이름의 코인으로 변경하여 보았습니다.
string public constant symbol = "TKC";
- ERC20 토큰은 기본적으로 심볼을 가지는데요, 원하는 심볼 문자를
symbol
변수에 저장하시면 됩니다. 꼭 세 글자가 아니어도 됩니다. - 심볼에 이모지(emoji) 를 저장하는 경우를 본 적 있는데요, 그렇다면 아마 한글도 될 겁니다. 또한
name
값도 한글로 지정해도 되지 않을까 싶네요. (해보진 않았습니다).
- ERC20 토큰은 기본적으로 심볼을 가지는데요, 원하는 심볼 문자를
uint256 public constant INITIAL_SUPPLY = 20180326 * (10 ** uint256(decimals));
INITIAL_SUPPLY
는 최초에 발행할 코인의 개수입니다. 그리고 이 코인은 코인을 만든 주인에게 할당됩니다.- 오늘 날짜에 맞춰 20180326 개의 TaeKimCoin 을 만들어 보았습니다.
(10 ** uint256(decimals))
는 소수점 이하 자리수에 해당하는 부분입니다. 무시하셔도 됩니다.
딱 이 세 부분만을 용도에 맞게 수정해서 사용하시면 됩니다.
자! 이제 실제로 코인을 배포해보겠습니다.
Ethereum Wallet 으로 코인 배포하기
실제 개발한다면 remix 에디터 혹은 geth 콘솔에서 코드를 배포하게 되실 겁니다. 하지만 오늘은 최대한 쉬운 방법인 Ethereum Wallet 에서 코드를 배포하는 방법을 보여드리겠습니다.
1. 신규 컨트랙트 설치
이더리움 월렛에 접속하면 우측 상단에 '컨트랙트' 메뉴가 있습니다. 해당 메뉴를 누르시면 화면 가장 윗 부분에 '신규 컨트랙트 설치' 버튼이 있습니다. 이 버튼을 누르세요.
2. 컨트랙트 설치하기 (상)
버튼을 눌러서 '컨트랙트 설치하기' 화면으로 들어오면 맨 위 '송신처' 에서 컨트랙트를 만들 계정을 고릅니다.
- 이 때 여기서 고르는 계정이 코인의 주인이 됩니다!
- 컨트랙트를 만들기 위해서는 소액이더라도 이더(ETHER) 가 있어야 합니다.
'금액 ' 부분은 그대로 0 으로 두시면 됩니다. 그 후, '솔리더티 컨트랙트 소스 코드' 부분에 위의 코드 전부를 복사해서 덮어 씌웁니다.
코드를 붙여 넣고 잠시 기다리면 우측에 '설치할 컨트랙트를 선택하세요.' 항목이 생깁니다. 소스 내의 컨트랙트가 여러 개이기 때문에 그 중에 배포를 원하는 컨트랙트를 골라야 합니다. 우리의 경우에는 'Simple Token' 을 선택하시면 됩니다.
3. 컨트랙트 설치하기 (하)
이제 아래 쪽에 컨트랙트를 만드는데 필요한 예상 수수료가 계산되어 보입니다. 게이지를 좌우측으로 움직이면서 수수료를 조정할 수 있는데요, 이론적으로는 비쌀 수록 빨리 처리됩니다. 저는 보통 1분 이내에 처리되는 수수료 중에 가장 저렴한 것으로 선택하는 편입니다.
저의 경우는 약 0.00067 이더 정도의 수수료가 든다고 나오네요. 최근 1 이더가 60만원 정도 하기 때문에 계산해보면 수수료가 400원 정도가 됩니다.
400원을 들여서 나만의 코인을 만듭니다. 과감하게 '설치' 를 눌러보세요! Go Go~!
4. Create Contract!
'설치' 를 누르면 콘트랙트를 생성하는 최종 창이 뜨면에 비밀번호를 넣으라고 합니다. 여기에 이더리움 계정의 비밀번호를 넣으시면 됩니다.
비밀번호를 넣고 'SEND TRANSACTION' 까지 모두 실행하고 나면 이제 블록이 만들어지기를 기다리시면 됩니다.
이 때, 아까 수수료 설정할 때 보였던 기준인 '보통 1분 이내' 라는 말에 속지 마세요. p2p 노드가 어떻게 연결되었느냐에 따라 빨리 처리될 수도 있고 오래 걸릴 수도 있습니다. 그런데 제 경험상 오래 걸릴 때는 게이지를 최대한 '높음' 으로 올려도 오래 걸립니다. 그냥 저가로 걸어두고 맘 편히 기다리시는게 낫습니다. 저는 이래저래 테스트해보다가 몇 만원 날렸습니다. ㅠ.ㅠ
만들어진 후 다시 '컨트랙트' 메뉴로 들어가면 '주문형 콘트랙트' 와 '주문형 토큰' 에 항목에 나의 코인이 보일 것입니다.
제가 참여하고 있는 KStarCoin 프로젝트(실제 상용화된 코인, 프리세일 중)와 새로 생성한 TaeKimCoin 이 보이네요 ^.^! 어디 TaeKimCoin 을 한번 눌러볼까요?
코인의 상세 내용을 보면 위와 같이 '토큰 콘트랙트 주소' 를 알 수 있습니다. 이 주소를 복사해서, 이더스캔에서 검색해보겠습니다. 다음 링크를 눌러보세요.
어때요? 이더스캔에서도 아주 잘 검색되죠?
오늘 연재 내용은 여기까지입니다. 그런데 기왕 토큰을 만든 김에 써봐야 하지 않겠어요? 그래서 다음 이벤트를 준비해보았습니다.
전격 TaeKimCoin 에어드랍 이벤트! 10,000 TKC 를 받아가세요!
댓글로 이더리움 계좌 주소를 남겨주시는 분들께 10,000 TKC 를 보내드립니다. 연재 내용이 이어지면서 이 코인으로 실습을 할 수 있게 될테니 받아두시면 좋을 겁니다. 제가 이더리움 가스비를 소모하면서 보내드리는 거니, 받는 분들은 꼭 열심히 공부하셔야 합니다. 차후 실습에 따라 오시는지 트랜잭션 보면 다 나와요!
- (팁 1) 계좌를 오픈하는 것이 꺼려지는 분은 새로운 계좌를 하나 만들어서 요청하시면 될 것 같습니다.
- (팁 2) 토큰을 거래소 지갑 주소로 받으시면 절대 안됩니다! 꼭 개인 지갑 주소를 보내주셔야 합니다. 보통 이더리움월렛 혹은 마이이더월렛(MEW)을 이용하니 참고하시기 바랍니다.
TaeKim(@nida-io) 의 프로젝트를 구경하세요.
감사합니다~ 대표님, 잘 배우고 있습니다~~!!!! :)
이사님~~ 못뵌지 너무 오래 되었습니다. 코믹스브이 사무실에 놀러가야지~ 라고 생각만 몇달째입니다 흑 ㅜ.ㅜ
존경스럽습니다 ㅎㅎ
댓글 확인이 늦었습니다. 감사합니다. ^^
"누구나~" 4편 잘 보았습니다. 덕분에 조금 이해할수 있었습니다.
그런데 SEND TRANSACTION이후 한참동안 컨트랙션 생성 작업을 하더니 오류가 나고 토큰이 생성되지 않습니다. 선택된 수수료가 너무 작아서 그런건가요?
제가 한동안 잠수를 탔다가 돌아왔습니다. 죄송합니다. 생성 트랜잭션 보내는 것까지 되었는데 실행이 안되고 오류가 났다는 것인가요? 그러면 수수료 문제일 것 같긴 한데요.. 너무 늦어서 아마 이제는 답변 드려도 도움이 안 될 것 같습니다. ㅠ.ㅠ
안녕하세요. 선생님. 강좌를 보며 하나하나 보고 배우는 학생입니다. 소스코드 수정을 다하고, 구매 테스트를 하기 위해 Metamask를 활용하여 구매를 진행하려고하니 Transaction Error. Exception thrown in contract code. 라는 에러가 뜨네요 ㅠ 뭐가 문제인걸까요;;; 소스는 Remix에서 작업하였습니다.
에고~ 제가 바쁘다는 핑계로 한동안 잠수를 탔었습니다. 죄송합니다. 보통 뭔가의 이유로 require 에 걸릴 때의 문제일텐데요, transferFrom 의 경우에는 권한을 주는 계정과 이체를 실행하는 계정을 바꿔가면서 실행해야 해서 헷갈릴 수 있어요. 너무 늦었지만 ㅠ.ㅠ 아직 해결이 안되셨다면, 어떤 기능을 실행할 때 문제가 되셨는지 댓글 달아주시면 제가 한번 살펴보도록 하겠습니다. (죄송 죄송 ...)
팔로우도 하고 잘 보고 있습니다. 지갑 앱도 만들수 있을까요?
0x2374636932d49fa16d60FD3d190c382289F9AAd7
ETH 지갑 주소입니다 +_+
1시간 11분째 Create Contract 중입니다. 아직 오류는 안났습니다. 수수료 높이면 되는 부분일까요?
지갑 앱을 직접 만드신다는 건가요? 지갑 앱을 만드는 건 아무래도 손이 많이 갈 것 같습니다. 보통 전문 팀도 3개월 정도 걸리는 것으로 알고 있어요. 그런데 이더 스캔에서 해당 주소로 검색하면 아무 트랜잭션이 없는데요, 수수료를 너무 낮게 책정하신거 아닌가 하는 생각이 듭니다.