Teach you how to get started with smart contract development on nervos CKB

Time:2022-1-8

Teach you how to get started with smart contract development on nervos CKB
Nervos CKB is a layer 1 Public chain based on pow. Its cell model is a generalization of bitcoin utxo model. Therefore, its smart contract development is different from that based on Ethereum account model. In this article, Dylan, the core developer of nervso CKB, introduced in detail how to develop smart contracts on CKB. More developers are welcome to experience the fun of development on CKB.

Summary

Ethereum’s contract is calculated on the chain. The contract caller needs to give the input of the contract method, and the chain will complete the calculation and obtain the output. CKB’s contract is verified on the chain, and the contract caller needs to give both input and output, and complete the verification from input to output on the chain.

For a simple analogy, if you want to implement the y = sqrt (x) function in the contract, you need to give the value of X for Ethereum, and the contract calculates the value of Y; For CKB, you need to give the values of X and y at the same time. The contract is responsible for verifying whether X and Y meet y = sqrt (x).

It can be seen from this example that Ethereum contract development only needs to pay attention to the input and contract functions to be called. The calculation and status update will be completed on the chain, while CKB needs to calculate the input and output outside the chain in advance. The contract only needs to verify whether the input and output meet the requirements according to the same calculation rules. In other words, CKB needs to implement generators outside the chain and validators on the chain at the same time, and the verification rules of both are consistent.

For developers familiar with Ethereum smart contract, CKB smart contract is a brand-new development model. All state changes need to be set in advance by generators outside the chain. What needs to be done on the chain is to verify whether the state changes comply with the rules. Compared with Ethereum, which only needs to implement contract rules on the chain, CKB needs to implement two sets of the same rules outside the chain and on the chain at the same time, which increases the complexity of contract development to a certain extent, but the advantage is that the complexity of contract operation can be greatly reduced, because verification is usually simpler than calculation.

In the example mentioned above, if you want to implement the y = sqrt (x) function in the contract, Ethereum needs to perform the square operation according to the input x to get y in the contract. In fact, the contract of CKB only needs to judge whether X and Y meet x = y ^ 2. Obviously, the computational complexity of square is much less than that of square. In other words, the contract algorithm of CKB does not need to be completely consistent with the off chain generator, as long as their calculations are equivalent.

Data structure of cell and transaction

Because the contract of CKB essentially changes the state of cell through transaction, we strongly recommend to be familiar with the data structure of cell and transaction first, otherwise it will affect the understanding of subsequent contracts. For details, please refer to transaction structure:

  • https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0022-transaction-structure/0022-transaction-structure.md
// Cell
{
  capacity: uint64,
  lock: Script,
  type: Script,
}

// Script
{
  code_hash: H256,
  args: Bytes,
  hash_type: String    // type or data
}

Inputs, outputs and outputs_ Data represents the state changes of the cell before and after a transaction. The cell contains lock script (required) and type script (not required). CKB VM will execute all lock scripts in inputs and all type scripts in inputs and outputs. Lock script and type script contain contract rules that restrict the cell state.

About code in script_ Hash, args and hash_ Type can refer to code locating. Please read it first, otherwise it will affect the understanding of subsequent contracts:

  • https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0022-transaction-structure/0022-transaction-structure.md#code-locating

VM Syscall

Since we need to judge whether the state changes of the cell before and after a transaction comply with certain rules in the contract, we first need to obtain the data in the cell and transaction in the contract. CKB VM provides syscall to help us access the data in the cell and transaction in the contract:

  • ckb_load_tx_hash
  • ckb_load_transaction
  • ckb_load_script_hash
  • ckb_load_script
  • ckb_load_cell
  • ckb_load_cell_by_field
  • ckb_load_cell_data
  • ckb_load_cell_data_as_code
  • ckb_load_input
  • ckb_load_input_by_field
  • ckb_load_header
  • ckb_load_header_by_field
  • ckb_load_witness

It can be seen that VM syscall provides a large number of methods to obtain cell and transaction data. These methods can be called directly in C language code. Refer to VM syscall for specific parameters and call details:

  • https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0009-vm-syscalls/0009-vm-syscalls.md
#include "ckb_syscalls.h"

// We are limiting the script size loaded to be 32KB at most.
// This should be more than enough.
// We are also using blake2b with 256-bit hash here,
// which is the same as CKB.
#define BLAKE2B_BLOCK_SIZE 32
#define SCRIPT_SIZE 32768

#define ERROR_SYSCALL -3
#define ERROR_SCRIPT_TOO_LONG -21

int main() {
  // First, let's load current running script,
  // so we can extract owner lock script hash from script args.

  unsigned char script[SCRIPT_SIZE];
  uint64_t len = SCRIPT_SIZE;

  int ret = ckb_load_script(script, &len, 0);
  if (ret != CKB_SUCCESS) {
    return ERROR_SYSCALL;
  }
  if (len > SCRIPT_SIZE) {
    return ERROR_SCRIPT_TOO_LONG;
  }

  return CKB_SUCCESS;
}

The above contract example shows how to read the current script data and judge whether the script data meets the length requirements. The system contracts of CKB are implemented in C language. For details, please refer to:

  • ckb-system-scripts:

https://github.com/nervosnetwork/ckb-system-scripts/tree/master/c

  • ckb-miscellaneous-scripts:

https://github.com/nervosnetwork/ckb-miscellaneous-scripts/tree/master/c

Capsule

In order to reduce the threshold of contract development, debugging, testing and deployment, nervos CKB launched the smart contract development framework capsule based on rust language, which aims to provide out of the box solutions to help developers quickly and easily complete common development tasks:

  • https://github.com/nervosnetwork/capsule
USAGE:
capsule [SUBCOMMAND]

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

SUBCOMMANDS:
    check           Check environment and dependencies
    new             Create a new project
    new-contract    Create a new contract
    build           Build contracts
    run             Run command in contract build image
    test            Run tests
    deploy          Deploy contracts, edit deployment.toml to custodian deployment recipe.
    debugger        CKB debugger
    help            Prints this message or the help of the given subcommand(s)

The creation, compilation, testing, debugging and deployment of smart contracts can be completed through the capsule command line. For detailed instructions on the use of capsule, please refer to write a sudt script by capsule:

  • https://docs.nervos.org/docs/labs/sudtbycapsule

In order to enable Rust developers to invoke the VM Syscall method in the Capsule framework, Nervos CKB provides ckb-std and related usage documents, and developers can introduce ckb-std into the contract to use high_. The method under the level module completes the call to CKB cell and transaction data.

// Module ckb-std::high_level
find_cell_by_data_hash
load_cell
load_cell_capacity
load_cell_data
load_cell_data_hash
load_cell_lock
load_cell_lock_hash
load_cell_occupied_capacity
load_cell_type
load_cell_type_hash
load_header
load_header_epoch_length
load_header_epoch_number
load_header_epoch_start_block_number
load_input
load_input_out_point
load_input_since
load_script
load_script_hash
load_transaction
load_tx_hash
load_witness_args

Here are some common uses of high_ Example of level method:

// Call current script and check script args length
let script = load_script()?;
let args: Bytes = script.args().unpack();
if args.len() != 20 {
    return Err(Error::InvalidArgument);
}

// Call the input of index 0
let cell_input = load_cell(0, Source::Input)?

// Call the output of index 0
let cell_output = load_cell(0, Source::Output)?

// Filter inputs whose lock script hash is equal to the given
// lock hash and calculate the sum of inputs' capacity
let cell_inputs = QueryIter::new(load_cell, Source::Input)
      .position(|cell| &hash::blake2b(cell.lock().as_slice())
      == lock_hash)

let inputs_sum_capacity = cell_inputs.into_iter()
      .fold(0, |sum, c| sum + c.capacity().unpack())

// Check if there is an output with lock script hash equal to
// the given lock hash
let has_output = QueryIter::new(load_cell, Source::Output)
      .any(|cell| &hash::blake2b(cell.lock().as_slice())
      == lock_hash)

// Check whether the witness args' lock is none of witness
// whose index in witnesses is 0
match load_witness_args(0, Source::Input) {
  Ok(witness_args) => {
    if witness_args.lock().to_opt().is_none() {
      Err(Error::WitnessSignatureWrong)
    } else {
      Ok(())
    }
  },
  Err(_) => Err(Error::WitnessSignatureWrong)
}

If the signature needs to be verified in the contract, ckb-dynamic-loading-secp256k1 shows how to call the C code of system secp256k1 through rust code, and ckb-dynamic-loading-rsa shows how to call the C code of RSA signature algorithm through rust code.

For more examples of capsule developing smart contracts, please refer to the following items:

  • my-sudt

https://github.com/jjyr/my-sudt

  • ckb-cheque-script

https://github.com/duanyytop/ckb-cheque-script

  • ckb-passport-lock

https://github.com/duanyytop/ckb-passport-lock

Debug

It is very common to encounter unexpected errors during contract development. A common debugging method is to print logs in the contract. CKB STD provides debug, Its usage is similar to print! In rust language, In the contract tests, you can directly use print! And println! To print.

Testing

For the CKB smart contract, capsule can help developers realize the local test of the contract without deploying to the nervos CKB development chain or test chain, which can greatly reduce the difficulty of contract debugging and improve the efficiency of contract testing. For test cases on how to implement contracts in capsule, please refer to write a sudt script by capsule # test:

  • https://docs.nervos.org/docs/labs/sudtbycapsule#testing

Deploy

For the CKB smart contract, in addition to the conventional binary code direct deployment, the binary code hash is used as the code hash, as well as the type ID deployment. The code hash is obtained from the type script hash.

Type ID and DEP_ Group can be deployed in deployment It is configured in the toml file. For the final deployment, please refer to write a sudt script by capsule # deployment:

  • https://docs.nervos.org/docs/labs/sudtbycapsule#deployment

Common errors

During the development of the contract, it is inevitable to encounter various errors. How to quickly locate and repair the problem is very important. If your contract uses the CKB system contract, such as secp256k1_ blake160_ sighash_ all、secp256k1_ blake160_ multisig_ All or nervos Dao, you can refer to the system contract error code and the corresponding error explanation to quickly locate the problem.

Common errors are:

  • 1: The array is out of bounds. Check whether the index exceeding the length of the array is accessed
  • 2: Some data is missing. For example, a cell needs a type script, but it is missing when assembling transactions
  • -1: The parameter length is wrong, which may be script args or signature length
  • -2: If the encoding is abnormal, check whether the data of cell and transaction meet the requirements of mole, such as more or less 0x, odd hex string length, etc
  • -101 ~ – 103: secp256k1 signature verification failed. Check whether the contract, transaction witness and script parameters are correct
  • Invalidcodehash: the script code hash is invalid. Check whether the code hash is correct and whether the cell DEPs contains the cell dep corresponding to the code hash
  • Exceededmaximumcycles: the number of cycles consumed by the contract has exceeded the maximum limit
  • Capacity overflow: capacity overflow. Please check whether the total capacity of outputs is greater than that of inputs
  • Insufficientcellcapacity: the number of bytes actually occupied by cell data is greater than the capacity of the current cell (capacity represents the number of bytes of data that the cell can carry)
  • Immature: the current input cannot be consumed because the input since is not zero

Of course, there are many system contract errors. The above only lists the common types of errors. For details, please refer to:

  • Error Codes:

https://github.com/nervosnetwork/ckb-system-scripts/wiki/Error-codes

  • Verification Error

https://github.com/nervosnetwork/ckb/blob/develop/verification/src/error.rs

  • Script Error:

https://github.com/nervosnetwork/ckb/blob/develop/script/src/error.rs

In addition to the error codes of system contracts, specific business contracts also have their own error codes. At this time, you need to look at the error codes defined in business contracts and locate possible errors, such as CKB cheque script error code:

  • https://github.com/duanyytop/ckb-cheque-script/blob/main/contracts/ckb-cheque-script/src/error.rs

The corresponding error code should be thrown where the contract may go wrong, which is not only conducive to the debugging of the contract itself, but also helps the off chain generator locate the problem more easily when assembling the transaction.

//If you like nervos and like development
//You can pay attention to me and trust me privately~
if (you like Nervos && you like dev) {
    println("you can follow me and private letter for me~");
}