Skip to content

Governance fun with Chopsticks

Xiliang Chen edited this page Dec 29, 2023 · 3 revisions

How to use Chopsticks to have a mainnet fork of Polkadot/Kusama locally for OpenGov experiments.

The developer docs can be found at https://acalanetwork.github.io/chopsticks/docs/

Requirements

  • Basic understanding of how to use command line tools
  • Know how to encode and decode extrinsics

Prerequisites

Run Chopsticks

Single Network

npx @acala-network/chopsticks@latest --config kusama

Or use bunx instead of npx if using bun.

List of available networks can be found here: https://github.com/AcalaNetwork/chopsticks/tree/master/configs

You may submit a PR to add new networks (or ask a dev to do it if you don't know how to submit a PR)

Multiple Networks

npx @acala-network/chopsticks@latest xcm --relaychain kusama --parachain karura

Multiple --parachain CHAIN_NAME can be specified. Check the log output for the port of each chain

Access the network

The Chopsticks RPC url will be ws://127.0.0.1:8000 or with port 800X for additional instances. You may use any tools to connect to this endpoint and start using it.

The easiest way is use pjs apps https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A8000#/explorer

Note: if it isn't loading, make sure you have configured your browser to support insecure localhost.

You may start using pjs apps to send transactions and browse the network. By default, the network will produce new block only when received a transaction.

Dev RPCs

Chopsticks offers multiple dev RPCs to modify the devnet

dev_newBlock

This can be used to build new blocks. Docs: https://acalanetwork.github.io/chopsticks/docs/chopsticks/README.html#newblock

dev_setHead

This can be used to revert block, in case you want to undo something, you can do await api.rpc('dev_setHead', -1). Docs: https://acalanetwork.github.io/chopsticks/docs/chopsticks/README.html#sethead

dev_setStorage

This can be used to modify storage. Docs: https://acalanetwork.github.io/chopsticks/docs/chopsticks/README.html#setstorage

api.rpc('dev_setStorage', {
   system: { account: [[['FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP'], { providers: 1, data: { free: '10000000000000'}}]] }
})

Run the above code in https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A8000#/js with Kusama will set the balance of Bob to 10 KSM.

Dispatch Call

For the networks that have sudo pallet, we can simply use dev_setStorage to set the sudo key to Alice and use sudo to dispatch calls. However, most of the networks does not have sudo pallet, including Kusama and Polkadot. In this case, we can use scheduler pallet to dispatch the call.

This is an example of using root to perform force transfer. Note: it can take up to a few minutes for the block to be produced.

const number = (await api.rpc.chain.getHeader()).number.toNumber()
await api.rpc('dev_setStorage', {
 scheduler: {
   agenda: [
     [
       [number + 1], [
         {
           call: {
             Inline: '0x040200d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48070010a5d4e8'
           },
           origin: {
             system: 'Root'
           }
         }
       ]
     ]
   ]
 }
})
await api.rpc('dev_newBlock')

Note that we can specify the dispatch origin. This means we can simulate the outcome of a call when dispatched by a particular OpenGov tracks.

For example, this simulates to spend 100 KSM from SmallTipper track

const number = (await api.rpc.chain.getHeader()).number.toNumber()
await api.rpc('dev_setStorage', {
 scheduler: {
   agenda: [
     [
       [number + 1], [
         {
           call: {
             Inline: '0x12030b00407a10f35a008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48'
           },
           origin: {
             origins: 'SmallTipper'
           }
         }
       ]
     ]
   ]
 }
})
await api.rpc('dev_newBlock')

and it will fail with treasury.InsufficientPermission error indicate SmallTipper cannot spend 100 KSM.

Propose Bounty

A bounty have multiple phases: Proposed, Approved, Funded, CuratorProposed, Active, PendingPayout. (Code: https://github.com/paritytech/polkadot-sdk/blob/048a9c2744ca83ef6faeadd2cd19466295d52b9d/substrate/frame/bounties/src/lib.rs#L147-L175)

It is only possible to propose a curator after the bounty is approved and fund and treasury only fund bounty and other spending proposals every funding period (6 days in Kusama and 24 days in Polkadot). This means it is not possible to have a single referendum to approve a bounty and propose a curator. Except with Treasurer origin, which have access to scheduler.schedule call. The original intention is to allow treasurer origin to schedule reoccurring payouts, but in this case, can also be used to schedule propose curator after the bounty is funded.

To try it out:

const number = (await api.rpc.chain.getHeader()).number.toNumber()
await api.rpc('dev_setStorage', {
 scheduler: {
   agenda: [
     [
       [number + 1], [
         {
           call: {
             Inline: '0x1802082301641d00815c400100fe230264008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48070010a5d4e8'
           },
           origin: {
             origins: 'Treasurer'
           }
         }
       ]
     ]
   ]
 }
})
await api.rpc('dev_newBlock')

This will dispatch a proposal using Treasurer origin to approve bounty 25, and schedule propose curator at block 20995201, the block after the current funding period ends.

The scheduled call can be found at https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A8000#/scheduler

Then we can do a time warp with await api.rpc('dev_newBlock', { count: 2, unsafeBlockHeight: 20995200 }) to directly build block 20995200 and 20995201. Note this is unsafe as it skips all the block between current block and 20995200. But we shall see the bounty status for bounty 25 is CuratorProposed now.