Truffle 是一个以太坊智能合约集成开发框架。Truffle 使用以太坊虚拟机(EVM)为区块链提供了世界级的开发环境、测试框架和资产管道,旨在让开发人员的工作更轻松。本文我们从一个 Truffle 官方示例入手,带你快速了解利用 Truffle 开发区块链项目的基础知识,包括 Truffle 项目的创建,智能合约的编译与部署,以及 Truffle 与智能合约进行交互实现区块链上的交易。

项目依赖

  • Truffle:区块链开发框架
  • Ganache:本地区块链开发节点
  • Node.js:JavaScript 运行环境
  • solc:Solidity 编译器
  • web3.js:以太坊 API 接口

安装

本文我们只介绍 Truffle 的安装,其它依赖包的安装请参考对应的安装文档。

通过下面的命令安装 Truffle:

npm install -g truffle

通过下面的命令确认是否已正确安装:

truffle version

控制台输出内容如下:

Truffle v5.6.7 (core: 5.6.7)Ganache v7.5.0Solidity - 0.8.13 (solc-js)Node v16.15.0Web3.js v1.7.4

通过下面的命令查看 Truffle 帮助:

truffle help

创建项目

首先,创建一个 Truffle 项目。

本文我们使用 Truffle 的官方示例 MetaCoin Box 来创建i项目。

通过下面的命令创建项目:

mkdir metacoincd metacointruffle unbox metacoin

以上命令在 metacoin 目录下创建了这个 Truffle 项目。

我们来看一下这个项目的目录结构:

  • contracts/:Solidity 合约目录
  • migrations/:部署脚本目录
  • tests/:测试文件目录
  • truffle-config.js:Truffle 配置文件

配置文件

配置文件位于项目根目录下,文件名为 truffle-config.js。

配置文件内容如下:

module.exports = {networks: {development: {host: "127.0.0.1", // Localhost (default: none)port: 7545,// Standard Ethereum port (default: none)network_id: "*", // Any network (default: none)}},compilers: {solc: {version: "0.8.13",// Fetch exact version from solc-bin}}};

这个配置文件中,development 是我们要部署的区块链配置,这里指我们本地的 Ganache 区块链节点。如果需要部署到其它区块链节点,请修改此配置文件。

智能合约

合约源码

我们来看一下合约的源码,文件名是 MetaCoin.sol。

pragma solidity ^0.8.13;import "./ConvertLib.sol";contract MetaCoin {mapping (address => uint) balances;event Transfer(address indexed _from, address indexed _to, uint256 _value);constructor() {balances[tx.origin] = 10000;}function sendCoin(address receiver, uint amount) public returns(bool sufficient) {if (balances[msg.sender] < amount) return false;balances[msg.sender] -= amount;balances[receiver] += amount;emit Transfer(msg.sender, receiver, amount);return true;}function getBalanceInEth(address addr) public view returns(uint){return ConvertLib.convert(getBalance(addr),2);}function getBalance(address addr) public view returns(uint) {return balances[addr];}}

这个合约包含一个构造函数、还定义了一个事件和三个函数。

  • constructor():合约的构造函数,在合约部署时自动存入一些 token 到发起者的账户。
  • event Transfer(...):事件。转账交易时触发。
  • sendCoin(...):将 token 从一个账户转账到另一个账户。
  • getBalanceInEth(...):取经转换后的账户余额(账户余额*2)。
  • getBalance(...):取账户余额(单位:1wei)。

测试合约

这个示例定义了两个测试文件,保存在 test 目录下:

  • metacoin.js 用 JavaScript 编写的测试脚本
  • TestMetaCoin.sol:用 Solidity 编写的测试脚本

Truffle 自带一个自动化测试框架,可以轻松测试合约,而不需要我们事先部署合约。

通过下面的命令运行所有测试:

truffle test

控制台输出如下:

Compiling your contracts...===========================> Compiling .\contracts\ConvertLib.sol> Compiling .\contracts\MetaCoin.sol> Compiling .\test\TestMetaCoin.sol> Compiling truffle\Assert.sol> Compiling truffle\AssertAddress.sol> Compiling truffle\AssertAddressArray.sol> Compiling truffle\AssertBalance.sol> Compiling truffle\AssertBool.sol> Compiling truffle\AssertBytes32.sol> Compiling truffle\AssertBytes32Array.sol> Compiling truffle\AssertGeneral.sol> Compiling truffle\AssertInt.sol> Compiling truffle\AssertIntArray.sol> Compiling truffle\AssertString.sol> Compiling truffle\AssertUint.sol> Compiling truffle\AssertUintArray.sol> Compiling truffle\DeployedAddresses.sol> Artifacts written to C:\Users\zhang\AppData\Local\Temp\test--19216-dTWx8M04rlBN> Compiled successfully using: - solc: 0.8.13+commit.abaa5c0e.Emscripten.clangTestMetaCoin√ testInitialBalanceUsingDeployedContract (93ms)√ testInitialBalanceWithNewMetaCoin (52ms)Contract: MetaCoin√ should put 10000 MetaCoin in the first account (43ms)√ should call a function that depends on a linked library (49ms)√ should send coin correctly (100ms)5 passing (5s)

你也可以指定特定的文件进行测试,如:

truffle test .\test\metacoin.jstruffle test .\test\TestMetaCoin.sol

经测试无误后的合约,我们就可以将合约编译,然后部署到区块链中。

编译合约

通过下面的命令编译合约:

truffle compile

控制台输出信息如下:

Compiling your contracts...===========================> Compiling .\contracts\ConvertLib.sol-bin. Attempt #1> Compiling .\contracts\MetaCoin.sol> Artifacts written to D:\work\blockchain\truffle-demo\metacoin\build\contracts> Compiled successfully using: - solc: 0.8.13+commit.abaa5c0e.Emscripten.clang_ Fetching solc version list from solc-bin. Attempt #1

编译后在根目录下自动生成 build\contracts 目录。编译后生成的 artifacts(我们翻译为”工件”或”构件”)将被放置在该目录下,文件名后缀为 .json,对应于合约名称(非文件名称)。

部署合约

部署合约的脚本位于根目录下的 migrations 目录下。

文件名: 1_deploy_contracts.js

内容如下:

const ConvertLib = artifacts.require("ConvertLib");const MetaCoin = artifacts.require("MetaCoin");module.exports = function(deployer) {deployer.deploy(ConvertLib);deployer.link(ConvertLib, MetaCoin);deployer.deploy(MetaCoin);};

在执行部署之前,确保已启动测试区块链,如 Ganache 本地区块链。

接下来,我们运行下面的命令来部署合约:

truffle migrate

控制台输出信息如下:

Starting migrations...======================> Network name:'development'> Network id:5777> Block gas limit: 6721975 (0x6691b7)1_deploy_contracts.js===================== Replacing 'ConvertLib' ---------------------- > transaction hash:0x626b0b2024f1de281a250f4c63d16cbaec056033235bc9f92e734ed42397787b > Blocks: 0Seconds: 0 > contract address:0x9D97751e2091147E4DFE61D1384951ac89A3f886 > block number:7 > block timestamp: 1669880418 > account: 0x1665135C6681A43ee2F08b681A4bD8a9Eb37fDc0 > balance: 99.96252516 > gas used:157568 (0x26780) > gas price: 20 gwei > value sent:0 ETH > total cost:0.00315136 ETH Linking ------- * Contract: MetaCoin  Library: ConvertLib (at address: 0x9D97751e2091147E4DFE61D1384951ac89A3f886) Replacing 'MetaCoin' -------------------- > transaction hash:0xfecbdb6d763a91d1cc909c14b2e778edc0c0fdad4c3e1b6a696ee33d2443785d > Blocks: 0Seconds: 0 > contract address:0x3E330cBe1Bc04766a8F83203277f349e58B243CD > block number:8 > block timestamp: 1669880419 > account: 0x1665135C6681A43ee2F08b681A4bD8a9Eb37fDc0 > balance: 99.95423528 > gas used:414494 (0x6531e) > gas price: 20 gwei > value sent:0 ETH > total cost:0.00828988 ETH > Saving artifacts ------------------------------------- > Total cost:0.01144124 ETHSummary=======> Total deployments: 2> Final cost:0.01144124 ETH

合约交互

启动控制台

合约部署完成后,我们就可以通过 Truffle 控制台与合约进行交互。

首先,我们启动 Truffle 控制台。命令如下:

truffle console

此命令连接到您当前配置的区块链节点,连接成功后进入控制台命令模式,如下:

truffle(development)>

我们来验证一下连接是否成功。执行下面的 web3.js API 接口来获取区块链账户:

truffle(development)> web3.eth.getAccounts()

输出如下:

['0x1665135C6681A43ee2F08b681A4bD8a9Eb37fDc0','0x28A71881645084c58eF7F82861b7ebd76BbAa75a','0xED1C82D205570735582150D8ebCdc576b0a18039','0xC81e32BaE8f99b324593A18CD72F6fd6d6AEB780','0x0FFF006AE1d7d2B88E196D45a5026Da6D7883aE5','0x868d62F3813Cf93d0654F87fb3a0dcCB2DA0cCab','0x926f0ca47f625082D0920308C1979EcEeE9c4A20','0x9B50CD8FBD95D9808762D398bd6923694a79b488','0x68c23E5Cc4b7d45ccd432f7D3E8E75Bf4A0BDFAA','0x00dd9aeBF04C842EC7005b3d7Bbf7f4bC7180d0b']

以上为 Ganache 区块链中的测试账户。说明已经成功连接到 Ganache。

与合约交互

根据合约源文件,我们知道该合约除了构造函数之外,还有三个方法(getBalancegetBalanceInEthsendCoin。下面我们来看实际的交互过程。

1. 合约抽象

首先,在与合约交互之前,我们先获取合约抽象实例。代码如下:

truffle(development)> let instance = await MetaCoin.deployed()truffle(development)> instance

2. 获取账户

通过下面的代码获取账户列表:

truffle(development)> let accounts = await web3.eth.getAccounts()truffle(development)> accounts

3.获取余额

调用函数 getBalance 获取账户余额。代码如下:

truffle(development)> let balance = await instance.getBalance(accounts[0])truffle(development)> balance.toNumber()10000

4. 获取余额(经转换后)

调用函数 getBalanceInEth 获取经转换后的账户余额。代码如下:

truffle(development)> let balance2 = await instance.getBalanceInEth(accounts[0])truffle(development)> balance2.toNumber()20000

5. 转账交易

调用函数 sendCoin 发起转账交易。代码如下:

truffle(development)> let result = await instance.sendCoin(accounts[1], 10)truffle(development)> result

以上转账从 accounts[0] 转账到 accounts[1],数量为 10 个 token。

result` 内容如下:

{tx: '0xb669c2bc68bf0c05d449b0acc85c50baa6a634744484b8cde507fd66f34dd337',receipt: {transactionHash: '0xb669c2bc68bf0c05d449b0acc85c50baa6a634744484b8cde507fd66f34dd337',transactionIndex: 0,blockHash: '0x8f3d6d378a067a8c255d90779beaf0e9a8625311bf0a031b5983b0306f4429fc',blockNumber: 9,from: '0x1665135c6681a43ee2f08b681a4bd8a9eb37fdc0',to: '0x3e330cbe1bc04766a8f83203277f349e58b243cd',gasUsed: 52485,cumulativeGasUsed: 52485,contractAddress: null,logs: [ [Object] ],status: true,......

我们来验证一下转账是否成功:

truffle(development)> let new_balance = await instance.getBalance(accounts[0])truffle(development)> new_balance.toNumber()9990truffle(development)> let new_balance2 = await instance.getBalance(accounts[1])truffle(development)> new_balance2.toNumber()10

6. 捕获事件

转账的同时会触发事件,我们来看一下事件的日志内容:

truffle(development)> result.logs[0]

控制台输出内容如下:

{logIndex: 0,transactionIndex: 0,transactionHash: '0xb669c2bc68bf0c05d449b0acc85c50baa6a634744484b8cde507fd66f34dd337',blockHash: '0x8f3d6d378a067a8c255d90779beaf0e9a8625311bf0a031b5983b0306f4429fc',blockNumber: 9,address: '0x3E330cBe1Bc04766a8F83203277f349e58B243CD',type: 'mined',id: 'log_ccd64ce1',event: 'Transfer',args: Result {'0': '0x1665135C6681A43ee2F08b681A4bD8a9Eb37fDc0','1': '0x28A71881645084c58eF7F82861b7ebd76BbAa75a','2': BN { negative: 0, words: [Array], length: 1, red: null },__length__: 3,_from: '0x1665135C6681A43ee2F08b681A4bD8a9Eb37fDc0',_to: '0x28A71881645084c58eF7F82861b7ebd76BbAa75a',_value: BN { negative: 0, words: [Array], length: 1, red: null }}}