關鍵要點
本教程系列的第三部分介紹了使用以太坊構建 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中文網其他相關文章!