How to add a proposal module
The proposal module system enables DAOs to support any type of execution flow that can be coded in a smart contract.
DAO DAO provides two modules:
- single choice (e.g. Yes/No/Abstain)
- multiple choice (e.g. A/B/C/None of the Above)
You can always add new modules or disable existing ones, though there must always be at least one module enabled (or else the DAO will not function). The most likely reason one would want to add a new module is to use a new version with new features. Because the proposal module interface is standardized and unchanging, newer versions of proposal modules should be backwards compatible.
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 to verify all proposal modules function correctly before disabling one you no longer need, because if the only enabled proposal modules are broken or the addresses are incorrect, the DAO will be bricked and all assets will be locked.
Guide
First you need the code IDs of the proposal module and its corresponding 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 add the v2.6.0
single choice proposal module to a DAO
on Osmosis mainnet (osmosis-1
), so we need the dao-proposal-single
and
dao-pre-propose-single
code IDs.
A few steps involve base64 encoding, which base64encode.org can help with.
Here is the configuration we will use:
- threshold: majority
- quorum: 20%
- max voting period: 1 week
- only members can execute: true
- close proposal on execution failure: true
- allow revoting: false
- submission policy: DAO members only
- no proposal deposit
You can look at the JSON schemas' instantiate
definitions for the proposal and
pre-propose modules to see the configuration fields you need to set, as well as
the dao-dao-core
contract's JSON schema to see the update_proposal_modules
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:
- dao-proposal-single
InstantiateMsg
struct - dao-pre-propose-single
InstantiateMsg
struct which just extends the dao-pre-propose-baseInstantiateMsg
struct - dao-dao-core
ExecuteMsg
struct defined in thedao-interface
crate.
Putting that all together, the full message we need to execute on the DAO via a proposal, with everything decoded for readability, follows:
{
"update_proposal_modules": {
"to_add": [{
"admin": { "core_module": {} },
"code_id": 1252,
"label": "dao-proposal-single-v2.6.0",
"msg": {
"threshold": {
"threshold_quorum": {
"threshold": { "majority": {} },
"quorum": { "percent": "0.2" }
}
},
"max_voting_period": { "time": 604800 },
"only_members_execute": true,
"close_proposal_on_execution_failure": true,
"allow_revoting": false,
"pre_propose_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"
}
}
}
},
"funds": []
}],
"to_disable": []
}
}
Now we have to recursively encode the msg
fields with base64, so starting from
the innermost msg
field for the pre-propose module instantiation message:
{
"submission_policy": {
"specific": {
"dao_members": true,
"allowlist": [],
"denylist": []
}
},
"extension": {}
}
This gets base64-encoded to:
ewogICJzdWJtaXNzaW9uX3BvbGljeSI6IHsKICAgICJzcGVjaWZpYyI6IHsKICAgICAgImRhb19tZW1iZXJzIjogdHJ1ZSwKICAgICAgImFsbG93bGlzdCI6IFtdLAogICAgICAiZGVueWxpc3QiOiBbXQogICAgfQogIH0sCiAgImV4dGVuc2lvbiI6IHt9Cn0=
Now isolate the proposal module instantiation msg
, and replace the pre-propose
module instantiation msg
field with the base64-encoded pre-propose module
instantiation message from above:
{
"threshold": {
"threshold_quorum": {
"threshold": { "majority": {} },
"quorum": { "percent": "0.2" }
}
},
"max_voting_period": { "time": 604800 },
"only_members_execute": true,
"close_proposal_on_execution_failure": true,
"allow_revoting": false,
"pre_propose_info": {
"module_may_propose": {
"info": {
"code_id": 1250,
"msg": "ewogICJzdWJtaXNzaW9uX3BvbGljeSI6IHsKICAgICJzcGVjaWZpYyI6IHsKICAgICAgImRhb19tZW1iZXJzIjogdHJ1ZSwKICAgICAgImFsbG93bGlzdCI6IFtdLAogICAgICAiZGVueWxpc3QiOiBbXQogICAgfQogIH0sCiAgImV4dGVuc2lvbiI6IHt9Cn0=",
"admin": { "core_module": {} },
"funds": [],
"label": "dao-pre-propose-single-v2.6.0"
}
}
}
}
This gets base64-encoded to:
ewogICJ0aHJlc2hvbGQiOiB7CiAgICAidGhyZXNob2xkX3F1b3J1bSI6IHsKICAgICAgInRocmVzaG9sZCI6IHsgIm1ham9yaXR5Ijoge30gfSwKICAgICAgInF1b3J1bSI6IHsgInBlcmNlbnQiOiAiMC4yIiB9CiAgICB9CiAgfSwKICAibWF4X3ZvdGluZ19wZXJpb2QiOiB7ICJ0aW1lIjogNjA0ODAwIH0sCiAgIm9ubHlfbWVtYmVyc19leGVjdXRlIjogdHJ1ZSwKICAiY2xvc2VfcHJvcG9zYWxfb25fZXhlY3V0aW9uX2ZhaWx1cmUiOiB0cnVlLAogICJhbGxvd19yZXZvdGluZyI6IGZhbHNlLAogICJwcmVfcHJvcG9zZV9pbmZvIjogewogICAgIm1vZHVsZV9tYXlfcHJvcG9zZSI6IHsKICAgICAgImluZm8iOiB7CiAgICAgICAgImNvZGVfaWQiOiAxMjUwLAogICAgICAgICJtc2ciOiAiZXdvZ0lDSnpkV0p0YVhOemFXOXVYM0J2YkdsamVTSTZJSHNLSUNBZ0lDSnpjR1ZqYVdacFl5STZJSHNLSUNBZ0lDQWdJbVJoYjE5dFpXMWlaWEp6SWpvZ2RISjFaU3dLSUNBZ0lDQWdJbUZzYkc5M2JHbHpkQ0k2SUZ0ZExBb2dJQ0FnSUNBaVpHVnVlV3hwYzNRaU9pQmJYUW9nSUNBZ2ZRb2dJSDBzQ2lBZ0ltVjRkR1Z1YzJsdmJpSTZJSHQ5Q24wPSIsCiAgICAgICAgImFkbWluIjogeyAiY29yZV9tb2R1bGUiOiB7fSB9LAogICAgICAgICJmdW5kcyI6IFtdLAogICAgICAgICJsYWJlbCI6ICJkYW8tcHJlLXByb3Bvc2Utc2luZ2xlLXYyLjYuMCIKICAgICAgfQogICAgfQogIH0KfQ==
Now replace the proposal module instantiation msg
field in the
update_proposal_modules
execution message with the base64-encoded proposal
module instantiation message from above:
{
"update_proposal_modules": {
"to_add": [{
"admin": { "core_module": {} },
"code_id": 1252,
"label": "dao-proposal-single-v2.6.0",
"msg": "ewogICJ0aHJlc2hvbGQiOiB7CiAgICAidGhyZXNob2xkX3F1b3J1bSI6IHsKICAgICAgInRocmVzaG9sZCI6IHsgIm1ham9yaXR5Ijoge30gfSwKICAgICAgInF1b3J1bSI6IHsgInBlcmNlbnQiOiAiMC4yIiB9CiAgICB9CiAgfSwKICAibWF4X3ZvdGluZ19wZXJpb2QiOiB7ICJ0aW1lIjogNjA0ODAwIH0sCiAgIm9ubHlfbWVtYmVyc19leGVjdXRlIjogdHJ1ZSwKICAiY2xvc2VfcHJvcG9zYWxfb25fZXhlY3V0aW9uX2ZhaWx1cmUiOiB0cnVlLAogICJhbGxvd19yZXZvdGluZyI6IGZhbHNlLAogICJwcmVfcHJvcG9zZV9pbmZvIjogewogICAgIm1vZHVsZV9tYXlfcHJvcG9zZSI6IHsKICAgICAgImluZm8iOiB7CiAgICAgICAgImNvZGVfaWQiOiAxMjUwLAogICAgICAgICJtc2ciOiAiZXdvZ0lDSnpkV0p0YVhOemFXOXVYM0J2YkdsamVTSTZJSHNLSUNBZ0lDSnpjR1ZqYVdacFl5STZJSHNLSUNBZ0lDQWdJbVJoYjE5dFpXMWlaWEp6SWpvZ2RISjFaU3dLSUNBZ0lDQWdJbUZzYkc5M2JHbHpkQ0k2SUZ0ZExBb2dJQ0FnSUNBaVpHVnVlV3hwYzNRaU9pQmJYUW9nSUNBZ2ZRb2dJSDBzQ2lBZ0ltVjRkR1Z1YzJsdmJpSTZJSHQ5Q24wPSIsCiAgICAgICAgImFkbWluIjogeyAiY29yZV9tb2R1bGUiOiB7fSB9LAogICAgICAgICJmdW5kcyI6IFtdLAogICAgICAgICJsYWJlbCI6ICJkYW8tcHJlLXByb3Bvc2Utc2luZ2xlLXYyLjYuMCIKICAgICAgfQogICAgfQogIH0KfQ==",
"funds": []
}],
"to_disable": []
}
}
And you have the full message to execute on the DAO 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's dao-dao-core
contract, and
the message field containing the above text.
Once you execute this, test the new proposal module by creating a proposal, voting on it, and executing it. If everything seems to work, you can now disable the old single choice proposal module by executing the following message in another proposal:
{
"update_proposal_modules": {
"to_add": [],
"to_disable": ["EXISTING PROPOSAL MODULE ADDRESS"]
}
}
Ideally, wait a while to see the new proposal module in action before disabling the old one.