22
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ERC20の簡単なまとめ

Last updated at Posted at 2019-05-08

#はじめに
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;
  }
}
2_MyToken_migration.js
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 {
...
}
2_MyToken_migration.js

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つ目ので例えると、keyaddress,uint256valueに当たります。

つまりbalanceOf()関数を利用する際に、引数にaddress型のデータを与えることで、uint256型のデータを取得できる、と言う意味です。

balanceOf[msg.sender] -= _value;

実際の使用例をみるとmsg.sender(メモ参照)はaddress型のデータですが、
このaddress型のデータに紐づくuint256型(数値)が取得されており、
同じuint256型(数値)である_valueと同時に使えて計算できています。

#3. Transfer関数
Transfer関数は、関数を呼び出した人から、指定した_toにトークンを送る関数です。
Trensfer関数は必ずイベントを呼び出す必要があります。

MyToken.js
  //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 ] }

わかりずらいかもしれませんが上記の流れを図で表すとこのようになります。

スクリーンショット 2019-05-06 12.44.44.png

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でフロントを作成してみようと思います。
また、自分の認識に間違いがあった場合はコメントください!!よろしくお願いします。

22
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?