Skip to content

Latest commit

 

History

History
310 lines (273 loc) · 8.03 KB

File metadata and controls

310 lines (273 loc) · 8.03 KB

第34讲(ERC721)

ERC165

IERC165接口合约只聲明supportsInterface函數: 檢查該合約是否實現interface id

interface IERC165 {
    /**
     * @dev 如果合约实现了查询的`interfaceId`,则返回true
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
function supportsInterface(bytes4 interfaceId) external pure override returns (bool)
{
    return
        interfaceId == type(IERC721).interfaceId ||
        interfaceId == type(IERC165).interfaceId;
}

若interfaceId為IERC721或IERC165的inteface id,則返回true, 反之則返回false

IERC721

Event

  1. Transfer: from, to, tokenId => 轉帳時被釋放
  2. Approval: owner(授權地址), approved(被授權地址), tokenId => 授權時釋放
  3. ApprovalForAll => 批量授權時釋放

Function

  1. balanceOf => NFT持有量
  2. ownerOf => 返回某tokenId的owner
  3. transferFrom => 普通轉帳
  4. safeTransferFrom => 安全轉帳 (若接收方是合約地址,會要求有ERC721Receiver接口)
  5. approve。授權其他地址使用NFT
  6. getApproved: 查詢tokenId被批准給哪個地址
  7. setApprovalForAll: 將自己持有的NFT批量授權給其他地址
  8. isApprovedForAll: 查詢某地址的NFT是否批量授權給其他地址

IERC721Receiver

// ERC721接收者接口:合约必须实现这个接口来通过安全转账接收ERC721
interface IERC721Receiver {
    function onERC721Received(
        address operator,
        address from,
        uint tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

ERC721利用_checkOnERC721Received實現onERC721Received()

function _checkOnERC721Received(
    address operator,
    address from,
    address to,
    uint256 tokenId,
    bytes memory data
) internal {
    if (to.code.length > 0) {
        try IERC721Receiver(to).onERC721Received(operator, from, tokenId, data) returns (bytes4 retval) {
            if (retval != IERC721Receiver.onERC721Received.selector) {
                // Token rejected
                revert IERC721Errors.ERC721InvalidReceiver(to);
            }
        } catch (bytes memory reason) {
            if (reason.length == 0) {
                // non-IERC721Receiver implementer
                revert IERC721Errors.ERC721InvalidReceiver(to);
            } else {
                /// @solidity memory-safe-assembly
                assembly {
                    revert(add(32, reason), mload(reason))
                }
            }
        }
    }
}

IERC721Metadata

  1. name => 代幣名稱
  2. symbol => 代幣代號
  3. tokenURI => 透過tokenId查詢小圖片url

ERC721主合約

變數

// Token名称
string public override name;
// Token代号
string public override symbol;
// tokenId 到 owner address 的持有人映射
mapping(uint => address) private _owners;
// address 到 持仓数量 的持仓量映射
mapping(address => uint) private _balances;
// tokenID 到 授权地址 的授权映射
mapping(uint => address) private _tokenApprovals;
//  owner地址。到operator地址 的批量授权映射
mapping(address => mapping(address => bool)) private _operatorApprovals;

檢查owner的NFT是否將所持有的NFT都授權給operator

function isApprovedForAll(address owner, address operator)
    external
    view
    override
    returns (bool)
{
    return _operatorApprovals[owner][operator];
}

將自己所持有的NFT都授權給operator

function setApprovalForAll(address operator, bool approved) external override {
    _operatorApprovals[msg.sender][operator] = approved;
    emit ApprovalForAll(msg.sender, operator, approved);
}

查詢此tokenId對應到的NFT授權給哪個地址

function getApproved(uint tokenId) external view override returns (address) {
    require(_owners[tokenId] != address(0), "token doesn't exist");
    return _tokenApprovals[tokenId];
}

將該tokenId對應到的NFT授權給to地址

function _approve(
    address owner,
    address to,
    uint tokenId
) private {
    _tokenApprovals[tokenId] = to;
    emit Approval(owner, to, tokenId);
}

將該tokenId對應到的NFT授權給to地址 且to不能是owner且msg.sender是owner或授權地址

function approve(address to, uint tokenId) external override {
    address owner = _owners[tokenId];
    require(
        msg.sender == owner || _operatorApprovals[owner][msg.sender],
        "not owner nor approved for all"
    );
    _approve(owner, to, tokenId);
}

檢查spender地址是否可以使用tokenId 需要是owner或被授權地址

function _isApprovedOrOwner(
    address owner,
    address spender,
    uint tokenId
) private view returns (bool) {
    return (spender == owner ||
        _tokenApprovals[tokenId] == spender ||
        _operatorApprovals[owner][spender]);
}

通過調整_balances和_owner變量 將tokenId從from轉給to 同時釋放Transfer事件

條件限制:

  1. tokenId要被from擁有
  2. to不是0地址
function _transfer(
        address owner,
        address from,
        address to,
        uint tokenId
    ) private {
        require(from == owner, "not owner");
        require(to != address(0), "transfer to the zero address");

        _approve(owner, address(0), tokenId);

        _balances[from] -= 1;
        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);
}

將tokenId從from轉給to 會檢查合約接受者是否支持ERC721協議(IERC721Receiver-onERC721Received) 條件

  1. from與to不能是0地址
  2. tokenId代幣需存在,且被from擁有
function _safeTransfer(
    address owner,
    address from,
    address to,
    uint tokenId,
    bytes memory _data
) private {
    _transfer(owner, from, to, tokenId);
    _checkOnERC721Received(from, to, tokenId, _data);
}

安全轉帳,調用_safeTransfer函數

function safeTransferFrom(
    address from,
    address to,
    uint tokenId,
    bytes memory _data
) public override {
    address owner = ownerOf(tokenId);
    require(
        _isApprovedOrOwner(owner, msg.sender, tokenId),
        "not owner nor approved"
    );
    _safeTransfer(owner, from, to, tokenId, _data);
}

若要轉給合約,會調用IERC721Receiver-onERC721Received,以防止tokenId被不小心轉入黑洞

function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private {
    if (to.code.length > 0) {
        try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 retval) {
            if (retval != IERC721Receiver.onERC721Received.selector) {
                revert ERC721InvalidReceiver(to);
            }
        } catch (bytes memory reason) {
            if (reason.length == 0) {
                revert ERC721InvalidReceiver(to);
            } else {
                /// @solidity memory-safe-assembly
                assembly {
                    revert(add(32, reason), mload(reason))
                }
            }
        }
    }
}

鑄造tokenId給to 釋放transfer事件 條件

  1. tokenId尚不存在
  2. to不是0 address
function _mint(address to, uint tokenId) internal virtual {
    require(to != address(0), "mint to zero address");
    require(_owners[tokenId] == address(0), "token already minted");

    _balances[to] += 1;
    _owners[tokenId] = to;

    emit Transfer(address(0), to, tokenId);
}

銷毀tokenId,同時釋放Transfer事件 條件

  1. tokenId存在
function _burn(uint tokenId) internal virtual {
    address owner = ownerOf(tokenId);
    require(msg.sender == owner, "not owner of token");

    _approve(owner, address(0), tokenId);

    _balances[owner] -= 1;
    delete _owners[tokenId];

    emit Transfer(owner, address(0), tokenId);
}

小圖片url

function _baseURI() internal view virtual returns (string memory) {
    return "";
}

每張小圖片的url

function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
    require(_owners[tokenId] != address(0), "Token Not Exist");

    string memory baseURI = _baseURI();
    return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}