当前位置:   article > 正文

以太坊应用层提案之ERC-721_erc721

erc721

一 NFT概述

1.1 什么是NFT

ERC-20是同质化代币标准,同质化的意思就是说,代币在单位内表示的价值和功能是一样, 代币之间是可以互换的。比如你现在有100个BNB, 那么每一个BNB价值和功能都是一样的。

但是,有时候我们希望给区块链中的虚拟资产赋予独特的、唯一的等特性的时候,比如对数字资产的所有权、纪念章、数字艺术品、各种唯一的虚拟物品,比如游戏道具等等,这时候同质化代币是做不到这一点,因此有了非同质化代币NFT,即Non-Fungible Tokens

NFT: 就是Non-Fungible Token,即非同质化代币,他也是以太坊区块链上一种虚拟数字资产,但是和同质化代币不同,它是唯一的,独特的,不可互换的代币,每一个NFT都具有独特的属性和价值。

1.2 Non-Fungible Token(NFT) 和 Fungible  Token(Token)比较

1.2.1 可互换性

Token是可以互换的,每一个Token的价值和功能是一样的

NFT: 不可互换,每一个NFT都是独一无二的,都可能具有不同价值

1.2.2 不可分割性

Token是可以分割的,通常可以分割为更小的单位进行交易。比如你可以购买0.1个或者0.001个代币

NFT通常不可分割,每个NFT都是一个完整的单位,你不能购买0.5个NFT

1.2.3 使用场景不一样

Token主要用于支付、交易和价值存储,适合需要标准化价值的场景

NFT主要用于表示独特的资产和所有权,适合需要强调独特性和所有权的场景。比如游戏道具、数字艺术品、数字收藏品、所有权、数字票据等等

二  ERC-721标准

2.1 什么是ERC-721

ERC-721是以太坊非同质化代币(NFT)的一个标准,它定义了一套规范或者标准,如果NFT实现了ERC-721标准,外部的钱包、交易所或者其他DApp应用程序就可以很方便的和NFT进行交互。

2.2 ERC-721特点

非同质化

与同质化代币(如比特币和以太币)不同,ERC-721 代币是独特的,每个代币有其独特的属性和标识符。

这种独特性使得 ERC-721 代币特别适合表示独一无二的资产,如数字艺术品、收藏品、游戏物品等。

不可分割

大多数 ERC-721 代币是不可分割的,即不能像同质化代币那样分割成更小的单位。一个完整的 ERC-721 代币必须作为一个整体进行交易。

所有权和转移

ERC-721 标准定义了一组接口,使得NFT的所有权和转移过程可以在区块链上透明和安全地进行。代币的所有权信息被永久记录在区块链上,任何人都可以验证。

2.3 ERC-721标准内容

2.3.1 字段

和ERC-20不同,ERC-721因为要表示代币独一无二。因此,给NFT添加了一个tokenId的字段,表示该NFT独一无二。

另外,每一个NFT都有一个tokenURI,通常指向一个包含该代币详细信息的 JSON 文件,如名称、描述、图像链接等。这使得每个 NFT 可以有丰富的元数据,与其独特性相对应。比如:

{
    "title": "Louis Vena",
    "description": "Cneter Left",
    "image": "ipfs://QmX6pUBZDRt2uzw79iatA9zH9sbbDrrT1wThs8cjamqAkF",
    "attributes": [
        {
        "trait_type": "Singer",
        "value": "Belly"
        },
        {
        "trait_type": "Area",
        "value": "America"
        }
    ],
    "version": "1"
}

2.3.2 函数

2.3.2.1 balanceOf

function balanceOf(address _owner) external view returns (uint256);

返回指定地址的NFT余额,一个人可能会拥有多个

2.3.2.2 ownerOf

function ownerOf(uint256 _tokenId) external view returns (address);

根据指定的tokenId返回该NFT的拥有者地址

2.3.2.3 safeTransferFrom

function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;

安全的将指定的tokenId的NFT从_from地址转移到_to地址,并且还可以携带一些额外数据。

在复杂的交易场景中,可能需要附加的上下文信息。例如,某个 NFT 可能代表一个游戏中的物品,附加的数据可以包含关于游戏场景的信息。

在目标地址是合约时,附加的数据可以用来传递特定指令给合约。

function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

安全的将指定的tokenId的NFT从_from地址转移到_to地址,不能携带一些额外数据

2.3.2.4 transferFrom

function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

将指定的tokenId的NFT从_from地址转移到_to地址

2.3.2.5 approve

function approve(address _approved, uint256 _tokenId) external payable;

授权某个地址_approved可以转移指定tokenId的NFT。

2.3.2.6 setApprovalForAll

function setApprovalForAll(address _operator, bool _approved) external;

授权或取消授权_operator地址可以管理调用者的所有NFT

2.3.2.7 getApproved

function getApproved(uint256 _tokenId) external view returns (address);

返回指定tokenId的NFT被授权哪个地址可以进行转移

2.3.2.8 isApprovedForAll

function isApprovedForAll(address _owner, address _operator) external view returns (bool);

是否_operator是否被授权管理_owner的所有NFT

2.3.3 事件

2.3.3.1 Transfer

event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);

在代币转移时需要触发

2.3.3.2 Approval

event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

在代币授权时触发

2.3.3.3 ApprovalForAll

event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

在设置或取消所有授权时触发

三 safeTransferFrom和transferFrom比较

function safeTransferFrom(address from, address to, uint256 tokenId) external;

function transferFrom(address from, address to, uint256 tokenId) external;

3.1 相同点

都是将 指定tokenId的NFT 从 `from` 地址转移到` to` 地址

3.2 不同点

不同点主要是安全问题:

我们知道智能合约地址一般分为EOA地址和CA地址,那么钱包一般属于EOA地址,这类地址他们不会有代码的执行,仅用于持有和发送代币等;但是CA地址就是合约地址,合约地址是可以有代码执行逻辑的。

那这个和NFT转移安全有什么关系呢?  难道CA地址就无法转移成功,EOA地址就可以?

其实是这样的,无论是EOA地址还是CA地址,safeTransferFrom和transferFrom都是可以转移成功的。但是转移给CA地址,需要考虑到这个合约是否有接口可以把NFT转出来,如果转不出来,那么这个NFT就一直在这个合约里面了。因此,如果 to 地址是一个不支持 操作ERC721的合约,最后safeTransferFrom就会revert, 这笔交易不会成功。

那safeTransferFrom是怎么实现检查合约是否具备操作NFT的能力的呢?

在OpenZeppelin中,定义了一个ERC721Receiver接口,如果你的合约你觉得需要让调用者知晓你是具备操作ERC721的能力的,比如转账,那么你需要在你的合约中实现这个ERC721Receiver接口的onERC721Received函数;否则调用者就无法知晓你这个合约到底是不是支持ERC721接口的,那么safeTransferFrom就会认为不支持,则判定失败,回滚交易。

  1. function onERC721Received(
  2.         address operator,
  3.         address from,
  4.         uint256 tokenId,
  5.         bytes calldata data
  6. ) external returns (bytes4);

那么实现了或者重载了,怎么认为他就具备处理ERC721的能力了?只需要这个函数返回值是`bytes4`类型,并且返回值为`this.onERC721Received.selector`,这样可以确保函数符合ERC721接口中定义的规范。比如:

  1. function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external override returns (bytes4) {
  2.         // 这里可以添加处理逻辑,例如记录接收到的代币信息
  3.         // 返回这个值表明成功接收
  4.         return this.onERC721Received.selector;
  5. }

四 手动实现ERC-721

  1. // SPDX-License-Identifier: UNLICENSED
  2. pragma solidity ^0.8.13;
  3. import "./IERC721.sol";
  4. abstract contract ERC720 is IERC721 {
  5.     string private _name;
  6.     string private _symbol;
  7.     mapping(uint256 => address) private _owners;
  8.     mapping(address => uint256) private _balances;
  9.     mapping(uint256 => address) private _approvals;
  10.     mapping(address owner => mapping(address operator => bool))
  11.         private _operatorApprovals;
  12.     constructor(string memory name, string memory symbol) {
  13.         _name = name;
  14.         _symbol = symbol;
  15.     }
  16.     function name() public view virtual returns (string memory) {
  17.         return _name;
  18.     }
  19.     function symbol() public view virtual returns (string memory) {
  20.         return _symbol;
  21.     }
  22.     function tokenURI(uint256 tokenId) public view virtual returns (string memory) {
  23.         require(_owners[tokenId] != address(0), "Not exists owner");
  24.         string memory baseURI = _baseURI();
  25.         return bytes(baseURI).length > 0 ? string.concat(baseURI, tokenId.toString()) : "";
  26.     }
  27.     // Any contract extends the ERC721 contract could ovveride the function and set the baseURI value, default is empty string
  28.     function _baseURI() internal view virtual returns (string memory) {
  29.         return "";
  30.     }
  31.     function balanceOf(
  32.         address owner
  33.     ) external view override returns (uint256 balance) {
  34.         return _balances[owner];
  35.     }
  36.     function ownerOf(
  37.         uint256 tokenId
  38.     ) external view override returns (address owner) {
  39.         return _owners[tokenId];
  40.     }
  41.     function safeTransferFrom(
  42.         address from,
  43.         address to,
  44.         uint256 tokenId,
  45.         bytes calldata data
  46.     ) external override {
  47.         _checkOnERC721Received(from, to, tokenId, data);
  48.         transferFrom(from, to, tokenId);
  49.     }
  50.     function safeTransferFrom(
  51.         address from,
  52.         address to,
  53.         uint256 tokenId
  54.     ) external override {
  55.         safeTransferFrom(from, to, tokenId, "");
  56.     }
  57.     /**
  58.      * Transfer ownership of an NFT
  59.      * @param from
  60.      * @param to
  61.      * @param tokenId
  62.      */
  63.     function transferFrom(
  64.         address from,
  65.         address to,
  66.         uint256 tokenId
  67.     ) external override {
  68.         // check if msg.sender has approved
  69.         require(_checkPermission(from, msg.sender, tokenId), "msg.sender has no previleges to transfer");
  70.         // update owner balance
  71.         address prev = _update(to, tokenId, msg.sender);
  72.         require(prev == from, "Incorrect owner");
  73.     }
  74.     // Approve permisson to `to` address for tokenId
  75.     function approve(address to, uint256 tokenId) external override {
  76.         _approve(to, tokenId, msg.sender);
  77.     }
  78.     function setApprovalForAll(
  79.         address operator,
  80.         bool approved
  81.     ) external override {
  82.         require(operator != address(0), "Invalid operator");
  83.         _operatorApprovals[msg.sender][operator] = approved;
  84.         emit ApprovalForAll(msg.sender, operator, approved);
  85.     }
  86.     function getApproved(
  87.         uint256 tokenId
  88.     ) external view override returns (address operator) {
  89.         require(_owners[tokenId] != address(0), "Not exist owner");
  90.         return _getApproved(tokenId);
  91.     }
  92.     function isApprovedForAll(
  93.         address owner,
  94.         address operator
  95.     ) public view override returns (bool) {
  96.         return _operatorApprovals[owner][operator];
  97.     }
  98.     function _update(address to, uint256 tokenId, address operator) internal virtual returns (address) {
  99.         address from = _owners[tokenId];
  100.         if (operator != address(0)) {
  101.             _checkPermission(from, operator, tokenId);
  102.         }
  103.         if (from != address(0)) {
  104.             unchecked {
  105.                 _balances[from] -= 1;
  106.             }
  107.         }
  108.         if (to != address(0)) {
  109.             unchecked {
  110.                 _balances[to] += 1;
  111.             }
  112.         }
  113.         _owners[tokenId] = to;
  114.         emit Transfer(from, to, tokenId);
  115.         return from;
  116.     }
  117.     function _approve(address to, uint256 tokenId, address owner) internal virtual {
  118.         require(_owners[tokenId] == owner, "Incorrect owner address")
  119.         require(to != address(0), "Invalid to address");
  120.         _approvals[tokenId] = to;
  121.         emit Approval(owner, to, tokenId);
  122.     }
  123.     function _getApproved(uint256 tokenId) internal view virtual returns (address) {
  124.         return approvals[tokenId];
  125.     }
  126.     /*
  127.      * Check if owner approved for spender about the tokenId
  128.      * @param owner
  129.      * @param spender
  130.      * @param tokenId
  131.      */
  132.     function _checkPermission(address owner, address operator, uint256 tokenId) internal returns (bool) {
  133.         require(_owners[tokenId] == owner, "Incorrect owner");
  134.         require(operator != address(0), "Illegal operator address");
  135.         // If operator is owner, so operator must have the permission
  136.         if (owner == operator) {
  137.             return true;
  138.         }
  139.         // If operator is approved, return true
  140.         if (_getApproved(tokenId) == operator) {
  141.             return true;
  142.         }
  143.         return isApprovedForAll(owner, operator);
  144.     }
  145.     function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) internal {
  146.         // check smart contract code length, EOA address length is zero, CA address length is greater than zero
  147.         if (to.code.length > 0) {
  148.             // if to address is contract address, see if implements the IERC721Receiver interface
  149.             // if IERC721Receiver.onERC721Received return value is IERC721Receiver.onERC721Received.selector
  150.             // it means the contract could operate NFT, such as transfer .etc
  151.             try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
  152.                 if (retval != IERC721Receiver.onERC721Received.selector) {
  153.                     revert ERC721InvalidReceiver(to);
  154.                 }
  155.             } catch (bytes memory reason) {
  156.                 if (reason.length == 0) {
  157.                     revert ERC721InvalidReceiver(to);
  158.                 } else {
  159.                     assembly {
  160.                         revert(add(32, reason), mload(reason))
  161.                     }
  162.                 }
  163.             }
  164.         }
  165.     }
  166.     /**
  167.      * Get nft approver address by tokenId
  168.      */
  169.     function _getApproved(uint256 tokenId) internal view virtual returns (address) {
  170.         return _approvals[tokenId];
  171.     }
  172.     /**
  173.      * Mint nft to `to` address
  174.      * @param to
  175.      * @param tokenId
  176.      */
  177.     function _mint(address to, uint256 tokenId) internal {
  178.         require(to != address(0), "Invalid mint address");
  179.         unchecked {
  180.             _balances[to] += 1;
  181.         }
  182.         _owners[tokenId] = to;
  183.         emit Transfer(from, to, tokenId);
  184.     }
  185.     function _safeMint(address to, uint256 tokenId) internal {
  186.         _safeMint(to, tokenId, "");
  187.     }
  188.     function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
  189.         _mint(to, tokenId);
  190.         _checkOnERC721Received(address(0), to, tokenId, data);
  191.     }   
  192. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/807295
推荐阅读
相关标签
  

闽ICP备14008679号