123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- = Utilities
- The OpenZeppelin Contracts provide a ton of useful utilities that you can use in your project. Here are some of the more popular ones.
- [[cryptography]]
- == Cryptography
- === Checking Signatures On-Chain
- xref:api:utils.adoc#ECDSA[`ECDSA`] provides functions for recovering and managing Ethereum account ECDSA signatures. These are often generated via https://web3js.readthedocs.io/en/v1.7.3/web3-eth.html#sign[`web3.eth.sign`], and are a 65 byte array (of type `bytes` in Solidity) arranged the following way: `[[v (1)], [r (32)], [s (32)]]`.
- The data signer can be recovered with xref:api:utils.adoc#ECDSA-recover-bytes32-bytes-[`ECDSA.recover`], and its address compared to verify the signature. Most wallets will hash the data to sign and add the prefix '\x19Ethereum Signed Message:\n', so when attempting to recover the signer of an Ethereum signed message hash, you'll want to use xref:api:utils.adoc#MessageHashUtils-toEthSignedMessageHash-bytes32-[`toEthSignedMessageHash`].
- [source,solidity]
- ----
- using ECDSA for bytes32;
- using MessageHashUtils for bytes32;
- function _verify(bytes32 data, bytes memory signature, address account) internal pure returns (bool) {
- return data
- .toEthSignedMessageHash()
- .recover(signature) == account;
- }
- ----
- WARNING: Getting signature verification right is not trivial: make sure you fully read and understand xref:api:utils.adoc#MessageHashUtils[`MessageHashUtils`]'s and xref:api:utils.adoc#ECDSA[`ECDSA`]'s documentation.
- === Verifying Merkle Proofs
- xref:api:utils.adoc#MerkleProof[`MerkleProof`] provides:
- * xref:api:utils.adoc#MerkleProof-verify-bytes32---bytes32-bytes32-[`verify`] - can prove that some value is part of a https://en.wikipedia.org/wiki/Merkle_tree[Merkle tree].
- * xref:api:utils.adoc#MerkleProof-multiProofVerify-bytes32-bytes32---bytes32---bool---[`multiProofVerify`] - can prove multiple values are part of a Merkle tree.
- [[introspection]]
- == Introspection
- In Solidity, it's frequently helpful to know whether or not a contract supports an interface you'd like to use. ERC-165 is a standard that helps do runtime interface detection. Contracts provide helpers both for implementing ERC-165 in your contracts and querying other contracts:
- * xref:api:utils.adoc#IERC165[`IERC165`] — this is the ERC-165 interface that defines xref:api:utils.adoc#IERC165-supportsInterface-bytes4-[`supportsInterface`]. When implementing ERC-165, you'll conform to this interface.
- * xref:api:utils.adoc#ERC165[`ERC165`] — inherit this contract if you'd like to support interface detection using a lookup table in contract storage. You can register interfaces using xref:api:utils.adoc#ERC165-_registerInterface-bytes4-[`_registerInterface(bytes4)`]: check out example usage as part of the ERC-721 implementation.
- * xref:api:utils.adoc#ERC165Checker[`ERC165Checker`] — ERC165Checker simplifies the process of checking whether or not a contract supports an interface you care about.
- * include with `using ERC165Checker for address;`
- * xref:api:utils.adoc#ERC165Checker-_supportsInterface-address-bytes4-[`myAddress._supportsInterface(bytes4)`]
- * xref:api:utils.adoc#ERC165Checker-_supportsAllInterfaces-address-bytes4---[`myAddress._supportsAllInterfaces(bytes4[\])`]
- [source,solidity]
- ----
- contract MyContract {
- using ERC165Checker for address;
- bytes4 private InterfaceId_ERC721 = 0x80ac58cd;
- /**
- * @dev transfer an ERC-721 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]]
- == Math
- Although Solidity already provides math operators (i.e. `+`, `-`, etc.), Contracts includes xref:api:utils.adoc#Math[`Math`]; a set of utilities for dealing with mathematical operators, with support for extra operations (eg. xref:api:utils.adoc#Math-average-uint256-uint256-[`average`]) and xref:api:utils.adoc#SignedMath[`SignedMath`]; a library specialized in signed math operations.
- Include these contracts with `using Math for uint256` or `using SignedMath for int256` and then use their functions in your code:
- [source,solidity]
- ----
- contract MyContract {
- using Math for uint256;
- using SignedMath for int256;
- function tryOperations(uint256 a, uint256 b) internal pure {
- (bool overflowsAdd, uint256 resultAdd) = x.tryAdd(y);
- (bool overflowsSub, uint256 resultSub) = x.trySub(y);
- (bool overflowsMul, uint256 resultMul) = x.tryMul(y);
- (bool overflowsDiv, uint256 resultDiv) = x.tryDiv(y);
- // ...
- }
- function unsignedAverage(int256 a, int256 b) {
- int256 avg = a.average(b);
- // ...
- }
- }
- ----
- Easy!
- [[structures]]
- == Structures
- Some use cases require more powerful data structures than arrays and mappings offered natively in Solidity. Contracts provides these libraries for enhanced data structure management:
- - xref:api:utils.adoc#BitMaps[`BitMaps`]: Store packed booleans in storage.
- - xref:api:utils.adoc#Checkpoints[`Checkpoints`]: Checkpoint values with built-in lookups.
- - xref:api:utils.adoc#DoubleEndedQueue[`DoubleEndedQueue`]: Store items in a queue with `pop()` and `queue()` constant time operations.
- - xref:api:utils.adoc#EnumerableSet[`EnumerableSet`]: A https://en.wikipedia.org/wiki/Set_(abstract_data_type)[set] with enumeration capabilities.
- - xref:api:utils.adoc#EnumerableMap[`EnumerableMap`]: A `mapping` variant with enumeration capabilities.
- The `Enumerable*` structures are similar to mappings in that they store and remove elements in constant time and don't allow for repeated entries, but they also support _enumeration_, which means you can easily query all stored entries both on and off-chain.
- [[misc]]
- == Misc
- === Base64
- xref:api:utils.adoc#Base64[`Base64`] util allows you to transform `bytes32` data into its Base64 `string` representation.
- This is especially useful for building URL-safe tokenURIs for both xref:api:token/ERC721.adoc#IERC721Metadata-tokenURI-uint256-[`ERC-721`] or xref:api:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[`ERC-1155`]. This library provides a clever way to serve URL-safe https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs/[Data URI] compliant strings to serve on-chain data structures.
- Here is an example to send JSON Metadata through a Base64 Data URI using an ERC-721:
- [source, solidity]
- ----
- // contracts/My721Token.sol
- // SPDX-License-Identifier: MIT
- import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
- import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
- import {Base64} from "@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 ERC-721 Metadata properties
- '}'
- );
- return string(
- abi.encodePacked(
- "data:application/json;base64,",
- Base64.encode(dataURI)
- )
- );
- }
- }
- ----
- === Multicall
- The `Multicall` abstract contract comes with a `multicall` function that bundles together multiple calls in a single external call. With it, external accounts may perform atomic operations comprising several function calls. This is not only useful for EOAs to make multiple calls in a single transaction, it's also a way to revert a previous call if a later one fails.
- Consider this dummy contract:
- [source,solidity]
- ----
- // contracts/Box.sol
- // SPDX-License-Identifier: MIT
- pragma solidity ^0.8.20;
- import "@openzeppelin/contracts/utils/Multicall.sol";
- contract Box is Multicall {
- function foo() public {
- ...
- }
- function bar() public {
- ...
- }
- }
- ----
- This is how to call the `multicall` function using Truffle, allowing `foo` and `bar` to be called in a single transaction:
- [source,javascript]
- ----
- // 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()
- ]);
- ----
|