iT邦幫忙

2022 iThome 鐵人賽

DAY 20
1
Web 3

從以太坊白皮書理解 web 3 概念系列 第 21

從以太坊白皮書理解 web 3 概念 - Day20

  • 分享至 

  • xImage
  •  

從以太坊白皮書理解 web 3 概念 - Day20

Learn Solidity - Day 12 - Build an Oracle - Part 3

昨天透過 Lession 15 教學

學會了建構與 Biance API 溝通的 Oracle Web Component

然而在更新 ETH Price 的元件部份仍然是限制 Owner 才能做更新也就是仍是中心化 Oracle

今天將會透過 Lession 16 建構一個比較去中心化的 Oracle 元件

使用 Roles

在昨天的教學使用 OpenZeppelin 的 Ownable Contract 來實作只允許 Owner 來呼叫 setLattestEthPrice 的功能

為了要讓 Oracle 更加去中心化,必須要來實作一個允許不同層級的存取的概念。也就是讓存取層級分為 owner 與 oracle: owner 能夠新增與移除 Oracle。 oracle 則可以透過 setLatestEthPrice 來更新 ETH 價格。

在 OpenZeppelin 提供了一個函式庫叫作 Roles 可以有這樣的功能

首先需要引入 Roles 如下:

import "openzeppelin-solidity/contracts/access/Roles.sol";

然後,可以先來看 Roles Contract 的內容

pragma solidity ^0.5.0;

/**
 * @title Roles
 * @dev Library for managing addresses assigned to a Role.
 */
library Roles {
    struct Role {
        mapping (address => bool) bearer;
    }

    /**
     * @dev Give an account access to this role.
     */
    function add(Role storage role, address account) internal {
        require(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;
    }

    /**
     * @dev Remove an account's access to this role.
     */
    function remove(Role storage role, address account) internal {
        require(has(role, account), "Roles: account does not have role");
        role.bearer[account] = false;
    }

    /**
     * @dev Check if an account has this role.
     * @return bool
     */
    function has(Role storage role, address account) internal view returns (bool) {
        require(account != address(0), "Roles: account is the zero address");
        return role.bearer[account];
    }
}

而要使用 Roles 除了 import 之外還需要使用以下語法

using Roles for Roles.Role;

一旦做了以上引用,就可使用 add, remove 與 has 來實作根據 Role 來限制存取

使用方式如下

oracles.add(_oracle); // Adds `_oracle` to the list of oracles
oracles.remove(_oracle); // Removes `_oracle` from the list of oracles
oracles.has(msg.sender); // Returns `true` if `msg.sender` is an `oracle`

實作

  1. import Role.sol 到 Contract 內
  2. 讓 Contract 不繼承 Ownable
  3. 引入 Roles 為 Roles.Role
  4. 宣告一個 Roles.Role 變數 owners ,設定存取為 private
  5. 宣告一個 Roles.Role 變數 oracles ,設定存取為 private
pragma solidity 0.5.0;
// 1. On the next line, import from the `openzeppelin-solidity/contracts/access/Roles.sol` file
import "openzeppelin-solidity/contracts/access/Roles.sol";
import "./CallerContractInterface.sol";
contract EthPriceOracle { //2. Remove `is Ownable`
  // 2. Continue here
  using Roles for Roles.Role;
  Roles.Role private owners;
  Roles.Role private oracles;
  uint private randNonce = 0;
  uint private modulus = 1000;
  mapping(uint256=>bool) pendingRequests;
  event GetLatestEthPriceEvent(address callerAddress, uint id);
  event SetLatestEthPriceEvent(uint256 ethPrice, address callerAddress);
  function getLatestEthPrice() public returns (uint256) {
    randNonce++;
    uint id = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % modulus;
    pendingRequests[id] = true;
    emit GetLatestEthPriceEvent(msg.sender, id);
    return id;
  }
  function setLatestEthPrice(uint256 _ethPrice, address _callerAddress, uint256 _id) public onlyOwner {
    require(pendingRequests[_id], "This request is not in my pending list.");
    delete pendingRequests[_id];
    CallerContractInterface callerContractInstance;
    callerContractInstance = CallerContractInterface(_callerAddress);
    callerContractInstance.callback(_ethPrice, _id);
    emit SetLatestEthPriceEvent(_ethPrice, _callerAddress);
  }
}

使用 Constructor 來設定 Owner

現在 Contract 已經不繼承 Ownable

所以必須透㳀 constructor 來設定 owner

constructor 是一個特別函數只會在發佈時執行一次

以下是 constructor 語法

contract MyAwesomeContract {
  constructor (address _owner) public {
    // Do something
  }
}

要呼叫時則需要使用以下語法

deployer.deploy(EthPriceOracle, '0xb090d88a3e55906de49d76b66bf4fe70b9d6d708')

而再 constructor 內需要使用以下語法設定 owner

owners.add(_owner);

實作 constructor 來設定 owenr

  1. 建立 EthPriceOracle Contract 的 constructor
    需要一個參數: _owner(address)
  2. 加入以下邏輯到 constructor 內
owners.add(_owner);

更新如下

pragma solidity 0.5.0;
import "openzeppelin-solidity/contracts/access/Roles.sol";
import "./CallerContractInterface.sol";
contract EthPriceOracle {
  using Roles for Roles.Role;
  Roles.Role private owners;
  Roles.Role private oracles;
  uint private randNonce = 0;
  uint private modulus = 1000;
  mapping(uint256=>bool) pendingRequests;
  event GetLatestEthPriceEvent(address callerAddress, uint id);
  event SetLatestEthPriceEvent(uint256 ethPrice, address callerAddress);
  // Start here
  constructor(address _owner) public {
    owners.add(_owner);
  }
  function getLatestEthPrice() public returns (uint256) {
    randNonce++;
    uint id = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % modulus;
    pendingRequests[id] = true;
    emit GetLatestEthPriceEvent(msg.sender, id);
    return id;
  }
  function setLatestEthPrice(uint256 _ethPrice, address _callerAddress, uint256 _id) public onlyOwner {
    require(pendingRequests[_id], "This request is not in my pending list.");
    delete pendingRequests[_id];
    CallerContractInterface callerContractInstance;
    callerContractInstance = CallerContractInterface(_callerAddress);
    callerContractInstance.callback(_ethPrice, _id);
    emit SetLatestEthPriceEvent(_ethPrice, _callerAddress);
  }
}

設定新的 Oracle

下一步需要實作把新的 oracle address 計錄下來給其他已知的 oracles

以下是執行流程

  • 驗證呼叫者是 Contract 的 owner
  • 驗證該 address 還沒加入任何一個 oracle
  • 當加入 address 之後需要發起一個 event 給前端通知已新增一個 Oracle address

實作細節

  1. 宣告一個 AddOracleEvent
    這個 event 需要一個參數: oracleAddress(address)
  2. 宣告 function addOracle(address _oracle) public
  3. 加入以下邏輯驗證 owners 有 msg.sender
require(owners.has(msg.sender), "Not an owner");

更新如下

pragma solidity 0.5.0;
import "openzeppelin-solidity/contracts/access/Roles.sol";
import "./CallerContractInterface.sol";
contract EthPriceOracle {
  using Roles for Roles.Role;
  Roles.Role private owners;
  Roles.Role private oracles;
  uint private randNonce = 0;
  uint private modulus = 1000;
  mapping(uint256=>bool) pendingRequests;
  event GetLatestEthPriceEvent(address callerAddress, uint id);
  event SetLatestEthPriceEvent(uint256 ethPrice, address callerAddress);
  // 1. Define `AddOracleEvent`
  event AddOracleEvent(address oracleAddress);
  constructor (address _owner) public {
    owners.add(_owner);
  }
  // 2. Continue here
  function addOracle(address _oracle) public {
    require(owners.has(msg.sender), "Not an owner!");
  }
  function getLatestEthPrice() public returns (uint256) {
    randNonce++;
    uint id = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % modulus;
    pendingRequests[id] = true;
    emit GetLatestEthPriceEvent(msg.sender, id);
    return id;
  }
  function setLatestEthPrice(uint256 _ethPrice, address _callerAddress, uint256 _id) public onlyOwner {
    require(pendingRequests[_id], "This request is not in my pending list.");
    delete pendingRequests[_id];
    CallerContractInterface callerContractInstance;
    callerContractInstance = CallerContractInterface(_callerAddress);
    callerContractInstance.callback(_ethPrice, _id);
    emit SetLatestEthPriceEvent(_ethPrice, _callerAddress);
  }
}

實作驗證 oracle address 不存在

  1. 在 addOracle function 新增以下邏輯驗證 _oracle 不存在
require(!oracles.has(owner), "Already an oracle");
  1. 呼叫 oracles.add 來新增 _oracle
  2. 最後 emit AddOracleEvent(_oracle);

更新如下

pragma solidity 0.5.0;
import "openzeppelin-solidity/contracts/access/Roles.sol";
import "./CallerContractInterface.sol";
contract EthPriceOracle {
  using Roles for Roles.Role;
  Roles.Role private owners;
  Roles.Role private oracles;
  uint private randNonce = 0;
  uint private modulus = 1000;
  mapping(uint256=>bool) pendingRequests;
  event GetLatestEthPriceEvent(address callerAddress, uint id);
  event SetLatestEthPriceEvent(uint256 ethPrice, address callerAddress);
  event AddOracleEvent(address oracleAddress);
  constructor (address _owner) public {
    owners.add(_owner);
  }
  function addOracle (address _oracle) public {
    require(owners.has(msg.sender), "Not an owner!");
    // Start here
    require(!oracles.has(_oracle), "Already an oracle!");
    oracles.add(_oracle);
    emit AddOracleEvent(_oracle);
  }
  function getLatestEthPrice() public returns (uint256) {
    randNonce++;
    uint id = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % modulus;
    pendingRequests[id] = true;
    emit GetLatestEthPriceEvent(msg.sender, id);
    return id;
  }
  function setLatestEthPrice(uint256 _ethPrice, address _callerAddress, uint256 _id) public onlyOwner {
    require(pendingRequests[_id], "This request is not in my pending list.");
    delete pendingRequests[_id];
    CallerContractInterface callerContractInstance;
    callerContractInstance = CallerContractInterface(_callerAddress);
    callerContractInstance.callback(_ethPrice, _id);
    emit SetLatestEthPriceEvent(_ethPrice, _callerAddress);
  }
}

實作 Remove Oracle 邏輯

  1. 宣告一個 function removeOracle(address _oracle) public
  2. 宣告一個 uint 變數 numOracles 設定存取權限為 private
  3. 宣告一個 event RemoveOracleEvent(address oracleAddress);
  4. 在 function removeOracle 內加入以下邏輯確保 numOracles > 1
require(numOracles > 1, "Do not remove the last oracle!");
  1. 呼叫 oracles.remove(_oracle);
  2. 把 numOracles - 1
  3. 最後 emit RemoveOracleEvent(_oracle);

更新如下

pragma solidity 0.5.0;
import "openzeppelin-solidity/contracts/access/Roles.sol";
import "./CallerContractInterface.sol";
contract EthPriceOracle {
  using Roles for Roles.Role;
  Roles.Role private owners;
  Roles.Role private oracles;
  uint private randNonce = 0;
  uint private modulus = 1000;
  // 1. Initialize `numOracles`
  uint private numOracles = 0;
  mapping(uint256=>bool) pendingRequests;
  event GetLatestEthPriceEvent(address callerAddress, uint id);
  event SetLatestEthPriceEvent(uint256 ethPrice, address callerAddress);
  event AddOracleEvent(address oracleAddress);
  //2. Declare `RemoveOracleEvent`
  event RemoveOracleEvent(address oracleAddress);
  constructor (address _owner) public {
    owners.add(_owner);
  }
  function addOracle (address _oracle) public {
    require(owners.has(msg.sender), "Not an owner!");
    require(!oracles.has(_oracle), "Already an oracle!");
    oracles.add(_oracle);
    numOracles++;
    emit AddOracleEvent(_oracle);
  }
  function removeOracle (address _oracle) public {
    require(owners.has(msg.sender), "Not an owner!");
    require(oracles.has(_oracle), "Not an oracle!");
    // 3. Continue here
    require(numOracles > 1, "Do not remove the last oracle!");
    oracles.remove(_oracle);
    numOracles--;
    emit RemoveOracleEvent(_oracle);
  }
  function getLatestEthPrice() public returns (uint256) {
    randNonce++;
    uint id = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % modulus;
    pendingRequests[id] = true;
    emit GetLatestEthPriceEvent(msg.sender, id);
    return id;
  }
  function setLatestEthPrice(uint256 _ethPrice, address _callerAddress, uint256 _id) public onlyOwner {
    require(pendingRequests[_id], "This request is not in my pending list.");
    delete pendingRequests[_id];
    CallerContractInterface callerContractInstance;
    callerContractInstance = CallerContractInterface(_callerAddress);
    callerContractInstance.callback(_ethPrice, _id);
    emit SetLatestEthPriceEvent(_ethPrice, _callerAddress);
  }
}

更新 setLatestEthPrice 邏輯

當新的 oracles 加入, Contract 會預期會產生新的一個 resposne 。

而之前是透過 pendingRequests 來紀錄有哪些 request 需要回應

為了要能正確的回應,每個 response 必須要紀錄以下資訊

  • oracleAddress
  • callerAddress
  • ethPrice

並且會需要把這些資訊與 request id 做關聯

可以透過 mapping 來紀錄 id 對應到以上資訊所形成的一個 array

以上的資訊可以透過 struct 語法組成一個資料集

語法如下

struct MyStruct {
	address anAddress;
	uint256 aNumber;
}

初始化 struct 語法則如下

MyStruct memory myStructInstance;
myStructInstance = MyStruct(msg.sender, 200);

實作

  1. 宣告一個 struct Response 如下
struct Response {
	address oracleAddress;
	address callerAddress;
	uint256 ethPrice;
}
  1. 在 setLatestEthPrice 內,宣告一個 Response memory resp;
  2. 初始化 resp 如下
resp = Rresponse(msg.sender, _callerAddress, _ethPrice);
  1. 使用以下語法把 resp 放入 requestIdToResponse
requestIdToResponse[_id].push(resp);

更新如下

pragma solidity 0.5.0;
import "openzeppelin-solidity/contracts/access/Roles.sol";
import "./CallerContractInterface.sol";
contract EthPriceOracle {
  using Roles for Roles.Role;
  Roles.Role private owners;
  Roles.Role private oracles;
  uint private randNonce = 0;
  uint private modulus = 1000;
  uint private numOracles = 0;
  mapping(uint256=>bool) pendingRequests;
  //1. Define `Response
  struct Response {
    address oracleAddress;
    address callerAddress;
    uint256 ethPrice;
  }
  mapping (uint256=>Response[]) public requestIdToResponse;
  event GetLatestEthPriceEvent(address callerAddress, uint id);
  event SetLatestEthPriceEvent(uint256 ethPrice, address callerAddress);
  event AddOracleEvent(address oracleAddress);
  event RemoveOracleEvent(address oracleAddress);
  constructor (address _owner) public {
    owners.add(_owner);
  }
  function addOracle (address _oracle) public {
    require(owners.has(msg.sender), "Not an owner!");
    require(!oracles.has(_oracle), "Already an oracle!");
    oracles.add(_oracle);
    numOracles++;
    emit AddOracleEvent(_oracle);
  }
  function removeOracle (address _oracle) public {
    require(owners.has(msg.sender), "Not an owner!");
    require(oracles.has(_oracle), "Not an oracle!");
    require (numOracles > 1, "Do not remove the last oracle!");
    oracles.remove(_oracle);
    numOracles--;
    emit RemoveOracleEvent(_oracle);
  }
  function getLatestEthPrice() public returns (uint256) {
    randNonce++;
    uint id = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % modulus;
    pendingRequests[id] = true;
    emit GetLatestEthPriceEvent(msg.sender, id);
    return id;
  }
  function setLatestEthPrice(uint256 _ethPrice, address _callerAddress, uint256 _id) public {
    require(oracles.has(msg.sender), "Not an oracle!");
    require(pendingRequests[_id], "This request is not in my pending list.");
    // 2. Continue here
    Response memory resp;
    resp = Response(msg.sender, _callerAddress, _ethPrice);
    requestIdToResponse[_id].push(resp); 
    delete pendingRequests[_id];
    CallerContractInterface callerContractInstance;
    callerContractInstance = CallerContractInterface(_callerAddress);
    callerContractInstance.callback(_ethPrice, _id);
    emit SetLatestEthPriceEvent(_ethPrice, _callerAddress);
  }
}

接受 ETH price

一旦 Smart Contradct 接到了新的 ETH 價格, 就可以透過 callback 來把資訊更新到 caller Contract

語法如下

callerContractInstance.callback(_ethPrice, _id);

然而,將會根據 numOracles 來更新每個 Request

代表所拿到的價格並無法即時,因為 Oracle Contract 無法隨時更新只有 Caller 呼叫才會去拿取新的 ETH price

假設 fetch API 失敗或是網路中斷了,則 Oracle Contract 將會無法回應正確的 ETH Price 也就是,只能等網路正常能做到回覆

然而比較好的作法是透過一個門閥值,當這個數字回等於這個門閥值,

Smart Contract 則會計算出 ETH 價格給 caller Contract。

這樣會比一直 block response 在 pending resquest 好一點點

實作

  1. 定義一個變數 THRESHOLD
  2. 在 setLatestEthPrice 內的 push resp 之後, 定一個變數 numResponse 設定值為 requestIdToResponse[_id] 長度
  3. 加入以下判斷式用來確認 numResponse == THRESHOLD
if (numResponse == THRESHOLD) {
	
}
  1. 把以下邏輯移入 if block 之內
delete pendingRequests[_id];   
CallerContractInterface callerContractInstance;
callerContractInstance = CallerContractInterface(_callerAddress);
callerContractInstance.callback(_ethPrice, _id);
emit SetLatestEthPriceEvent(_ethPrice, _callerAddress);

更新如下

pragma solidity 0.5.0;
import "openzeppelin-solidity/contracts/access/Roles.sol";
import "./CallerContractInterface.sol";
contract EthPriceOracle {
  using Roles for Roles.Role;
  Roles.Role private owners;
  Roles.Role private oracles;
  uint private randNonce = 0;
  uint private modulus = 1000;
  uint private numOracles = 0;
  uint private THRESHOLD = 0;
  mapping(uint256=>bool) pendingRequests;
  struct Response {
    address oracleAddress;
    address callerAddress;
    uint256 ethPrice;
  }
  event GetLatestEthPriceEvent(address callerAddress, uint id);
  event SetLatestEthPriceEvent(uint256 ethPrice, address callerAddress);
  event AddOracleEvent(address oracleAddress);
  event RemoveOracleEvent(address oracleAddress);
  event SetThresholdEvent (uint threshold);
  constructor (address _owner) public {
    owners.add(_owner);
  }
  function addOracle (address _oracle) public {
    require(owners.has(msg.sender), "Not an owner!");
    require(!oracles.has(_oracle), "Already an oracle!");
    oracles.add(_oracle);
    numOracles++;
    emit AddOracleEvent(_oracle);
  }
  function removeOracle (address _oracle) public {
    require(owners.has(msg.sender), "Not an owner!");
    require(oracles.has(_oracle), "Not an oracle!");
    require (numOracles > 1, "Do not remove the last oracle!");
    oracles.remove(_oracle);
    numOracles--;
    emit RemoveOracleEvent(_oracle);
  }
  function setThreshold (uint _threshold) public {
    require(owners.has(msg.sender), "Not an owner!");
    THRESHOLD = _threshold;
    emit SetThresholdEvent(THRESHOLD);
  }
  function getLatestEthPrice() public returns (uint256) {
    randNonce++;
    uint id = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % modulus;
    pendingRequests[id] = true;
    emit GetLatestEthPriceEvent(msg.sender, id);
    return id;
  }
  function setLatestEthPrice(uint256 _ethPrice, address _callerAddress, uint256 _id) public {
    require(oracles.has(msg.sender), "Not an oracle!");
    require(pendingRequests[_id], "This request is not in my pending list.");
    Response memory resp;
    resp = Response(msg.sender, _callerAddress, _ethPrice);
    requestIdToResponse[_id].push(resp);
    // Start here
    uint numResponses = requestIdToResponse[_id].length;
    if (numResponses == THRESHOLD) {
	  delete pendingRequests[_id]; 
      CallerContractInterface callerContractInstance;
      callerContractInstance = CallerContractInterface(_callerAddress);
      callerContractInstance.callback(_ethPrice, _id);
      emit SetLatestEthPriceEvent(_ethPrice, _callerAddress);
    }
  }
}

實作透過平均來計算 ETH price

  1. 宣告一個 uint 變數 computedEthPrice 並且設定為 0
  2. 宣告一個ˊ for loop 如下
for (uint f = 0; f < requestIdToResponse[_id].length; f++) {
	
}
  1. 更新 for loop 如下
for (uint f = 0; f < requestIdToResponse[_id].length; f++) {
	computedEthPrice += requestIdToResponse[_id][f].ethPrice;
}
  1. 更新 computedEthPrice = computedEthPrice/numResponses;

更新如下:

pragma solidity 0.5.0;
import "openzeppelin-solidity/contracts/access/Roles.sol";
import "./CallerContractInterface.sol";
contract EthPriceOracle {
  using Roles for Roles.Role;
  Roles.Role private owners;
  Roles.Role private oracles;
  uint private randNonce = 0;
  uint private modulus = 1000;
  uint private numOracles = 0;
  uint private THRESHOLD = 0;
  mapping(uint256=>bool) pendingRequests;
  struct Response {
    address oracleAddress;
    address callerAddress;
    uint256 ethPrice;
  }
  event GetLatestEthPriceEvent(address callerAddress, uint id);
  event SetLatestEthPriceEvent(uint256 ethPrice, address callerAddress);
  event AddOracleEvent(address oracleAddress);
  event RemoveOracleEvent(address oracleAddress);
  event SetThresholdEvent (uint threshold);
  constructor (address _owner) public {
    owners.add(_owner);
  }
  function addOracle (address _oracle) public {
    require(owners.has(msg.sender), "Not an owner!");
    require(!oracles.has(_oracle), "Already an oracle!");
    oracles.add(_oracle);
    numOracles++;
    emit AddOracleEvent(_oracle);
  }
  function removeOracle (address _oracle) public {
    require(owners.has(msg.sender), "Not an owner!");
    require(oracles.has(_oracle), "Not an oracle!");
    require (numOracles > 1, "Do not remove the last oracle!");
    oracles.remove(_oracle);
    numOracles--;
    emit RemoveOracleEvent(_oracle);
  }
  function setThreshold (uint _threshold) public {
    require(owners.has(msg.sender), "Not an owner!");
    THRESHOLD = _threshold;
    emit SetThresholdEvent(THRESHOLD);
  }
  function getLatestEthPrice() public returns (uint256) {
    randNonce++;
    uint id = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % modulus;
    pendingRequests[id] = true;
    emit GetLatestEthPriceEvent(msg.sender, id);
    return id;
  }
  function setLatestEthPrice(uint256 _ethPrice, address _callerAddress, uint256 _id) public {
    require(oracles.has(msg.sender), "Not an oracle!");
    require(pendingRequests[_id], "This request is not in my pending list.");
    Response memory resp;
    resp = Response(msg.sender, _callerAddress, _ethPrice);
    requestIdToResponse[_id].push(resp);
    uint numResponses = requestIdToResponse[_id].length;
    if (numResponses == THRESHOLD) {
      // Start here
      uint computedEthPrice = 0;
      for (uint f=0; f < requestIdToResponse[_id].length; f++) {
        computedEthPrice += requestIdToResponse[_id][f].ethPrice;
      }
      computedEthPrice = computedEthPrice/numResponses;
      delete pendingRequests[_id];
      CallerContractInterface callerContractInstance;
      callerContractInstance = CallerContractInterface(_callerAddress);
      callerContractInstance.callback(_ethPrice, _id);
      emit SetLatestEthPriceEvent(_ethPrice, _callerAddress);
    }
  }
}

使用 SafeMath 來避免 Overflow

  1. 宣告要使用對 uint256 使用 safeMath 語法如下
using SafeMath for uint256;
  1. 修正 computedEthPrice += requestIdToResponse[_id][f].ethPrice; 改用 SafeMath 運算子

  2. 修正 computedEthPrice = computedEthPrice / numResponses; 使用 SafeMath 運算子

更新如下

pragma solidity 0.5.0;
import "openzeppelin-solidity/contracts/access/Roles.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "./CallerContractInterface.sol";
contract EthPriceOracle {
  using Roles for Roles.Role;
  Roles.Role private owners;
  Roles.Role private oracles;
  // 1. Tell your contract to use `SafeMath` for `uint256`
  using SafeMath for uint256;
  uint private randNonce = 0;
  uint private modulus = 1000;
  uint private numOracles = 0;
  uint private THRESHOLD = 0;
  mapping(uint256=>bool) pendingRequests;
  struct Response {
    address oracleAddress;
    address callerAddress;
    uint256 ethPrice;
  }
  event GetLatestEthPriceEvent(address callerAddress, uint id);
  event SetLatestEthPriceEvent(uint256 ethPrice, address callerAddress);
  event AddOracleEvent(address oracleAddress);
  event RemoveOracleEvent(address oracleAddress);
  event SetThresholdEvent (uint threshold);
  constructor (address _owner) public {
    owners.add(_owner);
  }
  function addOracle (address _oracle) public {
    require(owners.has(msg.sender), "Not an owner!");
    require(!oracles.has(_oracle), "Already an oracle!");
    oracles.add(_oracle);
    numOracles++;
    emit AddOracleEvent(_oracle);
  }
  function removeOracle (address _oracle) public {
    require(owners.has(msg.sender), "Not an owner!");
    require(oracles.has(_oracle), "Not an oracle!");
    require (numOracles > 1, "Do not remove the last oracle!");
    oracles.remove(_oracle);
    numOracles--;
    emit RemoveOracleEvent(_oracle);
  }
  function setThreshold (uint _threshold) public {
    require(owners.has(msg.sender), "Not an owner!");
    THRESHOLD = _threshold;
    emit SetThresholdEvent(THRESHOLD);
  }
  function getLatestEthPrice() public returns (uint256) {
    randNonce++;
    uint id = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % modulus;
    pendingRequests[id] = true;
    emit GetLatestEthPriceEvent(msg.sender, id);
    return id;
  }
  function setLatestEthPrice(uint256 _ethPrice, address _callerAddress, uint256 _id) public {
    require(oracles.has(msg.sender), "Not an oracle!");
    require(pendingRequests[_id], "This request is not in my pending list.");
    Response memory resp;
    resp = Response(msg.sender, _callerAddress, _ethPrice);
    requestIdToResponse[_id].push(resp);
    uint numResponses = requestIdToResponse[_id].length;
    if (numResponses == THRESHOLD) {
      uint computedEthPrice = 0;
      for (uint f=0; f < requestIdToResponse[_id].length; f++) {
        computedEthPrice = computedEthPrice.add(requestIdToResponse[_id][f].ethPrice);
      }
      computedEthPrice = computedEthPrice.div(numResponses);
      delete pendingRequests[_id];
      CallerContractInterface callerContractInstance;
      callerContractInstance = CallerContractInterface(_callerAddress);
      callerContractInstance.callback(_ethPrice, _id);
      emit SetLatestEthPriceEvent(_ethPrice, _callerAddress);
    }
  }
}

修正其他部份使用 SafeMath

  1. 刪除 requestIdToResponse 的 _id 的 key
  2. 更新呼叫 callerContractInstance.callback 與 SetLatestEthPriceEvent 參數為 computedEthPrice

更新如下

pragma solidity 0.5.0;
import "openzeppelin-solidity/contracts/access/Roles.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "./CallerContractInterface.sol";
contract EthPriceOracle {
  using Roles for Roles.Role;
  Roles.Role private owners;
  Roles.Role private oracles;
  using SafeMath for uint256;
  uint private randNonce = 0;
  uint private modulus = 1000;
  uint private numOracles = 0;
  uint private THRESHOLD = 0;
  mapping(uint256=>bool) pendingRequests;
  struct Response {
    address oracleAddress;
    address callerAddress;
    uint256 ethPrice;
  }
  event GetLatestEthPriceEvent(address callerAddress, uint id);
  event SetLatestEthPriceEvent(uint256 ethPrice, address callerAddress);
  event AddOracleEvent(address oracleAddress);
  event RemoveOracleEvent(address oracleAddress);
  event SetThresholdEvent (uint threshold);
  constructor (address _owner) public {
    owners.add(_owner);
  }
  function addOracle (address _oracle) public {
    require(owners.has(msg.sender), "Not an owner!");
    require(!oracles.has(_oracle), "Already an oracle!");
    oracles.add(_oracle);
    numOracles++;
    emit AddOracleEvent(_oracle);
  }
  function removeOracle (address _oracle) public {
    require(owners.has(msg.sender), "Not an owner!");
    require(oracles.has(_oracle), "Not an oracle!");
    require (numOracles > 1, "Do not remove the last oracle!");
    oracles.remove(_oracle);
    numOracles--;
    emit RemoveOracleEvent(_oracle);
  }
  function setThreshold (uint _threshold) public {
    require(owners.has(msg.sender), "Not an owner!");
    THRESHOLD = _threshold;
    emit SetThresholdEvent(THRESHOLD);
  }
  function getLatestEthPrice() public returns (uint256) {
    randNonce++;
    uint id = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % modulus;
    pendingRequests[id] = true;
    emit GetLatestEthPriceEvent(msg.sender, id);
    return id;
  }
  function setLatestEthPrice(uint256 _ethPrice, address _callerAddress, uint256 _id) public {
    require(oracles.has(msg.sender), "Not an oracle!");
    require(pendingRequests[_id], "This request is not in my pending list.");
    Response memory resp;
    resp = Response(msg.sender, _callerAddress, _ethPrice);
    requestIdToResponse[_id].push(resp);
    uint numResponses = requestIdToResponse[_id].length;
    if (numResponses == THRESHOLD) {
      uint computedEthPrice = 0;
        for (uint f=0; f < requestIdToResponse[_id].length; f++) {
        computedEthPrice = computedEthPrice.add(requestIdToResponse[_id][f].ethPrice);
      }
      computedEthPrice = computedEthPrice.div(numResponses);
      delete pendingRequests[_id];
      // Delete `_id` from `requestIdToResponse
      delete requestIdToResponse[_id];
      CallerContractInterface callerContractInstance;
      callerContractInstance = CallerContractInterface(_callerAddress);
      callerContractInstance.callback(computedEthPrice, _id); // Update this line code
      emit SetLatestEthPriceEvent(computedEthPrice, _callerAddress); // Update this line of code
    }
  }
}


上一篇
從以太坊白皮書理解 web 3 概念 - Day19
下一篇
從以太坊白皮書理解 web 3 概念 - Day21
系列文
從以太坊白皮書理解 web 3 概念32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言