From 2e4b49b72c9921bc1e72cc86cc528c34af16bce0 Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Tue, 3 Aug 2021 11:01:25 +0100 Subject: [PATCH] Governance: Assert tokens to create governance (#2206) * feat: assert owner has enough tokens to create proposal * chore: test min tokens to create governance * chore: bump version --- Cargo.lock | 2 +- governance/program/Cargo.toml | 2 +- governance/program/src/error.rs | 4 ++ .../process_create_account_governance.rs | 10 ++- .../process_create_mint_governance.rs | 10 ++- .../process_create_program_governance.rs | 10 ++- .../process_create_token_governance.rs | 10 ++- .../program/src/state/token_owner_record.rs | 19 ++++++ .../process_create_account_governance.rs | 66 +++++++++++++++++++ .../program/tests/process_create_proposal.rs | 18 +++-- 10 files changed, 138 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1cdebfb7..8f936fcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3776,7 +3776,7 @@ dependencies = [ [[package]] name = "spl-governance" -version = "1.0.8" +version = "1.0.9" dependencies = [ "arrayref", "assert_matches", diff --git a/governance/program/Cargo.toml b/governance/program/Cargo.toml index 1522a725..7c3185f0 100644 --- a/governance/program/Cargo.toml +++ b/governance/program/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spl-governance" -version = "1.0.8" +version = "1.0.9" description = "Solana Program Library Governance Program" authors = ["Solana Maintainers "] repository = "https://github.com/solana-labs/solana-program-library" diff --git a/governance/program/src/error.rs b/governance/program/src/error.rs index 47c6884e..18ce4d55 100644 --- a/governance/program/src/error.rs +++ b/governance/program/src/error.rs @@ -311,6 +311,10 @@ pub enum GovernanceError { /// Invalid max vote weight supply fraction #[error("Invalid max vote weight supply fraction")] InvalidMaxVoteWeightSupplyFraction, + + /// Owner doesn't have enough governing tokens to create Governance + #[error("Owner doesn't have enough governing tokens to create Governance")] + NotEnoughTokensToCreateGovernance, } impl PrintProgramError for GovernanceError { diff --git a/governance/program/src/processor/process_create_account_governance.rs b/governance/program/src/processor/process_create_account_governance.rs index 36647791..19c0001e 100644 --- a/governance/program/src/processor/process_create_account_governance.rs +++ b/governance/program/src/processor/process_create_account_governance.rs @@ -7,6 +7,8 @@ use crate::{ assert_valid_create_governance_args, get_account_governance_address_seeds, Governance, GovernanceConfig, }, + realm::get_realm_data, + token_owner_record::get_token_owner_record_data_for_realm, }, tools::account::create_and_serialize_account_signed, }; @@ -30,7 +32,7 @@ pub fn process_create_account_governance( let account_governance_info = next_account_info(account_info_iter)?; // 1 let governed_account_info = next_account_info(account_info_iter)?; // 2 - let _token_owner_record_info = next_account_info(account_info_iter)?; // 3 + let token_owner_record_info = next_account_info(account_info_iter)?; // 3 let payer_info = next_account_info(account_info_iter)?; // 4 let system_info = next_account_info(account_info_iter)?; // 5 @@ -40,6 +42,12 @@ pub fn process_create_account_governance( assert_valid_create_governance_args(program_id, &config, realm_info)?; + let realm_data = get_realm_data(program_id, realm_info)?; + let token_owner_record_data = + get_token_owner_record_data_for_realm(program_id, token_owner_record_info, realm_info.key)?; + + token_owner_record_data.assert_can_create_governance(&realm_data)?; + let account_governance_data = Governance { account_type: GovernanceAccountType::AccountGovernance, realm: *realm_info.key, diff --git a/governance/program/src/processor/process_create_mint_governance.rs b/governance/program/src/processor/process_create_mint_governance.rs index 3976e1c1..050b8733 100644 --- a/governance/program/src/processor/process_create_mint_governance.rs +++ b/governance/program/src/processor/process_create_mint_governance.rs @@ -7,6 +7,8 @@ use crate::{ assert_valid_create_governance_args, get_mint_governance_address_seeds, Governance, GovernanceConfig, }, + realm::get_realm_data, + token_owner_record::get_token_owner_record_data_for_realm, }, tools::{ account::create_and_serialize_account_signed, @@ -36,7 +38,7 @@ pub fn process_create_mint_governance( let governed_mint_info = next_account_info(account_info_iter)?; // 2 let governed_mint_authority_info = next_account_info(account_info_iter)?; // 3 - let _token_owner_record_info = next_account_info(account_info_iter)?; // 4 + let token_owner_record_info = next_account_info(account_info_iter)?; // 4 let payer_info = next_account_info(account_info_iter)?; // 5 let spl_token_info = next_account_info(account_info_iter)?; // 6 @@ -48,6 +50,12 @@ pub fn process_create_mint_governance( assert_valid_create_governance_args(program_id, &config, realm_info)?; + let realm_data = get_realm_data(program_id, realm_info)?; + let token_owner_record_data = + get_token_owner_record_data_for_realm(program_id, token_owner_record_info, realm_info.key)?; + + token_owner_record_data.assert_can_create_governance(&realm_data)?; + let mint_governance_data = Governance { account_type: GovernanceAccountType::MintGovernance, realm: *realm_info.key, diff --git a/governance/program/src/processor/process_create_program_governance.rs b/governance/program/src/processor/process_create_program_governance.rs index dd99f389..0bd04d04 100644 --- a/governance/program/src/processor/process_create_program_governance.rs +++ b/governance/program/src/processor/process_create_program_governance.rs @@ -8,6 +8,8 @@ use crate::{ assert_valid_create_governance_args, get_program_governance_address_seeds, GovernanceConfig, }, + realm::get_realm_data, + token_owner_record::get_token_owner_record_data_for_realm, }, tools::{ account::create_and_serialize_account_signed, @@ -40,7 +42,7 @@ pub fn process_create_program_governance( let governed_program_data_info = next_account_info(account_info_iter)?; // 2 let governed_program_upgrade_authority_info = next_account_info(account_info_iter)?; // 3 - let _token_owner_record_info = next_account_info(account_info_iter)?; // 4 + let token_owner_record_info = next_account_info(account_info_iter)?; // 4 let payer_info = next_account_info(account_info_iter)?; // 5 let bpf_upgrade_loader_info = next_account_info(account_info_iter)?; // 6 @@ -52,6 +54,12 @@ pub fn process_create_program_governance( assert_valid_create_governance_args(program_id, &config, realm_info)?; + let realm_data = get_realm_data(program_id, realm_info)?; + let token_owner_record_data = + get_token_owner_record_data_for_realm(program_id, token_owner_record_info, realm_info.key)?; + + token_owner_record_data.assert_can_create_governance(&realm_data)?; + let program_governance_data = Governance { account_type: GovernanceAccountType::ProgramGovernance, realm: *realm_info.key, diff --git a/governance/program/src/processor/process_create_token_governance.rs b/governance/program/src/processor/process_create_token_governance.rs index 7ecdf1b0..50529974 100644 --- a/governance/program/src/processor/process_create_token_governance.rs +++ b/governance/program/src/processor/process_create_token_governance.rs @@ -7,6 +7,8 @@ use crate::{ assert_valid_create_governance_args, get_token_governance_address_seeds, Governance, GovernanceConfig, }, + realm::get_realm_data, + token_owner_record::get_token_owner_record_data_for_realm, }, tools::{ account::create_and_serialize_account_signed, @@ -36,7 +38,7 @@ pub fn process_create_token_governance( let governed_token_info = next_account_info(account_info_iter)?; // 2 let governed_token_owner_info = next_account_info(account_info_iter)?; // 3 - let _token_owner_record_info = next_account_info(account_info_iter)?; // 4 + let token_owner_record_info = next_account_info(account_info_iter)?; // 4 let payer_info = next_account_info(account_info_iter)?; // 5 let spl_token_info = next_account_info(account_info_iter)?; // 6 @@ -48,6 +50,12 @@ pub fn process_create_token_governance( assert_valid_create_governance_args(program_id, &config, realm_info)?; + let realm_data = get_realm_data(program_id, realm_info)?; + let token_owner_record_data = + get_token_owner_record_data_for_realm(program_id, token_owner_record_info, realm_info.key)?; + + token_owner_record_data.assert_can_create_governance(&realm_data)?; + let token_governance_data = Governance { account_type: GovernanceAccountType::TokenGovernance, realm: *realm_info.key, diff --git a/governance/program/src/state/token_owner_record.rs b/governance/program/src/state/token_owner_record.rs index 35f2641f..558ccb5d 100644 --- a/governance/program/src/state/token_owner_record.rs +++ b/governance/program/src/state/token_owner_record.rs @@ -105,6 +105,25 @@ impl TokenOwnerRecord { Ok(()) } + + /// Asserts TokenOwner has enough tokens to be allowed to create governance + pub fn assert_can_create_governance(&self, realm_data: &Realm) -> Result<(), ProgramError> { + let min_tokens_to_create_governance = + if self.governing_token_mint == realm_data.community_mint { + realm_data.config.min_community_tokens_to_create_governance + } else if Some(self.governing_token_mint) == realm_data.config.council_mint { + // For council tokens it's enough to be in possession of any number of tokens + 1 + } else { + return Err(GovernanceError::InvalidGoverningTokenMint.into()); + }; + + if self.governing_token_deposit_amount < min_tokens_to_create_governance { + return Err(GovernanceError::NotEnoughTokensToCreateGovernance.into()); + } + + Ok(()) + } } /// Returns TokenOwnerRecord PDA address diff --git a/governance/program/tests/process_create_account_governance.rs b/governance/program/tests/process_create_account_governance.rs index 163f86b4..f84974cd 100644 --- a/governance/program/tests/process_create_account_governance.rs +++ b/governance/program/tests/process_create_account_governance.rs @@ -130,3 +130,69 @@ async fn test_create_account_governance_with_invalid_config_error() { assert_eq!(err, GovernanceError::InvalidVoteThresholdPercentage.into()); } + +#[tokio::test] +async fn test_create_account_governance_with_not_enough_community_tokens_error() { + // Arrange + let mut governance_test = GovernanceProgramTest::start_new().await; + + let realm_cookie = governance_test.with_realm().await; + let governed_account_cookie = governance_test.with_governed_account().await; + + // Set token deposit amount below the required threshold + let token_amount = 4; + + let token_owner_record_cookie = governance_test + .with_community_token_deposit_amount(&realm_cookie, token_amount) + .await; + + // Act + let err = governance_test + .with_account_governance( + &realm_cookie, + &governed_account_cookie, + &token_owner_record_cookie, + ) + .await + .err() + .unwrap(); + + // Assert + assert_eq!( + err, + GovernanceError::NotEnoughTokensToCreateGovernance.into() + ); +} + +#[tokio::test] +async fn test_create_account_governance_with_not_enough_council_tokens_error() { + // Arrange + let mut governance_test = GovernanceProgramTest::start_new().await; + + let realm_cookie = governance_test.with_realm().await; + let governed_account_cookie = governance_test.with_governed_account().await; + + // Set token deposit amount below the required threshold + let token_amount: u64 = 0; + + let token_owner_record_cookie = governance_test + .with_council_token_deposit_amount(&realm_cookie, token_amount) + .await; + + // Act + let err = governance_test + .with_account_governance( + &realm_cookie, + &governed_account_cookie, + &token_owner_record_cookie, + ) + .await + .err() + .unwrap(); + + // Assert + assert_eq!( + err, + GovernanceError::NotEnoughTokensToCreateGovernance.into() + ); +} diff --git a/governance/program/tests/process_create_proposal.rs b/governance/program/tests/process_create_proposal.rs index 1cb1e259..3ac5dc6b 100644 --- a/governance/program/tests/process_create_proposal.rs +++ b/governance/program/tests/process_create_proposal.rs @@ -202,25 +202,29 @@ async fn test_create_proposal_with_not_enough_community_tokens_error() { let realm_cookie = governance_test.with_realm().await; let governed_account_cookie = governance_test.with_governed_account().await; - // Set token deposit amount below the required threshold - let token_amount = 4; - - let token_owner_record_cookie = governance_test - .with_community_token_deposit_amount(&realm_cookie, token_amount) + let token_owner_record_cookie1 = governance_test + .with_community_token_deposit(&realm_cookie) .await; let mut account_governance_cookie = governance_test .with_account_governance( &realm_cookie, &governed_account_cookie, - &token_owner_record_cookie, + &token_owner_record_cookie1, ) .await .unwrap(); + // Set token deposit amount below the required threshold + let token_amount = 4; + + let token_owner_record_cookie2 = governance_test + .with_community_token_deposit_amount(&realm_cookie, token_amount) + .await; + // Act let err = governance_test - .with_proposal(&token_owner_record_cookie, &mut account_governance_cookie) + .with_proposal(&token_owner_record_cookie2, &mut account_governance_cookie) .await .err() .unwrap();