关于区块链:erc721中safeMint与mint的区别

18次阅读

共计 6059 个字符,预计需要花费 16 分钟才能阅读完成。

id:BSN_2021

公众号:BSN 研习社

以下文章来源于红枣科技张雪良

指标:向大家解释一下 erc721 中 safeMint 与 mint 的区别(safeTransferFrom 与 transferFrom 同理)

章节流程:

1. 实践为先

2. 猜想预期

3. 验证猜想

4. 论断

一、实践为先
首先,咱们看一下示例代码(文件 ERC721Upgradeable.sol 和 IERC721ReceiverUpgradeable.sol)

示例代码地址

(https://github.com/OpenZeppel…)

1. 文件 ERC721Upgradeable.sol 中的要害内容:


/**
 * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
 * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
 */
function _safeMint(
    address to,
    uint256 tokenId,
    bytes memory _data
) internal virtual {_mint(to, tokenId);
    require(_checkOnERC721Received(address(0), to, tokenId, _data),
        "ERC721: transfer to non ERC721Receiver implementer"
    );
}

/**
 * @dev Mints `tokenId` and transfers it to `to`.
 *
 * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
 *
 * Requirements:
 *
 * - `tokenId` must not exist.
 * - `to` cannot be the zero address.
 *
 * Emits a {Transfer} event.
 */
function _mint(address to, uint256 tokenId) internal virtual {require(to != address(0), "ERC721: mint to the zero address");
    require(!_exists(tokenId), "ERC721: token already minted");

    _beforeTokenTransfer(address(0), to, tokenId);

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

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

function _checkOnERC721Received(
    address from,
    address to,
    uint256 tokenId,
    bytes memory _data
) private returns (bool) {if (to.isContract()) {try IERC721ReceiverUpgradeable(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) {return retval == IERC721ReceiverUpgradeable.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;}
}

2. 文件 IERC721ReceiverUpgradeable.sol 中的内容:


// SPDX-License-Identifier: MIT
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 IERC721ReceiverUpgradeable {  /**

•@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);}
}

二、猜想预期

通过上述示例源码咱们能够发现:其实办法 safeTransferFrom 中额定校验了一下 IERC721ReceiverUpgradeable(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval)的执行后果是否通过(注:通过则返回正确的办法签名 -IERC721.onERC721Received.selector),通过交易胜利,如果不通过交易则回滚。

那接下来,咱们提出一个猜想:“接受方为合约账户时,能够通过重写办法 onERC721Received 进行交易的平安校验”。

三、验证猜想

咱们来筹备两个简略的合约,一个为自定义的 721 合约 MyToken721UUPSV1.sol,另一个为实现 onERC721Received 办法的自定义接管方合约 MyToken721UUPSV1_Holder.sol。

接管方合约验证来自 721 合约 mint 办法传递的参数 data 是否为预期数据,是则交易通过,不是则交易不通过使其回滚,达到平安的成果。

具体代码如下:

1. 文件 MyToken721UUPSV1.sol 中的内容:


// SPDX-License-Identifier: MIT pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract MyToken721UUPSV1 is ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {/// @custom:oz-upgrades-unsafe-allow constructor    constructor() initializer {}

function initialize() initializer public {__ERC721_init("MyToken", "MTK");
    __Ownable_init();
    __UUPSUpgradeable_init();}

// 传递 data 为“bsn”// 注:to 须要为合约账户
function safeMint(address to, uint256 tokenId) public onlyOwner {bytes memory data = new bytes(3);
    data[0]="b";
    data[1]="s";
    data[2]="n";
    _safeMint(to, tokenId,data);
}

// 传递 data 为“zxl”// 注:to 须要为合约账户
function safeMintZxl(address to, uint256 tokenId) public onlyOwner {bytes memory data = new bytes(3);
    data[0]="z";
    data[1]="x";
    data[2]="l";
    _safeMint(to, tokenId,data);
}

function _authorizeUpgrade(address newImplementation)
    internal
    onlyOwner
    override
{}

function GetInitializeData() public pure returns(bytes memory){return abi.encodeWithSignature("initialize()");
}

function myName() public view virtual returns (string memory){return "zxlv1";}
}

2. 文件 MyToken721UUPSV1_Holder .sol 中的内容:

// SPDX-License-Identifier: MIT 
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken721UUPSV1_Holder is OwnableUpgradeable , IERC721Receiver
{event ERC721Received( address operator,        address from,        uint256 tokenId,        bytes data);    // 用于接管合约发来的数据     function onERC721Received(address operator,        address from,        uint256 tokenId,        bytes calldata data) public override returns (bytes4) {emit ERC721Received(operator,from,tokenId,data);        //safeMint 传递的参数数据        bytes memory safedata = new bytes(3);        safedata[0]="b";        safedata[1]="s";        safedata[2]="n";        // 如果为 safeMint 则返回正确的后果,否则返回谬误的后果。if (equal(safedata,data)) {return this.onERC721Received.selector;} else {return this.myName.selector;}    }    function myName() public view virtual returns (string memory){return "MyToken721UUPSV1_Holder";}

function equal(bytes memory self_rep, bytes memory other_rep) internal pure returns(bool){if(self_rep.length != other_rep.length){return false;}
    uint selfLen = self_rep.length;
    for(uint i=0;i<selfLen;i++){if(self_rep[i] != other_rep[i]) return false;
    }
    return true;           
}
}

应用工具 MetaMask 和 Remix 部署合约至 Rinkeby 测试网络:

holder:0xe9E8F76524aE41C93Dd2066dFEd3B41fbf21f59B

721:0x376bAfBE6619233b8E4536f2f7CAb611d35BFb79

proxy:0x4955db0b2E5C437A5C9e431118967C3699e0Dc43

接下来咱们开始测试,别离调用 safeMint 办法和 safeMintZxl 办法

执行 721 合约的 safeMint 办法,执行胜利,详情如下:

(注:传递数据为 ”bsn”)

交易输入截图:

交易 hash:https://rinkeby.etherscan.io/tx

/0x4f6fdacb3a2221103da71126820d5309ae19341d787a77e9cfede26479eb5c56

后果验证:调用办法 ownerof 验证 1 是否 mint 至账户 0xe9E8F76524aE41C93Dd2066dFEd3B41fbf21f59B,截图如下:

在 etherscan 中查看输入日志: 我在合约里打印了下传递过去的参数

执行 721 合约的 safeMintZxl 办法,执行失败,详情如下:

(注:传递数据 ”zxl”)

咱们发现曾经提醒交易异样,如果执意执行发送交易,后果如下:


也能够多余的验证下 2 没有 mint 至账户 0xe9E8F76524aE41C93Dd2066dFEd3B41fbf21f59B,调用 ownerOf 返回 ERC721: owner query for nonexistent token, 调用 balanceOf 返回 1, 截图如下:

合乎预期猜想!!!

四、论断

当接管方为合约账户时,能够应用 safeXXX 办法 (safeMint 或者 safeTransferFrom) 进行交易的平安校验。如果是一般账户的话,两者的执行没有区别。

顺便提一下 erc1155 也是同样的情理。

以上为集体的观点,欢送大家一块交换。

正文完
 0