【区块链开发入门】(四) Truffle详解篇1

由于本篇篇幅较长,因此转为两篇文章。Truffle详解篇篇2请见:link
目录导航页
【区块链开发入门】(一) 以太坊的搭建与运行
【区块链开发入门】(二) 以太坊的编程接口
【区块链开发入门】(三) Solidity合约编译、部署
【区块链开发入门】(四) Truffle详解篇1
【区块链开发入门】(四) Truffle详解篇2

一、什么是Truffle

Truffle是针对基于以太坊的Solidity语言的一套开发框架。本身基于Javascript。Truffle是一个世界级的开发环境,测试框架,以太坊的资源管理通道,旨在让以太坊上的开发变得简单,Truffle有以下:

  • 内置的智能合约编译,链接,部署和二进制文件的管理。
  • 利用快速开发的自动合约测试。
  • 可脚本化的,可扩展的部署与发布框架。
  • 网络管理,用于部署到任意数量的公网或私网的网络环
  • 支持持续集成可配置构建管道。
  • 与合约直接通信的直接交互控制台(写完合约就可以命令行里验证了)。
  • 可配的构建流程,支持紧密集成。
  • 在Truffle环境里支持执行外部的脚本。
  • 提供了合约抽象接口,可以直接通过var instance = Storage.deployde();拿到合约对象,然后在Javascript中直接操作对于的合约函数。原理是使用了基于web3.js封装的Ether Pudding工具包。
  • 提供了控制台,使用框架构建后,可以直接在命令行调用输出结果,可极大地方便开发和调试。

当开发基于Truffle的应用时,推荐使用EthereumJS TestRPC。它是一个完整的运行在内存中的区块链,仅存在于开发设备上。它在执行交易时是实时返回的,而不用等待默认的出块时间,这样可以快速验证新写的代码。当出现错误时,也能即时得到反馈。它同时还是一个支持自动化测试的功能powerful的客户端。最好使用TestRPC客户端进行充分测试后,再使用Geth,Parity,ruby-ethereum等以太坊全客户端。这些是完整的客户端实现,包括挖矿、网络、区块以及交易的处理,基于Truffle的应用可以在不需要额外配置的情况下发布到这些客户端。

二、安装Truffle

通过如下命令安装Truffle:

$ sudo npm install -g truffle

或者

//把npm的包源设置为淘宝的镜像,先安装cnpm
$ sudo npm install -g cnpm --registry=https://registry.npm.taobao.org
$ sudo cnpm install -g truffle

我用的是第二个命令安装(第一个命令我的安装不了Truffle也不知什么情况)。但是还是遇到问题如下:
在这里插入图片描述解决办法:就是更新node。

$ sudo npm install n -g //安装n这个工具,n这个工具是用于更新node版本的工具
$ sudo n stable //安装最新稳定版的nodejs
$ node -v //查看node的版本是否更新了

安装完Truffle之后,执行下面的命令确保其被正确安装了。

$ sudo truffle version
Truffle v5.1.55 (core: 5.1.55)
Solidity v0.5.16 (solc-js)
Node v14.15.1
Web3.js v1.2.9

三、创建并初始化项目

通过如下命令创建并初始化项目:

$ mkdir myproject
$ cd myproject
$ sudo truffle init
Starting init...
================

> Copying project files to /home/yulin/myproject

Init successful, sweet!

初始化完成后的目录结构如下:
在这里插入图片描述

  • contracts/:存放编写的合约。
  • migrations/:存放迁移部署脚本。
  • test/:存放合约测试脚本。
  • truffle-config.js:Truffle的配置文件。

四、创建合约

进入contracts目录,创建Storage.sol合约文件。

$ cd contracts
$ touch Storage.sol

合约的内容如下:

pragma solidity ^0.7.5;
contract Storage {
    
    
    uint256 public storedData;
    function set(uint256 data) public {
    
    
        storedData = data;
    }
    function get() public returns (uint256) {
    
    
        return storedData;
    }
}

五、编译合约

使用truffle compile命令编译Storage.sol合约。

遇到问题:
我的solidity版本是0.7.5。错误提醒我的truffle和solc编译器不匹配。在这里插入图片描述
做法:是在truffle-config.js修改成以下内容即可!
在这里插入图片描述

$ truffle compile

Compiling your contracts...
===========================
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/Storage.sol
> Compilation warnings encountered:

    /home/yulin/myproject/contracts/Storage.sol:7:5: Warning: Function state mutability can be restricted to view
    function get() public returns (uint256) {
    
    
    ^ (Relevant source part starts here and spans across multiple lines).
> Artifacts written to /home/yulin/myproject/build/contracts
> Compiled successfully using:
   - solc: 0.5.16+commit.9c3226ce.Emscripten.clang

可以看出,合约编译后的文件会写入/build/contracts目录中,这些合约编译后的文件对于truffle框架能否正常工作至关重要。不用手动修改这些文件,因为即使修改了,再次执行编译命令时又会被覆盖。

Truffle默认只编译自上次编译后被修改过的合约,目的是减少不必要的编译。如果想全部编译,可以使用–all选项。

$ truffle compile --all

合约编译完成后,需要部署Storage.sol合约,在truffle中部署合约需要用到迁移脚本。接着进入migrations目录中为Storage合约创建一个迁移脚本。

六、迁移合约

迁移脚本是由一些Javascript文件组成的,用来帮助把合约发布到以太坊网络中。之所以需要迁移脚本,是因为部署需求会随着时间改变。随着项目的发展,可以创建新的迁移脚本把这些变化的合约部署到区块链上,之前运行的迁移历史记录,会被一个特殊的Migrations.sol合约记录在区块链上。

迁移脚本的命名规则:文件名以数字开头,以一个描述性的后缀结尾。数字前缀是必需的,用于记录移植是否成功。后缀仅是为了提高可读性,以方便理解。

$ cd migrations
$ touch 2_storage_migration.js

文件为2_storage_migration.js:

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

1. arifacts.require()

在迁移脚本开始时,通过arifacts.require()告诉truffle将要和哪个合约交互。这个方法类似于欧NodeJs中的require,但在这里,它返回的是一个合约抽象。可以在迁移脚本的其余部分中使用这个合约抽象。arifacts.require()中使用的名字不是必须与合约源文件的文件名相同,相反,它应该与在合约源代码中定义的合约类的名称不同。

2. module.exports

在迁移脚本最后,通过module.exports导出一个函数,将迁移脚本导出的函数都应该接受一个deploy对象作为其第一个参数。deployer对象中的辅助函数在部署过程中有了清晰的语法,用于部署智能合约及执行一些常见的任务,比如把发布后的对象保存下来供以后使用。这个deployer对象部署任务的主接口。

至此准备工作已经做好,接下来就把Storage.sol部署到区块链上。这里将合约部署到TestRPC环境中。(因为我使用testrpc出现了问题,故我将合约部署到Ganache-cli环境中)。
启动Ganache-cli:

$ ganache-cli
Ganache CLI v6.12.1 (ganache-core: 2.13.1)

Available Accounts
==================
(0) 0x817b21276A314E454D8911b72F2e3eEDc7A4590b (100 ETH)
(1) 0xED2B6Ac7441D9514f979c4e026CEfEc5BD554c3c (100 ETH)
(2) 0x6262E43D56784D5488Ae2b50eD55d534dF306424 (100 ETH)
(3) 0xfBfb2280F9Db37ED10f0c6086299fAC806B89Bc3 (100 ETH)
(4) 0xCfF55d2682d9385614F4ADf4329825df4c2bB960 (100 ETH)
(5) 0x51A6cc07f8b7F2238B4CA17488Fa8B07fb4635C8 (100 ETH)
(6) 0xAccBDE3D4A1D6B9f8149eD264248943B098c98e6 (100 ETH)
(7) 0x3CC022c183B23cc5c7820F7a4892CEa58b056f2A (100 ETH)
(8) 0xEa5680CAf6F18e0B3Dd8b09AABaea368815b97Bf (100 ETH)
(9) 0x08753E3F5e58B5A108898CEa2cb6388a16b1B26a (100 ETH)

Private Keys
==================
(0) 0x32193d3d58fe2162180a4df59630d0ebbcfc9b8a15cca75051ab7487d7f7661d
(1) 0x5729a0dd9403b4253e24b0b8061bde8a649b3dda7e6e40c62f77278ad744626b
(2) 0xd65e049ca3fc405c7904478befcb70503324a8019ab32f82a75eb0b5d494bc1c
(3) 0xafd67abb1872ada2ad298bc1b99970a9ce21394dcab21a5a4007e8bd56f99607
(4) 0x5c0e84154d50a103908f8640647be38e38c1edd1dd0be001e7257242b96351b8
(5) 0xed9ea4b5f9b69eba09e408db3d4341c6754bbc1d3cd7b66a61c31018736203bd
(6) 0x5d0bb0859a2937b75484ad610ab880702e85f19104f21603680c66a0c76a5208
(7) 0x2909705b13a26498ad3deb1f2fbb2c65ed4d8df018eee5061dc2f76ca997b48d
(8) 0x695d539a3a5e2ffb35a36dfb78a01a58fff883cdf08f39209a7a652c8fd1189d
(9) 0x8d24adf5c1456d6e6d7c1b37f7c7a5036efeb39b799e8a234c0b864fefc38d32

HD Wallet
==================
Mnemonic:      hub cloth nice artist wall sail decline rice endorse differ area camera
Base HD Path:  m/44'/60'/0'/0/{
    
    account_index}

Gas Price
==================
20000000000

Gas Limit
==================
6721975

Call Gas Limit
==================
9007199254740991

Listening on 127.0.0.1:8545

ganache-cli启动成功后,回到myproject项目的目录中,执行迁移命令:

$ truffle migrate

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.



Starting migrations...
======================
> Network name:    'development'
> Network id:      1610168223472
> Block gas limit: 6721975 (0x6691b7)


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > transaction hash:    0xf3696426daea0a2c20148014fbbf793de1a09360c7dbbee2138c03e38dd8fe76
   > Blocks: 0            Seconds: 0
   > contract address:    0x64F97533057e353B267f1A4AD6E9085E755B1715
   > block number:        1
   > block timestamp:     1610172425
   > account:             0x817b21276A314E454D8911b72F2e3eEDc7A4590b
   > balance:             99.99626074
   > gas used:            186963 (0x2da53)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00373926 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00373926 ETH


2_storage_migration.js
======================

   Deploying 'Storage'
   -------------------
   > transaction hash:    0x1967eb25296ca2cd359d0a5242e6d5f8cb293ea3d73fde84ad064c2b5caa79ba
   > Blocks: 0            Seconds: 0
   > contract address:    0xe4379c94723384f2536f7424bC7f286630922e1c
   > block number:        3
   > block timestamp:     1610172429
   > account:             0x817b21276A314E454D8911b72F2e3eEDc7A4590b
   > balance:             99.99329598
   > gas used:            105903 (0x19daf)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00211806 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00211806 ETH


Summary
=======
> Total deployments:   2
> Final cost:          0.00585732 ETH

truffle migrate命令会执行所有位于migrations目录内的所有迁移文件。truffle migrate只会执行新创建的迁移。可以使用–reset来重新执行全部迁移脚本。

$ truffle migrate --reset

3. 初始化迁移合约

在之前有提到一个特殊的合约Migration.sol,这里详细了解一下这个合约。为了使用迁移功能,Truffle要求要有一个迁移合约。这个合约必须包含一个特定的接口,对于大多数项目来说,这个合约只会在第一次做迁移的时候被部署,以后都不会做任何的更改了。当第一次使用truffle init来创建一个项目的时候,它会默认创建这个合约。文件名为contracts/Migration.sol。

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.8.0;

contract Migrations {
    
    
  address public owner = msg.sender;
  uint public last_completed_migration;

  modifier restricted() {
    
    
    require(
      msg.sender == owner,
      "This function is restricted to the contract's owner"
    );
    _;
  }

  function setCompleted(uint completed) public restricted {
    
    
    last_completed_migration = completed;
  }
}

为了利益迁移的特性,首先必须部署Migration.sol合约。维持创建一下的迁移脚本。名为migrations/1_initial_migration.js。

const Migrations = artifacts.require("Migrations");

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

要部署其他合约,可以使用递增数字变换前缀来创建新的迁移脚本。

4. 部署器(deployer)

迁移脚本会使用deployer对象来组织部署任务。deployer对象会同步执行部署任务,隐藏可以按顺序编写部署任务。

//先部署A,再部署B
deployer.deploy(A);
deployer.deploy(B);

另外,deployer上的每一个函数都会返回一个promise,通过promise可以把有执行顺序依赖关系的部署任务组成队列。

//先部署A,然后部署B,把A部署后的地址传给B
deployer.deploy(A).then(function(){
    
    
	return deployer.deploy(B, A.address);
});

5. deployer API

deployer对象中包含许多方法,可以用来简化迁移工作。
(1)deployer.deploy(CONTRACT, ARGS…,OPTIONS)
这个API是用来部署合约的。contract参数传入需要部署的合约名字;args参数传入合约的构造函数需要的参数;options是一个可选参数,它的值是{overwrite:true/false}。如果overwrite被设置成false,那么当合约之前已经部署过了,这个deployer就不会再部署这个合约了,这在一个合约的依赖由一个外部合约地址提供的情况下是有用的。

为了快速进行部署多个合约,可以向deployer.deploy(…)函数中传入一个或多个数组。例子:

// 部署单个合约,不带任何构造参数
deployer.deploy(A);

// 部署单个合约带有构造参数
deployer.deploy(A, arg1, arg2, ...);

// 部署多个合约,一些带构造参数,一些不带构造参数.
// 比写3次 `deployer.deploy()` 语句更快, 因为deployer可以把所有的合约部署都一次性打包提交
deployer.deploy([
	[A, arg1, arg2, ...],
	B,
	[C, arg1]
]);

// 外部依赖的例子:
//
// overwrite: false 表示,如果 SomeDependency 合约之前已经被部署过,那么不在重新部署,直接使用之前已部署好的地址
// 如果我们的合约要运行在自己的测试链上,或者将要运行的链上没有SomeDependency合约,
// 那么把overwrite: false改成overwrite: true,表示不在检查之前SomeDependency有没有部署过,一律覆盖部署。
deployer.deploy(SomeDependency, {
    
    overwrite: false});

(2) deployer.link(LIBRARY, DESTINATIONS)把一个已部署好的库链接到一个或多个合约里。destinations 可以传入一个合约,也可以传入一组合约。 如果 destinations中的某个合约不依赖这个库,那deployer 的link函数就会忽略这个合约。

// 部署库LibA,然后把LibA 链接到合约B,然后部署合约B.
deployer.deploy(LibA);
deployer.link(LibA, B);
deployer.deploy(B);

//库LibA链接到多个合约
deployer.link(LibA, [B, C, D]);

(3) deployer.then(function() {…})在迁移过程中使用它调用特定合约的函数来部署新的合约,为已部署的合约做一些初始化工作等。例子:

var a, b;
deployer.then( function() {
    
    
	// 部署合约A的一个新版本到网络上
	return A.new();
}).then( function(instance) {
    
    
	a=instance;
	// 获取已部署的合约B的实例
	return B.deployed();
}).then( function(instance) {
    
    
	b=instance;
	// 使用合约B的setA()方法设置A的地址的新实例.
	return b.setA(a.address);
});

(4) 网络相关在执行迁移时,迁移脚本会把truffle.js里配置的networks传递给你,你可以在module.exports导出函数中第二个参数位置接受这个值。文件:truffle.js:

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

在迁移脚本中接收network:

module.exports=function(deployer, network) {
    
    
	if(network=="live") {
    
    
		// 当不在"live"的网络上的时候,做一些特定的操作.
	} else{
    
    
		// 当在的时候,做一些其他的操作.
	}
}

(5) 可用的账户在执行迁移时,迁移脚本会把当前以太坊客户端或web3.provider中可用的账户列表传递给你,这个列表与web3.eth.getAccounts()返回的账户列表完全一样。你可以在module.exports导出函数中第三个参数位置接受这个值。

module.exports=function(deployer, network, accounts) {
    
    
//在你的迁移脚本中使用账户
}

猜你喜欢

转载自blog.csdn.net/weixin_44029550/article/details/110530722
今日推荐