ERC-20トークンコントラクトはどのように動いているのか
Ethereumの現在の主流のトークンコントラクトであるERC20について、特徴と機能、仕組みを見ていきます。
Ethereum Wikiと以下のポストをまとめました。
ERC20 Token Standard - The Ethereum Wiki
Understanding ERC-20 token contracts – Jim McDonald – Medium
What is ERC-20 and What Does it Mean for Ethereum? | Investopedia
トークンコントラクトとは何か、開発者はどう扱うのか、などに役立てばと思います。
コントラクトの基礎については、こちらのアカウントのところを読んでいただければと思います。
ERC20は、Ethereumのトークンコントラクト用に、共通機能を持つインターフェースとして作られました。今ほとんどの流通しているトークンはERC20です。
このERC-20は、ウォレットで異なるトークンでも残高を一覧で確認できたり、取引所がトークンコントラクトのアドレスだけでトークンを上場させることができるなど、多くの構造的な利点があります。
目次
トークンコントラクトとは何か
まずそもそもトークンコントラクトとは、スマートコントラクトの一種で、「アカウントのアドレスとそれらの残高の対応表」を持ったスマートコントラクトです。
この残高は、コントラクトの作成者が定義した値で、リアルな所有物を表す場合もあれば、金銭的な価値や、ホルダーの評判を示す値の場合もあります。これらの残高の単位をトークンと呼びます。つまり残高が1であれば1トークン所有している、と同義になります。
あるアカウントから違うアカウントへトークンが送られるとき、トークンコントラクトは、それら2つのアカウントの残高を更新します。例えば、画像でいうと0x2299…のアドレスから0x1f59…のアドレスへ10トークン送ったとします。するとトークンコントラクトによって、残高は以下のように更新されます。
トークンの合計の流通量は多くする、もしくは少なくするという操作ができます。(トークンコントラクトでそれらの操作を許可するか定義できます。)
まず合計の流通量を多くするには、新しいトークンを発行し(鋳造し)多くすることができます。例えば、「100トークンをアカウント0x4ba5…に鋳造した」とすると、以下のように残高が更新されます。
また合計の流通量は「バーン」(燃やす)ことによって少なくすることができます。例えば、「アカウント0x4919…の50トークンをバーンした」とすると、以下のように残高が更新されます。
バーン以外の方法は、秘密鍵がわからないアドレス(いわゆるThe 0 address)にトークンを送ることです。このやり方は、トークンが使えなくなるという点ではバーンと同じ意味になりますが、存在するトークンの合計量は変化しません。例えば0x93f1…というアドレスで50トークンを 0アドレスに送り、トークンを潰すと、残高は以下のように更新されます。(合計のトークン量は変わっていないことがわかります。しかし実際の流通量は少なくなります。)
単純なトークンコントラクトは、上のようなアドレスと残高の対応表の情報を持ちます。「分割」などの複雑な機能を持つトークンコントラクトは、より便利になります。
ERC-20 トークンコントラクトのパラメータ
ERC-20 トークンコントラクトは、「コントラクトのアドレス」「トークンの合計流通量」によって定義されますが、他にもオプショナルな機能が多くあり、ユーザは細かな設定ができます。トークンの名前、トークンのシンボル、小数以下の桁数などです。これらは後ほど説明します。
ちなみに、トークンコントラクトには中央集権型の登録所はないので、名前やシンボルがかぶったりする可能性があることに注意です。
トークンコントラクトの name の部分は、トークンコントラクトの名前(正式名称的な部分)で、例えば “My token”などです。
長さに制限はありませんが、ウォレットアプリなどで切り捨てられる可能性もあるので、短くするのが良いでしょう。
次に symbol の部分は、シンボルを示し、上のトークンであれば頭文字をとって、”MYT” などがよいでしょう。株で言うティッカーシンボルのようなもので、こちらも長さに制限はありませんが、3,4文字のトークンが普通です。
decimals の部分は、わりと混乱しやすい部分ですが、そこまで難しくはありません。どの程度トークンを分割できるようにするか?という設定をする部分です。(小数点以下が、0~18個になるような設定ができます。18以上も設定ができます。)
このdecimalsがある理由は、Ethereumは小数を扱わず、すべて整数で処理されるためです。
では分割が使われないケース、使われるケースを見てみます。
まず分割が必要ないケースです。例えば、ライセンストークンがあったとしましょう。
トークンコントラクトはソフトウェア・ライセンスの割当を示します。
ライセンストークンを持っているユーザは、そのソフトにアクセスができます。ここでは、ライセンスが0.7個などと小数のライセンスを持っていても意味がありませんね。1.6個なども同様です。
なので、コントラクトの作成者は、「小数以下の桁数を表す」decimals を 0 に設定します。
数名がライセンストークンを持っている状況を想定すると、以下のような残高になるでしょう。
上では100個のライセンスがあるのがわかります。誰かがライセンスを買ったら、そのアカウントにライセンストークンが送られ(残高が変更され)るイメージです。そしてこの残高表を見れば、ライセンスを持っているアカウントかどうか見て確認することができます。
次に分割が必要なケースを考えてみます。例えばゴールドトークンというのがあったとして、物理的な金の所有権を意味するトークンだとします。
トークンの作成者は、金1kg=1トークンとしたいとします。しかし1kg未満のグラム単位でトークンをやり取りしてもらいたいと考えた場合、Ethereumは整数しかサポートしないため、このままではできません。そこで decimalsを3に設定します。(こうするとトークン数*10の3乗となります)
そして何人かがトークンを持っていると想定したとき、対応表は以下のようになります。
しかし、実際には、decimals が 3 に設定されているため、以下のように単位kgとして見ることができます。
decimals は、人に合わせるための部分と言われて、人にどんなふうに残高を見せるかを設定する部分といえます。なのでゴールドトークンはコントラクト内部の計算では decimals を使うことはなく、Kgの単位で計算がされます。
この分割によって、細かいトークンまで扱うことができて、多くのトークンは decimals を 18 に設定しています。
まとめ
・1トークンが現実世界の何か1つを表すとき、 decimals を 0 に
・小数以下の桁が決まってるとき、decimals をその桁数に
・どちらでもないとき、細かくトークンを使えるように decimalsを18に
totalsupply はERC20トークンコントラクトを定義する最後の部分で、必須のパラメータです。ERC20の仕様には明確にかかれていませんが、totalsupplyはシンプルで、totalsupplyは残高の合計とイコールとなります。
ERC-20トークンコントラクトの関数
ERC20トークンコントラクトには、残高をみたり、設定した条件になったら送金をしたりと色々な関数があります。例えば以下のような感じです。
balance0f( )関数は、与えられたアドレスの持つトークンを返します。
トークンを送る方法は2つあり、1つは transfer( )関数です。メッセージ送信者から他のアドレスに直接トークンを送ります。
transfer( )関数は、外部アカウントに送る分にはよいですが、スマートコントラクト内の関数にトークンを送るときには機能しません。
これはスマコンが走っているとき、資金を送金する元のアクセスの詳細にアクセスはせず、コントラクトをコールしているユーザがコントラクトを走らせるための資金を払っているためです。
Doer というコントラクトがあるとしましょう。 Doerは、doSomething( )という10 Do トークンで動くような関数を持つとします。AくんがdoSomething( )をコールしたくて 50 Doトークンを持っているとします。
では実際にどのようにdoSomething( ) を動かすためにDoerにトークンを送れば良いのでしょうか。
ここで、approve( )とtransferForm( )の2つの関数が必要になってきます。
まずはじめのステップは、トークンホルダーが自分用にもう1つのアドレスをつくり(通常はスマートコントラクトで行います)、一定数のトークンを格納します。ここで新しいアドレスに送られたトークンは、allowance(割当)と呼ばれます。トークンホルダーは approve( )を使うことでこの情報を得ることができます。
上の写真で2行目の「0x1f59…」がAくんのアドレスだとすると、 0xd8f0…のアドレスにあるコントラクト Doer に、Aくんアカウントから25トークン送ってもいいよと許可している状態になります。
一度allowanceが作られると、スマートコントラクトが割当数のトークンを、ユーザの割当(図でいうAllowance)から使います。(これはコントラクトの動作の一部として使われます。)
この結果、Aくんは doSomething()関数をコールすることができ、この関数は、transferFrom()という関数を使い、10 DoトークンをAくんのアカウントから使うことで、動作を実行します。もしAくんが10 Doトークンを持っていない場合(つまりAllowanceが10未満のとき)は、doSomething()関数は動きません。
allowance()関数は、送信できる割当トークンの数を返します。ここで重要なのは、割当は、残高以上になることがある、ということです。図のように0x2299…のアカウントは500トークンを送れることになっていますが、さらに上のほうの図にあるように、90トークンしか持っていません。
allowance()関数を使うコントラクトは、トークン数を計算する際にホルダーの残高も考慮する必要があります。
ERC20トークンコントラクトのプログラム実行(Event)
ERC-20ではコントラクトが定められた入力がされたときに実行するプログラム(イベント)が2つ定義されています。1つ目のイベントは Transfer( )で、これはどのアカウントからどのアカウントへ送られたというトークンの動きの情報を出力します。2つ目のイベントはApproval( )で、トークンの承認の情報を出力します。これらは残高をトラッキングする、もしくは割当の変化をトラッキングするのに使われます。
新しく作られたトークンは The 0 addressとともにTransfer( )イベントを出力します。
トークンがバーンされる際は、出力されるイベントはないので、バーンをしたい場合、トークンをthe 0 addressに送り、実質バーンすることが多いです。(発生イベントがなく処理が楽なため、こちらを選ぶことが一般的ということです。)
最後に
ERC-20は、トークンコントラクトの土台となっていますが、欠点がないわけではありません。そこでERC-223の提案は、安全対策や追加機能をいれていますが、ERC20との互換性はありません。今あるトークンコントラクトはERC20を引き続きサポートしながら、ERC223提案も同時におっていく必要があります。