diff --git a/addin-vesting/program/src/voter_weight.rs b/addin-vesting/program/src/voter_weight.rs index e8d1b12..028a09e 100644 --- a/addin-vesting/program/src/voter_weight.rs +++ b/addin-vesting/program/src/voter_weight.rs @@ -15,26 +15,26 @@ use spl_governance_tools::account::{ get_account_data, }; -use spl_governance_addin_api::voter_weight::VoterWeightRecord; +pub use spl_governance_addin_api::voter_weight::VoterWeightRecord; /// ExtendedVoterWeightRecord account /// The account is used as an api interface to provide voting power to the governance program /// and to save information about total amount of deposited token #[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] pub struct ExtendedVoterWeightRecord { - base: VoterWeightRecord, + pub base: VoterWeightRecord, /// ExtendedVoterWeightRecord discriminator sha256("account:ExtendedVoterWeightRecord")[..8] /// Note: The discriminator size must match the addin implementing program discriminator size /// to ensure it's stored in the private space of the account data and it's unique - account_discriminator: [u8; 8], + pub account_discriminator: [u8; 8], /// Total number of tokens owned by the account - total_amount: u64, + pub total_amount: u64, /// Percentage of the total number of tokens for calculating the voting weight /// (in hundredths of a percent) - vote_percentage: u16, + pub vote_percentage: u16, } impl ExtendedVoterWeightRecord { diff --git a/governance-lib/src/addin_vesting.rs b/governance-lib/src/addin_vesting.rs index fed7de3..7344725 100644 --- a/governance-lib/src/addin_vesting.rs +++ b/governance-lib/src/addin_vesting.rs @@ -3,6 +3,7 @@ use { client::Client, realm::Realm, }, + borsh::{BorshSerialize}, solana_sdk::{ pubkey::Pubkey, signer::Signer, @@ -10,9 +11,21 @@ use { program_error::ProgramError, }, spl_governance_addin_vesting::{ - state::VestingSchedule, + state::{ + VestingAccountType, + VestingSchedule, + VestingRecord, + }, instruction::{deposit, deposit_with_realm}, - voter_weight::get_voter_weight_record_address, + voter_weight::{ + VoterWeightRecord, + ExtendedVoterWeightRecord, + get_voter_weight_record_address, + }, + max_voter_weight::{ + MaxVoterWeightRecord, + get_max_voter_weight_record_address, + }, }, }; @@ -39,6 +52,54 @@ impl<'a> AddinVesting<'a> { vesting_account } + pub fn get_vesting_account_size(&self, number_of_schedules: u32, realm: bool) -> usize { + let record = VestingRecord { + account_type: VestingAccountType::VestingRecord, + owner: Pubkey::default(), + mint: Pubkey::default(), + token: Pubkey::default(), + realm: if realm {Some(Pubkey::default())} else {None}, + schedule: Vec::new(), + }; + let schedule = VestingSchedule { + release_time: 0, + amount: 0, + }; + record.try_to_vec().unwrap().len() + number_of_schedules as usize * schedule.try_to_vec().unwrap().len() + } + + pub fn get_voter_weight_account_size(&self) -> usize { + let record_data = ExtendedVoterWeightRecord { + base: VoterWeightRecord { + account_discriminator: VoterWeightRecord::ACCOUNT_DISCRIMINATOR, + realm: Pubkey::default(), + governing_token_mint: Pubkey::default(), + governing_token_owner: Pubkey::default(), + voter_weight: 0, + voter_weight_expiry: None, + weight_action: None, + weight_action_target: None, + reserved: [0u8; 8], + }, + account_discriminator: ExtendedVoterWeightRecord::ACCOUNT_DISCRIMINATOR, + total_amount: 0, + vote_percentage: 0, + }; + record_data.try_to_vec().unwrap().len() + } + + pub fn get_max_voter_weight_account_size(&self) -> usize { + let record_data = MaxVoterWeightRecord { + account_discriminator: MaxVoterWeightRecord::ACCOUNT_DISCRIMINATOR, + realm: Pubkey::default(), + governing_token_mint: Pubkey::default(), + max_voter_weight: 0, + max_voter_weight_expiry: None, + reserved: [0u8; 8], + }; + record_data.try_to_vec().unwrap().len() + } + pub fn get_voter_weight_record_address(&self, owner: &Pubkey, realm: &Realm) -> Pubkey { get_voter_weight_record_address( &self.program_id, @@ -47,8 +108,15 @@ impl<'a> AddinVesting<'a> { owner) } + pub fn get_max_voter_weight_record_address(&self, realm: &Realm) -> Pubkey { + get_max_voter_weight_record_address( + &self.program_id, + &realm.realm_address, + &realm.community_mint) + } + pub fn deposit_instruction(&self, source_token_authority: &Pubkey, source_token_account: &Pubkey, - vesting_owner: &Pubkey, vesting_token_account: &Pubkey, schedules: Vec) -> Result + vesting_owner: &Pubkey, vesting_token_account: &Pubkey, schedules: Vec, payer: Option) -> Result { deposit( &self.program_id, @@ -57,13 +125,13 @@ impl<'a> AddinVesting<'a> { &source_token_authority, source_token_account, vesting_owner, - &self.client.payer.pubkey(), + &payer.unwrap_or(self.client.payer.pubkey()), schedules, ) } pub fn deposit_with_realm_instruction(&self, source_token_authority: &Pubkey, source_token_account: &Pubkey, - vesting_owner: &Pubkey, vesting_token_account: &Pubkey, schedules: Vec, realm: &Realm) -> Result + vesting_owner: &Pubkey, vesting_token_account: &Pubkey, schedules: Vec, realm: &Realm, payer: Option) -> Result { deposit_with_realm( &self.program_id, @@ -72,7 +140,7 @@ impl<'a> AddinVesting<'a> { &source_token_authority, source_token_account, vesting_owner, - &self.client.payer.pubkey(), + &payer.unwrap_or(self.client.payer.pubkey()), schedules, &realm.program_id, &realm.realm_address, diff --git a/governance-lib/src/client.rs b/governance-lib/src/client.rs index a8d7390..c69d9f1 100644 --- a/governance-lib/src/client.rs +++ b/governance-lib/src/client.rs @@ -2,6 +2,7 @@ use { crate::errors::GovernanceLibError, borsh::BorshDeserialize, solana_sdk::{ + account::Account, account_utils::StateMut, borsh::try_from_slice_unchecked, commitment_config::CommitmentConfig, @@ -125,15 +126,18 @@ impl<'a> Client<'a> { RpcSendTransactionConfig {skip_preflight: true, ..RpcSendTransactionConfig::default()}).map_err(|e| e.into()) } + pub fn get_account(&self, account_key: &Pubkey) -> ClientResult> { + let account_info = self.solana_client.get_account_with_commitment( + &account_key, self.solana_client.commitment())?.value; + Ok(account_info) + } + pub fn get_account_data_pack( &self, owner_program_id: &Pubkey, account_key: &Pubkey, ) -> ClientResult> { - let account_info = &self.solana_client.get_account_with_commitment( - &account_key, self.solana_client.commitment())?.value; - - if let Some(account_info) = account_info { + if let Some(account_info) = self.get_account(account_key)? { if account_info.data.is_empty() { return Err(GovernanceLibError::StateError(*account_key, "Account is empty".to_string())); } @@ -157,10 +161,7 @@ impl<'a> Client<'a> { owner_program_id: &Pubkey, account_key: &Pubkey, ) -> ClientResult> { - let account_info = &self.solana_client.get_account_with_commitment( - &account_key, self.solana_client.commitment())?.value; - - if let Some(account_info) = account_info { + if let Some(account_info) = self.get_account(account_key)? { if account_info.data.is_empty() { return Err(GovernanceLibError::StateError(*account_key, "Account is empty".to_string())); } diff --git a/governance-lib/src/realm.rs b/governance-lib/src/realm.rs index 7331da9..75d951e 100644 --- a/governance-lib/src/realm.rs +++ b/governance-lib/src/realm.rs @@ -186,13 +186,13 @@ impl<'a> Realm<'a> { self.client.get_account_data_borsh::(&self.program_id, &realm_config_address) } - pub fn set_realm_config_instruction(&self, realm_authority: &Pubkey, realm_config: &RealmConfig) -> Instruction { + pub fn set_realm_config_instruction(&self, realm_authority: &Pubkey, realm_config: &RealmConfig, payer: Option) -> Instruction { set_realm_config( &self.program_id, &self.realm_address, realm_authority, realm_config.council_token_mint, - &self.client.payer.pubkey(), + &payer.unwrap_or(self.client.payer.pubkey()), realm_config.community_voter_weight_addin, realm_config.max_community_voter_weight_addin, realm_config.min_community_weight_to_create_governance, @@ -206,6 +206,7 @@ impl<'a> Realm<'a> { self.set_realm_config_instruction( &realm_authority.pubkey(), realm_config, + None, // default payer ), ], &[realm_authority], diff --git a/launch-script/src/main.rs b/launch-script/src/main.rs index 388cf0d..b6e01dc 100644 --- a/launch-script/src/main.rs +++ b/launch-script/src/main.rs @@ -5,7 +5,12 @@ mod helpers; mod msig; use crate::{ - tokens::{get_mint_data, get_account_data, create_mint_instructions}, + tokens::{ + get_mint_data, + get_account_data, + create_mint_instructions, + assert_is_valid_account_data, + }, errors::{StateError, ScriptError}, wallet::Wallet, helpers::{ @@ -247,6 +252,43 @@ fn process_environment(wallet: &Wallet, client: &Client, setup: bool, verbose: b 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); + // ------------ Transfer tokens to Vesting-addin MaxVoterWeightRecord ------ + { + let record_address = vesting_addin.get_max_voter_weight_record_address(&realm); + let record_length = vesting_addin.get_max_voter_weight_account_size(); + let record_lamports = Rent::default().minimum_balance(record_length); + executor.check_and_create_object("Vesting max_voter_weight_record", + client.get_account(&record_address)?, + |v| { + if v.lamports < record_lamports { + let transaction = client.create_transaction_with_payer_only( + &[ + system_instruction::transfer( + &wallet.payer_keypair.pubkey(), + &record_address, + record_lamports - v.lamports, + ), + ], + )?; + return Ok(Some(transaction)) + } + Ok(None) + }, + || { + let transaction = client.create_transaction_with_payer_only( + &[ + system_instruction::transfer( + &wallet.payer_keypair.pubkey(), + &record_address, + record_lamports, + ), + ], + )?; + Ok(Some(transaction)) + } + )?; + } + // -------------------- Create accounts for token_owner -------------------- let voter_list = fixed_weight_addin.get_voter_list()?; for (i, voter_weight) in voter_list.iter().enumerate() { @@ -280,6 +322,16 @@ fn process_environment(wallet: &Wallet, client: &Client, setup: bool, verbose: b &wallet.community_pubkey, &vesting_addin.find_vesting_account(&vesting_token_account), ).unwrap(), + system_instruction::transfer( // Charge VestingRecord + &wallet.payer_keypair.pubkey(), + &vesting_addin.find_vesting_account(&vesting_token_account), + Rent::default().minimum_balance(vesting_addin.get_vesting_account_size(1, true)), + ), + system_instruction::transfer( + &wallet.payer_keypair.pubkey(), // Charge VoterWeightRecord + &vesting_addin.get_voter_weight_record_address(&voter_weight.voter, &realm), + Rent::default().minimum_balance(vesting_addin.get_voter_weight_account_size()), + ), ], &[&wallet.creator_keypair] )?; @@ -302,12 +354,8 @@ fn process_environment(wallet: &Wallet, client: &Client, setup: bool, verbose: b executor.check_and_create_object(&seed, get_account_data(client, &token_account_address)?, |d| { - if d.mint != wallet.community_pubkey { - return Err(StateError::InvalidTokenAccountMint(token_account_address, d.mint).into()); - } - if d.owner != token_account_owner { - return Err(StateError::InvalidTokenAccountOwner(token_account_address, d.owner).into()); - } + assert_is_valid_account_data(d, &token_account_address, + &wallet.community_pubkey, &token_account_owner)?; Ok(None) }, || { @@ -396,6 +444,33 @@ fn process_environment(wallet: &Wallet, client: &Client, setup: bool, verbose: b } )?; + // --------- Create NEON associated token account ------------- + let governance_token_account = spl_associated_token_account::get_associated_token_address_with_program_id( + &governance.governance_address, &wallet.community_pubkey, &spl_token::id()); + println!("Governance address: {}", governance.governance_address); + println!("Governance token account: {}", governance_token_account); + + executor.check_and_create_object("NEON-token governance account", + get_account_data(client, &governance_token_account)?, + |d| { + assert_is_valid_account_data(d, &governance_token_account, + &wallet.community_pubkey, &governance.governance_address)?; + Ok(None) + }, + || { + let transaction = client.create_transaction_with_payer_only( + &[ + spl_associated_token_account::create_associated_token_account( + &wallet.payer_keypair.pubkey(), + &governance.governance_address, + &wallet.community_pubkey, + ).into(), + ], + )?; + Ok(Some(transaction)) + } + )?; + // --------------- Pass token and programs to governance ------ let mut collector = TransactionCollector::new(client, setup, verbose, "Pass under governance"); // 1. Mint @@ -542,19 +617,6 @@ fn setup_proposal_tge(wallet: &Wallet, client: &Client, proposal_index: Option() + EXTRA_TOKEN_ACCOUNTS.iter().map(|v| v.amount).sum::(); @@ -589,6 +651,7 @@ fn setup_proposal_tge(wallet: &Wallet, client: &Client, proposal_index: Option ClientResult(&spl_token::id(), account) } +pub fn assert_is_valid_account_data(d: &Account, address: &Pubkey, mint: &Pubkey, owner: &Pubkey) -> Result<(),StateError> { + if d.mint != *mint { + return Err(StateError::InvalidTokenAccountMint(*address, d.mint)); + } + if d.owner != *owner { + return Err(StateError::InvalidTokenAccountOwner(*address, d.owner)); + } + Ok(()) +} + pub fn create_mint_instructions(client: &Client, mint_pubkey: &Pubkey, mint_authority: &Pubkey, freeze_authority: Option<&Pubkey>, decimals: u8) -> ClientResult> {