Skip to content
On this page

工具

OpenZeppelin 合约提供了非常多的工具。下面介绍一些常用工具。

Cryptography

检查链上的签名

ECDSA为管理和还原以太坊地址ECDSA签名提供了函数方法。这个签名通常是通过web3.eth.sign来生成的,它是一个65字节的数组(solidity中的bytes类型),并按照一下的方式排列[[v (1)], [r (32)], [s (32)]]
数据签名可以通过ECDSA.recover来还原,然后将地址和签名进行校对。大部分钱包会将数据进行hash然后签名,然后添加前缀 \x19Ethereum Signed Message:\n,因此当恢复以太坊签名的hash信息时,你需要使用toEthSignedMessageHash

solidity
using ECDSA for bytes32;

function _verify(bytes32 data, bytes memory signature, address account) internal pure returns (bool) {
    return data
        .toEthSignedMessageHash()
        .recover(signature) == account;
}

警告

正确的使用签名验证并非易事,请确保充分的阅读和理解了ECDSA文档。

校验默克尔证明

MerkleProof 提供了 verify校验方法,可以用来校验某些值是不是默克尔树的一部分。

Introspection

在Solidity中,了解合约是否支持你想用的接口通常很有帮助。ERC165是一个有助于进行运行时接口检查的标准。它为在你的合约中实现ERC65或者查询其他的合约提供了很有用的帮助。

solidity
contract MyContract {
    using ERC165Checker for address;

    bytes4 private InterfaceId_ERC721 = 0x80ac58cd;

    /**
    * @dev transfer an ERC721 token from this contract to someone else
    */
    function transferERC721(
        address token,
        address to,
        uint256 tokenId
    )
        public
    {
        require(token.supportsInterface(InterfaceId_ERC721), "IS_NOT_721_TOKEN");
        IERC721(token).transferFrom(address(this), to, tokenId);
    }
}

Math

OpenZeppelin合约中使用最广泛的就是SafeMath,它提供了数学运算函数来防止合约中出现溢出或下溢。
在合约中引入using SafeMath for uint256;,然后就可以使用下面的函数了:

  • myNumber.add(otherNumber)
  • myNumber.sub(otherNumber)
  • myNumber.div(otherNumber)
  • myNumber.mul(otherNumber)
  • myNumber.mod(otherNumber)

非常简单。

Payment

想要对不同类型的用户分割使用不同的支付方式吗?也许在你的APP中,30%的艺术品购买分配给初创团队,70%的收益分配给现有用户,你可以通过PaymentSplitter来实现。
在Solidity中,盲目的向账户转账会有一些安全问题,因为它允许执行任意的代码。你可以在以太坊智能合约最佳实现中获悉这些安全问题。避免这些例如重入,失速问题的方式,就是不要直接将以太坊转账给某个账户,而是使用[PullPayment]来代替,它提供了_asyncTransfer函数来进行转账,稍后会调用[withdrawPayments()]来确认交易。
如果你想由第三者机构来保管资金,请参考EscrowConditionalEscrow

Collections

如果你想使用比solidity原生数组和映射更强大的集合的话,请查看EnumerableSet和[EnumerableMap]。它们和映射类似,但是存储和删除操作不会受输入参数的影响,并且不允许有重复的数据,同时它们支持枚举,也就意味着你在链上或链下都可以轻松的查询到数据。

Misc

想验证一个地址是否为合约?可以使用AddressAddress.isContract()
想要跟踪一些数字,每次你想要另一个数字时都会增加 1?请使用[Counters]。还有一些很有用的工具,比如自动递增的ID,请查看ERC721教程

Base64

Base64工具可以将bytes32数据转换成string类型的数据。
这对建立需要URI安全的 tokenURIs 来说很重要,比如ERC721ERC1155。这个库提供了一种非常巧妙的方式,将符合URL安全的数据URI字符串提供给链上的数据接口。
看一下下面这个ERC721中,以Base64数据URI形式传递的JSON原数据:

solidity
// contracts/My721Token.sol
// SPDX-License-Identifier: MIT

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Base64.sol";

contract My721Token is ERC721 {
    using Strings for uint256;

    constructor() ERC721("My721Token", "MTK") {}

    ...

    function tokenURI(uint256 tokenId)
        public
        pure
        override
        returns (string memory)
    {
        bytes memory dataURI = abi.encodePacked(
            '{',
                '"name": "My721Token #', tokenId.toString(), '"',
                // Replace with extra ERC721 Metadata properties
            '}'
        );

        return string(
            abi.encodePacked(
                "data:application/json;base64,",
                Base64.encode(dataURI)
            )
        );
    }
}

Multicall

Multicall抽象合约中带有一个multicall函数,它可以将多个调用捆绑在一个单独的外部调用中。有了它,外部账户就可以执行包含多个函数调用的原子操作。这不仅是对EOA在单个事务中调用很有用,它也可以在后续调用失败后恢复之前的状态。
来看看下面这个虚构的合约:

solidity
// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/Multicall.sol";

contract Box is Multicall {
    function foo() public {
        ...
    }

    function bar() public {
        ...
    }
}

下面展示在Truffle中使用multicall函数,在一个事务中调用foo函数和bar函数:

solidity
// scripts/foobar.js

const Box = artifacts.require('Box');
const instance = await Box.new();

await instance.multicall([
    instance.contract.methods.foo().encodeABI(),
    instance.contract.methods.bar().encodeABI()
]);

Released under the MIT License.