1. 程式人生 > >帶你玩轉以太坊智慧合約的"Hello World"

帶你玩轉以太坊智慧合約的"Hello World"

1.學習目標

  1. 瞭解智慧合約
  2. 簡單環境搭建
  3. 能夠利用solidity編寫Hello World合約
  4. 合約部署
  5. 和合約互動

2.使用solidity語言撰寫智慧合約

Ethereum上的智慧合約需要使用solidity語言來撰寫。solidity是一種類似Javsscript的語言,而且圍繞著solidity的各種開發工具鏈,都是使用屬於Javascript生態系的npm來提供的。但solidity在語法上比較像Java或C#,因為和Javascript不同,solidity與Java或C#同屬於強型別語言,、在定義函式時同樣需要指定回傳的型別、同樣也需要先編譯才能執行。這些特性都是Javascript所不具備的。

3.開發前的準備

我們將使用當前最活躍的智慧合約開發框架truffle為基礎來開發。ENS(Ethereum Name Service)也是採用truffle框架。其他選擇還有embark等。

開發過程中,我們將使用testrpc工具在電腦上模擬智慧合約所需的以太坊記憶體塊鏈測試環境。

testrpc中也包含了Javascript版本的Ethereum虛擬機器(Ethereum Virtual Machine),因此可以完整地執行智慧合約。

程式碼編輯器使用Atom,搭配solidity外掛來開發。Atom是Github為開發者設計的基於Chromium的編輯器,支援NodeJS寫的外掛,內建Git,使用MIT協議開源釋出。solidity外掛除了支援語法高亮之外,也會透過Solium檢查並提示基本的語法錯誤,相當方便。其他編輯器應該也有類似的外掛可選擇。

4.Ubuntu環境下Atom編輯器安裝及使用

sudo add-apt-repository ppa:webupd8team/atom
sudo apt-get update
sudo apt-get install atom

安裝外掛linter-solidity、autocomplete-solidity、linter-solium以方便solidity程式碼編寫。

5.安裝所需工具

首先安裝Node.js

安裝nvm

cd ~
mkdir .nvm
cd .nvm
git clone https://github.com/creationix/nvm

等待下載完畢後source ~/.nvm/nvm/nvm.sh

新增到~/.profile之類的檔案中

vim ~/.profile

並在檔案末尾新增source ~/.nvm/nvm/nvm.sh

source ~/.profile
nvm --version

檢查安裝是否正確

安裝node

nvm install node
node --version

安裝npm

git clone --recursive git://github.com/isaacs/npm.git
cd npm/bin
node npm-cli.js install npm -g
npm --version

安裝ethereumjs-testrpc和truffle

npm install -g ethereumjs-testrpc truffle
sily@lyg-sily:~$ npm install -g ethereumjs-testrpc truffle

/home/sily/.nvm/nvm/versions/node/v9.3.0/bin/testrpc -> /home/sily/.nvm/nvm/versions/node/v9.3.0/lib/node_modules/ethereumjs-testrpc/build/cli.node.js
/home/sily/.nvm/nvm/versions/node/v9.3.0/bin/truffle -> /home/sily/.nvm/nvm/versions/node/v9.3.0/lib/node_modules/truffle/build/cli.bundled.js
+ ethereumjs-testrpc@6.0.3
+ truffle@4.0.4
added 343 packages in 115.81s

啟動testrpc

安裝好後隨時可以使用testrpc命令來啟動以太坊測試環境。

sily@lyg-sily:~$ testrpc
EthereumJS TestRPC v6.0.3 (ganache-core: 2.0.2)

Available Accounts
==================
(0) 0x7e1897cec8e1a098384d17e85a9cd348ccb92b4a
(1) 0x6c96c6cb16442ab83aea0eb1bc3a183e28a922fe
(2) 0x163f0e06082c5cd14ff56658b186a30b0f6e7d9f
(3) 0x0e9558b2cb935ab26484c3d6783fbf2fe70041c8
(4) 0xa2233f08bbb7e3a826cc4c2cede22caf3a6922e6
(5) 0x3b1317510218f98946e0fb72b1a0685ac00bcbc4
(6) 0xca33f56a03e5ae86e25655f4dd7de88be12ec184
(7) 0xfb56aed85dd00908f1e0729ed80d06f456898588
(8) 0x6cfe240b49a28986057016ef2ecd9d6711f6b632
(9) 0x0e866473d6776edc39935dd5d00425b6ab2a423a

Private Keys
==================
(0) 3ae87bfaeabf103ce723b78525ab1278b2c7652b93b99a0131321adaa7000537
(1) 2f23eaef51f941b7af171a56acbd7524554ad529236b6abb6920935218f5df58
(2) 9b5b5d42786e74364e80b45ecf1a2b7201db7eafe12684f71006fbe3368e6dfe
(3) 33181b42772371fad50f29b8377f7a3de72000397f9b021677e329bede7bfcc7
(4) 3f5f366d89ab9263b92f35368840d24d562af043db8a5ad773dc8cc8f122662d
(5) 29933e3e79129da23da3cc0f5cecb03c583c3ad6c4c1ad8a39d68386fe92d91b
(6) 7e9bd47a6120bb9a240a96ce80866b98646f07cb98197bdffec389805575144b
(7) 0c7144621371a04e4ee733d06482b0a23ed775af17172e75e26f2b8915814e35
(8) 60ab702bdad5a348645f65fb0678cafec891c534964982d97b96f4a57abd78ef
(9) 8ff45f1ccb23bec8a317a6e218b3b50290646317b5c809c797e1522bcef9d031

HD Wallet
==================
Mnemonic:      vapor high liberty evoke pledge bronze critic stomach neck figure recipe mind
Base HD Path:  m/44'/60'/0'/0/{account_index}

Listening on localhost:8545

可以看到testrpc啟動後自動建立了10個賬號(Accounts),與每個賬號對應的私鑰(Private Key)。每個賬號中都有100個測試用的以太幣(Ether)。要注意testrpc僅執行在記憶體中,因此每次重開時都會回到全新的狀態。

一切就緒,我們可以開始建立第一個智慧合約專案了。

6.建立專案

開啟另一個終端,輸入以下命令以建立專案:

sily@lyg-sily:~$ mkdir SmartContractDemo
sily@lyg-sily:~$ cd SmartContractDemo/
sily@lyg-sily:~/SmartContractDemo$ mkdir HelloWorld
sily@lyg-sily:~/SmartContractDemo$ cd HelloWorld/
sily@lyg-sily:~/SmartContractDemo/HelloWorld$ truffle init
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!

Commands:

  Compile:        truffle compile
  Migrate:        truffle migrate
  Test contracts: truffle test
sily@lyg-sily:~/SmartContractDemo/HelloWorld$ ls
contracts  migrations  test  truffle-config.js  truffle.js
sily@lyg-sily:~/SmartContractDemo/HelloWorld$ 

目錄結構

/contracts:存放智慧合約原始程式碼的地方,可以看到裡面有個Migrations.sol檔案,我們的HelloWorld.sol檔案就存放在這裡。

/migrations:這是Truffle用來部署智慧合約的功能,待會兒我們會修改2_deploy_contracts.js來部署HelloWorld.sol

/test:測試智慧合約的程式碼放在這裡,支援jssol測試。

truffle.js:Truffle的設定文件。

7.新建HelloWorld合約

contracts資料夾下新建HelloWorld.sol檔案,當然也可以直接在HelloWorld路徑下面直接執行truffle create contract HelloWorld命令來建立HelloWorld.sol

sily@lyg-sily:~/SmartContractDemo/HelloWorld$ ls
contracts  migrations  test  truffle-config.js  truffle.js
sily@lyg-sily:~/SmartContractDemo/HelloWorld$ truffle create contract HelloWorld
sily@lyg-sily:~/SmartContractDemo/HelloWorld$ cd contracts/
sily@lyg-sily:~/SmartContractDemo/HelloWorld/contracts$ ls
HelloWorld.sol  Migrations.sol
sily@lyg-sily:~/SmartContractDemo/HelloWorld/contracts$ 

HelloWorld.sol的檔案內容如下:

pragma solidity ^0.4.4;

contract HelloWorld {
    function sayHello() returns (string) {
        return ("Hello World");
    }
}

程式碼說明

pragma solidity ^0.4.4;

第一行表明目前使用的solidity版本,不同版本的solidity可能會編譯出不同的bytecode^代表相容solidity 0.4.4~0.4.9的版本。

contract HelloWorld {
    ...
}

contract關鍵字類似於其他語言中較常見的class。因為solidity是專為智慧合約(Contract)設計的語言,宣告contract後即內建了開發智慧合約所需的功能。也可以把這句理解為

function sayHello() returns (string) {
    return ("Hello World");
}

函式的結構與其他程式類似,但如果有傳入的引數或回傳值,需要指定引數或回傳值的型別(type)。

8.編譯

現在執行truffle compile命令,我們可以將HelloWorld.sol原始碼編譯成Ethereum bytecode

sily@lyg-sily:~/SmartContractDemo/HelloWorld$ ls
contracts  migrations  test  truffle-config.js  truffle.js
sily@lyg-sily:~/SmartContractDemo/HelloWorld$ truffle compile
Compiling ./contracts/HelloWorld.sol...
Compiling ./contracts/Migrations.sol...

Compilation warnings encountered:

/home/sily/SmartContractDemo/HelloWorld/contracts/HelloWorld.sol:4:3: Warning: No visibility specified. Defaulting to "public".
  function sayHello() returns (string) {
  ^
Spanning multiple lines.
,/home/sily/SmartContractDemo/HelloWorld/contracts/HelloWorld.sol:4:3: Warning: Function state mutability can be restricted to pure
  function sayHello() returns (string) {
  ^
Spanning multiple lines.

Writing artifacts to ./build/contracts

sily@lyg-sily:~/SmartContractDemo/HelloWorld$ ls
build  contracts  migrations  test  truffle-config.js  truffle.js

編譯成功後,會在HelloWorld資料夾下面的build/contracts資料夾下面看見HelloWorld.json檔案。

9.部署

truffle框架中提供了方便部署合約的指令碼。開啟migrations/2_deploy_contracts.js檔案(指令碼使用Javascript編寫),將內容修改如下:

var HelloWorld = artifacts.require("HelloWorld");
module.exports = function(deployer) {
    deployer.deploy(HelloWorld);
};

使用artifacts.require語句來取得準備部署的合約。使用deployer.deploy語句將合約部署到區塊鏈上。這邊HelloWorldcontract的名稱而不是資料夾。因此可以用此語法讀入任一.sol檔案中的任一合約。

現在執行truffle migrate命令:

sily@lyg-sily:~/SmartContractDemo/HelloWorld$ truffle migrate
Compiling ./contracts/HelloWorld.sol...

Compilation warnings encountered:

/home/sily/SmartContractDemo/HelloWorld/contracts/HelloWorld.sol:4:3: Warning: Function state mutability can be restricted to pure
  function sayHello() public constant returns (string) {
  ^
Spanning multiple lines.

Writing artifacts to ./build/contracts

Error: No network specified. Cannot determine current network.
    at Object.detect (/home/sily/.nvm/nvm/versions/node/v9.3.0/lib/node_modules/truffle/build/cli.bundled.js:41338:23)
    at /home/sily/.nvm/nvm/versions/node/v9.3.0/lib/node_modules/truffle/build/cli.bundled.js:202239:19
    at /home/sily/.nvm/nvm/versions/node/v9.3.0/lib/node_modules/truffle/build/cli.bundled.js:41263:11
    at /home/sily/.nvm/nvm/versions/node/v9.3.0/lib/node_modules/truffle/build/cli.bundled.js:41295:9
    at <anonymous>

可以發現,此時在使用truffle migrate進行智慧合約test環境釋出時出現異常,原因是truffle.js裡面未配置連結合約釋出時的環境地址,找到對應的truffle.js檔案,修改程式碼為類似如下配置即可解決問題:

module.exports = {
    networks: {
        development: {
            host: "localhost",
            port:8545,
            network_id:"*"  // 匹配任何network id
        }
    }
};

重新執行truffle migrate:

sily@lyg-sily:~/SmartContractDemo/HelloWorld$ truffle migrate
Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0xd43c42b526addb75876bb26a3086cabf79eaf9b0dbb1cd18ce3d6cc78f8e5335
  Migrations: 0x69c7674e74ee9aee187ea05b97faf08d6a7c0b94
Saving successful migration to network...
  ... 0x4aa706f72045aec809b3ab5d286489c157e6f5e73e1538a441cfb199c512f849
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying HelloWorld...
  ... 0x35afc83dd8e594167f1ddb9f52250b02aa72508a9b77ad15f5bd58b19a1e7275
  HelloWorld: 0x93ca4fd7d2820b2156fd88e40783ac9a41e26cfe
Saving successful migration to network...
  ... 0x12174aa188a4c47f84e071356149dfc9a51942bd81ce53d84bb22225d18d2fae
Saving artifacts...
sily@lyg-sily:~/SmartContractDemo/HelloWorld$ 

如此以後,合約已經部署到testrpc中。切換到testrpc視窗,可以看到testrpc有反應了。

Listening on localhost:8545
net_version
eth_accounts
eth_accounts
net_version
net_version
eth_sendTransaction

  Transaction: 0xd43c42b526addb75876bb26a3086cabf79eaf9b0dbb1cd18ce3d6cc78f8e5335
  Contract created: 0x69c7674e74ee9aee187ea05b97faf08d6a7c0b94
  Gas usage: 269607
  Block Number: 1
  Block Time: Wed Jan 10 2018 20:19:00 GMT+0800 (CST)

eth_newBlockFilter
eth_getFilterChanges
eth_getTransactionReceipt
eth_getCode
eth_uninstallFilter
eth_sendTransaction

  Transaction: 0x4aa706f72045aec809b3ab5d286489c157e6f5e73e1538a441cfb199c512f849
  Gas usage: 41981
  Block Number: 2
  Block Time: Wed Jan 10 2018 20:19:01 GMT+0800 (CST)

eth_getTransactionReceipt
eth_accounts
net_version
net_version
eth_sendTransaction

  Transaction: 0x35afc83dd8e594167f1ddb9f52250b02aa72508a9b77ad15f5bd58b19a1e7275
  Contract created: 0x93ca4fd7d2820b2156fd88e40783ac9a41e26cfe
  Gas usage: 142468
  Block Number: 3
  Block Time: Wed Jan 10 2018 20:19:01 GMT+0800 (CST)

eth_newBlockFilter
eth_getFilterChanges
eth_getTransactionReceipt
eth_getCode
eth_uninstallFilter
eth_sendTransaction

  Transaction: 0x12174aa188a4c47f84e071356149dfc9a51942bd81ce53d84bb22225d18d2fae
  Gas usage: 26981
  Block Number: 4
  Block Time: Wed Jan 10 2018 20:19:01 GMT+0800 (CST)

eth_getTransactionReceipt

10.與合約互動

truffle提供命令列工具,執行truffle console命令後,可用Javascript來和剛剛部署的合約互動。

sily@lyg-sily:~/SmartContractDemo/HelloWorld$ ls
build  contracts  migrations  test  truffle-config.js  truffle.js
sily@lyg-sily:~/SmartContractDemo/HelloWorld$ truffle console
truffle(development)> let contract
undefined
truffle(development)> HelloWorld.deployed().then(instance => contract = instance)
TruffleContract {
  constructor: 
   { [Function: TruffleContract]
     _static_methods: 
      { setProvider: [Function: setProvider],
        new: [Function: new],
        at: [Function: at],
        deployed: [Function: deployed],
        defaults: [Function: defaults],
        hasNetwork: [Function: hasNetwork],
        isDeployed: [Function: isDeployed],
        detectNetwork: [Function: detectNetwork],
        setNetwork: [Function: setNetwork],
        resetAddress: [Function: resetAddress],
        link: [Function: link],
        clone: [Function: clone],
        addProp: [Function: addProp],
        toJSON: [Function: toJSON] },
     _properties: 
      { contract_name: [Object],
        contractName: [Object],
        abi: [Object],
        network: [Function: network],
        networks: [Function: networks],
        address: [Object],
        links: [Function: links],
        events: [Function: events],
        binary: [Function: binary],
        deployedBinary: [Function: deployedBinary],
        unlinked_binary: [Object],
        bytecode: [Object],
        deployedBytecode: [Object],
        sourceMap: [Object],
        deployedSourceMap: [Object],
        source: [Object],
        sourcePath: [Object],
        ast: [Object],
        compiler: [Object],
        schema_version: [Function: schema_version],
        schemaVersion: [Function: schemaVersion],
        updated_at: [Function: updated_at],
        updatedAt: [Function: updatedAt] },
     _property_values: {},
     _json: 
      { contractName: 'HelloWorld',
        abi: [Array],
        bytecode: '0x6060604052341561000f57600080fd5b6101578061001e6000396000f300606060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef5fb05b14610046575b600080fd5b341561005157600080fd5b6100596100d4565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561009957808201518184015260208101905061007e565b50505050905090810190601f1680156100c65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100dc610117565b6040805190810160405280600b81526020017f48656c6c6f20576f726c64000000000000000000000000000000000000000000815250905090565b6020604051908101604052806000815250905600a165627a7a72305820b98aa7cabcef6181bb855c27290b86a3ed577c3493b6cf1cebfeb4be9a990c2e0029',
        deployedBytecode: '0x606060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef5fb05b14610046575b600080fd5b341561005157600080fd5b6100596100d4565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561009957808201518184015260208101905061007e565b50505050905090810190601f1680156100c65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100dc610117565b6040805190810160405280600b81526020017f48656c6c6f20576f726c64000000000000000000000000000000000000000000815250905090565b6020604051908101604052806000815250905600a165627a7a72305820b98aa7cabcef6181bb855c27290b86a3ed577c3493b6cf1cebfeb4be9a990c2e0029',
        sourceMap: '25:112:0:-;;;;;;;;;;;;;;;;;',
        deployedSourceMap: '25:112:0:-;;;;;;;;;;;;;;;;;;;;;;;;49:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:2;8:100;;;99:1;94:3;90;84:5;80:1;75:3;71;64:6;52:2;49:1;45:3;40:15;;8:100;;;12:14;3:109;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;49:86:0;94:6;;:::i;:::-;108:22;;;;;;;;;;;;;;;;;;;;49:86;:::o;25:112::-;;;;;;;;;;;;;;;:::o',
        source: 'pragma solidity ^0.4.4;\n\ncontract HelloWorld {\n  function sayHello() public constant returns (string) {\n    return ("Hello World");\n  }\n}\n',
        sourcePath: '/home/sily/SmartContractDemo/HelloWorld/contracts/HelloWorld.sol',
        ast: [Object],
        compiler: [Object],
        networks: [Object],
        schemaVersion: '1.0.1',
        updatedAt: '2018-01-10T12:19:01.841Z' },
     setProvider: [Function: bound setProvider],
     new: [Function: bound new],
     at: [Function: bound at],
     deployed: [Function: bound deployed],
     defaults: [Function: bound defaults],
     hasNetwork: [Function: bound hasNetwork],
     isDeployed: [Function: bound isDeployed],
     detectNetwork: [Function: bound detectNetwork],
     setNetwork: [Function: bound setNetwork],
     resetAddress: [Function: bound resetAddress],
     link: [Function: bound link],
     clone: [Function: bound clone],
     addProp: [Function: bound addProp],
     toJSON: [Function: bound toJSON],
     web3: 
      Web3 {
        _requestManager: [RequestManager],
        currentProvider: [Provider],
        eth: [Eth],
        db: [DB],
        shh: [Shh],
        net: [Net],
        personal: [Personal],
        bzz: [Swarm],
        settings: [Settings],
        version: [Object],
        providers: [Object],
        _extend: [Function] },
     class_defaults: 
      { from: '0x7e1897cec8e1a098384d17e85a9cd348ccb92b4a',
        gas: 6721975,
        gasPrice: 100000000000 },
     currentProvider: 
      HttpProvider {
        host: 'http://localhost:8545',
        timeout: 0,
        user: undefined,
        password: undefined,
        send: [Function],
        sendAsync: [Function],
        _alreadyWrapped: true },
     network_id: '1515578597496' },
  abi: 
   [ { constant: true,
       inputs: [],
       name: 'sayHello',
       outputs: [Array],
       payable: false,
       stateMutability: 'view',
       type: 'function' } ],
  contract: 
   Contract {
     _eth: 
      Eth {
        _requestManager: [RequestManager],
        getBalance: [Function],
        getStorageAt: [Function],
        getCode: [Function],
        getBlock: [Function],
        getUncle: [Function],
        getCompilers: [Function],
        getBlockTransactionCount: [Function],
        getBlockUncleCount: [Function],
        getTransaction: [Function],
        getTransactionFromBlock: [Function],
        getTransactionReceipt: [Function],
        getTransactionCount: [Function],
        call: [Function],
        estimateGas: [Function],
        sendRawTransaction: [Function],
        signTransaction: [Function],
        sendTransaction: [Function],
        sign: [Function],
        compile: [Object],
        submitWork: [Function],
        getWork: [Function],
        coinbase: [Getter],
        getCoinbase: [Function],
        mining: [Getter],
        getMining: [Function],
        hashrate: [Getter],
        getHashrate: [Function],
        syncing: [Getter],
        getSyncing: [Function],
        gasPrice: [Getter],
        getGasPrice: [Function],
        accounts: [Getter],
        getAccounts: [Function],
        blockNumber: [Getter],
        getBlockNumber: [Function],
        protocolVersion: [Getter],
        getProtocolVersion: [Function],
        iban: [Function],
        sendIBANTransaction: [Function: bound transfer] },
     transactionHash: null,
     address: '0x93ca4fd7d2820b2156fd88e40783ac9a41e26cfe',
     abi: [ [Object] ],
     sayHello: 
      { [Function: bound ]
        request: [Function: bound ],
        call: [Function: bound ],
        sendTransaction: [Function: bound ],
        estimateGas: [Function: bound ],
        getData: [Function: bound ],
        '': [Circular] },
     allEvents: [Function: bound ] },
  sayHello: 
   { [Function]
     call: [Function],
     sendTransaction: [Function],
     request: [Function: bound ],
     estimateGas: [Function] },
  sendTransaction: [Function],
  send: [Function],
  allEvents: [Function: bound ],
  address: '0x93ca4fd7d2820b2156fd88e40783ac9a41e26cfe',
  transactionHash: null }
truffle(development)> contract.sayHello.call()
'Hello World'
truffle(development)> 

truffle console中預載了truffle-contract函式庫,以方便操作部署到區塊鏈上的合約。使用了HelloWorld.deployed().then語句來取得HelloWorld合約的Instance(例項),並存到contract變數中,以方便後續的呼叫。

上面用的是Javascript ES6+的語法,這句也可以寫成:

HelloWorld.deployed().then(instance => {
    contract = instance
});

還可以用ES5的寫法:

HelloWorld.deployed().then(function(instance) {
    hello = instance;
});
truffle(development)> contract.sayHello.call()
'Hello World'

最後通過contract.sayHello.call()成功打印出"Hello World",這裡直接呼叫contract.sayHello()也會得到一樣的結果。truffle-contract提供使用call()來讀取只讀(read only)的資料,這樣就不需提供gas。因此如果遇到的操作需要向區塊鏈寫入資料,我們就不能用call語句了。

這樣,我們的第一個智慧合約就已經寫好並部署好了,也驗證了合約確實可以執行。

11.加入新方法

我們在HelloWorld.sol中再加入一個echo方法,echo方法接受一個引數,並回傳傳送的引數。

function echo(string name) constant returns (string) {
    return name;
}

新的echo方法中傳入了一個name引數。我們也為echo方法加入一個constant宣告,表示呼叫這個方法並不會改變區塊鏈的狀態。如此一來,透過truffle-contract來呼叫此方法時,會自動選用call來呼叫,也不需要額外提供gas

由於更新了合約內容,我們需要先重新編譯一次,將編譯結果部署到testrpc上,再通過truffle console執行檢視結果。

sily@lyg-sily:~/SmartContractDemo/HelloWorld$ truffle migrate --reset
Compiling ./contracts/HelloWorld.sol...

Compilation warnings encountered:

/home/sily/SmartContractDemo/HelloWorld/contracts/HelloWorld.sol:4:3: Warning: Function state mutability can be restricted to pure
  function sayHello() public constant returns (string) {
  ^
Spanning multiple lines.
,/home/sily/SmartContractDemo/HelloWorld/contracts/HelloWorld.sol:8:3: Warning: Function state mutability can be restricted to pure
  function echo(string name) public constant returns (string) {
  ^
Spanning multiple lines.

Writing artifacts to ./build/contracts

Using network 'development'.

Running migration: 1_initial_migration.js
  Replacing Migrations...
  ... 0xfc5af4b443dcae96f96206e26d2bc93b9699e7d14980dd0247baea112bd04c44
  Migrations: 0xee671e4bb1e86df24120041587b0a4841924f7bd
Saving successful migration to network...
  ... 0x1d7e34e903cc04a5e5cd6e5cca1031034841bd001dd693b488500fe75c4df9e2
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Replacing HelloWorld...
  ... 0xdb8c8ef861498972ad769f947e9c03872b3f17f7292deff3fa25d5f5318ce94f
  HelloWorld: 0xace9ff211c9131362e05774335c6b3a353739119
Saving successful migration to network...
  ... 0x3a653f82abd0b92f389ea78ba654f51ff2256961f72b280874676cebac24f895
Saving artifacts...
sily@lyg-sily:~/SmartContractDemo/HelloWorld$ truffle console
truffle(development)> let contract
undefined
truffle(development)> HelloWorld.deployed().then(instance => contract = instance)
TruffleContract {
  constructor: 
   { [Function: TruffleContract]
     _static_methods: 
      { setProvider: [Function: setProvider],
        new: [Function: new],
        at: [Function: at],
        deployed: [Function: deployed],
        defaults: [Function: defaults],
        hasNetwork: [Function: hasNetwork],
        isDeployed: [Function: isDeployed],
        detectNetwork: [Function: detectNetwork],
        setNetwork: [Function: setNetwork],
        resetAddress: [Function: resetAddress],
        link: [Function: link],
        clone: [Function: clone],
        addProp: [Function: addProp],
        toJSON: [Function: toJSON] },
     _properties: 
      { contract_name: [Object],
        contractName: [Object],
        abi: [Object],
        network: [Function: network],
        networks: [Function: networks],
        address: [Object],
        links: [Function: links],
        events: [Function: events],
        binary: [Function: binary],
        deployedBinary: [Function: deployedBinary],
        unlinked_binary: [Object],
        bytecode: [Object],
        deployedBytecode: [Object],
        sourceMap: [Object],
        deployedSourceMap: [Object],
        source: [Object],
        sourcePath: [Object],
        ast: [Object],
        compiler: [Object],
        schema_version: [Function: schema_version],
        schemaVersion: [Function: schemaVersion],
        updated_at: [Function: updated_at],
        updatedAt: [Function: updatedAt] },
     _property_values: {},
     _json: 
      { contractName: 'HelloWorld',
        abi: [Array],
        bytecode: '0x6060604052341561000f57600080fd5b6102488061001e6000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef5fb05b14610051578063f15da729146100df575b600080fd5b341561005c57600080fd5b6100646101b5565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100a4578082015181840152602081019050610089565b50505050905090810190601f1680156100d15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156100ea57600080fd5b61013a600480803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919050506101f8565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561017a57808201518184015260208101905061015f565b50505050905090810190601f1680156101a75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101bd610208565b6040805190810160405280600b81526020017f48656c6c6f20576f726c64000000000000000000000000000000000000000000815250905090565b610200610208565b819050919050565b6020604051908101604052806000815250905600a165627a7a72305820ce2a412dd03391bea1a4d86dcaa57502a5d812ad4005a99570a2bb3b3ffd25ec0029',
        deployedBytecode: '0x60606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef5fb05b14610051578063f15da729146100df575b600080fd5b341561005c57600080fd5b6100646101b5565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100a4578082015181840152602081019050610089565b50505050905090810190601f1680156100d15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156100ea57600080fd5b61013a600480803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919050506101f8565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561017a57808201518184015260208101905061015f565b50505050905090810190601f1680156101a75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101bd610208565b6040805190810160405280600b81526020017f48656c6c6f20576f726c64000000000000000000000000000000000000000000815250905090565b610200610208565b819050919050565b6020604051908101604052806000815250905600a165627a7a72305820ce2a412dd03391bea1a4d86dcaa57502a5d812ad4005a99570a2bb3b3ffd25ec0029',
        sourceMap: '25:198:0:-;;;;;;;;;;;;;;;;;',
        deployedSourceMap: '25:198:0:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;49:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:2;8:100;;;99:1;94:3;90;84:5;80:1;75:3;71;64:6;52:2;49:1;45:3;40:15;;8:100;;;12:14;3:109;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;139:82:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:2;8:100;;;99:1;94:3;90;84:5;80:1;75:3;71;64:6;52:2;49:1;45:3;40:15;;8:100;;;12:14;3:109;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;49:86:0;94:6;;:::i;:::-;108:22;;;;;;;;;;;;;;;;;;;;49:86;:::o;139:82::-;191:6;;:::i;:::-;212:4;205:11;;139:82;;;:::o;25:198::-;;;;;;;;;;;;;;;:::o',
        source: 'pragma solidity ^0.4.4;\n\ncontract HelloWorld {\n  function sayHello() public constant returns (string) {\n    return ("Hello World");\n  }\n\n  function echo(string name) public constant returns (string) {\n    return name;\n  }\n}\n',
        sourcePath: '/home/sily/SmartContractDemo/HelloWorld/contracts/HelloWorld.sol',
        ast: [Object],
        compiler: [Object],
        networks: [Object],
        schemaVersion: '1.0.1',
        updatedAt: '2018-01-10T13:34:12.869Z' },
     setProvider: [Function: bound setProvider],
     new: [Function: bound new],
     at: [Function: bound at],
     deployed: [Function: bound deployed],
     defaults: [Function: bound defaults],
     hasNetwork: [Function: bound hasNetwork],
     isDeployed: [Function: bound isDeployed],
     detectNetwork: [Function: bound detectNetwork],
     setNetwork: [Function: bound setNetwork],
     resetAddress: [Function: bound resetAddress],
     link: [Function: bound link],
     clone: [Function: bound clone],
     addProp: [Function: bound addProp],
     toJSON: [Function: bound toJSON],
     web3: 
      Web3 {
        _requestManager: [RequestManager],
        currentProvider: [Provider],
        eth: [Eth],
        db: [DB],
        shh: [Shh],
        net: [Net],
        personal: [Personal],
        bzz: [Swarm],
        settings: [Settings],
        version: [Object],
        providers: [Object],
        _extend: [Function] },
     class_defaults: 
      { from: '0x7e1897cec8e1a098384d17e85a9cd348ccb92b4a',
        gas: 6721975,
        gasPrice: 100000000000 },
     currentProvider: 
      HttpProvider {
        host: 'http://localhost:8545',
        timeout: 0,
        user: undefined,
        password: undefined,
        send: [Function],
        sendAsync: [Function],
        _alreadyWrapped: true },
     network_id: '1515578597496' },
  abi: 
   [ { constant: true,
       inputs: [],
       name: 'sayHello',
       outputs: [Array],
       payable: false,
       stateMutability: 'view',
       type: 'function' },
     { constant: true,
       inputs: [Array],
       name: 'echo',
       outputs: [Array],
       payable: false,
       stateMutability: 'view',
       type: 'function' } ],
  contract: 
   Contract {
     _eth: 
      Eth {
        _requestManager: [RequestManager],
        getBalance: [Function],
        getStorageAt: [Function],
        getCode: [Function],
        getBlock: [Function],
        getUncle: [Function],
        getCompilers: [Function],
        getBlockTransactionCount: [Function],
        getBlockUncleCount: [Function],
        getTransaction: [Function],
        getTransactionFromBlock: [Function],
        getTransactionReceipt: [Function],
        getTransactionCount: [Function],
        call: [Function],
        estimateGas: [Function],
        sendRawTransaction: [Function],
        signTransaction: [Function],
        sendTransaction: [Function],
        sign: [Function],
        compile: [Object],
        submitWork: [Function],
        getWork: [Function],
        coinbase: [Getter],
        getCoinbase: [Function],
        mining: [Getter],
        getMining: [Function],
        hashrate: [Getter],
        getHashrate: [Function],
        syncing: [Getter],
        getSyncing: [Function],
        gasPrice: [Getter],
        getGasPrice: [Function],
        accounts: [Getter],
        getAccounts: [Function],
        blockNumber: [Getter],
        getBlockNumber: [Function],
        protocolVersion: [Getter],
        getProtocolVersion: [Function],
        iban: [Function],
        sendIBANTransaction: [Function: bound transfer] },
     transactionHash: null,
     address: '0xace9ff211c9131362e05774335c6b3a353739119',
     abi: [ [Object], [Object] ],
     sayHello: 
      { [Function: bound ]
        request: [Function: bound ],
        call: [Function: bound ],
        sendTransaction: [Function: bound ],
        estimateGas: [Function: bound ],
        getData: [Function: bound ],
        '': [Circular] },
     echo: 
      { [Function: bound ]
        request: [Function: bound ],
        call: [Function: bound ],
        sendTransaction: [Function: bound ],
        estimateGas: [Function: bound ],
        getData: [Function: bound ],
        string: [Circular] },
     allEvents: [Function: bound ] },
  sayHello: 
   { [Function]
     call: [Function],
     sendTransaction: [Function],
     request: [Function: bound ],
     estimateGas: [Function] },
  echo: 
   { [Function]
     call: [Function],
     sendTransaction: [Function],
     request: [Function: bound ],
     estimateGas: [Function] },
  sendTransaction: [Function],
  send: [Function],
  allEvents: [Function: bound ],
  address: '0xace9ff211c9131362e05774335c6b3a353739119',
  transactionHash: null }
truffle(development)> contract.echo("2018-1-10 blockchain")
'2018-1-10 blockchain'
truffle(development)> 

echo方法確實將我們輸入的內容回傳了。同時因為聲明瞭constant,我們不需要直接呼叫call()方法,truffle會自動選用call來呼叫。

另外需要注意,如果我們還是使用truffle migrate命令,會得到如下資訊:

$ truffle migrate
Using network 'development'.

Network up to date.

Truffle會告訴你現在網路上的合約都已經是最新的,但事實上剛剛程式中新增的方法並沒有更新到記憶體塊鏈上。要更新記憶體塊鏈上已部署的程式,需要改寫migrations中的指令碼,但現在還不到介紹migration的時候。還好我們開發用的記憶體塊鏈是怎麼修改都沒關係的testrpc,可以使用truffle migrate --reset命令直接在testrpc上部署一次。