国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

以太坊開發實戰學習-高級Solidity理論 (五)

sushi / 1386人閱讀

摘要:接上篇文章,這里繼續學習高級理論。實戰演練我們來寫一個返回某玩家的整個僵尸軍團的函數。但這樣每做一筆交易,都會改變僵尸軍團的秩序。在這里開始五可支付截至目前,我們只接觸到很少的函數修飾符。

接上篇文章,這里繼續學習Solidity高級理論。
一、深入函數修飾符

接下來,我們將添加一些輔助方法。我們為您創建了一個名為 zombiehelper.sol 的新文件,并且將 zombiefeeding.sol 導入其中,這讓我們的代碼更整潔。

我們打算讓僵尸在達到一定水平后,獲得特殊能力。但是達到這個小目標,我們還需要學一學什么是“函數修飾符”。

帶參的函數修飾符

之前我們已經讀過一個簡單的函數修飾符了:onlyOwner。函數修飾符也可以帶參數。例如:

// 存儲用戶年齡的映射
mapping (uint => uint) public age;

// 限定用戶年齡的修飾符
modifier olderThan(uint _age, uint _userId) {
  require(age[_userId] >= _age);
  _;
}

// 必須年滿16周歲才允許開車 (至少在美國是這樣的).
// 我們可以用如下參數調用`olderThan` 修飾符:
function driveCar(uint _userId) public olderThan(16, _userId) {
  // 其余的程序邏輯
}

看到了吧, olderThan 修飾符可以像函數一樣接收參數,是“宿主”函數 driveCar 把參數傳遞給它的修飾符的。

來,我們自己生產一個修飾符,通過傳入的level參數來限制僵尸使用某些特殊功能。

實戰演練

1、在ZombieHelper 中,創建一個名為 aboveLevel 的modifier,它接收2個參數, _level (uint類型) 以及 _zombieId (uint類型)。

2、運用函數邏輯確保僵尸 zombies[_zombieId].level 大于或等于 _level。

3、記住,修飾符的最后一行為 _;表示修飾符調用結束后返回,并執行調用函數余下的部分

pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  // 在這里開始
  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

}
函數修飾符應用

現在讓我們設計一些使用 aboveLevel 修飾符的函數。

作為游戲,您得有一些措施激勵玩家們去升級他們的僵尸:

2級以上的僵尸,玩家可給他們改名。

20級以上的僵尸,玩家能給他們定制的 DNA。

是實現這些功能的時候了。以下是上一課的示例代碼,供參考:

// 存儲用戶年齡的映射
mapping (uint => uint) public age;

// 限定用戶年齡的修飾符
modifier olderThan(uint _age, uint _userId) {
  require (age[_userId] >= _age);
  _;
}

// 必須年滿16周歲才允許開車 (至少在美國是這樣的).
// 我們可以用如下參數調用`olderThan` 修飾符:
function driveCar(uint _userId) public olderThan(16, _userId) {
  // 其余的程序邏輯
}
實戰演練

1、創建一個名為 changeName 的函數。它接收2個參數:_zombieId(uint類型)以及 _newName(string類型),可見性為 external。它帶有一個 aboveLevel 修飾符,調用的時候通過 _level 參數傳入2, 當然,別忘了同時傳 _zombieId 參數。

2、在這個函數中,首先我們用 require 語句,驗證 msg.sender 是否就是 zombieToOwner [_zombieId]

3、然后函數將 zombies[_zombieId] .name 設置為 _newName

4、在 changeName 下創建另一個名為 changeDna 的函數。它的定義和內容幾乎和 changeName 相同,不過它第二個參數是 _newDna(uint類型),在修飾符 aboveLevel 的 _level 參數中傳遞 20 。現在,他可以把僵尸的 dna 設置為 _newDna 了。

zombiehelper.sol

pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  // 在這里開始
  function changeName(uint _zombieId, string _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;

  }

}
二、利用view節省Gas

現在需要添加的一個功能是:我們的 DApp 需要一個方法來查看某玩家的整個僵尸軍團 - 我們稱之為 getZombiesByOwner

實現這個功能只需從區塊鏈中讀取數據,所以它可以是一個 view 函數。這讓我們不得不回顧一下“gas優化”這個重要話題。

“view” 函數不花 “gas”

當玩家從外部調用一個view函數,是不需要支付一分 gas 的。

這是因為 view 函數不會真正改變區塊鏈上的任何數據 - 它們只是讀取。因此用 view 標記一個函數,意味著告訴 web3.js運行這個函數只需要查詢你的本地以太坊節點,而不需要在區塊鏈上創建一個事務(事務需要運行在每個節點上,因此花費 gas)

稍后我們將介紹如何在自己的節點上設置 web3.js。但現在,你關鍵是要記住,在所能只讀的函數上標記上表示“只讀”的external view 聲明,就能為你的玩家減少在 DApp 中 gas 用量。

注意:如果一個 view 函數在另一個函數的內部被調用,而調用函數與 view 函數的不屬于同一個合約,也會產生調用成本。這是因為如果主調函數在以太坊創建了一個事務,它仍然需要逐個節點去驗證。所以標記為 view 的函數只有在外部調用時才是免費的。
實戰演練

我們來寫一個”返回某玩家的整個僵尸軍團“的函數。當我們從 web3.js 中調用它,即可顯示某一玩家的個人資料頁。

這個函數的邏輯有點復雜,我們需要好幾個章節來描述它的實現。

1、創建一個名為 getZombiesByOwner 的新函數。它有一個名為 _owneraddress 類型的參數。

2、將其申明為 external view 函數,這樣當玩家從 web3.js 中調用它時,不需要花費任何 gas。

3、函數需要返回一個uint []uint數組)。

先這么聲明著,我們將在下一章中填充函數體。
zombiehelper.sol

pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  function changeName(uint _zombieId, string _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 []) {

  }

}
三、存儲非常昂貴

Solidity 使用 storage(存儲)是相當昂貴的,”寫入“操作尤其貴。

這是因為,無論是寫入還是更改一段數據, 這都將永久性地寫入區塊鏈。”永久性“啊!需要在全球數千個節點的硬盤上存入這些數據,隨著區塊鏈的增長,拷貝份數更多,存儲量也就越大。這是需要成本的!

為了降低成本,不到萬不得已,避免將數據寫入存儲。這也會導致效率低下的編程邏輯 - 比如每次調用一個函數,都需要在 memory(內存) 中重建一個數組,而不是簡單地將上次計算的數組給存儲下來以便快速查找。

在大多數編程語言中,遍歷大數據集合都是昂貴的。但是在 Solidity 中,使用一個標記了external view的函數,遍歷比 storage 要便宜太多,因為 view 函數不會產生任何花銷。 (gas可是真金白銀啊!)。

我們將在下一章討論 for 循環,現在我們來看一下看如何如何在內存中聲明數組。

在內存中聲明數組

在數組后面加上 memory 關鍵字, 表明這個數組是僅僅在內存中創建,不需要寫入外部存儲,并且在函數調用結束時它就解散了。與在程序結束時把數據保存進 storage 的做法相比,內存運算可以大大節省gas開銷 -- 把這數組放在view里用,完全不用花錢。

以下是申明一個內存數組的例子:

function getArray() external pure returns(uint[]) {
  // 初始化一個長度為3的內存數組
  uint[] memory values = new uint[](3);
  // 賦值
  values.push(1);
  values.push(2);
  values.push(3);
  // 返回數組
  return values;
}

這個小例子展示了一些語法規則,下一章中,我們將通過一個實際用例,展示它和 for 循環結合的做法。

注意:內存數組 必須 用長度參數(在本例中為3)創建。目前不支持 array.push()之類的方法調整數組大小,在未來的版本可能會支持長度修改。
實戰演練

我們要要創建一個名為 getZombiesByOwner 的函數,它以uint []數組的形式返回某一用戶所擁有的所有僵尸。

1、聲明一個名為resultuint [] memory (內存變量數組)

2、將其設置為一個新的 uint 類型數組。數組的長度為該 _owner 所擁有的僵尸數量,這可通過調用 ownerZombieCount [_ owner] 來獲取。

3、函數結束,返回 result 。目前它只是個空數列,我們到下一章去實現它。

zombiehelper.sol

pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  function changeName(uint _zombieId, string _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[]) {
    // 在這里開始
    uint[] memory result = new uint[](ownerZombieCount[_ owner]);

    return result;
  }

}
四、For循環

在之前的博文中,我們提到過,函數中使用的數組是運行時在內存中通過 for 循環實時構建,而不是預先建立在存儲中的。

為什么要這樣做呢?

為了實現 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 函數將會便宜得多,因為我們不需要挪動存儲里的僵尸數組重新排序,總體上這個方法會更便宜,雖然有點反直覺。

使用for循環

for循環的語法在 Solidity 和 JavaScript 中類似。

來看一個創建偶數數組的例子:

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] 的數組。

實戰演練

我們回到 getZombiesByOwner 函數, 通過一條 for 循環來遍歷 DApp 中所有的僵尸, 將給定的‘用戶id"與每頭僵尸的‘主人’進行比較,并在函數返回之前將它們推送到我們的result 數組中。

1.聲明一個變量 counter,屬性為 uint,設其值為 0 。我們用這個變量作為 result 數組的索引。

2.聲明一個 for 循環, 從 uint i = 0 到 i

3.在每一輪 for 循環中,用一個 if 語句來檢查 zombieToOwner [i] 是否等于 _owner。這會比較兩個地址是否匹配。

4.在 if 語句中:

通過將 result [counter] 設置為 i,將僵尸ID添加到 result 數組中。

將counter加1(參見上面的for循環示例)。

就是這樣 - 這個函數能返回 _owner 所擁有的僵尸數組,不花一分錢 gas。

pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  function changeName(uint _zombieId, string _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[]) {
    uint[] memory result = new uint[](ownerZombieCount[_owner]);
    
    // 在這里開始
    uint counter = 0;
    for(uint i = 0; i < zombies.length; i++) {
      if(zombieToOwner[i] == _owner)
      {
        result[counter] = i;
        counter ++;


      }

    }

    return result;
  }

}
五、可支付

截至目前,我們只接觸到很少的 函數修飾符。 要記住所有的東西很難,所以我們來個概覽:

1、我們有決定函數何時和被誰調用的可見性修飾符: private 意味著它只能被合約內部調用internal 就像 private 但是也能被繼承的合約調用external 只能從合約外部調用;最后 public 可以在任何地方調用,不管是內部還是外部

2、我們也有狀態修飾符, 告訴我們函數如何和區塊鏈交互: view 告訴我們運行這個函數不會更改和保存任何數據; pure 告訴我們這個函數不但不會往區塊鏈寫數據,它甚至不從區塊鏈讀取數據。這兩種在被從合約外部調用的時候都不花費任何gas(但是它們在被內部其他函數調用的時候將會耗費gas)。

3、然后我們有了自定義的 modifiers,例如在第三課學習的: onlyOwneraboveLevel。 對于這些修飾符我們可以自定義其對函數的約束邏輯。

這些修飾符可以同時作用于一個函數定義上:

function test() external view onlyOwner anotherModifier { /* ... */ }

在這一章,我們來學習一個新的修飾符 payable.

payable修飾符

payable 方法是讓 Solidity 和以太坊變得如此酷的一部分 —— 它們是一種可以接收以太的特殊函數。

先放一下。當你在調用一個普通網站服務器上的API函數的時候,你無法用你的函數傳送美元——你也不能傳送比特幣。

但是在以太坊中, 因為錢 (以太), 數據 (事務負載), 以及合約代碼本身都存在于以太坊。你可以在同時調用函數 并付錢給另外一個合約。

這就允許出現很多有趣的邏輯, 比如向一個合約要求支付一定的錢來運行一個函數。

示例
contract OnlineStore {
  function buySomething() external payable {
    // 檢查以確定0.001以太發送出去來運行函數:
    require(msg.value == 0.001 ether);
    // 如果為真,一些用來向函數調用者發送數字內容的邏輯
    transferThing(msg.sender);
  }
}

在這里,msg.value 是一種可以查看向合約發送了多少以太的方法,另外 ether 是一個內建單元。

這里發生的事是,一些人會從 web3.js 調用這個函數 (從DApp的前端), 像這樣 :

// 假設 `OnlineStore` 在以太坊上指向你的合約:
OnlineStore.buySomething().send(from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001))

注意這個 value 字段, JavaScript 調用來指定發送多少(0.001)以太。如果把事務想象成一個信封,你發送到函數的參數就是信的內容。 添加一個 value 很像在信封里面放錢 —— 信件內容和錢同時發送給了接收者。

注意: 如果一個函數沒標記為payable, 而你嘗試利用上面的方法發送以太,函數將拒絕你的事務。
實戰演練

我們來在僵尸游戲里面創建一個payable 函數。

假定在我們的游戲中,玩家可以通過支付ETH來升級他們的僵尸。ETH將存儲在你擁有的合約中 —— 一個簡單明了的例子,向你展示你可以通過自己的游戲賺錢。

1、定義一個 uint ,命名為 levelUpFee, 將值設定為 0.001 ether

2、定義一個名為 levelUp 的函數。 它將接收一個 uint 參數 _zombieId。 函數應該修飾為 external 以及 payable

3、這個函數首先應該 require 確保 msg.value 等于 levelUpFee

然后它應該增加僵尸的 level: zombies[_zombieId].level++

zombiehelper.sol

pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  // 1. 在這里定義 levelUpFee
  uint levelUpFee = 0.001 ether;

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  // 2. 在這里插入 levelUp 函數 
  function levelUp(uint _zombieId) external payable {
    // 檢查以確定0.001以太發送出去來運行函數:
    require(msg.value == levelUpFee);

    zombies[_zombieId].level++;
  }

  function changeName(uint _zombieId, string _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[]) {
    uint[] memory result = new uint[](ownerZombieCount[_owner]);
    uint counter = 0;
    for (uint i = 0; i < zombies.length; i++) {
      if (zombieToOwner[i] == _owner) {
        result[counter] = i;
        counter++;
      }
    }
    return result;
  }

}
六、提現

在上一節,我們學習了如何向合約發送以太,那么在發送之后會發生什么呢?

在你發送以太之后,它將被存儲進以合約的以太坊賬戶中, 并凍結在哪里 —— 除非你添加一個函數來從合約中把以太提現。

你可以寫一個函數來從合約中提現以太,類似這樣:

contract GetPaid is Ownable {
  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }
}

注意我們使用 Ownable 合約中的 owneronlyOwner,假定它已經被引入了。

你可以通過 transfer 函數向一個地址發送以太, 然后 this.balance 將返回當前合約存儲了多少以太。 所以如果100個用戶每人向我們支付1以太, this.balance 將是100以太。

你可以通過 transfer 向任何以太坊地址付錢。 比如,你可以有一個函數在 msg.sender 超額付款的時候給他們退錢:

uint itemFee = 0.001 ether;
msg.sender.transfer(msg.value - itemFee);

或者在一個有賣家和賣家的合約中, 你可以把賣家的地址存儲起來, 當有人買了它的東西的時候,把買家支付的錢發送給它 seller.transfer(msg.value)

有很多例子來展示什么讓以太坊編程如此之酷 —— 你可以擁有一個不被任何人控制的去中心化市場。

實戰演練

1、在我們的合約里創建一個 withdraw 函數,它應該幾乎和上面的GetPaid一樣。

2、以太的價格在過去幾年內翻了十幾倍,在我們寫這個教程的時候 0.01 以太相當于1美元,如果它再翻十倍 0.001 以太將是10美元,那我們的游戲就太貴了。

所以我們應該再創建一個函數,允許我們以合約擁有者的身份來設置 levelUpFee。

a. 創建一個函數,名為 setLevelUpFee, 其接收一個參數 uint _fee,是 external 并使用修飾符 onlyOwner

b. 這個函數應該設置 levelUpFee 等于 _fee

zombiehelper.sol

pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  uint levelUpFee = 0.001 ether;

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  // 1. 在這里創建 withdraw 函數
  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }

  // 2. 在這里創建 setLevelUpFee 函數 
  function setLevelUpFee(uint _fee) external onlyOwner {
    levelUpFee = _fee;
  }
 
  function levelUp(uint _zombieId) external payable {
    require(msg.value == levelUpFee);
    zombies[_zombieId].level++;
  }

  function changeName(uint _zombieId, string _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[]) {
    uint[] memory result = new uint[](ownerZombieCount[_owner]);
    uint counter = 0;
    for (uint i = 0; i < zombies.length; i++) {
      if (zombieToOwner[i] == _owner) {
        result[counter] = i;
        counter++;
      }
    }
    return result;
  }

}

七、綜合應用

我們新建一個攻擊功能合約,并將代碼放進新的文件中,引入上一個合約。

再來新建一個合約吧。熟能生巧。

如果你不記得怎么做了, 查看一下 zombiehelper.sol — 不過最好先試著做一下,檢查一下你掌握的情況。

1、在文件開頭定義 Solidity 的版本 ^0.4.19.

2、importzombiehelper.sol .

3、聲明一個新的 contract,命名為 ZombieBattle, 繼承自ZombieHelper。函數體就先空著吧。

zombiebattle.sol

pragma solidity ^0.4.19;
import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  
}
八、隨機數

優秀的游戲都需要一些隨機元素,那么我們在 Solidity 里如何生成隨機數呢?

真正的答案是你不能,或者最起碼,你無法安全地做到這一點。

我們來看看為什么

keccak256 來制造隨機數
Solidity 中最好的隨機數生成器是 keccak256 哈希函數.

我們可以這樣來生成一些隨機數

// 生成一個0到100的隨機數:
uint randNonce = 0;
uint random = uint(keccak256(now, msg.sender, randNonce)) % 100;
randNonce++;
uint random2 = uint(keccak256(now, msg.sender, randNonce)) % 100;

這個方法首先拿到 now 的時間戳、 msg.sender、 以及一個自增數 nonce (一個僅會被使用一次的數,這樣我們就不會對相同的輸入值調用一次以上哈希函數了)。

然后利用 keccak 把輸入的值轉變為一個哈希值, 再將哈希值轉換為 uint, 然后利用 % 100 來取最后兩位, 就生成了一個0到100之間隨機數了。

這個方法很容易被不誠實的節點攻擊
在以太坊上, 當你在和一個合約上調用函數的時候, 你會把它廣播給一個節點或者在網絡上的 transaction 節點們。 網絡上的節點將收集很多事務, 試著成為第一個解決計算密集型數學問題的人,作為“工作證明”,然后將“工作證明”(Proof of Work, PoW)和事務一起作為一個 block 發布在網絡上。

一旦一個節點解決了一個PoW, 其他節點就會停止嘗試解決這個 PoW, 并驗證其他節點的事務列表是有效的,然后接受這個節點轉而嘗試解決下一個節點。

這就讓我們的隨機數函數變得可利用了

我們假設我們有一個硬幣翻轉合約——正面你贏雙倍錢,反面你輸掉所有的錢。假如它使用上面的方法來決定是正面還是反面 (random >= 50 算正面, random < 50 算反面)。

如果我正運行一個節點,我可以 只對我自己的節點 發布一個事務,且不分享它。 我可以運行硬幣翻轉方法來偷窺我的輸贏 — 如果我輸了,我就不把這個事務包含進我要解決的下一個區塊中去。我可以一直運行這個方法,直到我贏得了硬幣翻轉并解決了下一個區塊,然后獲利。

所以我們該如何在以太坊上安全地生成隨機數呢 ?

因為區塊鏈的全部內容對所有參與者來說是透明的, 這就讓這個問題變得很難,它的解決方法不在本課程討論范圍,你可以閱讀 這個 StackOverflow 上的討論 來獲得一些主意。 一個方法是利用 oracle 來訪問以太坊區塊鏈之外的隨機數函數。

當然, 因為網絡上成千上萬的以太坊節點都在競爭解決下一個區塊,我能成功解決下一個區塊的幾率非常之低。 這將花費我們巨大的計算資源來開發這個獲利方法 — 但是如果獎勵異常地高(比如我可以在硬幣翻轉函數中贏得 1個億), 那就很值得去攻擊了。

所以盡管這個方法在以太坊上不安全,在實際中,除非我們的隨機函數有一大筆錢在上面,你游戲的用戶一般是沒有足夠的資源去攻擊的。

因為在這個教程中,我們只是在編寫一個簡單的游戲來做演示,也沒有真正的錢在里面,所以我們決定接受這個不足之處,使用這個簡單的隨機數生成函數。但是要謹記它是不安全的。

實戰演練

我們來實現一個隨機數生成函數,好來計算戰斗的結果。雖然這個函數一點兒也不安全。

1、給我們合約一個名為 randNonceuint,將其值設置為 0。

2、建立一個函數,命名為 randMod (random-modulus)。它將作為internal 函數,傳入一個名為 _modulus的 uint,并 returns 一個 uint

3、這個函數首先將為 randNonce加一, (使用 randNonce++ 語句)。

4、最后,它應該 (在一行代碼中) 計算 now, msg.sender, 以及 randNonce 的 keccak256 哈希值并轉換為 uint—— 最后 return % _modulus 的值。 (天! 聽起來太拗口了。如果你有點理解不過來,看一下我們上面計算隨機數的例子,它們的邏輯非常相似)

zombiehelper.sol

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  // 在這里開始
  uint randNonce = 0;

  function randMod(uint _modulus) internal returns (uint) {

    randNonce ++;
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;

  }
}
九、游戲對戰

我們的合約已經有了一些隨機性的來源,可以用進我們的僵尸戰斗中去計算結果。

我們的僵尸戰斗看起來將是這個流程:

你選擇一個自己的僵尸,然后選擇一個對手的僵尸去攻擊。

如果你是攻擊方,你將有70%的幾率獲勝,防守方將有30%的幾率獲勝。

所有的僵尸(攻守雙方)都將有一個 winCount 和一個 lossCount,這兩個值都將根據戰斗結果增長。

若攻擊方獲勝,這個僵尸將升級并產生一個新僵尸。

如果攻擊方失敗,除了失敗次數將加一外,什么都不會發生。

無論輸贏,當前僵尸的冷卻時間都將被激活。

這有一大堆的邏輯需要處理,我們將把這些步驟分解到接下來的課程中去。

實戰演練

1、給我們合約一個 uint 類型的變量,命名為 attackVictoryProbability, 將其值設定為 70。

2、創建一個名為 attack的函數。它將傳入兩個參數: _zombieId (uint 類型) 以及 _targetId (也是 uint)。它將是一個 external 函數。

zombiehelper.sol

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  uint randNonce = 0;
  // 在這里創建 attackVictoryProbability
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
  }

  // 在這里創建新函數
  function attack(uint _zombieId, uint _targetId) external {
    
  }
}

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/24132.html

相關文章

  • 以太開發實戰學習-高級Solidity理論(四)

    摘要:第一個例子,在你把智能協議傳上以太坊之后,它就變得不可更改這種永固性意味著你的代碼永遠不能被調整或更新。允許將合約所有權轉讓給他人。為何要來驅動以太坊就像一個巨大緩慢但非常安全的電腦。 通過前邊的 Solidity 基礎語法學習,我們已經有了Solidity編程經驗,在這節就要學學 Ethereum 開發的技術細節,編寫真正的 DApp 時必知的:智能協議的所有權,Gas的花費,代碼優...

    feng409 評論0 收藏0
  • 以太開發實戰學習-高級Solidity理論 (六)

    摘要:接上篇文章,這里繼續學習高級理論。將這個函數的定義修改為其使用修飾符。我們將用一個到的隨機數來確定我們的戰斗結果。在這個教程中,簡單起見我們將這個狀態保存在結構體中,將其命名為和。在第六章我們計算出來一個到的隨機數。 接上篇文章,這里繼續學習Solidity高級理論。 一、重構通用邏輯 不管誰調用我們的 attack 函數 —— 我們想確保用戶的確擁有他們用來攻擊的僵尸。如果你能用其他...

    qc1iu 評論0 收藏0
  • 區塊鏈技術學習指引

    摘要:引言給迷失在如何學習區塊鏈技術的同學一個指引,區塊鏈技術是隨比特幣誕生,因此要搞明白區塊鏈技術,應該先了解下比特幣。但區塊鏈技術不單應用于比特幣,還有非常多的現實應用場景,想做區塊鏈應用開發,可進一步閱讀以太坊系列。 本文始發于深入淺出區塊鏈社區, 原文:區塊鏈技術學習指引 原文已更新,請讀者前往原文閱讀 本章的文章越來越多,本文是一個索引帖,方便找到自己感興趣的文章,你也可以使用左側...

    Cristic 評論0 收藏0
  • SegmentFault 技術周刊 Vol.41 - 深入學習區塊鏈

    摘要:和比特幣協議有所不同的是,以太坊的設計十分靈活,極具適應性。超級賬本區塊鏈的商業應用超級賬本超級賬本是基金會下的眾多項目中的一個。證書頒發機構負責簽發撤 showImg(https://segmentfault.com/img/bV2ge9?w=900&h=385); 從比特幣開始 一個故事告訴你比特幣的原理及運作機制 這篇文章的定位會比較科普,盡量用類比的方法將比特幣的基本原理講出來...

    qianfeng 評論0 收藏0
  • 以太開發實戰學習-solidity語法(二)

    摘要:以太坊開發高級語言學習。地址以太坊區塊鏈由賬戶組成,你可以把它想象成銀行賬戶。使用很安全,因為它具有以太坊區塊鏈的安全保障除非竊取與以太坊地址相關聯的私鑰,否則是沒有辦法修改其他人的數據的。 以太坊開發高級語言學習。 一、映射(Mapping)和地址(Address) 我們通過給數據庫中的僵尸指定主人, 來支持多玩家模式。 如此一來,我們需要引入2個新的數據類型:mapping(映射)...

    wemall 評論0 收藏0

發表評論

0條評論

sushi

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<