Skip to main content

How to change a pre-propose module

The proposal module system enables DAOs to support any type of execution flow that can be coded in a smart contract, and the pre-propose module is the first step in the proposal process. It determines the process for submitting a proposal: who can propose something, whether or not there's a deposit, etc.

Proposal modules can either set a pre-propose module, or not use one, which effectively removes all restrictions (meaning anyone can submit a proposal).

There are two pre-propose module types:

  • base: manages deposits and submission policy
  • approval: same as base, plus the ability to set an approver that must approve pending proposals before they open for voting on the proposal module

Each proposal module has corresponding pre-propose modules (e.g. the dao-proposal-single module has dao-pre-propose-single and dao-pre-propose-approval-single, the dao-proposal-multiple module has dao-pre-propose-multiple and dao-pre-propose-approval-multiple, etc.).

You can always add, remove, or replace the pre-propose module for a proposal module.

Adding and disabling proposal modules is not common, and can be dangerous, and thus is not easily supported by the UI, but it is not hard to do with a little bit of time.

⚠️ Be careful, the DAO will be bricked and all assets will be locked if the pre-propose module is misconfigured. It is best practice to ensure at least one is functioning before disabling another. Or, better yet, just add an entirely new proposal module and then disable the existing one, instead of trying to change the pre-propose module.

Guide

First you need the address of the proposal module and the code ID of the pre-propose module. You can find all uploaded DAO DAO code IDs here: https://github.com/DA0-DA0/dao-dao-ui/blob/development/packages/utils/constants/codeIds.json

The following guide will update a v2.6.0 single choice proposal module to use the v2.6.0 base pre-propose module on Osmosis mainnet (osmosis-1), so we need the dao-proposal-single address and dao-pre-propose-single code ID.

You can use the DAO DAO indexer to query for the active proposal modules in a DAO and find the dao-proposal-single address:

DAO_ADDRESS=INSERT_DAO_ADDRESS curl -s https://indexer.daodao.zone/osmosis-1/contract/$DAO_ADDRESS/daoCore/activeProposalModules | jq

Or query the chain directly:

osmosisd query contract-state smart $DAO_ADDRESS '{"active_proposal_modules":{}}' --output json | jq

The output will look like this:

[
{
"prefix": "A",
"status": "enabled",
"address": "DAO_PROPOSAL_SINGLE_ADDRESS",
"info": { "version": "2.6.0", "contract": "crates.io:dao-proposal-single" }
},
{
"prefix": "B",
"status": "enabled",
"address": "DAO_PROPOSAL_MULTIPLE_ADDRESS",
"info": {
"version": "2.6.0",
"contract": "crates.io:dao-proposal-multiple"
}
}
]

You can see the dao-proposal-single address is DAO_PROPOSAL_SINGLE_ADDRESS.

You can also find the dao-pre-propose-single code ID in the codeIds.json file linked above: 1250.

A few steps involve base64 encoding, which base64encode.org can help with.

Here is the configuration we will use:

  • submission policy: DAO members only
  • no proposal deposit

You can look at the JSON schemas' instantiate definitions for the pre-propose module to see the configuration fields you need to set, as well as the dao-proposal-single contract's JSON schema to see the update_pre_propose_info execution message that performs the actual update:

You can also look at the Rust source code, which contains the corresponding InstantiateMsg and ExecuteMsg structs, though you will need to know how serde serializes various types and enums which can be tricky. The JSON schemas above contain all the information you need, but here is the source code for reference:

Putting that all together, the full message we need to execute on the proposal module via a proposal, with everything decoded for readability, follows:

{
"update_pre_propose_info": {
"info": {
"module_may_propose": {
"info": {
"code_id": 1250,
"msg": {
"submission_policy": {
"specific": {
"dao_members": true,
"allowlist": [],
"denylist": []
}
},
"extension": {}
},
"admin": { "core_module": {} },
"funds": [],
"label": "dao-pre-propose-single-v2.6.0"
}
}
}
}
}

Now we have to encode the msg field with base64:

{
"submission_policy": {
"specific": {
"dao_members": true,
"allowlist": [],
"denylist": []
}
},
"extension": {}
}

This gets base64-encoded to:

ewogICJzdWJtaXNzaW9uX3BvbGljeSI6IHsKICAgICJzcGVjaWZpYyI6IHsKICAgICAgImRhb19tZW1iZXJzIjogdHJ1ZSwKICAgICAgImFsbG93bGlzdCI6IFtdLAogICAgICAiZGVueWxpc3QiOiBbXQogICAgfQogIH0sCiAgImV4dGVuc2lvbiI6IHt9Cn0=

Now replace the pre-propose module instantiation msg field with the base64-encoded instantiation message:

{
"update_pre_propose_info": {
"info": {
"module_may_propose": {
"info": {
"code_id": 1250,
"msg": "ewogICJzdWJtaXNzaW9uX3BvbGljeSI6IHsKICAgICJzcGVjaWZpYyI6IHsKICAgICAgImRhb19tZW1iZXJzIjogdHJ1ZSwKICAgICAgImFsbG93bGlzdCI6IFtdLAogICAgICAiZGVueWxpc3QiOiBbXQogICAgfQogIH0sCiAgImV4dGVuc2lvbiI6IHt9Cn0=",
"admin": { "core_module": {} },
"funds": [],
"label": "dao-pre-propose-single-v2.6.0"
}
}
}
}
}

And you have the full message to execute on the proposal module via a proposal. You can now use the DAO DAO UI to create a proposal with the Execute Smart Contract action, with the contract address being the dao-proposal-single module's address, and the message field containing the above text. Once you pass and execute the proposal, the pre-propose module will be updated.