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": | - - - - - CryptoZombies front-end - - - - - - - + + + + + + + + + 'cryptozombies_abi.js': | + var cryptozombiesABI = [ + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + } + ], + "name": "levelUp", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_kittyId", + "type": "uint256" + } + ], + "name": "feedOnKitty", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "zombies", + "outputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "dna", + "type": "uint256" + }, + { + "name": "level", + "type": "uint32" + }, + { + "name": "readyTime", + "type": "uint32" + }, + { + "name": "winCount", + "type": "uint16" + }, + { + "name": "lossCount", + "type": "uint16" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "getZombiesByOwner", + "outputs": [ + { + "name": "", + "type": "uint256[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "zombieToOwner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_address", + "type": "address" + } + ], + "name": "setKittyContractAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_newDna", + "type": "uint256" + } + ], + "name": "changeDna", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "_balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_name", + "type": "string" + } + ], + "name": "createRandomZombie", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAllZombies", + "outputs": [ + { + "name": "", + "type": "uint256[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "takeOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_newName", + "type": "string" + } + ], + "name": "changeName", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_fee", + "type": "uint256" + } + ], + "name": "setLevelUpFee", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_targetId", + "type": "uint256" + } + ], + "name": "attack", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_approved", + "type": "address" + }, + { + "indexed": false, + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "attackResult", + "type": "bool" + }, + { + "indexed": false, + "name": "winCount", + "type": "uint16" + }, + { + "indexed": false, + "name": "lossCount", + "type": "uint16" + } + ], + "name": "AttackResult", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "zombieId", + "type": "uint256" + }, + { + "indexed": false, + "name": "name", + "type": "string" + }, + { + "indexed": false, + "name": "dna", + "type": "uint256" + } + ], + "name": "NewZombie", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + } + ] + '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 { - // Now you can start your app & access web3 freely: - startApp() - - }) - - - - "cryptozombies_abi.js": | - var cryptozombiesABI = [ - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - } - ], - "name": "levelUp", - "outputs": [], - "payable": true, - "stateMutability": "payable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_kittyId", - "type": "uint256" - } - ], - "name": "feedOnKitty", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "zombies", - "outputs": [ - { - "name": "name", - "type": "string" - }, - { - "name": "dna", - "type": "uint256" - }, - { - "name": "level", - "type": "uint32" - }, - { - "name": "readyTime", - "type": "uint32" - }, - { - "name": "winCount", - "type": "uint16" - }, - { - "name": "lossCount", - "type": "uint16" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "withdraw", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "getZombiesByOwner", - "outputs": [ - { - "name": "", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "zombieToOwner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_address", - "type": "address" - } - ], - "name": "setKittyContractAddress", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_newDna", - "type": "uint256" - } - ], - "name": "changeDna", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "ownerOf", - "outputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "_balance", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_name", - "type": "string" - } - ], - "name": "createRandomZombie", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getAllZombies", - "outputs": [ - { - "name": "", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "takeOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_newName", - "type": "string" - } - ], - "name": "changeName", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_fee", - "type": "uint256" - } - ], - "name": "setLevelUpFee", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_targetId", - "type": "uint256" - } - ], - "name": "attack", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": true, - "name": "_approved", - "type": "address" - }, - { - "indexed": false, - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "attackResult", - "type": "bool" - }, - { - "indexed": false, - "name": "winCount", - "type": "uint16" - }, - { - "indexed": false, - "name": "lossCount", - "type": "uint16" - } - ], - "name": "AttackResult", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "zombieId", - "type": "uint256" - }, - { - "indexed": false, - "name": "name", - "type": "string" - }, - { - "indexed": false, - "name": "dna", - "type": "uint256" - } - ], - "name": "NewZombie", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - } - ] - "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 - - - - - - - - - + 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 和它对话。 @@ -892,9 +892,9 @@ Web3.js 有两个方法来调用我们合约的函数: `call` and `send`. ### Call -`call` 用来调用 `view` 和 `pure` 函数。它只运行在本地节点,不会在区块链上创建事务。 +`call` 用来调用 `view` 和 `pure` 函数。它只运行在本地节点,不会在区块链上创建交易。 -> **复习:** `view` 和 `pure` 函数是只读的并不会改变区块链的状态。它们也不会消耗任何gas。用户也不会被要求用MetaMask对事务签名。 +> **复习:** `view` 和 `pure` 函数是只读的并不会改变区块链的状态。它们也不会消耗任何 gas。用户也不会被要求用 MetaMask 对交易签名。 使用 Web3.js,你可以如下 `call` 一个名为`myMethod`的方法并传入一个 `123` 作为参数: @@ -904,11 +904,11 @@ myContract.methods.myMethod(123).call() ### Send -`send` 将创建一个事务并改变区块链上的数据。你需要用 `send` 来调用任何非 `view` 或者 `pure` 的函数。 +`send` 将创建一个交易并改变区块链上的数据。你需要用 `send` 来调用任何非 `view` 或者 `pure` 的函数。 -> **注意:** `send` 一个事务将要求用户支付gas,并会要求弹出对话框请求用户使用 Metamask 对事务签名。在我们使用 Metamask 作为我们的 web3 提供者的时候,所有这一切都会在我们调用 `send()` 的时候自动发生。而我们自己无需在代码中操心这一切,挺爽的吧。 +> **注意:** `send` 一个交易将要求用户支付 gas,并会要求弹出对话框请求用户使用 Metamask 对交易签名。在我们使用 Metamask 作为我们的 web3 提供者的时候,所有这一切都会在我们调用 `send()` 的时候自动发生。而我们自己无需在代码中操心这一切,挺爽的吧。 -使用 Web3.js, 你可以像这样 `send` 一个事务调用`myMethod` 并传入 `123` 作为参数: +使用 Web3.js, 你可以像这样 `send` 一个交易调用`myMethod` 并传入 `123` 作为参数: ``` myContract.methods.myMethod(123).send() @@ -920,7 +920,7 @@ myContract.methods.myMethod(123).send() 来看一个使用 `call` 读取我们合约数据的真实例子 -回忆一下,我们定义我们的僵尸数组为 `公开`(public): +回忆一下,我们定义我们的僵尸数组为 `公开`(public): ``` Zombie[] public zombies; @@ -930,7 +930,7 @@ Zombie[] public zombies; 这是如何在外面的前端界面中写一个 JavaScript 方法来传入一个僵尸 id,在我们的合同中查询那个僵尸并返回结果 -> 注意: 本课中所有的示例代码都使用 Web3.js 的 **1.0 版**,此版本使用的是 Promises 而不是回调函数。你在线上看到的其他教程可能还在使用老版的 Web3.js。在1.0版中,语法改变了不少。如果你从其他教程中复制代码,先确保你们使用的是相同版本的Web3.js。 +> 注意: 本课中所有的示例代码都使用 Web3.js 的 **1.0 版**,此版本使用的是 Promises 而不是回调函数。你在线上看到的其他教程可能还在使用老版的 Web3.js。在 1.0 版中,语法改变了不少。如果你从其他教程中复制代码,先确保你们使用的是相同版本的 Web3.js。 ``` function getZombieDetails(id) { @@ -946,9 +946,9 @@ getZombieDetails(15) 我们来看看这里都做了什么 -`cryptoZombies.methods.zombies(id).call()` 将和 Web3 提供者节点通信,告诉它返回从我们的合约中的 `Zombie[] public zombies`,`id`为传入参数的僵尸信息。 +`cryptoZombies.methods.zombies(id).call()` 将和 Web3 提供者节点通信,告诉它返回从我们的合约中的 `Zombie[] public zombies`,`id`为传入参数的僵尸信息。 -注意这是 **异步的**,就像从外部服务器中调用API。所以 Web3 在这里返回了一个 Promises. (如果你对 JavaScript的 Promises 不了解,最好先去学习一下这方面知识再继续)。 +注意这是 **异步的**,就像从外部服务器中调用 API。所以 Web3 在这里返回了一个 Promises. (如果你对 JavaScript 的 Promises 不了解,最好先去学习一下这方面知识再继续)。 一旦那个 `promise` 被 `resolve`, (意味着我们从 Web3 提供者那里获得了响应),我们的例子代码将执行 `then` 语句中的代码,在控制台打出 `result`。 @@ -973,16 +973,16 @@ getZombieDetails(15) 1. 先为`zombieToOwner` 创建一个类似的函数。如果你还记得 `ZombieFactory.sol`,我们有一个长这样的映射: - ``` - mapping (uint => address) public zombieToOwner; - ``` +``` +mapping (uint => address) public zombieToOwner; +``` - 定义一个 JavaScript 方法,起名为 `zombieToOwner`。和上面的 `getZombieDetails` 类似, 它将接收一个`id` 作为参数,并返回一个 Web3.js `call` 我们合约里的`zombieToOwner` 。 +定义一个 JavaScript 方法,起名为 `zombieToOwner`。和上面的 `getZombieDetails` 类似, 它将接收一个`id` 作为参数,并返回一个 Web3.js `call` 我们合约里的`zombieToOwner` 。 2. 之后在下面,为 `getZombiesByOwner` 定义一个方法。如果你还能记起 `ZombieHelper.sol`,这个方法定义像这样: - ``` - function getZombiesByOwner(address _owner) - ``` +``` +function getZombiesByOwner(address _owner) +``` - 我们的 `getZombiesByOwner` 方法将接收 `owner` 作为参数,并返回一个对我们函数 `getZombiesByOwner`的 Web3.js `call` +我们的 `getZombiesByOwner` 方法将接收 `owner` 作为参数,并返回一个对我们函数 `getZombiesByOwner`的 Web3.js `call` diff --git a/zh/6/07.md b/zh/6/07.md index bbc23dde9..97396a286 100644 --- a/zh/6/07.md +++ b/zh/6/07.md @@ -1,1001 +1,1001 @@ --- -title: 发送事务 +title: 发送交易 actions: ['checkAnswer', 'hints'] requireLogin: true material: - editor: - language: html - startingCode: - "index.html": | - - - - - CryptoZombies front-end - - - - - -
-
- - + + + + +
+
+ + + + + 'cryptozombies_abi.js': | + var cryptozombiesABI = [ + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + } + ], + "name": "levelUp", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_kittyId", + "type": "uint256" + } + ], + "name": "feedOnKitty", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "zombies", + "outputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "dna", + "type": "uint256" + }, + { + "name": "level", + "type": "uint32" + }, + { + "name": "readyTime", + "type": "uint32" + }, + { + "name": "winCount", + "type": "uint16" + }, + { + "name": "lossCount", + "type": "uint16" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "getZombiesByOwner", + "outputs": [ + { + "name": "", + "type": "uint256[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "zombieToOwner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_address", + "type": "address" + } + ], + "name": "setKittyContractAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_newDna", + "type": "uint256" + } + ], + "name": "changeDna", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "_balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_name", + "type": "string" + } + ], + "name": "createRandomZombie", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAllZombies", + "outputs": [ + { + "name": "", + "type": "uint256[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "takeOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_newName", + "type": "string" + } + ], + "name": "changeName", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_fee", + "type": "uint256" + } + ], + "name": "setLevelUpFee", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_targetId", + "type": "uint256" + } + ], + "name": "attack", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_approved", + "type": "address" + }, + { + "indexed": false, + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "attackResult", + "type": "bool" + }, + { + "indexed": false, + "name": "winCount", + "type": "uint16" + }, + { + "indexed": false, + "name": "lossCount", + "type": "uint16" + } + ], + "name": "AttackResult", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "zombieId", + "type": "uint256" + }, + { + "indexed": false, + "name": "name", + "type": "string" + }, + { + "indexed": false, + "name": "dna", + "type": "uint256" + } + ], + "name": "NewZombie", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + } + ] + '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); } - }, 100); - } - - function displayZombies(ids) { - $("#zombies").empty(); - for (const id of ids) { - // Look up zombie details from our contract. Returns a `zombie` object - getZombieDetails(id) - .then(function(zombie) { - // Using ES6's "template literals" to inject variables into the HTML. - // Append each one to our #zombies div - $("#zombies").append(`
-
    -
  • Name: ${zombie.name}
  • -
  • DNA: ${zombie.dna}
  • -
  • Level: ${zombie.level}
  • -
  • Wins: ${zombie.winCount}
  • -
  • Losses: ${zombie.lossCount}
  • -
  • Ready Time: ${zombie.readyTime}
  • -
-
`); - }); } - } + '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; - // Start here + modifier aboveLevel(uint _level, uint _zombieId) { + require(zombies[_zombieId].level >= _level); + _; + } - function getZombieDetails(id) { - return cryptoZombies.methods.zombies(id).call() - } + function withdraw() external onlyOwner { + owner.transfer(this.balance); + } - function zombieToOwner(id) { - return cryptoZombies.methods.zombieToOwner(id).call() - } + function setLevelUpFee(uint _fee) external onlyOwner { + levelUpFee = _fee; + } - function getZombiesByOwner(owner) { - return cryptoZombies.methods.getZombiesByOwner(owner).call() - } + function levelUp(uint _zombieId) external payable { + require(msg.value == levelUpFee); + zombies[_zombieId].level++; + } - window.addEventListener('load', function() { + 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; + } - // Checking if Web3 has been injected by the browser (Mist/MetaMask) - if (typeof web3 !== 'undefined') { - // Use Mist/MetaMask's provider - web3js = new Web3(web3.currentProvider); - } else { - // Handle the case where the user doesn't have Metamask installed - // Probably show them a message prompting them to install Metamask + 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 + ); } - // Now you can start your app & access web3 freely: - startApp() - - }) - - - - "cryptozombies_abi.js": | - var cryptozombiesABI = [ - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - } - ], - "name": "levelUp", - "outputs": [], - "payable": true, - "stateMutability": "payable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_kittyId", - "type": "uint256" - } - ], - "name": "feedOnKitty", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "zombies", - "outputs": [ - { - "name": "name", - "type": "string" - }, - { - "name": "dna", - "type": "uint256" - }, - { - "name": "level", - "type": "uint32" - }, - { - "name": "readyTime", - "type": "uint32" - }, - { - "name": "winCount", - "type": "uint16" - }, - { - "name": "lossCount", - "type": "uint16" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "withdraw", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "getZombiesByOwner", - "outputs": [ - { - "name": "", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "zombieToOwner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_address", - "type": "address" - } - ], - "name": "setKittyContractAddress", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_newDna", - "type": "uint256" - } - ], - "name": "changeDna", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "ownerOf", - "outputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "_balance", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_name", - "type": "string" - } - ], - "name": "createRandomZombie", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getAllZombies", - "outputs": [ - { - "name": "", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "takeOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_newName", - "type": "string" - } - ], - "name": "changeName", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_fee", - "type": "uint256" - } - ], - "name": "setLevelUpFee", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_targetId", - "type": "uint256" - } - ], - "name": "attack", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": true, - "name": "_approved", - "type": "address" - }, - { - "indexed": false, - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "attackResult", - "type": "bool" - }, - { - "indexed": false, - "name": "winCount", - "type": "uint16" - }, - { - "indexed": false, - "name": "lossCount", - "type": "uint16" - } - ], - "name": "AttackResult", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "zombieId", - "type": "uint256" - }, - { - "indexed": false, - "name": "name", - "type": "string" - }, - { - "indexed": false, - "name": "dna", - "type": "uint256" - } - ], - "name": "NewZombie", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - } - ] - "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 - - - - - -
-
- - - - + '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 + + + + + +
+
+ + + + --- 这下我们的界面能检测用户的 MetaMask 账户,并自动在首页显示它们的僵尸大军了,有没有很棒? @@ -1004,13 +1004,13 @@ material: 相对 `call` 函数,`send` 函数有如下主要区别: -1. `send` 一个事务需要一个 `from` 地址来表明谁在调用这个函数(也就是你 Solidity 代码里的 `msg.sender` )。 我们需要这是我们 DApp 的用户,这样一来 MetaMask 才会弹出提示让他们对事务签名。 +1. `send` 一个交易需要一个 `from` 地址来表明谁在调用这个函数(也就是你 Solidity 代码里的 `msg.sender` )。 我们需要这是我们 DApp 的用户,这样一来 MetaMask 才会弹出提示让他们对交易签名。 -2. `send` 一个事务将花费 gas +2. `send` 一个交易将花费 gas -3. 在用户 `send` 一个事务到该事务对区块链产生实际影响之间有一个不可忽略的延迟。这是因为我们必须等待事务被包含进一个区块里,以太坊上一个区块的时间平均下来是15秒左右。如果当前在以太坊上有大量挂起事务或者用户发送了过低的 gas 价格,我们的事务可能需要等待数个区块才能被包含进去,往往可能花费数分钟。 +3. 在用户 `send` 一个交易到该交易对区块链产生实际影响之间有一个不可忽略的延迟。这是因为我们必须等待交易被包含进一个区块里,以太坊上一个区块的时间平均下来是 15 秒左右。如果当前在以太坊上有大量挂起交易或者用户发送了过低的 gas 价格,我们的交易可能需要等待数个区块才能被包含进去,往往可能花费数分钟。 - 所以在我们的代码中我们需要编写逻辑来处理这部分异步特性。 +所以在我们的代码中我们需要编写逻辑来处理这部分异步特性。 ## 生成一个僵尸 @@ -1027,19 +1027,19 @@ function createRandomZombie(string _name) public { } ``` -这是如何在用 MetaMask 在 Web3.js 中调用这个函数的示例: +这是如何在用 MetaMask 在 Web3.js 中调用这个函数的示例: ``` function createRandomZombie(name) { // 这将需要一段时间,所以在界面中告诉用户这一点 - // 事务被发送出去了 + // 交易被发送出去了 $("#txStatus").text("正在区块链上创建僵尸,这将需要一会儿..."); - // 把事务发送到我们的合约: + // 把交易发送到我们的合约: return cryptoZombies.methods.createRandomZombie(name) .send({ from: userAccount }) .on("receipt", function(receipt) { $("#txStatus").text("成功生成了 " + name + "!"); - // 事务被区块链接受了,重新渲染界面 + // 交易被区块链接受了,重新渲染界面 getZombiesByOwner(userAccount).then(displayZombies); }) .on("error", function(error) { @@ -1049,29 +1049,29 @@ function createRandomZombie(name) { } ``` -我们的函数 `send` 一个事务到我们的 Web3 提供者,然后链式添加一些事件监听: +我们的函数 `send` 一个交易到我们的 Web3 提供者,然后链式添加一些事件监听: -- `receipt` 将在合约被包含进以太坊区块上以后被触发,这意味着僵尸被创建并保存进我们的合约了。 -- `error` 将在事务未被成功包含进区块后触发,比如用户未支付足够的 gas。我们需要在界面中通知用户事务失败以便他们可以再次尝试。 +- `receipt` 将在合约被包含进以太坊区块上以后被触发,这意味着僵尸被创建并保存进我们的合约了。 +- `error` 将在交易未被成功包含进区块后触发,比如用户未支付足够的 gas。我们需要在界面中通知用户交易失败以便他们可以再次尝试。 -> 注意:你可以在调用 `send` 时选择指定 `gas` 和 `gasPrice`, 例如: `.send({ from: userAccount, gas: 3000000 })`。如果你不指定,MetaMask 将让用户自己选择数值。 +> 注意:你可以在调用 `send` 时选择指定 `gas` 和 `gasPrice`, 例如: `.send({ from: userAccount, gas: 3000000 })`。如果你不指定,MetaMask 将让用户自己选择数值。 ## 实战演习 -我们添加了一个`div`, 指定 ID 为 `txStatus` — 这样我们可以通过更新这个 div 来通知用户事务的状态。 +我们添加了一个`div`, 指定 ID 为 `txStatus` —  这样我们可以通过更新这个 div 来通知用户交易的状态。 1. 在 `displayZombies`下面, 复制粘贴上面 `createRandomZombie` 的代码。 2. 我们来实现另外一个函数 `feedOnKitty`: - 调用 `feedOnKitty` 的逻辑几乎一样 — 我们将发送一个事务来调用这个函数,并且成功的事务会为我们创建一个僵尸,所以我们希望在成功后重新绘制界面。 +调用 `feedOnKitty` 的逻辑几乎一样 —  我们将发送一个交易来调用这个函数,并且成功的交易会为我们创建一个僵尸,所以我们希望在成功后重新绘制界面。 + +在 `createRandomZombie` 下面复制粘贴它的代码,改动这些地方: + +a) 给其命名为 `feedOnKitty`, 它将接收两个参数 `zombieId` 和 `kittyId` - 在 `createRandomZombie` 下面复制粘贴它的代码,改动这些地方: - - a) 给其命名为 `feedOnKitty`, 它将接收两个参数 `zombieId` 和 `kittyId` +b) `#txStatus` 的文本内容将更新为: `"正在吃猫咪,这将需要一会儿..."` - b) `#txStatus` 的文本内容将更新为: `"正在吃猫咪,这将需要一会儿..."` +c) 让其调用我们合约里面的 `feedOnKitty` 函数并传入相同的参数 - c) 让其调用我们合约里面的 `feedOnKitty` 函数并传入相同的参数 - - d) `#txStatus` 里面的的成功信息应该是 `"吃了一只猫咪并生成了一只新僵尸!"` +d) `#txStatus` 里面的的成功信息应该是 `"吃了一只猫咪并生成了一只新僵尸!"` diff --git a/zh/6/08.md b/zh/6/08.md index 01aab624e..cc61a5b67 100644 --- a/zh/6/08.md +++ b/zh/6/08.md @@ -3,1040 +3,1040 @@ title: 调用 Payable 函数 actions: ['checkAnswer', 'hints'] requireLogin: true material: - editor: - language: html - startingCode: - "index.html": | - - - - - CryptoZombies front-end - - - - - -
-
- - + + + + +
+
+ + + + + 'cryptozombies_abi.js': | + var cryptozombiesABI = [ + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + } + ], + "name": "levelUp", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_kittyId", + "type": "uint256" + } + ], + "name": "feedOnKitty", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "zombies", + "outputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "dna", + "type": "uint256" + }, + { + "name": "level", + "type": "uint32" + }, + { + "name": "readyTime", + "type": "uint32" + }, + { + "name": "winCount", + "type": "uint16" + }, + { + "name": "lossCount", + "type": "uint16" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "getZombiesByOwner", + "outputs": [ + { + "name": "", + "type": "uint256[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "zombieToOwner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_address", + "type": "address" + } + ], + "name": "setKittyContractAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_newDna", + "type": "uint256" + } + ], + "name": "changeDna", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "_balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_name", + "type": "string" + } + ], + "name": "createRandomZombie", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAllZombies", + "outputs": [ + { + "name": "", + "type": "uint256[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "takeOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_newName", + "type": "string" + } + ], + "name": "changeName", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_fee", + "type": "uint256" + } + ], + "name": "setLevelUpFee", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_targetId", + "type": "uint256" + } + ], + "name": "attack", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_approved", + "type": "address" + }, + { + "indexed": false, + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "attackResult", + "type": "bool" + }, + { + "indexed": false, + "name": "winCount", + "type": "uint16" + }, + { + "indexed": false, + "name": "lossCount", + "type": "uint16" + } + ], + "name": "AttackResult", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "zombieId", + "type": "uint256" + }, + { + "indexed": false, + "name": "name", + "type": "string" + }, + { + "indexed": false, + "name": "dna", + "type": "uint256" + } + ], + "name": "NewZombie", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + } + ] + '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); } - }, 100); - } - - function displayZombies(ids) { - $("#zombies").empty(); - for (const id of ids) { - // Look up zombie details from our contract. Returns a `zombie` object - getZombieDetails(id) - .then(function(zombie) { - // Using ES6's "template literals" to inject variables into the HTML. - // Append each one to our #zombies div - $("#zombies").append(`
-
    -
  • Name: ${zombie.name}
  • -
  • DNA: ${zombie.dna}
  • -
  • Level: ${zombie.level}
  • -
  • Wins: ${zombie.winCount}
  • -
  • Losses: ${zombie.lossCount}
  • -
  • Ready Time: ${zombie.readyTime}
  • -
-
`); - }); } - } - - function createRandomZombie(name) { - // This is going to take a while, so update the UI to let the user know - // the transaction has been sent - $("#txStatus").text("Creating new zombie on the blockchain. This may take a while..."); - // Send the tx to our contract: - return cryptoZombies.methods.createRandomZombie(name) - .send({ from: userAccount }) - .on("receipt", function(receipt) { - $("#txStatus").text("Successfully created " + name + "!"); - // Transaction was accepted into the blockchain, let's redraw the UI - getZombiesByOwner(userAccount).then(displayZombies); - }) - .on("error", function(error) { - // Do something to alert the user their transaction has failed - $("#txStatus").text(error); - }); - } - - function feedOnKitty(zombieId, kittyId) { - $("#txStatus").text("Eating a kitty. This may take a while..."); - return cryptoZombies.methods.feedOnKitty(zombieId, kittyId) - .send({ from: userAccount }) - .on("receipt", function(receipt) { - $("#txStatus").text("Ate a kitty and spawned a new Zombie!"); - getZombiesByOwner(userAccount).then(displayZombies); - }) - .on("error", function(error) { - $("#txStatus").text(error); - }); - } - - // Start here - - function getZombieDetails(id) { - return cryptoZombies.methods.zombies(id).call() - } - - function zombieToOwner(id) { - return cryptoZombies.methods.zombieToOwner(id).call() - } - - function getZombiesByOwner(owner) { - return cryptoZombies.methods.getZombiesByOwner(owner).call() - } - - window.addEventListener('load', function() { - - // Checking if Web3 has been injected by the browser (Mist/MetaMask) - if (typeof web3 !== 'undefined') { - // Use Mist/MetaMask's provider - web3js = new Web3(web3.currentProvider); - } else { - // Handle the case where the user doesn't have Metamask installed - // Probably show them a message prompting them to install Metamask + '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; - // Now you can start your app & access web3 freely: - startApp() - - }) - - - - "cryptozombies_abi.js": | - var cryptozombiesABI = [ - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - } - ], - "name": "levelUp", - "outputs": [], - "payable": true, - "stateMutability": "payable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_kittyId", - "type": "uint256" - } - ], - "name": "feedOnKitty", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "zombies", - "outputs": [ - { - "name": "name", - "type": "string" - }, - { - "name": "dna", - "type": "uint256" - }, - { - "name": "level", - "type": "uint32" - }, - { - "name": "readyTime", - "type": "uint32" - }, - { - "name": "winCount", - "type": "uint16" - }, - { - "name": "lossCount", - "type": "uint16" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "withdraw", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "getZombiesByOwner", - "outputs": [ - { - "name": "", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "zombieToOwner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_address", - "type": "address" - } - ], - "name": "setKittyContractAddress", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_newDna", - "type": "uint256" - } - ], - "name": "changeDna", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "ownerOf", - "outputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "_balance", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_name", - "type": "string" - } - ], - "name": "createRandomZombie", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getAllZombies", - "outputs": [ - { - "name": "", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "takeOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_newName", - "type": "string" - } - ], - "name": "changeName", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_fee", - "type": "uint256" - } - ], - "name": "setLevelUpFee", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_targetId", - "type": "uint256" - } - ], - "name": "attack", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": true, - "name": "_approved", - "type": "address" - }, - { - "indexed": false, - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "attackResult", - "type": "bool" - }, - { - "indexed": false, - "name": "winCount", - "type": "uint16" - }, - { - "indexed": false, - "name": "lossCount", - "type": "uint16" - } - ], - "name": "AttackResult", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "zombieId", - "type": "uint256" - }, - { - "indexed": false, - "name": "name", - "type": "string" - }, - { - "indexed": false, - "name": "dna", - "type": "uint256" - } - ], - "name": "NewZombie", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - } - ] - "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 - - - - - -
-
- - - - + '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 + + + + + +
+
+ + + + --- - `attack`, `changeName`, 以及 `changeDna` 的逻辑将非常雷同,所以本课将不会花时间在上面。 +`attack`, `changeName`, 以及 `changeDna` 的逻辑将非常雷同,所以本课将不会花时间在上面。 > 实际上,在调用这些函数的时候已经有了非常多的重复逻辑。所以最好是重构代码把相同的代码写成一个函数。(并对`txStatus`使用模板系统——我们已经看到用类似 Vue.js 类的框架是多么整洁) @@ -1053,13 +1053,13 @@ function levelUp(uint _zombieId) external payable { } ``` -和函数一起发送以太非常简单,只有一点需要注意: 我们需要指定发送多少 `wei`,而不是以太。 +和函数一起发送以太非常简单,只有一点需要注意: 我们需要指定发送多少 `wei`,而不是以太。 ## 啥是 Wei? -一个 `wei` 是以太的最小单位 —  1 `ether` 等于 10^18 `wei` +一个 `wei` 是以太的最小单位 —  1 `ether` 等于 10^18 `wei` -太多0要数了,不过幸运的是 Web3.js 有一个转换工具来帮我们做这件事: +太多 0 要数了,不过幸运的是 Web3.js 有一个转换工具来帮我们做这件事: ``` // 把 1 ETH 转换成 Wei @@ -1079,10 +1079,10 @@ cryptoZombies.methods.levelUp(zombieId) 1. 函数将接收一个参数, `zombieId` -2. 在发送事务之前,`txStatus` 的文本应该是 `"正在升级您的僵尸..."` +2. 在发送交易之前,`txStatus` 的文本应该是 `"正在升级您的僵尸..."` 3. 当它调用合约里的`levelUp`时,它应该发送`"0.001"` ETH,并用 `toWei` 转换,如同上面例子里那样。 4. 成功之后应该显示 `"不得了了!僵尸成功升级啦!"` -5. 我们 **不** 需要在调用 `getZombiesByOwner` 后重新绘制界面 — 因为在这里我们只是修改了僵尸的级别而已。 +5. 我们 **不** 需要在调用 `getZombiesByOwner` 后重新绘制界面 —  因为在这里我们只是修改了僵尸的级别而已。 diff --git a/zh/6/09.md b/zh/6/09.md index 7c7ec541c..916d6367f 100644 --- a/zh/6/09.md +++ b/zh/6/09.md @@ -3,1061 +3,1061 @@ title: 订阅事件 actions: ['checkAnswer', 'hints'] requireLogin: true material: - editor: - language: html - startingCode: - "index.html": | - - - - - CryptoZombies front-end - - - - - -
-
- - + + + + +
+
+ + + + + 'cryptozombies_abi.js': | + var cryptozombiesABI = [ + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + } + ], + "name": "levelUp", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_kittyId", + "type": "uint256" + } + ], + "name": "feedOnKitty", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "zombies", + "outputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "dna", + "type": "uint256" + }, + { + "name": "level", + "type": "uint32" + }, + { + "name": "readyTime", + "type": "uint32" + }, + { + "name": "winCount", + "type": "uint16" + }, + { + "name": "lossCount", + "type": "uint16" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "getZombiesByOwner", + "outputs": [ + { + "name": "", + "type": "uint256[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "zombieToOwner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_address", + "type": "address" + } + ], + "name": "setKittyContractAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_newDna", + "type": "uint256" + } + ], + "name": "changeDna", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "_balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_name", + "type": "string" + } + ], + "name": "createRandomZombie", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAllZombies", + "outputs": [ + { + "name": "", + "type": "uint256[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "takeOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_newName", + "type": "string" + } + ], + "name": "changeName", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_fee", + "type": "uint256" + } + ], + "name": "setLevelUpFee", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_zombieId", + "type": "uint256" + }, + { + "name": "_targetId", + "type": "uint256" + } + ], + "name": "attack", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_approved", + "type": "address" + }, + { + "indexed": false, + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "attackResult", + "type": "bool" + }, + { + "indexed": false, + "name": "winCount", + "type": "uint16" + }, + { + "indexed": false, + "name": "lossCount", + "type": "uint16" + } + ], + "name": "AttackResult", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "zombieId", + "type": "uint256" + }, + { + "indexed": false, + "name": "name", + "type": "string" + }, + { + "indexed": false, + "name": "dna", + "type": "uint256" + } + ], + "name": "NewZombie", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + } + ] + '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); } - }, 100); - - // Start here - } - - function displayZombies(ids) { - $("#zombies").empty(); - for (const id of ids) { - // Look up zombie details from our contract. Returns a `zombie` object - getZombieDetails(id) - .then(function(zombie) { - // Using ES6's "template literals" to inject variables into the HTML. - // Append each one to our #zombies div - $("#zombies").append(`
-
    -
  • Name: ${zombie.name}
  • -
  • DNA: ${zombie.dna}
  • -
  • Level: ${zombie.level}
  • -
  • Wins: ${zombie.winCount}
  • -
  • Losses: ${zombie.lossCount}
  • -
  • Ready Time: ${zombie.readyTime}
  • -
-
`); - }); } - } - - function createRandomZombie(name) { - // This is going to take a while, so update the UI to let the user know - // the transaction has been sent - $("#txStatus").text("Creating new zombie on the blockchain. This may take a while..."); - // Send the tx to our contract: - return cryptoZombies.methods.createRandomZombie(name) - .send({ from: userAccount }) - .on("receipt", function(receipt) { - $("#txStatus").text("Successfully created " + name + "!"); - // Transaction was accepted into the blockchain, let's redraw the UI - getZombiesByOwner(userAccount).then(displayZombies); - }) - .on("error", function(error) { - // Do something to alert the user their transaction has failed - $("#txStatus").text(error); - }); - } - - function feedOnKitty(zombieId, kittyId) { - $("#txStatus").text("Eating a kitty. This may take a while..."); - return cryptoZombies.methods.feedOnKitty(zombieId, kittyId) - .send({ from: userAccount }) - .on("receipt", function(receipt) { - $("#txStatus").text("Ate a kitty and spawned a new Zombie!"); - getZombiesByOwner(userAccount).then(displayZombies); - }) - .on("error", function(error) { - $("#txStatus").text(error); - }); - } - - function levelUp(zombieId) { - $("#txStatus").text("Leveling up your zombie..."); - return cryptoZombies.methods.levelUp(zombieId) - .send({ from: userAccount, value: web3.utils.toWei("0.001", "ether") }) - .on("receipt", function(receipt) { - $("#txStatus").text("Power overwhelming! Zombie successfully leveled up"); - }) - .on("error", function(error) { - $("#txStatus").text(error); - }); - } - - function getZombieDetails(id) { - return cryptoZombies.methods.zombies(id).call() - } - - function zombieToOwner(id) { - return cryptoZombies.methods.zombieToOwner(id).call() - } - - function getZombiesByOwner(owner) { - return cryptoZombies.methods.getZombiesByOwner(owner).call() - } - - window.addEventListener('load', function() { - - // Checking if Web3 has been injected by the browser (Mist/MetaMask) - if (typeof web3 !== 'undefined') { - // Use Mist/MetaMask's provider - web3js = new Web3(web3.currentProvider); - } else { - // Handle the case where the user doesn't have Metamask installed - // Probably show them a message prompting them to install Metamask + '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; - // Now you can start your app & access web3 freely: - startApp() - - }) - - - - "cryptozombies_abi.js": | - var cryptozombiesABI = [ - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - } - ], - "name": "levelUp", - "outputs": [], - "payable": true, - "stateMutability": "payable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_kittyId", - "type": "uint256" - } - ], - "name": "feedOnKitty", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "zombies", - "outputs": [ - { - "name": "name", - "type": "string" - }, - { - "name": "dna", - "type": "uint256" - }, - { - "name": "level", - "type": "uint32" - }, - { - "name": "readyTime", - "type": "uint32" - }, - { - "name": "winCount", - "type": "uint16" - }, - { - "name": "lossCount", - "type": "uint16" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "withdraw", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "getZombiesByOwner", - "outputs": [ - { - "name": "", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "zombieToOwner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_address", - "type": "address" - } - ], - "name": "setKittyContractAddress", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_newDna", - "type": "uint256" - } - ], - "name": "changeDna", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "ownerOf", - "outputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "_balance", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_name", - "type": "string" - } - ], - "name": "createRandomZombie", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getAllZombies", - "outputs": [ - { - "name": "", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "takeOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_newName", - "type": "string" - } - ], - "name": "changeName", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_fee", - "type": "uint256" - } - ], - "name": "setLevelUpFee", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_zombieId", - "type": "uint256" - }, - { - "name": "_targetId", - "type": "uint256" - } - ], - "name": "attack", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": true, - "name": "_approved", - "type": "address" - }, - { - "indexed": false, - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "attackResult", - "type": "bool" - }, - { - "indexed": false, - "name": "winCount", - "type": "uint16" - }, - { - "indexed": false, - "name": "lossCount", - "type": "uint16" - } - ], - "name": "AttackResult", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "zombieId", - "type": "uint256" - }, - { - "indexed": false, - "name": "name", - "type": "string" - }, - { - "indexed": false, - "name": "dna", - "type": "uint256" - } - ], - "name": "NewZombie", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - } - ] - "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 - - - - - -
-
- - - - + '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 和合约交互非常简单直接——一旦你的环境建立起来, `call` 函数和 `send` 事务和普通的网络API并没有多少不同。 +如你所见,通过 Web3.js 和合约交互非常简单直接——一旦你的环境建立起来, `call` 函数和 `send` 交易和普通的网络 API 并没有多少不同。 还有一点东西我们想要讲到——订阅合约事件 @@ -1069,7 +1069,7 @@ material: event NewZombie(uint zombieId, string name, uint dna); ``` -在 Web3.js里, 你可以 **订阅** 一个事件,这样你的 Web3 提供者可以在每次事件发生后触发你的一些代码逻辑: +在 Web3.js 里, 你可以 **订阅** 一个事件,这样你的 Web3 提供者可以在每次事件发生后触发你的一些代码逻辑: ``` cryptoZombies.events.NewZombie() @@ -1108,7 +1108,7 @@ cryptoZombies.events.Transfer({ filter: { _to: userAccount } }) ``` cryptoZombies.getPastEvents("NewZombie", { fromBlock: 0, toBlock: 'latest' }) .then(function(events) { - // events 是可以用来遍历的 `event` 对象 + // events 是可以用来遍历的 `event` 对象 // 这段代码将返回给我们从开始以来创建的僵尸列表 }); ``` @@ -1123,11 +1123,11 @@ cryptoZombies.getPastEvents("NewZombie", { fromBlock: 0, toBlock: 'latest' }) ## Web3.js 事件 和 MetaMask -上面的示例代码是针对 Web3.js 最新版1.0的,此版本使用了 **_WebSockets_** 来订阅事件。 +上面的示例代码是针对 Web3.js 最新版 1.0 的,此版本使用了 **_WebSockets_** 来订阅事件。 但是,MetaMask 尚且不支持最新的事件 API (尽管如此,他们已经在实现这部分功能了, 点击这里 查看进度) -所以现在我们必须使用一个单独 Web3 提供者,它针对事件提供了WebSockets支持。 我们可以用 Infura 来像实例化第二份拷贝: +所以现在我们必须使用一个单独 Web3 提供者,它针对事件提供了 WebSockets 支持。 我们可以用 Infura 来像实例化第二份拷贝: ``` var web3Infura = new Web3(new Web3.providers.WebsocketProvider("wss://mainnet.infura.io/ws")); @@ -1136,16 +1136,16 @@ var czEvents = new web3Infura.eth.Contract(cryptoZombiesABI, cryptoZombiesAddres 然后我们将使用 `czEvents.events.Transfer` 来监听事件,而不再使用 `cryptoZombies.events.Transfer`。我们将继续在课程的其他部分使用 `cryptoZombies.methods`。 -将来,在 MetaMask 升级了 API 支持 Web3.js 后,我们就不用这么做了。但是现在我们还是要这么做,以使用 Web3.js 更好的最新语法来监听事件。 +将来,在 MetaMask 升级了 API 支持 Web3.js 后,我们就不用这么做了。但是现在我们还是要这么做,以使用 Web3.js 更好的最新语法来监听事件。 ## 放在一起 来添加一些代码监听 `Transfer` 事件,并在当前用户获得一个新僵尸的时候为他更新界面。 -我们将需要在 `startApp` 底部添加代码,以保证在添加事件监听器之前 `cryptoZombies` 已经初始化了。 +我们将需要在 `startApp` 底部添加代码,以保证在添加事件监听器之前 `cryptoZombies` 已经初始化了。 -1. 在 `startApp()`底部,为 `cryptoZombies.events.Transfer` 复制粘贴上面的2行事件监听代码块 +1. 在 `startApp()`底部,为 `cryptoZombies.events.Transfer` 复制粘贴上面的 2 行事件监听代码块 -2. 复制监听 `Transfer` 事件的代码块,并用 `_to: userAccount` 过滤。要记得把 `cryptoZombies` 换成 `czEvents` 好在这 里使用 Infura 而不是 MetaMask 来作为提供者。 +2. 复制监听 `Transfer` 事件的代码块,并用 `_to: userAccount` 过滤。要记得把 `cryptoZombies` 换成 `czEvents` 好在这 里使用 Infura 而不是 MetaMask 来作为提供者。 3. 用 `getZombiesByOwner(userAccount).then(displayZombies);` 来更新界面 diff --git a/zh/6/10-wrappingitup.md b/zh/6/10-wrappingitup.md index 7345ab075..7469f2fdc 100644 --- a/zh/6/10-wrappingitup.md +++ b/zh/6/10-wrappingitup.md @@ -3,43 +3,43 @@ title: 放在一起 actions: ['checkAnswer', 'hints'] requireLogin: true material: - saveZombie: false - zombieDeck: - zombie: - lesson: 6 - hideSliders: true - answer: 1 + saveZombie: false + zombieDeck: + zombie: + lesson: 6 + hideSliders: true + answer: 1 --- 恭喜啊少年,你已经成功编写了一个 Web3.js 前端界面来和你的智能合约交互 ## 接下来的步骤 -这节课的内容非常基础。我们想要给你展示和智能合约交互的核心内容,而并不想用太多的时间来教你完整实现。我们也不想花太多时间在HTML/CSS上,因为大部分人都已经知道了。 +这节课的内容非常基础。我们想要给你展示和智能合约交互的核心内容,而并不想用太多的时间来教你完整实现。我们也不想花太多时间在 HTML/CSS 上,因为大部分人都已经知道了。 所以我们把一些实现略去了。这里是你要完整实现所需要完成的基本事项列表: -1. 为 `attack`, `changeName`, `changeDna` 以及 ERC721 函数 `transfer`, `ownerOf`, `balanceOf` 等实现前端函数。这些函数的实现将和我们讲过的 `send`事务的函数非常相似。 +1. 为 `attack`, `changeName`, `changeDna` 以及 ERC721 函数 `transfer`, `ownerOf`, `balanceOf` 等实现前端函数。这些函数的实现将和我们讲过的 `send`交易的函数非常相似。 2. 实现一个“管理界面”,在那里你可以调用 `setKittyContractAddress`, `setLevelUpFee`, 以及 `withdraw`。再次,在前端这块没有什么特别的代码——这些实现之间将非常相似。你应该保证从部署合同时候相同的以太坊地址调用这些函数,因为他们都有`onlyOwner` 修饰符。 3. 在应用里我们还应该实现一些其他的界面: - a. 一个僵尸页面,在那里你可以查看一个特定僵尸的信息并可以分享它的链接。这个页面应该渲染僵尸的外形,展示它的名字,它的所有者(以及用户主页的链接),它的输赢次数,它的战斗记录等等。 +a. 一个僵尸页面,在那里你可以查看一个特定僵尸的信息并可以分享它的链接。这个页面应该渲染僵尸的外形,展示它的名字,它的所有者(以及用户主页的链接),它的输赢次数,它的战斗记录等等。 - b. 一个用户界面,在那里你可以查看用户的僵尸大军并分享它的链接。 - - c. 一个主页,就是用户页面的变体,可以展示当前用户的僵尸大军(正如我们在index.html)里面实现的那样。 +b. 一个用户界面,在那里你可以查看用户的僵尸大军并分享它的链接。 -4. 界面中的一些方法允许用户用 CryptoKitties 喂食僵尸。我们可以给每一个僵尸添加一个按钮,叫做“给我投食”,再给一个输入框让用户输入一个猫咪的ID(或者一个猫咪的网址,比如https://www.cryptokitties.co/kitty/578397),它将触发我们的 `feedOnKitty` 函数。 +c. 一个主页,就是用户页面的变体,可以展示当前用户的僵尸大军(正如我们在 index.html)里面实现的那样。 + +4. 界面中的一些方法允许用户用 CryptoKitties 喂食僵尸。我们可以给每一个僵尸添加一个按钮,叫做“给我投食”,再给一个输入框让用户输入一个猫咪的 ID(或者一个猫咪的网址,比如https://www.cryptokitties.co/kitty/578397),它将触发我们的 `feedOnKitty` 函数。 5. 界面中的一些方法将让用户用来攻击其他用户的僵尸 - 实现这点的一个方法是,当用户浏览其他用户的页面的时候,可以在对方僵尸旁边显示一个按钮,叫做“攻击这头僵尸”。当用户点击的时候,可以弹出一个模块,展示当前用户的僵尸大军并询问用户“你想用哪头僵尸出战?” +实现这点的一个方法是,当用户浏览其他用户的页面的时候,可以在对方僵尸旁边显示一个按钮,叫做“攻击这头僵尸”。当用户点击的时候,可以弹出一个模块,展示当前用户的僵尸大军并询问用户“你想用哪头僵尸出战?” - 在用户的主页,也可以在每个僵尸旁边显示一个按钮,叫做“攻击一个僵尸”。当用户点击的时候,可以弹出一个模块,展示一个搜索框,可以让用户输入僵尸ID或者网址来搜索,或者也可以有一个按钮叫做“随机攻击一头僵尸”,将随机搜索一头僵尸来。 +在用户的主页,也可以在每个僵尸旁边显示一个按钮,叫做“攻击一个僵尸”。当用户点击的时候,可以弹出一个模块,展示一个搜索框,可以让用户输入僵尸 ID 或者网址来搜索,或者也可以有一个按钮叫做“随机攻击一头僵尸”,将随机搜索一头僵尸来。 - 我们也建议你将在冷却期的僵尸用特殊的颜色显示,比如使其变成灰色。这样界面就能告诉用户不能用冷却期的僵尸来进攻。 +我们也建议你将在冷却期的僵尸用特殊的颜色显示,比如使其变成灰色。这样界面就能告诉用户不能用冷却期的僵尸来进攻。 6. 在用户的主页,每一个僵尸也应该有选项可以更改名字、DNA、以及升级(通过付费)。若用户等级不到,无法使用的选项应该标灰。 @@ -47,13 +47,13 @@ material: 8. 也可以为我们的智能合约添加一个包含`indexed` 的用户地址属性的 `Attack` 事件。这样就可以创建实时通知了——我们可以在用户的僵尸遭受攻击的时候弹出一条通知,这样他们可以看到谁在用什么僵尸攻击他们并做出报复。 -9. 我们也许还想实现一些前端缓存层,这样就不用总是为了相同的数据去访问Infura。(在我们当前实现中,`displayZombies` 将在每次页面刷新的时候为每一个僵尸调用 `getZombieDetails`,但是实际中我们将只需要为新加入的僵尸调用这个函数) +9. 我们也许还想实现一些前端缓存层,这样就不用总是为了相同的数据去访问 Infura。(在我们当前实现中,`displayZombies` 将在每次页面刷新的时候为每一个僵尸调用 `getZombieDetails`,但是实际中我们将只需要为新加入的僵尸调用这个函数) 10. 一个实时聊天室,这样你就可以在你击溃别人的僵尸大军的同时嘲讽他们? -因为这将需要大量的前端代码来实现全部的界面(HTML, CSS, JavaScript 以及诸如 React 和 Vue.js 这样的框架)。光实现一个这样的前端界面也许会花费多达10节课,所以我们将这个光荣的任务交给你自己去完成。 +因为这将需要大量的前端代码来实现全部的界面(HTML, CSS, JavaScript 以及诸如 React 和 Vue.js 这样的框架)。光实现一个这样的前端界面也许会花费多达 10 节课,所以我们将这个光荣的任务交给你自己去完成。 -> 注意:尽管智能合约是去中心化的。这个用来和DApp交互的前端界面依然需要放在我们中心化的网络服务器上。不过,有了我们正在内测的Loom Network SDK,很快你就可以在应用自己的DApp链上运行前端界面而不是中心化的网络服务器。这样在以太坊和 Loom DApp 链上,你的整个应用都100%运行在区块链上了。 +> 注意:尽管智能合约是去中心化的。这个用来和 DApp 交互的前端界面依然需要放在我们中心化的网络服务器上。不过,有了我们正在内测的Loom Network SDK,很快你就可以在应用自己的 DApp 链上运行前端界面而不是中心化的网络服务器。这样在以太坊和 Loom DApp 链上,你的整个应用都 100%运行在区块链上了。 ## 总结