Execution Proposals for an Enterprise DAO

Kiruse
7 min readApr 15, 2023

--

Enterprise is a collection of Smart Contracts on the Terra blockchain, developed by Terra’s core developer team Terraform Labs (TFL). Enterprise allows you to easily create, organize & maintain your DAO, choosing one of 3 forms of decentralized governance at the time of writing: NFT DAO, Token DAO, or Multisig DAO.

A critical part of decentralized governance are proposals: a conceptualized call-to-action put up for vote amongst the DAO’s shareholders. But even more important are Execution Proposals, a special form of proposal which executes Smart Contracts in the name of the DAO.

Types of Proposals

A proposal is an initiative for change. One of the most common kinds of proposals is the Text Proposal, which simply consists of a text and is intended to indicate community sentiment, also called Signalling Proposal. Another common kind is the Treasury Spend Proposal, which allows a third party to request funding from the DAO’s Treasury.

However, transferring assets from the Treasury into the possession of a third party does not come without risks: the collective effectively places faith in this third party to handle the funds properly and to not simply flee.

Enter Execution Proposals, a special kind of proposal which contains encoded instructions for the DAO’s Treasury to execute a wide range of actions. For example, instead of entrusting the third party with funds, the DAO could directly stake Luna with the third party’s validator, or provide liquidity to an Astroport LP related to the third party’s project. The benefit is clear: the assets stay under control of the DAO, yet still benefit the third party one way or another.

Technical Details of Execution Proposals

However, Execution Proposals (aka Execute Messages Proposals) require some technical knowledge. TFL provides plenty of relevant information in their documentation which I strongly suggest you read. Here, I will only focus on Execute Messages Proposals & inform you of various relevant concepts.

Three main concepts are important for Execution Messages: (A) They directly correspond to Cosmos SDK Modules, (B) messages are formatted in JSON, and (C) every msg field is binary data encoded in Base64.

JSON (B) is a well-documented human-readable text format very common on the web. As such, I suggest you read this article by W3Schools. As for (C), msg fields are typically just another JSON data structure encoded in Base64. Once you’ve written your JSON, simply encode it with base64encode.org. If you’re inspecting another prop’s execution message, you may use base64decode.org to get the original message back out.

(A) is, unfortunately, drastically more complicated. Many different modules are built into the Cosmos SDK, such as Bank or Staking. Bank handles accounts, balances, and transfers of the blockchain’s native coin (on Terra, this is Luna) for both user wallets and smart contracts. Staking handles staking with the blockchain’s validators only — it has nothing to do with staking on, say, Enterprise. Looking at the list of modules, there’s a lot to learn here. Worse yet, often Cosmos SDK-based blockchains have custom modules. Terra now has Alliance, Secret is different altogether, Evmos has an EVM engine module, and Injective has various custom financial modules.

Fortunately, you’ll typically only need the WASM module — which on the linked page is listed separately. This is because the WASM module is the most comprehensive one enabling smart contracts using the CosmWasm Runtime.

WASM Messages & Formatting

TFL already documented various relevant messages, so I will only elaborate a bit further here on information they did not share (at the time of writing). Let’s examine a short example:

{
"wasm": {
"execute": {
"contract_addr": "terra1x7rf4nquswmmrjtzlxg6dk3d70ef69prth2q7vk3v9vdprepdh0sjxrvl7",
"funds": [],
"msg": "eyJzZW5kX25mdCI6IHsiY29udHJhY3QiOiAidGVycmExMjkwZDRxNmF2NDhkM3I4eTk5czRkNWZxcjVrNzVobjdsN3l0ejI3cHUzZnF2ZzNmNGpoc3FyOXZqdSIsIm1zZyI6ICJleUp6ZEdGclpTSTZlMzE5IiwidG9rZW5faWQiOiAiMzM2In19"
}
}
}

First, the JSON starts with a “variant” — a simple object with only a single identifying property. The first variant always tells the validator which Cosmos SDK Module to target — here it’s “wasm”. The second variant tells the SDK Module what we want to do —here it’s “execute”.

You can find the various actions and their required data in the documentation of a project. This goes for both SDK modules as well as third party smart contracts, like those of Enterprise. For the standard Cosmos SDK modules (not just wasm), I prefer the cosmwasm-std documentation on docs.rs — though it may not be very intuitive to read.

From the above documentation, we can learn that execute messages require 3 fields: contract_addr, msg, and funds.

contract_addr should be fairly self-explanatory: this is the on-chain address of the smart contract you wish to contact.

msg then is a smart contract-specific JSON message encoded in base64 as mentioned above; for this you will need to consult the smart contract’s documentation to learn what is available and how to format the message. There is no single solution I can provide you here.

Finally, funds is a collection of native coins (not CW20 tokens!) you’d like to send along with a smart contract call. On Terra, this is only “uluna”. However, funds are completely optional, and when not needed simply pass an empty array like in the example. This can happen for example when you are adding 2 CW20 tokens to an LP. Note that this does not include gas fees.

When you need to send along Luna with your call, it’ll look like funds: [{ denom: "uluna", amount: "1000000" }]. The “u” before Luna is the metric system’s alternative symbol for “micro”, meaning “a millionth,” so in this brief example we are sending 1 Luna with our execution.

In the example above, msg decodes to the following JSON:

{
"send_nft": {
"contract": "terra1290d4q6av48d3r8y99s4d5fqr5k75hn7l7ytz27pu3fqvg3f4jhsqr9vju",
"msg": "eyJzdGFrZSI6e319",
"token_id": "336"
}
}

It instructs an NFT contract to send an NFT to a smart contract with a sub-message. send differs from transfer in that the recipient (contract) can only be a smart contract — because it is expected to handle the given msg, which user wallets simply have no capacity for. If the recipient cannot handle the message, the transaction will fail and behave as if the NFT had never left your wallet.

The msg contained within the send_nft msg is simply {"stake":{}}. The contract receiving this NFT is the Skeleton Punks NFT DAO, and the stake sub-message instructs the DAO smart contract to officially stake our NFT with the DAO. We now have a DAO with stake in another DAO — DAOception!

This can also be accomplished with CW20 Token DAOs, e.g. if the Skeleton Punks DAO decided to stake $ROAR with the Lion DAO. CW721 (NFTs) and CW20 (FTs) are rather similar in their messages.

Conclusion

As this blog post likely has shown you, Execution Proposals are rather technical and require a considerable sum of knowledge and research. There is no simple solution to how you should format your message, and you are very dependent on proper documentation. Fortunately, most projects in our industry are sufficiently well documented.

Keep an eye out for more news from the Lion DAO! Soon, we will explore Warp protocol and implement various DeFi strategies with Treasury funds 🔥

Appendix

  • When a CW721’s send_nft or a CW20’s send execution does not know how to handle your custom msg field, it will most likely simply fail and revert your transaction. But you shouldn’t necessarily rely on this behavior and prefer to consult a Rust programmer to read the CW721’s or CW20’s smart contract code. However, even that is not an absolute guarantee, as the on-chain contract code may not be the same as the alleged source code.
  • You can find the standard interface declaration for CW20 in the cw-plus GitHub repository, or for CW721 in the cw-nfts GitHub repository.
  • You can transfer funds to a wallet or smart contract, but send only to a smart contract. You will most likely never want to transfer from the DAO Treasury.
  • The last field in a JSON object must not have a trailing comma. This is considered an error, and your execution may fail.
  • Even though JSON specifies numbers, due to programming language quirks, numbers in Cosmos messages are always strings — don’t forget your quotes. This is because numbers in Cosmos can be significantly larger than JSON numbers (up to 512 bits vs 53 bits).

A Note on Message Building

Smart Contracts in Cosmos are currently predominantly written in Rust. Thus, documentation swill typically show you Rust code. However, this code does not translate directly 1-to-1 to JSON. Consider the following Rust snippet:

Here, we have two Rust structs telling you the layout of their corresponding messages. In this case, it is to query some information about a specific DAO member.

In this example, you see two different types of “casing”: PascalCase and snake_case. Both types of cases are a way to encode natural language words into programming language identifiers. With PascalCase, you simply string all words together and capitalize the first letter of each word. With snake_case, you lower-case every word and string them together with an underscore (“_”).

However, when writing your JSON message, everything is snake_case, so you will need to convert PascalCase accordingly. Further, you may need to split off some words. In the above example, the correct message is:

{
"member_info": {
"member_address": "terra17c6ts8grcfrgquhj3haclg44le8s7qkx6l2yx33acguxhpf000xqhnl3je"
}
}

Which would query the member status of the Lion DAO in an Enterprise DAO.

--

--

Kiruse

I'm a degen writing educational stuff for my fellow degens.