extending-contracts.adoc 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. = Extending Contracts
  2. 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.
  3. This is the commonly found `is` syntax, like in `contract MyToken is ERC20`.
  4. [NOTE]
  5. ====
  6. 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.
  7. OpenZeppelin Contracts has some ``library``s: most are in the xref:api:utils.adoc[Utils] directory.
  8. ====
  9. == Overriding
  10. 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_.
  11. 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:
  12. ```solidity
  13. // contracts/ModifiedAccessControl.sol
  14. // SPDX-License-Identifier: MIT
  15. pragma solidity ^0.8.0;
  16. import "@openzeppelin/contracts/access/AccessControl.sol";
  17. contract ModifiedAccessControl is AccessControl {
  18. // Override the revokeRole function
  19. function revokeRole(bytes32, address) public override {
  20. revert("ModifiedAccessControl: cannot revoke roles");
  21. }
  22. }
  23. ```
  24. 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.
  25. === Calling `super`
  26. Sometimes you want to _extend_ a parent's behavior, instead of outright changing it to something else. This is where `super` comes in.
  27. 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.
  28. 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].
  29. 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`:
  30. ```solidity
  31. // contracts/ModifiedAccessControl.sol
  32. // SPDX-License-Identifier: MIT
  33. pragma solidity ^0.8.0;
  34. import "@openzeppelin/contracts/access/AccessControl.sol";
  35. contract ModifiedAccessControl is AccessControl {
  36. function revokeRole(bytes32 role, address account) public override {
  37. require(
  38. role != DEFAULT_ADMIN_ROLE,
  39. "ModifiedAccessControl: cannot revoke default admin role"
  40. );
  41. super.revokeRole(role, account);
  42. }
  43. }
  44. ```
  45. 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.
  46. 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!
  47. [[using-hooks]]
  48. == Using Hooks
  49. 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.
  50. 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**.
  51. 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.
  52. 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:
  53. ```solidity
  54. pragma solidity ^0.8.0;
  55. import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
  56. contract ERC20WithSafeTransfer is ERC20 {
  57. function _beforeTokenTransfer(address from, address to, uint256 amount)
  58. internal virtual override
  59. {
  60. super._beforeTokenTransfer(from, to, amount);
  61. require(_validRecipient(to), "ERC20WithSafeTransfer: invalid recipient");
  62. }
  63. function _validRecipient(address to) private view returns (bool) {
  64. ...
  65. }
  66. ...
  67. }
  68. ```
  69. Using hooks this way leads to cleaner and safer code, without having to rely on a deep understanding of the parent's internals.
  70. [NOTE]
  71. ====
  72. Hooks are a new feature of OpenZeppelin Contracts v3.0.0, and we're eager to learn how you plan to use them!
  73. 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!
  74. ====
  75. === Rules of Hooks
  76. 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:
  77. 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.
  78. 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.
  79. ```solidity
  80. contract MyToken is ERC20 {
  81. function _beforeTokenTransfer(address from, address to, uint256 amount)
  82. internal virtual override // Add virtual here!
  83. {
  84. super._beforeTokenTransfer(from, to, amount); // Call parent hook
  85. ...
  86. }
  87. }
  88. ```
  89. That's it! Enjoy simpler code using hooks!