Secureum Book
  • 🛡️Secureum Bootcamp
    • 🛡️Secureum Bootcamp
    • 🙌Participate
    • 📜History
  • 📚LEARN
    • Introduction
      • 🔷1. Ethereum Basics
        • 1.1 Ethereum: Concept, Infrastructure & Purpose
        • 1.2 Properties of the Ethereum Infrastructure
        • 1.3 Ethereum vs. Bitcoin
        • 1.4 Ethereum Core Components
        • 1.5 Gas Metering: Solving the Halting Problem
        • 1.6 web2 vs. web3: The Paradigm Shift
        • 1.7 Decentralization
        • 1.8 Cryptography, Digital Signature & Keys
        • 1.9 Ethereum State & Account Types
        • 1.10 Transactions: Properties & Components
        • 1.11 Contract Creation
        • 1.12 Transactions, Messages & Blockchain
        • 1.13 EVM (Ethereum Virtual Machine) in Depth
        • 1.14 Transaction Reverts & Data
        • 1.15 Block Explorer
        • 1.16 Mainnet & Testnets
        • 1.17 ERCs & EIPs
        • 1.18 Legal Aspects in web3: Pseudonymity & DAOs
        • 1.19 Security in web3
        • 1.20 web2 Timescales vs. web3 Timescales
        • 1.21 Test-in-Prod. SSLDC vs. Audits
        • Summary: 101 Keypoints
      • 🌀2. Solidity
        • 2.1 Solidity: Influence, Features & Layout
        • 2.2 SPDX & Pragmas
        • 2.3 Imports
        • 2.4 Comments & NatSpec
        • 2.5 Smart Contracts
        • 2.6 State Variables: Definition, Visibility & Mutability
        • 2.7 Data Location
        • 2.8 Functions
        • 2.9 Events
        • 2.10 Solidity Typing
        • 2.11 Solidity Variables
        • 2.12 Address Type
        • 2.13 Conversions
        • 2.14 Keywords & Shorthand Operators
        • 2.15 Solidity Units
        • 2.16 Block & Transaction Properties
        • 2.17 ABI Encoding & Decoding
        • 2.18 Error Handling
        • 2.19 Mathematical & Cryptographic Functions
        • 2.20 Control Structures
        • 2.21 Style & Conventions
        • 2.22 Inheritance
        • 2.23 EVM Storage
        • 2.24 EVM Memory
        • 2.25 Inline Assembly
        • 2.26 Solidity Version Changes
        • 2.27 Security Checks
        • 2.28 OpenZeppelin Libraries
        • 2.29 DAppSys Libraries
        • 2.30 Important Protocols
        • Summary: 201 Keypoints
      • 🔏3. Security Pitfalls & Best Practices
        • 3.1 Solidity Versions
        • 3.2 Access Control
        • 3.3 Modifiers
        • 3.4 Constructor
        • 3.5 Delegatecall
        • 3.6 Reentrancy
        • 3.7 Private Data
        • 3.8 PRNG & Time
        • 3.9 Math & Logic
        • 3.10 Transaction Order Dependence
        • 3.11 ecrecover
        • 3.12 Unexpected Returns
        • 3.13 Ether Accounting
        • 3.14 Transaction Checks
        • 3.15 Delete Mappings
        • 3.16 State Modification
        • 3.17 Shadowing & Pre-declaration
        • 3.18 Gas & Costs
        • 3.19 Events
        • 3.20 Unary Expressions
        • 3.21 Addresses
        • 3.22 Assertions
        • 3.23 Keywords
        • 3.24 Visibility
        • 3.25 Inheritance
        • 3.26 Reference Parameters
        • 3.27 Arbitrary Jumps
        • 3.28 Hash Collisions & Byte Level Issues
        • 3.29 Unicode RTLO
        • 3.30 Variables
        • 3.31 Pointers
        • 3.32 Out-of-range Enum
        • 3.33 Dead Code & Redundant Statements
        • 3.34 Compiler Bugs
        • 3.35 Proxy Pitfalls
        • 3.36 Token Pitfalls
        • 3.37 Special Token Pitfalls
        • 3.38 Guarded Launch Pitfalls
        • 3.39 System Pitfalls
        • 3.40 Access Control Pitfalls
        • 3.41 Testing, Unused & Redundand Code
        • 3.42 Handling Ether
        • 3.43 Application Logic Pitfalls
        • 3.44 Saltzer & Schroeder's Design Principles
        • Summary: 201 Keypoints
      • 🗜️4. Audit Techniques & Tools
        • 4.1 Audit
        • 4.2 Analysis Techniques
        • 4.3 Specification, Documentation & Testing
        • 4.4 False Positives & Negatives
        • 4.5 Security Tools
        • 4.6 Audit Process
        • Summary: 101 Keypoints
      • ☝️5. Audit Findings
        • 5.1 Criticals
        • 5.2 Highs
        • 5.3 Mediums
        • 5.4 Lows
        • 5.5 Informationals
        • Summary: 201 Keypoints
  • 🌱CARE
    • CARE
      • CARE Reports
  • 🚩CTFs
    • A-MAZE-X CTFs
      • Secureum A-MAZE-X
      • Secureum A-MAZE-X Stanford
      • Secureum A-MAZE-X Maison de la Chimie Paris
Powered by GitBook
On this page
  • Token Libraries
  • Access Control
  • Security
  • Utilities
  • Financial Utilities
  • Cryptography Utilities
  • Math Utilities
  • Structs Extension Libraries
  • Proxies
  1. LEARN
  2. Introduction
  3. 2. Solidity

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() and transferFrom() 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 as safeERC20. The transfer, transferFrom, approve, increase and decrease allowance functions of ERC20 tokens are expected by the specification to return a bool value. Contracts implementing the standard which might choose not to return a bool 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 this safeERC20 utility implements wrappers for these functions. It implements the safe versions, so safetransfer, safetransferFrom, safeapprove, safeincrease and safedecrease 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 of Solidity as using 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 specified tokenId).

  • The transferFrom() and safetransferFrom() functions (that allow transferring tokens from one address to another address; the safetransferFrom 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.

  1. 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 a bool. This function returns true if the account address is a contract. However, if it returns false, then it is not safe to assume that the specified address is an EOA.\

    The reason for that is because isContract will return false 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.

  2. The second function is sendValue. Remember that Solidity has a transfer 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 by Solidity, so for call, staticcall and delegatecall 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:

  1. The transaction signer, who signs and sends a transaction off-chain to the Gas relayer.

  2. The Gas relayer receives these transactions and is expected to pay for the Gas, then forwards it to a trusted forwarder contract.

  3. 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.

  4. 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 tokenIds 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)\mathcal{O}(n)O(n), nnn 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)\mathcal{O}(1)O(1) time (that's constant time). Enumerating them can be done in O(n)\mathcal{O}(n)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 the bool 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\text{Implementation Address}\rightarrow\text{UpgradeableBeacon}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 create

  • There is the equivalent version for CREATE2 the cloneDeterministic(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.

Previous2.27 Security ChecksNext2.29 DAppSys Libraries

Last updated 1 year ago

📚
🌀