Enable using ERC165 check for one supported interface directly (#3339)

Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Co-authored-by: Francisco <frangio.1@gmail.com>
pull/3510/head
ashhanai 3 years ago committed by GitHub
parent 40172c22d9
commit e734b42fc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 4
      contracts/mocks/ERC165CheckerMock.sol
  3. 12
      contracts/utils/introspection/ERC165Checker.sol
  4. 32
      test/utils/introspection/ERC165Checker.test.js

@ -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

@ -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);
}
}

@ -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;

@ -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);
});
});
});

Loading…
Cancel
Save