摘要:接上篇文章,這里繼續(xù)學(xué)習(xí)高級(jí)理論。實(shí)戰(zhàn)演練我們來(lái)寫一個(gè)返回某玩家的整個(gè)僵尸軍團(tuán)的函數(shù)。但這樣每做一筆交易,都會(huì)改變僵尸軍團(tuán)的秩序。在這里開始五可支付截至目前,我們只接觸到很少的函數(shù)修飾符。
接上篇文章,這里繼續(xù)學(xué)習(xí)Solidity高級(jí)理論。一、深入函數(shù)修飾符
接下來(lái),我們將添加一些輔助方法。我們?yōu)槟鷦?chuàng)建了一個(gè)名為 zombiehelper.sol 的新文件,并且將 zombiefeeding.sol 導(dǎo)入其中,這讓我們的代碼更整潔。
我們打算讓僵尸在達(dá)到一定水平后,獲得特殊能力。但是達(dá)到這個(gè)小目標(biāo),我們還需要學(xué)一學(xué)什么是“函數(shù)修飾符”。
帶參的函數(shù)修飾符之前我們已經(jīng)讀過一個(gè)簡(jiǎn)單的函數(shù)修飾符了:onlyOwner。函數(shù)修飾符也可以帶參數(shù)。例如:
// 存儲(chǔ)用戶年齡的映射 mapping (uint => uint) public age; // 限定用戶年齡的修飾符 modifier olderThan(uint _age, uint _userId) { require(age[_userId] >= _age); _; } // 必須年滿16周歲才允許開車 (至少在美國(guó)是這樣的). // 我們可以用如下參數(shù)調(diào)用`olderThan` 修飾符: function driveCar(uint _userId) public olderThan(16, _userId) { // 其余的程序邏輯 }
看到了吧, olderThan 修飾符可以像函數(shù)一樣接收參數(shù),是“宿主”函數(shù) driveCar 把參數(shù)傳遞給它的修飾符的。
來(lái),我們自己生產(chǎn)一個(gè)修飾符,通過傳入的level參數(shù)來(lái)限制僵尸使用某些特殊功能。
實(shí)戰(zhàn)演練1、在ZombieHelper 中,創(chuàng)建一個(gè)名為 aboveLevel 的modifier,它接收2個(gè)參數(shù), _level (uint類型) 以及 _zombieId (uint類型)。
2、運(yùn)用函數(shù)邏輯確保僵尸 zombies[_zombieId].level 大于或等于 _level。
3、記住,修飾符的最后一行為 _;,表示修飾符調(diào)用結(jié)束后返回,并執(zhí)行調(diào)用函數(shù)余下的部分。
pragma solidity ^0.4.19; import "./zombiefeeding.sol"; contract ZombieHelper is ZombieFeeding { // 在這里開始 modifier aboveLevel(uint _level, uint _zombieId) { require(zombies[_zombieId].level >= _level); _; } }函數(shù)修飾符應(yīng)用
現(xiàn)在讓我們?cè)O(shè)計(jì)一些使用 aboveLevel 修飾符的函數(shù)。
作為游戲,您得有一些措施激勵(lì)玩家們?nèi)ド?jí)他們的僵尸:
2級(jí)以上的僵尸,玩家可給他們改名。
20級(jí)以上的僵尸,玩家能給他們定制的 DNA。
是實(shí)現(xiàn)這些功能的時(shí)候了。以下是上一課的示例代碼,供參考:
// 存儲(chǔ)用戶年齡的映射 mapping (uint => uint) public age; // 限定用戶年齡的修飾符 modifier olderThan(uint _age, uint _userId) { require (age[_userId] >= _age); _; } // 必須年滿16周歲才允許開車 (至少在美國(guó)是這樣的). // 我們可以用如下參數(shù)調(diào)用`olderThan` 修飾符: function driveCar(uint _userId) public olderThan(16, _userId) { // 其余的程序邏輯 }實(shí)戰(zhàn)演練
1、創(chuàng)建一個(gè)名為 changeName 的函數(shù)。它接收2個(gè)參數(shù):_zombieId(uint類型)以及 _newName(string類型),可見性為 external。它帶有一個(gè) aboveLevel 修飾符,調(diào)用的時(shí)候通過 _level 參數(shù)傳入2, 當(dāng)然,別忘了同時(shí)傳 _zombieId 參數(shù)。
2、在這個(gè)函數(shù)中,首先我們用 require 語(yǔ)句,驗(yàn)證 msg.sender 是否就是 zombieToOwner [_zombieId]。
3、然后函數(shù)將 zombies[_zombieId] .name 設(shè)置為 _newName。
4、在 changeName 下創(chuàng)建另一個(gè)名為 changeDna 的函數(shù)。它的定義和內(nèi)容幾乎和 changeName 相同,不過它第二個(gè)參數(shù)是 _newDna(uint類型),在修飾符 aboveLevel 的 _level 參數(shù)中傳遞 20 。現(xiàn)在,他可以把僵尸的 dna 設(shè)置為 _newDna 了。
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; } }二、利用view節(jié)省Gas
現(xiàn)在需要添加的一個(gè)功能是:我們的 DApp 需要一個(gè)方法來(lái)查看某玩家的整個(gè)僵尸軍團(tuán) - 我們稱之為 getZombiesByOwner。
實(shí)現(xiàn)這個(gè)功能只需從區(qū)塊鏈中讀取數(shù)據(jù),所以它可以是一個(gè) view 函數(shù)。這讓我們不得不回顧一下“gas優(yōu)化”這個(gè)重要話題。
“view” 函數(shù)不花 “gas”當(dāng)玩家從外部調(diào)用一個(gè)view函數(shù),是不需要支付一分 gas 的。
這是因?yàn)?view 函數(shù)不會(huì)真正改變區(qū)塊鏈上的任何數(shù)據(jù) - 它們只是讀取。因此用 view 標(biāo)記一個(gè)函數(shù),意味著告訴 web3.js,運(yùn)行這個(gè)函數(shù)只需要查詢你的本地以太坊節(jié)點(diǎn),而不需要在區(qū)塊鏈上創(chuàng)建一個(gè)事務(wù)(事務(wù)需要運(yùn)行在每個(gè)節(jié)點(diǎn)上,因此花費(fèi) gas)。
稍后我們將介紹如何在自己的節(jié)點(diǎn)上設(shè)置 web3.js。但現(xiàn)在,你關(guān)鍵是要記住,在所能只讀的函數(shù)上標(biāo)記上表示“只讀”的external view 聲明,就能為你的玩家減少在 DApp 中 gas 用量。
注意:如果一個(gè) view 函數(shù)在另一個(gè)函數(shù)的內(nèi)部被調(diào)用,而調(diào)用函數(shù)與 view 函數(shù)的不屬于同一個(gè)合約,也會(huì)產(chǎn)生調(diào)用成本。這是因?yàn)槿绻髡{(diào)函數(shù)在以太坊創(chuàng)建了一個(gè)事務(wù),它仍然需要逐個(gè)節(jié)點(diǎn)去驗(yàn)證。所以標(biāo)記為 view 的函數(shù)只有在外部調(diào)用時(shí)才是免費(fèi)的。實(shí)戰(zhàn)演練
我們來(lái)寫一個(gè)”返回某玩家的整個(gè)僵尸軍團(tuán)“的函數(shù)。當(dāng)我們從 web3.js 中調(diào)用它,即可顯示某一玩家的個(gè)人資料頁(yè)。
這個(gè)函數(shù)的邏輯有點(diǎn)復(fù)雜,我們需要好幾個(gè)章節(jié)來(lái)描述它的實(shí)現(xiàn)。
1、創(chuàng)建一個(gè)名為 getZombiesByOwner 的新函數(shù)。它有一個(gè)名為 _owner 的 address 類型的參數(shù)。
2、將其申明為 external view 函數(shù),這樣當(dāng)玩家從 web3.js 中調(diào)用它時(shí),不需要花費(fèi)任何 gas。
3、函數(shù)需要返回一個(gè)uint [](uint數(shù)組)。
先這么聲明著,我們將在下一章中填充函數(shù)體。
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; } // 在這里創(chuàng)建你的函數(shù) function getZombiesByOwner (address _owner) external view returns (uint []) { } }三、存儲(chǔ)非常昂貴
Solidity 使用 storage(存儲(chǔ))是相當(dāng)昂貴的,”寫入“操作尤其貴。
這是因?yàn)椋瑹o(wú)論是寫入還是更改一段數(shù)據(jù), 這都將永久性地寫入?yún)^(qū)塊鏈?!庇谰眯浴鞍?!需要在全球數(shù)千個(gè)節(jié)點(diǎn)的硬盤上存入這些數(shù)據(jù),隨著區(qū)塊鏈的增長(zhǎng),拷貝份數(shù)更多,存儲(chǔ)量也就越大。這是需要成本的!
為了降低成本,不到萬(wàn)不得已,避免將數(shù)據(jù)寫入存儲(chǔ)。這也會(huì)導(dǎo)致效率低下的編程邏輯 - 比如每次調(diào)用一個(gè)函數(shù),都需要在 memory(內(nèi)存) 中重建一個(gè)數(shù)組,而不是簡(jiǎn)單地將上次計(jì)算的數(shù)組給存儲(chǔ)下來(lái)以便快速查找。
在大多數(shù)編程語(yǔ)言中,遍歷大數(shù)據(jù)集合都是昂貴的。但是在 Solidity 中,使用一個(gè)標(biāo)記了external view的函數(shù),遍歷比 storage 要便宜太多,因?yàn)?view 函數(shù)不會(huì)產(chǎn)生任何花銷。 (gas可是真金白銀啊?。?/p>
我們將在下一章討論 for 循環(huán),現(xiàn)在我們來(lái)看一下看如何如何在內(nèi)存中聲明數(shù)組。
在內(nèi)存中聲明數(shù)組在數(shù)組后面加上 memory 關(guān)鍵字, 表明這個(gè)數(shù)組是僅僅在內(nèi)存中創(chuàng)建,不需要寫入外部存儲(chǔ),并且在函數(shù)調(diào)用結(jié)束時(shí)它就解散了。與在程序結(jié)束時(shí)把數(shù)據(jù)保存進(jìn) storage 的做法相比,內(nèi)存運(yùn)算可以大大節(jié)省gas開銷 -- 把這數(shù)組放在view里用,完全不用花錢。
以下是申明一個(gè)內(nèi)存數(shù)組的例子:
function getArray() external pure returns(uint[]) { // 初始化一個(gè)長(zhǎng)度為3的內(nèi)存數(shù)組 uint[] memory values = new uint[](3); // 賦值 values.push(1); values.push(2); values.push(3); // 返回?cái)?shù)組 return values; }
這個(gè)小例子展示了一些語(yǔ)法規(guī)則,下一章中,我們將通過一個(gè)實(shí)際用例,展示它和 for 循環(huán)結(jié)合的做法。
注意:內(nèi)存數(shù)組 必須 用長(zhǎng)度參數(shù)(在本例中為3)創(chuàng)建。目前不支持 array.push()之類的方法調(diào)整數(shù)組大小,在未來(lái)的版本可能會(huì)支持長(zhǎng)度修改。實(shí)戰(zhàn)演練
我們要要?jiǎng)?chuàng)建一個(gè)名為 getZombiesByOwner 的函數(shù),它以uint []數(shù)組的形式返回某一用戶所擁有的所有僵尸。
1、聲明一個(gè)名為result的uint [] memory (內(nèi)存變量數(shù)組)
2、將其設(shè)置為一個(gè)新的 uint 類型數(shù)組。數(shù)組的長(zhǎng)度為該 _owner 所擁有的僵尸數(shù)量,這可通過調(diào)用 ownerZombieCount [_ owner] 來(lái)獲取。
3、函數(shù)結(jié)束,返回 result 。目前它只是個(gè)空數(shù)列,我們到下一章去實(shí)現(xiàn)它。
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; } function getZombiesByOwner(address _owner) external view returns(uint[]) { // 在這里開始 uint[] memory result = new uint[](ownerZombieCount[_ owner]); return result; } }四、For循環(huán)
在之前的博文中,我們提到過,函數(shù)中使用的數(shù)組是運(yùn)行時(shí)在內(nèi)存中通過 for 循環(huán)實(shí)時(shí)構(gòu)建,而不是預(yù)先建立在存儲(chǔ)中的。
為什么要這樣做呢?
為了實(shí)現(xiàn) getZombiesByOwner 函數(shù),一種“無(wú)腦式”的解決方案是在 ZombieFactory 中存入”主人“和”僵尸軍團(tuán)“的映射。
mapping (address => uint[]) public ownerToZombies
然后我們每次創(chuàng)建新僵尸時(shí),執(zhí)行 ownerToZombies[owner].push(zombieId) 將其添加到主人的僵尸數(shù)組中。而 getZombiesByOwner 函數(shù)也非常簡(jiǎn)單:
function getZombiesByOwner(address _owner) external view returns (uint[]) { return ownerToZombies[_owner]; }這個(gè)做法有問題
做法倒是簡(jiǎn)單。可是如果我們需要一個(gè)函數(shù)來(lái)把一頭僵尸轉(zhuǎn)移到另一個(gè)主人名下(我們一定會(huì)在后面的課程中實(shí)現(xiàn)的),又會(huì)發(fā)生什么?
這個(gè)“換主”函數(shù)要做到:
1.將僵尸push到新主人的 ownerToZombies 數(shù)組中,
2.從舊主的 ownerToZombies 數(shù)組中移除僵尸,
3.將舊主僵尸數(shù)組中“換主僵尸”之后的的每頭僵尸都往前挪一位,把挪走“換主僵尸”后留下的“空槽”填上,
4.將數(shù)組長(zhǎng)度減1。
但是第三步實(shí)在是太貴了!因?yàn)槊颗矂?dòng)一頭僵尸,我們都要執(zhí)行一次寫操作。如果一個(gè)主人有20頭僵尸,而第一頭被挪走了,那為了保持?jǐn)?shù)組的順序,我們得做19個(gè)寫操作。
由于寫入存儲(chǔ)是 Solidity 中最費(fèi) gas 的操作之一,使得換主函數(shù)的每次調(diào)用都非常昂貴。更糟糕的是,每次調(diào)用的時(shí)候花費(fèi)的 gas 都不同!具體還取決于用戶在原主軍團(tuán)中的僵尸頭數(shù),以及移走的僵尸所在的位置。以至于用戶都不知道應(yīng)該支付多少 gas。
注意:當(dāng)然,我們也可以把數(shù)組中最后一個(gè)僵尸往前挪來(lái)填補(bǔ)空槽,并將數(shù)組長(zhǎng)度減少一。但這樣每做一筆交易,都會(huì)改變僵尸軍團(tuán)的秩序。
由于從外部調(diào)用一個(gè) view 函數(shù)是免費(fèi)的,我們也可以在 getZombiesByOwner 函數(shù)中用一個(gè)for循環(huán)遍歷整個(gè)僵尸數(shù)組,把屬于某個(gè)主人的僵尸挑出來(lái)構(gòu)建出僵尸數(shù)組。那么我們的 transfer 函數(shù)將會(huì)便宜得多,因?yàn)槲覀儾恍枰矂?dòng)存儲(chǔ)里的僵尸數(shù)組重新排序,總體上這個(gè)方法會(huì)更便宜,雖然有點(diǎn)反直覺。
使用for循環(huán)for循環(huán)的語(yǔ)法在 Solidity 和 JavaScript 中類似。
來(lái)看一個(gè)創(chuàng)建偶數(shù)數(shù)組的例子:
function getEvens() pure external returns(uint[]) { uint[] memory evens = new uint[](5); // 在新數(shù)組中記錄序列號(hào) uint counter = 0; // 在循環(huán)從1迭代到10: for (uint i = 1; i <= 10; i++) { // 如果 `i` 是偶數(shù)... if (i % 2 == 0) { // 把它加入偶數(shù)數(shù)組 evens[counter] = i; //索引加一, 指向下一個(gè)空的‘even’ counter++; } } return evens; }
這個(gè)函數(shù)將返回一個(gè)形為 [2,4,6,8,10] 的數(shù)組。
實(shí)戰(zhàn)演練我們回到 getZombiesByOwner 函數(shù), 通過一條 for 循環(huán)來(lái)遍歷 DApp 中所有的僵尸, 將給定的‘用戶id"與每頭僵尸的‘主人’進(jìn)行比較,并在函數(shù)返回之前將它們推送到我們的result 數(shù)組中。
1.聲明一個(gè)變量 counter,屬性為 uint,設(shè)其值為 0 。我們用這個(gè)變量作為 result 數(shù)組的索引。
2.聲明一個(gè) for 循環(huán), 從 uint i = 0 到 i 3.在每一輪 for 循環(huán)中,用一個(gè) if 語(yǔ)句來(lái)檢查 zombieToOwner [i] 是否等于 _owner。這會(huì)比較兩個(gè)地址是否匹配。 4.在 if 語(yǔ)句中: 通過將 result [counter] 設(shè)置為 i,將僵尸ID添加到 result 數(shù)組中。 將counter加1(參見上面的for循環(huán)示例)。 就是這樣 - 這個(gè)函數(shù)能返回 _owner 所擁有的僵尸數(shù)組,不花一分錢 gas。 截至目前,我們只接觸到很少的 函數(shù)修飾符。 要記住所有的東西很難,所以我們來(lái)個(gè)概覽: 1、我們有決定函數(shù)何時(shí)和被誰(shuí)調(diào)用的可見性修飾符: private 意味著它只能被合約內(nèi)部調(diào)用; internal 就像 private 但是也能被繼承的合約調(diào)用; external 只能從合約外部調(diào)用;最后 public 可以在任何地方調(diào)用,不管是內(nèi)部還是外部。 2、我們也有狀態(tài)修飾符, 告訴我們函數(shù)如何和區(qū)塊鏈交互: view 告訴我們運(yùn)行這個(gè)函數(shù)不會(huì)更改和保存任何數(shù)據(jù); pure 告訴我們這個(gè)函數(shù)不但不會(huì)往區(qū)塊鏈寫數(shù)據(jù),它甚至不從區(qū)塊鏈讀取數(shù)據(jù)。這兩種在被從合約外部調(diào)用的時(shí)候都不花費(fèi)任何gas(但是它們?cè)诒粌?nèi)部其他函數(shù)調(diào)用的時(shí)候?qū)?huì)耗費(fèi)gas)。 3、然后我們有了自定義的 modifiers,例如在第三課學(xué)習(xí)的: onlyOwner 和 aboveLevel。 對(duì)于這些修飾符我們可以自定義其對(duì)函數(shù)的約束邏輯。 這些修飾符可以同時(shí)作用于一個(gè)函數(shù)定義上: 在這一章,我們來(lái)學(xué)習(xí)一個(gè)新的修飾符 payable. payable 方法是讓 Solidity 和以太坊變得如此酷的一部分 —— 它們是一種可以接收以太的特殊函數(shù)。 先放一下。當(dāng)你在調(diào)用一個(gè)普通網(wǎng)站服務(wù)器上的API函數(shù)的時(shí)候,你無(wú)法用你的函數(shù)傳送美元——你也不能傳送比特幣。 但是在以太坊中, 因?yàn)殄X (以太), 數(shù)據(jù) (事務(wù)負(fù)載), 以及合約代碼本身都存在于以太坊。你可以在同時(shí)調(diào)用函數(shù) 并付錢給另外一個(gè)合約。 這就允許出現(xiàn)很多有趣的邏輯, 比如向一個(gè)合約要求支付一定的錢來(lái)運(yùn)行一個(gè)函數(shù)。 在這里,msg.value 是一種可以查看向合約發(fā)送了多少以太的方法,另外 ether 是一個(gè)內(nèi)建單元。 這里發(fā)生的事是,一些人會(huì)從 web3.js 調(diào)用這個(gè)函數(shù) (從DApp的前端), 像這樣 : 注意這個(gè) value 字段, JavaScript 調(diào)用來(lái)指定發(fā)送多少(0.001)以太。如果把事務(wù)想象成一個(gè)信封,你發(fā)送到函數(shù)的參數(shù)就是信的內(nèi)容。 添加一個(gè) value 很像在信封里面放錢 —— 信件內(nèi)容和錢同時(shí)發(fā)送給了接收者。 我們來(lái)在僵尸游戲里面創(chuàng)建一個(gè)payable 函數(shù)。 假定在我們的游戲中,玩家可以通過支付ETH來(lái)升級(jí)他們的僵尸。ETH將存儲(chǔ)在你擁有的合約中 —— 一個(gè)簡(jiǎn)單明了的例子,向你展示你可以通過自己的游戲賺錢。 1、定義一個(gè) uint ,命名為 levelUpFee, 將值設(shè)定為 0.001 ether。 2、定義一個(gè)名為 levelUp 的函數(shù)。 它將接收一個(gè) uint 參數(shù) _zombieId。 函數(shù)應(yīng)該修飾為 external 以及 payable。 3、這個(gè)函數(shù)首先應(yīng)該 require 確保 msg.value 等于 levelUpFee。 然后它應(yīng)該增加僵尸的 level: zombies[_zombieId].level++。 zombiehelper.sol 在上一節(jié),我們學(xué)習(xí)了如何向合約發(fā)送以太,那么在發(fā)送之后會(huì)發(fā)生什么呢? 在你發(fā)送以太之后,它將被存儲(chǔ)進(jìn)以合約的以太坊賬戶中, 并凍結(jié)在哪里 —— 除非你添加一個(gè)函數(shù)來(lái)從合約中把以太提現(xiàn)。 你可以寫一個(gè)函數(shù)來(lái)從合約中提現(xiàn)以太,類似這樣: 注意我們使用 Ownable 合約中的 owner 和 onlyOwner,假定它已經(jīng)被引入了。 你可以通過 transfer 函數(shù)向一個(gè)地址發(fā)送以太, 然后 this.balance 將返回當(dāng)前合約存儲(chǔ)了多少以太。 所以如果100個(gè)用戶每人向我們支付1以太, this.balance 將是100以太。 你可以通過 transfer 向任何以太坊地址付錢。 比如,你可以有一個(gè)函數(shù)在 msg.sender 超額付款的時(shí)候給他們退錢: 或者在一個(gè)有賣家和賣家的合約中, 你可以把賣家的地址存儲(chǔ)起來(lái), 當(dāng)有人買了它的東西的時(shí)候,把買家支付的錢發(fā)送給它 seller.transfer(msg.value)。 有很多例子來(lái)展示什么讓以太坊編程如此之酷 —— 你可以擁有一個(gè)不被任何人控制的去中心化市場(chǎng)。 1、在我們的合約里創(chuàng)建一個(gè) withdraw 函數(shù),它應(yīng)該幾乎和上面的GetPaid一樣。 2、以太的價(jià)格在過去幾年內(nèi)翻了十幾倍,在我們寫這個(gè)教程的時(shí)候 0.01 以太相當(dāng)于1美元,如果它再翻十倍 0.001 以太將是10美元,那我們的游戲就太貴了。 所以我們應(yīng)該再創(chuàng)建一個(gè)函數(shù),允許我們以合約擁有者的身份來(lái)設(shè)置 levelUpFee。 a. 創(chuàng)建一個(gè)函數(shù),名為 setLevelUpFee, 其接收一個(gè)參數(shù) uint _fee,是 external 并使用修飾符 onlyOwner。 b. 這個(gè)函數(shù)應(yīng)該設(shè)置 levelUpFee 等于 _fee。 zombiehelper.sol 我們新建一個(gè)攻擊功能合約,并將代碼放進(jìn)新的文件中,引入上一個(gè)合約。 再來(lái)新建一個(gè)合約吧。熟能生巧。 如果你不記得怎么做了, 查看一下 zombiehelper.sol — 不過最好先試著做一下,檢查一下你掌握的情況。 1、在文件開頭定義 Solidity 的版本 ^0.4.19. 2、import 自 zombiehelper.sol . 3、聲明一個(gè)新的 contract,命名為 ZombieBattle, 繼承自ZombieHelper。函數(shù)體就先空著吧。 zombiebattle.sol 優(yōu)秀的游戲都需要一些隨機(jī)元素,那么我們?cè)?Solidity 里如何生成隨機(jī)數(shù)呢? 真正的答案是你不能,或者最起碼,你無(wú)法安全地做到這一點(diǎn)。 我們來(lái)看看為什么 用 keccak256 來(lái)制造隨機(jī)數(shù) 我們可以這樣來(lái)生成一些隨機(jī)數(shù) 這個(gè)方法首先拿到 now 的時(shí)間戳、 msg.sender、 以及一個(gè)自增數(shù) nonce (一個(gè)僅會(huì)被使用一次的數(shù),這樣我們就不會(huì)對(duì)相同的輸入值調(diào)用一次以上哈希函數(shù)了)。 然后利用 keccak 把輸入的值轉(zhuǎn)變?yōu)橐粋€(gè)哈希值, 再將哈希值轉(zhuǎn)換為 uint, 然后利用 % 100 來(lái)取最后兩位, 就生成了一個(gè)0到100之間隨機(jī)數(shù)了。 這個(gè)方法很容易被不誠(chéng)實(shí)的節(jié)點(diǎn)攻擊 一旦一個(gè)節(jié)點(diǎn)解決了一個(gè)PoW, 其他節(jié)點(diǎn)就會(huì)停止嘗試解決這個(gè) PoW, 并驗(yàn)證其他節(jié)點(diǎn)的事務(wù)列表是有效的,然后接受這個(gè)節(jié)點(diǎn)轉(zhuǎn)而嘗試解決下一個(gè)節(jié)點(diǎn)。 這就讓我們的隨機(jī)數(shù)函數(shù)變得可利用了 我們假設(shè)我們有一個(gè)硬幣翻轉(zhuǎn)合約——正面你贏雙倍錢,反面你輸?shù)羲械腻X。假如它使用上面的方法來(lái)決定是正面還是反面 (random >= 50 算正面, random < 50 算反面)。 如果我正運(yùn)行一個(gè)節(jié)點(diǎn),我可以 只對(duì)我自己的節(jié)點(diǎn) 發(fā)布一個(gè)事務(wù),且不分享它。 我可以運(yùn)行硬幣翻轉(zhuǎn)方法來(lái)偷窺我的輸贏 — 如果我輸了,我就不把這個(gè)事務(wù)包含進(jìn)我要解決的下一個(gè)區(qū)塊中去。我可以一直運(yùn)行這個(gè)方法,直到我贏得了硬幣翻轉(zhuǎn)并解決了下一個(gè)區(qū)塊,然后獲利。 所以我們?cè)撊绾卧谝蕴簧习踩厣呻S機(jī)數(shù)呢 ? 因?yàn)閰^(qū)塊鏈的全部?jī)?nèi)容對(duì)所有參與者來(lái)說是透明的, 這就讓這個(gè)問題變得很難,它的解決方法不在本課程討論范圍,你可以閱讀 這個(gè) StackOverflow 上的討論 來(lái)獲得一些主意。 一個(gè)方法是利用 oracle 來(lái)訪問以太坊區(qū)塊鏈之外的隨機(jī)數(shù)函數(shù)。 當(dāng)然, 因?yàn)榫W(wǎng)絡(luò)上成千上萬(wàn)的以太坊節(jié)點(diǎn)都在競(jìng)爭(zhēng)解決下一個(gè)區(qū)塊,我能成功解決下一個(gè)區(qū)塊的幾率非常之低。 這將花費(fèi)我們巨大的計(jì)算資源來(lái)開發(fā)這個(gè)獲利方法 — 但是如果獎(jiǎng)勵(lì)異常地高(比如我可以在硬幣翻轉(zhuǎn)函數(shù)中贏得 1個(gè)億), 那就很值得去攻擊了。 所以盡管這個(gè)方法在以太坊上不安全,在實(shí)際中,除非我們的隨機(jī)函數(shù)有一大筆錢在上面,你游戲的用戶一般是沒有足夠的資源去攻擊的。 因?yàn)樵谶@個(gè)教程中,我們只是在編寫一個(gè)簡(jiǎn)單的游戲來(lái)做演示,也沒有真正的錢在里面,所以我們決定接受這個(gè)不足之處,使用這個(gè)簡(jiǎn)單的隨機(jī)數(shù)生成函數(shù)。但是要謹(jǐn)記它是不安全的。 我們來(lái)實(shí)現(xiàn)一個(gè)隨機(jī)數(shù)生成函數(shù),好來(lái)計(jì)算戰(zhàn)斗的結(jié)果。雖然這個(gè)函數(shù)一點(diǎn)兒也不安全。 1、給我們合約一個(gè)名為 randNonce 的 uint,將其值設(shè)置為 0。 2、建立一個(gè)函數(shù),命名為 randMod (random-modulus)。它將作為internal 函數(shù),傳入一個(gè)名為 _modulus的 uint,并 returns 一個(gè) uint。 3、這個(gè)函數(shù)首先將為 randNonce加一, (使用 randNonce++ 語(yǔ)句)。 4、最后,它應(yīng)該 (在一行代碼中) 計(jì)算 now, msg.sender, 以及 randNonce 的 keccak256 哈希值并轉(zhuǎn)換為 uint—— 最后 return % _modulus 的值。 (天! 聽起來(lái)太拗口了。如果你有點(diǎn)理解不過來(lái),看一下我們上面計(jì)算隨機(jī)數(shù)的例子,它們的邏輯非常相似) zombiehelper.sol 我們的合約已經(jīng)有了一些隨機(jī)性的來(lái)源,可以用進(jìn)我們的僵尸戰(zhàn)斗中去計(jì)算結(jié)果。 我們的僵尸戰(zhàn)斗看起來(lái)將是這個(gè)流程: 你選擇一個(gè)自己的僵尸,然后選擇一個(gè)對(duì)手的僵尸去攻擊。 如果你是攻擊方,你將有70%的幾率獲勝,防守方將有30%的幾率獲勝。 所有的僵尸(攻守雙方)都將有一個(gè) winCount 和一個(gè) lossCount,這兩個(gè)值都將根據(jù)戰(zhàn)斗結(jié)果增長(zhǎng)。 若攻擊方獲勝,這個(gè)僵尸將升級(jí)并產(chǎn)生一個(gè)新僵尸。 如果攻擊方失敗,除了失敗次數(shù)將加一外,什么都不會(huì)發(fā)生。 無(wú)論輸贏,當(dāng)前僵尸的冷卻時(shí)間都將被激活。 這有一大堆的邏輯需要處理,我們將把這些步驟分解到接下來(lái)的課程中去。 1、給我們合約一個(gè) uint 類型的變量,命名為 attackVictoryProbability, 將其值設(shè)定為 70。 2、創(chuàng)建一個(gè)名為 attack的函數(shù)。它將傳入兩個(gè)參數(shù): _zombieId (uint 類型) 以及 _targetId (也是 uint)。它將是一個(gè) external 函數(shù)。 zombiehelper.solpragma 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[]) {
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;
}
}
五、可支付
function test() external view onlyOwner anotherModifier { /* ... */ }
contract OnlineStore {
function buySomething() external payable {
// 檢查以確定0.001以太發(fā)送出去來(lái)運(yùn)行函數(shù):
require(msg.value == 0.001 ether);
// 如果為真,一些用來(lái)向函數(shù)調(diào)用者發(fā)送數(shù)字內(nèi)容的邏輯
transferThing(msg.sender);
}
}
// 假設(shè) `OnlineStore` 在以太坊上指向你的合約:
OnlineStore.buySomething().send(from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001))
注意: 如果一個(gè)函數(shù)沒標(biāo)記為payable, 而你嘗試?yán)蒙厦娴姆椒òl(fā)送以太,函數(shù)將拒絕你的事務(wù)。
實(shí)戰(zhàn)演練
pragma solidity ^0.4.19;
import "./zombiefeeding.sol";
contract ZombieHelper is ZombieFeeding {
// 1. 在這里定義 levelUpFee
uint levelUpFee = 0.001 ether;
modifier aboveLevel(uint _level, uint _zombieId) {
require(zombies[_zombieId].level >= _level);
_;
}
// 2. 在這里插入 levelUp 函數(shù)
function levelUp(uint _zombieId) external payable {
// 檢查以確定0.001以太發(fā)送出去來(lái)運(yùn)行函數(shù):
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;
}
}
六、提現(xiàn)
contract GetPaid is Ownable {
function withdraw() external onlyOwner {
owner.transfer(this.balance);
}
}
uint itemFee = 0.001 ether;
msg.sender.transfer(msg.value - itemFee);
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);
_;
}
// 1. 在這里創(chuàng)建 withdraw 函數(shù)
function withdraw() external onlyOwner {
owner.transfer(this.balance);
}
// 2. 在這里創(chuàng)建 setLevelUpFee 函數(shù)
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;
}
}
七、綜合應(yīng)用
pragma solidity ^0.4.19;
import "./zombiehelper.sol";
contract ZombieBattle is ZombieHelper {
}
八、隨機(jī)數(shù)
Solidity 中最好的隨機(jī)數(shù)生成器是 keccak256 哈希函數(shù).// 生成一個(gè)0到100的隨機(jī)數(shù):
uint randNonce = 0;
uint random = uint(keccak256(now, msg.sender, randNonce)) % 100;
randNonce++;
uint random2 = uint(keccak256(now, msg.sender, randNonce)) % 100;
在以太坊上, 當(dāng)你在和一個(gè)合約上調(diào)用函數(shù)的時(shí)候, 你會(huì)把它廣播給一個(gè)節(jié)點(diǎn)或者在網(wǎng)絡(luò)上的 transaction 節(jié)點(diǎn)們。 網(wǎng)絡(luò)上的節(jié)點(diǎn)將收集很多事務(wù), 試著成為第一個(gè)解決計(jì)算密集型數(shù)學(xué)問題的人,作為“工作證明”,然后將“工作證明”(Proof of Work, PoW)和事務(wù)一起作為一個(gè) block 發(fā)布在網(wǎng)絡(luò)上。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;
}
}
九、游戲?qū)?zhàn)
pragma solidity ^0.4.19;
import "./zombiehelper.sol";
contract ZombieBattle is ZombieHelper {
uint randNonce = 0;
// 在這里創(chuàng)建 attackVictoryProbability
uint attackVictoryProbability = 70;
function randMod(uint _modulus) internal returns(uint) {
randNonce++;
return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
}
// 在這里創(chuàng)建新函數(shù)
function attack(uint _zombieId, uint _targetId) external {
}
}
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/24132.html
摘要:第一個(gè)例子,在你把智能協(xié)議傳上以太坊之后,它就變得不可更改這種永固性意味著你的代碼永遠(yuǎn)不能被調(diào)整或更新。允許將合約所有權(quán)轉(zhuǎn)讓給他人。為何要來(lái)驅(qū)動(dòng)以太坊就像一個(gè)巨大緩慢但非常安全的電腦。 通過前邊的 Solidity 基礎(chǔ)語(yǔ)法學(xué)習(xí),我們已經(jīng)有了Solidity編程經(jīng)驗(yàn),在這節(jié)就要學(xué)學(xué) Ethereum 開發(fā)的技術(shù)細(xì)節(jié),編寫真正的 DApp 時(shí)必知的:智能協(xié)議的所有權(quán),Gas的花費(fèi),代碼優(yōu)...
摘要:接上篇文章,這里繼續(xù)學(xué)習(xí)高級(jí)理論。將這個(gè)函數(shù)的定義修改為其使用修飾符。我們將用一個(gè)到的隨機(jī)數(shù)來(lái)確定我們的戰(zhàn)斗結(jié)果。在這個(gè)教程中,簡(jiǎn)單起見我們將這個(gè)狀態(tài)保存在結(jié)構(gòu)體中,將其命名為和。在第六章我們計(jì)算出來(lái)一個(gè)到的隨機(jī)數(shù)。 接上篇文章,這里繼續(xù)學(xué)習(xí)Solidity高級(jí)理論。 一、重構(gòu)通用邏輯 不管誰(shuí)調(diào)用我們的 attack 函數(shù) —— 我們想確保用戶的確擁有他們用來(lái)攻擊的僵尸。如果你能用其他...
摘要:引言給迷失在如何學(xué)習(xí)區(qū)塊鏈技術(shù)的同學(xué)一個(gè)指引,區(qū)塊鏈技術(shù)是隨比特幣誕生,因此要搞明白區(qū)塊鏈技術(shù),應(yīng)該先了解下比特幣。但區(qū)塊鏈技術(shù)不單應(yīng)用于比特幣,還有非常多的現(xiàn)實(shí)應(yīng)用場(chǎng)景,想做區(qū)塊鏈應(yīng)用開發(fā),可進(jìn)一步閱讀以太坊系列。 本文始發(fā)于深入淺出區(qū)塊鏈社區(qū), 原文:區(qū)塊鏈技術(shù)學(xué)習(xí)指引 原文已更新,請(qǐng)讀者前往原文閱讀 本章的文章越來(lái)越多,本文是一個(gè)索引帖,方便找到自己感興趣的文章,你也可以使用左側(cè)...
摘要:和比特幣協(xié)議有所不同的是,以太坊的設(shè)計(jì)十分靈活,極具適應(yīng)性。超級(jí)賬本區(qū)塊鏈的商業(yè)應(yīng)用超級(jí)賬本超級(jí)賬本是基金會(huì)下的眾多項(xiàng)目中的一個(gè)。證書頒發(fā)機(jī)構(gòu)負(fù)責(zé)簽發(fā)撤 showImg(https://segmentfault.com/img/bV2ge9?w=900&h=385); 從比特幣開始 一個(gè)故事告訴你比特幣的原理及運(yùn)作機(jī)制 這篇文章的定位會(huì)比較科普,盡量用類比的方法將比特幣的基本原理講出來(lái)...
摘要:以太坊開發(fā)高級(jí)語(yǔ)言學(xué)習(xí)。地址以太坊區(qū)塊鏈由賬戶組成,你可以把它想象成銀行賬戶。使用很安全,因?yàn)樗哂幸蕴粎^(qū)塊鏈的安全保障除非竊取與以太坊地址相關(guān)聯(lián)的私鑰,否則是沒有辦法修改其他人的數(shù)據(jù)的。 以太坊開發(fā)高級(jí)語(yǔ)言學(xué)習(xí)。 一、映射(Mapping)和地址(Address) 我們通過給數(shù)據(jù)庫(kù)中的僵尸指定主人, 來(lái)支持多玩家模式。 如此一來(lái),我們需要引入2個(gè)新的數(shù)據(jù)類型:mapping(映射)...
閱讀 2971·2021-11-22 13:54
閱讀 3579·2021-11-16 11:44
閱讀 1405·2021-09-07 10:19
閱讀 1503·2019-08-29 17:30
閱讀 3221·2019-08-29 11:33
閱讀 3570·2019-08-26 12:18
閱讀 2910·2019-08-26 11:53
閱讀 1372·2019-08-26 10:47