經過前邊的 Solidity 基礎語法學習,咱們已經有了Solidity編程經驗,在這節就要學學Ethereum
開發的技術細節,編寫真正的 DApp 時必知的:智能協議的全部權
,Gas的花費
,代碼優化
,和代碼安全
。
到如今爲止,咱們講的 Solidity 和其餘語言沒有質的區別,它長得也很像 JavaScript.git
可是,在有幾點以太坊上的 DApp 跟普通的應用程序有着天壤之別。算法
第一個例子,在你把智能協議傳上以太坊以後,它就變得不可更改
, 這種永固性意味着你的代碼永遠不能被調整或更新。編程
你編譯的程序會一直,永久的,不可更改的,存在以太網上。這就是Solidity代碼的安全性如此重要的一個緣由。若是你的智能協議有任何漏洞,即便你發現了也沒法補救。你只能讓你的用戶們放棄這個智能協議,而後轉移到一個新的修復後的合約上。安全
但這剛好也是智能合約的一大優點。 代碼說明一切。 若是你去讀智能合約的代碼,並驗證它,你會發現, 一旦函數被定義下來,每一次的運行,程序都會嚴格遵守函數中原有的代碼邏輯一絲不苟地執行,徹底不用擔憂函數被人篡改而獲得意外的結果。網絡
在上邊的文章中,咱們將加密小貓(CryptoKitties)合約的地址硬編碼
到DApp中去了。有沒有想過,若是加密小貓出了點問題,比方說,集體消失了會怎麼樣? 雖然這種事情幾乎不可能發生,可是,若是小貓沒了,咱們的 DApp 也會隨之失效 -- 由於咱們在 DApp 的代碼中用「硬編碼」的方式指定了加密小貓的地址,若是這個根據地址找不到小貓,咱們的殭屍也就吃不到小貓了,而按照前面的描述,咱們卻無法修改合約去應付這個變化!app
所以,咱們不能硬編碼,而要採用「函數」,以便於 DApp 的關鍵部分能夠以參數形式修改。dom
比方說,咱們再也不一開始就把獵物地址給寫入代碼,而是寫個函數 setKittyContractAddress
, 運行時再設定獵物的地址,這樣咱們就能夠隨時去鎖定新的獵物,也不用擔憂加密小貓集體消失了。編程語言
請修改前邊的代碼,使得能夠經過程序更改CryptoKitties合約地址。ide
ckAddress
代碼行。kittyContract
變量的那行代碼,修改成對 kittyContract
變量的聲明 -- 暫時不給它指定具體的實例。注意:你可能會注意到這個功能有個安全漏洞,別擔憂 - 我們到下一章裏解決它;)
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 { // 1. 移除這一行: // address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; // 2. 只聲明變量: // KittyInterface kittyContract = KittyInterface(ckAddress); KittyInterface kittyContract; // 3. 增長 setKittyContractAddress 方法 function setKittyContractAddress(address _address) external { 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"); } }
上面代碼中,您有沒有發現任何安全漏洞呢?
呀!setKittyContractAddress
可見性竟然申明爲「外部的」(external
),豈不是任何人均可以調用它! 也就是說,任何調用該函數的人均可以更改 CryptoKitties 合約的地址,使得其餘人都無法再運行咱們的程序了。
咱們確實是但願這個地址可以在合約中修改,但我可沒說讓每一個人去改它呀。
要對付這樣的狀況,一般的作法是指定合約的「
全部權
」 - 就是說,給它指定一個主人(沒錯,就是您),只有主人對它享有特權。
下面是一個 Ownable
合約的例子: 來自 OpenZeppelin
Solidity 庫的 Ownable
合約。 OpenZeppelin
是主打安保和社區審查的智能合約庫,您能夠在本身的 DApps中引用。等把這一課學完,您不要催咱們發佈下一課,最好利用這個時間把 OpenZeppelin 的網站看看,保管您會學到不少東西!
把樓下這個合約讀讀通,是否是還有些沒見過代碼?別擔憂,咱們隨後會解釋。
/** * @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; } }
下面有沒有您沒學過的東東?
function Ownable()
是一個 constructor
(構造函數),構造函數不是必須的,它與合約同名,構造函數一輩子中惟一的一次執行,就是在合約最初被建立的時候。modifier onlyOwner()
。 修飾符跟函數很相似,不過是用來修飾其餘已有函數用的, 在其餘語句執行前,爲它檢查下先驗條件。 在這個例子中,咱們就能夠寫個修飾符 onlyOwner
檢查下調用者,確保只有合約的主人才能運行本函數。咱們下一章中會詳細講述修飾符,以及那個奇怪的_;。indexed
關鍵字:別擔憂,咱們還用不到它。因此 Ownable
合約基本都會這麼幹:
owner
設置爲msg.sender
(其部署者)onlyOwner
,它會限制陌生人的訪問,將訪問某些函數的權限鎖定在 owner
上。onlyOwner
簡直人見人愛,大多數人開發本身的 Solidity DApps,都是從複製/粘貼 Ownable
開始的,從它再繼承出的子類,並在之上進行功能開發。
既然咱們想把 setKittyContractAddress
限制爲 onlyOwner
,咱們也要作一樣的事情。
首先,將 Ownable
合約的代碼複製一份到新文件 ownable.so
l 中。 接下來,建立一個 ZombieFactory
,繼承 Ownable
。
ownable.sol
的內容。 若是您不記得怎麼作了,參考下 zombiefeeding.sol
。ZombieFactory
合約, 讓它繼承自 Ownable
。 若是您不記得怎麼作了,看看 zombiefeeding.sol
。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; } }
zombiefactory.sol
pragma solidity ^0.4.19; // 1. 在這裏導入 import "./ownable.sol"; // 2. 在這裏繼承: contract ZombieFactory is Ownable{ event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } 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; 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); } }
如今咱們有了個基本版的合約 ZombieFactory
了,它繼承自 Ownable
接口,咱們也能夠給 ZombieFeeding
加上 onlyOwner
函數修飾符。
這就是合約繼承的工做原理。記得:
ZombieFeeding 是個 ZombieFactory ZombieFactory 是個 Ownable
函數修飾符看起來跟函數沒什麼不一樣,不過關鍵字modifier
告訴編譯器,這是個modifier(修飾符)
,而不是個function(函數)
。它不能像函數那樣被直接調用,只能被添加到函數定義的末尾,用以改變函數的行爲。
再仔細讀讀 onlyOwner
:
/** * @dev 調用者不是‘主人’,就會拋出異常 */ modifier onlyOwner() { require(msg.sender == owner); _; }
onlyOwner
函數修飾符是這麼用的:
contract MyContract is Ownable { event LaughManiacally(string laughter); //注意! `onlyOwner`上場 : function likeABoss() external onlyOwner { LaughManiacally("Muahahahaha"); } }
注意 likeABoss
函數上的 onlyOwner
修飾符。 當你調用 likeABoss
時,首先執行 onlyOwner
中的代碼, 執行到 onlyOwner
中的_;
語句時,程序再返回並執行 likeABoss
中的代碼。
可見,儘管函數修飾符也能夠應用到各類場合,但最多見的仍是放在函數執行以前添加快速的 require
檢查。
由於給函數添加了修飾符 onlyOwner
,使得惟有合約的主人(也就是部署者)才能調用它。
注意:主人對合約享有的特權固然是正當的,不過也可能被惡意使用。好比,萬一,主人添加了個後門,容許他偷走別人的殭屍呢?因此很是重要的是,部署在以太坊上的 DApp,並不能保證它真正作到去中心,你須要閱讀並理解它的源代碼,才能防止其中沒有被部署者惡意植入後門;做爲開發人員,如何作到既要給本身留下修復 bug 的餘地,又要儘可能地放權給使用者,以便讓他們放心你,從而願意把數據放在你的 DApp 中,這確實須要個微妙的平衡。
如今咱們能夠限制第三方對 setKittyContractAddress
的訪問,除了咱們本身,誰都沒法去修改它。
onlyOwner
函數修飾符添加到 setKittyContractAddress
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; // 修改這個函數,添加權限onlyOwner 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"); } }
如今咱們懂了如何在禁止第三方修改咱們的合約的同時,留個後門給我們本身去修改。
讓咱們來看另外一種使得 Solidity 編程語言不同凡響的特徵:
在 Solidity 中,你的用戶想要每次執行你的 DApp 都須要支付必定的 gas,gas 能夠用以太幣購買,所以,用戶每次跑 DApp 都得花費以太幣。
一個 DApp 收取多少 gas 取決於功能邏輯的複雜程度。每一個操做背後,都在計算完成這個操做所須要的計算資源,(好比,存儲數據就比作個加法運算貴得多), 一次操做所須要花費的 gas
等於這個操做背後的全部運算花銷的總和。
因爲運行你的程序須要花費用戶的真金白銀,在以太坊中代碼的編程語言,比其餘任何編程語言都更強調優化。一樣的功能,使用笨拙的代碼開發的程序,比起通過精巧優化的代碼來,運行花費更高,這顯然會給成千上萬的用戶帶來大量沒必要要的開銷。
以太坊就像一個巨大、緩慢、但很是安全的電腦。當你運行一個程序的時候,網絡上的每個節點都在進行相同的運算,以驗證它的輸出 —— 這就是所謂的」去中心化「 因爲數以千計的節點同時在驗證着每一個功能的運行,這能夠確保它的數據不會被被監控,或者被刻意修改。
可能會有用戶用無限循環堵塞網絡,抑或用密集運算來佔用大量的網絡資源,爲了防止這種事情的發生,以太坊的建立者爲以太坊上的資源制定了價格,想要在以太坊上運算或者存儲,你須要先付費
。
注意:若是你使用
側鏈
,卻是不必定須要付費,好比我們在 Loom Network 上構建的 CryptoZombies 就免費。你不會想要在以太坊主網上玩兒「魔獸世界」吧? - 所須要的 gas 可能會買到你破產。可是你能夠找個算法理念不一樣的側鏈來玩它。咱們將在之後的課程中我們會討論到,什麼樣的 DApp 應該部署在太坊主鏈上,什麼又最好放在側鏈。
在第1課中,咱們提到除了基本版的 uint
外,還有其餘變種 uint
:uint8
,uint16
,uint32
等。
一般狀況下咱們不會考慮使用 uint
變種,由於不管如何定義 uint
的大小,Solidity 爲它保留256位的存儲空間。例如,使用 uint8
而不是uint(uint256)不會爲你節省任何 gas。
除非,把 uint
綁定到 struct
裏面。
若是一個 struct
中有多個 uint
,則儘量使用較小的 uint
, Solidity 會將這些 uint 打包在一塊兒,從而佔用較少的存儲空間。例如:
struct NormalStruct { uint a; uint b; uint c; } struct MiniMe { uint32 a; uint32 b; uint c; } // 由於使用告終構打包,`mini` 比 `normal` 佔用的空間更少 NormalStruct normal = NormalStruct(10, 20, 30); MiniMe mini = MiniMe(10, 20, 30);
因此,當 uint
定義在一個 struct
中的時候,儘可能使用最小的整數子類型以節約空間。 而且把一樣類型的變量放一塊兒(即在 struct 中將把變量按照類型依次放置),這樣 Solidity 能夠將存儲空間最小化。例如,有兩個 struct
:
uint c; uint32 a; uint32 b;
和 uint32 a; uint c; uint32 b;
前者比後者須要的gas更少,由於前者把uint32
放一塊兒了。
我們給殭屍添2個新功能:level
和 readyTime
- 後者是用來實現一個「冷卻定時器」,以限制殭屍獵食的頻率。
讓咱們回到 zombiefactory.sol
。
Zombie
結構體 添加兩個屬性:level
(uint32
)和readyTime
(uint32
)。由於但願同類型數據打成一個包,因此把它們放在結構體的末尾。32位足以保存殭屍的級別和時間戳了,這樣比起使用普通的uint(256位),能夠更緊密地封裝數據,從而爲咱們省點 gas。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; 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; 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); } }
level
屬性表示殭屍的級別。之後,在咱們建立的戰鬥系統中,打勝仗的殭屍會逐漸升級並得到更多的能力。
readyTime
稍微複雜點。咱們但願增長一個「冷卻週期」,表示殭屍在兩次獵食或攻擊之之間必須等待的時間。若是沒有它,殭屍天天可能會攻擊和繁殖1,000次,這樣遊戲就太簡單了。
爲了記錄殭屍在下一次進擊前須要等待的時間,咱們使用了 Solidity 的時間單位。
Solidity 使用本身的本地時間單位。
變量 now
將返回當前的unix時間戳(自1970年1月1日以來通過的秒數)。我寫這句話時 unix 時間是 1515527488。
注意:Unix時間傳統用一個32位的整數進行存儲。這會致使「2038年」問題,當這個32位的unix時間戳不夠用,產生溢出,使用這個時間的遺留系統就麻煩了。因此,若是咱們想讓咱們的 DApp 跑夠20年,咱們可使用64位整數表示時間,但爲此咱們的用戶又得支付更多的 gas。真是個兩難的設計啊!
Solidity 還包含秒(seconds)
,分鐘(minutes)
,小時(hours)
,天(days)
,周(weeks)
和 年(years)
等時間單位。它們都會轉換成對應的秒數放入 uint
中。因此 1分鐘 就是 60,1小時是 3600(60秒×60分鐘),1天是86400
(24小時×60分鐘×60秒),以此類推。
下面是一些使用時間單位的實用案例:
uint lastUpdated; // 將‘上次更新時間’ 設置爲 ‘如今’ function updateTimestamp() public { lastUpdated = now; } // 若是到上次`updateTimestamp` 超過5分鐘,返回 'true' // 不到5分鐘返回 'false' function fiveMinutesHavePassed() public view returns (bool) { return (now >= (lastUpdated + 5 minutes)); }
有了這些工具,咱們能夠爲殭屍設定」冷靜時間「功能
如今我們給DApp添加一個「冷卻週期」的設定,讓殭屍兩次攻擊或捕獵之間必須等待 1天。
cooldownTime
的uint
,並將其設置爲 1 days。(沒錯,」1 days「使用了複數, 不然通不過編譯器)Zombie
結構體中添加 level
和 readyTime
兩個參數,因此如今建立一個新的 Zombie 結構體時,須要修改 _createZombie()
,在其中把新舊參數都初始化一下。zombies.push
那一行, 添加加2個參數:1(表示當前的 level )和uint32(now + cooldownTime 如今+冷靜時間)(表示下次容許攻擊的時間 readyTime)。注意:必須使用uint32
(...) 進行強制類型轉換,由於 now 返回類型uint256
。因此咱們須要明確將它轉換成一個 uint32 類型的變量。
now + cooldownTime
將等於當前的unix時間戳(以秒爲單位)加上」1天「裏的秒數 - 這將等於從如今起1天后的unix時間戳。而後咱們就比較,看看這個殭屍的 readyTime是否大於 now,以決定再次啓用殭屍的時機有沒有到來。
下一節中,咱們將討論如何經過 readyTime
來規範殭屍的行爲。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; // 1. 在這裏定義 `cooldownTime` 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 { // 2. 修改下面這行: 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); } }
如今,Zombie 結構體中定義好了一個 readyTime
屬性,讓咱們跳到 zombiefeeding.sol
, 去實現一個」冷卻週期定時器
「。
按照如下步驟修改 feedAndMultiply
:
這將限制殭屍,防止其無限制地捕獵小貓或者成天不停地繁殖。未來,當咱們增長戰鬥功能時,咱們一樣用」冷卻週期「限制殭屍之間打鬥的頻率。
首先,咱們要定義一些輔助函數,設置並檢查殭屍的 readyTime。
將結構體做爲參數傳入
因爲結構體的存儲指針能夠以參數的方式傳遞給一個 private 或 internal 的函數,所以結構體能夠在多個函數之間相互傳遞。
遵循這樣的語法:
function _doStuff(Zombie storage _zombie) internal { // do stuff with _zombie }
這樣咱們能夠將某殭屍的引用直接傳遞給一個函數,而不用是經過參數傳入殭屍ID後,函數再依據ID去查找。
_triggerCooldown
函數。它要求一個參數,_zombie
,表示一某個殭屍的存儲指針。這個函數可見性設置爲 internal
。uint32(now + cooldownTime)
。_isReady
的函數。這個函數的參數也是名爲 _zombie 的 Zombie storage
。這個功能只具備 internal 可見性,並返回一個 bool 值。(_zombie.readyTime <= now)
,值爲 true
或 false
。這個功能的目的是判斷下次容許獵食的時間是否已經到了。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); } // 1. 在這裏定義 `_triggerCooldown` 函數 function _triggerCooldown(Zombie storage _zombie) internal { _zombie.readyTime = uint32(now + cooldownTime); } // 2. 在這裏定義 `_isReady` 函數 function _isReady(Zombie storage _zombie) internal view returns (bool) { return (_zombie.readyTime <= now); } 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"); } }
如今來修改 feedAndMultiply
,實現冷卻週期。
回顧一下這個函數,前一課上咱們將其可見性設置爲public
。你必須仔細地檢查全部聲明爲 public
和 external
的函數,一個個排除用戶濫用它們的可能,謹防安全漏洞。請記住,若是這些函數沒有相似 onlyOwner
這樣的函數修飾符,用戶能利用各類可能的參數去調用它們。
檢查完這個函數,用戶就能夠直接調用這個它,並傳入他們所但願的 _targetDna
或 species
。打個遊戲還得遵循這麼多的規則,還能不能愉快地玩耍啊!
仔細觀察,這個函數只需被 feedOnKitty()
調用,所以,想要防止漏洞,最簡單的方法就是設其可見性爲 internal
。
feedAndMultiply
可見性爲 public
。咱們將其改成 internal
以保障合約安全。由於咱們不但願用戶調用它的時候塞進一堆亂七八糟的 DNA。feedAndMultiply
過程須要參考 cooldownTime
。首先,在找到 myZombie 以後,添加一個 require
語句來檢查 _isReady()
並將 myZombie 傳遞給它。這樣用戶必須等到殭屍的 冷卻週期 結束後才能執行 feedAndMultiply
功能。_triggerCooldown(myZombie)
,標明捕獵行爲觸發了殭屍新的冷卻週期。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); } // 1. 使這個函數的可見性爲 internal function feedAndMultiply(uint _zombieId, uint _targetDna, string species) internal { require(msg.sender == zombieToOwner[_zombieId]); Zombie storage myZombie = zombies[_zombieId]; // 2. 在這裏爲 `_isReady` 增長一個檢查 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); // 3. 調用 `triggerCooldown` _triggerCooldown(myZombie); } function feedOnKitty(uint _zombieId, uint _kittyId) public { uint kittyDna; (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); feedAndMultiply(_zombieId, kittyDna, "kitty"); } }