以太坊開發實戰學習-solidity語法(二)

以太坊開發高級語言學習。

1、映射(Mapping)和地址(Address)

咱們經過給數據庫中的殭屍指定「主人」, 來支持「多玩家」模式。git

如此一來,咱們須要引入2個新的數據類型:mapping(映射) 和 address(地址)。數據庫

Addresses(地址)

以太坊區塊鏈由 account (帳戶)組成,你能夠把它想象成銀行帳戶。一個賬戶的餘額是 以太 (在以太坊區塊鏈上使用的幣種),你能夠和其餘賬戶之間支付和接受以太幣,就像你的銀行賬戶能夠電匯資金到其餘銀行賬戶同樣。編程

每一個賬戶都有一個「地址」,你能夠把它想象成銀行帳號。這是帳戶惟一的標識符,它看起來長這樣:數組

0x0cE446255506E92DF41614C46F1d6df9Cc969183

咱們將在後面的課程中介紹地址的細節,如今你只須要了解地址屬於特定用戶(或智能合約)的。安全

因此咱們能夠指定「地址」做爲殭屍主人的 ID。當用戶經過與咱們的應用程序交互來建立新的殭屍時,新殭屍的全部權被設置到調用者的以太坊地址下。架構

Mapping(映射)

在上一篇博文中,咱們看到了 結構體數組映射 是另外一種在 Solidity 中存儲有組織數據的方法。app

映射是這樣定義的:框架

//對於金融應用程序,將用戶的餘額保存在一個 uint類型的變量中:
mapping (address => uint) public accountBalance;

//或者能夠用來經過userId 存儲/查找的用戶名
mapping (uint => string) userIdToName;

映射本質上是存儲和查找數據所用的鍵-值對。在第一個例子中,鍵是一個 address,值是一個 uint,在第二個例子中,鍵是一個uint,值是一個 stringdom

實戰演練

爲了存儲殭屍的全部權,咱們會使用到兩個映射:一個記錄殭屍擁有者的地址,另外一個記錄某地址所擁有殭屍的數量。編程語言

  • 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));
         
        // 十二、數組長度減一就是當前的數組ID
        uint id = zombies.push(Zombie(_name, _dna)) - 1;

        // 十二、這裏觸發事件
        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;
    }
        
     
     // 十一、綜合函數
    function createRandomZombie(string _name) public {
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

2、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 方法,將殭屍分配給函數調用者吧。

  • 一、首先,在獲得新的殭屍 id 後,更新 zombieToOwner 映射,在 id 下面存入 msg.sender
  • 二、而後,咱們爲這個 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);
    }

}

3、Require

咱們成功讓用戶經過調用 createRandomZombie 函數 並輸入一個名字來建立新的殭屍。 可是,若是用戶能持續調用這個函數來建立出無限多個殭屍加入他們的軍團,這遊戲就太沒意思了!

因而,咱們做出限定:每一個玩家只能調用一次這個函數。 這樣一來,新玩家能夠在剛開始玩遊戲時經過調用它,爲其軍團建立初始殭屍。

咱們怎樣才能限定每一個玩家只調用一次這個函數呢?

答案是使用requirerequire使得函數在執行過程當中,當不知足某些條件時拋出錯誤,並中止執行:

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 來確保這個函數只有在每一個用戶第一次調用它的時候執行,用以建立初始殭屍。

  • 一、在 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);
    }

}

4、繼承 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 {

}

5、引入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 {

}

6、Storage與Memory

在 Solidity 中,有兩個地方能夠存儲變量 —— storagememory

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 編譯器也發警示提醒你的。

如今,只要知道在某些場合下也須要你顯式地聲明 storagememory就夠了!

實戰演練

是時候給咱們的殭屍增長「獵食」和「繁殖」功能了!

當一個殭屍獵食其餘生物體時,它自身的DNA將與獵物生物的DNA結合在一塊兒,造成一個新的殭屍DNA。

  • 一、建立一個名爲 feedAndMultiply 的函數。 使用兩個參數:_zombieId( uint類型 )和_targetDna (也是 uint 類型)。 設置屬性爲 public 的。
  • 二、咱們不但願別人用咱們的殭屍去捕獵。 首先,咱們確保對本身殭屍的全部權。 經過添加一個require 語句來確保 msg.sender 只能是這個殭屍的主人(相似於咱們在 createRandomZombie 函數中作過的那樣)。
注意:一樣,由於咱們的答案檢查器比較呆萌,只認識把 msg.sender 放在前面的答案,若是你切換了參數的順序,它就不認得了。 但你正常編碼時,如何安排參數順序都是正確的。
  • 一、爲了獲取這個殭屍的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];
  }

}

7、實戰應用

咱們來把 feedAndMultiply 函數寫完吧。

獲取新的殭屍DNA的公式很簡單:計算獵食殭屍DNA和被獵殭屍DNA之間的平均值。
示例:

function testDnaSplicing() public {
  uint zombieDna = 2222222222222222;
  uint targetDna = 4444444444444444;
  uint newZombieDna = (zombieDna + targetDna) / 2;
  // newZombieDna 將等於 3333333333333333
}

之後,咱們也可讓函數變得更復雜些,比方給新的殭屍的 DNA 增長一些隨機性之類的。但如今先從最簡單的開始 —— 之後還能夠回來完善它嘛。

實戰演練

  • 一、首先咱們確保 _targetDna 不長於16位。要作到這一點,咱們能夠設置 _targetDna_targetDna%dnaModulus ,而且只取其最後16位數字。
  • 二、接下來爲咱們的函數聲明一個名叫 newDna 的 uint類型的變量,並將其值設置爲 myZombie的 DNA 和 _targetDna 的平均值(如上例所示)。
注意:您能夠用 myZombie.namemyZombie.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);

  }

}

8、函數可見性

咱們上面的代碼有問題!

編譯的時候編譯器就會報錯。

錯誤在於,咱們嘗試從 ZombieFeeding 中調用 _createZombie 函數,但 _createZombie 倒是 ZombieFactoryprivate (私有)函數。這意味着任何繼承自 ZombieFactory 的子合約都不能訪問它。

internal 和 external

publicprivate 屬性以外,Solidity 還使用了另外兩個描述函數可見性的修飾詞:internal(內部) 和 external(外部)。

internalprivate 相似,不過, 若是某個合約繼承自其父合約,這個合約便可以訪問父合約中定義的「內部」函數。(嘿,這聽起來正是咱們想要的那樣!)。

externalpublic 相似,只不過這些函數只能在合約以外調用 - 它們不能被合約內的其餘函數調用。稍後咱們將討論何時使用 externalpublic

聲明函數 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);
    }

}

9、與其餘合約的交互

是時候讓咱們的殭屍去捕獵! 那殭屍最喜歡的食物是什麼呢?

爲了作到這一點,咱們要讀出 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);
  }

}
相關文章
相關標籤/搜索