From 3754a86ab203095c880e93aae3a12bf28be1fbcb Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Wed, 8 Apr 2020 17:08:49 -0500 Subject: [PATCH] Add support for interfaceID. --- Changelog.md | 2 +- docs/units-and-global-variables.rst | 9 +++ libsolidity/analysis/TypeChecker.cpp | 2 + libsolidity/analysis/ViewPureChecker.cpp | 1 + libsolidity/ast/AST.cpp | 16 +++-- libsolidity/ast/AST.h | 6 +- libsolidity/ast/Types.cpp | 4 +- libsolidity/codegen/ExpressionCompiler.cpp | 9 +++ .../codegen/ir/IRGeneratorForStatements.cpp | 13 ++++ .../semanticTests/interfaceID/homer.sol | 36 ++++++++++ .../interfaceID/homer_interfaceId.sol | 36 ++++++++++ .../interfaceID/interfaceId_events.sol | 21 ++++++ .../semanticTests/interfaceID/interfaces.sol | 67 +++++++++++++++++++ .../semanticTests/interfaceID/lisa.sol | 47 +++++++++++++ .../interfaceID/lisa_interfaceId.sol | 47 +++++++++++++ 15 files changed, 304 insertions(+), 12 deletions(-) create mode 100644 test/libsolidity/semanticTests/interfaceID/homer.sol create mode 100644 test/libsolidity/semanticTests/interfaceID/homer_interfaceId.sol create mode 100644 test/libsolidity/semanticTests/interfaceID/interfaceId_events.sol create mode 100644 test/libsolidity/semanticTests/interfaceID/interfaces.sol create mode 100644 test/libsolidity/semanticTests/interfaceID/lisa.sol create mode 100644 test/libsolidity/semanticTests/interfaceID/lisa_interfaceId.sol diff --git a/Changelog.md b/Changelog.md index 2ddb74055953..607a0f02b65c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,11 +4,11 @@ Important Bugfixes: * Fix tuple assignments with components occupying multiple stack slots and different stack size on left- and right-hand-side. Language Features: + * Add support for EIP 165 interface identifiers with `type(I).interfaceId`. Compiler Features: - Bugfixes: * AST export: Export `immutable` property in the field `mutability`. * SMTChecker: Fix internal error in the CHC engine when calling inherited functions internally. diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 6c625e997c2d..caf3bebacbe9 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -310,3 +310,12 @@ available for a contract type ``C``: regular calls. The same restrictions as with ``.creationCode`` also apply for this property. + +In addition to the properties above, the following properties are available +for an interface type ``I``: + +``type(I).interfaceId``: + A ``bytes4`` value containing the `EIP-165 `_ + interface identifier of the given interface ``I``. This identifier is defined as the ``XOR`` of all + function selectors defined within the interface itself - excluding all inherited functions. + diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 2bdc706a2539..a252189dd38f 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -2617,6 +2617,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) } else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name") annotation.isPure = true; + else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "interfaceId") + annotation.isPure = true; } return false; diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 2468b03389a1..8fe35b1bc7c0 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -355,6 +355,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) {MagicType::Kind::MetaType, "creationCode"}, {MagicType::Kind::MetaType, "runtimeCode"}, {MagicType::Kind::MetaType, "name"}, + {MagicType::Kind::MetaType, "interfaceId"}, }; set static const payableMembers{ {MagicType::Kind::Message, "value"} diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index d9425937f12e..740317b15436 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -96,9 +96,9 @@ bool ContractDefinition::derivesFrom(ContractDefinition const& _base) const return util::contains(annotation().linearizedBaseContracts, &_base); } -map, FunctionTypePointer> ContractDefinition::interfaceFunctions() const +map, FunctionTypePointer> ContractDefinition::interfaceFunctions(bool _includeInheritedFunctions) const { - auto exportedFunctionList = interfaceFunctionList(); + auto exportedFunctionList = interfaceFunctionList(_includeInheritedFunctions); map, FunctionTypePointer> exportedFunctions; for (auto const& it: exportedFunctionList) @@ -174,14 +174,16 @@ vector const& ContractDefinition::interfaceEvents() cons return *m_interfaceEvents; } -vector, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList() const +vector, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const { - if (!m_interfaceFunctionList) + if (!m_interfaceFunctionList[_includeInheritedFunctions]) { set signaturesSeen; - m_interfaceFunctionList = make_unique, FunctionTypePointer>>>(); + m_interfaceFunctionList[_includeInheritedFunctions] = make_unique, FunctionTypePointer>>>(); for (ContractDefinition const* contract: annotation().linearizedBaseContracts) { + if (_includeInheritedFunctions == false && contract != this) + continue; vector functions; for (FunctionDefinition const* f: contract->definedFunctions()) if (f->isPartOfExternalInterface()) @@ -199,12 +201,12 @@ vector, FunctionTypePointer>> const& ContractDefinition: { signaturesSeen.insert(functionSignature); util::FixedHash<4> hash(util::keccak256(functionSignature)); - m_interfaceFunctionList->emplace_back(hash, fun); + m_interfaceFunctionList[_includeInheritedFunctions]->emplace_back(hash, fun); } } } } - return *m_interfaceFunctionList; + return *m_interfaceFunctionList[_includeInheritedFunctions]; } TypePointer ContractDefinition::type() const diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index c0f7e6dc97c7..684c86906ea4 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -488,8 +488,8 @@ class ContractDefinition: public Declaration, public StructurallyDocumented /// @returns a map of canonical function signatures to FunctionDefinitions /// as intended for use by the ABI. - std::map, FunctionTypePointer> interfaceFunctions() const; - std::vector, FunctionTypePointer>> const& interfaceFunctionList() const; + std::map, FunctionTypePointer> interfaceFunctions(bool _includeInheritedFunctions = true) const; + std::vector, FunctionTypePointer>> const& interfaceFunctionList(bool _includeInheritedFunctions = true) const; /// @returns a list of all declarations in this contract std::vector declarations() const { return filteredNodes(m_subNodes); } @@ -528,7 +528,7 @@ class ContractDefinition: public Declaration, public StructurallyDocumented ContractKind m_contractKind; bool m_abstract{false}; - mutable std::unique_ptr, FunctionTypePointer>>> m_interfaceFunctionList; + mutable std::unique_ptr, FunctionTypePointer>>> m_interfaceFunctionList[2]; mutable std::unique_ptr> m_interfaceEvents; }; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 2238e86f37b0..279666b7db3e 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -3719,7 +3719,9 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const {"name", TypeProvider::stringMemory()}, }); else - return {}; + return MemberList::MemberMap({ + {"interfaceId", TypeProvider::fixedBytes(4)}, + }); } } solAssert(false, "Unknown kind of magic."); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index a02497561279..ab4e120c072a 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1580,6 +1580,15 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; utils().storeStringData(contract.name()); } + else if (member == "interfaceId") + { + TypePointer arg = dynamic_cast(*_memberAccess.expression().annotation().type).typeArgument(); + ContractDefinition const& contract = dynamic_cast(*arg).contractDefinition(); + uint64_t result{0}; + for (auto const& function: contract.interfaceFunctionList(false)) + result ^= fromBigEndian(function.first.ref()); + m_context << (u256{result} << (256 - 32)); + } else if ((set{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}).count(member)) { // no-op diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 57c8cb152903..d14d587531a6 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -754,6 +754,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) } break; } + case FunctionType::Kind::MetaType: + { + break; + } default: solUnimplemented("FunctionKind " + toString(static_cast(functionType->kind())) + " not yet implemented"); } @@ -904,6 +908,15 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) { solUnimplementedAssert(false, ""); } + else if (member == "interfaceId") + { + TypePointer arg = dynamic_cast(*_memberAccess.expression().annotation().type).typeArgument(); + ContractDefinition const& contract = dynamic_cast(*arg).contractDefinition(); + uint64_t result{0}; + for (auto const& function: contract.interfaceFunctionList(false)) + result ^= fromBigEndian(function.first.ref()); + define(_memberAccess) << formatNumber(u256{result} << (256 - 32)) << "\n"; + } else if (set{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member)) { // no-op diff --git a/test/libsolidity/semanticTests/interfaceID/homer.sol b/test/libsolidity/semanticTests/interfaceID/homer.sol new file mode 100644 index 000000000000..243cba0b4edd --- /dev/null +++ b/test/libsolidity/semanticTests/interfaceID/homer.sol @@ -0,0 +1,36 @@ +interface ERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} + +interface Simpson { + function is2D() external returns (bool); + function skinColor() external returns (string memory); +} + +contract Homer is ERC165, Simpson { + function supportsInterface(bytes4 interfaceID) external view override returns (bool) { + return + interfaceID == this.supportsInterface.selector || // ERC165 + interfaceID == this.is2D.selector ^ this.skinColor.selector; // Simpson + } + + function is2D() external override returns (bool) { + return true; + } + + function skinColor() external override returns (string memory) { + return "yellow"; + } +} + +// ---- +// supportsInterface(bytes4): left(0x01ffc9a0) -> false +// supportsInterface(bytes4): left(0x01ffc9a7) -> true +// supportsInterface(bytes4): left(0x73b6b492) -> true +// supportsInterface(bytes4): left(0x70b6b492) -> false diff --git a/test/libsolidity/semanticTests/interfaceID/homer_interfaceId.sol b/test/libsolidity/semanticTests/interfaceID/homer_interfaceId.sol new file mode 100644 index 000000000000..d2fa2e821e59 --- /dev/null +++ b/test/libsolidity/semanticTests/interfaceID/homer_interfaceId.sol @@ -0,0 +1,36 @@ +interface ERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} + +interface Simpson { + function is2D() external returns (bool); + function skinColor() external returns (string memory); +} + +contract Homer is ERC165, Simpson { + function supportsInterface(bytes4 interfaceID) external view override returns (bool) { + return + interfaceID == type(ERC165).interfaceId || + interfaceID == type(Simpson).interfaceId; + } + + function is2D() external override returns (bool) { + return true; + } + + function skinColor() external override returns (string memory) { + return "yellow"; + } +} + +// ---- +// supportsInterface(bytes4): left(0x01ffc9a0) -> false +// supportsInterface(bytes4): left(0x01ffc9a7) -> true +// supportsInterface(bytes4): left(0x73b6b492) -> true +// supportsInterface(bytes4): left(0x70b6b492) -> false diff --git a/test/libsolidity/semanticTests/interfaceID/interfaceId_events.sol b/test/libsolidity/semanticTests/interfaceID/interfaceId_events.sol new file mode 100644 index 000000000000..09cb6c0d4f76 --- /dev/null +++ b/test/libsolidity/semanticTests/interfaceID/interfaceId_events.sol @@ -0,0 +1,21 @@ +interface HelloWorld { + function hello() external pure; + function world(int) external pure; +} + +interface HelloWorldWithEvent { + event Event(); + function hello() external pure; + function world(int) external pure; +} + +contract Test { + bytes4 public hello_world = type(HelloWorld).interfaceId; + bytes4 public hello_world_with_event = type(HelloWorldWithEvent).interfaceId; +} + +// ==== +// compileViaYul: also +// ---- +// hello_world() -> left(0xc6be8b58) +// hello_world_with_event() -> left(0xc6be8b58) diff --git a/test/libsolidity/semanticTests/interfaceID/interfaces.sol b/test/libsolidity/semanticTests/interfaceID/interfaces.sol new file mode 100644 index 000000000000..ba63cfb12483 --- /dev/null +++ b/test/libsolidity/semanticTests/interfaceID/interfaces.sol @@ -0,0 +1,67 @@ +interface HelloWorld { + function hello() external pure; + function world(int) external pure; +} + +interface HelloWorldDerived is HelloWorld { + function other() external pure; +} + +interface ERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} + +contract Test { + bytes4 public ghello_world_interfaceId = type(HelloWorld).interfaceId; + bytes4 public ERC165_interfaceId = type(ERC165).interfaceId; + + function hello() public pure returns (bytes4 data){ + HelloWorld i; + return i.hello.selector; + } + + function world() public pure returns (bytes4 data){ + HelloWorld i; + return i.world.selector; + } + + function hello_world() public pure returns (bytes4 data){ + // HelloWorld i; + // return i.hello.selector ^ i.world.selector; // = 0xc6be8b58 + return 0xc6be8b58; + } + + function hello_world_interfaceId() public pure returns (bytes4 data){ + return type(HelloWorld).interfaceId; + } + + function other() public pure returns (bytes4 data){ + HelloWorldDerived i; + return i.other.selector; + } + + function hello_world_derived_interfaceId() public pure returns (bytes4 data){ + return type(HelloWorldDerived).interfaceId; + } +} + +// ==== +// compileViaYul: also +// ---- +// hello() -> left(0x19ff1d21) +// world() -> left(0xdf419679) +// +// ERC165_interfaceId() -> left(0x01ffc9a7) +// +// hello_world() -> left(0xc6be8b58) +// hello_world_interfaceId() -> left(0xc6be8b58) +// ghello_world_interfaceId() -> left(0xc6be8b58) +// +// other() -> left(0x85295877) +// hello_world_derived_interfaceId() -> left(0x85295877) diff --git a/test/libsolidity/semanticTests/interfaceID/lisa.sol b/test/libsolidity/semanticTests/interfaceID/lisa.sol new file mode 100644 index 000000000000..ea844dd819b9 --- /dev/null +++ b/test/libsolidity/semanticTests/interfaceID/lisa.sol @@ -0,0 +1,47 @@ +interface ERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} + +contract ERC165MappingImplementation is ERC165 { + /// @dev You must not set element 0xffffffff to true + mapping(bytes4 => bool) internal supportedInterfaces; + + constructor() internal { + supportedInterfaces[this.supportsInterface.selector] = true; + } + + function supportsInterface(bytes4 interfaceID) external view override returns (bool) { + return supportedInterfaces[interfaceID]; + } +} + +interface Simpson { + function is2D() external returns (bool); + function skinColor() external returns (string memory); +} + +contract Lisa is ERC165MappingImplementation, Simpson { + constructor() public { + supportedInterfaces[this.is2D.selector ^ this.skinColor.selector] = true; + } + + function is2D() external override returns (bool) { + return true; + } + + function skinColor() external override returns (string memory) { + return "yellow"; + } +} + +// ---- +// supportsInterface(bytes4): left(0x01ffc9a0) -> false +// supportsInterface(bytes4): left(0x01ffc9a7) -> true +// supportsInterface(bytes4): left(0x73b6b492) -> true +// supportsInterface(bytes4): left(0x70b6b492) -> false diff --git a/test/libsolidity/semanticTests/interfaceID/lisa_interfaceId.sol b/test/libsolidity/semanticTests/interfaceID/lisa_interfaceId.sol new file mode 100644 index 000000000000..9c9dd5eb7fc7 --- /dev/null +++ b/test/libsolidity/semanticTests/interfaceID/lisa_interfaceId.sol @@ -0,0 +1,47 @@ +interface ERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} + +contract ERC165MappingImplementation is ERC165 { + /// @dev You must not set element 0xffffffff to true + mapping(bytes4 => bool) internal supportedInterfaces; + + constructor() internal { + supportedInterfaces[this.supportsInterface.selector] = true; + } + + function supportsInterface(bytes4 interfaceID) external view override returns (bool) { + return supportedInterfaces[interfaceID]; + } +} + +interface Simpson { + function is2D() external returns (bool); + function skinColor() external returns (string memory); +} + +contract Lisa is ERC165MappingImplementation, Simpson { + constructor() public { + supportedInterfaces[type(Simpson).interfaceId] = true; + } + + function is2D() external override returns (bool) { + return true; + } + + function skinColor() external override returns (string memory) { + return "yellow"; + } +} + +// ---- +// supportsInterface(bytes4): left(0x01ffc9a0) -> false +// supportsInterface(bytes4): left(0x01ffc9a7) -> true +// supportsInterface(bytes4): left(0x73b6b492) -> true +// supportsInterface(bytes4): left(0x70b6b492) -> false