|
@@ -1,6 +1,7 @@
|
|
= Utilities
|
|
= 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.
|
|
|
|
|
|
+The OpenZeppelin Contracts provide a ton of useful utilities that you can use in your project. For a complete list, check out the xref:api:utils.adoc[API Reference].
|
|
|
|
+Here are some of the more popular ones.
|
|
|
|
|
|
[[cryptography]]
|
|
[[cryptography]]
|
|
== Cryptography
|
|
== Cryptography
|
|
@@ -9,7 +10,7 @@ The OpenZeppelin Contracts provide a ton of useful utilities that you can use in
|
|
|
|
|
|
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)]]`.
|
|
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`].
|
|
|
|
|
|
+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]
|
|
[source,solidity]
|
|
----
|
|
----
|
|
@@ -27,12 +28,18 @@ WARNING: Getting signature verification right is not trivial: make sure you full
|
|
|
|
|
|
=== Verifying Merkle Proofs
|
|
=== Verifying Merkle Proofs
|
|
|
|
|
|
|
|
+Developers can build a Merkle Tree off-chain, which allows for verifying that an element (leaf) is part of a set by using a Merkle Proof. This technique is widely used for creating whitelists (e.g. for airdrops) and other advanced use cases.
|
|
|
|
+
|
|
|
|
+TIP: OpenZeppelin Contracts provides a https://github.com/OpenZeppelin/merkle-tree[JavaScript library] for building trees off-chain and generating proofs.
|
|
|
|
+
|
|
xref:api:utils.adoc#MerkleProof[`MerkleProof`] provides:
|
|
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-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.
|
|
* xref:api:utils.adoc#MerkleProof-multiProofVerify-bytes32-bytes32---bytes32---bool---[`multiProofVerify`] - can prove multiple values are part of a Merkle tree.
|
|
|
|
|
|
|
|
+For an on-chain Merkle Tree, see the xref:api:utils.adoc#MerkleTree[`MerkleTree`] library.
|
|
|
|
+
|
|
[[introspection]]
|
|
[[introspection]]
|
|
== Introspection
|
|
== Introspection
|
|
|
|
|
|
@@ -98,6 +105,8 @@ contract MyContract {
|
|
|
|
|
|
Easy!
|
|
Easy!
|
|
|
|
|
|
|
|
+TIP: While working with different data types that might require casting, you can use xref:api:utils.adoc#SafeCast[`SafeCast`] for type casting with added overflow checks.
|
|
|
|
+
|
|
[[structures]]
|
|
[[structures]]
|
|
== Structures
|
|
== Structures
|
|
|
|
|
|
@@ -108,12 +117,71 @@ Some use cases require more powerful data structures than arrays and mappings of
|
|
- xref:api:utils.adoc#DoubleEndedQueue[`DoubleEndedQueue`]: Store items in a queue with `pop()` and `queue()` constant time operations.
|
|
- 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#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.
|
|
- xref:api:utils.adoc#EnumerableMap[`EnumerableMap`]: A `mapping` variant with enumeration capabilities.
|
|
|
|
+- xref:api:utils.adoc#MerkleTree[`MerkleTree`]: An on-chain https://wikipedia.org/wiki/Merkle_Tree[Merkle Tree] with helper functions.
|
|
|
|
|
|
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.
|
|
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.
|
|
|
|
|
|
|
|
+=== Building a Merkle Tree
|
|
|
|
+
|
|
|
|
+Building an on-chain Merkle Tree allow developers to keep track of the history of roots in a decentralized manner. For these cases, the xref:api:utils.adoc#MerkleTree[`MerkleTree`] includes a predefined structure with functions to manipulate the tree (e.g. pushing values or resetting the tree).
|
|
|
|
+
|
|
|
|
+The Merkle Tree does not keep track of the roots purposely, so that developers can choose their tracking mechanism. Setting up and using an Merkle Tree in Solidity is as simple as follows:
|
|
|
|
+
|
|
|
|
+[source,solidity]
|
|
|
|
+----
|
|
|
|
+// NOTE: Functions are exposed without access control for demonstration purposes
|
|
|
|
+
|
|
|
|
+using MerkleTree for MerkleTree.Bytes32PushTree;
|
|
|
|
+MerkleTree.Bytes32PushTree private _tree;
|
|
|
|
+
|
|
|
|
+function setup(uint8 _depth, bytes32 _zero) public /* onlyOwner */ {
|
|
|
|
+ root = _tree.setup(_depth, _zero);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function push(bytes32 leaf) public /* onlyOwner */ {
|
|
|
|
+ (uint256 leafIndex, bytes32 currentRoot) = _tree.push(leaf);
|
|
|
|
+ // Store the new root.
|
|
|
|
+}
|
|
|
|
+----
|
|
|
|
+
|
|
[[misc]]
|
|
[[misc]]
|
|
== Misc
|
|
== Misc
|
|
|
|
|
|
|
|
+=== Storage Slots
|
|
|
|
+
|
|
|
|
+Solidity allocates a storage pointer for each variable declared in a contract. However, there are cases when it's required to access storage pointers that can't be derived by using regular Solidity.
|
|
|
|
+For those cases, the xref:api:utils.adoc#StorageSlot[`StorageSlot`] library allows for manipulating storage slots directly.
|
|
|
|
+
|
|
|
|
+[source,solidity]
|
|
|
|
+----
|
|
|
|
+bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
|
|
|
|
+
|
|
|
|
+function _getImplementation() internal view returns (address) {
|
|
|
|
+ return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function _setImplementation(address newImplementation) internal {
|
|
|
|
+ require(newImplementation.code.length > 0);
|
|
|
|
+ StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
|
|
|
|
+}
|
|
|
|
+----
|
|
|
|
+
|
|
|
|
+WARNING: Manipulating storage slots directly is an advanced practice. Developers MUST make sure that the storage pointer is not colliding with other variables.
|
|
|
|
+
|
|
|
|
+One of the most common use cases for writing directly to storage slots is ERC-7201 for namespaced storage, which is guaranteed to not collide with other storage slots derived by Solidity.
|
|
|
|
+
|
|
|
|
+Users can leverage this standard using the xref:api:utils.adoc#SlotDerivation[`SlotDerivation`] library.
|
|
|
|
+
|
|
|
|
+[source,solidity]
|
|
|
|
+----
|
|
|
|
+using SlotDerivation for bytes32;
|
|
|
|
+string private constant _NAMESPACE = "<namespace>" // eg. example.main
|
|
|
|
+
|
|
|
|
+function erc7201Pointer() internal view returns (bytes32) {
|
|
|
|
+ return _NAMESPACE.erc7201Slot();
|
|
|
|
+}
|
|
|
|
+----
|
|
|
|
+
|
|
=== Base64
|
|
=== Base64
|
|
|
|
|
|
xref:api:utils.adoc#Base64[`Base64`] util allows you to transform `bytes32` data into its Base64 `string` representation.
|
|
xref:api:utils.adoc#Base64[`Base64`] util allows you to transform `bytes32` data into its Base64 `string` representation.
|