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.