diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d794b4026..dabd8b21f7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * `UpgradeableProxy`: bubble revert reasons from initialization calls. ([#2454](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2454)) * `SafeMath`: fix a memory allocation issue by adding new `SafeMath.tryOp(uint,uint)→(bool,uint)` functions. `SafeMath.op(uint,uint,string)→uint` are now deprecated. ([#2462](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2462)) * `EnumerableMap`: fix a memory allocation issue by adding new `EnumerableMap.tryGet(uint)→(bool,address)` functions. `EnumerableMap.get(uint)→string` is now deprecated. ([#2462](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2462)) + * `ERC165Checker`: added batch `getSupportedInterfaces`. ([#2469](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2469)) ## 3.3.0 (2020-11-26) diff --git a/contracts/introspection/ERC165Checker.sol b/contracts/introspection/ERC165Checker.sol index 75eb50ee4e2..b538489ad54 100644 --- a/contracts/introspection/ERC165Checker.sol +++ b/contracts/introspection/ERC165Checker.sol @@ -40,6 +40,29 @@ library ERC165Checker { _supportsERC165Interface(account, interfaceId); } + /** + * @dev Returns a boolean array where each value corresponds to the + * interfaces passed in and whether they're supported or not. This allows + * you to batch check interfaces for a contract where your expectation + * is that some interfaces may not be supported. + * + * See {IERC165-supportsInterface}. + */ + function getSupportedInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool[] memory) { + // an array of booleans corresponding to interfaceIds and whether they're supported or not + bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length); + + // query support of ERC165 itself + if (supportsERC165(account)) { + // query support of each interface in interfaceIds + for (uint256 i = 0; i < interfaceIds.length; i++) { + interfaceIdsSupported[i] = _supportsERC165Interface(account, interfaceIds[i]); + } + } + + return interfaceIdsSupported; + } + /** * @dev Returns true if `account` supports all the interfaces defined in * `interfaceIds`. Support for {IERC165} itself is queried automatically. diff --git a/contracts/mocks/ERC165CheckerMock.sol b/contracts/mocks/ERC165CheckerMock.sol index 5c18e66bf64..5cf2f06eabe 100644 --- a/contracts/mocks/ERC165CheckerMock.sol +++ b/contracts/mocks/ERC165CheckerMock.sol @@ -18,4 +18,8 @@ contract ERC165CheckerMock { function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) public view returns (bool) { return account.supportsAllInterfaces(interfaceIds); } + + function getSupportedInterfaces(address account, bytes4[] memory interfaceIds) public view returns (bool[] memory) { + return account.getSupportedInterfaces(interfaceIds); + } } diff --git a/test/introspection/ERC165Checker.test.js b/test/introspection/ERC165Checker.test.js index bf5192522b9..ae2b97e6c1a 100644 --- a/test/introspection/ERC165Checker.test.js +++ b/test/introspection/ERC165Checker.test.js @@ -37,6 +37,12 @@ contract('ERC165Checker', function (accounts) { const supported = await this.mock.supportsAllInterfaces(this.target.address, [DUMMY_ID]); expect(supported).to.equal(false); }); + + it('does not support mock interface via getSupportedInterfaces', async function () { + const supported = await this.mock.getSupportedInterfaces(this.target.address, [DUMMY_ID]); + expect(supported.length).to.equal(1); + expect(supported[0]).to.equal(false); + }); }); context('ERC165 supported', function () { @@ -58,6 +64,12 @@ contract('ERC165Checker', function (accounts) { const supported = await this.mock.supportsAllInterfaces(this.target.address, [DUMMY_ID]); expect(supported).to.equal(false); }); + + it('does not support mock interface via getSupportedInterfaces', async function () { + const supported = await this.mock.getSupportedInterfaces(this.target.address, [DUMMY_ID]); + expect(supported.length).to.equal(1); + expect(supported[0]).to.equal(false); + }); }); context('ERC165 and single interface supported', function () { @@ -79,6 +91,12 @@ contract('ERC165Checker', function (accounts) { const supported = await this.mock.supportsAllInterfaces(this.target.address, [DUMMY_ID]); expect(supported).to.equal(true); }); + + it('supports mock interface via getSupportedInterfaces', async function () { + const supported = await this.mock.getSupportedInterfaces(this.target.address, [DUMMY_ID]); + expect(supported.length).to.equal(1); + expect(supported[0]).to.equal(true); + }); }); context('ERC165 and many interfaces supported', function () { @@ -117,6 +135,34 @@ contract('ERC165Checker', function (accounts) { const supported = await this.mock.supportsAllInterfaces(this.target.address, interfaceIdsToTest); expect(supported).to.equal(false); }); + + it('supports all interfaceIds via getSupportedInterfaces', async function () { + const supported = await this.mock.getSupportedInterfaces(this.target.address, this.supportedInterfaces); + expect(supported.length).to.equal(3); + expect(supported[0]).to.equal(true); + expect(supported[1]).to.equal(true); + expect(supported[2]).to.equal(true); + }); + + it('supports none of the interfaces queried via getSupportedInterfaces', async function () { + const interfaceIdsToTest = [DUMMY_UNSUPPORTED_ID, DUMMY_UNSUPPORTED_ID_2]; + + const supported = await this.mock.getSupportedInterfaces(this.target.address, interfaceIdsToTest); + expect(supported.length).to.equal(2); + expect(supported[0]).to.equal(false); + expect(supported[1]).to.equal(false); + }); + + it('supports not all of the interfaces queried via getSupportedInterfaces', async function () { + const interfaceIdsToTest = [...this.supportedInterfaces, DUMMY_UNSUPPORTED_ID]; + + const supported = await this.mock.getSupportedInterfaces(this.target.address, interfaceIdsToTest); + expect(supported.length).to.equal(4); + expect(supported[0]).to.equal(true); + expect(supported[1]).to.equal(true); + expect(supported[2]).to.equal(true); + expect(supported[3]).to.equal(false); + }); }); context('account address does not support ERC165', function () { @@ -134,5 +180,11 @@ contract('ERC165Checker', function (accounts) { const supported = await this.mock.supportsAllInterfaces(DUMMY_ACCOUNT, [DUMMY_ID]); expect(supported).to.equal(false); }); + + it('does not support mock interface via getSupportedInterfaces', async function () { + const supported = await this.mock.getSupportedInterfaces(DUMMY_ACCOUNT, [DUMMY_ID]); + expect(supported.length).to.equal(1); + expect(supported[0]).to.equal(false); + }); }); });