以太坊開發(fā)高級(jí)語(yǔ)言學(xué)習(xí)嘁字。
一、映射(Mapping)和地址(Address)
我們通過(guò)給數(shù)據(jù)庫(kù)中的僵尸指定“主人”企孩, 來(lái)支持“多玩家”模式潮剪。
如此一來(lái)涣楷,我們需要引入2個(gè)新的數(shù)據(jù)類型:mapping
(映射) 和 address
(地址)。
Addresses(地址)
以太坊區(qū)塊鏈由 account
(賬戶)組成抗碰,你可以把它想象成銀行賬戶狮斗。一個(gè)帳戶的余額是 以太
(在以太坊區(qū)塊鏈上使用的幣種),你可以和其他帳戶之間支付和接受以太幣弧蝇,就像你的銀行帳戶可以電匯資金到其他銀行帳戶一樣碳褒。
每個(gè)帳戶都有一個(gè)“地址”,你可以把它想象成銀行賬號(hào)看疗。這是賬戶唯一的標(biāo)識(shí)符沙峻,它看起來(lái)長(zhǎng)這樣:
0x0cE446255506E92DF41614C46F1d6df9Cc969183
我們將在后面的課程中介紹地址的細(xì)節(jié),現(xiàn)在你只需要了解地址屬于特定用戶(或智能合約)的两芳。
所以我們可以指定“地址”作為僵尸主人的 ID摔寨。當(dāng)用戶通過(guò)與我們的應(yīng)用程序交互來(lái)創(chuàng)建新的僵尸時(shí),新僵尸的所有權(quán)被設(shè)置到調(diào)用者的以太坊地址下怖辆。
Mapping(映射)
在上一篇博文中是复,我們看到了 結(jié)構(gòu)體
和 數(shù)組
。 映射
是另一種在 Solidity 中存儲(chǔ)有組織數(shù)據(jù)的方法竖螃。
映射是這樣定義的:
//對(duì)于金融應(yīng)用程序淑廊,將用戶的余額保存在一個(gè) uint類型的變量中:
mapping (address => uint) public accountBalance;
//或者可以用來(lái)通過(guò)userId 存儲(chǔ)/查找的用戶名
mapping (uint => string) userIdToName;
映射本質(zhì)上是存儲(chǔ)和查找數(shù)據(jù)所用的鍵-值
對(duì)。在第一個(gè)例子中特咆,鍵是一個(gè) address
季惩,值是一個(gè) uint
,在第二個(gè)例子中坚弱,鍵是一個(gè)uint
蜀备,值是一個(gè) string
关摇。
實(shí)戰(zhàn)演練
為了存儲(chǔ)僵尸的所有權(quán)荒叶,我們會(huì)使用到兩個(gè)映射:一個(gè)記錄僵尸擁有者的地址,另一個(gè)記錄某地址所擁有僵尸的數(shù)量输虱。
- 1.創(chuàng)建一個(gè)叫做
zombieToOwner
的映射些楣。其鍵是一個(gè)uint
(我們將根據(jù)它的id
存儲(chǔ)和查找僵尸),值為address
。映射屬性為public
愁茁。 - 2.創(chuàng)建一個(gè)名為
ownerZombieCount
的映射蚕钦,其中鍵是address
,值是uint
鹅很。
Contract.sol
// 1\. 這里寫版本指令
pragma solidity ^0.4.19;
// 2\. 這里建立智能合約
contract ZombieFactory {
// 12.這里建立事件
event NewZombie(uint zombieId, string name, uint dna);
// 3\. 定義 dnaDigits 為 uint 數(shù)據(jù)類型, 并賦值 16
uint dnaDigits = 16;
// 4\. 10 的 dnaDigits 次方
uint dnaModulus = 10 ** dnaDigits;
// 5.結(jié)構(gòu)體定義
struct Zombie {
string name;
uint dna;
}
// 6.數(shù)組類型為結(jié)構(gòu)體的公共數(shù)組
Zombie[] public zombies;
// 13.在這里定義映射
mapping(uint => address) public zombieToOwner;
mapping(address => uint) ownerZombieCount;
/*
// 7.創(chuàng)建函數(shù)
function createZombie(string _name, uint _dna){
// 8.使用結(jié)構(gòu)體和數(shù)組(初始化全局?jǐn)?shù)組)
zombies.push(Zombie(_name, _dna));
}
*/
// 7.創(chuàng)建函數(shù)(改為私有方法)
function _createZombie(string _name, uint _dna) private {
// 8.使用結(jié)構(gòu)體和數(shù)組(初始化全局?jǐn)?shù)組)
// zombies.push(Zombie(_name, _dna));
// 12嘶居、數(shù)組長(zhǎng)度減一就是當(dāng)前的數(shù)組ID
uint id = zombies.push(Zombie(_name, _dna)) - 1;
// 12、這里觸發(fā)事件
NewZombie(id, _name, _dna);
}
// 9.函數(shù)修飾符 private, view, returns 返回值
function _generateRandomDna(string _str) private view returns (uint){
// 10.散列并取模
uint rand = uint(keccak256(_str)); // 注意這里需要將string類型轉(zhuǎn)為uint類型
return rand % dnaModulus;
}
// 11促煮、綜合函數(shù)
function createRandomZombie(string _name) public {
uint randDna = _generateRandomDna(_name);
_createZombie(_name, randDna);
}
}
二邮屁、Msg.sender
現(xiàn)在有了一套映射來(lái)記錄僵尸的所有權(quán)了,我們可以修改 _createZombie
方法來(lái)運(yùn)用它們菠齿。
為了做到這一點(diǎn)佑吝,我們要用到 msg.sender
。
msg.sender
在 Solidity 中绳匀,有一些全局變量可以被所有函數(shù)調(diào)用芋忿。 其中一個(gè)就是 msg.sender
,它指的是當(dāng)前調(diào)用者(或智能合約)的 address
疾棵。
注意:在 Solidity 中戈钢,功能執(zhí)行始終需要從外部調(diào)用者開始。 一個(gè)合約只會(huì)在區(qū)塊鏈上什么也不做是尔,除非有人調(diào)用其中的函數(shù)逆趣。所以
msg.sender
總是存在的。
以下是使用 msg.sender
來(lái)更新 mapping
的例子:
mapping (address => uint) favoriteNumber;
function setMyNumber(uint _myNumber) public {
// 更新我們的 `favoriteNumber` 映射來(lái)將 `_myNumber`存儲(chǔ)在 `msg.sender`名下
favoriteNumber[msg.sender] = _myNumber;
// 存儲(chǔ)數(shù)據(jù)至映射的方法和將數(shù)據(jù)存儲(chǔ)在數(shù)組相似
}
function whatIsMyNumber() public view returns (uint) {
// 拿到存儲(chǔ)在調(diào)用者地址名下的值
// 若調(diào)用者還沒(méi)調(diào)用 setMyNumber嗜历, 則值為 `0`
return favoriteNumber[msg.sender];
}
在這個(gè)小小的例子中宣渗,任何人都可以調(diào)用 setMyNumber
在我們的合約中存下一個(gè) uint
并且與他們的地址相綁定。 然后梨州,他們調(diào)用 whatIsMyNumber
就會(huì)返回他們存儲(chǔ)的 uint
痕囱。
使用 msg.sender
很安全,因?yàn)樗哂幸蕴粎^(qū)塊鏈的安全保障 —— 除非竊取與以太坊地址相關(guān)聯(lián)的私鑰暴匠,否則是沒(méi)有辦法修改其他人的數(shù)據(jù)的鞍恢。
實(shí)戰(zhàn)演練
我們來(lái)修改前邊的_createZombie
方法,將僵尸分配給函數(shù)調(diào)用者吧每窖。
- 1帮掉、首先,在得到新的僵尸
id
后窒典,更新zombieToOwner
映射蟆炊,在id
下面存入msg.sender
。 - 2瀑志、然后涩搓,我們?yōu)檫@個(gè)
msg.sender
名下的ownerZombieCount
加 1污秆。
跟在 JavaScript 中一樣, 在 Solidity 中你也可以用 ++ 使 uint 遞增昧甘。
uint number = 0;
number++;
// `number` 現(xiàn)在是 `1`了
修改兩行代碼即可良拼。
pragma solidity ^0.4.19;
contract ZombieFactory {
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) private {
uint id = zombies.push(Zombie(_name, _dna)) - 1;
// 從這里開始,msg.sender表示當(dāng)前調(diào)用者的地址
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 {
uint randDna = _generateRandomDna(_name);
_createZombie(_name, randDna);
}
}
三、Require
我們成功讓用戶通過(guò)調(diào)用 createRandomZombie
函數(shù) 并輸入一個(gè)名字來(lái)創(chuàng)建新的僵尸充边。 但是庸推,如果用戶能持續(xù)調(diào)用這個(gè)函數(shù)來(lái)創(chuàng)建出無(wú)限多個(gè)僵尸加入他們的軍團(tuán),這游戲就太沒(méi)意思了浇冰!
于是予弧,我們作出限定:每個(gè)玩家只能調(diào)用一次這個(gè)函數(shù)
。 這樣一來(lái)湖饱,新玩家可以在剛開始玩游戲時(shí)通過(guò)調(diào)用它掖蛤,為其軍團(tuán)創(chuàng)建初始僵尸。
我們?cè)鯓硬拍芟薅總€(gè)玩家只調(diào)用一次這個(gè)函數(shù)呢井厌?
答案是使用require
蚓庭。 require
使得函數(shù)在執(zhí)行過(guò)程中,當(dāng)不滿足某些條件時(shí)拋出錯(cuò)誤仅仆,并停止執(zhí)行:
function sayHiToVitalik(string _name) public returns (string) {
// 比較 _name 是否等于 "Vitalik". 如果不成立器赞,拋出異常并終止程序
// (敲黑板: Solidity 并不支持原生的字符串比較, 我們只能通過(guò)比較
// 兩字符串的 keccak256 哈希值來(lái)進(jìn)行判斷)
require(keccak256(_name) == keccak256("Vitalik"));
// 如果返回 true, 運(yùn)行如下語(yǔ)句
return "Hi!";
}
如果你這樣調(diào)用函數(shù) sayHiToVitalik(“Vitalik”)
,它會(huì)返回“Hi!”墓拜。而如果調(diào)用的時(shí)候使用了其他參數(shù)港柜,它則會(huì)拋出錯(cuò)誤并停止執(zhí)行。
因此咳榜,在調(diào)用一個(gè)函數(shù)之前夏醉,用 require
驗(yàn)證前置條件是非常有必要的。
實(shí)戰(zhàn)演練
在我們的僵尸游戲中涌韩,我們不希望用戶通過(guò)反復(fù)調(diào)用 createRandomZombie
來(lái)給他們的軍隊(duì)創(chuàng)建無(wú)限多個(gè)僵尸 —— 這將使得游戲非常無(wú)聊畔柔。
我們使用了 require
來(lái)確保這個(gè)函數(shù)只有在每個(gè)用戶第一次調(diào)用它的時(shí)候執(zhí)行,用以創(chuàng)建初始僵尸臣樱。
- 1靶擦、在
createRandomZombie
的前面放置require
語(yǔ)句。 使得函數(shù)先檢查ownerZombieCount [msg.sender]
的值為 0 雇毫,不然就拋出一個(gè)錯(cuò)誤玄捕。
注意:在 Solidity 中,關(guān)鍵詞放置的順序并不重要
- 雖然參數(shù)的兩個(gè)位置是等效的棚放。 但是枚粘,由于我們的答案檢查器比較呆板,它只能認(rèn)定其中一個(gè)為正確答案
- 于是在這里席吴,我們就約定把
ownerZombieCount [msg.sender]
放前面吧
pragma solidity ^0.4.19;
contract ZombieFactory {
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) private {
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 判斷
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
_createZombie(_name, randDna);
}
}
四赌结、繼承 Inheritance
我們的游戲代碼越來(lái)越長(zhǎng)捞蛋。 當(dāng)代碼過(guò)于冗長(zhǎng)的時(shí)候孝冒,最好將代碼和邏輯分拆到多個(gè)不同的合約中
柬姚,以便于管理。
有個(gè)讓 Solidity
的代碼易于管理的功能庄涡,就是合約 inheritance
(繼承):
contract Doge {
function catchphrase() public returns (string) {
return "So Wow CryptoDoge";
}
}
contract BabyDoge is Doge {
function anotherCatchphrase() public returns (string) {
return "Such Moon BabyDoge";
}
}
由于 BabyDoge
是從 Doge
那里 inherits
(繼承)過(guò)來(lái)的量承。 這意味著當(dāng)你編譯和部署了 BabyDoge
,它將可以訪問(wèn) catchphrase()
和 anotherCatchphrase()
和其他我們?cè)?Doge
中定義的其他公共函數(shù)穴店。
這可以用于邏輯繼承(比如表達(dá)子類的時(shí)候撕捍,Cat
是一種 Animal
)。 但也可以簡(jiǎn)單地將類似的邏輯組合到不同的合約中以組織代碼泣洞。
實(shí)戰(zhàn)演練
在接下來(lái)的章節(jié)中忧风,我們將要為僵尸實(shí)現(xiàn)各種功能,讓它可以“獵食”和“繁殖”球凰。 通過(guò)將這些運(yùn)算放到父類 ZombieFactory
中狮腿,使得所有 ZombieFactory
的繼承者合約都可以使用這些方法。
在 ZombieFactory
下創(chuàng)建一個(gè)叫 ZombieFeeding
的合約呕诉,它是繼承自 ZombieFactory
合約的缘厢。
pragma solidity ^0.4.19;
contract ZombieFactory {
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) private {
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);
_createZombie(_name, randDna);
}
}
// Start here (合約繼承)
contract ZombieFeeding is ZombieFactory {
}
五、引入Import
在這一節(jié)中甩挫,我們將對(duì)上邊那個(gè)很大的合約進(jìn)行拆分贴硫。
上邊的代碼已經(jīng)夠長(zhǎng)了,我們把它分成多個(gè)文件以便于管理伊者。 通常情況下英遭,當(dāng) Solidity
項(xiàng)目中的代碼太長(zhǎng)的時(shí)候我們就是這么做的。
在 Solidity 中亦渗,當(dāng)你有多個(gè)文件并且想把一個(gè)文件導(dǎo)入另一個(gè)文件時(shí)贪绘,可以使用 import
語(yǔ)句:
import "./someothercontract.sol";
contract newContract is SomeOtherContract {
}
這樣當(dāng)我們?cè)诤霞s(contract)目錄下有一個(gè)名為 someothercontract.sol
的文件( ./
就是同一目錄的意思),它就會(huì)被編譯器導(dǎo)入央碟。
實(shí)戰(zhàn)演練
現(xiàn)在我們已經(jīng)建立了一個(gè)多文件架構(gòu)税灌,并用 import 來(lái)讀取來(lái)自另一個(gè)文件中合約的內(nèi)容:
- 1.將
zombiefactory.sol
導(dǎo)入到我們的新文件zombiefeeding.sol
中。
zombiefactory.sol
pragma solidity ^0.4.19;
contract ZombieFactory {
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) private {
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);
_createZombie(_name, randDna);
}
}
zombiefeeding.sol
pragma solidity ^0.4.19;
// put import statement here(導(dǎo)入合約)
// import './zombiefactory.sol'; // 導(dǎo)入另一個(gè)文件不能用單引號(hào)亿虽,只能用雙引號(hào)菱涤,否則會(huì)報(bào)錯(cuò)
import "./zombiefactory.sol";
contract ZombieFeeding is ZombieFactory {
}
六、Storage與Memory
在 Solidity 中洛勉,有兩個(gè)地方可以存儲(chǔ)變量 —— storage
或 memory
粘秆。
Storage
變量是指永久存儲(chǔ)在區(qū)塊鏈
中的變量。 Memory
變量則是臨時(shí)
的收毫,當(dāng)外部函數(shù)對(duì)某合約調(diào)用完成時(shí)攻走,內(nèi)存型變量即被移除殷勘。 你可以把它想象成存儲(chǔ)在你電腦的硬盤或是RAM中數(shù)據(jù)的關(guān)系。
大多數(shù)時(shí)候你都用不到這些關(guān)鍵字昔搂,默認(rèn)情況下 Solidity 會(huì)自動(dòng)處理它們玲销。 狀態(tài)變量(在函數(shù)之外聲明的變量)默認(rèn)為“存儲(chǔ)”形式,并永久寫入?yún)^(qū)塊鏈摘符;而在函數(shù)內(nèi)部聲明的變量是“內(nèi)存”型的贤斜,它們函數(shù)調(diào)用結(jié)束后消失。
然而也有一些情況下逛裤,你需要手動(dòng)聲明存儲(chǔ)類型瘩绒,主要用于處理函數(shù)內(nèi)的 結(jié)構(gòu)體
和 數(shù)組
時(shí):
contract SandwichFactory {
struct Sandwich {
string name;
string status;
}
Sandwich[] sandwiches;
function eatSandwich(uint _index) public {
// Sandwich mySandwich = sandwiches[_index];
// ^ 看上去很直接,不過(guò) Solidity 將會(huì)給出警告
// 告訴你應(yīng)該明確在這里定義 `storage` 或者 `memory`带族。
// 所以你應(yīng)該明確定義 `storage`:
Sandwich storage mySandwich = sandwiches[_index];
// ...這樣 `mySandwich` 是指向 `sandwiches[_index]`的指針
// 在存儲(chǔ)里锁荔,另外...
mySandwich.status = "Eaten!";
// ...這將永久把 `sandwiches[_index]` 變?yōu)閰^(qū)塊鏈上的存儲(chǔ)
// 如果你只想要一個(gè)副本,可以使用`memory`:
Sandwich memory anotherSandwich = sandwiches[_index + 1];
// ...這樣 `anotherSandwich` 就僅僅是一個(gè)內(nèi)存里的副本了
// 另外
anotherSandwich.status = "Eaten!";
// ...將僅僅修改臨時(shí)變量蝙砌,對(duì) `sandwiches[_index + 1]` 沒(méi)有任何影響
// 不過(guò)你可以這樣做:
sandwiches[_index + 1] = anotherSandwich;
// ...如果你想把副本的改動(dòng)保存回區(qū)塊鏈存儲(chǔ)
}
}
如果你還沒(méi)有完全理解究竟應(yīng)該使用哪一個(gè)阳堕,也不用擔(dān)心 —— 在本教程中,我們將告訴你何時(shí)使用 storage
或是 memory
拍霜,并且當(dāng)你不得不使用到這些關(guān)鍵字的時(shí)候嘱丢,Solidity 編譯器也發(fā)警示提醒你的。
現(xiàn)在祠饺,只要知道在某些場(chǎng)合下也需要你顯式地聲明 storage
或 memory
就夠了越驻!
實(shí)戰(zhàn)演練
是時(shí)候給我們的僵尸增加“獵食”和“繁殖”功能了!
當(dāng)一個(gè)僵尸獵食其他生物體時(shí)道偷,它自身的DNA將與獵物生物的DNA結(jié)合在一起缀旁,形成一個(gè)新的僵尸DNA。
- 1勺鸦、創(chuàng)建一個(gè)名為
feedAndMultiply
的函數(shù)并巍。 使用兩個(gè)參數(shù):_zombieId
( uint類型 )和_targetDna
(也是 uint 類型)。 設(shè)置屬性為 public 的换途。 - 2懊渡、我們不希望別人用我們的僵尸去捕獵。 首先军拟,我們確保對(duì)自己僵尸的所有權(quán)剃执。 通過(guò)添加一個(gè)
require
語(yǔ)句來(lái)確保msg.sender
只能是這個(gè)僵尸的主人(類似于我們?cè)?createRandomZombie 函數(shù)中做過(guò)的那樣)。
注意:同樣懈息,因?yàn)槲覀兊拇鸢笝z查器比較呆萌肾档,只認(rèn)識(shí)把
msg.sender
放在前面的答案,如果你切換了參數(shù)的順序,它就不認(rèn)得了怒见。 但你正常編碼時(shí)俗慈,如何安排參數(shù)順序都是正確的。
- 1遣耍、為了獲取這個(gè)僵尸的DNA闺阱,我們的函數(shù)需要聲明一個(gè)名為
myZombie
數(shù)據(jù)類型為Zombie的本地變量(這是一個(gè) storage 型的指針)。 將其值設(shè)定為在 zombies 數(shù)組中索引為_zombieId
所指向的值配阵。
到目前為止馏颂,包括函數(shù)結(jié)束符 }
的那一行示血, 總共4行代碼棋傍。
zombiefeeding.sol
pragma solidity ^0.4.19;
import "./zombiefactory.sol";
contract ZombieFeeding is ZombieFactory {
// Start here
function feedAndMultiply(uint _zombieId, uint _targetDna) public {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
}
}
七、實(shí)戰(zhàn)應(yīng)用
我們來(lái)把 feedAndMultiply
函數(shù)寫完吧难审。
獲取新的僵尸DNA的公式很簡(jiǎn)單:計(jì)算獵食僵尸DNA和被獵僵尸DNA之間的平均值瘫拣。
示例:
function testDnaSplicing() public {
uint zombieDna = 2222222222222222;
uint targetDna = 4444444444444444;
uint newZombieDna = (zombieDna + targetDna) / 2;
// newZombieDna 將等于 3333333333333333
}
以后,我們也可以讓函數(shù)變得更復(fù)雜些告喊,比方給新的僵尸的 DNA 增加一些隨機(jī)性之類的麸拄。但現(xiàn)在先從最簡(jiǎn)單的開始 —— 以后還可以回來(lái)完善它嘛。
實(shí)戰(zhàn)演練
- 1黔姜、首先我們確保
_targetDna
不長(zhǎng)于16位拢切。要做到這一點(diǎn),我們可以設(shè)置_targetDna
為_targetDna%dnaModulus
秆吵,并且只取其最后16
位數(shù)字淮椰。 - 2、接下來(lái)為我們的函數(shù)聲明一個(gè)名叫 newDna 的 uint類型的變量纳寂,并將其值設(shè)置為 myZombie的 DNA 和 _targetDna 的平均值(如上例所示)主穗。
注意:您可以用
myZombie.name
或myZombie.dna
訪問(wèn)myZombie
的屬性。
- 一旦我們計(jì)算出新的DNA毙芜,再調(diào)用
_createZombie
就可以生成新的僵尸了忽媒。如果你忘了調(diào)用這個(gè)函數(shù)所需要的參數(shù),可以查看zombiefactory.sol
選項(xiàng)卡腋粥。請(qǐng)注意晦雨,需要先給它命名,所以現(xiàn)在我們把新的僵尸的名字設(shè)為NoName - 我們回頭可以編寫一個(gè)函數(shù)來(lái)更改僵尸的名字隘冲。
注意:對(duì)于 Solidity 高手闹瞧,你可能會(huì)注意到我們的代碼存在一個(gè)問(wèn)題。別擔(dān)心对嚼,下一章會(huì)解決這個(gè)問(wèn)題的 ;)
zombiefeeding.sol
pragma solidity ^0.4.19;
import "./zombiefactory.sol";
contract ZombieFeeding is ZombieFactory {
function feedAndMultiply(uint _zombieId, uint _targetDna) public {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
// start here
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
_createZombie("NoName", newDna);
}
}
八夹抗、函數(shù)可見性
我們上面的代碼有問(wèn)題!
編譯的時(shí)候編譯器就會(huì)報(bào)錯(cuò)纵竖。
錯(cuò)誤在于漠烧,我們嘗試從 ZombieFeeding
中調(diào)用 _createZombie
函數(shù)杏愤,但 _createZombie
卻是 ZombieFactory
的 private
(私有)函數(shù)。這意味著任何繼承自 ZombieFactory
的子合約都不能訪問(wèn)它已脓。
internal 和 external
除 public
和 private
屬性之外珊楼,Solidity 還使用了另外兩個(gè)描述函數(shù)可見性的修飾詞:internal
(內(nèi)部) 和 external
(外部)。
internal
和 private
類似度液,不過(guò)厕宗, 如果某個(gè)合約繼承自其父合約,這個(gè)合約即可以訪問(wèn)父合約中定義的“內(nèi)部”函數(shù)堕担。(嘿已慢,這聽起來(lái)正是我們想要的那樣!)霹购。
external
與public
類似佑惠,只不過(guò)這些函數(shù)只能在合約之外調(diào)用 - 它們不能被合約內(nèi)的其他函數(shù)調(diào)用。稍后我們將討論什么時(shí)候使用 external
和 public
齐疙。
聲明函數(shù) internal 或 external 類型的語(yǔ)法膜楷,與聲明 private 和 public類 型相同:
contract Sandwich {
uint private sandwichesEaten = 0;
function eat() internal {
sandwichesEaten++;
}
}
contract BLT is Sandwich {
uint private baconSandwichesEaten = 0;
function eatWithBacon() public returns (string) {
baconSandwichesEaten++;
// 因?yàn)閑at() 是internal 的,所以我們能在這里調(diào)用
eat();
}
}
實(shí)戰(zhàn)演練
- 將
_createZombie()
函數(shù)的屬性從private
改為internal
贞奋, 使得其他的合約也能訪問(wèn)到它赌厅。
我們已經(jīng)成功把你的注意力集中在到zombiefactory.sol
這個(gè)基類合約上了。
pragma solidity ^0.4.19;
contract ZombieFactory {
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;
// 在這里修改函數(shù)的功能 private => internal
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);
_createZombie(_name, randDna);
}
}
九轿塔、與其他合約的交互
是時(shí)候讓我們的僵尸去捕獵特愿! 那僵尸最喜歡的食物是什么呢?
為了做到這一點(diǎn)催训,我們要讀出 CryptoKitties 智能合約中的 kittyDna洽议。這些數(shù)據(jù)是公開存儲(chǔ)在區(qū)塊鏈上的。區(qū)塊鏈?zhǔn)遣皇呛芸幔?/p>
別擔(dān)心 —— 我們的游戲并不會(huì)傷害到任何真正的CryptoKitty漫拭。 我們只 讀取 CryptoKitties 數(shù)據(jù)亚兄,但卻無(wú)法在物理上刪除它。
與其他合約的交互
如果我們的合約需要和區(qū)塊鏈上的其他的合約會(huì)話采驻,則需先定義一個(gè) interface
(接口)审胚。
先舉一個(gè)簡(jiǎn)單的栗子。 假設(shè)在區(qū)塊鏈上有這么一個(gè)合約:
contract LuckyNumber {
mapping(address => uint) numbers;
function setNum(uint _num) public {
numbers[msg.sender] = _num;
}
function getNum(address _myAddress) public view returns (uint) {
return numbers[_myAddress];
}
}
這是個(gè)很簡(jiǎn)單的合約礼旅,您可以用它存儲(chǔ)自己的幸運(yùn)號(hào)碼膳叨,并將其與您的以太坊地址關(guān)聯(lián)。 這樣其他人就可以通過(guò)您的地址查找您的幸運(yùn)號(hào)碼了痘系。
現(xiàn)在假設(shè)我們有一個(gè)外部合約菲嘴,使用 getNum
函數(shù)可讀取其中的數(shù)據(jù)。
首先,我們定義 LuckyNumber
合約的 interface
:
contract NumberInterface {
function getNum(address _myAddress) public view returns (uint);
}
請(qǐng)注意龄坪,這個(gè)過(guò)程雖然看起來(lái)像在定義一個(gè)合約昭雌,但其實(shí)內(nèi)里不同:
首先,我們只聲明了要與之交互的函數(shù) —— 在本例中為 getNum
—— 在其中我們沒(méi)有使用到任何其他的函數(shù)或狀態(tài)變量健田。
其次烛卧,我們并沒(méi)有使用大括號(hào)({
和 }
)定義函數(shù)體,我們單單用分號(hào)(;
)結(jié)束了函數(shù)聲明妓局。這使它看起來(lái)像一個(gè)合約框架总放。
編譯器就是靠這些特征認(rèn)出它是一個(gè)接口的。
在我們的 app 代碼中使用這個(gè)接口好爬,合約就知道其他合約的函數(shù)是怎樣的局雄,應(yīng)該如何調(diào)用,以及可期待什么類型的返回值抵拘。
在下一課中哎榴,我們將真正調(diào)用其他合約的函數(shù)型豁。目前我們只要聲明一個(gè)接口僵蛛,用于調(diào)用 CryptoKitties 合約就行了。
實(shí)戰(zhàn)演練
我們已經(jīng)為你查看過(guò)了 CryptoKitties 的源代碼迎变,并且找到了一個(gè)名為 getKitty
的函數(shù)充尉,它返回所有的加密貓的數(shù)據(jù),包括它的“基因”(我們的僵尸游戲要用它生成新的僵尸)衣形。
該函數(shù)如下所示:
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
) {
Kitty storage kit = kitties[_id];
// if this variable is 0 then it's not gestating
isGestating = (kit.siringWithId != 0);
isReady = (kit.cooldownEndBlock <= block.number);
cooldownIndex = uint256(kit.cooldownIndex);
nextActionAt = uint256(kit.cooldownEndBlock);
siringWithId = uint256(kit.siringWithId);
birthTime = uint256(kit.birthTime);
matronId = uint256(kit.matronId);
sireId = uint256(kit.sireId);
generation = uint256(kit.generation);
genes = kit.genes;
}
這個(gè)函數(shù)看起來(lái)跟我們習(xí)慣的函數(shù)不太一樣驼侠。 它竟然返回了...一堆不同的值! 如果您用過(guò) JavaScript 之類的編程語(yǔ)言谆吴,一定會(huì)感到奇怪 —— 在 Solidity中倒源,您可以讓一個(gè)函數(shù)返回多個(gè)值。
現(xiàn)在我們知道這個(gè)函數(shù)長(zhǎng)什么樣的了句狼,就可以用它來(lái)創(chuàng)建一個(gè)接口:
- 1.定義一個(gè)名為
KittyInterface
的接口笋熬。 請(qǐng)注意,因?yàn)槲覀兪褂昧?contract
關(guān)鍵字腻菇, 這過(guò)程看起來(lái)就像創(chuàng)建一個(gè)新的合約一樣胳螟。 - 2.在interface里定義了
getKitty
函數(shù)(不過(guò)是復(fù)制/粘貼上面的函數(shù),但在returns
語(yǔ)句之后用分號(hào)筹吐,而不是大括號(hào)內(nèi)的所有內(nèi)容糖耸。
zombiefeeding.sol
pragma solidity ^0.4.19;
import "./zombiefactory.sol";
// Create KittyInterface here
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 {
function feedAndMultiply(uint _zombieId, uint _targetDna) public {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
_createZombie("NoName", newDna);
}
}