A Proposal is a request to execute code as a one-time component according to the governance rules of the organization. The Proposal can be anything you like: attach/remove a microservice; attach/remove components; manage TreasuryManager funds; etc.

A Proposal follows this path:

  1. Any address allowed for by the set governance rules can propose a Proposal.

  2. The Proposal is created, and its references are saved in the Proposal Manager.

  3. The Proposal can be voted on by holders of the token(s) supported by the Proposal Manager.

  4. Voting methods and criteria for passing a Proposal are defined by the governance rules.

  5. The Proposal ultimately terminates. If it passes, its code will be executed as a temporary component, and immediately jettisoned afterwards.

Create a Proposal

A Proposal is represented by a ProposalCodes struct composed as follow:

struct ProposalCodes {
    ProposalCode[] codes;
    bool alsoTerminate;

ProposalCode is a struct composed as follows:

  • address location -> if the Proposal smart contract is already deployed, this represents its address.

  • bytes bytecode -> if the smart contract is not deployed, you can pass here the bytecode that will be deployed as the smart contract.

alsoTerminate is a parameter indicating whether the Proposal should be created and terminated in the same transaction (true) or not (false).

alsoTerminate as true can be used only if the Proposal Manager has set canTerminateAddresses and validatorsAddresses host only or if canTerminateAddresses is not present (and therefore the proposal can always terminate) and validatorsAddresses is not present (the proposal is always valid). In all other cases alsoTerminate has to be passed as false.

So, a ProposalCodes--i.e, a Proposal--may consist of one or more ProposalCode structs. It is not mandatory to have more than one, but there must be at least one.

The batchCreate function--i.e, the function used to create more than one Proposal-- takes, as input, an array of ProposalCodes parameters; multiple Proposals can be created at once in a single transaction.

The code of a ProposalCodes, composed of one or more proposalCode structs, will be executed if the Proposal passes.

In particular, all the smart contracts (represented by their location addresses) or bytecodes to deploy as smart contracts are executed following the order they were passed in the array as one-time components.

For example, a ProposalCodes containing the three contracts [proposalCode2, proposalCode1 and proposalCode3] is a single Proposal, and will be executed in that arrayed order (2 -> 1 -> 3).

A proposalCode can be managed in two different ways:

  1. If, in the proposalCode, the address location is populated, the Proposal will execute the code of that passed smart contract. In this case, that contract must be deployed before creating the Proposal, so two transactions are required: the first to deploy the contract, the second to create the Proposal via the create function.

  2. If, in the proposalCode, the bytes bytecode is populated, the Proposal Manager itself will deploy the contract (using the bytecode) and then execute it. In this case, the contract that contains the code to execute has not yet been deployed, but only one transaction is needed, because the code of the contract and the Proposal creation are made in a single transaction.

If both the location and bytecode are populated, the Proposal will both take the deployed contract address (location) and also deploy the contract (bytecode).

When executing a Proposal, if the Proposal passed, the Proposal Manager will call each contract in the ProposalCodes array.

Why Run Multiple Contracts In Sequence In a Proposal, and Not Just One?

It can be useful in certain situations.

For example, imagine root layer Org X. Org X clones Component A from a Factory to link it to itself. However, the code of Component A is customized for use by Org Y and in its current state doesn't comport with Org X. So Org X needs to clean up its State Manager before it can link Component A.

Obviously the contract that will carry out this cleanup must be executed before the link. Therefore, in the ProposalCodes, that contract will be the first address, and the second will be Component A's contract address.

If Org X needs to also execute a third contract to efficiently use Component A, that will be the third address passed in the ProposalCodes.

Keep in mind that you are not obliged by the ProposalManager syntax to insert multiple contracts.

For every created Proposal (and thus for each ProposalCodes), a proposal id is generated. This is a bytes32 key that identifies that Proposal, and is known only to the Proposal Manager.

Every ProposalCodes passed in a single batchCreate function becomes a Proposal represented by a Proposal struct.

Proposal struct

Once a Proposal--represented by a ProposalCodes--is created through the batchCreate function, it is represented by a Proposal struct.

The Proposal Manager uses the struct to manage the Proposal in all of its main operations, such as voting on it, terminating it, executing it; and so on.

The Proposal struct is composed of the following parameters:

  • address proposer -> this is the address of the user/project/wallet that created the Proposal.

  • address[] codeSequence -> this represents the addresses of the contract(s) that will be executed if the Proposal passes. There can be one or more contracts, and they will be executed in the same sequence they are positioned in the array.

  • uint256 creationBlock -> this is the Proposal creation block.

  • uint256 accept -> this is the accept vote tally, weighted according to the defined _weights rules.

  • uint256 refuse -> this is the refuse vote tally, weighted according to the defined _weights rules.

  • triggeringRules -> this is the address of the contract representing the defined triggeringRules rules.

  • address[] canTerminateAddress -> these are the addresses of the contract(s) representing the defined canTerminate rules.

  • address[] validatorsAddresses -> these are the addresses of the contract(s) representing the defined validators rules.

  • bool validationPassed -> this represents whether the Proposal passed (true) or not (false) according to the governance rules.

  • uint256 terminationBlock -> this is the Proposal termination block; i.e. the block when the terminate function is correctly executed.

  • bytes votingTokens -> this represents, encoded as bytes parameter, the collections, objectIds and weights set to vote the Proposal. In the Root Proposal Manager, collections, objectIds and weights are those of the local configuration set at the time of Proposal Manager initialization or changed at a later time via setConfiguration function.

Regarding those final two parameters, validationPassed and terminationBlock, there can be three possible outcomes after the terminate function is called:

  1. validationPassed false and terminationBlock populated -> the Proposal is refused (number of refuse votes > number of accept votes for example); terminationBlock represents the number of the block when the terminate function is called.

  2. validationPassed true and terminationBlock populated -> the Proposal is accepted (number of accept votes > number of refuse votes, for example); the terminationBlock represents the number of the block when the terminate function is called.

  3. validationPassed true and terminationBlock not populated -> the Proposal is accepted (number of accept votes > number of refuse votes, for example) but the terminationBlock is empty, because the Proposal code cannot be executed. This can happen if for example, the Proposal code does not compile or if it runs out of gas. Remember that in the proposal there can be written anything at code level(!).

Last updated