OpenZeppelin 7個最常使用的合約

買賣虛擬貨幣
OpenZeppelin的智慧合約程式碼庫[1]是以太坊開發者的寶庫,OpenZeppelin程式碼庫包含了經過社羣審查的ERC代幣標準、安全協議以及很多的輔助工具庫,這些程式碼可以幫助開發者專注業務邏輯的,而無需重新發明輪子。基於OpenZeppelin開發合約,即可以提高程式碼的安全性,又可以提高開發效率,文字列舉了最應該新增到我們專案的 7個OpenZeppelin合約。注意:在本文中我們使用的OpenZeppelin版本為2.5.x,使用 solidity 0.5.x編譯器編譯。訪問控制合約1. 使用 Ownable 進行所有者限制OpenZeppelin 的 Ownable合約提供的onlyOwner 修飾器[2]是用來限制某些特定合約函式的訪問許可權。
我們很多時候需要這樣做,因此這個模式在以太坊智慧合約開發中非常流行。Ownable合約的部署賬號會被當做合約的擁有者(owner),某些合約函式,例如轉移所有權,就限制在只允許擁有者(owner)呼叫。下面是Ownable合約的原始碼:pragma solidity ^0.5.0;import "../GSN/Context.sol";contract Ownable is Context {
    address private _owner;    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);    constructor () internal {        address msgSender = _msgSender();        _owner = msgSender;        emit OwnershipTransferred(address(0), msgSender);
    }    function owner() public view returns (address) {        return _owner;    }    modifier onlyOwner() {        require(isOwner(), "Ownable: caller is not the owner");
        _;    }    function isOwner() public view returns (bool) {        return _msgSender() == _owner;    }    function renounceOwnership() public onlyOwner {
        emit OwnershipTransferred(_owner, address(0));        _owner = address(0);    }    function transferOwnership(address newOwner) public onlyOwner {        _transferOwnership(newOwner);    }
    function _transferOwnership(address newOwner) internal {        require(newOwner != address(0), "Ownable: new owner is the zero address");        emit OwnershipTransferred(_owner, newOwner);        _owner = newOwner;    }}
注意在建構函式中如何設定合約的owner賬號。當Ownable的子合約(即繼承Ownable的合約)初始化時,部署的賬號就會設定為_owner。下面是一個簡單的、繼承自Ownable的合約:pragma solidity ^0.5.5;import "@openzeppelin/contracts/ownership/Ownable.sol";contract OwnableContract is Ownable {  function restrictedFunction() public onlyOwner returns (uint) {
    return 99;  }  function openFunction() public returns (uint) {    return 1;  }}
透過新增onlyOwner 修飾器 來限制 restrictedFunction 函式合約的owner賬號可以成功呼叫:2. 使用 Roles 進行角色控制進行訪問控制另一個相對於Ownable合約 更高階一些的是使用 Roles 庫, 它可以定義多個角色,對於需要多個訪問層次的控制時,應當考慮使用Roles庫。OpenZeppelin的Roles庫的原始碼如下:pragma solidity ^0.5.0;library Roles {
    struct Role {        mapping (address => bool) bearer;    }    function add(Role storage role, address account) internal {        require(!has(role, account), "Roles: account already has role");        role.bearer[account] = true;
    }    function remove(Role storage role, address account) internal {        require(has(role, account), "Roles: account does not have role");        role.bearer[account] = false;    }    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是一個Solidity庫而非合約,因此不能透過繼承的方式來使用,需要使用solidity的using語句[3]來將庫中定義的函式附加到指定的資料型別上。下面的程式碼使用Roles庫用 _minters和_burners 兩種角色去限制函式:
pragma solidity ^0.5.0;import "@openzeppelin/contracts/access/Roles.sol";import "@openzeppelin/contracts/token/ERC20/ERC20.sol";import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";contract MyToken is ERC20, ERC20Detailed {    using Roles for Roles.Role;
    Roles.Role private _minters;    Roles.Role private _burners;    constructor(address[] memory minters, address[] memory burners)        ERC20Detailed("MyToken", "MTKN", 18)        public    {
        for (uint256 i = 0; i < minters.length; ++i) {            _minters.add(minters[i]);        }        for (uint256 i = 0; i < burners.length; ++i) {            _burners.add(burners[i]);        }
    }    function mint(address to, uint256 amount) public {        // Only minters can mint        require(_minters.has(msg.sender), "DOES_NOT_HAVE_MINTER_ROLE");        _mint(to, amount);    }
    function burn(address from, uint256 amount) public {        // Only burners can burn        require(_burners.has(msg.sender), "DOES_NOT_HAVE_BURNER_ROLE");       _burn(from, amount);    }}
第8行的作用是將Roles庫中的函式附加到Roles.Role型別上。第18行就是在Roles.Role型別上直接使用這些庫函式的方法:_minters.add(),其中add()就是Roles庫提供的實現。算術運算3. 安全的算術運算庫:SafeMath永遠不要直接使用算術運算子例如:+、-、*、/ 進行數學計算,除非你瞭解如何檢查溢位漏洞,否則就沒法保證這些算術計算的安全性。SafeMath庫的作用是幫我們進行算術運中進行必要的檢查,避免程式碼中因算術運算(如溢位)而引入漏洞。下面是SafeMath的原始碼:
pragma solidity ^0.5.0;library SafeMath {    function add(uint256 a, uint256 b) internal pure returns (uint256) {        uint256 c = a + b;        require(c >= a, "SafeMath: addition overflow");        return c;
    }    function sub(uint256 a, uint256 b) internal pure returns (uint256) {        return sub(a, b, "SafeMath: subtraction overflow");    }    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {        require(b <= a, errorMessage);
        uint256 c = a - b;        return c;    }    function mul(uint256 a, uint256 b) internal pure returns (uint256) {        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522        if (a == 0) {            return 0;        }        uint256 c = a * b;        require(c / a == b, "SafeMath: multiplication overflow");
        return c;    }    function div(uint256 a, uint256 b) internal pure returns (uint256) {        return div(a, b, "SafeMath: division by zero");    }    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0        require(b > 0, errorMessage);        uint256 c = a / b;        // assert(a == b * c + a % b); // There is no case in which this doesn't hold        return c;    }
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {        return mod(a, b, "SafeMath: modulo by zero");    }    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {        require(b != 0, errorMessage);        return a % b;
    }}和Roles庫的用法類似,你需要使用using語句將SafeMath庫中的函式附加到uint256型別上,例如:using SafeMath for uint256;4. 安全型別轉換庫:SafeCast作為一個智慧合約開發者,我們常常會思考如何減少合約的執行時間以及空間,節約程式碼空間的一個辦法就是使用更少位數的整數型別。但不幸的是,如果你使用uint8作為變數型別,那麼在呼叫SafeMath庫函式之前,就必須先將其轉換為uint256型別,然後在呼叫SafeMath庫函式之後,還需要再轉換回uint8型別。SafeCast庫的作用就在於可以幫你完成這些轉換而無需擔心溢位問題。
SafeCast的原始碼如下:pragma solidity ^0.5.0;library SafeCast {    function toUint128(uint256 value) internal pure returns (uint128) {        require(value < 2**128, "SafeCast: value doesn\'t fit in 128 bits");        return uint128(value);
    }    function toUint64(uint256 value) internal pure returns (uint64) {        require(value < 2**64, "SafeCast: value doesn\'t fit in 64 bits");        return uint64(value);    }    function toUint32(uint256 value) internal pure returns (uint32) {
        require(value < 2**32, "SafeCast: value doesn\'t fit in 32 bits");        return uint32(value);    }    function toUint16(uint256 value) internal pure returns (uint16) {        require(value < 2**16, "SafeCast: value doesn\'t fit in 16 bits");        return uint16(value);
    }    function toUint8(uint256 value) internal pure returns (uint8) {        require(value < 2**8, "SafeCast: value doesn\'t fit in 8 bits");        return uint8(value);    }}
下面的示例程式碼是如何使用SafeCast將uint轉換為uint8:pragma solidity ^0.5.5;import "@openzeppelin/contracts/math/SafeCast.sol";contract BasicSafeCast {  using SafeCast for uint;  function castToUint8(uint _a) public returns (uint8) {
    return _a.toUint8();  }}Tokens (代幣或通證)ERC20Detailed不需要自己實現完整的ERC20代幣[4]合約 ,OpenZeppelin已經幫我們實現好了, 我們只需要繼承和初始化就好了。
OpenZeppelin的ERC20進行了標準的基礎實現,ERC20Detailed 合約包含了額外的選項:例如代幣名稱、代幣代號以及小數點位數。下面是一個利用OpenZeppelin的ERC20和ERC20Detailed合約實現定製代幣的例子:pragma solidity ^0.5.0;import "@openzeppelin/contracts/token/ERC20/ERC20.sol";import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";contract GLDToken is ERC20, ERC20Detailed {
    constructor(uint256 initialSupply) ERC20Detailed("Gold", "GLD", 18) public {        _mint(msg.sender, initialSupply);    }}6. 非同質化代幣:ERC721Enumerable / ERC721FullOpenZeppelin也提供了非同質化代幣的實現,我們同樣不需要把完整的把標準實現一次。
如果需要列舉一個賬號的所持有的ERC721資產,需要使用ERC721Enumerable合約而不是基礎的 ERC721,ERC721Enumerable提供了_tokensOfOwner()方法 直接支援列舉特定賬號的所有資產。如果你希望有所有的擴充套件功能合約,那麼可以直接選擇ERC721Full。下面的程式碼展示了基於ERC721Full定製非同質化代幣:pragma solidity ^0.5.0;import "@openzeppelin/contracts/token/ERC721/ERC721Full.sol";import "@openzeppelin/contracts/drafts/Counters.sol";contract GameItem is ERC721Full {
    using Counters for Counters.Counter;    Counters.Counter private _tokenIds;    constructor() ERC721Full("GameItem", "ITM") public {    }    function awardItem(address player, string memory tokenURI) public returns (uint256) {        _tokenIds.increment();
        uint256 newItemId = _tokenIds.current();        _mint(player, newItemId);        _setTokenURI(newItemId, tokenURI);        return newItemId;    }}
輔助工具庫7. 用 Address庫識別地址有時候在Solidity合約中需要了解一個地址是普通錢包地址還是合約地址。OpenZeppelin的Address庫提供了一個方法isContract()可以幫我們解決這個問題。下面的程式碼展示瞭如何使用isContract()函式:pragma solidity ^0.5.5;import "@openzeppelin/contracts/utils/Address.sol";
contract BasicUtils {    using Address for address;    function checkIfContract(address _addr) public {        return _addr.isContract();    }}
原文連結:7 OpenZeppelin Contracts You Should Always Use[5]作者:Alex Roan[6]References[1] OpenZeppelin的智慧合約程式碼庫: https://openzeppelin.com/[2] 修飾器: https://learnblockchain.cn/docs/solidity/structure-of-a-contract.html#modifier[3] using語句: https://learnblockchain.cn/docs/solidity/contracts.html#using-for
[4] ERC20代幣: https://learnblockchain.cn/docs/eips/eip-20.html[5] 7 OpenZeppelin Contracts You Should Always Use: https://medium.com/better-programming/7-openzeppelin-contracts-you-should-always-use-5ba2e7953cc4[6] Alex Roan: https://medium.com/@alexroan?source=post_page-----5ba2e7953cc4----------------------

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;