LoginSignup
21
15

More than 5 years have passed since last update.

Ethereum入門(3) - スマートコントラクト Truffleの使い方

Posted at

Truffleは、イーサリアムアプリケーションを開発するためのフレームワークで、nodejsのラッパーでもあります。

Ethereum入門 2では、nodejsコンソールを使用して、スマートコントラクトを直接記述してきましたが、Truffle では、アプリケーションを開発するためのフレームワークが提供されています。

TRUFFLEフレームワーク ドキュメントから、詳細を見ることができます。

では、Truffle 上で開発するためのディレクトリから作成していきます。Ethereum入門 2で行ったHelloworldスマートコントラクトと同じアプリケーションをTruffle上で展開していきます。

スマートコントラクトアプリケーションの準備

$ mkdir -p /Users/test/applications/blockchain/helloworldTruffle
$ cd helloworldTruffle

truffle init でプロジェクトを初期化します。

$ truffle init

treeで見てみると、ディレクトリとファイルが生成されているのが確認できます。

$ tree
.
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── test
├── truffle-config.js
└── truffle.js

contractsフォルダには、全てのsolidityで記述されたコントラクトが格納されます。
migrations フォルダには、コントラクトをデプロイするための処理を記述したjavascriptが格納されます。
test フォルダには、開発中のノードのテスト処理を記述します。
truffle-config.js と truffle.js は、コンフィギュレーションファイルです。
アプリケーションの構成に必要なコードを実行できます。ここではサンプルに則ってプロジェクト構成を表すオブジェクトをエクスポートします。

truffle.jsを以下のように記述してください。

truffle.js
module.exports = {
  networks: {
    development: {
      host: "localhost",
      port: 8545,
      network_id: "*" // Match any network id
    }
  }
};

contractsフォルダにhelloworld.sol を作成していきます。

helloworld.sol
pragma solidity ^0.4.18;

contract helloworld {
  string message;
  function helloworld() public {
    message = "Hello World!";
  }

  function setHelloworld(string _message) public {
    message = _message;
  }

  function getHelloworld() constant returns (string) {
    return message;
  }
}

次にデプロイ用のjavascriptをmigrationディレクトリ配下に記述していきます。ちなみに、ファイルに付番されていますが、この順番に実行されていきます。

2_deploy_contracts.js
var HelloWorld = artifacts.require("./helloworld.sol")

module.exports = function(deployer) {
    deployer.deploy(HelloWorld);
};

テスト環境の立上げ

では、実際にテスト用のノードで実行していきます。まず、testrpcを立ち上げます。

$ export PATH=$PATH:/usr/local/Cellar/node/8.9.1/bin/
$ testrpc

コントラクトのマイグレーション

ここで、マイグレーションスクリプトを実行していきます。

$ truffle migrate
Compiling ./contracts/Migrations.sol...
Compiling ./contracts/helloworld.sol...

Compilation warnings encountered:

/Users/takeo/Applications/blockchain/helloworldTruffle/contracts/helloworld.sol:13:3: Warning: No visibility specified. Defaulting to "public".
  function getHelloworld() constant returns (string) {
  ^
Spanning multiple lines.

Writing artifacts to ./build/contracts

Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... undefined
Error encountered, bailing. Network state unknown. Review successful transactions manually.
Error: Error: Exceeds block gas limit
    at StateManager.queueTransaction (/usr/local/Cellar/node/8.9.1/lib/node_modules/ethereumjs-testrpc/build/cli.node.js:84915:21)
    at GethApiDouble.eth_sendTransaction (/usr/local/Cellar/node/8.9.1/lib/node_modules/ethereumjs-testrpc/build/cli.node.js:84423:14)
:
:

にやらエラーが出てきました。
1_initial_migration.js を実行している際に、起こっているようです。

Error encountered, bailing. Network state unknown. Review successful transactions manually.
Error: Error: Exceeds block gas limit

gasの上限に関わるエラーのようです。

Gasについて調べる

すこし曖昧にしてきたので、Gasについて調べてみました。

ここに記載されているgasの説明が分かりやすかったので、リンクを張っておきます。
https://ethereum.stackexchange.com/questions/3/what-is-meant-by-the-term-gas

"Gas" is the name for a special unit used in Ethereum. It measures how much "work" an action or set of actions takes to perform

アクションを起こすために必要な"work"と記載されています。イーサリアムは、KECCAK-256(暗号学的ハッシュ関数)を使用していますが、これを実行するために、30Gas使用しているようです。トランザクションやコントラクトが実施される度に、イーサリアム上では、Gasが対価として支払われるということのようです。

Gasが重要な理由としては、トランザクションやコントラクトにおいて、適切な対価が支払われていることを保証することにあります。

注意点としては、Gasは保有できるものではなく、イーサリアム上でどれだけのworkがなされたかによってカウントされ、実際に支払うのは、通貨であるEtherとなります。通貨etherではなくgasとして定義する理由は、価値の急激な変動を伴う通貨と、コンピューターの処理上のコストであるGasを分けることにあります。

gas は、マイナーたちへの賃金のようなものです。もし、小さい額を設定しすぎると、マイナーは、それを無視し、トランザクションが実施されない可能性があります。

gas costとgas priceを区別して考えます。

The gas cost is the amount of work that goes into something, like the number of hours of labour, whereas the gas price is like the hourly wage you pay for the work to be done. The combination of the two determines your total transaction fee.

gas costは、作業の量であり、一方、gas priceは、作業に支払われる時給のようなものです。このコンビネーションにより、支払うべきトランザクションフィーが決まります。

If your gas price is fine but the gas cost of your transaction runs "over budget" the transaction fails but still goes into the blockchain, and you don't get the money back for the work that the labourers did.

もしアプリケーションにおけるgas price(作業に支払われる時給)が良いとしても、アプリケーションのトランザクションが、gas cost(作業量)を超過するなら、全体のトランザクションは失敗に終わります。ただし、ブロックチェーンには記載され、マイナーが行った作業に対するお金は支払われ、実行したアカウントには、返金がなされません。作業量が十分にあり、トランザクションが成功すれば、作業に設定したGasの残りは、実行者に返されます。

アプリケーションを設計する際には、労働者であるマイナーと、消費者であるユーザーの両方を考えたコードを実装する必要がありそうですね。

また、gasの単価は、マイナーによって決められます。労働者の金額は、労働を提供するマイナーが決められるのですね。
https://ethgasstation.info/
に現在のgasの単価を見ることができます。
もし作成したアプリケーションが、コントラクトが配置したり、トランザクションを実施した際は、その実施したアプリケーションを実行したアカウントの残高から、支払われます。

構成ファイルを修正して、再度、マイグレーション

ということで、Gasの記載が抜けていたようなので、
truffle.js に次のパラメータを加えてみてください。

truffle.js
module.exports = {
  networks: {
    development: {
      host: "localhost",
      port: 8545,
      network_id: "*", // Match any network id
      gas: 4600000
    }
  }
};

今度は成功しました。

$ truffle migrate
Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x73151f02e403fc97d10d4778335035e7901e72270753ca7e5575b925386d204e
  Migrations: 0x6bd04448a47c8f603cdfcfe1d233bb266d656066
Saving successful migration to network...
  ... 0x58fef776869eaa30bf9d44483e0649199d706785989ce777183c7bbbfd3a40e8
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying helloworld...
  ... 0xd59f0a4c159aa2a2058bd7d8dc4b759d1d6474d3f4b85606e3b4a3d00ff73e10
  helloworld: 0x2db4e48b49ba817feebd36713e3e6f9b987a4f5f
Saving successful migration to network...
  ... 0x424d5a226906dceb640b18d16e717c262f9d64c0a89eacab690a06cf007a833c
Saving artifacts...

これによって、1_initial_migration.js と2_deploy_contracts.js で、2つのコントラクトがデプロイされました。1つは、triffuleによってつかわれるマイグレーション、もうひとつは、スマートコントラクトであるHelloworldです。

testrpcを別のコンソールで見てみると下記のようなログが出力されています。全部で4つのトランザクションがされています。最初のトランザクションで、マイグレーションコントラクトのデプロイしています。2つめで、マイグレーションの状態を更新しています。3つめは、helloworldコントラクトのデプロイで、4つ目は、helloworldコントラクトの状態を更新しています。

eth_sendTransaction

  Transaction: 0x73151f02e403fc97d10d4778335035e7901e72270753ca7e5575b925386d204e
  Contract created: 0x6bd04448a47c8f603cdfcfe1d233bb266d656066
  Gas usage: 269607
  Block Number: 1
  Block Time: Sun Nov 12 2017 11:54:30 GMT+0900 (JST)

eth_newBlockFilter
eth_getFilterChanges
eth_getTransactionReceipt
eth_getCode
eth_uninstallFilter
eth_sendTransaction

  Transaction: 0x58fef776869eaa30bf9d44483e0649199d706785989ce777183c7bbbfd3a40e8
  Gas usage: 41981
  Block Number: 2
  Block Time: Sun Nov 12 2017 11:54:30 GMT+0900 (JST)

eth_getTransactionReceipt
eth_accounts
net_version
net_version
eth_sendTransaction

  Transaction: 0xd59f0a4c159aa2a2058bd7d8dc4b759d1d6474d3f4b85606e3b4a3d00ff73e10
  Contract created: 0x2db4e48b49ba817feebd36713e3e6f9b987a4f5f
  Gas usage: 284220
  Block Number: 3
  Block Time: Sun Nov 12 2017 11:54:30 GMT+0900 (JST)

eth_newBlockFilter
eth_getFilterChanges
eth_getTransactionReceipt
eth_getCode
eth_uninstallFilter
eth_sendTransaction

  Transaction: 0x424d5a226906dceb640b18d16e717c262f9d64c0a89eacab690a06cf007a833c
  Gas usage: 26981
  Block Number: 4
  Block Time: Sun Nov 12 2017 11:54:30 GMT+0900 (JST)

eth_getTransactionReceipt

スマートコントラクトの再配置

もしスマートコントラクトを更新して、このマイグレーションを再配置したい場合は、

$ truffle migrate --reset

とすることで、最初に配置したものは破棄して、新しいものが再配置されます。

truffle コンソール

truffle コンソールを使って、HelloWorldスマートコントラクトを操作することも可能です。truffle console コマンドを使用して、コンソールに移動します。

$ truffle console
truffle(development)> 
● helloworldスマートコントラクトのアドレスを表示。
>truffle(development)> helloworld.address

● web3オブジェクトも扱えます。
>truffle(development)> web3.eth.accounts

次のコマンドは、helloworldスマートコントラクトがデプロイされていれば、そのインスタンを取得するというものです。実施した後、appオブジェクトで、デプロイしたスマートコントラクトのインスタンスを見ることができます。

> truffle(development)> helloworld.deployed().then(function(instance){app = instance;})
undefined
truffle(development)> app
:
:

一旦デプロイしたコントラクトは、helloworldTruffle/build/contracts にファイルが生成されていることが確認できます。appで出力したjsonと helloworld.json が一致することが確認できると思います。

この取得したappオブジェクトを操作できます。

truffle(development)> app.getHelloworld()
'Hello World!'

ここで、このコマンドを実行してもトランザクションは発生しません。get と set の違いを見ればわかるように、get は、function getHelloworld() constant にあるようにconstantで定義されていて、変数を変更することが出来ません。ブロクチェーン上で状態を修正するのにコストが発生しますが、getは状態を参照しているだけなので、トランザクションが発生しません。

setHelloworld()を実施することで、トランザクションを発生させてみます。

> truffle(development)> app.setHelloworld("It is good world!", {from: web3.eth.accounts[0]})
{ tx: '0x53a417b64471732508799fa860ddb7a92ef6ca4057553e7cc3a2adaa9040f242',
  receipt: 
   { transactionHash: '0x53a417b64471732508799fa860ddb7a92ef6ca4057553e7cc3a2adaa9040f242',
     transactionIndex: 0,
     blockHash: '0xf34af690165c26dd7087983f47364d24d389f64e98a58b4c54928e876400ebe1',
     blockNumber: 5,
     gasUsed: 33800,
     cumulativeGasUsed: 33800,
     contractAddress: null,
     logs: [],
     status: 1 },
  logs: [] }

truffle(development)> app.setHelloworld("It is good world!", {from: web3.eth.accounts[0]})
からもわかるように、変更のトランザクションフィーは、accounts[0]からマイナーに支払われます。helloworld.solには、支払いの記述は特にしていませんが、コントラクトを変えるような処理をするとブロックチェーンの仕組み上で、トランザクションが発生するようになっています。まだどのような用途でブロックチェーンが実装され、社会に実装されていくのかぼんやりとしか見えませんが、とても可能性を感じる仕組みです。

トランザクションが発生して、メッセージが変更されたことが確認できます。もう一度、getHelloworld()関数を実行することで、変数が変更されていることがわかります。

> truffle(development)> app.getHelloworld()
'It is good world!'
21
15
0

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
15