Solidity开发之 NFT合约详解

编程入门 行业动态 更新时间:2024-10-08 19:45:01

Solidity开发之 NFT<a href=https://www.elefans.com/category/jswz/34/1770939.html style=合约详解"/>

Solidity开发之 NFT合约详解

1.1 IERC165 接口合约

IERC165 是一个合约标准,这个标准要求合约提供其实现了哪些接口,这样在与合约进行交互的时候可以先调用此接口进行查询。

pragma solidity ^0.8.0;/*** @dev Interface of the ERC165 standard, as defined in the* [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* [EIP section]* to learn more about how these ids are created.* 判断合约是否实现 接口ID 对应接口* This function call must use less than 30 000 gas.*/function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

1.2 ERC165 抽象合约

ERC165 是官方为 IERC165 提供的默认实现。

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)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;}
}

1.3 IERC721 接口合约

pragma solidity ^0.8.0;
import "./utils/introspection/IERC165.sol";/*** @dev Required interface of an ERC721 compliant contract.*/
interface IERC721 is IERC165 {// ============================事件==========================================// 代币转移事件,当发生代币转移时触发event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);// 代币授权事件,当 owner 对代币授权于 approved 时触发event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);// 代币全量授权事件event ApprovalForAll(address indexed owner, address indexed operator, bool approved);// ============================函数==========================================// 查看owner账号余额(拥有的代币数)function balanceOf(address owner) external view returns (uint256 balance);// 获取 tokenID 对应的代币的 owner 地址function ownerOf(uint256 tokenId) external view returns (address owner);// 安全转换方法function safeTransferFrom(address from, address to, uint256 tokenId) external;// 转移方法(开发中使用上者更多,more safe..)function transferFrom(address from, address to, uint256 tokenId) external;// 授权,owner调用该函数,将tokenID对应的代币授予 to 账号行使权function approve(address to, uint256 tokenId) external;// 获取tokenID代币对应的被授权账号地址function getApproved(uint256 tokenId) external view returns (address operator);// 全量授权,owner调用即将自己所有的代币行使权授予 operator 账号function setApprovalForAll(address operator, bool _approved) external;// 判断owner是否对账号 operator 进行全量授权function isApprovedForAll(address owner, address operator) external view returns (bool);// 安全转移,携带回调的数据function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
}

1.4 ERC721 实现合约

官方对 IERC721 接口合约提供的默认实现,我们编写的 NFT 合约一般继承于该合约;在其基础上编写自己的合约逻辑。官方实现主要提供了如下能力:

  • 基础的查询能力,诸如代币的owner、合约的名字、标志…
  • 代币的授权/回收、转移
  • 代币的生成/销毁(注意:销毁函数无检查所属权,需要我们自行检查)

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/ERC721.sol)pragma solidity ^0.8.0;import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./extensions/IERC721Metadata.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/Strings.sol";
import "../../utils/introspection/ERC165.sol";/*** @dev Implementation of [ERC721] Non-Fungible Token Standard, including* the Metadata extension, but not including the Enumerable extension, which is available separately as* {ERC721Enumerable}.*/
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {// Address和Strings可理解为工具类,这里是为了安全 or 操作便捷考虑。using Address for address;using Strings for uint256;// 合约名字 for IERC721Metadata 中的定义string private _name;// 合约标志 for IERC721Metadata 中的定义 string private _symbol;// map(tokenID, address),可理解为map结构,key为tokenID,value为该tokenID对应的owner地址mapping(uint256 => address) private _owners;// map(address, uint256), key为用户地址,value为该地址拥有的token数量mapping(address => uint256) private _balances;// map(uint256, address),key为代币ID,value为代币所授权地址(一个token只能授予一个账号行使权)mapping(uint256 => address) private _tokenApprovals;// Mapping from owner to operator approvalsmapping(address => mapping(address => bool)) private _operatorApprovals;/*** @dev 构造函数,初始化合约的 name 和 symbol*/constructor(string memory name_, string memory symbol_) {_name = name_;_symbol = symbol_;}/*** @dev See {IERC165-supportsInterface}.* 判断合约是否实现了 interfaceID 接口*/function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {returninterfaceId == type(IERC721).interfaceId ||interfaceId == type(IERC721Metadata).interfaceId ||super.supportsInterface(interfaceId);}/*** @dev See {IERC721-balanceOf}.* 返回账户拥有的代币数*/function balanceOf(address owner) public view virtual override returns (uint256) {require(owner != address(0), "ERC721: balance query for the zero address");return _balances[owner];}/*** @dev See {IERC721-ownerOf}.* 返回tokenID代币的owner地址*/function ownerOf(uint256 tokenId) public view virtual override returns (address) {address owner = _owners[tokenId];require(owner != address(0), "ERC721: owner query for nonexistent token");return owner;}/*** @dev See {IERC721Metadata-name}.* 元数据: 返回合约名字*/function name() public view virtual override returns (string memory) {return _name;}/*** @dev See {IERC721Metadata-symbol}.* 元数据:返回合约标志*/function symbol() public view virtual override returns (string memory) {return _symbol;}/*** @dev See {IERC721Metadata-tokenURI}.* 元数据:返回tokenID代币对应的 tokenURI;tokenURI在后文会详解介绍。*/function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");string memory baseURI = _baseURI();return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";}/*** @dev Base URI for computing {tokenURI}.* tokenURI 一般构建方式是 : baseURI + tokenID + baseExt的方式*/function _baseURI() internal view virtual returns (string memory) {return "";}/*** @dev See {IERC721-approve}.* 调用者msgSender()对账号to授予tokenID的行使权*/function approve(address to, uint256 tokenId) public virtual override {address owner = ERC721.ownerOf(tokenId);// 要求:被授权账号to不能为代币的ownerrequire(to != owner, "ERC721: approval to current owner");// 要求:函数调用者为代币的owner或者函数调用者是owner设置的所有代币授权用户require(_msgSender() == owner || isApprovedForAll(owner, _msgSender()),"ERC721: approve caller is not owner nor approved for all");// 内部函数,执行真正的授权操作_approve(to, tokenId);}/*** @dev Approve `to` to operate on `tokenId`* Emits a {Approval} event.* 授予to账号tokenID的行使权,并触发授权事件*/function _approve(address to, uint256 tokenId) internal virtual {_tokenApprovals[tokenId] = to;emit Approval(ERC721.ownerOf(tokenId), to, tokenId);}/*** @dev See {IERC721-getApproved}.* 获取代币tokenID的被授权用户地址*/function getApproved(uint256 tokenId) public view virtual override returns (address) {// 参数判断:tokenID必须存在require(_exists(tokenId), "ERC721: approved query for nonexistent token");return _tokenApprovals[tokenId];}/*** @dev See {IERC721-setApprovalForAll}.* 函数调用者msgSender()将自己所有的代币的行使权授予operator账号 (授予/回收)*/function setApprovalForAll(address operator, bool approved) public virtual override {// 内部函数,执行真正的授权/回收操作_setApprovalForAll(_msgSender(), operator, approved);}/*** @dev Approve `operator` to operate on all of `owner` tokens* Emits a {ApprovalForAll} event.* owner授予/回收operator对自己所有代币的权限 * 触发 ApprovalForAll 事件*/function _setApprovalForAll(address owner,address operator,bool approved) internal virtual {// 参数检查:owner不能为operator(被授权对象)require(owner != operator, "ERC721: approve to caller");// 全局变量记录授权/回收结果_operatorApprovals[owner][operator] = approved;// 触发事件emit ApprovalForAll(owner, operator, approved);}/*** @dev See {IERC721-isApprovedForAll}.* 判断用户owner是否对operator进行所有代币授权*/function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {return _operatorApprovals[owner][operator];}/*** @dev See {IERC721-transferFrom}.* 代币转移,将tokenID从from账号转移到to账号。*/function transferFrom(address from,address to,uint256 tokenId) public virtual override {// 前置判断:是代币的owner或者拥有行使权的用户require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");// 内部函数,执行真实的代币转移。_transfer(from, to, tokenId);}/*** @dev Returns whether `spender` is allowed to manage `tokenId`.* Requirements:* - `tokenId` must exist.* 内部方法:判断地址是否为代币的owner或者是拥有代币行使权的账号*/function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {// 前置判断:tokenID必须存在require(_exists(tokenId), "ERC721: operator query for nonexistent token");address owner = ERC721.ownerOf(tokenId);// owner? or 授权用户? or 全量授权用户?return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));}/*** @dev Transfers `tokenId` from `from` to `to`.*  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.* Requirements:* - `to` cannot be the zero address.* - `tokenId` token must be owned by `from`.* Emits a {Transfer} event.* 内部真实执行代币转移的函数*/function _transfer(address from,address to,uint256 tokenId) internal virtual {// 前置检查:被转移的代币属于from用户require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");// 前置检查:接收代币的用户to不能为零地址require(to != address(0), "ERC721: transfer to the zero address");// 钩子函数: 代币转移前触发,官方实现无做实际操作,如果业务有特殊的逻辑,我理解可重写 _beforeTokenTransfer函数_beforeTokenTransfer(from, to, tokenId);// 清除之前的授权—————指向零地址_approve(address(0), tokenId);// from账号代币数 -1_balances[from] -= 1;// to账号代币数 +1_balances[to] += 1;// 执行代币的转移,即更换owner地址_owners[tokenId] = to;// 触发转移事件emit Transfer(from, to, tokenId);// 钩子函数:代币转移后触发,官方实现无做实际操作_afterTokenTransfer(from, to, tokenId);}/*** @dev See {IERC721-safeTransferFrom}.* 安全代币转移,真实中使用该函数比较合适。*/function safeTransferFrom(address from,address to,uint256 tokenId) public virtual override {safeTransferFrom(from, to, tokenId, "");}/*** @dev See {IERC721-safeTransferFrom}.* 安全转移函数,携带回调数据 _data*/function safeTransferFrom(address from,address to,uint256 tokenId,bytes memory _data) public virtual override {// 前置检查:函数调用者是tokenID的owner或者拥有操作权限require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");// 内部函数,执行真正的安全转移_safeTransfer(from, to, tokenId, _data);}/*** @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients* are aware of the ERC721 protocol to prevent tokens from being forever locked.* `_data` is additional data, it has no specified format and it is sent in call to `to`.* This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.* implement alternative mechanisms to perform token transfer, such as signature-based.** Requirements:* - `from` cannot be the zero address.* - `to` cannot be the zero address.* - `tokenId` token must exist and be owned by `from`.* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.** Emits a {Transfer} event.*/function _safeTransfer(address from,address to,uint256 tokenId,bytes memory _data) internal virtual {// 内部函数:执行转移_transfer(from, to, tokenId);// 安全的由来:代币接收者如果是一个合约,那么其必须要实现IERC721Receiver规范,否则转移失败require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");}/*** @dev Returns whether `tokenId` exists.* 内部函数,判断代币是否存在*/function _exists(uint256 tokenId) internal view virtual returns (bool) {return _owners[tokenId] != address(0);}/*** @dev Safely mints `tokenId` and transfers it to `to`.* Requirements:* - `tokenId` must not exist.* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.* Emits a {Transfer} event.* 内部函数:安全铸造NFT,我们编写的合约在铸造NFT的时候,调用的就是该方法。*/function _safeMint(address to, uint256 tokenId) internal virtual {// 内部函数:铸造_safeMint(to, tokenId, "");}/*** 真实铸造NFT函数,内部函数*/function _safeMint(address to,uint256 tokenId,bytes memory _data) internal virtual {// 铸造_mint(to, tokenId);// 判断接收者是否是合约,如果是合约则需要实现 ERC721Receiver 规范。require(_checkOnERC721Received(address(0), to, tokenId, _data),"ERC721: transfer to non ERC721Receiver implementer");}/*** @dev Mints `tokenId` and transfers it to `to`.* Requirements:* - `tokenId` must not exist.* - `to` cannot be the zero address.* Emits a {Transfer} event.* 内部函数:铸造代币,但是我们一般不直接调用该函数;调用 _safeMint 更安全*/function _mint(address to, uint256 tokenId) internal virtual {// 前置检查:代币接收者不能是零地址require(to != address(0), "ERC721: mint to the zero address");// 前置检查:代币的tokenID不能是已存在的(独一无二)require(!_exists(tokenId), "ERC721: token already minted");// 钩子函数:代币转移前触发_beforeTokenTransfer(address(0), to, tokenId);// to账号代币数+1_balances[to] += 1;// 代币归属_owners[tokenId] = to;// 触发转移实现,从零地址发起,说明是铸造生成emit Transfer(address(0), to, tokenId);// 钩子函数:代币转移后触发_afterTokenTransfer(address(0), to, tokenId);}/*** @dev Destroys `tokenId`.* The approval is cleared when the token is burned.* Requirements:* - `tokenId` must exist.* Emits a {Transfer} event.* 内部函数:代币销毁* 注意:函数并没有做权限检查。所以我们在我们的合约中如果要调用该函数,需要自己做tokenID的所有权检查!*/function _burn(uint256 tokenId) internal virtual {address owner = ERC721.ownerOf(tokenId);// 钩子函数:代币转移前触发_beforeTokenTransfer(owner, address(0), tokenId);// 清除授权_approve(address(0), tokenId);// 用户代币数-1_balances[owner] -= 1;// 清理代币delete _owners[tokenId];// 触发代币转移事件,代币的接受者为零地址,说明是销毁emit Transfer(owner, address(0), tokenId);// 钩子函数:代币转移后触发_afterTokenTransfer(owner, address(0), tokenId);}/*** 内部方法:判断代币接收者是否是合约,并且是否实现了 IERC721Receiver 接口* 我们上面有介绍,如果代币的接受者是一个合约的话,那么合约需要实现 IERC721Receiver 接口*/function _checkOnERC721Received(address from,address to,uint256 tokenId,bytes memory _data) private returns (bool) {// 接收者是合约if (to.isContract()) {// 判断是否实现了 IERC721Receivertry IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) {return retval == IERC721Receiver.onERC721Received.selector;} catch (bytes memory reason) {if (reason.length == 0) {revert("ERC721: transfer to non ERC721Receiver implementer");} else {assembly {revert(add(32, reason), mload(reason))}}}} else {return true;}}/*** @dev Hook that is called before any token transfer. This includes minting* and burning.* Calling conditions:* - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be* transferred to `to`.* - When `from` is zero, `tokenId` will be minted for `to`.* - When `to` is zero, ``from``'s `tokenId` will be burned.* - `from` and `to` are never both zero.* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].* 钩子函数 */function _beforeTokenTransfer(address from,address to,uint256 tokenId) internal virtual {}/*** @dev Hook that is called after any transfer of tokens. This includes* minting and burning.* Calling conditions:* - when `from` and `to` are both non-zero.* - `from` and `to` are never both zero.** To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].* 钩子函数*/function _afterTokenTransfer(address from,address to,uint256 tokenId) internal virtual {}
}

1.5 IERC721Metadata 接口合约

定义合约的元数据信息;诸如:合约名字、标志、以及每个代币的 tokenURI。那么 tokenURI 是什么呢?

TokenURI 是 ERC721 规范提出用以描述 NFT 资源;我们可以通过 NFT 的 TokenURI 获取到该 NFT 对应的描述资源。假如有一款宠物养成类游戏,在对宠物数据进行上链的时候并不会完整的将整个宠物信息进行上链。一方面是成本问题,链上存储的数据越多,所需要的成本就越高;另外一方面则是诸如宠物等级等信息会随着宠物的成长而不断的变化,如果上链则会频繁的修改该数据,也不合适。因此 TokenURI 的存在就是为了解决这类问题,它是对 NFT 资源的扩充元描述

那比如退一步说:我的上链元数据不存在更新的场景并且数据量不大;那还存在维护 TokenURI 的必要吗?其实上,将所有的元数据存储在链上是正确的方式,但是目前很多 NFT 市场(例如OpenSea)不知道如何读取链上元数据(或者说是不支持);所以目前来说,使用 TokenURI 的链下元数据来可视化我们的 NFT,同时拥有所有的链上元数据是最理想的,这样你的代币就可以互相交互。

那如何维护 TokenURI 呢?比较常见的方式有 2种。

中心化存储,我们可以采用中心化存储系统,将 tokenURI 维护到我们自身的系统中;优点是操作简单,缺点也比较明显;当中心化系统故障后则无法响应。

IPFS 是一种点对点的超媒体协议,旨在使网络更快、更安全、更开放。它允许任何人上传一个文件,并且该文件是经过哈希校验的,所以如果文件发生改变,它的哈希值也会改变。这对于存储图片来说是非常理想的,因为这意味着每次图片更新时,链上的哈希/tokenURI也要改变,同时这意味着我们可以拥有元数据的历史记录。将图像添加到 IPFS 上也非常简单,而且不需要运行服务器。

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)pragma solidity ^0.8.0;import "../IERC721.sol";/*** @title ERC-721 Non-Fungible Token Standard, optional metadata extension* @dev See */
interface IERC721Metadata is IERC721 {/*** @dev Returns the token collection name.*/function name() external view returns (string memory);/*** @dev Returns the token collection symbol.*/function symbol() external view returns (string memory);/*** @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.*/function tokenURI(uint256 tokenId) external view returns (string memory);
}

1.6 IERC721Enumerable 接口合约

提高合约的可访问性(非必须实现,但是一般我们的合约都会实现以提高可访问性);主要提供:当前发行 NFT 总量、通过索引获取用户所拥有的TokenID…

pragma solidity ^0.8.0;
import "../IERC721.sol";/*** @title ERC-721 Non-Fungible Token Standard, optional enumeration extension* @dev See */
interface IERC721Enumerable is IERC721 {/*** @dev Returns the total amount of tokens stored by the contract.* 返回NFT总量*/function totalSupply() external view returns (uint256);/*** @dev Returns a token ID owned by `owner` at a given `index` of its token list.* Use along with {balanceOf} to enumerate all of ``owner``'s tokens.* 所有者可以一次拥有多个的NFT, 此函数返回_owner拥有的NFT列表中对应索引的tokenId*/function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId);/*** @dev Returns a token ID at a given `index` of all the tokens stored by the contract.* Use along with {totalSupply} to enumerate all tokens.* 通过索引返回对应的tokenId*/function tokenByIndex(uint256 index) external view returns (uint256);
}

1.7 ERC721Enumerable 抽象合约

官方对 IERC721Enumerable 提供的默认实现。代码比较简单,不做过度解析,自行阅读~

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/ERC721Enumerable.sol)pragma solidity ^0.8.0;import "../ERC721.sol";
import "./IERC721Enumerable.sol";/*** @dev This implements an optional extension of {ERC721} defined in the EIP that adds* enumerability of all the token ids in the contract as well as all token ids owned by each* account.*/
abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {// Mapping from owner to list of owned token IDsmapping(address => mapping(uint256 => uint256)) private _ownedTokens;// Mapping from token ID to index of the owner tokens listmapping(uint256 => uint256) private _ownedTokensIndex;// Array with all token ids, used for enumerationuint256[] private _allTokens;// Mapping from token id to position in the allTokens arraymapping(uint256 => uint256) private _allTokensIndex;/*** @dev See {IERC165-supportsInterface}.*/function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId);}/*** @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.*/function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {require(index < ERC721.balanceOf(owner), "ERC721Enumerable: owner index out of bounds");return _ownedTokens[owner][index];}/*** @dev See {IERC721Enumerable-totalSupply}.*/function totalSupply() public view virtual override returns (uint256) {return _allTokens.length;}/*** @dev See {IERC721Enumerable-tokenByIndex}.*/function tokenByIndex(uint256 index) public view virtual override returns (uint256) {require(index < ERC721Enumerable.totalSupply(), "ERC721Enumerable: global index out of bounds");return _allTokens[index];}/*** @dev Hook that is called before any token transfer. This includes minting* and burning.** Calling conditions:** - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be* transferred to `to`.* - When `from` is zero, `tokenId` will be minted for `to`.* - When `to` is zero, ``from``'s `tokenId` will be burned.* - `from` cannot be the zero address.* - `to` cannot be the zero address.** To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].*/function _beforeTokenTransfer(address from,address to,uint256 tokenId) internal virtual override {super._beforeTokenTransfer(from, to, tokenId);if (from == address(0)) {_addTokenToAllTokensEnumeration(tokenId);} else if (from != to) {_removeTokenFromOwnerEnumeration(from, tokenId);}if (to == address(0)) {_removeTokenFromAllTokensEnumeration(tokenId);} else if (to != from) {_addTokenToOwnerEnumeration(to, tokenId);}}/*** @dev Private function to add a token to this extension's ownership-tracking data structures.* @param to address representing the new owner of the given token ID* @param tokenId uint256 ID of the token to be added to the tokens list of the given address*/function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {uint256 length = ERC721.balanceOf(to);_ownedTokens[to][length] = tokenId;_ownedTokensIndex[tokenId] = length;}/*** @dev Private function to add a token to this extension's token tracking data structures.* @param tokenId uint256 ID of the token to be added to the tokens list*/function _addTokenToAllTokensEnumeration(uint256 tokenId) private {_allTokensIndex[tokenId] = _allTokens.length;_allTokens.push(tokenId);}/*** @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that* while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for* gas optimizations e.g. when performing a transfer operation (avoiding double writes).* This has O(1) time complexity, but alters the order of the _ownedTokens array.* @param from address representing the previous owner of the given token ID* @param tokenId uint256 ID of the token to be removed from the tokens list of the given address*/function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {// To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and// then delete the last slot (swap and pop).uint256 lastTokenIndex = ERC721.balanceOf(from) - 1;uint256 tokenIndex = _ownedTokensIndex[tokenId];// When the token to delete is the last token, the swap operation is unnecessaryif (tokenIndex != lastTokenIndex) {uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];_ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token_ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index}// This also deletes the contents at the last position of the arraydelete _ownedTokensIndex[tokenId];delete _ownedTokens[from][lastTokenIndex];}/*** @dev Private function to remove a token from this extension's token tracking data structures.* This has O(1) time complexity, but alters the order of the _allTokens array.* @param tokenId uint256 ID of the token to be removed from the tokens list*/function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {// To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and// then delete the last slot (swap and pop).uint256 lastTokenIndex = _allTokens.length - 1;uint256 tokenIndex = _allTokensIndex[tokenId];// When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so// rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding// an 'if' statement (like in _removeTokenFromOwnerEnumeration)uint256 lastTokenId = _allTokens[lastTokenIndex];_allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token_allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index// This also deletes the contents at the last position of the arraydelete _allTokensIndex[tokenId];_allTokens.pop();}
}

1.8 IERC721Receiver 接口合约

上面讲到在做 NFT 的转移时,如果 _to 是一个合约的话,那么其必须实现 IERC721Receiver接口才可以接收。

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.3.2 (token/ERC721/IERC721Receiver.sol)pragma solidity ^0.8.0;/*** @title ERC721 token receiver interface* @dev Interface for any contract that wants to support safeTransfers* from ERC721 asset contracts.*/
interface IERC721Receiver {/*** @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}* by `operator` from `from`, this function is called.** It must return its Solidity selector to confirm the token transfer.* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.** The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.*/function onERC721Received(address operator,address from,uint256 tokenId,bytes calldata data) external returns (bytes4);
}

更多推荐

Solidity开发之 NFT合约详解

本文发布于:2024-02-06 11:04:35,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1748664.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:合约   详解   Solidity   NFT

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!