每个软件工程师在从头开始构建应用程序时都会默认使用一系列工具。 在传统的软件行业中,有很多工具可供选择,可以互操作以进行端到端开发。 在 Web3 这个新兴行业中,没有那么多工具是在考虑互操作性的情况下构建的。
这就是 Truffle 为开发者解决的问题。 Truffle 通过在 Truffle 套件中创建与 Web3 中的其他工具兼容的工具和功能来简化开发人员体验。
这是新的 Truffle 内容系列的第一部分,我们在其中概述了从一个想法到最小可行 dapp 的步骤,展示了 Truffle 套件中为任何经验水平的智能合约开发人员提供的很酷的功能和工具。
在第一部分中,我们将专注于 Truffle CLI,并通过创建智能合约项目、编写智能合约代码、编译、迁移和与智能合约交互来展示 Truffle CLI 的所有可能,所有这些都在 Truffle CLI 中进行。
在后续的文章中,我们将通过添加自动化测试来构建这个项目,展示使用 Truffle 在 JavaScript/TypeScript 和 Solidity 中添加智能合约测试是多么容易。 稍后,我们还将探索通过 Truffle Debugger 和 console.log 提供给我们的调试选项。
到本系列结束时,您将能够从头开始构建 dapp,利用 Truffle 工具套件中提供的各种工具,包括 Ganache、用于 VS Code 扩展的 Truffle、Truffle 仪表板、Truffle boxes 等等更多的。
要求
为了能够跟进这篇文章的内容,您需要满足以下要求:
-
Node.js v14 - v18
-
Windows、Linux 或 macOS
建议您使用 Node 版本管理器下载 Node.js,以避免使用 sudo 下载 Truffle 导致权限错误。 按照此处的说明为您的操作系统下载 Node 版本管理器。
安装 Node.js 后,您可以使用命令 npm install -g truffle 全局安装 Truffle。 有关更多信息,请参阅 Truffle 安装指南。
使用 Truffle Init 创建一个 Truffle 项目
要使用 Truffle 创建智能合约项目,有两个选项可供选择,一个是 Truffle box 选项,我们将在本系列后面详细介绍。 第二个选项是 Truffle CLI 选项。 对于这篇文章,我们将使用 Truffle CLI 选项。
通过在全球范围内下载 Truffle,您可以使用 Truffle CLI。 为您的项目创建一个空文件夹并将其命名为 daily-nft,然后在这个新文件夹的根目录中运行 truffle init。 这应该创建一个不包含智能合约的准 Truffle 项目。
如果您检查新创建的项目结构,您会发现以下项目:
contracts/:Solidity 合约目录
migrations/:可编写脚本的部署文件的目录
test/:用于测试应用程序和合约的测试文件目录
truffle-config.js:松露配置文件
智能合约
对于智能合约,我们将使用我出色的同事 Josh 构建的这个项目。 它被称为Daily NFT。 该项目背后的想法是,根据拍卖的获胜者,每天都会展示一个 NFT。 用户可以在显示当天的 NFT 的同时继续竞标第二天的 NFT。
正如您将看到的,这是一个简单的项目,易于理解,但也足够复杂,不能成为一个 hello world 或计时器项目。
在 contracts/目录下,新建一个 Solidity 文件并命名为 Auction.sol,然后向其中添加以下内容:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract Auction {
event Start();
event Bid(address indexed sender, uint amount);
event Withdraw(address indexed bidder, uint amount);
event End(address winner, uint amount);
address payable public seller;
uint public endAt;
bool public started;
bool public ended;
address public highestBidder;
uint public highestBid; // Wei
mapping(address => uint) public bids;
mapping(address => address) public nfts;
mapping(address => uint256) public nftIds;
}
对代码中发生的事情的简要描述。 我们定义了四个事件,它们将在合约的四个重要阶段触发; 我们何时Start 拍卖 - 手动、何时Bid、何时触发Withdraw以及何时拍卖在 24 小时内End。
我们还定义了一些公共变量,如下所述:
seller:拍卖所有者的支付地址 - 部署地址。
endAt::拍卖结束后的小时数 - 24 小时。
started: 表示拍卖是否开始的 True 或 False 值。
ended:真值或假值,表示拍卖是否结束。
highestBidder:当前最高出价者的地址。
highestBid:以Wei表示的最高出价金额。
bids:地址与其各自出价金额之间的映射
nfts:地址和他们各自的出价nft地址之间的映射
nftIds:nft 地址和它们各自的 Id 之间的映射。
...
constructor(
uint _startingBid
) {
seller = payable(msg.sender);
highestBid = _startingBid;
}
function start() external {
require(!started, "started");
require(msg.sender == seller, "not seller");
started = true;
endAt = block.timestamp + 1 days;
emit Start();
}
function bid() external payable {
require(started, "not started");
require(block.timestamp < endAt, "ended");
require(msg.value > highestBid, "value < highest");
require(nfts[msg.sender] != address(0), "include nft to display");
if (highestBidder != address(0)) {
bids[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
emit Bid(msg.sender, msg.value);
}
// Overload if the user submits an NFT
function bid(address nft, uint256 nftId) external payable {
require(started, "not started");
require(block.timestamp < endAt, "ended");
require(msg.value > highestBid, "value < highest");
if (highestBidder != address(0)) {
bids[highestBidder] += highestBid;
}
if (nfts[msg.sender] == address(0)) {
require(nft != address(0), "invalid nft address");
nfts[msg.sender] = nft;
nftIds[msg.sender] = nftId;
}
highestBidder = msg.sender;
highestBid = msg.value;
emit Bid(msg.sender, msg.value);
}
function withdraw() external {
uint bal = bids[msg.sender];
bids[msg.sender] = 0;
payable(msg.sender).transfer(bal);
emit Withdraw(msg.sender, bal);
}
function end() external {
require(started, "not started");
require(block.timestamp >= endAt, "end time in future");
require(!ended, "ended");
ended = true;
emit End(highestBidder, highestBid);
}
以下是合约代码第二部分的摘要:
-
constructor() 函数在部署期间被调用,将卖家和 highestBid 分别设置为部署者的地址和提供的 _startingBid。
-
start() 函数被手动调用以开始拍卖。 它将拍卖结束时间设置为从开始时间算起的 24 小时。
-
有两个 bid() 函数,一个接受 nft 地址和 Id,而另一个不接受任何参数。 他们确保新出价的价值高于当前最高出价才能被接受。
-
withdraw()函数允许用户收回他们的出价金额。
-
end()函数在停止拍卖之前检查是否已达到拍卖结束时间。
使用 Truffle CLI 编译合约
要编译新创建的合约,请导航到项目根目录并运行truffle compile命令。 Truffle 将遍历 /contracts目录,编译以 .sol 结尾的每个文件和库。 在我们的例子中,我们只有一个合约文件需要编译。
另请注意,在第一次运行时,将编译所有合约,但在后续运行中,Truffle 将仅编译自上次编译以来已更改的合约。您可以通过使用--all选项运行上述命令来覆盖此行为。
编译成功后,请注意已build一个新的构建目录。该目录包含编译的工件,特别是在 build/contracts/ 目录中,相对于您的项目根目录。继续检查此目录的内容。
使用 Truffle CLI 在本地部署合约
Truffle 附带一个内置的个人区块链,可用于在本地与智能合约进行交互和测试。该区块链在您的系统本地,不与以太坊主网络或测试网络交互。要访问它,只需运行命令truffle develop。
为了能够使用 Truffle 将合约部署到任何以太坊网络,您必须为该合约创建一个迁移文件。迁移是 JavaScript 文件,假设您的部署需求会随时间变化而编写,可帮助您将合约部署到任何以太坊网络,包括本地运行的节点,如 Truffle 附带的节点。
现在在 migrations/文件夹中,创建一个新文件并将其命名为 1_migration.js。 “_migration”之前的数字“1”非常重要,因为它表示顺序,即它告诉 Truffle 在另一个迁移文件之前运行哪个迁移文件。每个添加到迁移文件夹的新文件都会增加此数字。
将以下内容复制到新创建的迁移文件中:
const Auction = artifacts.require("Auction");
module.exports = function (deployer) {
deployer.deploy(Auction, 100);
};
在这里,我们使用 artifacts.require()函数来告诉 Truffle 我们想要与哪个合约文件进行交互。 它返回一个合约抽象,我们可以在部署脚本的其余部分中使用它。
在第二行,我们现在导出一个函数,该函数在调用时采用部署程序对象并调用 deployer.deploy()函数来部署合约,同时传入拍卖合约构造函数中指定的所需参数。 如需更详细地了解迁移在 Truffle 中的工作方式,请访问 Truffle 迁移文档。
完成迁移文件后,我们准备将智能合约部署到 Truffle 内置的本地区块链。 首先运行命令truffle develop 以公开 Truffle 开发人员控制台——如果您还没有运行它的话。
请注意,它在端口 9545 上启动了一个本地区块链。此命令还公开了 10 个以太坊帐户以及相关的私钥和助记词。 确保不要向这些地址发送真实的 Eth 代币,因为它们不安全并且仅用于与您的智能合约进行交互。
在此控制台中,运行命令migrate 以将您的合约部署到本地区块链节点。 这应该返回已部署合约的交易 ID 和地址,包括成本摘要。
使用 Truffle CLI 与本地部署的合约交互
将合约部署到 Truffle 的本地区块链后,由于我们还没有 dapp 的前端,我们可以直接从 truffle develop 命令公开的开发人员控制台手动与部署的智能合约进行交互。 让我们开始吧!
获取已部署的合约:
truffle(develop)> let auction = await Auction.deployed()
获取当前设置的卖家和最高出价
truffle(develop)> let seller = await auction.seller()
truffle(develop)> let highestBid = await auction.highestBid()
显示卖家和最高出价,在我的例子中:
truffle(develop)> seller
'0x1a33B6853b36F4c2E3872E229F3a77Bf75943F9d'
truffle(develop)> highestBid.toNumber()
100
检查拍卖是否已经开始:
truffle(develop)> let started = await auction.stared()
truffle(develop)> started
false
开始拍卖:
truffle(develop)> await auction.start()
truffle(develop)> started = await auction.started()
true
下一步是什么?
您刚刚成功创建了一个新的 Truffle 项目,编写了智能合约,编译并部署了智能合约!我们甚至已经使用 Truffle 开发人员控制台在本地与部署的合约进行交互。恭喜!
在下一篇文章中,我们将探索使用 Truffle CLI 为我们的智能合约编写和运行自动化测试。
在 Truffle,我们始终致力于通过创建开发人员工具、资源和教育材料来改善和简化 Web3 生态系统中 dapp 开发人员的用户体验。
要了解有关我们的开发人员工具套件的更多信息,请访问 Truffle 官方网站。如果您有任何疑问,请随时在我们的 Github 讨论页面上开始讨论。