第04课:Truffle

第04课:Truffle 构建 DApp 应用

在上一篇中,我们实现了一个简单的 Hello 合约,并通过 solc 编译器将合约代码编译后,部署在私有链 Ganache 上。本篇将介绍通过 Truffle 框架来构建自动编译、部署合约代码。

Truffle 框架

Truffle 是基于 Solidity 语言的一套开发框架,它简化了去中心化应用(DApp)的构建和管理流程。本身是采用 Javascript 编写,支持智能合约的编译、部署和测试。

Truffle 开发框架提供了很多功能,简化了我们的开发、编译、部署与调试过程:

  • 内置了智能合约编译、链接、部署和二进制文件的管理;
  • 方便快速开发的合约自动化测试;
  • 方便扩展的、脚本化的部署与发布框架;
  • 方便的网络管理功能,不论是公有网络还是私有网络;
  • 基于 ERC190 标准,使用 EthPM & NPM进行依赖包管理;
  • 内置控制台功能。项目构建后,可以直接在命令行调用输出结果,方便了开发调试;
  • 可配的构建流程,支持持续集成;
  • 支持外部脚本的执行。

接下来,我们将通过 Truffle 框架来构建 Hello 合约的编译、部署过程。

Truffle安装

npm install -g truffle

安装好后,查看一下版本信息:

truffle version

本篇示例基于如下版本:

Truffle v4.0.1 (core: 4.0.1)
Solidity v0.4.18 (solc-js)

初始化项目

Truffle 提供了很多项目模板,可以快速搭建一个去中心化应用的代码骨架。我们使用 webpack 项目模板来构建 Hello 合约。在 smartcontract 目录下,执行如下命令:

truffle unbox webpack

初始化项目的时候,它会创建运行一个完整 DApp 所需的文件和目录。我们将 strings.solHello.sol 两个合约文件移动到 contracts 目录下,并删除 contracts 目录下原有的 ConvertLib.solMetaCoin.sol 文件(Migrations.sol 合约用来管理应用合约的部署,因此请勿删除)。目录结构如下:

smartcontract
  ├── app
  ├── contracts
          ├── Hello.sol
          ├── Migrations.sol
          └── strings.sol
  ├── migrations
          ├── 1_initial_migration.js
        └── 2_deploy_contracts.js
  ├── node_modules
  ├── package.json
  ├── test
  ├── truffle.js
  └── webpack.config.js

修改部署脚本

目录 migrations(迁移的意思) 非常重要。Truffle 使用该目录下的脚本来管理合约的部署。在上一篇中,我们是通过自己编写编译部署脚本 deploy.js ,并在 Node 控制台中运行来将 Hello 合约部署到区块链上的。有了 Truffle,以后再也不用这么做了。

第一个脚本 1_initial_migration.js 的作用是向区块链部署 Migrations 合约。

这个合约的作用是存储并跟踪已经部署的最新合约。每次运行脚本时,Truffle 就会向区块链查询获取已部署好的合约,然后部署新的合约。部署完成后,这个脚本会更新 Migrations 合约中的 last_completed_migration 字段指向最新部署的合约。

我们可以简单地把 Migrations 合约当成是一个数据库表,字段 last_completed_migration 总是保持最新状态。

我们来修改第二个脚本 2_deploy_contracts.js

//artifacts对象为truffle框架提供
//artifacts.require()方法与Node中的require()方法类似
//编译合约代码。自动调用solc编译器来编译合约代码并返回编译结果对象
var stringsContract = artifacts.require("./strings.sol");
var HelloContract = artifacts.require("./Hello.sol");

//部署器对象deployer为truffle框架提供
module.exports = function(deployer) {
  //部署string.sol合约
  deployer.deploy(stringsContract);
  //将已部署的strings合约类库连接到Hello合约
  deployer.link(stringsContract, HelloContract);
  //部署Hello.sol合约
  deployer.deploy(HelloContract);
};

代码不难,加上了注释很容易理解。

修改 Truffle 配置

部署脚本修改完后,我们还需要在配置文件中声明要连接的以太坊节点地址,这里使用 Ganache 的地址 http://localhost:7545

require('babel-register')
module.exports = {
  networks: {
    development: {
      host: 'localhost',
      port: 7545,
      network_id: '*'
    }
  }
}

注意 development 关键字。Truffle支持将合约部署到多个区块链网络,例如开发网络、私有网络、测试网或公网。 在上面的配置中,我们只定义了一个用于开发的网络。

编译合约

smartcontract 目录下执行 truffle compile 命令,终端输出如下:

Compiling ./contracts/Hello.sol...
Compiling ./contracts/Migrations.sol...
Compiling ./contracts/strings.sol...
Writing artifacts to ./build/contracts

可以看到,contracts 目录下的三个合约文件都编译了,并且在当前目录下生成了 build/contracts 目录,同时也产生了三个文件:

smartcontract/build/contracts
    ├── Hello.json
    ├── Migrations.json
    └── strings.json

这三个都是 abi 文件(abi 概念的解释,可以翻看第一篇文章 《理解以太坊相关概念》)。

部署合约

合约编译成功后,就可以部署了。在 smartcontract 目录下执行部署命令 truffle migrate ,可以看到终端上输出了部署日志:

部署日志

关键信息可以标注出来。我们也可以在 Ganache 中的 LOGS 面板中查看 transaction id 和 合约地址:

Ganache日志

合约调用

合约部署成功后,我们可以通过 truffle console 命令进入控制台。在控制台里调用刚才部署的合约:

$ truffle console
truffle(development)> Hello.deployed().then(function(contractInstance){contractInstance.say.call('Guys').then(function(result){console.log(result)})})
truffle(development)> Hello Guys

需要注意的是,Truffle 的所有调用都会返回 promise (Node 语法),所以每个响应都被包裹在 then() 函数里。

通过 Truffle 框架进行合约的编译、部署与调用就到此介绍完了。和之前手动编写编译部署脚本及调用脚本的方式相比,是不是更方便快捷?而且代码量更少更精炼。下面来介绍网页如何与智能合约进行交互。

编写网页

首先我们需要编写一个网页。打开 smartcontract/app/index.html 文件,修改 head 区代码,如下:

<head>
  <title>Hello - Truffle</title>
  <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
  <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
  <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
  <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
  <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
  <script src="./app.js"></script>
</head>

修改 body 区代码,如下:

<body>
  <div class="alert alert-success" role="alert" style="display:none;" id="message-box"></div>
  <div class="form-group">
    <input type="text" class="form-control" id="username" aria-describedby="basic-addon1">
  </div>
  <button type="submit" id="submit" class="btn btn-success">调用合约</button>
  <button type="submit" id="cancel" class="btn btn-default">重置</button>
</body>

网页呈现效果如下图所示:

demo

修改启动脚本

smartcontract/app/javascripts/app.js 脚本文件是智能合约与网页交互的核心,修改代码如下:

//导入CSS
import "../stylesheets/app.css";
//导入web3和truffle-contract库
import { default as Web3} from 'web3';
import { default as contract } from 'truffle-contract'

//导入Hello合约的ABI文件
import Hello_artifacts from '../../build/contracts/Hello.json'
//获取Hello合约对象
var HelloContract = contract(Hello_artifacts);

window.App = {
  init: function() {
    //设置web3连接
    HelloContract.setProvider(web3.currentProvider);
  },

  //封装合约中的say()方法调用过程,供javascript调用
  say: function(name, callback){
    //instance为Hello合约部署实例
    HelloContract.deployed().then(function(instance){
      //调用Hello合约中的say()方法,并传入参数name
      instance.say.call(name).then(function(result){
        //将返回结果传入回调函数
        callback(null, result);
      });
    }).catch(function(e){
      console.log(e, null);
    });
  }
};

window.addEventListener('load', function() {
  //设置web3连接  http://127.0.0.1:7545 为Ganache提供的节点链接
  window.web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:7545"));
  App.init();
});

前面的篇幅中,我们提到过 web3 模块是以太坊提供的工具包,主要用于与合约的通信*。从上面的代码中,我们可以看到,通过 Hello 合约的 ABI 文件获取到合约对象之后,再配合 web3 工具,就可以与 Hello 合约进行通信了。其中 App.say() 是对 Hello 合约中 say() 方法的调用过程进行了封装,方便前端代码进行调用。

智能合约与网页交互

启动脚本修改完了之后,就需要与网页进行整合。我们再返过来继续修改 smartcontract/app/index.html 文件。在文件末尾添加如下交互代码:

<script>
  $(document).ready(function(){
      $('#submit').on('click', function(){
          var username = $('#username').val();
          if(typeof username == 'undefined' || username == false){
              alert('不能为空');
          }else {
                //调用App的say()方法
              window.App.say(username, function (err, result) {
                  if(err){
                      alert(err);
                  }else {
                      $('#username').val(username);
                      $('#message-box').html(result);
                      $('#message-box').show();
                  }
              });
          }
      });
      $('#cancel').on('click', function(){
          $('#username').val('');
          $('#message-box').hide();
          $('#message-box').html('');
      });
  });
</script>

接下来,就是见证奇迹的时刻。

smartcontract 目录下执行 npm run dev

npm run dev

注意图中标识的部分。http://locahost:8083 为网页访问地址(每个人运行的端口号可能不一样)。我们也能看出,Truffle 开发框架集成了 Webpack 工具,对网页中包含的静态资源文件进行了打包。

最后,我们通过浏览器打开 **http://locahost:8083 ** 来测试下效果:

Hello

通过 Truffle 构建了一个简单的 DApp 应用示例就此完成了。我们也熟练掌握了通过 Truffle 开发框架编写合约代码、快速编译部署、构建 DApp 的整个过程。

上一篇
下一篇
内容互动
写评论
加载更多
评论文章