马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
ERC-6909 Token标准是 ERC-1155 Token标准的一种简化替代方案。
ERC-1155 标准引入了一种多Token接口,使得单个智能合约可以或许联合可替代的和不可替代的Token(即,ERC20 和 ERC721)。
ERC-1155 办理了多个挑战,比方低落摆设本钱、最小化以太坊区块链上的冗余字节码,以及简化多Token交易的Token批准流程。
然而,由于每次转账都强制要求回调、强制包罗批量转账,以及缺乏对单操纵员批准方案的细粒度控制,它引入了一些膨胀和气体低效问题。ERC-6909 通过消除合约级回调和批量转账,并用混合(限额-操纵员)权限方案替代单操纵员的凭据方案,从而办理了这些缺点,以实现颗粒化的Token管理。
留意: 以下部分假设读者对 ERC-1155 标准及其概念有肯定的相识。如果你不认识,请在继续之前阅读相干内容。
ERC-6909 和 ERC-1155 标准的对比
ERC-6909 移除转账的回调要求
ERC-1155 规范要求 safeTransferFrom 和 safeBatchTransferFrom 检查吸收账户是否为合约。如果是,则必须在吸收合约账户上调用 ERC1155TokenReceiver 接口函数(onERC1155Received,onERC1155BatchReceived)以检查其是否接受转账。
这些回调在某些情况下是有用的。然而,对于盼望不使用这种行为的吸收方,它们是不必要的外部调用。回调影响吸收合约账户的Gas本钱和代码巨细,因为它们必要实现多个回调(即,通过 onERC1155Received,onERC1155BatchReceived)并返回把戏的 4 字节值以吸收Token。相比之下,ERC-6909 的实现者可以自定义他们的回调架构。
ERC-6909 省略了批量转账逻辑
尽管批量转账有时是有益的,但在 ERC-6909 标准中故意省略,以答应开辟者根据特定执行环境实验批量转账逻辑。开辟者可以根据本身的必要实现批量转账,而无需仅仅为了遵循标准而添加额外的批量转账函数。
下面展示的 safeBatchTransferFrom 函数在 ERC-1155 标准中执行批量转账。然而,其强制的包罗为不必要它们的应用程序增加了膨胀:
- // ERC-1155
- function safeBatchTransferFrom(
- address _from,
- address _to,
- uint256[] calldata _ids,
- uint256[] calldata _values,
- bytes calldata _data
- ) external;
复制代码 以下是 ERC-6909 transferFrom 函数。我们可以看到批量特性和 _data 参数已被删除。
- // ERC-6909
- function transferFrom(
- address sender,
- address receiver,
- uint256 id,
- uint256 amount
- ) public returns (bool) {
- if (sender != msg.sender && !isOperator[sender][msg.sender]) {
- uint256 senderAllowance = allowance[sender][msg.sender][id];
- if (senderAllowance < amount) revert InsufficientPermission();
- if (senderAllowance != type(uint256).max) {
- allowance[sender][msg.sender][id] = senderAllowance - amount;
- }
- }
- if (balanceOf[sender][id] < amount) revert InsufficientBalance();
- balanceOf[sender][id] -= amount;
- balanceOf[receiver][id] += amount;
- emit Transfer(msg.sender, sender, receiver, id, amount);
- return true;
- }
复制代码 ERC-6909 同时支持全局批准和细粒度限额
- // 在 ERC-1155 →
- function setApprovalForAll(
- address _operator,
- bool _approved
- ) external;
复制代码 上面展示的 setApprovalForAll 函数是 ERC-1155 中的全局操纵员模子,答应一个账户授权另一个账户管理(作为操纵员)其所有Token ID 的操纵。一旦被授权,操纵员可以随意转移授权账户拥有的任何数量的任何Token ID。
固然这种方法简化了委托,但缺乏细粒度控制:
- 没有办法授予特定于单个Token ID 或数量的权限。
- 这种全有或全无的方法不适合必要受控权限的场景。
为引入细粒度控制,ERC-6909 混合操纵员权限方案包罗以下内容:
- 来自 ERC-1155 的操纵员模子,
- 和受 ERC-20 启发的限额模子。
ERC-6909 中的操纵员模子
在下面的 ERC-6909 setOperator 函数中,spender 变量被设置为操纵员,并被授权无条件的权限,以转移账户所拥有的所有Token ID 而没有限额限定。
- function setOperator(address spender, bool approved) public returns (bool) {
- isOperator[msg.sender][spender] = approved;
- emit OperatorSet(msg.sender, spender, approved);
- return true;
- }
复制代码 ERC-6909 中的限额模子
限额模子引入了一种特定于Token和数量的控制系统,其中一个账户可以为特定Token ID 设置有限的限额。
比方,Alice 可以答应 Bob 转移 100 个 ID 为 42 的Token,而不授予对其他Token ID 或不受限定的数量的访问权限,使用下一个展示的 ERC-6909 中的 approve 函数。
- function approve(address spender, uint256 id, uint256 amount) public returns (bool) {
- allowance[msg.sender][spender][id] = amount;
- emit Approval(msg.sender, spender, id, amount);
- return true;
- }
复制代码 在 approve 中的 spender 变量是被授权代表Token所有者转移特定金额的特定Token ID 的账户。
比方,Token所有者可以答应 spender 转移 <= 100 个特定Token ID。大概,他们还可以通过将限额设置为 type(uint256).max 来为特定Token ID 授予无穷定的批准。
ERC-6909 并没有指定是否应扣减设置为 type(uint256).max 的限额。相反,这种行为留给实现者的自由裁量权,类似于 ERC-20。
焦点数据布局
ERC-6909 实现使用三个映射来更新账户余额和批准状态。
balanceOf:ID 的所有者余额
balanceOf 映射跟踪特定Token ID 由地址 (owner) 持有的余额。映射中的 owner => (id => amount) 布局表示单个所有者可以持有多个Token,并通过各自的 ID 跟踪别的额。
- mapping(address owner => mapping(uint256 id => uint256 amount)) public balanceOf;
复制代码 allowance:ID 的支出者限额
答应映射定义支出者在所有者的授权下可以转移多少特定Token(ID)。它促进了对Token支出的细粒度控制。
- mapping(address owner => mapping(address spender => mapping(uint256 id => uint256 amount))) public allowance;
复制代码 比方,allowance[0xDEF...][0x123...][5] 将返回所有者 0xDEF... 答应支出者 0x123... 转移的Token(ID 为 5)的数量。
isOperator:操纵员批准状态
- mapping(address owner => mapping(address operator => bool isOperator)) public isOperator;
复制代码 该映射跟踪支出者是否被批准作为拥有地址的所有Token的操纵员。比方,isOperator[0x123...][0xABC...] 返回 true 如果地址 0xABC... 被答应支出地址 0x123... 所拥有的Token;否则返回 false。
焦点 ERC-6909 功能及其数据参数
转账函数
该规范并未遵循 ERC-721 和 ERC-1155 中的“安全转账机制”,因为由于其向恣意合约的外部调用被认为是误导性的。ERC-6909 使用 transfer 和 transferFrom 函数,详情如下。
转账:
ERC-6909 的 transfer 函数表现得与 ERC-20 的 transfer 类似,只不过它实用于特定的Token ID。该函数接受吸收地址、Token的 ID 和要转移的金额作为输入参数,并使用 balanceOf 映射更新余额。与 ERC-20 转账函数类似,成功执行事件时返回 true 是必要的。
- // ERC-20 接口转账函数
- function transfer(address _to, uint256 _value) public returns (bool)
- // ERC-6909 转账函数参考实现
- // @notice 将数量 id 从调用者转移至接收者。
- // @param receiver 接收者的地址。
- // @param id Token的 ID。
- // @param amount Token的数量。
- function transfer(address receiver, uint256 id, uint256 amount) public returns (bool) {
- if (balanceOf[msg.sender][id] < amount) revert InsufficientBalance(msg.sender, id);
- balanceOf[msg.sender][id] -= amount;
- balanceOf[receiver][id] += amount;
- emit Transfer(msg.sender, msg.sender, receiver, id, amount);
- return true;
- }
复制代码 transferFrom:
ERC-6909 的 transferFrom 函数与 ERC-20 的不同之处在于它要求提供一个Token ID。别的,它还检查操纵员的批准以及限额。
该函数起首检查 if (sender != msg.sender && !isOperator[sender][msg.sender]),确保调用者 (msg.sender) 是:
- 所有者(sender),大概
- 已批准的操纵员(isOperator[sender][msg.sender] == true)。
如果 msg.sender 不是所有者也不是已批准的操纵员,函数将检查调用者是否具有 足够的限额 以进行转移。如果有限额但未设置为无穷定(type(uint256).max),则从限额中扣除转移的 amount。
别的,标准规定如果调用者是操纵员或 sender,则该函数不应扣减调用者对于Token id 的 allowance 中的 amount。
- // ERC-6909 transferFrom
- function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public returns (bool) {
- if (sender != msg.sender && !isOperator[sender][msg.sender]) {
- uint256 senderAllowance = allowance[sender][msg.sender][id];
- if (senderAllowance < amount) revert InsufficientPermission();
- if (senderAllowance != type(uint256).max) {
- allowance[sender][msg.sender][id] = senderAllowance - amount;
- }
- }
- if (balanceOf[sender][id] < amount) revert InsufficientBalance();
- balanceOf[sender][id] -= amount;
- balanceOf[receiver][id] += amount;
- emit Transfer(msg.sender, sender, receiver, id, amount);
- return true;
- }
复制代码 approve:
approve 函数答应调用者(msg.sender)向支出者授予特定Token(ID)的特定限额。这会更新限额映射以反映新的限额并发出 Approval 事件。
- function approve(address spender, uint256 id, uint256 amount) public returns (bool) {
- allowance[msg.sender][spender][id] = amount;
- emit Approval(msg.sender, spender, id, amount);
- return true;
- }
复制代码 setOperator:
setOperator 函数答应调用者(msg.sender)通过将批准参数设置为 true 或 false 来授予或撤销特定地址(spender)的操纵员权限。该函数会相应地更新 isOperator 映射,并发出 OperatorSet 事件,以关照外部监听器有关更改的情况。
- function setOperator(address spender, bool approved) public returns (bool) {
- isOperator[msg.sender][spender] = approved;
- emit OperatorSet(msg.sender, spender, approved);
- return true;
- }
复制代码 ERC-6909 中的事件与日志
ERC-6909 定义了关键事件,以跟踪多Token合约中的Token转移、批准及操纵员权限。
1. 转移事件:
- /// @notice 转移发生时发出的事件。
- event Transfer(address caller, address indexed sender, address indexed receiver, uint256 indexed id, uint256 amount);
复制代码 ERC-6909 中的 Transfer 事件用于跟踪Token的移动,必须在以下条件下发出:
- 转移Token id 的 amount 从一个账户到另一个账户时,将记录 sender、receiver、token ID 和转移的 amount。
- 当创建新Token时,事件必须以 sender 为零地址(0x0)发出。
- 当Token被烧毁时,事件必须以吸收方为零地址(0x0)发出,以表示Token被移除。
2. OperatorSet 事件:
- /// @notice 操作员被设置时发出的事件。
- event OperatorSet(address indexed owner, address indexed spender, bool approved);
复制代码 OperatorSet 事件每当所有者分配或撤销另一个地址的操纵员权限时都会发出。事件记录所有者的地址、支出者的地址以及更新的批准状态(true 表示授予,false 表示撤销)。
3. Approval 事件:
- /// @notice 批准发生时发出的事件。
- event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);
复制代码 当所有者设置或更新支出者转移特定金额的特定Token ID 的批定时,必须发出 Approval 事件。事件记录 owner、spender、Token id 和批准的 amount。
如今我们已经探讨了 ERC-6909 与 ERC-1155 之间的差异,以及 ERC-6909 中的焦点方法和事件,让我们看看标准的一些实际应用。
Uniswap v4 PoolManager 怎样实现 ERC-6909。
在 Uniswap v3 中,工厂/池模子通过使用 UniswapV3Factory 合约为每个池摆设一个单独的合约来创建新的Token对。这种方法增加了Gas本钱,因为每个新池都必要新的合约摆设。
与此相比,Uniswap v4 引入了一个单例合约(PoolManager.sol),该合约将所有流动性池管理作为其内部状态的一部分,而不是要求单独的合约摆设。这一设计显著低落了池创建的Gas本钱。
别的,以前版本中涉及 多个 Uniswap 池 的交易必要跨多个合约的Token转移和冗余状态更新。在 Uniswap v4 中,PoolManager 合约可以会合持有用户的 ERC-6909 表示的 ERC-20 Token,而不必要在池中往返转移 ERC-20 Token。
比方,如果用户为Token A 提供流动性,他们厥后可以选择提取其股份并吸收Token A 作为转移到其钱包的 ERC-20。然而,如果他们选择不提取Token,那么 Uniswap v4 的 PoolManager 可以 铸造其Token余额的 ERC-6909 表示,而无需从合约转移 ERC-20 Token——节省了跨合约调用。这些 ERC-6909 余额答应用户在协议中交易或互动,而无需在钱包之间移动Token。
这意味着当用户厥后将Token A 兑换为Token B 时,Uniswap 只是更新他们在池中的 ERC-6909 余额。
留意:ERC-6909 在 Uniswap v4 中不作为 LP 代币使用。
ERC-6909 元数据在单例 DeFi 架构和 NFT 系列中的思量
以下是 IERC6909Metadata 接口,它定义了如何将 ERC-6909 标准和单独Token相干联的元数据,比方名称、符号和小数,其函数可以根据 id 的不同发展,答应 ERC-6909 中不同的Token具有不同的名称、符号和小数。
- /// @notice 包含单个Token元数据的合同。
- interface IERC6909Metadata is IERC6909 {
- /// @notice 给定Token的名称。
- /// @param id Token的 id。
- /// @return name Token的名字。
- function name(uint256 id) external view returns (string memory);
- /// @notice 给定Token的 символ。
- /// @param id Token的 id。
- /// @return symbol Token的符号。
- function symbol(uint256 id) external view returns (string memory);
- /// @notice 给定Token的小数位数。
- /// @param id Token的 id。
- /// @return decimals Token的小数位数。
- function decimals(uint256 id) external view returns (uint8);
- }
复制代码 对于 DeFi 协议,我们可能有多个 LP 代币,而且我们可能盼望将其标准化为都具有类似的小数,比方 18。然而,我们可能盼望名称和符号可以或许反映池中持有的不同资产。
相比之下,对于 NFT,decimals 值应始终设置为 1,因为 NFT 是不可分割的。
在典型的 NFT 系列(比方 ERC-721)中,所有Token共享类似的名字和符号,以表示整个系列(比方 "CryptoPunks" 和符号 " UNK")。ERC-6909 使我们可以或许遵循 ERC-712 规范,其中所有 NFT 在同一系列中共享类似的元数据。
ERC-6909 非同质化Token的实现示例。
ERC-6909 规范并没有明白规定支持非同质化Token的独特方法。但是,可以使用 ERC-1155 规范中形貌的 ID 位拆分技能在 ERC-6909 中实现非同质化Token。这种方法使用 位移和加法运算 将集合 ID 和项目 ID 编码在一个 uint256 Token ID 中。
- function getTokenId(uint256 collectionId, uint256 itemId) public pure returns (uint256) {
- return (collectionId << 128) + itemId;
- }
复制代码 下面的 ERC6909MultiCollectionNFT 合约是一个使用 getTokenId 根据 collectionId 和 itemId 生成Token ID 的非同质化Token(NFT)实现示例。
mintNFT 函数确保每个 tokenId 只能被铸造一次,无论地址如何。它使用 mintedTokens 映射跟踪 NFT tokenId 是否已被全球铸造。
由于在 mintNFT 中将 amount 变量设置为 1,因此函数中的 _mint(to, tokenId, amount) 调用将对 tokenId 铸造一份。在任何情况下,如果 amount > 1,Token将变为可替代,而不是非同质化。
- // SPDX-License-Identifier: MIT
- pragma solidity ^0.8.20;
- import "./ERC6909.sol";
- contract ERC6909MultiCollectionNFT is ERC6909 {
- struct NFT {
- string uri;
- }
- mapping(uint256 => NFT) private _tokens;
- mapping(uint256 => string) private _collectionURIs;
- mapping(uint256 => bool) public mintedTokens;
- event MintedNFT(address indexed to, uint256 indexed collectionId, uint256 indexed itemId, uint256 tokenId, string uri);
- // 通过连接 collectionId 和 itemId 计算 Token ID
- function getTokenId(uint256 collectionId, uint256 itemId) public pure returns (uint256) {
- return (collectionId << 128) + itemId;
- }
- function _mint(address to, uint256 tokenId, uint256 amount) internal {
- balanceOf[to][tokenId] += amount;
- emit Transfer(msg.sender, address(0), to, tokenId, amount);
- }
- function mintNFT(address to, uint256 collectionId, uint256 itemId, string memory uri) external {
- uint256 amount = 1;
- uint256 tokenId = getTokenId(collectionId, itemId);
- require(!mintedTokens[tokenId], "ERC6909MultiCollectionNFT: Token already minted");
- require(amount == 1, "ERC6909MultiCollectionNFT: Token copies must be 1");
- _tokens[tokenId] = NFT(uri);
- mintedTokens[tokenId] = true; // 标记为已铸造
- _mint(to, tokenId, amount); // amount 被定义为 1。
- emit MintedNFT(to, collectionId, itemId, tokenId, uri);
- }
- function nftBalanceOf(address owner, uint256 tokenId) public view returns (uint256) {
- return balanceOf[owner][tokenId];
- }
- }
复制代码 请记住,上面 mintNFT 中的 _mint 调用将余额映射更新为 1,因为这些铸造的Token完满是非同质化的。因此,在此合约中,如果 owner 地址确实铸造了 tokenId,nftBalanceOf 函数预计总是返回 1。
为了转移Token的所有权,下面的 nftTransfer 函数确保只有 NFT 所有者可以通过验证别的额启动转移,才气答应唯一存在的单元转移。
- function nftTransfer(address to, uint256 tokenId) external {
- require(balanceOf[tokenId][msg.sender] == 1, "ERC6909MultiCollectionNFT: This should be non-fungible.");
- require(to != address(0), "ERC6909MultiCollectionNFT: transfer to zero address");
- transfer(to, tokenId, 1);
- // 在此情况下,数量等于 1。
- emit Transfer(msg.sender, address(0), to, tokenId, 1);
- }
复制代码 ERC-6909 内容 URI 扩展与元数据 URI JSON 模式
为了标准化 ERC-6909 中的元数据访问,可选的 IERC6909ContentURI 接口为检索合约和Token条理的元数据定义了两个 URI 函数(contractURI 和 tokenURI)。ERC-6909 标准并不强制Token必要关联 URI 元数据。然而,如果实现中包罗这些 URI 函数,返回的 URI 应该指向遵循 ERC-6909 元数据 URI JSON 架构的 JSON 文件。
- pragma solidity ^0.8.19;
- import "./IERC6909.sol";
- /// @title ERC6909 内容 URI 接口
- interface IERC6909ContentURI is IERC6909 {
- /// @notice 合同级别 URI
- /// @return uri 合同级别的 URI。
- function contractURI() external view returns (string memory);
- /// @notice Token级别 URI
- /// @param id Token的 ID。
- /// @return uri Token级别的 URI。
- function tokenURI(uint256 id) external view returns (string memory);
- }
复制代码 如上所示,ERC-6909 IERC6909ContentURI 接口定义了两个可选的 URI 函数,即 contractURI 和 tokenURI;每个函数都有其相应的 URI JSON 模式。contractURI 函数(不带参数)返回指向合约级别元数据的单个 URI,而 tokenURI() 返回每个Token ID 特定的 URI。
以下是根据 ERC-6909 标准构造合约 URI JSON 架构的示例。
- {
- "title": "Contract Metadata",
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "description": "The name of the contract."
- },
- "description": {
- "type": "string",
- "description": "The description of the contract."
- },
- "image_url": {
- "type": "string",
- "format": "uri",
- "description": "The URL of the image representing the contract."
- },
- "banner_image_url": {
- "type": "string",
- "format": "uri",
- "description": "The URL of the banner image of the contract."
- },
- "external_link": {
- "type": "string",
- "format": "uri",
- "description": "The external link of the contract."
- },
- "editors": {
- "type": "array",
- "items": {
- "type": "string",
- "description": "An Ethereum address representing an authorized editor of the contract."
- },
- "description": "An array of Ethereum addresses representing editors (authorized editors) of the contract."
- },
- "animation_url": {
- "type": "string",
- "description": "An animation URL for the contract."
- }
- },
- "required": ["name"]
- }
复制代码 tokenURI 函数则接受一个 uint256 参数 id,并返回该Token的 URI。如果Token id 不存在,该函数可以回滚。与合约级别的 URI 一样,客户在与合约交互时 必须 替换 URI 中每处 {id} 的出现为实际的Token ID,以访问与该Token相干的精确元数据。
以下是 tokenURI 函数的实现,返回遵循占位符格式的 静态 URI 模板:
- // SPDX-License-Identifier: MIT
- pragma solidity ^0.8.19;
- import "./ERC6909.sol";
- import "./interfaces/IERC6909ContentURI.sol";
- contract ERC6909ContentURI is ERC6909, IERC6909ContentURI {
- /// @notice 合同级 URI。
- string public contractURI;
- /// @notice 每个 id 的 URI。
- /// @return Token的 URI。
- function tokenURI(uint256) public pure override returns (string memory) {
- return "<baseuri>/{id}";
- }
- }
复制代码 以下是根据 ERC-6909 标准构造 URI JSON 架构的示例。
- {
- "title": "Asset Metadata",
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "description": "Identifies the token"
- },
- "description": {
- "type": "string",
- "description": "Describes the token"
- },
- "image": {
- "type": "string",
- "description": "A URI pointing to an image resource."
- },
- "animation_url": {
- "type": "string",
- "description": "An animation URL for the token."
- }
- },
- "required": ["name", "description", "image"]
- }
复制代码 ERC-6909 规范中的限额和操纵员歧义。
思量一个场景,其中一个账户(A)授予另一个账户(B)操纵员权限,并为 B 设置可转移特定金额的Token的限额。
如果 B 代表 A 发起转移,则实现必须确定检查的精确顺序以及限额如何与操纵员权限互动。
歧义涉及检查的顺序。合约应该:
- 起首检查限额,如果不足则回滚,即使 B 具有操纵员权限。
- 起首检查操纵员权限,无论限额如何都答应转移。
在下面的 allowanceFirst 合约中,如果账户 B 具有操纵员权限,但限额不足,限额检查将失败,从而导致事件回滚。这可能是反直观的,因为操纵员权限通常意味着无穷定访问,用户可能会期望事件成功。
相反,在 operatorFirst 合约中,如果实现起首检查操纵员权限,将绕过限额检查,事件将基于操纵员的无穷定访问而成功。
- contract operatorFirst {
- function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public {
- // 首先检查 `isOperator`
- if (msg.sender != sender && !isOperator[sender][msg.sender]) {
- require(allowance[sender][msg.sender][id] >= amount, "insufficient allowance");
- allowance[sender][msg.sender][id] -= amount;
- }
- // -- 剪切 --
- }
- }
- contract allowanceFirst{
- function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public {
- // 首先检查限额是否充足
- if (msg.sender != sender && allowance[sender][msg.sender][id] < amount) {
- require(isOperator[sender][msg.sender], "insufficient allowance");
- }
- // 错误:当限额不足时,由于算术下溢而发生恐慌,无论调用者是否具有操作员权限。
- allowance[sender][msg.sender][id] -= amount;
- // -- 剪切 --
- }
- }
复制代码 该标准故意将权限检查的决定留给实现者,这给实现者提供灵活性。在一个账户同时拥有操纵员权限和不足的限额时,转账行为取决于检查的顺序。
结论
ERC-6909 标准通过移除转账函数中的批量和强制回调,显著提高了 ERC-1155 的效率。去除批量处置惩罚答应逐个案例优化,特殊是对于汇总或气体敏感的环境。
它还通过混合操纵员权限方案引入了可扩展的Token批准控制,更多信息,,https://t.me/+_QibemQqIIg1OTY1。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|