Skip to content

paladin-bladesmith/governance-program

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

51 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Paladin Governance Program

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.

Architecture

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>,
}

Proposal Creation

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.

Voting

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.

Cooldown Period

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.

Processing Accepted Proposals

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.

About

Paladin Governance program

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •