Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for interfaceId. #8642

Merged
merged 1 commit into from
Apr 23, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should have used EIP-165 😉



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.
Expand Down
9 changes: 9 additions & 0 deletions docs/units-and-global-variables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://eips.ethereum.org/EIPS/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.

2 changes: 2 additions & 0 deletions libsolidity/analysis/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions libsolidity/analysis/ViewPureChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<MagicMember> static const payableMembers{
{MagicType::Kind::Message, "value"}
Expand Down
16 changes: 9 additions & 7 deletions libsolidity/ast/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ bool ContractDefinition::derivesFrom(ContractDefinition const& _base) const
return util::contains(annotation().linearizedBaseContracts, &_base);
}

map<util::FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions() const
map<util::FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions(bool _includeInheritedFunctions) const
{
auto exportedFunctionList = interfaceFunctionList();
auto exportedFunctionList = interfaceFunctionList(_includeInheritedFunctions);

map<util::FixedHash<4>, FunctionTypePointer> exportedFunctions;
for (auto const& it: exportedFunctionList)
Expand Down Expand Up @@ -174,14 +174,16 @@ vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() cons
return *m_interfaceEvents;
}

vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList() const
vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const
{
if (!m_interfaceFunctionList)
if (!m_interfaceFunctionList[_includeInheritedFunctions])
{
set<string> signaturesSeen;
m_interfaceFunctionList = make_unique<vector<pair<util::FixedHash<4>, FunctionTypePointer>>>();
m_interfaceFunctionList[_includeInheritedFunctions] = make_unique<vector<pair<util::FixedHash<4>, FunctionTypePointer>>>();
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
{
if (_includeInheritedFunctions == false && contract != this)
continue;
vector<FunctionTypePointer> functions;
for (FunctionDefinition const* f: contract->definedFunctions())
if (f->isPartOfExternalInterface())
Expand All @@ -199,12 +201,12 @@ vector<pair<util::FixedHash<4>, 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
Expand Down
6 changes: 3 additions & 3 deletions libsolidity/ast/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<util::FixedHash<4>, FunctionTypePointer> interfaceFunctions() const;
std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>> const& interfaceFunctionList() const;
std::map<util::FixedHash<4>, FunctionTypePointer> interfaceFunctions(bool _includeInheritedFunctions = true) const;
std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>> const& interfaceFunctionList(bool _includeInheritedFunctions = true) const;

/// @returns a list of all declarations in this contract
std::vector<Declaration const*> declarations() const { return filteredNodes<Declaration>(m_subNodes); }
Expand Down Expand Up @@ -528,7 +528,7 @@ class ContractDefinition: public Declaration, public StructurallyDocumented
ContractKind m_contractKind;
bool m_abstract{false};

mutable std::unique_ptr<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList;
mutable std::unique_ptr<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList[2];
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents;
};

Expand Down
4 changes: 3 additions & 1 deletion libsolidity/ast/Types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down
9 changes: 9 additions & 0 deletions libsolidity/codegen/ExpressionCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
uint64_t result{0};
for (auto const& function: contract.interfaceFunctionList(false))
result ^= fromBigEndian<uint64_t>(function.first.ref());
m_context << (u256{result} << (256 - 32));
}
else if ((set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}).count(member))
{
// no-op
Expand Down
13 changes: 13 additions & 0 deletions libsolidity/codegen/ir/IRGeneratorForStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
}
break;
}
case FunctionType::Kind::MetaType:
{
break;
}
default:
solUnimplemented("FunctionKind " + toString(static_cast<int>(functionType->kind())) + " not yet implemented");
}
Expand Down Expand Up @@ -904,6 +908,15 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
{
solUnimplementedAssert(false, "");
}
else if (member == "interfaceId")
{
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
uint64_t result{0};
for (auto const& function: contract.interfaceFunctionList(false))
result ^= fromBigEndian<uint64_t>(function.first.ref());
define(_memberAccess) << formatNumber(u256{result} << (256 - 32)) << "\n";
}
else if (set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member))
{
// no-op
Expand Down
36 changes: 36 additions & 0 deletions test/libsolidity/semanticTests/interfaceID/homer.sol
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions test/libsolidity/semanticTests/interfaceID/homer_interfaceId.sol
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions test/libsolidity/semanticTests/interfaceID/interfaceId_events.sol
Original file line number Diff line number Diff line change
@@ -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)
67 changes: 67 additions & 0 deletions test/libsolidity/semanticTests/interfaceID/interfaces.sol
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HelloWorld.hello.selector does not work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, it seem to work. At least if Yul is not used. If Yul is used, we got the following error:

Exception during extracted test: /Users/alex/git/solidity/libsolidity/codegen/ir/IRGeneratorForStatements.cpp(988): Throw in function virtual void solidity::frontend::IRGeneratorForStatements::endVisit(const solidity::frontend::MemberAccess &)
Dynamic exception type: boost::wrapexcept<solidity::langutil::InternalCompilerError>
std::exception::what: Member access to unknown type.
[solidity::util::tag_comment*] = Member access to unknown type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

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;
aarlt marked this conversation as resolved.
Show resolved Hide resolved
}

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)
47 changes: 47 additions & 0 deletions test/libsolidity/semanticTests/interfaceID/lisa.sol
Original file line number Diff line number Diff line change
@@ -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
Loading