以太坊開(kāi)發(fā)(二)Truffle示例項(xiàng)目拆解

本篇文章將通過(guò)分析Truffle官網(wǎng)提供的簡(jiǎn)單示例項(xiàng)目metacoin,webpack學(xué)習(xí)怎樣寫(xiě)智能合約双炕,遷移腳本和測(cè)試腳本踱蠢,為開(kāi)發(fā)自己的DApp打下基礎(chǔ)。

MetaCoin是一個(gè)簡(jiǎn)單的代幣項(xiàng)目咪辱。下載代碼來(lái)看看吧~

zhujiantao@ubuntu:~$ mkdir truffle-examples
zhujiantao@ubuntu:~$ cd truffle-examples/
zhujiantao@ubuntu:~/truffle-examples$ mkdir MetaCoin
zhujiantao@ubuntu:~/truffle-examples$ cd MetaCoin/
zhujiantao@ubuntu:~/truffle-examples/MetaCoin$ truffle unbox metacoin
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!

Commands:

  Compile contracts: truffle compile
  Migrate contracts: truffle migrate
  Test contracts:    truffle test
zhujiantao@ubuntu:~/truffle-examples/MetaCoin$ tree
.
├── contracts
│   ├── ConvertLib.sol
│   ├── MetaCoin.sol
│   └── Migrations.sol
├── migrations
│   ├── 1_initial_migration.js
│   └── 2_deploy_contracts.js
├── test
│   ├── metacoin.js
│   └── TestMetacoin.sol
├── truffle-config.js
└── truffle.js

3 directories, 9 files

項(xiàng)目架構(gòu)在上篇文章中也有介紹
contracts目錄下存放合約文件振劳,其中Migrations.sol合約較為特殊,會(huì)在項(xiàng)目初始化的時(shí)候被創(chuàng)建油狂,是Truffle框架里必須的文件历恐,你運(yùn)行的遷移歷史記錄會(huì)被他記錄在區(qū)塊鏈上。
migrations下存放遷移部署腳本专筷,用來(lái)幫助你把合約發(fā)布到以太坊網(wǎng)絡(luò)中弱贼。
test目錄下存放測(cè)試腳本。Truffle內(nèi)置自動(dòng)化測(cè)試框架磷蛹,讓我們可以方便的測(cè)試自己的合約吮旅。該框架允許我們以兩種不同的方式編寫(xiě)測(cè)試用例:1,使用Javascript編寫(xiě)測(cè)試用例味咳,測(cè)試從外部執(zhí)行合約 2庇勃,使用Solidity編寫(xiě)測(cè)試用例,測(cè)試從內(nèi)部調(diào)用合約槽驶。
truffle.js和truffle-config.js為Truffle配置文件责嚷,配置了運(yùn)行網(wǎng)絡(luò)等相關(guān)信息。

合約Migrations.sol

pragma solidity ^0.4.2;    //聲明Solidity版本掂铐,兼容0.4.2以下版本

contract Migrations {        //contract聲明為合約罕拂,在Truffle中合約名稱需和文件名一致
  address public owner;        //公開(kāi)(外部可見(jiàn))揍异,地址類型全局變量owner,用于保存合約所有者地址
  uint public last_completed_migration;    //公開(kāi)聂受,無(wú)符號(hào)整型全局變量last_completed_migration蒿秦,在每次合約部署時(shí)更新

  modifier restricted() {        //修飾符,通常用于函數(shù)執(zhí)行前檢查某種前置條件是否滿足
    if (msg.sender == owner) _;    //判斷調(diào)用合約的是不是合約所有者
  }

  constructor() public {        //構(gòu)造函數(shù)蛋济,在合約部署時(shí)執(zhí)行
    owner = msg.sender;    //將合約所有者賦值給owner變量//msg為solidity中的全局變量棍鳖,msg.sender表示合約發(fā)送者地址
  }

  function setCompleted(uint completed) public restricted {    //用于更新last_completed_migration變量的函數(shù)
    last_completed_migration = completed;
  }

  function upgrade(address new_address) public restricted {    //用于更新合約所有者
    Migrations upgraded = Migrations(new_address);
    upgraded.setCompleted(last_completed_migration);
  }
}

Solidity在全局命名空間中預(yù)設(shè)了一些特殊的變量和函數(shù),用來(lái)提供關(guān)于區(qū)塊鏈的信息和一些通用的工具函數(shù)

  • block.blockhash(uint blockNumber) returns (bytes32):指定區(qū)塊的區(qū)塊哈贤肼茫——僅可用于最新的 256 個(gè)區(qū)塊且不包括當(dāng)前區(qū)塊渡处;而 blocks 從 0.4.22 版本開(kāi)始已經(jīng)不推薦使用,由 blockhash(uint blockNumber) 代替
  • block.coinbase (address): 挖出當(dāng)前區(qū)塊的礦工地址
  • block.difficulty (uint): 當(dāng)前區(qū)塊難度
  • block.gaslimit (uint): 當(dāng)前區(qū)塊 gas 限額
  • block.number (uint): 當(dāng)前區(qū)塊號(hào)
  • block.timestamp (uint): 自 unix epoch 起始當(dāng)前區(qū)塊以秒計(jì)的時(shí)間戳
  • gasleft() returns (uint256):剩余的 gas
  • msg.data (bytes): 完整的 calldata
  • msg.gas (uint): 剩余 gas - 自 0.4.21 版本開(kāi)始已經(jīng)不推薦使用祟辟,由 gesleft() 代替
  • msg.sender (address): 消息發(fā)送者(當(dāng)前調(diào)用)
  • msg.sig (bytes4): calldata 的前 4 字節(jié)(也就是函數(shù)標(biāo)識(shí)符)
  • msg.value (uint): 隨消息發(fā)送的 wei 的數(shù)量
  • now (uint): 目前區(qū)塊時(shí)間戳(block.timestamp)
  • tx.gasprice (uint): 交易的 gas 價(jià)格
  • tx.origin (address): 交易發(fā)起者(完全的調(diào)用鏈)

合約MetaCoin.sol

pragma solidity ^0.4.18;

import "./ConvertLib.sol";        //導(dǎo)入外部類庫(kù)

contract MetaCoin {
        mapping (address => uint) balances;    //mapping類型變量医瘫,用于保存地址對(duì)應(yīng)余額信息

        event Transfer(address indexed _from, address indexed _to, uint256 _value);//event事件,用于通知外部    //indexd修飾符可用于事件過(guò)濾

        constructor() public {
                balances[tx.origin] = 10000;    //給交易發(fā)起者賬號(hào)余額賦值
        }

        function sendCoin(address receiver, uint amount) public returns(bool sufficient) { //發(fā)送MetaCoin方法旧困,參數(shù)為接收者地址和交易幣數(shù)量醇份,返回值為交易成功失敗狀態(tài)
                if (balances[msg.sender] < amount) return false;    //需要合約發(fā)起者余額充足
                balances[msg.sender] -= amount;
                balances[receiver] += amount;
                emit Transfer(msg.sender, receiver, amount);        //交易后觸發(fā)事件,通知外部
                return true;
        }

        function getBalanceInEth(address addr) public view returns(uint){    //獲取某個(gè)地址余額吼具,轉(zhuǎn)換為Eth返回//view修飾符保證不修改狀態(tài)
                return ConvertLib.convert(getBalance(addr),2);    
        }

        function getBalance(address addr) public view returns(uint) {    //根據(jù)地址獲取余額
                return balances[addr];
        }
}

庫(kù)ConvertLib.sol

pragma solidity ^0.4.4;

library ConvertLib{
        function convert(uint amount,uint conversionRate) public pure returns (uint convertedAmount)
        {
                return amount * conversionRate;
        }
}

library是一種不同類型的合約僚纷,沒(méi)有存儲(chǔ),不用有以太幣拗盒。庫(kù)中的代碼可以被其他合約調(diào)用怖竭,而不需要重新部署,這樣可以節(jié)省大量gas陡蝇。該庫(kù)提供了convert函數(shù)轉(zhuǎn)換MetaCoin和ETH痊臭,一個(gè)MetaCoin值conversionRate個(gè)ETH。

遷移腳本1_initial_migration.js

var Migrations = artifacts.require("./Migrations.sol");    //告訴測(cè)試腳本要和哪個(gè)合約交互

module.exports = function(deployer) {    
  deployer.deploy(Migrations);            //遷移腳本使用deployer對(duì)象部署合約
};

可以看到遷移腳本命名規(guī)則是以數(shù)字開(kāi)頭登夫,后面加上描述性字符串广匙。數(shù)字前綴是必須的,用于記錄遷移記錄恼策,在前面Migration.sol中可以看到相關(guān)代碼鸦致。

遷移腳本2_deploy_contracts.js

var ConvertLib = artifacts.require("./ConvertLib.sol");
var MetaCoin = artifacts.require("./MetaCoin.sol");

module.exports = function(deployer) {
  deployer.deploy(ConvertLib);
  deployer.link(ConvertLib, MetaCoin);        //把已部署好的庫(kù)鏈接到MetaCoin合約
  deployer.deploy(MetaCoin);
};

測(cè)試腳本metacoin.js

var MetaCoin = artifacts.require("./MetaCoin.sol");        //指明要測(cè)試的合約

contract('MetaCoin', function(accounts) {        //contract 表示test sunit測(cè)試套件,表示一組相關(guān)測(cè)試戏蔑,第一個(gè)參數(shù)MetaCoin為套件名稱蹋凝,第二個(gè)參數(shù)為執(zhí)行函數(shù)
  it("should put 10000 MetaCoin in the first account", function() {    //it 表示test case測(cè)試用例,第一個(gè)參數(shù)為測(cè)試用例名稱总棵,第二個(gè)參數(shù)為執(zhí)行函數(shù)
    return MetaCoin.deployed().then(function(instance) {
      return instance.getBalance.call(accounts[0]);
    }).then(function(balance) {
      assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account");
    });
  });
  it("should call a function that depends on a linked library", function() {
    var meta;
    var metaCoinBalance;
    var metaCoinEthBalance;

    return MetaCoin.deployed().then(function(instance) {
      meta = instance;
      return meta.getBalance.call(accounts[0]);
    }).then(function(outCoinBalance) {
      metaCoinBalance = outCoinBalance.toNumber();
      return meta.getBalanceInEth.call(accounts[0]);
    }).then(function(outCoinBalanceEth) {
      metaCoinEthBalance = outCoinBalanceEth.toNumber();
    }).then(function() {
      assert.equal(metaCoinEthBalance, 2 * metaCoinBalance, "Library function returned unexpected function, linkage may be broken");
    });
  });
  it("should send coin correctly", function() {
    var meta;

    // Get initial balances of first and second account.
    var account_one = accounts[0];
    var account_two = accounts[1];

    var account_one_starting_balance;
    var account_two_starting_balance;
    var account_one_ending_balance;
    var account_two_ending_balance;

    var amount = 10;

    return MetaCoin.deployed().then(function(instance) {
      meta = instance;
      return meta.getBalance.call(account_one);
    }).then(function(balance) {
      account_one_starting_balance = balance.toNumber();
      return meta.getBalance.call(account_two);
    }).then(function(balance) {
      account_two_starting_balance = balance.toNumber();
      return meta.sendCoin(account_two, amount, {from: account_one});
    }).then(function() {
      return meta.getBalance.call(account_one);
    }).then(function(balance) {
      account_one_ending_balance = balance.toNumber();
      return meta.getBalance.call(account_two);
    }).then(function(balance) {
      account_two_ending_balance = balance.toNumber();

      assert.equal(account_one_ending_balance, account_one_starting_balance - amount, "Amount wasn't correctly taken from the sender");
      assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver");
    });
  });
});

一大串鳍寂,,情龄,不過(guò)可以看到是從外部執(zhí)行迄汛,在測(cè)試結(jié)束后可以看到各個(gè)測(cè)試用例測(cè)試結(jié)果及所用時(shí)間信息捍壤。

測(cè)試腳本TestMetacoin.sol

pragma solidity ^0.4.2;

import "truffle/Assert.sol";            //導(dǎo)入Truffle內(nèi)置斷言庫(kù)
import "truffle/DeployedAddresses.sol";
import "../contracts/MetaCoin.sol";

contract TestMetacoin {

  function testInitialBalanceUsingDeployedContract() public {
    MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin());    //部署合約并獲取合約抽象,記住這個(gè)寫(xiě)法就好啦

    uint expected = 10000;

    Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
  }

  function testInitialBalanceWithNewMetaCoin() public {
    MetaCoin meta = new MetaCoin();

    uint expected = 10000;

    Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
  }

}

這個(gè)就簡(jiǎn)潔多了鞍爱,emmmm鹃觉。。

需要補(bǔ)充的是當(dāng)執(zhí)行編譯后睹逃,在build文件夾下生成了對(duì)應(yīng)的json文件盗扇,json文件由ABI(Application Binary Interface)應(yīng)用二進(jìn)制接口,bytecode合約編譯后二進(jìn)制內(nèi)容等組成

zhujiantao@ubuntu:~/truffle-examples/MetaCoin$ truffle compile
Compiling ./contracts/ConvertLib.sol...
Compiling ./contracts/MetaCoin.sol...
Compiling ./contracts/Migrations.sol...
Writing artifacts to ./build/contracts

zhujiantao@ubuntu:~/truffle-examples/MetaCoin$ tree
.
├── build
│   └── contracts
│       ├── ConvertLib.json
│       ├── MetaCoin.json
│       └── Migrations.json
├── contracts
│   ├── ConvertLib.sol
│   ├── MetaCoin.sol
│   └── Migrations.sol
├── migrations
│   ├── 1_initial_migration.js
│   └── 2_deploy_contracts.js
├── test
│   ├── metacoin.js
│   └── TestMetacoin.sol
├── truffle-config.js
└── truffle.js

5 directories, 12 files

哈沉填,MetaCoin分析完畢疗隶。webpack相當(dāng)于在MetaCoin項(xiàng)目的基礎(chǔ)上進(jìn)行了補(bǔ)充,增加了服務(wù)和前端界面翼闹,可以通過(guò)網(wǎng)頁(yè)進(jìn)行轉(zhuǎn)賬操作斑鼻,厲害了。一起來(lái)玩一玩吧猎荠。
先下載編譯

$ truffle unbox webpack
$ truffle compile

查看truffle.js可以看到webpack默認(rèn)指定的網(wǎng)絡(luò)是ganache坚弱。

module.exports = {
  networks: {
    ganache: {
      host: '127.0.0.1',
      port: 7545,
      network_id: '*' // Match any network id
    }
  }
}

官網(wǎng)下載ganache并啟動(dòng)。


image.png

部署合約到ganache

zhujiantao@ubuntu:~/truffle-examples/WebPack$ truffle migrate --network ganache
Using network 'ganache'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0xbe34f8f11c1dcf1706743fa3923f205a142a7abc85483fe7fba16e175229d9bc
  Migrations: 0xa0e137e3429607bf15eeb1fa9aa7add5c586f9b3
Saving successful migration to network...
  ... 0x1c6690bb21c421d30a04c96d1fa2a56619c97b21e16dba31e42a079db2094557
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying ConvertLib...
  ... 0xd28c5de62b584b4a4cf926a3e03c49f2efe23b5c261d088cf26952a0914a53e0
  ConvertLib: 0x4fbaf174a9275377fd406433dc4a7d4c91ca9626
  Linking ConvertLib to MetaCoin
  Deploying MetaCoin...
  ... 0xadfe02879726a61ce7686bf69cdbbd382bf95e942a925a9c713bcc4e0669f31b
  MetaCoin: 0x6c43768bcf9760e3338bf60786c9d6be36d4876e
Saving successful migration to network...
  ... 0xbe8fbc8e4fd8bd438452f220b77d812dc2299583d4b19790b40cfa1b0a5d709d
Saving artifacts...

把服務(wù)運(yùn)行起來(lái)

zhujiantao@ubuntu:~/truffle-examples/WebPack$ npm run dev

> truffle-init-webpack@0.0.2 dev /home/zhujiantao/truffle-examples/WebPack
> webpack-dev-server

? ?wds?: Project is running at http://localhost:8081/
? ?wds?: webpack output is served from /

打開(kāi)網(wǎng)頁(yè)


image.png

哇关摇,可以通過(guò)前端轉(zhuǎn)賬荒叶,接下來(lái)就自己去耍吧。

希望看了文章的同學(xué)能對(duì)Solidity語(yǔ)言拒垃,Truffle項(xiàng)目架構(gòu)以及遷移停撞、測(cè)試腳本的編寫(xiě)有一定了解瓷蛙,當(dāng)然悼瓮,更全面的學(xué)習(xí),請(qǐng)上Solidity官方網(wǎng)站Truffle官網(wǎng)艰猬。

接下來(lái)横堡,還要去解鎖更復(fù)雜的項(xiàng)目,并試著寫(xiě)出自己的Dapp冠桃。任重道遠(yuǎn)命贴,加油加油!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末食听,一起剝皮案震驚了整個(gè)濱河市胸蛛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌樱报,老刑警劉巖葬项,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異迹蛤,居然都是意外死亡民珍,警方通過(guò)查閱死者的電腦和手機(jī)襟士,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)嚷量,“玉大人陋桂,你說(shuō)我怎么就攤上這事〉埽” “怎么了嗜历?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)抖所。 經(jīng)常有香客問(wèn)我秸脱,道長(zhǎng),這世上最難降的妖魔是什么部蛇? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任摊唇,我火速辦了婚禮,結(jié)果婚禮上涯鲁,老公的妹妹穿的比我還像新娘巷查。我一直安慰自己,他們只是感情好抹腿,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布岛请。 她就那樣靜靜地躺著,像睡著了一般警绩。 火紅的嫁衣襯著肌膚如雪崇败。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天肩祥,我揣著相機(jī)與錄音后室,去河邊找鬼。 笑死混狠,一個(gè)胖子當(dāng)著我的面吹牛岸霹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播将饺,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼贡避,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了予弧?” 一聲冷哼從身側(cè)響起刮吧,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掖蛤,沒(méi)想到半個(gè)月后杀捻,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坠七,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年水醋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了旗笔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拄踪,死狀恐怖蝇恶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情惶桐,我是刑警寧澤撮弧,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站姚糊,受9級(jí)特大地震影響贿衍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜救恨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一贸辈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肠槽,春花似錦擎淤、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至寂纪,卻和暖如春席吴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捞蛋。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工孝冒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人襟交。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓迈倍,卻偏偏與公主長(zhǎng)得像伤靠,于是被迫代替她去往敵國(guó)和親捣域。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容