diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c49064..0fa39f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ As of 12/13/2022, this repo has been renamed from "nftc-open-contracts" to "nftc ## Version -- 1.5.next [Not published] - TODO +## Version -- 1.5.20 +- Implement ERC165 compliant version of ERC20. +- Update the NFTSpecChecker and tests. ## Version -- 1.5.19 - package lock cleanup diff --git a/contracts/mocks/token/ERC20/MockERC20.sol b/contracts/mocks/token/ERC20/MockERC20.sol index 6233e58..a053979 100644 --- a/contracts/mocks/token/ERC20/MockERC20.sol +++ b/contracts/mocks/token/ERC20/MockERC20.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; +import '../../../token/openzeppelin/ERC20/extensions/ERC20_165.sol'; /** - * @title MockERC20 + * @title MockERC20_165 * @author @NFTCulture */ -contract MockERC20 is ERC20 { - uint256 private immutable TOTAL_SUPPLY; +contract MockERC20_165 is ERC20_165 { + uint256 private immutable _maxSupply; - constructor() ERC20('MockERC20', 'M20') { - TOTAL_SUPPLY = 1000000000; + constructor() ERC20('MockERC20_165', 'M20') { + _maxSupply = 1000000000; } } diff --git a/contracts/token/openzeppelin/ERC20/extensions/ERC20_165.sol b/contracts/token/openzeppelin/ERC20/extensions/ERC20_165.sol new file mode 100644 index 0000000..2b86b36 --- /dev/null +++ b/contracts/token/openzeppelin/ERC20/extensions/ERC20_165.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; +import '@openzeppelin/contracts/utils/introspection/ERC165.sol'; + +/** + * @title ERC20_165 + * @author @NiftyMike | @NFTCulture + * @dev ERC20 with ERC165 support tacked on, for consistency with more modern ERC specs. + */ +abstract contract ERC20_165 is ERC20, ERC165 { + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return + interfaceId == type(IERC20).interfaceId || + interfaceId == type(IERC20Metadata).interfaceId || + super.supportsInterface(interfaceId); + } +} diff --git a/contracts/utility/introspection/NFTSpecChecker.sol b/contracts/utility/introspection/NFTSpecChecker.sol index 57800b3..ee04c6c 100644 --- a/contracts/utility/introspection/NFTSpecChecker.sol +++ b/contracts/utility/introspection/NFTSpecChecker.sol @@ -29,17 +29,17 @@ contract NFTSpecChecker { bytes4 constant _ERC165_CONTRACT = type(IERC165).interfaceId; /// @dev See {ERC165Checker-supportsInterface} - ///> 0x36372b07 + ///> 0x36372b07 / 0xa219a025 bytes4 constant _ERC20_CONTRACT = type(IERC20).interfaceId; bytes4 constant _ERC20_CONTRACT_METADATA = type(IERC20Metadata).interfaceId; /// @dev See {ERC165Checker-supportsInterface} - ///> 0x80ac58cd + ///> 0x80ac58cd / 0x5b5e139f bytes4 constant _ERC721_CONTRACT = type(IERC721).interfaceId; bytes4 constant _ERC721_CONTRACT_METADATA = type(IERC721Metadata).interfaceId; /// @dev See {ERC165Checker-supportsInterface} - ///> 0xd9b67a26 + ///> 0xd9b67a26 / 0x0e89341c bytes4 constant _ERC1155_CONTRACT = type(IERC1155).interfaceId; bytes4 constant _ERC1155_CONTRACT_METADATA_URI = type(IERC1155MetadataURI).interfaceId; @@ -56,8 +56,8 @@ contract NFTSpecChecker { _checkContract(targetContract, _ERC20_CONTRACT) && _checkContract(targetContract, _ERC20_CONTRACT_METADATA); } - function getERC20Code() external pure returns (string memory) { - return bytes4ToString(_ERC20_CONTRACT); + function getERC20Codes() external pure returns (string memory, string memory) { + return (bytes4ToString(_ERC20_CONTRACT), bytes4ToString(_ERC20_CONTRACT_METADATA)); } function checkERC721(address targetContract) external view returns (bool) { @@ -66,8 +66,8 @@ contract NFTSpecChecker { _checkContract(targetContract, _ERC721_CONTRACT_METADATA); } - function getERC721Code() external pure returns (string memory) { - return bytes4ToString(_ERC721_CONTRACT); + function getERC721Codes() external pure returns (string memory, string memory) { + return (bytes4ToString(_ERC721_CONTRACT), bytes4ToString(_ERC721_CONTRACT_METADATA)); } function checkERC1155(address targetContract) external view returns (bool) { @@ -76,8 +76,8 @@ contract NFTSpecChecker { _checkContract(targetContract, _ERC1155_CONTRACT_METADATA_URI); } - function getERC1155Code() external pure returns (string memory) { - return bytes4ToString(_ERC1155_CONTRACT); + function getERC1155Codes() external pure returns (string memory, string memory) { + return (bytes4ToString(_ERC1155_CONTRACT), bytes4ToString(_ERC1155_CONTRACT_METADATA_URI)); } function checkERC2981(address targetContract) external view returns (bool) { diff --git a/package.json b/package.json index b0d1dd3..2bf8014 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nftculture/nftc-contracts", - "version": "1.5.19", + "version": "1.5.20", "description": "NFTCulture Open Source Contracts Project", "author": "@NFTCulture", "license": "MIT", diff --git a/test/contracts/utility/introspection/NFTSpecChecker.test.ts b/test/contracts/utility/introspection/NFTSpecChecker.test.ts index 8eb60e7..e8740ca 100644 --- a/test/contracts/utility/introspection/NFTSpecChecker.test.ts +++ b/test/contracts/utility/introspection/NFTSpecChecker.test.ts @@ -10,7 +10,7 @@ dotenv.config(); const TESTHARNESS_CONTRACT_NAME = 'TemplateTestHarness'; const SPEC_CHECKER_CONTRACT_NAME = 'NFTSpecChecker'; -const MOCK_E20_CONTRACT_NAME = 'MockERC20'; +const MOCK_E20_CONTRACT_NAME = 'MockERC20_165'; const MOCK_E721AB_CONTRACT_NAME = 'MockERC721ABurnable'; const MOCK_E1155_CONTRACT_NAME = 'MockERC1155'; @@ -91,14 +91,17 @@ describe(`File:${__filename}\nContract: ${TESTHARNESS_CONTRACT_NAME}\n`, functio context('Spec Checker:', function () { it('uses valid interface codes', async function () { - const erc20Code = await _specCheckerInstance.connect(this.owner).getERC20Code(); + const [erc20Code, erc20MetadataCode] = await _specCheckerInstance.connect(this.owner).getERC20Codes(); expect(erc20Code).to.equal('0x36372b07'); + expect(erc20MetadataCode).to.equal('0xa219a025'); - const erc721Code = await _specCheckerInstance.connect(this.owner).getERC721Code(); + const [erc721Code, erc721MetadataCode] = await _specCheckerInstance.connect(this.owner).getERC721Codes(); expect(erc721Code).to.equal('0x80ac58cd'); + expect(erc721MetadataCode).to.equal('0x5b5e139f'); - const erc1155Code = await _specCheckerInstance.connect(this.owner).getERC1155Code(); + const [erc1155Code, erc1155MetadataCode] = await _specCheckerInstance.connect(this.owner).getERC1155Codes(); expect(erc1155Code).to.equal('0xd9b67a26'); + expect(erc1155MetadataCode).to.equal('0x0e89341c'); const erc2981Code = await _specCheckerInstance.connect(this.owner).getERC2981Code(); expect(erc2981Code).to.equal('0x2a55205a'); @@ -107,7 +110,7 @@ describe(`File:${__filename}\nContract: ${TESTHARNESS_CONTRACT_NAME}\n`, functio expect(erc7572Code).to.equal('0xe8a3d485'); }); - it.skip('can validate ERC20s.', async function () { + it('can validate ERC20s.', async function () { // NOTE: Technically OZ ERC20 doesn't implement ERC165. Hopefully at some point they fix this. const result = await _specCheckerInstance.connect(this.owner).checkERC20(_mockERC20Instance.address);