tip: 165
title: TRC-165 Standard Interface Detection In Contract
author: timothychung [email protected]
discussions to: https://github.com/tronprotocol/tips/issues/200
status: Draft
type: Standards Track
category : TRC
created: 2020-11-20
A standard method to publish and detect what interfaces a smart contract have implemented.
This TIP is compatible with EIP-165, standardize the following in TRON version:
-
How interfaces are identified
-
How a contract will publish the interfaces it implements
-
How to detect if a contract implements ERC-165
-
How to detect if a contract implements any given interface
We don't have a standard interface to check the contract if support some "standard interfaces" like TRC-20 token interface. It's hard to interact with other contracts. For the purpose of the interactivity. This proposal standardizes the concept and identification of interfaces.
For this standard, an interface is a set of function selectors as defined by the Ethereum ABI. This a subset of Solidity’s concept of interfaces and the interface
keyword definition which also defines return types, mutability and events.
We define the interface identifier as the XOR of all function selectors in the interface. This code example shows how to calculate an interface identifier:
pragma solidity ^0.4.20;
interface Solidity101 {
function hello() external pure;
function world(int) external pure;
}
contract Selector {
function calculateSelector() public pure returns (bytes4) {
Solidity101 i;
return i.hello.selector ^ i.world.selector;
}
}
Note: interfaces do not permit optional functions, therefore, the interface identity will not include them.
A contract that is compliant with TRC-165 shall implement the following interface (referred as TRC165.sol
):
pragma solidity ^0.4.20;
interface TRC165 {
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in TRC-165
/// @dev Interface identification is specified in TRC-165.
/// uses less than 30,000 energy.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
The interface identifier for this interface is 0x01ffc9a7
. You can calculate this by running bytes4(keccak256(‘supportsInterface(bytes4)’));
or using the Selector
contract above.
Therefore the implementing contract will have a supportsInterface
function that returns:
true
wheninterfaceID
is0x01ffc9a7
(TIP165 interface)false
wheninterfaceID
is0xffffffff
true
for any otherinterfaceID
this contract implementsfalse
for any otherinterfaceID
This function must return a bool and use at most 30,000 energy.
Implementation note, there are several logical ways to implement this function. Please see the example implementations and the discussion on energy usage.
- The source contract makes a
STATICCALL
to the destination address with input data:0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000
and energy 30,000. This corresponds tocontract.supportsInterface(0x01ffc9a7)
. - If the call fails or return false, the destination contract does not implement TRC-165.
- If the call returns true, a second call is made with input data
0x01ffc9a7ffffffff00000000000000000000000000000000000000000000000000000000
. - If the second call fails or returns true, the destination contract does not implement TRC-165.
- Otherwise it implements TRC-165.
- If you are not sure if the contract implements TRC-165, use the above procedure to confirm.
- If it does not implement TRC-165, then you will have to see what methods it uses the old-fashioned way.
- If it implements TRC-165 then just call
supportsInterface(interfaceID)
to determine if it implements an interface you can use.
We tried to keep this specification as simple as possible. This implementation is also compatible with the current Solidity version.
The mechanism described above (with 0xffffffff
) should work with most of the contracts previous to this standard to determine that they do not implement TRC-165.
Following is a contract that detects which interfaces other contracts implement.
pragma solidity ^0.4.20;
contract TRC165Query {
bytes4 constant InvalidID = 0xffffffff;
bytes4 constant TRC165ID = 0x01ffc9a7;
function doesContractImplementInterface(address _contract, bytes4 _interfaceId) external view returns (bool) {
uint256 success;
uint256 result;
(success, result) = noThrowCall(_contract, TRC165ID);
if ((success==0)||(result==0)) {
return false;
}
(success, result) = noThrowCall(_contract, InvalidID);
if ((success==0)||(result!=0)) {
return false;
}
(success, result) = noThrowCall(_contract, _interfaceId);
if ((success==1)&&(result==1)) {
return true;
}
return false;
}
function noThrowCall(address _contract, bytes4 _interfaceId) constant internal returns (uint256 success, uint256 result) {
bytes4 trc165ID = TRC165ID;
assembly {
let x := mload(0x40) // Find empty storage location using "free memory pointer"
mstore(x, trc165ID) // Place signature at beginning of empty storage
mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature
success := staticcall(
30000, // 30k energy
_contract, // To addr
x, // Inputs are stored at location x
0x24, // Inputs are 36 bytes long
x, // Store output over input (saves space)
0x20) // Outputs are 32 bytes long
result := mload(x) // Load the result
}
}
}
This approach uses a view
function implementation of supportsInterface
. The TRC165MappingImplementation
contract is generic and reusable.
pragma solidity ^0.4.20;
import "./TRC165.sol";
contract TRC165MappingImplementation is TRC165 {
/// @dev You must not set element 0xffffffff to true
mapping(bytes4 => bool) internal supportedInterfaces;
function TRC165MappingImplementation() internal {
supportedInterfaces[this.supportsInterface.selector] = true;
}
function supportsInterface(bytes4 interfaceID) external view returns (bool) {
return supportedInterfaces[interfaceID];
}
}
interface Simpson {
function is2D() external returns (bool);
function skinColor() external returns (string);
}
contract Lisa is TRC165MappingImplementation, Simpson {
function Lisa() public {
supportedInterfaces[this.is2D.selector ^ this.skinColor.selector] = true;
}
function is2D() external returns (bool){}
function skinColor() external returns (string){}
}
Following is a pure
function implementation of supportsInterface
.
pragma solidity ^0.4.20;
import "./TRC165.sol";
interface Simpson {
function is2D() external returns (bool);
function skinColor() external returns (string);
}
contract Homer is TRC165, Simpson {
function supportsInterface(bytes4 interfaceID) external view returns (bool) {
return
interfaceID == this.supportsInterface.selector || // TRC165
interfaceID == this.is2D.selector
^ this.skinColor.selector; // Simpson
}
function is2D() external returns (bool){}
function skinColor() external returns (string){}
}
With three or more supported interfaces (including TRC165 itself as a required supported interface), the mapping approach (in every case) costs less energy than the pure approach (at worst case).
- PR 1640, finalized 2019-01-23 – This corrects the noThrowCall test case to use 36 bytes rather than the previous 32 bytes. The previous code was an error that still silently worked in Solidity 0.4.x but which was broken by new behavior introduced in Solidity 0.5.0. This change was discussed at #1640.
- EIP 165, finalized 2018-04-20 – Original published version.
Copyright and related rights waived via CC0.
Please cite this document as:
Christian Reitwießner, Nick Johnson, Fabian Vogelsteller, Jordi Baylina, Konrad Feldmeier, William Entriken, "EIP-165: ERC-165 Standard Interface Detection," Ethereum Improvement Proposals, no. 165, January 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-165.