Skip to content

Commit 53bb340

Browse files
ernestognwAmxxjames-toussaint
authored
Fix ERC165Checker detection (#5880)
Co-authored-by: Hadrien Croubois <[email protected]> Co-authored-by: James Toussaint <[email protected]>
1 parent 005c9c9 commit 53bb340

File tree

8 files changed

+145
-97
lines changed

8 files changed

+145
-97
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
### Bug fixes
4+
5+
- `ERC165Checker`: Ensure the `supportsERC165` function returns false if the target reverts during the `supportsInterface(0xffffffff)` call. ([#5810](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5880))
36

47
### Breaking changes
58

contracts/mocks/ERC165/ERC165MaliciousData.sol

Lines changed: 0 additions & 12 deletions
This file was deleted.

contracts/mocks/ERC165/ERC165MissingData.sol

Lines changed: 0 additions & 7 deletions
This file was deleted.

contracts/mocks/ERC165/ERC165NotSupported.sol

Lines changed: 0 additions & 5 deletions
This file was deleted.

contracts/mocks/ERC165/ERC165ReturnBomb.sol

Lines changed: 0 additions & 18 deletions
This file was deleted.

contracts/mocks/ERC165/ERC165InterfacesSupported.sol renamed to contracts/mocks/ERC165Mock.sol

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
pragma solidity ^0.8.20;
44

5-
import {IERC165} from "../../utils/introspection/IERC165.sol";
5+
import {IERC165} from "../utils/introspection/IERC165.sol";
66

77
/**
88
* https://eips.ethereum.org/EIPS/eip-214#specification
@@ -36,7 +36,7 @@ contract SupportsInterfaceWithLookupMock is IERC165 {
3636
/**
3737
* @dev Implement supportsInterface(bytes4) using a lookup table.
3838
*/
39-
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
39+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
4040
return _supportedInterfaces[interfaceId];
4141
}
4242

@@ -56,3 +56,45 @@ contract ERC165InterfacesSupported is SupportsInterfaceWithLookupMock {
5656
}
5757
}
5858
}
59+
60+
// Similar to ERC165InterfacesSupported, but revert (without reason) when an interface is not supported
61+
contract ERC165RevertInvalid is SupportsInterfaceWithLookupMock {
62+
constructor(bytes4[] memory interfaceIds) {
63+
for (uint256 i = 0; i < interfaceIds.length; i++) {
64+
_registerInterface(interfaceIds[i]);
65+
}
66+
}
67+
68+
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
69+
require(super.supportsInterface(interfaceId));
70+
return true;
71+
}
72+
}
73+
74+
contract ERC165MaliciousData {
75+
function supportsInterface(bytes4) public pure returns (bool) {
76+
assembly {
77+
mstore(0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
78+
return(0, 32)
79+
}
80+
}
81+
}
82+
83+
contract ERC165MissingData {
84+
function supportsInterface(bytes4 interfaceId) public view {} // missing return
85+
}
86+
87+
contract ERC165NotSupported {}
88+
89+
contract ERC165ReturnBombMock is IERC165 {
90+
function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
91+
if (interfaceId == type(IERC165).interfaceId) {
92+
assembly {
93+
mstore(0, 1)
94+
}
95+
}
96+
assembly {
97+
return(0, 101500)
98+
}
99+
}
100+
}

contracts/utils/introspection/ERC165Checker.sol

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@ library ERC165Checker {
2222
function supportsERC165(address account) internal view returns (bool) {
2323
// Any contract that implements ERC-165 must explicitly indicate support of
2424
// InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
25-
return
26-
supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
27-
!supportsERC165InterfaceUnchecked(account, INTERFACE_ID_INVALID);
25+
if (supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId)) {
26+
(bool success, bool supported) = _trySupportsInterface(account, INTERFACE_ID_INVALID);
27+
return success && !supported;
28+
} else {
29+
return false;
30+
}
2831
}
2932

3033
/**
@@ -106,19 +109,34 @@ library ERC165Checker {
106109
* Interface identification is specified in ERC-165.
107110
*/
108111
function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
109-
// prepare call
110-
bytes memory encodedParams = abi.encodeCall(IERC165.supportsInterface, (interfaceId));
112+
(bool success, bool supported) = _trySupportsInterface(account, interfaceId);
113+
return success && supported;
114+
}
115+
116+
/**
117+
* @dev Attempts to call `supportsInterface` on a contract and returns both the call
118+
* success status and the interface support result.
119+
*
120+
* This function performs a low-level static call to the contract's `supportsInterface`
121+
* function. It returns:
122+
*
123+
* * `success`: true if the call didn't revert, false if it did
124+
* * `supported`: true if the call succeeded AND returned data indicating the interface is supported
125+
*/
126+
function _trySupportsInterface(
127+
address account,
128+
bytes4 interfaceId
129+
) private view returns (bool success, bool supported) {
130+
bytes4 selector = IERC165.supportsInterface.selector;
111131

112-
// perform static call
113-
bool success;
114-
uint256 returnSize;
115-
uint256 returnValue;
116132
assembly ("memory-safe") {
117-
success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
118-
returnSize := returndatasize()
119-
returnValue := mload(0x00)
133+
mstore(0x00, selector)
134+
mstore(0x04, interfaceId)
135+
success := staticcall(30000, account, 0x00, 0x24, 0x00, 0x20)
136+
supported := and(
137+
gt(returndatasize(), 0x1F), // we have at least 32 bytes of returndata
138+
iszero(iszero(mload(0x00))) // the first 32 bytes of returndata are non-zero
139+
)
120140
}
121-
122-
return success && returnSize >= 0x20 && returnValue > 0;
123141
}
124142
}

0 commit comments

Comments
 (0)