Dapps開発の際に調査したので備忘録としてシェア。
背景
Ethereumの決済予約ができるスマートコントラクトを作ったのだが、ERC20にも対応させようと思って調べていたら、意外と日本では情報発信されてなかったので備忘録がわりにシェアします。
ERC20を実装するには
ERC20トークンはOpenZeppelinを使えば比較的簡単に実装することができます。具体的な実装方法はこちらの記事などを参考にしていただければと思います。
ERC20の構造
ERC20の主な内部構造は以下のようになっています。 * データ格納テーブル2つ * 送金用関数2つデータ格納用テーブル
-
_balancesテーブル
残高情報格納用テーブル。所有者のethereumアドレスと、そのアドレスの残高を記録しているテーブルです。 -
_allowedテーブル
送金許可フラグを格納するテーブル。送金オペレーションを自分以外の人に委託する場合に使用します。「送金先」と「送金額」を登録して、送金を自分以外の人に委託できるようになります。
pragma solidity ^0.4.24;
import "./IERC20.sol"; // 雛形のコントラクトを読み込み
import "../../math/SafeMath.sol"; // 算術演算処理を集めたコントラクトを読み込み
contract ERC20 is IERC20 {
using SafeMath for uint256;
// _balancesテーブルの定義
mapping (address => uint256) private _balances;
// _allowedテーブルの定義
mapping (address => mapping (address => uint256)) private _allowed;
送金関連の関数
-
transfer関数
自分のアドレスから他人のアドレスにトークンを送金する関数(送金操作をするのは自分)- 入力値:
- to (宛先アドレス address型)
- value (送金金額 uint256型)
- (送信アドレスは自分のアドレスで固定)
- 入力値:
-
transferFrom関数
自分のアドレスから他人のアドレスにトークンを送金する関数(送金操作をするのは自分以外でも可能)- 入力値:
- from (送信アドレス address型)
- to (宛先アドレス address型)
- value (送金金額 uint256型)
- 入力値:
transfer関数
_balancesテーブルの残高を変更することによって、資金移動を表現します。
// transfer(自分から他人のアドレスにトークンを送金する関数)
function transfer(address to, uint256 value) public returns (bool) {
// _transferを実行
_transfer(msg.sender, to, value);
return true;
}
function _transfer(address from, address to, uint256 value) internal {
require(to != address(0)); // 送信先アドレスが実在する必要がある
_balances[from] = _balances[from].sub(value); // 自分のアドレスの残高 - value
_balances[to] = _balances[to].add(value); // 相手のアドレスの残高 + value
emit Transfer(from, to, value);
}
transferFrom関数
以下の3段階でトークンを送金します。
- 送金許可テーブル(未承認)作成
- 送金許可テーブルを承認
- 送金処理を実行
1. 送金許可テーブル(未承認)作成
送信アドレスには自分以外のアドレスを指定可能。ただし、実際に送金をするには送信アドレスの所有者が承認する必要がある。
// 事前処理①:送金許可テーブルを作る(実行者以外のアドレスを送信アドレスに指定可能)
function allowance(
address owner, // 送信アドレス
address spender // 受信アドレス
)
public
view
returns (uint256)
{
return _allowed[owner][spender]; // 送金許可テーブル(未承認)を作成
}
2. 送金許可テーブルを承認する
送信者のみが送金許可テーブルにアクセスでき、承認処理を実行できる。(この段階ではまだコインの移動は行われない)
// 事前処理②:送信者のみが承認できる
function approve(address spender, uint256 value) public returns (bool) {
require(spender != address(0));
_allowed[msg.sender][spender] = value; // 送信者のみが送金許可テーブルにアクセス可能
// 金額が入力された送金許可テーブル = 許可済み とみなす
emit Approval(msg.sender, spender, value);
return true;
}
3. 送金処理を実行する
送金許可が承認されていれば誰でも送金処理を実行できる。(この段階でコインが移動する)
// 送金処理:_allowedテーブルを元に、送金処理を実行する
function transferFrom(
address from, // 送信アドレス
address to, // 受信アドレス
uint256 value // 送金金額
)
public
returns (bool)
{
_allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); // 送金許可テーブルの内容を更新
_transfer(from, to, value); // 送金処理を実行
return true;
}