关键要点
本教程系列的第三部分介绍了使用以太坊构建 DApp,我们构建并将令牌部署到以太坊测试网络 Rinkeby。在本部分中,我们将开始编写 Story DAO 代码。
我们将使用介绍性文章中列出的条件来指导我们。
合约概述
让我们创建一个新的合约 StoryDao.sol,其框架如下:
pragma solidity ^0.4.24; import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol"; import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol"; contract StoryDao is Ownable { using SafeMath for uint256; mapping(address => bool) whitelist; uint256 public whitelistedNumber = 0; mapping(address => bool) blacklist; event Whitelisted(address addr, bool status); event Blacklisted(address addr, bool status); uint256 public daofee = 100; // 百分之几,即 100 为 1% uint256 public whitelistfee = 10000000000000000; // 以 Wei 为单位,这是 0.01 以太币 event SubmissionCommissionChanged(uint256 newFee); event WhitelistFeeChanged(uint256 newFee); uint256 public durationDays = 21; // 故事章节持续时间(天) uint256 public durationSubmissions = 1000; // 故事章节持续时间(条目) function changedaofee(uint256 _fee) onlyOwner external { require(_fee <= 1000); // 限制最大费用为 10% daofee = _fee; emit SubmissionCommissionChanged(_fee); } function changewhitelistfee(uint256 _fee) onlyOwner external { require(_fee > 0); // 确保费用大于 0 whitelistfee = _fee; emit WhitelistFeeChanged(_fee); } function changeDurationDays(uint256 _days) onlyOwner external { require(_days >= 1); durationDays = _days; } function changeDurationSubmissions(uint256 _subs) onlyOwner external { require(_subs > 99); durationSubmissions = _subs; } }
我们导入 SafeMath 以再次进行安全计算,但这次我们还使用 Zeppelin 的 Ownable 合约,它允许某人“拥有”故事并执行某些仅限管理员的功能。简单地说,我们的 StoryDao 是 Ownable 就足够了;随意检查合约以了解其工作原理。
我们还使用此合约中的 onlyOwner 修饰符。函数修饰符基本上是函数的扩展、插件。onlyOwner 修饰符如下所示:
modifier onlyOwner() { require(msg.sender == owner); _; }
当 onlyOwner 添加到函数时,该函数的主体将粘贴到 _; 部分所在的位置,并且之前的部分将首先执行。因此,通过使用此修饰符,该函数会自动检查消息发送者是否是合约的所有者,然后如果属实则照常继续。如果不是,则会崩溃。
通过在更改 Story DAO 的费用和其他参数的函数上使用 onlyOwner 修饰符,我们确保只有管理员才能进行这些更改。
让我们测试初始函数。
如果不存在,请创建文件夹 test。然后在其中创建文件 TestStoryDao.sol 和 TestStoryDao.js。由于 Truffle 中没有本地方法来测试异常,因此还使用以下内容创建 helpers/expectThrow.js:
export default async promise => { try { await promise; } catch (error) { const invalidOpcode = error.message.search('invalid opcode') >= 0; const outOfGas = error.message.search('out of gas') >= 0; const revert = error.message.search('revert') >= 0; assert( invalidOpcode || outOfGas || revert, 'Expected throw, got \'' + error + '\' instead', ); return; } assert.fail('Expected throw not received'); };
注意:Solidity 测试通常用于测试低级、基于合约的函数,即智能合约的内部结构。JS 测试通常用于测试合约是否可以从外部正确交互,这是我们的最终用户将要执行的操作。
在 TestStoryDao.sol 中,放入以下内容:
pragma solidity ^0.4.24; import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol"; import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol"; contract StoryDao is Ownable { using SafeMath for uint256; mapping(address => bool) whitelist; uint256 public whitelistedNumber = 0; mapping(address => bool) blacklist; event Whitelisted(address addr, bool status); event Blacklisted(address addr, bool status); uint256 public daofee = 100; // 百分之几,即 100 为 1% uint256 public whitelistfee = 10000000000000000; // 以 Wei 为单位,这是 0.01 以太币 event SubmissionCommissionChanged(uint256 newFee); event WhitelistFeeChanged(uint256 newFee); uint256 public durationDays = 21; // 故事章节持续时间(天) uint256 public durationSubmissions = 1000; // 故事章节持续时间(条目) function changedaofee(uint256 _fee) onlyOwner external { require(_fee <= 1000); // 限制最大费用为 10% daofee = _fee; emit SubmissionCommissionChanged(_fee); } function changewhitelistfee(uint256 _fee) onlyOwner external { require(_fee > 0); // 确保费用大于 0 whitelistfee = _fee; emit WhitelistFeeChanged(_fee); } function changeDurationDays(uint256 _days) onlyOwner external { require(_days >= 1); durationDays = _days; } function changeDurationSubmissions(uint256 _subs) onlyOwner external { require(_subs > 99); durationSubmissions = _subs; } }
这将检查 StoryDao 合约是否以正确的费用和持续时间数字正确部署。第一行确保它通过从已部署地址列表中读取它来部署,并且最后一节进行一些断言——检查声明是真还是假。在我们的例子中,我们将数字与已部署合约的初始值进行比较。每当它是“真”时,Assert.equals 部分都会发出一个事件,说明“真”,这就是 Truffle 在测试时正在监听的。
在 TestStoryDao.js 中,放入以下内容:
modifier onlyOwner() { require(msg.sender == owner); _; }
为了使我们的测试能够成功运行,我们还需要告诉 Truffle 我们希望部署 StoryDao——因为它不会为我们这样做。因此,让我们在迁移中使用几乎与我们之前编写的迁移相同的 content 创建 3_deploy_storydao.js:
export default async promise => { try { await promise; } catch (error) { const invalidOpcode = error.message.search('invalid opcode') >= 0; const outOfGas = error.message.search('out of gas') >= 0; const revert = error.message.search('revert') >= 0; assert( invalidOpcode || outOfGas || revert, 'Expected throw, got \'' + error + '\' instead', ); return; } assert.fail('Expected throw not received'); };
此时,我们还应该更新(或创建,如果不存在)项目文件夹根目录中的 package.json 文件,其中包含我们到目前为止需要的依赖项,以及将来可能需要的依赖项:
pragma solidity ^0.4.24; import "truffle/Assert.sol"; import "truffle/DeployedAddresses.sol"; import "../contracts/StoryDao.sol"; contract TestStoryDao { function testDeploymentIsFine() public { StoryDao sd = StoryDao(DeployedAddresses.StoryDao()); uint256 daofee = 100; // 百分之几,即 100 为 1% uint256 whitelistfee = 10000000000000000; // 以 Wei 为单位,这是 0.01 以太币 uint256 durationDays = 21; // 故事章节持续时间(天) uint256 durationSubmissions = 1000; // 故事章节持续时间(条目) Assert.equal(sd.daofee(), daofee, "初始 DAO 费用应为 100"); Assert.equal(sd.whitelistfee(), whitelistfee, "初始白名单费用应为 0.01 以太币"); Assert.equal(sd.durationDays(), durationDays, "初始天数持续时间应设置为 3 周"); Assert.equal(sd.durationSubmissions(), durationSubmissions, "初始提交持续时间应设置为 1000 个条目"); } }
以及包含以下内容的 .babelrc 文件:
import expectThrow from './helpers/expectThrow'; const StoryDao = artifacts.require("StoryDao"); contract('StoryDao Test', async (accounts) => { it("should make sure environment is OK by checking that the first 3 accounts have over 20 eth", async () =>{ assert.equal(web3.eth.getBalance(accounts[0]).toNumber() > 2e+19, true, "Account 0 has more than 20 eth"); assert.equal(web3.eth.getBalance(accounts[1]).toNumber() > 2e+19, true, "Account 1 has more than 20 eth"); assert.equal(web3.eth.getBalance(accounts[2]).toNumber() > 2e+19, true, "Account 2 has more than 20 eth"); }); it("should make the deployer the owner", async () => { let instance = await StoryDao.deployed(); assert.equal(await instance.owner(), accounts[0]); }); it("should let owner change fee and duration", async () => { let instance = await StoryDao.deployed(); let newDaoFee = 50; let newWhitelistFee = 1e+10; // 1 ether let newDayDuration = 42; let newSubsDuration = 1500; instance.changedaofee(newDaoFee, {from: accounts[0]}); instance.changewhitelistfee(newWhitelistFee, {from: accounts[0]}); instance.changeDurationDays(newDayDuration, {from: accounts[0]}); instance.changeDurationSubmissions(newSubsDuration, {from: accounts[0]}); assert.equal(await instance.daofee(), newDaoFee); assert.equal(await instance.whitelistfee(), newWhitelistFee); assert.equal(await instance.durationDays(), newDayDuration); assert.equal(await instance.durationSubmissions(), newSubsDuration); }); it("should forbid non-owners from changing fee and duration", async () => { let instance = await StoryDao.deployed(); let newDaoFee = 50; let newWhitelistFee = 1e+10; // 1 ether let newDayDuration = 42; let newSubsDuration = 1500; await expectThrow(instance.changedaofee(newDaoFee, {from: accounts[1]})); await expectThrow(instance.changewhitelistfee(newWhitelistFee, {from: accounts[1]})); await expectThrow(instance.changeDurationDays(newDayDuration, {from: accounts[1]})); await expectThrow(instance.changeDurationSubmissions(newSubsDuration, {from: accounts[1]})); }); it("should make sure the owner can only change fees and duration to valid values", async () =>{ let instance = await StoryDao.deployed(); let invalidDaoFee = 20000; let invalidDayDuration = 0; let invalidSubsDuration = 98; await expectThrow(instance.changedaofee(invalidDaoFee, {from: accounts[0]})); await expectThrow(instance.changeDurationDays(invalidDayDuration, {from: accounts[0]})); await expectThrow(instance.changeDurationSubmissions(invalidSubsDuration, {from: accounts[0]})); }) });
我们还需要在 Truffle 配置中要求 Babel,以便它知道在编译测试时应该使用它。
注意:Babel 是 NodeJS 的一个附加组件,它允许我们在当前一代 NodeJS 中使用下一代 JavaScript,因此我们可以编写 import 等内容。如果您不理解这一点,只需忽略它并逐字粘贴即可。安装后,您可能再也不用处理这个问题了。
var Migrations = artifacts.require("./Migrations.sol"); var StoryDao = artifacts.require("./StoryDao.sol"); module.exports = function(deployer, network, accounts) { if (network == "development") { deployer.deploy(StoryDao, {from: accounts[0]}); } else { deployer.deploy(StoryDao); } };
现在,最后运行 truffle test。输出应该类似于此:
有关测试的更多信息,请参阅本教程,我们专门准备了本教程来涵盖智能合约的测试。
在本课程的后续部分中,我们将跳过测试,因为逐字输入它们会使教程过长,但请参考项目的最终源代码以检查所有测试。我们刚刚完成的过程已经为测试设置了环境,因此您可以编写测试而无需进一步设置。
白名单
现在让我们构建白名单机制,它允许用户参与构建故事。将以下函数框架添加到 StoryDao.sol:
{ "name": "storydao", "devDependencies": { "babel-preset-es2015": "^6.18.0", "babel-preset-stage-2": "^6.24.1", "babel-preset-stage-3": "^6.17.0", "babel-polyfill": "^6.26.0", "babel-register": "^6.23.0", "dotenv": "^6.0.0", "truffle": "^4.1.12", "openzeppelin-solidity": "^1.10.0", "openzeppelin-solidity-metadata": "^1.2.0", "openzeppelin-zos": "", "truffle-wallet-provider": "^0.0.5", "ethereumjs-wallet": "^0.6.0", "web3": "^1.0.0-beta.34", "truffle-assertions": "^0.3.1" } }
未命名的函数 function() 被称为回退函数,当向此合约发送资金但没有特定指令(即没有专门调用另一个函数)时,就会调用此函数。这允许人们通过仅向 DAO 发送以太币来加入 StoryDao,并根据他们是否已列入白名单来立即列入白名单或购买代币。
whitelistSender 函数用于列入白名单,可以直接调用,但我们将确保回退函数在收到一些以太币后自动调用它,前提是发送者尚未列入白名单。whitelistAddress 函数被声明为 public,因为它也应该可以从其他合约调用,而回退函数是 external,因为资金只会从外部地址发送到此地址。调用此合约的合约可以轻松直接调用所需函数。
让我们首先处理回退函数。
pragma solidity ^0.4.24; import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol"; import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol"; contract StoryDao is Ownable { using SafeMath for uint256; mapping(address => bool) whitelist; uint256 public whitelistedNumber = 0; mapping(address => bool) blacklist; event Whitelisted(address addr, bool status); event Blacklisted(address addr, bool status); uint256 public daofee = 100; // 百分之几,即 100 为 1% uint256 public whitelistfee = 10000000000000000; // 以 Wei 为单位,这是 0.01 以太币 event SubmissionCommissionChanged(uint256 newFee); event WhitelistFeeChanged(uint256 newFee); uint256 public durationDays = 21; // 故事章节持续时间(天) uint256 public durationSubmissions = 1000; // 故事章节持续时间(条目) function changedaofee(uint256 _fee) onlyOwner external { require(_fee <= 1000); // 限制最大费用为 10% daofee = _fee; emit SubmissionCommissionChanged(_fee); } function changewhitelistfee(uint256 _fee) onlyOwner external { require(_fee > 0); // 确保费用大于 0 whitelistfee = _fee; emit WhitelistFeeChanged(_fee); } function changeDurationDays(uint256 _days) onlyOwner external { require(_days >= 1); durationDays = _days; } function changeDurationSubmissions(uint256 _subs) onlyOwner external { require(_subs > 99); durationSubmissions = _subs; } }
我们检查发送者是否尚未列入白名单,并将调用委托给 whitelistAddress 函数。请注意,我们注释掉了 buyTokens 函数,因为我们还没有它。
接下来,让我们处理白名单。
modifier onlyOwner() { require(msg.sender == owner); _; }
请注意,此函数接受地址作为参数,而不是从消息(从事务)中提取它。这还有一个额外的好处,即如果有人负担不起加入 DAO 的费用,例如,人们可以将其他人列入白名单。
我们从一些健全性检查开始函数:发送者不得已列入白名单或列入黑名单(被禁止),并且必须已发送足够的费用来支付费用。如果这些条件令人满意,则将地址添加到白名单,发出 Whitelisted 事件,最后,如果发送的以太币数量大于支付白名单费用所需的以太币数量,则剩余部分将用于购买代币。
注意:我们使用 sub 而不是 - 来减去,因为这是用于安全计算的 SafeMath 函数。
只要用户向 StoryDao 合约发送 0.01 以太币或更多以太币,他们现在就可以将自己或其他人列入白名单。
结论
在本教程中,我们构建了 DAO 的初始部分,但还有很多工作要做。敬请期待:在下一部分中,我们将处理向故事中添加内容!
在开始构建以太坊 DApp 之前,您需要对区块链技术、以太坊和智能合约有基本的了解。您还应该熟悉 JavaScript 和 Solidity 等编程语言,Solidity 用于编写以太坊上的智能合约。此外,您需要安装 Node.js、Truffle、Ganache 和 MetaMask 等工具,这些工具对于开发和测试 DApp 至关重要。
白名单是 DApp 中用于限制对应用程序的某些功能或区域的访问的安全措施。它涉及创建一个批准的地址列表,这些地址允许与 DApp 交互。只有从这些地址发起的交易才会被接受,而其他交易将被拒绝。这有助于防止未经授权的访问和恶意活动。
智能合约是自执行合约,其协议条款直接写入代码中。它们在 DApp 中发挥着至关重要的作用,因为它们可以自动化区块链上业务逻辑的执行。它们确保透明度、安全性和不可变性,因为一旦部署,它们就无法更改或篡改。
测试是 DApp 开发中至关重要的一部分,用于确保其功能和安全性。您可以使用 Truffle 和 Ganache 等工具进行测试。Truffle 为以太坊提供开发环境、测试框架和资产管道,而 Ganache 允许您创建用于测试的私有以太坊区块链。
DAO 代表去中心化自治组织。它是一种由编码为计算机程序的规则表示的组织类型,该程序是透明的,由组织成员控制,不受中央政府的影响。DAO 的财务交易和规则保存在区块链上,这使其成为一种 DApp。
确保 DApp 的安全性涉及多种实践。这包括编写安全的智能合约、彻底测试、进行安全审计以及保持软件和依赖项的最新状态。遵循安全编码的最佳实践并随时了解区块链领域最新的安全漏洞和威胁也很重要。
MetaMask 是一个浏览器扩展,允许您直接从浏览器与以太坊区块链和 DApp 交互。它还可以作为以太坊钱包来管理您的以太币和 ERC-20 代币。它在 DApp 开发中很重要,因为它为用户提供了一个用户友好的界面,以便用户无需运行完整的以太坊节点即可与您的 DApp 交互。
一旦您开发并测试了您的 DApp,您就可以将其部署到以太坊主网或测试网上。这涉及编译您的智能合约,将它们部署到区块链,并将您的 DApp 连接到这些合约。您可以使用 Truffle 和 Infura 等工具来完成此过程。
DApp 开发面临着一些挑战。这包括处理以太坊网络的可扩展性问题、确保 DApp 的安全性、管理交易的波动性燃气价格以及提供用户友好的界面。它还需要随时了解快速发展的区块链技术和法规。
部署后更新 DApp 可能具有挑战性,因为区块链上的智能合约是不可变的。但是,您可以通过将数据和逻辑分离到不同的合约中或使用委托调用来升级合约来设计可升级的合约。在 DApp 的设计阶段规划升级和更改非常重要。
以上是建造以太坊dapps:白名单和测试一个故事dao的详细内容。更多信息请关注PHP中文网其他相关文章!