以太坊Solidity之Truffle的使用流程与集成指南|猿创征文

╰つ栺尖篴夢ゞ
iOS领域优质创作者
博客专家认证
2022-07-04 06:20:50

一、Truffle 简介

① 什么是 Truffle ?

- Truffle 是一个世界级的开发环境,测试框架,以太坊的资源管理通道,致力于让以太坊上的开发变得简单。

- Truffle 有以下:

- - 内置的智能合约编译,链接,部署和二进制文件的管理;

- - 快速开发下的自动合约测试;

- - 脚本化的,可扩展的部署与发布框架;

- - 部署到不管多少的公网或私网的网络环境管理功能;

- - 使用 EthPM&NPM 提供的包管理,使用 ERC190 标准;

- - 与合约直接通信的直接交互控制台;

- - 可配的构建流程,支持紧密集成;

- - 在 Truffle 环境里支持执行外部的脚本。

② 环境要求和安装

- 环境要求:

- - NodeJS 5.0+;

- - Windows,Linux,或 Mac OS X。

- 安装方式:

```cpp

$ npm install -g truffle

```

- Truffle 需要以太坊客户端,需要支持标准的 JSON RPC API。

- 如果你是 Windows 用户,推荐使用 Powershell 或 [Git BASH](https://gitforwindows.org/) 来安装和使用 Truffle 框架。

③ Truffle 开发工具

- 当开发基于 Truffle 的应用时,推荐使用 [EthereumJS TestRPC](https://github.com/trufflesuite/ganache-cli-archive),它是一个完整的在内存中的区块链仅仅存在于开发的设备上。它在执行交易时是实时返回,而不等待默认的出块时间,这样你可以快速验证你新写的代码,当出现错误时,也能即时反馈给你。它同时还是一个支持自动化测试的功能强大的客户端,Truffle 充分利用它的特性,能将测试运行时间提速近 90%。

- 使用 TestRPC 客户端充分测试后,可是尝试使用正式发布的客户端:[Geth (go-ethereum)](https://github.com/ethereum/go-ethereum)、[WebThree(cpp-ethereum)](https://github.com/ethereum/webthree-umbrella)、[More](https://ethereum.org/en/developers/),这些是完整的客户端实现,包括挖矿,网络,块及交易的处理,Truffle 可以在不需要额外配置的情况下发布到这些客户端。

二、Truffle 工程创建

- 创建工程目录,可以使用文件浏览器或使用下面的命令在命令行创建一个目录:

```cpp

$ mkdir myproject

```

- 接下来,通过下面的命令初始化一个 Truffle 工程:

```cpp

$ truffle

Truffle v3.4.11 - a development framework for Ethereum

$ cd myproject

$ truffle init

```

- 完成后,将拥有如下目录:

- - app/ - 应用文件运行的默认目录,这里面包括推荐的 javascript 文件和 css 样式文件目录,但可以完全决定如何使用这些目录;

- - contract/ - Truffle 默认的合约文件存放地址;

- - migrations/ - 存放发布脚本文件 ;

- - test/ - 用来测试应用和合约的测试文件;

- - truffle.js - Truffle 的配置文件。

- truffle init 会默认创建一个构建在以太坊内的代币 demo 应用:METACOIN,可以使用这个工程来进行快速的学习,或者也可以删除这些文件来创建一个自己的工程。

三、编译合约

① 合约位置

- 所有合约应该位于 ./contracts 目录,默认提供一个合约文件,一个库文件,均以 .sol 结尾作为示例。

- 尽管库文件有一定的特殊性,但为简单起见,当前均称之为合约。

② 命令

- 要编译合约,使用:

```cpp

truffle compile

```

- Truffle 仅默认编译自上次编译后被修改过的文件,来减少不必要的编译。如果想编译全部文件,可以使用 --compile-all 选项:

```cpp

truffle compile --compile-all

```

③ 约定

- Truffle 需要定义的合约名称和文件名准确匹配。

- 举例来说,如果文件名为 MyContract.sol,那么合约文件须为如下两者之一:

```cpp

contract MyContract {

  ...

}

// or

library MyContract {

  ...

}

```

- 这种匹配是区分大小写的,也就是说大小写也要一致,推荐大写每一个开头字母。

④ 依赖

- 可以通过使用 import 来声明依赖,Truffle 将会按正确顺序依次编译合约,并在需要的时候自动关联库。

⑤ 编译目录

- 编译的输出位于 ./build/contracts 目录,如果目录不存在会自动创建。

- 编译文件对于 Truffle 框架能否正常工作至关重要,不应该在正常的编译或发布以外手动修改这些文件。

四、移植

① 移植目的

- 移植是由一些 Javascript 文件组成来协助发布到以太坊网络,主要目的是用来缓存你的发布任务,它的存在基于你的发布需求会改变的前提。

- 当你的工程发生了重要的改变,你将创建新的移植脚本来将这些变化带到区块链上,之前运行移植的历史记录通过一个特殊的 Migrations 合约来记录到链上。

② 命令

- 执行移植,使用下述命令:

```cpp

truffle migrate

```

- 如果你之前的移植是成功执行的,这个命令会执行所有的位于 migrations 目录内的移植脚本,truffle migrate 仅会执行新创建的移植。

- 如果没有新的移植脚本,这个命令不同执行任何操作,可以使用选项 --reset 来从头执行移植脚本。

③ 移植脚本文件

- 一个样例文件如下:文件名:4_example_migration.js

```cpp

module.exports = function(deployer) {

  // deployment steps

  deployer.deploy(MyContract);

};

```

- 需要注意的是文件名以数字开头,一个描述性的后缀结尾,数字前缀是必须的,用于记录移植是否成功,后缀仅是为了提高可读性,以方便理解。

- 移植 js 里的 exports 的函数接受一个 deployer 对象作为第一个参数,这个对象用于发布过程,提供了一个清晰的语法支持,同时提供一些通过的合约部署职责,比如保存发布的文件以备稍后使用。deployer 对象是用来缓存(stage)发布任务的主要操作接口。

- 像所有其它在 Truffle 中的代码一样,Truffle 提供了自己代码的合约抽象层(contract abstractions),并且进行了初始化,以方便可以便利的与以太坊的网络交互,这些抽象接口是发布流程的一部分。

④ 初始移植

- Truffle 需要一个移植合约来使用移植特性,这个合约内需要指定的接口,但可以按你的意味修改合约。对大多数工程来说,这个合约会在第一次移植时进行的第一次部署,后续都不会再更新。

- 通过 truffle init 创建一个全新工程时,获得一个默认的合约:文件名:contracts/Migration.sol:

```cpp

contract Migrations {

  address public owner;

 

  // A function with the signature `last_completed_migration()`, returning a uint, is required.

  uint public last_completed_migration;

 

  modifier restricted() {

    if (msg.sender == owner) _

  }

  function Migrations() {

    owner = msg.sender;

  }

 

  // A function with the signature `setCompleted(uint)` is required.

  function setCompleted(uint completed) restricted {

    last_completed_migration = completed;

  }

 

  function upgrade(address new_address) restricted {

    Migrations upgraded = Migrations(new_address);

    upgraded.setCompleted(last_completed_migration);

  }

}

```

- 如果想使用移植特性,必须在第一次部署合约时,部署这个合约,可以使用如下方式来创建一次移植:文件名:migrations/1_initial_migrations.js:

```cpp

module.exports = function(deployer) {

  // Deploy the Migrations contract as our only task

  deployer.deploy(Migrations);

};

```

- 由此,可以接着创建递增的数字前缀来部署其它合约。

⑤ 部署器(deployer)

- 移植文件会使用部署器来缓存部署任务,因此可以按一定顺序排列发布任务,它们会按正确顺序执行:

```cpp

// Stage deploying A before B

deployer.deploy(A);

deployer.deploy(B);

```

- 另一选中可选的部署方式是使用 Promise,将部署任务做成一个队列,是否部署依赖于前一个合约的执行情况:

```cpp

// Deploy A, then deploy B, passing in A's newly deployed address

deployer.deploy(A).then(function() {

  return deployer.deploy(B, A.address);

});

```

- 如果想更清晰,也可以选择实现一个 Promise 链。

- 可以根据发布到的网络的具体情况进行不同的部署流程,要实现不同条件的不同部署步骤,移植代码中需要第二个参数 network。示例如下:

```cpp

module.exports = function(deployer, network) {

  // Add demo data if we're not deploying to the live network.

  if (network != "live") {

    deployer.exec("add_demo_data.js");

  }

}

```

- 指定一个网络:大多数 Truffle 提供的命令根据指定的网络不同而表现不同,会使用对应网络下的合约和配置信息,可以通过 --network 选项在参数上进行控制:

```cpp

$ truffle migrate --network live

networks: {

  development: {

    host: "localhost",

    port: 8545,

    network_id: "*" // match any network

  },

  live: {

    host: "178.25.19.88", // Random IP for example purposes (do not use)

    port: 80,

    network_id: 1,        // Ethereum public network

    // optional config values

    // gas

    // gasPrice

    // from - default address to use for any transaction Truffle makes during migrations

  }

}

```

- 在上面这个例子中,Truffle 会在 live 网络中进行移植,如果配置如上述配置示例的 Example 所指定的内容的话,是最终在以太坊网络上进行部署。

⑥ 部署 API

(A)DEPLOYER.DEPLOY(CONTRACT, ARGS...)

- 发布一个指定的合约,第一参数是合约对象,后面是一些可选的构造器参数。这个函数适用于单例合约,它只会在 dapp 中只创建一个这个合约的实例(单例),函数会在部署后设置合约的地址(如:C ontract.address 将等于新的部署地址),它将会覆盖之前存储的地址。也可以传入一个合约数组,或数组的数组来加速多合约的部署。

- 需要注意的是如果库的地址可用,deploy 会自动为这个部署的合约联接任何需要的库,因此如果合约依赖某个库,应该先部署这个库:

```cpp

// Deploy a single contract without constructor arguments

deployer.deploy(A);

 

// Deploy a single contract with constructor arguments

deployer.deploy(A, arg1, arg2, ...);

 

// Deploy multiple contracts, some with arguments and some without.

// This is quicker than writing three `deployer.deploy()` statements as the deployer

// can perform the deployment as a batched request.

deployer.deploy([

  [A, arg1, arg2, ...],

  B,

  [C, arg1]

]);

```

(B)DEPLOYER.LINK(LIBRARY, DESTINATIONS)

- 联接一个已经发布的库到一个或多个合约,destinations 可以是一个合约或多个合约组成的一个数组,如果目标合约并不依赖这个库,部署器会忽略掉这个合约。

- 这对于在 dapp 中不打算部署的合约(如:非单例)但却需要在使用前先联接的情况下非常有用。

```cpp

// Deploy library LibA, then link LibA to contract B

deployer.deploy(LibA);

deployer.link(LibA, B);

 

// Link LibA to many contracts

deployer.link(LibA, [B, C, D]);

```

(C)DEPLOYER.AUTOLINK(CONTRACT)

- 关联合约依赖的所有库,这需要所依赖的库已经部署,或在其前一步部署:

```cpp

// Assume A depends on a LibB and LibC

deployer.deploy([LibB, LibC]);

deployer.autolink(A);

```

- 另外可以省略参数来调用函数 autolink(),这会自动关联合约依赖的所有库,需要保证在调用这个函数前,所有被需要的库已经部署:

```cpp

// Link *all* libraries to all available contracts

deployer.autolink();

```

(D)DEPLOYER.THEN(FUNCTION() {...})

- Promise 语法糖,执行做生意的部署流程:

```cpp

deployer.then(function() {

  // Create a new version of A

  return A.new();

}).then(function(instance) {

  // Set the new instance of A's address on B.

  var b = B.deployed();

  return b.setA(instance.address);

});

```

(E)DEPLOYER.EXEC(PATHTOFILE)

- 执行 truffle exec 做为部署的一部分:

```cpp

// Run the script, relative to the migrations file.

deployer.exec("../path/to/file/demo_data.js");

```

五、构建应用

① 默认构建

- Truffle 集成了默认的构建来方便使用,但也许不适合每个项目,所以也许需要其它的来打包应用。默认的构造目标是 web 应用,但也可以很容易的转变为其它的构造流程,比如适用于命令行或库的流程。

- 默认构建有一些特性快速的开始:

- - 在浏览器内自动的初始化你的应用,包括引入你编译的合约,部署的合约信息,和以太坊客户端信息配置;

- - 包含常见的依赖,如 web3 和 Ether Pudding;

- - 内置支持 ES6 和 JSX;

- - SASS 支持;

- - Uglifyjs 支持。

② 配置

- 可以随间的修改默认的构建内容,原始的构建内容目录如下:

```cpp

app/

- javascripts/

  - app.js

- stylesheets/

  - app.css

- images/

- index.html

```

- 构建配置文件如下:

```cpp

{

  "build": {

    // Copy ./app/index.html (right hand side) to ./build/index.html (left hand side).

    "index.html": "index.html",

 

    // Process all files in the array, concatenating them together

    // to create a resultant app.js

    "app.js": [

      "javascripts/app.js"

    ],

 

    // Process all files in the array, concatenating them together

    // to create a resultant app.css

    "app.css": [

      "stylesheets/app.scss"

    ],

 

    // Copy over the whole directory to the build destination.

    "images/": "images/"

  }

}

```

- 配置文件中的配置键描述了最终的打包目标名称,右边的配置目录或文件数组则是要打包的目录的内容。打包过程根据文件扩展,将文件连接形成一个结果文件,并放到构建的目标位置。如果指定的是一个字符串而不是一个数组,这个字符串代指的文件如果需要会直接拷到对应的构建目录。如果字符串以“/”结尾,则会被识别为一个目录,整个目录会不经调整直接拷贝到对应的目录,所以的指定值都是默认相对于 /app 目录来指定的。

- 可以在任何时间改变配置和目录结构,并不强制要求需要 javascript 和 css 文件目录,所以删除构建配置文件中的对应配置就可以

- 特别注意:如果想默认构建在前端初始化你的应用,务必保证有一个构造目标 app.js,因为默认构建会将相关代码附加到这个文件,而不是其它文件。

③ 命令

- 要创建前端工程,执行:

```cpp

truffle build

```

④ 构建结果

- 构建结果存在 ./build 目录,所以合约文件则在对应的位置 ./build/contracts。

- 默认构建虽简单易用,但它仍有一些缺点:

- - 当前不支持 import,require 等,不能提供 browserify,Webpack 和 CommonJS 这样的工具,由此让依赖管理变得有些困难。

- - 这是一套自定义的构建系统,与其它流行构建系统不兼容;

- - 它可以扩展,但是自定义的方法和 API。

六、合约交互

① 读写数据

- 标准的与以太坊网络交互的方法是通过以太坊官方构建的 Web3 库。尽管这个库非常有用,但使用其提供接口与合约交互有些困难,特别是以太坊的新手。为降低学习曲线,Truffle 使用 Ether Pudding 库,它也是基于 Web3 的基础之上,目的是为了让交互更简单。

- 以太坊网络把在网络上读与写数据进行了区分,这个区分对于如何写程序影响很大。通常来说,写数据被称作交易(transaction),读数据被称作调用(call)。

- 对于交易与调用,它们分别有如下特性:

- - 交易(Transaction):交易本质上改变了整个以太坊网络的数据状态,一个交易可以是向另一个帐户发送ether(以太坊网络代币)这样的简单行为,也可以是执行合约函数,添加一个新合约到以太坊网络这样的复杂行为。交易的典型特征是写入(或修改)数据,交易需要花费 ether,也被称作 gas,交易的执行需要时间。当你通过交易执行一个合约的函数时,你并不能立即得到执行结果,因为交易并不是立即执行的。大多娄情况下,通过执行交易不会返回值;它会返回一个交易的 ID,总的来说,交易具有如下特征:

- - - 需要gas(Ether)

- - - 改变网络的状态

- - - 不会立即执行

- - - 不会暴露返回结果(仅有交易 ID)

- - 调用:则与上述的交易非常不同,调用可以在网络上执行代码,但没有数据会被改变(也许仅仅是些临时变量被改变),调用的执行是免费的,典型的行为就是读取数据。通过调用执行一个合约函数,你会立即得到结果。总的来说,调用具有如下特征:

- - - 免费(不花费 gas)

- - - 不改变网络状态

- - - 立即执行

- - - 有返回结果。

- 如果选择,取决于你想干什么,或者说想写数据,还是读数据。

② 接口(abstract)

- 为了来体验一下合约接口的作用,使用框架自带的默认 metacoin 的合约:

```cpp

import "ConvertLib.sol";

 

contract MetaCoin {

  mapping (address => uint) balances;

 

    event Transfer(address indexed _from, address indexed _to, uint256 _value);

 

    function MetaCoin() {

        balances[tx.origin] = 10000;

    }

 

    function sendCoin(address receiver, uint amount) returns(bool sufficient) {

        if (balances[msg.sender] < amount) return false;

        balances[msg.sender] -= amount;

        balances[receiver] += amount;

        Transfer(msg.sender, receiver, amount);

        return true;

    }

    function getBalanceInEth(address addr) returns(uint){

        return ConvertLib.convert(getBalance(addr),2);

    }

    function getBalance(address addr) returns(uint) {

        return balances[addr];

    }

}

```

- 合约有三个方法和一个构造方法,三个方法可以被执行为交易或调用。现在来看看 Truffle 和 Ether Pudding 提供的叫 MetaCoin 的 Javascript 对象,可以在前端中使用:

 

```cpp

// Print the deployed version of MetaCoin

console.log(MetaCoin.deployed());

 

// outputs:

//

// Contract

// - address: "0xa9f441a487754e6b27ba044a5a8eb2eec77f6b92"

// - allEvents: ()

// - getBalance: ()

// - getBalanceInEth: ()

// - sendCoin: ()

```

- 接口层提供了合约中以应的函数名,它还包含一个地址,指向到 MetaCoin 合约的部署版本。

③ 执行合约函数

(A)执行交易

- 在上述例子 MetaCoin 合约中,有三个可以执行的函数,如果对这三个函数稍加分析就会发现,只有 sendCoin 会对网络造成更改,sendCoin 函数的目标将 Meta Coin 从一个帐户发送到另一些帐户,这些更改需要被永久存下来。

- 当调用 sendCoin,我们将把它们作为一个交易来执行。如下所示,来演示下把 10 个币,从一个帐户发到另一个帐户,改变要永久的保存在网络上:

```cpp

var account_one = "0x1234..."; // an address

var account_two = "0xabcd..."; // another address

 

var meta = MetaCoin.deployed();

meta.sendCoin(account_two, 10, {from: account_one}).then(function(tx_id) {

  // If this callback is called, the transaction was successfully processed.

  // Note that Ether Pudding takes care of watching the network and triggering

  // this callback.

  alert("Transaction successful!")

}).catch(function(e) {

  // There was an error! Handle it.

})

```

- 分析:

- - 直接调用接口的 sendCoin 函数,最终是默认以交易的方式来执行的;

- - 交易被成功执行时,回调函数会直到交易被执行时才真正被触发,这样带来的一个好处是不用一直去检查交易的状态;

- - 对 sendCoin 函数传递了第三个参数,需要注意的是原始合约函数的定义中并没有第三个参数,这里是一个特殊的对象,用于编辑一些交易中的指定细节,它可以总是做为第三个参数传进,设置 from 的地址为 account_one。

(B)执行调用

- 继续用 MetaCoin 的例子,其中的 getBalance 函数就是一个很好的从网络中读取数据的例子,它压根不需要进行任何数据上的变更,它只是返回传入的地址的帐户余额,来简单看一下:

```cpp

var account_one = "0x1234..."; // an address

var meta = MetaCoin.deployed();

meta.getBalance.call(account_one, {from: account_one}).then(function(balance) {

  // If this callback is called, the call was successfully executed.

  // Note that this returns immediately without any waiting.

  // Let's print the return value.

  console.log(balance.toNumber());

}).catch(function(e) {

  // There was an error! Handle it.

})

```

- 分析:

- - 必须通过 .call() 来显示的向以太坊网络表明,并不会持久化一些数据变化;

- - 得到返回结果,而不是一个交易 ID,需要注意的是,以太坊网网络可以处理非常大的数字,我们被返回一个 BigNumber 对象,框架再将这个对象转化了一个 number 类型。

- 在上述的例子中将返回值转成了一个 number 类型,是因为例子中的返回值比较小,如果将一个 BigNumber 转换为比 javascript 支持的 number 最大整数都大,将会出现错误或不可预期的行为。

(C)捕捉事件(Catching Events)

- 合约可以触发事件,可以进行捕捉以进行更多的控制,事件 API 与 Web3 一样:

```cpp

var meta = MetaCoin.deployed();

var transfers = meta.Transfer({fromBlock: "latest"});

transfers.watch(function(error, result) {

  // This will catch all Transfer events, regardless of how they originated.

  if (error == null) {

    console.log(result.args);

  }

}

```

- METHOD:DEPLOYED():每一个抽象出来的合约接口都有一个 deployed() 方法,上述例子中,调用这个函数返回一个实例,这个实例代表的是之前部署到网络的合约所对应的抽象接口的实例:

```cpp

var meta = MetaCoin.deployed();

```

- 这仅对使用 truffle deploy 部署的合约,且一定是在 project configuration 中配置发布的才有效,如果不是这样,这个函数执行时会抛出异常。

- METHOD:AT():类似于 deployed(),可以通过一个地址来得到一个代表合约的抽象接口实例,当然这个地址一定是这个合约的部署地址:

```cpp

var meta = MetaCoin.at("0x1234...")

```

 

- 当你的地址不正确,或地址对应的合约不正确时,这个函数并不会抛出异常,但调用接口时会报错,请保证在使用 at() 时输入正确的地址。

- METHOD:NEW():可以通过这个方法来部署一个完全全新的合约到网络中:

```cpp

MetaCoin.new().then(function(instance) {

  // `instance` is a new instance of the abstraction.

  // If this callback is called, the deployment was successful.

  console.log(instance.address);

}).catch(function(e) {

  // There was an error! Handle it.

});

```

- 需要注意的是这是一个交易,会改变网络的状态。

七、测试合约

① 框架

- Truffle 使用 Mocha 测试框架来做自动化测试,使用 Chai 来做断言,这两个库的结合可能让人耳目一新,基于这两者之上,提供一种方式来编译简单和可管理的合约自动化测试用例。

- 测试文件应置于 ./tests 目录,Truffle 只会运行以 .js,.es,.es6 和 .jsx 结尾的测试文件,其它的都会被忽略。

② 测试用例

- 每个测试文件至少应该包含至少一个对 Mocha 的 describe() 函数的调用,另一种方式是使用 Truffle 自定义的 contract() 函数,作用类型 describe() 函数,但额外添加了一些特性:

- - 在每一个 contract() 函数执行前,合约都会重部署到以太坊客户端中,这样测试用例会在一个干净状态下执行;

- - contract() 函数支持传入多个可用的帐户做为第二个参数传入,可以用此来进行测试。

- 当需要与所写的合约进行交互时,可以使用 contract(),否则使用 describe() 函数。

③ 测试用例示例

- truffle init 命令提供一个简单的测试用例例子,它会先部署你的合约,然后执行在 it() 块中指定的测试用例:

```cpp

contract('MetaCoin', function(accounts) {

  it("should put 10000 MetaCoin in the first account", function() {

    // Get a reference to the deployed MetaCoin contract, as a JS object.

    var meta = MetaCoin.deployed();

 

    // Get the MetaCoin balance of the first account and assert that it's 10000.

    return meta.getBalance.call(accounts[0]).then(function(balance) {

      assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account");

    });

  });

});

```

- 需要注意的是在 contract() 函数的传入的 MetaCoin 仅仅因为用来展示,说明它是 MetaCoin 相关的测试,并无本质作用。

④ 合约

- Truffle 提供了接口抽象,方便与合约进行便捷交互,通过 var meta = MetaCoin.deployed() 这行,Truffl e设法保证可以在测试用例、前端、移植(Migration)中都能用这种方式与你自己写的合约进行交互。

⑤ 命令

- 要执行测试,执行下面的命令:

```cpp

truffle test

```

- 也可以对单个文件执行测试:

```cpp

truffle test ./path/to/test/file.js

```

八、控制台

- 有时在进行测试和 debug 时,或手动执行交易时与合约进行直接交互是需要的。Truffle 提供了一种更加简单的方式,通过交互式控制台来与你的那些准备好的合约进行交互。

- 启动控制台,使用:

```cpp

truffle console

```

- 这会使用默认网络来调起一个控制台,会自动连接到一个运行中的以太坊客户端,可以使用选项 --network 来修改这个特性。

- 当你加载了控制台,会看到下面的输出,default 是当前连接到的是默认网络:

```cpp

$ truffle console

truffle(default)>

```

- 控制台支持 Truffle 命令行支持的命令,比如可以在控制台中执行 migrate --reset,其效果与在命令行中执行 truffle migrate --reset 的效果一致。

- Truffle 的控制台额外增加如下特性:

- - 所有已经编译的合约都可用,就像在开发测试,前端代码中,或者移植代码中那样使用;

- - 在每个命令后,合约会被重新加载,如使用 migrate --reset 命令后,可以立即使用新分配的地址和二进制;

- - web3 库也可以使用,且也连到以太坊客户端;

- - 所有命令返回的 promise,会自动解析,直接打印出结果,可以不用输入 then(),简化了命令。如下:

```cpp

truffle(default)> MyContract.deployed().getValue.call();

```

九、外部脚本

- 也许会经常的执行外部脚本来与合约进行交互,Truffle 提供一个简单的方式来进行。

- 首先配置文件是 truffle.js,位于项目的根目录下,这个文件是 Javascript 文件,支持执行代码来创建配置,它必须导出一个对象,来代表项目配置。

- 当使用 Windows 的命令行时,默认的配置文件名与 truffle 冲突,这种情况下,推荐使用 Windows 的 power Shell 或 Git BASH,也可以将配置文件重命名为 truffle-config.js 来避免冲突:

```cpp

module.exports = {

  build: {

    "index.html": "index.html",

    "app.js": [

      "javascripts/app.js"

    ],

    "app.css": [

      "stylesheets/app.css"

    ],

    "images/": "images/"

  },

  rpc: {

    host: "localhost",

    port: 8545

  }

};

```

- BUILD:这个是前端的构建配置,默认调用默认构建器,但也可以自定的构建流程:

```cpp

build: {

  "index.html": "index.html",

  "app.js": [

    "javascripts/app.js"

  ],

  "app.css": [

    "stylesheets/app.css"

  ],

  "images/": "images/"

}

```

- NETWORKS:指定在移植(Migration)时使用哪个网络,当在某个特定的网络上编译或运行移植时,合约会缓存起来方便后续使用。当合约抽象层检查到你连到某个网络上时,它会使用这个这个网络上原有的缓存合约来简化部署流程,网络通过以太坊的 RPC 调用中的 net_version 来进行标识。

- networks 对象,通过一个网络名做为配置的键,值对应定义了其网络参数。networks 的对应选项不是必须的,但如果一旦指定,每个网络必须定义一个对应的 network_id,如果想指定一个默认网络,可以通过将 netword_id 的值标记为 default 来实现,当没有匹配到其它的网络时,就会使用默认网络。需要注意的是整个配置中,应该有且仅有一个 default 的网络。一般来说,默认网络主要用于开发,配置,合约等数据没有长期保存的需要,网络 ID 也会因 TestRPC 的重启而频繁改变时。

- 网络名称用于用户接口调用时使用,在移植中的使用方式如下:

```cpp

$ truffle migrate --network live

```

- 还可以选择性的指定 rpc 的配置信息:

```cpp

networks: {

  "live": {

    network_id: 1, // Ethereum public network

    // optional config values

    // host - defaults to "localhost"

    // port - defaults to 8545

    // gas

    // gasPrice

    // from - default address to use for any transaction Truffle makes during migrations

  },

  "morden": {

    network_id: 2,        // Official Ethereum test network

    host: "178.25.19.88", // Random IP for example purposes (do not use)

    port: 80

  },

  "staging": {

    network_id: 1337 // custom private network

    // use default rpc settings

  },

  "development": {

    network_id: "default"

  }

}

```

- 关于如何连接到以太坊客户端的一些细节,host 和 port 是需要,另外还需要一些其它的:

- - host:指向以太坊客户端的地址,本机开发时,一般为 localhost;

- - port:以太坊客户端接收请求的端口,默认是8545;

- - gas:部署时的 Gas 限制,默认是 4712388;

- - gasPrice:部署时的 Gas 价格,默认是 100000000000(100 Shannon);

- - from:移植时使用的源地址,如果没有指定,默认是以太坊客户端第一个可用帐户。

```cpp

rpc: {

  host: "localhost",

  port: 8545

}

```

- MOCHA:

```cpp

mocha: {

  useColors: true

}

```

- 启动合约,连上想要的网络,要执行外部(external)脚本,执行下述命令:

```cpp

$ truffle exec <path/to/file.js>

```

- 为了外部脚本能正常执行,Truffle 需要它们能通过 Javascript 的模块的方式导出一个函数,且有一个回调函数作为参数:

```cpp

module.exports = function(callback) {

  // perform actions

}

```

- 脚本内,可以执行你想要做的任何事,这个回调在脚本执行结束后被调用。回调函数只有一个参数,这个参数传的是错误状态,如果出现错误,整个执行会中止,并返回一个非 0 的退出码(exit code)。

 

十、工作流

① Truffle watch

- 监控文件系统的文件变化,重编译,重部署合约,在被修改后需要的时候,会重构建前端代码。

- 使用方法:

```cpp

truffle watch

```

② Truffle serve

- 监控文件系统的变化,重编译,部署,构建,并在 http://localhost:8080/ 提供服务。

- 使用方式:

 

```cpp

truffle serve

```

十一、Truffle命令指南

- 使用方式:

```cpp

truffle [command] [options]

```

① build

- 构建一个开发中的 app 版本,创建 .build 目录,可选参数 --dist:,仅在使用默认构造器时可用:

```cpp

truffle build

```

② console

- 运行一个控制台,里面包含已初始化,且随时可用的合约对象:

```cpp

truffle console

```

- 一旦控制台启动,可以使用通过命令行来使用你的合约,就像代码中那样,另外所有 Truffle 的列在这里的命令都可以在控制台使用。

- 可选参数:

- - --network 名称:指定要使用的网络;

- - --verbose-rpc:输出 Truffle 与 RPC 通信的详细信息。

③ compile

- 智能编译合约,仅会编译自上次编译后修改过的合约,除非另外指定强制刷新。

```cpp

truffle compile

```

- 可选参数:

- - --compile-all: 强制编译所有合约;

- - --network 名称:指定使用的网络,保存编译的结果到指定的网络上。

④ create:contract

- 工具方法使用脚手架来创建一个新合约,名称需要符合驼峰命名:

```cpp

$ truffle create:contract MyContract

```

⑤ create:test

- 工具方法,使用脚手架来创建一个新的测试方法,名称需要符合驼峰命名:

```cpp

$ truffle create:test MyTest

```

⑥ migrate

- 运行工程的移植:

```cpp

truffle migrate

```

- 可选的参数:

- - --reset: 从头运行所有的移植;

- - --network 名称:指定要使用的网络,并将编译后的资料保存到那个网络;

- - --to number:将版本从当前版本移植到序号指定的版本;

- - --compile-all:强制编译所有的合约;

- - --verbose-rpc:打印 Truffle 与 RPC 交互的详细日志。

⑦ exec

- 在 Truffle 的环境下执行一个 Javascript 文件,环境内包含 web3、基于网络设置的默认 provider,作为全局对象的你的合约对象,这个 Javascript 文件需要 export 一个函数,这样 Truffle 才可以执行:

```cpp

$ truffle exec /path/to/my/script.js

```

- 可选参数:--network 名称:名称:指定要使用的网络,并将编译后的资料保存到那个网络;

⑧ init

- 在当前目录下初始化一个全新的APP,一个全新的工程,会自带默认合约和前端配置:

```cpp

$ truffle init

```

⑨ list

- 列出所有可用的命令,与 --help 等同:

```cpp

truffle list

```

⑩ serve

- 在 http://localhost:8080 提供编译的 app 对应的服务,且在需要的时候自动构建,自动部署。与 truffle watch 类似,区别在于增加 web 服务器功能:

```cpp

truffle serve

```

- 可选参数:

- - -p port: 指定 http 服务的端口,默认是 8080;

- - --network 名称:名称:指定要使用的网络,并将编译后的资料保存到那个网络。

⑪ test

- 运行所有在 ./test 目录下的测试用例,或可选的运行单个测试文件:

```cpp

$ truffle test [/path/to/test/file]

```

- 可选参数:

- - --network 名称:指定要使用的网络,并将编译后的资料保存到那个网络;

- - --compile-all: 强制编译所有的合约;

- - --verbose-rpc:打印 Truffle 与 RPC 交互的详细日志。

⑫ version

- 输出版本号然后退出:

```cpp

truffle version

```

⑬ watch

- Watch 合约,APP 和配置文件变化,在需要时自动构建 APP:

```cpp

truffle watch

```

 

十二、Truffle3.0 集成 NodeJS

① 升级到 Truffle3.0

- 如果之前安装的是 Truffle2.0 版本,需要主动升级到 Truffle3.0,两者的语法变化有点大。由于 Truffle 是一个命令行工具,需要更新全局空间安装的 Truffle:

```cpp

$ sudo npm update -g truffle

```

- 注意需要 root 权限运行命令,否则可能会报错无足够的访问权限,导致升级失败。

- 安装成功后,可以通过版本命令查看当前的版本,如出现下述类似字样说明升级成功:

```cpp

$ truffle version

Truffle v3.1.2

```

② 初始化工程

- 创建一个新工程目录,使用 Truffle3.0 初始化工程,并在 truffle3 目录内初始化 Truffle 框架:

```cpp

$ mkdir truffle3 && cd truffle3

$ truffle init

Downloading project...

Project initialized.

 

  Documentation: http://truffleframework.com/docs

 

Commands:

 

  Compile: truffle compile

 

  Migrate: truffle migrate

  Test:    truffle test

```

- 创建了一个新工程目录 truffle3,进入到这个目录,使用 truffle init 命令,初始化一个全新的 Truffle 3.0 的工程,工程目录如下:

 

![在这里插入图片描述](https://img-blog.csdnimg.cn/387010d12e7e47d99c921cee3ffc2f85.jpeg#pic_center)

③ 集成 NodeJS

- truffle console 命令会默认集成 web3,合约抽象层,如果想要在自已的 NodeJS 环境使用 Truffle 合约,就要手动集成这两个模块。在集成前,需要创建工程的 npm 包管理环境,首先进入 truffle3 工程目录,使用 npm init 来初始化工程的 npm 包管理环境:

```cpp

$ npm init

This utility will walk you through creating a package.json file.

It only covers the most common items, and tries to guess sensible defaults.

 

See `npm help json` for definitive documentation on these fields

and exactly what they do.

 

Use `npm install <pkg> --save` afterwards to install a package and

save it as a dependency in the package.json file.

 

 

Press ^C at any time to quit.

name: (truffle3) TruffleForTest

Sorry, name can no longer contain capital letters.

name: (truffle3) truffle3

version: (1.0.0) 1.0.0

description: This is a sample project for integrate truffle 3.0 with nodejs.

entry point: (truffle.js) main.js

test command: truffle3

git repository:main.js

keywords: truffle3.0

author: TryBlockchain

license: (ISC)

About to write to /Users/TryBlockchain/develop/blockchain_workspace/truffle3/package.json:

 

{

  "name": "truffle3",

  "version": "1.0.0",

  "description": "This is a sample project for integrate truffle 3.0 with nodejs.",

 

  "main": "main.js",

  "directories": {

    "test": "test"

  },

  "scripts": {

    "test": "truffle3"

  },

  "keywords": [

    "truffle3.0"

  ],

  "author": "TryBlockchain",

  "license": "ISC"

}

 

 

Is this ok? (yes) yes

```

- 如果不进行npm init初始化,就进行后续模块安装,会报如下错误:

```cpp

$ npm install truffle-contract

/Users/TryBlockchain

└─┬ truffle-contract@1.1.10

  ├─┬ ethjs-abi@0.1.9

  │ ├── bn.js@4.11.6

  │ ├── js-sha3@0.5.5

  │ └─┬ number-to-bn@1.7.0

  │   ├── bn.js@4.11.6  deduped

  │   └─┬ strip-hex-prefix@1.0.0

  │     └── is-hex-prefixed@1.0.0

 

  ├── truffle-blockchain-utils@0.0.1

  ├─┬ truffle-contract-schema@0.0.5

  │ └── crypto-js@3.1.9-1

  └─┬ web3@0.16.0

    └── bignumber.js@2.0.7  (git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2)

 

npm WARN enoent ENOENT: no such file or directory, open '/Users/TryBlockchain/package.json'

npm WARN TryBlockchain No description

npm WARN TryBlockchain No repository field.

npm WARN TryBlockchain No README data

npm WARN TryBlockchain No license field.

```

- 由于没有包管理环境,所以对 package.json 的包依赖写入会失败,报错 npm WARN enoent ENOENT: no such file or directory, open '/Users/TryBlockchain/package.json'。要解决这个问题,需要使用 npm init 来初始化当前工程的包管理环境。

④ 安装 NodeJS 中用到的 Truffle 合约抽象层运行环境

- 这个工具是 Truffle 提供的,用于在 NodeJS 和浏览器中集成 Truffle 的合约抽象运行环境:

```cpp

$ npm install truffle-contract

truffle3@1.0.0 /Users/TryBlockchain/develop/blockchain_workspace/truffle3

└─┬ truffle-contract@1.1.10

  ├─┬ ethjs-abi@0.1.9

  │ ├── bn.js@4.11.6

  │ ├── js-sha3@0.5.5

  │ └─┬ number-to-bn@1.7.0

  │   ├── bn.js@4.11.6  deduped

  │   └─┬ strip-hex-prefix@1.0.0

 

  │     └── is-hex-prefixed@1.0.0

  ├─┬ truffle-blockchain-utils@0.0.1

  │ └─┬ web3@0.18.2

  │   ├── bignumber.js@2.0.7  (git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2)

  │   ├── crypto-js@3.1.8  deduped

  │   ├── utf8@2.1.2  deduped

  │   ├── xhr2@0.1.4

  │   └── xmlhttprequest@1.8.0  deduped

  ├─┬ truffle-contract-schema@0.0.5

  │ └── crypto-js@3.1.9-1

  └─┬ web3@0.16.0

    ├── bignumber.js@2.0.7  (git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2)

    ├── crypto-js@3.1.8

    ├── utf8@2.1.2

    └── xmlhttprequest@1.8.0

```

⑤ 安装 NodeJS 中用到的 Truffle 运行时需要的 web3 环境

```cpp

$ npm install web3

- bignumber.js@2.0.7 node_modules/web3/node_modules/bignumber.js

truffle3@1.0.0 /Users/TryBlockchain/develop/blockchain_workspace/truffle3

└── web3@0.18.2

```

- 使用 npm install web3 即可安装 web3 模块,如果没有集成 web3 环境,就跑相关的代码,可能会报下述错:

```cpp

$ node main.js

/Users/TryBlockchain/develop/blockchain_workspace/truffle3/main/main.js:4

var provider = new Web3.providers.HttpProvider("http://localhost:8545");

                   ^

 

ReferenceError: Web3 is not defined

    at Object.<anonymous> (/Users/TryBlockchain/develop/blockchain_workspace/truffle3/main/main.js:4:20)

    at Module._compile (module.js:571:32)

    at Object.Module._extensions..js (module.js:580:10)

    at Module.load (module.js:488:32)

    at tryModuleLoad (module.js:447:12)

    at Function.Module._load (module.js:439:3)

    at Module.runMain (module.js:605:10)

    at run (bootstrap_NodeJS:420:7)

    at startup (bootstrap_NodeJS:139:9)

    at bootstrap_NodeJS:535:3

```

⑥ 创建自己的合约文件

(A)新增加测试合约

- 在 truffle3/contracts 目录下创建测试合约文件 Test.sol:

 

```cpp

pragma solidity ^0.4.4;

 

contract Test{

    function f() returns (string){

        return "method f()";

    }

    function g() returns (string){

        return "method g()";

    }

}

```

- 提供两个函数,f() 和 g(),分别返回用于说明它们是哪个函数的返回结果的字符串说明。

(B)增加deploy配置

- 修改 migrations/2_deploy_contracts.js 为如下:

```cpp

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

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

var Test = artifacts.require("./Test.sol");

 

module.exports = function(deployer) {

  deployer.deploy(ConvertLib);

  deployer.link(ConvertLib, MetaCoin);

  deployer.deploy(MetaCoin);

  deployer.deploy(Test);

};

```

- 上述代码主要增加两行,一行为 var Test = artifacts.require("./Test.sol"); 声明一个新的合约文件实例并命名为 Test;增加的另一行内容 deployer.deploy(Test); 用于将 Test 进行部署。

(C)编译合约

- 使用 truffle migrate --reset 来强制重编译并发布所有合约,由于合约移植是懒编译的,如果发现已经发布过,且发布的版本号没有变化就不会再发布,所以使用 --reset。请务必弄清楚为何使用 --reset 再使用这个命令 2,运行 truffle migrate 前,需要确认节点处于运行状态:

```cpp

$ truffle migrate --reset

Using network 'development'.

 

Running migration: 1_initial_migration.js

 

  Replacing Migrations...

  Migrations: 0xdc59c5de4e7b1dcf23f864425a704020e53666b5

Saving successful migration to network...

Saving artifacts...

Running migration: 2_deploy_contracts.js

  Replacing ConvertLib...

  ConvertLib: 0x19cf958fede2e0f082cbcf5629f1a1344b221bf3

  Linking ConvertLib to MetaCoin

  Replacing MetaCoin...

  MetaCoin: 0x39073d502491f57537f999584071691d19cf5f24

  Replacing Test...

  Test: 0x8ca770415902e5a64ef53062b5ba85626c3dd5dc

Saving successful migration to network...

Saving artifacts...

```

⑦ 使用 NodeJS 集成 Truffle3.0 代码完整 DEMO

```cpp

var Web3 = require('web3');

var contract = require("truffle-contract");

 

var provider = new Web3.providers.HttpProvider("http://localhost:8545");

 

//使用truffle-contract包的contract()方法

//请务必使用你自己编译的.json文件内容

var Test = contract({

  "contract_name": "Test",

  "abi": [

    {

      "constant": false,

      "inputs": [],

      "name": "f",

      "outputs": [

        {

          "name": "",

          "type": "string"

        }

      ],

      "payable": false,

      "type": "function"

    },

    {

      "constant": false,

      "inputs": [],

      "name": "g",

      "outputs": [

        {

          "name": "",

          "type": "string"

        }

      ],

      "payable": false,

      "type": "function"

    }

  ],

  "unlinked_binary": "0x606060405234610000575b6101ff806100196000396000f300606060405263ffffffff60e060020a60003504166326121ff0811461002f578063e2179b8e146100bc575b610000565b346100005761003c610149565b604080516020808252835181830152835191928392908301918501908083838215610082575b80518252602083111561008257601f199092019160209182019101610062565b505050905090810190601f1680156100ae5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b346100005761003c61018e565b604080516020808252835181830152835191928392908301918501908083838215610082575b80518252602083111561008257601f199092019160209182019101610062565b505050905090810190601f1680156100ae5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080516020818101835260009091528151808301909252600a82527f6d6574686f642066282900000000000000000000000000000000000000000000908201525b90565b604080516020818101835260009091528151808301909252600a82527f6d6574686f642067282900000000000000000000000000000000000000000000908201525b905600a165627a7a72305820c238bd4de6aa330fcc88946b9948bc265c7ac1408dc5c8b7ee6e648413ae540f0029",

  "networks": {

    "1489826524891": {

      "events": {},

      "links": {},

      "address": "0x9db90af99faa32ed14dccfb19326e917efac456b",

      "updated_at": 1489827968151

    }

  },

  "schema_version": "0.0.5",

  "updated_at": 1489827968151

});

 

Test.setProvider(provider);

 

//没有默认地址,会报错

//UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): Error: invalid address

//务必设置为自己的钱包地址,如果不知道,查看自己的客户端启动时,观察打印到控制台的地址

Test.defaults({

  from : "0x299127d72e28cb92d09f856aaedeb139d1e7e74a"

});

var instance;

Test.deployed().then(function(contractInstance) {

  instance = contractInstance;

  return instance.f.call();

}).then(function(result){

  console.log(result);

  return instance.g.call();

}).then(function(result){

  console.log(result);

});

```

⑧ 集成示例

- 要在 NodeJS 中使用 Truffle,要先引入 web3:

```cpp

var Web3 = require('web3');

var provider = new Web3.providers.HttpProvider("http://localhost:8545");

 

// 省略了无关代码

// 合约初始化

var Test = contract(/*合约JSON*/);

 

// 设置连接

Test.setProvider(provider);

```

- 先通过 var Web3 = require('web3'); 引入依赖,并初始化一个实例 Web3,并为实例设置 HttpProvider。

- 要在 NodeJS 初始化 Truffle 编译好的合约,要使用 contract() 方法,将 Truffle3.0 编译后的 .json 文件,一般在 build/contracts/Test.json,将此文件的内容放入 contract() 的括号内:

```cpp

var contract = require("truffle-contract");

 

// 请使用使用Truffle3.0编译的.json文件内容

var Test = contract(/*参数是`JSON`对象,放入Truffle3.0生成的.json文件内容*/)

```

- 然后使用 .deployed() 或 at(/*某个地址*/)来进行调用 [trufflecontract]。

- 如果引入的 JSON 数据有问题,可能会看到下面的错误:

```cpp

$ node main.js

/Users/TryBlockchain/develop/blockchain_workspace/truffle3/node_modules/truffle-contract/node_modules/web3/lib/web3/contract.js:56

    contract.abi.filter(function (json) {

 

                ^

 

TypeError: Cannot read property 'filter' of undefined

    at addFunctionsToContract (/Users/TryBlockchain/develop/blockchain_workspace/truffle3/node_modules/truffle-contract/node_modules/web3/li

b/web3/contract.js:56:17)

    at ContractFactory.at (/Users/TryBlockchain/develop/blockchain_workspace/truffle3/node_modules/truffle-contract/node_modules/web3/lib/we

b3/contract.js:255:5)

    at TruffleContract.Contract (/Users/TryBlockchain/develop/blockchain_workspace/truffle3/node_modules/truffle-contract/contract.js:257:33

)

    at new TruffleContract (/Users/TryBlockchain/develop/blockchain_workspace/truffle3/node_modules/truffle-contract/contract.js:572:25)

    at Function.at (/Users/TryBlockchain/develop/blockchain_workspace/truffle3/node_modules/truffle-contract/contract.js:390:22)

    at Object.<anonymous> (/Users/TryBlockchain/develop/blockchain_workspace/truffle3/main/main.js:54:6)

    at Module._compile (module.js:571:32)

    at Object.Module._extensions..js (module.js:580:10)

    at Module.load (module.js:488:32)

    at tryModuleLoad (module.js:447:12)

```

- 如果出现了上述报错,请再次确认是否原封不动的把合约编译后的 .json 文件的内容,复制进 contract() 的括号内的,不要加任何东西,因为 .json 文件内就是一个 JSON 对象。

- truffle-contract 框架默认没有读取 coinbase 的默认地址,所以需要按如下方式主动设置:

```cpp

// 没有默认地址,会报错

// UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): Error: invalid address

Test.defaults({

  from : "0x299127d72e28cb92d09f856aaedeb139d1e7e74a"

});

```

- 否则会报错 UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): Error: invalid address。

- 对于新的 Truffle3.0 语法,需要注意的是函数调用方式要遵循调用规则,对于一个不会改写区块链状态的 f() 函数,使用 instance.f.call();;而对于一个会改写区块链状态的函数f(),使用 instance.f(),底层在实现调用上,会使用不同 gas 计算方式:

```cpp

var instance;

Test.deployed().then(function(contractInstance) {

  instance = contractInstance;

  return instance.f.call();

}).then(function(result){

  console.log(result);

  return instance.g.call();

}).then(function(result){

  console.log(result);

});

```

- 如果对于一个不会改写状态 f(),使用 instance.f() 会返回对应的交易状态结果:

```cpp

$ node main.js

{ tx: '0x2ac645310278d971e3911e8f880947105f582aa4eab3d3d66d14c95333391ac9',

  receipt:

 

   { transactionHash: '0x2ac645310278d971e3911e8f880947105f582aa4eab3d3d66d14c95333391ac9',

     transactionIndex: 0,

     blockHash: '0x69a6788032c7bef12d6d51bc045548fa9edb0665bb0fbcf9cf55d30f9744cd61',

     blockNumber: 29,

     gasUsed: 21803,

     cumulativeGasUsed: 21803,

     contractAddress: null,

     logs: [] },

  logs: [] }

{ tx: '0x65aa2c4da73ef5a17221c26e74b9b329bdc353856564f8d1f49c07f6dcd055ea',

  receipt:

   { transactionHash: '0x65aa2c4da73ef5a17221c26e74b9b329bdc353856564f8d1f49c07f6dcd055ea',

     transactionIndex: 0,

     blockHash: '0x837ec6a3df2cc4d9a8ccf8d77c14b88a13b0053a5149a74c1a984fe88a70eaa8',

     blockNumber: 30,

     gasUsed: 21825,

     cumulativeGasUsed: 21825,

     contractAddress: null,

     logs: [] },

  logs: [] }

```

...全文
673 1 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
1 条回复
切换为时间正序
请发表友善的回复…
发表回复
EargoChen 2022-07-07
  • 打赏
  • 举报
回复 1

挺好的,就是CSDN不支持markdown语法

344

社区成员

发帖
与我相关
我的任务
社区描述
这里是由区块链、以太坊、Web3.0、元宇宙等相关技术组成的国内最大的开发者聚集地,帮助社区成员快速获得更全面、更深度的技术信息,链接更多资源,让开发者更好地入门学习、成长与应用实践。
区块链开源 企业社区
社区管理员
  • ETHPlanet
  • kkkuntor
  • 活动助手
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

Hi,欢迎加入我们!

 

在这里你可以:

  • 获取最官方最新最全的区块链、以太坊、Web3.0学习内容与资源
  • 获取行业内更有价值的研讨会、公开课资源,或参与社区发起的主题活动课程
  • 获得专业的以太坊、区块链、Web3.0相关技术资深专家/讲师的回复或指导,突破学习瓶颈
  • 进行学习打卡、提问或回答问题,提高个人或在社区的影响力,将有机会与各大技术官方深度合作
  • 参与丰富的社区活动,获得更多学习资源、行业资源等
  • 结识更多行业伙伴,参与线上/线下课程、交流会,拓展行业交流圈

 

【最新活动】:

1、6月1日21:00-21:30,DappLearning Infura AMA(线上)点击查看详情

试试用AI创作助手写篇文章吧