Skip to content

Verifying DAO DAO v2 Contracts

Rae edited this page Feb 6, 2023 · 3 revisions

If you're planning on doing anything mission critical with a DAO DAO DAO you should consider verifying it is configured correctly. This wiki page covers:

  1. Verifying that a DAO's instantiate message is valid.
  2. Verifying that an already instantiated DAO is using the correct smart contracts.

If you intend to follow this guide to verify a DAO DAO DAO, it may be helpful to familiarize yourself with the DAO DAO contracts design here.

Verifying a v2 DAO instantiate message

There are two steps to verifying that a DAO DAO DAO is being instantiated correctly. First, the instantiate message should be verified to ensure that the DAO's voting configuration is correct. Then, the code IDs in the instantiate message should be verified to ensure that they point to the correct code.

Verifying the DAO configuration

A typical DAO DAO DAO instantiate message looks something like this:

{
  "admin": "juno1jv65s3grqf6v6jl3dp4t6c9t9rk99cd83d88wr",
  "automatically_add_cw20s": false,
  "automatically_add_cw721s": false,
  "description": "Growing and maintaining core web3 infrastructure for Juno Network.",
  "image_url": "https://nftstorage.link/ipfs/bafkreidawbt34hsqio4lfrivviccm4ahyrmltkpgancjmlh7mzubriyate/",
  "name": "Core 1 SubDAO",
  "proposal_modules_instantiate_info": [
    {
      "admin": {
        "core_module": {}
      },
      "code_id": 1694,
      "label": "DAO_Core_1_SubDAO_DaoProposalSingle",
      "msg": "eyJhbGxvd19yZXZvdGluZyI6ZmFsc2UsImNsb3NlX3Byb3Bvc2FsX29uX2V4ZWN1dGlvbl9mYWlsdXJlIjp0cnVlLCJtYXhfdm90aW5nX3BlcmlvZCI6eyJ0aW1lIjo0MzIwMDB9LCJtaW5fdm90aW5nX3BlcmlvZCI6bnVsbCwib25seV9tZW1iZXJzX2V4ZWN1dGUiOnRydWUsInByZV9wcm9wb3NlX2luZm8iOnsibW9kdWxlX21heV9wcm9wb3NlIjp7ImluZm8iOnsiYWRtaW4iOnsiY29yZV9tb2R1bGUiOnt9fSwiY29kZV9pZCI6MTY5MiwibGFiZWwiOiJEQU9fQ29yZSAxIFN1YkRBT19wcmUtcHJvcG9zZS1EYW9Qcm9wb3NhbFNpbmdsZSIsIm1zZyI6ImV5SmtaWEJ2YzJsMFgybHVabThpT201MWJHd3NJbVY0ZEdWdWMybHZiaUk2ZTMwc0ltOXdaVzVmY0hKdmNHOXpZV3hmYzNWaWJXbHpjMmx2YmlJNlptRnNjMlY5In19fSwidGhyZXNob2xkIjp7ImFic29sdXRlX3BlcmNlbnRhZ2UiOnsicGVyY2VudGFnZSI6eyJtYWpvcml0eSI6e319fX19"
    }
  ],
  "voting_module_instantiate_info": {
    "admin": {
      "core_module": {}
    },
    "code_id": 1696,
    "label": "DAO_Core_1_SubDAO_DaoVotingCw4",
    "msg": "eyJjdzRfZ3JvdXBfY29kZV9pZCI6MTY2OCwiaW5pdGlhbF9tZW1iZXJzIjpbeyJhZGRyIjoianVubzE4cXc5eWRwZXdoNDA1dzRsdm11aGxnOWd0YWVwNzl2eTJnbXRyMiIsIndlaWdodCI6MX0seyJhZGRyIjoianVubzFyYTRtbWU2c3I1cjZwcnFoemFuMDNtejAzamV6NnMydHdwbHdtZCIsIndlaWdodCI6MX0seyJhZGRyIjoianVubzF1OTN6NHhscHRsOXVqeDZwcTN5MHc0cGhka2o1bWFwZ2swd3V1aiIsIndlaWdodCI6MX0seyJhZGRyIjoianVubzFzMzN6Y3QyemhoYWY2MHg0YTkwY3BlOXlxdXc5OWpqMHplbjhwdCIsIndlaWdodCI6MX0seyJhZGRyIjoianVubzFlano5MHdzdzUyYXVra2ZmZnJkNHVtZGQwZXQzenZtNzB1cXMwMyIsIndlaWdodCI6MX0seyJhZGRyIjoianVubzE3cHk4Z2ZuZWFhbTY0dnQ5a2FlYzBmc2Vxd3h2a3EwZmxtc21oZyIsIndlaWdodCI6MX0seyJhZGRyIjoianVubzEzMG1kdTlhMGV0bWV1dzUycWZ4azczcG4wZ2E2Z2F3azRrNTM5eCIsIndlaWdodCI6MX1dfQ=="
  }
}

Here is a description of each field above and their values in the above message:

  • admin - This is an optional field which sets an admin for the DAO. The admin has the ability to execute messages on the instantiated DAO. For example, the admin could withdraw the DAO's treasury or upgrade its modules in the event of a security vulnerability. In this case the admin is set to the address of the Juno community pool.
  • automatically_add_cw20s - If this field is set to true cw20 tokens sent to the DAO will be automatically added to the DAOs treasury and displayed in the DAO DAO UI when they are sent to the DAO via a cw20 send message. If you are concerned about potential spam this should be toggled off.
  • automatically_add_cw721s - If this field is set to true cw721 tokens (NFTs) sent to the DAO will be automatically added to the treasury. This should be disabled if you are concerned about potential spam.
  • name / description / image_url - These fields set the name, description, and image URL that will be displayed in the DAO DAO UI to identify it.
  • proposal_modules_instantiate_info - This field contains the list of proposal modules that will be added to the DAO when it is created. Proposal modules handle the creation of, voting on, and execution of DAO proposals. At this time the only proposal module supported by DAO DAO is cw-proposal-single.
    • admin - This sets the contract admin. The admin of a contract can perform a migration of that contract. This particular message sets the admin of the proposal module to the core contract meaning that the DAO will able to migrate this module via governance. Most DAOs should set the admin like this.
    • code_id - This is an identifier which points towards some code on the Juno blockchain. That code will be the proposal module.
    • label - This is a human readable label for the proposal module.
    • msg - This is a base64 encoded string which contains the instantiate message that will be used to create this module.
  • voting_module_instantiate_info - This object and its fields are identical to proposal_module_instantiate_info except that it deals with instantiating the DAO's voting power module. Voting power modules are responsible for determining the voting power of members when they vote on proposals.

To verify that proposal configuration of this DAO we can decode the msg field of the proposal module instantiate info as follows:

echo eyJhbGxvd19yZXZvdGluZyI6ZmFsc2UsImNsb3NlX3Byb3Bvc2FsX29uX2V4ZWN1dGlvbl9mYWlsdXJlIjp0cnVlLCJtYXhfdm90aW5nX3BlcmlvZCI6eyJ0aW1lIjo0MzIwMDB9LCJtaW5fdm90aW5nX3BlcmlvZCI6bnVsbCwib25seV9tZW1iZXJzX2V4ZWN1dGUiOnRydWUsInByZV9wcm9wb3NlX2luZm8iOnsibW9kdWxlX21heV9wcm9wb3NlIjp7ImluZm8iOnsiYWRtaW4iOnsiY29yZV9tb2R1bGUiOnt9fSwiY29kZV9pZCI6MTY5MiwibGFiZWwiOiJEQU9fQ29yZSAxIFN1YkRBT19wcmUtcHJvcG9zZS1EYW9Qcm9wb3NhbFNpbmdsZSIsIm1zZyI6ImV5SmtaWEJ2YzJsMFgybHVabThpT201MWJHd3NJbVY0ZEdWdWMybHZiaUk2ZTMwc0ltOXdaVzVmY0hKdmNHOXpZV3hmYzNWaWJXbHpjMmx2YmlJNlptRnNjMlY5In19fSwidGhyZXNob2xkIjp7ImFic29sdXRlX3BlcmNlbnRhZ2UiOnsicGVyY2VudGFnZSI6eyJtYWpvcml0eSI6e319fX19 | base64 --decode

This yields the JSON message that will be used to instantiate that module. In this case it looks like this:

{
  "allow_revoting": false,
  "close_proposal_on_execution_failure": true,
  "max_voting_period": {
    "time": 432000
  },
  "min_voting_period": null,
  "only_members_execute": true,
  "pre_propose_info": {
    "module_may_propose": {
      "info": {
        "admin": {
          "core_module": {}
        },
        "code_id": 1692,
        "label": "DAO_Core 1 SubDAO_pre-propose-DaoProposalSingle",
        "msg": "eyJkZXBvc2l0X2luZm8iOm51bGwsImV4dGVuc2lvbiI6e30sIm9wZW5fcHJvcG9zYWxfc3VibWlzc2lvbiI6ZmFsc2V9"
      }
    }
  },
  "threshold": {
    "absolute_percentage": {
      "percentage": {
        "majority": {}
      }
    }
  }
}

Here is a description of the fields:

  • allow_revoting - If this is set to true, members will be allowed to change their votes on proposals. If revoting is enabled it will slow down DAO governance as proposals will not be allowed to close early.
  • max_voting_period - This is the amount of time (in seconds) that proposals in the DAO will be open for voting.
  • only_members_execute - If set to true only DAO members will be able to execute passed proposals. Otherwise, any address can execute a passed proposal.
  • threshold - This is the passing threshold for proposals in the DAO. In this case it is set to an absolute count of 5. This means that if five votes are cast in favor of a proposal it will pass. Other types of voting thresholds include absolute_threshold and threshold_quorum. By default DAO DAO DAOs use threshold_quorum though for some DAOs with non token based voting absolute_count may simplify the voting process.
  • pre-propose-info: Contains information to instantiate a pre-propose module.

Let's decode the pre-propose-info message:

echo eyJkZXBvc2l0X2luZm8iOm51bGwsImV4dGVuc2lvbiI6e30sIm9wZW5fcHJvcG9zYWxfc3VibWlzc2lvbiI6ZmFsc2V9 | base64 --decode

This gives us:

{"deposit_info":null,"extension":{},"open_proposal_submission":false}
  • deposit_info: contains information about any deposits required for proposals.
  • open_proposal_submission: is whether members who are not part of the DAO can submit proposals.

One last module to go! The voting module determines who can vote in the DAO. Giving the same treatment as earlier to the voting module msg field:

echo eyJjdzRfZ3JvdXBfY29kZV9pZCI6MTY2OCwiaW5pdGlhbF9tZW1iZXJzIjpbeyJhZGRyIjoianVubzE4cXc5eWRwZXdoNDA1dzRsdm11aGxnOWd0YWVwNzl2eTJnbXRyMiIsIndlaWdodCI6MX0seyJhZGRyIjoianVubzFyYTRtbWU2c3I1cjZwcnFoemFuMDNtejAzamV6NnMydHdwbHdtZCIsIndlaWdodCI6MX0seyJhZGRyIjoianVubzF1OTN6NHhscHRsOXVqeDZwcTN5MHc0cGhka2o1bWFwZ2swd3V1aiIsIndlaWdodCI6MX0seyJhZGRyIjoianVubzFzMzN6Y3QyemhoYWY2MHg0YTkwY3BlOXlxdXc5OWpqMHplbjhwdCIsIndlaWdodCI6MX0seyJhZGRyIjoianVubzFlano5MHdzdzUyYXVra2ZmZnJkNHVtZGQwZXQzenZtNzB1cXMwMyIsIndlaWdodCI6MX0seyJhZGRyIjoianVubzE3cHk4Z2ZuZWFhbTY0dnQ5a2FlYzBmc2Vxd3h2a3EwZmxtc21oZyIsIndlaWdodCI6MX0seyJhZGRyIjoianVubzEzMG1kdTlhMGV0bWV1dzUycWZ4azczcG4wZ2E2Z2F3azRrNTM5eCIsIndlaWdodCI6MX1dfQ== | base64 --decode

We see that it contains the following JSON data:

{
  "cw4_group_code_id": 1668,
  "initial_members": [
    {
      "addr": "juno18qw9ydpewh405w4lvmuhlg9gtaep79vy2gmtr2",
      "weight": 1
    },
    {
      "addr": "juno1ra4mme6sr5r6prqhzan03mz03jez6s2twplwmd",
      "weight": 1
    },
    {
      "addr": "juno1u93z4xlptl9ujx6pq3y0w4phdkj5mapgk0wuuj",
      "weight": 1
    },
    {
      "addr": "juno1s33zct2zhhaf60x4a90cpe9yquw99jj0zen8pt",
      "weight": 1
    },
    {
      "addr": "juno1ejz90wsw52aukkfffrd4umdd0et3zvm70uqs03",
      "weight": 1
    },
    {
      "addr": "juno17py8gfneaam64vt9kaec0fseqwxvkq0flmsmhg",
      "weight": 1
    },
    {
      "addr": "juno130mdu9a0etmeuw52qfxk73pn0ga6gawk4k539x",
      "weight": 1
    }
  ]
}

This is reasonably straightforward. This DAO will have a fixed set of members (there will be no governance token), there will be seven members, and each member will have the same voting power. This message instantiates a cw4_voting voting power module.

Verifying the Code IDs

Our releases page contains a list of all DAO DAO releases as well as Code IDs on Juno mainnet.

Next, we'll want to verify that the code ID fields in the instantiate message are what we expect. In all cases (at the time of writing) the code ID field of the proposal module instantiate message should correspond to cw_proposal_single. For the voting module, if a non-token based DAO is being used the voting module should correspond to cw4_voting. If a token is being used it should correspond to cw20_staked_balance_voting. These contracts can all be found in the dao-contracts repo.

To verify that a code ID matches:

  1. Clone and build the dao-contracts git repository.

    To clone, install git, and then run:

    git clone https://github.com/DA0-DA0/dao-contracts.git
    

    To check out a particular release or tag that contains your Code ID of interest:

    get checkout <version-tag>
    

    To build, install docker, and then run:

    docker run --rm -v "$(pwd)":/code \
     --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
     --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
     cosmwasm/workspace-optimizer:0.12.6
    

    from inside the dao-contracts directory. Your built wasm contracts will be placed in the artifacts subdirectory.

  2. Download the code for the code ID by running:

    junod query wasm code <CODE_ID> check.wasm
    

    This downloads the code being used and places it in a file called check.wasm.

  3. Verify that the shasum of the downloaded code matches the shasum of the program you have built locally. To compute the shasum:

    shasum <WASM_FILE>
    

    For example, to check if a code ID matches the cw-proposal-single contract, run:

    shasum artifacts/cw_proposal_single.wasm
    

    and then run:

    shasum check.wasm
    

    If the output from the commands matches then you have the contract that you want!

Follow these steps for every code ID in the instantiate message to verify that it is creating the DAO that you expect.

Verifying an already instantiated DAO

  1. Write down the address of the DAO.

    This can be found either in the "Addresses" section of the DAO page or in the URL.

  2. Configure junod.

    This will allow you to query the Juno blockchain directly. You can find instructions for getting junod installed here.

  3. Clone and build the dao-contracts git repository.

    To clone, install git, and then run:

    git clone https://github.com/DA0-DA0/dao-contracts.git
    

    To build, install docker, and then run:

    docker run --rm -v "$(pwd)":/code \
     --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
     --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
     cosmwasm/workspace-optimizer:0.12.6
    

    from inside the dao-contracts directory.

Having finished those first three steps, there should be a number of .wasm files in the artifacts/ directory inside dao-contracts. DAO DAO DAOs are constructed out of three contracts:

  1. A core contract: cw_core.wasm.
  2. A proposal contract: cw_proposal_single.wasm.
  3. A voting power contract. If token based voting is being used, this contract will be cw20_staked_balance_voting.wasm. If non-token based voting is being used this contract will be cw4_voting.wasm.

To verify that our DAO is using the contracts from the dao-contracts git repo, for each contract, we will:

  1. Download the contract's code from the Juno blockchain.
  2. Compare the contract's code to the code that we built locally.

If the two programs have the same code it means that the contract is what we expect. If they are different, something may be amis.

To start, we'll collect the addresses of all our contracts. The address from earlier corresponds to the cw-core contract. We can ask that contract for the other addresses running:

junod query wasm contract-state smart <DAO_ADDRESS> '{"dump_state":{}}'

Make sure to replace <DAO_ADDRESS> above with the address you wrote down earlier. Once you run this you ought to get a response that looks something like this:

data:
  admin: juno1jv65s3grqf6v6jl3dp4t6c9t9rk99cd83d88wr
  config:
    automatically_add_cw20s: true
    automatically_add_cw721s: true
    description: A Juno SubDAO in charge of organizing and running Hack Juno.
    image_url: https://cloudflare-ipfs.com/ipfs/bafybeibnuzc52kmcu4c5pxxwkr3vyp34gsrdomlvw3e66w4ltidr2v4oxi
    name: Hack Juno
  pause_info:
    Unpaused: {}
  proposal_modules:
  - juno139tumjnzrsu4td5pk8jz5d4sllcnj7f5ut2ld3ja8q3cd4hudyjsd8uuh4
  version:
    contract: crates.io:cw-core
    version: 0.1.0
  voting_module: juno145cessdvpguc8alqwgf27qhny9ysz44n34h7d54z3uc9w6vcr8qsex42tm

Here, the voting_module field corresponds to the voting power contract we will verify against and the address under proposal_modules corresponds to the cw_proposal_single contract.

To download the code associated with a contract, first determine the code ID that contract was instantiated with by checking the value of the code_id field after running:

junod query wasm contract <CONTRACT_ADDRESS>

Then, download that contract's code by running:

junod query wasm code <CODE_ID> check.wasm

Finally, compare the shasum of the two wasm files. If the shasums are the same, the contract is the one you expect. To compute a shasum, run:

shasum <WASM_FILE>

An example

To crystallize things, lets walk through an example and verify a test Hack Juno DAO with the address juno1g325q3ddaxk943s52z9ejac4envfje98y0pnfw7lpnmxxdwdyw2sum2jgg. This example assumes you have already followed steps 1 through 3 above.

First, we'll query the DAO to get its module's addresses:

$ junod query wasm contract-state smart juno1g325q3ddaxk943s52z9ejac4envfje98y0pnfw7lpnmxxdwdyw2sum2jgg '{"dump_state":{}}'
data:
  admin: juno1jv65s3grqf6v6jl3dp4t6c9t9rk99cd83d88wr
  config:
    automatically_add_cw20s: true
    automatically_add_cw721s: true
    description: A Juno SubDAO in charge of organizing and running Hack Juno.
    image_url: https://cloudflare-ipfs.com/ipfs/bafybeibnuzc52kmcu4c5pxxwkr3vyp34gsrdomlvw3e66w4ltidr2v4oxi
    name: Hack Juno
  pause_info:
    Unpaused: {}
  proposal_modules:
  - juno139tumjnzrsu4td5pk8jz5d4sllcnj7f5ut2ld3ja8q3cd4hudyjsd8uuh4
  version:
    contract: crates.io:cw-core
    version: 0.1.0
  voting_module: juno145cessdvpguc8alqwgf27qhny9ysz44n34h7d54z3uc9w6vcr8qsex42tm

From this we determine that our voting module contract address is juno145cessdvpguc8alqwgf27qhny9ysz44n34h7d54z3uc9w6vcr8qsex42tm and our proposal module contract address is juno139tumjnzrsu4td5pk8jz5d4sllcnj7f5ut2ld3ja8q3cd4hudyjsd8uuh4.

Next, we'll verify the voting module contract against the cw4_voting contract:

$ junod query wasm contract juno145cessdvpguc8alqwgf27qhny9ysz44n34h7d54z3uc9w6vcr8qsex42tm
address: juno145cessdvpguc8alqwgf27qhny9ysz44n34h7d54z3uc9w6vcr8qsex42tm
contract_info:
  admin: juno1g325q3ddaxk943s52z9ejac4envfje98y0pnfw7lpnmxxdwdyw2sum2jgg
  code_id: "429"
  created: null
  creator: juno1g325q3ddaxk943s52z9ejac4envfje98y0pnfw7lpnmxxdwdyw2sum2jgg
  extension: null
  ibc_port_id: ""
  label: DAO_Hack Juno_cw4-voting
$ junod query wasm code 429 check.wasm
Downloading wasm code to check.wasm
$ shasum check.wasm
9a33c618ebd172fcb063a55b9c1e2e2afbde36c1  check.wasm
$ shasum dao-contracts/artifacts/cw4_voting.wasm
9a33c618ebd172fcb063a55b9c1e2e2afbde36c1  dao-contracts/artifacts/cw4_voting.wasm

From the above sequence of commands we can see that the two shasums are the same. That means this DAO is using the correct voting module! To finish this verification you should check the proposal module and the DAO contract itself using the same steps as above.

Verifying voting modules

Before we can be sure that our contracts are valid we also need to verify that our voting module is using the correct underlying contract. For token based DAOs, staking is used to determine voting power, this corresponds to the cw20_stake contract. For non-token based DAOs, the cw4_group contract is used.

cw4_group

To verify a cw4_group contract, first find it's address by running:

junod query wasm contract-state smart <VOTING_ADDRESS> '{"group_contract":{}}'

Then, follow the same docker build steps from above but using the cw-plus git repo. Compare the contract's code to the cw4_group.wasm file.

cw20_stake

To verify a cw20_stake contract, first find it's address by running:

junod query wasm contract-state smart <VOTING_ADDRESS> '{"staking_contract": {}}'

Then, verify the contract against the cw20_stake.wasm file in dao-contracts/artifacts.