摘要:以太坊開發高級語言學習。地址以太坊區塊鏈由賬戶組成,你可以把它想象成銀行賬戶。使用很安全,因為它具有以太坊區塊鏈的安全保障除非竊取與以太坊地址相關聯的私鑰,否則是沒有辦法修改其他人的數據的。
以太坊開發高級語言學習。一、映射(Mapping)和地址(Address)
我們通過給數據庫中的僵尸指定“主人”, 來支持“多玩家”模式。
如此一來,我們需要引入2個新的數據類型:mapping(映射) 和 address(地址)。
Addresses(地址)以太坊區塊鏈由 account (賬戶)組成,你可以把它想象成銀行賬戶。一個帳戶的余額是 以太 (在以太坊區塊鏈上使用的幣種),你可以和其他帳戶之間支付和接受以太幣,就像你的銀行帳戶可以電匯資金到其他銀行帳戶一樣。
每個帳戶都有一個“地址”,你可以把它想象成銀行賬號。這是賬戶唯一的標識符,它看起來長這樣:
0x0cE446255506E92DF41614C46F1d6df9Cc969183
我們將在后面的課程中介紹地址的細節,現在你只需要了解地址屬于特定用戶(或智能合約)的。
所以我們可以指定“地址”作為僵尸主人的 ID。當用戶通過與我們的應用程序交互來創建新的僵尸時,新僵尸的所有權被設置到調用者的以太坊地址下。
Mapping(映射)在上一篇博文中,我們看到了 結構體 和 數組 。 映射 是另一種在 Solidity 中存儲有組織數據的方法。
映射是這樣定義的:
//對于金融應用程序,將用戶的余額保存在一個 uint類型的變量中: mapping (address => uint) public accountBalance; //或者可以用來通過userId 存儲/查找的用戶名 mapping (uint => string) userIdToName;
映射本質上是存儲和查找數據所用的鍵-值對。在第一個例子中,鍵是一個 address,值是一個 uint,在第二個例子中,鍵是一個uint,值是一個 string。
實戰演練為了存儲僵尸的所有權,我們會使用到兩個映射:一個記錄僵尸擁有者的地址,另一個記錄某地址所擁有僵尸的數量。
1.創建一個叫做 zombieToOwner 的映射。其鍵是一個uint(我們將根據它的 id 存儲和查找僵尸),值為 address。映射屬性為public。
2.創建一個名為 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 數據類型, 并賦值 16 uint dnaDigits = 16; // 4. 10 的 dnaDigits 次方 uint dnaModulus = 10 ** dnaDigits; // 5.結構體定義 struct Zombie { string name; uint dna; } // 6.數組類型為結構體的公共數組 Zombie[] public zombies; // 13.在這里定義映射 mapping(uint => address) public zombieToOwner; mapping(address => uint) ownerZombieCount; /* // 7.創建函數 function createZombie(string _name, uint _dna){ // 8.使用結構體和數組(初始化全局數組) zombies.push(Zombie(_name, _dna)); } */ // 7.創建函數(改為私有方法) function _createZombie(string _name, uint _dna) private { // 8.使用結構體和數組(初始化全局數組) // zombies.push(Zombie(_name, _dna)); // 12、數組長度減一就是當前的數組ID uint id = zombies.push(Zombie(_name, _dna)) - 1; // 12、這里觸發事件 NewZombie(id, _name, _dna); } // 9.函數修飾符 private, view, returns 返回值 function _generateRandomDna(string _str) private view returns (uint){ // 10.散列并取模 uint rand = uint(keccak256(_str)); // 注意這里需要將string類型轉為uint類型 return rand % dnaModulus; } // 11、綜合函數 function createRandomZombie(string _name) public { uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); } }二、Msg.sender
現在有了一套映射來記錄僵尸的所有權了,我們可以修改 _createZombie 方法來運用它們。
為了做到這一點,我們要用到 msg.sender。
msg.sender在 Solidity 中,有一些全局變量可以被所有函數調用。 其中一個就是 msg.sender,它指的是當前調用者(或智能合約)的 address。
注意:在 Solidity 中,功能執行始終需要從外部調用者開始。 一個合約只會在區塊鏈上什么也不做,除非有人調用其中的函數。所以 msg.sender 總是存在的。
以下是使用 msg.sender 來更新 mapping 的例子:
mapping (address => uint) favoriteNumber; function setMyNumber(uint _myNumber) public { // 更新我們的 `favoriteNumber` 映射來將 `_myNumber`存儲在 `msg.sender`名下 favoriteNumber[msg.sender] = _myNumber; // 存儲數據至映射的方法和將數據存儲在數組相似 } function whatIsMyNumber() public view returns (uint) { // 拿到存儲在調用者地址名下的值 // 若調用者還沒調用 setMyNumber, 則值為 `0` return favoriteNumber[msg.sender]; }
在這個小小的例子中,任何人都可以調用 setMyNumber 在我們的合約中存下一個 uint 并且與他們的地址相綁定。 然后,他們調用 whatIsMyNumber 就會返回他們存儲的 uint。
使用 msg.sender 很安全,因為它具有以太坊區塊鏈的安全保障 —— 除非竊取與以太坊地址相關聯的私鑰,否則是沒有辦法修改其他人的數據的。
實戰演練我們來修改前邊的_createZombie 方法,將僵尸分配給函數調用者吧。
1、首先,在得到新的僵尸 id 后,更新 zombieToOwner 映射,在 id 下面存入 msg.sender。
2、然后,我們為這個 msg.sender 名下的 ownerZombieCount 加 1。
跟在 JavaScript 中一樣, 在 Solidity 中你也可以用 ++ 使 uint 遞增。
uint number = 0; number++; // `number` 現在是 `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表示當前調用者的地址 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
我們成功讓用戶通過調用 createRandomZombie 函數 并輸入一個名字來創建新的僵尸。 但是,如果用戶能持續調用這個函數來創建出無限多個僵尸加入他們的軍團,這游戲就太沒意思了!
于是,我們作出限定:每個玩家只能調用一次這個函數。 這樣一來,新玩家可以在剛開始玩游戲時通過調用它,為其軍團創建初始僵尸。
我們怎樣才能限定每個玩家只調用一次這個函數呢?
答案是使用require。 require使得函數在執行過程中,當不滿足某些條件時拋出錯誤,并停止執行:
function sayHiToVitalik(string _name) public returns (string) { // 比較 _name 是否等于 "Vitalik". 如果不成立,拋出異常并終止程序 // (敲黑板: Solidity 并不支持原生的字符串比較, 我們只能通過比較 // 兩字符串的 keccak256 哈希值來進行判斷) require(keccak256(_name) == keccak256("Vitalik")); // 如果返回 true, 運行如下語句 return "Hi!"; }
如果你這樣調用函數 sayHiToVitalik(“Vitalik”) ,它會返回“Hi!”。而如果調用的時候使用了其他參數,它則會拋出錯誤并停止執行。
因此,在調用一個函數之前,用 require 驗證前置條件是非常有必要的。
實戰演練在我們的僵尸游戲中,我們不希望用戶通過反復調用 createRandomZombie 來給他們的軍隊創建無限多個僵尸 —— 這將使得游戲非常無聊。
我們使用了 require 來確保這個函數只有在每個用戶第一次調用它的時候執行,用以創建初始僵尸。
1、在 createRandomZombie 的前面放置 require 語句。 使得函數先檢查 ownerZombieCount [msg.sender] 的值為 0 ,不然就拋出一個錯誤。
注意:在 Solidity 中,關鍵詞放置的順序并不重要
雖然參數的兩個位置是等效的。 但是,由于我們的答案檢查器比較呆板,它只能認定其中一個為正確答案
于是在這里,我們就約定把 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
我們的游戲代碼越來越長。 當代碼過于冗長的時候,最好將代碼和邏輯分拆到多個不同的合約中,以便于管理。
有個讓 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 (繼承)過來的。 這意味著當你編譯和部署了 BabyDoge,它將可以訪問 catchphrase() 和 anotherCatchphrase()和其他我們在 Doge 中定義的其他公共函數。
這可以用于邏輯繼承(比如表達子類的時候,Cat 是一種 Animal)。 但也可以簡單地將類似的邏輯組合到不同的合約中以組織代碼。
實戰演練在接下來的章節中,我們將要為僵尸實現各種功能,讓它可以“獵食”和“繁殖”。 通過將這些運算放到父類 ZombieFactory 中,使得所有 ZombieFactory 的繼承者合約都可以使用這些方法。
在 ZombieFactory 下創建一個叫 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
在這一節中,我們將對上邊那個很大的合約進行拆分。
上邊的代碼已經夠長了,我們把它分成多個文件以便于管理。 通常情況下,當 Solidity 項目中的代碼太長的時候我們就是這么做的。
在 Solidity 中,當你有多個文件并且想把一個文件導入另一個文件時,可以使用 import 語句:
import "./someothercontract.sol"; contract newContract is SomeOtherContract { }
這樣當我們在合約(contract)目錄下有一個名為 someothercontract.sol 的文件( ./ 就是同一目錄的意思),它就會被編譯器導入。
實戰演練現在我們已經建立了一個多文件架構,并用 import 來讀取來自另一個文件中合約的內容:
1.將 zombiefactory.sol 導入到我們的新文件 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(導入合約) // import "./zombiefactory.sol"; // 導入另一個文件不能用單引號,只能用雙引號,否則會報錯 import "./zombiefactory.sol"; contract ZombieFeeding is ZombieFactory { }六、Storage與Memory
在 Solidity 中,有兩個地方可以存儲變量 —— storage 或 memory。
Storage 變量是指永久存儲在區塊鏈中的變量。 Memory 變量則是臨時的,當外部函數對某合約調用完成時,內存型變量即被移除。 你可以把它想象成存儲在你電腦的硬盤或是RAM中數據的關系。
大多數時候你都用不到這些關鍵字,默認情況下 Solidity 會自動處理它們。 狀態變量(在函數之外聲明的變量)默認為“存儲”形式,并永久寫入區塊鏈;而在函數內部聲明的變量是“內存”型的,它們函數調用結束后消失。
然而也有一些情況下,你需要手動聲明存儲類型,主要用于處理函數內的 結構體 和 數組 時:
contract SandwichFactory { struct Sandwich { string name; string status; } Sandwich[] sandwiches; function eatSandwich(uint _index) public { // Sandwich mySandwich = sandwiches[_index]; // ^ 看上去很直接,不過 Solidity 將會給出警告 // 告訴你應該明確在這里定義 `storage` 或者 `memory`。 // 所以你應該明確定義 `storage`: Sandwich storage mySandwich = sandwiches[_index]; // ...這樣 `mySandwich` 是指向 `sandwiches[_index]`的指針 // 在存儲里,另外... mySandwich.status = "Eaten!"; // ...這將永久把 `sandwiches[_index]` 變為區塊鏈上的存儲 // 如果你只想要一個副本,可以使用`memory`: Sandwich memory anotherSandwich = sandwiches[_index + 1]; // ...這樣 `anotherSandwich` 就僅僅是一個內存里的副本了 // 另外 anotherSandwich.status = "Eaten!"; // ...將僅僅修改臨時變量,對 `sandwiches[_index + 1]` 沒有任何影響 // 不過你可以這樣做: sandwiches[_index + 1] = anotherSandwich; // ...如果你想把副本的改動保存回區塊鏈存儲 } }
如果你還沒有完全理解究竟應該使用哪一個,也不用擔心 —— 在本教程中,我們將告訴你何時使用 storage 或是 memory,并且當你不得不使用到這些關鍵字的時候,Solidity 編譯器也發警示提醒你的。
現在,只要知道在某些場合下也需要你顯式地聲明 storage 或 memory就夠了!
實戰演練是時候給我們的僵尸增加“獵食”和“繁殖”功能了!
當一個僵尸獵食其他生物體時,它自身的DNA將與獵物生物的DNA結合在一起,形成一個新的僵尸DNA。
1、創建一個名為 feedAndMultiply 的函數。 使用兩個參數:_zombieId( uint類型 )和_targetDna (也是 uint 類型)。 設置屬性為 public 的。
2、我們不希望別人用我們的僵尸去捕獵。 首先,我們確保對自己僵尸的所有權。 通過添加一個require 語句來確保 msg.sender 只能是這個僵尸的主人(類似于我們在 createRandomZombie 函數中做過的那樣)。
注意:同樣,因為我們的答案檢查器比較呆萌,只認識把 msg.sender 放在前面的答案,如果你切換了參數的順序,它就不認得了。 但你正常編碼時,如何安排參數順序都是正確的。
1、為了獲取這個僵尸的DNA,我們的函數需要聲明一個名為 myZombie 數據類型為Zombie的本地變量(這是一個 storage 型的指針)。 將其值設定為在 zombies 數組中索引為_zombieId所指向的值。
到目前為止,包括函數結束符 } 的那一行, 總共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]; } }七、實戰應用
我們來把 feedAndMultiply 函數寫完吧。
獲取新的僵尸DNA的公式很簡單:計算獵食僵尸DNA和被獵僵尸DNA之間的平均值。
示例:
function testDnaSplicing() public { uint zombieDna = 2222222222222222; uint targetDna = 4444444444444444; uint newZombieDna = (zombieDna + targetDna) / 2; // newZombieDna 將等于 3333333333333333 }
以后,我們也可以讓函數變得更復雜些,比方給新的僵尸的 DNA 增加一些隨機性之類的。但現在先從最簡單的開始 —— 以后還可以回來完善它嘛。
實戰演練1、首先我們確保 _targetDna 不長于16位。要做到這一點,我們可以設置 _targetDna 為 _targetDna%dnaModulus ,并且只取其最后16位數字。
2、接下來為我們的函數聲明一個名叫 newDna 的 uint類型的變量,并將其值設置為 myZombie的 DNA 和 _targetDna 的平均值(如上例所示)。
注意:您可以用 myZombie.name 或 myZombie.dna 訪問 myZombie 的屬性。
一旦我們計算出新的DNA,再調用 _createZombie 就可以生成新的僵尸了。如果你忘了調用這個函數所需要的參數,可以查看 zombiefactory.sol 選項卡。請注意,需要先給它命名,所以現在我們把新的僵尸的名字設為NoName - 我們回頭可以編寫一個函數來更改僵尸的名字。
注意:對于 Solidity 高手,你可能會注意到我們的代碼存在一個問題。別擔心,下一章會解決這個問題的 ;)
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); } }八、函數可見性
我們上面的代碼有問題!
編譯的時候編譯器就會報錯。
錯誤在于,我們嘗試從 ZombieFeeding 中調用 _createZombie 函數,但 _createZombie 卻是 ZombieFactory 的 private (私有)函數。這意味著任何繼承自 ZombieFactory 的子合約都不能訪問它。
internal 和 external除 public 和 private 屬性之外,Solidity 還使用了另外兩個描述函數可見性的修飾詞:internal(內部) 和 external(外部)。
internal 和 private 類似,不過, 如果某個合約繼承自其父合約,這個合約即可以訪問父合約中定義的“內部”函數。(嘿,這聽起來正是我們想要的那樣!)。
external 與public 類似,只不過這些函數只能在合約之外調用 - 它們不能被合約內的其他函數調用。稍后我們將討論什么時候使用 external 和 public。
聲明函數 internal 或 external 類型的語法,與聲明 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++; // 因為eat() 是internal 的,所以我們能在這里調用 eat(); } }實戰演練
將 _createZombie() 函數的屬性從 private 改為 internal , 使得其他的合約也能訪問到它。
我們已經成功把你的注意力集中在到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; // 在這里修改函數的功能 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); } }九、與其他合約的交互
是時候讓我們的僵尸去捕獵! 那僵尸最喜歡的食物是什么呢?
為了做到這一點,我們要讀出 CryptoKitties 智能合約中的 kittyDna。這些數據是公開存儲在區塊鏈上的。區塊鏈是不是很酷?
別擔心 —— 我們的游戲并不會傷害到任何真正的CryptoKitty。 我們只 讀取 CryptoKitties 數據,但卻無法在物理上刪除它。
與其他合約的交互如果我們的合約需要和區塊鏈上的其他的合約會話,則需先定義一個 interface (接口)。
先舉一個簡單的栗子。 假設在區塊鏈上有這么一個合約:
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]; } }
這是個很簡單的合約,您可以用它存儲自己的幸運號碼,并將其與您的以太坊地址關聯。 這樣其他人就可以通過您的地址查找您的幸運號碼了。
現在假設我們有一個外部合約,使用 getNum 函數可讀取其中的數據。
首先,我們定義 LuckyNumber 合約的 interface :
contract NumberInterface { function getNum(address _myAddress) public view returns (uint); }
請注意,這個過程雖然看起來像在定義一個合約,但其實內里不同:
首先,我們只聲明了要與之交互的函數 —— 在本例中為 getNum —— 在其中我們沒有使用到任何其他的函數或狀態變量。
其次,我們并沒有使用大括號({ 和 })定義函數體,我們單單用分號(;)結束了函數聲明。這使它看起來像一個合約框架。
編譯器就是靠這些特征認出它是一個接口的。
在我們的 app 代碼中使用這個接口,合約就知道其他合約的函數是怎樣的,應該如何調用,以及可期待什么類型的返回值。
在下一課中,我們將真正調用其他合約的函數。目前我們只要聲明一個接口,用于調用 CryptoKitties 合約就行了。
實戰演練我們已經為你查看過了 CryptoKitties 的源代碼,并且找到了一個名為 getKitty的函數,它返回所有的加密貓的數據,包括它的“基因”(我們的僵尸游戲要用它生成新的僵尸)。
該函數如下所示:
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; }
這個函數看起來跟我們習慣的函數不太一樣。 它竟然返回了...一堆不同的值! 如果您用過 JavaScript 之類的編程語言,一定會感到奇怪 —— 在 Solidity中,您可以讓一個函數返回多個值。
現在我們知道這個函數長什么樣的了,就可以用它來創建一個接口:
1.定義一個名為 KittyInterface 的接口。 請注意,因為我們使用了 contract 關鍵字, 這過程看起來就像創建一個新的合約一樣。
2.在interface里定義了 getKitty 函數(不過是復制/粘貼上面的函數,但在 returns 語句之后用分號,而不是大括號內的所有內容。
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); } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/24137.html
摘要:接上一節,繼續學習高級語法。添加語句,并且將后兩位數替換為添加參數四部署以太坊實現實現我們只用編譯和部署,就可以將這個合約部署到以太坊了。 接上一節,繼續學習solidity高級語法。 一、使用接口 繼續前面上一節 NumberInterface 的例子,我們既然將接口定義為: contract NumberInterface { function getNum(address _...
摘要:原文發表于以太坊智能合約開發第一篇對語法的支持最近在研究以太坊智能合約的開發。是一種語法類似的高級語言,它被設計成以編譯的方式生成以太坊虛擬機代碼。 原文發表于:以太坊智能合約開發第一篇:IDE對solidity語法的支持 最近在研究以太坊智能合約的開發。隨著研究的深入,準備寫一個系列教程,將我的實際經驗與大家分享,供大家參考借鑒。 solidity是什么? 以太坊官方推薦使用Sol...
摘要:接上篇文章,這里繼續學習高級理論。實戰演練我們來寫一個返回某玩家的整個僵尸軍團的函數。但這樣每做一筆交易,都會改變僵尸軍團的秩序。在這里開始五可支付截至目前,我們只接觸到很少的函數修飾符。 接上篇文章,這里繼續學習Solidity高級理論。 一、深入函數修飾符 接下來,我們將添加一些輔助方法。我們為您創建了一個名為 zombiehelper.sol 的新文件,并且將 zombiefee...
摘要:如果當前在以太坊上有大量掛起事務或者用戶發送了過低的價格,我們的事務可能需要等待數個區塊才能被包含進去,往往可能花費數分鐘。 接上篇 Web3.js,這節課繼續學習Web3.js 的相關知識。 一、發送事務 這下我們的界面能檢測用戶的 MetaMask 賬戶,并自動在首頁顯示它們的僵尸大軍了,有沒有很棒? 現在我們來看看用 send 函數來修改我們智能合約里面的數據。 相對 call...
閱讀 797·2023-04-25 22:57
閱讀 3051·2021-11-23 10:03
閱讀 613·2021-11-22 15:24
閱讀 3156·2021-11-02 14:47
閱讀 2901·2021-09-10 11:23
閱讀 3115·2021-09-06 15:00
閱讀 3936·2019-08-30 15:56
閱讀 3322·2019-08-30 15:52