1.truffle 介绍基本用法

目录结构

合约编译

合约文件目录

所有合约都位于项目的 contracts/ 目录中。 由于合约是用Solidity语言编写的,所有包含合约的文件都将具有 .sol 文件扩展名。 相关的 Solidity 也将有一个.sol扩展名。

使用truffle init命令创建的空 Truffle 工程会生成一个用于部署的Migrations.sol 合约文件。 如果我们使用 Truffle Box 来创建工程,则会有多个合约文件。

编译命令

要编译Truffle项目里的合约,请切换到项目工程所在根目录,然后在终端中键入以下内容:

truffle compile

首次运行时,将编译所有合约。 在后续运行中,Truffle将仅编译自上次编译以来有更改的合约。 如果我们想覆盖此行为,可以使用 --all 选项运行上面的命令。

构建工件 Artifacts

注解

译者注:Artifacts 主要是指编译的目标(或目标文件),因为中文中有时会使用”工件“,为了避免理解问题,大部分会保留英文词 Artifacts。

编译的目标文件 Artifacts 将放在 build/contracts/ 目录中,相对于项目根目录(如果该目录不存在,将创建该目录。)

这些 Artifacts 是Truffle内部工作的组成部分,它们在成功部署应用程序中起着重要作用。 我们不应编辑这些文件,因为这些文件将被合约编译和部署覆盖。

引入合约依赖文件

我们可以使用Solidity的 import 命令声明合约依赖文件。 Truffle 将以正确的顺序编译合约,并确保将所有依赖文件发送给编译器。 可以通过两种方式指定依赖关系:

通过文件名导入依赖文件

要从单独的文件导入合约,请将以下代码添加到Solidity源文件中:

import “./AnotherContract.sol”;

这将导入 AnotherContract.sol 中的所有合约,这里 AnotherContract.sol 是在当前编写的合约目录下。

Solidity 还有其他的导入语法,可以参考Solidity文档:导入源文件 了解更多。

从外部包导入合约

Truffle支持通过EthPMNPM安装的依赖。 可使用以下语法把依赖导入合约:

import “somepackage/SomeContract.sol”;

这里,somepackage 表示通过EthPM或NPM安装的包,SomeContract.sol 表示该包提供的Solidity源文件。

请注意,在搜索从NPM安装的软件包之前,Truffle将首先从EthPM搜索已安装的软件包,因此在极少数情况下会出现命名冲突,将使用通过EthPM安装的软件包。

有关如何使用Truffle软件包管理功能的更多信息,请参阅Truffle EthPMNPM文档。

合约部署

注解
译者注:Migrations 直译”迁移“,当作为一个名词时,有时指的是用来部署的脚本文件,称之为迁移文件,作为动词会翻译成部署,请读者了解。

迁移脚本(JavaScript文件)可帮助我们将合约部署到以太坊网络。 这些文件负责暂存我们的部署任务,并且假设我们的部署需求会随着时间的推移而发生变化。 随着项目的发展,我们将创建新的迁移脚本,以进一步推动区块链的发展。 先前运行的部署记录通过特殊的 Migrations 迁移合约记录在链上,详细信息如下。

部署命令

要运行部署,请运行以下命令:

$ truffle migrate

这将部署在项目的 migrations 目录中的所有迁移文件。 最简单的迁移只是一组管理部署脚本。 如果我们的迁移先前已成功运行,则 truffle migrate 将从上次运行的迁移开始执行,仅运行新创建的迁移。 如果不存在新的迁移,truffle migrate 将不会执行任何操作。 我们可以使用 --reset 选项从头开始运行所有迁移。 对于本地测试,确保在执行 migrate 之前安装并运行了 Ganache等 测试区块链。

脚本文件

一个简单的迁移文件,如文件名:4_example_migration.js :

var MyContract = artifacts.require(“XlbContract”);

module.exports = function(deployer) {
// 部署步骤
deployer.deploy(MyContract);
};

请注意,文件名以数字为前缀,后缀为描述。 编号前缀是必需的,以便记录迁移是否成功运行。 后缀纯粹是为了人类的可读性和理解力。

注解

译者注:编号 还有记录 运行迁移文件顺序的作用。

artifacts.require()

在迁移开始时,我们通过 artifacts.require()方法告诉 Truffle 我们想要与哪些合约进行交互。 这个方法类似于Node的 require,但在我们的例子中,它特别返回了一个 合约抽象 contract abstraction,我们可以在其余的部署脚本中使用它。 指定的名称应与该源文件中的合约定义的名称相匹配。 不传递源文件的文件名,因为文件可以包含多个合约。

考虑这个示例,其中在同一源文件中指定了两个合约:

文件名: ./contracts/Contracts.sol

contract ContractOne {
// …
}

contract ContractTwo {
// …
}

通过 artifacts.require() 引入 ContractTwo 的语句像下面这样:

var ContractTwo = artifacts.require(“ContractTwo”);

也可以引入两个合约,语句如下:

var ContractOne = artifacts.require(“ContractOne”);
var ContractTwo = artifacts.require(“ContractTwo”);

module.exports

所有迁移都必须通过 module.exports 语法导出函数。 每次迁移导出的函数都应该接受 deployer 对象作为其第一个参数。 此对象通过为部署智能合约提供清晰的语法以及执行部署职责(例如保存已部署的 artifacts 供以后使用)。 deployer 对象是用于暂存部署任务最主要接口,其API在本页底部描述。

我们的迁移功能也可以接受其他参数。 请参阅以下示例。

初始化迁移功能

Truffle要求我们有迁移合约(Migrations 合约)才能使用迁移功能。 此合约必须包含特定的接口,但我们可以随意编辑此合约。 对于大多数项目,此合约最初将作为第一个迁移文件进行部署,不会再次更新。 在使用truffle init创建新项目时,我们也会默认收到此合约。

文件: contracts/Migrations.sol

pragma solidity >=0.4.8 <0.6.0;

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_migration.js

var Migrations = artifacts.require(“Migrations”);

module.exports = function(deployer) {
// 任务就是 部署迁移合约
deployer.deploy(Migrations);
};

这里,我们可以使用增加的编号前缀创建新的迁移,以部署其他合约并执行更多的部署步骤。

部署程序 Deployer

我们的迁移文件将用于部署程序 deployer 来(分阶段)部署任务。 因此,我们可以同步编写部署任务,它们将以正确的顺序执行:

// Stage deploying A before B
deployer.deploy(A);
deployer.deploy(B);

或者,部署程序上的每个函数可以使用 Promise,等待上一个任务执行的部署任务完成之后执行(进入一个部署队列):

// Deploy A, then deploy B, passing in A’s newly deployed address
deployer.deploy(A).then(function() {
return deployer.deploy(B, A.address);
});

如果你发现更清晰语法也可以为部署编写为单个 promise 链, 部署API将在本页底部讨论。

考虑网络

可以根据网络条件,条件性地运行部署。 这是一项高级功能,因此请在继续之前先参阅网络 部分。

要有条件地运行部署步骤,在编写迁移时,加入第二个参数 network, 例如:

module.exports = function(deployer, network) {
if (network == “live”) {
// Do something specific to the network named “live”.
} else {
// Perform a different step otherwise.
}
}

可用账号

迁移也会通过我们的以太坊客户端和 Web3 provider 提供给我们的帐户列表,供我们在部署期间使用。 下面和 从web3.eth.getAccounts()返回的完全相同的帐户列表。

module.exports = function(deployer, network, accounts) {
// Use the accounts within your migrations.
}

部署程序接口 Deployer API

部署程序包含许多可用于简化迁移的功能。

deployer.deploy(contract, args…, options)

部署合约可以通过使用指定合约对象可选的合约构造函数的参数来进行合约部署。对于单个合约很有用,DApp只存在此合约的一个实例。 它将在部署之后设置合约地址(即Contract.address 将等于新部署的地址),并且它将覆盖任何先前存储的地址。

注解

译者注:上面的段落的第一句 有一点拗口, 其意思是:传给 deploy 函数的可选参数 会传递给智能合约的构造函数,看下面的例子就很容易理解。

我们也可以选择传递一组合约或一组数组,以加快多个合约的部署。另外,最后一个参数是一个可选对象,它可以包含名为overwrite的键以及其他交易参数如 gas 和from。如果overwrite设置为false,则部署程序如果发现之前已经部署了该合约,则不会再次部署该合约。这对于由外部依赖提供合约地址的某些情况下很有用。

注意,在调用deploy之前,我们需要首先部署和链接合约所依赖的库。有关详细信息,请参阅下面的链接功能。

有关详细信息,请参阅truffle-contract 文档。

通过下面示例会更好理解 deploy 方法:

// 部署没有构造函数的合约
deployer.deploy(A);

//  部署合约 并使用一些参数传递给合约的构造函数。
deployer.deploy(A, arg1, arg2, ...);

// 如果合约部署过,不会覆盖
deployer.deploy(A, {overwrite: false});

// 设置gasLimit 和部署合约的账号
deployer.deploy(A, {gas: 4612388, from: "0x...."});

// 部署多个合约,一些包含参数,另一些没有。
// 这比编写三个`deployer.deploy()`语句更快,因为部署者可以作为单个批处理请求执行部署。
deployer.deploy([
  [A, arg1, arg2, ...],
  B,
  [C, arg1]
]);

// 外部依赖示例:
//对于此示例,我们的依赖在部署到线上网络时提供了一个地址,但是没有为测试和开发等任何其他网络提供地址。
//当我们部署到线上网络时,我们希望它使用该地址,但在测试和开发中,我们需要部署自己的版本。 我们可以简单地使用`overwrite`键来代替编写一堆条件。

deployer.deploy(SomeDependency, {overwrite: false});

deployer.link(library, destinations)

将已部署的库链接到合约或多个合约。 参数 destinations 可以是单个合约,也可以是多个合约的数组。 如果目的(即参数指定的)合约中有不依赖于链接的库,则合约将被忽略。

示例:

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

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

deployer.then(function() {…})

就像 promise 一样,可运行任意部署步骤。 使用此选项可在迁移期间调用特定的合约函数,以添加,编辑和重新组织合约数据。

示例:

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()函数在B上设置A的新实例地址
  return b.setA(a.address);
});

与合约进行交互

介绍

如果我们为了与合约进行(测试)交互而向每次都向以太坊网络进行原始请求,我们很快就会意识到编写这些请求是笨重而繁琐的。 同样,我们可能会发现管理每个请求的状态是 复杂的。 幸运的是,Truffle为我们处理这种复杂性,使我们与合约的互动变得轻而易举。

数据的读和写

以太坊网络区分将数据写入网络和从网络读取数据,在编写应用程序我们需要关注这个区别。 通常,写入数据称为交易 transaction,而读取数据称为 调用 call。 交易调用的处理方式是截然不同的,下面介绍:

交易 Transactions

交易从改变了网络的状态。 交易可以像 发送 Ether 到一个帐户一样简单,也可以像执行合约函数或向网络部署新合约一样复杂。 交易的特征是它写入(或更改)数据。 一个交易需要耗费以太运行,称为 “gas”,交易同样需要(较长)时间来处理。 当我们通过交易执行合约的函数时,我们无法接收该函数的返回值,因为交易不会立即处理。 通常,通过交易执行的函数不会返回值,仅仅是返回一个交易ID。 可总结交易的特征如下:

  • 消耗Gas 费用(以太)

  • 会更改网络状态

  • 不会立即执行(需要等待网络矿工打包)

  • 没有执行返回值(只是一个交易ID)。

调用 Calls

调用则不同,调用依然可以在网络上执行合约代码,但不会永久更改任何数据(如状态变量)。 调用的特征是读取数据。 当我们通过调用执行合约函数时,我们可以立刻获取到返回值。 可总结调用Call的特点:

  • 免费(不消耗 Gas)

  • 不改变网络状态

  • 立即执行

  • 有返回值

选择使用 交易还是调用 关键是看 读取数据 还是需要写入数据。

什么是合约抽象

合约抽象( Contract abstraction)是从 Javascript 与以太坊合约交互的基础和黄油。 简单说,合约抽象是一种代码封装,让我们可以轻松地与合约进行交互,从而让我们忘记在引擎盖下执行的引擎和齿轮。 Truffle 通过truffle-contract模块使用合约抽象,下面会介绍。

在这里,我们通过一个例子 metacoin,来介绍合约抽象的作用,通过Truffle Boxes,执行truffle unbox metacoin使用MetaCoin合约,下面合约代码:

pragma solidity >=0.4.25 <0.6.0;

import “./ConvertLib.sol”;

//这只是一个类似Coin 合约的简单例子,并不是一个标准代币合约
// 常见Token合约可参考:https://github.com/ConsenSys/Tokens

contract MetaCoin {
mapping (address => uint) balances;

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

constructor() public {
	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];
}

}

注解

译者注: 这段代码其实有点旧了(不过并不影响本节要表达的意思), 现在Solidity 升级到 0.5 以上,应该在合约里显示的标明合约是否修改状态。

除了构造函数之外,这个合约还有三个方法(sendCoingetBalanceInEthgetBalance),这三种方法都可以作为交易调用来执行。

现在让我们来看看Truffle为我们提供的名为 “MetaCoin” 的 Javascript 对象,它可以在Truffle控制台访问,如:

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

// outputs:
//
// Contract
// - address: “0xa9f441a487754e6b27ba044a5a8eb2eec77f6b92”
// - allEvents: ()
// - getBalance: ()
// - getBalanceInEth: ()
// - sendCoin: ()
// …

注意: 合约抽象包含与合约中完全相同的函数。 它还包含一个指向 MetaCoin合约 部署版本的地址。

执行合约函数

使用合约抽象,我们可以轻松地在以太坊网络上执行合约函数。

执行交易Transactions

我们可以执行MetaCoin合约上的三个函数。 如果我们分析每一个函数,会发现sendCoin是唯一一个会改变网络状态的函数。 sendCoin 的作用是从一个帐户“发送”一些 Meta coins 到另一个帐户,这个变化是需要持续保存的。

当调用sendCoin时,我们需要它作为一个交易执行。 如在下面的示例中,使用交易调用的方式从一个帐户向另一个帐户发送10个币:

truffle(develop)> let accounts = await web3.eth.getAccounts()
truffle(develop)> instance.sendCoin(accounts[1], 10, {from: accounts[0]})

以上代码有一些有趣的事情:

  • 我们直接调用了抽象合约sendCoin函数。 它默认使用交易的方式去执行,而不是使用调用

  • 我们还用一个对象作为第三个参数传递给sendCoin函数。 注意,在 Solidity 合约中的sendCoin函数没有第三个参数。 这是一个特殊对象,它始终可以作为最后一个参数传递给函数,该函数允许我们编辑有关交易的特定信息。 在这里,我们设置了from地址,确保此交易来自accounts [0]

执行调用 call

继续使用MetaCoin,注意getBalance函数是从网络读取数据的理想选择。 它不需要进行任何更改,因为它只返回地址参数的 MetaCoin 余额。 让我们试一试:

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

调用会得到返回值。 注意,由于以太坊网络可以处理非常大的数字,我们会得到一个BigNum 对象,然后将其转换为数字。

注解

这里返回值转换为数字,因为在此示例中数字很小。 但是如果尝试转换大于 Javascript 支持的最大整数的 BigNum(简写:BN),可能会遇到错误或无法预期的行为。

处理交易结果

当我们进行交易时,我们会得到一个result对象,它为我们提供了大量有关交易的信息。

truffle(develop)> let result = await contract.sendCoin(accounts[1], 10, {from: accounts[0]})
truffle(develop)> result

具体来说,我们将获得以下内容:

  • result.tx (string) - 交易哈希 hash

  • result.logs (array) - 解码过的事件 (日志)

  • result.receipt (object) - 交易收据 receipt(包括使用的gas)

想了解更多,可参阅truffle-contract包中的README

捕获事件 events

通过捕获合约触发的事件,可以更深入地了解合约正在做什么。 处理事件的最简单方法是处理交易结果result对象中包含的logs数组。

如果我们显式输出第一个日志条目,我们可以看到 sendCoin 函数触发事件(Transfer(msg.sender,receiver,amount);)的细节:

truffle(develop)> result.logs[0]
{ logIndex: 0,
transactionIndex: 0,
transactionHash: ‘0x3b33960e99416f687b983d4a6bb628d38bf7855c6249e71d0d16c7930a588cb2’,
blockHash: ‘0xe36787063e114a763469e7dabc7aa57545e67eb2c395a1e6784988ac065fdd59’,
blockNumber: 8,
address: ‘0x6891Ac4E2EF3dA9bc88C96fEDbC9eA4d6D88F768’,
type: ‘mined’,
id: ‘log_3181e274’,
event: ‘Transfer’,
args:
Result {
‘0’: ‘0x8128880DC48cde7e471EF6b99d3877357bb93f01’,
‘1’: ‘0x12B6971f6eb35dD138a03Bd6cBdf9Fc9b9a87d7e’,
‘2’: <BN: a>,
length: 3,
_from: ‘0x8128880DC48cde7e471EF6b99d3877357bb93f01’,
_to: ‘0x12B6971f6eb35dD138a03Bd6cBdf9Fc9b9a87d7e’,
_value: <BN: a> } }

部署新合约

在上述所有情况中,我们一直在使用已经部署的合约抽象。 还可以使用.new()函数把自己版本的合约部署到网络:

truffle(develop)> let newInstance = await MetaCoin.new()
truffle(develop)> newInstance.address
‘0x64307b67314b584b1E3Be606255bd683C835A876’

指定合约地址

如果我们已有合约的地址,则可以在该地址上创建新的抽象。

let specificInstance = await MetaCoin.at(“0x1234…”);

给合约发送以太

可以简单的把 Ether 直接发送给合约地址,或是触发合约的Fallback 函数。有两种方式:

  1. 方法1:通过instance.sendTransaction()将交易直接发送到合约。 它像执行所有可用的合约实例函数一样有效,并且和web3.eth.sendTransaction功能相同,但没有回调。 如果没有指定,目标地址to值将自动填入。

instance.sendTransaction({…}).then(function(result) {
// Same transaction result object as above.
});

  1. 方法2:还可以直接调用send

instance.send(web3.toWei(1, “ether”)).then(function(result) {
// Same result object as above.
});

使用控制台

有时,以交互方式处理合约以进行测试和调试,或手动执行事务是件好事。Truffle 为您提供了两种简单的方法,可以通过交互式控制台执行此操作,您的合同可用且随时可用。

  • Truffle 控制台:连接到任何以太坊客户端的基本交互式控制台

  • Truffle Develop:一个交互式控制台,也生成了一个开发区块链

为什么有两个控制台?

拥有两个不同的控制台可让您选择最适合您需求的工具。

使用松露控制台的原因:

  • 您有一个已经在使用的客户端,例如 Ganache 或 geth

  • 您想迁移到测试网(或主以太坊网络)

  • 您想要使用特定的助记符或帐户列表

使用Truffle Develop的理由:

  • 您正在测试您的项目,但无意立即部署

  • 您不需要使用特定帐户(并且可以使用默认开发帐户)

  • 您不想安装和管理单独的区块链客户端

次序

所有命令都要求您位于项目文件夹中。你不需要在根。

登录控制台

要启动控制台,请执行以下操作:

truffle console

这将查找配置中调用的网络定义,并连接到该定义(如果可用)。您可以使用该选项覆盖此选项或自定义网络设置。有关详细信息,请参阅“网络”部分以及命令参考development``--network <name>``development

加载控制台时,您将立即看到以下提示:

truffle(development)>

这表示您正在使用网络在 Truffle 控制台中运行。development

登录Develop控制台

要启动 Truffle Develop:

truffle develop

默认情况下,这将在端口本地生成一个开发区块链。如果您已经有一个会话正在运行,它将连接到该开发区块链。9545``truffle develop

加载 Truffle Develop 时,您将看到以下内容:

Truffle Develop started at http://localhost:9545/

Accounts:
(0) 0x627306090abab3a6e1400e9345bc60c78a8bef57
(1) 0xf17f52151ebef6c7334fad080c5704d77216b732
(2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef
(3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544
(4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2
(5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e
(6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5
(7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5
(8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc
(9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de

Private Keys:
(0) c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3
(1) ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f
(2) 0dbbe8e4ae425a6d2687f1a7e3ba17bc98c673636790f1b8ad91193c05875ef1
(3) c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c
(4) 388c684f0ba1ef5017716adb5d21a053ea8e90277d0868337519f97bede61418
(5) 659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63
(6) 82d052c865f5763aad42add438569276c00d3d88a2d062d36b2bae914d58b8c8
(7) aa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7
(8) 0f62d96d6675f32685bbdb8ac13cda7c23436f63efbb9d07700d8669ff12b7c4
(9) 8d5366123cb560bb606379f90a0bfd4769eecc0557f1b362dcae9012b548b1e5

Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat

This shows you the addresses, private keys, and mnemonic for this particular blockchain.

Note: When you run truffle develop for the first time, Truffle will generate a random mnemonic that will persist for you and you alone. If you want to use a different mnemonic or set of addresses, we recommend using Ganache.

Warning: Remember to never use any of these addresses or the mnemonic on the mainnet. This is for development only.

配置 Truffle Develop

You can configure to use any of the available ganache-core options and configurable network settings.truffle develop

For example:

module.exports = {
/* … rest of config */

networks: {
/* … other networks */

develop: {
  port: 8545,
  network_id: 20,
  accounts: 5,
  defaultEtherBalance: 500,
  blockTime: 3
}

}
};

功能

Truffle Develop 和控制台都提供了 Truffle 命令行工具中提供的大部分功能。例如,您可以在控制台中键入,它将被解释为您在命令行上运行的相同。migrate --reset``truffle migrate --reset

此外,Truffle Develop 和控制台都具有以下功能:

  • 您所有编译的合同都可用并可供使用。

  • 在每个命令(如 )之后,将重新预配您的协定,以便您可以立即开始使用新分配的地址和二进制文件。migrate --reset

  • 该库可用,并设置为连接到您的以太坊客户端。web3

可用命令

  • build

  • compile

  • create

  • debug

  • deploy

  • exec

  • help

  • install

  • migrate

  • networks

  • opcode

  • publish

  • run

  • test

  • version

如果 Truffle 命令不可用,那是因为它与现有项目无关(例如,)或没有意义(例如,或)。init``develop``console

有关详细信息,请参阅完整的命令参考

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值