The Paladin Governance program facilitates the creation and management of governance proposals. Proposals can be created by any Paladin staker and can contain one or more Solana instructions to gatekeep. Proposals are voted on by stakers and can be executed if they reach a quorum of votes.
A global configuration is used to set certain parameters for governance operations, including the minimum stake support required for a proposal to be accepted or rejected, the voting time period, and more.
The global configuration - the Governance Config - can be set using the
program's InitializeGovernance
instruction and updated using the program's
UpdateGovernance
instruction. Updating an existing governance configuration
can only be done through an accepted governance proposal.
pub struct GovernanceConfig {
/// The cooldown period that begins when a proposal reaches the
/// `proposal_acceptance_threshold` and upon its conclusion will execute
/// the proposal's instruction.
pub cooldown_period_seconds: u64,
/// The minimum amount of effective stake (in 1e9 scaled format) that must
/// vote for the proposal to be considered valid.
pub proposal_minimum_quorum: u32,
/// The minimum required threshold of cast votes (in 1e9 scaled format) that
/// must be `For` for the proposal to pass.
pub proposal_pass_threshold: u32,
/// The Paladin stake config account that this governance config account
/// corresponds to.
pub stake_config_address: Pubkey,
/// The voting period for proposals.
pub voting_period_seconds: u64,
/// The required stake per active proposal for a user.
///
/// Note that if a user has less than this amount of stake, they will not be
/// able to create a proposal.
pub stake_per_proposal: u64,
/// Timestamp for when the cooldown period expires and proposals can be created
pub cooldown_expires: u64,
}
This global configuration dictates the behavior of all proposals created while this configuration is active. If the configuration changes, newly created proposals will use the updated configuration, while existing proposals will continue to use the ruleset that was active when they were created.
This can be observed by considering the account state layout for a proposal, which stores a local copy of the governance config, added upon creation.
pub struct Proposal {
discriminator: [u8; 8],
/// The proposal author.
pub author: Pubkey,
/// Timestamp for when the cooldown period began.
///
/// A `None` value means cooldown has not begun.
pub cooldown_timestamp: Option<NonZeroU64>,
/// Timestamp for when proposal was created.
pub creation_timestamp: UnixTimestamp,
/// The governance config for this proposal.
pub governance_config: GovernanceConfig,
/// Amount of stake against the proposal.
pub stake_against: u64,
/// Amount of stake in favor of the proposal.
pub stake_for: u64,
/// Proposal status
pub status: ProposalStatus,
_padding: [u8; 7],
/// The timestamp when voting began.
pub voting_start_timestamp: Option<NonZeroU64>,
}
The program requires a valid Paladin stake account to create a new proposal.
Once created, a proposal can support a list of instructions that are to be
executed if and when the proposal is accepted. The proposal author can add
(push) and remove instructions using the PushInstruction
and
RemoveInstruction
instructions, respectively.
pub enum PaladinGovernanceInstruction {
/* ... */
/// Insert an instruction into a governance proposal.
///
/// Expects an initialized proposal and proposal transaction account.
///
/// Authority account provided must be the proposal creator.
///
/// Accounts expected by this instruction:
///
/// 0. `[s]` Paladin stake authority account.
/// 1. `[ ]` Proposal account.
/// 2. `[w]` Proposal transaction account.
PushInstruction {
/// The program ID to invoke.
instruction_program_id: Pubkey,
/// The accounts to pass to the program.
instruction_account_metas: Vec<ProposalAccountMeta>,
/// The data to pass to the program.
instruction_data: Vec<u8>,
},
/// Removes an instruction from a governance proposal.
///
/// Authority account provided must be the proposal creator.
///
/// Accounts expected by this instruction:
///
/// 0. `[s]` Paladin stake authority account.
/// 1. `[ ]` Proposal account.
/// 2. `[w]` Proposal transaction account.
RemoveInstruction {
/// The index of the instruction to remove.
instruction_index: u32,
},
/* ... */
}
Instructions are serialized into a vector, stored in an account adjacent to the
proposal account whose address is the PDA derivation of the string literal
"proposal_transaction"
plus the address of the proposal.
struct ProposalInstruction {
/// The program ID to invoke.
pub program_id: Pubkey,
/// The accounts to pass to the program.
pub accounts: Vec<ProposalAccountMeta>,
/// The data to pass to the program.
pub data: Vec<u8>,
/// Whether the instruction has been executed.
pub executed: bool,
}
Whenever the author is ready to commence voting on their proposal, the
instruction BeginVoting
will finalize the proposal, making its contents
and instruction set immutable. This also configures the proposal's stage to
Voting
, which means votes can be cast and tallied on the proposal.
Each given stake account can vote either in favor or against a proposal, but the absence of a vote can also be tallied, if so desired.
enum ProposalVoteElection {
/// Validator voted in favor of the proposal.
For,
/// Validator voted against the proposal.
Against,
}
The Vote
instruction allows a new vote, and expects no prior vote for this
proposal to exist, while SwitchVote
allows a stake account to modify their
vote and expects a prior vote. Votes are stored in a PDA account, whose address
is the derivation of the string literal "proposal_vote"
plus the stake
address and the proposal address. This, of course, means one stake account can
have one vote PDA per proposal.
Once the number of votes for pass proposal_minimum_quorum
, the cooldown is
started. At the end of the quorum, if the share of votes for is above
proposal_pass_threshold
then the proposal is accepted, else it is rejected.
Once a proposal has been accepted, its instructions can be processed. Many instructions, such as a transfer from the governance treasury, require the Governance PDA signer's signature in order to successfully execute. An accepted proposal is the only way to obtain such a signature.
Users can invoke the Paladin Governance program's ProcessIntruction
instruction with the index of the proposal instruction to process. This will
process the desired serialized instruction via CPI, applying the governance PDA
signature.
Note: Proposal instructions must be processed in order, and if the previous instruction has not been executed, attempting to process an instruction will result in an error. In other words, in order to process any instruction, its parent instruction must have been processed successfully. If an instruction cannot be processed successfully, the proposal will have to be re-created and accepted once again in order to retry.