Interoperation and Composability within CKB

Composability, a.k.a. the ability of different applications to talk to each other, is very impoartant to smart contract platforms. For account based blockchains like Ethereum, the composability is achieved by cross-contract function invocation. You may wonder that whether or not the composability can be achieved by UTXO based blockchains like CKB, since the states are generated off-chain and there is no “function interface” within them. The answer is Yes, and this post proposes a general method to making cross-contract interoperation within CKB.

The bridge for script interoperation

We use function invocation to link different contracts in account based blockchains, where the ABI serves as the bridge.


* ABI bridge in account based blockchain

Similarly, we use cell transformation to link different scripts in UTXO based blockchains, where the specific action serves as the bridge. To build the interaction relationship between different scripts, we need to define the following properties of the action bridge in detail.

  • The requirement to input cells
    • Whether with specific Type
    • Whether with specific Lock
    • Whether with restriction to Data
  • The requirement to output cells
  • The input and output relationship

It’s actually defining which scripts you are going to talk with, and what the interoperation consequences will result in. For example, if an asset pool script wants to talk with NervosDAO (to obtain interest from it), it should build an action bridge with asset pool cell as input and NervosDAO deposit cell as output. If a market prediction script wants to talk with an oracle script, it should build an action bridge with oracle data cell and market prediction cell as input, and arbitration settlement cell as output.


* Action bridge in CKB

Action bridge example

Let’s take on-chain DEX as an example. Here are two different scripts, sUDT and DEX. They are composable, which means any sUDT could be listed on the DEX. This DEX consists of three simple actions, deposit assets to DEX, exchange, and withdraw assets from it.

// Deposit action of DEX
Inputs:
    sUDT_Instance_Cell:
        Type:
            <sUDT + UUID>
        Lock:
            <Alice_lock>
Outputs:
    sUDT_Instance_Cell:
        Type:
            <sUDT + UUID>
        Lock:
            <DEX_DEPOSIT_LOCK>
    DEX_Receipt_Cell:
        Data:
            amount, 
            sUDT UUID,
            personal_order_book
        Type:
            <DEX_Type>
        Lock:
            <DEX_Exchange_Lock with Alice_lock as args>

The deposit action of the DEX actually defines the input & output cell details and the verification rules. It spends sUDT instance cell, then create new sUDT instance cell with DEX_DEPOSIT_LOCK, and a
DEX_Receipt_Cell. The DEX_Receipt_Cell enables uses to exchange or withdraw their deposit later.

// Exchange action
Inputs:
    DEX_Receipt_Cell:
        Data:
            1000 USDT
             order_book:
                  ask: <600 USDT for 10 GOLD>
        Lock:
            <DEX_Exchange_Lock with Alice_lock as args>
    DEX_Receipt_Cell:
        Data:
            50 GOLD
        Lock:
            <DEX_Exchange_Lock with Bob_lock as args>
Outputs:
    DEX_Receipt_Cell:
        Data:
            400 USDT
        Lock:
            <DEX_Exchange_Lock with Alice_lock as args>
    DEX_Receipt_Cell:
        Data:
            10 GOLD
        Lock:
            <DEX_Exchange_Lock with Alice_lock as args>
    DEX_Receipt_Cell:
        Data:
            40 GOLD
        Lock:
            <DEX_Exchange_Lock with Bob_lock as args>
    DEX_Receipt_Cell:
        Data:
            600 USDT
        Lock:
            <DEX_Exchange_Lock with Bob_lock as args>

The DEX_Receipt_Cell can be the credential to withdraw sUDT from DEX deposit pool.

// Withdraw action
Inputs:
    DEX_Receipt_Cell:
        Lock:
            <DEX_Exchange_Lock with Bob_lock as args>
    sUDT_Instance_Cell:
        Data:
            reserve_amount
        Lock:
            <DEX_DEPOSIT_LOCK>
Outputs:
    sUDT_Instance_Cell:
        Data:
            withdraw_amount
        Lock:
            <Bob_lock>
    sUDT_Instance:
        Data:
            reserve_amount - withdraw_amount
        Lock:
            <DEX_DEPOSIT_LOCK>

The actions deposit and withdraw are actually hard-coded to accommodate sUDT token to DEX rules. It is similar to that on Etheruem, where the DEX contract is hard-coded to call the specific ABI of ERC20.

Compared with ABI bridge

Firstly, the ABI bridge is a weak connection, which only defines the intention of interaction. On the contrary, the action bridge defines the substance of interaction, including the states it depends, changes, and creates. So it’s more reliable and secure than ABI bridge.

Secondly, the ABI bridge is a one-to-one connection. To connect with multiple contracts, it has to create multiple ABI bridges. Action bridge, however, is a many to many connection. You can define an action with multiple input cells and multiple output cells, whose Typescripts and Lockscripts belong to different dApps.

Thirdly, the compose level of action bridge is lower than ABI bridge. Take UDT composition as an example, the action bridge links the third part dApp to sUDT template, while the ABI bridge links the third part dApp to ERC20 instance. You cannot enforce every ERC20 instance really implement the right logic, but the template always behaves as expected.

5 Likes

翻译来啦!! 和 @Cipher一块更新了

CKB的可组合性与互操作性

可组合性,也就是不同应用程序之间相互通信的能力,对于智能合约平台来说是非常重要的。对于基于账户模型的区块链,如Ethereum,可通过跨合约函数调用来实现可组合性。您可能想知道,是否可以通过像 CKB 这种基于 UTXO 模型的区块链来实现可组合性,因为这些状态是在链下生成的,并且它们内部没有“功能接口”。答案是肯定的,这篇文章提出了一种在 CKB 实现跨合约互操作的通用方法。

脚本互操作的桥接 The bridge for script interoperation

我们使用函数调用来链接在账户模型的区块链中不同的合约,在此 ABI 可以充当桥接的角色。

  • 在账户模型中的 ABI 桥接

相似的,我们在 UTXO 模型的区块链中也使用 cell 的交易来链接不同的脚本,在此由特定的指令作为桥接。为了要在不同的脚本间产生交互关系,我们需要详细定义以下几种桥接指令的特性。

  • 对 input cells 的需求
    • 是否有特定的 Type (script)
    • 是否有特定的 Lock (script)
    • 是否有对 Data 字段的特定限制
  • 对 output cells 的需求
  • input 与 output 之间的关系

它实际上定义了您将与哪些脚本进行对话,以及将产生哪些互操作的结果。例如,如果一个资金池脚本想要与 NervosDAO 对话(从它那里获得利息),那么它应该构建一个桥接的操作,以资金池的cell 作为输入,而 NervosDAO 存款的 cell 作为输出。如果市场预测脚本想要与 oracle 脚本进行对话,则需要构建一个以 oracle 数据的 cell 和市场预测的 cell 做为输入,仲裁结算的 cell 做为输出的桥接指令。

  • CKB中的Action桥接

桥接指令的范例

以链上 DEX 为例。这里有两个不同的脚本,sUDT 和 DEX。它们是可组合的,这意味着任何 sUDT都可以在 DEX 上上币。该 DEX 包括三个简单的操作:1.将资产存入 DEX,2.进行交易,3.从中提取资产。(注:以下会有这三种操作的代码)

// Deposit action of DEX DEX 的存款指令
Inputs:
    sUDT_Instance_Cell:
        Type:
            <sUDT + UUID>
        Lock:
            <Alice_lock>
Outputs:
    sUDT_Instance_Cell:
        Type:
            <sUDT + UUID>
        Lock:
            <DEX_DEPOSIT_LOCK>
    DEX_Receipt_Cell:
        Data:
            amount, 
            sUDT UUID,
            personal_order_book
        Type:
            <DEX_Type>
        Lock:
            <DEX_Exchange_Lock with Alice_lock as args>

DEX的存储操作实际上定义了输入和输出的 cell 需要的详细信息和验证规则。它花费 sUDT 这个例子的 cell,然后用 DEX_DEPOSIT_LOCKDEX_Receipt_Cell 创建新的 sUDT cell。DEX_Receipt_Cell 可以在稍后用来交易或提取存款。

// Exchange action 交易指令
Inputs:
    DEX_Receipt_Cell:
        Data:
            1000 USDT
             order_book:
                  ask: <600 USDT for 10 GOLD>
        Lock:
            <DEX_Exchange_Lock with Alice_lock as args>
    DEX_Receipt_Cell:
        Data:
            50 GOLD
        Lock:
            <DEX_Exchange_Lock with Bob_lock as args>
Outputs:
    DEX_Receipt_Cell:
        Data:
            400 USDT
        Lock:
            <DEX_Exchange_Lock with Alice_lock as args>
    DEX_Receipt_Cell:
        Data:
            10 GOLD
        Lock:
            <DEX_Exchange_Lock with Alice_lock as args>
    DEX_Receipt_Cell:
        Data:
            40 GOLD
        Lock:
            <DEX_Exchange_Lock with Bob_lock as args>
    DEX_Receipt_Cell:
        Data:
            600 USDT
        Lock:
            <DEX_Exchange_Lock with Bob_lock as args>

DEX_Receipt_Cell 可作为将 sUDT 从 DEX 存款池中取出的凭据。

// Withdraw action  取出指令
Inputs:
    DEX_Receipt_Cell:
        Lock:
            <DEX_Exchange_Lock with Bob_lock as args>
    sUDT_Instance_Cell:
        Data:
            reserve_amount
        Lock:
            <DEX_DEPOSIT_LOCK>
Outputs:
    sUDT_Instance_Cell:
        Data:
            withdraw_amount
        Lock:
            <Bob_lock>
    sUDT_Instance:
        Data:
            reserve_amount - withdraw_amount
        Lock:
            <DEX_DEPOSIT_LOCK>

存款和取款的动作实际上是硬编码的,好让 sUDT token 可以符合的 DEX 规则。它类似于Etheruem将 DEX 合约被以硬编码的形式来调用 ERC20 的特定 ABI。

和 ABI 桥接比较 Compared with ABI bridge
首先,ABI桥是一个弱联结,它只定义了交互的意图。相反,我们的桥接指令定义了交互的实质内容,包括它所依赖、更改和创建的状态。因此,它比 ABI bridge 更可靠、更安全。
其次,ABI桥是一对一的连接。为了连接多个合约,它必须创建多个 ABI 桥接。然而,我们的指令桥接是一个多对多的连接。您可以使用多个 input cell 和多个 output cell 去定义一个操作,这些 input 和 output cell 的 type scripts 和 Lock scripts 属于不同的dapp。
第三是我们的指令桥接的组成标准低于 ABI 桥接。以 UDT 组成为例,Action 桥接将第三方的 dApp 链接到 sUDT 模板,ABI 桥接则将第三方的 dApp 链接到 ERC20 的实际案例中 。您不能强制每个 ERC20 都真正实现正确的逻辑,但(在 sUDT 模板的)格式总是能如预期的表现。

3 Likes

不知道这个 level 怎么翻比较好,但应该是在讲确定性和非确定性的差异

Action Bridge 可能就翻做Action桥接或者指令桥接

Can you explain more what you mean by defining:

This is because I’m wondering in the case of a DEX, how do you get deterministic execution of orders which is required by the state validation model? If there are two orders that come at the same time then two transactions may be generated at the same time, states generated and submitted but they could conflict. All the exchange rates and balances have to be generated before executing on chain.

For example, if Alice and Bob’s orders get filled while Frank’s was submitted to fill Alice’s order as well then only one of the transactions will succeed. Miners will be validating, so is it up to them to decide who gets filled?

So it seems that something like 0x could work with CKB but I’m having trouble imagining a fully on-chain liquidity protocol like Kyber with this model because if you want to fill separate orders from the same liquidity pool for multiple participants the state is dependent on the ordering for orders being executed. It’s find if you only fill one per liquidity pool for each block but if you want to fill more in each block the intermediate balances have to be updated accordingly.

Perhaps there is a way of “collapsing” these transactions per block since this interop and composability model allows for multiple cell input and outputs. Currently I’m struggling to see as the dependency tree grows in nervos, how is transaction submission going to scale with it?

1 Like

The problem here is essentially the contradiction between deterministic execution and unpredictable participation. There are accordingly two distinct methods to solve it. One is to leave the execution indeterminately until the transaction is packed into blocks and executed by miners, which is adopted by account based blockchains like Ethereum. Another method is to make the random participation sort of predictable. For example, we can seperate the users’ request into different state shards, so that their actions are independent. Or we can seperate the state transition into submit period and commit period. We collect users’ request during the submit period, and generate the state transition in commit period. All the operations are on-chain, and trustless.

Although it’s not so obvious, CKB actually accepts Ethereum like state transition model. Users send their intentions instead of the consequences to the chain, and then the miners (or some aggregators) calculate the result then commit it to the chain. One possible trick to achieve this is to use open transaction. Let’s say a vote dApp, there is a global state cell manages the votes for all candidates, and users send transactions to vote them. User sends an open transaction with specific input cells (to stand for their voting power) and the restriction to the output cell’s typehash. The transaction doesn’t constrain on the output cell’s data, only on the verification logics. Some off-chain aggregator collects such otxs, combines them into one single transaction, then calculates the final result, and set it as the content of the output cell. The aggregator is decentralized and trustless, anyone can be the aggregator.

2 Likes