You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
190 lines
8.7 KiB
190 lines
8.7 KiB
= 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#ECDSA-toEthSignedMessageHash-bytes32-[`toEthSignedMessageHash`].
|
|
|
|
[source,solidity]
|
|
----
|
|
using ECDSA 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#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. ERC165 is a standard that helps do runtime interface detection. Contracts provide helpers both for implementing ERC165 in your contracts and querying other contracts:
|
|
|
|
* xref:api:utils.adoc#IERC165[`IERC165`] — this is the ERC165 interface that defines xref:api:utils.adoc#IERC165-supportsInterface-bytes4-[`supportsInterface`]. When implementing ERC165, 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 ERC721 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 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]]
|
|
== Math
|
|
|
|
The most popular math related library OpenZeppelin Contracts provides is xref:api:utils.adoc#SafeMath[`SafeMath`], which provides mathematical functions that protect your contract from overflows and underflows.
|
|
|
|
Include the contract with `using SafeMath for uint256;` and then call the functions:
|
|
|
|
* `myNumber.add(otherNumber)`
|
|
* `myNumber.sub(otherNumber)`
|
|
* `myNumber.div(otherNumber)`
|
|
* `myNumber.mul(otherNumber)`
|
|
* `myNumber.mod(otherNumber)`
|
|
|
|
Easy!
|
|
|
|
[[payment]]
|
|
== Payment
|
|
|
|
Want to split some payments between multiple people? Maybe you have an app that sends 30% of art purchases to the original creator and 70% of the profits to the current owner; you can build that with xref:api:finance.adoc#PaymentSplitter[`PaymentSplitter`]!
|
|
|
|
In Solidity, there are some security concerns with blindly sending money to accounts, since it allows them to execute arbitrary code. You can read up on these security concerns in the https://consensys.github.io/smart-contract-best-practices/[Ethereum Smart Contract Best Practices] website. One of the ways to fix reentrancy and stalling problems is, instead of immediately sending Ether to accounts that need it, you can use xref:api:security.adoc#PullPayment[`PullPayment`], which offers an xref:api:security.adoc#PullPayment-_asyncTransfer-address-uint256-[`_asyncTransfer`] function for sending money to something and requesting that they xref:api:security.adoc#PullPayment-withdrawPayments-address-payable-[`withdrawPayments()`] it later.
|
|
|
|
If you want to Escrow some funds, check out xref:api:utils.adoc#Escrow[`Escrow`] and xref:api:utils.adoc#ConditionalEscrow[`ConditionalEscrow`] for governing the release of some escrowed Ether.
|
|
|
|
[[collections]]
|
|
== Collections
|
|
|
|
If you need support for more powerful collections than Solidity's native arrays and mappings, take a look at xref:api:utils.adoc#EnumerableSet[`EnumerableSet`] and xref:api:utils.adoc#EnumerableMap[`EnumerableMap`]. They 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
|
|
|
|
Want to check if an address is a contract? Use xref:api:utils.adoc#Address[`Address`] and xref:api:utils.adoc#Address-isContract-address-[`Address.isContract()`].
|
|
|
|
Want to keep track of some numbers that increment by 1 every time you want another one? Check out xref:api:utils.adoc#Counters[`Counters`]. This is useful for lots of things, like creating incremental identifiers, as shown on the xref:erc721.adoc[ERC721 guide].
|
|
|
|
=== 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-[`ERC721`] or xref:api:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[`ERC1155`]. 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 ERC721:
|
|
|
|
[source, 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
|
|
|
|
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.0;
|
|
|
|
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()
|
|
]);
|
|
----
|
|
|