2.28 OpenZeppelin Libraries
Most libaries that you'll encounter inside smart contracts are written and maintained by OpenZeppelin
, which is one of the leaders in the space in not only developing these libraries, but in Ethereum smart contracts security.
They provide multiple services and multiple tools in this context, so these OpenZeppelin
libraries are widely used and have been time tested for several years now. Furthermore, they've also been optimized over time with respect to the Gas consumed by them and also with respect to the various Solidity
versions that have been released over time.
One of the most common OpenZeppelin
libraries is the SafeMath
library that we discussed in the context of arithmetic checks. There numerous other OpenZeppelin
libraries related to the implementation of token standards, various security functionalities, proxy contracts and utilities. You'll encounter one or more of these OpenZeppelin
libraries when you're developing smart contracts as a developer or when you're auditing smart contracts for the security.
Token Libraries
OpenZeppelin Token Libraries
ERC20
Let's start with the OpenZeppelin
library that implements ERC20 token standard. This is perhaps the most popular, widely used and commonly seen token standard that you would encounter as a developer or as a smart contact security auditor.
This library implements all the required functions specified by the token standard. It implements:
Name.
Symbol.
Decimals.
Total supply (that returns the amount of tokens in existence so far).
The
balanceOf()
function (that returns the amount of tokens owned by specific accounts).The
transfer()
andtransferFrom()
functions (that help moving tokens from one address to another).The notion of allowance (which specifies a spender in addition to the owner of the tokens where the owner grants a certain allowance to the spender after which the spender can spend those tokens and send them to different other addresses).
The notion of increasing or decreasing allowance (that the owner implements for a specific spender).
There are various extensions and presets and utilities related to these standards.
safeERC20
\One such utility related to the
ERC20
token standard is what is referred to assafeERC20
. Thetransfer
,transferFrom
,approve
,increase
anddecrease allowance
functions ofERC20
tokens are expected by the specification to return abool
value. Contracts implementing the standard which might choose not to return abool
effectively deviate from the specification. They may revert for these tokens on these functions under certain conditions or they may return no value.\These differing return values, or exception handling in the case of
ERC20
tokens, have resulted in security vulnerabilities, therefore thissafeERC20
utility implements wrappers for these functions. It implements the safe versions, sosafetransfer
,safetransferFrom
,safeapprove
,safeincrease
andsafedecrease
that always revert to failure after checking the different conditions for these functions.\You may notice this utility being used with the contracts with the
using for
directive ofSolidity
asusing safeERC20 for IERC20;
.TokenTimelock
\The next utility is what is known as
TokenTimelock
. This implements a token holder contract where tokens are held by the contract and there is a specific address that is defined as the beneficiary address for all the tokens held by this contract, that are only released to that beneficiary address after a particular time has expired.\The application are things like token investing, where a certain number of tokens are allocated to the various team members: to the advisors and so on... that need to be claimable by them only after a certain point in time.\
This library implements the notion of a token, the beneficiary address and specifically a release function that, when triggered, checks if the
block.timestamp
is greater than the release time that was declared earlier and if so, transfers the amount of tokens held by the contract to the beneficiary address.
ERC721
The next one is the OpenZeppelin library that implements ERC721
token standard. This is the token standard that is commonly referred to as NFTs or non-fungible tokens. It is perhaps the other widely used popular token standard besides ERC20
that we just talked about.
Unlike ERC20
tokens, ERC721
tokens are considered as non-fungible because every token is distinguishable from the other every token has a tokenId
, unlike ERC20 tokens that are indistinguishable from each other.
So this library implements all the required functions as per the specification:
The
balanceOf()
function (that returns a number of tokens in the specified owner address).The
orderOf()
function (that returns the address that owns the specifiedtokenId
).The
transferFrom()
andsafetransferFrom()
functions (that allow transferring tokens from one address to another address; thesafetransferFrom
function makes certain checks before doing the transfer).
There are multiple checks implemented with respect to the zero address, the ownership of the tokens and specifically to check if the recipient is a contract account, and if so, if that contract recipient is aware of the ERC721
protocol itself. This is done to prevent these tokens from getting locked in that address forever.
Approvals with ERC721
work differently from ERC20
: unlike ERC20 (that has a notion of spender for the tokens), ERC721
introduces the concept of an operator, which is somewhat similar. The approve function in this case specifies the address of the operator, the specific tokenId
and it gives permission to the operator to transfer this particular token to another account.
This approval is automatically cleared when the token is transferred and only a single account can be approved at any time, which means that approving the zero address clears the previous approvals. There are other functions associated with the ERC721
as part of this library and there are also various extensions presets and utilities similar to the ERC20
contract.
ERC777
The next library is one that implements the ERC777
token standard. This is a token standard similar to ERC20
. It's backwards compatible with ERC20
, so it implements a standard for fungible tokens and it's considered as implementing several improvements over ERC20
.
One of the key features is the notion of hooks, which are functions within the contract that are called automatically when tokens are being sent from it, or when tokens are being received. This allows the contract to control and reject which tokens are being sent and which tokens are being received. These features allow us to implement several improvements over ERC20
such as avoiding the need for a separate approve
and transferFrom
transactions, which is considered as a significant user experience challenge for ERC20
contracts.
ERC777
also allows one to prevent tokens from getting stuck in the contracts using the hooks feature. This also implements the decimals as being a fixed value of 18, so there's no need for the contract to set or change it. It introduces a notion of operators that are special accounts that can transfer tokens on behalf of others and it also implements a send
function where, if the recipient contract is not aware of ERC777
by not having registered itself as being aware, then transfers to that contract are disabled to prevent tokens from getting stuck in that contract.
ERC1155
ERC1155
is another token standard that allows a contract to manage tokens in a fungibility agnostic and Gas efficient manner, so a single contract that implements a standard that can manage multiple tokens, some of which can be fungible tokens like ERC20
or NFTs. All these are managed within a single contract: this means that a single transaction can manipulate multiple tokens within that transaction.
This makes it very convenient from a user experience perspective. It also makes this standard very Gas efficient. This standard specifically provides two functions: balanceOfBatch()
and safeBatchtransfersFrom()
that allow querying balances of multiple tokens and transferring multiple tokens in the same transaction. This makes the management of these tokens within the contract very simple and Gas efficient.
Access Control
OpenZeppelin Access Control Libraries
Ownable
The Ownable
library of OpenZeppelin
allows a smart contract to implement basic access control by introducing the notion of the owner for a particular contract.
The default owner is the address that deployed the contract, this allows the smart contract to implement access control on special or critical functions that modify critical parameters within that contract to only be accessible by this owner address. This is made possible by the modifier onlyOwner
within this library.
This library also supports the transferring of ownership where a new owner can be specified to be switched over from the existing owner. There's also the renounceOwnership
where the ownership is set to the zero address, which essentially makes all the only owner functions uncallable thereafter.
AccessControl
OpenZeppelin
provides a second library to implement a more flexible access control known as role based access control (RBAC for short). This allows a contract to define different roles that are mapped to different sets of permissions, and by using the onlyRole
modifier, access to different functions can be restricted to specific roles.
Every role also has an associated admin
with it that can grant and revoke those roles. So unlike ownable
which implements a very basic access control using the notion of an owner address and all other addresses, this library allows for a more flexible role-based access control.
Security
OpenZeppelin Security Libraries
Pausable
The pausable library from OpenZeppelin
is interesting from a security perspective because it allows teams to execute what is known as a "guarded launch". What this means is that when the team is launching a new project with smart contracts, it's good for the team to anticipate potential emergencies that could arise and using this functionality of the pausable library, they can pause the smart contracts to deal with the emergency, remediate any risks and then unpause the contract to continue normal operations. This is made possible using the pause
and unpause
functions that can be triggered by authorized accounts.
These functions allow the authorized accounts to pause the contract and unpause it at the desired times. The way this works is by using the whenPaused
and whenNotPaused
modifiers on different functions. So in all functions that should be callable during the normal operations of the contract, the whenNotPaused
modifier should be used and for those functions that should still be callable during emergencies, the whenPaused
modifier should be used.
Effectively, this library allows project teams to implement a circuit breaker mechanism to deal with any vulnerabilities discovered in the contract or to also deal with exploits that are happening with the contracts when they can use the pause
functionality, pause the contracts and all the user interactions with the contract, mitigate the risk from that emergency, if possible, then resume normal operations by unpausing the contract.
ReentrancyGuard
The other OpenZeppelin library that is very critical to security is the reentrancy guard library. This is used to mitigate the risk from re-entrancy vulnerabilities that are somewhat unique to smart contracts and very dangerous. This is the vulnerability category that was exploited during the DAO hack, which has historical significance to Ethereum.
Reentrancy vulnerability is: if our smart contract is making an external call to any function of an external contract where that external contract is potentially untrusted (it is not one of our own contracts and it's been deployed by some other project team), then in such cases those external contracts can make a nested call to our contract. So they can re-enter our contract function (the function that made that external call or any other function) and in cases where certain contract state has not been updated within our contract, that aspect can be exploited by this nested call to do things such as transferring tokens multiple times or triggering logic multiple times where in fact it should have been able to do that only one time.
The name "reentrancy attack" because because of the concept of re-entering or nesting that happens, that can be exploited in many different ways. This particular library introduces a modifier called nonReentrant
and when this modifier is applied to different functions in our contract, those functions can't be re-entered after making an external call. This can be used to mitigate reentrancy risk and is one of the standard security best practices that is recommended.
Note that all these security features implemented in these different libraries where specific modifiers need to be used for implementing those checks, are applicable only on functions that use those modifiers. So just by using those libraries in those contracts we do not get the security benefits. Those benefits are realized only on functions where this modifier is used in the expected manner.
PullPayment
OpenZeppelin
implements a pull payment library that is relevant in the context of payments. Payments between two contracts can be done either by the paying contract (by pushing the payment to the receiver account) or the receiving contract (by doing a pull of the payment from the paying contract).
This is interesting in the context of avoiding re-entrancy attacks, so in the case of the pull payment library, the paying contract makes no calls on any of the functions of the receiver contract because the receiver contract may be potentially malicious, and it's better for that receiving contract or account to withdraw the payment itself by using the notion of pull. This prevents reentrancy by favoring the pull payment as opposed to the push payment and therefore is a standard security best practice that is recommended.
Utilities
Various OpenZeppelin Utilities Libraries
Address
The OpenZeppelin
Address
library implements a set of functions related to the address type.
The first one is the
isContract
function that we often encounter within different smart contracts. It takes an address and a contract as parameters and returns abool
. This function returnstrue
if the account address is a contract. However, if it returnsfalse
, then it is not safe to assume that the specified address is an EOA.\The reason for that is because
isContract
will returnfalse
in 4 different situations:If it is an EOA.
If it is a contract account that is in construction (so within the constructor of that contract account).
If it is an address where a contract will be created.
If the address specified had a contract in it, but was later destroyed.
So for all these 4 cases, this function will return
false
and an EOA is only one of the four reasons, so this is something where contracts using this function typically make incorrect assumptions about what this function does and something that has to be paid attention from a security perspective.The second function is
sendValue
. Remember thatSolidity
has atransfer
primitive that sends wei to a recipient contract, but limits the Gas supplied to 2300 Gas units.\This has the drawback that if the Gas Cost of certain opcodes changes (for example, increases over time) then the 2300 subsidy is not going to be sufficient for some of the logic that would be implemented within the
fallback
function of that contract.\So the
sendValue
function removes this 2300 limitation and forwards all the available Gas to the callee contract. This library further implements wrappers around the low-level call primitives supported bySolidity
, so forcall
,staticcall
anddelegatecall
primitives, there are equivalent wrappers that are considered as safer alternatives to using these low primitives directly (functionCall
,functionCallWithValue
,functionStaticCall
,functionDelegateCall
).
Arrays
The OpenZeppelin
Arrays
library implements array related functions. There is a findUpperBound()
function that takes in a uint256
array along with the uint256
element. The array is expected to be sorted in ascending order with no repeat elements in it, and it returns the first index in that array that contains a value greater or equal to the specified element. If there is no such index which means that all the values in the array are strictly less than the element, then in those cases the length of the array itself is returned.
Strings
OpenZeppelin
provides a Strings
library that allows one to perform some basic string operations: there is a toString
function that converts a uint256
to its ASCII
string decimal representation, a toHexString
function that converts it to an ASCII
string hexadecimal representation and finally, a toHex String
that takes in a length parameter that converts a uint256
to a hexadecimal representation with a fixed length.
Context
The context library provides current execution context, specific to the msg.sender
and msg.data
primitives. Remember that these parameters are provided by Solidity
in situations where our smart contract is working with what are known as meta-transactions, where the account sending the transaction and paying for the Gas costs may not be the actual user as far as our applications context is concerned. In such situations, which happen where there are relayers between the user and our smart contract, the functions implemented by this library help us distinguish between the users context and the relayers context.
ERC2771Context
ERC2771Context
library is a variant of the Context
library, that's specific to ERC2771
.
At a high level, there is a transaction signer who originates transactions, by signing it from an EOA, and sends this signed transactions to a relayer off-chain. Then, this relayer is responsible for paying the Gas. ERC2771
specifies a secure protocol for a particular contract to accept such meta-transactions. This protocol is concerned about the Gas layer from forging, modifying or duplicating the requests that are sent by the transaction signer.
It specifies four different entities:
The transaction signer, who signs and sends a transaction off-chain to the Gas relayer.
The Gas relayer receives these transactions and is expected to pay for the Gas, then forwards it to a trusted forwarder contract.
The trusted forwarder contract on-chain, is further responsible for verifying the assigned transaction to look at the nonce, the signature and make sure they are correct. Finally, it forwardz that verified transaction to the contract that is the ultimate destination for the transaction.
Destination contract.
So this protocol is defined by this ERC
, the library provides various functions to help with it.
MinimalForwarder
The MinimalForwarder
library provides support for implementing the trusted forwarder that we discussed in the context of the ERC2771
meta-transactions.
It implements a very simple MinimalForwarder
that verifies the nonce and signature of the forwarded transaction before calling the destination contract and it does.
So with two functions, the verify
function for verification of nonce
and signature; and the execute
function for executing the specific function on the destination contract.
Counters
There's a simple Counters
library that allows a contract to declare new counters, increment and decrement them. This is useful for doing things like tracking the number of mapping elements for ERC721
tokenId
s or for request IDs depending on the application context. There are different functions that let the contract get the current value of a counter, reset it to zero, increment and decrement the counter by one.
Create2
OpenZeppelin
has a Create2
library that provides library functions to use the CREATE2
EVM opcode functionality in an easier and safer manner.
Remember that EVM has two instructions: CREATE
and CREATE2
that allow contracts to programmatically create other contracts. This is in contrast to creating contracts by sending a transaction to the zero address so, if we think of this as a deployer contract that is creating a newly deployed contract, then the CREATE
opcode uses the address of the deployer contract along with the state of the deployed contract in the form of the nonce of that contract account to determine the address of the newly deployed contract.
Contrast to this, the CREATE2
opcode does not use the state of the deployer contact at all. Instead it only uses the bytecode of the newly deployed contract along with a value provided by the deployer contract (known as the salt
), to determine the address of the newly deployed contract.
Because of this change, the address of the newly deployed contract becomes deterministic. In this case the deploy
library function uses 3 parameters: the amount
, salt
and bytecode to create and deploy a newly deployed contract.
amount
is the amount of the Ether balance the newly deployed contract will start off with, if one only wants to determine the address of the new contract without actually deploying it, there is a library function called the computeAddress
that helps one to do that and, if one wants to compute the address of this contract, if it is going to be deployed from a different deployer address, then there's a different library function computeAddress
that takes an additional parameter which is the address of the deployer.
Multicall
OpenZeppelin
provides a Multicall
library that allows a smart contract to batch multiple calls together in a single external call to this contract.
This function is multicall
: it takes in a single data parameter and it returns a bytes
array of all the return parameters from those multiple points. It helps the contract to receive and execute multiple function calls in a batch. The benefit of this is that it is less overhead and makes it more Gas efficient because all these multiple calls are now packaged in a single call within the same transaction of the same block.
ERC165
The ERC165
library allows one to determine if a particular contract supports a particular function interface. This runtime detection is implemented using a lookup table.
It provides two functions: the first one is _registerInterface
and is used for registering function interfaces. The second one, supportsInterface
, is to determine if a particular interface is supported which returns a bool
either true
or false
.
TimelockController
The TimelockController
library provides library functions for enforcing timelocks. Timelocks are nothing but time delayed operations: ff there are operations that need to be executed only after a certain window of time delay has passed or occurred, that is referred to as timelock.
This library provides various functions to enforce a timelock on onlyOwner
operations. OnlyOwner
here refers to the modifier for access control which when applied to functions allows only the Owner of that smart contract to execute that function. This becomes critical from a security perspective because onlyOwner
operations are used in smart contracts to make changes to critical parameters of that protocol or project.
They're also used on functions that enforce or change access control for that smart contract, so in all these scenarios, if we want to give the users who interact with the smart contract an opportunity to notice these operations that are making these critical changes, then decide if they would like to continue engaging with the smart contract or if they would like to exit from engaging with the smart contract by removing the funds from the smart contract or some other logic, then Timelock
becomes useful for providing a mechanism to do so.
This library provides various functions that help us schedule, delay, execute, cancel such operations or do, so in batches all in a timelocked specific manner. There are also functions that let us query, if an operation is pending, if it is ready, if it is already done in the context of the timelock and one can also update the delay that is specific to the timelock operation.
Financial Utilities
OpenZeppelin Financial Utilities Libraries
Escrow
The Escrow
library allows a smart contract to hold funds for a designated payee until they withdraw them. The contract that uses this as the payment method is its owner and it provides three functions to allow this functionality: there is the depositsOf
function that returns the the amount of the funds designated for the payee, there are the deposit
and the withdraw
functions themselves that are only callable by the owner.
ConditionalEscrow
The ConditionalEscrow
library is derived from the Escrow
library and as the name says it only allows withdrawal if a particular condition is met. The withdrawalAllowed
function checks for this condition and returns true
or false
, if it is met or not. The withdraw
function itself is of public visibility and does not have the onlyOwner
modifier here, but it checks the withdrawalAllowed
condition and if that is met it calls the base contract's withdraw
function that has the onlyOwner
modifier.
RefundEscrow
The RefundEscrow
library is further built on top of the ConditionalEscrow
library that we just discussed. This allows holding funds for a beneficiary that are deposited from multiple parties multiple depositors.
This contract has three states in which it can be:
The active state: when deposits are allowed to be made by the multiple depositors.
The refunding state: refunding is where refunds are sent back to the depositors.
The closed state: the state in which the beneficiary can make the withdrawals.
PaymentSplitter
The PaymentSplitter
library provides functions that allows to split Ether payments among a group of accounts. The sender, who sends Ether to this contract that uses this library does not know about the splitting aspect, so it is sender agnostic. The splitting can be done in equal proportions or in an arbitrary manner.
This is done by assigning a particular number of shares to every account. That account can later claim an amount of Ether that is proportional to the percentage of the total shares that they were assigned. This follows the PullPayment
model that we have discussed earlier, which is much safer from a security perspective than a PushPayment model.
Cryptography Utilities
OpenZeppelin Cryptography Utilities Libraries
ECDSA
OpenZeppelin
provides an ECDSA
library. Remember that ECDSA signatures are used very commonly in Ethereum smart contracts. The signature itself has three components v
, r
and s
which are bytes1
, byte32
and bytes32
in length respectively, making the signature 65 bytes.
The EVM has an ecrecover
opcode and Solidity
has a similar primitive that supports this opcode. But that opcode allows for what are known as malleable (or non-unique signatures if you remember).
This library prevents that by providing a library function recovered that is not susceptible to this malleability. The way that it's made possible is that this function requires the s
value that signature to be in the lower half order, the v
value to be either 27 or 28, so this becomes important depending on how the smart contract is using the signatures and, if malleability is a concern or a risk, for that use case the ecrecover
function takes in the hash of the message (the signature component of that message) and returns a signer address.
To sum it up, the EVM ecrecover
is malleable which may be a concern depending on how the signature is being used in the smart contract logic. This library provides a non-malleable way of using ecrecover
.
MerkleProof
The MerkleProof
library provides functionality to help with the verification of Merkle tree proofs. Remember that Merkle trees are data structures where the leaves contain the data and all the other nodes in the tree contain a combination of the hashes of their two child nodes.
This library provides a verify function that takes in three parameters: the leaf, the root, the proof, and returns a bool
value which is true
if the leaf parameter can be proved to be a part of the Merkle tree defined by the root parameter.
In order to do that, a proof must be provided to this function that contains all the sibling hashes on the branch from the leave to the root of the tree. This is an interesting library that is used often where Mertkle tree proofs are required within smart contracts.
SignatureChecker
The SignatureChecker
library provides functionality that allows smart contracts to work with both ECDSA signatures and ERC1271
signatures.
We've talked about ECDSA signatures that are signatures that can be created with the use of a private key which is possible only with EOAs. The reason for this is that contracts can't possess a private key because all contract state is public.
ERC1271
allows the concept of contract signatures in in a manner that is different from ECDSA signatures. This library becomes interesting for applications such as smart contact wallets that need to work with the contract signatures and ECDSA signatures.
EIP-712
There is an EIP712
library that provides support for the hashing and signing of typed structured data as opposed to binary blobs. This supports the notion of an EIP-712
domain separator.
The source code of this library this is again often used in smart contracts and from a security perspective, what becomes interesting here is whether this signature includes the chainId
of the chain where the smart contract is deployed and being executed and whether this also includes the address of the smart contract itself.
Not using these two values within the signature can allow replay attacks, if the contact is redeployed to some other address on the same chain or to a different chain.
Math Utilities
OpenZeppelin Math Utilities Libraries
Math
OpenZeppelin
provides a Math
library that has some basic standard math utilities that are missing in the Solidity
language itself. There's a max
function that returns the maximum of two uint256
values. There's a min
function that provides the minimum of those two values. Then the average
function that returns the average of those two numbers, which is rounded towards zero.
SafeMath
Then there is the SafeMath
library which we have talked about earlier. It provides the basic math functions that are safe from overflow and underflow conditions because of wrapping.
It has support for add
, sub
, mul
, div
and mod
functions. The typical usage is done via the using for
directive where you would see something like using SafeMath for uint256
where the SafeMath
library functions are applied to all variables of type uint256
in that contract.
There are the try...
variants of these functions where instead of reverting, if the overflow and underflows happen a flag is returned. This is useful for exception handling, so this SafeMath
library is almost absolutely required for smart contracts that deal with integers and use a Solidity
compiler version below 0.8.0
(because remember that Solidity 0.8.0
introduced default overflow and underflow checked arithmetic).
SignedSafeMath
The SignedSafeMath
library provides the same mathematical functions as SafeMath, but for signed integers. The only operation that is missing is the modulus operation which does not make sense for signed integers. The motivation for this is the same as SafeMath.
SafeCast
Remember that Solidity
allows both implicit casting of types and explicit casting between types. Explicit casting is where the developers can force the compiler to cast one type into another type where the compiler may not be able to determine that it is safe to do. So in cases where the developers want to do what is known as downcasting, the OpenZeppelin
's SafeCast
library provides various functions to do so in a safe manner.
Downcasting is when the developer wants to cast a source type into a target type where the target type has fewer storage bits to represent it than the source type. In such cases, because the target type has fewer storage bits, it may not always be safe to do so.
If the variable of that type actually requires the storage bits being reduced from the source type to destination type. The SafeCast
library provides functions that allow the developer to determine if that downcasting is safe and if not, it raises an exception by reverting the transaction.
There are various functions to safely downcast from uint256
to uint224
and all the way to uint8
. Similarly, there are functions for signed integers to do so as well, so these functions become very useful for developers when they're doing downcasting to prevent overflows because of doing so.
Structs Extension Libraries
OpenZeppelin Structs Extension Libraries
EnumerableMap
Remember that the mapping types and Solidity
can't be enumerated for all the keys and values that they contain. The EnumerableMap
library of OpenZeppelin
allows a developer to create and use EnumerableMaps.
Adding and removing entries from this mapping type can be done in constant time. Checking for existence of entries can also be done in constant time. Enumerating the maps can be done in O(n), n is the size of the mapping. As of the latest version, the only supported mapping type is the one where keys are of uint256
and the values are of address
type.
EnumerableSet
The EnumerableSet
library allows the developers to use enumerated sets. There are various functions that are provided to manage the sets, adding and removing entries to the set and checking entries for existence. Again can be done in O(1) time (that's constant time). Enumerating them can be done in O(n) time. As of the latest version, the only supported set types are those that contain bytes
, address
or uint256
.
BitMaps
Bitmaps are commonly encountered data structures in computer science, where every bit of the underlying type can be thought of as representing a different variable. The BitMaps
library maps a uint256
type to bool
types, where this bitmap can be used to represent 256 different bool
values within that single uin256
type.
This library allows developers to do that in a very compact and efficient manner. The library provides 4 different functions to operate on these BitMaps:
The
get
function returns thebool
value at a particular index of the bitmap.The
setTo
function allows us to set the value at a particular index of the bitmap to the specified value.The
set
function sets the value of the bitmap at that index to 1.The
unset
function sets the value of the index at that bitmap to 0.
Proxies
OpenZeppelin
provides support for different libraries that help with proxies. At a high level the Proxy setup requires two contracts: the Proxy contract, and what is known as the implementation contract.
The Proxy contract receives the calls from the user, and forwards it to the implementation contract, this forwarding is done via delegateCall
. In this setup the Proxy contract is typically the one that holds the contract state, the implementation contract is the one that implements the logic. So when the forwarding is done via delegateCall
, the implementation logic executes that logic on the state held in the Proxy contract.
As you can imagine this has to be done in a very careful manner because it can lead to a variety of security issues, there are many many articles that have been written on this topic by OpenZeppelin
and also by Trail of Bits
and other security firms.
So, OpenZeppelin
's basic Proxy
library provides a fallback
function, that forwards the call to an implementation. It also provides a delegate
function, that allows one to specify, the delegation to a specific implementation contract. This also allows us to specify a hook, via the beforeFallback
function, that gets called before falling back to the implementation.
Various OpenZeppelin Proxy Libraries
ERC1967Proxy
The ERC1967Proxy
library helps us implement what are known as upgradable proxies. These are upgradable because the implementation contract that sits behind the Proxy can be changed to point to a different implementation contract.
Remember the Proxy setup where the application state is held in the Proxy contract, the logic may be implemented in the implementation contract. So, if you want the logic to change for whatever reason maybe to fix a bug, in the current implementation or to enhance and add more logic, upgradeable proxies are one way to do so.
In this case, the address of the implementation contract that can be changed is stored in the storage of the Proxy contract. This specific storage location is specified by the EIP, so that it does not conflict with the layout of the implementation contact that sits behind the Proxy.
The address of the logic or the implementation contract can be specified as part of the constructor, the address of the new implementation can be provided while upgrading using the upgrade function. So upgradeable proxies are something that we encounter commonly in smart contracts, this again has to be done in a very careful manner because it can lead to security issues such as the storage conflict that is specified here.
TransparentUpgradeableProxy
Another Proxy related library is the TransparentUpgradeableProxy
. This helps one implement a Proxy that is upgradable only by an admin. It specifically helps us mitigate the risk due to attacks from Selector Clash.
What this means is that, if a function is present both in the Proxy and the implementation such that their selectors, their function selectors clash (i.e. they evaluate to the same value) which could lead to problems, because if there is a function call to that function, then it will not be clear if the function should be executed in the context of the Proxy contract or, if it should be forwarded to the implementation contract.
So this library specifies that all function calls coming from the non-admin users will be forwarded to the implementation contract even, if those calls match the function selected of the Proxy contract. Similarly, the function calls made by the admin users are restricted to the Proxy contract, they are not forwarded to the implementation contract.
This allows for clean separation where the admin functions are restricted to the Proxy contract and non-admin functions are forwarded to the implementation contract. So the admin can do things such as upgrade the implementation contract or create the admin address itself.
ProxyAdmin
The ProxyAdmin
library is meant to be used as the admin of the TransparentUpgradeableProxy
that we just discussed. It provides support for various functions that are required by the admin, these include:
The
getProxyImplementation()
which returns the implementation contract address.The
getProxyAdmin()
which returns the admin address.changeProxyAdmin()
, that changes the ProxyAdmin.(upgrade(proxy, implementation)
, that upgrades the implementation contract pointed to by the Proxy.The
upgradeAndCall(proxy, implementation, data)
function that both upgrades implementation, then makes a call to that new implementation.
BeaconProxy
The BeaconProxy library allows one to implement a Proxy where the implementation address is obtained from a different contract known as a beacon contract. That beacon contract itself is upgraded:
Implementation Address→UpgradeableBeacon
The address of the beacon contract is stored in the Proxy storage at a slot specified by EIP1967
:
Beacon Address -> Slot uint256(keccak256("eip1967.proxy.beacon")) - 1
The constructor can be used to initialize where the beacon contact is located. There are functions that allow us to get the address of the beacon the address of the implementation:
Constructor -> Beacon Init, _beacon() -> Beacon Addr
Finally, to set the beacon contract to a different address than what was initialized:
_implementation()
_setBeacon(beacon, data)
UpgradeableBeacon
The UpgradeableBeacon
library provides support for implementing the beacon contract in the context of the BeaconProxy
that we just discussed.
The Owner of this contract can change the implementation contract that this BeaconProxy
points to. The initial implementation contract is specified in the constructor, the Owner is the one who deployed the contract.
There are functions that allow one to determine what that implementation contract is and also to upgrade it to a new implementation: _implementation()
, upgradeTo(newImlementation)
.
Clones
OpenZeppelin
's Clones
library helps one implement what are known as minimal Proxy contracts as specified by EIP1167
. In this case all the implementation contracts are clones of specific byte code, where all the calls are delegated to a known fixed address.
The deployment can be done in a traditional way using create or it can be done in a deterministic way using CREATE2
.
Corresponding to these two deployment options, there are two functions:
There's the
clone(implementation)
function that clones that implementation and returns the address of the instance deployed using createThere is the equivalent version for
CREATE2
thecloneDeterministic(implementation, salt)
that takes in the implementation, the sort and returns the instance of the clone that was created.
Initializable
The Initializable
library provides critical functionality that is required for applications that work with Proxy contracts.
Remember that in the Proxy setup we have a Proxy contract that forwards all the calls to an implementation contract. The Proxy contract maintains the data or the application state and delegates the calls to the implementation contract, which implements the logic that works on the application state maintained by the Proxy contract.
So in this setup, if there are functions in the implementation contract that need to work with certain initialized values, then all such initialization should not be done in the constructor of the implementation contract, because the constructor would modify the state of the implementation contract which is never used in this setup.
So all this initialization is expected to be moved to a different function, which is typically called the initialize function that has an external visibility, this initialized function is expected to be called by the Proxy contract.
This aspect of not using constructors for initialization, but using a separate initialize function applies not only to the implementation contract, but to all the base contracts that it derives. This initialization should be performed only once and should be performed immediately after the implementation contract is deployed, either from a deploy script or from a factory contract.
The Initializable
library provides an initializer
modifier, which when applied to this initialize function allows that to be called only once. So these concepts of the Proxy setup, the fact that the implementation contract should not be using a constructor, but instead an OpenZeppelin
Initializable
library function that needs to be called immediately after deployment, more importantly needs to be called only once.
These are very critical from a security perspective there have been multiple vulnerabilities reported because of this not being followed multiple exploits and something that therefore needs to be paid very careful attention to.
Last updated