Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable using ERC165 check for one supported interface directly #3339

Merged
merged 8 commits into from Jun 27, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions contracts/mocks/ERC165CheckerMock.sol
Expand Up @@ -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);
}
}
12 changes: 6 additions & 6 deletions contracts/utils/introspection/ERC165Checker.sol
Expand Up @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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]);
}
}

Expand All @@ -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;
}
}
Expand All @@ -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;
Expand Down
32 changes: 32 additions & 0 deletions test/utils/introspection/ERC165Checker.test.js
Expand Up @@ -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 () {
Expand Down Expand Up @@ -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 () {
Expand Down Expand Up @@ -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 () {
Expand Down Expand Up @@ -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 () {
Expand Down Expand Up @@ -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 () {
Expand All @@ -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);
});
});
});