From 1f017ede8810542a7d6b3bf4650656a9ddd67745 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. --- libsolidity/analysis/TypeChecker.cpp | 2 + libsolidity/analysis/ViewPureChecker.cpp | 1 + libsolidity/ast/AST.cpp | 16 +++-- libsolidity/ast/AST.h | 6 +- libsolidity/ast/Types.cpp | 6 +- libsolidity/codegen/ExpressionCompiler.cpp | 11 +++ .../codegen/ir/IRGeneratorForStatements.cpp | 4 ++ .../semanticTests/interfaceId/interfaces.sol | 66 +++++++++++++++++ .../semanticTests/interfaceId/simple.sol | 70 +++++++++++++++++++ 9 files changed, 171 insertions(+), 11 deletions(-) create mode 100644 test/libsolidity/semanticTests/interfaceId/interfaces.sol create mode 100644 test/libsolidity/semanticTests/interfaceId/simple.sol 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..6822bb293086 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 _includeDerivedFunctions) const { - auto exportedFunctionList = interfaceFunctionList(); + auto exportedFunctionList = interfaceFunctionList(_includeDerivedFunctions); 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 _includeDerivedFunctions) const { - if (!m_interfaceFunctionList) + if (!m_interfaceFunctionList[_includeDerivedFunctions]) { set signaturesSeen; - m_interfaceFunctionList = make_unique, FunctionTypePointer>>>(); + m_interfaceFunctionList[_includeDerivedFunctions] = make_unique, FunctionTypePointer>>>(); for (ContractDefinition const* contract: annotation().linearizedBaseContracts) { + if (_includeDerivedFunctions == 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[_includeDerivedFunctions]->emplace_back(hash, fun); } } } } - return *m_interfaceFunctionList; + return *m_interfaceFunctionList[_includeDerivedFunctions]; } TypePointer ContractDefinition::type() const diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index c0f7e6dc97c7..02130ec87cac 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 _includeDerivedFunctions = true) const; + std::vector, FunctionTypePointer>> const& interfaceFunctionList(bool _includeDerivedFunctions = 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::map, FunctionTypePointer>>>> m_interfaceFunctionList; mutable std::unique_ptr> m_interfaceEvents; }; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 2238e86f37b0..08559c5c6075 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -3717,9 +3717,13 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const {"creationCode", TypeProvider::array(DataLocation::Memory)}, {"runtimeCode", TypeProvider::array(DataLocation::Memory)}, {"name", TypeProvider::stringMemory()}, + {"interfaceId", TypeProvider::fixedBytes(4)}, }); else - return {}; + return MemberList::MemberMap({ + {"name", TypeProvider::stringMemory()}, + {"interfaceId", TypeProvider::fixedBytes(4)}, + }); } } solAssert(false, "Unknown kind of magic."); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index a02497561279..43d0a89e80b1 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1580,6 +1580,17 @@ 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(); + u256 result{0}; + for (auto const& function: contract.interfaceFunctionList(false)) + result = result ^ u256(function.first.asBytes()); + m_context << result; + /// need to store it as bytes4 + utils().leftShiftNumberOnStack(224); + } 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..ca9e23b0ceae 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -904,6 +904,10 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) { solUnimplementedAssert(false, ""); } + else if (member == "interfaceId") + { + solUnimplementedAssert(false, ""); + } else if (set{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member)) { // no-op diff --git a/test/libsolidity/semanticTests/interfaceId/interfaces.sol b/test/libsolidity/semanticTests/interfaceId/interfaces.sol new file mode 100644 index 000000000000..68365e3692e5 --- /dev/null +++ b/test/libsolidity/semanticTests/interfaceId/interfaces.sol @@ -0,0 +1,66 @@ +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; + string public name = type(ERC165).name; + 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; + } + + 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; + } +} + +// ---- +// name() -> 0x20, 6, "ERC165" +// 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/simple.sol b/test/libsolidity/semanticTests/interfaceId/simple.sol new file mode 100644 index 000000000000..6e493a4a08a7 --- /dev/null +++ b/test/libsolidity/semanticTests/interfaceId/simple.sol @@ -0,0 +1,70 @@ +contract HelloWorld { + function hello() external pure { + } + function world(int) external pure { + } +} + +contract HelloWorldDerived is HelloWorld { + function other() external pure { + } +} + +contract 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; + string public name = type(ERC165).name; + + 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; + } + + 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; + } +} + +// ---- +// name() -> 0x20, 6, "ERC165" +// 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)