ERC165:
https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md
就是一种发布并能检测到一个智能合约实现了什么接口的标准
这么做的原因:
it is sometimes useful to query whether a contract supports the interface and if yes, which version of the interface, in order to adapt the way in which the contract is to be interacted with
首先我们是怎么表明一个interface的——使用selector:
举例:
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; } }
在这个例子中有一个接口,表明一个接口的方法就是生成一个interfaceID,即(Solidity101 i;)interfaceID = i.hello.selector ^ i.world.selector,有多少个函数就并多少个函数.selector
然后合约是怎么发布它实现的接口的:
pragma solidity ^0.4.20; 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); }
我们在这里以ERC165 token标准为例,如果合约兼容了ERC165,那就说明该合约一定实现了上面的接口,因为它只有一个函数,所以它的(ERC165 i;)interfaceID = i.supportsInterface.selector,或者使用bytes4(keccak256('supportsInterface(bytes4)'))也一样,结果相同
Therefore the implementing contract will have a supportsInterface
function that returns:
就是说那这个兼容ERC165的合约它的supportsInterface函数里面应该要实现的内容就是:
因为这个合约可能不仅只有兼容ERC165,可能还兼容了其他接口,那么它应该在interfaceID
为0x01ffc9a7或其他兼容接口的
interfaceID
作为它覆写的supportsInterface函数的参数时,返回true(使用 || 判断)
true
wheninterfaceID
is0x01ffc9a7
(EIP165 interface)true
for any otherinterfaceID
this contract implements
然后在
为interfaceID
0xffffffff
或者为其他没有兼容的接口的interfaceID时返回false
false
wheninterfaceID
is0xffffffff
false
for any otherinterfaceID
This function must return a bool and use at most 30,000 gas.
比如:
pragma solidity ^0.4.20; import "./ERC165.sol"; interface Simpson { function is2D() external returns (bool); function skinColor() external returns (string); } contract Homer is ERC165, Simpson { function supportsInterface(bytes4 interfaceID) external view returns (bool) { return interfaceID == this.supportsInterface.selector || // ERC165 interfaceID == this.is2D.selector ^ this.skinColor.selector; // Simpson } function is2D() external returns (bool){} function skinColor() external returns (string){} }
这上面的这个例子就很好地解释了合约是怎么发布(即告诉别人)它实现了什么接口的
在这里合约Homer实现了两个接口ERC165和Simpson,(因为当你兼容了一个接口时,你一定是要覆写它的所有函数的),所以计算它的interfaceID的时候可以使用this.函数名.selector来计算interfaceID,this即代表本合约(在该例子中即合约Homer)
接下来就是如何检测一个合约是否使用了某些接口:
- If you are not sure if the contract implements ERC-165, use the above procedure to confirm.
- If it does not implement ERC-165, then you will have to see what methods it uses the old-fashioned way.
- If it implements ERC-165 then just call
supportsInterface(interfaceID)
to determine if it implements an interface you can use.
1)首先,你有没有使用ERC-165接口会导致你检测的方法有一些不同,所以我们一般先检测某合约是不是使用了ERC-165接口,即通过使用solidity的汇编语言:
- The source contact makes a
STATICCALL
to the destination address with input data:0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000
and gas 30,000. This corresponds tocontract.supportsInterface(0x01ffc9a7)
. - If the call fails or return false, the destination contract does not implement ERC-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 ERC-165.
- Otherwise it implements ERC-165.
success := staticcall( 30000, // 30k gas,g使用的gas _address, // To addr,a合约地址
//in,input data,其实就是0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000
//前面是合约签名0x01ffc9a7,代表调用了函数
encodedParams_data, encodedParams_size, //insize,数据大小 output, //out,输出指针位置 0x20 // Outputs are 32 bytes long,0x20 == 32,outsize )supportsInterface(interfaceID)
,后面的即参数interfaceID,这里为01ffc9a7
从上面的步骤我们可以看见,要测试是否使用了ERC-165接口,要进行两步测试,即先测是否使用了接口0x01ffc9a7
以及是否没有使用接口0xffffffff
2)
1》然后如果你查出该合约使用了ERC-165接口,那么你就可以直接调用这个合约的supportsInterface(interfaceID)
函数去判断是否使用了其他的接口,因为一般你的supportsInterface(interfaceID)
函数就是用来实现这个功能的
2》如果你发现它没有使用ERC-165接口,那么你就只能使用比较笨的办法,就是人工对比查看了
所以一般你看见一个合约中使用了ERC-165接口的话,功能就是用来发布并测试使用了什么接口
上面的过程的实现例子:
https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/introspection/ERC165Checker.sol
pragma solidity ^0.4.24; /** * @title ERC165Checker * @dev Use `using ERC165Checker for address`; to include this library * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md */ library ERC165Checker { // As per the EIP-165 spec, no interface should ever match 0xffffffff bytes4 private constant InterfaceId_Invalid = 0xffffffff; bytes4 private constant InterfaceId_ERC165 = 0x01ffc9a7; /** * 0x01ffc9a7 === * bytes4(keccak256('supportsInterface(bytes4)')) */ /** * @notice Query if a contract implements an interface, also checks support of ERC165 * @param _address The address of the contract to query for support of an interface * @param _interfaceId The interface identifier, as specified in ERC-165 * @return true if the contract at _address indicates support of the interface with * identifier _interfaceId, false otherwise * @dev Interface identification is specified in ERC-165. */ //就是查看一个合约(使用合约地址address表明),是否支持某个接口(使用接口ID表明,该接口该接口是出了ERC165的supportsInterface(bytes4)即ID为0x01ffc9a7的其他接口) function supportsInterface(address _address, bytes4 _interfaceId) internal view returns (bool) { // query support of both ERC165 as per the spec and support of _interfaceId
//根据上面的过程,首先先查看是否支持ERC165,然后再使用supportsInterface(bytes4)去查看是否使用了接口_interfaceId
//这里是&&,所以是以实现了ERC165为前提在检测接口的 //当你合约支持ERC165且支持ERC165的接口时,就说明你支持 return supportsERC165(_address) && supportsERC165Interface(_address, _interfaceId); } /** * @notice Query if a contract implements interfaces, also checks support of ERC165 * @param _address The address of the contract to query for support of an interface * @param _interfaceIds A list of interface identifiers, as specified in ERC-165 * @return true if the contract at _address indicates support all interfaces in the * _interfaceIds list, false otherwise * @dev Interface identification is specified in ERC-165. */ //就是查看一个合约(使用合约地址address表明),是否支持多个接口(使用接口ID表明) function supportsInterfaces(address _address, bytes4[] _interfaceIds) internal view returns (bool) { // query support of ERC165 itself if (!supportsERC165(_address)) {//从这里就更能明显看出,如果你没有使用ERC165接口,就直接返回false了,所以是以实现了ERC165为前提在检测接口的 return false; } // query support of each interface in _interfaceIds for (uint256 i = 0; i < _interfaceIds.length; i++) { if (!supportsERC165Interface(_address, _interfaceIds[i])) { return false; } } // all interfaces supported return true; } /** * @notice Query if a contract supports ERC165 * @param _address The address of the contract to query for support of ERC165 * @return true if the contract at _address implements ERC165 */ //查看一个合约是否支持ERC165 function supportsERC165(address _address) internal view returns (bool) { // Any contract that implements ERC165 must explicitly indicate support of // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid //支持ERC165的合约都会显示地表现它支持ERC165的supportsInterface(bytes4)函数ID并且不支持接口ID为0xffffffff return supportsERC165Interface(_address, InterfaceId_ERC165) && !supportsERC165Interface(_address, InterfaceId_Invalid); } /** * @notice Query if a contract implements an interface, does not check ERC165 support * @param _address The address of the contract to query for support of an interface * @param _interfaceId The interface identifier, as specified in ERC-165 * @return true if the contract at _address indicates support of the interface with * identifier _interfaceId, false otherwise * @dev Assumes that _address contains a contract that supports ERC165, otherwise * the behavior of this method is undefined. This precondition can be checked * with the `supportsERC165` method in this library. * Interface identification is specified in ERC-165. */ function supportsERC165Interface(address _address, bytes4 _interfaceId) private view returns (bool) { // success determines whether the staticcall succeeded and result determines // whether the contract at _address indicates support of _interfaceId (bool success, bool result) = callERC165SupportsInterface( _address, _interfaceId); return (success && result); } /** * @notice Calls the function with selector 0x01ffc9a7 (ERC165) and suppresses throw * @param _address The address of the contract to query for support of an interface * @param _interfaceId The interface identifier, as specified in ERC-165 * @return success true if the STATICCALL succeeded, false otherwise * @return result true if the STATICCALL succeeded and the contract at _address * indicates support of the interface with identifier _interfaceId, false otherwise */
//调用staticcall来使用supportsInterface(bytes4)函数去查看使用接口的情况 function callERC165SupportsInterface( address _address, bytes4 _interfaceId ) private view returns (bool success, bool result) { //在使用ABI调用合约函数时,传入的ABI会被编码成calldata(一串hash值)。calldata由function signature和argument encoding两部分组成。通过读取call data的内容, EVM可以得知需要执行的函数,以及函数的传入值,并作出相应的操作。
//即形如0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000
//额外添加知识:Call Data: 是除了storage,memory的另一个数据保存位置,保存了inputdata。长度是4bytes+32bypes*n,n为参数个数,可通过CALLDATALOAD,CALLDATASIZE, CALLDATACOPY等指令进行操作 //首先要生成input data,一开始为4bytes,为函数supportsInterface(bytes4)的签名0x01ffc9a7,然后后面为32bypes的参数_interfaceId, bytes memory encodedParams = abi.encodeWithSelector(//对给定的参数进行ABI编码——从第二个预置给定的四字节选择器开始 InterfaceId_ERC165, _interfaceId );
//encodedParams = abi.encodeWithSelector(/
0x01ffc9a7,
0x01ffc9a6
);
返回:0x01ffc9a70000000000000000000000000000000000000000000000000000000001ffc9a6
//solidity的汇编语言 // solium-disable-next-line security/no-inline-assembly assembly { let encodedParams_data := add(0x20, encodedParams)//因为内存前32bits存储的是数据的长度,所以要想得到数据,要往后32bits读取 let encodedParams_size := mload(encodedParams)//得到该input data的大小,因为内存存储前32bits存储的是数据的长度 //solidity管理内存方式:内部存在一个空间内存的指针在内存位置0x40。如果你想分配内存,可以直接使用从那个位置的内存,并相应的更新指针。 //得到指向空内存位置0x40的指针 let output := mload(0x40) // Find empty storage location using "free memory pointer" //mem [a ... b]表示从位置a开始到(不包括)位置b的存储器的字节 //mstore(p, v)即mem[p..(p+32)] := v,在指针指向的内存位置后32个字节中填0,以免在过程中该内存位置被别的操作使用 mstore(output, 0x0) //staticcall(g, a, in, insize, out, outsize):identical to call(g, a, 0, in, insize, out, outsize) but do not allow state modifications,这个操作不会改变合约的状态 //call(g, a, v, in, insize, out, outsize) : call contract at address a with input mem[in..(in+insize)) providing g gas and v wei and // output area mem[out..(out+outsize)) returning 0 on error (eg. out of gas) and 1 on success success := staticcall( 30000, // 30k gas,g _address, // To addr,a encodedParams_data, //in encodedParams_size, //insize output, //out,指针位置 0x20 // Outputs are 32 bytes long,0x20 == 32,outsize ) //就是在地址_address(from)出调用合约,输入是mem[in..(in+insize)),即取得input data,即调用函数后,将得到的值放到内存位置mem[out..(out+outsize))处 //过程中使用了30000 gas和0 wei result := mload(output) // Load the result,得到调用合约得到的结果 } } }
下面还有一种更简单的方法,使用mapping存储使用接口情况:
https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/introspection/SupportsInterfaceWithLookup.sol
pragma solidity ^0.4.24; import "./ERC165.sol"; /** * @title SupportsInterfaceWithLookup * @author Matt Condon (@shrugs) * @dev Implements ERC165 using a lookup table. */ contract SupportsInterfaceWithLookup is ERC165 { bytes4 public constant InterfaceId_ERC165 = 0x01ffc9a7; /** * 0x01ffc9a7 === * bytes4(keccak256('supportsInterface(bytes4)')) */ /** * @dev a mapping of interface id to whether or not it's supported */ mapping(bytes4 => bool) internal supportedInterfaces_; /** * @dev A contract implementing SupportsInterfaceWithLookup * implement ERC165 itself */ constructor() public { _registerInterface(InterfaceId_ERC165);//函数一开始构建的时候就会将其使用的接口ID写到supportedInterfaces_数组中 } /** * @dev implement supportsInterface(bytes4) using a lookup table */ function supportsInterface(bytes4 _interfaceId)//然后后面想要检测接口时,直接调用数组,调用了接口返回true,否则为false external view returns (bool) { return supportedInterfaces_[_interfaceId]; } /** * @dev private method for registering an interface */ function _registerInterface(bytes4 _interfaceId) internal { require(_interfaceId != 0xffffffff); supportedInterfaces_[_interfaceId] = true; } }
所以erc165只是一个标准,要求如何使用一种标准的方法去发布或者检测(supportsInterface)一个智能合约所实现的接口
For this standard, an interface is a set of function selectors as defined by the Ethereum ABI.
ABI的相关知识:https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector
ERC721
https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
A standard interface for non-fungible tokens, also known as deeds.即非同质代币的标准接口
NFTs are distinguishable and you must track the ownership of each one separately.就是每一个NFT token都是第一无二,不可替代的
Every ERC-721 compliant contract must implement the ERC721
and ERC165
interfaces (subject to "caveats" below):
这个接口里写的函数并不代表这你都要用,你可以根据自己设计的token需要用到的功能来选择性地使用,比如如果你只需要transfer,那你就只写safeTransferFrom()即可,,下面这个只是一个标准
pragma solidity ^0.4.20; /// @title ERC-721 Non-Fungible Token Standard /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md /// Note: the ERC-165 identifier for this interface is 0x80ac58cd.而不是我们上面所说的0x01ffc9a7 interface ERC721 /* is ERC165 */ { /// @dev This emits when ownership of any NFT changes by any mechanism. /// This event emits when NFTs are created (`from` == 0) and destroyed /// (`to` == 0). Exception: during contract creation, any number of NFTs /// may be created and assigned without emitting Transfer. At the time of /// any transfer, the approved address for that NFT (if any) is reset to none. event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); /// @dev This emits when the approved address for an NFT is changed or /// reaffirmed. The zero address indicates there is no approved address. /// When a Transfer event emits, this also indicates that the approved /// address for that NFT (if any) is reset to none. event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); /// @dev This emits when an operator is enabled or disabled for an owner. /// The operator can manage all NFTs of the owner. event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); /// @notice Count all NFTs assigned to an owner /// @dev NFTs assigned to the zero address are considered invalid, and this /// function throws for queries about the zero address. /// @param _owner An address for whom to query the balance /// @return The number of NFTs owned by `_owner`, possibly zero function balanceOf(address _owner) external view returns (uint256); /// @notice Find the owner of an NFT /// @dev NFTs assigned to zero address are considered invalid, and queries /// about them do throw. /// @param _tokenId The identifier for an NFT /// @return The address of the owner of the NFT function ownerOf(uint256 _tokenId) external view returns (address); /// @notice Transfers the ownership of an NFT from one address to another address /// @dev Throws unless `msg.sender` is the current owner, an authorized /// operator, or the approved address for this NFT. Throws if `_from` is /// not the current owner. Throws if `_to` is the zero address. Throws if /// `_tokenId` is not a valid NFT. When transfer is complete, this function /// checks if `_to` is a smart contract (code size > 0). If so, it calls /// `onERC721Received` on `_to` and throws if the return value is not /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. /// @param _from The current owner of the NFT /// @param _to The new owner /// @param _tokenId The NFT to transfer /// @param data Additional data with no specified format, sent in call to `_to` function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; /// @notice Transfers the ownership of an NFT from one address to another address /// @dev This works identically to the other function with an extra data parameter, /// except this function just sets data to "". /// @param _from The current owner of the NFT /// @param _to The new owner /// @param _tokenId The NFT to transfer function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE /// THEY MAY BE PERMANENTLY LOST /// @dev Throws unless `msg.sender` is the current owner, an authorized /// operator, or the approved address for this NFT. Throws if `_from` is /// not the current owner. Throws if `_to` is the zero address. Throws if /// `_tokenId` is not a valid NFT. /// @param _from The current owner of the NFT /// @param _to The new owner /// @param _tokenId The NFT to transfer function transferFrom(address _from, address _to, uint256 _tokenId) external payable; /// @notice Change or reaffirm the approved address for an NFT /// @dev The zero address indicates there is no approved address. /// Throws unless `msg.sender` is the current NFT owner, or an authorized /// operator of the current owner. /// @param _approved The new approved NFT controller /// @param _tokenId The NFT to approve function approve(address _approved, uint256 _tokenId) external payable; /// @notice Enable or disable approval for a third party ("operator") to manage /// all of `msg.sender`'s assets /// @dev Emits the ApprovalForAll event. The contract MUST allow /// multiple operators per owner. /// @param _operator Address to add to the set of authorized operators /// @param _approved True if the operator is approved, false to revoke approval function setApprovalForAll(address _operator, bool _approved) external; /// @notice Get the approved address for a single NFT /// @dev Throws if `_tokenId` is not a valid NFT. /// @param _tokenId The NFT to find the approved address for /// @return The approved address for this NFT, or the zero address if there is none function getApproved(uint256 _tokenId) external view returns (address); /// @notice Query if an address is an authorized operator for another address /// @param _owner The address that owns the NFTs /// @param _operator The address that acts on behalf of the owner /// @return True if `_operator` is an approved operator for `_owner`, false otherwise function isApprovedForAll(address _owner, address _operator) external view returns (bool); } 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); }
A wallet/broker/auction application MUST implement the wallet interface if it will accept safe transfers.
这个一定要用吗,作用是啥
/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02. interface ERC721TokenReceiver { /// @notice Handle the receipt of an NFT /// @dev The ERC721 smart contract calls this function on the recipient /// after a `transfer`. This function MAY throw to revert and reject the /// transfer. Return of other than the magic value MUST result in the /// transaction being reverted. /// Note: the contract address is always the message sender. /// @param _operator The address which called `safeTransferFrom` function /// @param _from The address which previously owned the token /// @param _tokenId The NFT identifier which is being transferred /// @param _data Additional data with no specified format /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` /// unless throwing function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4); }
This allows your smart contract to be interrogated for its name and for details about the assets which your NFTs represent.
这个只是用于显示一些信息,比如你token的名字(name)等,可用可不用(option)
/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md /// Note: the ERC-165 identifier for this interface is 0x5b5e139f. interface ERC721Metadata /* is ERC721 */ { /// @notice A descriptive name for a collection of NFTs in this contract function name() external view returns (string _name); /// @notice An abbreviated name for NFTs in this contract function symbol() external view returns (string _symbol); /// @notice A distinct Uniform Resource Identifier (URI) for a given asset. /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC /// 3986. The URI may point to a JSON file that conforms to the "ERC721 /// Metadata JSON Schema". function tokenURI(uint256 _tokenId) external view returns (string); }
This allows your contract to publish its full list of NFTs and make them discoverable.
这个就是用于展示你的token的各种信息,比如通过index得到token或者得到owner等,可用可不用(option)
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md /// Note: the ERC-165 identifier for this interface is 0x780e9d63. interface ERC721Enumerable /* is ERC721 */ { /// @notice Count NFTs tracked by this contract /// @return A count of valid NFTs tracked by this contract, where each one of /// them has an assigned and queryable owner not equal to the zero address function totalSupply() external view returns (uint256); /// @notice Enumerate valid NFTs /// @dev Throws if `_index` >= `totalSupply()`. /// @param _index A counter less than `totalSupply()` /// @return The token identifier for the `_index`th NFT, /// (sort order not specified) function tokenByIndex(uint256 _index) external view returns (uint256); /// @notice Enumerate NFTs assigned to an owner /// @dev Throws if `_index` >= `balanceOf(_owner)` or if /// `_owner` is the zero address, representing invalid NFTs. /// @param _owner An address where we are interested in NFTs owned by them /// @param _index A counter less than `balanceOf(_owner)` /// @return The token identifier for the `_index`th NFT assigned to `_owner`, /// (sort order not specified) function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256); }
警告(caveat),这是solidity 0.4.20的版本出的警告,现在已经是0.4.24版本了,有些警告已经解决
1)Solidity issue #3412
在remix中编译下面的两个例子
pragma solidity ^0.4.19; interface NamedThing { function name() public view returns (string _name); } contract Bob is NamedThing { string public constant name = "Bob"; }
报错:
DeclarationError:Identifier already declared: string public constant name = "Bob"; The previous declaration is here:
function name() public view returns (string _name);
所以实现interface的函数的时候是能够这样子写的吗????
警告:
warning:function in interface should be declared external
pragma solidity ^0.4.19; interface NamedThing { function name() public view returns (string _name); } contract Bob is NamedThing { function name() public pure returns (string) { return "Bob"; } }
上面这两个都是实现了使用接口的例子
警告:
warning:function in interface should be declared external
报错:
TypeError:overriding changes states mutability from "view" to "pure"
只看第二个例子,这个错误产生的原因是mutability guarantees是有程度排序的:
Mutability is expressed in these forms:
- Payable(强)
- Nonpayable
- View
- Pure(弱)
所以上面错误发生时因为接口的函数为view,所以不能在实现函数时把函数声明为pure
然后后面以为反过来实现应该没问题,即接口函数声明为pure时,实现声明为view,后面发现还是报错:
TypeError:overriding changes states mutability from "pure" to "view
2)Solidity issue #3419: A contract that implements ERC721Metadata
or ERC721Enumerable
SHALL also implement ERC721
. ERC-721 implements the requirements of interface ERC-165.
就是interface是不能继承自另一个interface的
比如:
pragma solidity ^0.4.22; interface I1 { function a() public; } interface I2 is I1 { function a() public; } interface I3 is I1 { function a() public; } contract C is I2, I3 { function a() external; }
会报错:
TypeError:Interfaces cannot inherit:
interface I2 is I1{...}
TypeError:Interfaces cannot inherit:
interface I3 is I1{...}
TypeError:Overriding function visibility differs:
function a() external;
overrinden function is here:
function a() public;
除此之外还有interface中是不能写变量的,如:
pragma solidity ^0.4.24; interface A { uint32[] public contracts; } interface B is A { function lastContract() public returns (uint32 _contract); }
报错:
TypeError:Variables cannot be declared in interfaces:(但是可以声明常量constant) uint32[] public contracts; TypeError:Interfaces cannot inherit: interface I3 is I1{...}
The problem is that we want to properly fix inheritance and function overloading, and this requires some discussion. Please take a look here:
https://github.com/ethereum/solidity/pull/3729
这里写了希望改进的有关override和overload的内容,还没有实现
3)Solidity issue #2330: If a function is shown in this specification as external
then a contract will be compliant if it uses public
visibility. As a workaround for version 0.4.20, you can edit this interface to switch to public
before inheriting from your contract.
pragma solidity ^0.4.24; interface NamedThing { function name() public view returns (string _name); } contract Bob is NamedThing { function name() public pure returns (string) { return "Bob"; } }
不会报错,现在是能够这样override了
4)Solidity issues #3494, #3544: Use of this.*.selector
is marked as a warning by Solidity, a future version of Solidity will not mark this as an error.
pragma solidity ^0.4.24; interface Solidity101 { function hello() external pure; function world(int) external pure; } contract Selector { function calculateSelector() public pure returns (bytes4) { return this.hello.selector ^ this.world.selector; } function hello() external pure; function world(int) external pure; }
之前会报错:
TypeError:Function declared as pure,but this expression reads from the environment or state and thus requires "view"
The problem is that this
is disallowed in a view context - reading the current address is not a pure action.就是this不能在声明为pure的函数中使用,但是现在可以了(Allow `this.f.selector` to be pure. #3498,https://github.com/ethereum/solidity/pull/3498)
others:
Creating of NFTs ("minting") and destruction NFTs ("burning") is not included in the specification. Your contract may implement these by other means. Please see the event
documentation for your responsibilities when creating or destroying NFTs.
NFT token是可以被destruct的
在这里使用ERC-165 Interface的目的:
We chose Standard Interface Detection (ERC-165) to expose the interfaces that a ERC-721 smart contract supports.
就是用来发布这个NFT合约使用的接口
使用ERC721 接口的函数也是要实现ERC165的接口的
举例说明上面提到的接口的使用:
完整代码在:https://github.com/0xcert/ethereum-erc721/tree/master/contracts/tokens
1)SupportsInterface.sol为对ERC165接口中的supportsInterface函数的实现:
pragma solidity ^0.4.24; import "./ERC165.sol"; /** * @dev Implementation of standard for detect smart contract interfaces. */ contract SupportsInterface is ERC165 { /** * @dev Mapping of supported intefraces. * @notice You must not set element 0xffffffff to true. */ mapping(bytes4 => bool) internal supportedInterfaces; /** * @dev Contract constructor. */ constructor() public { supportedInterfaces[0x01ffc9a7] = true; // ERC165 } /** * @dev Function to check which interfaces are suported by this contract. * @param _interfaceID Id of the interface. */ function supportsInterface( bytes4 _interfaceID ) external view returns (bool) { return supportedInterfaces[_interfaceID]; } }
2)ERC721.sol就是正常的接口,写了自己想要实现的函数功能和事件
ERC721TokenReceiver.sol,是当你调用了safeTransferFrom函数时使用的接口
因为有一些情况你transfer token,可能会导致token的丢失,比如
If you will send 100 ERC20 tokens to a contract that is not intended to work with ERC20 tokens, then it will not reject tokens because it cant recognize an incoming transaction. As the result, your tokens will get stuck at the contracts balance.
pragma solidity ^0.4.24; /** * @dev ERC-721 interface for accepting safe transfers. See https://goo.gl/pc9yoS.
* 所以这个接口的目的就是保证接受的是安全的transfer,这个接口的作用就是给了你的transfer进行一些条件限制,比如说不能transfer给的地址_to是你本身的合约地址。
* 因为在非safe的transfer中是没有这些限制的,基本上_to可以是任意地址,ERC721TokenReceiver接口的实现让transfer更safe了 */ interface ERC721TokenReceiver { /** * @dev Handle the receipt of a NFT. The ERC721 smart contract calls this function on the * recipient after a `transfer`. This function MAY throw to revert and reject the transfer. Return * of other than the magic value MUST result in the transaction being reverted. * Returns `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` unless throwing. * @notice The contract address is always the message sender. A wallet/broker/auction application * MUST implement the wallet interface if it will accept safe transfers. * @param _operator The address which called `safeTransferFrom` function. * @param _from The address which previously owned the token. * @param _tokenId The NFT identifier which is being transferred. * @param _data Additional data with no specified format. */ function onERC721Received( address _operator, address _from, uint256 _tokenId, bytes _data ) external returns(bytes4); }
AddressUtils.sol是用来核查一个地址是不是合约地址的库,是则返回true,否则为false
pragma solidity ^0.4.24; /** * @dev Utility library of inline functions on addresses. */ library AddressUtils { /** * @dev Returns whether the target address is a contract. * @param _addr Address to check. */ function isContract( address _addr ) internal view returns (bool) { uint256 size; /** * XXX Currently there is no better way to check if there is a contract in an address than to * check the size of the code at that address. * See https://ethereum.stackexchange.com/a/14016/36603 for more details about how this works. * TODO: Check this again before the Serenity release, because all addresses will be * contracts then. */
//如何查看一个地址是EOS还是智能合约地址,就在于查看该账户代码的长度。EOS下是不能存放代码的,所以它的长度是0,只有合约账户的地址是能够存放代码的 assembly { size := extcodesize(_addr) } // solium-disable-line security/no-inline-assembly return size > 0; } }
EOS的全称为「Enterprise Operation System」,是一条高可用性的公链,交易几乎可以在一秒内确认
3)NFToken.sol实现过程即:
pragma solidity ^0.4.24; import "./ERC721.sol"; import "./ERC721TokenReceiver.sol"; import "@0xcert/ethereum-utils/contracts/math/SafeMath.sol"; import "@0xcert/ethereum-utils/contracts/utils/SupportsInterface.sol"; import "@0xcert/ethereum-utils/contracts/utils/AddressUtils.sol"; /** * @dev Implementation of ERC-721 non-fungible token standard. */ contract NFToken is ERC721, SupportsInterface { //这两个是library using SafeMath for uint256; using AddressUtils for address; /** * @dev Magic value of a smart contract that can recieve NFT. * Equal to: bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")). */ bytes4 constant MAGIC_ON_ERC721_RECEIVED = 0x150b7a02; constructor() public { //表明使用了ERC721接口,就是当你的合约使用了什么接口的时候,都可以通过这个方法将得到的interface ID写到数组中进行记录,这样检测也就只用查询数组即可 supportedInterfaces[0x80ac58cd] = true; // ERC721 } /** * @dev Transfers the ownership of an NFT from one address to another address. * @notice This works identically to the other function with an extra data parameter, except this * function just sets data to "" * @param _from The current owner of the NFT. * @param _to The new owner. * @param _tokenId The NFT to transfer. */ function safeTransferFrom( address _from, address _to, uint256 _tokenId ) external { _safeTransferFrom(_from, _to, _tokenId, ""); } /** * @dev Actually perform the safeTransferFrom. * @param _from The current owner of the NFT. * @param _to The new owner. * @param _tokenId The NFT to transfer. * @param _data Additional data with no specified format, sent in call to `_to`. */ function _safeTransferFrom( address _from, address _to, uint256 _tokenId, bytes _data ) internal canTransfer(_tokenId) validNFToken(_tokenId) { address tokenOwner = idToOwner[_tokenId]; require(tokenOwner == _from); require(_to != address(0)); _transfer(_to, _tokenId); //即如果transfer的_to地址是一个合约地址的话,该transfer将会失败,因为在ERC721TokenReceiver接口中,
//onERC721Received函数的调用什么都没有返回,默认为null
//这就是对transfer的一种限制,所以为safe
if (_to.isContract()) {//在library AddressUtils中实现 bytes4 retval = ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data); require(retval == MAGIC_ON_ERC721_RECEIVED);//该require将revert这个transfer transaction } }