#はじめに
ERC20を使って簡単にトークンを作れる、ということをよく聞くので、実際に作ってみようと思いました。
そして結論から言うと、作るだけなら簡単にできました。
Truffle で始める Ethereum 入門 - ERC20 トークンを作ってみよう
この記事は開発環境から説明してありわかりやすいと思います。
ただ詳細を理解しようとすると、難しいところがありましたので、自分なりに重要だとまとめてみました。
#コントラクトの全体像
コントラクトの全体像は下記のようなコードになります。
MyToken.sol
pragma solidity >=0.4.21 <0.6.0;
contract MyToken {
string public name = "My Token"; //Tokenの名前
string public symbol = "MY"; //Tokenの単位
string public standard = "Token v1.0"; //Tokenのバージョン
uint256 public totalSupply;
event Transfer(
address indexed _from,
address indexed _to,
uint256 _value
);
event Approval(
address indexed _owner,
address indexed _spender,
uint256 _value
);
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
constructor(uint256 _initialSupply) public {
balanceOf[msg.sender] = _initialSupply;
totalSupply = _initialSupply;
}
function transfer(address _to, uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value);
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
function approve (address _spender, uint256 _value) public returns (bool success) {
//allowance
allowance[msg.sender][_spender] = _value;
//Approval event
emit Approval (msg.sender, _spender, _value);
return true;
}
function transferFrom (address _from, address _to, uint256 _value) public returns (bool success) {
require(_value <= balanceOf[_from]);
require(_value <= allowance[_from][msg.sender] );
balanceOf[_from] -= _value;
balanceOf[_to] += _value;
allowance[_from][msg.sender] -= _value;
emit Transfer(_from, _to, _value);
return true;
}
}
var MyToken = artifacts.require("./MyToken.sol");
module.exports = function(deployer).then(function() {
deployer.deploy(MyToken, 1000000)
});
};
上記のコードで個人的に重要だと思ったところを書いていきます。
1. constructor
コンストラクタはコントラクトのデプロイ時に実行される関数で、コントラクトに状態変数を持たせることができます。
ここでは_initialSupply
が初期値として渡されています。
pragma solidity >=0.4.21 <0.6.0;
contract MyToken {
uint256 public totalSupply;
//constructor
constructor(uint256 _initialSupply) public {
//コントラクトの作成者に全てのトークンを持たせる
balanceOf[msg.sender] = _initialSupply;
totalSupply = _initialSupply;
}
...
}
##コンストラクタ作成時の注意点
先ほど説明したように、「コントラクトのデプロイ時に実行される関数」ですので、migrationファイルで変数を渡すことを忘れないようにしましょう。
今回は1000000
を_initialSupply
として渡しました。
//MyToken.sol
constructor(uint256 _initialSupply) public {
...
}
var MyToken = artifacts.require("./MyToken.sol");
...
deployer.deploy(MyToken, 1000000)
###コンストラクタの書き方
Solidityではv0.4.23以降でコンストラクタの書き方が変わってるので参考にしてみてください。
Ethereum: Solidity v0.4.23〜 の新しいコンストラクタの書き方
balanceOf変数はmapping型(Key-Value配列)になっており、Keyがアカウントのアドレス、Valueが残高を表します。送金機能(transfer)を持っており、送金先のアカウントアドレスと送金額を指定することでトークンを送ることができます。
#2. mapping
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
mappingは連想配列やデータ保管のキーバリューストアと言われています。
上記の1つ目ので例えると、key
がaddress
,uint256
がvalue
に当たります。
つまりbalanceOf()
関数を利用する際に、引数にaddress型のデータを与えることで、uint256型のデータを取得できる、と言う意味です。
balanceOf[msg.sender] -= _value;
実際の使用例をみるとmsg.sender
(メモ参照)はaddress型のデータですが、
このaddress型のデータに紐づくuint256型(数値)が取得されており、
同じuint256型(数値)である_value
と同時に使えて計算できています。
#3. Transfer関数
Transfer関数は、関数を呼び出した人から、指定した_to
にトークンを送る関数です。
Trensfer関数は必ずイベントを呼び出す必要があります。
//Transfer event 必須
event Transfer(
address indexed _from,
address indexed _to,
uint256 _value
);
...
//Transfer関数
function transfer(address _to, uint256 _value) public returns (bool success) {
//Tokenを送る人が十分なTokenを保有してるか確認
require(balanceOf[msg.sender] >= _value);
//msg.senderから_toに送金され、各アカウントのトークンバランスを変更する
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
//Transfer event の呼び出し
emit Transfer(msg.sender, _to, _value);
//boolをtrueに
return true;
}
#4. allowance関数, transferFrom関数, approve関数
transferFrom, approve, approveは Delegate functionとも呼ばれており、
第三者がTokenの送信、受け取りを可能にする関数です。
4-1. approve関数
関数を呼びたした人が、_spender
に対して、関数を呼びたした人の残高から_value
分のTokenを引き出すことを許可する関数です。
Tokenの小切手のようにイメージするとわかりやすいと思います。
approve関数を使う際は、Approval eventの呼び出しが必須になります。
function approve (address _spender, uint256 _value) public returns (bool success) {
//allowance関数(4-2参照)
allowance[msg.sender][_spender] = _value;
//Approval eventの呼び出し
emit Approval (msg.sender, _spender, _value);
return true;
}
4-2.allowance関数
approve関数で小切手を受け取った_spender
が引き出せるTokenの量を返す関数です。
allowanceはmappinngで定義し、approve関数内で使えるようにしています。
mapping(address => mapping(address => uint256)) public allowance;
1つ目のaddressが、approve関数を呼び出した人、つまり小切手を渡した人
2つ目のaddressが、approve関数の_spender
,つまり小切手を受け取った人
uint256
は_spender
が使える小切手の量を表します。
そして下記のように使えます。
第一引数に小切手を渡した人のアドレス、第二引数に小切手を受け取った人を与えることで
使える小切手の量を取得できます。
allowance[msg.sender][_spender]
4-3. transferFrom関数
transferFrom
関数はTransfer
関数と似ていますが、
Transfer
関数は関数の呼び出し者からある特定のアドレスにTokenを送信できるのに対し、
transferFrom
は第三者があるアカウントからあるアカウントにTokenを送信することを可能にする関数です。
transferFrom関数は単独でも使えると思いますが、Transfer関数との違いを明確にするため
approve関数とallowance関数と一緒に使っています。
今回は、
approve関数で小切手を渡した人が関数を呼び出す人
_from
がapprove関数で小切手を受け取った人
_to
が実際にtokenを受け取る人
と仮定します。
function transferFrom (address _from, address _to, uint256 _value) public returns (bool success) {
//小切手を受け取った人の小切手の額が送信額より多いか
require(allowance[_from][msg.sender] >= _value );
//_toに送信され、_fromが保有する小切手の額を変更する
balanceOf[_to] += _value;
allowance[_from][msg.sender] -= _value;
//Transfer eventの呼び出し
emit Transfer(_from, _to, _value);
return true;
#5.consoleでの動作確認
truffle(development)> MyToken.deployed().then(function(instance) {tokenInstance = instance ; })
undefined
truffle(development)> tokenInstance
TruffleContract {
constructor:
{ [Function: TruffleContract]
_static_methods:
......
##Tokenの名前の取得
truffle(development)> tokenInstance.name();
'My Token'
truffle(development)> tokenInstance.symbol();
'MY'
truffle(development)> tokenInstance.standard();
'My Token v1.0'
##totalSupplyの取得
totalSupply
はpublicだけどtotalSupply
はconstructorのargumentでデプロイした際に定義されています。
truffle(development)> tokenInstance.totalSupply().then(function (s) {supply = s});
undefined
truffle(development)> supply
BigNumber { s: 1, e: 6, c: [ 1000000 ] }
##Ganacheのaccount addressの取得
truffle(development)> web3.eth.accounts
[ '0x82eb7aa8e65099fdffb14a0cca8f7eab0026dc86',
'0xeb827c448545e8562d26e1d70741a7d5ced86ac3',
'0x19235a06bbd84e0f475e17774ad61b9c76b4091d',
'0x6322ea177d669ff5674412b49fce7eb05e8d167e',
'0xb3ca53eddcdd60c96880cd84136b338394cc0406',
'0x6f43c1b676d8b53fb9f28327881fec1134b9128a',
'0x2642c077d5514c366e9248d7175d802c038b5e06',
'0xc555ea1163931b5d8dd7e9bc1fc556e6cad2db69',
'0x900c5e4572854faae159dbbd4314a1a05d2050f4',
'0xf3dbc7b5c5710049674aaca74d7880ea28283a9f' ]
1番目のアドレスを取得
truffle(development)> web3.eth.accounts[0]
'0x82eb7aa8e65099fdffb14a0cca8f7eab0026dc86'
##Transer関数で送金
//web3.eth.accounts[0]のバランスの確認
truffle(development)> tokenInstance.balanceOf(web3.eth.accounts[0])
BigNumber { s: 1, e: 6, c: [ 1000000 ] }
//web3.eth.accounts[1]に10Tokenの送信
truffle(development)> tokenInstance.transfer(web3.eth.accounts[1], 10, {from: web3.eth.accounts[0]})
{ tx:
'0x8a2f5c7efd2b7898663f7079723a193173a5f58ebd6170d2d12a0f3718558b66',
receipt:
{ transactionHash:
'0x8a2f5c7efd2b7898663f7079723a193173a5f58ebd6170d2d12a0f3718558b66',
...
*web3.eth.accounts[0]はコントラクトの作成者になるので全てのTokenを保有します。
##残高の確認
truffle(development)> tokenInstance.balanceOf(web3.eth.accounts[0])
BigNumber { s: 1, e: 5, c: [ 999990 ] }
truffle(development)> tokenInstance.balanceOf(web3.eth.accounts[1])
BigNumber { s: 1, e: 1, c: [ 10 ] }
##approve関数、allowance関数, transferFrom関数の確認
truffle(development)> tokenInstance.approve(web3.eth.accounts[1], 100, {from: web3.eth.accounts[0]})
...
truffle(development)> tokenInstance.allowance(web3.eth.accounts[0], web3.eth.accounts[1]);
BigNumber { s: 1, e: 2, c: [ 10 ] }
truffle(development)> tokenInstance.transferFrom(web3.eth.accounts[0], web3.eth.accounts[2], 10, {from: web3.eth.accounts[1]})
...
truffle(development)> tokenInstance.balanceOf(web3.eth.accounts[2])
BigNumber { s: 1, e: 2, c: [ 10 ] }
truffle(development)> tokenInstance.allowance(web3.eth.accounts[0], web3.eth.accounts[1])
BigNumber { s: 1, e: 0, c: [ 0 ] }
わかりずらいかもしれませんが上記の流れを図で表すとこのようになります。
Aはweb3.eth.accounts[0]
Bはweb3.eth.accounts[1]
Cはweb3.eth.accounts[2]
*A(web3.eth.accounts[0]
)はコントラクトをデプロイしたアドレスなので1000000Token持っています。
#メモ
開発を行う際に重要だと思ったことを簡単に整理しておきます。
##_
をつける理由
関数の中でのみ使える変数は変数の前に_
をつけて他の変数と区別する
##・msg.sender
msg.sender
はSolidityで使えるグローバル変数で、関数を呼び出したaddressを取得します。
Solidityのグローバル変数リスト
##require()
require
の中身がtrue
だった場合のみ、その後のコードを実行するが、
true
出なかったら関数の処理をerror
にする。
errorになった場合、Gasはmsg.sender
に戻されます。
#参考サイト
Code Your Own Cryptocurrency on Ethereum
7時間ほどのビデオです。
基礎的なことから説明されているためかなりオススメです。
#終わりに
次はReactでフロントを作成してみようと思います。
また、自分の認識に間違いがあった場合はコメントください!!よろしくお願いします。