dexorder
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
= Access Control
|
||||
|
||||
Access control—that is, "who is allowed to do this thing"—is incredibly important in the world of smart contracts. The access control of your contract may govern who can mint tokens, vote on proposals, freeze transfers, and many other things. It is therefore *critical* to understand how you implement it, lest someone else https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7[steals your whole system].
|
||||
|
||||
[[ownership-and-ownable]]
|
||||
== Ownership and `Ownable`
|
||||
|
||||
The most common and basic form of access control is the concept of _ownership_: there's an account that is the `owner` of a contract and can do administrative tasks on it. This approach is perfectly reasonable for contracts that have a single administrative user.
|
||||
|
||||
OpenZeppelin Contracts provides xref:api:access.adoc#Ownable[`Ownable`] for implementing ownership in your contracts.
|
||||
|
||||
```solidity
|
||||
include::api:example$access-control/MyContractOwnable.sol[]
|
||||
```
|
||||
|
||||
At deployment, the xref:api:access.adoc#Ownable-owner--[`owner`] of an `Ownable` contract is set to the provided `initialOwner` parameter.
|
||||
|
||||
Ownable also lets you:
|
||||
|
||||
* xref:api:access.adoc#Ownable-transferOwnership-address-[`transferOwnership`] from the owner account to a new one, and
|
||||
* xref:api:access.adoc#Ownable-renounceOwnership--[`renounceOwnership`] for the owner to relinquish this administrative privilege, a common pattern after an initial stage with centralized administration is over.
|
||||
|
||||
WARNING: Removing the owner altogether will mean that administrative tasks that are protected by `onlyOwner` will no longer be callable!
|
||||
|
||||
Ownable is a simple and effective way to implement access control, but you should be mindful of the dangers associated with transferring the ownership to an incorrect account that can't interact with this contract anymore. An alternative to this problem is using xref:api:access.adoc#Ownable2Step[`Ownable2Step`]; a variant of Ownable that requires the new owner to explicitly accept the ownership transfer by calling xref:api:access.adoc#Ownable2Step-acceptOwnership--[`acceptOwnership`].
|
||||
|
||||
Note that *a contract can also be the owner of another one*! This opens the door to using, for example, a https://gnosis-safe.io[Gnosis Safe], an https://aragon.org[Aragon DAO], or a totally custom contract that _you_ create.
|
||||
|
||||
In this way, you can use _composability_ to add additional layers of access control complexity to your contracts. Instead of having a single regular Ethereum account (Externally Owned Account, or EOA) as the owner, you could use a 2-of-3 multisig run by your project leads, for example. Prominent projects in the space, such as https://makerdao.com[MakerDAO], use systems similar to this one.
|
||||
|
||||
[[role-based-access-control]]
|
||||
== Role-Based Access Control
|
||||
|
||||
While the simplicity of _ownership_ can be useful for simple systems or quick prototyping, different levels of authorization are often needed. You may want for an account to have permission to ban users from a system, but not create new tokens. https://en.wikipedia.org/wiki/Role-based_access_control[_Role-Based Access Control (RBAC)_] offers flexibility in this regard.
|
||||
|
||||
In essence, we will be defining multiple _roles_, each allowed to perform different sets of actions. An account may have, for example, 'moderator', 'minter' or 'admin' roles, which you will then check for instead of simply using `onlyOwner`. This check can be enforced through the `onlyRole` modifier. Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more.
|
||||
|
||||
Most software uses access control systems that are role-based: some users are regular users, some may be supervisors or managers, and a few will often have administrative privileges.
|
||||
|
||||
[[using-access-control]]
|
||||
=== Using `AccessControl`
|
||||
|
||||
OpenZeppelin Contracts provides xref:api:access.adoc#AccessControl[`AccessControl`] for implementing role-based access control. Its usage is straightforward: for each role that you want to define,
|
||||
you will create a new _role identifier_ that is used to grant, revoke, and check if an account has that role.
|
||||
|
||||
Here's a simple example of using `AccessControl` in an xref:erc20.adoc[ERC-20 token] to define a 'minter' role, which allows accounts that have it create new tokens:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
include::api:example$access-control/AccessControlERC20MintBase.sol[]
|
||||
----
|
||||
|
||||
NOTE: Make sure you fully understand how xref:api:access.adoc#AccessControl[`AccessControl`] works before using it on your system, or copy-pasting the examples from this guide.
|
||||
|
||||
While clear and explicit, this isn't anything we wouldn't have been able to achieve with `Ownable`. Indeed, where `AccessControl` shines is in scenarios where granular permissions are required, which can be implemented by defining _multiple_ roles.
|
||||
|
||||
Let's augment our ERC-20 token example by also defining a 'burner' role, which lets accounts destroy tokens, and by using the `onlyRole` modifier:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
include::api:example$access-control/AccessControlERC20MintOnlyRole.sol[]
|
||||
----
|
||||
|
||||
So clean! By splitting concerns this way, more granular levels of permission may be implemented than were possible with the simpler _ownership_ approach to access control. Limiting what each component of a system is able to do is known as the https://en.wikipedia.org/wiki/Principle_of_least_privilege[principle of least privilege], and is a good security practice. Note that each account may still have more than one role, if so desired.
|
||||
|
||||
[[granting-and-revoking]]
|
||||
=== Granting and Revoking Roles
|
||||
|
||||
The ERC-20 token example above uses `_grantRole`, an `internal` function that is useful when programmatically assigning roles (such as during construction). But what if we later want to grant the 'minter' role to additional accounts?
|
||||
|
||||
By default, **accounts with a role cannot grant it or revoke it from other accounts**: all having a role does is making the `hasRole` check pass. To grant and revoke roles dynamically, you will need help from the _role's admin_.
|
||||
|
||||
Every role has an associated admin role, which grants permission to call the `grantRole` and `revokeRole` functions. A role can be granted or revoked by using these if the calling account has the corresponding admin role. Multiple roles may have the same admin role to make management easier. A role's admin can even be the same role itself, which would cause accounts with that role to be able to also grant and revoke it.
|
||||
|
||||
This mechanism can be used to create complex permissioning structures resembling organizational charts, but it also provides an easy way to manage simpler applications. `AccessControl` includes a special role, called `DEFAULT_ADMIN_ROLE`, which acts as the **default admin role for all roles**. An account with this role will be able to manage any other role, unless `_setRoleAdmin` is used to select a new admin role.
|
||||
|
||||
Since it is the admin for all roles by default, and in fact it is also its own admin, this role carries significant risk. To mitigate this risk we provide xref:api:access.adoc#AccessControlDefaultAdminRules[`AccessControlDefaultAdminRules`], a recommended extension of `AccessControl` that adds a number of enforced security measures for this role: the admin is restricted to a single account, with a 2-step transfer procedure with a delay in between steps.
|
||||
|
||||
Let's take a look at the ERC-20 token example, this time taking advantage of the default admin role:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
include::api:example$access-control/AccessControlERC20MintMissing.sol[]
|
||||
----
|
||||
|
||||
Note that, unlike the previous examples, no accounts are granted the 'minter' or 'burner' roles. However, because those roles' admin role is the default admin role, and _that_ role was granted to `msg.sender`, that same account can call `grantRole` to give minting or burning permission, and `revokeRole` to remove it.
|
||||
|
||||
Dynamic role allocation is often a desirable property, for example in systems where trust in a participant may vary over time. It can also be used to support use cases such as https://en.wikipedia.org/wiki/Know_your_customer[KYC], where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction.
|
||||
|
||||
[[querying-privileged-accounts]]
|
||||
=== Querying Privileged Accounts
|
||||
|
||||
Because accounts might <<granting-and-revoking, grant and revoke roles>> dynamically, it is not always possible to determine which accounts hold a particular role. This is important as it allows proving certain properties about a system, such as that an administrative account is a multisig or a DAO, or that a certain role has been removed from all users, effectively disabling any associated functionality.
|
||||
|
||||
Under the hood, `AccessControl` uses `EnumerableSet`, a more powerful variant of Solidity's `mapping` type, which allows for key enumeration. `getRoleMemberCount` can be used to retrieve the number of accounts that have a particular role, and `getRoleMember` can then be called to get the address of each of these accounts.
|
||||
|
||||
```javascript
|
||||
const minterCount = await myToken.getRoleMemberCount(MINTER_ROLE);
|
||||
|
||||
const members = [];
|
||||
for (let i = 0; i < minterCount; ++i) {
|
||||
members.push(await myToken.getRoleMember(MINTER_ROLE, i));
|
||||
}
|
||||
```
|
||||
|
||||
== Delayed operation
|
||||
|
||||
Access control is essential to prevent unauthorized access to critical functions. These functions may be used to mint tokens, freeze transfers or perform an upgrade that completely changes the smart contract logic. While xref:api:access.adoc#Ownable[`Ownable`] and xref:api:access.adoc#AccessControl[`AccessControl`] can prevent unauthorized access, they do not address the issue of a misbehaving administrator attacking their own system to the prejudice of their users.
|
||||
|
||||
This is the issue the xref:api:governance.adoc#TimelockController[`TimelockController`] is addressing.
|
||||
|
||||
The xref:api:governance.adoc#TimelockController[`TimelockController`] is a proxy that is governed by proposers and executors. When set as the owner/admin/controller of a smart contract, it ensures that whichever maintenance operation is ordered by the proposers is subject to a delay. This delay protects the users of the smart contract by giving them time to review the maintenance operation and exit the system if they consider it is in their best interest to do so.
|
||||
|
||||
=== Using `TimelockController`
|
||||
|
||||
By default, the address that deployed the xref:api:governance.adoc#TimelockController[`TimelockController`] gets administration privileges over the timelock. This role grants the right to assign proposers, executors, and other administrators.
|
||||
|
||||
The first step in configuring the xref:api:governance.adoc#TimelockController[`TimelockController`] is to assign at least one proposer and one executor. These can be assigned during construction or later by anyone with the administrator role. These roles are not exclusive, meaning an account can have both roles.
|
||||
|
||||
Roles are managed using the xref:api:access.adoc#AccessControl[`AccessControl`] interface and the `bytes32` values for each role are accessible through the `ADMIN_ROLE`, `PROPOSER_ROLE` and `EXECUTOR_ROLE` constants.
|
||||
|
||||
There is an additional feature built on top of `AccessControl`: giving the executor role to `address(0)` opens access to anyone to execute a proposal once the timelock has expired. This feature, while useful, should be used with caution.
|
||||
|
||||
At this point, with both a proposer and an executor assigned, the timelock can perform operations.
|
||||
|
||||
An optional next step is for the deployer to renounce its administrative privileges and leave the timelock self-administered. If the deployer decides to do so, all further maintenance, including assigning new proposers/schedulers or changing the timelock duration will have to follow the timelock workflow. This links the governance of the timelock to the governance of contracts attached to the timelock, and enforce a delay on timelock maintenance operations.
|
||||
|
||||
WARNING: If the deployer renounces administrative rights in favour of timelock itself, assigning new proposers or executors will require a timelocked operation. This means that if the accounts in charge of any of these two roles become unavailable, then the entire contract (and any contract it controls) becomes locked indefinitely.
|
||||
|
||||
With both the proposer and executor roles assigned and the timelock in charge of its own administration, you can now transfer the ownership/control of any contract to the timelock.
|
||||
|
||||
TIP: A recommended configuration is to grant both roles to a secure governance contract such as a DAO or a multisig, and to additionally grant the executor role to a few EOAs held by people in charge of helping with the maintenance operations. These wallets cannot take over control of the timelock but they can help smoothen the workflow.
|
||||
|
||||
=== Minimum delay
|
||||
|
||||
Operations executed by the xref:api:governance.adoc#TimelockController[`TimelockController`] are not subject to a fixed delay but rather a minimum delay. Some major updates might call for a longer delay. For example, if a delay of just a few days might be sufficient for users to audit a minting operation, it makes sense to use a delay of a few weeks, or even a few months, when scheduling a smart contract upgrade.
|
||||
|
||||
The minimum delay (accessible through the xref:api:governance.adoc#TimelockController-getMinDelay--[`getMinDelay`] method) can be updated by calling the xref:api:governance.adoc#TimelockController-updateDelay-uint256-[`updateDelay`] function. Bear in mind that access to this function is only accessible by the timelock itself, meaning this maintenance operation has to go through the timelock itself.
|
||||
|
||||
[[access-management]]
|
||||
== Access Management
|
||||
|
||||
For a system of contracts, better integrated role management can be achieved with an xref:api:access.adoc#AccessManager[`AccessManager`] instance. Instead of managing each contract's permission separately, AccessManager stores all the permissions in a single contract, making your protocol easier to audit and maintain.
|
||||
|
||||
Although xref:api:access.adoc#AccessControl[`AccessControl`] offers a more dynamic solution for adding permissions to your contracts than Ownable, decentralized protocols tend to become more complex after integrating new contract instances and requires you to keep track of permissions separately in each contract. This increases the complexity of permissions management and monitoring across the system.
|
||||
|
||||
image::access-control-multiple.svg[Access Control multiple]
|
||||
|
||||
Protocols managing permissions in production systems often require more integrated alternatives to fragmented permissions through multiple `AccessControl` instances.
|
||||
|
||||
image::access-manager.svg[AccessManager]
|
||||
|
||||
The AccessManager is designed around the concept of role and target functions:
|
||||
|
||||
* Roles are granted to accounts (addresses) following a many-to-many approach for flexibility. This means that each user can have one or multiple roles and multiple users can have the same role.
|
||||
* Access to a restricted target function is limited to one role. A target function is defined by one https://docs.soliditylang.org/en/v0.8.20/abi-spec.html#function-selector[function selector] on one contract (called target).
|
||||
|
||||
For a call to be authorized, the caller must bear the role that is assigned to the current target function (contract address + function selector).
|
||||
|
||||
image::access-manager-functions.svg[AccessManager functions]
|
||||
|
||||
=== Using `AccessManager`
|
||||
|
||||
OpenZeppelin Contracts provides xref:api:access.adoc#AccessManager[`AccessManager`] for managing roles across any number of contracts. The `AccessManager` itself is a contract that can be deployed and used out of the box. It sets an initial admin in the constructor who will be allowed to perform management operations.
|
||||
|
||||
In order to restrict access to some functions of your contract, you should inherit from the xref:api:access.adoc#AccessManaged[`AccessManaged`] contract provided along with the manager. This provides the `restricted` modifier that can be used to protect any externally facing function. Note that you will have to specify the address of the AccessManager instance (xref:api:access.adoc#AccessManaged-constructor-address-[`initialAuthority`]) in the constructor so the `restricted` modifier knows which manager to use for checking permissions.
|
||||
|
||||
Here's a simple example of an xref:tokens.adoc#ERC20[ERC-20 token] that defines a `mint` function that is restricted by an xref:api:access.adoc#AccessManager[`AccessManager`]:
|
||||
|
||||
```solidity
|
||||
include::api:example$access-control/AccessManagedERC20MintBase.sol[]
|
||||
```
|
||||
|
||||
NOTE: Make sure you fully understand how xref:api:access.adoc#AccessManager[`AccessManager`] works before using it or copy-pasting the examples from this guide.
|
||||
|
||||
Once the managed contract has been deployed, it is now under the manager's control. The initial admin can then assign the minter role to an address and also allow the role to call the `mint` function. For example, this is demonstrated in the following Javascript code using Ethers.js:
|
||||
|
||||
```javascript
|
||||
// const target = ...;
|
||||
// const user = ...;
|
||||
const MINTER = 42n; // Roles are uint64 (0 is reserved for the ADMIN_ROLE)
|
||||
|
||||
// Grant the minter role with no execution delay
|
||||
await manager.grantRole(MINTER, user, 0);
|
||||
|
||||
// Allow the minter role to call the function selector
|
||||
// corresponding to the mint function
|
||||
await manager.setTargetFunctionRole(
|
||||
target,
|
||||
['0x40c10f19'], // bytes4(keccak256('mint(address,uint256)'))
|
||||
MINTER
|
||||
);
|
||||
```
|
||||
|
||||
Even though each role has its own list of function permissions, each role member (`address`) has an execution delay that will dictate how long the account should wait to execute a function that requires its role. Delayed operations must have the xref:api:access.adoc#AccessManager-schedule-address-bytes-uint48-[`schedule`] function called on them first in the AccessManager before they can be executed, either by calling to the target function or using the AccessManager's xref:api:access.adoc#AccessManager-execute-address-bytes-[`execute`] function.
|
||||
|
||||
Additionally, roles can have a granting delay that prevents adding members immediately. The AccessManager admins can set this grant delay as follows:
|
||||
|
||||
```javascript
|
||||
const HOUR = 60 * 60;
|
||||
|
||||
const GRANT_DELAY = 24 * HOUR;
|
||||
const EXECUTION_DELAY = 5 * HOUR;
|
||||
const ACCOUNT = "0x...";
|
||||
|
||||
await manager.connect(initialAdmin).setGrantDelay(MINTER, GRANT_DELAY);
|
||||
|
||||
// The role will go into effect after the GRANT_DELAY passes
|
||||
await manager.connect(initialAdmin).grantRole(MINTER, ACCOUNT, EXECUTION_DELAY);
|
||||
```
|
||||
|
||||
Note that roles do not define a name. As opposed to the xref:api:access.adoc#AccessControl[`AccessControl`] case, roles are identified as numeric values instead of being hardcoded in the contract as `bytes32` values. It is still possible to allow for tooling discovery (e.g. for role exploration) using role labeling with the xref:api:access.adoc#AccessManager-labelRole-uint64-string-[`labelRole`] function.
|
||||
|
||||
```javascript
|
||||
await manager.labelRole(MINTER, "MINTER");
|
||||
```
|
||||
|
||||
Given the admins of the `AccessManaged` can modify all of its permissions, it's recommended to keep only a single admin address secured under a multisig or governance layer. To achieve this, it is possible for the initial admin to set up all the required permissions, targets, and functions, assign a new admin, and finally renounce its admin role.
|
||||
|
||||
For improved incident response coordination, the manager includes a mode where administrators can completely close a target contract. When closed, all calls to restricted target functions in a target contract will revert.
|
||||
|
||||
Closing and opening contracts don't alter any of their settings, neither permissions nor delays. Particularly, the roles required for calling specific target functions are not modified.
|
||||
|
||||
This mode is useful for incident response operations that require temporarily shutting down a contract in order to evaluate emergencies and reconfigure permissions.
|
||||
|
||||
```javascript
|
||||
const target = await myToken.getAddress();
|
||||
|
||||
// Token's `restricted` functions closed
|
||||
await manager.setTargetClosed(target, true);
|
||||
|
||||
// Token's `restricted` functions open
|
||||
await manager.setTargetClosed(target, false);
|
||||
```
|
||||
|
||||
WARNING: Even if an `AccessManager` defines permissions for a target function, these won't be applied if the managed contract instance is not using the xref:api:access.adoc#AccessManaged-restricted--[`restricted`] modifier for that function, or if its manager is a different one.
|
||||
|
||||
=== Role Admins and Guardians
|
||||
|
||||
An important aspect of the AccessControl contract is that roles aren't granted nor revoked by role members. Instead, it relies on the concept of a role admin for granting and revoking.
|
||||
|
||||
In the case of the `AccessManager`, the same rule applies and only the role's admins are able to call xref:api:access.adoc#AccessManager-grantRole-uint64-address-uint32-[grant] and xref:api:access.adoc#AccessManager-revokeRole-uint64-address-[revoke] functions. Note that calling these functions will be subject to the execution delay that the executing role admin has.
|
||||
|
||||
Additionally, the `AccessManager` stores a _guardian_ as an extra protection for each role. This guardian has the ability to cancel operations that have been scheduled by any role member with an execution delay. Consider that a role will have its initial admin and guardian default to the `ADMIN_ROLE` (`0`).
|
||||
|
||||
IMPORTANT: Be careful with the members of `ADMIN_ROLE`, since it acts as the default admin and guardian for every role. A misbehaved guardian can cancel operations at will, affecting the AccessManager's operation.
|
||||
|
||||
=== Manager configuration
|
||||
|
||||
The `AccessManager` provides a built-in interface for configuring permission settings that can be accessed by its `ADMIN_ROLE` members.
|
||||
|
||||
This configuration interface includes the following functions:
|
||||
|
||||
* Add a label to a role using the xref:api:access.adoc#AccessManager-labelRole-uint64-string-[`labelRole`] function.
|
||||
* Assign the admin and guardian of a role with xref:api:access.adoc#AccessManager-setRoleAdmin-uint64-uint64-[`setRoleAdmin`] and xref:api:access.adoc#AccessManager-setRoleGuardian-uint64-uint64-[`setRoleGuardian`].
|
||||
* Set each role's grant delay via xref:api:access.adoc#AccessManager-setGrantDelay-uint64-uint32-[`setGrantDelay`].
|
||||
|
||||
As an admin, some actions will require a delay. Similar to each member's execution delay, some admin operations require waiting for execution and should follow the xref:api:access.adoc#AccessManager-schedule-address-bytes-uint48-[`schedule`] and xref:api:access.adoc#AccessManager-execute-address-bytes-[`execute`] workflow.
|
||||
|
||||
More specifically, these delayed functions are those for configuring the settings of a specific target contract. The delay applied to these functions can be adjusted by the manager admins with xref:api:access.adoc#AccessManager-setTargetAdminDelay-address-uint32-[`setTargetAdminDelay`].
|
||||
|
||||
The delayed admin actions are:
|
||||
|
||||
* Updating an `AccessManaged` contract xref:api:access.adoc#AccessManaged-authority--[authority] using xref:api:access.adoc#AccessManager-updateAuthority-address-address-[`updateAuthority`].
|
||||
* Closing or opening a target via xref:api:access.adoc#AccessManager-setTargetClosed-address-bool-[`setTargetClosed`].
|
||||
* Changing permissions of whether a role can call a target function with xref:api:access.adoc#AccessManager-setTargetFunctionRole-address-bytes4---uint64-[`setTargetFunctionRole`].
|
||||
|
||||
=== Using with Ownable
|
||||
|
||||
Contracts already inheriting from xref:api:access.adoc#Ownable[`Ownable`] can migrate to AccessManager by transferring ownership to the manager. After that, all calls to functions with the `onlyOwner` modifier should be called through the manager's xref:api:access.adoc#AccessManager-execute-address-bytes-[`execute`] function, even if the caller doesn't require a delay.
|
||||
|
||||
```javascript
|
||||
await ownable.connect(owner).transferOwnership(accessManager);
|
||||
```
|
||||
|
||||
=== Using with AccessControl
|
||||
|
||||
For systems already using xref:api:access.adoc#AccessControl[`AccessControl`], the `DEFAULT_ADMIN_ROLE` can be granted to the `AccessManager` after revoking every other role. Subsequent calls should be made through the manager's xref:api:access.adoc#AccessManager-execute-address-bytes-[`execute`] method, similar to the Ownable case.
|
||||
|
||||
```javascript
|
||||
// Revoke old roles
|
||||
await accessControl.connect(admin).revokeRole(MINTER_ROLE, account);
|
||||
|
||||
// Grant the admin role to the access manager
|
||||
await accessControl.connect(admin).grantRole(DEFAULT_ADMIN_ROLE, accessManager);
|
||||
|
||||
await accessControl.connect(admin).renounceRole(DEFAULT_ADMIN_ROLE, admin);
|
||||
```
|
||||
@@ -0,0 +1,48 @@
|
||||
= Backwards Compatibility
|
||||
:page-aliases: releases-stability.adoc
|
||||
|
||||
OpenZeppelin Contracts uses semantic versioning to communicate backwards compatibility of its API and storage layout. Patch and minor updates will generally be backwards compatible, with rare exceptions as detailed below. Major updates should be assumed incompatible with previous releases. On this page, we provide details about these guarantees.
|
||||
|
||||
== API
|
||||
|
||||
In backwards compatible releases, all changes should be either additions or modifications to internal implementation details. Most code should continue to compile and behave as expected. The exceptions to this rule are listed below.
|
||||
|
||||
=== Security
|
||||
|
||||
Infrequently a patch or minor update will remove or change an API in a breaking way, but only if the previous API is considered insecure. These breaking changes will be noted in the changelog and release notes, and published along with a security advisory.
|
||||
|
||||
=== Draft or Pre-Final ERCs
|
||||
|
||||
ERCs that are not Final can change in incompatible ways. For this reason, we avoid shipping implementations of ERCs before they are Final. Some exceptions are made for ERCs that have been published for a long time and that seem unlikely to change. Breaking changes to the ERC specification are still technically possible in those cases, so these implementations are published in files named `draft-*.sol` to make that condition explicit.
|
||||
|
||||
=== Virtual & Overrides
|
||||
|
||||
Almost all functions in this library are virtual with some exceptions, but this does not mean that overrides are encouraged. There is a subset of functions that are designed to be overridden. By defining overrides outside of this subset you are potentially relying on internal implementation details. We make efforts to preserve backwards compatibility even in these cases but it is extremely difficult and easy to accidentally break. Caution is advised.
|
||||
|
||||
Additionally, some minor updates may result in new compilation errors of the kind "two or more base classes define function with same name and parameter types" or "need to specify overridden contract", due to what Solidity considers ambiguity in inherited functions. This should be resolved by adding an override that invokes the function via `super`.
|
||||
|
||||
See xref:extending-contracts.adoc[Extending Contracts] for more about virtual and overrides.
|
||||
|
||||
=== Structs
|
||||
|
||||
Struct members with an underscore prefix should be considered "private" and may break in minor versions. Struct data should only be accessed and modified through library functions.
|
||||
|
||||
=== Errors
|
||||
|
||||
The specific error format and data that is included with reverts should not be assumed stable unless otherwise specified.
|
||||
|
||||
=== Major Releases
|
||||
|
||||
Major releases should be assumed incompatible. Nevertheless, the external interfaces of contracts will remain compatible if they are standardized, or if the maintainers judge that changing them would cause significant strain on the ecosystem.
|
||||
|
||||
An important aspect that major releases may break is "upgrade compatibility", in particular storage layout compatibility. It will never be safe for a live contract to upgrade from one major release to another.
|
||||
|
||||
== Storage Layout
|
||||
|
||||
Minor and patch updates always preserve storage layout compatibility. This means that a live contract can be upgraded from one minor to another without corrupting the storage layout. In some cases it may be necessary to initialize new state variables when upgrading, although we expect this to be infrequent.
|
||||
|
||||
We recommend using xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins or CLI] to ensure storage layout safety of upgrades.
|
||||
|
||||
== Solidity Version
|
||||
|
||||
The minimum Solidity version required to compile the contracts will remain unchanged in minor and patch updates. New contracts introduced in minor releases may make use of newer Solidity features and require a more recent version of the compiler.
|
||||
@@ -0,0 +1,11 @@
|
||||
= Crowdsales
|
||||
|
||||
All crowdsale-related contracts were removed from the OpenZeppelin Contracts library on the https://forum.openzeppelin.com/t/openzeppelin-contracts-v3-0-beta-release/2256[v3.0.0 release] due to both a decline in their usage and the complexity associated with migrating them to Solidity v0.6.
|
||||
|
||||
They are however still available on the v2.5 release of OpenZeppelin Contracts, which you can install by running:
|
||||
|
||||
```console
|
||||
$ npm install @openzeppelin/contracts@v2.5
|
||||
```
|
||||
|
||||
Refer to the https://docs.openzeppelin.com/contracts/2.x/crowdsales[v2.x documentation] when working with them.
|
||||
@@ -0,0 +1,19 @@
|
||||
= Drafts
|
||||
|
||||
All draft contracts were either moved into a different directory or removed from the OpenZeppelin Contracts library on the https://forum.openzeppelin.com/t/openzeppelin-contracts-v3-0-beta-release/2256[v3.0.0 release].
|
||||
|
||||
* `ERC20Migrator`: removed.
|
||||
* xref:api:token/ERC20.adoc#ERC20Snapshot[`ERC20Snapshot`]: moved to `token/ERC20`.
|
||||
* `ERC20Detailed` and `ERC1046`: removed.
|
||||
* `TokenVesting`: removed. Pending a replacement that is being discussed in https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1214[`#1214`].
|
||||
* xref:api:utils.adoc#Counters[`Counters`]: moved to xref:api:utils.adoc[`utils`].
|
||||
* xref:api:utils.adoc#Strings[`Strings`]: moved to xref:api:utils.adoc[`utils`].
|
||||
* xref:api:utils.adoc#SignedSafeMath[`SignedSafeMath`]: moved to xref:api:utils.adoc[`utils`].
|
||||
|
||||
Removed contracts are still available on the v2.5 release of OpenZeppelin Contracts, which you can install by running:
|
||||
|
||||
```console
|
||||
$ npm install @openzeppelin/contracts@v2.5
|
||||
```
|
||||
|
||||
Refer to the xref:2.x@contracts:api:drafts.adoc[v2.x documentation] when working with them.
|
||||
118
lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc1155.adoc
Normal file
118
lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc1155.adoc
Normal file
@@ -0,0 +1,118 @@
|
||||
= ERC-1155
|
||||
|
||||
ERC-1155 is a novel token standard that aims to take the best from previous standards to create a xref:tokens.adoc#different-kinds-of-tokens[*fungibility-agnostic*] and *gas-efficient* xref:tokens.adoc#but_first_coffee_a_primer_on_token_contracts[token contract].
|
||||
|
||||
TIP: ERC-1155 draws ideas from all of xref:erc20.adoc[ERC-20], xref:erc721.adoc[ERC-721], and https://eips.ethereum.org/EIPS/eip-777[ERC-777]. If you're unfamiliar with those standards, head to their guides before moving on.
|
||||
|
||||
[[multi-token-standard]]
|
||||
== Multi Token Standard
|
||||
|
||||
The distinctive feature of ERC-1155 is that it uses a single smart contract to represent multiple tokens at once. This is why its xref:api:token/ERC1155.adoc#IERC1155-balanceOf-address-uint256-[`balanceOf`] function differs from ERC-20's and ERC-777's: it has an additional `id` argument for the identifier of the token that you want to query the balance of.
|
||||
|
||||
This is similar to how ERC-721 does things, but in that standard a token `id` has no concept of balance: each token is non-fungible and exists or doesn't. The ERC-721 xref:api:token/ERC721.adoc#IERC721-balanceOf-address-[`balanceOf`] function refers to _how many different tokens_ an account has, not how many of each. On the other hand, in ERC-1155 accounts have a distinct balance for each token `id`, and non-fungible tokens are implemented by simply minting a single one of them.
|
||||
|
||||
This approach leads to massive gas savings for projects that require multiple tokens. Instead of deploying a new contract for each token type, a single ERC-1155 token contract can hold the entire system state, reducing deployment costs and complexity.
|
||||
|
||||
[[batch-operations]]
|
||||
== Batch Operations
|
||||
|
||||
Because all state is held in a single contract, it is possible to operate over multiple tokens in a single transaction very efficiently. The standard provides two functions, xref:api:token/ERC1155.adoc#IERC1155-balanceOfBatch-address---uint256---[`balanceOfBatch`] and xref:api:token/ERC1155.adoc#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-[`safeBatchTransferFrom`], that make querying multiple balances and transferring multiple tokens simpler and less gas-intensive.
|
||||
|
||||
In the spirit of the standard, we've also included batch operations in the non-standard functions, such as xref:api:token/ERC1155.adoc#ERC1155-_mintBatch-address-uint256---uint256---bytes-[`_mintBatch`].
|
||||
|
||||
== Constructing an ERC-1155 Token Contract
|
||||
|
||||
We'll use ERC-1155 to track multiple items in our game, which will each have their own unique attributes. We mint all items to the deployer of the contract, which we can later transfer to players. Players are free to keep their tokens or trade them with other people as they see fit, as they would any other asset on the blockchain!
|
||||
|
||||
For simplicity, we will mint all items in the constructor, but you could add minting functionality to the contract to mint on demand to players.
|
||||
|
||||
TIP: For an overview of minting mechanisms, check out xref:erc20-supply.adoc[Creating ERC-20 Supply].
|
||||
|
||||
Here's what a contract for tokenized items might look like:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
include::api:example$token/ERC1155/GameItems.sol[]
|
||||
----
|
||||
|
||||
Note that for our Game Items, Gold is a fungible token whilst Thor's Hammer is a non-fungible token as we minted only one.
|
||||
|
||||
The xref:api:token/ERC1155.adoc#ERC1155[`ERC1155`] contract includes the optional extension xref:api:token/ERC1155.adoc#IERC1155MetadataURI[`IERC1155MetadataURI`]. That's where the xref:api:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[`uri`] function comes from: we use it to retrieve the metadata uri.
|
||||
|
||||
Also note that, unlike ERC-20, ERC-1155 lacks a `decimals` field, since each token is distinct and cannot be partitioned.
|
||||
|
||||
Once deployed, we will be able to query the deployer’s balance:
|
||||
[source,javascript]
|
||||
----
|
||||
> gameItems.balanceOf(deployerAddress,3)
|
||||
1000000000
|
||||
----
|
||||
|
||||
We can transfer items to player accounts:
|
||||
[source,javascript]
|
||||
----
|
||||
> gameItems.safeTransferFrom(deployerAddress, playerAddress, 2, 1, "0x0")
|
||||
> gameItems.balanceOf(playerAddress, 2)
|
||||
1
|
||||
> gameItems.balanceOf(deployerAddress, 2)
|
||||
0
|
||||
----
|
||||
|
||||
We can also batch transfer items to player accounts and get the balance of batches:
|
||||
[source,javascript]
|
||||
----
|
||||
> gameItems.safeBatchTransferFrom(deployerAddress, playerAddress, [0,1,3,4], [50,100,1,1], "0x0")
|
||||
> gameItems.balanceOfBatch([playerAddress,playerAddress,playerAddress,playerAddress,playerAddress], [0,1,2,3,4])
|
||||
[50,100,1,1,1]
|
||||
----
|
||||
|
||||
The metadata uri can be obtained:
|
||||
|
||||
[source,javascript]
|
||||
----
|
||||
> gameItems.uri(2)
|
||||
"https://game.example/api/item/{id}.json"
|
||||
----
|
||||
|
||||
The `uri` can include the string `++{id}++` which clients must replace with the actual token ID, in lowercase hexadecimal (with no 0x prefix) and leading zero padded to 64 hex characters.
|
||||
|
||||
For token ID `2` and uri `++https://game.example/api/item/{id}.json++` clients would replace `++{id}++` with `0000000000000000000000000000000000000000000000000000000000000002` to retrieve JSON at `https://game.example/api/item/0000000000000000000000000000000000000000000000000000000000000002.json`.
|
||||
|
||||
The JSON document for token ID 2 might look something like:
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"name": "Thor's hammer",
|
||||
"description": "Mjölnir, the legendary hammer of the Norse god of thunder.",
|
||||
"image": "https://game.example/item-id-8u5h2m.png",
|
||||
"strength": 20
|
||||
}
|
||||
----
|
||||
|
||||
For more information about the metadata JSON Schema, check out the https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md#erc-1155-metadata-uri-json-schema[ERC-1155 Metadata URI JSON Schema].
|
||||
|
||||
NOTE: You'll notice that the item's information is included in the metadata, but that information isn't on-chain! So a game developer could change the underlying metadata, changing the rules of the game!
|
||||
|
||||
TIP: If you'd like to put all item information on-chain, you can extend ERC-721 to do so (though it will be rather costly) by providing a xref:utilities.adoc#base64[`Base64`] Data URI with the JSON schema encoded. You could also leverage IPFS to store the URI information, but these techniques are out of the scope of this overview guide
|
||||
|
||||
[[sending-to-contracts]]
|
||||
== Sending Tokens to Contracts
|
||||
|
||||
A key difference when using xref:api:token/ERC1155.adoc#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-[`safeTransferFrom`] is that token transfers to other contracts may revert with the following custom error:
|
||||
|
||||
[source,text]
|
||||
----
|
||||
ERC1155InvalidReceiver("<ADDRESS>")
|
||||
----
|
||||
|
||||
This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC-1155 protocol, so transfers to it are disabled to *prevent tokens from being locked forever*. As an example, https://etherscan.io/token/0xa74476443119A942dE498590Fe1f2454d7D4aC0d?a=0xa74476443119A942dE498590Fe1f2454d7D4aC0d[the Golem contract currently holds over 350k `GNT` tokens], worth multiple tens of thousands of dollars, and lacks methods to get them out of there. This has happened to virtually every ERC20-backed project, usually due to user error.
|
||||
|
||||
In order for our contract to receive ERC-1155 tokens we can inherit from the convenience contract xref:api:token/ERC1155.adoc#ERC1155Holder[`ERC1155Holder`] which handles the registering for us. Though we need to remember to implement functionality to allow tokens to be transferred out of our contract:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
include::api:example$token/ERC1155/MyERC115HolderContract.sol[]
|
||||
----
|
||||
|
||||
We can also implement more complex scenarios using the xref:api:token/ERC1155.adoc#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-[`onERC1155Received`] and xref:api:token/ERC1155.adoc#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-[`onERC1155BatchReceived`] functions.
|
||||
@@ -0,0 +1,71 @@
|
||||
= Creating ERC-20 Supply
|
||||
|
||||
In this guide, you will learn how to create an ERC-20 token with a custom supply mechanism. We will showcase two idiomatic ways to use OpenZeppelin Contracts for this purpose that you will be able to apply to your smart contract development practice.
|
||||
|
||||
The standard interface implemented by tokens built on Ethereum is called ERC-20, and Contracts includes a widely used implementation of it: the aptly named xref:api:token/ERC20.adoc[`ERC20`] contract. This contract, like the standard itself, is quite simple and bare-bones. In fact, if you try to deploy an instance of `ERC20` as-is it will be quite literally useless... it will have no supply! What use is a token with no supply?
|
||||
|
||||
The way that supply is created is not defined in the ERC-20 document. Every token is free to experiment with its own mechanisms, ranging from the most decentralized to the most centralized, from the most naive to the most researched, and more.
|
||||
|
||||
[[fixed-supply]]
|
||||
== Fixed Supply
|
||||
|
||||
Let's say we want a token with a fixed supply of 1000, initially allocated to the account that deploys the contract. If you've used Contracts v1, you may have written code like the following:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
contract ERC20FixedSupply is ERC20 {
|
||||
constructor() {
|
||||
totalSupply += 1000;
|
||||
balances[msg.sender] += 1000;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Starting with Contracts v2, this pattern is not only discouraged, but disallowed. The variables `totalSupply` and `balances` are now private implementation details of `ERC20`, and you can't directly write to them. Instead, there is an internal xref:api:token/ERC20.adoc#ERC20-_mint-address-uint256-[`_mint`] function that will do exactly this:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
contract ERC20FixedSupply is ERC20 {
|
||||
constructor() ERC20("Fixed", "FIX") {
|
||||
_mint(msg.sender, 1000);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Encapsulating state like this makes it safer to extend contracts. For instance, in the first example we had to manually keep the `totalSupply` in sync with the modified balances, which is easy to forget. In fact, we omitted something else that is also easily forgotten: the `Transfer` event that is required by the standard, and which is relied on by some clients. The second example does not have this bug, because the internal `_mint` function takes care of it.
|
||||
|
||||
[[rewarding-miners]]
|
||||
== Rewarding Miners
|
||||
|
||||
The internal xref:api:token/ERC20.adoc#ERC20-_mint-address-uint256-[`_mint`] function is the key building block that allows us to write ERC-20 extensions that implement a supply mechanism.
|
||||
|
||||
The mechanism we will implement is a token reward for the miners that produce Ethereum blocks. In Solidity, we can access the address of the current block's miner in the global variable `block.coinbase`. We will mint a token reward to this address whenever someone calls the function `mintMinerReward()` on our token. The mechanism may sound silly, but you never know what kind of dynamic this might result in, and it's worth analyzing and experimenting with!
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
contract ERC20WithMinerReward is ERC20 {
|
||||
constructor() ERC20("Reward", "RWD") {}
|
||||
|
||||
function mintMinerReward() public {
|
||||
_mint(block.coinbase, 1000);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
As we can see, `_mint` makes it super easy to do this correctly.
|
||||
|
||||
[[automating-the-reward]]
|
||||
== Automating the Reward
|
||||
|
||||
So far our supply mechanism was triggered manually, but `ERC20` also allows us to extend the core functionality of the token through the xref:api:token/ERC20.adoc#ERC20-_update-address-address-uint256-[`_update`] function.
|
||||
|
||||
Adding to the supply mechanism from the previous section, we can use this function to mint a miner reward for every token transfer that is included in the blockchain.
|
||||
|
||||
```solidity
|
||||
include::api:example$ERC20WithAutoMinerReward.sol[]
|
||||
```
|
||||
|
||||
[[wrapping-up]]
|
||||
== Wrapping Up
|
||||
|
||||
We've seen how to implement a ERC-20 supply mechanism: internally through `_mint`. Hopefully this has helped you understand how to use OpenZeppelin Contracts and some of the design principles behind it, and you can apply them to your own smart contracts.
|
||||
@@ -0,0 +1,67 @@
|
||||
= ERC-20
|
||||
|
||||
An ERC-20 token contract keeps track of xref:tokens.adoc#different-kinds-of-tokens[_fungible_ tokens]: any one token is exactly equal to any other token; no tokens have special rights or behavior associated with them. This makes ERC-20 tokens useful for things like a *medium of exchange currency*, *voting rights*, *staking*, and more.
|
||||
|
||||
OpenZeppelin Contracts provides many ERC20-related contracts. On the xref:api:token/ERC20.adoc[`API reference`] you'll find detailed information on their properties and usage.
|
||||
|
||||
[[constructing-an-erc20-token-contract]]
|
||||
== Constructing an ERC-20 Token Contract
|
||||
|
||||
Using Contracts, we can easily create our own ERC-20 token contract, which will be used to track _Gold_ (GLD), an internal currency in a hypothetical game.
|
||||
|
||||
Here's what our GLD token might look like.
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
include::api:example$token/ERC20/GLDToken.sol[]
|
||||
----
|
||||
|
||||
Our contracts are often used via https://solidity.readthedocs.io/en/latest/contracts.html#inheritance[inheritance], and here we're reusing xref:api:token/ERC20.adoc#erc20[`ERC20`] for both the basic standard implementation and the xref:api:token/ERC20.adoc#ERC20-name--[`name`], xref:api:token/ERC20.adoc#ERC20-symbol--[`symbol`], and xref:api:token/ERC20.adoc#ERC20-decimals--[`decimals`] optional extensions. Additionally, we're creating an `initialSupply` of tokens, which will be assigned to the address that deploys the contract.
|
||||
|
||||
TIP: For a more complete discussion of ERC-20 supply mechanisms, see xref:erc20-supply.adoc[Creating ERC-20 Supply].
|
||||
|
||||
That's it! Once deployed, we will be able to query the deployer's balance:
|
||||
|
||||
[source,javascript]
|
||||
----
|
||||
> GLDToken.balanceOf(deployerAddress)
|
||||
1000000000000000000000
|
||||
----
|
||||
|
||||
We can also xref:api:token/ERC20.adoc#IERC20-transfer-address-uint256-[transfer] these tokens to other accounts:
|
||||
|
||||
[source,javascript]
|
||||
----
|
||||
> GLDToken.transfer(otherAddress, 300000000000000000000)
|
||||
> GLDToken.balanceOf(otherAddress)
|
||||
300000000000000000000
|
||||
> GLDToken.balanceOf(deployerAddress)
|
||||
700000000000000000000
|
||||
----
|
||||
|
||||
[[a-note-on-decimals]]
|
||||
== A Note on `decimals`
|
||||
|
||||
Often, you'll want to be able to divide your tokens into arbitrary amounts: say, if you own `5 GLD`, you may want to send `1.5 GLD` to a friend, and keep `3.5 GLD` to yourself. Unfortunately, Solidity and the EVM do not support this behavior: only integer (whole) numbers can be used, which poses an issue. You may send `1` or `2` tokens, but not `1.5`.
|
||||
|
||||
To work around this, xref:api:token/ERC20.adoc#ERC20[`ERC20`] provides a xref:api:token/ERC20.adoc#ERC20-decimals--[`decimals`] field, which is used to specify how many decimal places a token has. To be able to transfer `1.5 GLD`, `decimals` must be at least `1`, since that number has a single decimal place.
|
||||
|
||||
How can this be achieved? It's actually very simple: a token contract can use larger integer values, so that a balance of `50` will represent `5 GLD`, a transfer of `15` will correspond to `1.5 GLD` being sent, and so on.
|
||||
|
||||
It is important to understand that `decimals` is _only used for display purposes_. All arithmetic inside the contract is still performed on integers, and it is the different user interfaces (wallets, exchanges, etc.) that must adjust the displayed values according to `decimals`. The total token supply and balance of each account are not specified in `GLD`: you need to divide by `10 ** decimals` to get the actual `GLD` amount.
|
||||
|
||||
You'll probably want to use a `decimals` value of `18`, just like Ether and most ERC-20 token contracts in use, unless you have a very special reason not to. When minting tokens or transferring them around, you will be actually sending the number `num GLD * (10 ** decimals)`.
|
||||
|
||||
NOTE: By default, `ERC20` uses a value of `18` for `decimals`. To use a different value, you will need to override the `decimals()` function in your contract.
|
||||
|
||||
```solidity
|
||||
function decimals() public view virtual override returns (uint8) {
|
||||
return 16;
|
||||
}
|
||||
```
|
||||
|
||||
So if you want to send `5` tokens using a token contract with 18 decimals, the method to call will actually be:
|
||||
|
||||
```solidity
|
||||
transfer(recipient, 5 * (10 ** 18));
|
||||
```
|
||||
214
lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc4626.adoc
Normal file
214
lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc4626.adoc
Normal file
@@ -0,0 +1,214 @@
|
||||
= ERC-4626
|
||||
:stem: latexmath
|
||||
|
||||
https://eips.ethereum.org/EIPS/eip-4626[ERC-4626] is an extension of xref:erc20.adoc[ERC-20] that proposes a standard interface for token vaults. This standard interface can be used by widely different contracts (including lending markets, aggregators, and intrinsically interest bearing tokens), which brings a number of subtleties. Navigating these potential issues is essential to implementing a compliant and composable token vault.
|
||||
|
||||
We provide a base implementation of ERC-4626 that includes a simple vault. This contract is designed in a way that allows developers to easily re-configure the vault's behavior, with minimal overrides, while staying compliant. In this guide, we will discuss some security considerations that affect ERC-4626. We will also discuss common customizations of the vault.
|
||||
|
||||
[[inflation-attack]]
|
||||
== Security concern: Inflation attack
|
||||
|
||||
=== Visualizing the vault
|
||||
|
||||
In exchange for the assets deposited into an ERC-4626 vault, a user receives shares. These shares can later be burned to redeem the corresponding underlying assets. The number of shares a user gets depends on the amount of assets they put in and on the exchange rate of the vault. This exchange rate is defined by the current liquidity held by the vault.
|
||||
|
||||
- If a vault has 100 tokens to back 200 shares, then each share is worth 0.5 assets.
|
||||
- If a vault has 200 tokens to back 100 shares, then each share is worth 2.0 assets.
|
||||
|
||||
In other words, the exchange rate can be defined as the slope of the line that passes through the origin and the current number of assets and shares in the vault. Deposits and withdrawals move the vault in this line.
|
||||
|
||||
image::erc4626-rate-linear.png[Exchange rates in linear scale]
|
||||
|
||||
When plotted in log-log scale, the rate is defined similarly, but appears differently (because the point (0,0) is infinitely far away). Rates are represented by "diagonal" lines with different offsets.
|
||||
|
||||
image::erc4626-rate-loglog.png[Exchange rates in logarithmic scale]
|
||||
|
||||
In such a representation, widely different rates can be clearly visible in the same graph. This wouldn't be the case in linear scale.
|
||||
|
||||
image::erc4626-rate-loglogext.png[More exchange rates in logarithmic scale]
|
||||
|
||||
=== The attack
|
||||
|
||||
When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor or the vault (i.e. in favor of all the current share holders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit <1 share worth of tokens, then you get 0 shares, and you basically made a donation.
|
||||
|
||||
For a given amount of assets, the more shares you receive the safer you are. If you want to limit your losses to at most 1%, you need to receive at least 100 shares.
|
||||
|
||||
image::erc4626-deposit.png[Depositing assets]
|
||||
|
||||
In the figure we can see that for a given deposit of 500 assets, the number of shares we get and the corresponding rounding losses depend on the exchange rate. If the exchange rate is that of the orange curve, we are getting less than a share, so we lose 100% of our deposit. However, if the exchange rate is that of the green curve, we get 5000 shares, which limits our rounding losses to at most 0.02%.
|
||||
|
||||
image::erc4626-mint.png[Minting shares]
|
||||
|
||||
Symmetrically, if we focus on limiting our losses to a maximum of 0.5%, we need to get at least 200 shares. With the green exchange rate that requires just 20 tokens, but with the orange rate that requires 200000 tokens.
|
||||
|
||||
We can clearly see that that the blue and green curves correspond to vaults that are safer than the yellow and orange curves.
|
||||
|
||||
The idea of an inflation attack is that an attacker can donate assets to the vault to move the rate curve to the right, and make the vault unsafe.
|
||||
|
||||
image::erc4626-attack.png[Inflation attack without protection]
|
||||
|
||||
Figure 6 shows how an attacker can manipulate the rate of an empty vault. First the attacker must deposit a small amount of tokens (1 token) and follow up with a donation of 1e5 tokens directly to the vault to move the exchange rate "right". This puts the vault in a state where any deposit smaller than 1e5 would be completely lost to the vault. Given that the attacker is the only share holder (from their donation), the attacker would steal all the tokens deposited.
|
||||
|
||||
An attacker would typically wait for a user to do the first deposit into the vault, and would frontrun that operation with the attack described above. The risk is low, and the size of the "donation" required to manipulate the vault is equivalent to the size of the deposit that is being attacked.
|
||||
|
||||
In math that gives:
|
||||
|
||||
- stem:[a_0] the attacker deposit
|
||||
- stem:[a_1] the attacker donation
|
||||
- stem:[u] the user deposit
|
||||
|
||||
[%header,cols=4*]
|
||||
|===
|
||||
|
|
||||
| Assets
|
||||
| Shares
|
||||
| Rate
|
||||
|
||||
| initial
|
||||
| stem:[0]
|
||||
| stem:[0]
|
||||
| -
|
||||
|
||||
| after attacker's deposit
|
||||
| stem:[a_0]
|
||||
| stem:[a_0]
|
||||
| stem:[1]
|
||||
|
||||
| after attacker's donation
|
||||
| stem:[a_0+a_1]
|
||||
| stem:[a_0]
|
||||
| stem:[\frac{a_0}{a_0+a_1}]
|
||||
|===
|
||||
|
||||
This means a deposit of stem:[u] will give stem:[\frac{u \times a_0}{a_0 + a_1}] shares.
|
||||
|
||||
For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that
|
||||
|
||||
[stem]
|
||||
++++
|
||||
\frac{u \times a_0}{a_0+a_1} < 1 \iff u < 1 + \frac{a_1}{a_0}
|
||||
++++
|
||||
|
||||
Using stem:[a_0 = 1] and stem:[a_1 = u] is enough. So the attacker only needs stem:[u+1] assets to perform a successful attack.
|
||||
|
||||
It is easy to generalize the above results to scenarios where the attacker is going after a smaller fraction of the user's deposit. In order to target stem:[\frac{u}{n}], the user needs to suffer rounding of a similar fraction, which means the user must receive at most stem:[n] shares. This results in:
|
||||
|
||||
[stem]
|
||||
++++
|
||||
\frac{u \times a_0}{a_0+a_1} < n \iff \frac{u}{n} < 1 + \frac{a_1}{a_0}
|
||||
++++
|
||||
|
||||
In this scenario, the attack is stem:[n] times less powerful (in how much it is stealing) and costs stem:[n] times less to execute. In both cases, the amount of funds the attacker needs to commit is equivalent to its potential earnings.
|
||||
|
||||
=== Defending with a virtual offset
|
||||
|
||||
The defense we propose is based on the approach used in link:https://github.com/boringcrypto/YieldBox[YieldBox]. It consists of two parts:
|
||||
|
||||
- Use an offset between the "precision" of the representation of shares and assets. Said otherwise, we use more decimal places to represent the shares than the underlying token does to represent the assets.
|
||||
- Include virtual shares and virtual assets in the exchange rate computation. These virtual assets enforce the conversion rate when the vault is empty.
|
||||
|
||||
These two parts work together in enforcing the security of the vault. First, the increased precision corresponds to a high rate, which we saw is safer as it reduces the rounding error when computing the amount of shares. Second, the virtual assets and shares (in addition to simplifying a lot of the computations) capture part of the donation, making it unprofitable for a developer to perform an attack.
|
||||
|
||||
Following the previous math definitions, we have:
|
||||
|
||||
- stem:[\delta] the vault offset
|
||||
- stem:[a_0] the attacker deposit
|
||||
- stem:[a_1] the attacker donation
|
||||
- stem:[u] the user deposit
|
||||
|
||||
[%header,cols=4*]
|
||||
|===
|
||||
|
|
||||
| Assets
|
||||
| Shares
|
||||
| Rate
|
||||
|
||||
| initial
|
||||
| stem:[1]
|
||||
| stem:[10^\delta]
|
||||
| stem:[10^\delta]
|
||||
|
||||
| after attacker's deposit
|
||||
| stem:[1+a_0]
|
||||
| stem:[10^\delta \times (1+a_0)]
|
||||
| stem:[10^\delta]
|
||||
|
||||
| after attacker's donation
|
||||
| stem:[1+a_0+a_1]
|
||||
| stem:[10^\delta \times (1+a_0)]
|
||||
| stem:[10^\delta \times \frac{1+a_0}{1+a_0+a_1}]
|
||||
|===
|
||||
|
||||
One important thing to note is that the attacker only owns a fraction stem:[\frac{a_0}{1 + a_0}] of the shares, so when doing the donation, he will only be able to recover that fraction stem:[\frac{a_1 \times a_0}{1 + a_0}] of the donation. The remaining stem:[\frac{a_1}{1+a_0}] are captured by the vault.
|
||||
|
||||
[stem]
|
||||
++++
|
||||
\mathit{loss} = \frac{a_1}{1+a_0}
|
||||
++++
|
||||
|
||||
When the user deposits stem:[u], he receives
|
||||
|
||||
[stem]
|
||||
++++
|
||||
10^\delta \times u \times \frac{1+a_0}{1+a_0+a_1}
|
||||
++++
|
||||
|
||||
For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that
|
||||
|
||||
[stem]
|
||||
++++
|
||||
10^\delta \times u \times \frac{1+a_0}{1+a_0+a_1} < 1
|
||||
++++
|
||||
|
||||
[stem]
|
||||
++++
|
||||
\iff 10^\delta \times u < \frac{1+a_0+a_1}{1+a_0}
|
||||
++++
|
||||
|
||||
[stem]
|
||||
++++
|
||||
\iff 10^\delta \times u < 1 + \frac{a_1}{1+a_0}
|
||||
++++
|
||||
|
||||
[stem]
|
||||
++++
|
||||
\iff 10^\delta \times u \le \mathit{loss}
|
||||
++++
|
||||
|
||||
- If the offset is 0, the attacker loss is at least equal to the user's deposit.
|
||||
- If the offset is greater than 0, the attacker will have to suffer losses that are orders of magnitude bigger than the amount of value that can hypothetically be stolen from the user.
|
||||
|
||||
This shows that even with an offset of 0, the virtual shares and assets make this attack non profitable for the attacker. Bigger offsets increase the security even further by making any attack on the user extremely wasteful.
|
||||
|
||||
The following figure shows how the offset impacts the initial rate and limits the ability of an attacker with limited funds to inflate it effectively.
|
||||
|
||||
image::erc4626-attack-3a.png[Inflation attack without offset=3]
|
||||
stem:[\delta = 3], stem:[a_0 = 1], stem:[a_1 = 10^5]
|
||||
|
||||
image::erc4626-attack-3b.png[Inflation attack without offset=3 and an attacker deposit that limits its losses]
|
||||
stem:[\delta = 3], stem:[a_0 = 100], stem:[a_1 = 10^5]
|
||||
|
||||
image::erc4626-attack-6.png[Inflation attack without offset=6]
|
||||
stem:[\delta = 6], stem:[a_0 = 1], stem:[a_1 = 10^5]
|
||||
|
||||
|
||||
[[fees]]
|
||||
== Custom behavior: Adding fees to the vault
|
||||
|
||||
In an ERC-4626 vaults, fees can be captured during the deposit/mint and/or during the withdraw/redeem steps. In both cases it is essential to remain compliant with the ERC-4626 requirements with regard to the preview functions.
|
||||
|
||||
For example, if calling `deposit(100, receiver)`, the caller should deposit exactly 100 underlying tokens, including fees, and the receiver should receive a number of shares that matches the value returned by `previewDeposit(100)`. Similarly, `previewMint` should account for the fees that the user will have to pay on top of share's cost.
|
||||
|
||||
As for the `Deposit` event, while this is less clear in the EIP spec itself, there seems to be consensus that it should include the number of assets paid for by the user, including the fees.
|
||||
|
||||
On the other hand, when withdrawing assets, the number given by the user should correspond to what he receives. Any fees should be added to the quote (in shares) performed by `previewWithdraw`.
|
||||
|
||||
The `Withdraw` event should include the number of shares the user burns (including fees) and the number of assets the user actually receives (after fees are deducted).
|
||||
|
||||
The consequence of this design is that both the `Deposit` and `Withdraw` events will describe two exchange rates. The spread between the "Buy-in" and the "Exit" prices correspond to the fees taken by the vault.
|
||||
|
||||
The following example describes how fees proportional to the deposited/withdrawn amount can be implemented:
|
||||
|
||||
```solidity
|
||||
include::api:example$ERC4626Fees.sol[]
|
||||
```
|
||||
@@ -0,0 +1,58 @@
|
||||
= ERC-721
|
||||
|
||||
We've discussed how you can make a _fungible_ token using xref:erc20.adoc[ERC-20], but what if not all tokens are alike? This comes up in situations like *real estate*, *voting rights*, or *collectibles*, where some items are valued more than others, due to their usefulness, rarity, etc. ERC-721 is a standard for representing ownership of xref:tokens.adoc#different-kinds-of-tokens[_non-fungible_ tokens], that is, where each token is unique.
|
||||
|
||||
ERC-721 is a more complex standard than ERC-20, with multiple optional extensions, and is split across a number of contracts. The OpenZeppelin Contracts provide flexibility regarding how these are combined, along with custom useful extensions. Check out the xref:api:token/ERC721.adoc[API Reference] to learn more about these.
|
||||
|
||||
== Constructing an ERC-721 Token Contract
|
||||
|
||||
We'll use ERC-721 to track items in our game, which will each have their own unique attributes. Whenever one is to be awarded to a player, it will be minted and sent to them. Players are free to keep their token or trade it with other people as they see fit, as they would any other asset on the blockchain! Please note any account can call `awardItem` to mint items. To restrict what accounts can mint items we can add xref:access-control.adoc[Access Control].
|
||||
|
||||
Here's what a contract for tokenized items might look like:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
include::api:example$token/ERC721/GameItem.sol[]
|
||||
----
|
||||
|
||||
The xref:api:token/ERC721.adoc#ERC721URIStorage[`ERC721URIStorage`] contract is an implementation of ERC-721 that includes the metadata standard extensions (xref:api:token/ERC721.adoc#IERC721Metadata[`IERC721Metadata`]) as well as a mechanism for per-token metadata. That's where the xref:api:token/ERC721.adoc#ERC721-_setTokenURI-uint256-string-[`_setTokenURI`] method comes from: we use it to store an item's metadata.
|
||||
|
||||
Also note that, unlike ERC-20, ERC-721 lacks a `decimals` field, since each token is distinct and cannot be partitioned.
|
||||
|
||||
New items can be created:
|
||||
|
||||
[source,javascript]
|
||||
----
|
||||
> gameItem.awardItem(playerAddress, "https://game.example/item-id-8u5h2m.json")
|
||||
Transaction successful. Transaction hash: 0x...
|
||||
Events emitted:
|
||||
- Transfer(0x0000000000000000000000000000000000000000, playerAddress, 7)
|
||||
----
|
||||
|
||||
And the owner and metadata of each item queried:
|
||||
|
||||
[source,javascript]
|
||||
----
|
||||
> gameItem.ownerOf(7)
|
||||
playerAddress
|
||||
> gameItem.tokenURI(7)
|
||||
"https://game.example/item-id-8u5h2m.json"
|
||||
----
|
||||
|
||||
This `tokenURI` should resolve to a JSON document that might look something like:
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"name": "Thor's hammer",
|
||||
"description": "Mjölnir, the legendary hammer of the Norse god of thunder.",
|
||||
"image": "https://game.example/item-id-8u5h2m.png",
|
||||
"strength": 20
|
||||
}
|
||||
----
|
||||
|
||||
For more information about the `tokenURI` metadata JSON Schema, check out the https://eips.ethereum.org/EIPS/eip-721[ERC-721 specification].
|
||||
|
||||
NOTE: You'll notice that the item's information is included in the metadata, but that information isn't on-chain! So a game developer could change the underlying metadata, changing the rules of the game!
|
||||
|
||||
TIP: If you'd like to put all item information on-chain, you can extend ERC-721 to do so (though it will be rather costly) by providing a xref:utilities.adoc#base64[`Base64`] Data URI with the JSON schema encoded. You could also leverage IPFS to store the tokenURI information, but these techniques are out of the scope of this overview guide.
|
||||
@@ -0,0 +1,51 @@
|
||||
= Extending Contracts
|
||||
|
||||
Most of the OpenZeppelin Contracts are expected to be used via https://solidity.readthedocs.io/en/latest/contracts.html#inheritance[inheritance]: you will _inherit_ from them when writing your own contracts.
|
||||
|
||||
This is the commonly found `is` syntax, like in `contract MyToken is ERC20`.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Unlike ``contract``s, Solidity ``library``s are not inherited from and instead rely on the https://solidity.readthedocs.io/en/latest/contracts.html#using-for[`using for`] syntax.
|
||||
|
||||
OpenZeppelin Contracts has some ``library``s: most are in the xref:api:utils.adoc[Utils] directory.
|
||||
====
|
||||
|
||||
== Overriding
|
||||
|
||||
Inheritance is often used to add the parent contract's functionality to your own contract, but that's not all it can do. You can also _change_ how some parts of the parent behave using _overrides_.
|
||||
|
||||
For example, imagine you want to change xref:api:access.adoc#AccessControl[`AccessControl`] so that xref:api:access.adoc#AccessControl-revokeRole-bytes32-address-[`revokeRole`] can no longer be called. This can be achieved using overrides:
|
||||
|
||||
```solidity
|
||||
include::api:example$access-control/AccessControlModified.sol[]
|
||||
```
|
||||
|
||||
The old `revokeRole` is then replaced by our override, and any calls to it will immediately revert. We cannot _remove_ the function from the contract, but reverting on all calls is good enough.
|
||||
|
||||
=== Calling `super`
|
||||
|
||||
Sometimes you want to _extend_ a parent's behavior, instead of outright changing it to something else. This is where `super` comes in.
|
||||
|
||||
The `super` keyword will let you call functions defined in a parent contract, even if they are overridden. This mechanism can be used to add additional checks to a function, emit events, or otherwise add functionality as you see fit.
|
||||
|
||||
TIP: For more information on how overrides work, head over to the https://solidity.readthedocs.io/en/latest/contracts.html#index-17[official Solidity documentation].
|
||||
|
||||
Here is a modified version of xref:api:access.adoc#AccessControl[`AccessControl`] where xref:api:access.adoc#AccessControl-revokeRole-bytes32-address-[`revokeRole`] cannot be used to revoke the `DEFAULT_ADMIN_ROLE`:
|
||||
|
||||
|
||||
```solidity
|
||||
include::api:example$access-control/AccessControlNonRevokableAdmin.sol[]
|
||||
```
|
||||
|
||||
The `super.revokeRole` statement at the end will invoke ``AccessControl``'s original version of `revokeRole`, the same code that would've run if there were no overrides in place.
|
||||
|
||||
NOTE: The same rule is implemented and extended in xref:api:access.adoc#AccessControlDefaultAdminRules[`AccessControlDefaultAdminRules`], an extension that also adds enforced security measures for the `DEFAULT_ADMIN_ROLE`.
|
||||
|
||||
== Security
|
||||
|
||||
The maintainers of OpenZeppelin Contracts are mainly concerned with the correctness and security of the code as published in the library, and the combinations of base contracts with the official extensions from the library.
|
||||
|
||||
Custom overrides, and those of hooks in particular, may break some important assumptions and introduce vulnerabilities in otherwise secure code. While we try to ensure the contracts remain secure in the face of a wide range of potential customizations, this is done in a best-effort manner. While we try to document all important assumptions, this should not be relied upon. Custom overrides should be carefully reviewed and checked against the source code of the contract they are customizing so as to fully understand their impact and guarantee their security.
|
||||
|
||||
The way functions interact internally should not be assumed to stay stable across releases of the library. For example, a function that is used in one context in a particular release may not be used in the same context in the next release. Contracts that override functions should revalidate their assumptions when updating the version of OpenZeppelin Contracts they are built on.
|
||||
13
lib_openzeppelin_contracts/docs/modules/ROOT/pages/faq.adoc
Normal file
13
lib_openzeppelin_contracts/docs/modules/ROOT/pages/faq.adoc
Normal file
@@ -0,0 +1,13 @@
|
||||
= Frequently Asked Questions
|
||||
|
||||
== Can I restrict a function to EOAs only?
|
||||
|
||||
When calling external addresses from your contract it is unsafe to assume that an address is an externally-owned account (EOA) and not a contract. Attempting to prevent calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract constructor.
|
||||
|
||||
Although checking that the address has code, `address.code.length > 0`, may seem to differentiate contracts from EOAs, it can only say that an address is currently a contract, and its negation (that an address is not currently a contract) does not imply that the address is an EOA. Some counterexamples are:
|
||||
|
||||
- address of a contract in construction
|
||||
- address where a contract will be created
|
||||
- address where a contract lived, but was destroyed
|
||||
|
||||
Furthermore, an address will be considered a contract within the same transaction where it is scheduled for destruction by `SELFDESTRUCT`, which only has an effect at the end of the entire transaction.
|
||||
@@ -0,0 +1,239 @@
|
||||
= How to set up on-chain governance
|
||||
|
||||
In this guide we will learn how OpenZeppelin’s Governor contract works, how to set it up, and how to use it to create proposals, vote for them, and execute them, using tools provided by Ethers.js and Tally.
|
||||
|
||||
NOTE: Find detailed contract documentation at xref:api:governance.adoc[Governance API].
|
||||
|
||||
== Introduction
|
||||
|
||||
Decentralized protocols are in constant evolution from the moment they are publicly released. Often, the initial team retains control of this evolution in the first stages, but eventually delegates it to a community of stakeholders. The process by which this community makes decisions is called on-chain governance, and it has become a central component of decentralized protocols, fueling varied decisions such as parameter tweaking, smart contract upgrades, integrations with other protocols, treasury management, grants, etc.
|
||||
|
||||
This governance protocol is generally implemented in a special-purpose contract called “Governor”. The GovernorAlpha and GovernorBravo contracts designed by Compound have been very successful and popular so far, with the downside that projects with different requirements have had to fork the code to customize it for their needs, which can pose a high risk of introducing security issues. For OpenZeppelin Contracts, we set out to build a modular system of Governor contracts so that forking is not needed, and different requirements can be accommodated by writing small modules using Solidity inheritance. You will find the most common requirements out of the box in OpenZeppelin Contracts, but writing additional ones is simple, and we will be adding new features as requested by the community in future releases. Additionally, the design of OpenZeppelin Governor requires minimal use of storage and results in more gas efficient operation.
|
||||
|
||||
== Compatibility
|
||||
|
||||
OpenZeppelin’s Governor system was designed with a concern for compatibility with existing systems that were based on Compound’s GovernorAlpha and GovernorBravo. Because of this, you will find that many modules are presented in two variants, one of which is built for compatibility with those systems.
|
||||
|
||||
=== ERC20Votes & ERC20VotesComp
|
||||
|
||||
The ERC-20 extension to keep track of votes and vote delegation is one such case. The shorter one is the more generic version because it can support token supplies greater than 2^96, while the “Comp” variant is limited in that regard, but exactly fits the interface of the COMP token that is used by GovernorAlpha and Bravo. Both contract variants share the same events, so they are fully compatible when looking at events only.
|
||||
|
||||
=== Governor & GovernorStorage
|
||||
|
||||
An OpenZeppelin Governor contract is not interface-compatible with Compound's GovernorAlpha or Bravo. Even though events are fully compatible, proposal lifecycle functions (creation, execution, etc.) have different signatures that are meant to optimize storage use. Other functions from GovernorAlpha are Bravo are likewise not available. It’s possible to opt in some Bravo-like behavior by inheriting from the GovernorStorage module. This module provides proposal enumerability and alternate versions of the `queue`, `execute` and `cancel` function that only take the proposal id. This module reduces the calldata needed by some operations in exchange for an increased the storage footprint. This might be a good trade-off for some L2 chains. It also provides primitives for indexer-free frontends.
|
||||
|
||||
Note that even with the use of this module, one important difference with Compound's GovernorBravo is the way that `proposalId`s are calculated. Governor uses the hash of the proposal parameters with the purpose of keeping its data off-chain by event indexing, while the original Bravo implementation uses sequential `proposalId`s.
|
||||
|
||||
=== GovernorTimelockControl & GovernorTimelockCompound
|
||||
|
||||
When using a timelock with your Governor contract, you can use either OpenZeppelin’s TimelockController or Compound’s Timelock. Based on the choice of timelock, you should choose the corresponding Governor module: GovernorTimelockControl or GovernorTimelockCompound respectively. This allows you to migrate an existing GovernorAlpha instance to an OpenZeppelin-based Governor without changing the timelock in use.
|
||||
|
||||
=== Tally
|
||||
|
||||
https://www.tally.xyz[Tally] is a full-fledged application for user owned on-chain governance. It comprises a voting dashboard, proposal creation wizard, real time research and analysis, and educational content.
|
||||
|
||||
For all of these options, the Governor will be compatible with Tally: users will be able to create proposals, see voting periods and delays following xref:api:interfaces.adoc#IERC6372[IERC6372], visualize voting power and advocates, navigate proposals, and cast votes. For proposal creation in particular, projects can also use https://docs.openzeppelin.com/defender/module/actions#transaction-proposals-reference[Defender Transaction Proposals] as an alternative interface.
|
||||
|
||||
In the rest of this guide, we will focus on a fresh deploy of the vanilla OpenZeppelin Governor features without concern for compatibility with GovernorAlpha or Bravo.
|
||||
|
||||
== Setup
|
||||
|
||||
=== Token
|
||||
|
||||
The voting power of each account in our governance setup will be determined by an ERC-20 token. The token has to implement the ERC20Votes extension. This extension will keep track of historical balances so that voting power is retrieved from past snapshots rather than current balance, which is an important protection that prevents double voting.
|
||||
|
||||
```solidity
|
||||
include::api:example$governance/MyToken.sol[]
|
||||
```
|
||||
|
||||
If your project already has a live token that does not include ERC20Votes and is not upgradeable, you can wrap it in a governance token by using ERC20Wrapper. This will allow token holders to participate in governance by wrapping their tokens 1-to-1.
|
||||
|
||||
```solidity
|
||||
include::api:example$governance/MyTokenWrapped.sol[]
|
||||
```
|
||||
|
||||
NOTE: The only other source of voting power available in OpenZeppelin Contracts currently is xref:api:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]. ERC-721 tokens that don't provide this functionality can be wrapped into a voting tokens using a combination of xref:api:token/ERC721.adoc#ERC721Votes[`ERC721Votes`] and xref:api:token/ERC721Wrapper.adoc#ERC721Wrapper[`ERC721Wrapper`].
|
||||
|
||||
NOTE: The internal clock used by the token to store voting balances will dictate the operating mode of the Governor contract attached to it. By default, block numbers are used. Since v4.9, developers can override the xref:api:interfaces.adoc#IERC6372[IERC6372] clock to use timestamps instead of block numbers.
|
||||
|
||||
=== Governor
|
||||
|
||||
Initially, we will build a Governor without a timelock. The core logic is given by the Governor contract, but we still need to choose: 1) how voting power is determined, 2) how many votes are needed for quorum, 3) what options people have when casting a vote and how those votes are counted, and 4) what type of token should be used to vote. Each of these aspects is customizable by writing your own module, or more easily choosing one from OpenZeppelin Contracts.
|
||||
|
||||
For 1) we will use the GovernorVotes module, which hooks to an IVotes instance to determine the voting power of an account based on the token balance they hold when a proposal becomes active. This module requires as a constructor parameter the address of the token. This module also discovers the clock mode (ERC-6372) used by the token and applies it to the Governor.
|
||||
|
||||
For 2) we will use GovernorVotesQuorumFraction which works together with ERC20Votes to define quorum as a percentage of the total supply at the block a proposal’s voting power is retrieved. This requires a constructor parameter to set the percentage. Most Governors nowadays use 4%, so we will initialize the module with parameter 4 (this indicates the percentage, resulting in 4%).
|
||||
|
||||
For 3) we will use GovernorCountingSimple, a module that offers 3 options to voters: For, Against, and Abstain, and where only For and Abstain votes are counted towards quorum.
|
||||
|
||||
Besides these modules, Governor itself has some parameters we must set.
|
||||
|
||||
votingDelay: How long after a proposal is created should voting power be fixed. A large voting delay gives users time to unstake tokens if necessary.
|
||||
|
||||
votingPeriod: How long does a proposal remain open to votes.
|
||||
|
||||
These parameters are specified in the unit defined in the token's clock. Assuming the token uses block numbers, and assuming block time of around 12 seconds, we will have set votingDelay = 1 day = 7200 blocks, and votingPeriod = 1 week = 50400 blocks.
|
||||
|
||||
We can optionally set a proposal threshold as well. This restricts proposal creation to accounts who have enough voting power.
|
||||
|
||||
```solidity
|
||||
include::api:example$governance/MyGovernor.sol[]
|
||||
```
|
||||
|
||||
=== Timelock
|
||||
|
||||
It is good practice to add a timelock to governance decisions. This allows users to exit the system if they disagree with a decision before it is executed. We will use OpenZeppelin’s TimelockController in combination with the GovernorTimelockControl module.
|
||||
|
||||
IMPORTANT: When using a timelock, it is the timelock that will execute proposals and thus the timelock that should hold any funds, ownership, and access control roles. Before version 4.5 there was no way to recover funds in the Governor contract when using a timelock! Before version 4.3, when using the Compound Timelock, ETH in the timelock was not easily accessible.
|
||||
|
||||
TimelockController uses an AccessControl setup that we need to understand in order to set up roles.
|
||||
|
||||
- The Proposer role is in charge of queueing operations: this is the role the Governor instance should be granted, and it should likely be the only proposer in the system.
|
||||
- The Executor role is in charge of executing already available operations: we can assign this role to the special zero address to allow anyone to execute (if operations can be particularly time sensitive, the Governor should be made Executor instead).
|
||||
- Lastly, there is the Admin role, which can grant and revoke the two previous roles: this is a very sensitive role that will be granted automatically to the timelock itself, and optionally to a second account, which can be used for ease of setup but should promptly renounce the role.
|
||||
|
||||
== Proposal Lifecycle
|
||||
|
||||
Let’s walk through how to create and execute a proposal on our newly deployed Governor.
|
||||
|
||||
A proposal is a sequence of actions that the Governor contract will perform if it passes. Each action consists of a target address, calldata encoding a function call, and an amount of ETH to include. Additionally, a proposal includes a human-readable description.
|
||||
|
||||
=== Create a Proposal
|
||||
|
||||
Let’s say we want to create a proposal to give a team a grant, in the form of ERC-20 tokens from the governance treasury. This proposal will consist of a single action where the target is the ERC-20 token, calldata is the encoded function call `transfer(<team wallet>, <grant amount>)`, and with 0 ETH attached.
|
||||
|
||||
Generally a proposal will be created with the help of an interface such as Tally or https://docs.openzeppelin.com/defender/module/actions#transaction-proposals-reference[Defender Proposals]. Here we will show how to create the proposal using Ethers.js.
|
||||
|
||||
First we get all the parameters necessary for the proposal action.
|
||||
|
||||
```javascript
|
||||
const tokenAddress = ...;
|
||||
const token = await ethers.getContractAt(‘ERC20’, tokenAddress);
|
||||
|
||||
const teamAddress = ...;
|
||||
const grantAmount = ...;
|
||||
const transferCalldata = token.interface.encodeFunctionData(‘transfer’, [teamAddress, grantAmount]);
|
||||
```
|
||||
|
||||
Now we are ready to call the propose function of the Governor. Note that we don’t pass in one array of actions, but instead three arrays corresponding to the list of targets, the list of values, and the list of calldatas. In this case it’s a single action, so it’s simple:
|
||||
|
||||
```javascript
|
||||
await governor.propose(
|
||||
[tokenAddress],
|
||||
[0],
|
||||
[transferCalldata],
|
||||
“Proposal #1: Give grant to team”,
|
||||
);
|
||||
```
|
||||
|
||||
This will create a new proposal, with a proposal id that is obtained by hashing together the proposal data, and which will also be found in an event in the logs of the transaction.
|
||||
|
||||
=== Cast a Vote
|
||||
|
||||
Once a proposal is active, delegates can cast their vote. Note that it is delegates who carry voting power: if a token holder wants to participate, they can set a trusted representative as their delegate, or they can become a delegate themselves by self-delegating their voting power.
|
||||
|
||||
Votes are cast by interacting with the Governor contract through the `castVote` family of functions. Voters would generally invoke this from a governance UI such as Tally.
|
||||
|
||||
image::tally-vote.png[Voting in Tally]
|
||||
|
||||
=== Execute the Proposal
|
||||
|
||||
Once the voting period is over, if quorum was reached (enough voting power participated) and the majority voted in favor, the proposal is considered successful and can proceed to be executed. Once a proposal passes, it can be queued and executed from the same place you voted.
|
||||
|
||||
image::tally-exec.png[Administration Panel in Tally]
|
||||
|
||||
We will see now how to do this manually using Ethers.js.
|
||||
|
||||
If a timelock was set up, the first step to execution is queueing. You will notice that both the queue and execute functions require passing in the entire proposal parameters, as opposed to just the proposal id. This is necessary because this data is not stored on chain, as a measure to save gas. Note that these parameters can always be found in the events emitted by the contract. The only parameter that is not sent in its entirety is the description, since this is only needed in its hashed form to compute the proposal id.
|
||||
|
||||
To queue, we call the queue function:
|
||||
|
||||
```javascript
|
||||
const descriptionHash = ethers.utils.id(“Proposal #1: Give grant to team”);
|
||||
|
||||
await governor.queue(
|
||||
[tokenAddress],
|
||||
[0],
|
||||
[transferCalldata],
|
||||
descriptionHash,
|
||||
);
|
||||
```
|
||||
|
||||
This will cause the Governor to interact with the timelock contract and queue the actions for execution after the required delay.
|
||||
|
||||
After enough time has passed (according to the timelock parameters), the proposal can be executed. If there was no timelock to begin with, this step can be ran immediately after the proposal succeeds.
|
||||
|
||||
```javascript
|
||||
await governor.execute(
|
||||
[tokenAddress],
|
||||
[0],
|
||||
[transferCalldata],
|
||||
descriptionHash,
|
||||
);
|
||||
```
|
||||
|
||||
Executing the proposal will transfer the ERC-20 tokens to the chosen recipient. To wrap up: we set up a system where a treasury is controlled by the collective decision of the token holders of a project, and all actions are executed via proposals enforced by on-chain votes.
|
||||
|
||||
== Timestamp based governance
|
||||
|
||||
=== Motivation
|
||||
|
||||
It is sometimes difficult to deal with durations expressed in number of blocks because of inconsistent or unpredictable time between blocks. This is particularly true of some L2 networks where blocks are produced based on blockchain usage. Using number of blocks can also lead to the governance rules being affected by network upgrades that modify the expected time between blocks.
|
||||
|
||||
The difficulty of replacing block numbers with timestamps is that the Governor and the token must both use the same format when querying past votes. If a token is designed around block numbers, it is not possible for a Governor to reliably do timestamp based lookups.
|
||||
|
||||
Therefore, designing a timestamp based voting system starts with the token.
|
||||
|
||||
=== Token
|
||||
|
||||
Since v4.9, all voting contracts (including xref:api:token/ERC20.adoc#ERC20Votes[`ERC20Votes`] and xref:api:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]) rely on xref:api:interfaces.adoc#IERC6372[IERC6372] for clock management. In order to change from operating with block numbers to operating with timestamps, all that is required is to override the `clock()` and `CLOCK_MODE()` functions.
|
||||
|
||||
```solidity
|
||||
include::api:example$governance/MyTokenTimestampBased.sol[]
|
||||
```
|
||||
|
||||
=== Governor
|
||||
|
||||
The Governor will automatically detect the clock mode used by the token and adapt to it. There is no need to override anything in the Governor contract. However, the clock mode does affect how some values are interpreted. It is therefore necessary to set the `votingDelay()` and `votingPeriod()` accordingly.
|
||||
|
||||
```solidity
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Governor} from "@openzeppelin/contracts/governance/Governor.sol";
|
||||
import {GovernorCountingSimple} from "@openzeppelin/contracts/governance/compatibility/GovernorCountingSimple.sol";
|
||||
import {GovernorVotes} from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
|
||||
import {GovernorVotesQuorumFraction} from "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
|
||||
import {GovernorTimelockControl} from "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
|
||||
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
|
||||
import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
|
||||
|
||||
contract MyGovernor is Governor, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl {
|
||||
constructor(IVotes _token, TimelockController _timelock)
|
||||
Governor("MyGovernor")
|
||||
GovernorVotes(_token)
|
||||
GovernorVotesQuorumFraction(4)
|
||||
GovernorTimelockControl(_timelock)
|
||||
{}
|
||||
|
||||
function votingDelay() public pure virtual override returns (uint256) {
|
||||
return 1 days;
|
||||
}
|
||||
|
||||
function votingPeriod() public pure virtual override returns (uint256) {
|
||||
return 1 weeks;
|
||||
}
|
||||
|
||||
function proposalThreshold() public pure virtual override returns (uint256) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
=== Disclaimer
|
||||
|
||||
Timestamp based voting is a recent feature that was formalized in ERC-6372 and ERC-5805, and introduced in v4.9. At the time this feature is released, some governance tooling may not support it yet. Users can expect invalid reporting of deadlines & durations if the tool is not able to interpret the ERC6372 clock. This invalid reporting by offchain tools does not affect the onchain security and functionality of the governance contract.
|
||||
|
||||
Governors with timestamp support (v4.9 and above) are compatible with old tokens (before v4.9) and will operate in "block number" mode (which is the mode all old tokens operate on). On the other hand, old Governor instances (before v4.9) are not compatible with new tokens operating using timestamps. If you update your token code to use timestamps, make sure to also update your Governor code.
|
||||
@@ -0,0 +1,70 @@
|
||||
= Contracts
|
||||
|
||||
*A library for secure smart contract development.* Build on a solid foundation of community-vetted code.
|
||||
|
||||
* Implementations of standards like xref:erc20.adoc[ERC20] and xref:erc721.adoc[ERC721].
|
||||
* Flexible xref:access-control.adoc[role-based permissioning] scheme.
|
||||
* Reusable xref:utilities.adoc[Solidity components] to build custom contracts and complex decentralized systems.
|
||||
|
||||
IMPORTANT: OpenZeppelin Contracts uses semantic versioning to communicate backwards compatibility of its API and storage layout. For upgradeable contracts, the storage layout of different major versions should be assumed incompatible, for example, it is unsafe to upgrade from 4.9.3 to 5.0.0. Learn more at xref:backwards-compatibility.adoc[Backwards Compatibility].
|
||||
|
||||
== Overview
|
||||
|
||||
[[install]]
|
||||
=== Installation
|
||||
|
||||
==== Hardhat (npm)
|
||||
|
||||
```console
|
||||
$ npm install @openzeppelin/contracts
|
||||
```
|
||||
|
||||
==== Foundry (git)
|
||||
|
||||
WARNING: When installing via git, it is a common error to use the `master` branch. This is a development branch that should be avoided in favor of tagged releases. The release process involves security measures that the `master` branch does not guarantee.
|
||||
|
||||
WARNING: Foundry installs the latest version initially, but subsequent `forge update` commands will use the `master` branch.
|
||||
|
||||
```console
|
||||
$ forge install OpenZeppelin/openzeppelin-contracts
|
||||
```
|
||||
|
||||
Add `@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/` in `remappings.txt.`
|
||||
|
||||
[[usage]]
|
||||
=== Usage
|
||||
|
||||
Once installed, you can use the contracts in the library by importing them:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
include::api:example$MyNFT.sol[]
|
||||
----
|
||||
|
||||
TIP: If you're new to smart contract development, head to xref:learn::developing-smart-contracts.adoc[Developing Smart Contracts] to learn about creating a new project and compiling your contracts.
|
||||
|
||||
To keep your system secure, you should **always** use the installed code as-is, and neither copy-paste it from online sources, nor modify it yourself. The library is designed so that only the contracts and functions you use are deployed, so you don't need to worry about it needlessly increasing gas costs.
|
||||
|
||||
[[security]]
|
||||
== Security
|
||||
|
||||
Please report any security issues you find via our https://www.immunefi.com/bounty/openzeppelin[bug bounty program on Immunefi] or directly to security@openzeppelin.org.
|
||||
|
||||
The https://contracts.openzeppelin.com/security[Security Center] contains more details about the secure development process.
|
||||
|
||||
[[next-steps]]
|
||||
== Learn More
|
||||
|
||||
The guides in the sidebar will teach about different concepts, and how to use the related contracts that OpenZeppelin Contracts provides:
|
||||
|
||||
* xref:access-control.adoc[Access Control]: decide who can perform each of the actions on your system.
|
||||
* xref:tokens.adoc[Tokens]: create tradable assets or collectibles, like the well known xref:erc20.adoc[ERC20] and xref:erc721.adoc[ERC721] standards.
|
||||
* xref:utilities.adoc[Utilities]: generic useful tools, including non-overflowing math, signature verification, and trustless paying systems.
|
||||
|
||||
The xref:api:token/ERC20.adoc[full API] is also thoroughly documented, and serves as a great reference when developing your smart contract application. You can also ask for help or follow Contracts' development in the https://forum.openzeppelin.com[community forum].
|
||||
|
||||
Finally, you may want to take a look at the https://blog.openzeppelin.com/guides/[guides on our blog], which cover several common use cases and good practices. The following articles provide great background reading, though please note, some of the referenced tools have changed as the tooling in the ecosystem continues to rapidly evolve.
|
||||
|
||||
* https://blog.openzeppelin.com/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05[The Hitchhiker’s Guide to Smart Contracts in Ethereum] will help you get an overview of the various tools available for smart contract development, and help you set up your environment.
|
||||
* https://blog.openzeppelin.com/a-gentle-introduction-to-ethereum-programming-part-1-783cc7796094[A Gentle Introduction to Ethereum Programming, Part 1] provides very useful information on an introductory level, including many basic concepts from the Ethereum platform.
|
||||
* For a more in-depth dive, you may read the guide https://blog.openzeppelin.com/designing-the-architecture-for-your-ethereum-application-9cec086f8317[Designing the architecture for your Ethereum application], which discusses how to better structure your application and its relationship to the real world.
|
||||
@@ -0,0 +1,31 @@
|
||||
= Tokens
|
||||
|
||||
Ah, the "token": blockchain's most powerful and most misunderstood tool.
|
||||
|
||||
A token is a _representation of something in the blockchain_. This something can be money, time, services, shares in a company, a virtual pet, anything. By representing things as tokens, we can allow smart contracts to interact with them, exchange them, create or destroy them.
|
||||
|
||||
[[but_first_coffee_a_primer_on_token_contracts]]
|
||||
== But First, [strikethrough]#Coffee# a Primer on Token Contracts
|
||||
|
||||
Much of the confusion surrounding tokens comes from two concepts getting mixed up: _token contracts_ and the actual _tokens_.
|
||||
|
||||
A _token contract_ is simply an Ethereum smart contract. "Sending tokens" actually means "calling a method on a smart contract that someone wrote and deployed". At the end of the day, a token contract is not much more than a mapping of addresses to balances, plus some methods to add and subtract from those balances.
|
||||
|
||||
It is these balances that represent the _tokens_ themselves. Someone "has tokens" when their balance in the token contract is non-zero. That's it! These balances could be considered money, experience points in a game, deeds of ownership, or voting rights, and each of these tokens would be stored in different token contracts.
|
||||
|
||||
[[different-kinds-of-tokens]]
|
||||
== Different Kinds of Tokens
|
||||
|
||||
Note that there's a big difference between having two voting rights and two deeds of ownership: each vote is equal to all others, but houses usually are not! This is called https://en.wikipedia.org/wiki/Fungibility[fungibility]. _Fungible goods_ are equivalent and interchangeable, like Ether, fiat currencies, and voting rights. _Non-fungible_ goods are unique and distinct, like deeds of ownership, or collectibles.
|
||||
|
||||
In a nutshell, when dealing with non-fungibles (like your house) you care about _which ones_ you have, while in fungible assets (like your bank account statement) what matters is _how much_ you have.
|
||||
|
||||
== Standards
|
||||
|
||||
Even though the concept of a token is simple, they have a variety of complexities in the implementation. Because everything in Ethereum is just a smart contract, and there are no rules about what smart contracts have to do, the community has developed a variety of *standards* (called EIPs or ERCs) for documenting how a contract can interoperate with other contracts.
|
||||
|
||||
You've probably heard of the ERC-20 or ERC-721 token standards, and that's why you're here. Head to our specialized guides to learn more about these:
|
||||
|
||||
* xref:erc20.adoc[ERC-20]: the most widespread token standard for fungible assets, albeit somewhat limited by its simplicity.
|
||||
* xref:erc721.adoc[ERC-721]: the de-facto solution for non-fungible tokens, often used for collectibles and games.
|
||||
* xref:erc1155.adoc[ERC-1155]: a novel standard for multi-tokens, allowing for a single contract to represent multiple fungible and non-fungible tokens, along with batched operations for increased gas efficiency.
|
||||
@@ -0,0 +1,77 @@
|
||||
= Using with Upgrades
|
||||
|
||||
If your contract is going to be deployed with upgradeability, such as using the xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins], you will need to use the Upgradeable variant of OpenZeppelin Contracts.
|
||||
|
||||
This variant is available as a separate package called `@openzeppelin/contracts-upgradeable`, which is hosted in the repository https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable[OpenZeppelin/openzeppelin-contracts-upgradeable]. It uses `@openzeppelin/contracts` as a peer dependency.
|
||||
|
||||
It follows all of the rules for xref:upgrades-plugins::writing-upgradeable.adoc[Writing Upgradeable Contracts]: constructors are replaced by initializer functions, state variables are initialized in initializer functions, and we additionally check for storage incompatibilities across minor versions.
|
||||
|
||||
TIP: OpenZeppelin provides a full suite of tools for deploying and securing upgradeable smart contracts. xref:openzeppelin::upgrades.adoc[Check out the full list of resources].
|
||||
|
||||
== Overview
|
||||
|
||||
=== Installation
|
||||
|
||||
```console
|
||||
$ npm install @openzeppelin/contracts-upgradeable @openzeppelin/contracts
|
||||
```
|
||||
|
||||
=== Usage
|
||||
|
||||
The Upgradeable package replicates the structure of the main OpenZeppelin Contracts package, but every file and contract has the suffix `Upgradeable`.
|
||||
|
||||
```diff
|
||||
-import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
||||
+import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
|
||||
|
||||
-contract MyCollectible is ERC721 {
|
||||
+contract MyCollectible is ERC721Upgradeable {
|
||||
```
|
||||
|
||||
NOTE: Interfaces and libraries are not included in the Upgradeable package, but are instead imported from the main OpenZeppelin Contracts package.
|
||||
|
||||
Constructors are replaced by internal initializer functions following the naming convention `+__{ContractName}_init+`. Since these are internal, you must always define your own public initializer function and call the parent initializer of the contract you extend.
|
||||
|
||||
```diff
|
||||
- constructor() ERC721("MyCollectible", "MCO") public {
|
||||
+ function initialize() initializer public {
|
||||
+ __ERC721_init("MyCollectible", "MCO");
|
||||
}
|
||||
```
|
||||
|
||||
CAUTION: Use with multiple inheritance requires special attention. See the section below titled <<multiple-inheritance>>.
|
||||
|
||||
Once this contract is set up and compiled, you can deploy it using the xref:upgrades-plugins::index.adoc[Upgrades Plugins]. The following snippet shows an example deployment script using Hardhat.
|
||||
|
||||
```js
|
||||
// scripts/deploy-my-collectible.js
|
||||
const { ethers, upgrades } = require("hardhat");
|
||||
|
||||
async function main() {
|
||||
const MyCollectible = await ethers.getContractFactory("MyCollectible");
|
||||
|
||||
const mc = await upgrades.deployProxy(MyCollectible);
|
||||
|
||||
await mc.waitForDeployment();
|
||||
console.log("MyCollectible deployed to:", await mc.getAddress());
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
== Further Notes
|
||||
|
||||
[[multiple-inheritance]]
|
||||
=== Multiple Inheritance
|
||||
|
||||
Initializer functions are not linearized by the compiler like constructors. Because of this, each `+__{ContractName}_init+` function embeds the linearized calls to all parent initializers. As a consequence, calling two of these `init` functions can potentially initialize the same contract twice.
|
||||
|
||||
The function `+__{ContractName}_init_unchained+` found in every contract is the initializer function minus the calls to parent initializers, and can be used to avoid the double initialization problem, but doing this manually is not recommended. We hope to be able to implement safety checks for this in future versions of the Upgrades Plugins.
|
||||
|
||||
=== Namespaced Storage
|
||||
|
||||
You may notice that contracts use a struct with the `@custom:storage-location erc7201:<NAMESPACE_ID>` annotation to store the contract's state variables. This follows the https://eips.ethereum.org/EIPS/eip-7201[ERC-7201: Namespaced Storage Layout] pattern, where each contract has its own storage layout in a namespace that is separate from other contracts in the inheritance chain.
|
||||
|
||||
Without namespaced storage, it isn't safe to simply add a state variable because it "shifts down" all of the state variables below in the inheritance chain. This makes the storage layouts incompatible, as explained in xref:upgrades-plugins::writing-upgradeable.adoc#modifying-your-contracts[Writing Upgradeable Contracts].
|
||||
|
||||
The namespaced storage pattern used in the Upgradeable package allows us to freely add new state variables in the future without compromising the storage compatibility with existing deployments. It also allows changing the inheritance order with no impact on the resulting storage layout, as long as all inherited contracts use namespaced storage.
|
||||
@@ -0,0 +1,235 @@
|
||||
= Utilities
|
||||
|
||||
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
|
||||
|
||||
=== Checking Signatures On-Chain
|
||||
|
||||
xref:api:utils.adoc#ECDSA[`ECDSA`] provides functions for recovering and managing Ethereum account ECDSA signatures. These are often generated via https://web3js.readthedocs.io/en/v1.7.3/web3-eth.html#sign[`web3.eth.sign`], and are a 65 byte array (of type `bytes` in Solidity) arranged the following way: `[[v (1)], [r (32)], [s (32)]]`.
|
||||
|
||||
The data signer can be recovered with xref:api:utils.adoc#ECDSA-recover-bytes32-bytes-[`ECDSA.recover`], and its address compared to verify the signature. Most wallets will hash the data to sign and add the prefix `\x19Ethereum Signed Message:\n`, so when attempting to recover the signer of an Ethereum signed message hash, you'll want to use xref:api:utils.adoc#MessageHashUtils-toEthSignedMessageHash-bytes32-[`toEthSignedMessageHash`].
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
using ECDSA for bytes32;
|
||||
using MessageHashUtils for bytes32;
|
||||
|
||||
function _verify(bytes32 data, bytes memory signature, address account) internal pure returns (bool) {
|
||||
return data
|
||||
.toEthSignedMessageHash()
|
||||
.recover(signature) == account;
|
||||
}
|
||||
----
|
||||
|
||||
WARNING: Getting signature verification right is not trivial: make sure you fully read and understand xref:api:utils.adoc#MessageHashUtils[`MessageHashUtils`]'s and xref:api:utils.adoc#ECDSA[`ECDSA`]'s documentation.
|
||||
|
||||
=== Verifying Merkle Proofs
|
||||
|
||||
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-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.
|
||||
|
||||
For an on-chain Merkle Tree, see the xref:api:utils.adoc#MerkleTree[`MerkleTree`] library.
|
||||
|
||||
[[introspection]]
|
||||
== Introspection
|
||||
|
||||
In Solidity, it's frequently helpful to know whether or not a contract supports an interface you'd like to use. ERC-165 is a standard that helps do runtime interface detection. Contracts provide helpers both for implementing ERC-165 in your contracts and querying other contracts:
|
||||
|
||||
* xref:api:utils.adoc#IERC165[`IERC165`] — this is the ERC-165 interface that defines xref:api:utils.adoc#IERC165-supportsInterface-bytes4-[`supportsInterface`]. When implementing ERC-165, you'll conform to this interface.
|
||||
* xref:api:utils.adoc#ERC165[`ERC165`] — inherit this contract if you'd like to support interface detection using a lookup table in contract storage. You can register interfaces using xref:api:utils.adoc#ERC165-_registerInterface-bytes4-[`_registerInterface(bytes4)`]: check out example usage as part of the ERC-721 implementation.
|
||||
* xref:api:utils.adoc#ERC165Checker[`ERC165Checker`] — ERC165Checker simplifies the process of checking whether or not a contract supports an interface you care about.
|
||||
* include with `using ERC165Checker for address;`
|
||||
* xref:api:utils.adoc#ERC165Checker-_supportsInterface-address-bytes4-[`myAddress._supportsInterface(bytes4)`]
|
||||
* xref:api:utils.adoc#ERC165Checker-_supportsAllInterfaces-address-bytes4---[`myAddress._supportsAllInterfaces(bytes4[\])`]
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
contract MyContract {
|
||||
using ERC165Checker for address;
|
||||
|
||||
bytes4 private InterfaceId_ERC721 = 0x80ac58cd;
|
||||
|
||||
/**
|
||||
* @dev transfer an ERC-721 token from this contract to someone else
|
||||
*/
|
||||
function transferERC721(
|
||||
address token,
|
||||
address to,
|
||||
uint256 tokenId
|
||||
)
|
||||
public
|
||||
{
|
||||
require(token.supportsInterface(InterfaceId_ERC721), "IS_NOT_721_TOKEN");
|
||||
IERC721(token).transferFrom(address(this), to, tokenId);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[math]]
|
||||
== Math
|
||||
|
||||
Although Solidity already provides math operators (i.e. `+`, `-`, etc.), Contracts includes xref:api:utils.adoc#Math[`Math`]; a set of utilities for dealing with mathematical operators, with support for extra operations (eg. xref:api:utils.adoc#Math-average-uint256-uint256-[`average`]) and xref:api:utils.adoc#SignedMath[`SignedMath`]; a library specialized in signed math operations.
|
||||
|
||||
Include these contracts with `using Math for uint256` or `using SignedMath for int256` and then use their functions in your code:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
contract MyContract {
|
||||
using Math for uint256;
|
||||
using SignedMath for int256;
|
||||
|
||||
function tryOperations(uint256 a, uint256 b) internal pure {
|
||||
(bool succeededAdd, uint256 resultAdd) = x.tryAdd(y);
|
||||
(bool succeededSub, uint256 resultSub) = x.trySub(y);
|
||||
(bool succeededMul, uint256 resultMul) = x.tryMul(y);
|
||||
(bool succeededDiv, uint256 resultDiv) = x.tryDiv(y);
|
||||
// ...
|
||||
}
|
||||
|
||||
function unsignedAverage(int256 a, int256 b) {
|
||||
int256 avg = a.average(b);
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
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
|
||||
|
||||
Some use cases require more powerful data structures than arrays and mappings offered natively in Solidity. Contracts provides these libraries for enhanced data structure management:
|
||||
|
||||
- xref:api:utils.adoc#BitMaps[`BitMaps`]: Store packed booleans in storage.
|
||||
- xref:api:utils.adoc#Checkpoints[`Checkpoints`]: Checkpoint values with built-in lookups.
|
||||
- xref:api:utils.adoc#DoubleEndedQueue[`DoubleEndedQueue`]: Store items in a queue with `pop()` and `queue()` constant time operations.
|
||||
- xref:api:utils.adoc#EnumerableSet[`EnumerableSet`]: A https://en.wikipedia.org/wiki/Set_(abstract_data_type)[set] with enumeration capabilities.
|
||||
- xref:api:utils.adoc#EnumerableMap[`EnumerableMap`]: A `mapping` variant with enumeration capabilities.
|
||||
- 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.
|
||||
|
||||
=== 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
|
||||
|
||||
=== 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;
|
||||
}
|
||||
----
|
||||
|
||||
The xref:api:utils.adoc#StorageSlot[`StorageSlot`] library also supports transient storage through user defined value types (UDVTs[https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types]), which enables the same value types as in Solidity.
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542;
|
||||
|
||||
function _getTransientLock() internal view returns (bool) {
|
||||
return _LOCK_SLOT.asBoolean().tload();
|
||||
}
|
||||
|
||||
function _setTransientLock(bool lock) internal {
|
||||
_LOCK_SLOT.asBoolean().tstore(lock);
|
||||
}
|
||||
----
|
||||
|
||||
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
|
||||
|
||||
xref:api:utils.adoc#Base64[`Base64`] util allows you to transform `bytes32` data into its Base64 `string` representation.
|
||||
|
||||
This is especially useful for building URL-safe tokenURIs for both xref:api:token/ERC721.adoc#IERC721Metadata-tokenURI-uint256-[`ERC-721`] or xref:api:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[`ERC-1155`]. This library provides a clever way to serve URL-safe https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs/[Data URI] compliant strings to serve on-chain data structures.
|
||||
|
||||
Here is an example to send JSON Metadata through a Base64 Data URI using an ERC-721:
|
||||
|
||||
[source, solidity]
|
||||
----
|
||||
include::api:example$utilities/Base64NFT.sol[]
|
||||
----
|
||||
|
||||
=== Multicall
|
||||
|
||||
The `Multicall` abstract contract comes with a `multicall` function that bundles together multiple calls in a single external call. With it, external accounts may perform atomic operations comprising several function calls. This is not only useful for EOAs to make multiple calls in a single transaction, it's also a way to revert a previous call if a later one fails.
|
||||
|
||||
Consider this dummy contract:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
include::api:example$utilities/Multicall.sol[]
|
||||
----
|
||||
|
||||
This is how to call the `multicall` function using Ethers.js, allowing `foo` and `bar` to be called in a single transaction:
|
||||
[source,javascript]
|
||||
----
|
||||
// scripts/foobar.js
|
||||
|
||||
const instance = await ethers.deployContract("Box");
|
||||
|
||||
await instance.multicall([
|
||||
instance.interface.encodeFunctionData("foo"),
|
||||
instance.interface.encodeFunctionData("bar")
|
||||
]);
|
||||
----
|
||||
@@ -0,0 +1,15 @@
|
||||
= Contracts Wizard
|
||||
:page-notoc:
|
||||
|
||||
Not sure where to start? Use the interactive generator below to bootstrap your
|
||||
contract and learn about the components offered in OpenZeppelin Contracts.
|
||||
|
||||
TIP: Place the resulting contract in your `contracts` or `src` directory in order to compile it with a tool like Hardhat or Foundry. Consider reading our guide on xref:learn::developing-smart-contracts.adoc[Developing Smart Contracts] for more guidance!
|
||||
|
||||
++++
|
||||
<script async src="https://wizard.openzeppelin.com/build/embed.js"></script>
|
||||
|
||||
<oz-wizard style="display: block; min-height: 40rem;"></oz-wizard>
|
||||
++++
|
||||
|
||||
|
||||
Reference in New Issue
Block a user