diff --git a/CHANGELOG.md b/CHANGELOG.md index 34a569669b7..e3d964be2e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * `PaymentSplitter`: add `releasable` getters. ([#3350](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3350)) * `Initializable`: refactored implementation of modifiers for easier understanding. ([#3450](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3450)) * `Proxies`: remove runtime check of ERC1967 storage slots. ([#3455](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3455)) + * `ERC165Checker`: add `supportsERC165InterfaceUnchecked` for consulting individual interfaces without the full ERC165 protocol. ([#3339](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3339)) * `Address`: optimize `functionCall` functions by checking contract size only if there is no returned data. ([#3469](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3469)) ### Breaking changes diff --git a/contracts/mocks/ERC165CheckerMock.sol b/contracts/mocks/ERC165CheckerMock.sol index bda5cfc78c2..9ff7e7df6cf 100644 --- a/contracts/mocks/ERC165CheckerMock.sol +++ b/contracts/mocks/ERC165CheckerMock.sol @@ -22,4 +22,8 @@ contract ERC165CheckerMock { function getSupportedInterfaces(address account, bytes4[] memory interfaceIds) public view returns (bool[] memory) { return account.getSupportedInterfaces(interfaceIds); } + + function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) public view returns (bool) { + return account.supportsERC165InterfaceUnchecked(interfaceId); + } } diff --git a/contracts/utils/introspection/ERC165Checker.sol b/contracts/utils/introspection/ERC165Checker.sol index 6a240e15531..c936d3f31ae 100644 --- a/contracts/utils/introspection/ERC165Checker.sol +++ b/contracts/utils/introspection/ERC165Checker.sol @@ -23,8 +23,8 @@ library ERC165Checker { // Any contract that implements ERC165 must explicitly indicate support of // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid return - _supportsERC165Interface(account, type(IERC165).interfaceId) && - !_supportsERC165Interface(account, _INTERFACE_ID_INVALID); + supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) && + !supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID); } /** @@ -35,7 +35,7 @@ library ERC165Checker { */ function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) { // query support of both ERC165 as per the spec and support of _interfaceId - return supportsERC165(account) && _supportsERC165Interface(account, interfaceId); + return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId); } /** @@ -60,7 +60,7 @@ library ERC165Checker { if (supportsERC165(account)) { // query support of each interface in interfaceIds for (uint256 i = 0; i < interfaceIds.length; i++) { - interfaceIdsSupported[i] = _supportsERC165Interface(account, interfaceIds[i]); + interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]); } } @@ -84,7 +84,7 @@ library ERC165Checker { // query support of each interface in _interfaceIds for (uint256 i = 0; i < interfaceIds.length; i++) { - if (!_supportsERC165Interface(account, interfaceIds[i])) { + if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) { return false; } } @@ -104,7 +104,7 @@ library ERC165Checker { * with {supportsERC165}. * Interface identification is specified in ERC-165. */ - function _supportsERC165Interface(address account, bytes4 interfaceId) private view returns (bool) { + function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) { bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId); (bool success, bytes memory result) = account.staticcall{gas: 30000}(encodedParams); if (result.length < 32) return false; diff --git a/test/utils/introspection/ERC165Checker.test.js b/test/utils/introspection/ERC165Checker.test.js index c3a6cdc66e4..c325adb7e0f 100644 --- a/test/utils/introspection/ERC165Checker.test.js +++ b/test/utils/introspection/ERC165Checker.test.js @@ -44,6 +44,11 @@ contract('ERC165Checker', function (accounts) { expect(supported.length).to.equal(1); expect(supported[0]).to.equal(false); }); + + it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () { + const supported = await this.mock.supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID); + expect(supported).to.equal(false); + }); }); context('ERC165 not supported', function () { @@ -71,6 +76,11 @@ contract('ERC165Checker', function (accounts) { expect(supported.length).to.equal(1); expect(supported[0]).to.equal(false); }); + + it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () { + const supported = await this.mock.supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID); + expect(supported).to.equal(false); + }); }); context('ERC165 supported', function () { @@ -98,6 +108,11 @@ contract('ERC165Checker', function (accounts) { expect(supported.length).to.equal(1); expect(supported[0]).to.equal(false); }); + + it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () { + const supported = await this.mock.supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID); + expect(supported).to.equal(false); + }); }); context('ERC165 and single interface supported', function () { @@ -125,6 +140,11 @@ contract('ERC165Checker', function (accounts) { expect(supported.length).to.equal(1); expect(supported[0]).to.equal(true); }); + + it('supports mock interface via supportsERC165InterfaceUnchecked', async function () { + const supported = await this.mock.supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID); + expect(supported).to.equal(true); + }); }); context('ERC165 and many interfaces supported', function () { @@ -191,6 +211,13 @@ contract('ERC165Checker', function (accounts) { expect(supported[2]).to.equal(true); expect(supported[3]).to.equal(false); }); + + it('supports each interfaceId via supportsERC165InterfaceUnchecked', async function () { + for (const interfaceId of this.supportedInterfaces) { + const supported = await this.mock.supportsERC165InterfaceUnchecked(this.target.address, interfaceId); + expect(supported).to.equal(true); + }; + }); }); context('account address does not support ERC165', function () { @@ -214,5 +241,10 @@ contract('ERC165Checker', function (accounts) { expect(supported.length).to.equal(1); expect(supported[0]).to.equal(false); }); + + it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () { + const supported = await this.mock.supportsERC165InterfaceUnchecked(DUMMY_ACCOUNT, DUMMY_ID); + expect(supported).to.equal(false); + }); }); });