diff --git a/governance-lib/Cargo.toml b/governance-lib/Cargo.toml index de52898..430c00d 100644 --- a/governance-lib/Cargo.toml +++ b/governance-lib/Cargo.toml @@ -8,10 +8,12 @@ edition = "2021" [dependencies] solana-sdk = "1.9" solana-client = "1.9" +solana-account-decoder = "1.9" borsh = "0.9.1" goblin = "0.4.2" log = "0.4.11" thiserror = "1.0" +bs58 = "0.4.0" spl-token = { version = "3.3", path = "../solana-program-library/token/program", features = [ "no-entrypoint" ] } spl-associated-token-account = "1" spl-governance = { version = "2.2", path = "../solana-program-library/governance/program" } diff --git a/governance-lib/src/governance.rs b/governance-lib/src/governance.rs index e201399..fc197d4 100644 --- a/governance-lib/src/governance.rs +++ b/governance-lib/src/governance.rs @@ -63,7 +63,7 @@ impl<'a> Governance<'a> { &token_owner.token_owner_record_address, &self.realm.client.payer.pubkey(), &creator, // realm_authority OR token_owner authority - token_owner.voter_weight_record_address, + token_owner.get_voter_weight_record_address(), gov_config, ) } @@ -90,7 +90,7 @@ impl<'a> Governance<'a> { &token_owner.token_owner_record_address, &self.realm.client.payer.pubkey(), &create_authority.pubkey(), // realm_authority OR token_owner authority - token_owner.voter_weight_record_address, + token_owner.get_voter_weight_record_address(), gov_config, transfer_mint_authorities, ), diff --git a/governance-lib/src/proposal.rs b/governance-lib/src/proposal.rs index 5075e03..ab20157 100644 --- a/governance-lib/src/proposal.rs +++ b/governance-lib/src/proposal.rs @@ -20,6 +20,7 @@ use { instruction::{ cast_vote, sign_off_proposal, + finalize_vote, insert_transaction, remove_transaction, execute_transaction, @@ -65,7 +66,7 @@ impl<'a> Proposal<'a> { &token_owner.token_owner_record_address, &create_authority, &self.governance.realm.client.payer.pubkey(), - token_owner.voter_weight_record_address, + token_owner.get_voter_weight_record_address(), &self.governance.realm.realm_address, proposal_name.to_string(), @@ -106,8 +107,6 @@ impl<'a> Proposal<'a> { &self.get_proposal_transaction_address(option_index, index)) } -// pub fn finalize_vote(&self, sign_authority: &Keypair, token_owner: &TokenOwner) -> ClientResult; - pub fn insert_transaction_instruction(&self, authority: &Pubkey, token_owner: &TokenOwner, option_index: u8, index: u16, hold_up_time: u32, instructions: Vec) -> Instruction { insert_transaction( &self.governance.realm.program_id, @@ -208,6 +207,22 @@ impl<'a> Proposal<'a> { ) } + pub fn finalize_vote(&self, sign_authority: &Keypair, token_owner: &TokenOwner) -> ClientResult { + self.governance.realm.client.send_and_confirm_transaction_with_payer_only( + &[ + finalize_vote( + &self.governance.realm.program_id, + &self.governance.realm.realm_address, + &self.governance.governance_address, + &self.proposal_address, + &token_owner.token_owner_record_address, + &self.governance.realm.community_mint, + self.governance.realm.settings().max_voter_weight_record_address, + ), + ], + ) + } + pub fn sign_off_proposal(&self, sign_authority: &Keypair, token_owner: &TokenOwner) -> ClientResult { self.governance.realm.client.send_and_confirm_transaction( &[ @@ -251,7 +266,7 @@ impl<'a> Proposal<'a> { &voter_authority.pubkey(), &self.governance.realm.community_mint, &payer.pubkey(), - voter.voter_weight_record_address, + voter.get_voter_weight_record_address(), self.governance.realm.settings().max_voter_weight_record_address, vote, ), diff --git a/governance-lib/src/realm.rs b/governance-lib/src/realm.rs index ac9b32a..5cef31e 100644 --- a/governance-lib/src/realm.rs +++ b/governance-lib/src/realm.rs @@ -1,9 +1,11 @@ use { crate::{ + errors::GovernanceLibError, client::{Client, ClientResult}, token_owner::TokenOwner, governance::Governance, }, + borsh::{BorshSchema,BorshSerialize}, solana_sdk::{ pubkey::Pubkey, instruction::Instruction, @@ -15,7 +17,6 @@ use { enums::MintMaxVoteWeightSource, realm::{RealmV2, SetRealmAuthorityAction, get_realm_address}, realm_config::{RealmConfigAccount, get_realm_config_address}, - token_owner_record::get_token_owner_record_address, governance::get_governance_address, }, instruction::{ @@ -24,12 +25,23 @@ use { create_realm, }, }, + spl_governance_addin_api::max_voter_weight::MaxVoterWeightRecord, solana_client::{ client_error::ClientError, }, std::cell::{RefCell, Ref, RefMut}, }; +use solana_account_decoder::{UiDataSliceConfig, UiAccountEncoding}; +use solana_client::{ + rpc_client::RpcClient, + rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, + rpc_filter::{MemcmpEncodedBytes, RpcFilterType, Memcmp}, +}; +use solana_sdk::{ + commitment_config::CommitmentConfig, +}; + const MIN_COMMUNITY_WEIGHT_TO_CREATE_GOVERNANCE: u64 = 1; pub struct RealmConfig { @@ -79,6 +91,52 @@ impl<'a> Realm<'a> { pub fn settings(&self) -> Ref {self._settings.borrow()} pub fn settings_mut(&self) -> RefMut {self._settings.borrow_mut()} + pub fn update_max_voter_weight_record_address(&self) -> Result,GovernanceLibError> { + #[derive(BorshSchema,BorshSerialize)] + struct MaxVoterWeightFilterData { + pub account_discriminator: [u8; 8], + pub realm: Pubkey, + pub governing_token_mint: Pubkey, + } + + let max_voter_weight_record_address = match self.get_realm_config()? { + Some(RealmConfigAccount {max_community_voter_weight_addin: Some(max_voter_weight_addin),..}) => { + let filter = MaxVoterWeightFilterData { + account_discriminator: MaxVoterWeightRecord::ACCOUNT_DISCRIMINATOR, + realm: self.realm_address, + governing_token_mint: self.community_mint, + }; + + let config = RpcProgramAccountsConfig { + filters: Some(vec![ + RpcFilterType::Memcmp(Memcmp { + offset: 0, + bytes: MemcmpEncodedBytes::Base58(bs58::encode(filter.try_to_vec()?).into_string()), + encoding: None, + }), + ]), + account_config: RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64), + data_slice: None, + commitment: Some(CommitmentConfig::confirmed()), + }, + with_context: Some(false), + }; + let accounts = self.client.solana_client.get_program_accounts_with_config( + &max_voter_weight_addin, + config, + )?; + if accounts.is_empty() { + return Err(GovernanceLibError::StateError(max_voter_weight_addin, "Missed max_voter_weight_record".to_string())); + } + Some(accounts[0].0) + }, + _ => {None}, + }; + self.settings_mut().max_voter_weight_record_address = max_voter_weight_record_address; + return Ok(max_voter_weight_record_address) + } + pub fn get_data(&self) -> ClientResult> { self.client.get_account_data_borsh::(&self.program_id, &self.realm_address) } @@ -111,17 +169,7 @@ impl<'a> Realm<'a> { } pub fn token_owner_record<'b:'a>(&'b self, token_owner: &Pubkey) -> TokenOwner<'a> { - let token_owner_record_address: Pubkey = get_token_owner_record_address( - &self.program_id, - &self.realm_address, - &self.community_mint, token_owner - ); - TokenOwner { - realm: self, - token_owner_address: *token_owner, - token_owner_record_address, - voter_weight_record_address: None, - } + TokenOwner::new(self, token_owner) } pub fn governance<'b:'a>(&'b self, governed_account: &Pubkey) -> Governance<'a> { diff --git a/governance-lib/src/token_owner.rs b/governance-lib/src/token_owner.rs index 35464a3..a80247b 100644 --- a/governance-lib/src/token_owner.rs +++ b/governance-lib/src/token_owner.rs @@ -1,8 +1,10 @@ use { crate::{ + errors::GovernanceLibError, realm::Realm, client::ClientResult, }, + borsh::{BorshSchema,BorshSerialize}, solana_sdk::{ signer::{Signer, keypair::Keypair}, pubkey::Pubkey, @@ -10,21 +12,52 @@ use { signature::Signature, }, spl_governance::{ - state::token_owner_record::TokenOwnerRecordV2, + state::{ + realm_config::RealmConfigAccount, + token_owner_record::{ + TokenOwnerRecordV2, + get_token_owner_record_address, + }, + }, instruction::{ create_token_owner_record, set_governance_delegate, }, }, + spl_governance_addin_api::voter_weight::VoterWeightRecord, std::fmt, + std::cell::{RefCell, Ref, RefMut}, +}; +use solana_account_decoder::{UiDataSliceConfig, UiAccountEncoding}; +use solana_client::{ + rpc_client::RpcClient, + rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, + rpc_filter::{MemcmpEncodedBytes, RpcFilterType, Memcmp}, +}; +use solana_sdk::{ + commitment_config::CommitmentConfig, }; +#[derive(Debug)] +pub struct TokenOwnerSettings { + pub voter_weight_record_address: Option, +} + +impl TokenOwnerSettings { + pub fn default() -> Self { + Self { + voter_weight_record_address: None, + } + } +} + #[derive(Debug)] pub struct TokenOwner<'a> { pub realm: &'a Realm<'a>, pub token_owner_address: Pubkey, pub token_owner_record_address: Pubkey, - pub voter_weight_record_address: Option, + _settings: RefCell, + //pub voter_weight_record_address: Option, } impl<'a> fmt::Display for TokenOwner<'a> { @@ -34,17 +67,31 @@ impl<'a> fmt::Display for TokenOwner<'a> { .field("realm", &self.realm.realm_address) .field("token_owner", &self.token_owner_address) .field("token_owner_record", &self.token_owner_record_address) - .field("voter_weight_record", &self.voter_weight_record_address) + .field("settings", &self._settings.borrow()) .finish() } } impl<'a> TokenOwner<'a> { - pub fn set_voter_weight_record_address(&mut self, voter_weight_record_address: Option) { - self.voter_weight_record_address = voter_weight_record_address; + pub fn new(realm: &'a Realm, token_owner: &Pubkey) -> Self { + let token_owner_record_address: Pubkey = get_token_owner_record_address( + &realm.program_id, + &realm.realm_address, + &realm.community_mint, + token_owner, + ); + Self { + realm, + token_owner_address: *token_owner, + token_owner_record_address, + _settings: RefCell::new(TokenOwnerSettings::default()), + } } + pub fn settings(&self) -> Ref {self._settings.borrow()} + pub fn settings_mut(&self) -> RefMut {self._settings.borrow_mut()} + pub fn get_data(&self) -> ClientResult> { self.realm.client.get_account_data_borsh::( &self.realm.program_id, @@ -52,6 +99,63 @@ impl<'a> TokenOwner<'a> { ) } + pub fn set_voter_weight_record_address(&mut self, voter_weight_record_address: Option) { + self.settings_mut().voter_weight_record_address = voter_weight_record_address; + } + + pub fn get_voter_weight_record_address(&self) -> Option { + self.settings().voter_weight_record_address + } + + pub fn update_voter_weight_record_address(&self) -> Result,GovernanceLibError> { + #[derive(BorshSchema,BorshSerialize)] + struct VoterWeightFilterData { + pub account_discriminator: [u8; 8], + pub realm: Pubkey, + pub governing_token_mint: Pubkey, + pub governing_token_owner: Pubkey, + } + + let voter_weight_record_address = match self.realm.get_realm_config()? { + Some(RealmConfigAccount {community_voter_weight_addin: Some(voter_weight_addin),..}) => { + let filter = VoterWeightFilterData { + account_discriminator: VoterWeightRecord::ACCOUNT_DISCRIMINATOR, + realm: self.realm.realm_address, + governing_token_mint: self.realm.community_mint, + governing_token_owner: self.token_owner_address, + }; + + let config = RpcProgramAccountsConfig { + filters: Some(vec![ + RpcFilterType::Memcmp(Memcmp { + offset: 0, + bytes: MemcmpEncodedBytes::Base58(bs58::encode(filter.try_to_vec()?).into_string()), + encoding: None, + }), + ]), + account_config: RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64), + data_slice: None, + commitment: Some(CommitmentConfig::confirmed()), + }, + with_context: Some(false), + }; + let accounts = self.realm.client.solana_client.get_program_accounts_with_config( + &voter_weight_addin, + config, + )?; + if accounts.is_empty() { + return Err(GovernanceLibError::StateError(voter_weight_addin, + format!("Missed voter_weight_record for {}", self.token_owner_address))); + } + Some(accounts[0].0) + }, + _ => {None}, + }; + self.settings_mut().voter_weight_record_address = voter_weight_record_address; + return Ok(voter_weight_record_address) + } + pub fn create_token_owner_record_instruction(&self) -> Instruction { create_token_owner_record( &self.realm.program_id, diff --git a/launch-script/src/main.rs b/launch-script/src/main.rs index 6940cf3..44f23be 100644 --- a/launch-script/src/main.rs +++ b/launch-script/src/main.rs @@ -257,8 +257,7 @@ fn process_environment(wallet: &Wallet, client: &Client, setup: bool, verbose: b // TODO it is correct for fixed_weight_addin! let creator_token_owner: &Keypair = &wallet.voter_keypairs[0]; let mut creator_token_owner_record = realm.token_owner_record(&creator_token_owner.pubkey()); - let creator_voter_weight_record = fixed_weight_addin.get_voter_weight_record_address(&realm, &creator_token_owner.pubkey()); - creator_token_owner_record.set_voter_weight_record_address(Some(creator_voter_weight_record)); + creator_token_owner_record.update_voter_weight_record_address()?; // TODO setup delegate through multisig executor.check_and_create_object("Delegate for creator_token_owner", @@ -409,20 +408,16 @@ fn setup_proposal_tge(wallet: &Wallet, client: &Client, proposal_index: Option, verbose: bool) -> Result<(), ScriptError> { + let realm = Realm::new(&client, &wallet.governance_program_id, REALM_NAME, &wallet.community_pubkey); + realm.update_max_voter_weight_record_address()?; + + let fixed_weight_addin = AddinFixedWeights::new(&client, wallet.fixed_weight_addin_id); + let vesting_addin = AddinVesting::new(&client, wallet.vesting_addin_id); + let governance = realm.governance(&wallet.governed_account_pubkey); + + let creator_token_owner = { + let voter_list = fixed_weight_addin.get_voter_list()?; + let creator_token_owner = realm.token_owner_record(&voter_list[0].voter); + creator_token_owner.update_voter_weight_record_address()?; + creator_token_owner + }; + + let governance_proposal_count = governance.get_proposals_count(); + let proposal_number = proposal_index.unwrap_or(governance_proposal_count); + if proposal_number > governance_proposal_count {return Err(StateError::InvalidProposalIndex.into());} + println!("Use {} for proposal_index", proposal_number); + + let proposal: Proposal = governance.proposal(proposal_number); + if let None = proposal.get_data()? { + return Err(StateError::InvalidProposalIndex.into()); + } + + proposal.finalize_vote(&wallet.creator_keypair, &creator_token_owner)?; + + Ok(()) +} + fn sign_off_proposal(wallet: &Wallet, client: &Client, proposal_index: Option, verbose: bool) -> Result<(), ScriptError> { let realm = Realm::new(&client, &wallet.governance_program_id, REALM_NAME, &wallet.community_pubkey); + realm.update_max_voter_weight_record_address()?; + let fixed_weight_addin = AddinFixedWeights::new(&client, wallet.fixed_weight_addin_id); let vesting_addin = AddinVesting::new(&client, wallet.vesting_addin_id); let governance = realm.governance(&wallet.governed_account_pubkey); - // TODO is is correct for fixed_weight_addin only! let creator_token_owner = { let voter_list = fixed_weight_addin.get_voter_list()?; - let mut creator_token_owner = realm.token_owner_record(&voter_list[0].voter); - let creator_voter_weight_record = fixed_weight_addin.get_voter_weight_record_address(&realm, &voter_list[0].voter); - creator_token_owner.set_voter_weight_record_address(Some(creator_voter_weight_record)); + let creator_token_owner = realm.token_owner_record(&voter_list[0].voter); + creator_token_owner.update_voter_weight_record_address()?; creator_token_owner }; @@ -611,23 +636,17 @@ fn sign_off_proposal(wallet: &Wallet, client: &Client, proposal_index: Option, verbose: bool) -> Result<(), ScriptError> { let realm = Realm::new(&client, &wallet.governance_program_id, REALM_NAME, &wallet.community_pubkey); - let fixed_weight_addin = AddinFixedWeights::new(&client, wallet.fixed_weight_addin_id); - let vesting_addin = AddinVesting::new(&client, wallet.vesting_addin_id); - let governance = realm.governance(&wallet.governed_account_pubkey); - - // TODO it is correct for fixed_weight_addin only! - let max_voter_weight_record_address = fixed_weight_addin.setup_max_voter_weight_record(&realm).unwrap(); - realm.settings_mut().max_voter_weight_record_address = Some(max_voter_weight_record_address); + realm.update_max_voter_weight_record_address()?; - // TODO is is correct for fixed_weight_addin only! + let fixed_weight_addin = AddinFixedWeights::new(&client, wallet.fixed_weight_addin_id); let creator_token_owner = { let voter_list = fixed_weight_addin.get_voter_list()?; let mut creator_token_owner = realm.token_owner_record(&voter_list[0].voter); - let creator_voter_weight_record = fixed_weight_addin.get_voter_weight_record_address(&realm, &voter_list[0].voter); - creator_token_owner.set_voter_weight_record_address(Some(creator_voter_weight_record)); + creator_token_owner.update_voter_weight_record_address()?; creator_token_owner }; + let governance = realm.governance(&wallet.governed_account_pubkey); let governance_proposal_count = governance.get_proposals_count(); let proposal_number = proposal_index.unwrap_or(governance_proposal_count); if proposal_number > governance_proposal_count {return Err(StateError::InvalidProposalIndex.into());} @@ -641,9 +660,7 @@ fn approve_proposal(wallet: &Wallet, client: &Client, proposal_index: Option { approve_proposal(&wallet, &client, proposal_index, verbose).unwrap() }, + ("finalize-vote", Some(arg_matches)) => { + finalize_vote_proposal(&wallet, &client, proposal_index, verbose).unwrap() + }, ("execute", Some(arg_matches)) => { execute_proposal(&wallet, &client, proposal_index, verbose).unwrap() },