A new treasury cell is generated every block

A new treasury cell is generated every block

The Onchain DAO Treasury mechanism as described on Github. Can you think of it like the coinbase transaction that is created in every block through mining?

Very interesting and an important step for the future. A mechanism that accumulates funds until the currently available resources are exhausted.

Users should be able to see how much funds are available in the traditional wallets from CKB’s early years and how much has accumulated through DAO treasure cells.

0, so far all treasury issuance has been burned

1 Like

Treasury Lock Script Specification []

This spec describes a lock script used in a treasury system. See design document.

Introduction

A cell with a treasury lock script is called a treasury cell. Treasury cells can only be created by consensus, similar to a cell base. A treasury cell can be consumed via two methods: the burning method or the reward method.

Script

The lock script has the following structure:

code_hash: <code hash to treasury lock script binary>
hash_type: Type
args: <8-byte block number>

The 8-byte block number is parsed as a 64-bit unsigned little-endian integer. It represents the block number at the time the cell was created, set by the CKB node.

Witness

The corresponding witness must be in WitnessArgs molecule format. The full data structure is as follows:

WitnessArgs:
    lock: <1 byte consuming_method>
    input_type: <>
    output_type: <>

The 1-byte consuming_method can be 0 (burning method) or 1 (reward method).

Unlocking Process

Depending on the consuming_method, the treasury lock script can be unlocked via one of two methods: burning or reward.

Burning Method

When this method is chosen, the script iterates over all headers using ckb_load_header (with source = 4, header deps) to fetch all block numbers. It parses each header to extract the block number, and the largest block number found is treated as the current block number. The transaction creator should put the latest block number in header_deps.

A duration block count is then calculated by subtracting the block number in args from the current block number.

This duration must be greater than a predefined value called burn_expiry_blocks, which is stored in the config cell described later.

To encourage anyone to burn expired treasury cells, an incentive mechanism is included. The incentive amount is calculated as follows:

incentive amount = (duration block count - burn_expiry_blocks) * burn_incentive_rate + base_burn_incentive

The config parameters burn_incentive_rate and base_burn_incentive are also stored in the config cell. The incentive amount is denominated in shannons, not CKB.

This design ensures the incentive amount grows over time if no one burns the cell, addressing the case where transaction fees exceed the incentive amount.

The lock script takes the capacity of the current cell, subtracts the incentive amount, and finds a corresponding output cell with exactly that capacity. This output cell must have zero-length script args, cell data, and type script, with code_hash and hash_type both set to zero, meaning it is permanently unspendable (a zero lock).

The remaining capacity can be split between transaction fee and changes; it is the transaction creator’s responsibility to distribute these.

Reward Method

The reward method reads reward_script_code_hash, reward_script_hash_type, and reward_script_verifying_key from the config cell. If a type script in the transaction matches all of the following:

  • code_hash
  • hash_type
  • args[20..52]

then the cell is unlocked.

The following rules must be followed:

  1. Sort input treasury cells by capacity in decreasing order. Let N be the total cell count. The sum of capacity for the leading N - 1 cells must be less than the required CKBytes of the proposal. This prevents the inclusion of arbitrary treasury cells.
  2. There must be exactly one change cell, locked by a zero lock, with empty script args and cell data. Its amount equals the total capacity of the N treasury cells minus the required CKBytes of the proposal.
  3. The transaction fee is not covered by treasury cells. It may be provided by the proposal creator.

The reason treasury cells do not cover the transaction fee is that the fee is dynamic and cannot be determined in advance, so it is left to the proposal creator. In practice, the fee is typically very small.

At most one treasury cell may be burned per transaction. Since a new treasury cell is generated every block, the burned amount is small and acceptable.

Config Cell

The config cell is deployed on-chain and locked by a different key than the treasury lock script, allowing config updates to be delegated to a separate team. Its type script must follow Type ID rules. The treasury lock script references the config cell by its type script hash, which is hard-coded.

The data in the config cell is encoded in molecule format as follows:

table TreasuryConfig {
    burn_expiry_blocks: Uint32,
    burn_incentive_rate: Uint64,
    base_burn_incentive: Uint64,
    reward_script_code_hash: Bytes32,
    reward_script_hash_type: byte,
    reward_script_verifying_key: Bytes32
}

reward_script_code_hash and reward_script_hash_type identify the proposal type script. reward_script_verifying_key is described in the proposal type spec.

Treasury Creating Component [¹]

In the treasure system, there are two components: a creating component and a voting component. The creating component mints CKB out of thin air, similar to a coinbase transaction. The voting component votes on and allocates the treasure created in the previous step. These two components are independent and can be designed and implemented separately More details in spec document.

Creating

The creating component depends on a hardfork. The voting component does not. This document describe the process of creating component.

For every block, in addition to the cellbase cell, a treasury cell is created, similar to cellbase. Unlike cellbase, the address is not set by the miner: the cell, including its lock script, is determined by consensus.

The capacity field is determined by the economic model; we do not elaborate on it here. The lock script should be a special lock script defined in a later section. Variable-length fields in the cell, such as args, cell data, and witness, should be left empty. This prevents capacity attacks, where an attacker fills these fields with garbage data.

Treasury Lock Script

More details in spec document.

This lock script can be spent in two ways: burn and reward.

After a specific time, anyone can burn this cell. When burning it, the output cell should use a zero lock so that the burned CKB cannot be used as a transaction fee. Why use the term “after a specific time” rather than “only allow transfer before a specific time”? Because CKB’s since mechanism supports only > comparisons, not <. Anyone can burn the cell this way, and those who do receive a small CKB reward (for example, 1–2 CKB) as an incentive. This mechanism works as a lookback: the treasury allocation from a block is available for x days. If no approved proposal spends those funds within x days, they are no longer available and are burned.

It is also possible to transfer such cells as a reward. When doing so, the script iterates over all cells to check for any matching proposal script (see the zkVM voting system). Only a specific amount of CKB can be transferred as the reward for the vote result.

The treasury lock script is a system lock script, similar to the Genesis Scripts described in RFC-0024.

Configuration

In the section above, the matching proposal script should be configurable. When the matching proposal script must change (for example, because the guest program changed), it should be possible to update it on chain. To simplify this process, the matching proposal script is configurable: it is stored in a special cell that can be upgraded. Only a designated team can update this configuration cell.

Future

After being operated by a designated team for some time, the system can be transitioned to a fully decentralized model, using a special lock-based voting system powered by zkVM. DAO users can vote on updating configurable cells. The voting threshold should be set higher than for a normal vote.

There will be two zkVM guest programs: one for the proposal script and the other for the configure cell. The latter is hardcoded and will not change after deployment. They share almost the same mechanics, such as vote counting, but the configure cell program requires more “YES” votes since it governs rule updates.

The process of updating the zkVM guest program is as follows:

  • Someone proposes a new guest program for the proposal script (e.g., changing the “YES” vote threshold)
  • The changes are published in a public channel
  • Someone initiates a proposal for the configure cell. The proposal must include the verifying key (hash of the program) of the new guest program, ensuring the configure cell can only be updated to the exact program that was proposed.
  • Every voter can review the changes and vote “YES” or “NO”
  • When the proposal passes, the vote results serve as a zkVM proof to authorize the update
  • The configure cell is upgraded to the new guest program for the proposal

Other issues

UDT

One suggestion for transferring assets from the treasury cell to users is to add an extra layer: UDT. When a proposal is passed, it mints a specific amount of special UDT. These UDT tokens can later be converted to CKB via the treasury cell.

The benefit is that we can control UDT: for example, we can use an emergency halt to freeze all UDT in case of bugs or security issues.

The drawback is that it introduces an extra layer, making the whole system more complex. It also affects the economic system: users can accumulate UDT and never convert it to CKB, yet we still need to guarantee that conversion remains possible.

Changes

When a treasury cell is consumed, like any normal transaction, change outputs are required. For example, if there are 2 treasury cells containing 10,000 CKB in total but the proposal only requests 8,000 CKB, the remaining 2,000 CKB must be returned to a treasury cell. Without a change output, the remainder would be consumed as a transaction fee, which is not the intended behavior.

The change cell’s lock script and type script must be identical to those of the first treasury cell.

Another consideration is who pays the transaction fee. A simple strategy is that whoever claims the CKB also pays the fee, so the change amount can be calculated straightforwardly.

Deployment Upgrade

The lesson from DAO and genesis scripts is that no script is correct forever. There should always be a way to upgrade them. The proposal script, vote script, and treasury lock script should all be upgradable, and a designated team must be responsible for maintaining them.

1 Like