常用ERC标准学习


最近看了一些智能合约,发现用的最经常的ERC标准为ERC-20、ERC-165、ERC-721,以此学习一下做个笔记

ERC-20

ERC-20标准接口主要是在智能合约中实现了代币的标准api,也就是通过函数来为代币提供基本的功能:例如代币转账、授权等等。

可选函数

ERC-20标准接口中有三个可选函数,主要是返回代币的基本信息,所以也可以称为元数据。我们来看某个智能合约中的代码:

// IERC20Metadata.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC20.sol";

interface IERC20Metadata is IERC20 {

    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint8);
}

其中,函数name():返回的是代币的名称,例如Binance Coin

函数symbol():返回的是代币的代号,例如BNB

函数decimals():返回的是代币的使用的小数位数,例如8(意味着将代币总量除以100000000来获取表现的形式)

必要函数

继续看上一个智能合约中标准定义的ERC20的必要函数

// IERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IERC20 {

    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

其中,函数totalSupply:返回总令牌供应量

函数balanceOf:返回账户的余额

函数transfer:向to地址转移value数量的代币,函数必须触发事件Transfer

函数transferFrom:从fromto地址转移value数量的代币,函数必须触发事件Transfer

transferFrom函数,可以允许第三方代表我们转移代币。

如果 _from 账号没有授权调用帐户转移代币,则该函数需要抛出异常。

函数approve:授权spender可以从我们在账户最多转移代币的数量value,可以多次转移,但总量不超过value

这个函数可以再次调用,以覆盖授权额度 _value,调用者可以在调整授权额度时,先设置为0,然后在设置为一个其他额度

函数allowance:查询owner授权给spender的额度

事件Transfer:当有代币转移时,必须触发Transfer事件

如果是新产生代币,触发 Transfer 事件的 from 应该设置为 0x0

事件Approvalapprove函数成功执行时,必须触发Approval事件


ERC-165

ERC-165只是一个标准,要求使用一种标准的方法去发布或者检测(supportsInterface)一个智能合约所实现的接口。

//IERC165.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

我们可以看到接口的supportsInterface函数需要一个参数,为接口id

函数选择器:可以使用 bytes4(keccak256('function_name(bytes4)')); 计算得到,或者使用合约函数的selector方法

接口ID:为接口中所有函数选择器的异或(XOR)

例如:

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;
    }
}

实现ERC-165标准的合约:

//ERC165.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

兼容 ERC-165的合约应该实现接口 ERC165.sol(上面的IERC165.sol)

这个接口的接口ID 为 0x01ffc9a7

因此,若合约实现supportInterface函数将返回:

  • true:当接口IDinterfaceID0x01ffc9a7(EIP165标准接口),返回true
  • false:当interfaceID0xffffffff,返回false
  • true:任何合约实现了接口的interfaceID都返回true
  • false:其他的返回false

以上是检测合约是否采用ERC-165接口的方法,那么如何检测合约是否实现了某个接口呢?

  1. 若不确定合约是否实现ERC-165,则按上面的检测步骤检测
  2. 如果没有实现ERC-165,则不得不查看它采用哪种老式方法
  3. 如果实现了ERC-165,则直接调用supportInterface(InterfaceID)来确定它是否实现了对应的接口

ERC-721

ERC-721:非同质化(Non-Fungible Token,以下简称 NFT 或 NFTs)代币标准

  • 是智能合约中实现 NFT 的标准API。 标准提供了跟踪和转移NFTs的基本功能。

  • NFT可以代表对数字或物理资产的所有权。非同质代表独一无二,NFT是可区分的

  • ERC20代币是可置换的,且可细分为N份(1 = 10 * 0.1), 而ERC721的Token最小的单位为1,无法再分割。

每个符合ERC-721的合同都必须实现 ERC721ERC165 接口

pragma solidity ^0.4.20;

interface IERC721 is IERC165 {

	// 变更NFT所有权、NFT的创建(from == 0)和销毁时(to == 0)触发。合约创建时除外
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
	// 在NFT的授权地址approved address变更或者重新确认时被触发
	// 发起transfer时,approved address会被重置为none
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
	// operator被授权或撤权时触发。operator可以管理owner的所有NFT
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
	// 返回由owner持有的NFTs的数量。
    function balanceOf(address owner) external view returns (uint256 balance);
	// 返回tokenId代币持有者的地址
    function ownerOf(uint256 tokenId) external view returns (address owner);
	// 将NFT的所有权从一个地址转移到另一个地址
	// 1. 调用者msg.sender应该是当前tokenId的所有者或被授权的地址
	// 2. from不是tokenId的所有者 、to是零地址、tokenId不是有效id均抛出异常。
	// 3. 当转移完成时,函数检查 to是否是合约,如果是,调用onERC721Receiver方法,并检查其返回值是否是0x150b7a02,(即bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")),如果不是则抛出异常
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;
	// 转移所有权 调用者负责确认to是否有能力接收NFTs,否则可能永久丢失
	// 1. 调用者msg.sender应该是当前tokenId的所有者或被授权的地址
	// 2. from不是tokenId的所有者 、to是零地址、tokenId不是有效id 均抛出异常。
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;
	// 授予地址to具有tokenId的控制权,方法成功后需触发Approval 事件。
    function approve(address to, uint256 tokenId) external;
	// 获取单个NFT的授权地址
    function getApproved(uint256 tokenId) external view returns (address operator);
	// 启用或禁用第三方(操作员)管理msg.sender所有资产
	// 触发 ApprovalForAll 事件,合约必须允许每个所有者可以有多个操作员
	// approved True 表示授权, false 表示撤销
    function setApprovalForAll(address operator, bool _approved) external;
	// 查询一个地址是否是另一个地址的授权操作员
    function isApprovedForAll(address owner, address operator) external view returns (bool);
	// 将NFT的所有权从一个地址转移到另一个地址,功能同上,附带data参数,传递给接收者
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;
}

如果接收NFT的是合约,则需要实现以下接口:

// IERC721Receiver.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;


interface IERC721Receiver {
	// 处理接收NFT,ERC721智能合约在transfer完成后,在接收这地址上调用这个函数
	// 调用这个函数的 msg.sender是ERC721的合约地址
	/// @param _from :之前的NFT拥有者
    /// @param _tokenId : NFT token id
    /// @param _data : 附加信息
    /// @return 正确处理时返回bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

可选扩展函数-元信息:

//IERC721Metadata.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../IERC721.sol";


interface IERC721Metadata is IERC721 {
	// NFTs集合的名字
    function name() external view returns (string memory);
	// NFTs缩写代号
    function symbol() external view returns (string memory);
	//  一个给定资产的唯一的统一资源标识符(URI)
	// URI 也许指向一个 符合 "ERC721 元数据 JSON Schema" 的 JSON 文件
    function tokenURI(uint256 tokenId) external view returns (string memory);
}

ERC721 元数据 JSON Schema描述:

{
    "title": "Asset Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "指示NFT代表什么"
        },
        "description": {
            "type": "string",
            "description": "描述NFT 代表的资产"
        },
        "image": {
            "type": "string",
            "description": "指向NFT表示资产的资源的URI(MIME 类型为 image/*) , 可以考虑宽度在320到1080像素之间,宽高比在1.91:1到4:5之间的图像。
        }
    }
}

ERC-721标准的枚举扩展信息(可选):

// IERC721Enumerable.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../IERC721.sol";

interface IERC721Enumerable is IERC721 {
	//NFTs 总数
    function totalSupply() external view returns (uint256);
	// 枚举索引某个所有者的 NFTs
	// 如果index >= balanceOf(owner)或owner为零地址则抛出异常
    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId);
		// 枚举索引NFT,index应小于totalSupply()
    function tokenByIndex(uint256 index) external view returns (uint256);
}

参考资料:https://learnblockchain.cn/docs/eips/


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM