Proposals

Proposals, if and when they are adopted by the community, evolve the dapp that is governed by the SNS or the SNS itself. The proposal lifecycle in SNSs closely resembles the one for NNS proposals. In contrast to the NNS, SNSs have a notion of critical proposals for which slightly different decision rules apply.

Moreover, SNSs distinguish two kinds of proposals: Native proposals that are the same in each SNS and generic or custom proposals that can be defined by each SNS community for their specific needs.

In the following, we first introduce the most important rules for critical an non-critical proposals and then introduce all native proposals and how to define custom proposals.

Decision rules for critical and non-critical proposals

Some proposal types are considered critical. These are DeregisterDappCanisters, TransferSnsTreasuryFunds, and MintSnsTokens. Critical proposal types have more strict rules to ensure they are only passed with broad community consensus.

Voting thresholds

Non-critical proposals can be passed if 3% of the total voting power votes yes and 50% of the exercised voting power votes yes.

Critical proposals can only be passed if 20% of the total voting power votes yes and 67% of the exercised voting power votes yes.

Catch-all following

Voters can follow other neurons on critical proposal types, but each neuron has to make an active decision of following for each type of critical proposals, as the catch-all following “All topics” does not apply to critical proposals. Users who have multiple neurons can actively vote with just one of them and follow this one neuron with all their other neurons.

Voting period

The voting period for critical proposal types is 5-10 days and cannot be changed by the SNS. In contrast, for non-critical proposals the default is 4-8 days and this can be adjusted by each SNS DAO.

Critical proposal have a longer voting period as they require a larger voting participation and it is therefore beneficial to give voters a bit more time to participate.

As in the NNS, for all proposals the wait-for-quiet algorithm ensures that controversial proposals will have a longer voting period (up to 10 days for critical proposals) while proposals where everyone agrees on have a shorter voting period (5 days for critical proposals).

Native proposals

An SNS comes with built-in proposals called native proposals. All proposals used to manage an SNS are executed on the SNS governance canister so it helps to have for reference the interface for the governance canister.

Motion

A motion proposal is the only kind of proposal that does not have any immediate effect, i.e., it does not trigger the execution of a method as other proposals do. For example, it can be used for opinion polls before even starting certain features.

UpgradeSnsToNextVersion

Upgrades the suite of SNS canisters to the next available version. All approved SNS canister versions are stored on the NNS canister SNS-W. New SNS canister wasm codes are approved by NNS proposals and then added to SNS-W. Each SNS community can then simply decide if and when they want to upgrade their SNS instance to the next SNS version that is available on SNS-W.

To do so, they can use an UpgradeSnsToNextVersion proposal. If this proposal is adopted, it will trigger a call to SNS root that will ask SNS-W which new wasm version is available and then upgrade to this new version.

RegisterDappCanisters

An SNS controls a set of dapp canisters. An SNS community can decide that new dapps should be added to the SNS' control.
The proposal RegisterDappCanisters allows the SNS to accept the control of a set of new dapp canisters. The new canisters that should be registered are identified by their canister ID and it is allowed to register a list of canisters (not just a single one).

DeregisterDappCanisters

The proposal DeregisterDappCanisters is the counterpart of RegisterDappCanisters. If an SNS community decides that they would like to give up the control of a given dapp canister, they can use this proposal to do so. To this end, the proposal defines the canister ID of the dapp canister to be deregistered and a principal to whom the canister will be handed over to (i.e., this principal will be set as the new controller of the specified dapp canister).

This is a critical proposal type, which means it is subject to higher voting thresholds before it is accepted.

TransferSnsTreasuryFunds

The SNS DAO has control over a treasury from which funds can be sent to other accounts by TransferSnsTreasuryFunds proposals. This is a critical proposal type, which means it is subject to higher voting thresholds before it is accepted.

Maximum 7-day amount total

The rate at which funds can be transferred from the treasury is capped. The cap varies based on the
value of the tokens in the treasury in XDR, denoted as T. For the purposes of capping, T can fall within three ranges:

Size Range of T (XDR)
Small 0 ≤ T ≤ 100_000
Medium 100_000 < T ≤ 1_200_000
Large 1_200_000 < T️ 

The total amount that can be transferred from the treasury in a 7 day period is at most L(T), where
L(T) defines the rules that depend on the amount T in the treasury and is defined in the following
table:

Treasury size L(T) Fraction of T
Small T 100%
Medium 0.25 * T 25%
Large 300_000 XDR 0% - 25%

ICP and SNS tokens are considered separately.

For example, if the treasury contains 75_000 XDR worth of SNS tokens, then this amount is considered "small". In this case up to 75_000 XDR worth of SNS tokens can be transferred from the treasury.

To assess the tokens in the treasury, external price information is fetched at the time of proposal
submission. The price of ICP is taken from the cycles minting canister's get_average_icp_xdr_conversion_rate method. The price of the SNS token in ICP is taken from the swap canister's get_derived_state method.

UpgradeSnsControlledCanister

The proposal UpgradeSnsControlledCanister is to upgrade a dapp canister that is controlled by the SNS DAO to a new wasm module.

ManageSnsMetadata

Each SNS has metadata that defines the SNS project and includes, e.g., a URL under which the main dapp canister can be found, a logo, a name, and a description summarizing the purpose of the project. This metadata can be updated at any time, for example if there is a re-branding for the associated project. To do so, the SNS DAO can use the ManageSnsMetadata proposal.

MintSnsTokens

Each SNS can have SNS tokens in its treasury, but it also has the ability to mint new SNS tokens to a particular user. This is a critical proposal type, which means it is subject to higher voting thresholds before it is accepted.

ManageLedgerParameters

The proposal ManageLedgerParameters can be used to update some of the SNS ledger canister's parameters. The ledger parameters that can be changed are the transfer fee, the token symbol, the token name, and the token logo. Later, additional parameters might be added. Fields where a value is set to None will remain unchanged.

ManageDappCanisterSettings

This proposal allows to upgrade the settings of one or more SNS DAO-controlled dapp canister.
The canisters whose settings should be updates are specified in canister_ids and the proposal allows updating the freezing threshold, the reserved cycles limit, the log visibility (which can be visible to just controllers or public), the memory allocation, and the compute allocation.

Generic proposals

SNS DAOs can also use proposals for functionality specific to their dapp. Generic proposals, also called generic functions or generic nervous system functions, allow a flexible way for SNS communities to define such functionality.

Some examples:

  • A dapp may have lots of canisters to maintain. For example, there may be one canister for each user, in which case they may want to apply upgrades through an orchestrator canister. For this workflow, they would have to tell this orchestrator what the user-canisters should be upgraded to and when to trigger this upgrade. In a DAO-governed dapp, this should happen via proposal.
  • Many dapps have an asset canister. Updating the assets cannot be done via a normal canister upgrade as the content is larger than a proposal can be. Therefore you need a custom way to update the assets.
  • Developers might want the DAO to be the only entity that can elect moderators, call certain methods, make certain payments etc…

For all these cases, SNS DAOs can use generic proposals. Fundamentally, a generic proposal is just a call to a method on a canister with a certain argument. This means that one can do anything with a generic proposal as long as one can tell the SNS governance canister which method to call.

Defining a generic proposal

A generic proposal is defined by two parts:

  1. A target method and canister (called target_method_name and target_canister_id in the code): This is the method that will be called if this generic proposal is adopted. A community can implement any behavior in a proposal by writing a target method on a canister, then registering that target method in a generic proposal.
  2. A validator method and canister (called validator_method_name and validator_canister_id in the code): Since the governance canister is not aware of what a generic proposal does or in which context it will be applied, it cannot validate the proposal’s payload. Therefore, to check whether a proposal’s payload is valid at proposal submission time, and to render a description of the effect of the proposal to the voters, the SNS community must implement this validation in a separate method (this can be on the same canister as the target method or on a different one). This method is then called whenever such a generic proposal is submitted. If the validator method fails, the proposal will not put to vote in the SNS.

The overall flow is then as follows. When a generic proposal is submitted to SNS governance, SNS governance calls the validator method on the validator canister to see if the payload makes sense. If this is the case, the proposal is created and can be voted on. If the proposal is adopted, then the SNS governance canister will execute the proposal by calling the target method on the target canister.

Security considerations when designing generic proposals

There are a few important, security-critical considerations to make when adding a generic proposal. A few recommendations are:

  • The canisters where the target and validator methods are defined should be controlled by the SNS DAO. Otherwise, such a method could change the behavior or not be available without the SNS’s control. If you need to call another method, consider the next point.
  • The target and validator methods, but most importantly the target method, should check that only the SNS governance canister can be the caller of the method. Otherwise, it would not be enforced that the actions defined by the generic proposals can only be triggered by an SNS DAO decision.
  • Make sure that the target and validator methods always return an answer. If this is not the case, there is a risk that the SNS governance canister has some open call contexts, which in turn means that it cannot be stopped and therefore cannot be upgraded. This is very risky, e.g., if an urgent upgrade of governance is needed. Therefore it is recommended to only call trusted code.
  • Validate everything that your code relies on again during the execution time. Even though one method is validator, its main purpose is to disregard proposal contents that are obviously wrong. However, due to the fact that a proposal is voted on for multiple days, any validation that you did when the proposal was submitted might have become incorrect by the time the proposal is executed. Therefore it is of utmost importance to repeat any validation in the target method, which is important for the validation of the proposal.
  • Avoid asynchronous inter-canister calls in the validator and target method to minimize the risk for re-entrancy bugs. During the execution of inter-canister calls, other execution can happen (thus interleaving with your method) and change the state of the system. The easiest way to avoid this risk is to avoid inter-canister calls.
  • If inter-canister calls cannot be avoided, try to limit them to the last operation of your validator and target methods. A prominent source of bugs with inter-canister calls is to check a condition, then apply an inter-canister call, and then execute code that relies on this condition. This is called TOCTOU-bug: the status of the system has changed between the time of check and time of use of a condition. One way to avoid that a checking and using of a condition are separated by an inter-canister call is to defer all inter-canister calls to the very end of the method.
  • If the above is also not possible, implement a lock to avoid re-entrancy bugs. If the above two recommendations cannot be applied, implement a lock to ensures that no method that would change a relevant condition can be executing during the validator and target method.

See more security best practices.

Adding/removing generic proposals

Generic proposal types first have to be registered with the SNS governance system. The SNS DAO needs to approve that the proposed type should be supported going forward. As generic proposals have security implications, it is important to have this explicit approval.

Generic proposals can be removed again from SNS governance.

To use a generic proposal, i.e., submit such a proposal, one uses the execute generic nervous system function proposal type and specifies which of the registered generic proposals should be used.

AddGenericNervousSystemFunction

Adds a generic function as a generic proposal to the SNS governance system. Proposers must select an id to be used to identify this generic proposal, and this id is then used to follow other neurons on this proposal. Ids 0-999 are reserved for native proposal types that may be added in the future, all other ids are valid. The proposal specifies the validator and target functions, specified by validator_canister_id and validator_method_name, and target_canister_id and target_method_name, respectively.

ExecuteGenericNervousSystemFunction

Executes a generic function as a generic proposal to the SNS governance system. The proposal identifies the previously added generic proposal by its id and, in addition, defines a payload. Upon submission of such a proposal, the defined validator method is called, which checks that the given payload is valid for this kind of proposal. If this validation is successful, the proposal will be created. Later, once the proposal is adopted, the SNS governance canister will call the target method on the specified canister.

RemoveGenericNervousSystemFunction

Removes a generic function as a generic proposal from the SNS governance system.