From 506c49e7d46c7ecfc1fbd6a112951158946cd278 Mon Sep 17 00:00:00 2001 From: Semen Medvedev Date: Fri, 22 Apr 2022 17:08:12 +0700 Subject: [PATCH] Simplify the writing of object creation #30 --- governance-lib/src/client.rs | 35 ++ governance-lib/src/realm.rs | 26 +- governance-lib/src/token_owner.rs | 20 +- governance-test-scripts/src/helpers.rs | 249 +++++++++++ launch-script/src/helpers.rs | 249 +++++++++++ launch-script/src/main.rs | 571 ++++++++++++------------- launch-script/src/tokens.rs | 21 + 7 files changed, 846 insertions(+), 325 deletions(-) create mode 100644 governance-test-scripts/src/helpers.rs create mode 100644 launch-script/src/helpers.rs diff --git a/governance-lib/src/client.rs b/governance-lib/src/client.rs index dacbeca..a8d7390 100644 --- a/governance-lib/src/client.rs +++ b/governance-lib/src/client.rs @@ -59,6 +59,41 @@ impl<'a> Client<'a> { } } + pub fn send_transaction(&self, transaction: &Transaction) -> ClientResult { + //self.solana_client.send_and_confirm_transaction(&transaction) + self.solana_client.send_and_confirm_transaction_with_spinner_and_config(&transaction, + self.solana_client.commitment(), + RpcSendTransactionConfig {skip_preflight: true, ..RpcSendTransactionConfig::default()}).map_err(|e| e.into()) + } + + pub fn create_transaction_with_payer_only( + &self, + instructions: &[Instruction], + ) -> ClientResult { + self.create_transaction::<[&dyn solana_sdk::signature::Signer;0]>( + instructions, + &[], + ) + } + + pub fn create_transaction( + &self, + instructions: &[Instruction], + signing_keypairs: &T, + ) -> ClientResult { + let mut transaction: Transaction = + Transaction::new_with_payer( + instructions, + Some(&self.payer.pubkey()), + ); + + let blockhash = self.solana_client.get_latest_blockhash().unwrap(); + transaction.partial_sign(&[self.payer], blockhash); + transaction.sign(signing_keypairs, blockhash); + + Ok(transaction) + } + pub fn send_and_confirm_transaction_with_payer_only( &self, instructions: &[Instruction], diff --git a/governance-lib/src/realm.rs b/governance-lib/src/realm.rs index 3d19761..ac9b32a 100644 --- a/governance-lib/src/realm.rs +++ b/governance-lib/src/realm.rs @@ -83,21 +83,29 @@ impl<'a> Realm<'a> { self.client.get_account_data_borsh::(&self.program_id, &self.realm_address) } + pub fn create_realm_instruction(&self, realm_authority: &Pubkey, voter_weight_addin: Option, max_voter_weight_addin: Option) -> Instruction { + create_realm( + &self.program_id, + &realm_authority, + &self.community_mint, + &self.client.payer.pubkey(), + None, + voter_weight_addin, + max_voter_weight_addin, + self.realm_name.clone(), + MIN_COMMUNITY_WEIGHT_TO_CREATE_GOVERNANCE, + MintMaxVoteWeightSource::FULL_SUPPLY_FRACTION, + ) + } + pub fn create_realm(&self, realm_authority: &'a Keypair, voter_weight_addin: Option, max_voter_weight_addin: Option) -> ClientResult { self.client.send_and_confirm_transaction_with_payer_only( &[ - create_realm( - &self.program_id, + self.create_realm_instruction( &realm_authority.pubkey(), - &self.community_mint, - &self.client.payer.pubkey(), - None, voter_weight_addin, max_voter_weight_addin, - self.realm_name.clone(), - MIN_COMMUNITY_WEIGHT_TO_CREATE_GOVERNANCE, - MintMaxVoteWeightSource::FULL_SUPPLY_FRACTION, - ) + ), ], ) } diff --git a/governance-lib/src/token_owner.rs b/governance-lib/src/token_owner.rs index e5aba1a..35464a3 100644 --- a/governance-lib/src/token_owner.rs +++ b/governance-lib/src/token_owner.rs @@ -70,17 +70,21 @@ impl<'a> TokenOwner<'a> { ) } + pub fn set_delegate_instruction(&self, authority: &Pubkey, new_delegate: &Option) -> Instruction { + set_governance_delegate( + &self.realm.program_id, + &authority, + &self.realm.realm_address, + &self.realm.community_mint, + &self.token_owner_address, + new_delegate, + ) + } + pub fn set_delegate(&self, authority: &Keypair, new_delegate: &Option) -> ClientResult { self.realm.client.send_and_confirm_transaction( &[ - set_governance_delegate( - &self.realm.program_id, - &authority.pubkey(), - &self.realm.realm_address, - &self.realm.community_mint, - &self.token_owner_address, - new_delegate, - ), + self.set_delegate_instruction(&authority.pubkey(), new_delegate), ], &[authority], ) diff --git a/governance-test-scripts/src/helpers.rs b/governance-test-scripts/src/helpers.rs new file mode 100644 index 0000000..84810af --- /dev/null +++ b/governance-test-scripts/src/helpers.rs @@ -0,0 +1,249 @@ +use crate::{ + errors::{StateError, ScriptError}, +}; +use colored::*; +use solana_sdk::{ + signer::{ + Signer, + keypair::Keypair, + }, + instruction::Instruction, + transaction::Transaction, + signature::Signature, +}; + +use spl_governance::{ + state::{ + proposal_transaction::InstructionData, + }, +}; + +use governance_lib::{ + client::Client, + proposal::Proposal, + token_owner::TokenOwner, +}; + + +macro_rules! println_item { + ($format:literal, $($item:expr),*) => { + println!(concat!("\x1b[34m", $format, "\x1b[0m"), $($item),*); + } +} + +macro_rules! println_error { + ($format:literal, $($item:expr),*) => { + println!(concat!("\x1b[31m", $format, "\x1b[0m"), $($item),*); + } +} + +pub struct TransactionExecutor<'a> { + pub client: &'a Client<'a>, + pub setup: bool, + pub verbose: bool, +} + +impl<'a> TransactionExecutor<'a> { + pub fn check_and_create_object(&self, name: &str, + object: Option, verify: V, create: C) -> Result,ScriptError> + where + V: FnOnce(&T) -> Result,ScriptError>, + C: FnOnce() -> Result,ScriptError>, + T: std::fmt::Debug, + { + if let Some(data) = object { + if self.verbose {println_item!("{}: {:?}", name, data);}; + match verify(&data) { + Ok(None) => { + println!("{}: correct", name); + }, + Ok(Some(transaction)) => { + if self.setup { + let result = self.client.send_transaction(&transaction); + match result { + Ok(signature) => { + println!("{}: updated in trx {}", name, signature); + return Ok(Some(signature)); + }, + Err(error) => { + println_error!("{}: failed update with {}", name, error); + return Err(error.into()); + } + }; + } else { + if self.verbose {println_item!("{}: {:?}", name, transaction);}; + println!("{}: will be updated", name); + } + }, + Err(error) => { + println_error!("{}: wrong object {:?}", name, error); + if self.setup {return Err(error);} + } + } + } else { + match create() { + Ok(None) => { + println!("{}: missed ok", name); + }, + Ok(Some(transaction)) => { + if self.setup { + let result = self.client.send_transaction(&transaction); + match result { + Ok(signature) => { + println!("{}: created in trx {}", name, signature); + return Ok(Some(signature)); + }, + Err(error) => { + println_error!("{}: failed create with {}", name, error); + return Err(error.into()); + } + }; + } else { + if self.verbose {println_item!("{}: {:?}", name, transaction);}; + println!("{}: will be created", name); + } + }, + Err(error) => { + println_error!("{}: can't be created: {:?}", name, error); + if self.setup {return Err(error);} + } + } + } + Ok(None) + } +} + +pub struct TransactionCollector<'a> { + pub client: &'a Client<'a>, + pub setup: bool, + pub verbose: bool, + pub name: String, + instructions: Vec, + signers: Vec<&'a dyn Signer>, +} + +impl<'a> TransactionCollector<'a> { + pub fn new(client: &'a Client<'a>, setup: bool, verbose: bool, name: &str) -> Self { + println!("{}: collect instructions...", name); + Self { + client, + setup, + verbose, + name: name.to_string(), + instructions: Vec::new(), + signers: Vec::new(), + } + } + + fn add_signers(&mut self, keypairs: Vec<&'a Keypair>) { + for keypair in keypairs { + self.signers.push(keypair as &dyn Signer); + } + } + + pub fn check_and_create_object(&mut self, name: &str, + object: Option, verify: V, create: C) -> Result<(),ScriptError> + where + V: FnOnce(&T) -> Result,Vec<&'a Keypair>)>,ScriptError>, + C: FnOnce() -> Result,Vec<&'a Keypair>)>,ScriptError>, + T: std::fmt::Debug, + { + if let Some(data) = object { + if self.verbose {println_item!("{}: {:?}", name, data);}; + match verify(&data) { + Ok(None) => { + println!("{}: correct", name); + }, + Ok(Some((instructions,signers,))) => { + if self.verbose {println_item!("{}: {:?}", name, instructions);}; + println!("{}: update instructions was added", name); + self.instructions.extend(instructions); + self.add_signers(signers); + }, + Err(error) => { + println_error!("{}: wrong object {:?}", name, error); + if self.setup {return Err(error);} + } + } + } else { + match create() { + Ok(None) => { + println!("{}: missed ok", name); + }, + Ok(Some((instructions,signers,))) => { + if self.verbose {println_item!("{}: {:?}", name, instructions);}; + println!("{}: create instructions was added", name); + self.instructions.extend(instructions); + self.add_signers(signers); + }, + Err(error) => { + println_error!("{}: can't be created: {:?}", name, error); + if self.setup {return Err(error);} + } + } + } + Ok(()) + } + + pub fn execute_transaction(&self) -> Result,ScriptError> { + if self.setup { + let result = if self.signers.is_empty() { + self.client.send_and_confirm_transaction_with_payer_only(&self.instructions) + } else { + self.client.send_and_confirm_transaction(&self.instructions, &self.signers) + }; + match result { + Ok(signature) => { + println!("{}: processed in trx {}", self.name, signature); + return Ok(Some(signature)); + }, + Err(error) => { + println_error!("{}: failed process with {}", self.name, error); + return Err(error.into()); + } + } + } else { + println!("{}: no instructions for execute", self.name); + Ok(None) + } + } +} + +pub struct ProposalTransactionInserter<'a> { + pub proposal: &'a Proposal<'a>, + pub creator_keypair: &'a Keypair, + pub creator_token_owner: &'a TokenOwner<'a>, + pub hold_up_time: u32, + pub setup: bool, + pub verbose: bool, + + pub proposal_transaction_index: u16, +} + +impl<'a> ProposalTransactionInserter<'a> { + pub fn insert_transaction_checked(&mut self, name: &str, instructions: Vec) -> Result<(), ScriptError> { + if let Some(transaction_data) = self.proposal.get_proposal_transaction_data(0, self.proposal_transaction_index)? { + if self.verbose {println_item!("Proposal transaction '{}'/{}: {:?}", name, self.proposal_transaction_index, transaction_data);}; + if transaction_data.instructions != instructions { + let error = StateError::InvalidProposalTransaction(self.proposal_transaction_index); + if self.setup {return Err(error.into())} else {println_error!("Proposal transaction '{}'/{}: {:?}", name, self.proposal_transaction_index, error);} + } else { + println!("Proposal transaction '{}'/{} correct", name, self.proposal_transaction_index); + } + } else if self.setup { + let signature = self.proposal.insert_transaction( + &self.creator_keypair, + &self.creator_token_owner, + 0, self.proposal_transaction_index, self.hold_up_time, + instructions + )?; + println!("Proposal transaction '{}'/{} was inserted in trx: {}", name, self.proposal_transaction_index, signature); + } else { + println!("Proposal transaction '{}'/{} will be inserted", name, self.proposal_transaction_index); + } + self.proposal_transaction_index += 1; + Ok(()) + } +} + + diff --git a/launch-script/src/helpers.rs b/launch-script/src/helpers.rs new file mode 100644 index 0000000..84810af --- /dev/null +++ b/launch-script/src/helpers.rs @@ -0,0 +1,249 @@ +use crate::{ + errors::{StateError, ScriptError}, +}; +use colored::*; +use solana_sdk::{ + signer::{ + Signer, + keypair::Keypair, + }, + instruction::Instruction, + transaction::Transaction, + signature::Signature, +}; + +use spl_governance::{ + state::{ + proposal_transaction::InstructionData, + }, +}; + +use governance_lib::{ + client::Client, + proposal::Proposal, + token_owner::TokenOwner, +}; + + +macro_rules! println_item { + ($format:literal, $($item:expr),*) => { + println!(concat!("\x1b[34m", $format, "\x1b[0m"), $($item),*); + } +} + +macro_rules! println_error { + ($format:literal, $($item:expr),*) => { + println!(concat!("\x1b[31m", $format, "\x1b[0m"), $($item),*); + } +} + +pub struct TransactionExecutor<'a> { + pub client: &'a Client<'a>, + pub setup: bool, + pub verbose: bool, +} + +impl<'a> TransactionExecutor<'a> { + pub fn check_and_create_object(&self, name: &str, + object: Option, verify: V, create: C) -> Result,ScriptError> + where + V: FnOnce(&T) -> Result,ScriptError>, + C: FnOnce() -> Result,ScriptError>, + T: std::fmt::Debug, + { + if let Some(data) = object { + if self.verbose {println_item!("{}: {:?}", name, data);}; + match verify(&data) { + Ok(None) => { + println!("{}: correct", name); + }, + Ok(Some(transaction)) => { + if self.setup { + let result = self.client.send_transaction(&transaction); + match result { + Ok(signature) => { + println!("{}: updated in trx {}", name, signature); + return Ok(Some(signature)); + }, + Err(error) => { + println_error!("{}: failed update with {}", name, error); + return Err(error.into()); + } + }; + } else { + if self.verbose {println_item!("{}: {:?}", name, transaction);}; + println!("{}: will be updated", name); + } + }, + Err(error) => { + println_error!("{}: wrong object {:?}", name, error); + if self.setup {return Err(error);} + } + } + } else { + match create() { + Ok(None) => { + println!("{}: missed ok", name); + }, + Ok(Some(transaction)) => { + if self.setup { + let result = self.client.send_transaction(&transaction); + match result { + Ok(signature) => { + println!("{}: created in trx {}", name, signature); + return Ok(Some(signature)); + }, + Err(error) => { + println_error!("{}: failed create with {}", name, error); + return Err(error.into()); + } + }; + } else { + if self.verbose {println_item!("{}: {:?}", name, transaction);}; + println!("{}: will be created", name); + } + }, + Err(error) => { + println_error!("{}: can't be created: {:?}", name, error); + if self.setup {return Err(error);} + } + } + } + Ok(None) + } +} + +pub struct TransactionCollector<'a> { + pub client: &'a Client<'a>, + pub setup: bool, + pub verbose: bool, + pub name: String, + instructions: Vec, + signers: Vec<&'a dyn Signer>, +} + +impl<'a> TransactionCollector<'a> { + pub fn new(client: &'a Client<'a>, setup: bool, verbose: bool, name: &str) -> Self { + println!("{}: collect instructions...", name); + Self { + client, + setup, + verbose, + name: name.to_string(), + instructions: Vec::new(), + signers: Vec::new(), + } + } + + fn add_signers(&mut self, keypairs: Vec<&'a Keypair>) { + for keypair in keypairs { + self.signers.push(keypair as &dyn Signer); + } + } + + pub fn check_and_create_object(&mut self, name: &str, + object: Option, verify: V, create: C) -> Result<(),ScriptError> + where + V: FnOnce(&T) -> Result,Vec<&'a Keypair>)>,ScriptError>, + C: FnOnce() -> Result,Vec<&'a Keypair>)>,ScriptError>, + T: std::fmt::Debug, + { + if let Some(data) = object { + if self.verbose {println_item!("{}: {:?}", name, data);}; + match verify(&data) { + Ok(None) => { + println!("{}: correct", name); + }, + Ok(Some((instructions,signers,))) => { + if self.verbose {println_item!("{}: {:?}", name, instructions);}; + println!("{}: update instructions was added", name); + self.instructions.extend(instructions); + self.add_signers(signers); + }, + Err(error) => { + println_error!("{}: wrong object {:?}", name, error); + if self.setup {return Err(error);} + } + } + } else { + match create() { + Ok(None) => { + println!("{}: missed ok", name); + }, + Ok(Some((instructions,signers,))) => { + if self.verbose {println_item!("{}: {:?}", name, instructions);}; + println!("{}: create instructions was added", name); + self.instructions.extend(instructions); + self.add_signers(signers); + }, + Err(error) => { + println_error!("{}: can't be created: {:?}", name, error); + if self.setup {return Err(error);} + } + } + } + Ok(()) + } + + pub fn execute_transaction(&self) -> Result,ScriptError> { + if self.setup { + let result = if self.signers.is_empty() { + self.client.send_and_confirm_transaction_with_payer_only(&self.instructions) + } else { + self.client.send_and_confirm_transaction(&self.instructions, &self.signers) + }; + match result { + Ok(signature) => { + println!("{}: processed in trx {}", self.name, signature); + return Ok(Some(signature)); + }, + Err(error) => { + println_error!("{}: failed process with {}", self.name, error); + return Err(error.into()); + } + } + } else { + println!("{}: no instructions for execute", self.name); + Ok(None) + } + } +} + +pub struct ProposalTransactionInserter<'a> { + pub proposal: &'a Proposal<'a>, + pub creator_keypair: &'a Keypair, + pub creator_token_owner: &'a TokenOwner<'a>, + pub hold_up_time: u32, + pub setup: bool, + pub verbose: bool, + + pub proposal_transaction_index: u16, +} + +impl<'a> ProposalTransactionInserter<'a> { + pub fn insert_transaction_checked(&mut self, name: &str, instructions: Vec) -> Result<(), ScriptError> { + if let Some(transaction_data) = self.proposal.get_proposal_transaction_data(0, self.proposal_transaction_index)? { + if self.verbose {println_item!("Proposal transaction '{}'/{}: {:?}", name, self.proposal_transaction_index, transaction_data);}; + if transaction_data.instructions != instructions { + let error = StateError::InvalidProposalTransaction(self.proposal_transaction_index); + if self.setup {return Err(error.into())} else {println_error!("Proposal transaction '{}'/{}: {:?}", name, self.proposal_transaction_index, error);} + } else { + println!("Proposal transaction '{}'/{} correct", name, self.proposal_transaction_index); + } + } else if self.setup { + let signature = self.proposal.insert_transaction( + &self.creator_keypair, + &self.creator_token_owner, + 0, self.proposal_transaction_index, self.hold_up_time, + instructions + )?; + println!("Proposal transaction '{}'/{} was inserted in trx: {}", name, self.proposal_transaction_index, signature); + } else { + println!("Proposal transaction '{}'/{} will be inserted", name, self.proposal_transaction_index); + } + self.proposal_transaction_index += 1; + Ok(()) + } +} + + diff --git a/launch-script/src/main.rs b/launch-script/src/main.rs index 04d574a..6940cf3 100644 --- a/launch-script/src/main.rs +++ b/launch-script/src/main.rs @@ -1,12 +1,18 @@ mod errors; mod tokens; mod wallet; +mod helpers; mod token_distribution; use crate::{ - tokens::{get_mint_data, get_account_data, create_mint}, + tokens::{get_mint_data, get_account_data, create_mint, create_mint_instructions}, errors::{StateError, ScriptError}, wallet::Wallet, + helpers::{ + TransactionExecutor, + TransactionCollector, + ProposalTransactionInserter, + }, }; use colored::*; use solana_sdk::{ @@ -15,7 +21,9 @@ use solana_sdk::{ Signer, keypair::{Keypair, read_keypair_file}, }, + instruction::Instruction, transaction::Transaction, + signature::Signature, system_instruction, rent::Rent, }; @@ -73,18 +81,6 @@ const REALM_NAME: &str = "Test_Realm_9"; //const PROPOSAL_NAME: &str = "Token Genesis Event"; //const PROPOSAL_DESCRIPTION: &str = "proposal_description"; -macro_rules! println_item { - ($format:literal, $($item:expr),*) => { - println!(concat!("\x1b[34m", $format, "\x1b[0m"), $($item),*); - } -} - -macro_rules! println_error { - ($format:literal, $($item:expr),*) => { - println!(concat!("\x1b[31m", $format, "\x1b[0m"), $($item),*); - } -} - enum ExtraTokenAccountOwner { MainGovernance, EmergencyGovernance, @@ -105,83 +101,63 @@ const extra_token_accounts: [ExtraTokenAccount;3] = [ -fn process_environment(wallet: &Wallet, client: &Client, setup: bool) -> Result<(), ScriptError> { +fn process_environment(wallet: &Wallet, client: &Client, setup: bool, 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); + let executor = TransactionExecutor {client, setup, verbose}; // ----------- Check or create community mint ---------------------- - let mint_data = get_mint_data(client, &wallet.community_pubkey)?; - if let Some(mint_info) = mint_data { - println_item!("Mint: {:?}", mint_info); - if mint_info.mint_authority.contains(&wallet.creator_keypair.pubkey()) { - // All ok: mint exist and creator can mint - println!("Mint {} exists with mint-authority belongs to creator {}", - &wallet.community_pubkey, &wallet.creator_keypair.pubkey()); - - } else if mint_info.mint_authority.contains(&governance.governance_address) { - // All ok: mint exist but governance can mint - // It seems like environment was already setup - println!("Mint {} exists with mint-authority belongs to governance {}", - &wallet.community_pubkey, &governance.governance_address); - } else { - // Error: mint authority doesn't belong to creator or governance - let error = StateError::InvalidMintAuthority(wallet.community_pubkey, mint_info.mint_authority); - if setup {return Err(error.into())} else {println_error!("{:?}", error);}; - } - - if mint_info.freeze_authority.is_some() { - // Error: freeze authority should be None - let error = StateError::InvalidFreezeAuthority(wallet.community_pubkey, mint_info.freeze_authority); - if setup {return Err(error.into())} else {println_error!("{:?}", error);}; - } - } else { - if setup { - let signature = create_mint( - &client, - &wallet.community_keypair, - &wallet.creator_keypair.pubkey(), - None, - 6, - )?; - println!("Mint {} created in trx: {}", &wallet.community_pubkey, signature); - } else { - println!("Mint {} missed", &wallet.community_pubkey); - } - } + executor.check_and_create_object("Mint", get_mint_data(client, &wallet.community_pubkey)?, + |d| { + if !d.mint_authority.contains(&wallet.creator_keypair.pubkey()) && + !d.mint_authority.contains(&governance.governance_address) { + return Err(StateError::InvalidMintAuthority(wallet.community_pubkey, d.mint_authority).into()); + } + Ok(None) + }, + || { + let transaction = client.create_transaction( + &create_mint_instructions( + &client, + &wallet.community_keypair.pubkey(), + &wallet.creator_keypair.pubkey(), + None, + 6, + )?, + &[&wallet.community_keypair], + )?; + Ok(Some(transaction)) + }, + )?; // -------------- Check or create Realm --------------------------- - if let Some(realm_data) = realm.get_data()? { - println_item!("Realm: {:?}", realm_data); - if realm_data.community_mint != realm.community_mint { - let error = StateError::InvalidRealmCommunityMint(realm.realm_address, realm_data.community_mint); - if setup {return Err(error.into())} else {println_error!("{:?}", error);}; - } - if realm_data.authority == Some(wallet.creator_keypair.pubkey()) { - println!("Realm {} exists with authority belongs to creator {}", - realm.realm_address, &wallet.creator_keypair.pubkey()); - } else if realm_data.authority == Some(governance.governance_address) { - println!("Realm {} exists with authority belongs to governance {}", - realm.realm_address, &governance.governance_address); - } else { - let error = StateError::InvalidRealmAuthority(realm.realm_address, realm_data.authority); - if setup {return Err(error.into())} else {println_error!("{:?}", error);}; - } - // TODO Check realm config structure (for right addin)! - } else { - if setup { - let signature = realm.create_realm( - &wallet.creator_keypair, - Some(wallet.fixed_weight_addin_id), - Some(wallet.fixed_weight_addin_id), + executor.check_and_create_object("Realm", realm.get_data()?, + |d| { + if d.community_mint != realm.community_mint { + return Err(StateError::InvalidRealmCommunityMint(realm.realm_address, d.community_mint).into()); + } + if d.authority != Some(wallet.creator_keypair.pubkey()) && + d.authority != Some(governance.governance_address) { + return Err(StateError::InvalidRealmAuthority(realm.realm_address, d.authority).into()); + } + Ok(None) + }, + || { + let transaction = client.create_transaction_with_payer_only( + &[ + realm.create_realm_instruction( + &wallet.creator_keypair.pubkey(), + Some(wallet.fixed_weight_addin_id), + Some(wallet.fixed_weight_addin_id), + ), + ], )?; - println!("Realm {} created in trx: {}", realm.realm_address, signature); - } else { - println!("Realm {} missed", &realm.realm_address); - } - } + Ok(Some(transaction)) + }, + )?; // ------------ Setup and configure max_voter_weight_record ---------------- // TODO check max_voter_weight_record_address created correctly @@ -196,12 +172,13 @@ fn process_environment(wallet: &Wallet, client: &Client, setup: bool) -> Result< let seed: String = format!("{}_vesting_{}", REALM_NAME, i); let vesting_token_account = Pubkey::create_with_seed(&wallet.creator_keypair.pubkey(), &seed, &spl_token::id())?; - if let Some(token_owner_record) = token_owner_record.get_data()? { - // TODO check that all accounts needed to this owner created correctly - println!("token_owner_record {} exists", voter_weight.voter); - } else { - if setup { - let signature = client.send_and_confirm_transaction( + executor.check_and_create_object(&seed, token_owner_record.get_data()?, + |d| { + // TODO check that all accounts needed to this owner created correctly + Ok(None) + }, + || { + let transaction = client.create_transaction( &[ token_owner_record.create_token_owner_record_instruction(), fixed_weight_addin.setup_voter_weight_record_instruction( @@ -224,11 +201,9 @@ fn process_environment(wallet: &Wallet, client: &Client, setup: bool) -> Result< ], &[&wallet.creator_keypair] )?; - println!("token_owner_record {} created in trx: {}", voter_weight.voter, signature); - } else { - println!("Missing token_owner_record for {}", voter_weight.voter); + Ok(Some(transaction)) } - } + )?; } // -------------------- Create extra token accounts ------------------------ @@ -242,72 +217,74 @@ fn process_environment(wallet: &Wallet, client: &Client, setup: bool) -> Result< }; println!("Extra token account '{}' {}", token_account.name, token_account_address); - if let Some(token_account_data) = get_account_data(client, &token_account_address)? { - if token_account_data.mint != wallet.community_pubkey { - let error = StateError::InvalidTokenAccountMint(token_account_address, token_account_data.mint); - if setup {return Err(error.into())} else {println_error!("{:?}", error);}; - } - if token_account_data.owner != token_account_owner { - let error = StateError::InvalidTokenAccountOwner(token_account_address, token_account_data.owner); - if setup {return Err(error.into())} else {println_error!("{:?}", error);}; + 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()); + } + Ok(None) + }, + || { + let transaction = client.create_transaction( + &[ + system_instruction::create_account_with_seed( + &wallet.payer_keypair.pubkey(), // from + &token_account_address, // to + &wallet.creator_keypair.pubkey(), // base + &seed, // seed + Rent::default().minimum_balance(165), // lamports + 165, // space + &spl_token::id(), // owner + ), + spl_token::instruction::initialize_account( + &spl_token::id(), + &token_account_address, + &wallet.community_pubkey, + &token_account_owner, + ).unwrap(), + ], + &[&wallet.creator_keypair] + )?; + Ok(Some(transaction)) } - } else if setup { - let signature = client.send_and_confirm_transaction( - &[ - system_instruction::create_account_with_seed( - &wallet.payer_keypair.pubkey(), // from - &token_account_address, // to - &wallet.creator_keypair.pubkey(), // base - &seed, // seed - Rent::default().minimum_balance(165), // lamports - 165, // space - &spl_token::id(), // owner - ), - spl_token::instruction::initialize_account( - &spl_token::id(), - &token_account_address, - &wallet.community_pubkey, - &token_account_owner, - ).unwrap(), - ], - &[&wallet.creator_keypair] - )?; - println!("Extra token account '{}' {} created in trx: {}", token_account.name, token_account_address, signature); - } else { - println!("Extra token account '{}' {} missed", token_account.name, token_account_address); - } + )?; } // ----------- Build creator_token_owner record --------------- // TODO it is correct for fixed_weight_addin! - 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: &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)); // TODO setup delegate through multisig - let creator_token_owner_data = creator_token_owner.get_data()?; - println_item!("Creator token_owner_record: {:?}", creator_token_owner_data); - if let Some(creator_token_owner_data) = creator_token_owner_data { - if let Some(delegate) = creator_token_owner_data.governance_delegate { - if delegate == wallet.creator_keypair.pubkey() { - println!("Creator token_owner has correct delegate {}", delegate); + executor.check_and_create_object("Delegate for creator_token_owner", + creator_token_owner_record.get_data()?, + |d| { + if let Some(delegate) = d.governance_delegate { + if delegate == wallet.creator_keypair.pubkey() { + return Ok(None); + } else { + return Err(StateError::InvalidDelegate(creator_token_owner.pubkey(), Some(delegate)).into()); + } } else { - let error = StateError::InvalidDelegate(voter_list[0].voter, Some(delegate)); - if setup {return Err(error.into())} else {println_error!("{:?}", error);}; - } - } else { - if setup { - let signature = creator_token_owner.set_delegate(&wallet.voter_keypairs[0], &Some(wallet.creator_keypair.pubkey()))?; - println!("Creator token_owner delegate set in trx: {}", signature); - } else { - println!("Creator token_owner delegate doesn't installed"); + let transaction = client.create_transaction( + &[ + creator_token_owner_record.set_delegate_instruction( + &creator_token_owner.pubkey(), + &Some(wallet.creator_keypair.pubkey()), + ), + ], + &[creator_token_owner], + )?; + Ok(Some(transaction)) } - } - } else { - let error = StateError::MissingTokenOwnerRecord(voter_list[0].voter); - if setup {return Err(error.into())} else {println_error!("{:?}", error);}; - } + }, + || {return Err(StateError::MissingTokenOwnerRecord(creator_token_owner.pubkey()).into());} + )?; // ------------- Setup main governance ------------------------ let gov_config: GovernanceConfig = @@ -321,149 +298,115 @@ fn process_environment(wallet: &Wallet, client: &Client, setup: bool) -> Result< min_council_weight_to_create_proposal: 0, }; - if let Some(governance_data) = governance.get_data()? { - // TODO check governance config - println_item!("Governance: {:?}", governance_data); - println!("Governance {} exists", governance.governance_address); - } else { - if setup { - let signature = client.send_and_confirm_transaction( + executor.check_and_create_object("Governance", governance.get_data()?, + |d| {Ok(None)}, + || { + let transaction = client.create_transaction( &[ governance.create_governance_instruction( &wallet.creator_keypair.pubkey(), - &creator_token_owner, + &creator_token_owner_record, gov_config ), ], &[&wallet.creator_keypair] )?; - println!("Governance {} created in trx: {}", governance.governance_address, signature); - } else { - println!("Missing governance {}", governance.governance_address); + Ok(Some(transaction)) } - } + )?; // --------------- Pass token and programs to governance ------ + let mut collector = TransactionCollector::new(client, setup, verbose, "Pass under governance"); // 1. Mint - let mut instructions = vec!(); - let mint_data = get_mint_data(client, &wallet.community_pubkey)?; - if let Some(mint_data) = mint_data { - if mint_data.mint_authority.contains(&wallet.creator_keypair.pubkey()) { - instructions.push( - spl_token::instruction::set_authority( - &spl_token::id(), - &wallet.community_pubkey, - Some(&governance.governance_address), - spl_token::instruction::AuthorityType::MintTokens, - &wallet.creator_keypair.pubkey(), - &[], - ).unwrap() - ); - } else if mint_data.mint_authority.contains(&governance.governance_address) { - // Ok, mint already under governance authority - } else { - let error = StateError::InvalidMintAuthority(wallet.community_pubkey, mint_data.mint_authority); - if setup {return Err(error.into())} else {println_error!("{:?}", error);} - } - } else { - let error = StateError::MissingMint(wallet.community_pubkey); - if setup {return Err(error.into())} else {println_error!("{:?}", error);} - } + collector.check_and_create_object("NEON-token mint-authority", + get_mint_data(client, &wallet.community_pubkey)?, + |d| { + if d.mint_authority.contains(&wallet.creator_keypair.pubkey()) { + let instructions = [ + spl_token::instruction::set_authority( + &spl_token::id(), + &wallet.community_pubkey, + Some(&governance.governance_address), + spl_token::instruction::AuthorityType::MintTokens, + &wallet.creator_keypair.pubkey(), + &[], + ).unwrap() + ].to_vec(); + let signers = [&wallet.creator_keypair].to_vec(); + Ok(Some((instructions, signers,))) + } else if d.mint_authority.contains(&governance.governance_address) { + Ok(None) + } else { + Err(StateError::InvalidMintAuthority(wallet.community_pubkey, d.mint_authority).into()) + } + }, + || {Err(StateError::MissingMint(wallet.community_pubkey).into())}, + )?; // 2. Realm - let realm_data = realm.get_data()?; - if let Some(realm_data) = realm_data { - if realm_data.authority == Some(wallet.creator_keypair.pubkey()) { - instructions.push( - realm.set_realm_authority_instruction( - &wallet.creator_keypair.pubkey(), - Some(&governance.governance_address), - SetRealmAuthorityAction::SetChecked, - ) - ); - } else if realm_data.authority == Some(governance.governance_address) { - // Ok, realm already under governance authority - } else { - let error = StateError::InvalidRealmAuthority(realm.realm_address, realm_data.authority); - if setup {return Err(error.into())} else {println_error!("{:?}", error);} - } - } else { - let error = StateError::MissingRealm(realm.realm_address); - if setup {return Err(error.into())} else {println_error!("{:?}", error);} - } + collector.check_and_create_object("Realm authority", realm.get_data()?, + |d| { + if d.authority == Some(wallet.creator_keypair.pubkey()) { + let instructions = [ + realm.set_realm_authority_instruction( + &wallet.creator_keypair.pubkey(), + Some(&governance.governance_address), + SetRealmAuthorityAction::SetChecked, + ) + ].to_vec(); + let signers = [&wallet.creator_keypair].to_vec(); + Ok(Some((instructions, signers,))) + } else if d.authority == Some(governance.governance_address) { + Ok(None) + } else { + Err(StateError::InvalidRealmAuthority(realm.realm_address, d.authority).into()) + } + }, + || {Err(StateError::MissingRealm(realm.realm_address).into())} + )?; // 3. Programs... - for program in [&wallet.governance_program_id, &wallet.fixed_weight_addin_id, &wallet.vesting_addin_id,] { - let upgrade_authority = client.get_program_upgrade_authority(program)?; - if upgrade_authority == Some(wallet.creator_keypair.pubkey()) { - println!("Program upgrade-authority for {} will be changed to {}", program, governance.governance_address); - instructions.push( - client.set_program_upgrade_authority_instruction( - program, - &wallet.creator_keypair.pubkey(), - Some(&governance.governance_address), - )? - ); - } else if upgrade_authority == Some(governance.governance_address) { - // Ok, program already under governance authority - println!("Program upgrade-authority for {} belongs to governance {}", program, governance.governance_address); - } else { - let error = StateError::InvalidProgramUpgradeAuthority(*program, upgrade_authority); - if setup {return Err(error.into())} else {println_error!("{:?}", error);} - } - } - if setup && !instructions.is_empty() { - client.send_and_confirm_transaction( - &instructions, - &[&wallet.creator_keypair], - ).unwrap(); + for (name,program) in [ + ("spl-governance", &wallet.governance_program_id), + ("fixed-weight-addin", &wallet.fixed_weight_addin_id), + ("vesting-addin", &wallet.vesting_addin_id), + ] + { + collector.check_and_create_object(&format!("{} upgrade-authority", name), + Some(client.get_program_upgrade_authority(program)?), + |&upgrade_authority| { + if upgrade_authority == Some(wallet.creator_keypair.pubkey()) { + let instructions = [ + client.set_program_upgrade_authority_instruction( + program, + &wallet.creator_keypair.pubkey(), + Some(&governance.governance_address), + )? + ].to_vec(); + let signers = [&wallet.creator_keypair].to_vec(); + Ok(Some((instructions, signers,))) + } else if upgrade_authority == Some(governance.governance_address) { + Ok(None) + } else { + Err(StateError::InvalidProgramUpgradeAuthority(*program, upgrade_authority).into()) + } + }, + || {unreachable!()}, + )?; } + collector.execute_transaction()?; + Ok(()) } -struct ProposalTransactionInserter<'a> { - pub proposal: &'a Proposal<'a>, - pub creator_keypair: &'a Keypair, - pub creator_token_owner: &'a TokenOwner<'a>, - pub hold_up_time: u32, - pub setup: bool, - - pub proposal_transaction_index: u16, -} - -impl<'a> ProposalTransactionInserter<'a> { - pub fn insert_transaction_checked(&mut self, name: &str, instructions: Vec) -> Result<(), ScriptError> { - if let Some(transaction_data) = self.proposal.get_proposal_transaction_data(0, self.proposal_transaction_index)? { - println_item!("Proposal transaction '{}'/{}: {:?}", name, self.proposal_transaction_index, transaction_data); - if transaction_data.instructions != instructions { - let error = StateError::InvalidProposalTransaction(self.proposal_transaction_index); - if self.setup {return Err(error.into())} else {println_error!("{:?}", error);} - } else { - println!("Proposal transaction '{}'/{} correct", name, self.proposal_transaction_index); - } - } else if self.setup { - let signature = self.proposal.insert_transaction( - &self.creator_keypair, - &self.creator_token_owner, - 0, self.proposal_transaction_index, self.hold_up_time, - instructions - )?; - println!("Proposal transaction '{}'/{} was inserted in trx: {}", name, self.proposal_transaction_index, signature); - } else { - println!("Proposal transaction '{}'/{} will be inserted", name, self.proposal_transaction_index); - } - self.proposal_transaction_index += 1; - Ok(()) - } -} - // ========================================================================= // Create TGE proposal (Token Genesis Event) // ========================================================================= -fn setup_proposal_tge(wallet: &Wallet, client: &Client, proposal_index: Option, setup: bool) -> Result<(), ScriptError> { +fn setup_proposal_tge(wallet: &Wallet, client: &Client, proposal_index: Option, setup: bool, verbose: bool) -> Result<(), ScriptError> { + let executor = TransactionExecutor {client, setup, verbose}; 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); @@ -491,12 +434,11 @@ 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); + 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)); + 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()); + } + + if proposal.get_state()? == ProposalState::Draft { proposal.sign_off_proposal(&wallet.creator_keypair, &creator_token_owner)?; } Ok(()) } -fn approve_proposal(wallet: &Wallet, client: &Client, proposal_index: Option) -> Result<(), ScriptError> { +fn approve_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); @@ -682,7 +652,7 @@ fn approve_proposal(wallet: &Wallet, client: &Client, proposal_index: Option) -> Result<(), ScriptError> { +fn execute_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 governance = realm.governance(&wallet.governed_account_pubkey); @@ -716,21 +686,13 @@ fn main() { .help("Show additional information") ) .arg( - Arg::with_name("force") - .long("force") - .short("f") + Arg::with_name("send_trx") + .long("send-trx") .takes_value(false) - .global(true) - .help("Force execution dangerous actions") + .help("Send transactions to blockchain") ) .subcommand(SubCommand::with_name("environment") .about("Prepare environment for launching") - .subcommand(SubCommand::with_name("check") - .about("Check environemnt") - ) - .subcommand(SubCommand::with_name("setup") - .about("Setup environment") - ) ) .subcommand(SubCommand::with_name("proposal") .about("Prepare and execute proposal") @@ -742,15 +704,12 @@ fn main() { .value_name("PROPOSAL_INDEX") .help("Proposal index") ) - .arg( - Arg::with_name("send_trx") - .long("send-trx") - .takes_value(false) - .help("Send transactions to blockchain") - ) .subcommand(SubCommand::with_name("create-tge") .about("Create Token Genesis Event proposal") ) + .subcommand(SubCommand::with_name("sign-off") + .about("Sign Off proposal") + ) .subcommand(SubCommand::with_name("approve") .about("Approve proposal") ) @@ -764,30 +723,26 @@ fn main() { let client = Client::new("http://localhost:8899", &wallet.payer_keypair); + let send_trx: bool = matches.is_present("send_trx"); + let verbose: bool = matches.is_present("verbose"); match matches.subcommand() { ("environment", Some(arg_matches)) => { - match arg_matches.subcommand() { - ("check", Some(arg_matches)) => { - process_environment(&wallet, &client, false).unwrap() - }, - ("setup", Some(arg_matches)) => { - process_environment(&wallet, &client, true).unwrap() - }, - _ => unreachable!(), - } + process_environment(&wallet, &client, send_trx, verbose).unwrap() }, ("proposal", Some(arg_matches)) => { let proposal_index = arg_matches.value_of("index").map(|v| v.parse::().unwrap()); - let send_trx: bool = arg_matches.is_present("send_trx"); match arg_matches.subcommand() { ("create-tge", Some(arg_matches)) => { - setup_proposal_tge(&wallet, &client, proposal_index, send_trx).unwrap() + setup_proposal_tge(&wallet, &client, proposal_index, send_trx, verbose).unwrap() + }, + ("sign-off", Some(arg_matches)) => { + sign_off_proposal(&wallet, &client, proposal_index, verbose).unwrap() }, ("approve", Some(arg_matches)) => { - approve_proposal(&wallet, &client, proposal_index).unwrap() + approve_proposal(&wallet, &client, proposal_index, verbose).unwrap() }, ("execute", Some(arg_matches)) => { - execute_proposal(&wallet, &client, proposal_index).unwrap() + execute_proposal(&wallet, &client, proposal_index, verbose).unwrap() }, _ => unreachable!(), } diff --git a/launch-script/src/tokens.rs b/launch-script/src/tokens.rs index 3a0238b..86e77a2 100644 --- a/launch-script/src/tokens.rs +++ b/launch-script/src/tokens.rs @@ -29,6 +29,27 @@ pub fn get_account_data(client: &Client, account: &Pubkey) -> ClientResult(&spl_token::id(), account) } +pub fn create_mint_instructions(client: &Client, mint_pubkey: &Pubkey, mint_authority: &Pubkey, + freeze_authority: Option<&Pubkey>, decimals: u8) -> ClientResult> +{ + Ok([ + solana_sdk::system_instruction::create_account( + &client.payer.pubkey(), + &mint_pubkey, + client.solana_client.get_minimum_balance_for_rent_exemption(Mint::LEN)?, + Mint::LEN as u64, + &spl_token::id(), + ), + spl_token::instruction::initialize_mint( + &spl_token::id(), + &mint_pubkey, + mint_authority, + freeze_authority, + decimals + ).unwrap(), + ].to_vec()) +} + pub fn create_mint(client: &Client, mint_keypair: &Keypair, mint_authority: &Pubkey, freeze_authority: Option<&Pubkey>, decimals: u8) -> ClientResult {