Alert Source Discuss
⚠️ Review Standards Track: ERC

ERC-6997: 具有交易验证步骤的 ERC-721

用于转移和批准调用的新验证步骤,在钱包被盗的情况下实现安全步骤。

Authors Eduard López i Fina (@eduardfina)
Created 2023-05-07
Requires EIP-721

摘要

此标准是 ERC-721 的扩展。它定义了新的验证功能以避免钱包被盗:每个 transferapprove 都将被锁定等待验证。

动机

区块链的力量同时也是它的弱点:让用户对他们的数据负全部责任。

目前存在许多 NFT 盗窃案例,并且当前的 NFT 防盗方案(例如将 NFT 转移到冷钱包)使得 NFT 使用起来很不方便。

在每次 transferapprove 之前进行验证步骤,将使智能合约开发者有机会创建安全的 NFT 防盗方案。

一个实现示例是一个系统,其中验证器地址负责验证所有智能合约交易。

此地址将连接到一个 dApp,用户可以在其中查看其 NFT 的验证请求并接受正确的验证请求。

仅赋予该地址验证交易的权力将使系统更加安全,窃取 NFT 的窃贼必须同时拥有用户的地址和验证器地址。

规范

本文档中的关键词“必须”,“不得”,“必需”,“应该”,“不应该”,“推荐”,“可以”和“可选”应按照 RFC 2119 中的描述进行解释。

符合 ERC-721 的合约可以实现此 EIP。

所有更改 NFT 所有权的操作,例如 transferFrom/safeTransferFrom,都应创建一个待验证的 TransferValidation 并发出 ValidateTransfer,并且不应转移 NFT 的所有权。

所有允许批准管理 NFT 的操作,例如 approve/setApprovalForAll,都应创建一个待验证的 ApprovalValidation 并发出 ValidateApproval,并且不应启用批准。

当转账由批准的帐户而不是所有者调用时,必须直接执行它,而无需验证。这是为了适应所有当前需要批准才能直接移动你的 NFT 的市场。

验证 TransferValidationApprovalValidation 时,有效字段必须设置为 true,并且不得再次验证。

验证 TransferValidation 的操作应更改 NFT 的所有权或启用批准。

验证 ApprovalValidation 的操作应启用批准。

合约接口

 interface IERC6997 {

    struct TransferValidation {
        // The address of the owner.
        address from;
        // The address of the receiver.
        address to;
        // The token Id.
        uint256 tokenId;
        // Whether is a valid transfer.
        bool valid;
    }

    struct ApprovalValidation {
        // The address of the owner.
        address owner;
        // The approved address.
        address approve;
        // The token Id.
        uint256 tokenId;
        // Whether it is a total approval.
        bool approveAll;
        // Whether it is a valid approval.
        bool valid;
    }

    /**
     * @dev Emitted when a new transfer validation has been requested.
     */
    event ValidateTransfer(address indexed from, address to, uint256 indexed tokenId, uint256 indexed transferValidationId);

    /**
    * @dev Emitted when a new approval validation has been requested.
    */
    event ValidateApproval(address indexed owner, address approve, uint256 tokenId, bool indexed approveAll, uint256 indexed approvalValidationId);

    /**
     * @dev Returns true if this contract is a validator ERC721.
     */
    function isValidatorContract() external view returns (bool);

    /**
     * @dev Returns the transfer validation struct using the transfer ID.
     *
     */
    function transferValidation(uint256 transferId) external view returns (TransferValidation memory);

    /**
    * @dev Returns the approval validation struct using the approval ID.
    *
    */
    function approvalValidation(uint256 approvalId) external view returns (ApprovalValidation memory);

    /**
     * @dev Return the total amount of transfer validations created.
     *
     */
    function totalTransferValidations() external view returns (uint256);

    /**
     * @dev Return the total amount of transfer validations created.
     *
     */
    function totalApprovalValidations() external view returns (uint256);
}

isValidatorContract() 函数必须实现为 public

transferValidation(uint256 transferId) 函数可以实现为 publicexternal

approvalValidation(uint256 approveId) 函数可以实现为 publicexternal

totalTransferValidations() 函数可以实现为 pureview

totalApprovalValidations() 函数可以实现为 pureview

原理

普遍性

该标准仅定义了验证功能,但未定义应如何使用它们。它将验证定义为内部的,并让用户决定如何管理它们。

一个例子是拥有一个连接到 dApp 的地址验证器,以便用户可以管理他们的验证。

此验证器可以用于所有 NFT,也可以仅用于某些用户。

它也可以用作现有 ERC-721 的包装智能合约,允许与现有 NFT 进行 1/1 转换。

扩展性

该标准仅定义了验证功能,但未定义必须使用何种系统进行验证。第三方协议可以根据需要定义它希望如何调用这些功能。

向后兼容性

此标准是 ERC-721 的扩展,与除 transferFrom/safeTransferFrom/approve/setApprovalForAll 之外的所有操作兼容。

这些操作将被覆盖以创建一个验证申请,而不是转移 NFT 的所有权或启用批准。

参考实现

// SPDX-License-Identifier: CC0-1.0

pragma solidity ^0.8.0;

import "./IERC6997.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

/**
 * @dev Implementation of ERC6997
 */
contract ERC6997 is IERC6997, ERC721 {

    // Mapping from transfer ID to transfer validation
    // 从转移 ID 到转移验证的映射
    mapping(uint256 => TransferValidation) private _transferValidations;

    // Mapping from approval ID to approval validation
    // 从批准 ID 到批准验证的映射
    mapping(uint256 => ApprovalValidation) private _approvalValidations;

    // Total number of transfer validations
    // 转移验证总数
    uint256 private _totalTransferValidations;

    // Total number of approval validations
    // 批准验证总数
    uint256 private _totalApprovalValidations;

    /**
     * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
     */
    constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_){
    }

    /**
    * @dev Returns true if this contract is a validator ERC721.
    */
    function isValidatorContract() public pure returns (bool) {
        return true;
    }

    /**
     * @dev Returns the transfer validation struct using the transfer ID.
     *
     */
    function transferValidation(uint256 transferId) public view override returns (TransferValidation memory) {
        require(transferId < _totalTransferValidations, "ERC6997: invalid transfer ID");
        TransferValidation memory v = _transferValidation(transferId);

        return v;
    }

    /**
     * @dev Returns the approval validation struct using the approval ID.
     *
     */
    function approvalValidation(uint256 approvalId) public view override returns (ApprovalValidation memory) {
        require(approvalId < _totalApprovalValidations, "ERC6997: invalid approval ID");
        ApprovalValidation memory v = _approvalValidation(approvalId);

        return v;
    }

    /**
     * @dev Return the total amount of transfer validations created.
     *
     */
    function totalTransferValidations() public view override returns (uint256) {
        return _totalTransferValidations;
    }

    /**
     * @dev Return the total amount of approval validations created.
     *
     */
    function totalApprovalValidations() public view override returns (uint256) {
        return _totalApprovalValidations;
    }

    /**
     * @dev Returns the transfer validation of the `transferId`. Does NOT revert if transfer doesn't exist
     */
    function _transferValidation(uint256 transferId) internal view virtual returns (TransferValidation memory) {
        return _transferValidations[transferId];
    }

    /**
     * @dev Returns the approval validation of the `approvalId`. Does NOT revert if transfer doesn't exist
     */
    function _approvalValidation(uint256 approvalId) internal view virtual returns (ApprovalValidation memory) {
        return _approvalValidations[approvalId];
    }

    /**
     * @dev Validate the transfer using the transfer ID.
     *
     */
    function _validateTransfer(uint256 transferId) internal virtual {
        TransferValidation memory v = transferValidation(transferId);
        require(!v.valid, "ERC6997: the transfer is already validated");

        address from = v.from;
        address to = v.to;
        uint256 tokenId = v.tokenId;

        super._transfer(from, to, tokenId);

        _transferValidations[transferId].valid = true;
    }

    /**
     * @dev Validate the approval using the approval ID.
     *
     */
    function _validateApproval(uint256 approvalId) internal virtual {
        ApprovalValidation memory v = approvalValidation(approvalId);
        require(!v.valid, "ERC6997: the approval is already validated");

        if(!v.approveAll) {
            require(v.owner == ownerOf(v.tokenId), "ERC6997: The token have a new owner");
            super._approve(v.approve, v.tokenId);
        }
        else {
            super._setApprovalForAll(v.owner, v.approve, true);
        }

        _approvalValidations[approvalId].valid = true;
    }

    /**
     * @dev Create a transfer petition of `tokenId` from `from` to `to`.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {TransferValidate} event.
     */
    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual override {
        require(ERC721.ownerOf(tokenId) == from, "ERC6997: transfer from incorrect owner");
        require(to != address(0), "ERC6997: transfer to the zero address");

        if(_msgSender() == from) {
            TransferValidation memory v;

            v.from = from;
            v.to = to;
            v.tokenId = tokenId;

            _transferValidations[_totalTransferValidations] = v;

            emit ValidateTransfer(from, to, tokenId, _totalTransferValidations);

            _totalTransferValidations++;
        } else {
            super._transfer(from, to, tokenId);
        }
    }

    /**
     * @dev Create an approval petition from `to` to operate on `tokenId`
     *
     * Emits an {ValidateApproval} event.
     */
    function _approve(address to, uint256 tokenId) internal override virtual {
        ApprovalValidation memory v;

        v.owner = ownerOf(tokenId);
        v.approve = to;
        v.tokenId = tokenId;

        _approvalValidations[_totalApprovalValidations] = v;

        emit ValidateApproval(v.owner, to, tokenId, false, _totalApprovalValidations);

        _totalApprovalValidations++;
    }

    /**
     * @dev If approved is true create an approval petition from `operator` to operate on
     * all of `owner` tokens, if not remove `operator` from operate on all of `owner` tokens
     *
     * Emits an {ValidateApproval} event.
     */
    function _setApprovalForAll(
        address owner,
        address operator,
        bool approved
    ) internal override virtual {
        require(owner != operator, "ERC6997: approve to caller");

        if(approved) {
            ApprovalValidation memory v;

            v.owner = owner;
            v.approve = operator;
            v.approveAll = true;

            _approvalValidations[_totalApprovalValidations] = v;

            emit ValidateApproval(v.owner, operator, 0, true, _totalApprovalValidations);

            _totalApprovalValidations++;
        }
        else {
            super._setApprovalForAll(owner, operator, approved);
        }
    }
}

安全考虑

正如规范中所定义的那样,更改 NFT 所有权或启用批准以管理 NFT 的操作应创建一个待验证的 TransferValidationApprovalValidation,并且不应转移 NFT 的所有权或启用批准。

考虑到这一前提,负责验证 TransferValidationApprovalValidation 的操作必须受到应用系统所需的最大安全性的保护。

例如,一个有效的系统是其中有一个验证器地址负责验证交易。

再举一个例子,每个用户都可以选择他的验证器地址的系统也是正确的。

在任何情况下,安全性的重要性在于,未经所选系统许可,任何地址都无法验证 TransferValidationApprovalValidation

版权

版权及相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Eduard López i Fina (@eduardfina), "ERC-6997: 具有交易验证步骤的 ERC-721 [DRAFT]," Ethereum Improvement Proposals, no. 6997, May 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6997.