It is our belief that most of the power of the DAO lies in its autonomy. Pilots can vote on proposals that perform actions, and when those proposals pass, they can trigger the actions immediately and trustlessly, on behalf of the DAO. In order for the DAO to understand a proposal's associated actions, they must be encoded as transaction data, and this data must be included at the time of the proposal's creation. This article explains the process of encoding and submitting transaction data for proposals.
Selecting Transaction Data
The majority of transactions you'll want to attach will either send tokens directly to an address, or will make some sort of call to a contract. As it turns out, they're both very similar. At the time of writing, all transactions that support attachment have the following shape:
- target - This is the address of the wallet to which you're sending ether, or the address of the contract that the transaction will call.
- value - This is the number of wei to send to the target during the transaction. On Polygon, this is MATIC wei, and on Ethereum, it is ETH wei. On both networks, a full token (MATIC/ETH) consists of wei.
- calldata - This data is included so that the target knows how to process your transactions. For contract calls, calldata selects the function that you are calling and the inputs that you are passing to it. For direct token transfers, calldata may be either , or a UTF-8 encoded note to the recipient.
We'll focus on encoding calldata for contract calls, since it is effectively optional in direct transfers. For contract calls, calldata consists of the following items, packed together through Solidity's non-standard packed encoding:
- signature - This is a 4-byte number that identifies the function and overload that your transaction will call. You can find a function's signature by copying its canonical signature (formatted name and parameters), hashing it with , and then taking the top 4 bytes of the hash.
- parameters - This consists of all of the inputs that your transaction will pass to the contract function, encoded under Solidity's standard ABI encoding.
As an example, let's encode a transfer of 5 WETH from the timelock to Vitalik.
First, we look up the WETH contract
on Polygon and take a look at its
transfer function. We can see that its canonical signature is
We can then compute its signature:
To compute our parameters, we first find their native representations. This means Vitalik's hex address for the recipient and 5 WETH's worth of wei for the amount.
- Vitalik's address -
- 5 WETH in wei -
We can then use Solidity's ABI encoding to put them together:
Putting them together, with the signature at the beginning, we get:
Including Transaction Data in Proposals
Once you have selected and encoded your transaction data, including it within a proposal is fairly straightforward.
To create any proposal, with or without transaction data, you call the
propose function on the governor contract:
address memory targets,
uint256 memory values,
bytes memory calldatas,
string memory description
For proposals without transactions, you pass empty arrays for all parameters except for
description, which you would
fill with text describing your proposal (ideally, in line with the best practices we've outlined).
The only change we make to include transactions is filling the
calldatas arrays with the targets,
values, and calldatas for each of our transactions, in order. In the case that one transaction does not need a value or
has no calldata, we do not omit its value or calldata from the list. Instead, we use .
Let's revisit our example of sending Vitalik 5 WETH. Using our calldata from earlier, we can create our proposal by calling
// The address of the WETH contract on Polygon
// We're requesting a transfer of an ERC20 token, not sending MATIC directly, so we pass 0
// Our calldata from the previous section
// This is the text portion of the proposal that gets shown to voters
"Vitalik needs 5 WETH ASAP"