diff --git a/contracts/mocks/docs/MyNFT.sol b/contracts/mocks/docs/MyNFT.sol new file mode 100644 index 000000000..1a442fa0a --- /dev/null +++ b/contracts/mocks/docs/MyNFT.sol @@ -0,0 +1,9 @@ +// contracts/MyNFT.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC721} from "../../token/ERC721/ERC721.sol"; + +contract MyNFT is ERC721 { + constructor() ERC721("MyNFT", "MNFT") {} +} diff --git a/contracts/mocks/docs/access-control/AccessControlModified.sol b/contracts/mocks/docs/access-control/AccessControlModified.sol new file mode 100644 index 000000000..9994afc92 --- /dev/null +++ b/contracts/mocks/docs/access-control/AccessControlModified.sol @@ -0,0 +1,14 @@ +// contracts/AccessControlModified.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControl} from "../../../access/AccessControl.sol"; + +contract AccessControlModified is AccessControl { + error AccessControlNonRevokable(); + + // Override the revokeRole function + function revokeRole(bytes32, address) public pure override { + revert AccessControlNonRevokable(); + } +} diff --git a/contracts/mocks/docs/access-control/AccessControlUnrevokableAdmin.sol b/contracts/mocks/docs/access-control/AccessControlUnrevokableAdmin.sol new file mode 100644 index 000000000..7440a2238 --- /dev/null +++ b/contracts/mocks/docs/access-control/AccessControlUnrevokableAdmin.sol @@ -0,0 +1,17 @@ +// contracts/AccessControlNonRevokableAdmin.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControl} from "../../../access/AccessControl.sol"; + +contract AccessControlNonRevokableAdmin is AccessControl { + error AccessControlNonRevokable(); + + function revokeRole(bytes32 role, address account) public override { + if (role == DEFAULT_ADMIN_ROLE) { + revert AccessControlNonRevokable(); + } + + super.revokeRole(role, account); + } +} diff --git a/contracts/mocks/docs/token/ERC1155/GameItems.sol b/contracts/mocks/docs/token/ERC1155/GameItems.sol new file mode 100644 index 000000000..e84fc0ba5 --- /dev/null +++ b/contracts/mocks/docs/token/ERC1155/GameItems.sol @@ -0,0 +1,21 @@ +// contracts/GameItems.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC1155} from "../../../../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, ""); + } +} diff --git a/contracts/mocks/docs/token/ERC1155/MyERC115HolderContract.sol b/contracts/mocks/docs/token/ERC1155/MyERC115HolderContract.sol new file mode 100644 index 000000000..742a53ba4 --- /dev/null +++ b/contracts/mocks/docs/token/ERC1155/MyERC115HolderContract.sol @@ -0,0 +1,7 @@ +// contracts/MyERC115HolderContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC1155Holder} from "../../../../token/ERC1155/utils/ERC1155Holder.sol"; + +contract MyERC115HolderContract is ERC1155Holder {} diff --git a/contracts/mocks/docs/token/ERC20/GLDToken.sol b/contracts/mocks/docs/token/ERC20/GLDToken.sol new file mode 100644 index 000000000..b6c545459 --- /dev/null +++ b/contracts/mocks/docs/token/ERC20/GLDToken.sol @@ -0,0 +1,11 @@ +// contracts/GLDToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../../../token/ERC20/ERC20.sol"; + +contract GLDToken is ERC20 { + constructor(uint256 initialSupply) ERC20("Gold", "GLD") { + _mint(msg.sender, initialSupply); + } +} diff --git a/contracts/mocks/docs/token/ERC721/GameItem.sol b/contracts/mocks/docs/token/ERC721/GameItem.sol new file mode 100644 index 000000000..b7f576f10 --- /dev/null +++ b/contracts/mocks/docs/token/ERC721/GameItem.sol @@ -0,0 +1,19 @@ +// contracts/GameItem.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC721URIStorage, ERC721} from "../../../../token/ERC721/extensions/ERC721URIStorage.sol"; + +contract GameItem is ERC721URIStorage { + uint256 private _nextTokenId; + + constructor() ERC721("GameItem", "ITM") {} + + function awardItem(address player, string memory tokenURI) public returns (uint256) { + uint256 tokenId = _nextTokenId++; + _mint(player, tokenId); + _setTokenURI(tokenId, tokenURI); + + return tokenId; + } +} diff --git a/contracts/mocks/docs/utilities/Base64NFT.sol b/contracts/mocks/docs/utilities/Base64NFT.sol new file mode 100644 index 000000000..1fb662343 --- /dev/null +++ b/contracts/mocks/docs/utilities/Base64NFT.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC721} from "../../../token/ERC721/ERC721.sol"; +import {Strings} from "../../../utils/Strings.sol"; +import {Base64} from "../../../utils/Base64.sol"; + +contract Base64NFT is ERC721 { + using Strings for uint256; + + constructor() ERC721("Base64NFT", "MTK") {} + + // ... + + function tokenURI(uint256 tokenId) public pure override returns (string memory) { + // Equivalent to: + // { + // "name": "Base64NFT #1", + // // Replace with extra ERC-721 Metadata properties + // } + // prettier-ignore + string memory dataURI = string.concat("{\"name\": \"Base64NFT #", tokenId.toString(), "\"}"); + + return string.concat("data:application/json;base64,", Base64.encode(bytes(dataURI))); + } +} diff --git a/contracts/mocks/docs/utilities/Multicall.sol b/contracts/mocks/docs/utilities/Multicall.sol new file mode 100644 index 000000000..6faac6a4c --- /dev/null +++ b/contracts/mocks/docs/utilities/Multicall.sol @@ -0,0 +1,15 @@ +// contracts/Box.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Multicall} from "../../../utils/Multicall.sol"; + +contract Box is Multicall { + function foo() public { + // ... + } + + function bar() public { + // ... + } +} diff --git a/docs/modules/ROOT/pages/erc1155.adoc b/docs/modules/ROOT/pages/erc1155.adoc index 0d771048e..5bfb49acc 100644 --- a/docs/modules/ROOT/pages/erc1155.adoc +++ b/docs/modules/ROOT/pages/erc1155.adoc @@ -32,27 +32,7 @@ Here's what a contract for tokenized items might look like: [source,solidity] ---- -// contracts/GameItems.sol -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC1155} from "@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, ""); - } -} +include::api:example$token/ERC1155/GameItems.sol[] ---- Note that for our Game Items, Gold is a fungible token whilst Thor's Hammer is a non-fungible token as we minted only one. @@ -119,27 +99,20 @@ TIP: If you'd like to put all item information on-chain, you can extend ERC-721 [[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: +A key difference when using xref:api:token/ERC1155.adoc#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-[`safeTransferFrom`] is that token transfers to other contracts may revert with the following custom error: [source,text] ---- -ERC-1155: transfer to non ERC-1155 Receiver implementer +ERC1155InvalidReceiver("
") ---- This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC-1155 protocol, so transfers to it are disabled to *prevent tokens from being locked forever*. As an example, https://etherscan.io/token/0xa74476443119A942dE498590Fe1f2454d7D4aC0d?a=0xa74476443119A942dE498590Fe1f2454d7D4aC0d[the Golem contract currently holds over 350k `GNT` tokens], worth multiple tens of thousands of dollars, and lacks methods to get them out of there. This has happened to virtually every ERC20-backed project, usually due to user error. -In order for our contract to receive ERC-1155 tokens we can inherit from the convenience contract xref:api:token/ERC1155.adoc#ERC1155Holder[`ERC1155Holder`] which handles the registering for us. Though we need to remember to implement functionality to allow tokens to be transferred out of our contract: +In order for our contract to receive ERC-1155 tokens we can inherit from the convenience contract xref:api:token/ERC1155.adoc#ERC1155Holder[`ERC1155Holder`] which handles the registering for us. Though we need to remember to implement functionality to allow tokens to be transferred out of our contract: [source,solidity] ---- -// contracts/MyContract.sol -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; - -contract MyContract is ERC1155Holder { -} +include::api:example$token/ERC1155/MyERC115HolderContract.sol[] ---- We can also implement more complex scenarios using the xref:api:token/ERC1155.adoc#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-[`onERC1155Received`] and xref:api:token/ERC1155.adoc#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-[`onERC1155BatchReceived`] functions. diff --git a/docs/modules/ROOT/pages/erc20.adoc b/docs/modules/ROOT/pages/erc20.adoc index 620b85a26..104b4efe3 100644 --- a/docs/modules/ROOT/pages/erc20.adoc +++ b/docs/modules/ROOT/pages/erc20.adoc @@ -13,17 +13,7 @@ Here's what our GLD token might look like. [source,solidity] ---- -// contracts/GLDToken.sol -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract GLDToken is ERC20 { - constructor(uint256 initialSupply) ERC20("Gold", "GLD") { - _mint(msg.sender, initialSupply); - } -} +include::api:example$token/ERC20/GLDToken.sol[] ---- Our contracts are often used via https://solidity.readthedocs.io/en/latest/contracts.html#inheritance[inheritance], and here we're reusing xref:api:token/ERC20.adoc#erc20[`ERC20`] for both the basic standard implementation and the xref:api:token/ERC20.adoc#ERC20-name--[`name`], xref:api:token/ERC20.adoc#ERC20-symbol--[`symbol`], and xref:api:token/ERC20.adoc#ERC20-decimals--[`decimals`] optional extensions. Additionally, we're creating an `initialSupply` of tokens, which will be assigned to the address that deploys the contract. diff --git a/docs/modules/ROOT/pages/erc721.adoc b/docs/modules/ROOT/pages/erc721.adoc index 269a78c6d..4b784db67 100644 --- a/docs/modules/ROOT/pages/erc721.adoc +++ b/docs/modules/ROOT/pages/erc721.adoc @@ -12,28 +12,7 @@ Here's what a contract for tokenized items might look like: [source,solidity] ---- -// contracts/GameItem.sol -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; - -contract GameItem is ERC721URIStorage { - uint256 private _nextTokenId; - - constructor() ERC721("GameItem", "ITM") {} - - function awardItem(address player, string memory tokenURI) - public - returns (uint256) - { - uint256 tokenId = _nextTokenId++; - _mint(player, tokenId); - _setTokenURI(tokenId, tokenURI); - - return tokenId; - } -} +include::api:example$token/ERC721/GameItem.sol[] ---- The xref:api:token/ERC721.adoc#ERC721URIStorage[`ERC721URIStorage`] contract is an implementation of ERC-721 that includes the metadata standard extensions (xref:api:token/ERC721.adoc#IERC721Metadata[`IERC721Metadata`]) as well as a mechanism for per-token metadata. That's where the xref:api:token/ERC721.adoc#ERC721-_setTokenURI-uint256-string-[`_setTokenURI`] method comes from: we use it to store an item's metadata. diff --git a/docs/modules/ROOT/pages/extending-contracts.adoc b/docs/modules/ROOT/pages/extending-contracts.adoc index 330d3a5bc..1cdc0d781 100644 --- a/docs/modules/ROOT/pages/extending-contracts.adoc +++ b/docs/modules/ROOT/pages/extending-contracts.adoc @@ -18,18 +18,7 @@ Inheritance is often used to add the parent contract's functionality to your own 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.20; - -import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; - -contract ModifiedAccessControl is AccessControl { - // Override the revokeRole function - function revokeRole(bytes32, address) public override { - revert("ModifiedAccessControl: cannot revoke roles"); - } -} +include::api:example$access-control/AccessControlModified.sol[] ``` The old `revokeRole` is then replaced by our override, and any calls to it will immediately revert. We cannot _remove_ the function from the contract, but reverting on all calls is good enough. @@ -46,22 +35,7 @@ Here is a modified version of xref:api:access.adoc#AccessControl[`AccessControl` ```solidity -// contracts/ModifiedAccessControl.sol -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {AccessControl} from "@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); - } -} +include::api:example$access-control/AccessControlNonRevokableAdmin.sol[] ``` The `super.revokeRole` statement at the end will invoke ``AccessControl``'s original version of `revokeRole`, the same code that would've run if there were no overrides in place. diff --git a/docs/modules/ROOT/pages/governance.adoc b/docs/modules/ROOT/pages/governance.adoc index 27efeaf9a..d2ff3cd41 100644 --- a/docs/modules/ROOT/pages/governance.adoc +++ b/docs/modules/ROOT/pages/governance.adoc @@ -230,7 +230,6 @@ contract MyGovernor is Governor, GovernorCountingSimple, GovernorVotes, Governor // ... } - ``` === Disclaimer diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index a4e163fec..1ba449559 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -38,16 +38,7 @@ 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.20; - -import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -contract MyNFT is ERC721 { - constructor() ERC721("MyNFT", "MNFT") { - } -} +include::api:example$MyNFT.sol[] ---- TIP: If you're new to smart contract development, head to xref:learn::developing-smart-contracts.adoc[Developing Smart Contracts] to learn about creating a new project and compiling your contracts. diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index b9ed2c4a1..41df1f32b 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -124,41 +124,7 @@ Here is an example to send JSON Metadata through a Base64 Data URI using an ERC- [source, solidity] ---- -// contracts/My721Token.sol -// SPDX-License-Identifier: MIT - -import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {Base64} from "@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 ERC-721 Metadata properties - '}' - ); - - return string( - abi.encodePacked( - "data:application/json;base64,", - Base64.encode(dataURI) - ) - ); - } -} +include::api:example$utilities/Base64NFT.sol[] ---- === Multicall @@ -169,21 +135,7 @@ Consider this dummy contract: [source,solidity] ---- -// contracts/Box.sol -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts/utils/Multicall.sol"; - -contract Box is Multicall { - function foo() public { - ... - } - - function bar() public { - ... - } -} +include::api:example$utilities/Multicall.sol[] ---- This is how to call the `multicall` function using Ethers.js, allowing `foo` and `bar` to be called in a single transaction: