cryptozombies9

为什么cryptozombies要教truffle而不是fountry和hardhat

选择 Truffle 而不是 FoundryHardhat 作为 CryptoZombies 教程的框架,主要基于以下几个原因:

1. Truffle 的广泛采用与成熟性

Truffle 是最早被广泛采用的 Ethereum 开发框架之一,且自发布以来,已经积累了庞大的用户基础和社区支持。它提供了完整的开发工具链,包括智能合约编译、迁移、部署和测试等功能。这使得它对于初学者来说非常有吸引力,尤其是像 CryptoZombies 这样旨在向用户介绍基本概念的教程。

  • Truffle 的学习曲线相对平缓:对于初学者来说,Truffle 提供了一个易于使用且完整的开发环境,适合用来学习智能合约开发和测试。
  • 完整的工具链:Truffle 提供了强大的迁移工具,能够帮助用户管理合约部署和版本控制。它还内置了用于智能合约测试的功能,极大简化了部署和测试过程。

2. 教育性

CryptoZombies 作为一个入门级的区块链开发教程,旨在让用户快速上手并理解基础概念。Truffle 作为一个全面的框架,包含了简单的命令行操作(如 truffle inittruffle migrate 等),这些操作能帮助用户快速创建和部署智能合约,从而聚焦于 Ethereum 和智能合约的基本原理,而无需过多关注复杂的开发工具链配置。

  • 易于理解的配置:Truffle 的配置相对简单,许多新手可以很容易地上手并理解。与其更灵活但复杂的工具(如 Foundry 或 Hardhat)相比,Truffle 更适合用来展示基本的概念。

3. 广泛的文档和社区支持

Truffle 拥有丰富的文档和教程,适合各种层次的开发者。从初学者到高级用户,都可以通过 Truffle 的官方文档、教程和社区资源找到帮助。这对于 CryptoZombies 这种教育型项目来说,至关重要,因为它可以帮助学习者在遇到问题时迅速找到解决方案。

  • 社区支持:Truffle 是 Ethereum 领域最活跃的开发框架之一,拥有一个庞大的开发者社区。如果遇到问题,可以轻松找到解决方案或者向社区寻求帮助。

4. 与 Ganache 的紧密集成

Truffle 与 Ganache 紧密集成,提供了一个本地的 Ethereum 仿真网络,适合开发和测试。Ganache 是 Truffle 的一部分,能够模拟真实的区块链环境,并提供即时反馈。这对于学习如何部署和测试智能合约非常有帮助,因为它使得用户无需连接到实际的网络即可进行本地测试。

  • 本地开发环境:Ganache 提供了一个免费的本地环境,允许开发者模拟区块链交易,而无需支付任何 Gas 费用。对于学习者来说,这是一个非常方便的工具。

5. 与以太坊主网的接入方式简单

Truffle 提供了简单的配置方式,使得部署到主网或者测试网(如 Rinkeby)变得非常容易。通过 truffle-config.js 和网络配置,用户可以轻松部署智能合约到不同的网络。

为什么不使用 FoundryHardhat

虽然 FoundryHardhat 也有其优势,但它们更适合有一定经验的开发者或需要高度自定义的开发环境。

  • Foundry:以其高性能和速度闻名,适合对性能要求较高的开发者,但对于初学者来说,Foundry 的学习曲线较陡峭,且需要更多的配置和理解。
  • Hardhat:非常灵活,适合用于复杂的开发场景,提供了丰富的插件系统和调试工具,然而,Hardhat 的灵活性也意味着开发者需要理解更多的细节,这对于初学者来说可能会过于复杂。

总结

Truffle 是一个成熟、易于上手的框架,适合用来教学和快速开发原型。对于像 CryptoZombies 这样的教育性项目,它提供了一个清晰、简化的学习路径,帮助用户掌握智能合约的基础。因此,Truffle 是一个非常适合教学的工具。

一旦用户掌握了 Truffle 后,可以进一步学习 FoundryHardhat,这些工具在性能、灵活性和高度自定义上具有优势,更适合用于复杂的项目和生产环境。

既然truffle简单易上手,为什么不教remix,那不是更简单上手吗

这是一个很好的问题!的确,Remix 是另一个非常流行的智能合约开发工具,它在很多方面都非常适合初学者。它的用户界面直观、易于使用,并且是基于浏览器的开发环境,因此不需要设置复杂的本地开发环境。

为什么选择 Truffle 而不是 Remix

  1. 项目规模和自动化流程

    • Truffle 是一个完整的开发框架,它提供了从编写、编译、迁移到测试和部署的一整套工具链。Truffle 更适合用于较大的项目,尤其是需要自动化构建、迁移和测试的项目。在你逐渐深入学习智能合约开发时,Truffle 提供了更多的结构和组织方式。
    • Remix 更适合用于快速原型设计和小规模的合约开发,但它并没有像 Truffle 那样提供自动化迁移、测试和部署的全面支持。当你想要在本地模拟区块链环境并对多个合约进行管理时,Truffle 的功能会更加适合。
  2. 适应多网络部署

    • Truffle 提供了对多种网络的内建支持,包括本地、Rinkeby、Ropsten、Mainnet 等。这对于那些需要部署到不同测试网或主网的开发者来说非常有帮助,而 Remix 主要是为本地开发环境和简单的部署而设计。虽然 Remix 也支持连接到测试网,但它的工作流和网络配置不如 Truffle 那么灵活和自动化。
  3. 开发环境和工作流程

    • Truffle 提供了本地环境(与 Ganache 集成),可以在没有连接到真实区块链的情况下测试智能合约的功能。这让开发者能够模拟不同的网络条件和测试各种功能,而不需要担心实际网络的限制。
    • Remix 是基于浏览器的,虽然它非常方便,但对于大型项目或多合约的管理而言,其环境并不如 Truffle 灵活。例如,Truffle 提供了更好的测试框架和自动化工具,可以帮助开发者管理复杂的项目。
  4. 测试和自动化

    • Truffle 提供了强大的单元测试功能,支持 JavaScript 和 Solidity 编写的测试用例。对于希望确保合约稳定性和安全性的开发者来说,Truffle 提供了完整的测试框架,能够在多个环境中自动执行测试。
    • Remix 也支持简单的合约测试,但它的测试功能不如 Truffle 完善。虽然你可以使用 Remix 的插件进行一些基本的单元测试,但对于大型项目,Truffle 提供的自动化测试工具和集成的 CI/CD 流程更加适合。
  5. 社区和文档支持

    • Truffle 拥有广泛的社区支持和文档,提供了丰富的教程和示例。无论是学习基础的智能合约开发,还是进行复杂的合约部署,Truffle 的文档和社区都能提供非常多的资源。
    • Remix 的社区也很活跃,但它更多的是用于快速实验和小规模项目的开发。虽然它有广泛的文档和教程,但相较于 Truffle,它的生态系统不那么完备,尤其是在涉及到部署、测试和复杂合约的管理时。

总结

Remix 是一个非常适合初学者用来快速上手 Solidity 编程和测试的小工具,它的界面友好,使用方便,特别适合快速原型和小规模合约的开发。然而,Truffle 提供了一个更加全面的开发框架,适合更大规模的项目、自动化的合约部署和测试。如果你计划开发更复杂的 DApp 或者需要更强大的自动化功能,Truffle 可能是更合适的选择。

在 CryptoZombies 的教学中,Truffle 被选择是因为它提供了一个完整的开发工具链,帮助学习者了解如何从编写合约到自动化测试、部署等每个环节的工作流程。而 Remix 虽然适合快速实验,但当你深入智能合约开发时,Truffle 提供的功能更为强大和全面。

如果我学会了truffle,是不是不需要学hardhat和fountry?

学会了 Truffle 并不意味着你完全不需要学习 HardhatFoundry。虽然 Truffle 提供了一个完整的开发工具链,适合很多开发者,但 HardhatFoundry 都有其独特的优势和不同的应用场景。因此,学习它们的好处还是很大的,特别是当你处理更复杂的项目时。

为什么即使学会了 Truffle,还是值得学 HardhatFoundry

1. Hardhat:更多的灵活性和现代开发体验

  • 灵活性:Hardhat 提供了比 Truffle 更加灵活和高度可定制的开发环境。它允许开发者按需安装和配置插件,支持更复杂的工作流。Truffle 也有一些插件,但 Hardhat 的插件生态和灵活性更强,适合各种不同的开发需求。

  • 调试工具:Hardhat 提供了非常强大的调试工具,可以通过 console.log 和详细的堆栈跟踪信息来调试智能合约,这在开发和排错时非常有帮助。相比之下,Truffle 的调试功能不如 Hardhat 那么强大。

  • 自动化部署与任务:Hardhat 的部署和任务自动化更加灵活,可以与其他工具集成,例如,使用 hardhat-deploy 插件来管理合约的版本和部署脚本,这对于大型 DApp 项目特别有用。

  • 兼容性:Hardhat 与 ethers.js(以太坊 JavaScript 库)兼容得非常好,而 Truffle 更依赖于 Web3.js。这使得 Hardhat 在与其他现代 JavaScript 库和工具的兼容性上占有一定优势。

2. Foundry:高性能和快速开发

  • 性能:Foundry 是由 Rust 编写的,因而在合约编译和测试方面表现出色。它非常快速,尤其适合需要高效反馈的开发场景。如果你有性能要求较高的项目,Foundry 会非常合适。它的高效性使得它在需要频繁编译和测试的开发环境中更具优势。

  • 轻量级:Foundry 是一个非常轻量级的框架,它不会像 Truffle 那样依赖大量的依赖库。对于那些喜欢简洁开发流程的开发者来说,Foundry 可能是一个理想的选择。

  • 直接使用 Solidity 编写测试:Foundry 允许你直接用 Solidity 编写测试,这对于习惯 Solidity 语言的开发者来说更为直接和便捷。而 Truffle 和 Hardhat 则需要你使用 JavaScript 来编写测试,虽然也很方便,但对于 Solidity 开发者来说,使用 Solidity 编写测试可能会更符合他们的习惯。

3. 对不同开发场景的适应性

  • Truffle:适合初学者、快速开发原型、自动化的测试、部署、迁移等。它提供了一个非常完整的开发工具链,适合初期入门并快速构建项目。

  • Hardhat:适合更复杂、需要高度自定义的项目。它提供了强大的调试功能、灵活的插件机制和更现代的开发体验,适合开发者需要在不同开发需求之间快速切换时使用。

  • Foundry:适合需要极高性能、快速测试和编译的项目。对于那些关注开发效率、性能和快速反馈的开发者,Foundry 是一个非常好的选择。

总结

Truffle 是一个功能强大且成熟的框架,非常适合快速上手和学习智能合约开发。学会 Truffle 后,你已经具备了开发和部署智能合约的基础能力,但 HardhatFoundry 各自有其独特的优点,可以让你在面对不同的开发需求时更加得心应手。

  • Hardhat 提供了更多灵活性和更现代的开发体验,适合更复杂和定制化的项目。
  • Foundry 以其出色的性能和轻量级的设计,适合对性能要求较高的开发者。

因此,即使你学会了 Truffle,还是有理由继续学习 Hardhat 和 Foundry,特别是当你开始处理更复杂的项目或想要提高开发效率时。

第5章:第一个测试 - 创建新僵尸(续)

现在我们已经把僵尸们(咳咳,整理好)准备好了,接下来我们进入下一个阶段… 🧟🦆‍🧟🦆🧟🦆‍🧟🦆🧟🦆‍🧟🦆

2. 执行(Act)

我们已经到达了调用创建新僵尸函数的阶段——createRandomZombie

但是有一个小问题——我们如何让这个方法“知道”是谁在调用它?换句话说,如何确保是 Alice(而不是 Bob)成为这个新僵尸的拥有者呢?🧐

嗯… 这个问题通过合约抽象解决了。Truffle 的一个特点是它封装了原始的 Solidity 实现,并允许我们通过传递地址作为参数来指定调用该函数的地址。

下面的代码调用了 createRandomZombie 并确保 msg.sender 被设置为 Alice 的地址:

1
const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});

现在我有一个快速问题问你:你知道 result 中存储了什么吗?

让我来解释一下。

日志和事件

一旦我们使用 artifacts.require 指定了我们要测试的合约,Truffle 会自动提供我们智能合约生成的日志。这意味着我们现在可以通过以下方式获取 Alice 新创建的僵尸的名字:result.logs[0].args.name。类似地,我们也可以获取该僵尸的 ID 和 DNA。

除了这些信息外,result 还会提供一些关于交易的其他有用细节:

  • result.tx:交易哈希
  • result.receipt:包含交易收据的对象。如果 result.receipt.status 等于 true,则意味着交易成功;否则,意味着交易失败。

注意:日志也可以作为一个更便宜的数据存储选项来使用,缺点是它们不能从智能合约内部访问。

3. 断言(Assert)

在本章中,我们将使用内置的断言模块,该模块提供了一些断言函数,例如 equal()deepEqual()。简而言之,这些函数会检查条件,如果结果不符合预期,则抛出错误。由于我们将比较简单的值,所以我们会使用 assert.equal()

测试一下

让我们完成第一个测试。

  1. 声明一个名为 result 的常量,并将其设置为 contractInstance.createRandomZombie 函数的结果,传入僵尸的名字和所有者作为参数。

  2. 一旦我们得到结果,调用 assert.equal,传入两个参数——result.receipt.statustrue

如果上述条件为 true,那么我们可以认为我们的测试通过了。为了保险起见,我们再加一个检查。

  1. 在下一行,检查 result.logs[0].args.name 是否等于 zombieNames[0]。像上面一样使用 assert.equal

现在,运行 truffle test 看看我们的第一个测试是否通过。Truffle 会检查 “test” 目录,并执行其中的文件。

实际上,我们已经为你完成了这一步。输出应该如下所示:

1
2
3
4
Contract: CryptoZombies
✓ should be able to create a new zombie (323ms)

1 passing (768ms)

这就是你第一次测试的全部内容 —— 做得好!接下来还有几个测试,赶快进入下一课吧…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const CryptoZombies = artifacts.require("CryptoZombies");
const zombieNames = ["Zombie 1", "Zombie 2"];
contract("CryptoZombies", (accounts) => {
let [alice, bob] = accounts;

// start here

it("should be able to create a new zombie", async () => {
const contractInstance = await CryptoZombies.new();
const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
assert.equal(result.receipt.status, true);
assert.equal(result.logs[0].args.name,zombieNames[0]);
})

//define the new it() function
})

第6章:保持游戏的乐趣

到目前为止,做得很棒!现在我们可以确定用户能够创建新的僵尸👌🏻。

然而,如果他们可以不断调用这个函数来创建无限数量的僵尸,那么游戏就不会那么有趣了。因此,在第2课的第4章中,我们在 createZombieFunction() 中添加了一个 require 语句,确保每个用户最多只能拥有一个僵尸:

1
require(ownerZombieCount[msg.sender] == 0);

让我们来测试一下这个功能,看看它是否能正常工作。

钩子(Hooks)

很快🤞,我们将有多个测试,而测试的运行方式是每个测试应该从一个干净的状态开始。因此,对于每一个测试,我们需要像这样创建一个新的智能合约实例:

1
const contractInstance = await CryptoZombies.new();

如果你只需要写一次这个代码,而让 Truffle 自动为每个测试运行它,那该多好呢?

嗯… Mocha(和 Truffle)有一个特性,允许我们在测试前或测试后运行一些代码片段,这些代码片段被称为 钩子(hooks)。要在测试执行之前运行某个函数,代码应该放在一个名为 beforeEach() 的函数中。

所以,代替每次都写 contract.new(),你只需像这样写一次:

1
2
3
beforeEach(async () => {
// 在这里放入创建新合约实例的代码
});

然后,Truffle 会处理一切。这是不是很方便?

测试一下

  1. 在初始化 alicebob 的代码行下面,让我们声明一个名为 contractInstance 的变量。不要将其赋值为任何内容。

注意:我们希望 contractInstance 的作用范围仅限于它被定义的块。所以要使用 let 而不是 var

  1. 接下来,复制并粘贴上面定义 beforeEach() 函数的代码。

  2. 让我们填充新函数的内容。将创建新合约实例的代码行移到 beforeEach() 函数内部。现在我们已经在别的地方定义了 contractInstance,你可以去掉 const 关键字。

  3. 我们需要为我们的测试创建一个新的空 it 函数。将测试的名称(即我们传递给 it 函数的第一个参数)设置为 “should not allow two zombies”。

我们将在下一章继续完善这个函数!


🧟‍♂️这里有……各种各样的僵尸!!!🧟‍♂️

如果你真的、真的想要获得更高的掌握,继续读下去吧。否则……只需点击“下一章”去学习下一课。

你还在吗?😁

太棒了!毕竟,为什么要拒绝自己获得更多精彩的东西呢?

现在,让我们回到 contract.new() 的工作原理。基本上,每次我们调用这个函数时,Truffle 会确保一个新的合约被部署。

一方面,这对我们有帮助,因为它让我们每个测试都能从一个干净的状态开始。

另一方面,如果每个人都创建无数个合约,区块链就会变得臃肿。我们希望你能一直待着,但不希望你留下过时的测试合约!

我们应该防止这种情况发生,对吧?

幸运的是,解决方案相当简单… 我们的合约应该在不再需要时自行销毁。

这个过程如下:

首先,我们希望在 CryptoZombies 智能合约中添加一个新函数:

1
2
3
function kill() public onlyOwner {
selfdestruct(owner());
}

注意:如果你想了解更多关于 selfdestruct() 的信息,可以查阅 Solidity 文档。最重要的一点是,selfdestruct 是唯一一种从区块链上移除某个地址代码的方法,这使得它成为一个非常重要的功能!

接下来,和上面 beforeEach() 函数的方式类似,我们将创建一个名为 afterEach() 的函数:

1
2
3
afterEach(async () => {
await contractInstance.kill();
});

最后,Truffle 会确保在测试执行完毕后调用这个函数。

于是,哇哦,智能合约自己被销毁了!

我们在这一课中还有很多内容要讲,且实现这个功能可能需要至少 2 章的内容。所以,我们相信你能够将它加进去。💪🏻

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const CryptoZombies = artifacts.require("CryptoZombies");
const zombieNames = ["Zombie 1", "Zombie 2"];
contract("CryptoZombies", (accounts) => {
let [alice, bob] = accounts;
let contractInstance;
beforeEach(async () => {
contractInstance = await CryptoZombies.new();
// let's put here the code that creates a new contract instance
});

it("should be able to create a new zombie", async () => {
const contractInstance = await CryptoZombies.new();
const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
assert.equal(result.receipt.status, true);
assert.equal(result.logs[0].args.name,zombieNames[0]);
})
it("should not allow two zombies", async() => {
})
//define the new it() function
})

这段代码是 Truffle 框架中的 Solidity 智能合约测试代码,使用 JavaScript 编写,旨在测试名为 CryptoZombies 的智能合约功能。Truffle 是一个用于开发以太坊智能合约的开发框架,其中的测试通常采用 Mocha 测试框架和 Chai 断言库来编写。

我们一行一行地分析这段代码:

1. const CryptoZombies = artifacts.require("CryptoZombies");

  • artifacts.require("CryptoZombies") 是 Truffle 的方法,用来导入已经编译的 CryptoZombies 智能合约。通过 CryptoZombies 变量,后续代码将能访问合约的所有方法和属性。
  • 这意味着 Truffle 会从你项目中的 build/contracts 目录加载已经编译好的智能合约文件(ABI 和字节码)。

2. const zombieNames = ["Zombie 1", "Zombie 2"];

  • 这行代码创建了一个名为 zombieNames 的数组,包含了两个僵尸的名字:"Zombie 1""Zombie 2"。这些名字将在后续的测试中用作新僵尸的名称。

3. contract("CryptoZombies", (accounts) => { ... })

  • contract 是 Truffle 测试框架提供的一个函数,用来定义智能合约的测试套件。这里 "CryptoZombies" 是合约的名称,(accounts) 是 Truffle 自动提供的账户数组,包含了多个以太坊账户(通常是 accounts[0]accounts[9])。
  • contract 函数内的回调函数定义了测试的主体。在这个回调中,你会写多个具体的测试用例。

4. let [alice, bob] = accounts;

  • 这行代码使用 JavaScript 的解构赋值语法,将 accounts 数组中的前两个账户分别赋值给变量 alicebob。这两个账户将在后续的测试中充当两个不同的用户,用于发起交易。

5. let contractInstance;

  • 这是定义一个变量 contractInstance,它将用于保存部署的合约实例。在测试中,你将用它来调用合约中的方法。

6. beforeEach(async () => { ... })

  • beforeEach 是 Mocha 测试框架中的钩子函数,它在每个测试用例运行之前执行一次。这里,beforeEach 用于在每个测试之前重新部署一个新的 CryptoZombies 合约实例,以确保每个测试都是在 干净的环境 下运行,不会受到之前测试的影响。
1
contractInstance = await CryptoZombies.new();
  • CryptoZombies.new() 用来部署一个新的 CryptoZombies 合约实例,contractInstance 变量将保存这个实例。

7. it("should be able to create a new zombie", async () => { ... })

  • it 是 Mocha 测试框架用来定义单个测试用例的函数。这里定义了一个测试用例,名称为 "should be able to create a new zombie",意思是“应该能够创建一个新的僵尸”。

  • async () => { ... } 是一个异步函数,用来执行创建僵尸的操作,并进行断言测试。

1
const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
  • contractInstance.createRandomZombie(zombieNames[0], {from: alice}) 调用了 CryptoZombies 合约中的 createRandomZombie 方法,传入 zombieNames[0](即 "Zombie 1")作为新僵尸的名称,并指定由 alice 账户发起交易。由于这是一个 异步操作,我们使用 await 来等待交易完成并返回结果。
1
assert.equal(result.receipt.status, true);
  • result.receipt.status 检查交易是否成功,true 表示交易成功。如果交易成功,statustrue,如果失败,则为 false
1
assert.equal(result.logs[0].args.name, zombieNames[0]);
  • 这里检查智能合约是否正确触发了事件,result.logs[0].args.name 是事件日志的第一个参数 name,它应该等于 zombieNames[0](即 "Zombie 1")。这确保了合约创建僵尸时,事件被正确触发,并且事件参数也正确。

8. it("should not allow two zombies", async () => { ... })

  • 这是第二个测试用例的模板,目的是测试合约是否允许同一个用户创建多个僵尸。当前,测试用例没有写具体的代码,你可以在此处补充逻辑。
  • 例如,可以写一个测试,确保 alice 只能创建一个僵尸,如果再次创建,则应该失败或抛出异常。

9. 如何完成第二个测试用例

在这个测试用例中,你可以实现以下逻辑:

  • 第一次调用 createRandomZombie 创建一个僵尸。
  • 第二次尝试调用 createRandomZombie 时,应该失败,或抛出错误,表明同一账户不能创建两个僵尸。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
it("should not allow two zombies", async () => {
// 创建第一个僵尸
await contractInstance.createRandomZombie(zombieNames[0], {from: alice});

// 尝试再次创建僵尸,应该失败
try {
await contractInstance.createRandomZombie(zombieNames[1], {from: alice});
assert.fail("Should not be able to create a second zombie");
} catch (error) {
assert(error.message.includes("revert"), "Expected revert error, got: " + error.message);
}
});

总结:

这段代码定义了两个测试用例:

  1. 测试是否能够成功创建一个僵尸,并验证事件日志中的僵尸名称。
  2. 第二个测试用例目前是空的,应该用来验证合约是否限制一个账户只能创建一个僵尸。

通过 Truffle 测试框架和 Mocha,你能够模拟实际的交易场景,验证智能合约的逻辑是否按预期工作。