Transpile 7bce2b72
This commit is contained in:
BIN
docs/modules/ROOT/images/tally-admin.png
Normal file
BIN
docs/modules/ROOT/images/tally-admin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/modules/ROOT/images/tally-vote.png
Normal file
BIN
docs/modules/ROOT/images/tally-vote.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
19
docs/modules/ROOT/nav.adoc
Normal file
19
docs/modules/ROOT/nav.adoc
Normal file
@ -0,0 +1,19 @@
|
||||
* xref:index.adoc[Overview]
|
||||
* xref:wizard.adoc[Wizard]
|
||||
* xref:extending-contracts.adoc[Extending Contracts]
|
||||
* xref:upgradeable.adoc[Using with Upgrades]
|
||||
|
||||
* xref:releases-stability.adoc[Releases & Stability]
|
||||
|
||||
* xref:access-control.adoc[Access Control]
|
||||
|
||||
* xref:tokens.adoc[Tokens]
|
||||
** xref:erc20.adoc[ERC20]
|
||||
*** xref:erc20-supply.adoc[Creating Supply]
|
||||
** xref:erc721.adoc[ERC721]
|
||||
** xref:erc777.adoc[ERC777]
|
||||
** xref:erc1155.adoc[ERC1155]
|
||||
|
||||
* xref:governance.adoc[Governance]
|
||||
|
||||
* xref:utilities.adoc[Utilities]
|
||||
217
docs/modules/ROOT/pages/access-control.adoc
Normal file
217
docs/modules/ROOT/pages/access-control.adoc
Normal file
@ -0,0 +1,217 @@
|
||||
= 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.
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
// contracts/MyContract.sol
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
|
||||
contract MyContract is Ownable {
|
||||
function normalThing() public {
|
||||
// anyone can call this normalThing()
|
||||
}
|
||||
|
||||
function specialThing() public onlyOwner {
|
||||
// only the owner can call specialThing()!
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
By default, the xref:api:access.adoc#Ownable-owner--[`owner`] of an `Ownable` contract is the account that deployed it, which is usually exactly what you want.
|
||||
|
||||
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!
|
||||
|
||||
Note that *a contract can also be the owner of another one*! This opens the door to using, for example, a https://github.com/gnosis/MultiSigWallet[Gnosis Multisig] or https://safe.gnosis.io[Gnosis Safe], an https://aragon.org[Aragon DAO], an https://www.uport.me[ERC725/uPort] identity contract, 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:tokens.adoc#ERC20[`ERC20` token] to define a 'minter' role, which allows accounts that have it create new tokens:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
// contracts/MyToken.sol
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract MyToken is ERC20, AccessControl {
|
||||
// Create a new role identifier for the minter role
|
||||
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
|
||||
|
||||
constructor(address minter) ERC20("MyToken", "TKN") {
|
||||
// Grant the minter role to a specified account
|
||||
_setupRole(MINTER_ROLE, minter);
|
||||
}
|
||||
|
||||
function mint(address to, uint256 amount) public {
|
||||
// Check that the calling account has the minter role
|
||||
require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter");
|
||||
_mint(to, amount);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
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 ERC20 token example by also defining a 'burner' role, which lets accounts destroy tokens, and by using the `onlyRole` modifier:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
// contracts/MyToken.sol
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract MyToken is ERC20, AccessControl {
|
||||
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
|
||||
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
|
||||
|
||||
constructor(address minter, address burner) ERC20("MyToken", "TKN") {
|
||||
_setupRole(MINTER_ROLE, minter);
|
||||
_setupRole(BURNER_ROLE, burner);
|
||||
}
|
||||
|
||||
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
|
||||
_mint(to, amount);
|
||||
}
|
||||
|
||||
function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) {
|
||||
_burn(from, amount);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
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 ERC20 token example above uses `_setupRole`, 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.
|
||||
|
||||
Let's take a look at the ERC20 token example, this time taking advantage of the default admin role:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
// contracts/MyToken.sol
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract MyToken is ERC20, AccessControl {
|
||||
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
|
||||
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
|
||||
|
||||
constructor() ERC20("MyToken", "TKN") {
|
||||
// Grant the contract deployer the default admin role: it will be able
|
||||
// to grant and revoke any roles
|
||||
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
|
||||
}
|
||||
|
||||
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
|
||||
_mint(to, amount);
|
||||
}
|
||||
|
||||
function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) {
|
||||
_burn(from, amount);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
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 to prove 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.
|
||||
11
docs/modules/ROOT/pages/crowdsales.adoc
Normal file
11
docs/modules/ROOT/pages/crowdsales.adoc
Normal file
@ -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.
|
||||
19
docs/modules/ROOT/pages/drafts.adoc
Normal file
19
docs/modules/ROOT/pages/drafts.adoc
Normal file
@ -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.
|
||||
151
docs/modules/ROOT/pages/erc1155.adoc
Normal file
151
docs/modules/ROOT/pages/erc1155.adoc
Normal file
@ -0,0 +1,151 @@
|
||||
= ERC1155
|
||||
|
||||
ERC1155 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: ERC1155 draws ideas from all of xref:erc20.adoc[ERC20], xref:erc721.adoc[ERC721], and xref:erc777.adoc[ERC777]. If you're unfamiliar with those standards, head to their guides before moving on.
|
||||
|
||||
[[multi-token-standard]]
|
||||
== Multi Token Standard
|
||||
|
||||
The distinctive feature of ERC1155 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 ERC20's and ERC777'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 ERC721 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 ERC721 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 ERC1155 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 ERC1155 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 ERC1155 Token Contract
|
||||
|
||||
We'll use ERC1155 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 ERC20 Supply].
|
||||
|
||||
Here's what a contract for tokenized items might look like:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
// contracts/GameItems.sol
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
|
||||
|
||||
contract GameItems is ERC1155 {
|
||||
uint256 public constant GOLD = 0;
|
||||
uint256 public constant SILVER = 1;
|
||||
uint256 public constant THORS_HAMMER = 2;
|
||||
uint256 public constant SWORD = 3;
|
||||
uint256 public constant SHIELD = 4;
|
||||
|
||||
constructor() ERC1155("https://game.example/api/item/{id}.json") {
|
||||
_mint(msg.sender, GOLD, 10**18, "");
|
||||
_mint(msg.sender, SILVER, 10**27, "");
|
||||
_mint(msg.sender, THORS_HAMMER, 1, "");
|
||||
_mint(msg.sender, SWORD, 10**9, "");
|
||||
_mint(msg.sender, SHIELD, 10**9, "");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
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 ERC20, ERC1155 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 ERC721 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 message:
|
||||
|
||||
[source,text]
|
||||
----
|
||||
ERC1155: transfer to non ERC1155Receiver implementer
|
||||
----
|
||||
|
||||
This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC1155 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 ERC1155 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]
|
||||
----
|
||||
// contracts/MyContract.sol
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC1155/ERC1155Holder.sol";
|
||||
|
||||
contract MyContract is ERC1155Holder {
|
||||
}
|
||||
----
|
||||
|
||||
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.
|
||||
|
||||
[[Presets]]
|
||||
== Preset ERC1155 contract
|
||||
A preset ERC1155 is available, xref:api:presets#ERC1155PresetMinterPauser[`ERC1155PresetMinterPauser`]. It is preset to allow for token minting (create) - including batch minting, stop all token transfers (pause) and allow holders to burn (destroy) their tokens. The contract uses xref:access-control.adoc[Access Control] to control access to the minting and pausing functionality. The account that deploys the contract will be granted the minter and pauser roles, as well as the default admin role.
|
||||
|
||||
This contract is ready to deploy without having to write any Solidity code. It can be used as-is for quick prototyping and testing, but is also suitable for production environments.
|
||||
113
docs/modules/ROOT/pages/erc20-supply.adoc
Normal file
113
docs/modules/ROOT/pages/erc20-supply.adoc
Normal file
@ -0,0 +1,113 @@
|
||||
= Creating ERC20 Supply
|
||||
|
||||
In this guide you will learn how to create an ERC20 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 ERC20, 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 ERC20 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 ERC20 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.
|
||||
|
||||
[[modularizing-the-mechanism]]
|
||||
== Modularizing the Mechanism
|
||||
|
||||
There is one supply mechanism already included in Contracts: `ERC20PresetMinterPauser`. This is a generic mechanism in which a set of accounts is assigned the `minter` role, granting them the permission to call a `mint` function, an external version of `_mint`.
|
||||
|
||||
This can be used for centralized minting, where an externally owned account (i.e. someone with a pair of cryptographic keys) decides how much supply to create and for whom. There are very legitimate use cases for this mechanism, such as https://medium.com/reserve-currency/why-another-stablecoin-866f774afede#3aea[traditional asset-backed stablecoins].
|
||||
|
||||
The accounts with the minter role don't need to be externally owned, though, and can just as well be smart contracts that implement a trustless mechanism. We can in fact implement the same behavior as the previous section.
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
contract MinerRewardMinter {
|
||||
ERC20PresetMinterPauser _token;
|
||||
|
||||
constructor(ERC20PresetMinterPauser token) {
|
||||
_token = token;
|
||||
}
|
||||
|
||||
function mintMinerReward() public {
|
||||
_token.mint(block.coinbase, 1000);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
This contract, when initialized with an `ERC20PresetMinterPauser` instance, and granted the `minter` role for that contract, will result in exactly the same behavior implemented in the previous section. What is interesting about using `ERC20PresetMinterPauser` is that we can easily combine multiple supply mechanisms by assigning the role to multiple contracts, and moreover that we can do this dynamically.
|
||||
|
||||
TIP: To learn more about roles and permissioned systems, head to our xref:access-control.adoc[Access Control guide].
|
||||
|
||||
[[automating-the-reward]]
|
||||
== Automating the Reward
|
||||
|
||||
So far our supply mechanisms were triggered manually, but `ERC20` also allows us to extend the core functionality of the token through the xref:api:token/ERC20.adoc#ERC20-_beforeTokenTransfer-address-address-uint256-[`_beforeTokenTransfer`] hook (see xref:extending-contracts.adoc#using-hooks[Using Hooks]).
|
||||
|
||||
Adding to the supply mechanism from previous sections, we can use this hook to mint a miner reward for every token transfer that is included in the blockchain.
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
contract ERC20WithAutoMinerReward is ERC20 {
|
||||
constructor() ERC20("Reward", "RWD") {}
|
||||
|
||||
function _mintMinerReward() internal {
|
||||
_mint(block.coinbase, 1000);
|
||||
}
|
||||
|
||||
function _beforeTokenTransfer(address from, address to, uint256 value) internal virtual override {
|
||||
if (!(from == address(0) && to == block.coinbase)) {
|
||||
_mintMinerReward();
|
||||
}
|
||||
super._beforeTokenTransfer(from, to, value);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[wrapping-up]]
|
||||
== Wrapping Up
|
||||
|
||||
We've seen two ways to implement ERC20 supply mechanisms: internally through `_mint`, and externally through `ERC20PresetMinterPauser`. 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.
|
||||
83
docs/modules/ROOT/pages/erc20.adoc
Normal file
83
docs/modules/ROOT/pages/erc20.adoc
Normal file
@ -0,0 +1,83 @@
|
||||
= ERC20
|
||||
|
||||
An ERC20 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 ERC20 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 ERC20 Token Contract
|
||||
|
||||
Using Contracts, we can easily create our own ERC20 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]
|
||||
----
|
||||
// contracts/GLDToken.sol
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract GLDToken is ERC20 {
|
||||
constructor(uint256 initialSupply) ERC20("Gold", "GLD") {
|
||||
_mint(msg.sender, initialSupply);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
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 ERC20 supply mechanisms, see xref:erc20-supply.adoc[Creating ERC20 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 ERC20 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);
|
||||
```
|
||||
|
||||
[[Presets]]
|
||||
== Preset ERC20 contract
|
||||
A preset ERC20 is available, xref:api:presets#ERC20PresetMinterPauser[`ERC20PresetMinterPauser`]. It is preset to allow for token minting (create), stop all token transfers (pause) and allow holders to burn (destroy) their tokens. The contract uses xref:access-control.adoc[Access Control] to control access to the minting and pausing functionality. The account that deploys the contract will be granted the minter and pauser roles, as well as the default admin role.
|
||||
|
||||
This contract is ready to deploy without having to write any Solidity code. It can be used as-is for quick prototyping and testing, but is also suitable for production environments.
|
||||
90
docs/modules/ROOT/pages/erc721.adoc
Normal file
90
docs/modules/ROOT/pages/erc721.adoc
Normal file
@ -0,0 +1,90 @@
|
||||
= ERC721
|
||||
|
||||
We've discussed how you can make a _fungible_ token using xref:erc20.adoc[ERC20], 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. ERC721 is a standard for representing ownership of xref:tokens.adoc#different-kinds-of-tokens[_non-fungible_ tokens], that is, where each token is unique.
|
||||
|
||||
ERC721 is a more complex standard than ERC20, 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 ERC721 Token Contract
|
||||
|
||||
We'll use ERC721 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]
|
||||
----
|
||||
// contracts/GameItem.sol
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
||||
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
|
||||
import "@openzeppelin/contracts/utils/Counters.sol";
|
||||
|
||||
contract GameItem is ERC721, ERC721URIStorage {
|
||||
using Counters for Counters.Counter;
|
||||
Counters.Counter private _tokenIds;
|
||||
|
||||
constructor() ERC721("GameItem", "ITM") {}
|
||||
|
||||
function awardItem(address player, string memory tokenURI)
|
||||
public
|
||||
returns (uint256)
|
||||
{
|
||||
_tokenIds.increment();
|
||||
|
||||
uint256 newItemId = _tokenIds.current();
|
||||
_mint(player, newItemId);
|
||||
_setTokenURI(newItemId, tokenURI);
|
||||
|
||||
return newItemId;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The xref:api:token/ERC721.adoc#ERC721URIStorage[`ERC721URIStorage`] contract is an implementation of ERC721 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 ERC20, ERC721 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[ERC721 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 ERC721 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.
|
||||
|
||||
[[Presets]]
|
||||
== Preset ERC721 contract
|
||||
A preset ERC721 is available, xref:api:presets#ERC721PresetMinterPauserAutoId[`ERC721PresetMinterPauserAutoId`]. It is preset to allow for token minting (create) with token ID and URI auto generation, stop all token transfers (pause) and allow holders to burn (destroy) their tokens. The contract uses xref:access-control.adoc[Access Control] to control access to the minting and pausing functionality. The account that deploys the contract will be granted the minter and pauser roles, as well as the default admin role.
|
||||
|
||||
This contract is ready to deploy without having to write any Solidity code. It can be used as-is for quick prototyping and testing, but is also suitable for production environments.
|
||||
73
docs/modules/ROOT/pages/erc777.adoc
Normal file
73
docs/modules/ROOT/pages/erc777.adoc
Normal file
@ -0,0 +1,73 @@
|
||||
= ERC777
|
||||
|
||||
Like xref:erc20.adoc[ERC20], ERC777 is a standard for xref:tokens.adoc#different-kinds-of-tokens[_fungible_ tokens], and is focused around allowing more complex interactions when trading tokens. More generally, it brings tokens and Ether closer together by providing the equivalent of a `msg.value` field, but for tokens.
|
||||
|
||||
The standard also brings multiple quality-of-life improvements, such as getting rid of the confusion around `decimals`, minting and burning with proper events, among others, but its killer feature is *receive hooks*. A hook is simply a function in a contract that is called when tokens are sent to it, meaning *accounts and contracts can react to receiving tokens*.
|
||||
|
||||
This enables a lot of interesting use cases, including atomic purchases using tokens (no need to do `approve` and `transferFrom` in two separate transactions), rejecting reception of tokens (by reverting on the hook call), redirecting the received tokens to other addresses (similarly to how xref:api:payment#PaymentSplitter[`PaymentSplitter`] does it), among many others.
|
||||
|
||||
Furthermore, since contracts are required to implement these hooks in order to receive tokens, _no tokens can get stuck in a contract that is unaware of the ERC777 protocol_, as has happened countless times when using ERC20s.
|
||||
|
||||
== What If I Already Use ERC20?
|
||||
|
||||
The standard has you covered! The ERC777 standard is *backwards compatible with ERC20*, meaning you can interact with these tokens as if they were ERC20, using the standard functions, while still getting all of the niceties, including send hooks. See the https://eips.ethereum.org/EIPS/eip-777#backward-compatibility[EIP's Backwards Compatibility section] to learn more.
|
||||
|
||||
== Constructing an ERC777 Token Contract
|
||||
|
||||
We will replicate the `GLD` example of the xref:erc20.adoc#constructing-an-erc20-token-contract[ERC20 guide], this time using ERC777. As always, check out the xref:api:token/ERC777.adoc[`API reference`] to learn more about the details of each function.
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
// contracts/GLDToken.sol
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC777/ERC777.sol";
|
||||
|
||||
contract GLDToken is ERC777 {
|
||||
constructor(uint256 initialSupply, address[] memory defaultOperators)
|
||||
ERC777("Gold", "GLD", defaultOperators)
|
||||
{
|
||||
_mint(msg.sender, initialSupply, "", "");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
In this case, we'll be extending from the xref:api:token/ERC777.adoc#ERC777[`ERC777`] contract, which provides an implementation with compatibility support for ERC20. The API is quite similar to that of xref:api:token/ERC777.adoc#ERC777[`ERC777`], and we'll once again make use of xref:api:token/ERC777.adoc#ERC777-_mint-address-address-uint256-bytes-bytes-[`_mint`] to assign the `initialSupply` to the deployer account. Unlike xref:api:token/ERC20.adoc#ERC20-_mint-address-uint256-[ERC20's `_mint`], this one includes some extra parameters, but you can safely ignore those for now.
|
||||
|
||||
You'll notice both xref:api:token/ERC777.adoc#IERC777-name--[`name`] and xref:api:token/ERC777.adoc#IERC777-symbol--[`symbol`] are assigned, but not xref:api:token/ERC777.adoc#ERC777-decimals--[`decimals`]. The ERC777 specification makes it mandatory to include support for these functions (unlike ERC20, where it is optional and we had to include xref:api:token/ERC20.adoc#ERC20Detailed[`ERC20Detailed`]), but also mandates that `decimals` always returns a fixed value of `18`, so there's no need to set it ourselves. For a review of ``decimals``'s role and importance, refer back to our xref:erc20.adoc#a-note-on-decimals[ERC20 guide].
|
||||
|
||||
Finally, we'll need to set the xref:api:token/ERC777.adoc#IERC777-defaultOperators--[`defaultOperators`]: special accounts (usually other smart contracts) that will be able to transfer tokens on behalf of their holders. If you're not planning on using operators in your token, you can simply pass an empty array. _Stay tuned for an upcoming in-depth guide on ERC777 operators!_
|
||||
|
||||
That's it for a basic token contract! We can now deploy it, and use the same xref:api:token/ERC777.adoc#IERC777-balanceOf-address-[`balanceOf`] method to query the deployer's balance:
|
||||
|
||||
[source,javascript]
|
||||
----
|
||||
> GLDToken.balanceOf(deployerAddress)
|
||||
1000
|
||||
----
|
||||
|
||||
To move tokens from one account to another, we can use both xref:api:token/ERC777.adoc#ERC777-transfer-address-uint256-[``ERC20``'s `transfer`] method, or the new xref:api:token/ERC777.adoc#ERC777-send-address-uint256-bytes-[``ERC777``'s `send`], which fulfills a very similar role, but adds an optional `data` field:
|
||||
|
||||
[source,javascript]
|
||||
----
|
||||
> GLDToken.transfer(otherAddress, 300)
|
||||
> GLDToken.send(otherAddress, 300, "")
|
||||
> GLDToken.balanceOf(otherAddress)
|
||||
600
|
||||
> GLDToken.balanceOf(deployerAddress)
|
||||
400
|
||||
----
|
||||
|
||||
== Sending Tokens to Contracts
|
||||
|
||||
A key difference when using xref:api:token/ERC777.adoc#ERC777-send-address-uint256-bytes-[`send`] is that token transfers to other contracts may revert with the following message:
|
||||
|
||||
[source,text]
|
||||
----
|
||||
ERC777: token recipient contract has no implementer for ERC777TokensRecipient
|
||||
----
|
||||
|
||||
This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC777 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.
|
||||
|
||||
_An upcoming guide will cover how a contract can register itself as a recipient, send and receive hooks, and other advanced features of ERC777!_
|
||||
131
docs/modules/ROOT/pages/extending-contracts.adoc
Normal file
131
docs/modules/ROOT/pages/extending-contracts.adoc
Normal file
@ -0,0 +1,131 @@
|
||||
= 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
|
||||
// contracts/ModifiedAccessControl.sol
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
|
||||
contract ModifiedAccessControl is AccessControl {
|
||||
// Override the revokeRole function
|
||||
function revokeRole(bytes32, address) public override {
|
||||
revert("ModifiedAccessControl: cannot revoke roles");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
// contracts/ModifiedAccessControl.sol
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
|
||||
contract ModifiedAccessControl is AccessControl {
|
||||
function revokeRole(bytes32 role, address account) public override {
|
||||
require(
|
||||
role != DEFAULT_ADMIN_ROLE,
|
||||
"ModifiedAccessControl: cannot revoke default admin role"
|
||||
);
|
||||
|
||||
super.revokeRole(role, account);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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: As of v3.0.0, `view` functions are not `virtual` in OpenZeppelin, and therefore cannot be overridden. We're considering https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2154[lifting this restriction] in an upcoming release. Let us know if this is something you care about!
|
||||
|
||||
[[using-hooks]]
|
||||
== Using Hooks
|
||||
|
||||
Sometimes, in order to extend a parent contract you will need to override multiple related functions, which leads to code duplication and increased likelihood of bugs.
|
||||
|
||||
For example, consider implementing safe xref:api:token/ERC20.adoc#ERC20[`ERC20`] transfers in the style of xref:api:token/ERC721.adoc#IERC721Receiver[`IERC721Receiver`]. You may think overriding xref:api:token/ERC20.adoc#ERC20-transfer-address-uint256-[`transfer`] and xref:api:token/ERC20.adoc#ERC20-transferFrom-address-address-uint256-[`transferFrom`] would be enough, but what about xref:api:token/ERC20.adoc#ERC20-_transfer-address-address-uint256-[`_transfer`] and xref:api:token/ERC20.adoc#ERC20-_mint-address-uint256-[`_mint`]? To prevent you from having to deal with these details, we introduced **hooks**.
|
||||
|
||||
Hooks are simply functions that are called before or after some action takes place. They provide a centralized point to _hook into_ and extend the original behavior.
|
||||
|
||||
Here's how you would implement the `IERC721Receiver` pattern in `ERC20`, using the xref:api:token/ERC20.adoc#ERC20-_beforeTokenTransfer-address-address-uint256-[`_beforeTokenTransfer`] hook:
|
||||
|
||||
```solidity
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract ERC20WithSafeTransfer is ERC20 {
|
||||
function _beforeTokenTransfer(address from, address to, uint256 amount)
|
||||
internal virtual override
|
||||
{
|
||||
super._beforeTokenTransfer(from, to, amount);
|
||||
|
||||
require(_validRecipient(to), "ERC20WithSafeTransfer: invalid recipient");
|
||||
}
|
||||
|
||||
function _validRecipient(address to) private view returns (bool) {
|
||||
...
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Using hooks this way leads to cleaner and safer code, without having to rely on a deep understanding of the parent's internals.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Hooks are a new feature of OpenZeppelin Contracts v3.0.0, and we're eager to learn how you plan to use them!
|
||||
|
||||
So far, the only available hook is `_beforeTransferHook`, in all of xref:api:token/ERC20.adoc#ERC20-_beforeTokenTransfer-address-address-uint256-[`ERC20`], xref:api:token/ERC721.adoc#ERC721-_beforeTokenTransfer-address-address-uint256-[`ERC721`], xref:api:token/ERC777.adoc#ERC777-_beforeTokenTransfer-address-address-address-uint256-[`ERC777`] and xref:api:token/ERC1155.adoc#ERC1155-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-[`ERC1155`]. If you have ideas for new hooks, let us know!
|
||||
====
|
||||
|
||||
=== Rules of Hooks
|
||||
|
||||
There's a few guidelines you should follow when writing code that uses hooks in order to prevent issues. They are very simple, but do make sure you follow them:
|
||||
|
||||
1. Whenever you override a parent's hook, re-apply the `virtual` attribute to the hook. That will allow child contracts to add more functionality to the hook.
|
||||
2. **Always** call the parent's hook in your override using `super`. This will make sure all hooks in the inheritance tree are called: contracts like xref:api:token/ERC20.adoc#ERC20Pausable[`ERC20Pausable`] rely on this behavior.
|
||||
|
||||
```solidity
|
||||
contract MyToken is ERC20 {
|
||||
function _beforeTokenTransfer(address from, address to, uint256 amount)
|
||||
internal virtual override // Add virtual here!
|
||||
{
|
||||
super._beforeTokenTransfer(from, to, amount); // Call parent hook
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
That's it! Enjoy simpler code using hooks!
|
||||
|
||||
337
docs/modules/ROOT/pages/governance.adoc
Normal file
337
docs/modules/ROOT/pages/governance.adoc
Normal file
@ -0,0 +1,337 @@
|
||||
= 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 ERC20 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 & GovernorCompatibilityBravo
|
||||
|
||||
An OpenZeppelin Governor contract is by default not interface-compatible with GovernorAlpha or Bravo, since some of the functions are different or missing, although it shares all of the same events. However, it’s possible to opt in to full compatibility by inheriting from the GovernorCompatibilityBravo module. The contract will be cheaper to deploy and use without this module.
|
||||
|
||||
=== 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.withtally.com[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, visualize voting power and advocates, navigate proposals, and cast votes. For proposal creation in particular, projects can also use Defender Admin 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 ERC20 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
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.2;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
|
||||
|
||||
contract MyToken is ERC20, ERC20Permit, ERC20Votes {
|
||||
constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}
|
||||
|
||||
// The functions below are overrides required by Solidity.
|
||||
|
||||
function _afterTokenTransfer(address from, address to, uint256 amount)
|
||||
internal
|
||||
override(ERC20, ERC20Votes)
|
||||
{
|
||||
super._afterTokenTransfer(from, to, amount);
|
||||
}
|
||||
|
||||
function _mint(address to, uint256 amount)
|
||||
internal
|
||||
override(ERC20, ERC20Votes)
|
||||
{
|
||||
super._mint(to, amount);
|
||||
}
|
||||
|
||||
function _burn(address account, uint256 amount)
|
||||
internal
|
||||
override(ERC20, ERC20Votes)
|
||||
{
|
||||
super._burn(account, amount);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.2;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol";
|
||||
|
||||
contract MyToken is ERC20, ERC20Permit, ERC20Votes, ERC20Wrapper {
|
||||
constructor(IERC20 wrappedToken)
|
||||
ERC20("MyToken", "MTK")
|
||||
ERC20Permit("MyToken")
|
||||
ERC20Wrapper(wrappedToken)
|
||||
{}
|
||||
|
||||
// The functions below are overrides required by Solidity.
|
||||
|
||||
function _afterTokenTransfer(address from, address to, uint256 amount)
|
||||
internal
|
||||
override(ERC20, ERC20Votes)
|
||||
{
|
||||
super._afterTokenTransfer(from, to, amount);
|
||||
}
|
||||
|
||||
function _mint(address to, uint256 amount)
|
||||
internal
|
||||
override(ERC20, ERC20Votes)
|
||||
{
|
||||
super._mint(to, amount);
|
||||
}
|
||||
|
||||
function _burn(address account, uint256 amount)
|
||||
internal
|
||||
override(ERC20, ERC20Votes)
|
||||
{
|
||||
super._burn(account, amount);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
NOTE: Voting power could be determined in different ways: multiple ERC20 tokens, ERC721 tokens, sybil resistant identities, etc. All of these options are potentially supported by writing a custom Votes module for your Governor. The only other source of voting power available in OpenZeppelin Contracts currently is xref:api:token/ERC721.adoc#ERC721Votes[`ERC721Votes`].
|
||||
|
||||
=== 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.
|
||||
|
||||
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 number of blocks. Assuming block time of around 13.14 seconds, we will set votingDelay = 1 day = 6570 blocks, and votingPeriod = 1 week = 45992 blocks.
|
||||
|
||||
We can optionally set a proposal threshold as well. This restricts proposal creation to accounts who have enough voting power.
|
||||
|
||||
```solidity
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.2;
|
||||
|
||||
import "./governance/Governor.sol";
|
||||
import "./governance/compatibility/GovernorCompatibilityBravo.sol";
|
||||
import "./governance/extensions/GovernorVotes.sol";
|
||||
import "./governance/extensions/GovernorVotesQuorumFraction.sol";
|
||||
import "./governance/extensions/GovernorTimelockControl.sol";
|
||||
|
||||
contract MyGovernor is Governor, GovernorCompatibilityBravo, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl {
|
||||
constructor(IVotes _token, TimelockController _timelock)
|
||||
Governor("MyGovernor")
|
||||
GovernorVotes(_token)
|
||||
GovernorVotesQuorumFraction(4)
|
||||
GovernorTimelockControl(_timelock)
|
||||
{}
|
||||
|
||||
function votingDelay() public pure override returns (uint256) {
|
||||
return 6575; // 1 day
|
||||
}
|
||||
|
||||
function votingPeriod() public pure override returns (uint256) {
|
||||
return 46027; // 1 week
|
||||
}
|
||||
|
||||
function proposalThreshold() public pure override returns (uint256) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The functions below are overrides required by Solidity.
|
||||
|
||||
function quorum(uint256 blockNumber)
|
||||
public
|
||||
view
|
||||
override(IGovernor, GovernorVotesQuorumFraction)
|
||||
returns (uint256)
|
||||
{
|
||||
return super.quorum(blockNumber);
|
||||
}
|
||||
|
||||
function getVotes(address account, uint256 blockNumber)
|
||||
public
|
||||
view
|
||||
override(IGovernor, GovernorVotes)
|
||||
returns (uint256)
|
||||
{
|
||||
return super.getVotes(account, blockNumber);
|
||||
}
|
||||
|
||||
function state(uint256 proposalId)
|
||||
public
|
||||
view
|
||||
override(Governor, IGovernor, GovernorTimelockControl)
|
||||
returns (ProposalState)
|
||||
{
|
||||
return super.state(proposalId);
|
||||
}
|
||||
|
||||
function propose(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, string memory description)
|
||||
public
|
||||
override(Governor, GovernorCompatibilityBravo, IGovernor)
|
||||
returns (uint256)
|
||||
{
|
||||
return super.propose(targets, values, calldatas, description);
|
||||
}
|
||||
|
||||
function _execute(uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash)
|
||||
internal
|
||||
override(Governor, GovernorTimelockControl)
|
||||
{
|
||||
super._execute(proposalId, targets, values, calldatas, descriptionHash);
|
||||
}
|
||||
|
||||
function _cancel(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash)
|
||||
internal
|
||||
override(Governor, GovernorTimelockControl)
|
||||
returns (uint256)
|
||||
{
|
||||
return super._cancel(targets, values, calldatas, descriptionHash);
|
||||
}
|
||||
|
||||
function _executor()
|
||||
internal
|
||||
view
|
||||
override(Governor, GovernorTimelockControl)
|
||||
returns (address)
|
||||
{
|
||||
return super._executor();
|
||||
}
|
||||
|
||||
function supportsInterface(bytes4 interfaceId)
|
||||
public
|
||||
view
|
||||
override(Governor, IERC165, GovernorTimelockControl)
|
||||
returns (bool)
|
||||
{
|
||||
return super.supportsInterface(interfaceId);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
=== 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. Funds in the Governor contract are not currently retrievable when using a timelock! (As of version 4.3 there is a caveat when using the Compound Timelock: ETH in the timelock is not easily usable, so it is recommended to manage ERC20 funds only in this combination until a future version resolves the issue.)
|
||||
|
||||
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 both deployer and timelock itself, but should be renounced by the deployer after setup.
|
||||
|
||||
== 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 ERC20 tokens from the governance treasury. This proposal will consist of a single action where the target is the ERC20 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 Defender. 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, stakeholders can cast their vote. This is done through a function in the Governor contract that users can invoke directly 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. This can also be done in Tally in the "Administration Panel" section of a project.
|
||||
|
||||
image::tally-admin.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 ERC20 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.
|
||||
63
docs/modules/ROOT/pages/index.adoc
Normal file
63
docs/modules/ROOT/pages/index.adoc
Normal file
@ -0,0 +1,63 @@
|
||||
= 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.
|
||||
|
||||
== Overview
|
||||
|
||||
[[install]]
|
||||
=== Installation
|
||||
|
||||
```console
|
||||
$ npm install @openzeppelin/contracts
|
||||
```
|
||||
|
||||
OpenZeppelin Contracts features a xref:releases-stability.adoc#api-stability[stable API], which means your contracts won't break unexpectedly when upgrading to a newer minor version.
|
||||
|
||||
[[usage]]
|
||||
=== Usage
|
||||
|
||||
Once installed, you can use the contracts in the library by importing them:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
// contracts/MyNFT.sol
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
||||
|
||||
contract MyNFT is ERC721 {
|
||||
constructor() ERC721("MyNFT", "MNFT") {
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
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.
|
||||
|
||||
[[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.
|
||||
85
docs/modules/ROOT/pages/releases-stability.adoc
Normal file
85
docs/modules/ROOT/pages/releases-stability.adoc
Normal file
@ -0,0 +1,85 @@
|
||||
= New Releases and API Stability
|
||||
|
||||
Developing smart contracts is hard, and a conservative approach towards dependencies is sometimes favored. However, it is also very important to stay on top of new releases: these may include bug fixes, or deprecate old patterns in favor of newer and better practices.
|
||||
|
||||
Here we describe when you should expect new releases to come out, and how this affects you as a user of OpenZeppelin Contracts.
|
||||
|
||||
[[release-schedule]]
|
||||
== Release Schedule
|
||||
|
||||
OpenZeppelin Contracts follows a <<versioning-scheme, semantic versioning scheme>>.
|
||||
|
||||
We aim for a new minor release every 1 or 2 months.
|
||||
|
||||
[[minor-releases]]
|
||||
=== Release Candidates
|
||||
|
||||
Before every release, we publish a feature-frozen release candidate. The purpose of the release candidate is to have a period where the community can review the new code before the actual release. If important problems are discovered, several more release candidates may be required. After a week of no more changes to the release candidate, the new version is published.
|
||||
|
||||
[[major-releases]]
|
||||
=== Major Releases
|
||||
|
||||
After several months or a year a new major release may come out. These are not scheduled, but will be based on the need to release breaking changes such as a redesign of a core feature of the library (e.g. https://github.com/OpenZeppelin/openzeppelin-contracts/pulls/2112[access control] in 3.0). Since we value stability, we aim for these to happen infrequently (expect no less than six months between majors). However, we may be forced to release one when there are big changes to the Solidity language.
|
||||
|
||||
[[api-stability]]
|
||||
== API Stability
|
||||
|
||||
On the https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.0.0[OpenZeppelin Contracts 2.0 release], we committed ourselves to keeping a stable API. We aim to more precisely define what we understand by _stable_ and _API_ here, so users of the library can understand these guarantees and be confident their project won't break unexpectedly.
|
||||
|
||||
In a nutshell, the API being stable means _if your project is working today, it will continue to do so after a minor upgrade_. New contracts and features will be added in minor releases, but only in a backwards compatible way.
|
||||
|
||||
[[versioning-scheme]]
|
||||
=== Versioning Scheme
|
||||
|
||||
We follow https://semver.org/[SemVer], which means API breakage may occur between major releases (which <<release-schedule, don't happen very often>>).
|
||||
|
||||
[[solidity-functions]]
|
||||
=== Solidity Functions
|
||||
|
||||
While the internal implementation of functions may change, their semantics and signature will remain the same. The domain of their arguments will not be less restrictive (e.g. if transferring a value of 0 is disallowed, it will remain disallowed), nor will general state restrictions be lifted (e.g. `whenPaused` modifiers).
|
||||
|
||||
If new functions are added to a contract, it will be in a backwards-compatible way: their usage won't be mandatory, and they won't extend functionality in ways that may foreseeable break an application (e.g. https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1512[an `internal` method may be added to make it easier to retrieve information that was already available]).
|
||||
|
||||
[[internal]]
|
||||
==== `internal`
|
||||
|
||||
This extends not only to `external` and `public` functions, but also `internal` ones: many contracts are meant to be used by inheriting them (e.g. `Pausable`, `PullPayment`, `AccessControl`), and are therefore used by calling these functions. Similarly, since all OpenZeppelin Contracts state variables are `private`, they can only be accessed this way (e.g. to create new `ERC20` tokens, instead of manually modifying `totalSupply` and `balances`, `_mint` should be called).
|
||||
|
||||
`private` functions have no guarantees on their behavior, usage, or existence.
|
||||
|
||||
Finally, sometimes language limitations will force us to e.g. make `internal` a function that should be `private` in order to implement features the way we want to. These cases will be well documented, and the normal stability guarantees won't apply.
|
||||
|
||||
[[libraries]]
|
||||
=== Libraries
|
||||
|
||||
Some of our Solidity libraries use ``struct``s to handle internal data that should not be accessed directly (e.g. `Counter`). There's an https://github.com/ethereum/solidity/issues/4637[open issue] in the Solidity repository requesting a language feature to prevent said access, but it looks like it won't be implemented any time soon. Because of this, we will use leading underscores and mark said `struct` s to make it clear to the user that its contents and layout are _not_ part of the API.
|
||||
|
||||
[[events]]
|
||||
=== Events
|
||||
|
||||
No events will be removed, and their arguments won't be changed in any way. New events may be added in later versions, and existing events may be emitted under new, reasonable circumstances (e.g. https://github.com/OpenZeppelin/openzeppelin-contracts/issues/707[from 2.1 on, `ERC20` also emits `Approval` on `transferFrom` calls]).
|
||||
|
||||
[[drafts]]
|
||||
=== Drafts
|
||||
|
||||
Some contracts implement EIPs that are still in Draft status, recognizable by a file name beginning with `draft-`, such as `utils/cryptography/draft-EIP712.sol`. Due to their nature as drafts, the details of these contracts may change and we cannot guarantee their stability. Minor releases of OpenZeppelin Contracts may contain breaking changes for the contracts labelled as Drafts, which will be duly announced in the https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md[changelog]. The EIPs included are used by projects in production and this may make them less likely to change significantly.
|
||||
|
||||
[[gas-costs]]
|
||||
=== Gas Costs
|
||||
|
||||
While attempts will generally be made to lower the gas costs of working with OpenZeppelin Contracts, there are no guarantees regarding this. In particular, users should not assume gas costs will not increase when upgrading library versions.
|
||||
|
||||
[[bugfixes]]
|
||||
=== Bug Fixes
|
||||
|
||||
The API stability guarantees may need to be broken in order to fix a bug, and we will do so. This decision won't be made lightly however, and all options will be explored to make the change as non-disruptive as possible. When sufficient, contracts or functions which may result in unsafe behavior will be deprecated instead of removed (e.g. https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1543[#1543] and https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1550[#1550]).
|
||||
|
||||
[[solidity-compiler-version]]
|
||||
=== Solidity Compiler Version
|
||||
|
||||
Starting on version 0.5.0, the Solidity team switched to a faster release cycle, with minor releases every few weeks (v0.5.0 was released on November 2018, and v0.5.5 on March 2019), and major, breaking-change releases every couple of months (with v0.6.0 released on December 2019 and v0.7.0 on July 2020). Including the compiler version in OpenZeppelin Contract's stability guarantees would therefore force the library to either stick to old compilers, or release frequent major updates simply to keep up with newer Solidity releases.
|
||||
|
||||
Because of this, *the minimum required Solidity compiler version is not part of the stability guarantees*, and users may be required to upgrade their compiler when using newer versions of Contracts. Bug fixes will still be backported to past major releases so that all versions currently in use receive these updates.
|
||||
|
||||
You can read more about the rationale behind this, the other options we considered and why we went down this path https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1498#issuecomment-449191611[here].
|
||||
|
||||
32
docs/modules/ROOT/pages/tokens.adoc
Normal file
32
docs/modules/ROOT/pages/tokens.adoc
Normal file
@ -0,0 +1,32 @@
|
||||
= 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 ERC20 or ERC721 token standards, and that's why you're here. Head to our specialized guides to learn more about these:
|
||||
|
||||
* xref:erc20.adoc[ERC20]: the most widespread token standard for fungible assets, albeit somewhat limited by its simplicity.
|
||||
* xref:erc721.adoc[ERC721]: the de-facto solution for non-fungible tokens, often used for collectibles and games.
|
||||
* xref:erc777.adoc[ERC777]: a richer standard for fungible tokens, enabling new use cases and building on past learnings. Backwards compatible with ERC20.
|
||||
* xref:erc1155.adoc[ERC1155]: 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.
|
||||
73
docs/modules/ROOT/pages/upgradeable.adoc
Normal file
73
docs/modules/ROOT/pages/upgradeable.adoc
Normal file
@ -0,0 +1,73 @@
|
||||
= 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 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
|
||||
```
|
||||
|
||||
=== Usage
|
||||
|
||||
The package replicates the structure of the main OpenZeppelin Contracts package, but every file and contract has the suffix `Upgradeable`.
|
||||
|
||||
```diff
|
||||
-import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
||||
+import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
|
||||
|
||||
-contract MyCollectible is ERC721 {
|
||||
+contract MyCollectible is ERC721Upgradeable {
|
||||
```
|
||||
|
||||
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.deployed();
|
||||
console.log("MyCollectible deployed to:", mc.address);
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
=== Storage Gaps
|
||||
|
||||
You may notice that every contract includes a state variable named `+__gap+`. This is empty reserved space in storage that is put in place in Upgradeable contracts. It allows us to freely add new state variables in the future without compromising the storage compatibility with existing deployments.
|
||||
|
||||
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 size of the `+__gap+` array is calculated so that the amount of storage used by a contract always adds up to the same number (in this case 50 storage slots).
|
||||
186
docs/modules/ROOT/pages/utilities.adoc
Normal file
186
docs/modules/ROOT/pages/utilities.adoc
Normal file
@ -0,0 +1,186 @@
|
||||
= Utilities
|
||||
|
||||
The OpenZeppelin Contracts provide a ton of useful utilities that you can use in your project. Here are some of the more popular ones.
|
||||
|
||||
[[cryptography]]
|
||||
== Cryptography
|
||||
|
||||
=== Checking Signatures On-Chain
|
||||
|
||||
xref:api:cryptography.adoc#ECDSA[`ECDSA`] provides functions for recovering and managing Ethereum account ECDSA signatures. These are often generated via https://web3js.readthedocs.io/en/v1.2.4/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:cryptography.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:cryptography.adoc#ECDSA-toEthSignedMessageHash-bytes32-[`toEthSignedMessageHash`].
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
using ECDSA for bytes32;
|
||||
|
||||
function _verify(bytes32 data, bytes memory signature, address account) internal pure returns (bool) {
|
||||
return data
|
||||
.toEthSignedMessageHash()
|
||||
.recover(signature) == account;
|
||||
}
|
||||
----
|
||||
|
||||
WARNING: Getting signature verification right is not trivial: make sure you fully read and understand xref:api:cryptography.adoc#ECDSA[`ECDSA`]'s documentation.
|
||||
|
||||
=== Verifying Merkle Proofs
|
||||
|
||||
xref:api:cryptography.adoc#MerkleProof[`MerkleProof`] provides xref:api:cryptography.adoc#MerkleProof-verify-bytes32---bytes32-bytes32-[`verify`], which can prove that some value is part of a https://en.wikipedia.org/wiki/Merkle_tree[Merkle tree].
|
||||
|
||||
[[introspection]]
|
||||
== Introspection
|
||||
|
||||
In Solidity, it's frequently helpful to know whether or not a contract supports an interface you'd like to use. ERC165 is a standard that helps do runtime interface detection. Contracts provide helpers both for implementing ERC165 in your contracts and querying other contracts:
|
||||
|
||||
* xref:api:introspection.adoc#IERC165[`IERC165`] — this is the ERC165 interface that defines xref:api:introspection.adoc#IERC165-supportsInterface-bytes4-[`supportsInterface`]. When implementing ERC165, you'll conform to this interface.
|
||||
* xref:api:introspection.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:introspection.adoc#ERC165-_registerInterface-bytes4-[`_registerInterface(bytes4)`]: check out example usage as part of the ERC721 implementation.
|
||||
* xref:api:introspection.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:introspection.adoc#ERC165Checker-_supportsInterface-address-bytes4-[`myAddress._supportsInterface(bytes4)`]
|
||||
* xref:api:introspection.adoc#ERC165Checker-_supportsAllInterfaces-address-bytes4---[`myAddress._supportsAllInterfaces(bytes4[\])`]
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
contract MyContract {
|
||||
using ERC165Checker for address;
|
||||
|
||||
bytes4 private InterfaceId_ERC721 = 0x80ac58cd;
|
||||
|
||||
/**
|
||||
* @dev transfer an ERC721 token from this contract to someone else
|
||||
*/
|
||||
function transferERC721(
|
||||
address token,
|
||||
address to,
|
||||
uint256 tokenId
|
||||
)
|
||||
public
|
||||
{
|
||||
require(token.supportsInterface(InterfaceId_ERC721), "IS_NOT_721_TOKEN");
|
||||
IERC721(token).transferFrom(address(this), to, tokenId);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[math]]
|
||||
== Math
|
||||
|
||||
The most popular math related library OpenZeppelin Contracts provides is xref:api:math.adoc#SafeMath[`SafeMath`], which provides mathematical functions that protect your contract from overflows and underflows.
|
||||
|
||||
Include the contract with `using SafeMath for uint256;` and then call the functions:
|
||||
|
||||
* `myNumber.add(otherNumber)`
|
||||
* `myNumber.sub(otherNumber)`
|
||||
* `myNumber.div(otherNumber)`
|
||||
* `myNumber.mul(otherNumber)`
|
||||
* `myNumber.mod(otherNumber)`
|
||||
|
||||
Easy!
|
||||
|
||||
[[payment]]
|
||||
== Payment
|
||||
|
||||
Want to split some payments between multiple people? Maybe you have an app that sends 30% of art purchases to the original creator and 70% of the profits to the current owner; you can build that with xref:api:payment.adoc#PaymentSplitter[`PaymentSplitter`]!
|
||||
|
||||
In Solidity, there are some security concerns with blindly sending money to accounts, since it allows them to execute arbitrary code. You can read up on these security concerns in the https://consensys.github.io/smart-contract-best-practices/[Ethereum Smart Contract Best Practices] website. One of the ways to fix reentrancy and stalling problems is, instead of immediately sending Ether to accounts that need it, you can use xref:api:payment.adoc#PullPayment[`PullPayment`], which offers an xref:api:payment.adoc#PullPayment-_asyncTransfer-address-uint256-[`_asyncTransfer`] function for sending money to something and requesting that they xref:api:payment.adoc#PullPayment-withdrawPayments-address-payable-[`withdrawPayments()`] it later.
|
||||
|
||||
If you want to Escrow some funds, check out xref:api:payment.adoc#Escrow[`Escrow`] and xref:api:payment.adoc#ConditionalEscrow[`ConditionalEscrow`] for governing the release of some escrowed Ether.
|
||||
|
||||
[[collections]]
|
||||
== Collections
|
||||
|
||||
If you need support for more powerful collections than Solidity's native arrays and mappings, take a look at xref:api:utils.adoc#EnumerableSet[`EnumerableSet`] and xref:api:utils.adoc#EnumerableMap[`EnumerableMap`]. They are similar to mappings in that they store and remove elements in constant time and don't allow for repeated entries, but they also support _enumeration_, which means you can easily query all stored entries both on and off-chain.
|
||||
|
||||
[[misc]]
|
||||
== Misc
|
||||
|
||||
Want to check if an address is a contract? Use xref:api:utils.adoc#Address[`Address`] and xref:api:utils.adoc#Address-isContract-address-[`Address.isContract()`].
|
||||
|
||||
Want to keep track of some numbers that increment by 1 every time you want another one? Check out xref:api:utils.adoc#Counters[`Counters`]. This is useful for lots of things, like creating incremental identifiers, as shown on the xref:erc721.adoc[ERC721 guide].
|
||||
|
||||
=== Base64
|
||||
|
||||
xref:api:utils.adoc#Base64[`Base64`] util allows you to transform `bytes32` data into its Base64 `string` representation.
|
||||
|
||||
This is specially useful to build URL-safe tokenURIs for both xref:api:token/ERC721.adoc#IERC721Metadata-tokenURI-uint256-[`ERC721`] or xref:api:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[`ERC1155`]. This library provides a clever way to serve URL-safe https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs/[Data URI] compliant strings to serve on-chain data structures.
|
||||
|
||||
Consider this is an example to send JSON Metadata through a Base64 Data URI using an ERC721:
|
||||
|
||||
[source, solidity]
|
||||
----
|
||||
// contracts/My721Token.sol
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
||||
import "@openzeppelin/contracts/utils/Strings.sol";
|
||||
import "@openzeppelin/contracts/utils/Base64.sol";
|
||||
|
||||
contract My721Token is ERC721 {
|
||||
using Strings for uint256;
|
||||
|
||||
constructor() ERC721("My721Token", "MTK") {}
|
||||
|
||||
...
|
||||
|
||||
function tokenURI(uint256 tokenId)
|
||||
public
|
||||
pure
|
||||
override
|
||||
returns (string memory)
|
||||
{
|
||||
bytes memory dataURI = abi.encodePacked(
|
||||
'{',
|
||||
'"name": "My721Token #', tokenId.toString(), '"',
|
||||
// Replace with extra ERC721 Metadata properties
|
||||
'}'
|
||||
);
|
||||
|
||||
return string(
|
||||
abi.encodePacked(
|
||||
"data:application/json;base64,",
|
||||
Base64.encode(dataURI)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
=== Multicall
|
||||
|
||||
The `Multicall` abstract contract comes with a `multicall` function that bundles together multiple calls in a single external call. With it, external accounts may perform atomic operations comprising several function calls. This is not only useful for EOAs to make multiple calls in a single transaction, it's also a way to revert a previous call if a later one fails.
|
||||
|
||||
Consider this dummy contract:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
// contracts/Box.sol
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/utils/Multicall.sol";
|
||||
|
||||
contract Box is Multicall {
|
||||
function foo() public {
|
||||
...
|
||||
}
|
||||
|
||||
function bar() public {
|
||||
...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
This is how to call the `multicall` function using Truffle, allowing `foo` and `bar` to be called in a single transaction:
|
||||
[source,javascript]
|
||||
----
|
||||
// scripts/foobar.js
|
||||
|
||||
const Box = artifacts.require('Box');
|
||||
const instance = await Box.new();
|
||||
|
||||
await instance.multicall([
|
||||
instance.contract.methods.foo().encodeABI(),
|
||||
instance.contract.methods.bar().encodeABI()
|
||||
]);
|
||||
----
|
||||
15
docs/modules/ROOT/pages/wizard.adoc
Normal file
15
docs/modules/ROOT/pages/wizard.adoc
Normal file
@ -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` directory in order to compile it with a tool like Hardhat or Truffle. 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