Truffle 2.0升級3.0升級指南

FLy_鵬程萬里發表於2018-07-14

備註:這個指南同樣適用於從beta 3.0.0-9升級到3.0.1的使用者。

介紹

3.0版本引入了大量的新特性,這些特性為我們帶來了大量的重要革新性變化。讓我們的network的管理更簡單,新的抽象的合約層,允許你從第三方引入各種依賴檔案。伴隨以太坊的開發工具逐步成熟,我們認為這樣的革新非常有價值。下面我們將一步步指引你來享受這些新特性帶來的好處。

為了展示2.0到3.0版本的變化,後續會使用下面示例這種對比的方式。

v2.0

// 2.0版本程式碼

v3.0

// 3.0版本程式碼

先說最重要的:配置(Configuration)

Truffle 2.0中我們使用了一個不舒服的配置方式。你不僅可以使用一個default,匿名的網路設定(通過rpc配置項);同時你也可以使用命名的網路設定,比如ropsten,或者live。由此帶來的一個後果是,你可能會無意中覆蓋了網路配置,或者部署到錯誤的網路。在Truffle 3.0中,我們解決了這個問題,代價是對配置方式的調整。

在2.0版本中,一個命名網路的宣告方式示例如下:

module.exports = {
  rpc: {
    host: "localhost",
    port: 8545
  },
  networks: {
    staging: {
      host: "localhost",
      port: 8546,
      network_id: 1337
    },
    ropsten: {
      host: "158.253.8.12",
      port: 8545,
      network_id: 3
    }
  }
};

在3.0中,我們改為了下述的方式。

module.exports = {
  networks: {
    development: {
      host: "localhost",
      port: 8545,
      network_id: "*"
    },
    staging: {
      host: "localhost",
      port: 8546,
      network_id: 1337
    },
    ropsten: {
      host: "158.253.8.12",
      port: 8545,
      network_id: 3
    }
  }
};

變化點如下:

  • 預設網路配置項rpc被移除了。取而代之的是在networks配置項下的一個專門的名為development的網路配置項。development中會指定的ipport,和配置的網路型別1,預設是任意*
  • 如果沒有指定網路,在執行migrate等命令時預設使用名為development的網路。
  • 為了避免出現部署到錯誤網路的情況,你也可以完全移除配置項development。如果已經移除development的網路配置,但在truffle migrate等命令時不指定網路會有下述錯誤發生:
$ truffle migrate
Compiling ./contracts/ConvertLib.sol...
Compiling ./contracts/MetaCoin.sol...
Compiling ./contracts/Migrations.sol...
Writing artifacts to ./build/contracts

Error: No network specified. Cannot determine current network.
    at Object.detect (/usr/local/lib/node_modules/truffle/lib/environment.js:27:23)
    at /usr/local/lib/node_modules/truffle/lib/commands/migrate.js:33:19
    at /usr/local/lib/node_modules/truffle/lib/contracts.js:51:11
    at /usr/local/lib/node_modules/truffle/lib/contracts.js:83:9

如果要指定網路,如ropsten,使用命令:

truffle migrate --network ropsten

另外每個網路配置項,都需要指定一個網路ID1,這可以算是一種安全機制來保證部署的安全性,來保證一定是部署到你想要部署的網路。development這樣的網路環境,可以使用*來表示任意,以方便調測。

移植和測試依賴(Migrations and Test Dependencies)

在沒有引入包管理前,Truffle可以假設所有你寫的智慧合約都是需要移植和測試的。在有了包管理之後,由於依賴可以來自多個不同的地方,我們不再進行預設關聯,如果需要關聯,你必須明確指定。以減少自動關聯帶來的潛在問題。下面來看一個migration(移植)的手動引入關聯的例子。

2.0版本中的./migrations/2_deploy_contracts.js

module.exports = function(deployer) {
  deployer.deploy(ConvertLib);
  deployer.autolink();
  deployer.deploy(MetaCoin);
};

3.0版本中的./migrations/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);
  deployer.deploy(MetaCoin);
};

改進之處如下:

  • 我們提供了artifacts.require()方法,類似Node.jsrequire()語句,用於宣告依賴。
  • 你可以通過artifacts.require()來引入一個本地的Solidity檔案。或者是包中依賴路徑,這個方法將負責引入對應的檔案。
  • Javascript中的測試用例也需要通過artifacts.require()語句來引入依賴。

Migrations(移植)不再提供autolink(自動關聯)

正如上一部分所述,自動關聯為依賴問題的解決帶來了一點點方便。但隨著包管理的引入,我們不再可能自動判斷在你的移植中所真正需要的所有的依賴。我們提供了替代方案是,通過明確指定的方式來關聯你的庫。可以參考上一節的artifacts.require()例子。

合約抽象:JSON格式(不再使用.sol.js!)

Truffle之前將合約定義為Javascript的格式,以.sol.js為字尾的檔案儲存。存為這樣的格式的考慮,主要是打算你可以在任何地方容易的使用。但最終發現,我們考慮的並不完善。不僅Javascript中存在一些情況中難以直接使用,更在非Javascript環境完全不可用。為解決這樣的侷限性,Truffle3.0將所有合約編譯後的結果存為JSON格式,以能隨時隨地的,跨環境使用。

如果你已經使用Truffle2.0生成了許多的檔案,我們提供了一個工具來進行轉換。

如果你要升級Truffle2.0生成的.sol.js,請先安裝truffle-soljs-updater

$ npm install -g truffle-soljs-updater

安裝好後,你可以通過sjsu命令來使用這個工具。使用方法如下:

$ cd ./build/contracts
$ sjsu
Converting ConvertLib.sol.js...
Converting MetaCoin.sol.js...
Converting Migrations.sol.js...
Files converted successfully.

具體使用方法是,進入到你的.sol.js檔案所在目錄,這裡是./build/contracts。然後執行sjsu即可。

預設情況下sjsu命令只會建立新格式的.json字尾結尾的檔案,並不會刪除你的原始檔案,你需要手動刪除舊的.sol.js來保證Truffle3.0能正常工作。請注意,建議刪除前將舊的.sol.js的檔案備份到其它地方。最終確認生成的.json檔案正確的情況下再刪除。

另外一種選擇是使用強制模式,sjsu -f,通過加-f引數,這樣sjsu會刪除舊的.sol.js檔案,並生成生的.json檔案。但請務必保證舊的檔案已提前進行了妥善的儲存,以避免造成不必要的損失。

$ cd ./build/contracts
//務必保證你已經備份了舊的`.sol.js`檔案
//`-f`引數會強制刪除舊的`.sol.js`檔案
$ sjsu -f
Converting ConvertLib.sol.js...
Converting MetaCoin.sol.js...
Converting Migrations.sol.js...
Files converted successfully.
Successfully deleted old .sol.js files.

合約互動抽象層: .deployed()現在提供了then()

這個改變,會影響你的Migrations(移植),測試,應用程式碼

在Truffle2.0中,合約抽象層使用原生的方式來管理網路。提供上面章節中提到的default的方式,引發潛在的網路不正確的可能,導致可能的部署到錯誤的網路的問題。

原抽象層提供了一個精心考慮,方便使用的語法,MyContract.deployed().myFunction(...),但將錯誤直接暴露給了開發者。在Truffle3.0中,我們改變了這個語法,為.deployed()提供了類似promise的語法。同時,當前的合約抽象層可以與以太坊的包管理標準,EIP190eip190,進行無縫整合。但這意味著大家必須改變原有程式碼的語法。

v2.0版本的語法:

MyContract.setProvider(someWeb3Provider);
MyContract.deployed().someFunction().then(function(tx) {
  
});

v3.0版本的語法:

MyContract.setProvider(someWeb3Provider);
MyContract.deployed().then(function(instance) {  
  return instance.someFunction();
}).then(function(result) {
  
});

語法上有些囉嗦,但目的是保證合約抽象層連線到了正確的網路。

我們來看看新語法的例子:

MyContract.setProvider(someWeb3Provider);

var deployed;

MyContract.deployed().then(function(instance) {
  deployed = instance;  
  return deployed.someFunction();
}).then(function(result) {
  return deployed.anotherFunction();
}).then(function(result) {
  
});

這裡需要注意的是

  • 如果合約方法f()不會改變區塊鏈上的資料的,那麼呼叫時要使用instance.f.call()
  • 如果合約方法f()會改變區塊鏈上的資料的,則需使用instance.f()來進行呼叫。
  • 如果你想在一個promise鏈中呼叫多個方法,需要注意示例中的instance的作用域。可以在外部定義一個物件,檢視上例中var deployed;的定義。

一個能跑通的完整例子參考附錄2

合約抽象層:交易結果物件

大家一直以來有個麻煩的事,關於在Web3中監聽事件。在大多數情況下,事件一般不是通過主動跟蹤事件,而是我們進行了對應的操作,從而引發對應的事件產生。

儘管主動監聽事件的方式仍然支援,但為了讓後一種情況實現起來更加簡單,我們調整了transaction的返回結果。

在Truffle2.0版本中,transaction簡單的返回了一個交易的雜湊串,而在Truffle3.0中,我們將返回一個包含交易詳情的結果物件2

v2.0

MyContract.deployed().someFunction().then(function(tx) {
  
});

v3.0

MyContract.deployed().then(function(instance) {
  deployed = instance;  
  return deployed.someFunction();
}).then(function(result) {
  //
});

在Truffle3.0中通過返回transaction的詳情資訊,我們可以更加方便的決定我們是否要觸發某些相應的行為。下面是一個假想的關注合約釋出事件的例子。

var assert = require("assert");
var PackageIndex = artifacts.require("PackageIndex.sol");

contract("PackageIndex", function(accounts) {

  it("publishes a release correctly", function() {
    return PackageIndex.deployed().then(function(deployed) {
      return deployed.publish("v2.0.0");
    }).then(function(result) {
      
      

      var found_published_event = false;

      for (var i = 0; i < result.logs.length; i++) {
        var log = result.logs[i];

        if (log.event == "ReleasePublished") {
          found_published_event = true;
          break;
        }
      }

      assert(found_published_event, "Uh oh! We didn't find the published event!")
    });
  });

});

雖然這樣的話,我們需要在所有返回結果中,去找我們感興趣的事件,但也許是比PackageIndex.ReleasePublished.watch(...)更好的一種方式,因為我們可以在當前的程式碼中管理事件邏輯,而不是分散在程式碼各處。

一個能跑通的完整例子參考附錄2

構建流:預設不再需要構建器

Truffle1.0和Truffle2.0中,我們將Web應用與整個框架緊緊的捆綁在一起。所以Truffle打包提供了一個預設的構建流,以便你可以快速的建立你的dapp,並執行起來。雖然在有些情況下,這帶來了極大的便利,但在其它的場景下卻顯得極為雞肋。

去年中,基於以太坊的應用持續的在增加。從開始的僅僅支援Webdapp應用,發展為可以支援原生語言,在手機或電腦上獨立執行。支援各種各樣的使用者場景,一直是Truffle的初衷,所以我們決定移除預設的構建流。如果你想使用Truffle來整合你的應用,你仍然可以編寫自定義的構建流3,因為Truffle打算專注做智慧合約相關的最好用工具。所以關於構建,通過整合更好工具,如webpackbrowserifyGruntMetalsmith,來實現。

儘管構建流預設被移除了,但並不意味著你沒得選擇。在Truffle中我們非常注重開發者的使用體驗,也永遠不會對大家棄之不顧,所以下面我們提供了兩種可選方式,後一種選擇會讓你深度繫結到你最終選擇的構建工具上,如webpack。我們來一起看看,可用的選擇吧。

在Truffle3.0中使用舊的構建器

如果你在Truffle2.0中使用了預設的構建器,而你又想升級到Truffle3.0。我們升級了預設構建器4,所以它完全可以與Truffle3.0相容。但這將是最後一次升級預設的構建器,因為要使之相容所有的場景,將是一件工程非常複雜的事。所以我們推薦你最終選用後面提到的其它構建系統。

預設構建器,並未整合在Truffle3.0中。所以要使用它,你需要安裝truffle-default-builder,並做為一個依賴進行引入。在你的工程目錄執行下面的命令:

$ npm install truffle-default-builder --save

一旦安裝,你可以在你的truffle.js配置檔案中,使用預設構建器。我們來看看配置檔案的前後變化。

v2.0:Truffle.js

module.exports = {
  build: {
    "index.html": "index.html",
    "app.js": [
      "javascripts/app.js"
    ],
    "app.css": [
      "stylesheets/app.css"
    ],
    "images/": "images/"
  },
  rpc: {
    host: "localhost",
    port: 8545
  }
};

v3.0: truffle.js

var DefaultBuilder = require("truffle-default-builder");

module.exports = {
  build: new DefaultBuilder({
    "index.html": "index.html",
    "app.js": [
      "javascripts/app.js"
    ],
    "app.css": [
      "stylesheets/app.css"
    ],
    "images/": "images/"
  }),
  networks: {
    development: {
      host: "localhost",
      port: 8545,
      network_id: "*" // Match any network id
    }
  }
};

你會發現,除了需要引入作為依賴的預設構建器包,放入構建器需要的配置檔案以外。其它是完全一致的。

由於預設構建器已經使用了最新的合約抽象層5。所以上面提到的所有版本升級需要進行的調整,都適用使用預設構建器。

使用自定義構建流程/構建工具

自定義構建流程並沒有想像中難以使用,真正的難點在於寫一個構建流程能相容各種各樣的構建需求。我推薦你去看看適合你自己專案的構建工具,比如之前提及的webpackbrowserifyGruntMetalsmith。最終哪個的特效能滿足你的需要,需要視你的專案及構建需求來定。

無論你想構建一個瀏覽器應用,命令列工具,JS庫,還是原生 的手機應用。合約的初始化,部署合約的使用均遵循通用的流程。

當你配置你的自定義工具或應用時,應遵循下述的流程:

  • 編譯合約檔案,將生成的.json結果放到./build/contracts目錄下。
  • 通過truffle-contract5,將你編譯的合約結果,轉為合約抽象層,來方便你的使用。
  • 為你的合約抽象層設定web3 provider。需要注意的是在MetamaskMist中,環境內會自動提供。但在其它情況下,你需要手動通過配置完成指定。

下面是整合NodeJS的一個例子:

//1. 引入編譯好的合約檔案結果 
var json = require("./build/contracts/MyContract.json");

// 2. 將合約轉為合約抽象層例項
var contract = require("truffle-contract");
var MyContract = contract(json);

// 3. 設定合約抽象層例項的web3 provider
MyContract.setProvider(new Web3.providers.HttpProvider("http://localhost:8545"));

// 4. 現在你可以使用啦
MyContract.deployed().then(function(deployed) {
  return deployed.someFunction();
});

所有的構建流程,均遵循上述的流程。核心關注點是保證自定義流程要載入所有的合約資源,並正確的設定合約抽象層。

一個能跑通的完整例子參考附錄2

相關文章