Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-7943: uRWA - 通用现实世界资产接口

用于常见基础代币的接口,定义了现实世界资产 (RWA) 的合规性检查、转账控制和强制执行操作。

Authors Dario Lo Buglio (@xaler5)
Created 2025-06-10
Discussion Link https://ethereum-magicians.org/t/erc-universal-rwa-interface/23972
Requires EIP-165

摘要

本 EIP 提出了通用 RWA (uRWA) 标准,这是一种用于链上代币化现实世界资产 (RWA)(例如证券、房地产、商品或其他实物/金融资产)的接口。

现实世界资产通常需要标准代币中没有的监管合规功能,包括冻结资产、执行强制转账以符合法律以及限制转账给授权用户的能力。uRWA 标准通过引入基本的合规性功能来扩展常见的代币标准,如 ERC-20ERC-721ERC-1155,同时保持最小化,并且不对具体的实现细节做出主观臆断。

这使得 DeFi 协议和应用程序能够以标准化的方式与代币化的现实世界资产进行交互,从而了解它们可以检查转账权限、用户是否被允许交互、适当地处理冻结的资产,并与符合标准的 RWA 代币集成,而不管底层资产类型或内部合规性逻辑如何。它还采用了 ERC-165 进行自省。

动机

现实世界资产 (RWA) 代表着弥合传统金融和去中心化金融 (DeFi) 的一个重要机会。通过将房地产、公司债券、商品、艺术品或证券等资产代币化,我们可以释放诸如部分所有权、可编程合规性、通过传统上缺乏流动性资产的二级市场增强流动性以及与去中心化协议集成等优势。

然而,代币化现实世界资产引入了纯数字资产中通常不存在的监管要求,例如用户白名单、转账限制、资产冻结或执法规则。现有的代币标准,如 ERC-20ERC-721ERC-1155,缺乏固有的结构来直接在标准本身中解决这些合规性需求。

历史上,定义通用 RWA 标准的尝试对不需要全套功能的更简单用例施加了不必要的复杂性和 gas 开销,全套功能包括精细的角色访问控制、强制性的链上白名单、特定链上身份解决方案或标准强制的元数据处理解决方案。

此外,广泛的 RWA 类别本质上表明需要摆脱一刀切的解决方案。为了定义 EIP,应牢记极简主义方法、非主观的功能列表和最大兼容的设计。

uRWA 标准旨在通过定义一个基本的接口,建立一个关于合规性和控制的交互的共同基础,而不规定底层的实现机制,从而寻求更精细的平衡。这允许核心代币实现保持精简,同时为 RWA 特定的交互提供标准功能。

最终目标是围绕 RWA 构建可组合的 DeFi,在处理合规性和监管时提供相同的接口。

规范

本文档中的关键词“必须 (MUST)”、“禁止 (MUST NOT)”、“必需 (REQUIRED)”、“应当 (SHALL)”、“不应当 (SHALL NOT)”、“应该 (SHOULD)”、“不应该 (SHOULD NOT)”、“推荐 (RECOMMENDED)”、“不推荐 (NOT RECOMMENDED)”、“可以 (MAY)”和“可选 (OPTIONAL)”应解释为 RFC 2119RFC 8174 中所述。

以下定义了 ERC-7943 代币合约的标准接口,该合约必须从基本代币接口(如 ERC-20ERC-721ERC-1155)扩展:

pragma solidity ^0.8.29;

/// @notice 定义 ERC-7943 接口,即 uRWA。
/// 在与特定代币标准交互时:
/// - 对于类似 ERC-721 的(非同质化)代币,'amount' 参数通常代表单个代币(即 1)。
/// - 对于类似 ERC-20 的(同质化)代币,'tokenId' 参数通常不适用,应设置为 0。
interface IERC7943 /*is IERC165*/ {
    /// @notice 当代币从一个地址转移到另一个地址时发出。
    /// @param from 代币被取出的地址。
    /// @param to 被扣押的代币转移到的地址。
    /// @param tokenId 被转移的代币的 ID。
    /// @param amount 被扣押的数量。
    event ForcedTransfer(address indexed from, address indexed to, uint256 tokenId, uint256 amount);

    /// @notice 当调用 `setFrozen` 时发出,改变 `user` 的 `tokenId` 代币的冻结 `amount`。
    /// @param user 其代币被冻结的用户的地址。
    /// @param tokenId 要冻结的代币的 ID。
    /// @param amount 更改后要冻结的代币数量。
    event Frozen(address indexed user, uint256 indexed tokenId, uint256 amount);

    /// @notice 当用户不允许交互时回滚的错误。
    /// @param account 不允许交互的用户的地址。
    error ERC7943NotAllowedUser(address account);

    /// @notice 当由于现有限制而不允许转账时回滚的错误。
    /// @param from 要从中转移代币的地址。
    /// @param to 要将代币转移到的地址。
    /// @param tokenId 被转移的代币的 ID。
    /// @param amount 被转移的数量。
    error ERC7943NotAllowedTransfer(address from, address to, uint256 tokenId, uint256 amount);

    /// @notice 当尝试从 `user` 转移 `tokenId` 的 `amount` 小于或等于其余额但大于其未冻结余额时回滚的错误。
    /// @param user 持有代币的地址。
    /// @param tokenId 被转移的代币的 ID。
    /// @param amount 被转移的数量。
    /// @param unfrozen 未冻结且可用于转移的代币数量。
    error ERC7943InsufficientUnfrozenBalance(address user, uint256 tokenId, uint256 amount, uint256 unfrozen);

    /// @notice 将代币从一个地址转移到另一个地址。
    /// @dev 需要特定授权。用于法规遵从或恢复场景。
    /// @param from 从中获取 `amount` 的地址。
    /// @param to 接收 `amount` 的地址。
    /// @param tokenId 被转移的代币的 ID。
    /// @param amount 要强制转移的数量。
    function forceTransfer(address from, address to, uint256 tokenId, uint256 amount) external;

    /// @notice 更改属于 `user` 的 `tokenId` 代币的 `amount` 的冻结状态。
    /// 这将覆盖当前值,类似于 `approve` 函数。
    /// @dev 需要特定授权。冻结的代币不能由用户转移。
    /// @param user 要冻结/解冻其代币的用户的地址。
    /// @param tokenId 要冻结/解冻的代币的 ID。
    /// @param amount 要冻结/解冻的代币数量。
    function setFrozen(address user, uint256 tokenId, uint256 amount) external;

    /// @notice 检查特定 `tokenId` 的冻结状态/数量。
    /// @param user 用户的地址。
    /// @param tokenId 代币的 ID。
    /// @return amount 当前为 `user` 冻结的 `tokenId` 代币的数量。
    function getFrozen(address user, uint256 tokenId) external view returns (uint256 amount);
 
    /// @notice 检查根据代币规则当前是否可以进行转账。它强制执行对冻结代币的验证。
    /// @dev 这可能涉及检查,例如白名单、黑名单、转账限制和其他策略定义的限制。
    /// @param from 发送代币的地址。
    /// @param to 接收代币的地址。
    /// @param tokenId 被转移的代币的 ID。
    /// @param amount 被转移的数量。
    /// @return allowed 如果允许转移,则为 True,否则为 False。
    function isTransferAllowed(address from, address to, uint256 tokenId, uint256 amount) external view returns (bool allowed);

    /// @notice 检查是否允许特定用户根据代币规则进行交互。
    /// @dev 这通常用于白名单/KYC/KYB/AML 检查。
    /// @param user 要检查的地址。
    /// @return allowed 如果允许用户,则为 True,否则为 False。
    function isUserAllowed(address user) external view returns (bool allowed);
}

isUserAllowedisTransferAllowedgetFrozen

这些提供了对实现合约的合规性、转账策略逻辑和冻结状态的视图。这些函数:

  • 禁止回滚 (MUST NOT revert)。
  • 禁止更改合约的存储 (MUST NOT change the storage of the contract)。
  • 可能依赖于上下文 (MAY depend on context)(例如,当前时间戳、区块号)。
  • ‘isTransferAllowed’ 必须验证被转移的 ‘amount’ 没有超过未冻结的数量 (未冻结的数量是当前余额和冻结余额之间的差值)。此外,它必须对 “from” 和 “to” 参数执行 “isUserAllowed” 检查。

forceTransfer

此函数提供了一种将资产从 from 地址强制转移到 to 地址的标准机制。此函数:

  • 必须直接操作余额或所有权,以便通过从 from 转移或销毁资产以及铸造到 to 来将资产从 from 转移到 to
  • 必须限制访问 (MUST be restricted in access)。
  • 必须执行必要的验证检查 (e.g., sufficient balance/ownership of a specific token)。
  • 必须发出标准转账事件(来自基本标准)和 ForcedTransfer 事件。
  • 它可以绕过冻结验证并相应地更新冻结状态。只有在这种情况下,它必须首先解冻资产并在反映更改的底层基本代币转账事件之前发出 Frozen 事件。在实际转账之前更改未冻结金额对于可能容易受到对接收者进行外部检查的重入攻击的代币至关重要,ERC-721ERC-1155 代币就是这种情况。
  • 必须绕过 isTransferAllowed 执行的检查。
  • 必须对 to 参数执行 isUserAllowed 检查。

setFrozen

它提供了一种冻结或解冻特定用户持有的资产的方法。这对于临时锁定机制非常有用。此函数:

  • 必须发出 Frozen 事件。
  • 必须限制访问 (MUST be restricted in access)。
  • 不应允许冻结比用户持有的资产更多的资产 (SHOULD NOT allow freezing more assets than those held by the user)。

附加规范

合约必须 (MUST) 实现 ERC-165 supportsInterface 函数,并且必须对 ERC-7943interfaceId 0xf35fc3bebytes4 值返回 true。

鉴于该标准对所使用的特定基础代币标准具有不可知性,对于基于 ERC-20 的实现,实现应使用 tokenId = 0,对于 ForcedTransferFrozen 事件、ERC7943NotAllowedTransferERC7943InsufficientUnfrozenBalance 错误以及 forceTransfersetFrozengetFrozenisTransferAllowed 函数中基于 ERC-721 的实现,则使用 amount = 1。集成者可以决定不强制执行此操作,但是该标准不鼓励这样做。这被认为是为不同的代币标准提供唯一的标准接口而不重叠语法的一种折衷方案。

此接口的实现必须实现其选择的基础标准的必要功能(例如,ERC-20ERC-721ERC-1155 功能),并且还必须使用适当的访问控制机制(例如,onlyOwner、基于角色的访问控制)限制对敏感功能(如 forceTransfersetFrozen)的访问。此接口标准不强制执行特定的机制。

实现必须确保其转账方法表现出以下行为:

  • 公共转账transfertransferFromsafeTransferFrom 等)在 isTransferAllowedisUserAllowed 将为 fromto 地址之一或两者返回 false 的情况下,必须不成功 (MUST NOT succeed)。
  • 铸造 必须不能成功转移到 isUserAllowed 会返回 false 的帐户 (MUST NOT succeed to accounts where isUserAllowed would return false)。
  • 销毁 不应受到对代币持有者的 isTransferAllowedisUserAllowed 检查的限制 (SHOULD NOT be restricted by isTransferAllowed or isUserAllowed checks on the token holder)。可以限制它以防止销毁比未冻结数量更多的资产(例如,在公共销毁函数中)。它可以销毁比未冻结数量更多的资产(例如,在授权的销毁函数中),在这种情况下,合约必须相应地更新冻结状态,并在底层的基本代币转账事件之前发出 Frozen 事件。

每当对 isUserAllowedisTransferAllowed 的内部调用返回 false 时,ERC7943NotAllowedTransferERC7943NotAllowedUser 错误都可以用作通用回滚机制 (CAN be used as a general revert mechanism)。这些错误可能不被使用,或者可能会被更具体的错误所取代,具体取决于在这些调用中执行的自定义检查。

通常,该标准优先考虑错误的特异性,这意味着如果诸如 ERC7943InsufficientUnfrozenBalance 之类的更具体的错误不适用,则应抛出诸如 ERC7943NotAllowedTransfer 之类的广泛错误。对于当尝试从 user 转移 tokenIdamount 小于或等于其余额,但大于其未冻结余额时应触发的 ERC7943InsufficientUnfrozenBalance 也是如此。但是,如果金额大于整个余额,并且与冻结金额无关,则应使用来自基本标准的更具体的错误。

理由

  • 极简主义: 仅定义常见 RWA 合规性和控制模式所需的必要函数(forceTransfersetFrozenisUserAllowedisTransferAllowedgetFrozen)以及相关的事件/错误,避免强制的复杂性或主观功能。引入特定错误(ERC7943NotAllowedUserERC7943NotAllowedTransferERC7943InsufficientUnfrozenBalance)的原因是为了提供与引入的功能(isUserAllowedisTransferAllowedgetFrozen)的完整性。正如规范中所述,错误特异性是优先考虑的,从而为实现提供更大的灵活性以适应更明确的错误。关于事件 FrozenForcedTransfer,它们存在的原因是发出 不常见 转账的信号(例如在 forceTransfer 中),而且还有助于链下索引器正确跟踪和说明资产扣押和冻结。正如规范中所述,应特别注意这些事件与基础代币合约事件相关的发出顺序。
  • 灵活的合规性: 为合规性检查提供标准视图函数(isUserAllowedisTransferAllowedgetFrozen),而不规定代币合约在内部 如何 实现这些检查。这允许多样化的合规性策略。
  • 兼容性: 被设计为与现有基础标准(如 ERC-20ERC-721ERC-1155)兼容的接口层。实现与它们的基础标准接口一起从 ERC-7943 扩展。
  • 基本的执行规则: 包括 forceTransfersetFrozen 作为标准函数,承认其对于 RWA 领域的监管执行的重要性,这与标准转账不同。强制执行此敏感功能的访问控制。
  • ERC-165 确保实现合约可以指示对此接口的支持。

例如,AMM 池或借贷协议可以通过调用 isUserAllowedisTransferAllowed 来以兼容的方式处理这些资产,从而与基于 ERC-7943ERC-20 代币集成。像 forceTransfersetFrozen 这样的强制执行操作可以由第三方实体调用,也可以由外部协议集成,以实现自动化和可编程的合规性。然后,用户可以使用其他功能扩展这些代币,以适应单个资产类型的特定需求,无论是使用链上身份系统、用于股息分配的历史余额跟踪、具有代币元数据的半同质化,还是其他自定义功能。

关于命名的注意事项:

  • forceTransfer: 选择此术语是因为它的中立性。虽然像“没收”、“撤销”或“收回”这样的名称描述了特定的动机,但 forceTransfer 纯粹表示转让资产的直接行为,而不管根本原因如何。
  • setFrozen / getFrozen: 选择这些名称是为了管理转让限制。
    • 整合方法: 为了保持 EIP 的精简,我们倾向于使用单个 setFrozen 函数(它会覆盖冻结的资产数量)和一个 Frozen 事件,而不是不同的 freeze / unfreeze 函数和事件。
    • 术语: 选择“Frozen”是因为它通常适用于同质化(基于数量)和非同质化(基于状态)资产,因为像“amount”或“asset(s)”这样的术语可能不是普遍适用的。
  • ERC7943InsufficientUnfrozenBalance: 出现了关于“insufficient”与“unavailable”相似的讨论,其中“unavailable”可能更好地表明了像冻结状态这样的时间条件。然而,术语“available”/“unavailable”也与“frozen”/“unfrozen”重叠,造成了更多的混淆和二元性。最后,根据 ERC-6093 指南,将“insufficient”与指定的“unfrozen balance”结合起来可以更好地表示错误的域、前缀和主题。

向后兼容性

此 EIP 定义了一个新的接口标准,并且不更改现有的标准,如 ERC-20ERC-721ERC-1155。标准钱包和浏览器可以与实现合约的基础代币功能进行交互,但要遵守该合约对 isUserAllowedisTransferAllowedgetFrozen 函数的实现所强制执行的规则。ERC-7943 功能的完整支持需要显式集成。

参考实现

ERC-20ERC-721ERC-1155 的基本实现的示例,其中包括用户基本白名单和可枚举的基于角色的访问控制:

ERC-20 示例

pragma solidity ^0.8.29;

/* required imports ... */

contract uRWA20 is Context, ERC20, AccessControlEnumerable, IERC7943 {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
    bytes32 public constant ENFORCER_ROLE = keccak256("ENFORCER_ROLE");
    bytes32 public constant WHITELIST_ROLE = keccak256("WHITELIST_ROLE");

    mapping(address user => bool whitelisted) public isWhitelisted;
    mapping(address user => uint256 amount) internal _frozenTokens;

    event Whitelisted(address indexed account, bool status);
    error NotZeroAddress();

    constructor(string memory name, string memory symbol, address initialAdmin) ERC20(name, symbol) {
        /* give initialAdmin necessary roles ...*/
        // 给予初始管理员必要的角色...
    }

    function isTransferAllowed(address from, address to, uint256, uint256 amount) public virtual view returns (bool allowed) {
        if (amount > balanceOf(from) - _frozenTokens[from]) return;
        if (!isUserAllowed(from) || !isUserAllowed(to)) return;
        allowed = true;
    }

    function isUserAllowed(address user) public virtual view returns (bool allowed) {
        if (isWhitelisted[user]) allowed = true;
    } 

    function getFrozen(address user, uint256) external view returns (uint256 amount) {
        amount = _frozenTokens[user];
    }

    function changeWhitelist(address account, bool status) external onlyRole(WHITELIST_ROLE) {
        require(account != address(0), NotZeroAddress());
        isWhitelisted[account] = status;
        emit Whitelisted(account, status);
    }

    /* standard mint and burn functions with access control ...*/ 
    // 具有访问控制的标准铸造和销毁功能...

    function setFrozen(address user, uint256, uint256 amount) public onlyRole(ENFORCER_ROLE) {
        require(amount <= balanceOf(user), IERC20Errors.ERC20InsufficientBalance(user, balanceOf(user), amount));
        _frozenTokens[user] = amount;
        emit Frozen(user, 0, amount);
    }

    function forceTransfer(address from, address to, uint256, uint256 amount) public onlyRole(ENFORCER_ROLE) {
        require(isUserAllowed(to), ERC7943NotAllowedUser(to));
        _excessFrozenUpdate(from, amount);
        super._update(from, to, amount);
        emit ForcedTransfer(from, to, 0, amount);
    }

    function _excessFrozenUpdate(address user, uint256 amount) internal {
        uint256 unfrozenBalance = balanceOf(user) - _frozenTokens[user];
        if(amount > unfrozenBalance && amount <= balanceOf(user)) { 
            // Protect from underflow: if amount > balanceOf(user) the call will revert in super._update with insufficient balance error
            // 防止下溢:如果 amount > balanceOf(user),则调用将在 super._update 中回滚,并出现余额不足错误
            _frozenTokens[user] -= amount - unfrozenBalance; // Reduce by excess amount
            // 减少过剩数量
            emit Frozen(user, 0, _frozenTokens[user]);
        }
    }

    function _update(address from, address to, uint256 amount) internal virtual override {
        if (from != address(0) && to != address(0)) { // Transfer
            // 转账
            require(amount <= balanceOf(from), IERC20Errors.ERC20InsufficientBalance(from, balanceOf(from), amount));
            require(amount <= balanceOf(from) - _frozenTokens[from], ERC7943InsufficientUnfrozenBalance(from, 0, amount, balanceOf(from) - _frozenTokens[from]));
            require(isTransferAllowed(from, to, 0, amount), ERC7943NotAllowedTransfer(from, to, 0, amount));
        } else if (from == address(0)) { // Mint
            // 铸造
            require(isUserAllowed(to), ERC7943NotAllowedUser(to));
        } else { // Burn
            // 销毁
            _excessFrozenUpdate(from, amount);
        }

        super._update(from, to, amount);
    }

    function supportsInterface(bytes4 interfaceId) public view virtual override(AccessControlEnumerable, IERC165) returns (bool) {
        return interfaceId == type(IERC7943).interfaceId ||
            interfaceId == type(IERC20).interfaceId ||
            super.supportsInterface(interfaceId);
    }
}

ERC-721 示例

pragma solidity ^0.8.29;

/* required imports ... */

contract uRWA721 is Context, ERC721, AccessControlEnumerable, IERC7943 {
    /* same definitions, constructor and changeWhitelist function as before ...*/
    // 与之前相同的定义、构造函数和 changeWhitelist 函数...
    
    mapping(address user => mapping(uint256 tokenId => uint8 frozen)) internal _frozenTokens;

    function isUserAllowed(address user) public view virtual override returns (bool allowed) {
        if (isWhitelisted[user]) allowed = true;        
    }

    function isTransferAllowed(address from, address to, uint256 tokenId, uint256) public view virtual override returns (bool allowed) {
        address owner = _ownerOf(tokenId);
        if (owner != from || owner == address(0)) return;
        if (!isUserAllowed(from) || !isUserAllowed(to)) return;
        if (_frozenTokens[from][tokenId] > 0) return;
        allowed = true;
    }

    function getFrozen(address user, uint256 tokenId) external view returns (uint256 amount) {
        amount = _frozenTokens[user][tokenId];
    }

    function setFrozen(address user, uint256 tokenId, uint256 amount) public onlyRole(ENFORCER_ROLE) {
        require(user == ownerOf(tokenId), IERC721Errors.ERC721InvalidOwner(user));
        require(amount == 0 || amount == 1, InvalidAmount(amount));
        _frozenTokens[user][tokenId] = uint8(amount);
        emit Frozen(user, tokenId, amount);
    }

    function forceTransfer(address from, address to, uint256 tokenId, uint256) public virtual override onlyRole(ENFORCER_ROLE) {
        require(to != address(0), ERC721InvalidReceiver(address(0)));
        require(isUserAllowed(to), ERC7943NotAllowedUser(to));
        _excessFrozenUpdate(from , tokenId);
        super._update(to, tokenId, address(0)); // Skip _update override
        // 跳过 _update 覆盖
        ERC721Utils.checkOnERC721Received(_msgSender(), from, to, tokenId, "");
        emit ForcedTransfer(from, to, tokenId, 1);
    }

    function _excessFrozenUpdate(address from, uint256 tokenId) internal {
        _validateCorrectOwner(from, tokenId);
        if(_frozenTokens[from][tokenId] > 0) {
            _frozenTokens[from][tokenId] = 0; // Unfreeze the token if it was frozen
            // 如果代币已冻结,则解冻代币
            emit Frozen(from, tokenId, 0);
        }
    }

    function _validateCorrectOwner(address claimant, uint256 tokenId) internal view {
        address currentOwner = ownerOf(tokenId);
        require(currentOwner == claimant, ERC721IncorrectOwner(claimant, tokenId, currentOwner));
    }

    /* standard mint function with access control ...*/ 
    //具有访问控制的标准铸造功能...

    function burn(uint256 tokenId) external virtual onlyRole(BURNER_ROLE) {
        address previousOwner = _update(address(0), tokenId, _msgSender()); 
        if (previousOwner == address(0)) revert ERC721NonexistentToken(tokenId);
    }

    function _update(address to, uint256 tokenId, address auth) internal virtual override returns(address) {
        address from = _ownerOf(tokenId);

        if (auth != address(0)) {
            _checkAuthorized(from, auth, tokenId);
        }

        if (from != address(0) && to != address(0)) { // Transfer
            // 转账
            _validateCorrectOwner(from, tokenId);
            require(_frozenTokens[from][tokenId] == 0, ERC7943InsufficientUnfrozenBalance(from, tokenId, 1, 0));
            require(isTransferAllowed(from, to, tokenId, 1), ERC7943NotAllowedTransfer(from, to, tokenId, 1));
        } else if (from == address(0)) { // Mint
            // 铸造
            require(isUserAllowed(to), ERC7943NotAllowedUser(to));
        } else { // Burn
            // 销毁
            _excessFrozenUpdate(from, tokenId);
        } 

        return super._update(to, tokenId, auth);
    }

    function supportsInterface(bytes4 interfaceId) public view virtual override(AccessControlEnumerable, ERC721, IERC165) returns (bool) {
        return interfaceId == type(IERC7943).interfaceId ||
               super.supportsInterface(interfaceId);
    }
}

ERC-1155 示例

```solidity pragma solidity ^0.8.29;

/* required imports … */

contract uRWA1155 is Context, ERC1155, AccessControlEnumerable, IERC7943 {

/* same definitions, constructor and changeWhitelist function as before ...*/
// 与之前相同的定义、构造函数和 changeWhitelist 函数...

mapping(address user => mapping(uint256 tokenId => uint256 amount)) internal _frozenTokens;

function isTransferAllowed(address from, address to, uint256 tokenId, uint256 amount) public view virtual override returns (bool allowed) {
    if (balanceOf(from, tokenId) < amount) return;
    if (!isUserAllowed(from) || !isUserAllowed(to)) return;
    if (amount > balanceOf(from, tokenId) - _frozenTokens[from][tokenId]) return;
    allowed = true;
}

function isUserAllowed(address user) public view virtual override returns (bool allowed) {
    if (isWhitelisted[user]) allowed = true;        
}

function getFrozen(address user, uint256 tokenId) external view returns (uint256 amount) {
    amount = _frozenTokens[user][tokenId];
}

function setFrozen(address user, uint256 tokenId, uint256 amount) public onlyRole(ENFORCER_ROLE) {
    require(amount <= balanceOf(user, tokenId), ERC1155InsufficientBalance(user, balanceOf(user,tokenId), amount, tokenId));
    _frozenTokens[user][tokenId] = amount;        
    emit Frozen(user, tokenId, amount);
}

function forceTransfer(address from, address to, uint256 tokenId, uint256 amount) public onlyRole(ENFORCER_ROLE) {
    require(isUserAllowed(to), ERC7943NotAllowedUser(to));

    // Reimplementing _safeTransferFrom to avoid the check on _update
    // 重新实现 _safeTransferFrom 以避免对 _update 进行检查
    if (to == address(0)) {
        revert ERC1155InvalidReceiver(address(0));
    }
    if (from == address(0)) {
        revert ERC1155InvalidSender(address(0));
    }

    _excessFrozenUpdate(from, tokenId, amount);

    uint256[] memory ids = new uint256[](1);
    uint256[] memory values = new uint256[](1);
    ids[0] = tokenId;
    values[0] = amount;

    super._update(from, to, ids, values);
    
    if (to != address(0)) {
        address operator = _msgSender();
        if (ids.length == 1) {
            uint256 id = ids[0];
            uint256 value = values[0];
            ERC1155Utils.checkOnERC1155Received(operator, from, to, id, value, "");
        } else {
            ERC1155Utils.checkOnERC1155BatchReceived(operator, from, to, ids, values, "");
        }
    } 

    emit ForcedTransfer(from, to, tokenId, amount);
}

function _excessFrozenUpdate(address user, uint256 tokenId, uint256 amount) internal {
    uint256 unfrozenBalance = balanceOf(user, tokenId) - _frozenTokens[user][tokenId];
    if(amount > unfrozenBalance && amount <= balanceOf(user, tokenId)) { 
        // Protect from underflow: if amount > balanceOf(user) the call will revert in super._update with insufficient balance error
        // 防止下溢:如果 amount > balanceOf(user),则调用将在 super._update 中回滚,并出现余额不足错误
        _frozenTokens[user][tokenId] -= amount - unfrozenBalance; // Reduce by excess amount
        // 减少过剩数量
        emit Frozen(user, tokenId, _frozenTokens[user][tokenId]);
    }
}

/* standard mint and burn functions with access control ...*/ 
//具有访问控制的标准铸造和销毁功能...

function _update(address from, address to, uint256[] memory ids, uint256[] memory values) internal virtual override {
    if (ids.length != values.length) {
        revert ERC1155InvalidArrayLength(ids.length, values.length);
    }

    if (from != address(0) && to != address(0)) { // Transfer
        // 转账
        for (uint256 i = 0; i < ids.length; ++i) {
            uint256 id = ids[i];
            uint256 value = values[i];
            uint256 unfrozenBalance = balanceOf(from, id) - _frozenTokens[from][id];

            require(value <= balanceOf(from, id), ERC1155InsufficientBalance(from, balanceOf

Citation

Please cite this document as:

Dario Lo Buglio (@xaler5), "ERC-7943: uRWA - 通用现实世界资产接口 [DRAFT]," Ethereum Improvement Proposals, no. 7943, June 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7943.