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 p(uint256 a, uint256 b) internal pure returns (uint256) { return p(a, b, "SafeMath: pision by zero"); } function p(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { // Solidity only automatically asserts when piding 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 / ERC721Full
OpenZeppelin 也提供了非同质化代币的实现,我们同样不需要把完整的把标准实现一次。
如果需要枚举一个账号的所持有的 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]
来源链接:mp.weixin.qq.com
- 免责声明
- 世链财经作为开放的信息发布平台,所有资讯仅代表作者个人观点,与世链财经无关。如文章、图片、音频或视频出现侵权、违规及其他不当言论,请提供相关材料,发送到:2785592653@qq.com。
- 风险提示:本站所提供的资讯不代表任何投资暗示。投资有风险,入市须谨慎。
- 世链粉丝群:提供最新热点新闻,空投糖果、红包等福利,微信:juu3644。

币小葱



