Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions contracts/mocks/ERC165/ERC165MaliciousData.sol

This file was deleted.

7 changes: 0 additions & 7 deletions contracts/mocks/ERC165/ERC165MissingData.sol

This file was deleted.

5 changes: 0 additions & 5 deletions contracts/mocks/ERC165/ERC165NotSupported.sol

This file was deleted.

18 changes: 0 additions & 18 deletions contracts/mocks/ERC165/ERC165ReturnBomb.sol

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";
import {IERC165} from "../utils/introspection/IERC165.sol";

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

Expand All @@ -56,3 +56,45 @@ contract ERC165InterfacesSupported is SupportsInterfaceWithLookupMock {
}
}
}

// Similar to ERC165InterfacesSupported, but revert (without reason) when an interface is not supported
contract ERC165RevertInvalid is SupportsInterfaceWithLookupMock {
constructor(bytes4[] memory interfaceIds) {
for (uint256 i = 0; i < interfaceIds.length; i++) {
_registerInterface(interfaceIds[i]);
}
}

function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
require(super.supportsInterface(interfaceId));
return true;
}
}

contract ERC165MaliciousData {
function supportsInterface(bytes4) public pure returns (bool) {
assembly {
mstore(0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
return(0, 32)
}
}
}

contract ERC165MissingData {
function supportsInterface(bytes4 interfaceId) public view {} // missing return
}

contract ERC165NotSupported {}

contract ERC165ReturnBombMock is IERC165 {
function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
if (interfaceId == type(IERC165).interfaceId) {
assembly {
mstore(0, 1)
}
}
assembly {
return(0, 101500)
}
}
}
46 changes: 32 additions & 14 deletions contracts/utils/introspection/ERC165Checker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ library ERC165Checker {
function supportsERC165(address account) internal view returns (bool) {
// Any contract that implements ERC-165 must explicitly indicate support of
// InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
return
supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
!supportsERC165InterfaceUnchecked(account, INTERFACE_ID_INVALID);
if (supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId)) {
(bool success, bool supported) = trySupportsInterface(account, INTERFACE_ID_INVALID);
return success && !supported;
} else {
return false;
}
}

/**
Expand Down Expand Up @@ -106,19 +109,34 @@ library ERC165Checker {
* Interface identification is specified in ERC-165.
*/
function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
// prepare call
bytes memory encodedParams = abi.encodeCall(IERC165.supportsInterface, (interfaceId));
(bool success, bool supported) = trySupportsInterface(account, interfaceId);
return success && supported;
}

/**
* @dev Attempts to call `supportsInterface` on a contract and returns both the call
* success status and the interface support result.
*
* This function performs a low-level static call to the contract's `supportsInterface`
* function. It returns:
*
* * `success`: true if the call didn't revert, false if it did
* * `supported`: true if the call succeeded AND returned data indicating the interface is supported
*/
function trySupportsInterface(
address account,
bytes4 interfaceId
) internal view returns (bool success, bool supported) {
bytes4 selector = IERC165.supportsInterface.selector;

// perform static call
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
returnSize := returndatasize()
returnValue := mload(0x00)
mstore(0x00, selector)
mstore(0x04, interfaceId)
success := staticcall(30000, account, 0x00, 0x24, 0x00, 0x20)
supported := and(
gt(returndatasize(), 0x1F), // we have at least 20 bytes of returndata
mload(0x00) // the first 20 bytes of returndata are non-zero
)
}

return success && returnSize >= 0x20 && returnValue > 0;
}
}
Loading