diff --git a/Cargo.toml b/Cargo.toml index 50cc5ed..a4e59c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "addin-fixed-weights/program", "addin-vesting/program", # "addin-vesting/program/fuzz", + "governance-test-scripts", ] exclude = [ "solana-program-library", diff --git a/governance-test-scripts/Cargo.toml b/governance-test-scripts/Cargo.toml new file mode 100644 index 0000000..5da9228 --- /dev/null +++ b/governance-test-scripts/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "governance-test-scripts" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +solana-sdk = "1.9" +solana-client = "1.9" +borsh = "0.9.1" +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" } +spl-governance-addin-api= { version = "0.1.1", path = "../solana-program-library/governance/addin-api"} +spl-governance-addin-mock = { version = "0.1", path = "../solana-program-library/governance/addin-mock/program" } + +spl-governance-addin-fixed-weights = { version = "0.1", path = "../addin-fixed-weights/program" } diff --git a/governance-test-scripts/README.md b/governance-test-scripts/README.md new file mode 100644 index 0000000..5a729cf --- /dev/null +++ b/governance-test-scripts/README.md @@ -0,0 +1 @@ +# spl-governance-dev \ No newline at end of file diff --git a/governance-test-scripts/community_mint.keypair b/governance-test-scripts/community_mint.keypair new file mode 100644 index 0000000..8f35d12 --- /dev/null +++ b/governance-test-scripts/community_mint.keypair @@ -0,0 +1 @@ +[180,246,24,202,72,140,36,173,163,183,130,48,155,133,224,186,161,154,90,88,186,160,144,93,138,82,157,174,32,159,8,99,136,50,169,190,75,38,89,39,108,43,138,119,25,8,211,221,71,57,205,213,197,176,104,202,133,194,223,78,167,159,242,1] \ No newline at end of file diff --git a/governance-test-scripts/governance.keypair b/governance-test-scripts/governance.keypair new file mode 100644 index 0000000..024f6da --- /dev/null +++ b/governance-test-scripts/governance.keypair @@ -0,0 +1 @@ +[30,135,165,225,246,149,235,136,96,115,96,102,141,150,184,54,248,59,226,48,254,170,60,189,238,181,158,56,201,150,109,155,18,248,22,87,178,226,232,120,58,6,190,91,113,53,102,148,88,60,249,220,193,45,147,20,203,202,129,17,79,100,15,126] \ No newline at end of file diff --git a/governance-test-scripts/src/commands.rs b/governance-test-scripts/src/commands.rs new file mode 100644 index 0000000..33541cc --- /dev/null +++ b/governance-test-scripts/src/commands.rs @@ -0,0 +1,719 @@ +use solana_sdk::{ + commitment_config::CommitmentConfig, + pubkey::{ Pubkey }, + instruction::{ Instruction }, + transaction::{ Transaction }, + signer::{ + Signer, + keypair::{ Keypair }, + }, + signature::Signature, +}; + +use solana_client::rpc_client::RpcClient; +use solana_client::client_error::Result as ClientResult; +use solana_client::client_error::ClientError; + +use borsh::{BorshDeserialize}; + +use spl_governance::{ + state::{ + enums::{ + // VoteThresholdPercentage, + // VoteWeightSource, + // VoteTipping, + MintMaxVoteWeightSource, + }, + governance::{ + GovernanceConfig, + GovernanceV2, + get_governance_address, + }, + realm::{ + RealmV2, + get_realm_address, + }, + proposal::{ + VoteType, + ProposalV2, + get_proposal_address, + }, + token_owner_record::{ + TokenOwnerRecordV2, + get_token_owner_record_address, + }, + // signatory_record::{ + // get_signatory_record_address, + // }, + vote_record::{ + Vote, + VoteChoice, + }, + }, + instruction::{ + create_realm, + create_token_owner_record, + create_governance, + // set_governance_config, + create_proposal, + sign_off_proposal, + add_signatory, + cast_vote, + } +}; + +use spl_governance_addin_api::{ + max_voter_weight::{ + MaxVoterWeightRecord, + }, + voter_weight::{ + VoterWeightRecord, + }, +}; + +use spl_governance_addin_mock::{ + instruction::{ + setup_voter_weight_record, + setup_max_voter_weight_record, + } +}; + +use spl_governance_addin_fixed_weights::{ + instruction::{ + get_max_voter_weight_address, + } +}; + +const MIN_COMMUNITY_WEIGHT_TO_CREATE_GOVERNANCE: u64 = 1; + +pub struct SplGovernanceInteractor { + solana_client: RpcClient, + spl_governance_program_address: Pubkey, + spl_governance_voter_weight_addin_address: Pubkey, +} + +impl SplGovernanceInteractor { + + pub fn new(url: &str, program_address: Pubkey, addin_address: Pubkey) -> Self { + SplGovernanceInteractor { + solana_client: RpcClient::new_with_commitment(url.to_string(),CommitmentConfig::confirmed()), + spl_governance_program_address: program_address, + spl_governance_voter_weight_addin_address: addin_address, + } + } + pub fn account_exists(&self, address: &Pubkey) -> bool { + self.solana_client.get_account(&address).is_ok() + } + pub fn get_realm_address(&self, name: &str) -> Pubkey { + get_realm_address(&self.spl_governance_program_address, name) + } + pub fn get_token_owner_record_address(&self, goverinig_token_owner: &Pubkey, community_mint_pubkey: &Pubkey, realm_name: &str) -> Pubkey { + let realm_pubkey: Pubkey = self.get_realm_address(realm_name); + get_token_owner_record_address(&self.spl_governance_program_address, &realm_pubkey, community_mint_pubkey, goverinig_token_owner) + } + pub fn get_governance_address(&self, realm_name: &str, governed_account_pubkey: &Pubkey) -> Pubkey { + let realm_pubkey: Pubkey = self.get_realm_address(realm_name); + get_governance_address(&self.spl_governance_program_address, &realm_pubkey, governed_account_pubkey) + } + pub fn get_proposal_address(&self, community_mint_pubkey: &Pubkey, realm_name: &str, governed_account_pubkey: &Pubkey, proposal_index: u8) -> Pubkey { + let governance_pubkey: Pubkey = self.get_governance_address(realm_name, governed_account_pubkey); + + let proposal_index_arr: [u8; 4] = [proposal_index,0,0,0]; + get_proposal_address(&self.spl_governance_program_address, &governance_pubkey, community_mint_pubkey, &proposal_index_arr) + } + pub fn get_realm_v2(&self, realm_name: &str) -> Result { + let realm_pubkey: Pubkey = self.get_realm_address(realm_name); + + self.solana_client.get_account_data(&realm_pubkey) + .map_err(|_|()) + .and_then(|data|{ + let mut data_slice: &[u8] = &data; + RealmV2::deserialize(&mut data_slice).map_err(|_|()) + + }) + } + pub fn get_token_owner_record_v2(&self, goverinig_token_owner: &Pubkey, community_mint_pubkey: &Pubkey, realm_name: &str) -> TokenOwnerRecordV2 { + let token_owner_record_pubkey: Pubkey = self.get_token_owner_record_address(goverinig_token_owner, community_mint_pubkey, realm_name); + + let mut dt: &[u8] = &self.solana_client.get_account_data(&token_owner_record_pubkey).unwrap(); + TokenOwnerRecordV2::deserialize(&mut dt).unwrap() + } + pub fn get_governance_v2(&self, realm_name: &str, governed_account_pubkey: &Pubkey) -> GovernanceV2 { + let governance_pubkey: Pubkey = self.get_governance_address(realm_name, governed_account_pubkey); + + let mut dt: &[u8] = &self.solana_client.get_account_data(&governance_pubkey).unwrap(); + GovernanceV2::deserialize(&mut dt).unwrap() + } + pub fn get_proposal_v2(&self, community_mint_pubkey: &Pubkey, realm_name: &str, governed_account_pubkey: &Pubkey, proposal_index: u8) -> ProposalV2 { + let proposal_pubkey: Pubkey = self.get_proposal_address(community_mint_pubkey, realm_name, governed_account_pubkey, proposal_index); + + let mut dt: &[u8] = &self.solana_client.get_account_data(&proposal_pubkey).unwrap(); + ProposalV2::deserialize(&mut dt).unwrap() + } + pub fn get_voter_weight_record(&self, voter_weight_record_pubkey: &Pubkey) -> VoterWeightRecord { + let mut dt: &[u8] = &self.solana_client.get_account_data(voter_weight_record_pubkey).unwrap(); + VoterWeightRecord::deserialize(&mut dt).unwrap() + } + pub fn get_max_voter_weight_record(&self, max_voter_weight_record_pubkey: &Pubkey) -> MaxVoterWeightRecord { + let mut dt: &[u8] = &self.solana_client.get_account_data(max_voter_weight_record_pubkey).unwrap(); + MaxVoterWeightRecord::deserialize(&mut dt).unwrap() + } + + pub fn create_realm(&self, realm_authority: Keypair, community_mint_pubkey: &Pubkey, addin_opt: Option, realm_name: &str) -> Result { + let realm_pubkey: Pubkey = self.get_realm_address(realm_name); + + if self.account_exists(&realm_pubkey) { + Ok( + Realm { + authority: realm_authority, + address: realm_pubkey, + data: self.get_realm_v2(realm_name).unwrap(), + max_voter_weight_addin_address: addin_opt, + // voter_weight_addin_address: addin_opt, + } + ) + } else { + let realm_authority_pubkey: Pubkey = realm_authority.pubkey(); + + let create_realm_instruction: Instruction = + create_realm( + &self.spl_governance_program_address, + &realm_authority_pubkey, + community_mint_pubkey, + &realm_authority_pubkey, + None, + addin_opt, + addin_opt, + realm_name.to_string(), + MIN_COMMUNITY_WEIGHT_TO_CREATE_GOVERNANCE, + MintMaxVoteWeightSource::SupplyFraction(10_000_000_000), + // MintMaxVoteWeightSource::FULL_SUPPLY_FRACTION, + ); + + let transaction: Transaction = + Transaction::new_signed_with_payer( + &[ + create_realm_instruction, + ], + Some(&realm_authority_pubkey), + &[ + &realm_authority, + ], + self.solana_client.get_latest_blockhash().unwrap(), + ); + + self.solana_client.send_and_confirm_transaction(&transaction) + .map(|_| + Realm { + authority: realm_authority, + address: realm_pubkey, + data: self.get_realm_v2(realm_name).unwrap(), + max_voter_weight_addin_address: addin_opt, + // voter_weight_addin_address: addin_opt, + } + ) + // .map_err(|_|()) + } + } + + pub fn create_token_owner_record(&self, realm: &Realm, token_owner_keypair: Keypair) -> Result { + let token_owner_pubkey: Pubkey = token_owner_keypair.pubkey(); + let token_owner_record_pubkey: Pubkey = self.get_token_owner_record_address(&token_owner_pubkey, &realm.data.community_mint, &realm.data.name); + + if self.account_exists(&token_owner_record_pubkey) { + Ok( + TokenOwner { + authority: token_owner_keypair, + token_owner_record_address: token_owner_record_pubkey, + token_owner_record: self.get_token_owner_record_v2(&token_owner_pubkey, &realm.data.community_mint, &realm.data.name), + // voter_weight_record_authority: None, + voter_weight_record_address: None, + // voter_weight_record: None, + } + ) + } else { + let realm_authority_pubkey: Pubkey = realm.authority.pubkey(); + + let create_token_owner_record_instruction: Instruction = + create_token_owner_record( + &self.spl_governance_program_address, + &realm.address, + &token_owner_pubkey, + &realm.data.community_mint, + &realm_authority_pubkey, + ); + + let transaction: Transaction = + Transaction::new_signed_with_payer( + &[ + create_token_owner_record_instruction, + ], + Some(&realm_authority_pubkey), + &[ + &realm.authority, + ], + self.solana_client.get_latest_blockhash().unwrap(), + ); + + self.solana_client.send_and_confirm_transaction(&transaction) + .map(|_| + TokenOwner { + authority: token_owner_keypair, + token_owner_record_address: token_owner_record_pubkey, + token_owner_record: self.get_token_owner_record_v2(&token_owner_pubkey, &realm.data.community_mint, &realm.data.name), + // voter_weight_record_authority: None, + voter_weight_record_address: None, + // voter_weight_record: None, + } + ) + .map_err(|_|()) + } + } + + pub fn _setup_max_voter_weight_record_mock(&self, realm: &Realm, max_voter_weight_record_keypair: Keypair, max_voter_weight: u64) -> Result { + let max_voter_weight_record_pubkey: Pubkey = max_voter_weight_record_keypair.pubkey(); + + if self.account_exists(&max_voter_weight_record_pubkey) { + Err(()) + } else { + let realm_authority_pubkey: Pubkey = realm.authority.pubkey(); + + let setup_max_voter_weight_record_instruction: Instruction = + setup_max_voter_weight_record( + &self.spl_governance_voter_weight_addin_address, + &realm.address, + &realm.data.community_mint, + &max_voter_weight_record_pubkey, + &realm_authority_pubkey, + max_voter_weight, + None, + ); + + let transaction: Transaction = + Transaction::new_signed_with_payer( + &[ + setup_max_voter_weight_record_instruction, + ], + Some(&realm_authority_pubkey), + &[ + &realm.authority, + &max_voter_weight_record_keypair, + ], + self.solana_client.get_latest_blockhash().unwrap(), + ); + + self.solana_client.send_and_confirm_transaction(&transaction) + .map_err(|_|()) + } + } + + // pub fn setup_max_voter_weight_record_fixed(&self, voter_weight_addin_autority: &Keypair, realm: &Realm, max_voter_weight: u64) -> ClientResult { + pub fn setup_max_voter_weight_record_fixed(&self, realm: &Realm) -> Result { + // let max_voter_weight_record_pubkey: Pubkey = max_voter_weight_record_keypair.pubkey(); + let (max_voter_weight_record_pubkey,_): (Pubkey,u8) = spl_governance_addin_fixed_weights::instruction::get_max_voter_weight_address(&self.spl_governance_voter_weight_addin_address, &realm.address, &realm.data.community_mint); + + if self.account_exists(&max_voter_weight_record_pubkey) { + Err(()) + } else { + let realm_authority_pubkey: Pubkey = realm.authority.pubkey(); + + let setup_max_voter_weight_record_instruction: Instruction = + spl_governance_addin_fixed_weights::instruction::setup_max_voter_weight_record( + &self.spl_governance_voter_weight_addin_address, + &realm.address, + &realm.data.community_mint, + &realm_authority_pubkey, + // max_voter_weight, + ); + + let transaction: Transaction = + Transaction::new_signed_with_payer( + &[ + setup_max_voter_weight_record_instruction, + ], + Some(&realm_authority_pubkey), + &[ + &realm.authority, + // voter_weight_addin_autority, + // &max_voter_weight_record_keypair, + ], + self.solana_client.get_latest_blockhash().unwrap(), + ); + + self.solana_client.send_and_confirm_transaction(&transaction) + .map_err(|_|()) + } + } + + pub fn _setup_voter_weight_record_mock(&self, realm: &Realm, token_owner: TokenOwner, voter_weight_record_keypair: Keypair, voter_weight: u64) -> Result { + let voter_weight_record_pubkey: Pubkey = voter_weight_record_keypair.pubkey(); + + if self.account_exists(&voter_weight_record_pubkey) { + Ok( + TokenOwner { + authority: token_owner.authority, + token_owner_record_address: token_owner.token_owner_record_address, + token_owner_record: token_owner.token_owner_record, + // voter_weight_record_authority: Some(voter_weight_record_keypair), + voter_weight_record_address: Some(voter_weight_record_pubkey), + // voter_weight_record: Some(self.get_voter_weight_record(&voter_weight_record_pubkey)), + } + ) + } else { + let realm_authority_pubkey: Pubkey = realm.authority.pubkey(); + + let setup_voter_weight_record_instruction: Instruction = + setup_voter_weight_record( + &self.spl_governance_voter_weight_addin_address, + &realm.address, + &realm.data.community_mint, + &token_owner.authority.pubkey(), + &voter_weight_record_pubkey, + &realm_authority_pubkey, + voter_weight, + None, + None, + None, + ); + + let transaction: Transaction = + Transaction::new_signed_with_payer( + &[ + setup_voter_weight_record_instruction, + ], + Some(&realm_authority_pubkey), + &[ + &realm.authority, + &voter_weight_record_keypair, + ], + self.solana_client.get_latest_blockhash().unwrap(), + ); + + self.solana_client.send_and_confirm_transaction(&transaction) + .map(|_| + TokenOwner { + authority: token_owner.authority, + token_owner_record_address: token_owner.token_owner_record_address, + token_owner_record: token_owner.token_owner_record, + // voter_weight_record_authority: Some(voter_weight_record_keypair), + voter_weight_record_address: Some(voter_weight_record_pubkey), + // voter_weight_record: Some(self.get_voter_weight_record(&voter_weight_record_pubkey)), + } + ) + .map_err(|_|()) + } + } + + pub fn setup_voter_weight_record_fixed(&self, realm: &Realm, token_owner: TokenOwner) -> Result { + let token_owner_pubkey: Pubkey = token_owner.authority.pubkey(); + let (voter_weight_record_pubkey,_): (Pubkey,u8) = spl_governance_addin_fixed_weights::instruction::get_voter_weight_address(&self.spl_governance_voter_weight_addin_address, &realm.address, &realm.data.community_mint, &token_owner_pubkey); + + if self.account_exists(&voter_weight_record_pubkey) { + Ok( + TokenOwner { + authority: token_owner.authority, + token_owner_record_address: token_owner.token_owner_record_address, + token_owner_record: token_owner.token_owner_record, + // voter_weight_record_authority: None, + voter_weight_record_address: Some(voter_weight_record_pubkey), + // voter_weight_record: Some(self.get_voter_weight_record(&voter_weight_record_pubkey)), + } + ) + } else { + let realm_authority_pubkey: Pubkey = realm.authority.pubkey(); + + let setup_voter_weight_record_instruction: Instruction = + spl_governance_addin_fixed_weights::instruction::setup_voter_weight_record( + &self.spl_governance_voter_weight_addin_address, + &realm.address, + &realm.data.community_mint, + &token_owner_pubkey, + &realm_authority_pubkey, + ); + + let transaction: Transaction = + Transaction::new_signed_with_payer( + &[ + setup_voter_weight_record_instruction, + ], + Some(&realm_authority_pubkey), + &[ + &realm.authority, + // &voter_weight_record_keypair, + ], + self.solana_client.get_latest_blockhash().unwrap(), + ); + + self.solana_client.send_and_confirm_transaction(&transaction) + .map(|_| + TokenOwner { + authority: token_owner.authority, + token_owner_record_address: token_owner.token_owner_record_address, + token_owner_record: token_owner.token_owner_record, + // voter_weight_record_authority: None, + voter_weight_record_address: Some(voter_weight_record_pubkey), + // voter_weight_record: Some(self.get_voter_weight_record(&voter_weight_record_pubkey)), + } + ) + .map_err(|_|()) + } + } + + pub fn create_governance(&self, realm: &Realm, token_owner: &TokenOwner, governed_account_pubkey: &Pubkey, gov_config: GovernanceConfig) -> Result { + let governance_pubkey: Pubkey = self.get_governance_address(&realm.data.name, governed_account_pubkey); + + if self.account_exists(&governance_pubkey) { + Ok( + Governance { + address: governance_pubkey, + data: self.get_governance_v2(&realm.data.name, governed_account_pubkey) + } + ) + } else { + let realm_authority_pubkey: Pubkey = realm.authority.pubkey(); + // let token_owner_record_pubkey: Pubkey = self.get_token_owner_record_address(&realm_authority_pubkey, &community_mint_pubkey, realm_name); + + let create_governance_instruction: Instruction = + create_governance( + &self.spl_governance_program_address, + &realm.address, + Some(governed_account_pubkey), + &token_owner.token_owner_record_address, + &realm_authority_pubkey, + &realm_authority_pubkey, + token_owner.voter_weight_record_address, + gov_config, + ); + + let transaction: Transaction = + Transaction::new_signed_with_payer( + &[ + create_governance_instruction, + ], + Some(&realm_authority_pubkey), + &[ + &realm.authority, + ], + self.solana_client.get_latest_blockhash().unwrap(), + ); + + self.solana_client.send_and_confirm_transaction(&transaction) + .map(|_| + Governance { + address: governance_pubkey, + data: self.get_governance_v2(&realm.data.name, governed_account_pubkey) + } + ) + // .map_err(|_|()) + } + } + + pub fn create_proposal(&self, realm: &Realm, token_owner: &TokenOwner, governance: &Governance, proposal_name: &str, proposal_description: &str, proposal_index: u32) -> Result { + let proposal_address: Pubkey = self.get_proposal_address(&realm.data.community_mint, &realm.data.name, &governance.data.governed_account, proposal_index as u8); + + if self.account_exists(&proposal_address) { + let proposal_v2: ProposalV2 = self.get_proposal_v2(&realm.data.community_mint, &realm.data.name, &governance.data.governed_account, proposal_index as u8); + Ok( + Proposal { + address: proposal_address, + data: proposal_v2, + } + ) + } else { + let realm_authority_pubkey: Pubkey = realm.authority.pubkey(); + + let create_proposal_instruction: Instruction = + create_proposal( + &self.spl_governance_program_address, + &governance.address, + &token_owner.token_owner_record_address, + &realm_authority_pubkey, + &realm_authority_pubkey, + token_owner.voter_weight_record_address, + &realm.address, + proposal_name.to_string(), + proposal_description.to_string(), + &realm.data.community_mint, + VoteType::SingleChoice, + vec!["Yes".to_string()], + true, + proposal_index, + ); + + let transaction: Transaction = + Transaction::new_signed_with_payer( + &[ + create_proposal_instruction, + ], + Some(&realm_authority_pubkey), + &[ + &realm.authority, + ], + self.solana_client.get_latest_blockhash().unwrap(), + ); + + self.solana_client.send_and_confirm_transaction(&transaction) + .map(|_| { + let proposal_v2: ProposalV2 = self.get_proposal_v2(&realm.data.community_mint, &realm.data.name, &governance.data.governed_account, proposal_index as u8); + Proposal { + address: proposal_address, + data: proposal_v2, + } + }) + } + } + + pub fn sign_off_proposal(&self, realm: &Realm, governance: &Governance, proposal: Proposal, token_owner: &TokenOwner) -> Result { + let realm_authority_pubkey: Pubkey = realm.authority.pubkey(); + + let sign_off_proposal_instruction: Instruction = + sign_off_proposal( + &self.spl_governance_program_address, + &realm.address, + &governance.address, + &proposal.address, + &realm_authority_pubkey, + Some(&token_owner.token_owner_record_address), + ); + + let transaction: Transaction = + Transaction::new_signed_with_payer( + &[ + sign_off_proposal_instruction, + ], + Some(&realm_authority_pubkey), + &[ + &realm.authority, + ], + self.solana_client.get_latest_blockhash().unwrap(), + ); + + self.solana_client.send_and_confirm_transaction(&transaction) + .map(|_| + Proposal { + address: proposal.address, + data: self.get_proposal_v2(&realm.data.community_mint, &realm.data.name, &governance.data.governed_account, governance.data.proposals_count as u8), + } + ) + } + + pub fn _add_signatory(&self, realm: &Realm, _governance: &Governance, proposal: &Proposal, token_owner: &TokenOwner) -> Result { + let realm_authority_pubkey: Pubkey = realm.authority.pubkey(); + // let signatory_record_address = get_signatory_record_address(&self.spl_governance_program_address, &proposal.address, &token_owner.authority.pubkey()); + + let add_signatory_instruction: Instruction = + add_signatory( + &self.spl_governance_program_address, + &proposal.address, + &token_owner.token_owner_record_address, + &realm_authority_pubkey, + &realm_authority_pubkey, + &token_owner.authority.pubkey(), + ); + + let transaction: Transaction = + Transaction::new_signed_with_payer( + &[ + add_signatory_instruction, + ], + Some(&realm_authority_pubkey), + &[ + &realm.authority, + ], + self.solana_client.get_latest_blockhash().unwrap(), + ); + + self.solana_client.send_and_confirm_transaction(&transaction) + // .map(|_| + // Proposal { + // address: proposal.address, + // data: self.get_proposal_v2(&realm.data.community_mint, &realm.data.name, &governance.data.governed_account, governance.data.proposals_count as u8), + // } + // ) + } + + pub fn cast_vote(&self, realm: &Realm, governance: &Governance, proposal: &Proposal, voter: &TokenOwner, vote_yes_no: bool) -> ClientResult { + let voter_authority_pubkey: Pubkey = voter.authority.pubkey(); + let max_voter_weight_addin_address: &Pubkey = &realm.max_voter_weight_addin_address.unwrap(); + let (max_voter_weight_record_address,_) = get_max_voter_weight_address(max_voter_weight_addin_address, &realm.address, &realm.data.community_mint); + + let vote: Vote = + if vote_yes_no { + Vote::Approve(vec![ + VoteChoice { + rank: 0, + weight_percentage: 100, + } + ]) + } else { + Vote::Deny + }; + + let cast_vote_instruction: Instruction = + cast_vote( + &self.spl_governance_program_address, + &realm.address, + &governance.address, + &proposal.address, + &proposal.data.token_owner_record, + &voter.token_owner_record_address, + &voter_authority_pubkey, + &realm.data.community_mint, + &voter_authority_pubkey, + voter.voter_weight_record_address, + Some(max_voter_weight_record_address), + vote, + ); + + let transaction: Transaction = + Transaction::new_signed_with_payer( + &[ + cast_vote_instruction, + ], + Some(&voter_authority_pubkey), + &[ + &voter.authority, + ], + self.solana_client.get_latest_blockhash().unwrap(), + ); + + self.solana_client.send_and_confirm_transaction(&transaction) + } +} + +#[derive(Debug)] +pub struct Realm { + authority: Keypair, + pub address: Pubkey, + data: RealmV2, + max_voter_weight_addin_address: Option, + // voter_weight_addin_address: Option, +} + +#[derive(Debug)] +pub struct Governance { + address: Pubkey, + data: GovernanceV2, +} + +impl Governance { + pub fn get_proposal_count(&self) -> u32 { + self.data.proposals_count + } +} + +#[derive(Debug)] +pub struct Proposal { + address: Pubkey, + pub data: ProposalV2, +} + +#[derive(Debug)] +pub struct TokenOwner { + pub authority: Keypair, + token_owner_record_address: Pubkey, + token_owner_record: TokenOwnerRecordV2, + // voter_weight_record_authority: Option, + voter_weight_record_address: Option, + // voter_weight_record: Option, +} diff --git a/governance-test-scripts/src/main.rs b/governance-test-scripts/src/main.rs new file mode 100644 index 0000000..eb3c989 --- /dev/null +++ b/governance-test-scripts/src/main.rs @@ -0,0 +1,221 @@ + +use solana_sdk::{ + pubkey::{ Pubkey }, + signer::{ + Signer, + keypair::{ Keypair, read_keypair_file }, + }, +}; + +use spl_governance::{ + state::{ + enums::{ + VoteThresholdPercentage, + VoteTipping, + ProposalState, + }, + governance::{ + GovernanceConfig, + }, + }, +}; + +use spl_governance_addin_fixed_weights::{ + instruction::{ + get_max_voter_weight_address, + get_voter_weight_address, + } +}; + +// mod tokens; +mod commands; + +use commands::{ Realm, Governance, Proposal, TokenOwner }; + +const GOVERNANCE_KEY_FILE_PATH: &'static str = "solana-program-library/target/deploy/spl_governance-keypair.json"; +const VOTER_WEIGHT_ADDIN_KEY_FILE_PATH: &'static str = "target/deploy/spl_governance_addin_fixed_weights-keypair.json"; +const COMMUTINY_MINT_KEY_FILE_PATH: &'static str = "governance-test-scripts/community_mint.keypair"; +const GOVERNED_MINT_KEY_FILE_PATH: &'static str = "governance-test-scripts/governance.keypair"; + +const VOTER1_KEY_FILE_PATH: &'static str = "artifacts/voter1.keypair"; +const VOTER2_KEY_FILE_PATH: &'static str = "artifacts/voter2.keypair"; +const VOTER3_KEY_FILE_PATH: &'static str = "artifacts/voter3.keypair"; +const VOTER4_KEY_FILE_PATH: &'static str = "artifacts/voter4.keypair"; +const VOTER5_KEY_FILE_PATH: &'static str = "artifacts/voter5.keypair"; + +// const REALM_NAME: &'static str = "Test Realm"; +const REALM_NAME: &'static str = "_Test_Realm_5"; +// const REALM_NAME: &'static str = "Test Realm 6"; +const PROPOSAL_NAME: &'static str = "Proposal To Vote"; +const PROPOSAL_DESCRIPTION: &'static str = "proposal_description"; + +fn main() { + + let owner_keypair: Keypair = read_keypair_file(VOTER1_KEY_FILE_PATH).unwrap(); + // let owner_pubkey: Pubkey = owner_keypair.pubkey(); + // println!("Owner Pubkey: {}", owner_pubkey); + + let program_keypair: Keypair = read_keypair_file(GOVERNANCE_KEY_FILE_PATH).unwrap(); + let program_id: Pubkey = program_keypair.pubkey(); + println!("Governance Program Id: {}", program_id); + + let community_keypair: Keypair = read_keypair_file(COMMUTINY_MINT_KEY_FILE_PATH).unwrap(); + let community_pubkey: Pubkey = community_keypair.pubkey(); + println!("Community Token Mint Pubkey: {}", community_pubkey); + + let voter_weight_addin_keypair: Keypair = read_keypair_file(VOTER_WEIGHT_ADDIN_KEY_FILE_PATH).unwrap(); + let voter_weight_addin_pubkey: Pubkey = voter_weight_addin_keypair.pubkey(); + println!("Voter Weight Addin Pubkey: {}", voter_weight_addin_pubkey); + + let governed_account_keypair: Keypair = read_keypair_file(GOVERNED_MINT_KEY_FILE_PATH).unwrap(); + let governed_account_pubkey: Pubkey = governed_account_keypair.pubkey(); + println!("Governed Account (Mint) Pubkey: {}", governed_account_pubkey); + + let voter1_keypair: Keypair = read_keypair_file(VOTER1_KEY_FILE_PATH).unwrap(); + let voter1_pubkey: Pubkey = voter1_keypair.pubkey(); + println!("Voter1 Pubkey: {}", voter1_pubkey); + + let voter2_keypair: Keypair = read_keypair_file(VOTER2_KEY_FILE_PATH).unwrap(); + let voter2_pubkey: Pubkey = voter2_keypair.pubkey(); + println!("Voter2 Pubkey: {}", voter2_pubkey); + + let voter3_keypair: Keypair = read_keypair_file(VOTER3_KEY_FILE_PATH).unwrap(); + let voter3_pubkey: Pubkey = voter3_keypair.pubkey(); + println!("Voter3 Pubkey: {}", voter3_pubkey); + + let voter4_keypair: Keypair = read_keypair_file(VOTER4_KEY_FILE_PATH).unwrap(); + let voter4_pubkey: Pubkey = voter4_keypair.pubkey(); + println!("Voter4 Pubkey: {}", voter4_pubkey); + + let voter5_keypair: Keypair = read_keypair_file(VOTER5_KEY_FILE_PATH).unwrap(); + let voter5_pubkey: Pubkey = voter5_keypair.pubkey(); + println!("Voter5 Pubkey: {}", voter5_pubkey); + + // let max_voter_weight_record_keypair: Keypair = read_keypair_file(MAX_VOTER_WEIGHT_RECORD_KEY_FILE_PATH).unwrap(); + // let max_voter_weight_record_pubkey: Pubkey = max_voter_weight_record_keypair.pubkey(); + // println!("Max Voter Weight Record Pubkey: {}", max_voter_weight_record_pubkey); + + // let voter_weight_record_keypair: Keypair = read_keypair_file(VOTER_WEIGHT_RECORD_KEY_FILE_PATH).unwrap(); + // let voter_weight_record_pubkey: Pubkey = voter_weight_record_keypair.pubkey(); + // println!("Voter Weight Record Pubkey: {}", voter_weight_record_pubkey); + + // let voter2_weight_record_keypair: Keypair = read_keypair_file(VOTER2_WEIGHT_RECORD_KEY_FILE_PATH).unwrap(); + // let voter2_weight_record_pubkey: Pubkey = voter2_weight_record_keypair.pubkey(); + // println!("Voter2 Weight Record Pubkey: {}", voter2_weight_record_pubkey); + + // let voter3_weight_record_keypair: Keypair = read_keypair_file(VOTER3_WEIGHT_RECORD_KEY_FILE_PATH).unwrap(); + // let voter3_weight_record_pubkey: Pubkey = voter3_weight_record_keypair.pubkey(); + // println!("Voter3 Weight Record Pubkey: {}", voter3_weight_record_pubkey); + + let interactor = commands::SplGovernanceInteractor::new("http://localhost:8899", program_id, voter_weight_addin_pubkey); + // let interactor = commands::SplGovernanceInteractor::new("https://api.devnet.solana.com", program_id, voter_weight_addin_pubkey); + + let realm: Realm = interactor.create_realm(owner_keypair, &community_pubkey, Some(voter_weight_addin_pubkey), REALM_NAME).unwrap(); + println!("{:?}", realm); + + println!("Realm Pubkey: {}", interactor.get_realm_address(REALM_NAME)); + + // let result = interactor.setup_max_voter_weight_record_mock(&realm, max_voter_weight_record_keypair, 10_000_000_000); + let result = interactor.setup_max_voter_weight_record_fixed(&realm); + println!("{:?}", result); + + let (max_voter_weight_record_address,_) = get_max_voter_weight_address(&voter_weight_addin_pubkey, &realm.address, &community_pubkey); + println!("MaxVoterWeightRecord Pubkey {:?}", max_voter_weight_record_address); + let max_voter_weight_record = interactor.get_max_voter_weight_record(&max_voter_weight_record_address); + println!("{:?}", max_voter_weight_record); + // return; + + let token_owner1: TokenOwner = interactor.create_token_owner_record(&realm, voter1_keypair).unwrap(); + // let token_owner: TokenOwner = interactor.setup_voter_weight_record_mock(&realm, token_owner, voter_weight_record_keypair, 10_000_000_000).unwrap(); + let token_owner1: TokenOwner = interactor.setup_voter_weight_record_fixed(&realm, token_owner1).unwrap(); + println!("Token Owner 1 \n{:?}", token_owner1); + + let (voter_weight_record_address,_) = get_voter_weight_address(&voter_weight_addin_pubkey, &realm.address, &community_pubkey, &token_owner1.authority.pubkey()); + let voter_weight_record = interactor.get_voter_weight_record(&voter_weight_record_address); + println!("Token Owner 1 VoterWeightRecord \n{:?}", voter_weight_record); + + let token_owner2: TokenOwner = interactor.create_token_owner_record(&realm, voter2_keypair).unwrap(); + // let token_owner2: TokenOwner = interactor.setup_voter_weight_record_mock(&realm, token_owner2, voter2_weight_record_keypair, 2_000_000_000).unwrap(); + let token_owner2: TokenOwner = interactor.setup_voter_weight_record_fixed(&realm, token_owner2).unwrap(); + println!("Token Owner 2 \n{:?}", token_owner2); + + let (voter_weight_record_address,_) = get_voter_weight_address(&voter_weight_addin_pubkey, &realm.address, &community_pubkey, &token_owner2.authority.pubkey()); + let voter_weight_record = interactor.get_voter_weight_record(&voter_weight_record_address); + println!("Token Owner 2 VoterWeightRecord \n{:?}", voter_weight_record); + + let token_owner3: TokenOwner = interactor.create_token_owner_record(&realm, voter3_keypair).unwrap(); + let token_owner3: TokenOwner = interactor.setup_voter_weight_record_fixed(&realm, token_owner3).unwrap(); + println!("Token Owner 3 \n{:?}", token_owner3); + + let (voter_weight_record_address,_) = get_voter_weight_address(&voter_weight_addin_pubkey, &realm.address, &community_pubkey, &token_owner3.authority.pubkey()); + let voter_weight_record = interactor.get_voter_weight_record(&voter_weight_record_address); + println!("Token Owner 3 VoterWeightRecord \n{:?}", voter_weight_record); + + let token_owner4: TokenOwner = interactor.create_token_owner_record(&realm, voter4_keypair).unwrap(); + let token_owner4: TokenOwner = interactor.setup_voter_weight_record_fixed(&realm, token_owner4).unwrap(); + println!("Token Owner 4 \n{:?}", token_owner4); + + let (voter_weight_record_address,_) = get_voter_weight_address(&voter_weight_addin_pubkey, &realm.address, &community_pubkey, &token_owner4.authority.pubkey()); + let voter_weight_record = interactor.get_voter_weight_record(&voter_weight_record_address); + println!("Token Owner 4 VoterWeightRecord \n{:?}", voter_weight_record); + + let token_owner5: TokenOwner = interactor.create_token_owner_record(&realm, voter5_keypair).unwrap(); + let token_owner5: TokenOwner = interactor.setup_voter_weight_record_fixed(&realm, token_owner5).unwrap(); + println!("Token Owner 5 \n{:?}", token_owner4); + + let (voter_weight_record_address,_) = get_voter_weight_address(&voter_weight_addin_pubkey, &realm.address, &community_pubkey, &token_owner5.authority.pubkey()); + let voter_weight_record = interactor.get_voter_weight_record(&voter_weight_record_address); + println!("Token Owner 5 VoterWeightRecord \n{:?}", voter_weight_record); + + let gov_config: GovernanceConfig = + GovernanceConfig { + vote_threshold_percentage: VoteThresholdPercentage::YesVote(60), + min_community_weight_to_create_proposal: 10, + min_transaction_hold_up_time: 0, + max_voting_time: 78200, + vote_tipping: VoteTipping::Strict, + proposal_cool_off_time: 0, + min_council_weight_to_create_proposal: 0, + }; + + let governance: Governance = interactor.create_governance(&realm, &token_owner1, &governed_account_pubkey, gov_config).unwrap(); + println!("{:?}", governance); + + let proposal_number: u32 = + if governance.get_proposal_count() > 0 { + // governance.get_proposal_count() + 0 + } else { + 0 + }; + let proposal: Proposal = interactor.create_proposal(&realm, &token_owner1, &governance, PROPOSAL_NAME, PROPOSAL_DESCRIPTION, proposal_number).unwrap(); + println!("{:?}", proposal); + + // let result = interactor.add_signatory(&realm, &governance, &proposal, &token_owner); + // println!("Add signatory {:?}", result); + + let proposal: Proposal = + if proposal.data.state == ProposalState::Draft { + interactor.sign_off_proposal(&realm, &governance, proposal, &token_owner1).unwrap() + } else { + proposal + }; + println!("{:?}\n", proposal); + + // // let result = interactor.cast_vote(&realm, &governance, &proposal, &token_owner, Some(max_voter_weight_record_pubkey), true); + let result = interactor.cast_vote(&realm, &governance, &proposal, &token_owner1, true); + println!("{:?}", result); + + let result = interactor.cast_vote(&realm, &governance, &proposal, &token_owner2, false); + println!("{:?}", result); + + // let result = interactor.cast_vote(&realm, &governance, &proposal, &token_owner3, false); + // println!("{:?}", result); + + // let result = interactor.cast_vote(&realm, &governance, &proposal, &token_owner4, true); + // println!("{:?}", result); + + // let result = interactor.cast_vote(&realm, &governance, &proposal, &token_owner5, true); + // println!("{:?}", result); + +} diff --git a/governance-test-scripts/src/tokens.rs b/governance-test-scripts/src/tokens.rs new file mode 100644 index 0000000..1ca70ba --- /dev/null +++ b/governance-test-scripts/src/tokens.rs @@ -0,0 +1,181 @@ +use solana_sdk::{ + pubkey::{ Pubkey }, + instruction::{ Instruction }, + transaction::{ Transaction }, + signer::{ + Signer, + keypair::{ Keypair }, + }, + program_pack::{ Pack }, +}; + +use spl_token::state::{ Account }; + +use solana_client::rpc_client::{ RpcClient }; + +pub fn create_account(client: &RpcClient, owner_keypair: &Keypair, mint_keypair: &Keypair, mint_pubkey: &Pubkey) -> Result { + + let owner_pubkey: Pubkey = owner_keypair.pubkey(); + // let minter_pubkey: Pubkey = minter.pubkey(); + + // let mint_keypair: Keypair = + // if let Ok(keypair) = read_keypair_file(format!("/media/mich/speedwork/NeonLabs/artifacts/dev/token_mints/{}.keypair",token_info.symbol)) { + // keypair + // } else { + // return Err(()) + // }; + // let mint_pubkey = mint_keypair.pubkey(); + + // Create new Supply Account + // let supply_keypair: Keypair = Keypair::new(); + // let supply_pubkey: Pubkey = supply_keypair.pubkey(); + // println!("Supply Account: {}", supply_pubkey); + let associated_token_pubkey: Pubkey = spl_associated_token_account::get_associated_token_address(&owner_pubkey, &mint_pubkey); + + let create_supply_account_instruction: Instruction = + spl_associated_token_account::create_associated_token_account( + &owner_pubkey, + &owner_pubkey, + &mint_pubkey, + ); + // solana_sdk::system_instruction::create_account( + // &owner_pubkey, + // &associated_token_pubkey, + // // 0, + // client.get_minimum_balance_for_rent_exemption(Account::LEN).unwrap(), + // Account::LEN as u64, + // &spl_token::id(), + // ); + + let initialize_supply_account2_instruction: Instruction = + spl_token::instruction::initialize_account2( + &spl_token::id(), + &associated_token_pubkey, + &mint_pubkey, + &owner_pubkey, + ).unwrap(); + + + let transaction: Transaction = + Transaction::new_signed_with_payer( + &vec![ + create_supply_account_instruction, + // initialize_supply_account2_instruction, + ], + Some(&owner_pubkey), + &[ + owner_keypair, + // owner_keypair, + // mint_keypair + ], + client.get_latest_blockhash().unwrap(), + ); + + let result = client.send_and_confirm_transaction(&transaction); + println!("'Create Initialize Supply Account' Transaction Result: {:?}", result); + + Ok(associated_token_pubkey) + // Ok((mint_pubkey, supply_pubkey)) +} + +pub fn mint_tokens(client: &RpcClient, mint_authority: &Keypair, mint_pubkey: &Pubkey, recipient_pubkey: &Pubkey, amount: u64) { + + // let mint_authority: &Keypair = &self.mint_authority.as_ref().unwrap(); + let mint_authority_pubkey: Pubkey = mint_authority.pubkey(); + + let mint_to_instruction: Instruction = + spl_token::instruction::mint_to( + &spl_token::id(), + &mint_pubkey, + &recipient_pubkey, + &mint_authority_pubkey, + &[ &mint_authority_pubkey ], + amount, + ).unwrap(); + + let transaction: Transaction = + Transaction::new_signed_with_payer( + &vec![ mint_to_instruction ], + Some(&mint_authority_pubkey), + &[ mint_authority ], + client.get_latest_blockhash().unwrap(), + ); + + let result = client.send_and_confirm_transaction(&transaction); + println!("Mint result: {:?}", result); +} + +pub fn create_accounts_mint_liquidity(client: &RpcClient, owner_keypair: &Keypair, mint_keypair: &Keypair, mint_pubkey: &Pubkey) { + + let amount: u64 = 10_000_000_000; + // let amount: u64 = 2; + + // let client: SolClient = + // SolClient::new(NETWORK) + // .with_authority(WALLET_FILE_PATH) + // .with_mint_authority(MINTER_FILE_PATH); + + // let owner_keypair: Keypair = read_keypair_file(crate::WALLET_FILE_PATH).unwrap(); + let owner_pubkey: Pubkey = owner_keypair.pubkey(); + // println!("Solana Owner / Payer Pubkey: {}", owner_pubkey); + + let balance = client.get_balance(&owner_pubkey).unwrap(); + println!("Solana Owner / Payer Balance: {}", balance); + if balance == 0 { + println!("No Owner balance!!!"); + return; + } + + // let eth_receiver_address: EthAddress = EthAddress::from_str(receivers[0]).unwrap(); + // println!("NeonEvm Receiver Address : {}\n", eth_receiver_address); + + // let token_infos: Vec = { + // let token_list_string: String = std::fs::read_to_string("/media/mich/speedwork/NeonLabs/artifacts/tokenlist.json").unwrap(); + // let token_list: TokenList = serde_json::from_str(&token_list_string).unwrap(); + // token_list.filter_for_network(&NETWORK).unwrap() + // }; + + // for token_info in token_infos.iter() { + // let mint_pubkey: Pubkey = Pubkey::from_str(&token_info.address_spl.as_ref().unwrap()).unwrap(); + // println!("'{}' [ {} ] : Mint Pubkey: {}", token_info.name, token_info.symbol, mint_pubkey); + + // let amount_expanded: u64 = amount.expand_to_decimals(token_info.decimals).unwrap(); + // let eth_erc20_address: EthAddress = EthAddress::from_str(&token_info.address).unwrap(); + // println!("eth_erc20_address: {}", eth_erc20_address); + + // let supply_keypair: Keypair = create_liquidity(&solana_client, &owner_keypair, &minter_keypair, &mint_pubkey, token_info, amount_expanded).unwrap(); + + // transfer_liquidity(&solana_client, &NETWORK, &owner_keypair, &mint_pubkey, &supply_keypair.pubkey(), &token_info, ð_receiver_address, ð_erc20_address, amount_expanded).unwrap(); + + // let evm_loader_program_id: Pubkey = Pubkey::from_str(NETWORK.get_evm_loader_program_id()).unwrap(); + // let recipient_pubkey: Pubkey = Erc20AccountIdentity::new(ð_receiver_address,ð_erc20_address,&mint_pubkey).derive_pubkey(&evm_loader_program_id); + // let recipient_pubkey: Pubkey = client.create_derived_erc20_identity(ð_receiver_address, ð_erc20_address, &mint_pubkey); + + // match client.get_account(&recipient_pubkey) { + // Ok(_) => { + // println!("'{}' [ {} ] : ERC20 Associated Token Account: {}", token_info.name, token_info.symbol, recipient_pubkey); + // }, + // Err(_) => { + // let result = client.create_associated_token_account(&mint_pubkey, ð_receiver_address, ð_erc20_address); + // match result { + // Ok(_) => + // println!("'{}' [ {} ] : ERC20 Associated Token Account Created: {}", token_info.name, token_info.symbol, recipient_pubkey), + // Err(e) => + // println!("Transaction error while creating associated token account {} for '{}' [ {} ].\n{:?}", recipient_pubkey, token_info.name, token_info.symbol, e), + // } + // } + // }; + + let recipient_pubkey: Pubkey = create_account(&client, &owner_keypair, &mint_keypair, &mint_pubkey).unwrap(); + println!("Recipient_pubkey: {}", recipient_pubkey); + + mint_tokens(client, &owner_keypair, mint_pubkey, &recipient_pubkey, amount); + // match result { + // Ok(_) => + // println!("'{}' [ {} ] : {} Minted To {}", token_info.name, token_info.symbol, amount_expanded, recipient_pubkey), + // Err(e) => + // println!("Transaction error while minting for '{}' [ {} ].\n{:?}", token_info.name, token_info.symbol, e), + // } + // return; + // } +} \ No newline at end of file