目錄
一、前言
看了一些區塊鏈的教程,論文,在網上剛剛找到了一個項目實戰,CryptoZombies。
前面我們講到了Gas,今天我們再來講一下如何節約Gas。
如果你想了解更多有關於機器學習、深度學習、區塊鏈、計算機視覺等相關技術的內容,想與更多大佬一起溝通,那就掃描下方二維碼加入我們吧!
二、For循環
1、引入
爲了實現 getZombiesByOwner
函數,一種解決方案是在 ZombieFactory
中存入”主人“和”殭屍軍團“的映射。
mapping (address => uint[]) public ownerToZombies
然後每次創建新的殭屍的時候,就需要執行ownerToZombies [owner] .push(zombieId)
將其添加到主人的殭屍數組中。而 getZombiesByOwner
函數也非常簡單:
function getZombiesByOwner(address _owner) external view returns (uint[]) {
return ownerToZombies[_owner];
}
這種做法確實很簡單,很直接,但是它存在問題:
如果我們需要一個函數來把一頭殭屍轉移到另一個主人名下(我們一定會在後面的課程中實現的),又會發生什麼?
這次更換主人的操作需要實現:
1.將殭屍push到新主人的
ownerToZombies
數組中。
2.
從舊主的ownerToZombies
數組中移除殭屍。3.將舊主殭屍數組中“換主殭屍”之後的的每頭殭屍都往前挪一位,把挪走“換主殭屍”後留下的“空槽”填上。
4.將數組長度減1。
但是第三步實在是太貴了!因爲每挪動一頭殭屍,我們都要執行一次寫操作。如果一個主人有20頭殭屍,而第一頭被挪走了,那爲了保持數組的順序,我們得做19個寫操作。由於寫入存儲是 Solidity 中最費 gas 的操作之一,使得換主函數的每次調用都非常昂貴。更糟糕的是,每次調用的時候花費的 gas 都不同!具體還取決於用戶在原主軍團中的殭屍頭數,以及移走的殭屍所在的位置。以至於用戶都不知道應該支付多少 gas。
有人說,我們也可以把數組中最後一個殭屍往前挪來填補空槽,並將數組長度減少一。但這樣每做一筆交易,都會改變殭屍軍團的秩序。
由於從外部調用一個 view
函數是免費的,我們也可以在 getZombiesByOwner
函數中用一個for循環遍歷整個殭屍數組,把屬於某個主人的殭屍挑出來構建出殭屍數組。那麼我們的 transfer
函數將會便宜得多,因爲我們不需要挪動存儲裏的殭屍數組重新排序,總體上這個方法會更便宜,雖然有點反直覺。
2、For循環
爲了實現上面的功能,我們要用到for循環。for循環對於大家來說,如果學過其他編程語言,就相當熟悉了。
我們看一個簡單的示例:
function getEvens() pure external returns(uint[]) {
uint[] memory evens = new uint[](5);
// 在新數組中記錄序列號
uint counter = 0;
// 在循環從1迭代到10:
for (uint i = 1; i <= 10; i++) {
// 如果 `i` 是偶數...
if (i % 2 == 0) {
// 把它加入偶數數組
evens[counter] = i;
//索引加一, 指向下一個空的‘even’
counter++;
}
}
return evens;
}
這個函數將返回一個形爲 [2,4,6,8,10]
的數組。
3、實戰
1.要求
我們在 getZombiesByOwner
函數中通過一條 for
循環來遍歷 DApp 中所有的殭屍, 將給定的‘用戶id'與每頭殭屍的‘主人’進行比較,並在函數返回之前將它們推送到我們的result
數組中。
1.聲明一個變量
counter
,屬性爲uint
,設其值爲0
。我們用這個變量作爲result
數組的索引。
2.
聲明一個for
循環, 從uint i = 0
到i <zombies.length
。它將遍歷數組中的每一頭殭屍。3.在每一輪
for
循環中,用一個if
語句來檢查zombieToOwner [i]
是否等於_owner
。這會比較兩個地址是否匹配。4.在
if
語句中:(1)通過將
result [counter]
設置爲i
,將殭屍ID添加到result
數組中。(2)將counter加1。
這樣 - 這個函數能返回 _owner
所擁有的殭屍數組,不花一分錢 gas。
2.代碼
pragma solidity >=0.5.0 <0.6.0;
import "./zombiefeeding.sol";
contract ZombieHelper is ZombieFeeding {
modifier aboveLevel(uint _level, uint _zombieId) {
require(zombies[_zombieId].level >= _level);
_;
}
function changeName(uint _zombieId, string calldata _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[] memory) {
uint[] memory result = new uint[](ownerZombieCount[_owner]);
// Start here
uint counter = 0;
for(uint i = 0; i < zombies.length; i++) {
if(zombieToOwner[i] == _owner) {
result[counter] = i;
counter++;
}
}
return result;
}
}