LoginSignup
21
13

More than 5 years have passed since last update.

BATトークンのスマートコントラクトを全行コメント解説

Last updated at Posted at 2017-09-22

BAT.png

BAT(Basic Attention Token)はEthereumのスマートコントラクトを利用したトークンです。
ソースコードはsolidityというJSに似た言語で書かれており200行程度です。

  • solidityを学びたい人
  • solidity開発者
  • BATについて知りたい開発者
  • トークンの仕組みをしりたい開発者

がターゲットです。

BATとは

BATとは広告主とパブリッシャーとユーザーをつなぐスマートコントラクトです。
bat_triad_diagram.png

広告料の支払い等をスマートコントラクトで実行し、広告効果の計測等は外部のシステムで行います。
なのでsolidityのコードには送金に関わるものしかありません。

BATに関する詳しいことは【BAT】Basic Attention Token(ベーシック・アテンション・トークン)に乗ってます。Google翻訳っぽい日本語ですがホワイトペーパーよりも簡潔なのでおすすめです。

solidityのコード

基本的に全行コメントしてあります。わからない部分はコメントを頂ければ書き足します。

pragma solidity ^0.4.10;

/* SafeMath
四則演算で予想外の結果が帰る場合は例外を投げるメソッド集。
*/
contract SafeMath {
    /* safeAdd()
    オーバーフローしないことを確認した上で足し算をする。 
     */
    /* Tip: internal
    オブジェクト指向言語のprotectedと同じ概念。
    継承先のcontract内からはアクセスできる。
    contractの外からはアクセスできない。
    */
    /* Tip: assert
    assert(false)となった場合に残りのgasを使い切った上でrevertする
    */
    function safeAdd(uint256 x, uint256 y) internal returns(uint256) {
      uint256 z = x + y;
      assert((z >= x) && (z >= y));
      return z;
    }

    /* safeSubtract()
    負の値にならないことを確認した上で引き算をする。
    */
    function safeSubtract(uint256 x, uint256 y) internal returns(uint256) {
      assert(x >= y);
      uint256 z = x - y;
      return z;
    }

    /* safeMult()
    変なことが起きないことを確認した上で掛け算する。
    */
    function safeMult(uint256 x, uint256 y) internal returns(uint256) {
      uint256 z = x * y;
      assert((x == 0)||(z/x == y)); /* TODO: なにを確認してるんだろう */
      return z;
    }

}

/* Token
ERC 20の仕様に則った抽象クラス
*/
contract Token {
    uint256 public totalSupply;
    function balanceOf(address _owner) constant returns (uint256 balance);
    function transfer(address _to, uint256 _value) returns (bool success);
    function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
    function approve(address _spender, uint256 _value) returns (bool success);
    function allowance(address _owner, address _spender) constant returns (uint256 remaining);
    /* Transferメソッドが実行されたときにログを残す */
    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    /* Approvalメソッドが実行されたときにログを残す */
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}


/* StandardToken
ERC 20の仕様に則ったクラス
*/
contract StandardToken is Token {

    /* transfer()
    コントラクト実行主から他アドレスへ送金する
    */
    function transfer(address _to, uint256 _value) returns (bool success) {
      /* 送金主の残高が足りているか確認 */
      if (balances[msg.sender] >= _value && _value > 0) {
        /* 送金主の残高から取引額を差し引く */
        balances[msg.sender] -= _value;
        /* 送金先の残高へ取引額を足す */
        balances[_to] += _value;
        /* 送金イベントを発火 */
        Transfer(msg.sender, _to, _value);
        return true;
      } else {
        return false;
      }
    }

    /* transferFrom()
    特定のアドレスから他アドレスへ送金する
    */
    function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
      /*
      送金主の残高が足りているか確認
      コントラクト実行主が送金の権限を持っているかを確認
      */
      if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) {
        /* 送金先の残高へ取引額を足す */
        balances[_to] += _value;
        /* 送金主の残高から取引額を差し引く */
        balances[_from] -= _value;
        /* 実行主が持つ送金権限の残高から取引額を差し引く */
        allowed[_from][msg.sender] -= _value;
        /* 送金イベントを発火 */
        Transfer(_from, _to, _value);
        return true;
      } else {
        return false;
      }
    }

    /* balanceOf()
    特定のアカウントの残高を確認する
    */
    function balanceOf(address _owner) constant returns (uint256 balance) {
        return balances[_owner];
    }

    /* approve()
    コントラクト実行主の残高を_value分だけ引き出す権限を_spanderに付与する
    */
    function approve(address _spender, uint256 _value) returns (bool success) {
        /* 承認確認イベントを発火 */
        allowed[msg.sender][_spender] = _value;
        /* 承認イベントを発火 */
        Approval(msg.sender, _spender, _value);
        return true;
    }

    /* allowance()
    _ownerの残高を_value分だけ引き出す権限を_spanderが持っているか確認する
    */
    function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
      return allowed[_owner][_spender];
    }

    /* 構造体balancesを定義 */
    mapping (address => uint256) balances;
    /* 構造体allowedを定義 */
    mapping (address => mapping (address => uint256)) allowed;
}

/* BAToken
トークン本体のcontractの実装
*/
contract BAToken is StandardToken, SafeMath {

    /* メタデータ */
    /* コントラクト名 */
    string public constant name = "Basic Attention Token";
    /* コントラクトシンボル */
    string public constant symbol = "BAT";
    /* コントラクトの小数点以下の桁数 */
    uint256 public constant decimals = 18;
    /* コントラクトのバージョン */
    string public version = "1.0";

    /* 運営用のETHを貯めておくアドレス */
    address public ethFundDeposit;
    /* 運営用のBATを貯めておくアドレス */
    address public batFundDeposit;

    /* クラウドセール用変数 */

    /* クラウドセールが終わったかどうか */
    bool public isFinalized;              
    /* クラウドセールが始まるブロック */
    uint256 public fundingStartBlock;
    /* クラウドセールが終わるブロック */
    uint256 public fundingEndBlock;
    /* 最初に運営側にデポジットするトークンの量 */
    uint256 public constant batFund = 500 * (10**6) * 10**decimals;
    /* 6400BATトークンを1ETHと交換する */
    uint256 public constant tokenExchangeRate = 6400;
    /* 作るBATトークン数の上限 */
    uint256 public constant tokenCreationCap =  1500 * (10**6) * 10**decimals;
    /* 作るBATトークン数の下限 */
    uint256 public constant tokenCreationMin =  675 * (10**6) * 10**decimals;


    /* LogRefundメソッドが実行されたときにログを残す */
    event LogRefund(address indexed _to, uint256 _value);
    /* CreateBATメソッドが実行されたときにログを残す */
    event CreateBAT(address indexed _to, uint256 _value);

    /* BAToken()
    コンストラクタ
    */
    function BAToken(
        address _ethFundDeposit,
        address _batFundDeposit,
        uint256 _fundingStartBlock,
        uint256 _fundingEndBlock)
    {
      /* クラウドセールが終わったかどうか */
      isFinalized = false;
      /* 運営用のETHを貯めておくアドレス */
      ethFundDeposit = _ethFundDeposit;
      /* 運営用のBATを貯めておくアドレス */
      batFundDeposit = _batFundDeposit;
      /* クラウドセールを開始するブロック */
      fundingStartBlock = _fundingStartBlock;
      /* クラウドセールを終了するブロック */
      fundingEndBlock = _fundingEndBlock;
      /* 運営側にデポジットする分だけ、配ったことにする */
      totalSupply = batFund;
      /* 運営側にデポジット */
      balances[batFundDeposit] = batFund;
      /* BATの生成をログ */
      CreateBAT(batFundDeposit, batFund);
    }

    /* createTokens()
    BATトークンを生成する
    */
    function createTokens() payable external {
      /* クラウドセールが終了していれば例外を投げる */
      if (isFinalized) throw;
      /* fundingStratBlockより今のblockが古ければ例外を投げる */
      if (block.number < fundingStartBlock) throw;
      /* fundingEndBlockより今のblockが新しければ例外を投げる */
      if (block.number > fundingEndBlock) throw;
      /* メッセージと共に送られたvalueが0であれば例外を投げる */
      if (msg.value == 0) throw;

      /* tokens = msg.value * tokenExchangeRate */
      uint256 tokens = safeMult(msg.value, tokenExchangeRate);
      /* checkedSupply = totalSupply + tokens */
      uint256 checkedSupply = safeAdd(totalSupply, tokens);

      /* BATトークン作成の上限を超えていれば例外を投げる */
      if (tokenCreationCap < checkedSupply) throw;

      /* 作ったBATトークンの総量を更新 */
      totalSupply = checkedSupply;
      /* メッセージ主にBATトークンを付与 */
      balances[msg.sender] += tokens;
      /* BATトークンの作成をログ */
      CreateBAT(msg.sender, tokens);
    }

    /* finalize()
    クラウドセールを終了する
    */
    function finalize() external {
      /* クラウドセールが終了していれば例外を投げる */
      if (isFinalized) throw;
      /* メッセージの送り主がethFundDepositでなければ例外を投げる */
      if (msg.sender != ethFundDeposit) throw;
      /* 作ったBATトークンの総量がtokenCreationMinを超えてなければ例外を投げる */
      if(totalSupply < tokenCreationMin) throw;
      /* fundingEndBlockより今のblockが古く */
      /* 作ったBATトークンの総量がtokenCreationCapと等しくなければ例外を投げる */
      if(block.number <= fundingEndBlock && totalSupply != tokenCreationCap) throw;
      /* isFinializeにtrueをセット(クラウドセール終了) */
      isFinalized = true;
      /* ethFundDepositにthis.balance(ETH)を送金 */
      if(!ethFundDeposit.send(this.balance)) throw;
    }

    /* refund()
    なにか失敗した場合に集めたETHを全額運営に送金する
    */
    function refund() external {
      /* クラウドセールが終了していれば例外を投げる */
      if(isFinalized) throw;
      /* fundingEndBlockより今のblockが古ければ例外を投げる */
      if (block.number <= fundingEndBlock) throw;
      /* 作ったBATトークンの総量がtokenCreationMinを超えていれば例外を投げる */
      if(totalSupply >= tokenCreationMin) throw;
      /* メッセージの送り主がethFundDepositでなければ例外を投げる */
      if(msg.sender == batFundDeposit) throw;
      /* メッセージの送り主(ethFundDeposit)の所有トークンを代入 */
      uint256 batVal = balances[msg.sender];
      /* トークンを所有してなければ例外を投げる */
      if (batVal == 0) throw;
      /* 所有トークンを0にする */
      balances[msg.sender] = 0;
      /* tokenSupply = totalSupply - batVal */
      totalSupply = safeSubtract(totalSupply, batVal);
      /* batValをETH換算に変換 */
      uint256 ethVal = batVal / tokenExchangeRate;
      /* refundをログ */
      LogRefund(msg.sender, ethVal);
      /* メッセージの送り主にethVal(ETH)送金 */
      if (!msg.sender.send(ethVal)) throw;
    }
}

次はThe DAOのソースコードを読もうかな。

21
13
5

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
21
13