diff --git a/zh/10/00-overview.md b/zh/10/00-overview.md
index 045c45dd9..5414797db 100644
--- a/zh/10/00-overview.md
+++ b/zh/10/00-overview.md
@@ -1,6 +1,6 @@
---
-title: "用 Truffle 部署以太坊 dapp"
-header: "用 Truffle 部署以太坊 dapp"
+title: '用 Truffle 部署以太坊 dapp'
+header: '用 Truffle 部署以太坊 dapp'
roadmap: roadmap.jpg
path: solidity_advanced
position: 2
@@ -16,10 +16,10 @@ position: 2
为什么要部署到 **Loom**?毕竟,**_以太坊_** 才是最安全的网络。
-是的,我们完全同意。但在以太坊上每个事务都要耗费 _gas_, 因此你的用户就必须为每个事务付费。而且,每个事务确认还得等上至少10秒。
+是的,我们完全同意。但在以太坊上每个交易都要耗费 _gas_, 因此你的用户就必须为每个交易付费。而且,每个交易确认还得等上至少 10 秒。
-简而言之,**_在以太坊上,所有事务都受益于相同的安全保障_**。对于面向用户的 dapp 或游戏等,往往并不一定需要这种级别的安全性。事实上,它只会破坏用户体验。
+简而言之,**_在以太坊上,所有交易都受益于相同的安全保障_**。对于面向用户的 dapp 或游戏等,往往并不一定需要这种级别的安全性。事实上,它只会破坏用户体验。
-在 **Loom** 上, 用户可以有更快速且零 gas 的事务。这使得 **Loom** 更适合游戏或面向用户的 dapp。
+在 **Loom** 上, 用户可以有更快速且零 gas 的交易。这使得 **Loom** 更适合游戏或面向用户的 dapp。
-好了,废话少说!现在就开始吧 😉
\ No newline at end of file
+好了,废话少说!现在就开始吧 😉
diff --git a/zh/10/02.md b/zh/10/02.md
index 60faae63a..f2d979e43 100644
--- a/zh/10/02.md
+++ b/zh/10/02.md
@@ -3,11 +3,11 @@ title: 开始使用 Truffle
actions: ['checkAnswer', 'hints']
requireLogin: true
material:
- terminal:
+ terminal:
help: |
First, you should probably run `truffle init`. Next, execute `npm install truffle-hdwallet-provider`
commands:
- "truffle init":
+ 'truffle init':
hint: truffle init
output: |
Downloading...
@@ -20,12 +20,12 @@ material:
Compile: truffle compile
Migrate: truffle migrate
Test contracts: truffle test
- "npm install truffle-hdwallet-provider":
+ 'npm install truffle-hdwallet-provider':
hint: npm install truffle-hdwallet-provider
output: |
- + truffle-hdwallet-provider@0.0.6
- added 1 package from 1 contributor and audited 71402 packages in 5.612s
- found 0 vulnerabilities
+ + truffle-hdwallet-provider@0.0.6
+ added 1 package from 1 contributor and audited 71402 packages in 5.612s
+ found 0 vulnerabilities
---
既然已经安装了 **Truffle**,现在就可以通过运行 `Truffle init` 来初始化我们的新项目了。它所做的就是创建一组文件夹和配置文件,其结构如下:
@@ -48,35 +48,33 @@ truffle.js
在 `CryptoZombies` 目录里,`truffle init` 会创建几个目录以及一些 JavaScript 和 Solidity 文件。让我们仔细看看:
- - **_contracts_**: **Truffle** 会在在这里找到我们所有的智能合约。为了保持代码有序,我们甚至可以创建诸如 `contracts/tokens` 之类的嵌套文件夹。很简洁吧 😉。
- >注意: `truffle init` 会自动添加一个叫 `Migrations.sol` 的合约以及相应的迁移文件。后面我们会对其进行解释。
+- **_contracts_**: **Truffle** 会在在这里找到我们所有的智能合约。为了保持代码有序,我们甚至可以创建诸如 `contracts/tokens` 之类的嵌套文件夹。很简洁吧 😉。
- - **_migrations_**: 迁移是一个 JavaScript 文件,它告诉 `truffle` 如何部署智能合约。
+ > 注意: `truffle init` 会自动添加一个叫 `Migrations.sol` 的合约以及相应的迁移文件。后面我们会对其进行解释。
- - **_test_**: 这里我们会放单元测试,它们是 JavaScript 或 Solidity 文件。请记住,合约一旦部署,就不能再更改,因此在部署之前对智能合约进行测试是非常重要的。
+- **_migrations_**: 迁移是一个 JavaScript 文件,它告诉 `truffle` 如何部署智能合约。
- - **_truffle.js_** 和 **_truffle-config.js_**: 这些是配置文件,用于存储部署所需的网络设置。**Truffle** 需要两个配置文件,因为 Windows 上 `truffle.js` 和 `truffle.exe` 存在于同一个文件夹可能会产生冲突。长话短说 —— 如果你在运行 Windows,建议你删除 `truffle.js` 并使用 `truffle-config.js` 作为默认配置文件。查看 **Truffle** 的官方文件,以进一步了解。
+- **_test_**: 这里我们会放单元测试,它们是 JavaScript 或 Solidity 文件。请记住,合约一旦部署,就不能再更改,因此在部署之前对智能合约进行测试是非常重要的。
+- **_truffle.js_** 和 **_truffle-config.js_**: 这些是配置文件,用于存储部署所需的网络设置。**Truffle** 需要两个配置文件,因为 Windows 上 `truffle.js` 和 `truffle.exe` 存在于同一个文件夹可能会产生冲突。长话短说 —— 如果你在运行 Windows,建议你删除 `truffle.js` 并使用 `truffle-config.js` 作为默认配置文件。查看 **Truffle** 的官方文件,以进一步了解。
但是我为什么要使用这个目录结构呢?我不太习惯,而且它看起来很复杂……
嗯,原因有好几个。首先,如果你更改了这些文件夹的名称,**Truffle** 将无法正常运行。
-
其次,通过遵守此规范,你的项目将很容易被其他开发人员理解。简而言之,如果后面你的团队扩大或有什么变动,使用标准的文件夹结构和代码规范将让事情变得简单。
## truffle-hdwallet-provider
-在本课中,我们将使用 _Infura_ 来把代码部署到 **_以太坊_**。这样,我们的用户便可以直接运行该应用程序,他们无需设置自己的 **_以太坊_** 节点或钱包。然而,为了保证安全,_Infura_ 不管理私钥,这也意味着它不能代表我们签署事务易。由于部署智能合约需要 **Truffle** 签署事务,所以我们将需要一个叫做 `truffle-hdwallet-provider` 的工具。它惟一的目的就是处理事务签名。
+在本课中,我们将使用 _Infura_ 来把代码部署到 **_以太坊_**。这样,我们的用户便可以直接运行该应用程序,他们无需设置自己的 **_以太坊_** 节点或钱包。然而,为了保证安全,_Infura_ 不管理私钥,这也意味着它不能代表我们签署交易易。由于部署智能合约需要 **Truffle** 签署交易,所以我们将需要一个叫做 `truffle-hdwallet-provider` 的工具。它惟一的目的就是处理交易签名。
->注意: 也许你会问,为什么我们不在上一章安装 `truffle-hdwallet-provider`, 使用像下面这样的命令:
+> 注意: 也许你会问,为什么我们不在上一章安装 `truffle-hdwallet-provider`, 使用像下面这样的命令:
- ```JavaScript
- npm install truffle truffle-hdwallet-provider -g
- ```
-
- 嗯… `truffle init` 命令期望找到一个空目录。如果有任何文件在那里,它就会出错,因此我们需要按照正确的顺序来做,运行 `truffle init` 之后再安装 `truffle-hdwallet-provider`。
+```JavaScript
+npm install truffle truffle-hdwallet-provider -g
+```
+嗯… `truffle init` 命令期望找到一个空目录。如果有任何文件在那里,它就会出错,因此我们需要按照正确的顺序来做,运行 `truffle init` 之后再安装 `truffle-hdwallet-provider`。
# 实战演习:
diff --git a/zh/10/05.md b/zh/10/05.md
index 2800c0e2c..6afec9c4d 100644
--- a/zh/10/05.md
+++ b/zh/10/05.md
@@ -3,56 +3,57 @@ title: 配置文件
actions: ['checkAnswer', 'hints']
requireLogin: true
material:
- editor:
- language: javascript
- startingCode:
- "truffle.js": |
- /*
- * NB: since truffle-hdwallet-provider 0.0.5 you must wrap HDWallet providers in a
- * function when declaring them. Failure to do so will cause commands to hang. ex:
- *
- * mainnet: {
- * provider: function() {
- * return new HDWalletProvider(mnemonic, 'https://mainnet.infura.io/')
- * },
- * network_id: '1',
- * gas: 4500000,
- * gasPrice: 10000000000,
- * },
- */
-
- answer: |
-
- const HDWalletProvider = require("truffle-hdwallet-provider");
-
- // Set your own mnemonic here
- const mnemonic = "YOUR_MNEMONIC";
-
- // Module exports to make this configuration available to Truffle itself
- module.exports = {
- // Object with configuration for each network
- networks: {
- // Configuration for mainnet
- mainnet: {
- provider: function () {
- // Setting the provider with the Infura Rinkeby address and Token
- return new HDWalletProvider(mnemonic, "https://mainnet.infura.io/v3/YOUR_TOKEN")
- },
- network_id: "1"
- },
- // Configuration for rinkeby network
- rinkeby: {
- // Special function to setup the provider
- provider: function () {
- // Setting the provider with the Infura Rinkeby address and Token
- return new HDWalletProvider(mnemonic, "https://rinkeby.infura.io/v3/YOUR_TOKEN")
- },
- // Network id is 4 for Rinkeby
- network_id: 4
- }
- }
- };
+ editor:
+ language: javascript
+ startingCode:
+ 'truffle.js': |
+ /*
+ * NB: since truffle-hdwallet-provider 0.0.5 you must wrap HDWallet providers in a
+ * function when declaring them. Failure to do so will cause commands to hang. ex:
+ *
+ * mainnet: {
+ * provider: function() {
+ * return new HDWalletProvider(mnemonic, 'https://mainnet.infura.io/')
+ * },
+ * network_id: '1',
+ * gas: 4500000,
+ * gasPrice: 10000000000,
+ * },
+ */
+
+ answer: |
+
+ const HDWalletProvider = require("truffle-hdwallet-provider");
+
+ // Set your own mnemonic here
+ const mnemonic = "YOUR_MNEMONIC";
+
+ // Module exports to make this configuration available to Truffle itself
+ module.exports = {
+ // Object with configuration for each network
+ networks: {
+ // Configuration for mainnet
+ mainnet: {
+ provider: function () {
+ // Setting the provider with the Infura Rinkeby address and Token
+ return new HDWalletProvider(mnemonic, "https://mainnet.infura.io/v3/YOUR_TOKEN")
+ },
+ network_id: "1"
+ },
+ // Configuration for rinkeby network
+ rinkeby: {
+ // Special function to setup the provider
+ provider: function () {
+ // Setting the provider with the Infura Rinkeby address and Token
+ return new HDWalletProvider(mnemonic, "https://rinkeby.infura.io/v3/YOUR_TOKEN")
+ },
+ // Network id is 4 for Rinkeby
+ network_id: 4
+ }
+ }
+ };
---
+
太棒了!你已经成功地编译了源代码并创建了迁移文件。
在部署之前还有一件事要做。我们必须编辑配置文件,来告诉 **Truffle** 我们想要部署到哪些网络。
@@ -61,9 +62,9 @@ material:
## 以太坊测试网络
-好几个 **_以太坊_** 公共测试网络允许你在将合约部署到主网之前免费测试(请记住,一旦将合约部署到主网后,就不能再更改了)。这些测试网使用与主网不同的共识算法(通常是PoA),为鼓励全面测试,Ether 免费试用。Ether is free to encourage thorough testing.
+好几个 **_以太坊_** 公共测试网络允许你在将合约部署到主网之前免费测试(请记住,一旦将合约部署到主网后,就不能再更改了)。这些测试网使用与主网不同的共识算法(通常是 PoA),为鼓励全面测试,Ether 免费试用。Ether is free to encourage thorough testing.
-在这节课中,我们将使用 **_Rinkeby_** —— 由以太坊基金会创建的公共测试网络。
+在这节课中,我们将使用 **_Rinkeby_** —— 由以太坊基金会创建的公共测试网络。
## truffle.js 配置文件
@@ -88,11 +89,11 @@ $ cat truffle.js
它只是个空 shell。因此,我们需要更新这个文件,以便将合约部署到 Rinkeby 和以太坊主网。
-### Truffle 的硬件钱包 provider
+### Truffle 的硬件钱包 provider
还记得第二章吗?
-我们让你装了一个叫做 `truffle-hdwallet-provider` 的附加包,好帮助 **Truffle** 签署事务。
+我们让你装了一个叫做 `truffle-hdwallet-provider` 的附加包,好帮助 **Truffle** 签署交易。
现在,我们想要编辑配置文件以使用 `HDWalletProvider`。先得在文件顶部添加一行:
@@ -114,7 +115,7 @@ var mnemonic = "onions carrots beans ...";
**在本例中,为了简单起见**,我们复制了助记词并将其存储在一个变量中。
-### 为 Rinkeby 和以太坊主网设置 Truffle
+### 为 Rinkeby 和以太坊主网设置 Truffle
接下来,为了确保 **Truffle** “知道”我们想要部署到哪些网络,我们必须创建两个单独的对象 —— 一个用于 Rinkeby,另一个用于 **_以太坊_** 主网:
@@ -140,7 +141,7 @@ networks: {
}
```
->注意: provider 值包装在一个函数中,这确保了在需要时才对它进行初始化。
+> 注意: provider 值包装在一个函数中,这确保了在需要时才对它进行初始化。
### 快结束了
@@ -184,5 +185,4 @@ module.exports = {
我们已经为你更新了大部分配置文件。现在来填补缺失的部分吧:
1. 在文件顶部,添加初始化 `truff -hdwallet-provider` 的代码行。
-
2. 填写 Rinkeby 网络的 `network_id`。如果你不记得该参数的值,请检查上面的代码片段。
diff --git a/zh/10/07.md b/zh/10/07.md
index 8ccc777f6..005874076 100644
--- a/zh/10/07.md
+++ b/zh/10/07.md
@@ -3,16 +3,15 @@ title: 在 Loom 上使用 Truffle!
actions: ['checkAnswer', 'hints']
requireLogin: true
material:
- terminal:
- help:
- You should probably run `npm install loom-truffle-provider`.
- commands:
- "npm install loom-truffle-provider":
- hint: npm install loom-truffle-provider
- output: |
- + loom-truffle-provider@0.8.0
- added 227 packages from 193 contributors and audited 158456 packages in 50.265s
- found 0 vulnerabilities
+ terminal:
+ help: You should probably run `npm install loom-truffle-provider`.
+ commands:
+ 'npm install loom-truffle-provider':
+ hint: npm install loom-truffle-provider
+ output: |
+ + loom-truffle-provider@0.8.0
+ added 227 packages from 193 contributors and audited 158456 packages in 50.265s
+ found 0 vulnerabilities
---
看起来好像没什么,但你刚刚已经部署了 `CryptoZombies` 智能合约!
@@ -21,9 +20,9 @@ material:
## Loom Basechain
-现在,如果你想在 `以太坊` 上构建 DApp,有一点你应该清楚 —— 在主网上,用户需要 `为每笔事务支付 gas 费`。这对于面向用户的 DApp 或游戏来说并不理想。它很容易破坏用户体验。
+现在,如果你想在 `以太坊` 上构建 DApp,有一点你应该清楚 —— 在主网上,用户需要 `为每笔交易支付 gas 费`。这对于面向用户的 DApp 或游戏来说并不理想。它很容易破坏用户体验。
-相反,在 `Loom` 上,你的用户可以有更快速且无 gas 的事务,这使得它更适合游戏和其他非金融类应用。
+相反,在 `Loom` 上,你的用户可以有更快速且无 gas 的交易,这使得它更适合游戏和其他非金融类应用。
这意味着你的 `Loom` 僵尸将是快僵尸!
@@ -31,7 +30,6 @@ material:
下一章,我们将带你了解部署到 `Loom`。
-
## loom-truffle-provider
在 `Loom`,我们使用 **Truffle** 构建、测试和部署我们的智能合约。为了让事情变得简单,我们开发了一个叫做 `provider` 的东西,它使得在 `Loom` 上部署 Truffle 跟在 Rinkeby 或以太坊主网上一样。
@@ -42,4 +40,4 @@ material:
1. 我们已经让 `loom-truffle-provider` 作为一个 `npm` 包使用。现在来安装吧:
- >注意:这一次,不需要让包全局可见。
+> 注意:这一次,不需要让包全局可见。
diff --git a/zh/10/lessoncomplete.md b/zh/10/lessoncomplete.md
index d3fbb18e9..11de0bb89 100644
--- a/zh/10/lessoncomplete.md
+++ b/zh/10/lessoncomplete.md
@@ -2,14 +2,14 @@
title: 学完啦!
actions: ['checkAnswer', 'hints']
material:
- lessonComplete: 1
+ lessonComplete: 1
---
太厉害了!👏🏻👏🏻👏🏻
你已经掌握了使用 **Truffle** 部署智能合约的技能!
-请记住,在 **_Loom_** 上构建将为你带来更快速、无 gas 的事务,是你创建区块链游戏和面向用户型 dapp 的完美选择。与此同时,你的用户也将享受到以太坊提供的安全保障!
+请记住,在 **_Loom_** 上构建将为你带来更快速、无 gas 的交易,是你创建区块链游戏和面向用户型 dapp 的完美选择。与此同时,你的用户也将享受到以太坊提供的安全保障!
另一点也请记住,部署到 **_Loom_** 与部署到以太网差不多。你已经知道具体怎么实现,下一个项目就请选择使用最适合你的吧 😉。
@@ -17,4 +17,4 @@ material:
要进一步了解这些知识,请访问我们的开发者文档。
-祝搬砖愉快!
\ No newline at end of file
+祝搬砖愉快!
diff --git a/zh/11/05.md b/zh/11/05.md
index 2dfcf5696..ef3201eb7 100644
--- a/zh/11/05.md
+++ b/zh/11/05.md
@@ -6,7 +6,7 @@ material:
editor:
language: javascript
startingCode:
- "test/CryptoZombies.js": |
+ 'test/CryptoZombies.js': |
const CryptoZombies = artifacts.require("CryptoZombies");
const zombieNames = ["Zombie 1", "Zombie 2"];
contract("CryptoZombies", (accounts) => {
@@ -31,6 +31,7 @@ material:
})
})
---
+
现在我们的僵尸已经列队准备就绪,进入下一个阶段吧…… 🧟🧟🧟🧟🧟🧟
## 2. 行动
@@ -44,7 +45,9 @@ material:
以下调用 `createRandomZombie` 并 `msg.sender` 设置为 Alice 的地址:
```javascript
-const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
+const result = await contractInstance.createRandomZombie(zombieNames[0], {
+ from: alice,
+})
```
那我问你:你知道 `result` 中存储了什么吗?
@@ -55,12 +58,12 @@ const result = await contractInstance.createRandomZombie(zombieNames[0], {from:
在使用 `artifacts.require` 指定了我们要测试的合约后,_Truffle_ 就会自动提供我们智能合约产生的日志。这意味着我们现在可以这样来检索 Alice 的新僵尸名字:`result.logs[0].args.name`。用类似的方式,我们还可以获得其 `id` 和`_dna`。
-除了这些信息外,`result` 还将为我们提供关于事务的其他一些有用细节:
-- `result.tx`: 事务哈希
-- `result.receipt`: 包含事务收据的对象。如果 `result.receipt.status` 的值是 `true`, 表示事务成功。否则,就意味着事务失败。
+除了这些信息外,`result` 还将为我们提供关于交易的其他一些有用细节:
+- `result.tx`: 交易哈希
+- `result.receipt`: 包含交易收据的对象。如果 `result.receipt.status` 的值是 `true`, 表示交易成功。否则,就意味着交易失败。
->注意:还可以选择日志来存储数据。优点是很低廉,缺点是无法从智能合约内部对其进行访问。
+> 注意:还可以选择日志来存储数据。优点是很低廉,缺点是无法从智能合约内部对其进行访问。
## 3. 断言
@@ -70,18 +73,16 @@ const result = await contractInstance.createRandomZombie(zombieNames[0], {from:
让我们来完成第一个测试。
-
1. 声明一个名为 `result` 的 `const` ,并将它赋值为 `contractInstance.createRandomZombie` 的结果,以僵尸的名字和主人作为参数。
2. 有了 `result` 之后,使用两个参数调用 `assert.equal` —— `result.receipt.status` 和 `true`。
如果以上条件为 true,我们可以假定我们的测试已通过。为了安全起见,我们再加一层检查。
-3. 在下一行中,检查 `result.logs[0].args.name` 是否等于 `zombieNames[0]`。像上面一样使用 `assert.equal`。
+3. 在下一行中,检查 `result.logs[0].args.name` 是否等于 `zombieNames[0]`。像上面一样使用 `assert.equal`。
现在,可以运行`truffle test` 了,看看我们第一个测试是否会通过。方式是,_Truffle_ 只检查 _"test"_ 目录并执行它在其中找到的文件。
-
实上,我们已经为你做好了。输出会是这样的:
```bash
diff --git a/zh/11/12.md b/zh/11/12.md
index fead458c5..73b7289ab 100644
--- a/zh/11/12.md
+++ b/zh/11/12.md
@@ -3,14 +3,135 @@ title: 僵尸战斗
actions: ['checkAnswer', 'hints']
requireLogin: true
material:
- editor:
- language: javascript
- startingCode:
- "test/CryptoZombies.js": |
+ editor:
+ language: javascript
+ startingCode:
+ 'test/CryptoZombies.js': |
+ const CryptoZombies = artifacts.require("CryptoZombies");
+ const utils = require("./helpers/utils");
+ const time = require("./helpers/time");
+ const zombieNames = ["Zombie 1", "Zombie 2"];
+ contract("CryptoZombies", (accounts) => {
+ let [alice, bob] = accounts;
+ let contractInstance;
+ beforeEach(async () => {
+ contractInstance = await CryptoZombies.new();
+ });
+ it("should be able to create a new zombie", async () => {
+ 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 () => {
+ await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
+ await utils.shouldThrow(contractInstance.createRandomZombie(zombieNames[1], {from: alice}));
+ })
+ context("with the single-step transfer scenario", async () => {
+ it("should transfer a zombie", async () => {
+ const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
+ const zombieId = result.logs[0].args.zombieId.toNumber();
+ await contractInstance.transferFrom(alice, bob, zombieId, {from: alice});
+ const newOwner = await contractInstance.ownerOf(zombieId);
+ assert.equal(newOwner, bob);
+ })
+ })
+ context("with the two-step transfer scenario", async () => {
+ it("should approve and then transfer a zombie when the approved address calls transferForm", async () => {
+ const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
+ const zombieId = result.logs[0].args.zombieId.toNumber();
+ await contractInstance.approve(bob, zombieId, {from: alice});
+ await contractInstance.transferFrom(alice, bob, zombieId, {from: bob});
+ const newOwner = await contractInstance.ownerOf(zombieId);
+ assert.equal(newOwner,bob);
+ })
+ it("should approve and then transfer a zombie when the owner calls transferForm", async () => {
+ const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
+ const zombieId = result.logs[0].args.zombieId.toNumber();
+ await contractInstance.approve(bob, zombieId, {from: alice});
+ await contractInstance.transferFrom(alice, bob, zombieId, {from: alice});
+ const newOwner = await contractInstance.ownerOf(zombieId);
+ assert.equal(newOwner,bob);
+ })
+ })
+ it("zombies should be able to attack another zombie", async () => {
+ let result;
+ result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
+ const firstZombieId = result.logs[0].args.zombieId.toNumber();
+ result = await contractInstance.createRandomZombie(zombieNames[1], {from: bob});
+ const secondZombieId = result.logs[0].args.zombieId.toNumber();
+ //TODO: increase the time
+ await contractInstance.attack(firstZombieId, secondZombieId, {from: alice});
+ assert.equal(result.receipt.status, true);
+ })
+ })
+ 'test/helpers/utils.js': |
+ async function shouldThrow(promise) {
+ try {
+ await promise;
+ assert(true);
+ }
+ catch (err) {
+ return;
+ }
+ assert(false, "The contract did not throw.");
+
+ }
+
+ module.exports = {
+ shouldThrow,
+ };
+
+ 'test/helpers/time.js': |
+ async function increase(duration) {
+
+ //first, let's increase time
+ await web3.currentProvider.sendAsync({
+ jsonrpc: "2.0",
+ method: "evm_increaseTime",
+ params: [duration], // 86400 seconds in a day
+ id: new Date().getTime()
+ }, () => {});
+
+ //next, let's mine a new block
+ web3.currentProvider.send({
+ jsonrpc: '2.0',
+ method: 'evm_mine',
+ params: [],
+ id: new Date().getTime()
+ })
+
+ }
+
+ const duration = {
+
+ seconds: function (val) {
+ return val;
+ },
+ minutes: function (val) {
+ return val * this.seconds(60);
+ },
+ hours: function (val) {
+ return val * this.minutes(60);
+ },
+ days: function (val) {
+ return val * this.hours(24);
+ },
+ }
+
+ module.exports = {
+ increase,
+ duration,
+ };
+
+ answer: >
const CryptoZombies = artifacts.require("CryptoZombies");
+
const utils = require("./helpers/utils");
+
const time = require("./helpers/time");
+
const zombieNames = ["Zombie 1", "Zombie 2"];
+
contract("CryptoZombies", (accounts) => {
let [alice, bob] = accounts;
let contractInstance;
@@ -59,133 +180,13 @@ material:
const firstZombieId = result.logs[0].args.zombieId.toNumber();
result = await contractInstance.createRandomZombie(zombieNames[1], {from: bob});
const secondZombieId = result.logs[0].args.zombieId.toNumber();
- //TODO: increase the time
+ await time.increase(time.duration.days(1));
await contractInstance.attack(firstZombieId, secondZombieId, {from: alice});
assert.equal(result.receipt.status, true);
})
})
- "test/helpers/utils.js": |
- async function shouldThrow(promise) {
- try {
- await promise;
- assert(true);
- }
- catch (err) {
- return;
- }
- assert(false, "The contract did not throw.");
-
- }
-
- module.exports = {
- shouldThrow,
- };
-
- "test/helpers/time.js": |
- async function increase(duration) {
-
- //first, let's increase time
- await web3.currentProvider.sendAsync({
- jsonrpc: "2.0",
- method: "evm_increaseTime",
- params: [duration], // 86400 seconds in a day
- id: new Date().getTime()
- }, () => {});
-
- //next, let's mine a new block
- web3.currentProvider.send({
- jsonrpc: '2.0',
- method: 'evm_mine',
- params: [],
- id: new Date().getTime()
- })
-
- }
-
- const duration = {
-
- seconds: function (val) {
- return val;
- },
- minutes: function (val) {
- return val * this.seconds(60);
- },
- hours: function (val) {
- return val * this.minutes(60);
- },
- days: function (val) {
- return val * this.hours(24);
- },
- }
-
- module.exports = {
- increase,
- duration,
- };
-
- answer: >
- const CryptoZombies = artifacts.require("CryptoZombies");
-
- const utils = require("./helpers/utils");
-
- const time = require("./helpers/time");
-
- const zombieNames = ["Zombie 1", "Zombie 2"];
-
- contract("CryptoZombies", (accounts) => {
- let [alice, bob] = accounts;
- let contractInstance;
- beforeEach(async () => {
- contractInstance = await CryptoZombies.new();
- });
- it("should be able to create a new zombie", async () => {
- 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 () => {
- await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
- await utils.shouldThrow(contractInstance.createRandomZombie(zombieNames[1], {from: alice}));
- })
- context("with the single-step transfer scenario", async () => {
- it("should transfer a zombie", async () => {
- const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
- const zombieId = result.logs[0].args.zombieId.toNumber();
- await contractInstance.transferFrom(alice, bob, zombieId, {from: alice});
- const newOwner = await contractInstance.ownerOf(zombieId);
- assert.equal(newOwner, bob);
- })
- })
- context("with the two-step transfer scenario", async () => {
- it("should approve and then transfer a zombie when the approved address calls transferForm", async () => {
- const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
- const zombieId = result.logs[0].args.zombieId.toNumber();
- await contractInstance.approve(bob, zombieId, {from: alice});
- await contractInstance.transferFrom(alice, bob, zombieId, {from: bob});
- const newOwner = await contractInstance.ownerOf(zombieId);
- assert.equal(newOwner,bob);
- })
- it("should approve and then transfer a zombie when the owner calls transferForm", async () => {
- const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
- const zombieId = result.logs[0].args.zombieId.toNumber();
- await contractInstance.approve(bob, zombieId, {from: alice});
- await contractInstance.transferFrom(alice, bob, zombieId, {from: alice});
- const newOwner = await contractInstance.ownerOf(zombieId);
- assert.equal(newOwner,bob);
- })
- })
- it("zombies should be able to attack another zombie", async () => {
- let result;
- result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
- const firstZombieId = result.logs[0].args.zombieId.toNumber();
- result = await contractInstance.createRandomZombie(zombieNames[1], {from: bob});
- const secondZombieId = result.logs[0].args.zombieId.toNumber();
- await time.increase(time.duration.days(1));
- await contractInstance.attack(firstZombieId, secondZombieId, {from: alice});
- assert.equal(result.receipt.status, true);
- })
- })
---
+
哇哦!前几章的信息量有点大哦,但很多都是基础内容。
所以,全部的场景都讲完了吗?还没有哦,压轴的部分肯定会放在最后啦。
@@ -194,9 +195,9 @@ material:
这个测试非常简单,包括以下步骤:
-- **第一步**,我们将创建两个新僵尸 —— 一个 Alice 的,一个 Bob 的。
-- **第二步**,Alice 将以 Bob 的 `zombieId` 作为参数在她的僵尸上运行 `attack`。
-- **最后**,为了使测试通过,我们将检查 `result.receipt.status` 是否等于 `true`。
+- **第一步**,我们将创建两个新僵尸 —— 一个 Alice 的,一个 Bob 的。
+- **第二步**,Alice 将以 Bob 的 `zombieId` 作为参数在她的僵尸上运行 `attack`。
+- **最后**,为了使测试通过,我们将检查 `result.receipt.status` 是否等于 `true`。
假设我已经快速编写了所有这些逻辑,将其封装在一个 `it()` 函数中,并运行了 `truffle test` 测试。
@@ -251,7 +252,7 @@ function _createZombie(string _name, uint _dna) internal {
看到问题了吗?
-测试失败的原因是因为我们在游戏中增加了一个**冷却时间**,使得僵尸在攻击(或进食)后必须等待**1天**才能再次攻击。
+测试失败的原因是因为我们在游戏中增加了一个**冷却时间**,使得僵尸在攻击(或进食)后必须等待**1 天**才能再次攻击。
没有这个的话,僵尸每天可以无数次攻击和增殖,这将让游戏很弱智。。
@@ -261,35 +262,38 @@ function _createZombie(string _name, uint _dna) internal {
幸好,我们不必等那么久。事实上,根本就不需要等。因为 _Ganache_ 提供了一种通过两个辅助功能及时前行的方法:
- - `evm_increaseTime`: 增加下一个区块的时间。
- - `evm_mine`: 挖一个新区块.
+- `evm_increaseTime`: 增加下一个区块的时间。
+- `evm_mine`: 挖一个新区块.
你甚至不需要 Tardis 或 DeLorean 来进行这种时间旅行。
让我来解释下这些函数是如何运行的:
-- 每次挖一个新区块时,矿工都会向它添加一个时间戳。假设在第5个区块中挖到了生成僵尸的事务。
+- 每次挖一个新区块时,矿工都会向它添加一个时间戳。假设在第 5 个区块中挖到了生成僵尸的交易。
-- 接下来,我们调用 `evm_increaseTime`,但由于区块链是不可变的,所以不可能修改现有区块。所以,当合约检查时间时,它不会增加。
+- 接下来,我们调用 `evm_increaseTime`,但由于区块链是不可变的,所以不可能修改现有区块。所以,当合约检查时间时,它不会增加。
-- 如果我们运行 `evm_mine`,那么第6个区块就会被挖出(并加上时间戳),这意味着,当我们让僵尸投入战斗时,智能合约将“看到”一天已经过去了。
+- 如果我们运行 `evm_mine`,那么第 6 个区块就会被挖出(并加上时间戳),这意味着,当我们让僵尸投入战斗时,智能合约将“看到”一天已经过去了。
综上所述,我们可以通过时间旅行来修正我们的测试,具体以下:
```javascript
-await web3.currentProvider.sendAsync({
- jsonrpc: "2.0",
- method: "evm_increaseTime",
- params: [86400], // there are 86400 seconds in a day
- id: new Date().getTime()
-}, () => { });
+await web3.currentProvider.sendAsync(
+ {
+ jsonrpc: '2.0',
+ method: 'evm_increaseTime',
+ params: [86400], // there are 86400 seconds in a day
+ id: new Date().getTime(),
+ },
+ () => {}
+)
web3.currentProvider.send({
jsonrpc: '2.0',
method: 'evm_mine',
params: [],
- id: new Date().getTime()
-});
+ id: new Date().getTime(),
+})
```
嗯,这段代码不错,但是我们不会将这个逻辑添加到我们的 `CryptoZombies.js` 文件中。
@@ -300,7 +304,7 @@ web3.currentProvider.send({
所以我们添加了另一个名为 `days` 的 _辅助函数_,它以希望增加时间的天数作为参数。你可以这样来调用这个函数:`await time.increase(time.duration.days(1))`
->注意:很明显,时间旅行在主网或任何由矿工保护的现有测试链上都是不可用的。如果有人可以改变现实世界中时间的运作方式,那岂不就乱套了!对于测试智能合约,时间旅行可是程序员相当重要的一项技能。
+> 注意:很明显,时间旅行在主网或任何由矿工保护的现有测试链上都是不可用的。如果有人可以改变现实世界中时间的运作方式,那岂不就乱套了!对于测试智能合约,时间旅行可是程序员相当重要的一项技能。
# 实战演习
diff --git a/zh/11/13.md b/zh/11/13.md
index 1b240399e..f9cba1896 100644
--- a/zh/11/13.md
+++ b/zh/11/13.md
@@ -3,15 +3,143 @@ title: 用 Chai 进行语义更丰富的断言
actions: ['checkAnswer', 'hints']
requireLogin: true
material:
- editor:
- language: javascript
- startingCode:
- "test/CryptoZombies.js": |
+ editor:
+ language: javascript
+ startingCode:
+ 'test/CryptoZombies.js': |
+ const CryptoZombies = artifacts.require("CryptoZombies");
+ const utils = require("./helpers/utils");
+ const time = require("./helpers/time");
+ //TODO: import expect into our project
+ const zombieNames = ["Zombie 1", "Zombie 2"];
+ contract("CryptoZombies", (accounts) => {
+ let [alice, bob] = accounts;
+ let contractInstance;
+ beforeEach(async () => {
+ contractInstance = await CryptoZombies.new();
+ });
+ it("should be able to create a new zombie", async () => {
+ const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
+ //TODO: replace with expect
+ assert.equal(result.receipt.status, true);
+ assert.equal(result.logs[0].args.name,zombieNames[0]);
+ })
+ it("should not allow two zombies", async () => {
+ await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
+ await utils.shouldThrow(contractInstance.createRandomZombie(zombieNames[1], {from: alice}));
+ })
+ context("with the single-step transfer scenario", async () => {
+ it("should transfer a zombie", async () => {
+ const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
+ const zombieId = result.logs[0].args.zombieId.toNumber();
+ await contractInstance.transferFrom(alice, bob, zombieId, {from: alice});
+ const newOwner = await contractInstance.ownerOf(zombieId);
+ //TODO: replace with expect
+ assert.equal(newOwner, bob);
+ })
+ })
+ context("with the two-step transfer scenario", async () => {
+ it("should approve and then transfer a zombie when the approved address calls transferForm", async () => {
+ const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
+ const zombieId = result.logs[0].args.zombieId.toNumber();
+ await contractInstance.approve(bob, zombieId, {from: alice});
+ await contractInstance.transferFrom(alice, bob, zombieId, {from: bob});
+ const newOwner = await contractInstance.ownerOf(zombieId);
+ //TODO: replace with expect
+ assert.equal(newOwner,bob);
+ })
+ it("should approve and then transfer a zombie when the owner calls transferForm", async () => {
+ const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
+ const zombieId = result.logs[0].args.zombieId.toNumber();
+ await contractInstance.approve(bob, zombieId, {from: alice});
+ await contractInstance.transferFrom(alice, bob, zombieId, {from: alice});
+ const newOwner = await contractInstance.ownerOf(zombieId);
+ //TODO: replace with expect
+ assert.equal(newOwner,bob);
+ })
+ })
+ it("zombies should be able to attack another zombie", async () => {
+ let result;
+ result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
+ const firstZombieId = result.logs[0].args.zombieId.toNumber();
+ result = await contractInstance.createRandomZombie(zombieNames[1], {from: bob});
+ const secondZombieId = result.logs[0].args.zombieId.toNumber();
+ await time.increase(time.duration.days(1));
+ await contractInstance.attack(firstZombieId, secondZombieId, {from: alice});
+ //TODO: replace with expect
+ assert.equal(result.receipt.status, true);
+ })
+ })
+
+ 'test/helpers/utils.js': |
+ async function shouldThrow(promise) {
+ try {
+ await promise;
+ assert(true);
+ }
+ catch (err) {
+ return;
+ }
+ assert(false, "The contract did not throw.");
+
+ }
+
+ module.exports = {
+ shouldThrow,
+ };
+ 'test/helpers/time.js': |
+ async function increase(duration) {
+
+ //first, let's increase time
+ await web3.currentProvider.sendAsync({
+ jsonrpc: "2.0",
+ method: "evm_increaseTime",
+ params: [duration], // 86400 seconds in a day
+ id: new Date().getTime()
+ }, () => {});
+
+ //next, let's mine a new block
+ web3.currentProvider.send({
+ jsonrpc: '2.0',
+ method: 'evm_mine',
+ params: [],
+ id: new Date().getTime()
+ })
+
+ }
+
+ const duration = {
+
+ seconds: function (val) {
+ return val;
+ },
+ minutes: function (val) {
+ return val * this.seconds(60);
+ },
+ hours: function (val) {
+ return val * this.minutes(60);
+ },
+ days: function (val) {
+ return val * this.hours(24);
+ },
+ }
+
+ module.exports = {
+ increase,
+ duration,
+ };
+
+ answer: >
const CryptoZombies = artifacts.require("CryptoZombies");
+
const utils = require("./helpers/utils");
+
const time = require("./helpers/time");
- //TODO: import expect into our project
+
+ var expect = require('chai').expect;
+
const zombieNames = ["Zombie 1", "Zombie 2"];
+
contract("CryptoZombies", (accounts) => {
let [alice, bob] = accounts;
let contractInstance;
@@ -20,9 +148,8 @@ material:
});
it("should be able to create a new zombie", async () => {
const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
- //TODO: replace with expect
- assert.equal(result.receipt.status, true);
- assert.equal(result.logs[0].args.name,zombieNames[0]);
+ expect(result.receipt.status).to.equal(true);
+ expect(result.logs[0].args.name).to.equal(zombieNames[0]);
})
it("should not allow two zombies", async () => {
await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
@@ -34,8 +161,7 @@ material:
const zombieId = result.logs[0].args.zombieId.toNumber();
await contractInstance.transferFrom(alice, bob, zombieId, {from: alice});
const newOwner = await contractInstance.ownerOf(zombieId);
- //TODO: replace with expect
- assert.equal(newOwner, bob);
+ expect(newOwner).to.equal(bob);
})
})
context("with the two-step transfer scenario", async () => {
@@ -45,8 +171,7 @@ material:
await contractInstance.approve(bob, zombieId, {from: alice});
await contractInstance.transferFrom(alice, bob, zombieId, {from: bob});
const newOwner = await contractInstance.ownerOf(zombieId);
- //TODO: replace with expect
- assert.equal(newOwner,bob);
+ expect(newOwner).to.equal(bob);
})
it("should approve and then transfer a zombie when the owner calls transferForm", async () => {
const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
@@ -54,8 +179,7 @@ material:
await contractInstance.approve(bob, zombieId, {from: alice});
await contractInstance.transferFrom(alice, bob, zombieId, {from: alice});
const newOwner = await contractInstance.ownerOf(zombieId);
- //TODO: replace with expect
- assert.equal(newOwner,bob);
+ expect(newOwner).to.equal(bob);
})
})
it("zombies should be able to attack another zombie", async () => {
@@ -66,134 +190,11 @@ material:
const secondZombieId = result.logs[0].args.zombieId.toNumber();
await time.increase(time.duration.days(1));
await contractInstance.attack(firstZombieId, secondZombieId, {from: alice});
- //TODO: replace with expect
- assert.equal(result.receipt.status, true);
- })
- })
-
- "test/helpers/utils.js": |
- async function shouldThrow(promise) {
- try {
- await promise;
- assert(true);
- }
- catch (err) {
- return;
- }
- assert(false, "The contract did not throw.");
-
- }
-
- module.exports = {
- shouldThrow,
- };
- "test/helpers/time.js": |
- async function increase(duration) {
-
- //first, let's increase time
- await web3.currentProvider.sendAsync({
- jsonrpc: "2.0",
- method: "evm_increaseTime",
- params: [duration], // 86400 seconds in a day
- id: new Date().getTime()
- }, () => {});
-
- //next, let's mine a new block
- web3.currentProvider.send({
- jsonrpc: '2.0',
- method: 'evm_mine',
- params: [],
- id: new Date().getTime()
- })
-
- }
-
- const duration = {
-
- seconds: function (val) {
- return val;
- },
- minutes: function (val) {
- return val * this.seconds(60);
- },
- hours: function (val) {
- return val * this.minutes(60);
- },
- days: function (val) {
- return val * this.hours(24);
- },
- }
-
- module.exports = {
- increase,
- duration,
- };
-
- answer: >
- const CryptoZombies = artifacts.require("CryptoZombies");
-
- const utils = require("./helpers/utils");
-
- const time = require("./helpers/time");
-
- var expect = require('chai').expect;
-
- const zombieNames = ["Zombie 1", "Zombie 2"];
-
- contract("CryptoZombies", (accounts) => {
- let [alice, bob] = accounts;
- let contractInstance;
- beforeEach(async () => {
- contractInstance = await CryptoZombies.new();
- });
- it("should be able to create a new zombie", async () => {
- const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
- expect(result.receipt.status).to.equal(true);
- expect(result.logs[0].args.name).to.equal(zombieNames[0]);
- })
- it("should not allow two zombies", async () => {
- await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
- await utils.shouldThrow(contractInstance.createRandomZombie(zombieNames[1], {from: alice}));
- })
- context("with the single-step transfer scenario", async () => {
- it("should transfer a zombie", async () => {
- const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
- const zombieId = result.logs[0].args.zombieId.toNumber();
- await contractInstance.transferFrom(alice, bob, zombieId, {from: alice});
- const newOwner = await contractInstance.ownerOf(zombieId);
- expect(newOwner).to.equal(bob);
+ expect(result.receipt.status).to.equal(true);
})
})
- context("with the two-step transfer scenario", async () => {
- it("should approve and then transfer a zombie when the approved address calls transferForm", async () => {
- const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
- const zombieId = result.logs[0].args.zombieId.toNumber();
- await contractInstance.approve(bob, zombieId, {from: alice});
- await contractInstance.transferFrom(alice, bob, zombieId, {from: bob});
- const newOwner = await contractInstance.ownerOf(zombieId);
- expect(newOwner).to.equal(bob);
- })
- it("should approve and then transfer a zombie when the owner calls transferForm", async () => {
- const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
- const zombieId = result.logs[0].args.zombieId.toNumber();
- await contractInstance.approve(bob, zombieId, {from: alice});
- await contractInstance.transferFrom(alice, bob, zombieId, {from: alice});
- const newOwner = await contractInstance.ownerOf(zombieId);
- expect(newOwner).to.equal(bob);
- })
- })
- it("zombies should be able to attack another zombie", async () => {
- let result;
- result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
- const firstZombieId = result.logs[0].args.zombieId.toNumber();
- result = await contractInstance.createRandomZombie(zombieNames[1], {from: bob});
- const secondZombieId = result.logs[0].args.zombieId.toNumber();
- await time.increase(time.duration.days(1));
- await contractInstance.attack(firstZombieId, secondZombieId, {from: alice});
- expect(result.receipt.status).to.equal(true);
- })
- })
---
+
到目前为止,我们一直使用内置的`断言`模块来编写断言。虽然还不错,但是这个`断言`模块有一个大的弊端 —— 代码不易读。幸好,还有几个更好的断言模块,`Chai` 就是其中最好的一个。
## Chai 断言库
@@ -205,32 +206,32 @@ material:
- _expect_: 让你可以链接自然语言断言,如下所示:
```javascript
- let lessonTitle = "Testing Smart Contracts with Truffle";
- expect(lessonTitle).to.be.a("string");
+ let lessonTitle = 'Testing Smart Contracts with Truffle'
+ expect(lessonTitle).to.be.a('string')
```
- _should_: 许使用与 `expect` 接口类似的断言,但是该链以 `should` 属性开始:
```javascript
- let lessonTitle = "Testing Smart Contracts with Truffle";
- lessonTitle.should.be.a("string");
+ let lessonTitle = 'Testing Smart Contracts with Truffle'
+ lessonTitle.should.be.a('string')
```
- _assert_: 提供一个类似于 node.js 打包的符号,包括几个额外的测试,且与浏览器兼容:
- ```javascript
- let lessonTitle = "Testing Smart Contracts with Truffle";
- assert.typeOf(lessonTitle, "string");
- ```
+```javascript
+let lessonTitle = 'Testing Smart Contracts with Truffle'
+assert.typeOf(lessonTitle, 'string')
+```
在本章中,我们将向你展示如何使用 `expect` 来改进你的断言。
->注意:我们假设 `chai` 包已经安装到你的电脑上了。如果没有,很容易就能安装:`npm -g install chai`
+> 注意:我们假设 `chai` 包已经安装到你的电脑上了。如果没有,很容易就能安装:`npm -g install chai`
为了使用 `expect` 样式,我们首先要做的是将它导入到我们的项目中,如下所示:
```javascript
-var expect = require('chai').expect;
+var expect = require('chai').expect
```
## expect().to.equal()
@@ -238,8 +239,8 @@ var expect = require('chai').expect;
现在我们已经将 `expect` 导入到我们的项目中,检查两个字符串是否相等如下所示:
```javascript
-let zombieName = 'My Awesome Zombie';
-expect(zombieName).to.equal('My Awesome Zombie');
+let zombieName = 'My Awesome Zombie'
+expect(zombieName).to.equal('My Awesome Zombie')
```
话不多说,来好好利用下 `Chai` 的超能力吧!
@@ -248,15 +249,16 @@ expect(zombieName).to.equal('My Awesome Zombie');
1. 将 `expect` 移植到我们的项目中。
-2. 用 `zombieName` 继续上面的例子,我们可以使用 `expect` 来为一个成功的事务测试,如下所示:
+2. 用 `zombieName` 继续上面的例子,我们可以使用 `expect` 来为一个成功的交易测试,如下所示:
```javascript
-expect(result.receipt.status).to.equal(true);
+expect(result.receipt.status).to.equal(true)
```
+
我们可以这样来查看 Alice 是否有僵尸:
```javascript
-expect(zombieOwner).to.equal(alice);
+expect(zombieOwner).to.equal(alice)
```
1. 用 `expect` 来替换所有的 `assert.equal`。为了便于查找,我们在代码中留下了一些注释。
diff --git a/zh/11/14.md b/zh/11/14.md
index 9f5c3ccc9..b3d34c856 100644
--- a/zh/11/14.md
+++ b/zh/11/14.md
@@ -2,10 +2,59 @@
title: 在 Loom 上测试
actions: ['checkAnswer', 'hints']
material:
- editor:
- language: javascript
- startingCode:
- "truffle.js": |
+ editor:
+ language: javascript
+ startingCode:
+ 'truffle.js': |
+ const HDWalletProvider = require("truffle-hdwallet-provider");
+ const LoomTruffleProvider = require('loom-truffle-provider');
+ const mnemonic = "YOUR MNEMONIC HERE";
+ module.exports = {
+ // Object with configuration for each network
+ networks: {
+ //development
+ development: {
+ host: "127.0.0.1",
+ port: 7545,
+ network_id: "*",
+ gas: 9500000
+ },
+ // Configuration for mainnet
+ mainnet: {
+ provider: function() {
+ return new HDWalletProvider(mnemonic, "https://mainnet.infura.io/")
+ },
+ network_id: "1" // Match any network id
+ },
+ // Configuration for rinkeby network
+ rinkeby: {
+ provider: function() {
+ // Setting the provider with the Infura Rinkeby address and Token
+ return new HDWalletProvider(mnemonic, "https://rinkeby.infura.io/v3/e60cea1cd16e4a0bb524359bf83a8c01")
+ },
+ network_id: 4
+ },
+ // Configuration for Loom
+ loom_dapp_chain: {
+ provider: function() {
+ const privateKey = 'YOUR_PRIVATE_KEY';
+ const chainId = 'extdev-plasma-us1';
+ const writeUrl = 'http://extdev-plasma-us1.dappchains.com:80/rpc';
+ const readUrl = 'http://extdev-plasma-us1.dappchains.com:80/query';
+ // TODO: Replace the line below
+ return new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey);
+ },
+ network_id: '9545242630824'
+ }
+ },
+ compilers: {
+ solc: {
+ version: "0.4.25"
+ }
+ }
+ };
+
+ answer: |
const HDWalletProvider = require("truffle-hdwallet-provider");
const LoomTruffleProvider = require('loom-truffle-provider');
const mnemonic = "YOUR MNEMONIC HERE";
@@ -24,12 +73,11 @@ material:
provider: function() {
return new HDWalletProvider(mnemonic, "https://mainnet.infura.io/")
},
- network_id: "1" // Match any network id
+ network_id: "1"
},
// Configuration for rinkeby network
rinkeby: {
provider: function() {
- // Setting the provider with the Infura Rinkeby address and Token
return new HDWalletProvider(mnemonic, "https://rinkeby.infura.io/v3/e60cea1cd16e4a0bb524359bf83a8c01")
},
network_id: 4
@@ -41,8 +89,9 @@ material:
const chainId = 'extdev-plasma-us1';
const writeUrl = 'http://extdev-plasma-us1.dappchains.com:80/rpc';
const readUrl = 'http://extdev-plasma-us1.dappchains.com:80/query';
- // TODO: Replace the line below
- return new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey);
+ const loomTruffleProvider = new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey);
+ loomTruffleProvider.createExtraAccountsFromMnemonic(mnemonic, 10);
+ return loomTruffleProvider;
},
network_id: '9545242630824'
}
@@ -53,63 +102,13 @@ material:
}
}
};
-
-
- answer: |
- const HDWalletProvider = require("truffle-hdwallet-provider");
- const LoomTruffleProvider = require('loom-truffle-provider');
- const mnemonic = "YOUR MNEMONIC HERE";
- module.exports = {
- // Object with configuration for each network
- networks: {
- //development
- development: {
- host: "127.0.0.1",
- port: 7545,
- network_id: "*",
- gas: 9500000
- },
- // Configuration for mainnet
- mainnet: {
- provider: function() {
- return new HDWalletProvider(mnemonic, "https://mainnet.infura.io/")
- },
- network_id: "1"
- },
- // Configuration for rinkeby network
- rinkeby: {
- provider: function() {
- return new HDWalletProvider(mnemonic, "https://rinkeby.infura.io/v3/e60cea1cd16e4a0bb524359bf83a8c01")
- },
- network_id: 4
- },
- // Configuration for Loom
- loom_dapp_chain: {
- provider: function() {
- const privateKey = 'YOUR_PRIVATE_KEY';
- const chainId = 'extdev-plasma-us1';
- const writeUrl = 'http://extdev-plasma-us1.dappchains.com:80/rpc';
- const readUrl = 'http://extdev-plasma-us1.dappchains.com:80/query';
- const loomTruffleProvider = new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey);
- loomTruffleProvider.createExtraAccountsFromMnemonic(mnemonic, 10);
- return loomTruffleProvider;
- },
- network_id: '9545242630824'
- }
- },
- compilers: {
- solc: {
- version: "0.4.25"
- }
- }
- };
---
厉害!你肯定有好好练习。
现在,如果不向你展示如何在 **_Loom_** 测试网上进行测试,本教程就不完整啦。
-回想一下我们之前的课程,在 **_Loom_** 上,用户可以比在**以太坊**上有更快、更且无 gas 的事务。这使得 DAppChain 更适合游戏或面向用户的 DApp。
+回想一下我们之前的课程,在 **_Loom_** 上,用户可以比在**以太坊**上有更快、更且无 gas 的交易。这使得 DAppChain 更适合游戏或面向用户的 DApp。
而且,你知道吗?在 **Loom** 上部署和测试没有任何不同。我们已经为你总结了在 **_Loom_** 上测试所需要做的,快来看下吧。
@@ -129,7 +128,8 @@ material:
network_id: 'extdev'
}
```
->注意:永远不要泄露你的私钥!我们这样做只是为了简单起见。更安全的办法是将你的私钥保存到文件中,并从该文件中读取其值。如果这样做,请确保不要将保存私钥的文件放到 GitHub 上,因为那样任何人都能看到它。
+
+> 注意:永远不要泄露你的私钥!我们这样做只是为了简单起见。更安全的办法是将你的私钥保存到文件中,并从该文件中读取其值。如果这样做,请确保不要将保存私钥的文件放到 GitHub 上,因为那样任何人都能看到它。
## 账户数组
@@ -139,12 +139,17 @@ material:
return new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey)
```
- 用这个替换:
+用这个替换:
```javascript
-const loomTruffleProvider = new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey);
-loomTruffleProvider.createExtraAccountsFromMnemonic(mnemonic, 10);
-return loomTruffleProvider;
+const loomTruffleProvider = new LoomTruffleProvider(
+ chainId,
+ writeUrl,
+ readUrl,
+ privateKey
+)
+loomTruffleProvider.createExtraAccountsFromMnemonic(mnemonic, 10)
+return loomTruffleProvider
```
# 实战演习
@@ -154,9 +159,9 @@ return loomTruffleProvider;
还有一件事我们要注意。时间旅行只在针对 _Ganache_ 进行测试时可用,因此我们应该跳过这个测试。你已经知道如何通过在函数名前面放置 `x` 来 _跳过_ 测试了。但是,这次我们想让你再学点新东西。长话短说…… 你可以通过链接一个 `skip()` 函数调用来 _跳过_ 测试,如下所示:
```javascript
-it.skip("zombies should be able to attack another zombie", async () => {
+it.skip('zombies should be able to attack another zombie', async () => {
//We're skipping the body of the function for brevity
- })
+})
```
我们已经为你跳过了测试,并运行了 `truffle test --network loom_dapp_chain`。
@@ -177,6 +182,6 @@ Contract: CryptoZombies
5 passing (2m)
1 pending
- ```
+```
-各位,就到这里了!我们已经完成了 `CryptoZombies` 智能合约的测试。
\ No newline at end of file
+各位,就到这里了!我们已经完成了 `CryptoZombies` 智能合约的测试。
diff --git a/zh/3/10-savinggasview.md b/zh/3/10-savinggasview.md
index 677041247..73e3e578d 100644
--- a/zh/3/10-savinggasview.md
+++ b/zh/3/10-savinggasview.md
@@ -3,211 +3,210 @@ title: 利用 'View' 函数节省 Gas
actions: ['checkAnswer', 'hints']
requireLogin: true
material:
- editor:
- language: sol
- startingCode:
- "zombiehelper.sol": |
- pragma solidity ^0.4.19;
-
- import "./zombiefeeding.sol";
-
- contract ZombieHelper is ZombieFeeding {
-
- modifier aboveLevel(uint _level, uint _zombieId) {
- require(zombies[_zombieId].level >= _level);
- _;
- }
-
- function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
- require(msg.sender == zombieToOwner[_zombieId]);
- zombies[_zombieId].name = _newName;
- }
-
- function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
- require(msg.sender == zombieToOwner[_zombieId]);
- zombies[_zombieId].dna = _newDna;
- }
-
- // 在这里创建你的函数
-
- }
-
- "zombiefeeding.sol": |
- pragma solidity ^0.4.19;
-
- import "./zombiefactory.sol";
-
- contract KittyInterface {
- function getKitty(uint256 _id) external view returns (
- bool isGestating,
- bool isReady,
- uint256 cooldownIndex,
- uint256 nextActionAt,
- uint256 siringWithId,
- uint256 birthTime,
- uint256 matronId,
- uint256 sireId,
- uint256 generation,
- uint256 genes
- );
- }
-
- contract ZombieFeeding is ZombieFactory {
-
- KittyInterface kittyContract;
-
- function setKittyContractAddress(address _address) external onlyOwner {
- kittyContract = KittyInterface(_address);
- }
-
- function feedAndMultiply(uint _zombieId, uint _targetDna, string species) public {
- require(msg.sender == zombieToOwner[_zombieId]);
- Zombie storage myZombie = zombies[_zombieId];
- _targetDna = _targetDna % dnaModulus;
- uint newDna = (myZombie.dna + _targetDna) / 2;
- if (keccak256(species) == keccak256("kitty")) {
- newDna = newDna - newDna % 100 + 99;
- }
- _createZombie("NoName", newDna);
- }
-
- function feedOnKitty(uint _zombieId, uint _kittyId) public {
- uint kittyDna;
- (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
- feedAndMultiply(_zombieId, kittyDna, "kitty");
- }
-
- }
- "zombiefactory.sol": |
- pragma solidity ^0.4.19;
-
- import "./ownable.sol";
-
- contract ZombieFactory is Ownable {
+ editor:
+ language: sol
+ startingCode:
+ 'zombiehelper.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./zombiefeeding.sol";
+
+ contract ZombieHelper is ZombieFeeding {
+
+ modifier aboveLevel(uint _level, uint _zombieId) {
+ require(zombies[_zombieId].level >= _level);
+ _;
+ }
+
+ function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
+ require(msg.sender == zombieToOwner[_zombieId]);
+ zombies[_zombieId].name = _newName;
+ }
+
+ function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
+ require(msg.sender == zombieToOwner[_zombieId]);
+ zombies[_zombieId].dna = _newDna;
+ }
+
+ // 在这里创建你的函数
+
+ }
+
+ 'zombiefeeding.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./zombiefactory.sol";
+
+ contract KittyInterface {
+ function getKitty(uint256 _id) external view returns (
+ bool isGestating,
+ bool isReady,
+ uint256 cooldownIndex,
+ uint256 nextActionAt,
+ uint256 siringWithId,
+ uint256 birthTime,
+ uint256 matronId,
+ uint256 sireId,
+ uint256 generation,
+ uint256 genes
+ );
+ }
+
+ contract ZombieFeeding is ZombieFactory {
+
+ KittyInterface kittyContract;
+
+ function setKittyContractAddress(address _address) external onlyOwner {
+ kittyContract = KittyInterface(_address);
+ }
+
+ function feedAndMultiply(uint _zombieId, uint _targetDna, string species) public {
+ require(msg.sender == zombieToOwner[_zombieId]);
+ Zombie storage myZombie = zombies[_zombieId];
+ _targetDna = _targetDna % dnaModulus;
+ uint newDna = (myZombie.dna + _targetDna) / 2;
+ if (keccak256(species) == keccak256("kitty")) {
+ newDna = newDna - newDna % 100 + 99;
+ }
+ _createZombie("NoName", newDna);
+ }
+
+ function feedOnKitty(uint _zombieId, uint _kittyId) public {
+ uint kittyDna;
+ (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
+ feedAndMultiply(_zombieId, kittyDna, "kitty");
+ }
+
+ }
+ 'zombiefactory.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./ownable.sol";
+
+ contract ZombieFactory is Ownable {
+
+ event NewZombie(uint zombieId, string name, uint dna);
+
+ uint dnaDigits = 16;
+ uint dnaModulus = 10 ** dnaDigits;
+ uint cooldownTime = 1 days;
+
+ struct Zombie {
+ string name;
+ uint dna;
+ uint32 level;
+ uint32 readyTime;
+ }
+
+ Zombie[] public zombies;
+
+ mapping (uint => address) public zombieToOwner;
+ mapping (address => uint) ownerZombieCount;
+
+ function _createZombie(string _name, uint _dna) internal {
+ uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime))) - 1;
+ zombieToOwner[id] = msg.sender;
+ ownerZombieCount[msg.sender]++;
+ NewZombie(id, _name, _dna);
+ }
+
+ function _generateRandomDna(string _str) private view returns (uint) {
+ uint rand = uint(keccak256(_str));
+ return rand % dnaModulus;
+ }
+
+ function createRandomZombie(string _name) public {
+ require(ownerZombieCount[msg.sender] == 0);
+ uint randDna = _generateRandomDna(_name);
+ randDna = randDna - randDna % 100;
+ _createZombie(_name, randDna);
+ }
+
+ }
+ 'ownable.sol': |
+ /**
+ * @title Ownable
+ * @dev The Ownable contract has an owner address, and provides basic authorization control
+ * functions, this simplifies the implementation of "user permissions".
+ */
+ contract Ownable {
+ address public owner;
+
+ event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
+
+ /**
+ * @dev The Ownable constructor sets the original `owner` of the contract to the sender
+ * account.
+ */
+ function Ownable() public {
+ owner = msg.sender;
+ }
+
+
+ /**
+ * @dev Throws if called by any account other than the owner.
+ */
+ modifier onlyOwner() {
+ require(msg.sender == owner);
+ _;
+ }
+
+
+ /**
+ * @dev Allows the current owner to transfer control of the contract to a newOwner.
+ * @param newOwner The address to transfer ownership to.
+ */
+ function transferOwnership(address newOwner) public onlyOwner {
+ require(newOwner != address(0));
+ OwnershipTransferred(owner, newOwner);
+ owner = newOwner;
+ }
+
+ }
+ answer: >
+ pragma solidity ^0.4.19;
+
+ import "./zombiefeeding.sol";
+
+ contract ZombieHelper is ZombieFeeding {
+
+ modifier aboveLevel(uint _level, uint _zombieId) {
+ require(zombies[_zombieId].level >= _level);
+ _;
+ }
+
+ function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
+ require(msg.sender == zombieToOwner[_zombieId]);
+ zombies[_zombieId].name = _newName;
+ }
+
+ function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
+ require(msg.sender == zombieToOwner[_zombieId]);
+ zombies[_zombieId].dna = _newDna;
+ }
+
+ function getZombiesByOwner(address _owner) external view returns(uint[]) {
+
+ }
- event NewZombie(uint zombieId, string name, uint dna);
-
- uint dnaDigits = 16;
- uint dnaModulus = 10 ** dnaDigits;
- uint cooldownTime = 1 days;
-
- struct Zombie {
- string name;
- uint dna;
- uint32 level;
- uint32 readyTime;
}
-
- Zombie[] public zombies;
-
- mapping (uint => address) public zombieToOwner;
- mapping (address => uint) ownerZombieCount;
-
- function _createZombie(string _name, uint _dna) internal {
- uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime))) - 1;
- zombieToOwner[id] = msg.sender;
- ownerZombieCount[msg.sender]++;
- NewZombie(id, _name, _dna);
- }
-
- function _generateRandomDna(string _str) private view returns (uint) {
- uint rand = uint(keccak256(_str));
- return rand % dnaModulus;
- }
-
- function createRandomZombie(string _name) public {
- require(ownerZombieCount[msg.sender] == 0);
- uint randDna = _generateRandomDna(_name);
- randDna = randDna - randDna % 100;
- _createZombie(_name, randDna);
- }
-
- }
- "ownable.sol": |
- /**
- * @title Ownable
- * @dev The Ownable contract has an owner address, and provides basic authorization control
- * functions, this simplifies the implementation of "user permissions".
- */
- contract Ownable {
- address public owner;
-
- event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
-
- /**
- * @dev The Ownable constructor sets the original `owner` of the contract to the sender
- * account.
- */
- function Ownable() public {
- owner = msg.sender;
- }
-
-
- /**
- * @dev Throws if called by any account other than the owner.
- */
- modifier onlyOwner() {
- require(msg.sender == owner);
- _;
- }
-
-
- /**
- * @dev Allows the current owner to transfer control of the contract to a newOwner.
- * @param newOwner The address to transfer ownership to.
- */
- function transferOwnership(address newOwner) public onlyOwner {
- require(newOwner != address(0));
- OwnershipTransferred(owner, newOwner);
- owner = newOwner;
- }
-
- }
- answer: >
- pragma solidity ^0.4.19;
-
- import "./zombiefeeding.sol";
-
- contract ZombieHelper is ZombieFeeding {
-
- modifier aboveLevel(uint _level, uint _zombieId) {
- require(zombies[_zombieId].level >= _level);
- _;
- }
-
- function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
- require(msg.sender == zombieToOwner[_zombieId]);
- zombies[_zombieId].name = _newName;
- }
-
- function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
- require(msg.sender == zombieToOwner[_zombieId]);
- zombies[_zombieId].dna = _newDna;
- }
-
- function getZombiesByOwner(address _owner) external view returns(uint[]) {
-
- }
-
- }
---
酷炫!现在高级别僵尸可以拥有特殊技能了,这一定会鼓动我们的玩家去打怪升级的。你喜欢的话,回头我们还能添加更多的特殊技能。
现在需要添加的一个功能是:我们的 DApp 需要一个方法来查看某玩家的整个僵尸军团 - 我们称之为 `getZombiesByOwner`。
-实现这个功能只需从区块链中读取数据,所以它可以是一个 `view` 函数。这让我们不得不回顾一下“gas优化”这个重要话题。
+实现这个功能只需从区块链中读取数据,所以它可以是一个 `view` 函数。这让我们不得不回顾一下“gas 优化”这个重要话题。
## “view” 函数不花 “gas”
当玩家从外部调用一个`view`函数,是不需要支付一分 gas 的。
-这是因为 `view` 函数不会真正改变区块链上的任何数据 - 它们只是读取。因此用 `view` 标记一个函数,意味着告诉 `web3.js`,运行这个函数只需要查询你的本地以太坊节点,而不需要在区块链上创建一个事务(事务需要运行在每个节点上,因此花费 gas)。
+这是因为 `view` 函数不会真正改变区块链上的任何数据 - 它们只是读取。因此用 `view` 标记一个函数,意味着告诉 `web3.js`,运行这个函数只需要查询你的本地以太坊节点,而不需要在区块链上创建一个交易(交易需要运行在每个节点上,因此花费 gas)。
稍后我们将介绍如何在自己的节点上设置 web3.js。但现在,你关键是要记住,在所能只读的函数上标记上表示“只读”的“`external view` 声明,就能为你的玩家减少在 DApp 中 gas 用量。
->注意:如果一个 `view` 函数在另一个函数的内部被调用,而调用函数与 `view` 函数的不属于同一个合约,也会产生调用成本。这是因为如果主调函数在以太坊创建了一个事务,它仍然需要逐个节点去验证。所以标记为 `view` 的函数只有在外部调用时才是免费的。
-
+> 注意:如果一个 `view` 函数在另一个函数的内部被调用,而调用函数与 `view` 函数的不属于同一个合约,也会产生调用成本。这是因为如果主调函数在以太坊创建了一个交易,它仍然需要逐个节点去验证。所以标记为 `view` 的函数只有在外部调用时才是免费的。
## 实战演习
diff --git a/zh/4/battle-02.md b/zh/4/battle-02.md
index 242e265c5..d5c0819c3 100644
--- a/zh/4/battle-02.md
+++ b/zh/4/battle-02.md
@@ -3,222 +3,221 @@ title: 随机数
actions: ['checkAnswer', 'hints']
requireLogin: true
material:
- editor:
- language: sol
- startingCode:
- "zombieattack.sol": |
- pragma solidity ^0.4.19;
-
- import "./zombiehelper.sol";
-
- contract ZombieBattle is ZombieHelper {
- // 在这里开始
- }
- "zombiehelper.sol": |
- pragma solidity ^0.4.19;
-
- import "./zombiefeeding.sol";
-
- contract ZombieHelper is ZombieFeeding {
-
- uint levelUpFee = 0.001 ether;
-
- modifier aboveLevel(uint _level, uint _zombieId) {
- require(zombies[_zombieId].level >= _level);
- _;
- }
-
- function withdraw() external onlyOwner {
- owner.transfer(this.balance);
- }
-
- function setLevelUpFee(uint _fee) external onlyOwner {
- levelUpFee = _fee;
- }
-
- function levelUp(uint _zombieId) external payable {
- require(msg.value == levelUpFee);
- zombies[_zombieId].level++;
- }
-
- function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
- require(msg.sender == zombieToOwner[_zombieId]);
- zombies[_zombieId].name = _newName;
- }
-
- function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
- require(msg.sender == zombieToOwner[_zombieId]);
- zombies[_zombieId].dna = _newDna;
- }
-
- function getZombiesByOwner(address _owner) external view returns(uint[]) {
- uint[] memory result = new uint[](ownerZombieCount[_owner]);
- uint counter = 0;
- for (uint i = 0; i < zombies.length; i++) {
- if (zombieToOwner[i] == _owner) {
- result[counter] = i;
- counter++;
+ editor:
+ language: sol
+ startingCode:
+ 'zombieattack.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./zombiehelper.sol";
+
+ contract ZombieBattle is ZombieHelper {
+ // 在这里开始
+ }
+ 'zombiehelper.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./zombiefeeding.sol";
+
+ contract ZombieHelper is ZombieFeeding {
+
+ uint levelUpFee = 0.001 ether;
+
+ modifier aboveLevel(uint _level, uint _zombieId) {
+ require(zombies[_zombieId].level >= _level);
+ _;
+ }
+
+ function withdraw() external onlyOwner {
+ owner.transfer(this.balance);
+ }
+
+ function setLevelUpFee(uint _fee) external onlyOwner {
+ levelUpFee = _fee;
+ }
+
+ function levelUp(uint _zombieId) external payable {
+ require(msg.value == levelUpFee);
+ zombies[_zombieId].level++;
+ }
+
+ function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
+ require(msg.sender == zombieToOwner[_zombieId]);
+ zombies[_zombieId].name = _newName;
+ }
+
+ function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
+ require(msg.sender == zombieToOwner[_zombieId]);
+ zombies[_zombieId].dna = _newDna;
+ }
+
+ function getZombiesByOwner(address _owner) external view returns(uint[]) {
+ uint[] memory result = new uint[](ownerZombieCount[_owner]);
+ uint counter = 0;
+ for (uint i = 0; i < zombies.length; i++) {
+ if (zombieToOwner[i] == _owner) {
+ result[counter] = i;
+ counter++;
+ }
+ }
+ return result;
+ }
+
+ }
+ 'zombiefeeding.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./zombiefactory.sol";
+
+ contract KittyInterface {
+ function getKitty(uint256 _id) external view returns (
+ bool isGestating,
+ bool isReady,
+ uint256 cooldownIndex,
+ uint256 nextActionAt,
+ uint256 siringWithId,
+ uint256 birthTime,
+ uint256 matronId,
+ uint256 sireId,
+ uint256 generation,
+ uint256 genes
+ );
+ }
+
+ contract ZombieFeeding is ZombieFactory {
+
+ KittyInterface kittyContract;
+
+ function setKittyContractAddress(address _address) external onlyOwner {
+ kittyContract = KittyInterface(_address);
+ }
+
+ function _triggerCooldown(Zombie storage _zombie) internal {
+ _zombie.readyTime = uint32(now + cooldownTime);
+ }
+
+ function _isReady(Zombie storage _zombie) internal view returns (bool) {
+ return (_zombie.readyTime <= now);
+ }
+
+ function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal {
+ require(msg.sender == zombieToOwner[_zombieId]);
+ Zombie storage myZombie = zombies[_zombieId];
+ require(_isReady(myZombie));
+ _targetDna = _targetDna % dnaModulus;
+ uint newDna = (myZombie.dna + _targetDna) / 2;
+ if (keccak256(_species) == keccak256("kitty")) {
+ newDna = newDna - newDna % 100 + 99;
+ }
+ _createZombie("NoName", newDna);
+ _triggerCooldown(myZombie);
+ }
+
+ function feedOnKitty(uint _zombieId, uint _kittyId) public {
+ uint kittyDna;
+ (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
+ feedAndMultiply(_zombieId, kittyDna, "kitty");
+ }
+ }
+ 'zombiefactory.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./ownable.sol";
+
+ contract ZombieFactory is Ownable {
+
+ event NewZombie(uint zombieId, string name, uint dna);
+
+ uint dnaDigits = 16;
+ uint dnaModulus = 10 ** dnaDigits;
+ uint cooldownTime = 1 days;
+
+ struct Zombie {
+ string name;
+ uint dna;
+ uint32 level;
+ uint32 readyTime;
+ }
+
+ Zombie[] public zombies;
+
+ mapping (uint => address) public zombieToOwner;
+ mapping (address => uint) ownerZombieCount;
+
+ function _createZombie(string _name, uint _dna) internal {
+ uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime))) - 1;
+ zombieToOwner[id] = msg.sender;
+ ownerZombieCount[msg.sender]++;
+ NewZombie(id, _name, _dna);
+ }
+
+ function _generateRandomDna(string _str) private view returns (uint) {
+ uint rand = uint(keccak256(_str));
+ return rand % dnaModulus;
+ }
+
+ function createRandomZombie(string _name) public {
+ require(ownerZombieCount[msg.sender] == 0);
+ uint randDna = _generateRandomDna(_name);
+ randDna = randDna - randDna % 100;
+ _createZombie(_name, randDna);
+ }
+
+ }
+ 'ownable.sol': |
+ pragma solidity ^0.4.19;
+ /**
+ * @title Ownable
+ * @dev The Ownable contract has an owner address, and provides basic authorization control
+ * functions, this simplifies the implementation of "user permissions".
+ */
+ contract Ownable {
+ address public owner;
+
+ event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
+
+ /**
+ * @dev The Ownable constructor sets the original `owner` of the contract to the sender
+ * account.
+ */
+ function Ownable() public {
+ owner = msg.sender;
+ }
+
+
+ /**
+ * @dev Throws if called by any account other than the owner.
+ */
+ modifier onlyOwner() {
+ require(msg.sender == owner);
+ _;
+ }
+
+
+ /**
+ * @dev Allows the current owner to transfer control of the contract to a newOwner.
+ * @param newOwner The address to transfer ownership to.
+ */
+ function transferOwnership(address newOwner) public onlyOwner {
+ require(newOwner != address(0));
+ OwnershipTransferred(owner, newOwner);
+ owner = newOwner;
+ }
+
+ }
+ answer: >
+ pragma solidity ^0.4.19;
+
+ import "./zombiehelper.sol";
+
+ contract ZombieBattle is ZombieHelper {
+ uint randNonce = 0;
+
+ function randMod(uint _modulus) internal returns(uint) {
+ randNonce++;
+ return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
}
}
- return result;
- }
-
- }
- "zombiefeeding.sol": |
- pragma solidity ^0.4.19;
-
- import "./zombiefactory.sol";
-
- contract KittyInterface {
- function getKitty(uint256 _id) external view returns (
- bool isGestating,
- bool isReady,
- uint256 cooldownIndex,
- uint256 nextActionAt,
- uint256 siringWithId,
- uint256 birthTime,
- uint256 matronId,
- uint256 sireId,
- uint256 generation,
- uint256 genes
- );
- }
-
- contract ZombieFeeding is ZombieFactory {
-
- KittyInterface kittyContract;
-
- function setKittyContractAddress(address _address) external onlyOwner {
- kittyContract = KittyInterface(_address);
- }
-
- function _triggerCooldown(Zombie storage _zombie) internal {
- _zombie.readyTime = uint32(now + cooldownTime);
- }
-
- function _isReady(Zombie storage _zombie) internal view returns (bool) {
- return (_zombie.readyTime <= now);
- }
-
- function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal {
- require(msg.sender == zombieToOwner[_zombieId]);
- Zombie storage myZombie = zombies[_zombieId];
- require(_isReady(myZombie));
- _targetDna = _targetDna % dnaModulus;
- uint newDna = (myZombie.dna + _targetDna) / 2;
- if (keccak256(_species) == keccak256("kitty")) {
- newDna = newDna - newDna % 100 + 99;
- }
- _createZombie("NoName", newDna);
- _triggerCooldown(myZombie);
- }
-
- function feedOnKitty(uint _zombieId, uint _kittyId) public {
- uint kittyDna;
- (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
- feedAndMultiply(_zombieId, kittyDna, "kitty");
- }
- }
- "zombiefactory.sol": |
- pragma solidity ^0.4.19;
-
- import "./ownable.sol";
-
- contract ZombieFactory is Ownable {
-
- event NewZombie(uint zombieId, string name, uint dna);
-
- uint dnaDigits = 16;
- uint dnaModulus = 10 ** dnaDigits;
- uint cooldownTime = 1 days;
-
- struct Zombie {
- string name;
- uint dna;
- uint32 level;
- uint32 readyTime;
- }
-
- Zombie[] public zombies;
-
- mapping (uint => address) public zombieToOwner;
- mapping (address => uint) ownerZombieCount;
-
- function _createZombie(string _name, uint _dna) internal {
- uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime))) - 1;
- zombieToOwner[id] = msg.sender;
- ownerZombieCount[msg.sender]++;
- NewZombie(id, _name, _dna);
- }
-
- function _generateRandomDna(string _str) private view returns (uint) {
- uint rand = uint(keccak256(_str));
- return rand % dnaModulus;
- }
-
- function createRandomZombie(string _name) public {
- require(ownerZombieCount[msg.sender] == 0);
- uint randDna = _generateRandomDna(_name);
- randDna = randDna - randDna % 100;
- _createZombie(_name, randDna);
- }
-
- }
- "ownable.sol": |
- pragma solidity ^0.4.19;
- /**
- * @title Ownable
- * @dev The Ownable contract has an owner address, and provides basic authorization control
- * functions, this simplifies the implementation of "user permissions".
- */
- contract Ownable {
- address public owner;
-
- event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
-
- /**
- * @dev The Ownable constructor sets the original `owner` of the contract to the sender
- * account.
- */
- function Ownable() public {
- owner = msg.sender;
- }
-
-
- /**
- * @dev Throws if called by any account other than the owner.
- */
- modifier onlyOwner() {
- require(msg.sender == owner);
- _;
- }
-
-
- /**
- * @dev Allows the current owner to transfer control of the contract to a newOwner.
- * @param newOwner The address to transfer ownership to.
- */
- function transferOwnership(address newOwner) public onlyOwner {
- require(newOwner != address(0));
- OwnershipTransferred(owner, newOwner);
- owner = newOwner;
- }
-
- }
- answer: >
- pragma solidity ^0.4.19;
-
- import "./zombiehelper.sol";
-
- contract ZombieBattle is ZombieHelper {
- uint randNonce = 0;
-
- function randMod(uint _modulus) internal returns(uint) {
- randNonce++;
- return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
- }
- }
-
---
你太棒了!接下来我们梳理一下战斗逻辑。
@@ -245,25 +244,25 @@ uint random2 = uint(keccak256(now, msg.sender, randNonce)) % 100;
这个方法首先拿到 `now` 的时间戳、 `msg.sender`、 以及一个自增数 `nonce` (一个仅会被使用一次的数,这样我们就不会对相同的输入值调用一次以上哈希函数了)。
-然后利用 `keccak` 把输入的值转变为一个哈希值, 再将哈希值转换为 `uint`, 然后利用 `% 100` 来取最后两位, 就生成了一个0到100之间随机数了。
+然后利用 `keccak` 把输入的值转变为一个哈希值, 再将哈希值转换为 `uint`, 然后利用 `% 100` 来取最后两位, 就生成了一个 0 到 100 之间随机数了。
### 这个方法很容易被不诚实的节点攻击
-在以太坊上, 当你在和一个合约上调用函数的时候, 你会把它广播给一个节点或者在网络上的 **_transaction_** 节点们。 网络上的节点将收集很多事务, 试着成为第一个解决计算密集型数学问题的人,作为“工作证明”,然后将“工作证明”(Proof of Work, PoW)和事务一起作为一个 **_block_** 发布在网络上。
+在以太坊上, 当你在和一个合约上调用函数的时候, 你会把它广播给一个节点或者在网络上的 **_transaction_** 节点们。 网络上的节点将收集很多交易, 试着成为第一个解决计算密集型数学问题的人,作为“工作证明”,然后将“工作证明”(Proof of Work, PoW)和交易一起作为一个 **_block_** 发布在网络上。
-一旦一个节点解决了一个PoW, 其他节点就会停止尝试解决这个 PoW, 并验证其他节点的事务列表是有效的,然后接受这个节点转而尝试解决下一个节点。
+一旦一个节点解决了一个 PoW, 其他节点就会停止尝试解决这个 PoW, 并验证其他节点的交易列表是有效的,然后接受这个节点转而尝试解决下一个节点。
**这就让我们的随机数函数变得可利用了**
我们假设我们有一个硬币翻转合约——正面你赢双倍钱,反面你输掉所有的钱。假如它使用上面的方法来决定是正面还是反面 (`random >= 50` 算正面, `random < 50` 算反面)。
-如果我正运行一个节点,我可以 **只对我自己的节点** 发布一个事务,且不分享它。 我可以运行硬币翻转方法来偷窥我的输赢 — 如果我输了,我就不把这个事务包含进我要解决的下一个区块中去。我可以一直运行这个方法,直到我赢得了硬币翻转并解决了下一个区块,然后获利。
+如果我正运行一个节点,我可以 **只对我自己的节点** 发布一个交易,且不分享它。 我可以运行硬币翻转方法来偷窥我的输赢 — 如果我输了,我就不把这个交易包含进我要解决的下一个区块中去。我可以一直运行这个方法,直到我赢得了硬币翻转并解决了下一个区块,然后获利。
## 所以我们该如何在以太坊上安全地生成随机数呢
因为区块链的全部内容对所有参与者来说是透明的, 这就让这个问题变得很难,它的解决方法不在本课程讨论范围,你可以阅读 这个 StackOverflow 上的讨论 来获得一些主意。 一个方法是利用 **_oracle_** 来访问以太坊区块链之外的随机数函数。
-当然, 因为网络上成千上万的以太坊节点都在竞争解决下一个区块,我能成功解决下一个区块的几率非常之低。 这将花费我们巨大的计算资源来开发这个获利方法 — 但是如果奖励异常地高(比如我可以在硬币翻转函数中赢得 1个亿), 那就很值得去攻击了。
+当然, 因为网络上成千上万的以太坊节点都在竞争解决下一个区块,我能成功解决下一个区块的几率非常之低。 这将花费我们巨大的计算资源来开发这个获利方法 — 但是如果奖励异常地高(比如我可以在硬币翻转函数中赢得 1 个亿), 那就很值得去攻击了。
所以尽管这个方法在以太坊上不安全,在实际中,除非我们的随机函数有一大笔钱在上面,你游戏的用户一般是没有足够的资源去攻击的。
diff --git a/zh/4/payable.md b/zh/4/payable.md
index d1a4bb77e..b1e421711 100644
--- a/zh/4/payable.md
+++ b/zh/4/payable.md
@@ -3,237 +3,237 @@ title: 可支付
actions: ['checkAnswer', 'hints']
requireLogin: true
material:
- editor:
- language: sol
- startingCode:
- "zombiehelper.sol": |
- pragma solidity ^0.4.19;
-
- import "./zombiefeeding.sol";
-
- contract ZombieHelper is ZombieFeeding {
-
- // 1. 在这里定义 levelUpFee
-
- modifier aboveLevel(uint _level, uint _zombieId) {
- require(zombies[_zombieId].level >= _level);
- _;
- }
-
- // 2. 在这里插入 levelUp 函数
-
- function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
- require(msg.sender == zombieToOwner[_zombieId]);
- zombies[_zombieId].name = _newName;
- }
-
- function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
- require(msg.sender == zombieToOwner[_zombieId]);
- zombies[_zombieId].dna = _newDna;
- }
-
- function getZombiesByOwner(address _owner) external view returns(uint[]) {
- uint[] memory result = new uint[](ownerZombieCount[_owner]);
- uint counter = 0;
- for (uint i = 0; i < zombies.length; i++) {
- if (zombieToOwner[i] == _owner) {
- result[counter] = i;
- counter++;
+ editor:
+ language: sol
+ startingCode:
+ 'zombiehelper.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./zombiefeeding.sol";
+
+ contract ZombieHelper is ZombieFeeding {
+
+ // 1. 在这里定义 levelUpFee
+
+ modifier aboveLevel(uint _level, uint _zombieId) {
+ require(zombies[_zombieId].level >= _level);
+ _;
+ }
+
+ // 2. 在这里插入 levelUp 函数
+
+ function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
+ require(msg.sender == zombieToOwner[_zombieId]);
+ zombies[_zombieId].name = _newName;
+ }
+
+ function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
+ require(msg.sender == zombieToOwner[_zombieId]);
+ zombies[_zombieId].dna = _newDna;
+ }
+
+ function getZombiesByOwner(address _owner) external view returns(uint[]) {
+ uint[] memory result = new uint[](ownerZombieCount[_owner]);
+ uint counter = 0;
+ for (uint i = 0; i < zombies.length; i++) {
+ if (zombieToOwner[i] == _owner) {
+ result[counter] = i;
+ counter++;
+ }
+ }
+ return result;
+ }
+
+ }
+ 'zombiefeeding.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./zombiefactory.sol";
+
+ contract KittyInterface {
+ function getKitty(uint256 _id) external view returns (
+ bool isGestating,
+ bool isReady,
+ uint256 cooldownIndex,
+ uint256 nextActionAt,
+ uint256 siringWithId,
+ uint256 birthTime,
+ uint256 matronId,
+ uint256 sireId,
+ uint256 generation,
+ uint256 genes
+ );
+ }
+
+ contract ZombieFeeding is ZombieFactory {
+
+ KittyInterface kittyContract;
+
+ function setKittyContractAddress(address _address) external onlyOwner {
+ kittyContract = KittyInterface(_address);
+ }
+
+ function _triggerCooldown(Zombie storage _zombie) internal {
+ _zombie.readyTime = uint32(now + cooldownTime);
+ }
+
+ function _isReady(Zombie storage _zombie) internal view returns (bool) {
+ return (_zombie.readyTime <= now);
+ }
+
+ function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal {
+ require(msg.sender == zombieToOwner[_zombieId]);
+ Zombie storage myZombie = zombies[_zombieId];
+ require(_isReady(myZombie));
+ _targetDna = _targetDna % dnaModulus;
+ uint newDna = (myZombie.dna + _targetDna) / 2;
+ if (keccak256(_species) == keccak256("kitty")) {
+ newDna = newDna - newDna % 100 + 99;
+ }
+ _createZombie("NoName", newDna);
+ _triggerCooldown(myZombie);
+ }
+
+ function feedOnKitty(uint _zombieId, uint _kittyId) public {
+ uint kittyDna;
+ (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
+ feedAndMultiply(_zombieId, kittyDna, "kitty");
+ }
+ }
+ 'zombiefactory.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./ownable.sol";
+
+ contract ZombieFactory is Ownable {
+
+ event NewZombie(uint zombieId, string name, uint dna);
+
+ uint dnaDigits = 16;
+ uint dnaModulus = 10 ** dnaDigits;
+ uint cooldownTime = 1 days;
+
+ struct Zombie {
+ string name;
+ uint dna;
+ uint32 level;
+ uint32 readyTime;
+ }
+
+ Zombie[] public zombies;
+
+ mapping (uint => address) public zombieToOwner;
+ mapping (address => uint) ownerZombieCount;
+
+ function _createZombie(string _name, uint _dna) internal {
+ uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime))) - 1;
+ zombieToOwner[id] = msg.sender;
+ ownerZombieCount[msg.sender]++;
+ NewZombie(id, _name, _dna);
+ }
+
+ function _generateRandomDna(string _str) private view returns (uint) {
+ uint rand = uint(keccak256(_str));
+ return rand % dnaModulus;
+ }
+
+ function createRandomZombie(string _name) public {
+ require(ownerZombieCount[msg.sender] == 0);
+ uint randDna = _generateRandomDna(_name);
+ randDna = randDna - randDna % 100;
+ _createZombie(_name, randDna);
+ }
+
+ }
+ 'ownable.sol': |
+ /**
+ * @title Ownable
+ * @dev The Ownable contract has an owner address, and provides basic authorization control
+ * functions, this simplifies the implementation of "user permissions".
+ */
+ contract Ownable {
+ address public owner;
+
+ event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
+
+ /**
+ * @dev The Ownable constructor sets the original `owner` of the contract to the sender
+ * account.
+ */
+ function Ownable() public {
+ owner = msg.sender;
+ }
+
+
+ /**
+ * @dev Throws if called by any account other than the owner.
+ */
+ modifier onlyOwner() {
+ require(msg.sender == owner);
+ _;
+ }
+
+
+ /**
+ * @dev Allows the current owner to transfer control of the contract to a newOwner.
+ * @param newOwner The address to transfer ownership to.
+ */
+ function transferOwnership(address newOwner) public onlyOwner {
+ require(newOwner != address(0));
+ OwnershipTransferred(owner, newOwner);
+ owner = newOwner;
+ }
+
+ }
+ answer: >
+ pragma solidity ^0.4.19;
+
+ import "./zombiefeeding.sol";
+
+ contract ZombieHelper is ZombieFeeding {
+
+ uint levelUpFee = 0.001 ether;
+
+ modifier aboveLevel(uint _level, uint _zombieId) {
+ require(zombies[_zombieId].level >= _level);
+ _;
}
- }
- return result;
- }
-
- }
- "zombiefeeding.sol": |
- pragma solidity ^0.4.19;
-
- import "./zombiefactory.sol";
-
- contract KittyInterface {
- function getKitty(uint256 _id) external view returns (
- bool isGestating,
- bool isReady,
- uint256 cooldownIndex,
- uint256 nextActionAt,
- uint256 siringWithId,
- uint256 birthTime,
- uint256 matronId,
- uint256 sireId,
- uint256 generation,
- uint256 genes
- );
- }
-
- contract ZombieFeeding is ZombieFactory {
-
- KittyInterface kittyContract;
-
- function setKittyContractAddress(address _address) external onlyOwner {
- kittyContract = KittyInterface(_address);
- }
-
- function _triggerCooldown(Zombie storage _zombie) internal {
- _zombie.readyTime = uint32(now + cooldownTime);
- }
-
- function _isReady(Zombie storage _zombie) internal view returns (bool) {
- return (_zombie.readyTime <= now);
- }
-
- function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal {
- require(msg.sender == zombieToOwner[_zombieId]);
- Zombie storage myZombie = zombies[_zombieId];
- require(_isReady(myZombie));
- _targetDna = _targetDna % dnaModulus;
- uint newDna = (myZombie.dna + _targetDna) / 2;
- if (keccak256(_species) == keccak256("kitty")) {
- newDna = newDna - newDna % 100 + 99;
- }
- _createZombie("NoName", newDna);
- _triggerCooldown(myZombie);
- }
-
- function feedOnKitty(uint _zombieId, uint _kittyId) public {
- uint kittyDna;
- (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
- feedAndMultiply(_zombieId, kittyDna, "kitty");
- }
- }
- "zombiefactory.sol": |
- pragma solidity ^0.4.19;
-
- import "./ownable.sol";
-
- contract ZombieFactory is Ownable {
-
- event NewZombie(uint zombieId, string name, uint dna);
-
- uint dnaDigits = 16;
- uint dnaModulus = 10 ** dnaDigits;
- uint cooldownTime = 1 days;
-
- struct Zombie {
- string name;
- uint dna;
- uint32 level;
- uint32 readyTime;
- }
-
- Zombie[] public zombies;
- mapping (uint => address) public zombieToOwner;
- mapping (address => uint) ownerZombieCount;
+ function levelUp(uint _zombieId) external payable {
+ require(msg.value == levelUpFee);
+ zombies[_zombieId].level++;
+ }
- function _createZombie(string _name, uint _dna) internal {
- uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime))) - 1;
- zombieToOwner[id] = msg.sender;
- ownerZombieCount[msg.sender]++;
- NewZombie(id, _name, _dna);
- }
+ function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
+ require(msg.sender == zombieToOwner[_zombieId]);
+ zombies[_zombieId].name = _newName;
+ }
- function _generateRandomDna(string _str) private view returns (uint) {
- uint rand = uint(keccak256(_str));
- return rand % dnaModulus;
- }
+ function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
+ require(msg.sender == zombieToOwner[_zombieId]);
+ zombies[_zombieId].dna = _newDna;
+ }
- function createRandomZombie(string _name) public {
- require(ownerZombieCount[msg.sender] == 0);
- uint randDna = _generateRandomDna(_name);
- randDna = randDna - randDna % 100;
- _createZombie(_name, randDna);
- }
+ function getZombiesByOwner(address _owner) external view returns(uint[]) {
+ uint[] memory result = new uint[](ownerZombieCount[_owner]);
+ uint counter = 0;
+ for (uint i = 0; i < zombies.length; i++) {
+ if (zombieToOwner[i] == _owner) {
+ result[counter] = i;
+ counter++;
+ }
+ }
+ return result;
+ }
- }
- "ownable.sol": |
- /**
- * @title Ownable
- * @dev The Ownable contract has an owner address, and provides basic authorization control
- * functions, this simplifies the implementation of "user permissions".
- */
- contract Ownable {
- address public owner;
-
- event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
-
- /**
- * @dev The Ownable constructor sets the original `owner` of the contract to the sender
- * account.
- */
- function Ownable() public {
- owner = msg.sender;
- }
-
-
- /**
- * @dev Throws if called by any account other than the owner.
- */
- modifier onlyOwner() {
- require(msg.sender == owner);
- _;
- }
-
-
- /**
- * @dev Allows the current owner to transfer control of the contract to a newOwner.
- * @param newOwner The address to transfer ownership to.
- */
- function transferOwnership(address newOwner) public onlyOwner {
- require(newOwner != address(0));
- OwnershipTransferred(owner, newOwner);
- owner = newOwner;
- }
-
- }
- answer: >
- pragma solidity ^0.4.19;
-
- import "./zombiefeeding.sol";
-
- contract ZombieHelper is ZombieFeeding {
-
- uint levelUpFee = 0.001 ether;
-
- modifier aboveLevel(uint _level, uint _zombieId) {
- require(zombies[_zombieId].level >= _level);
- _;
- }
-
- function levelUp(uint _zombieId) external payable {
- require(msg.value == levelUpFee);
- zombies[_zombieId].level++;
- }
-
- function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
- require(msg.sender == zombieToOwner[_zombieId]);
- zombies[_zombieId].name = _newName;
- }
-
- function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
- require(msg.sender == zombieToOwner[_zombieId]);
- zombies[_zombieId].dna = _newDna;
- }
-
- function getZombiesByOwner(address _owner) external view returns(uint[]) {
- uint[] memory result = new uint[](ownerZombieCount[_owner]);
- uint counter = 0;
- for (uint i = 0; i < zombies.length; i++) {
- if (zombieToOwner[i] == _owner) {
- result[counter] = i;
- counter++;
}
- }
- return result;
- }
-
- }
---
截至目前,我们只接触到很少的 **_函数修饰符_**。 要记住所有的东西很难,所以我们来个概览:
1. 我们有决定函数何时和被谁调用的可见性修饰符: `private` 意味着它只能被合约内部调用; `internal` 就像 `private` 但是也能被继承的合约调用; `external` 只能从合约外部调用;最后 `public` 可以在任何地方调用,不管是内部还是外部。
-2. 我们也有状态修饰符, 告诉我们函数如何和区块链交互: `view` 告诉我们运行这个函数不会更改和保存任何数据; `pure` 告诉我们这个函数不但不会往区块链写数据,它甚至不从区块链读取数据。这两种在被从合约外部调用的时候都不花费任何gas(但是它们在被内部其他函数调用的时候将会耗费gas)。
+2. 我们也有状态修饰符, 告诉我们函数如何和区块链交互: `view` 告诉我们运行这个函数不会更改和保存任何数据; `pure` 告诉我们这个函数不但不会往区块链写数据,它甚至不从区块链读取数据。这两种在被从合约外部调用的时候都不花费任何 gas(但是它们在被内部其他函数调用的时候将会耗费 gas)。
3. 然后我们有了自定义的 `modifiers`,例如在第三课学习的: `onlyOwner` 和 `aboveLevel`。 对于这些修饰符我们可以自定义其对函数的约束逻辑。
@@ -245,17 +245,18 @@ function test() external view onlyOwner anotherModifier { /* ... */ }
在这一章,我们来学习一个新的修饰符 `payable`.
-## `payable` 修饰符
+## `payable` 修饰符
`payable` 方法是让 Solidity 和以太坊变得如此酷的一部分 —— 它们是一种可以接收以太的特殊函数。
-先放一下。当你在调用一个普通网站服务器上的API函数的时候,你无法用你的函数传送美元——你也不能传送比特币。
+先放一下。当你在调用一个普通网站服务器上的 API 函数的时候,你无法用你的函数传送美元——你也不能传送比特币。
-但是在以太坊中, 因为钱 (_以太_), 数据 (*事务负载*), 以及合约代码本身都存在于以太坊。你可以在同时调用函数 **并**付钱给另外一个合约。
+但是在以太坊中, 因为钱 (_以太_), 数据 (_交易负载_), 以及合约代码本身都存在于以太坊。你可以在同时调用函数 **并**付钱给另外一个合约。
这就允许出现很多有趣的逻辑, 比如向一个合约要求支付一定的钱来运行一个函数。
## 来看个例子
+
```
contract OnlineStore {
function buySomething() external payable {
@@ -269,23 +270,22 @@ contract OnlineStore {
在这里,`msg.value` 是一种可以查看向合约发送了多少以太的方法,另外 `ether` 是一个內建单元。
-这里发生的事是,一些人会从 web3.js 调用这个函数 (从DApp的前端), 像这样 :
+这里发生的事是,一些人会从 web3.js 调用这个函数 (从 DApp 的前端), 像这样 :
```
// 假设 `OnlineStore` 在以太坊上指向你的合约:
OnlineStore.buySomething().send(from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001))
```
-注意这个 `value` 字段, JavaScript 调用来指定发送多少(0.001)`以太`。如果把事务想象成一个信封,你发送到函数的参数就是信的内容。 添加一个 `value` 很像在信封里面放钱 —— 信件内容和钱同时发送给了接收者。
-
->注意: 如果一个函数没标记为`payable`, 而你尝试利用上面的方法发送以太,函数将拒绝你的事务。
+注意这个 `value` 字段, JavaScript 调用来指定发送多少(0.001)`以太`。如果把交易想象成一个信封,你发送到函数的参数就是信的内容。 添加一个 `value` 很像在信封里面放钱 —— 信件内容和钱同时发送给了接收者。
+> 注意: 如果一个函数没标记为`payable`, 而你尝试利用上面的方法发送以太,函数将拒绝你的交易。
## 实战演习
我们来在僵尸游戏里面创建一个`payable` 函数。
-假定在我们的游戏中,玩家可以通过支付ETH来升级他们的僵尸。ETH将存储在你拥有的合约中 —— 一个简单明了的例子,向你展示你可以通过自己的游戏赚钱。
+假定在我们的游戏中,玩家可以通过支付 ETH 来升级他们的僵尸。ETH 将存储在你拥有的合约中 —— 一个简单明了的例子,向你展示你可以通过自己的游戏赚钱。
1. 定义一个 `uint` ,命名为 `levelUpFee`, 将值设定为 `0.001 ether`。
@@ -293,4 +293,4 @@ OnlineStore.buySomething().send(from: web3.eth.defaultAccount, value: web3.utils
3. 这个函数首先应该 `require` 确保 `msg.value` 等于 `levelUpFee`。
-4. 然后它应该增加僵尸的 `level`: `zombies[_zombieId].level++`。
+4. 然后它应该增加僵尸的 `level`: `zombies[_zombieId].level++`。
diff --git a/zh/6/02.md b/zh/6/02.md
index 71ac2c700..36258e1ad 100644
--- a/zh/6/02.md
+++ b/zh/6/02.md
@@ -3,394 +3,394 @@ title: Web3 提供者
actions: ['checkAnswer', 'hints']
requireLogin: true
material:
- editor:
- language: html
- startingCode:
- "index.html": |
-
-
-
-
- CryptoZombies front-end
-
-
-
-
-
-
-
-
- "zombieownership.sol": |
- pragma solidity ^0.4.19;
-
- import "./zombieattack.sol";
- import "./erc721.sol";
- import "./safemath.sol";
-
- contract ZombieOwnership is ZombieAttack, ERC721 {
-
- using SafeMath for uint256;
-
- mapping (uint => address) zombieApprovals;
-
- function balanceOf(address _owner) public view returns (uint256 _balance) {
- return ownerZombieCount[_owner];
- }
-
- function ownerOf(uint256 _tokenId) public view returns (address _owner) {
- return zombieToOwner[_tokenId];
- }
-
- function _transfer(address _from, address _to, uint256 _tokenId) private {
- ownerZombieCount[_to] = ownerZombieCount[_to].add(1);
- ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1);
- zombieToOwner[_tokenId] = _to;
- Transfer(_from, _to, _tokenId);
- }
-
- function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
- _transfer(msg.sender, _to, _tokenId);
- }
-
- function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
- zombieApprovals[_tokenId] = _to;
- Approval(msg.sender, _to, _tokenId);
- }
-
- function takeOwnership(uint256 _tokenId) public {
- require(zombieApprovals[_tokenId] == msg.sender);
- address owner = ownerOf(_tokenId);
- _transfer(owner, msg.sender, _tokenId);
- }
- }
- "zombieattack.sol": |
- pragma solidity ^0.4.19;
-
- import "./zombiehelper.sol";
-
- contract ZombieAttack is ZombieHelper {
- uint randNonce = 0;
- uint attackVictoryProbability = 70;
-
- function randMod(uint _modulus) internal returns(uint) {
- randNonce++;
- return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
- }
-
- function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) {
- Zombie storage myZombie = zombies[_zombieId];
- Zombie storage enemyZombie = zombies[_targetId];
- uint rand = randMod(100);
- if (rand <= attackVictoryProbability) {
- myZombie.winCount++;
- myZombie.level++;
- enemyZombie.lossCount++;
- feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
- } else {
- myZombie.lossCount++;
- enemyZombie.winCount++;
- _triggerCooldown(myZombie);
- }
- }
- }
- "zombiehelper.sol": |
- pragma solidity ^0.4.19;
-
- import "./zombiefeeding.sol";
-
- contract ZombieHelper is ZombieFeeding {
-
- uint levelUpFee = 0.001 ether;
-
- modifier aboveLevel(uint _level, uint _zombieId) {
- require(zombies[_zombieId].level >= _level);
- _;
- }
-
- function withdraw() external onlyOwner {
- owner.transfer(this.balance);
- }
-
- function setLevelUpFee(uint _fee) external onlyOwner {
- levelUpFee = _fee;
- }
-
- function levelUp(uint _zombieId) external payable {
- require(msg.value == levelUpFee);
- zombies[_zombieId].level++;
- }
-
- function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) onlyOwnerOf(_zombieId) {
- zombies[_zombieId].name = _newName;
- }
-
- function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_zombieId) {
- zombies[_zombieId].dna = _newDna;
- }
-
- function getZombiesByOwner(address _owner) external view returns(uint[]) {
- uint[] memory result = new uint[](ownerZombieCount[_owner]);
- uint counter = 0;
- for (uint i = 0; i < zombies.length; i++) {
- if (zombieToOwner[i] == _owner) {
- result[counter] = i;
- counter++;
- }
- }
- return result;
- }
-
- }
- "zombiefeeding.sol": |
- pragma solidity ^0.4.19;
-
- import "./zombiefactory.sol";
-
- contract KittyInterface {
- function getKitty(uint256 _id) external view returns (
- bool isGestating,
- bool isReady,
- uint256 cooldownIndex,
- uint256 nextActionAt,
- uint256 siringWithId,
- uint256 birthTime,
- uint256 matronId,
- uint256 sireId,
- uint256 generation,
- uint256 genes
- );
- }
-
- contract ZombieFeeding is ZombieFactory {
-
- KittyInterface kittyContract;
-
- modifier onlyOwnerOf(uint _zombieId) {
- require(msg.sender == zombieToOwner[_zombieId]);
- _;
- }
-
- function setKittyContractAddress(address _address) external onlyOwner {
- kittyContract = KittyInterface(_address);
- }
-
- function _triggerCooldown(Zombie storage _zombie) internal {
- _zombie.readyTime = uint32(now + cooldownTime);
- }
-
- function _isReady(Zombie storage _zombie) internal view returns (bool) {
- return (_zombie.readyTime <= now);
- }
-
- function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal onlyOwnerOf(_zombieId) {
- Zombie storage myZombie = zombies[_zombieId];
- require(_isReady(myZombie));
- _targetDna = _targetDna % dnaModulus;
- uint newDna = (myZombie.dna + _targetDna) / 2;
- if (keccak256(_species) == keccak256("kitty")) {
- newDna = newDna - newDna % 100 + 99;
- }
- _createZombie("NoName", newDna);
- _triggerCooldown(myZombie);
- }
-
- function feedOnKitty(uint _zombieId, uint _kittyId) public {
- uint kittyDna;
- (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
- feedAndMultiply(_zombieId, kittyDna, "kitty");
- }
- }
- "zombiefactory.sol": |
- pragma solidity ^0.4.19;
-
- import "./ownable.sol";
- import "./safemath.sol";
-
- contract ZombieFactory is Ownable {
-
- using SafeMath for uint256;
-
- event NewZombie(uint zombieId, string name, uint dna);
-
- uint dnaDigits = 16;
- uint dnaModulus = 10 ** dnaDigits;
- uint cooldownTime = 1 days;
-
- struct Zombie {
- string name;
- uint dna;
- uint32 level;
- uint32 readyTime;
- uint16 winCount;
- uint16 lossCount;
- }
-
- Zombie[] public zombies;
-
- mapping (uint => address) public zombieToOwner;
- mapping (address => uint) ownerZombieCount;
-
- function _createZombie(string _name, uint _dna) internal {
- uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
- zombieToOwner[id] = msg.sender;
- ownerZombieCount[msg.sender]++;
- NewZombie(id, _name, _dna);
- }
-
- function _generateRandomDna(string _str) private view returns (uint) {
- uint rand = uint(keccak256(_str));
- return rand % dnaModulus;
- }
-
- function createRandomZombie(string _name) public {
- require(ownerZombieCount[msg.sender] == 0);
- uint randDna = _generateRandomDna(_name);
- randDna = randDna - randDna % 100;
- _createZombie(_name, randDna);
- }
-
- }
- "ownable.sol": |
- pragma solidity ^0.4.19;
- /**
- * @title Ownable
- * @dev The Ownable contract has an owner address, and provides basic authorization control
- * functions, this simplifies the implementation of "user permissions".
- */
- contract Ownable {
- address public owner;
-
- event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
-
- /**
- * @dev The Ownable constructor sets the original `owner` of the contract to the sender
- * account.
- */
- function Ownable() public {
- owner = msg.sender;
- }
-
-
- /**
- * @dev Throws if called by any account other than the owner.
- */
- modifier onlyOwner() {
- require(msg.sender == owner);
- _;
- }
-
-
- /**
- * @dev Allows the current owner to transfer control of the contract to a newOwner.
- * @param newOwner The address to transfer ownership to.
- */
- function transferOwnership(address newOwner) public onlyOwner {
- require(newOwner != address(0));
- OwnershipTransferred(owner, newOwner);
- owner = newOwner;
- }
-
- }
- "safemath.sol": |
- pragma solidity ^0.4.18;
-
- /**
- * @title SafeMath
- * @dev Math operations with safety checks that throw on error
- */
- library SafeMath {
-
- /**
- * @dev Multiplies two numbers, throws on overflow.
- */
- function mul(uint256 a, uint256 b) internal pure returns (uint256) {
- if (a == 0) {
- return 0;
- }
- uint256 c = a * b;
- assert(c / a == b);
- return c;
- }
-
- /**
- * @dev Integer division of two numbers, truncating the quotient.
- */
- function div(uint256 a, uint256 b) internal pure returns (uint256) {
- // assert(b > 0); // Solidity automatically throws when dividing by 0
- uint256 c = a / b;
- // assert(a == b * c + a % b); // There is no case in which this doesn't hold
- return c;
- }
-
- /**
- * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
- */
- function sub(uint256 a, uint256 b) internal pure returns (uint256) {
- assert(b <= a);
- return a - b;
- }
-
- /**
- * @dev Adds two numbers, throws on overflow.
- */
- function add(uint256 a, uint256 b) internal pure returns (uint256) {
- uint256 c = a + b;
- assert(c >= a);
- return c;
- }
- }
- "erc721.sol": |
- contract ERC721 {
- event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
- event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);
-
- function balanceOf(address _owner) public view returns (uint256 _balance);
- function ownerOf(uint256 _tokenId) public view returns (address _owner);
- function transfer(address _to, uint256 _tokenId) public;
- function approve(address _to, uint256 _tokenId) public;
- function takeOwnership(uint256 _tokenId) public;
- }
- answer: |
-
-
-
-
- CryptoZombies front-end
-
-
-
-
-
-
-
-
+ editor:
+ language: html
+ startingCode:
+ 'index.html': |
+
+
+
+
+ CryptoZombies front-end
+
+
+
+
+
+
+
+
+ 'zombieownership.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./zombieattack.sol";
+ import "./erc721.sol";
+ import "./safemath.sol";
+
+ contract ZombieOwnership is ZombieAttack, ERC721 {
+
+ using SafeMath for uint256;
+
+ mapping (uint => address) zombieApprovals;
+
+ function balanceOf(address _owner) public view returns (uint256 _balance) {
+ return ownerZombieCount[_owner];
+ }
+
+ function ownerOf(uint256 _tokenId) public view returns (address _owner) {
+ return zombieToOwner[_tokenId];
+ }
+
+ function _transfer(address _from, address _to, uint256 _tokenId) private {
+ ownerZombieCount[_to] = ownerZombieCount[_to].add(1);
+ ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1);
+ zombieToOwner[_tokenId] = _to;
+ Transfer(_from, _to, _tokenId);
+ }
+
+ function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
+ _transfer(msg.sender, _to, _tokenId);
+ }
+
+ function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
+ zombieApprovals[_tokenId] = _to;
+ Approval(msg.sender, _to, _tokenId);
+ }
+
+ function takeOwnership(uint256 _tokenId) public {
+ require(zombieApprovals[_tokenId] == msg.sender);
+ address owner = ownerOf(_tokenId);
+ _transfer(owner, msg.sender, _tokenId);
+ }
+ }
+ 'zombieattack.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./zombiehelper.sol";
+
+ contract ZombieAttack is ZombieHelper {
+ uint randNonce = 0;
+ uint attackVictoryProbability = 70;
+
+ function randMod(uint _modulus) internal returns(uint) {
+ randNonce++;
+ return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
+ }
+
+ function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) {
+ Zombie storage myZombie = zombies[_zombieId];
+ Zombie storage enemyZombie = zombies[_targetId];
+ uint rand = randMod(100);
+ if (rand <= attackVictoryProbability) {
+ myZombie.winCount++;
+ myZombie.level++;
+ enemyZombie.lossCount++;
+ feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
+ } else {
+ myZombie.lossCount++;
+ enemyZombie.winCount++;
+ _triggerCooldown(myZombie);
+ }
+ }
+ }
+ 'zombiehelper.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./zombiefeeding.sol";
+
+ contract ZombieHelper is ZombieFeeding {
+
+ uint levelUpFee = 0.001 ether;
+
+ modifier aboveLevel(uint _level, uint _zombieId) {
+ require(zombies[_zombieId].level >= _level);
+ _;
+ }
+
+ function withdraw() external onlyOwner {
+ owner.transfer(this.balance);
+ }
+
+ function setLevelUpFee(uint _fee) external onlyOwner {
+ levelUpFee = _fee;
+ }
+
+ function levelUp(uint _zombieId) external payable {
+ require(msg.value == levelUpFee);
+ zombies[_zombieId].level++;
+ }
+
+ function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) onlyOwnerOf(_zombieId) {
+ zombies[_zombieId].name = _newName;
+ }
+
+ function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_zombieId) {
+ zombies[_zombieId].dna = _newDna;
+ }
+
+ function getZombiesByOwner(address _owner) external view returns(uint[]) {
+ uint[] memory result = new uint[](ownerZombieCount[_owner]);
+ uint counter = 0;
+ for (uint i = 0; i < zombies.length; i++) {
+ if (zombieToOwner[i] == _owner) {
+ result[counter] = i;
+ counter++;
+ }
+ }
+ return result;
+ }
+
+ }
+ 'zombiefeeding.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./zombiefactory.sol";
+
+ contract KittyInterface {
+ function getKitty(uint256 _id) external view returns (
+ bool isGestating,
+ bool isReady,
+ uint256 cooldownIndex,
+ uint256 nextActionAt,
+ uint256 siringWithId,
+ uint256 birthTime,
+ uint256 matronId,
+ uint256 sireId,
+ uint256 generation,
+ uint256 genes
+ );
+ }
+
+ contract ZombieFeeding is ZombieFactory {
+
+ KittyInterface kittyContract;
+
+ modifier onlyOwnerOf(uint _zombieId) {
+ require(msg.sender == zombieToOwner[_zombieId]);
+ _;
+ }
+
+ function setKittyContractAddress(address _address) external onlyOwner {
+ kittyContract = KittyInterface(_address);
+ }
+
+ function _triggerCooldown(Zombie storage _zombie) internal {
+ _zombie.readyTime = uint32(now + cooldownTime);
+ }
+
+ function _isReady(Zombie storage _zombie) internal view returns (bool) {
+ return (_zombie.readyTime <= now);
+ }
+
+ function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal onlyOwnerOf(_zombieId) {
+ Zombie storage myZombie = zombies[_zombieId];
+ require(_isReady(myZombie));
+ _targetDna = _targetDna % dnaModulus;
+ uint newDna = (myZombie.dna + _targetDna) / 2;
+ if (keccak256(_species) == keccak256("kitty")) {
+ newDna = newDna - newDna % 100 + 99;
+ }
+ _createZombie("NoName", newDna);
+ _triggerCooldown(myZombie);
+ }
+
+ function feedOnKitty(uint _zombieId, uint _kittyId) public {
+ uint kittyDna;
+ (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
+ feedAndMultiply(_zombieId, kittyDna, "kitty");
+ }
+ }
+ 'zombiefactory.sol': |
+ pragma solidity ^0.4.19;
+
+ import "./ownable.sol";
+ import "./safemath.sol";
+
+ contract ZombieFactory is Ownable {
+
+ using SafeMath for uint256;
+
+ event NewZombie(uint zombieId, string name, uint dna);
+
+ uint dnaDigits = 16;
+ uint dnaModulus = 10 ** dnaDigits;
+ uint cooldownTime = 1 days;
+
+ struct Zombie {
+ string name;
+ uint dna;
+ uint32 level;
+ uint32 readyTime;
+ uint16 winCount;
+ uint16 lossCount;
+ }
+
+ Zombie[] public zombies;
+
+ mapping (uint => address) public zombieToOwner;
+ mapping (address => uint) ownerZombieCount;
+
+ function _createZombie(string _name, uint _dna) internal {
+ uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
+ zombieToOwner[id] = msg.sender;
+ ownerZombieCount[msg.sender]++;
+ NewZombie(id, _name, _dna);
+ }
+
+ function _generateRandomDna(string _str) private view returns (uint) {
+ uint rand = uint(keccak256(_str));
+ return rand % dnaModulus;
+ }
+
+ function createRandomZombie(string _name) public {
+ require(ownerZombieCount[msg.sender] == 0);
+ uint randDna = _generateRandomDna(_name);
+ randDna = randDna - randDna % 100;
+ _createZombie(_name, randDna);
+ }
+
+ }
+ 'ownable.sol': |
+ pragma solidity ^0.4.19;
+ /**
+ * @title Ownable
+ * @dev The Ownable contract has an owner address, and provides basic authorization control
+ * functions, this simplifies the implementation of "user permissions".
+ */
+ contract Ownable {
+ address public owner;
+
+ event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
+
+ /**
+ * @dev The Ownable constructor sets the original `owner` of the contract to the sender
+ * account.
+ */
+ function Ownable() public {
+ owner = msg.sender;
+ }
+
+
+ /**
+ * @dev Throws if called by any account other than the owner.
+ */
+ modifier onlyOwner() {
+ require(msg.sender == owner);
+ _;
+ }
+
+
+ /**
+ * @dev Allows the current owner to transfer control of the contract to a newOwner.
+ * @param newOwner The address to transfer ownership to.
+ */
+ function transferOwnership(address newOwner) public onlyOwner {
+ require(newOwner != address(0));
+ OwnershipTransferred(owner, newOwner);
+ owner = newOwner;
+ }
+
+ }
+ 'safemath.sol': |
+ pragma solidity ^0.4.18;
+
+ /**
+ * @title SafeMath
+ * @dev Math operations with safety checks that throw on error
+ */
+ library SafeMath {
+
+ /**
+ * @dev Multiplies two numbers, throws on overflow.
+ */
+ function mul(uint256 a, uint256 b) internal pure returns (uint256) {
+ if (a == 0) {
+ return 0;
+ }
+ uint256 c = a * b;
+ assert(c / a == b);
+ return c;
+ }
+
+ /**
+ * @dev Integer division of two numbers, truncating the quotient.
+ */
+ function div(uint256 a, uint256 b) internal pure returns (uint256) {
+ // assert(b > 0); // Solidity automatically throws when dividing by 0
+ uint256 c = a / b;
+ // assert(a == b * c + a % b); // There is no case in which this doesn't hold
+ return c;
+ }
+
+ /**
+ * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
+ */
+ function sub(uint256 a, uint256 b) internal pure returns (uint256) {
+ assert(b <= a);
+ return a - b;
+ }
+
+ /**
+ * @dev Adds two numbers, throws on overflow.
+ */
+ function add(uint256 a, uint256 b) internal pure returns (uint256) {
+ uint256 c = a + b;
+ assert(c >= a);
+ return c;
+ }
+ }
+ 'erc721.sol': |
+ contract ERC721 {
+ event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
+ event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);
+
+ function balanceOf(address _owner) public view returns (uint256 _balance);
+ function ownerOf(uint256 _tokenId) public view returns (address _owner);
+ function transfer(address _to, uint256 _tokenId) public;
+ function approve(address _to, uint256 _tokenId) public;
+ function takeOwnership(uint256 _tokenId) public;
+ }
+ answer: |
+
+
+
+
+ CryptoZombies front-end
+
+
+
+
+
+
+
+
---
-太棒了。现在我们的项目中有了Web3.js, 来初始化它然后和区块链对话吧。
+太棒了。现在我们的项目中有了 Web3.js, 来初始化它然后和区块链对话吧。
首先我们需要 **_Web3 Provider_**.
要记住,以太坊是由共享同一份数据的相同拷贝的 **_节点_** 构成的。 在 Web3.js 里设置 Web3 的 `Provider`(提供者) 告诉我们的代码应该和 **哪个节点** 交互来处理我们的读写。这就好像在传统的 Web 应用程序中为你的 API 调用设置远程 Web 服务器的网址。
-你可以运行你自己的以太坊节点来作为 Provider。 不过,有一个第三方的服务,可以让你的生活变得轻松点,让你不必为了给你的用户提供DApp而维护一个以太坊节点— **_Infura_**.
+你可以运行你自己的以太坊节点来作为 Provider。 不过,有一个第三方的服务,可以让你的生活变得轻松点,让你不必为了给你的用户提供 DApp 而维护一个以太坊节点— **_Infura_**.
## Infura
@@ -402,9 +402,9 @@ material:
var web3 = new Web3(new Web3.providers.WebsocketProvider("wss://mainnet.infura.io/ws"));
```
-不过,因为我们的 DApp 将被很多人使用,这些用户不单会从区块链读取信息,还会向区块链 **_写_** 入信息,我们需要用一个方法让用户可以用他们的私钥给事务签名。
+不过,因为我们的 DApp 将被很多人使用,这些用户不单会从区块链读取信息,还会向区块链 **_写_** 入信息,我们需要用一个方法让用户可以用他们的私钥给交易签名。
-> 注意: 以太坊 (以及通常意义上的 blockchains )使用一个公钥/私钥对来对给事务做数字签名。把它想成一个数字签名的异常安全的密码。这样当我修改区块链上的数据的时候,我可以用我的公钥来 **证明** 我就是签名的那个。但是因为没人知道我的私钥,所以没人能伪造我的事务。
+> 注意: 以太坊 (以及通常意义上的 blockchains )使用一个公钥/私钥对来对给交易做数字签名。把它想成一个数字签名的异常安全的密码。这样当我修改区块链上的数据的时候,我可以用我的公钥来 **证明** 我就是签名的那个。但是因为没人知道我的私钥,所以没人能伪造我的交易。
加密学非常复杂,所以除非你是个专家并且的确知道自己在做什么,你最好不要在你应用的前端中管理你用户的私钥。
@@ -414,15 +414,15 @@ var web3 = new Web3(new Web3.providers.WebsocketProvider("wss://mainnet.infura.i
Metamask 是 Chrome 和 Firefox 的浏览器扩展, 它能让用户安全地维护他们的以太坊账户和私钥, 并用他们的账户和使用 Web3.js 的网站互动(如果你还没用过它,你肯定会想去安装的——这样你的浏览器就能使用 Web3.js 了,然后你就可以和任何与以太坊区块链通信的网站交互了)
-作为开发者,如果你想让用户从他们的浏览器里通过网站和你的DApp交互(就像我们在 CryptoZombies 游戏里一样),你肯定会想要兼容 Metamask 的。
+作为开发者,如果你想让用户从他们的浏览器里通过网站和你的 DApp 交互(就像我们在 CryptoZombies 游戏里一样),你肯定会想要兼容 Metamask 的。
> **注意**: Metamask 默认使用 Infura 的服务器做为 web3 提供者。 就像我们上面做的那样。不过它还为用户提供了选择他们自己 Web3 提供者的选项。所以使用 Metamask 的 web3 提供者,你就给了用户选择权,而自己无需操心这一块。
## 使用 Metamask 的 web3 提供者
-Metamask 把它的 web3 提供者注入到浏览器的全局 JavaScript对象`web3`中。所以你的应用可以检查 `web3` 是否存在。若存在就使用 `web3.currentProvider` 作为它的提供者。
+Metamask 把它的 web3 提供者注入到浏览器的全局 JavaScript 对象`web3`中。所以你的应用可以检查 `web3` 是否存在。若存在就使用 `web3.currentProvider` 作为它的提供者。
-这里是一些 Metamask 提供的示例代码,用来检查用户是否安装了MetaMask,如果没有安装就告诉用户需要安装MetaMask来使用我们的应用。
+这里是一些 Metamask 提供的示例代码,用来检查用户是否安装了 MetaMask,如果没有安装就告诉用户需要安装 MetaMask 来使用我们的应用。
```
window.addEventListener('load', function() {
@@ -444,10 +444,10 @@ window.addEventListener('load', function() {
你可以在你所有的应用中使用这段样板代码,好检查用户是否安装以及告诉用户安装 MetaMask。
-> 注意: 除了MetaMask,你的用户也可能在使用其他他的私钥管理应用,比如 **Mist** 浏览器。不过,它们都实现了相同的模式来注入 `web3` 变量。所以我这里描述的方法对两者是通用的。
+> 注意: 除了 MetaMask,你的用户也可能在使用其他他的私钥管理应用,比如 **Mist** 浏览器。不过,它们都实现了相同的模式来注入 `web3` 变量。所以我这里描述的方法对两者是通用的。
## 实战演习
-我们在HTML文件中的 `
-
-
+
+
+
+
+
+
+
+ ` 标签前面放置了一个空的 `script` 标签。可以把这节课的 JavaScript 代码写在里面。
+我们在 HTML 文件中的 `` 标签前面放置了一个空的 `script` 标签。可以把这节课的 JavaScript 代码写在里面。
-1. 把上面用来检测 MetaMask 是否安装的模板代码粘贴进来。请粘贴到以 `window.addEventListener` 开头的代码块中。
\ No newline at end of file
+1. 把上面用来检测 MetaMask 是否安装的模板代码粘贴进来。请粘贴到以 `window.addEventListener` 开头的代码块中。
diff --git a/zh/6/04.md b/zh/6/04.md
index f44065a8d..d431f689a 100644
--- a/zh/6/04.md
+++ b/zh/6/04.md
@@ -3,887 +3,887 @@ title: 调用和合约函数
actions: ['checkAnswer', 'hints']
requireLogin: true
material:
- editor:
- language: html
- startingCode:
- "index.html": |
-
-
-