erc721中safeMint与mint的区别

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

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

holder:0xe9E8F76524aE41C93Dd2066dFEd3B41fbf21f59B

721:0x376bAfBE6619233b8E4536f2f7CAb611d35BFb79

proxy:0x4955db0b2E5C437A5C9e431118967C3699e0Dc43

接下来我们开始测试,分别调用safeMint方法和safeMintZxl方法

执行721合约的safeMint方法,执行成功,详情如下:

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

交易输出截图:

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

/0x4f6fdacb3a2221103da71126820d5309ae19341d787a77e9cfede26479eb5c56
erc721中safeMint与mint的区别_第1张图片
结果验证:调用方法ownerof验证1是否mint至账户0xe9E8F76524aE41C93Dd2066dFEd3B41fbf21f59B,截图如下:
erc721中safeMint与mint的区别_第2张图片
在etherscan中查看输出日志: 我在合约里打印了下传递过来的参数
erc721中safeMint与mint的区别_第3张图片
执行721合约的safeMintZxl方法,执行失败,详情如下:

(注:传递数据"zxl")
erc721中safeMint与mint的区别_第4张图片
我们发现已经提示交易异常,如果执意执行发送交易,结果如下:

erc721中safeMint与mint的区别_第5张图片
也可以多余的验证下2没有mint至账户0xe9E8F76524aE41C93Dd2066dFEd3B41fbf21f59B,调用ownerOf返回ERC721: owner query for nonexistent token,调用balanceOf返回1,截图如下:
erc721中safeMint与mint的区别_第6张图片
符合预期猜测!!!

四、结论

当接收方为合约账户时,可以使用safeXXX方法(safeMint或者safeTransferFrom)进行交易的安全校验。如果是普通账户的话,两者的执行没有区别。

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

以上为个人的观点,欢迎大家一块交流。

你可能感兴趣的:(区块链)