RFC: Modified Extensible UDT for better script-driven UDT

In the existing CKB UDT design, the issuer of the UDT must be an Owner. Specifically, the mint transaction of the UDT must ensure that a cell with specific owner_lock in the input_cell is destroyed. Logically this is not a problem, any operation on mint can be written to owner’s Lockscript, but in practice, this can cause problems for script-driven UDTs.

For example: Wrapped CKB: When we want to design a simple script to convert CKB to wCKB, originally this only requires simple logic, and there is no state contention, but due to the design of the existing UDT standard, if there is not enough owner_cells is for use, and when there are too many mint txs at the same time, there will be serious state contention, which will affect the user experience. Of course, the user can construct an owner_cell at his own expense, but this is an inexplicable expense for the user and cannot be returned, which will also affect the user experience.

The same problem exists for the scheme of releasing the liquidity of NervosDAO pledged CKB, such as dCKB. All in all, for all UDTs is not issued by a specific owner but are based on a certain logic, the existing owner-centric UDT standards have affected the user experience.

Therefore, the reverse design is a better choice. Instead of converting the logic into owner_cell, the owner unlocking is regarded as a special logic.

The following logical decision has made a small modification to xUDT ( RFC: Extensible UDT - English / CKB Development & Technical Discussion - Nervos Talk), so it is still called xUDT.

I think the modification described below is a minor change to xUDT and can bring many benefits.

Data Structure

xUDT Cell

An xUDT cell is backwards compatible with Simple UDT, all the existing rules defined in the Simple UDT spec must still hold true for xUDT cells. On top of sUDT, xUDT extends a cell like following:

data:
    <amount: uint128> <audt data>
type:
    code_hash: modified extensible_udt type script
    args: <owner lock script hash> 
    	or <xudt args>   //this is the only change, and -> or
lock:
    <user_defined>

The rules are as follows:

  • If the length of args is 32 Bytes and a cell’s lock_hash in input_cells matches it, the logic of simple UDT is followed.
  • Otherwise, go through the verification process of xUDT.
    • For UDTs that need extended functions and are issued by private keys, such as UDTs issued by centralized institutions and require compliance verification, the owner logic can be used as a Script in ScriptVec.
    • Of course, we can also use the unused xudt_flags to propose a new xUDT args type. if xudt_flags is 0x3, the extension data is owner_lock_script_hash | scriptVec; if xudt_flags is 0x4, the extension data is owner_lock_script_hash | scriptVecHash.

Under this design, script-driven UDTs such as ckb->wCKB can be written as logic, and only one cell needs to be the dependency of all mint transactions without destroying it.

We can do more design on xUDT, here are some ideas:

  • The face value of UDT can only be fixed, such as 0.1, 0.5, 1, 5, 10.
  • The face value of UDT can only be 1, at this time we can use it as a simple NFT.

If you have an idea, please give a suggestion.

1 Like

Suppose we want to transfer 1000 tokens:

Inputs:
  data:
      <amount: 1000>
  type:
      code_hash: modified extensible_udt type script
      args: <owner lock script hash> <4-byte xUDT flags >  <Owner Lock Script> <ScriptVec>
  lock:
      <user_defined>

Outputs:
  data:
      <amount: 1000>
  type:
      code_hash: modified extensible_udt type script
      args: <owner lock script hash> <4-byte xUDT flags >  <Owner Lock Script> <ScriptVec>
  lock:
      <user_defined>

The owner lock script will be run during every transaction. It is unexpected.

Inspired by this, the following enhancement is suggested.

Witness

The witness will be updated like below:

table XudtWitnessInput {
    owner_script: ScriptOpt,		
    owner_signature: BytesOpt,
    raw_extension_data: ScriptVecOpt,
    extension_data: BytesVec,
}

The optional fields owner_script and owner_signature are appended. If the owner_script isn’t None and its blake2b hash is the same as the owner lock script hash in args, this script will be run as an extension script. If the script returns success, is_owner_mode is set to true.

Note, the owner_signature field can be used by this owner script. Usually, a valid signature can be placed here.

When tokens are minted, the owner_script and owner_signature can be set to some proper values. When tokens are transferred, they can be set to None.

yeah, if we put owner_lock in script_vec, we will meet the issue.

Maybe we can assign different weights to the scripts in xudt_args, so that some scripts are sufficient conditions for unlocking, and some need to accumulate enough weights to unlock, and use a bitmap to indicate which scripts our unlocking will execute.

4 byte xUDT flags Variable length extension data


table Script {
    code_hash:      Byte32,
    hash_type:      byte,
    args:           Bytes,
    weight:         Uint32,
}

<required weight> + vector ScriptVec <Script>;

and when call next script, we need to pass the accumulated weights down.

int validate(int is_owner_mode, size_t extension_index, const uint8_t* args, size_t args_length);

if weights is enough, script finish without more execution, if weights is not enough and no more scripts to execute, script return error.

so if required weight = 100, we can set owner_check script’s weight 100.

This can solve this problem, but if we add weight attribute to scripts in script_vec, we can implement more complex and flexible UDT, and of course, the design will be more complicated.

u r right, I think it is necessary to distinguish owner lock, otherwise we need to classify permissions.