It’s now or never

IT系の技術ブログです。気になったこと、勉強したことを備忘録的にまとめて行きます。

【Ethereum】【Solidity0.4.16】viewとpure修飾子

Solidity 0.4.16からviewpureという関数の修飾子が新たに追加されました。

view 修飾子

pragma solidity ^0.4.16;

contract C {
    function f(uint a, uint b) view returns (uint) {
        return a * (b + 42) + now;
    }
}

内部で状態を変更しないことを宣言します。
状態変更の定義は以下となっており、以下の実装が含まれる関数にview修飾子がついていると警告が発生します。

  • 1 . 状態変数を書き換える
  • 2 . eventwを呼び出す
  • 3 . 他のcontractを作成する
  • 4 . selfdestructを使ってcontractの破棄を行う
  • 5 . ethを送る
  • 6 . 関数内部から view または pure 修飾子のつかない関数を呼び出す
  • 7 . low-level callを使用する
  • 8 . opcodeを含むインラインアセンブリを使用する

pure 修飾子

pragma solidity ^0.4.16;

contract C {
    function f(uint a, uint b) pure returns (uint) {
        return a * (b + 42);
    }
}

状態の読み込み、変更を行わないことを宣言します。
view修飾子で記載した状態の変更条件に加え以下を行うことを制限します。

  • 1 . 状態変数を読み込む
  • 2 . this.balance または <address>.balanceへアクセスする
  • 3 . blocktxmsgのメンバにアクセスする(msg.sigとmsg.dataを除く)
  • 4 . 関数内部からpure修飾子のつかない関数を呼び出す
  • 5 . opcodeを含むインラインアセンブリを使用する

今までは、状態を変更しない関数にはconstant修飾子を使用していましたが、今後はviewpureを使い分けて使用していくのが良さそうです。
まだまだSolidityは言語のバージョンアップが早いのでキャッチアップが大変です。

【Ethereum】コントラクトから外部のコントラクトの関数を実行する

EthereumのContractにおいて、 Contract内部から他のContractの関数を呼び出したいケースがあります。 その時に考えられる方法としては主に以下の2つかと思います。

  • ①Contract内で別のContractをnewする
  • ②既にデプロイされている外部のContractを使う

今回は、②の方法について検証してみました。

①の場合は特に意識をする必要ないですが、②の場合は既にデプロイされているContractを使うため、外部から値を受け取る必要があり、今回はContractのaddressを受取るという方法をとっています。

デプロイ済みの(共通利用される)Contract

pragma solidity ^0.4.11;

contract MyToken {
    uint256 public totalSupply;

    mapping (address => uint256) public balanceOf;

    function MyToken(address owner) {
        totalSupply = 10000;
        balanceOf[owner] = 1000;
    }

    function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
        if (_to == 0x0) revert();
        if (balanceOf[_from] < _value) revert();
        if (balanceOf[_to] + _value < balanceOf[_to]) revert();
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        return true;
    }
}
  • 簡単なTokenを作成しています
  • このTokenは、送信用の関数としてtransferFromを持っています
  • このTokenは既にデプロイされている想定でこのContractのアドレスを使用します
  • 簡単のため、コンストラクタで渡されるアカウントのアドレスに1000Tokenを渡しています

MyTokenを利用するContract

pragma solidity ^0.4.11;

// 呼び出す対象のコンストラクタはimportする必要がある
import './MyToken.sol';

contract TokenCaller {
    MyToken public token;
  
    function TokenCaller(address _token) {
        require(_token != 0x0);
        // コンストラクタでアドレスからMyTokenObjectに変換
        token = MyToken(_token);
    }

    function transfer(address from, address to, uint256 total) returns (bool) {
        // tokenからtransferFromを実行する
        return token.transferFrom(from, to, total);
    }
}
  • Tokenを利用する側のContractです
  • コンストラクタでTokenのアドレスを受取りTokenのオブジェクトを生成します
  • 関数transferは、生成したTokenのtransferを内部で呼び出しTokenの送信処理を行います

動作確認

動作確認は、truffleを使ってtestRPC上で実行しています。

var TokenCaller = artifacts.require('../contracts/TokenCaller.sol');
var MyToken = artifacts.require('../contracts/MyToken.sol');

contract('TokenCaller', function(accounts) {
    it ('test', async function() {
        // MyTokenのデプロイ
        let token = await MyToken.new(accounts[1])
        // Callerのデプロイ
        let caller = await TokenCaller.new(token.address)
        // Tokenの送り先のaccount[2]にtokenがないことを確認
        console.log(await token.balanceOf.call(accounts[2]));
        // account1からaccount2にトークンを100送る
        await caller.transfer(accounts[1], accounts[2], 100)
        // account2のトークンを確認
        console.log(await token.balanceOf.call(accounts[2]));
    });
});

結果

  Contract: TokenCaller
{ [String: '0'] s: 1, e: 0, c: [ 0 ] }
{ [String: '100'] s: 1, e: 2, c: [ 100 ] }
  • account2にトークンが送られていることが確認できました。

参考

https://dappsforbeginners.wordpress.com/tutorials/interactions-between-contracts/

【Ethereum】【Solidity】OpenZeppelinのDayLimitのソースを読んでみる

OpenZeppelinというフレームワークソースコードを引き続き読んでいます。

過去はこちら inon29.hateblo.jp

今回は、DayLimitというContractのソースを読んでみました。 DayLimitコントラクトは、関数に日付の実行制限を付与するためのコントラクトです。

ソース

https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.2.0/contracts/DayLimit.sol

フィールド

uint256 public dailyLimit;
uint256 public spentToday;
uint256 public lastDay;
  • dailyLimit: 一日に動かせる制限値
  • spentToday: 一日に動かした量
  • lastDay: 最後に動かした日(int)

コンストラクタ

function DayLimit(uint256 _limit) {
  // 1日あたりの制限値を設定
  dailyLimit = _limit;
  // 最後に実行された日を今日に初期化
  lastDay = today();
}
  • _limit: 一日に動かせる制限値を初期化できます。

制限値の初期化(_setDailyLimit)

function _setDailyLimit(uint256 _newLimit) internal {
  dailyLimit = _newLimit;
}
  • 一日の制限値の変更。
  • internalのため、このコンストラクタまたは継承コンストラクタからのみ実行可能です。

使用値の初期化(_resetSpentToday)

function _resetSpentToday() internal {
  spentToday = 0;
}
  • 一日に使用した量の初期化

制限チェック(underLimit)

function underLimit(uint256 _value) internal returns (bool) {
  // 日付が変わっていれば、使用値とチェック日付をリセットする
  if (today() > lastDay) {
    spentToday = 0;
    lastDay = today();
  }
  // 制限値を超えていなければ、使用値に加算してtrueを返す
  if (spentToday + _value >= spentToday && spentToday + _value <= dailyLimit) {
    spentToday += _value;
    return true;
  }
  return false;
}
  • 現在の値が、制限を超えているかをチェックします
  • spentToday + _value >= spentTodayは_valueがマイナスかをチェックしている?
    • この処理は、他のcontractでもよく見かけるが、uintでもマイナス値が入ることがあるのか?

今日の日付を返す(today)

function today() private constant returns (uint256) {
  return now / 1 days;
}
  • 現在日付を1日で割り、今日の日付のint値を返しています。

実行制限のmodifier(limitedDaily)

modifier limitedDaily(uint256 _value) {
  require(underLimit(_value));
  _;
}
  • 関数に制限値のチェックを行う実行チェックを付与できます。
  • underLimitでfalseが返されるとエラーになります。

感想

  • Shareableに比べるとかなりシンプルです。
  • 1dayの計算箇所を変更すれば、色々な期間の制限に応用ができそうです。

【Ethereum】【Solidity】OpenZeppelinのShareableのソースを読んでみる

Solidity言語を使って実装された、 基本的なContractをフォーマット化したOpenZeppelinというフレームワークが開発されています。

このフレームワークに含まれるShareableというContractのソースを読んでみたので、メモを記載します。

Shareableコントラクトは、コントラクトの実行権を複数のオーナーで管理するためのコントラクトです。
このContractを継承すると、オーナーの実行制御を付与するためのmodifierを関数に定義することができます。(onlyOwner,onlymanyowners)

ソース

https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.2.0/contracts/ownership/Shareable.sol

コンストラクタ

  function Shareable(address[] _owners, uint256 _required) {
    // contract登録者をownerとして登録する
    // オーナーのindexは後述の処理でビットフラグとして使用される。
    // その為、はじめの格納位置は0ではなく1からになる
    owners[1] = msg.sender;
    // 逆引き用の配列にindex(1)をセット
    ownerIndex[msg.sender] = 1;
    // 初期引数で渡されるオーナーを登録する
    for (uint256 i = 0; i < _owners.length; ++i) {
      owners[2 + i] = _owners[i];
      ownerIndex[_owners[i]] = 2 + i;
    }
    required = _required;
    require(required <= owners.length);
  }
  • コンストラクタでは、オーナーの登録と必要なindexパラメタの初期化を行います。
  • _ownersには、共同管理するオーナーアドレスの配列を指定します。
  • _requiredには、実行に必要なオーナー数(承認数)を指定します。
  • ownersはオーナーのアドレスを管理するフィールド変数です。
    • address[256] owners;
    • 最大255人(初めにindex1をつかっているので255)のオーナーを登録できます。
  • ownerIndexは、アドレスからオーナーのindexを逆引きするためのフィールドです。
    • mapping(address => uint256) ownerIndex;
  • [気になった点]初めに仮引数で渡されるownersを登録していますが、senderを含めていないためowndersにsenderと同じアドレスがいると上手く動かなそうに見えます。
    • _owners配列自体もそもそも重複チェックしていないため、_ownersに重複addressが存在すると逆引きのownerIndexがズレてしまう気がします。

onlymanyowners modifier

  modifier onlymanyowners(bytes32 _operation) {
    if (confirmAndCheck(_operation)) {
      _;
    }
  }
  • このmodifierを使用して、複数の登録オーナーが関数を承認(実行)しないと実行されない関数を実現できます。
  • _operationには実行許可の操作を管理するためのバイトコードが渡されます。
  • confirmAndCheckは内部で実行したオーナーが_operationの操作に対して実行(承認)を行ったかを記録しています。
    • その為、このmodifireを指定した関数は、規定数に達するまでオーナーからの実行を拒否することができます。

confirmAndCheck

  function confirmAndCheck(bytes32 _operation) internal returns (bool) {
    // 関数実行者からindexを逆引き
    uint256 index = ownerIndex[msg.sender];
    // 対象indexがなければ終了
    require(index != 0);

    // 現在、ペンディング中のoperation情報を検索
    var pending = pendings[_operation];
    // そのオペレーションがまだ一度も実行されていないときは値を初期化
    if (pending.yetNeeded == 0) {
      // 必要な実行数を初期化
      pending.yetNeeded = required;
      // 確認済みオーナー数を0リセット
      pending.ownersDone = 0;
      // indexをセット
      pending.index = pendingsIndex.length++;
      pendingsIndex[pending.index] = _operation;
    }
    // オーナーのビットを決定
    uint256 ownerIndexBit = 2**index;
    // オーナーが既に実行していないかのチェック(論理積)
    if (pending.ownersDone & ownerIndexBit == 0) {
      Confirmation(msg.sender, _operation);
      // この実行で承認数を満たす場合
      if (pending.yetNeeded <= 1) {
        // 保留中の操作情報を削除(初期化)して終了
        delete pendingsIndex[pendings[_operation].index];
        delete pendings[_operation];
        return true;
      } else { // 必要数に足りない場合
        // 必要数を減算
        pending.yetNeeded--;
        // 承認フラグにbitを立てる
        pending.ownersDone |= ownerIndexBit;
      }
    }
    return false;
  }
  • このContractのキーとなる関数です。各オーナーが対象の操作に対して承認(実行)を行ったかを管理する関数しています。
  • pendingsは、実行を保留されている操作を管理する配列フィールドです。
    • mapping(bytes32 => PendingState) pendings;
    • PendingStateという構造対を操作のバイト列をキーとして持ちます。
  struct PendingState {
    uint256 yetNeeded; // その操作に必要な残りの実行オーナー数
    uint256 ownersDone; // 現在実行したオーナー数
    uint256 index; // 保留中の操作のindex
  }
  • PendingStateは操作の各状態をプロパティとして持っています。
  • pendingsIndexは、保留中の操作のindexを逆引きするための配列フィールドです。

revoke

  function revoke(bytes32 _operation) external {
    uint256 index = ownerIndex[msg.sender];
    if (index == 0) {
      return;
    }
    // 自分のビットを立てる
    uint256 ownerIndexBit = 2**index;
    var pending = pendings[_operation];
    // 自分が承認(実行)しているか確認
    if (pending.ownersDone & ownerIndexBit > 0) {
      pending.yetNeeded++; // 必要数の加算
      pending.ownersDone -= ownerIndexBit; // ビットを削除
      Revoke(msg.sender, _operation);
    }
  }
  • 確認操作を取り消すための関数です。

onlyOwner modifier

  modifier onlyOwner {
    require(isOwner(msg.sender));
    _;
  }
  • オーナーであれば実行できる権限を付与するためのmodifierです。

isOwner

  function isOwner(address _addr) constant returns (bool) {
    return ownerIndex[_addr] > 0;
  }
  • 自分がオーナーに登録されているかチェックする関数です。

hasConfirmed

  function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
    var pending = pendings[_operation];
    uint256 index = ownerIndex[_owner];
    // オーナーであるかのチェック
    if (index == 0) {
      return false;
    }
    uint256 ownerIndexBit = 2**index;
    // ビット判定
    // pending.ownersDone & ownerIndexBit > 0 でも良さそう?
    return !(pending.ownersDone & ownerIndexBit == 0);
  }
  • 指定のオーナーが指定の操作に対して承認(実行)をチェックする関数です。

clearPending

  function clearPending() internal {
    uint256 length = pendingsIndex.length;
    for (uint256 i = 0; i < length; ++i) {
      if (pendingsIndex[i] != 0) {
        delete pendings[pendingsIndex[i]];
      }
    }
    delete pendingsIndex;
  }
  • 保留中操作の初期化処理。全ての保留中操作をリセットする関数です。
  • internalのためContract内部または、継承するContractからのみ実行を許可します。

感想

  • ビット演算を使ってシンプルな仕組みで承認を管理する仕組みを実現しています。
  • Ethereum上のプログラムは、チェーンサイズが限られていることや、Contractサイズが手数料に影響することもあり、バイトサイズを意識した実装は勉強になります。
  • オーナーの数とビットを連動しているため、現状の実装だと255人が最大のようなので、大人数の承認システムを作りたい場合は別の方法を検討する必要がありそうです。

【Ethereum】Contractをネットワークにデプロイする

Ethereumの勉強を引き続きしています。
Contractをネットワークへデプロイする場合、 今までは、Ethereum WalletRemix経由で行っていたのですが、ソースをコンパイルしていデプロイする方法がよくわからなかったので調べてまとめてみました。

古いバージョンのgethでは、コンソール上でコンパイルができるようなのですが、最新のgethではできないようです。
なので今回は、javascriptAPIであるweb3を使ってSollidityのソースをコンパイルします。

Solidityのインストール

npm install solc

web3のインストール

npm install web3@0.20.0
  • JavaScriptAPI環境であるweb3をインストールします。
  • 現状のnpmの最新バージョンは、ベータ版の1.0.0-beta.13でしたが、想定のAPIが上手く動かなかったりとハマったためStable版の0.20.0をインストールしました。

ノード起動

geth --datadir <ネットワークのrootディレクトリ> --mine --nodiscover --maxpeers 0 --networkid <ネットワークID> --rpc --rpcport 8545 --rpcaddr "0.0.0.0" --rpcapi="db,eth,net,web3,personal,web3" --rpccorsdomain "*" 
  • プライベートネットワークのノードを起動します。
  • datadirはプライベートネットワークを作成したディレクトリです。
  • networkidは任意のIDで大丈夫です。
  • web3はRPC経由でアクセスするため、RPCを許可します。
  • プライベートネットワークの構築方法は以下を参照いただければ幸いです。

inon29.hateblo.jp

Contract

pragma solidity ^0.4.0;

contract TestContract {
    uint data;

    function set(uint x) {
        data = x;
    }

    function get() constant returns (uint) {
        return data;
    }
}
  • 今回は、このテスト用のContractをコンパイルしてノード上にデプロイしてみます。
  • Javascriptを実行するディレクトリ上に上記ファイルを配置しておきます。

Javascript

// 必要なパッケージをインポート
const fs = require("fs");
const solc = require('solc');
const Web3 = require('web3');

// web3の初期化
web3 = new Web3();
// プライベートネットワークと接続
if (!web3.currentProvider) {
    web3.setProvider(new web3.providers.HttpProvider("http://localhost:8545"));
}
// contractソースをファイルから読み込み
// ここは、ソースを文字列として読み込んでいるだけです。
let source = fs.readFileSync('test_contract.sol', 'utf8');
// コンパイル
let compiledContract = solc.compile(source, 1);
// ABIを取得(配列のキーはなぜか頭に:が必要でした)
let abi = compiledContract.contracts[':TestContract'].interface;
// コントラクトのバイトコードの取得(頭に0xを付けないと後述のAPIで弾かれる)
let bytecode = "0x" + compiledContract.contracts[':TestContract'].bytecode;
// デプロイに必要なGasを問い合わせる
let gasEstimate = web3.eth.estimateGas({data: bytecode});
// コントラクトオブジェクトの生成
let TestContract = web3.eth.contract(JSON.parse(abi));
// デプロイアカウントのLockを外す
web3.personal.unlockAccount(web3.eth.accounts[0], "<アカウントのパス>");
// ネットワークにデプロイ
// コンストラクタがある場合は、ハッシュの前の引数にコンストラクタのパラメタを渡すことで初期化可能
TestContract.new({from: web3.eth.accounts[0], data:bytecode, gas:gasEstimate });
node deploy.js

デプロイの確認

INFO [08-02|22:49:38] Submitted contract creation              fullhash=0x7605a5560f0d729f89f5b1e4ea632d84b4ebd038f930d53620c521adc8f90f0d contract=0xd06c3fce760aa1ab550b0cde8fe4792a1e4a3e11
  • 上記のようなログがgethに出力されていればデプロイに成功しています。

デプロイしたコントラクトを使う場合

const Web3 = require('web3');

web3 = new Web3();
// providerとの接続
if (!web3.currentProvider) {
    web3.setProvider(new web3.providers.HttpProvider("http://localhost:8545"));
}
 
let abi = [{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}];
let address = "0xd06c3fce760aa1ab550b0cde8fe4792a1e4a3e11";
let TestContract = web3.eth.contract(abi);
let contract = TestContract.at(address);
let num = contract.get.call();

console.log(num.toPrecision());
  • 一度ネットワークにデプロイされたコントラクトはABIコントラクトのアドレスからアクセスすることが可能です
0
  • 現状は、値のセットがされていないため0が返ります。

参考リンク

https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethcontract

【Ethereum】Ethereumで独自のトークンの作成してみる

Ethereumのトークンとは

Ethereum上でトークンとよばれるコントラクトを作成することで独自の暗号通貨を作成することが可能です。 トークンを使うことで、コイン、ポイントなど様々な仮想的な価値をEthereumの仕組みの上で利用することができます。 トークンはEhereumのネットワーク上のアカウント間で自由にやりとりすることが可能です。

トークンコンストラクト

トークンを定義するためのコンストラクトには、幾つかの標準APIが定義されています。
トークAPIの議論についてはここでされています。
Ethereumのトークンのサンプル実装は、ここにあります。

pragma solidity ^0.4.8;
contract tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData); }

contract MyToken {
    /* Public variables of the token */
    string public standard = 'Token 0.1';
    string public name;
    string public symbol;
    uint8 public decimals;
    uint256 public totalSupply;

    /* This creates an array with all balances */
    mapping (address => uint256) public balanceOf;
    mapping (address => mapping (address => uint256)) public allowance;

    /* This generates a public event on the blockchain that will notify clients */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /* This notifies clients about the amount burnt */
    event Burn(address indexed from, uint256 value);

    /* Initializes contract with initial supply tokens to the creator of the contract */
    function MyToken(
        uint256 initialSupply,
        string tokenName,
        uint8 decimalUnits,
        string tokenSymbol
        ) {
        balanceOf[msg.sender] = initialSupply;              // Give the creator all initial tokens
        totalSupply = initialSupply;                        // Update total supply
        name = tokenName;                                   // Set the name for display purposes
        symbol = tokenSymbol;                               // Set the symbol for display purposes
        decimals = decimalUnits;                            // Amount of decimals for display purposes
    }

    /* Send coins */
    function transfer(address _to, uint256 _value) {
        if (_to == 0x0) revert();                               // Prevent transfer to 0x0 address. Use burn() instead
        if (balanceOf[msg.sender] < _value) revert();           // Check if the sender has enough
        if (balanceOf[_to] + _value < balanceOf[_to]) revert(); // Check for overflows
        balanceOf[msg.sender] -= _value;                     // Subtract from the sender
        balanceOf[_to] += _value;                            // Add the same to the recipient
        Transfer(msg.sender, _to, _value);                   // Notify anyone listening that this transfer took place
    }

    /* Allow another contract to spend some tokens in your behalf */
    function approve(address _spender, uint256 _value)
        returns (bool success) {
        allowance[msg.sender][_spender] = _value;
        return true;
    }

    /* Approve and then communicate the approved contract in a single tx */
    function approveAndCall(address _spender, uint256 _value, bytes _extraData)
        returns (bool success) {
        tokenRecipient spender = tokenRecipient(_spender);
        if (approve(_spender, _value)) {
            spender.receiveApproval(msg.sender, _value, this, _extraData);
            return true;
        }
    }        

    /* A contract attempts to get the coins */
    function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
        if (_to == 0x0) revert();                                // Prevent transfer to 0x0 address. Use burn() instead
        if (balanceOf[_from] < _value) revert();                 // Check if the sender has enough
        if (balanceOf[_to] + _value < balanceOf[_to]) revert();  // Check for overflows
        if (_value > allowance[_from][msg.sender]) revert();     // Check allowance
        balanceOf[_from] -= _value;                           // Subtract from the sender
        balanceOf[_to] += _value;                             // Add the same to the recipient
        allowance[_from][msg.sender] -= _value;
        Transfer(_from, _to, _value);
        return true;
    }

    function burn(uint256 _value) returns (bool success) {
        if (balanceOf[msg.sender] < _value) revert();            // Check if the sender has enough
        balanceOf[msg.sender] -= _value;                      // Subtract from the sender
        totalSupply -= _value;                                // Updates totalSupply
        Burn(msg.sender, _value);
        return true;
    }

    function burnFrom(address _from, uint256 _value) returns (bool success) {
        if (balanceOf[_from] < _value) revert();                // Check if the sender has enough
        if (_value > allowance[_from][msg.sender]) revert();    // Check allowance
        balanceOf[_from] -= _value;                          // Subtract from the sender
        totalSupply -= _value;                               // Updates totalSupply
        Burn(_from, _value);
        return true;
    }
}
  • 上記がサンプル実装のトークンコントラクトのソースです。
  • プログラム処理としてはそれほど難しくないため、何となく書いてあることは理解できます。

各構文の理解もしていきたいのですが、今回はこのトークンコントラクトのサンプルソースを使って、新しいトークンをデプロイしてみます。

実行環境

  • Ubuntu14.04 on docker

Ethereumのプライベートネットワークを作成する

  • トークンは本番ネットワークではなくプライベートネットワークで作成します。
  • プライベートネットワークの作成については以下を参照ください。

inon29.hateblo.jp

プライベートネットワーク(node)の起動

プライベートネットワークを起動してコントラクトを作成する準備をします。
コントラクトを作成するトランザクションでマイニングが必要なため予めマイニングをスタートしておきます。

geth \
--datadir /ethereum/eth_private  \
--mine  \
--nodiscover  \
--maxpeers 0  \
--networkid 13  \
--rpc  \
--rpcport 8545  \
--rpcaddr "0.0.0.0"  \
--rpcapi="db,eth,net,web3,personal,web3" \
--rpccorsdomain "*" 
  • mine: マイニングを許可する。このオプションを指定するとマイニングが始まります。
  • nodiscover: Gethはデフォルトで自動的に(同じネットワークID)のEthereumネットワークのノード(Peer)を探し接続を試みます。プライベート・ネットで未知のノードとの接続を避けるため、このオプションを指定することで自動Peer探索機能を無効にします。
  • maxpeers: ネットワークに接続するノードの最大数(デフォルト25) 0を指定するとネットワークが無効になります。
  • networkid: ネットワークID(デフォルト1) 1を指定するとグローバルのネットワークに接続されます。
    • 2,3,4も予約されているようです。(2=Morden (disused), 3=Ropsten)
  • rpc: HTTP-RPC serverとして動作を許可する
  • rpcport: HTTP-RPCで使用するポート番号
  • rpcaddr: 接続ホスト
  • rpcapi: HTTP-RPCのインタフェースの種類
  • rpccorsdomain クロスオリジンを許可するリスト

後述するEthereum walletJSON-RPC経由で接続するためにRPCの許可をしています。

送信先のテストアカウントを作成

作成したトークンを送信するためのテストアカウントをコンソール上で作成しておきます。

> personal.newAccount('<任意のパスフレーズ>')
"0xe41253c3218ade0650469b87dcb97a3298d3b5d8"
  • コンソールからアカウントを作成します。

Ethereum walletのインストー

  • Ethereum walletとはEthereumの公式のツールです。Ethereum Walletを使うとウォレットの管理をGUIベースで行うことができます。
  • その他にもEthereum walletでは、コントラクトの作成や独自トークンの管理など便利な機能があります。
  • 今回はこのEthereum walletを使って独自のトークンを作成&デプロイします。

f:id:inon29:20170716182236p:plain### インストー

  • ダウンロードページから自分にあった環境のツールをダウンロードします。
    • 僕はDocker環境上でプライベートネットワークを動かしていますが、Ethereum walletはMac上のアプリとしてインストールしました。

Ethereum walletの起動(Mac環境)

% /Applications/Ethereum\ Wallet.app/Contents/MacOS/Ethereum\ Wallet --rpc http://localhost:8545
  • MacアプリのEthereum walletから先程起動したプライベートノードへ接続します
    • 僕は、Geth環境をDocker上に作成しているため、あらかじめDockerで8545ポートをフォワーディングしています。

f:id:inon29:20170716182536p:plain

  • rpcのセキュリティ警告がでますがOKを押下して進みます。
    • (RPCは接続制御ができないため、テスト環境以外では使わない方がいいと思います。)

f:id:inon29:20170716182624p:plain

  • LAUNCH APPLICATIONを押下します

f:id:inon29:20170716182739p:plain

  • ネットワークの接続に成功するとwallet画面が表示されます
  • 先程作成したアカウント(coinbaseと送信用)が2つ表示されていれば正しく接続されています。

カスタムトークンをデプロイする

  • Ethereum wallet上からカスタムのトークンを作成してみます。
  • アプリ上メニューのCONTRACTS > DEPLOY NEW CONTRACT を押下

f:id:inon29:20170716183035p:plain

f:id:inon29:20170716183046p:plain

  • Deploy contract画面を少しスクロールしてSOLIDITY CONTRACT SOURCE CODEの部分にサンプルソースをそのまま添付します

  • しばらくするとコンパイルが完了するので、画面右のSELECT CONTRACT TO DEPLOYからMy Tokenを選択します

f:id:inon29:20170716183239p:plain

  • 作成するトークンの情報を入力します
  • Initial supply: 初期のトークン総量。今回は10000トークン発行します。
  • Token name: トークンの名前です。今回はMyTokenという名前のトークンを発行します。
  • Decimal units: トークンの小数点以下の桁数を入力します。(下2桁であれば2と入力する)。今回は2にします。
    • Initial supplyが10000でDecimal unitsが2の場合、100.00になります。下2桁までいれて合計10000トークンということです。
  • Token symbol: トークンの単位です。今回はmtとしました。(1トークン=1mt)

f:id:inon29:20170716183410p:plain

この辺、ちゃんと理解できていないのですが、SELECT FEEは1トランザクションを処理する時の手数料で、 大きな値を設定するほど、トランザクション処理の時間が短くなるということっぽいです。 今回は、そのまま指定の値で作成します。

全て入力を終えたらDeployボタンを押下します。

f:id:inon29:20170716183536p:plain

  • 作成するContractの情報が表示されるので、作成アカウントのパスワードを入力してSEND TRANSACTIONを押下します。

f:id:inon29:20170716183654p:plain

f:id:inon29:20170716183749p:plain

  • 承認が終わったら、Contractを作成したアカウントを押下してアカウントの詳細画面へ遷移すると自分が作成したトークン情報が確認できます。

トークンを送信してみる。

f:id:inon29:20170716184207p:plain

  • アカウント画面のMyTokenの右にあるSendボタンを押下してみます。

f:id:inon29:20170716184318p:plain

  • トークンの送信画面になるので、相手先のアカウントのアドレスと送信したいトークン数を入力してトークンを送ってみます。
  • 相手のアカウントのアドレスは予めコピーしておきます。

f:id:inon29:20170716184351p:plain

f:id:inon29:20170716184433p:plain

  • 送信相手のアカウント情報を見てみると30mtが送られていることが確認できます。

参考リンク

【Ethereum】Solidityを使ってSmartContractの作成してみる

引き続き、Ethereumを勉強中です。
前回はEthereumのプライベートネットワークを構築する方法について調べました。

inon29.hateblo.jp

今回は、Ethereumのコアな機能であるSmartContractについて実際に動かしてみたいと思います。

実行環境

  • Ubuntu14.04 on docker

スマートコントラクトとは

スマートコントラクトは、Ethereumを使う上で重要な機能の一つです。
スマートコントラクトとはその名のとおり契約を行う機能で、簡単にいうとブロックチェーンを利用して自動で契約の実行を行うことができるというものです。
例えば、初めに契約の一定の条件と契約時の実行内容を定義しておくと、その条件を満たした時に自動で契約内容が処理される。 といったことが実現できます。
イメージとしては、ブロックチェーン上で行われるやり取り(トランザクション)にブログラムソースが付与されていて、任意のタイミングで実行されるという感じです。
Ethereumではこのスマートコントラクトを柔軟に低コストで実現する機能を提供しています。

コントラクトコード

Ethereumには2つのタイプのアカウントが存在します。

一つはEOA(Externally Owned Account)です。 これは我々ユーザー自身が持つアカウントです。
そしてもう一つがContractです。このContractを使ってスマートコントラクトを実現しています。
Contractは、内部に実行コードである「コントラクト・コード」を持っています。
これはプログラム言語のメソッドのようなイメージです。

Solidity

コントラクトコードは、ネットワーク上で「EVM Code(Ethereum Virtual Machine Code)」と呼ばれる、バイトコードの形式で記述されています。

EVN Codeは低水準言語のため人間には読みにくいコードです。
そのためEVN Codeへのコンパイル言語として「Solidity」というJavascriptライクな言語が用意されています。
Solidityはsolcというコンパイラを使用して、EVMへのコンパイルを行います。

Remix(browser-solidity)

  • Solidityをビルドする環境(IDE)としてRemixというツールが提供されています。
  • 単独でsolcをインストールしてコンパイルすることも可能ですが、今回はRemixを使って実装します。
  • geth上でsolcを連携してgethでコンパイルする方法もあったようですが、最新のバージョン(1.6.6)では使えなくなっているようです。

Remix(browser-solidity)をインストー

git clone https://github.com/ethereum/browser-solidity
cd browser-solidity
npm install

アプリケーションの起動

npm start
  • 起動して127.0.0.1:8080にブラウザ上でアクセスするとRemixの画面が確認できます。

f:id:inon29:20170712152955p:plain

  • 上記のように表示されていれば正しく動いています。

ローカルファイルとの同期

  • Remixではローカルファイル(ディレクトリ)と同期を取ることが可能です。

    • (デフォルトの画面上でソースを追加することもできるのですが、そこで作成したファイルがどこのディレクトリに作成されるがよくわかりませんでした。。)
  • ファイルの同期にはremixdというツールを使います。

インストー
npm install -g remixd
同期ディレクトリの指定
remixd -S <同期を取りたいディレクトリ(絶対パス)>
  • remixdでは65520ポートを使用しているため、127.0.0.1:65520へアクセスできるようにしておきます。
ディレクトリ同期

f:id:inon29:20170712153246p:plain

remixのブラウザ画面に戻って左上のメニューから鎖のようなアイコンをクリックしConnectボタンを押下します。

f:id:inon29:20170712153334p:plain

左のファイルエクスプローラに指定したディレクトリとファイルが表示されていれば正しく同期できています。

Contractの登録

contractを動かす環境が整ったので、実際にサンプルソースを動かしてみます。 まずはcontractのサンプルソースをsolidityで記述し、プライベートネットワーク上に登録してみます。

pragma solidity ^0.4.0;
contract sample {
    int num;
    function set_num(int n){
        num = n;
    }
    function get_num() returns(int) {
        return num;
    }
}
  • 上記が動かすサンプルソースです。
  • pragma solidity ^0.4.0;はSolidityのバージョン記述です。
    • 上記ではバージョン0.4.0のコンパイラ以外は動作しないという意味です。
  • contractの後ろに続くのはコントラクト名です。他の言語でいうクラス名みたいなものです。

細かい文法は割愛しますが、これはnumという変数に値をセットするメソッドset_numと値を取得するメソッドget_numという2つのメソッドを定義したcontractになります。
上記のソースをRemix上のファイルに記述します。

ソースが書けたらGethと連携してプライベートネットワークにこのContractを登録します。 まずは、プライベートネットワークへアクセスします。

geth
\ --datadir /ethereum/eth_private  
\--mine  
\--nodiscover  
\--maxpeers 0  
\--networkid 13
\--rpc
\--rpcport 8545
\--rpcaddr "0.0.0.0"
\--rpccorsdomain "*"
\--unlock 0xd8a4f5db00a5f1648ce8731b15ac3a1f72ce106e 
\console

細かいオプションはここでは割愛します。
プライベートネットワークの作成方法については、こちらの記事を参照いただけると幸いです。

inon29.hateblo.jp

  • rpc: JSON-RPCの許可
  • rpcport: JSON-RPCのポート
  • rpcaddr: JSON-RPCの接続ホスト
  • unlock: アカウントへの接続にはアカウントのロックを解除する必要があります。ここでは予めContractを登録するアカウントのロックを解除するためにアドレスを指定しています。

Remixは、nodeとJSON-RPC経由でアクセスするためノードの起動オプションとしてJSON-RPCを許可する必要があります。
環境に合わせて適宜ポートやアドレスを設定します。

f:id:inon29:20170712153925p:plain

Gethの起動が完了したら、Remix上から作成したコントラクトの登録処理を行います。 Remixの右画面 > Contract > EnviromentWeb3 Providerに変更してください。

f:id:inon29:20170712153945p:plain f:id:inon29:20170712153949p:plain

上記のように接続ノードの情報を求められるのでGeth起動時の情報に合わせて接続します。
エラーが表示されなければ正しく接続できていると思います。
(右画面のAccountに接続ノードのアカウントが反映されていれば、正しく接続できていると思います。)

f:id:inon29:20170712154905p:plain

次に右画面の下部にあるCreateというボタンを押下してContractを登録します。

f:id:inon29:20170712154322p:plain

「Waiting for transaction to be mined…」と表示されています。

INFO [07-12|04:11:21] Submitted contract creation              fullhash=0x972db68b4a28be14baebfd17b466817a4bd2e77f0e0e3dbd5c8c87a324bc6857 contract=0xda1dd064dd2c5cb2aa7f3b4603ee973021edb329

Gethに戻ってみると、上記のようにcontractがSubmitされたログが表示されています。

> eth.pendingTransactions
[{
    blockHash: null,
    blockNumber: null,
    from: "0xd8a4f5db00a5f1648ce8731b15ac3a1f72ce106e",
    gas: 107586,
    gasPrice: 18000000000,
    hash: "0x972db68b4a28be14baebfd17b466817a4bd2e77f0e0e3dbd5c8c87a324bc6857",
    input: "0x6060604052341561000f57600080fd5b5b60ce8061001e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633e27a8e8146047578063545a48d014606d575b600080fd5b3415605157600080fd5b6057608d565b6040518082815260200191505060405180910390f35b3415607757600080fd5b608b60048080359060200190919050506097565b005b6000805490505b90565b806000819055505b505600a165627a7a723058204eddaa62d484bcd6eb395ac4fdccd0dcf6c06e86a755c47db93b7cab60d930500029",
    nonce: 8,
    r: "0x76e10da1414ee85ea215b78d9b3e97e5021aeb4d47617c05395cc10195042d8c",
    s: "0x5b3a86db12a335952bb99ee17ff6bd0939204cb43ad56fec5b994c9b830c313a",
    to: null,
    transactionIndex: 0,
    v: "0x3d",
    value: 0
}]

今の状態は、マイニングがされていないためノードに登録されたContractがブロックには登録されていない状態です。
(上記のようにpendingTransactionsコマンドで登録されていないトランザクション情報が確認できます。)
これをGeth上でマイニングしてブロックに登録します。

> miner.start()
INFO [07-12|04:16:43] Updated mining threads                   threads=0
INFO [07-12|04:16:43] Transaction pool price threshold updated price=18000000000
INFO [07-12|04:16:43] Starting mining operation
null
> INFO [07-12|04:16:43] Commit new mining work                   number=2470 txs=1 uncles=0 elapsed=600.469µs
INFO [07-12|04:16:53] Successfully sealed new block            number=2470 hash=ddb1b6…f5f1ef
INFO [07-12|04:16:53] 🔗 block reached canonical chain          number=2465 hash=2ae180…48ff9c
INFO [07-12|04:16:53] 🔨 mined potential block                  number=2470 hash=ddb1b6…f5f1ef
INFO [07-12|04:16:53] Commit new mining work                   number=2471 txs=0 uncles=0 elapsed=586.566µs

マイニングをしばらくして、再度eth.pendingTransactionsを実行するとペンディングトランザクションがなくなっていることがわかります。

f:id:inon29:20170712154453p:plain

Remixに戻ってみると止まっていた処理が完了していることがわかります。 これでContractをアカウントに登録することができました。

Contractの実行

次は登録したContractを実際に動かしてみます。

Contractに作成ユーザー以外がアクセスするためには、以下の2種類の情報を他のユーザーに伝える必要があります。

  • Contractのアドレス
  • ContractのABI (Application Binary Interface)
    • ※ABIとはパラメタや戻り値の型など関数の定義が記述された情報です

コントラクトのオブジェクトは、Geth上で以下のように生成することができます。

eth.contract(<ABI_DEF>).at(<ADDRESS>);

それでは、この2つの情報をRemixから取得して、ContractObjectを作成してみます。

f:id:inon29:20170712154549p:plain

Remix上で上記ボタンを押下するとAddressがクリップボードにコピーされます。

f:id:inon29:20170712154633p:plain

  • Contract details (bytecode, interface etc)のリンクをクリックするとContractの詳細情報を確認できます。
  • ここにあるInterfaceという項目がABIになります。

この2つを使ってcontractのオブジェクトを作成してみます。

> var address = "0xed9d193ecdd86033dbfd74088f3cbfa0f3bb5f55"
undefined
> var abi = [{"constant":false,"inputs":[],"name":"get_num","outputs":[{"name":"","type":"int256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"n","type":"int256"}],"name":"set_num","outputs":[],"payable":false,"type":"function"}]
undefined
> var contract = eth.contract(abi).at(address);
undefined
> contract
{
  abi: [{
      constant: false,
      inputs: [],
      name: "get_num",
      outputs: [{...}],
      payable: false,
      type: "function"
  }, {
      constant: false,
      inputs: [{...}],
      name: "set_num",
      outputs: [],
      payable: false,
      type: "function"
  }],
  address: "0xed9d193ecdd86033dbfd74088f3cbfa0f3bb5f55",
  transactionHash: null,
  allEvents: function(),
  get_num: function(),
  set_num: function()
}
  • address情報とabi情報を保持したオブジェクトが生成されています。
  • 定義したget_numset_numについてもfunctionとして定義されています。

このcontractの関数を実行するには以下のようにします。

> contract.get_num.call()
> 0
> contract.set_num.call()
> []

Contractへ正しくアクセスできていることが確認できます。

contract.set_num(10,{from:eth.accounts[0]})
or
contract.set_num.sendTransaction(10,{from:eth.accounts[0]})
> contract.set_num(11,{from:eth.accounts[0]})
INFO [07-12|05:56:30] Submitted transaction                    fullhash=0x61086bb8f21b6a7bf3735b7f560996680a6021e4bcf9593799f05515e9731667 recipient=0xed9d193ecdd86033dbfd74088f3cbfa0f3bb5f55
"0x61086bb8f21b6a7bf3735b7f560996680a6021e4bcf9593799f05515e9731667"

上記のように新しいトランザクションが作成されます。
この状態では先程と同様ブロックに登録されていないため、値が反映されていません。

> contract.get_num.call()
0

> eth.pendingTransactions
[{
    blockHash: null,
    blockNumber: null,
    from: "0xd8a4f5db00a5f1648ce8731b15ac3a1f72ce106e",
    gas: 90000,
    gasPrice: 18000000000,
    hash: "0x61086bb8f21b6a7bf3735b7f560996680a6021e4bcf9593799f05515e9731667",
    input: "0x545a48d0000000000000000000000000000000000000000000000000000000000000000b",
    nonce: 10,
    r: "0x256450e05ed9a61147ba3a9b8ba6b63ba03fbec73abdb93ffe4a015a17bacd1",
    s: "0x5ef0655ae3c76673cd608d0a71ef8b1a0a6ac9947fb27492782ef38b262dcabc",
    to: "0xed9d193ecdd86033dbfd74088f3cbfa0f3bb5f55",
    transactionIndex: 0,
    v: "0x3d",
    value: 0
}]

先程と同様にマイニングを行いトランザクションをブロックに登録します。

> miner.start()
・・・
> contract.get_num.call()
11

トランザクションがブロックに登録され、メソッドが実行されると値が正しく反映されます。

> eth.getTransaction("0x61086bb8f21b6a7bf3735b7f560996680a6021e4bcf9593799f05515e9731667")
{
  blockHash: "0xd4e2f46e803deb100c3b852c07919ab2533ac1194f1ae3b0cef71f0b9be85a4e",
  blockNumber: 2476,
  from: "0xd8a4f5db00a5f1648ce8731b15ac3a1f72ce106e",
  gas: 90000,
  gasPrice: 18000000000,
  hash: "0x61086bb8f21b6a7bf3735b7f560996680a6021e4bcf9593799f05515e9731667",
  input: "0x545a48d0000000000000000000000000000000000000000000000000000000000000000b",
  nonce: 10,
  r: "0x256450e05ed9a61147ba3a9b8ba6b63ba03fbec73abdb93ffe4a015a17bacd1",
  s: "0x5ef0655ae3c76673cd608d0a71ef8b1a0a6ac9947fb27492782ef38b262dcabc",
  to: "0xed9d193ecdd86033dbfd74088f3cbfa0f3bb5f55",
  transactionIndex: 0,
  v: "0x3d",
  value: 0
}
  • eth.getTransactionトランザクション情報を参照するとどのブロックに登録されたかも確認することができます。
    • eth.getTransactionの引数はcontractのAddressではなくHashなので注意が必要です。

これでSolidityという言語でContractを動かすことができるようになりました。
Contractは基本的にプログラムの実行なのでエンジニア的には理解し易い気がします。
どのようにトランザクションが処理されるかなど詳しく知りたいことはまだありますが少しずつ覚えたいと思います。

参考リンク

Accessing a shared folder in Remix IDE using Remixd — Remix 1 documentation

Installing Solidity — Solidity 0.4.14 documentation

wiki/[Japanese]-Solidity-Tutorial.md at master · ethereum/wiki · GitHub