Enhanced UDT Standard

Introduction

The UDT (User-Defined Token) standard has been a long-standing issue in the CKB community. Previously, there were two standards: sUDT and xUDT. The sUDT was too simple, while the extension mode of xUDT was hardly used due to compatibility issues with other applications. Furthermore, until now, there has been no standard for binding UDT information on-chain.

A practical approach is that UDT should focus on implementation like Solana’s SPL token standard, rather than focusing on abstract interfaces like ERC20.

Why is it difficult to advance efforts like xUDT, which defines an abstract interface for extensions to expand the token standard? This is because the EVM is an on-chain computation model, while the Cell model is not. Arbitrary implementation of extensions would make it difficult for other applications to integrate. Therefore, a reasonable approach is for the UDT designer to decide what functions the corresponding UDT script should have, and provide both on-chain script implementation and off-chain SDK implementation. This ensures that various applications such as wallets, DEXs, and lending platforms won’t encounter problems when interfacing. Token issuers can select and combine the needed functions from the existing UDT feature set.

When the demand for a new feature becomes strong enough, and existing UDTs fail to provide the required functionality, a new UDT implementation can be designed. This implementation should ensure backward compatibility as much as possible, allowing most applications to integrate with token transfers simply by replacing the code_hash.

Solana currently has two token standards:

  • SPL Token, with the following features:

    • Mint authority
    • Freeze authority (ability to freeze accounts)
    • Decimals information
    • Metadata: name, decimal, URI
  • SPL Token 2022, which adds the following features based on SPL Token:

    • Confidential transfers (hiding transaction amounts)
    • Transfer hooks: calling a specific program for each transfer
    • Transfer fees: ability to tax transfers
    • Closing mint accounts, reclaiming storage rent
    • Interest-bearing tokens: token balances can grow at a certain ratio
    • Non-transferable tokens: only the issuer can change token ownership
    • Transfer memos: for compliance and audit tracing
    • Closing mint authority

It can be said that most extension needs for token standards would not exceed the set outlined above.

Considering the importance and difficulty of implementation in the Cell model, I believe it’s reasonable to first implement the basic SPL Token functionality. Other features are challenging to implement in the Cell model. For example:

  1. Transfer fees require consideration of state contention or state cost issues.
  2. Interest-bearing tokens need to address the problem of obtaining on-chain time.

Therefore, for a simple and practical UDT standard implementation, it should have the following features, with metadata binding for easy application integration and freeze authority for centralized stablecoin issuance:

  1. Programmability of mint authority, which AMM LP tokens and collateralized stablecoins depend on.
  2. On-chain binding of metadata, allowing indexing of token metadata information from the token Cell.
  3. Freeze authority, the ability to blacklist a specific lockscript.
  4. Ability to modify mint authority.
  5. Ability to modify metadata.
  6. Ability to modify freeze authority.
  7. Supply control capability.

To simplify usage and address different scenarios, this proposal will introduce two script implementations: Enhanced sUDT and Enhanced xUDT.

Enhanced sUDT will be consistent with sUDT in terms of transfer usage, allowing it to integrate with existing applications and infrastructure without code modifications.

Enhanced xUDT will be able to implement additional constraints on UDT Transfers, such as the freeze function. The args for both UDT standards will be 32 bytes to ensure full compatibility with current UDTs.

Enhanced sUDT will include the following features:

  1. Programmability of mint authority.
  2. On-chain binding of metadata.
  3. Ability to modify mint authority.
  4. Ability to modify metadata.
  5. Supply control capability.

Enhanced xUDT will add the following features based on Enhanced sUDT:

  1. Freeze authority.
  2. Ability to modify freeze authority.
  3. Plugin extension capability.

Basic Data Structures

struct ScriptAttr {
    location: byte,
    script_hash: Byte32,
    script: ScriptOpt,
}

option ScriptAttrOpt (ScriptAttr);

vector ScriptAttrVec <ScriptAttr>;

ScriptAttr represents on-chain permissions, with Location defined as follows:

  • 0: input_lock, meaning the constraint check should verify if there exists an input_cell’s lock_hash that satisfies the rule.
  • 1: input_type, similar check as above.
  • 2: output_type, similar check as above.
  • 3: dynamic linking, this script must have a value and execute according to xUDT rules using dynamic linking.
  • 4: spawn, this script must have a value and execute the corresponding script according to spawn rules. (Awaiting hard fork to enable)

Among these types, 0, 1, and 2 must rely on an input or output during execution and may have contention issues, while 3 and 4 only execute the corresponding code without state contention issues.

Enhanced sUDT

Data Definition

When creating a token, first establish an sUDT Meta Cell, with its type script’s code_hash as sUDT Meta Type and args as type_id.

struct ScriptAttr {
    location: byte,
    script_hash: Byte32,
    script: ScriptOpt,
}

option ScriptAttrOpt (ScriptAttr);

table sUDTMetadata {
 	flag: byte,
    mint_authority: ScriptAttrOpt,
    metadata_authority: ScriptAttrOpt,
    supply: Uint128,
    decimals: byte,
    name: Bytes,
    symbol: Bytes,
    uri: Bytes,
    extra_data: Bytes,
}

The flag will indicate the configuration used by the UDT:

  • Bit 0 set to 1 indicates that the supply mode is enabled.
  • Other bits are reserved and set to 0.

Token Creation

The token creator should first generate an sUDT Meta Cell, filling in the corresponding information. The UDT Cell’s type code_hash should be Enhanced sUDT_code_hash, and args should be sUDT Meta type_hash, which is 32 bytes in length.

Token Issuance

When issuing tokens, there are two scenarios:

  • If Supply mode is enabled, the sUDT Meta Cell needs to be included as an Input, checking the mint_authority, and verifying the increase or decrease in UDT quantity relative to the input, modifying the supply value. The token issuer can first issue the total supply of tokens, then set mint_authority to None, meaning no further token issuance is possible.
  • If Supply mode is not enabled, the sUDT Meta Cell needs to be included as CellDeps, and the mint_authority is checked.

Token Transfer and Destruction

Token transfer and destruction follow the same rules as ordinary sUDT, entirely controlled by its lock, allowing free operation.

Meta Cell Modification

After mint_authority verification passes, the mint authority can be transferred.

After metadata_authority verification passes, the metadata modification authority can be transferred, and name, symbol, URI, and decimals can be modified. Modifying decimals can be understood as splitting tokens or the reverse.

Once any authority is transferred to None, it cannot be reclaimed.

Enhanced xUDT

When creating a token, first establish an xUDT Meta Cell, with its type script’s code_hash as sUDT Meta Type and args as type_id.

struct ScriptAttr {
    location: byte,
    script_hash: Byte32,
    script: ScriptOpt,
}

option ScriptAttrOpt (ScriptAttr);

vector ScriptAttrVec <ScriptAttr>;

table xUDTMetadata {
 	flag: byte,
    paused: byte,
    mint_authority: ScriptAttrOpt,
    pause_authority: ScriptAttrOpt,
    metadata_authority: ScriptAttrOpt,
    freeze_authority: ScriptAttrOpt,
    extensions: ScriptAttrVec,
    supply: Uint128,
    decimals: byte,
    name: Bytes,
    symbol: Bytes,
    uri: Bytes,
    extra_data: Bytes,
}

The flag will indicate the configuration used by the UDT:

  • Bit 0 set to 1 indicates that the supply mode is enabled.
  • Bit 1 set to 1 indicates that the freeze mode is enabled.
  • Other bits are reserved and set to 0.

Compared to Enhanced sUDT, Enhanced xUDT’s metadata has two additional items: paused, paused_authority, freeze_authority and extensions. The ‘paused’ indicates whether the contract is paused; the pause_authority can modify the value of paused, limited to 0 or 1 only; the freeze_authority has the right to add and remove from the blacklist; while extensions are additional checks applied to each UDT Transfer, which can be more than one.

Token Creation

The token creator should first generate an xUDT Meta Cell, filling in the corresponding information. The UDT Cell’s type code_hash should be Enhanced xUDT_code_hash, and args should be xUDT Meta type_hash, which is 32 bytes in length.

Token Issuance

When issuing tokens, there are two scenarios:

  • If Supply mode is enabled, the sUDT Meta Cell needs to be included as an Input, checking the mint_authority, and verifying the increase or decrease in UDT quantity relative to the input, modifying the supply value. The token issuer can first issue the total supply of tokens, then set mint_authority to None, meaning no further token issuance is possible.
  • If Supply mode is not enabled, the sUDT Meta Cell needs to be included as CellDeps, and the mint_authority is checked.

Token issuance should also go through extensions checks, but passing information that the current operation has passed mint_authority checks. Most extensions can directly return verification success.

During token issuance, paused should be 0, otherwise new token issuance is not allowed.

Freeze Functionality

The token’s blacklist will be implemented using a linked list. When the flag indicates that the corresponding token has freeze_authority, it needs to ensure that a linked list cell is created simultaneously, with the range of this linked list cell covering 000…000 to FFF…FFF.

array BlackListRange [Byte32; 2];

table BlackListData {
    range: BlackListRange,
    blacklist: Byte32Vec,
}

Each blacklist’s data is a Bytes32Vec, representing the frozen lock_hash. There are four operations:

  • Insert some items within the range.
  • Delete some items within the range.
  • Split the Cell, turning one range into two ranges.
  • Merge Cells, combining two ranges into one range.

The typescript of this linked list cell has a code_hash of blacklist code_hash, and args is UDT Meta type_hash. When inserting new items into the linked list or splitting a linked list cell into multiple ones.

The script execution check reads a cell from cellDeps whose type_hash equals the args of blacklist_lock_script, uses UDTMetadata to parse, obtains freeze_authority, then verifies permissions, and simultaneously verifies the correctness of insertion, deletion, splitting, and merging.

When the flag has freeze functionality, during transfers, the corresponding blacklist_cell needs to be placed in cellDeps, with its range covering all input and output UDT Cell lockhashes. If distributed across multiple blacklist linked list cells, multiple cellDeps need to be placed.

UDT needs to check that all lockhashes involved in UDT are not present in the blacklist_cell for the transaction to successfully go on-chain.

Scripts with freeze_authority can modify the freeze_authority in UDTMetaData, i.e., transfer authority.

Compared to SMT-based blacklist implementation, the linked list is a pure on-chain implementation, with all information queryable on-chain, and adding/removing from the blacklist is also simple.

At the same time, paused can be directly set to 1 by pause_authority, which disallows any transfers.

Token Transfer and Destruction

Both token transfer and destruction need to pass blacklist and extensions checks.

  • The sUDT Meta Cell needs to be included as CellDeps.
  • If the metadata’s flag indicates that freeze mode is enabled, all blacklist Cells need to be read from cellDeps, and all lock_hashes in inputs and outputs need to be checked to ensure they don’t exist in the blacklist.
  • If the length of extensions in the metadata is non-zero, all extensions checks are executed one by one.
  • If paused in the meta is equal to 1, no transfers are allowed to be initiated.

Meta Cell Modification

After mint_authority verification passes, mint authority can be transferred, and extensions settings can be modified, such as adding or removing extensions.

After freeze_authority verification passes, freeze authority can be transferred, and blacklist cells can be added or removed.

After metadata_authority verification passes, metadata modification authority can be transferred, and name, symbol, URI, and decimals can be modified. Modifying decimals can be understood as splitting tokens or the reverse.

Once any authority is transferred to None, it cannot be reclaimed.

Script Analysis

For Enhanced sUDT, there are two Scripts:

One is the Meta type, responsible for checking permissions for modifying Meta information and format checks. Since all checks for MetaCell are executed by type, it is recommended that its lock be forced to always_success, such as the non-upgradable deployment in ckb-ecofund/ckb-proxy-locks. It is recommended that always_success have a recognized deployment.

The other is Enhanced sUDT type, responsible for checking UDT transfers, while for minting, it obtains MetaCell information and performs proxy checks.

There is a dependency relationship here. Due to SupplyMode, Meta type needs to be able to read the quantity of UDT in inputs and outputs, so it needs to hard-code the code_hash and hash_type of Enhanced sUDT type in the code.

However, analysis shows that only Meta type needs to know the deployment-time information of Enhanced sUDT type, while Enhanced sUDT type doesn’t need to know Meta type’s deployment information. It only needs to get its own args and find a type that meets the requirements in inputs or celldeps, read its CellData according to the sUDT Meta format, and use it.

Therefore, both scripts can use non-upgradable data_hash deployment.

For Enhanced sUDT, there are three Scripts:

One is the Meta type, responsible for checking permissions for modifying Meta information and format checks. Since all checks for MetaCell are executed by type, it is recommended that its lock be forced to always_success, such as the non-upgradable deployment in ckb-ecofund/ckb-proxy-locks. It is recommended that always_success have a recognized deployment.

The second is Enhanced xUDT type, responsible for checking UDT transfers, while for minting, it obtains MetaCell information and performs proxy checks. It also needs to read blacklist Cells to determine if the transaction is legal.

The third is BlackList type, responsible for checking the addition and removal of BlackList, and the splitting and merging of Cells.

When considering the deployment dependency relationships of the three scripts, first, Meta type depends on the deployment of Enhanced xUDT type code, while Enhanced xUDT type depends on the deployment of BlackList type, and Meta type also depends on the deployment of BlackList type.

Because when the Enhanced xUDT script executes, how does it find the blacklist cell? A simple method is to directly extract the blacklist code_hash and hash_type and concatenate them with its own args to find the blacklist.

Why does MetaCell also need to depend on the deployment of BlackList Cell? Because when creating MetaCell, if the Freeze function is enabled, it must ensure that a BlackList covering the full range is created.

BlackList type and Enhanced xUDT type, like Meta Cell, only need to obtain Meta Cell information through args, parse and read, and perform permission verification, so there is no circular dependency. They can all be deployed using data deployment, just deployed in the order of dependencies.

For MetaCell and Blacklist Cell, since their constraints are entirely completed by type, the function of lock will affect functionality, so AlwaysSuccess script can be hard-coded in the code, and it is mandatory to require that the lock of these two types of Cells must be AlwaysSuccess.

Comparison with SMT-based Freeze Mechanism

In RFC: Regulation Compliance Extension - English / CKB Development & Technical Discussion - Nervos Talk, an SMT-based freeze mechanism was proposed. Its advantage is that the on-chain state occupation is extremely small, so the cost of adding to the blacklist is very low.

The disadvantages are as follows:

  1. For each token adopting this mechanism, all involved applications need to index and store the latest state of the SMT tree, while the linked list-based approach doesn’t require maintaining off-chain state and is easier to integrate.
  2. It has strong invasiveness to applications. Transfer transactions need to place proofs in the Witness, which becomes problematic when dealing with OTX transactions. Since user signatures include the corresponding Witness, and the final Aggregator may not initially know which lockscripts will be involved, it requires careful selection of combination modes. The linked list-based approach only adds CellDeps, which has almost no impact on application composition.

Moreover, based on the extensions mechanism, if the cost of blacklists becomes very high in the future, for example, a blacklist involving 10 thousands locks would require 320,000 CKB.

If this cost is deemed too high at that time, a more easily combinable SMT-based freeze scheme can be designed, migrated from the linked list scheme, and all CKB can be reclaimed.

The migration process is as follows:

  1. mint_authority adds an SMT-based freeze extension, with its args being the type_hash of the SMTRootCell. The execution process of this plugin is:
    1. Read the Cell pointed to by the type_hash and read its data. The data should include two pieces of information: 1. SMT Root; 2. UDT type hash to be checked
    2. Read the SMT Proof from the Witness
    3. Scan the locks involved in the UDT Type hash corresponding to inputs and outputs.
    4. Check that all locks comply with the rules according to the SMT Proof.
  2. First, add all existing blacklisted locks to the SMT blacklist. At this point, each transfer will undergo two checks, one freeze mode and one freeze extension.
  3. Gradually clear and merge all blacklists from the blacklist cell, and reclaim CKB, until returning to the full range empty linked list.
  4. At this point, CKB has been reclaimed, and the blacklist linked list is empty, equivalent to only being subject to the extension’s blacklist check.

Analysis of Permission Management

According to the Principle of least privilege, regarding UDT, especially compliant UDT, the permission control in the final implementation can be more fine-grained, not limited to the details in this document.

In the Enhanced xUDT of this proposal, there are four types of permissions:

  • mint_authority: Permission to mint tokens
  • pause_authority: Permission to pause minting and transfers
  • metadata_authority: Permission to modify metadata
  • freeze_authority: Permission to freeze accounts

However, sometimes more fine-grained layered permission control can be added. For example, similar to USDC’s contract design on ETH, there exists a MasterMinter that can add minters and give each minter a certain mint limit. After minting a certain amount of tokens each time, the limit will decrease accordingly to isolate risks.

Then the metadata could become like this:

table xUDTMetadata {
 	flag: byte,
    paused: byte,
    master_mint_authority: ScriptAttrOpt,
    minter: ScriptAttrVec,
    mint_allowance: Uint128Vec,
    pause_authority: ScriptAttrOpt,
    metadata_authority: ScriptAttrOpt,
    freeze_authority: ScriptAttrOpt,
    extensions: ScriptAttrVec,
    supply: Uint128,
    decimals: byte,
    name: Bytes,
    symbol: Bytes,
    uri: Bytes,
    extra_data: Bytes,
}

Of course, the best approach is to reference the product design of relevant compliant asset and investigate requirements to design a more general applicable security permission management system.

7 Likes

增强 UDT 标准

介绍

UDT标准是CKB社区长久以来的问题,之前有sUDT和xUDT两个标准,sUDT过于简单,xUDT的extension模式则因为很难与其他应用兼容导致几乎无人使用,此外,到目前为止,还没有一个在链上可以绑定UDT信息的标准。

一个实用的思路是,UDT应该像Solana的SPL代币标准一样专注于实现,而非像ERC20那样关注抽象接口。

为什么像xUDT这样,定义一个extension的抽象接口来扩展代币标准的努力难以推进呢?因为EVM是一个链上计算的模型,而Cell模型不是,任意实现扩展将使得其他应用难以对接。所以,合理的做法是,由UDT设计者决定对应的UDT脚本具有哪些功能,并提供链上脚本实现和链下SDK实现,这样才能保证诸多应用,如钱包,DEX,Lending在对接的时候不会遇到问题,代币发行方可以在UDT已有的功能集中选择需要的功能并组合使用。

而一旦对于某个新功能的需求足够强烈,同时已有的UDT未能提供所需功能,则可以设计一个新的UDT实现使用,该实现应尽可能保证向后兼容,使得在仅仅替换code_hash的前提下,大部分应用简单对接代币的转移。

Solana目前有两个代币标准:

  • SPL Token,具有的功能如下:

    • Mint权限
    • Freeze权限,即可以冻结账户
    • decimals信息
    • 元数据,name,decimal,uri
  • SPL Token 2022,在 SPL Token 的基础上新支持:

    • 隐私转账,转账过程可以隐藏交易金额
    • 转账hook:对于每笔转账,调用一个特定的程序
    • 转账费用:可以对转账收税
    • 关闭mint账户,收回里面的存储租金
    • 计息代币:代币余额可以以一个比例增长
    • 不可转移代币:只允许发行者变更代币所有者。
    • 转账备注:用于合规,审计追溯
    • 关闭mint权限

    可以说,对代币标准的扩展需求大部分时候都不会超过上面所指出的集合。

    而考虑到重要性以及在Cell模型上实现的难度,我认为先实现基础SPL Token的功能是合理的,而其他功能,在Cell模型上实现都很有难度,试举几例:

    1. 转账费用,需要考虑状态争用或者状态成本问题。
    2. 计息代币,需要考虑链上时间获取问题。

    那么,对于一个简单且实用的UDT标准实现,其应具有以下功能,元数据绑定方便应用方对接,冻结权限方便中心化稳定币发布:

    1. mint权限的可编程性,AMM的LP代币,抵押稳定币等都依赖这一点。
    2. 元数据的链上绑定,即根据代币Cell,即可索引到代币的元数据信息
    3. 冻结权限,即将某个特定lockscript拉入黑名单的能力
    4. mint权限的可修改能力
    5. 元数据的可修改能力
    6. 冻结权限的可修改能力
    7. Supply控制能力

为了简化使用,并应对不同的场景,本方案将提出两个脚本实现,分别为 Enhanced sUDT 和 Enhanced xUDT。

其中 Enhanced sUDT,在 transfer 的使用将与 sUDT 一致,这使得它可以在不修改代码的时候与已有的应用和基础设施对接,而Enhanced xUDT将可以实施对 UDT Transfer 的额外约束,比如冻结功能,两种UDT标准的args都为32字节,以保证与当前UDT的完全兼容。

Enhanced sUDT,将包含以下功能:

  1. mint 权限的可编程性。
  2. 元数据的链上绑定
  3. mint权限的可修改能力
  4. 元数据的可修改能力
  5. Supply控制能力

Enhanced xUDT,将在Enhanced sUDT的基础上增加以下功能:

  1. 冻结权限
  2. 冻结权限的可修改能力
  3. 插件扩展能力

基础数据结构

struct ScriptAttr {
    location: byte,
    script_hash: Byte32,
    script: ScriptOpt,
}

option ScriptAttrOpt (ScriptAttr);

vector ScriptAttrVec <ScriptAttr>;

ScriptAttr 是对链上权限的表示,其中Location的规定如下:

  • 0,表示input_lock,即识别约束时应检查是否存在一input_cell的lock_hash满足规则。
  • 1,表示 input_type,检查与上类似。
  • 2,表示output_type,检查与上类似。
  • 3,表示 dynamic linking,此script必须有值,根据 xUDT 的规则使用动态链接执行。
  • 4,表示 spawn,此script必须有值,根据spawn执行对应脚本。(等待硬分叉后开启)

以上诸种类型,0,1,2在执行时必须依托在一个输入或者输出上,可能会有争用问题,而3,4则仅执行对应代码,不会有状态争用问题。

Enhanced sUDT

数据定义

在创建代币时,首先建立一个sUDT Meta Cell,其type script的code_hash为 sUDT Meta Type,args为type_id。

struct ScriptAttr {
    location: byte,
    script_hash: Byte32,
    script: ScriptOpt,
}

option ScriptAttrOpt (ScriptAttr);

table sUDTMetadata {
 	flag: byte,
    mint_authority: ScriptAttrOpt,
    metadata_authority: ScriptAttrOpt,
    supply: Uint128,
    decimals: byte,
    name: Bytes,
    symbol: Bytes,
    uri: Bytes,
    extra_data: Bytes,
}

flag将指明该UDT使用的配置:

  • 0号bit为1,代表开启supply功能。
  • 其他bit保留,全部为0。

代币创建

代币创建者应先生成一个sUDT Meta Cell,填好相应的信息,UDT Cell的type的code_hash为 Enhanced sUDT_code_hash,args为 sUDT Meta type_hash,即长度为 32byte。

代币发行

在发行代币时,存在两种情况:

  • 如果开启了 Supply mode,需要将 sUDT Meta Cell 作为Input,并检查 mint_authority,并检查输出相对于输入增加或减少的UDT数量,并修改 supply 的值。代币发行者可以先发行总量代币,再把 mint_authority 设置为 None,即永不可再增发代币。
  • 不开启 Supply mode,需要将 sUDT Meta Cell 作为 CellDeps,并检查 mint_authority。

代币转移与销毁

其代币转移与销毁与普通sUDT规则一样,完全由其lock控制,可随意操作。

Meta Cell 修改

mint_authority 验证通过后可以转移 mint 权限,metadata_authority 验证通过后可以转移 metadata修改权限,以及修改 name,symbol,uri,decimals。修改decimals可以理解为拆分代币或相反。

所有权限,一旦转移至 None,则无法再收回。

Enhanced xUDT

在创建代币时,首先建立一个xUDT Meta Cell,其type script的code_hash为 sUDT Meta Type,args为type_id。

struct ScriptAttr {
    location: byte,
    script_hash: Byte32,
    script: ScriptOpt,
}

option ScriptAttrOpt (ScriptAttr);

vector ScriptAttrVec <ScriptAttr>;

table xUDTMetadata {
    flag: byte,
    paused: byte,
    mint_authority: ScriptAttrOpt,
    pause_authority: ScriptAttrOpt,
    metadata_authority: ScriptAttrOpt,
    freeze_authority: ScriptAttrOpt,
    extensions: ScriptAttrVec,
    supply: Uint128,
    decimals: byte,
    name: Bytes,
    symbol: Bytes,
    uri: Bytes,
    extra_data: Bytes,
}

flag将指明该UDT使用的配置:

  • 0号 bit 为 1,代表开启 supply 功能。
  • 1号 bit 为 1,代表 freeze 功能。
  • 其他bit保留,全部为0。

相比 Enhanced sUDT,Enhanced xUDT 的元数据多出了三项,即paused,pause_authority,freeze_authority 和 extensions,其中 paused指代合约是否暂停,pause_authority可以修改paused的值,仅限0或1,freeze_authority 拥有增删黑名单的权限,extensions则为针对每笔 UDT Transfer 施加的额外检查,可以不止一项。

代币创建

代币创建者应先生成一个xUDT Meta Cell,填好相应的信息,UDT Cell的type的code_hash为 Enhanced xUDT_code_hash,args为 xUDT Meta type_hash,即长度为 32byte。

代币发行

在发行代币时,存在两种情况:

  • 如果开启了 Supply mode,需要将 sUDT Meta Cell 作为Input,并检查 mint_authority,并检查输出相对于输入增加或减少的UDT数量,并修改 supply 的值。代币发行者可以先发行总量代币,再把 mint_authority 设置为 None,即永不可再增发代币。
  • 不开启 Supply mode,需要将 sUDT Meta Cell 作为 CellDeps,并检查 mint_authority。

代币发行,也应该经过 extensions 检查,不过传入当前操作已通过mint_authority检查的信息,大部分extensions可以直接返回验证成功。代币发行时,paused应为0,否则不允许发行新代币。

Freeze 功能

代币的黑名单将使用链表实现,当 flag 表明对应代币是一个具有 freeze_authority 的代币时,需要保证同时创建一个链表cell,该链表cell包含的范围是000…000到FFF…FFF。

array BlackListRange [Byte32; 2];

table BlackListData {
    range: BlackListRange,
    blacklist: Byte32Vec,
}

每个blacklist的黑名单数据是一个Bytes32Vec,代表被冻结的lock_hash,具有四种操作:

  • 插入一些在range范围的项。
  • 删除一些在range范围的项。
  • 拆分Cell,一个range变成两个range
  • 合并Cell,两个range合并成一个range。

该链表 cell 的 typescript 的 code_hash 是 blacklist code_hash,args是 UDT Meta type_hash,当往链表中插入新的项,或者将链表cell从一个拆成多个时。

脚本执行检查,在 cellDeps 中读取一个 cell,其type_hash等于 blacklist_lock_script的args,并使用 UDTMetadata 解析,获取freeze_authority,然后验证权限,同时验证插入,删除,拆分,合并的正确性。

当 flag 存在 freeze 功能时,转账时,需要在cellDeps放置对应的blacklist_cell,其范围应涵盖所有输入以及输出的UDT Cell的 lockhash,如果分布在多个黑名单链表cell中,则需要放置多个cellDeps。

UDT需要检查所有涉及UDT的lockhash都不存在于blacklist_cell中,交易才能成功上链。

拥有freeze_authority的脚本,可以修改 UDTMetaData 中的 freeze_authority,即转移权限。

相比使用SMT的黑名单实现,链表是纯链上实现,一切信息在链上可查,并且增删黑名单也很简单。

同时,pause_authority可以直接将paused设置为1,使得任意转账都不被允许。

代币转移与销毁

其代币转移与销毁都需要经过黑名单和extensions的检查。

  • 需要将 sUDT Meta Cell 作为 CellDeps。
  • 如果元信息的flag表明开启了 freeze 模式,则需要检查从cellDeps读取所有的blacklist Cell,并检查输入输出中所有的lock_hash都不存在于blacklist中。
  • 如果元信息中的 extensions 长度不为零,则逐一执行所有的extensions 检查。
  • 如果meta中的paused等于1,则不允许发起任何转账。

Meta Cell 修改

mint_authority 验证通过后可以转移 mint 权限,同时可以修改extensions设置,比如增删 extensions。

freeze_authority 验证通过后可以转移 freeze权限,并增删 blacklist cell。

metadata_authority 验证通过后可以转移 metadata修改权限,以及修改 name,symbol,uri,decimals。修改decimals可以理解为拆分代币或相反。

所有权限,一旦转移至 None,则无法再收回。

脚本分析

对于 Enhanced sUDT,存在两个Script:

一个是 Meta type,其负责检查一些修改Meta信息的权限,以及格式的检查,由于MetaCell的所有检查均由type执行,建议其lock强制为 always_success,如 ckb-ecofund/ckb-proxy-locks 内的不可升级部署,建议 always_success 能有一个公认的部署。

另一个是 Enhanced sUDT type,其负责检查UDT转移,而对于mint,则通过获取 MetaCell 的信息,并进行代理检查。

这里存在一个依赖关系,由于 SupplyMode,Meta type需要能读取 UDT在输入输出的数量,所以它要在代码里硬编码 Enhanced sUDT type 的 code_hash和hash_type。

但是经过分析可以发现,只有 Meta type 需要知道 Enhanced sUDT type的信息,而Enhanced sUDT type是不需要直到 Meta type 的部署时信息的,其只需要获取自己的args,并在inputs或者celldeps找到一个type满足要求,并按照 sUDT Meta的格式读取其CellData并使用即可。

故两个脚本都可以使用data_hash的不可升级部署。

对于 Enhanced sUDT,存在三个Script:

一个是 Meta type,其负责检查一些修改Meta信息的权限,以及格式的检查,由于MetaCell的所有检查均由type执行,建议其lock强制为 always_success,如 ckb-ecofund/ckb-proxy-locks 内的不可升级部署,建议 always_success 能有一个公认的部署。

二是 Enhanced xUDT type,其负责检查UDT转移,而对于mint,则通过获取 MetaCell 的信息,并进行代理检查,同时还需要读取黑名单 Cell,判断交易是否合法。

三是 BlackList type,其负责检查 BlackList 的增删,Cell 的拆分以及合并。

在思考三个脚本的部署依赖关系时,首先 Meta type是依赖 Enhanced xUDT type 代码的部署的,同时 Enhanced xUDT type 会依赖 BlackList type 的部署,同时 Meta type 也是依赖 BlackList type 的部署。

因为 Enhanced xUDT 脚本在执行时,如何找到blacklist cell呢?一个简单的方法是,直接取出 blacklist code_hash和hash_type并拼接上自己的args,即可找到blacklist。

而 MetaCell 为何也需要依赖 BlackList Cell 的部署呢,因为在创建MetaCell时,如果开启了Freeze功能,则必须保证有一个覆盖全范围的BlackList被创建出来。

而 BlackList type 和 Enhanced xUDT type一样,只用通过args获取Meta Cell的信息,并解析读取,进行权限验证即可,所以也不存在循环依赖,可以全部用data部署,无非是按照依赖的顺序进行部署。

对于 MetaCell 和 Blacklist Cell,由于其约束全由type完成,lock的功能会影响功能,故可以在代码里硬编码 AlwaysSuccess 的脚本,并强制要求这两类Cell的lock必须为 AlwaysSuccess。

对比基于SMT的冻结机制

RFC: Regulation Compliance Extension - English / CKB Development & Technical Discussion - Nervos Talk 中,提出了一种基于SMT的冻结机制,其优点是链上状态占用极小,所以增加黑名单的成本极低。

缺点如下:

  1. 对于每个采用该机制的代币,所有涉及到的应用都需要索引并存储SMT树的最新状态,而基于链表的方案不需要维护链下状态,更易于对接。
  2. 对应用的侵入性强,转账交易需要在Witness内放置证明,那么当涉及到OTX交易时,由于用户签名包含对应的Witness,而最后的Aggregator可能一开始并不知道会涉及哪些lockscript,从而需要精心选择组合模式。而基于链表的方案,唯一增加的只是CellDeps,几乎完全不影响应用组合。

并且,基于extensions机制,如果未来黑名单的成本真的很高,比如说涉及一万个lock的黑名单,则需要 320000 CKB。

如果到时觉得这个成本过高,可以设计一个更易于组合的基于SMT的冻结方案,并从链表方案迁移过去,并收回所有的CKB。

迁移流程如下:

  1. mint_authority增加一个基于SMT冻结的插件,其args为 SMTRootCell 的 type_hash,执行该插件的流程为:
    1. 读取 type_hash 指向的 Cell,并读取其数据,数据中应包含两个信息,1:SMT Root;2:待检查的UDT type hash
    2. 从 Witness 中读取 SMT Proof
    3. 扫描输入输出对应 UDT Type hash 涉及到的lock。
    4. 根据SMT Proof,检查所有的lock符合规则。
  2. 首先将所有已有的黑名单lock增加进SMT的黑名单,此时每次转账都要经过两次检查,一次freeze,一次插件。
  3. 从blacklist cell中逐步清除合并所有的黑名单,并收回CKB,直到回到全范围的空链表。
  4. 此时CKB已收回,并且黑名单链表为空,相当于只受插件的黑名单检查。

关于权限管理的分析

根据 Principle of least privilege,关于UDT,尤其是合规的UDT,最终的实现中权限控制可以更精细,不限于本文的细节。

在本方案的Enhanced xUDT中,存在四种权限:

  • mint_authority:铸造代币的权限
  • pause_authority:暂停铸造和转移的权限
  • metadata_authority:修改元数据权限
  • freeze_authority:冻结账户权限

但有时可以增加更精细的分层权限控制,比如类似 USDC 在 ETH上的合约设计,存在一个 MasterMinter,可以增加minter,并给每个minter一定的mint限额,在每次铸造一定数量的代币之后,限额会随之减少,以隔离风险。

那么元数据可以变成接下来这样:

table xUDTMetadata {
 	flag: byte,
    paused: byte,
    master_mint_authority: ScriptAttrOpt,
    minter: ScriptAttrVec,
    mint_allowance: Uint128Vec,
    pause_authority: ScriptAttrOpt,
    metadata_authority: ScriptAttrOpt,
    freeze_authority: ScriptAttrOpt,
    extensions: ScriptAttrVec,
    supply: Uint128,
    decimals: byte,
    name: Bytes,
    symbol: Bytes,
    uri: Bytes,
    extra_data: Bytes,
}

当然,最好的办法是参考相关合规资产发行方的产品设计,并调研需求,以设计更通用普适的安全权限管理设计。

1 Like

Hey @orange-xc, you kindly requested my feedback, so here I’m!! Thank you for writing such a well detailed proposal!

Let me start by saying compliance is not really my preferred topic, then again having USDC / USDT on Nervos L1 would be nice.

To me it feels closer to 32k CKB (1k * 32CKB + low overhead) than to 320k CKB, care to explain the 10x difference?

I really like this aspect of Enhanced UDT :+1::+1:

It also comes with downsides. Let’s assume Alice just hacked a Nervos L1 DeFi project and made it out with millions in USDC:

  1. First Alice would try to use some kind of mixer to cover her tracks. (At the moment Nervos L1 doesn’t have a mixer, but it may very well have it in a few years.)
  2. Then she would convert part of the loot to CKB and split these USDC across a thousands different addresses. (This would make standard Enhanced UDT blacklisting enforcement difficult as said in the proposal.)
  3. She would keep an eye on the txPoll for updates to the blacklist cells. If an update is detected, move the affected funds to a different address. (This would make updates to blacklist rules ineffective.)
  4. She would slowly use P2P markets to cash out the funds over a long time. (Notorius hacking groups act exatly in this manner.)

Say Circle incurs in this situation on Nervos L1 and a court declares the need to freeze the stolen assets, how can Circle handle effectively the situation?

1 Like

it’s a typo, fixed.

Compliance freezing and money laundering are a competition. Even on ETH, money launderers can quickly convert USDC into ETH to avoid tracking, and freezing is a deterrent that makes recipients of stablecoins dare not accept black money.

4 Likes

Any example for composing OTX things?
有结合otx一块用的tx该长啥样的例子吗

Using Enhanced xUDT as an example, as Enhanced sUDT is simpler.

For minting,

cell_deps: black_list cells
inputs: otx_1_inputs, otx_2_inputs, otx_3_inputs, ..., UDT Meta Cell, UDT owner Cell.
outputs: otx_1_outputs(new udt), otx_2_outputs(new udt), otx_3_outputs(new udt), ..., new UDT Meta Cell, new UDT owner Cell.

or

cell_deps: UDT Meta Cell, black_list cells
inputs: otx_1_inputs, otx_2_inputs, otx_3_inputs, ...,  UDT owner Cell.
outputs: otx_1_outputs(new udt), otx_2_outputs(new udt), otx_3_outputs(new udt) , ...,  new UDT owner Cell.

Aggregator will analyse all otxs, and find all related blacklist cells and add them to cell_deps.

if user carry UDT to invoke a aggregator-based dApp, the procedure is the same, aggregator will handle issue of black list cells.

2 Likes