常用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