Skip to content

Commit

Permalink
Refactor ERC165 to use function overriding instead of storage (#2505)
Browse files Browse the repository at this point in the history
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
  • Loading branch information
Amxx and frangio committed Feb 18, 2021
1 parent f7c8252 commit e733b24
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 54 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* `Strings`: addition of a `toHexString` function. ([#2504](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2504))
* `EnumerableMap`: change implementation to optimize for `key → value` lookups instead of enumeration. ([#2518](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2518))
* `GSN`: Deprecate GSNv1 support in favor of upcomming support for GSNv2. ([#2521](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2521))
* `ERC165`: Remove uses of storage in the base ERC165 implementation. ERC165 based contracts now use storage-less virtual functions. Old behaviour remains available in the `ERC165Storage` extension. ([#2505](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2505))

## 3.4.0 (2021-02-02)

Expand Down
43 changes: 11 additions & 32 deletions contracts/introspection/ERC165.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,22 @@ import "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts may inherit from this and call {_registerInterface} to declare
* their support of an interface.
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev Mapping of interface ids to whether or not it's supported.
*/
mapping(bytes4 => bool) private _supportedInterfaces;

constructor () {
// Derived contracts need only register support for their own interfaces,
// we register support for ERC165 itself here
_registerInterface(type(IERC165).interfaceId);
}

/**
* @dev See {IERC165-supportsInterface}.
*
* Time complexity O(1), guaranteed to always use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return _supportedInterfaces[interfaceId];
}

/**
* @dev Registers the contract as an implementer of the interface defined by
* `interfaceId`. Support of the actual ERC165 interface is automatic and
* registering its interface id is not required.
*
* See {IERC165-supportsInterface}.
*
* Requirements:
*
* - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`).
*/
function _registerInterface(bytes4 interfaceId) internal virtual {
require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
_supportedInterfaces[interfaceId] = true;
return interfaceId == type(IERC165).interfaceId;
}
}
41 changes: 41 additions & 0 deletions contracts/introspection/ERC165Storage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./ERC165.sol";

/**
* @dev Storage based implementation of the {IERC165} interface.
*
* Contracts may inherit from this and call {_registerInterface} to declare
* their support of an interface.
*/
abstract contract ERC165Storage is ERC165 {
/**
* @dev Mapping of interface ids to whether or not it's supported.
*/
mapping(bytes4 => bool) private _supportedInterfaces;

/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return super.supportsInterface(interfaceId) || _supportedInterfaces[interfaceId];
}

/**
* @dev Registers the contract as an implementer of the interface defined by
* `interfaceId`. Support of the actual ERC165 interface is automatic and
* registering its interface id is not required.
*
* See {IERC165-supportsInterface}.
*
* Requirements:
*
* - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`).
*/
function _registerInterface(bytes4 interfaceId) internal virtual {
require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
_supportedInterfaces[interfaceId] = true;
}
}
2 changes: 2 additions & 0 deletions contracts/introspection/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Note that, in all cases, accounts simply _declare_ their interfaces, but they ar

{{ERC165}}

{{ERC165Storage}}

{{ERC165Checker}}

== Global
Expand Down
4 changes: 2 additions & 2 deletions contracts/mocks/ERC1155ReceiverMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

pragma solidity ^0.8.0;

import "../introspection/ERC165.sol";
import "../token/ERC1155/IERC1155Receiver.sol";
import "./ERC165Mock.sol";

contract ERC1155ReceiverMock is IERC1155Receiver, ERC165Mock {
contract ERC1155ReceiverMock is IERC1155Receiver, ERC165 {
bytes4 private _recRetval;
bool private _recReverts;
bytes4 private _batRetval;
Expand Down
3 changes: 0 additions & 3 deletions contracts/mocks/ERC165Mock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,4 @@ pragma solidity ^0.8.0;
import "../introspection/ERC165.sol";

contract ERC165Mock is ERC165 {
function registerInterface(bytes4 interfaceId) public {
_registerInterface(interfaceId);
}
}
11 changes: 11 additions & 0 deletions contracts/mocks/ERC165StorageMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../introspection/ERC165Storage.sol";

contract ERC165StorageMock is ERC165Storage {
function registerInterface(bytes4 interfaceId) public {
_registerInterface(interfaceId);
}
}
13 changes: 8 additions & 5 deletions contracts/token/ERC1155/ERC1155.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
*/
constructor (string memory uri_) {
_setURI(uri_);
}

// register the supported interfaces to conform to ERC1155 via ERC165
_registerInterface(type(IERC1155).interfaceId);

// register the supported interfaces to conform to ERC1155MetadataURI via ERC165
_registerInterface(type(IERC1155MetadataURI).interfaceId);
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return interfaceId == type(IERC1155).interfaceId
|| interfaceId == type(IERC1155MetadataURI).interfaceId
|| super.supportsInterface(interfaceId);
}

/**
Expand Down
8 changes: 6 additions & 2 deletions contracts/token/ERC1155/ERC1155Receiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import "../../introspection/ERC165.sol";
* @dev _Available since v3.1._
*/
abstract contract ERC1155Receiver is ERC165, IERC1155Receiver {
constructor() {
_registerInterface(type(IERC1155Receiver).interfaceId);
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return interfaceId == type(IERC1155Receiver).interfaceId
|| super.supportsInterface(interfaceId);
}
}
13 changes: 9 additions & 4 deletions contracts/token/ERC721/ERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,16 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable
constructor (string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}

// register the supported interfaces to conform to ERC721 via ERC165
_registerInterface(type(IERC721).interfaceId);
_registerInterface(type(IERC721Metadata).interfaceId);
_registerInterface(type(IERC721Enumerable).interfaceId);
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return interfaceId == type(IERC721).interfaceId
|| interfaceId == type(IERC721Metadata).interfaceId
|| interfaceId == type(IERC721Enumerable).interfaceId
|| super.supportsInterface(interfaceId);
}

/**
Expand Down
6 changes: 0 additions & 6 deletions test/introspection/ERC165.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const { expectRevert } = require('@openzeppelin/test-helpers');

const { shouldSupportInterfaces } = require('./SupportsInterface.behavior');

const ERC165Mock = artifacts.require('ERC165Mock');
Expand All @@ -9,10 +7,6 @@ contract('ERC165', function (accounts) {
this.mock = await ERC165Mock.new();
});

it('does not allow 0xffffffff', async function () {
await expectRevert(this.mock.registerInterface('0xffffffff'), 'ERC165: invalid interface id');
});

shouldSupportInterfaces([
'ERC165',
]);
Expand Down
25 changes: 25 additions & 0 deletions test/introspection/ERC165Storage.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const { expectRevert } = require('@openzeppelin/test-helpers');

const { shouldSupportInterfaces } = require('./SupportsInterface.behavior');

const ERC165Mock = artifacts.require('ERC165StorageMock');

contract('ERC165Storage', function (accounts) {
beforeEach(async function () {
this.mock = await ERC165Mock.new();
});

it('register interface', async function () {
expect(await this.mock.supportsInterface('0x00000001')).to.be.equal(false);
await this.mock.registerInterface('0x00000001');
expect(await this.mock.supportsInterface('0x00000001')).to.be.equal(true);
});

it('does not allow 0xffffffff', async function () {
await expectRevert(this.mock.registerInterface('0xffffffff'), 'ERC165: invalid interface id');
});

shouldSupportInterfaces([
'ERC165',
]);
});

0 comments on commit e733b24

Please sign in to comment.