solidityの勉強のために、めそコインのコントラクトコードを読んでみる

こちらで紹介されているMetho Coinを読み解いてsolidityの勉強します。
はじめてsolidityのコードに目を通すので、かなり初歩的なところから調べる感じです。笑

仕様

仕様は以下の通りです。
この辺は、いくつか他のコードも読んでみないとわからないところですが、固定レートってあまり他にはなさそうです。

最初に発行者(オーナー)が全発行トークンを保有する。
ethと固定レートでトークンを全量無期限に販売する。(発行日以前の日時からのリクエストは弾くよう制御する)
適切に権限設定されたトークンを購入者に送付し、購入者はウォレット内にそれを保持できる。
購入者はオーナーから権限が委譲されているので、保有しているトークンを第三者に送付できる。
オーバーフロー攻撃への対策を施す。

コインの概要は以下の通り。

トークン名: Metho Coin
ティッカー: MTC
総発行数: 1,000 (1,000.000-)
レート: 20mtc = 1eth
時価総額: 1,505,162円 (2017年9月22日現在)

コード

すごく丁寧にコメントが書かれているので、もしかするとすぐ読めるかもしれないですね。

SafeMathをインポート

import "./SafeMath.sol";

おおよそ振ったコメントの通りのコーディングですが、ライブラリとしてOpenZeppelin製のセキュリティフレームワークのひとつ、SafeMathをインポートしています。スマートコントラクトで怖いのはオーバーフロー攻撃もさることながらuintに対して負の数字を渡すアンダーフロー攻撃なので、その脆弱性をふさぐために全ての四則演算は += のような演算子を使わず、SafeMathのメソッドで行なっています。

オーバーフロー攻撃(かなり大きい容量のデータを送りつけてくる攻撃)や負の値が送られてきたときにエラーを投げるように設計されています。

contract MTC

contract MTC {

  ...

}

contract内にコントラクトのコードを書くのがsolidityの決まりです。

SafeMathの適用

using SafeMath for uint256;  //uint256変数にはSafeMathを適用する。

インポートしたSafeMathをuint256に適用するにはusingを使えばいいんですね。

定数の定義

//ネットワーク上にリリースするトークン名とティッカー
string public constant symbol = "MTC";
string public constant name = "Metho Coin";

string型でpublicとして宣言しています。
constantなので、ここで定義した定数は変更できないようですね。
確かにsymbolとかnameとかは修正しないでしょう。

供給量・レート

//総供給量と対ethレートの定義
uint256 _totalSupply = 1000000; //1000 x 1000 (小数点以下分を含む)
uint256 _currentSupply = 0;
uint256 public constant RATE = 20; //20mtc = 1eth
uint256 public constant decimals = 3;
address public owner;

_totalSupply_currentSupplyconstantになっていないです。
_currentSupplyは流通しているトークン数だと思うので、constantになっていないのはわかるのですが、_totalSupplyがconstantになっていないのは、トークンを更に発行するためや、償却のためでしょうか?

オーナーとユーザー

// オーナーとユーザーの関係を定義してマッピング
mapping(address => uint256) balances;
mapping(address => mapping (address => uint256)) allowed;  //流通のため、オーナーから払い出したトークンの取り扱いをユーザーに許可する。
modifier onlyOwner() {
    require(msg.sender != owner);
    _;
}

mappingbalancesallowedを定義して、modifierを定義しています。
modifierはメソッドみたいなもので、function実行時の権限に使います。
requireでownerがsenderでなかった時はconstractを中止しています。

require関数はconsitionがfalseを返した場合、そこでcontractの実行を停止し、contractの状態を実行前に戻して残りのガスをcallerに返却します。
assertと違い、requireでは使った分のガスだけが消費されます。
requireをコンパイルすると、revert opcodeに変換されます。

_; // continue executing rest of method bodyなので、senderがownerの時はそのまま実行します。

支払い上限

modifier onlyPayloadSize(uint256 size){
    assert(msg.data.length >= size + 4);
    _;
}

もう一つmodifierです。
支払い上限を設定します。
assertはcontract実行前まで状況を戻します。

assert関数はconditionがfalseを返した場合、そこでcontractの実行を停止し、contractの状態を実行前に戻して残りのすべてのガスを消費します。
assertをコンパイルするとopcode=0xfeになり、evm上では0xfeは未定義のopcodeなので動作が停止されます。

フォールバック関数

//トークンのフォールバック関数    
function() payable{  
    createTokens(msg.sender);
}

contractは一つだけ無名関数を作ることができます。
引数は持てないです。
実行するfunction名の指定がなかったときや、データが何もない時に実行されます。
何もデータがなくてもEtherの情報だけあれば実行してくれるので、payableにしておくのがよさそう。

コンストラクタ

// 以下動作部分となるコンストラクター
function MTC() {
    owner = msg.sender;
    balances[owner] = _totalSupply;
}

クラス生成時に実行されるメソッド。

交換処理

//ethとmtcの交換処理
function createTokens(address addr) payable{
    require(msg.value > 0); 
    uint256 tokens = msg.value.mul(RATE).mul(1000).div(1 ether);
    require(_currentSupply.add(tokens) <= _totalSupply); 
    balances[owner] = balances[owner].sub(tokens);
    balances[addr] = balances[addr].add(tokens);
    Transfer(owner, addr, tokens);

    owner.transfer(msg.value);
    _currentSupply = _currentSupply.add(tokens);
}

tokensを定義する時にインポートしたSafeMathのmul functionとdiv functionを使っています。
payableはfucntion実行時にetherも受け取ることができます。

payable for functions: Allows them to receive Ether together with a call.

  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;
  }

SafeMathのmul functionは上記のようになっています。

しかし、めそコインでは以下のように使われています。

uint256 tokens = msg.value.mul(RATE).mul(1000).div(1 ether);

これはmun functionの引数aがmag.valueのことだからです。
solidityはjavascriptライクと言われていますが、javascriptライクってこんな感じだったでしょうか?
はじめてお目にかかった…

1 etherというのは、units variableです。
つまりは、定数として値が入っています。

トークン総額のリフレッシュ

//交換処理後にオーナーのトークン総数をリフレッシュ
function totalSupply()  constant returns (uint256 totalSupply) {
     return _totalSupply;
}

コメントのままです。

トークン数の表示

//アカウントにあるトークン数の表示
function balanceOf(address _owner) constant returns (uint256 balance) {
    return balances[_owner];
}

コメントのままです。

めそコインの送付

// 交換したmtcの送付
function transfer(address _to, uint256 _value) returns (bool success) {
    require(
        balances[msg.sender] >= _value 
        && _value > 0
       ); 
   balances[msg.sender] = balances[msg.sender].sub(_value);
   balances[_to] = balances[_to].add(_value);
   Transfer(msg.sender, _to, _value);
   return true;
}

senderにtransferするだけの残金があるか、transferの金額が0よりも大きいか確認してから、Transferを呼び出します。
返り値は、boolean。
発行者(オーナー)がめそコインを送付するときの関数。

流通

// mtc獲得後の流通のための関数
function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
     require(
         balances[_from] >= _value
         && allowed[_from][msg.sender] >= _value
         && _value > 0
    );
         balances[_from] = balances[_from].sub(_value);
         allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
         balances[_to] = balances[_to].add(_value);
         Transfer(_from, _to, _value);
         return true;
}

発行者(オーナー)ではない人がめそコインを送付するときの関数。

流通権限

// 発行元から入手した保有者に流通権限を与える(それ以外の流通を許可しない安全策)
function approve(address _spender, uint256 _value) returns (bool success) {
    allowed[msg.sender][_spender] = _value;
    Approval(msg.sender, _spender, _value);
    return true;
}

function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
    return allowed[_owner][_spender];
} 

コメントのまま。

トランザクションのログ

event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);

Eventがcallされるとトランザクションにログを残すことができます。
ここは、Block Chain特有ですね。

参考




コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です