From 819280318696be487b22dd9cf84218550236b4cf Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Sat, 21 May 2022 21:36:18 +0300 Subject: [PATCH 01/11] wip: initial clone --- Cargo.lock | 18 + programs/squads-voter/Cargo.toml | 33 + programs/squads-voter/Xargo.toml | 2 + programs/squads-voter/src/error.rs | 73 + .../src/instructions/cast_nft_vote.rs | 133 ++ .../src/instructions/configure_collection.rs | 106 ++ .../create_max_voter_weight_record.rs | 57 + .../src/instructions/create_registrar.rs | 81 ++ .../create_voter_weight_record.rs | 63 + programs/squads-voter/src/instructions/mod.rs | 20 + .../src/instructions/relinquish_nft_vote.rs | 119 ++ .../update_voter_weight_record.rs | 73 + programs/squads-voter/src/lib.rs | 69 + .../src/state/collection_config.rs | 28 + programs/squads-voter/src/state/idl_types.rs | 17 + .../src/state/max_voter_weight_record.rs | 95 ++ programs/squads-voter/src/state/mod.rs | 15 + .../squads-voter/src/state/nft_vote_record.rs | 84 ++ programs/squads-voter/src/state/registrar.rs | 141 ++ .../src/state/voter_weight_record.rs | 109 ++ programs/squads-voter/src/tools/anchor.rs | 2 + programs/squads-voter/src/tools/governance.rs | 19 + programs/squads-voter/src/tools/mod.rs | 4 + programs/squads-voter/src/tools/spl_token.rs | 15 + .../squads-voter/src/tools/token_metadata.rs | 31 + programs/squads-voter/tests/cast_nft_vote.rs | 1211 +++++++++++++++++ .../tests/configure_collection.rs | 413 ++++++ .../tests/create_max_voter_weight_record.rs | 123 ++ .../squads-voter/tests/create_registrar.rs | 157 +++ .../tests/create_voter_weight_record.rs | 126 ++ .../tests/fixtures/mpl_token_metadata.so | Bin 0 -> 460888 bytes .../tests/fixtures/spl_governance.so | Bin 0 -> 867600 bytes .../tests/program_test/governance_test.rs | 389 ++++++ .../squads-voter/tests/program_test/mod.rs | 5 + .../tests/program_test/nft_voter_test.rs | 595 ++++++++ .../tests/program_test/program_test_bench.rs | 324 +++++ .../tests/program_test/token_metadata_test.rs | 221 +++ .../squads-voter/tests/program_test/tools.rs | 76 ++ .../squads-voter/tests/relinquish_nft_vote.rs | 363 +++++ .../tests/update_voter_weight_record.rs | 575 ++++++++ 40 files changed, 5985 insertions(+) create mode 100644 programs/squads-voter/Cargo.toml create mode 100644 programs/squads-voter/Xargo.toml create mode 100644 programs/squads-voter/src/error.rs create mode 100644 programs/squads-voter/src/instructions/cast_nft_vote.rs create mode 100644 programs/squads-voter/src/instructions/configure_collection.rs create mode 100644 programs/squads-voter/src/instructions/create_max_voter_weight_record.rs create mode 100644 programs/squads-voter/src/instructions/create_registrar.rs create mode 100644 programs/squads-voter/src/instructions/create_voter_weight_record.rs create mode 100644 programs/squads-voter/src/instructions/mod.rs create mode 100644 programs/squads-voter/src/instructions/relinquish_nft_vote.rs create mode 100644 programs/squads-voter/src/instructions/update_voter_weight_record.rs create mode 100644 programs/squads-voter/src/lib.rs create mode 100644 programs/squads-voter/src/state/collection_config.rs create mode 100644 programs/squads-voter/src/state/idl_types.rs create mode 100644 programs/squads-voter/src/state/max_voter_weight_record.rs create mode 100644 programs/squads-voter/src/state/mod.rs create mode 100644 programs/squads-voter/src/state/nft_vote_record.rs create mode 100644 programs/squads-voter/src/state/registrar.rs create mode 100644 programs/squads-voter/src/state/voter_weight_record.rs create mode 100644 programs/squads-voter/src/tools/anchor.rs create mode 100644 programs/squads-voter/src/tools/governance.rs create mode 100644 programs/squads-voter/src/tools/mod.rs create mode 100644 programs/squads-voter/src/tools/spl_token.rs create mode 100644 programs/squads-voter/src/tools/token_metadata.rs create mode 100644 programs/squads-voter/tests/cast_nft_vote.rs create mode 100644 programs/squads-voter/tests/configure_collection.rs create mode 100644 programs/squads-voter/tests/create_max_voter_weight_record.rs create mode 100644 programs/squads-voter/tests/create_registrar.rs create mode 100644 programs/squads-voter/tests/create_voter_weight_record.rs create mode 100755 programs/squads-voter/tests/fixtures/mpl_token_metadata.so create mode 100755 programs/squads-voter/tests/fixtures/spl_governance.so create mode 100644 programs/squads-voter/tests/program_test/governance_test.rs create mode 100644 programs/squads-voter/tests/program_test/mod.rs create mode 100644 programs/squads-voter/tests/program_test/nft_voter_test.rs create mode 100644 programs/squads-voter/tests/program_test/program_test_bench.rs create mode 100644 programs/squads-voter/tests/program_test/token_metadata_test.rs create mode 100644 programs/squads-voter/tests/program_test/tools.rs create mode 100644 programs/squads-voter/tests/relinquish_nft_vote.rs create mode 100644 programs/squads-voter/tests/update_voter_weight_record.rs diff --git a/Cargo.lock b/Cargo.lock index e28bcf39..847717e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1076,6 +1076,24 @@ dependencies = [ "spl-token", ] +[[package]] +name = "gpl-squads-voter" +version = "0.0.1" +dependencies = [ + "anchor-lang", + "anchor-spl", + "arrayref", + "borsh", + "itertools", + "mpl-token-metadata", + "solana-program", + "solana-program-test", + "solana-sdk", + "spl-governance", + "spl-governance-tools", + "spl-token", +] + [[package]] name = "h2" version = "0.3.12" diff --git a/programs/squads-voter/Cargo.toml b/programs/squads-voter/Cargo.toml new file mode 100644 index 00000000..e09326d3 --- /dev/null +++ b/programs/squads-voter/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "gpl-squads-voter" +version = "0.0.1" +description = "SPL Governance addin implementing Squads Protocol based governance" +license = "Apache-2.0" +edition = "2018" + +[lib] +crate-type = ["cdylib", "lib"] +name = "gpl_squads_voter" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] + +[dependencies] +arrayref = "0.3.6" +anchor-lang = { version = "0.24.2", features = ["init-if-needed"] } +anchor-spl = "0.24.2" +itertools = "0.10.2" +mpl-token-metadata = { version = "1.1.0", features = ["no-entrypoint"] } +solana-program = "1.9.13" +spl-governance = { version = "2.2.2", features = ["no-entrypoint"] } +spl-governance-tools= "0.1.2" +spl-token = { version = "3.3", features = [ "no-entrypoint" ] } + +[dev-dependencies] +borsh = "0.9.1" +solana-sdk = "1.9.5" +solana-program-test = "1.9.13" \ No newline at end of file diff --git a/programs/squads-voter/Xargo.toml b/programs/squads-voter/Xargo.toml new file mode 100644 index 00000000..475fb71e --- /dev/null +++ b/programs/squads-voter/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/programs/squads-voter/src/error.rs b/programs/squads-voter/src/error.rs new file mode 100644 index 00000000..4b03fb2b --- /dev/null +++ b/programs/squads-voter/src/error.rs @@ -0,0 +1,73 @@ +use anchor_lang::prelude::*; + +#[error_code] +pub enum NftVoterError { + #[msg("Invalid Realm Authority")] + InvalidRealmAuthority, + + #[msg("Invalid Realm for Registrar")] + InvalidRealmForRegistrar, + + #[msg("Invalid Collection Size")] + InvalidCollectionSize, + + #[msg("Invalid MaxVoterWeightRecord Realm")] + InvalidMaxVoterWeightRecordRealm, + + #[msg("Invalid MaxVoterWeightRecord Mint")] + InvalidMaxVoterWeightRecordMint, + + #[msg("CastVote Is Not Allowed")] + CastVoteIsNotAllowed, + + #[msg("Invalid VoterWeightRecord Realm")] + InvalidVoterWeightRecordRealm, + + #[msg("Invalid VoterWeightRecord Mint")] + InvalidVoterWeightRecordMint, + + #[msg("Invalid TokenOwner for VoterWeightRecord")] + InvalidTokenOwnerForVoterWeightRecord, + + #[msg("Collection must be verified")] + CollectionMustBeVerified, + + #[msg("Voter does not own NFT")] + VoterDoesNotOwnNft, + + #[msg("Collection not found")] + CollectionNotFound, + + #[msg("Missing Metadata collection")] + MissingMetadataCollection, + + #[msg("Token Metadata doesn't match")] + TokenMetadataDoesNotMatch, + + #[msg("Invalid account owner")] + InvalidAccountOwner, + + #[msg("Invalid token metadata account")] + InvalidTokenMetadataAccount, + + #[msg("Duplicated NFT detected")] + DuplicatedNftDetected, + + #[msg("Invalid NFT amount")] + InvalidNftAmount, + + #[msg("NFT already voted")] + NftAlreadyVoted, + + #[msg("Invalid Proposal for NftVoteRecord")] + InvalidProposalForNftVoteRecord, + + #[msg("Invalid TokenOwner for NftVoteRecord")] + InvalidTokenOwnerForNftVoteRecord, + + #[msg("VoteRecord must be withdrawn")] + VoteRecordMustBeWithdrawn, + + #[msg("Invalid VoteRecord for NftVoteRecord")] + InvalidVoteRecordForNftVoteRecord, +} diff --git a/programs/squads-voter/src/instructions/cast_nft_vote.rs b/programs/squads-voter/src/instructions/cast_nft_vote.rs new file mode 100644 index 00000000..853c35a5 --- /dev/null +++ b/programs/squads-voter/src/instructions/cast_nft_vote.rs @@ -0,0 +1,133 @@ +use crate::error::NftVoterError; +use crate::{id, state::*}; +use anchor_lang::prelude::*; +use anchor_lang::Accounts; +use itertools::Itertools; +use spl_governance_tools::account::create_and_serialize_account_signed; + +/// Casts NFT vote. The NFTs used for voting are tracked using NftVoteRecord accounts +/// This instruction updates VoterWeightRecord which is valid for the current Slot and the target Proposal only +/// and hance the instruction has to be executed inside the same transaction as spl-gov.CastVote +/// +/// CastNftVote is accumulative and can be invoked using several transactions if voter owns more than 5 NFTs to calculate total voter_weight +/// In this scenario only the last CastNftVote should be bundled with spl-gov.CastVote in the same transaction +/// +/// CastNftVote instruction and NftVoteRecord are not directional. They don't record vote choice (ex Yes/No) +/// VoteChoice is recorded by spl-gov in VoteRecord and this CastNftVote only tracks voting NFTs +/// +#[derive(Accounts)] +#[instruction(proposal: Pubkey)] +pub struct CastNftVote<'info> { + /// The NFT voting registrar + pub registrar: Account<'info, Registrar>, + + #[account( + mut, + constraint = voter_weight_record.realm == registrar.realm + @ NftVoterError::InvalidVoterWeightRecordRealm, + + constraint = voter_weight_record.governing_token_mint == registrar.governing_token_mint + @ NftVoterError::InvalidVoterWeightRecordMint, + )] + pub voter_weight_record: Account<'info, VoterWeightRecord>, + + /// The token owner who casts the vote + #[account( + address = voter_weight_record.governing_token_owner @ NftVoterError::InvalidTokenOwnerForVoterWeightRecord + )] + pub governing_token_owner: Signer<'info>, + + /// The account which pays for the transaction + #[account(mut)] + pub payer: Signer<'info>, + + pub system_program: Program<'info, System>, +} + +/// Casts vote with the NFT +pub fn cast_nft_vote<'a, 'b, 'c, 'info>( + ctx: Context<'a, 'b, 'c, 'info, CastNftVote<'info>>, + proposal: Pubkey, +) -> Result<()> { + let registrar = &ctx.accounts.registrar; + let governing_token_owner = &ctx.accounts.governing_token_owner.key(); + + let mut voter_weight = 0u64; + + // Ensure all voting nfts in the batch are unique + let mut unique_nft_mints = vec![]; + + let rent = Rent::get()?; + + for (nft_info, nft_metadata_info, nft_vote_record_info) in + ctx.remaining_accounts.iter().tuples() + { + let (nft_vote_weight, nft_mint) = resolve_nft_vote_weight_and_mint( + registrar, + governing_token_owner, + nft_info, + nft_metadata_info, + &mut unique_nft_mints, + )?; + + voter_weight = voter_weight.checked_add(nft_vote_weight as u64).unwrap(); + + // Create NFT vote record to ensure the same NFT hasn't been already used for voting + // Note: The correct PDA of the NftVoteRecord is validated in create_and_serialize_account_signed + // It ensures the NftVoteRecord is for ('nft-vote-record',proposal,nft_mint) seeds + require!( + nft_vote_record_info.data_is_empty(), + NftVoterError::NftAlreadyVoted + ); + + // Note: proposal.governing_token_mint must match voter_weight_record.governing_token_mint + // We don't verify it here because spl-gov does the check in cast_vote + // and it would reject voter_weight_record if governing_token_mint doesn't match + + // Note: Once the NFT plugin is enabled the governing_token_mint is used only as identity + // for the voting population and the tokens of that mint are no longer used + let nft_vote_record = NftVoteRecord { + account_discriminator: NftVoteRecord::ACCOUNT_DISCRIMINATOR, + proposal, + nft_mint, + governing_token_owner: *governing_token_owner, + reserved: [0; 8], + }; + + // Anchor doesn't natively support dynamic account creation using remaining_accounts + // and we have to take it on the manual drive + create_and_serialize_account_signed( + &ctx.accounts.payer.to_account_info(), + nft_vote_record_info, + &nft_vote_record, + &get_nft_vote_record_seeds(&proposal, &nft_mint), + &id(), + &ctx.accounts.system_program.to_account_info(), + &rent, + )?; + } + + let voter_weight_record = &mut ctx.accounts.voter_weight_record; + + if voter_weight_record.weight_action_target == Some(proposal) + && voter_weight_record.weight_action == Some(VoterWeightAction::CastVote) + { + // If cast_nft_vote is called for the same proposal then we keep accumulating the weight + // this way cast_nft_vote can be called multiple times in different transactions to allow voting with any number of NFTs + voter_weight_record.voter_weight = voter_weight_record + .voter_weight + .checked_add(voter_weight) + .unwrap(); + } else { + voter_weight_record.voter_weight = voter_weight; + } + + // The record is only valid as of the current slot + voter_weight_record.voter_weight_expiry = Some(Clock::get()?.slot); + + // The record is only valid for casting vote on the given Proposal + voter_weight_record.weight_action = Some(VoterWeightAction::CastVote); + voter_weight_record.weight_action_target = Some(proposal); + + Ok(()) +} diff --git a/programs/squads-voter/src/instructions/configure_collection.rs b/programs/squads-voter/src/instructions/configure_collection.rs new file mode 100644 index 00000000..dab3b5aa --- /dev/null +++ b/programs/squads-voter/src/instructions/configure_collection.rs @@ -0,0 +1,106 @@ +use anchor_lang::{ + account, + prelude::{Context, Signer}, + Accounts, +}; + +use anchor_lang::prelude::*; +use anchor_spl::token::Mint; +use spl_governance::state::realm; + +use crate::error::NftVoterError; +use crate::state::{max_voter_weight_record::MaxVoterWeightRecord, CollectionConfig, Registrar}; + +/// Configures NFT voting collection which defines what NFTs can be used for governances +/// and what weight they have +/// The instruction updates MaxVoterWeightRecord which is used by spl-gov to determine max voting power +/// used to calculate voting quorum +#[derive(Accounts)] +pub struct ConfigureCollection<'info> { + /// Registrar for which we configure this Collection + #[account(mut)] + pub registrar: Account<'info, Registrar>, + + #[account( + address = registrar.realm @ NftVoterError::InvalidRealmForRegistrar, + owner = registrar.governance_program_id + )] + /// CHECK: Owned by spl-governance instance specified in registrar.governance_program_id + pub realm: UncheckedAccount<'info>, + + /// Authority of the Realm must sign and match Realm.authority + pub realm_authority: Signer<'info>, + + // Collection which is going to be used for voting + pub collection: Account<'info, Mint>, + + #[account( + mut, + constraint = max_voter_weight_record.realm == registrar.realm + @ NftVoterError::InvalidMaxVoterWeightRecordRealm, + + constraint = max_voter_weight_record.governing_token_mint == registrar.governing_token_mint + @ NftVoterError::InvalidMaxVoterWeightRecordMint, + )] + pub max_voter_weight_record: Account<'info, MaxVoterWeightRecord>, +} + +pub fn configure_collection( + ctx: Context, + weight: u64, + size: u32, +) -> Result<()> { + require!(size > 0, NftVoterError::InvalidCollectionSize); + + let registrar = &mut ctx.accounts.registrar; + + let realm = realm::get_realm_data_for_governing_token_mint( + ®istrar.governance_program_id, + &ctx.accounts.realm, + ®istrar.governing_token_mint, + )?; + + require!( + realm.authority.unwrap() == ctx.accounts.realm_authority.key(), + NftVoterError::InvalidRealmAuthority + ); + + let collection = &ctx.accounts.collection; + + let collection_config = CollectionConfig { + collection: collection.key(), + weight, + reserved: [0; 8], + size, + }; + + let collection_idx = registrar + .collection_configs + .iter() + .position(|cc| cc.collection == collection.key()); + + if let Some(collection_idx) = collection_idx { + registrar.collection_configs[collection_idx] = collection_config; + } else { + // Note: In the current runtime version push() would throw an error if we exceed + // max_collections specified when the Registrar was created + registrar.collection_configs.push(collection_config); + } + + // TODO: if weight == 0 then remove the collection from config + // Currently if weight is set to 0 then the collection won't be removed but it won't have any governance power + + // Update MaxVoterWeightRecord based on max voting power of the collections + let max_voter_weight_record = &mut ctx.accounts.max_voter_weight_record; + + max_voter_weight_record.max_voter_weight = registrar + .collection_configs + .iter() + .try_fold(0u64, |sum, cc| sum.checked_add(cc.get_max_weight())) + .unwrap(); + + // The weight never expires and only changes when collections are configured + max_voter_weight_record.max_voter_weight_expiry = None; + + Ok(()) +} diff --git a/programs/squads-voter/src/instructions/create_max_voter_weight_record.rs b/programs/squads-voter/src/instructions/create_max_voter_weight_record.rs new file mode 100644 index 00000000..0749fd4e --- /dev/null +++ b/programs/squads-voter/src/instructions/create_max_voter_weight_record.rs @@ -0,0 +1,57 @@ +use anchor_lang::prelude::*; +use anchor_spl::token::Mint; +use spl_governance::state::realm; + +use crate::state::max_voter_weight_record::MaxVoterWeightRecord; + +/// Creates MaxVoterWeightRecord used by spl-gov +/// This instruction should only be executed once per realm/governing_token_mint to create the account +#[derive(Accounts)] +pub struct CreateMaxVoterWeightRecord<'info> { + #[account( + init, + seeds = [ b"max-voter-weight-record".as_ref(), + realm.key().as_ref(), + realm_governing_token_mint.key().as_ref()], + bump, + payer = payer, + space = MaxVoterWeightRecord::get_space() + )] + pub max_voter_weight_record: Account<'info, MaxVoterWeightRecord>, + + /// The program id of the spl-governance program the realm belongs to + /// CHECK: Can be any instance of spl-governance and it's not known at the compilation time + #[account(executable)] + pub governance_program_id: UncheckedAccount<'info>, + + #[account(owner = governance_program_id.key())] + /// CHECK: Owned by spl-governance instance specified in governance_program_id + pub realm: UncheckedAccount<'info>, + + /// Either the realm community mint or the council mint. + pub realm_governing_token_mint: Account<'info, Mint>, + + #[account(mut)] + pub payer: Signer<'info>, + + pub system_program: Program<'info, System>, +} + +pub fn create_max_voter_weight_record(ctx: Context) -> Result<()> { + // Deserialize the Realm to validate it + let _realm = realm::get_realm_data_for_governing_token_mint( + &ctx.accounts.governance_program_id.key(), + &ctx.accounts.realm, + &ctx.accounts.realm_governing_token_mint.key(), + )?; + + let max_voter_weight_record = &mut ctx.accounts.max_voter_weight_record; + + max_voter_weight_record.realm = ctx.accounts.realm.key(); + max_voter_weight_record.governing_token_mint = ctx.accounts.realm_governing_token_mint.key(); + + // Set expiry to expired + max_voter_weight_record.max_voter_weight_expiry = Some(0); + + Ok(()) +} diff --git a/programs/squads-voter/src/instructions/create_registrar.rs b/programs/squads-voter/src/instructions/create_registrar.rs new file mode 100644 index 00000000..efaa29f8 --- /dev/null +++ b/programs/squads-voter/src/instructions/create_registrar.rs @@ -0,0 +1,81 @@ +use crate::error::NftVoterError; +use crate::state::*; +use anchor_lang::prelude::*; +use anchor_spl::token::Mint; +use spl_governance::state::realm; + +/// Creates Registrar storing NFT governance configuration for spl-gov Realm +/// This instruction should only be executed once per realm/governing_token_mint to create the account +#[derive(Accounts)] +#[instruction(max_collections: u8)] +pub struct CreateRegistrar<'info> { + /// The NFT voting Registrar + /// There can only be a single registrar per governance Realm and governing mint of the Realm + #[account( + init, + seeds = [b"registrar".as_ref(),realm.key().as_ref(), governing_token_mint.key().as_ref()], + bump, + payer = payer, + space = Registrar::get_space(max_collections) + )] + pub registrar: Account<'info, Registrar>, + + /// The program id of the spl-governance program the realm belongs to + /// CHECK: Can be any instance of spl-governance and it's not known at the compilation time + #[account(executable)] + pub governance_program_id: UncheckedAccount<'info>, + + /// An spl-governance Realm + /// + /// Realm is validated in the instruction: + /// - Realm is owned by the governance_program_id + /// - governing_token_mint must be the community or council mint + /// - realm_authority is realm.authority + /// CHECK: Owned by spl-governance instance specified in governance_program_id + #[account(owner = governance_program_id.key())] + pub realm: UncheckedAccount<'info>, + + /// Either the realm community mint or the council mint. + /// It must match Realm.community_mint or Realm.config.council_mint + /// + /// Note: Once the NFT plugin is enabled the governing_token_mint is used only as identity + /// for the voting population and the tokens of that are no longer used + pub governing_token_mint: Account<'info, Mint>, + + /// realm_authority must sign and match Realm.authority + pub realm_authority: Signer<'info>, + + #[account(mut)] + pub payer: Signer<'info>, + + pub system_program: Program<'info, System>, +} + +/// Creates a new Registrar which stores NFT voting configuration for given Realm +/// +/// To use the registrar, call ConfigureCollection to register NFT collections that may be +/// used for governance +/// +/// max_collections is used allocate account size for the maximum number of governing NFT collections +/// Note: Once Solana runtime supports account resizing the max value won't be required +pub fn create_registrar(ctx: Context, _max_collections: u8) -> Result<()> { + let registrar = &mut ctx.accounts.registrar; + registrar.governance_program_id = ctx.accounts.governance_program_id.key(); + registrar.realm = ctx.accounts.realm.key(); + registrar.governing_token_mint = ctx.accounts.governing_token_mint.key(); + + // Verify that realm_authority is the expected authority of the Realm + // and that the mint matches one of the realm mints too + let realm = realm::get_realm_data_for_governing_token_mint( + ®istrar.governance_program_id, + &ctx.accounts.realm, + ®istrar.governing_token_mint, + )?; + + require!( + realm.authority.unwrap() == ctx.accounts.realm_authority.key(), + NftVoterError::InvalidRealmAuthority + ); + + Ok(()) +} diff --git a/programs/squads-voter/src/instructions/create_voter_weight_record.rs b/programs/squads-voter/src/instructions/create_voter_weight_record.rs new file mode 100644 index 00000000..d1ef66db --- /dev/null +++ b/programs/squads-voter/src/instructions/create_voter_weight_record.rs @@ -0,0 +1,63 @@ +use crate::state::*; +use anchor_lang::prelude::*; +use anchor_spl::token::Mint; +use spl_governance::state::realm; + +/// Creates VoterWeightRecord used by spl-gov +/// This instruction should only be executed once per realm/governing_token_mint/governing_token_owner +/// to create the account +#[derive(Accounts)] +#[instruction(governing_token_owner: Pubkey)] +pub struct CreateVoterWeightRecord<'info> { + #[account( + init, + seeds = [ b"voter-weight-record".as_ref(), + realm.key().as_ref(), + realm_governing_token_mint.key().as_ref(), + governing_token_owner.as_ref()], + bump, + payer = payer, + space = VoterWeightRecord::get_space() + )] + pub voter_weight_record: Account<'info, VoterWeightRecord>, + + /// The program id of the spl-governance program the realm belongs to + /// CHECK: Can be any instance of spl-governance and it's not known at the compilation time + #[account(executable)] + pub governance_program_id: UncheckedAccount<'info>, + + /// CHECK: Owned by spl-governance instance specified in governance_program_id + #[account(owner = governance_program_id.key())] + pub realm: UncheckedAccount<'info>, + + /// Either the realm community mint or the council mint. + pub realm_governing_token_mint: Account<'info, Mint>, + + #[account(mut)] + pub payer: Signer<'info>, + + pub system_program: Program<'info, System>, +} + +pub fn create_voter_weight_record( + ctx: Context, + governing_token_owner: Pubkey, +) -> Result<()> { + // Deserialize the Realm to validate it + let _realm = realm::get_realm_data_for_governing_token_mint( + &ctx.accounts.governance_program_id.key(), + &ctx.accounts.realm, + &ctx.accounts.realm_governing_token_mint.key(), + )?; + + let voter_weight_record = &mut ctx.accounts.voter_weight_record; + + voter_weight_record.realm = ctx.accounts.realm.key(); + voter_weight_record.governing_token_mint = ctx.accounts.realm_governing_token_mint.key(); + voter_weight_record.governing_token_owner = governing_token_owner; + + // Set expiry to expired + voter_weight_record.voter_weight_expiry = Some(0); + + Ok(()) +} diff --git a/programs/squads-voter/src/instructions/mod.rs b/programs/squads-voter/src/instructions/mod.rs new file mode 100644 index 00000000..518677c5 --- /dev/null +++ b/programs/squads-voter/src/instructions/mod.rs @@ -0,0 +1,20 @@ +pub use configure_collection::*; +mod configure_collection; + +pub use create_registrar::*; +mod create_registrar; + +pub use create_voter_weight_record::*; +mod create_voter_weight_record; + +pub use create_max_voter_weight_record::*; +mod create_max_voter_weight_record; + +pub use update_voter_weight_record::*; +mod update_voter_weight_record; + +pub use relinquish_nft_vote::*; +mod relinquish_nft_vote; + +pub use cast_nft_vote::*; +mod cast_nft_vote; diff --git a/programs/squads-voter/src/instructions/relinquish_nft_vote.rs b/programs/squads-voter/src/instructions/relinquish_nft_vote.rs new file mode 100644 index 00000000..3ef9796c --- /dev/null +++ b/programs/squads-voter/src/instructions/relinquish_nft_vote.rs @@ -0,0 +1,119 @@ +use crate::error::NftVoterError; +use crate::state::*; +use crate::state::{get_nft_vote_record_data_for_proposal_and_token_owner, Registrar}; +use crate::tools::governance::get_vote_record_address; +use anchor_lang::prelude::*; +use spl_governance::state::{enums::ProposalState, governance, proposal}; +use spl_governance_tools::account::dispose_account; + +/// Disposes NftVoteRecord and recovers the rent from the accounts +/// It can only be executed when voting on the target Proposal ended or voter withdrew vote from the Proposal +#[derive(Accounts)] +pub struct RelinquishNftVote<'info> { + /// The NFT voting Registrar + pub registrar: Account<'info, Registrar>, + + #[account( + mut, + constraint = voter_weight_record.realm == registrar.realm + @ NftVoterError::InvalidVoterWeightRecordRealm, + + constraint = voter_weight_record.governing_token_mint == registrar.governing_token_mint + @ NftVoterError::InvalidVoterWeightRecordMint, + )] + pub voter_weight_record: Account<'info, VoterWeightRecord>, + + /// CHECK: Owned by spl-governance instance specified in registrar.governance_program_id + /// Governance account the Proposal is for + #[account(owner = registrar.governance_program_id)] + pub governance: UncheckedAccount<'info>, + + /// CHECK: Owned by spl-governance instance specified in registrar.governance_program_id + #[account(owner = registrar.governance_program_id)] + pub proposal: UncheckedAccount<'info>, + + /// The token owner who cast the original vote + #[account( + address = voter_weight_record.governing_token_owner @ NftVoterError::InvalidTokenOwnerForVoterWeightRecord + )] + pub governing_token_owner: Signer<'info>, + + /// CHECK: Owned by spl-governance instance specified in registrar.governance_program_id + /// The account is used to validate that it doesn't exist and if it doesn't then Anchor owner check throws error + /// The check is disabled here and performed inside the instruction + /// #[account(owner = registrar.governance_program_id)] + pub vote_record: UncheckedAccount<'info>, + + /// CHECK: The beneficiary who receives lamports from the disposed NftVoterRecord accounts can be any account + #[account(mut)] + pub beneficiary: UncheckedAccount<'info>, +} + +pub fn relinquish_nft_vote(ctx: Context) -> Result<()> { + let registrar = &ctx.accounts.registrar; + + // Ensure the Governance belongs to Registrar.realm and is owned by Registrar.governance_program_id + let _governance = governance::get_governance_data_for_realm( + ®istrar.governance_program_id, + &ctx.accounts.governance, + ®istrar.realm, + )?; + + // Ensure the Proposal belongs to Governance from Registrar.realm and Registrar.governing_token_mint and is owned by Registrar.governance_program_id + let proposal = proposal::get_proposal_data_for_governance_and_governing_mint( + ®istrar.governance_program_id, + &ctx.accounts.proposal, + &ctx.accounts.governance.key(), + ®istrar.governing_token_mint, + )?; + + // If the Proposal is still in Voting state then we can only Relinquish the NFT votes if the Vote was withdrawn in spl-gov first + // When vote is withdrawn in spl-gov then VoteRecord is disposed and we have to assert it doesn't exist + // + // If the Proposal is in any other state then we can dispose NftVoteRecords without any additional Proposal checks + if proposal.state == ProposalState::Voting { + let vote_record_info = &ctx.accounts.vote_record.to_account_info(); + + // Ensure the given VoteRecord address matches the expected PDA + let vote_record_key = get_vote_record_address( + ®istrar.governance_program_id, + ®istrar.realm, + ®istrar.governing_token_mint, + &ctx.accounts.governing_token_owner.key(), + &ctx.accounts.proposal.key(), + ); + + require!( + vote_record_key == vote_record_info.key(), + NftVoterError::InvalidVoteRecordForNftVoteRecord + ); + + require!( + // VoteRecord doesn't exist if data is empty or account_type is 0 when the account was disposed in the same Tx + vote_record_info.data_is_empty() || vote_record_info.try_borrow_data().unwrap()[0] == 0, + NftVoterError::VoteRecordMustBeWithdrawn + ); + } + + // Dispose all NftVoteRecords + for nft_vote_record_info in ctx.remaining_accounts.iter() { + // Ensure NftVoteRecord is for the given Proposal and TokenOwner + let _nft_vote_record = get_nft_vote_record_data_for_proposal_and_token_owner( + nft_vote_record_info, + &ctx.accounts.proposal.key(), + &ctx.accounts.governing_token_owner.key(), + )?; + + dispose_account(nft_vote_record_info, &ctx.accounts.beneficiary); + } + + let voter_weight_record = &mut ctx.accounts.voter_weight_record; + + // Reset VoterWeightRecord and set expiry to expired to prevent it from being used + voter_weight_record.voter_weight = 0; + voter_weight_record.voter_weight_expiry = Some(0); + + voter_weight_record.weight_action_target = None; + + Ok(()) +} diff --git a/programs/squads-voter/src/instructions/update_voter_weight_record.rs b/programs/squads-voter/src/instructions/update_voter_weight_record.rs new file mode 100644 index 00000000..ee9d74dd --- /dev/null +++ b/programs/squads-voter/src/instructions/update_voter_weight_record.rs @@ -0,0 +1,73 @@ +use crate::error::NftVoterError; +use crate::state::*; +use anchor_lang::prelude::*; +use itertools::Itertools; + +/// Updates VoterWeightRecord to evaluate governance power for non voting use cases: CreateProposal, CreateGovernance etc... +/// This instruction updates VoterWeightRecord which is valid for the current Slot and the given target action only +/// and hance the instruction has to be executed inside the same transaction as the corresponding spl-gov instruction +/// +/// Note: UpdateVoterWeight is not cumulative the same way as CastNftVote and hence voter_weight for non voting scenarios +/// can only be used with max 5 NFTs due to Solana transaction size limit +/// It could be supported in future version by introducing bookkeeping accounts to track the NFTs +/// which were already used to calculate the total weight +#[derive(Accounts)] +#[instruction(voter_weight_action:VoterWeightAction)] +pub struct UpdateVoterWeightRecord<'info> { + /// The NFT voting Registrar + pub registrar: Account<'info, Registrar>, + + #[account( + mut, + constraint = voter_weight_record.realm == registrar.realm + @ NftVoterError::InvalidVoterWeightRecordRealm, + + constraint = voter_weight_record.governing_token_mint == registrar.governing_token_mint + @ NftVoterError::InvalidVoterWeightRecordMint, + )] + pub voter_weight_record: Account<'info, VoterWeightRecord>, +} + +pub fn update_voter_weight_record( + ctx: Context, + voter_weight_action: VoterWeightAction, +) -> Result<()> { + let registrar = &ctx.accounts.registrar; + let governing_token_owner = &ctx.accounts.voter_weight_record.governing_token_owner; + + // CastVote can't be evaluated using this instruction + require!( + voter_weight_action != VoterWeightAction::CastVote, + NftVoterError::CastVoteIsNotAllowed + ); + + let mut voter_weight = 0u64; + + // Ensure all nfts are unique + let mut unique_nft_mints = vec![]; + + for (nft_info, nft_metadata_info) in ctx.remaining_accounts.iter().tuples() { + let (nft_vote_weight, _) = resolve_nft_vote_weight_and_mint( + registrar, + governing_token_owner, + nft_info, + nft_metadata_info, + &mut unique_nft_mints, + )?; + + voter_weight = voter_weight.checked_add(nft_vote_weight as u64).unwrap(); + } + + let voter_weight_record = &mut ctx.accounts.voter_weight_record; + + voter_weight_record.voter_weight = voter_weight; + + // Record is only valid as of the current slot + voter_weight_record.voter_weight_expiry = Some(Clock::get()?.slot); + + // Set the action to make it specific and prevent being used for voting + voter_weight_record.weight_action = Some(voter_weight_action); + voter_weight_record.weight_action_target = None; + + Ok(()) +} diff --git a/programs/squads-voter/src/lib.rs b/programs/squads-voter/src/lib.rs new file mode 100644 index 00000000..78cf1191 --- /dev/null +++ b/programs/squads-voter/src/lib.rs @@ -0,0 +1,69 @@ +use anchor_lang::prelude::*; + +pub mod error; + +mod instructions; +use instructions::*; + +pub mod state; + +pub mod tools; + +use crate::state::*; + +declare_id!("GnftV5kLjd67tvHpNGyodwWveEKivz3ZWvvE3Z4xi2iw"); + +#[program] +pub mod squads_voter { + + use crate::state::VoterWeightAction; + + use super::*; + pub fn create_registrar(ctx: Context, max_collections: u8) -> Result<()> { + log_version(); + instructions::create_registrar(ctx, max_collections) + } + pub fn create_voter_weight_record( + ctx: Context, + governing_token_owner: Pubkey, + ) -> Result<()> { + log_version(); + instructions::create_voter_weight_record(ctx, governing_token_owner) + } + pub fn create_max_voter_weight_record(ctx: Context) -> Result<()> { + log_version(); + instructions::create_max_voter_weight_record(ctx) + } + pub fn update_voter_weight_record( + ctx: Context, + voter_weight_action: VoterWeightAction, + ) -> Result<()> { + log_version(); + instructions::update_voter_weight_record(ctx, voter_weight_action) + } + pub fn relinquish_nft_vote(ctx: Context) -> Result<()> { + log_version(); + instructions::relinquish_nft_vote(ctx) + } + pub fn configure_collection( + ctx: Context, + weight: u64, + size: u32, + ) -> Result<()> { + log_version(); + instructions::configure_collection(ctx, weight, size) + } + + pub fn cast_nft_vote<'a, 'b, 'c, 'info>( + ctx: Context<'a, 'b, 'c, 'info, CastNftVote<'info>>, + proposal: Pubkey, + ) -> Result<()> { + log_version(); + instructions::cast_nft_vote(ctx, proposal) + } +} + +fn log_version() { + // TODO: Check if Anchor allows to log it before instruction is deserialized + msg!("VERSION:{:?}", env!("CARGO_PKG_VERSION")); +} diff --git a/programs/squads-voter/src/state/collection_config.rs b/programs/squads-voter/src/state/collection_config.rs new file mode 100644 index 00000000..56cd7e4e --- /dev/null +++ b/programs/squads-voter/src/state/collection_config.rs @@ -0,0 +1,28 @@ +use anchor_lang::prelude::*; + +/// Configuration of an NFT collection used for governance power +#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy, PartialEq, Default)] +pub struct CollectionConfig { + /// The NFT collection used for governance + pub collection: Pubkey, + + /// The size of the NFT collection used to calculate max voter weight + /// Note: At the moment the size is not captured on Metaplex accounts + /// and it has to be manually updated on the Registrar + pub size: u32, + + /// Governance power weight of the collection + /// Each NFT in the collection has governance power = 1 * weight + /// Note: The weight is scaled accordingly to the governing_token_mint decimals + /// Ex: if the the mint has 2 decimal places then weight of 1 should be stored as 100 + pub weight: u64, + + /// Reserved for future upgrades + pub reserved: [u8; 8], +} + +impl CollectionConfig { + pub fn get_max_weight(&self) -> u64 { + (self.size as u64).checked_mul(self.weight).unwrap() + } +} diff --git a/programs/squads-voter/src/state/idl_types.rs b/programs/squads-voter/src/state/idl_types.rs new file mode 100644 index 00000000..06a57aa7 --- /dev/null +++ b/programs/squads-voter/src/state/idl_types.rs @@ -0,0 +1,17 @@ +//! IDL only types which are required in IDL but not exported automatically by Anchor +use anchor_lang::prelude::*; + +/// NftVoteRecord exported to IDL without account_discriminator +/// TODO: Once we can support these accounts in Anchor via remaining_accounts then it should be possible to remove it +#[account] +pub struct NftVoteRecord { + /// Proposal which was voted on + pub proposal: Pubkey, + + /// The mint of the NFT which was used for the vote + pub nft_mint: Pubkey, + + /// The voter who casted this vote + /// It's a Realm member pubkey corresponding to TokenOwnerRecord.governing_token_owner + pub governing_token_owner: Pubkey, +} diff --git a/programs/squads-voter/src/state/max_voter_weight_record.rs b/programs/squads-voter/src/state/max_voter_weight_record.rs new file mode 100644 index 00000000..a4c94a60 --- /dev/null +++ b/programs/squads-voter/src/state/max_voter_weight_record.rs @@ -0,0 +1,95 @@ +use crate::id; +use crate::tools::anchor::{DISCRIMINATOR_SIZE, PUBKEY_SIZE}; +use anchor_lang::prelude::Pubkey; +use anchor_lang::prelude::*; + +/// MaxVoterWeightRecord account as defined in spl-governance-addin-api +/// It's redefined here without account_discriminator for Anchor to treat it as native account +/// +/// The account is used as an api interface to provide max voting power to the governance program from external addin contracts +#[account] +#[derive(Debug, PartialEq)] +pub struct MaxVoterWeightRecord { + /// The Realm the MaxVoterWeightRecord belongs to + pub realm: Pubkey, + + /// Governing Token Mint the MaxVoterWeightRecord is associated with + /// Note: The addin can take deposits of any tokens and is not restricted to the community or council tokens only + // The mint here is to link the record to either community or council mint of the realm + pub governing_token_mint: Pubkey, + + /// Max voter weight + /// The max voter weight provided by the addin for the given realm and governing_token_mint + pub max_voter_weight: u64, + + /// The slot when the max voting weight expires + /// It should be set to None if the weight never expires + /// If the max vote weight decays with time, for example for time locked based weights, then the expiry must be set + /// As a pattern Revise instruction to update the max weight should be invoked before governance instruction within the same transaction + /// and the expiry set to the current slot to provide up to date weight + pub max_voter_weight_expiry: Option, + + /// Reserved space for future versions + pub reserved: [u8; 8], +} + +impl Default for MaxVoterWeightRecord { + fn default() -> Self { + Self { + realm: Default::default(), + governing_token_mint: Default::default(), + max_voter_weight: Default::default(), + max_voter_weight_expiry: Some(0), + reserved: Default::default(), + } + } +} + +impl MaxVoterWeightRecord { + pub fn get_space() -> usize { + DISCRIMINATOR_SIZE + PUBKEY_SIZE * 2 + 8 + 1 + 8 + 8 + } +} + +/// Returns MaxVoterWeightRecord PDA seeds +pub fn get_max_voter_weight_record_seeds<'a>( + realm: &'a Pubkey, + governing_token_mint: &'a Pubkey, +) -> [&'a [u8]; 3] { + [ + b"max-voter-weight-record", + realm.as_ref(), + governing_token_mint.as_ref(), + ] +} + +/// Returns MaxVoterWeightRecord PDA address +pub fn get_max_voter_weight_record_address( + realm: &Pubkey, + governing_token_mint: &Pubkey, +) -> Pubkey { + Pubkey::find_program_address( + &get_max_voter_weight_record_seeds(realm, governing_token_mint), + &id(), + ) + .0 +} + +#[cfg(test)] +mod test { + + use super::*; + + #[test] + fn test_get_space() { + // Arrange + let expected_space = MaxVoterWeightRecord::get_space(); + + // Act + let actual_space = + DISCRIMINATOR_SIZE + MaxVoterWeightRecord::default().try_to_vec().unwrap().len(); + + // Assert + assert_eq!(expected_space, actual_space); + } +} diff --git a/programs/squads-voter/src/state/mod.rs b/programs/squads-voter/src/state/mod.rs new file mode 100644 index 00000000..4084a580 --- /dev/null +++ b/programs/squads-voter/src/state/mod.rs @@ -0,0 +1,15 @@ +pub use registrar::*; +pub mod registrar; + +pub use collection_config::*; +pub mod collection_config; + +pub use nft_vote_record::*; +pub mod nft_vote_record; + +pub mod max_voter_weight_record; + +pub use voter_weight_record::*; +pub mod voter_weight_record; + +pub mod idl_types; diff --git a/programs/squads-voter/src/state/nft_vote_record.rs b/programs/squads-voter/src/state/nft_vote_record.rs new file mode 100644 index 00000000..25fcf101 --- /dev/null +++ b/programs/squads-voter/src/state/nft_vote_record.rs @@ -0,0 +1,84 @@ +use anchor_lang::prelude::*; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use solana_program::program_pack::IsInitialized; + +use spl_governance_tools::account::{get_account_data, AccountMaxSize}; + +use crate::{error::NftVoterError, id}; + +/// Vote record indicating the given NFT voted on the Proposal +/// The PDA of the record is ["nft-vote-record",proposal,nft_mint] +/// It guarantees uniques and ensures the same NFT can't vote twice +#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] +pub struct NftVoteRecord { + /// NftVoteRecord discriminator sha256("account:NftVoteRecord")[..8] + /// Note: The discriminator is used explicitly because NftVoteRecords + /// are created and consumed dynamically using remaining_accounts + /// and Anchor doesn't really support this scenario without going through lots of hoops + /// Once Anchor has better support for the scenario it shouldn't be necessary + pub account_discriminator: [u8; 8], + + /// Proposal which was voted on + pub proposal: Pubkey, + + /// The mint of the NFT which was used for the vote + pub nft_mint: Pubkey, + + /// The voter who casted this vote + /// It's a Realm member pubkey corresponding to TokenOwnerRecord.governing_token_owner + pub governing_token_owner: Pubkey, + + /// Reserved for future upgrades + pub reserved: [u8; 8], +} + +impl NftVoteRecord { + /// sha256("account:NftVoteRecord")[..8] + pub const ACCOUNT_DISCRIMINATOR: [u8; 8] = [137, 6, 55, 139, 251, 126, 254, 99]; +} + +impl AccountMaxSize for NftVoteRecord {} + +impl IsInitialized for NftVoteRecord { + fn is_initialized(&self) -> bool { + self.account_discriminator == NftVoteRecord::ACCOUNT_DISCRIMINATOR + } +} + +/// Returns NftVoteRecord PDA seeds +pub fn get_nft_vote_record_seeds<'a>(proposal: &'a Pubkey, nft_mint: &'a Pubkey) -> [&'a [u8]; 3] { + [b"nft-vote-record", proposal.as_ref(), nft_mint.as_ref()] +} + +/// Returns NftVoteRecord PDA address +pub fn get_nft_vote_record_address(proposal: &Pubkey, nft_mint: &Pubkey) -> Pubkey { + Pubkey::find_program_address(&get_nft_vote_record_seeds(proposal, nft_mint), &id()).0 +} + +/// Deserializes account and checks owner program +pub fn get_nft_vote_record_data(nft_vote_record_info: &AccountInfo) -> Result { + Ok(get_account_data::( + &id(), + nft_vote_record_info, + )?) +} + +pub fn get_nft_vote_record_data_for_proposal_and_token_owner( + nft_vote_record_info: &AccountInfo, + proposal: &Pubkey, + governing_token_owner: &Pubkey, +) -> Result { + let nft_vote_record = get_nft_vote_record_data(nft_vote_record_info)?; + + require!( + nft_vote_record.proposal == *proposal, + NftVoterError::InvalidProposalForNftVoteRecord + ); + + require!( + nft_vote_record.governing_token_owner == *governing_token_owner, + NftVoterError::InvalidTokenOwnerForNftVoteRecord + ); + + Ok(nft_vote_record) +} diff --git a/programs/squads-voter/src/state/registrar.rs b/programs/squads-voter/src/state/registrar.rs new file mode 100644 index 00000000..990b64ff --- /dev/null +++ b/programs/squads-voter/src/state/registrar.rs @@ -0,0 +1,141 @@ +use crate::{ + error::NftVoterError, + id, + state::CollectionConfig, + tools::{ + anchor::{DISCRIMINATOR_SIZE, PUBKEY_SIZE}, + spl_token::get_spl_token_amount, + token_metadata::get_token_metadata_for_mint, + }, +}; +use anchor_lang::prelude::*; +use spl_governance::tools::spl_token::{get_spl_token_mint, get_spl_token_owner}; + +/// Registrar which stores NFT voting configuration for the given Realm +#[account] +#[derive(Debug, PartialEq)] +pub struct Registrar { + /// spl-governance program the Realm belongs to + pub governance_program_id: Pubkey, + + /// Realm of the Registrar + pub realm: Pubkey, + + /// Governing token mint the Registrar is for + /// It can either be the Community or the Council mint of the Realm + /// When the plugin is used the mint is only used as identity of the governing power (voting population) + /// and the actual token of the mint is not used + pub governing_token_mint: Pubkey, + + /// MPL Collection used for voting + pub collection_configs: Vec, + + /// Reserved for future upgrades + pub reserved: [u8; 128], +} + +impl Registrar { + pub fn get_space(max_collections: u8) -> usize { + DISCRIMINATOR_SIZE + + PUBKEY_SIZE * 3 + + 4 + + max_collections as usize * (PUBKEY_SIZE + 4 + 8 + 8) + + 128 + } +} + +/// Returns Registrar PDA seeds +pub fn get_registrar_seeds<'a>( + realm: &'a Pubkey, + governing_token_mint: &'a Pubkey, +) -> [&'a [u8]; 3] { + [b"registrar", realm.as_ref(), governing_token_mint.as_ref()] +} + +/// Returns Registrar PDA address +pub fn get_registrar_address(realm: &Pubkey, governing_token_mint: &Pubkey) -> Pubkey { + Pubkey::find_program_address(&get_registrar_seeds(realm, governing_token_mint), &id()).0 +} + +impl Registrar { + pub fn get_collection_config(&self, collection: Pubkey) -> Result<&CollectionConfig> { + return self + .collection_configs + .iter() + .find(|cc| cc.collection == collection) + .ok_or_else(|| NftVoterError::CollectionNotFound.into()); + } +} + +/// Resolves vote weight and voting mint for the given NFT +pub fn resolve_nft_vote_weight_and_mint( + registrar: &Registrar, + governing_token_owner: &Pubkey, + nft_info: &AccountInfo, + nft_metadata_info: &AccountInfo, + unique_nft_mints: &mut Vec, +) -> Result<(u64, Pubkey)> { + let nft_owner = get_spl_token_owner(nft_info)?; + + // voter_weight_record.governing_token_owner must be the owner of the NFT + require!( + nft_owner == *governing_token_owner, + NftVoterError::VoterDoesNotOwnNft + ); + + let nft_mint = get_spl_token_mint(nft_info)?; + + // Ensure the same NFT was not provided more than once + if unique_nft_mints.contains(&nft_mint) { + return Err(NftVoterError::DuplicatedNftDetected.into()); + } + unique_nft_mints.push(nft_mint); + + // Ensure the token amount is exactly 1 + let nft_amount = get_spl_token_amount(nft_info)?; + + require!(nft_amount == 1, NftVoterError::InvalidNftAmount); + + let nft_metadata = get_token_metadata_for_mint(nft_metadata_info, &nft_mint)?; + + // The NFT must have a collection and the collection must be verified + let collection = nft_metadata + .collection + .ok_or(NftVoterError::MissingMetadataCollection)?; + + require!(collection.verified, NftVoterError::CollectionMustBeVerified); + + let collection_config = registrar.get_collection_config(collection.key)?; + + Ok((collection_config.weight, nft_mint)) +} + +#[cfg(test)] +mod test { + + use super::*; + + #[test] + fn test_get_space() { + // Arrange + let expected_space = Registrar::get_space(3); + + let registrar = Registrar { + governance_program_id: Pubkey::default(), + realm: Pubkey::default(), + governing_token_mint: Pubkey::default(), + collection_configs: vec![ + CollectionConfig::default(), + CollectionConfig::default(), + CollectionConfig::default(), + ], + reserved: [0; 128], + }; + + // Act + let actual_space = DISCRIMINATOR_SIZE + registrar.try_to_vec().unwrap().len(); + + // Assert + assert_eq!(expected_space, actual_space); + } +} diff --git a/programs/squads-voter/src/state/voter_weight_record.rs b/programs/squads-voter/src/state/voter_weight_record.rs new file mode 100644 index 00000000..7032aa33 --- /dev/null +++ b/programs/squads-voter/src/state/voter_weight_record.rs @@ -0,0 +1,109 @@ +use anchor_lang::prelude::*; + +use crate::tools::anchor::{DISCRIMINATOR_SIZE, PUBKEY_SIZE}; + +/// VoterWeightAction enum as defined in spl-governance-addin-api +/// It's redefined here for Anchor to export it to IDL +#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy, PartialEq)] +pub enum VoterWeightAction { + /// Cast vote for a proposal. Target: Proposal + CastVote, + + /// Comment a proposal. Target: Proposal + CommentProposal, + + /// Create Governance within a realm. Target: Realm + CreateGovernance, + + /// Create a proposal for a governance. Target: Governance + CreateProposal, + + /// Signs off a proposal for a governance. Target: Proposal + /// Note: SignOffProposal is not supported in the current version + SignOffProposal, +} + +/// VoterWeightRecord account as defined in spl-governance-addin-api +/// It's redefined here without account_discriminator for Anchor to treat it as native account +/// +/// The account is used as an api interface to provide voting power to the governance program from external addin contracts +#[account] +#[derive(Debug, PartialEq)] +pub struct VoterWeightRecord { + /// The Realm the VoterWeightRecord belongs to + pub realm: Pubkey, + + /// Governing Token Mint the VoterWeightRecord is associated with + /// Note: The addin can take deposits of any tokens and is not restricted to the community or council tokens only + // The mint here is to link the record to either community or council mint of the realm + pub governing_token_mint: Pubkey, + + /// The owner of the governing token and voter + /// This is the actual owner (voter) and corresponds to TokenOwnerRecord.governing_token_owner + pub governing_token_owner: Pubkey, + + /// Voter's weight + /// The weight of the voter provided by the addin for the given realm, governing_token_mint and governing_token_owner (voter) + pub voter_weight: u64, + + /// The slot when the voting weight expires + /// It should be set to None if the weight never expires + /// If the voter weight decays with time, for example for time locked based weights, then the expiry must be set + /// As a common pattern Revise instruction to update the weight should be invoked before governance instruction within the same transaction + /// and the expiry set to the current slot to provide up to date weight + pub voter_weight_expiry: Option, + + /// The governance action the voter's weight pertains to + /// It allows to provided voter's weight specific to the particular action the weight is evaluated for + /// When the action is provided then the governance program asserts the executing action is the same as specified by the addin + pub weight_action: Option, + + /// The target the voter's weight action pertains to + /// It allows to provided voter's weight specific to the target the weight is evaluated for + /// For example when addin supplies weight to vote on a particular proposal then it must specify the proposal as the action target + /// When the target is provided then the governance program asserts the target is the same as specified by the addin + pub weight_action_target: Option, + + /// Reserved space for future versions + pub reserved: [u8; 8], +} + +impl VoterWeightRecord { + pub fn get_space() -> usize { + DISCRIMINATOR_SIZE + PUBKEY_SIZE * 4 + 8 + 1 + 8 + 1 + 1 + 1 + 8 + } +} + +impl Default for VoterWeightRecord { + fn default() -> Self { + Self { + realm: Default::default(), + governing_token_mint: Default::default(), + governing_token_owner: Default::default(), + voter_weight: Default::default(), + voter_weight_expiry: Some(0), + weight_action: Some(VoterWeightAction::CastVote), + weight_action_target: Some(Default::default()), + reserved: Default::default(), + } + } +} + +#[cfg(test)] +mod test { + + use super::*; + + #[test] + fn test_get_space() { + // Arrange + let expected_space = VoterWeightRecord::get_space(); + + // Act + let actual_space = + DISCRIMINATOR_SIZE + VoterWeightRecord::default().try_to_vec().unwrap().len(); + + // Assert + assert_eq!(expected_space, actual_space); + } +} diff --git a/programs/squads-voter/src/tools/anchor.rs b/programs/squads-voter/src/tools/anchor.rs new file mode 100644 index 00000000..461f8871 --- /dev/null +++ b/programs/squads-voter/src/tools/anchor.rs @@ -0,0 +1,2 @@ +pub const DISCRIMINATOR_SIZE: usize = 8; +pub const PUBKEY_SIZE: usize = 32; diff --git a/programs/squads-voter/src/tools/governance.rs b/programs/squads-voter/src/tools/governance.rs new file mode 100644 index 00000000..ccb25732 --- /dev/null +++ b/programs/squads-voter/src/tools/governance.rs @@ -0,0 +1,19 @@ +use anchor_lang::prelude::Pubkey; +use spl_governance::state::{token_owner_record, vote_record}; + +pub fn get_vote_record_address( + program_id: &Pubkey, + realm: &Pubkey, + governing_token_mint: &Pubkey, + governing_token_owner: &Pubkey, + proposal: &Pubkey, +) -> Pubkey { + let token_owner_record_key = token_owner_record::get_token_owner_record_address( + program_id, + realm, + governing_token_mint, + governing_token_owner, + ); + + vote_record::get_vote_record_address(program_id, proposal, &token_owner_record_key) +} diff --git a/programs/squads-voter/src/tools/mod.rs b/programs/squads-voter/src/tools/mod.rs new file mode 100644 index 00000000..13516dbb --- /dev/null +++ b/programs/squads-voter/src/tools/mod.rs @@ -0,0 +1,4 @@ +pub mod anchor; +pub mod governance; +pub mod spl_token; +pub mod token_metadata; diff --git a/programs/squads-voter/src/tools/spl_token.rs b/programs/squads-voter/src/tools/spl_token.rs new file mode 100644 index 00000000..49a97d5b --- /dev/null +++ b/programs/squads-voter/src/tools/spl_token.rs @@ -0,0 +1,15 @@ +use anchor_lang::prelude::*; +use arrayref::array_ref; +use spl_governance::tools::spl_token::assert_is_valid_spl_token_account; + +/// Computationally cheap method to get amount from a token account +/// It reads amount without deserializing full account data +pub fn get_spl_token_amount(token_account_info: &AccountInfo) -> Result { + assert_is_valid_spl_token_account(token_account_info)?; + + // TokeAccount layout: mint(32), owner(32), amount(8), ... + let data = token_account_info.try_borrow_data()?; + let amount_bytes = array_ref![data, 64, 8]; + + Ok(u64::from_le_bytes(*amount_bytes)) +} diff --git a/programs/squads-voter/src/tools/token_metadata.rs b/programs/squads-voter/src/tools/token_metadata.rs new file mode 100644 index 00000000..d5fe2384 --- /dev/null +++ b/programs/squads-voter/src/tools/token_metadata.rs @@ -0,0 +1,31 @@ +use anchor_lang::prelude::*; + +use mpl_token_metadata::state::Metadata; + +use crate::error::NftVoterError; + +pub fn get_token_metadata(account_info: &AccountInfo) -> Result { + if *account_info.owner != mpl_token_metadata::ID { + return Err(NftVoterError::InvalidAccountOwner.into()); + } + + let metadata = Metadata::from_account_info(account_info)?; + + // I'm not sure if this is needed but try_from_slice_checked in from_account_info + // ignores Key::Uninitialized and hence checking for the exact Key match here + if metadata.key != mpl_token_metadata::state::Key::MetadataV1 { + return Err(NftVoterError::InvalidTokenMetadataAccount.into()); + } + + Ok(metadata) +} + +pub fn get_token_metadata_for_mint(account_info: &AccountInfo, mint: &Pubkey) -> Result { + let token_metadata = get_token_metadata(account_info)?; + + if token_metadata.mint != *mint { + return Err(NftVoterError::TokenMetadataDoesNotMatch.into()); + } + + Ok(token_metadata) +} diff --git a/programs/squads-voter/tests/cast_nft_vote.rs b/programs/squads-voter/tests/cast_nft_vote.rs new file mode 100644 index 00000000..24cd8ea7 --- /dev/null +++ b/programs/squads-voter/tests/cast_nft_vote.rs @@ -0,0 +1,1211 @@ +use crate::program_test::nft_voter_test::ConfigureCollectionArgs; +use gpl_nft_voter::error::NftVoterError; +use gpl_nft_voter::state::*; +use program_test::token_metadata_test::CreateNftArgs; +use program_test::{nft_voter_test::*, tools::assert_nft_voter_err}; + +use solana_program_test::*; +use solana_sdk::transport::TransportError; + +mod program_test; + +#[tokio::test] +async fn test_cast_nft_vote() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let nft_cookie1 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + nft_voter_test.bench.advance_clock().await; + let clock = nft_voter_test.bench.get_clock().await; + + // Act + let nft_vote_record_cookies = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + None, + ) + .await?; + + // Assert + let nft_vote_record = nft_voter_test + .get_nf_vote_record_account(&nft_vote_record_cookies[0].address) + .await; + + assert_eq!(nft_vote_record_cookies[0].account, nft_vote_record); + + let voter_weight_record = nft_voter_test + .get_voter_weight_record(&voter_weight_record_cookie.address) + .await; + + assert_eq!(voter_weight_record.voter_weight, 10); + assert_eq!(voter_weight_record.voter_weight_expiry, Some(clock.slot)); + assert_eq!( + voter_weight_record.weight_action, + Some(VoterWeightAction::CastVote.into()) + ); + assert_eq!( + voter_weight_record.weight_action_target, + Some(proposal_cookie.address) + ); + + Ok(()) +} + +#[tokio::test] +async fn test_cast_nft_vote_with_multiple_nfts() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let nft_cookie1 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + let nft_cookie2 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + nft_voter_test.bench.advance_clock().await; + let clock = nft_voter_test.bench.get_clock().await; + + // Act + let nft_vote_record_cookies = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1, &nft_cookie2], + None, + ) + .await?; + + // Assert + let nft_vote_record1 = nft_voter_test + .get_nf_vote_record_account(&nft_vote_record_cookies[0].address) + .await; + + assert_eq!(nft_vote_record_cookies[0].account, nft_vote_record1); + + let nft_vote_record2 = nft_voter_test + .get_nf_vote_record_account(&nft_vote_record_cookies[1].address) + .await; + + assert_eq!(nft_vote_record_cookies[1].account, nft_vote_record2); + + let voter_weight_record = nft_voter_test + .get_voter_weight_record(&voter_weight_record_cookie.address) + .await; + + assert_eq!(voter_weight_record.voter_weight, 20); + assert_eq!(voter_weight_record.voter_weight_expiry, Some(clock.slot)); + assert_eq!( + voter_weight_record.weight_action, + Some(VoterWeightAction::CastVote.into()) + ); + assert_eq!( + voter_weight_record.weight_action_target, + Some(proposal_cookie.address) + ); + + Ok(()) +} + +#[tokio::test] +async fn test_cast_nft_vote_with_nft_already_voted_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + None, + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let nft_cookie1 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + None, + ) + .await?; + + nft_voter_test.bench.advance_clock().await; + + // Act + + let err = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + None, + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, SquadsVoterError::NftAlreadyVoted); + + Ok(()) +} + +#[tokio::test] +async fn test_cast_nft_vote_invalid_voter_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + None, + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let nft_cookie1 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + let voter_cookie2 = nft_voter_test.bench.with_wallet().await; + + // Act + + let err = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie2, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + None, + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, NftVoterError::InvalidTokenOwnerForVoterWeightRecord); + + Ok(()) +} + +#[tokio::test] +async fn test_cast_nft_vote_with_unverified_collection_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + // Create NFT without verified collection + let nft_cookie1 = nft_voter_test + .token_metadata + .with_nft_v2( + &nft_collection_cookie, + &voter_cookie, + Some(CreateNftArgs { + verify_collection: false, + ..Default::default() + }), + ) + .await?; + + // Act + let err = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + None, + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, NftVoterError::CollectionMustBeVerified); + + Ok(()) +} + +#[tokio::test] +async fn test_cast_nft_vote_with_invalid_owner_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let voter_cookie2 = nft_voter_test.bench.with_wallet().await; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let nft_cookie = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie2, None) + .await?; + + // Act + let err = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie], + None, + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, NftVoterError::VoterDoesNotOwnNft); + + Ok(()) +} + +#[tokio::test] +async fn test_cast_nft_vote_with_invalid_collection_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let nft_collection_cookie2 = nft_voter_test.token_metadata.with_nft_collection().await?; + + let nft_cookie = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie2, &voter_cookie, None) + .await?; + + // Act + let err = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie], + None, + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, NftVoterError::CollectionNotFound); + + Ok(()) +} + +#[tokio::test] +async fn test_cast_nft_vote_with_invalid_metadata_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let mut nft1_cookie = nft_voter_test + .token_metadata + .with_nft_v2( + &nft_collection_cookie, + &voter_cookie, + Some(CreateNftArgs { + verify_collection: false, + ..Default::default() + }), + ) + .await?; + + let nft2_cookie = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + // Try to use verified NFT Metadata + nft1_cookie.metadata = nft2_cookie.metadata; + + // Act + let err = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft1_cookie], + None, + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, NftVoterError::TokenMetadataDoesNotMatch); + + Ok(()) +} + +#[tokio::test] +async fn test_cast_nft_vote_with_same_nft_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + None, + ) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let nft_cookie = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + // Act + let err = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie, &nft_cookie], + None, + ) + .await + .err() + .unwrap(); + + // Assert + + assert_nft_voter_err(err, NftVoterError::DuplicatedNftDetected); + + Ok(()) +} + +#[tokio::test] +async fn test_cast_nft_vote_with_no_nft_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let nft_cookie1 = nft_voter_test + .token_metadata + .with_nft_v2( + &nft_collection_cookie, + &voter_cookie, + Some(CreateNftArgs { + amount: 0, + ..Default::default() + }), + ) + .await?; + + // Act + let err = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + None, + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, NftVoterError::InvalidNftAmount); + + Ok(()) +} + +#[tokio::test] +async fn test_cast_nft_vote_with_max_5_nfts() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let mut nft_cookies = vec![]; + + for _ in 0..5 { + nft_voter_test.bench.advance_clock().await; + let nft_cookie = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + nft_cookies.push(nft_cookie) + } + + nft_voter_test.bench.advance_clock().await; + let clock = nft_voter_test.bench.get_clock().await; + + // Act + let nft_vote_record_cookies = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &nft_cookies.iter().collect::>(), + None, + ) + .await?; + + // Assert + let nft_vote_record1 = nft_voter_test + .get_nf_vote_record_account(&nft_vote_record_cookies[0].address) + .await; + + assert_eq!(nft_vote_record_cookies[0].account, nft_vote_record1); + + let nft_vote_record2 = nft_voter_test + .get_nf_vote_record_account(&nft_vote_record_cookies[1].address) + .await; + + assert_eq!(nft_vote_record_cookies[1].account, nft_vote_record2); + + let voter_weight_record = nft_voter_test + .get_voter_weight_record(&voter_weight_record_cookie.address) + .await; + + assert_eq!(voter_weight_record.voter_weight, 50); + assert_eq!(voter_weight_record.voter_weight_expiry, Some(clock.slot)); + assert_eq!( + voter_weight_record.weight_action, + Some(VoterWeightAction::CastVote.into()) + ); + assert_eq!( + voter_weight_record.weight_action_target, + Some(proposal_cookie.address) + ); + + Ok(()) +} + +#[tokio::test] +async fn test_cast_nft_vote_using_multiple_instructions() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let nft_cookie1 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + nft_voter_test.bench.advance_clock().await; + let clock = nft_voter_test.bench.get_clock().await; + + let args = CastNftVoteArgs { + cast_spl_gov_vote: false, + }; + + nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + Some(args), + ) + .await?; + + let nft_cookie2 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + // Act + + nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie2], + None, + ) + .await?; + + // Assert + + let voter_weight_record = nft_voter_test + .get_voter_weight_record(&voter_weight_record_cookie.address) + .await; + + assert_eq!(voter_weight_record.voter_weight, 20); + assert_eq!(voter_weight_record.voter_weight_expiry, Some(clock.slot)); + assert_eq!( + voter_weight_record.weight_action, + Some(VoterWeightAction::CastVote.into()) + ); + assert_eq!( + voter_weight_record.weight_action_target, + Some(proposal_cookie.address) + ); + + Ok(()) +} + +#[tokio::test] +async fn test_cast_nft_vote_using_multiple_instructions_with_nft_already_voted_error( +) -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let nft_cookie1 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + let args = CastNftVoteArgs { + cast_spl_gov_vote: false, + }; + + nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + Some(args), + ) + .await?; + + // Act + + let err = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + None, + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, NftVoterError::NftAlreadyVoted); + + Ok(()) +} + +#[tokio::test] +async fn test_cast_nft_vote_using_multiple_instructions_with_attempted_sandwiched_relinquish( +) -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let nft_cookie1 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + let args = CastNftVoteArgs { + cast_spl_gov_vote: false, + }; + + // Cast vote with NFT + let nft_vote_record_cookies = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + Some(args), + ) + .await?; + + // Try relinquish NftVoteRecords to accumulate vote + nft_voter_test + .relinquish_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &nft_vote_record_cookies, + ) + .await?; + + // Act + + nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + None, + ) + .await?; + + // Assert + + let voter_weight_record = nft_voter_test + .get_voter_weight_record(&voter_weight_record_cookie.address) + .await; + + assert_eq!(voter_weight_record.voter_weight, 10); + + Ok(()) +} diff --git a/programs/squads-voter/tests/configure_collection.rs b/programs/squads-voter/tests/configure_collection.rs new file mode 100644 index 00000000..4d83f5e3 --- /dev/null +++ b/programs/squads-voter/tests/configure_collection.rs @@ -0,0 +1,413 @@ +use gpl_nft_voter::error::NftVoterError; +use program_test::{ + nft_voter_test::NftVoterTest, + tools::{assert_anchor_err, assert_nft_voter_err}, +}; + +use solana_program_test::*; +use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; + +use crate::program_test::nft_voter_test::ConfigureCollectionArgs; + +mod program_test; + +#[tokio::test] +async fn test_configure_collection() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + // Act + let collection_config_cookie = nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + None, + ) + .await?; + + // Assert + let registrar = nft_voter_test + .get_registrar_account(®istrar_cookie.address) + .await; + + assert_eq!(registrar.collection_configs.len(), 1); + + assert_eq!( + registrar.collection_configs[0], + collection_config_cookie.collection_config + ); + + let max_voter_weight_record = nft_voter_test + .get_max_voter_weight_record(&max_voter_weight_record_cookie.address) + .await; + + assert_eq!(max_voter_weight_record.max_voter_weight_expiry, None); + assert_eq!( + max_voter_weight_record.max_voter_weight, + (registrar.collection_configs[0].weight as u32 * registrar.collection_configs[0].size) + as u64 + ); + + Ok(()) +} + +#[tokio::test] +async fn test_configure_multiple_collections() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie1 = nft_voter_test.token_metadata.with_nft_collection().await?; + let nft_collection_cookie2 = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + // Act + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie1, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { weight: 1, size: 5 }), + ) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie2, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 2, + size: 10, + }), + ) + .await?; + + // Assert + let registrar = nft_voter_test + .get_registrar_account(®istrar_cookie.address) + .await; + + assert_eq!(registrar.collection_configs.len(), 2); + + let max_voter_weight_record = nft_voter_test + .get_max_voter_weight_record(&max_voter_weight_record_cookie.address) + .await; + + assert_eq!(max_voter_weight_record.max_voter_weight_expiry, None); + assert_eq!(max_voter_weight_record.max_voter_weight, 25); + + Ok(()) +} + +#[tokio::test] +async fn test_configure_max_collections() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + // Act + + for _ in 0..registrar_cookie.max_collections { + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + None, + ) + .await?; + } + + // Assert + let registrar = nft_voter_test + .get_registrar_account(®istrar_cookie.address) + .await; + + assert_eq!( + registrar.collection_configs.len() as u8, + registrar_cookie.max_collections + ); + + let max_voter_weight_record = nft_voter_test + .get_max_voter_weight_record(&max_voter_weight_record_cookie.address) + .await; + + assert_eq!(max_voter_weight_record.max_voter_weight_expiry, None); + assert_eq!(max_voter_weight_record.max_voter_weight, 30); + + Ok(()) +} + +#[tokio::test] +async fn test_configure_existing_collection() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + None, + ) + .await?; + + // Act + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 2, + size: 10, + }), + ) + .await?; + + // Assert + let registrar = nft_voter_test + .get_registrar_account(®istrar_cookie.address) + .await; + + assert_eq!(registrar.collection_configs.len(), 1); + + let max_voter_weight_record = nft_voter_test + .get_max_voter_weight_record(&max_voter_weight_record_cookie.address) + .await; + + assert_eq!(max_voter_weight_record.max_voter_weight_expiry, None); + assert_eq!(max_voter_weight_record.max_voter_weight, 20); + + Ok(()) +} + +// TODO: Remove collection test + +#[tokio::test] +async fn test_configure_collection_with_invalid_realm_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + // Try to use a different Realm + let realm_cookie2 = nft_voter_test.governance.with_realm().await?; + + // Act + let err = nft_voter_test + .with_collection_using_ix( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + None, + |i| i.accounts[1].pubkey = realm_cookie2.address, // realm + None, + ) + .await + .err() + .unwrap(); + + // Assert + + assert_nft_voter_err(err, NftVoterError::InvalidRealmForRegistrar); + + Ok(()) +} + +#[tokio::test] +async fn test_configure_collection_with_realm_authority_must_sign_error( +) -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + // Act + let err = nft_voter_test + .with_collection_using_ix( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + None, + |i| i.accounts[2].is_signer = false, // realm_authority + Some(&[]), + ) + .await + .err() + .unwrap(); + + // Assert + + assert_anchor_err(err, anchor_lang::error::ErrorCode::AccountNotSigner); + + Ok(()) +} + +#[tokio::test] +async fn test_configure_collection_with_invalid_realm_authority_error() -> Result<(), TransportError> +{ + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + let realm_authority = Keypair::new(); + + // Act + let err = nft_voter_test + .with_collection_using_ix( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + None, + |i| i.accounts[2].pubkey = realm_authority.pubkey(), // realm_authority + Some(&[&realm_authority]), + ) + .await + .err() + .unwrap(); + + // Assert + + assert_nft_voter_err(err, NftVoterError::InvalidRealmAuthority); + + Ok(()) +} + +#[tokio::test] +async fn test_configure_collection_with_invalid_max_voter_weight_realm_error( +) -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let realm_cookie2 = nft_voter_test.governance.with_realm().await?; + let registrar_cookie2 = nft_voter_test.with_registrar(&realm_cookie2).await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie2) + .await?; + + // Act + let err = nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + None, + ) + .await + .err() + .unwrap(); + + // Assert + + assert_nft_voter_err(err, NftVoterError::InvalidMaxVoterWeightRecordRealm); + + Ok(()) +} + +#[tokio::test] +async fn test_configure_collection_with_invalid_max_voter_weight_mint_error( +) -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let mut realm_cookie = nft_voter_test.governance.with_realm().await?; + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + // Create Registrar for council mint + realm_cookie.account.community_mint = realm_cookie.account.config.council_mint.unwrap(); + let registrar_cookie2 = nft_voter_test.with_registrar(&realm_cookie).await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie2) + .await?; + + // Act + let err = nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + None, + ) + .await + .err() + .unwrap(); + + // Assert + + assert_nft_voter_err(err, NftVoterError::InvalidMaxVoterWeightRecordMint); + + Ok(()) +} diff --git a/programs/squads-voter/tests/create_max_voter_weight_record.rs b/programs/squads-voter/tests/create_max_voter_weight_record.rs new file mode 100644 index 00000000..2e5c7b35 --- /dev/null +++ b/programs/squads-voter/tests/create_max_voter_weight_record.rs @@ -0,0 +1,123 @@ +use program_test::{nft_voter_test::NftVoterTest, tools::assert_ix_err}; +use solana_program::instruction::InstructionError; +use solana_program_test::*; +use solana_sdk::transport::TransportError; + +mod program_test; + +#[tokio::test] +async fn test_create_max_voter_weight_record() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + // Act + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + // Assert + + let max_voter_weight_record = nft_voter_test + .get_max_voter_weight_record(&max_voter_weight_record_cookie.address) + .await; + + assert_eq!( + max_voter_weight_record_cookie.account, + max_voter_weight_record + ); + + Ok(()) +} + +#[tokio::test] +async fn test_create_max_voter_weight_record_with_invalid_realm_error() -> Result<(), TransportError> +{ + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let realm_cookie2 = nft_voter_test.governance.with_realm().await?; + + // Act + let err = nft_voter_test + .with_max_voter_weight_record_using_ix(®istrar_cookie, |i| { + i.accounts[2].pubkey = realm_cookie2.address // Realm + }) + .await + .err() + .unwrap(); + + // Assert + + // PDA doesn't match and hence the error is PrivilegeEscalation + assert_ix_err(err, InstructionError::PrivilegeEscalation); + + Ok(()) +} + +#[tokio::test] +async fn test_create_max_voter_weight_record_with_invalid_mint_error() -> Result<(), TransportError> +{ + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let realm_cookie2 = nft_voter_test.governance.with_realm().await?; + + // Act + let err = nft_voter_test + .with_max_voter_weight_record_using_ix(®istrar_cookie, |i| { + i.accounts[2].pubkey = realm_cookie2.address // Mint + }) + .await + .err() + .unwrap(); + + // Assert + + // PDA doesn't match and hence the error is PrivilegeEscalation + assert_ix_err(err, InstructionError::PrivilegeEscalation); + + Ok(()) +} + +#[tokio::test] +async fn test_create_max_voter_weight_record_with_already_exists_error( +) -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test.bench.advance_clock().await; + + // Act + let err = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await + .err() + .unwrap(); + + // Assert + + // InstructionError::Custom(0) is returned for TransactionError::AccountInUse + assert_ix_err(err, InstructionError::Custom(0)); + + Ok(()) +} diff --git a/programs/squads-voter/tests/create_registrar.rs b/programs/squads-voter/tests/create_registrar.rs new file mode 100644 index 00000000..29a9cc30 --- /dev/null +++ b/programs/squads-voter/tests/create_registrar.rs @@ -0,0 +1,157 @@ +mod program_test; + +use anchor_lang::prelude::Pubkey; +use gpl_nft_voter::error::NftVoterError; +use program_test::nft_voter_test::NftVoterTest; + +use solana_program::instruction::InstructionError; +use solana_program_test::*; +use solana_sdk::{signature::Keypair, transport::TransportError}; + +use program_test::tools::{assert_anchor_err, assert_ix_err, assert_nft_voter_err}; + +#[tokio::test] +async fn test_create_registrar() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + // Act + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + // Assert + let registrar = nft_voter_test + .get_registrar_account(®istrar_cookie.address) + .await; + + assert_eq!(registrar, registrar_cookie.account); + + Ok(()) +} + +#[tokio::test] +async fn test_create_registrar_with_invalid_realm_authority_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let mut realm_cookie = nft_voter_test.governance.with_realm().await?; + realm_cookie.realm_authority = Keypair::new(); + + // Act + let err = nft_voter_test + .with_registrar(&realm_cookie) + .await + .err() + .unwrap(); + + assert_nft_voter_err(err, NftVoterError::InvalidRealmAuthority); + + Ok(()) +} + +#[tokio::test] +async fn test_create_registrar_with_realm_authority_must_sign_error() -> Result<(), TransportError> +{ + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let mut realm_cookie = nft_voter_test.governance.with_realm().await?; + realm_cookie.realm_authority = Keypair::new(); + + // Act + let err = nft_voter_test + .with_registrar_using_ix( + &realm_cookie, + |i| i.accounts[4].is_signer = false, // realm_authority + Some(&[]), + ) + .await + .err() + .unwrap(); + + assert_anchor_err(err, anchor_lang::error::ErrorCode::AccountNotSigner); + + Ok(()) +} + +#[tokio::test] +async fn test_create_registrar_with_invalid_spl_gov_program_id_error() -> Result<(), TransportError> +{ + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let mut realm_cookie = nft_voter_test.governance.with_realm().await?; + realm_cookie.realm_authority = Keypair::new(); + + // Try to use a different program id + let governance_program_id = nft_voter_test.program_id; + + // Act + let err = nft_voter_test + .with_registrar_using_ix( + &realm_cookie, + |i| i.accounts[1].pubkey = governance_program_id, //governance_program_id + None, + ) + .await + .err() + .unwrap(); + + assert_anchor_err(err, anchor_lang::error::ErrorCode::ConstraintOwner); + + Ok(()) +} + +#[tokio::test] +async fn test_create_registrar_with_invalid_realm_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let mut realm_cookie = nft_voter_test.governance.with_realm().await?; + realm_cookie.realm_authority = Keypair::new(); + + // Act + let err = nft_voter_test + .with_registrar_using_ix( + &realm_cookie, + |i| i.accounts[2].pubkey = Pubkey::new_unique(), // realm + None, + ) + .await + .err() + .unwrap(); + + // PDA doesn't match and hence the error is PrivilegeEscalation + assert_ix_err(err, InstructionError::PrivilegeEscalation); + + Ok(()) +} + +#[tokio::test] +async fn test_create_registrar_with_invalid_governing_token_mint_error( +) -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let mut realm_cookie = nft_voter_test.governance.with_realm().await?; + realm_cookie.realm_authority = Keypair::new(); + + let mint_cookie = nft_voter_test.bench.with_mint().await?; + + // Act + let err = nft_voter_test + .with_registrar_using_ix( + &realm_cookie, + |i| i.accounts[3].pubkey = mint_cookie.address, // governing_token_mint + None, + ) + .await + .err() + .unwrap(); + + // PDA doesn't match and hence the error is PrivilegeEscalation + assert_ix_err(err, InstructionError::PrivilegeEscalation); + + Ok(()) +} diff --git a/programs/squads-voter/tests/create_voter_weight_record.rs b/programs/squads-voter/tests/create_voter_weight_record.rs new file mode 100644 index 00000000..c23d7858 --- /dev/null +++ b/programs/squads-voter/tests/create_voter_weight_record.rs @@ -0,0 +1,126 @@ +use program_test::nft_voter_test::NftVoterTest; +use program_test::tools::assert_ix_err; +use solana_program::instruction::InstructionError; +use solana_program_test::*; +use solana_sdk::transport::TransportError; + +mod program_test; + +#[tokio::test] +async fn test_create_voter_weight_record() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + // Act + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + // Assert + + let voter_weight_record = nft_voter_test + .get_voter_weight_record(&voter_weight_record_cookie.address) + .await; + + assert_eq!(voter_weight_record_cookie.account, voter_weight_record); + + Ok(()) +} + +#[tokio::test] +async fn test_create_voter_weight_record_with_invalid_realm_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let realm_cookie2 = nft_voter_test.governance.with_realm().await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + // Act + let err = nft_voter_test + .with_voter_weight_record_using_ix(®istrar_cookie, &voter_cookie, |i| { + i.accounts[2].pubkey = realm_cookie2.address // Realm + }) + .await + .err() + .unwrap(); + + // Assert + + // PDA doesn't match and hence the error is PrivilegeEscalation + assert_ix_err(err, InstructionError::PrivilegeEscalation); + + Ok(()) +} + +#[tokio::test] +async fn test_create_voter_weight_record_with_invalid_mint_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let realm_cookie2 = nft_voter_test.governance.with_realm().await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + // Act + let err = nft_voter_test + .with_voter_weight_record_using_ix(®istrar_cookie, &voter_cookie, |i| { + i.accounts[2].pubkey = realm_cookie2.address // Mint + }) + .await + .err() + .unwrap(); + + // Assert + + // PDA doesn't match and hence the error is PrivilegeEscalation + assert_ix_err(err, InstructionError::PrivilegeEscalation); + + Ok(()) +} + +#[tokio::test] +async fn test_create_voter_weight_record_with_already_exists_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + nft_voter_test.bench.advance_clock().await; + + // Act + let err = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await + .err() + .unwrap(); + + // Assert + + // InstructionError::Custom(0) is returned for TransactionError::AccountInUse + assert_ix_err(err, InstructionError::Custom(0)); + + Ok(()) +} diff --git a/programs/squads-voter/tests/fixtures/mpl_token_metadata.so b/programs/squads-voter/tests/fixtures/mpl_token_metadata.so new file mode 100755 index 0000000000000000000000000000000000000000..7c8ae98f6868ca62844ffc214e3190d1c68b6f2d GIT binary patch literal 460888 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 yyFOOAO9b?P}W)i literal 0 HcmV?d00001 diff --git a/programs/squads-voter/tests/fixtures/spl_governance.so b/programs/squads-voter/tests/fixtures/spl_governance.so new file mode 100755 index 0000000000000000000000000000000000000000..9a6f5962e095a4df4f2c971e76a89e3c31c3df4e GIT binary patch literal 867600 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�pHXt1E?NnqYk${Q?vM(I^@ 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%0A>NM3uyhzul!*UOjf5T(_{TJ+S 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, +} + +impl RealmCookie { + pub fn get_realm_authority(&self) -> Keypair { + clone_keypair(&self.realm_authority) + } +} + +pub struct ProposalCookie { + pub address: Pubkey, + pub account: ProposalV2, +} + +pub struct TokenOwnerRecordCookie { + pub address: Pubkey, + pub account: TokenOwnerRecordV2, +} + +pub struct GovernanceTest { + pub program_id: Pubkey, + pub bench: Arc, + pub next_id: u8, + pub community_voter_weight_addin: Option, + pub max_community_voter_weight_addin: Option, +} + +impl GovernanceTest { + pub fn program_id() -> Pubkey { + Pubkey::from_str("Governance111111111111111111111111111111111").unwrap() + } + + #[allow(dead_code)] + pub fn add_program(program_test: &mut ProgramTest) { + program_test.add_program("spl_governance", Self::program_id(), None); + } + + #[allow(dead_code)] + pub fn new( + bench: Arc, + community_voter_weight_addin: Option, + max_community_voter_weight_addin: Option, + ) -> Self { + GovernanceTest { + bench, + program_id: Self::program_id(), + next_id: 0, + community_voter_weight_addin, + max_community_voter_weight_addin, + } + } + + #[allow(dead_code)] + pub async fn with_realm(&mut self) -> Result { + let realm_authority = Keypair::new(); + + let community_mint_cookie = self.bench.with_mint().await?; + let council_mint_cookie = self.bench.with_mint().await?; + + self.next_id += 1; + let realm_name = format!("Realm #{}", self.next_id).to_string(); + + let min_community_weight_to_create_governance = 1; + let community_mint_max_vote_weight_source = MintMaxVoteWeightSource::FULL_SUPPLY_FRACTION; + + let realm_key = get_realm_address(&self.program_id, &realm_name); + + let create_realm_ix = create_realm( + &self.program_id, + &realm_authority.pubkey(), + &community_mint_cookie.address, + &self.bench.payer.pubkey(), + Some(council_mint_cookie.address), + self.community_voter_weight_addin, + self.max_community_voter_weight_addin, + realm_name.clone(), + min_community_weight_to_create_governance, + community_mint_max_vote_weight_source.clone(), + ); + + self.bench + .process_transaction(&[create_realm_ix], None) + .await?; + + let account = RealmV2 { + account_type: GovernanceAccountType::RealmV2, + community_mint: community_mint_cookie.address, + + name: realm_name, + reserved: [0; 6], + authority: Some(realm_authority.pubkey()), + config: RealmConfig { + council_mint: Some(council_mint_cookie.address), + reserved: [0; 6], + min_community_weight_to_create_governance, + community_mint_max_vote_weight_source, + use_community_voter_weight_addin: false, + use_max_community_voter_weight_addin: false, + }, + voting_proposal_count: 0, + reserved_v2: [0; 128], + }; + + Ok(RealmCookie { + address: realm_key, + account, + realm_authority, + community_mint_cookie, + council_mint_cookie: Some(council_mint_cookie), + }) + } + + #[allow(dead_code)] + pub async fn with_proposal( + &mut self, + realm_cookie: &RealmCookie, + ) -> Result { + let token_account_cookie = self + .bench + .with_token_account(&realm_cookie.account.community_mint) + .await?; + + let token_owner = self.bench.payer.pubkey(); + let council_mint_cookie = realm_cookie.council_mint_cookie.as_ref().unwrap(); + let governing_token_mint = council_mint_cookie.address; + + let governing_token_account_cookie = self + .bench + .with_tokens(council_mint_cookie, &token_owner, 1) + .await?; + + let proposal_owner_record_key = get_token_owner_record_address( + &self.program_id, + &realm_cookie.address, + &governing_token_mint, + &token_owner, + ); + + let create_tor_ix = create_token_owner_record( + &self.program_id, + &realm_cookie.address, + &self.bench.payer.pubkey(), + &governing_token_mint, + &self.bench.payer.pubkey(), + ); + + self.bench + .process_transaction(&[create_tor_ix], None) + .await?; + + let deposit_ix = deposit_governing_tokens( + &self.program_id, + &realm_cookie.address, + &governing_token_account_cookie.address, + &token_owner, + &token_owner, + &self.bench.payer.pubkey(), + 1, + &governing_token_mint, + ); + + self.bench.process_transaction(&[deposit_ix], None).await?; + + let governance_key = get_governance_address( + &self.program_id, + &realm_cookie.address, + &token_account_cookie.address, + ); + + let create_governance_ix = create_governance( + &self.program_id, + &realm_cookie.address, + Some(&token_account_cookie.address), + &proposal_owner_record_key, + &self.bench.payer.pubkey(), + &realm_cookie.realm_authority.pubkey(), + None, + spl_governance::state::governance::GovernanceConfig { + vote_threshold_percentage: VoteThresholdPercentage::YesVote(60), + min_community_weight_to_create_proposal: 1, + min_transaction_hold_up_time: 0, + max_voting_time: 600, + vote_tipping: VoteTipping::Disabled, + proposal_cool_off_time: 0, + min_council_weight_to_create_proposal: 1, + }, + ); + + self.bench + .process_transaction( + &[create_governance_ix], + Some(&[&realm_cookie.realm_authority]), + ) + .await?; + + let proposal_index: u32 = 0; + let proposal_governing_token_mint = realm_cookie.account.community_mint; + + let proposal_key = get_proposal_address( + &self.program_id, + &governance_key, + &proposal_governing_token_mint, + &proposal_index.to_le_bytes(), + ); + + let create_proposal_ix = create_proposal( + &self.program_id, + &governance_key, + &proposal_owner_record_key, + &token_owner, + &self.bench.payer.pubkey(), + None, + &realm_cookie.address, + String::from("Proposal #1"), + String::from("Proposal #1 link"), + &proposal_governing_token_mint, + spl_governance::state::proposal::VoteType::SingleChoice, + vec!["Yes".to_string()], + true, + 0_u32, + ); + + let sign_off_proposal_ix = sign_off_proposal( + &self.program_id, + &realm_cookie.address, + &governance_key, + &proposal_key, + &token_owner, + Some(&proposal_owner_record_key), + ); + + self.bench + .process_transaction(&[create_proposal_ix, sign_off_proposal_ix], None) + .await?; + + let account = ProposalV2 { + account_type: GovernanceAccountType::GovernanceV2, + governing_token_mint: proposal_governing_token_mint, + state: ProposalState::Voting, + governance: governance_key, + token_owner_record: proposal_owner_record_key, + signatories_count: 1, + signatories_signed_off_count: 1, + vote_type: spl_governance::state::proposal::VoteType::SingleChoice, + options: vec![], + deny_vote_weight: Some(1), + veto_vote_weight: None, + abstain_vote_weight: None, + start_voting_at: None, + draft_at: 1, + signing_off_at: None, + voting_at: None, + voting_at_slot: None, + voting_completed_at: None, + executing_at: None, + closed_at: None, + execution_flags: spl_governance::state::enums::InstructionExecutionFlags::None, + max_vote_weight: None, + max_voting_time: None, + vote_threshold_percentage: None, + reserved: [0; 64], + name: String::from("Proposal #1"), + description_link: String::from("Proposal #1 link"), + }; + + Ok(ProposalCookie { + address: proposal_key, + account, + }) + } + + #[allow(dead_code)] + pub async fn with_token_owner_record( + &mut self, + realm_cookie: &RealmCookie, + token_owner_cookie: &WalletCookie, + ) -> Result { + let token_owner_record_key = get_token_owner_record_address( + &self.program_id, + &realm_cookie.address, + &realm_cookie.account.community_mint, + &token_owner_cookie.address, + ); + + let create_tor_ix = create_token_owner_record( + &self.program_id, + &realm_cookie.address, + &token_owner_cookie.address, + &realm_cookie.account.community_mint, + &self.bench.payer.pubkey(), + ); + + self.bench + .process_transaction(&[create_tor_ix], None) + .await?; + + let account = TokenOwnerRecordV2 { + account_type: GovernanceAccountType::TokenOwnerRecordV2, + realm: realm_cookie.address, + governing_token_mint: realm_cookie.account.community_mint, + governing_token_owner: token_owner_cookie.address, + governing_token_deposit_amount: 0, + unrelinquished_votes_count: 0, + total_votes_count: 0, + outstanding_proposal_count: 0, + reserved: [0; 7], + governance_delegate: None, + reserved_v2: [0; 128], + }; + + Ok(TokenOwnerRecordCookie { + address: token_owner_record_key, + account, + }) + } + + #[allow(dead_code)] + pub async fn relinquish_vote( + &mut self, + proposal_cookie: &ProposalCookie, + token_owner_cookie: &WalletCookie, + token_owner_record_cookie: &TokenOwnerRecordCookie, + ) -> Result<(), TransportError> { + let relinquish_vote_ix = relinquish_vote( + &self.program_id, + &proposal_cookie.account.governance, + &proposal_cookie.address, + &token_owner_record_cookie.address, + &proposal_cookie.account.governing_token_mint, + Some(token_owner_record_cookie.account.governing_token_owner), + Some(self.bench.payer.pubkey()), + ); + + self.bench + .process_transaction(&[relinquish_vote_ix], Some(&[&token_owner_cookie.signer])) + .await?; + + Ok(()) + } + + #[allow(dead_code)] + pub async fn get_proposal(&mut self, proposal_key: &Pubkey) -> ProposalV2 { + self.bench + .get_borsh_account::(proposal_key) + .await + } + + #[allow(dead_code)] + pub async fn get_token_owner_record( + &mut self, + token_owner_record_key: &Pubkey, + ) -> TokenOwnerRecordV2 { + self.bench + .get_borsh_account::(token_owner_record_key) + .await + } +} diff --git a/programs/squads-voter/tests/program_test/mod.rs b/programs/squads-voter/tests/program_test/mod.rs new file mode 100644 index 00000000..60cbb5dd --- /dev/null +++ b/programs/squads-voter/tests/program_test/mod.rs @@ -0,0 +1,5 @@ +pub mod governance_test; +pub mod nft_voter_test; +pub mod program_test_bench; +pub mod token_metadata_test; +pub mod tools; diff --git a/programs/squads-voter/tests/program_test/nft_voter_test.rs b/programs/squads-voter/tests/program_test/nft_voter_test.rs new file mode 100644 index 00000000..75c9e11e --- /dev/null +++ b/programs/squads-voter/tests/program_test/nft_voter_test.rs @@ -0,0 +1,595 @@ +use std::sync::Arc; + +use anchor_lang::prelude::{AccountMeta, Pubkey}; + +use gpl_nft_voter::state::max_voter_weight_record::{ + get_max_voter_weight_record_address, MaxVoterWeightRecord, +}; +use gpl_nft_voter::state::*; +use solana_sdk::transport::TransportError; +use spl_governance::instruction::cast_vote; +use spl_governance::state::vote_record::{self, Vote, VoteChoice}; + +use gpl_nft_voter::state::{ + get_nft_vote_record_address, get_registrar_address, CollectionConfig, NftVoteRecord, Registrar, +}; + +use solana_program_test::ProgramTest; +use solana_sdk::instruction::Instruction; +use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; + +use crate::program_test::governance_test::GovernanceTest; +use crate::program_test::program_test_bench::ProgramTestBench; + +use crate::program_test::governance_test::{ProposalCookie, RealmCookie, TokenOwnerRecordCookie}; +use crate::program_test::program_test_bench::WalletCookie; +use crate::program_test::token_metadata_test::{NftCollectionCookie, NftCookie, TokenMetadataTest}; +use crate::program_test::tools::NopOverride; + +#[derive(Debug, PartialEq)] +pub struct RegistrarCookie { + pub address: Pubkey, + pub account: Registrar, + + pub realm_authority: Keypair, + pub max_collections: u8, +} + +pub struct VoterWeightRecordCookie { + pub address: Pubkey, + pub account: VoterWeightRecord, +} + +pub struct MaxVoterWeightRecordCookie { + pub address: Pubkey, + pub account: MaxVoterWeightRecord, +} + +pub struct CollectionConfigCookie { + pub collection_config: CollectionConfig, +} + +pub struct ConfigureCollectionArgs { + pub weight: u64, + pub size: u32, +} + +impl Default for ConfigureCollectionArgs { + fn default() -> Self { + Self { weight: 1, size: 3 } + } +} + +#[derive(Debug, PartialEq)] +pub struct NftVoteRecordCookie { + pub address: Pubkey, + pub account: NftVoteRecord, +} + +pub struct CastNftVoteArgs { + pub cast_spl_gov_vote: bool, +} + +impl Default for CastNftVoteArgs { + fn default() -> Self { + Self { + cast_spl_gov_vote: true, + } + } +} + +pub struct NftVoterTest { + pub program_id: Pubkey, + pub bench: Arc, + pub governance: GovernanceTest, + pub token_metadata: TokenMetadataTest, +} + +impl NftVoterTest { + #[allow(dead_code)] + pub fn add_program(program_test: &mut ProgramTest) { + program_test.add_program("gpl_nft_voter", gpl_nft_voter::id(), None); + } + + #[allow(dead_code)] + pub async fn start_new() -> Self { + let mut program_test = ProgramTest::default(); + + NftVoterTest::add_program(&mut program_test); + GovernanceTest::add_program(&mut program_test); + TokenMetadataTest::add_program(&mut program_test); + + let program_id = gpl_nft_voter::id(); + + let bench = ProgramTestBench::start_new(program_test).await; + let bench_rc = Arc::new(bench); + + let governance_bench = + GovernanceTest::new(bench_rc.clone(), Some(program_id), Some(program_id)); + let token_metadata_bench = TokenMetadataTest::new(bench_rc.clone()); + + Self { + program_id, + bench: bench_rc, + governance: governance_bench, + token_metadata: token_metadata_bench, + } + } + + #[allow(dead_code)] + pub async fn with_registrar( + &mut self, + realm_cookie: &RealmCookie, + ) -> Result { + self.with_registrar_using_ix(realm_cookie, NopOverride, None) + .await + } + + #[allow(dead_code)] + pub async fn with_registrar_using_ix( + &mut self, + realm_cookie: &RealmCookie, + instruction_override: F, + signers_override: Option<&[&Keypair]>, + ) -> Result { + let registrar_key = + get_registrar_address(&realm_cookie.address, &realm_cookie.account.community_mint); + + let max_collections = 10; + + let data = + anchor_lang::InstructionData::data(&gpl_nft_voter::instruction::CreateRegistrar { + max_collections, + }); + + let accounts = anchor_lang::ToAccountMetas::to_account_metas( + &gpl_nft_voter::accounts::CreateRegistrar { + registrar: registrar_key, + realm: realm_cookie.address, + governance_program_id: self.governance.program_id, + governing_token_mint: realm_cookie.account.community_mint, + realm_authority: realm_cookie.get_realm_authority().pubkey(), + payer: self.bench.payer.pubkey(), + system_program: solana_sdk::system_program::id(), + }, + None, + ); + + let mut create_registrar_ix = Instruction { + program_id: gpl_nft_voter::id(), + accounts, + data, + }; + + instruction_override(&mut create_registrar_ix); + + let default_signers = &[&realm_cookie.realm_authority]; + let signers = signers_override.unwrap_or(default_signers); + + self.bench + .process_transaction(&[create_registrar_ix], Some(signers)) + .await?; + + let account = Registrar { + governance_program_id: self.governance.program_id, + realm: realm_cookie.address, + governing_token_mint: realm_cookie.account.community_mint, + collection_configs: vec![], + reserved: [0; 128], + }; + + Ok(RegistrarCookie { + address: registrar_key, + account, + realm_authority: realm_cookie.get_realm_authority(), + max_collections, + }) + } + + #[allow(dead_code)] + pub async fn with_voter_weight_record( + &self, + registrar_cookie: &RegistrarCookie, + voter_cookie: &WalletCookie, + ) -> Result { + self.with_voter_weight_record_using_ix(registrar_cookie, voter_cookie, NopOverride) + .await + } + + #[allow(dead_code)] + pub async fn with_voter_weight_record_using_ix( + &self, + registrar_cookie: &RegistrarCookie, + voter_cookie: &WalletCookie, + instruction_override: F, + ) -> Result { + let governing_token_owner = voter_cookie.address; + + let (voter_weight_record_key, _) = Pubkey::find_program_address( + &[ + b"voter-weight-record".as_ref(), + registrar_cookie.account.realm.as_ref(), + registrar_cookie.account.governing_token_mint.as_ref(), + governing_token_owner.as_ref(), + ], + &gpl_nft_voter::id(), + ); + + let data = anchor_lang::InstructionData::data( + &gpl_nft_voter::instruction::CreateVoterWeightRecord { + governing_token_owner, + }, + ); + + let accounts = gpl_nft_voter::accounts::CreateVoterWeightRecord { + governance_program_id: self.governance.program_id, + realm: registrar_cookie.account.realm, + realm_governing_token_mint: registrar_cookie.account.governing_token_mint, + voter_weight_record: voter_weight_record_key, + payer: self.bench.payer.pubkey(), + system_program: solana_sdk::system_program::id(), + }; + + let mut create_voter_weight_record_ix = Instruction { + program_id: gpl_nft_voter::id(), + accounts: anchor_lang::ToAccountMetas::to_account_metas(&accounts, None), + data, + }; + + instruction_override(&mut create_voter_weight_record_ix); + + self.bench + .process_transaction(&[create_voter_weight_record_ix], None) + .await?; + + let account = VoterWeightRecord { + realm: registrar_cookie.account.realm, + governing_token_mint: registrar_cookie.account.governing_token_mint, + governing_token_owner, + voter_weight: 0, + voter_weight_expiry: Some(0), + weight_action: None, + weight_action_target: None, + reserved: [0; 8], + }; + + Ok(VoterWeightRecordCookie { + address: voter_weight_record_key, + account, + }) + } + + #[allow(dead_code)] + pub async fn with_max_voter_weight_record( + &mut self, + registrar_cookie: &RegistrarCookie, + ) -> Result { + self.with_max_voter_weight_record_using_ix(registrar_cookie, NopOverride) + .await + } + + #[allow(dead_code)] + pub async fn with_max_voter_weight_record_using_ix( + &mut self, + registrar_cookie: &RegistrarCookie, + instruction_override: F, + ) -> Result { + let max_voter_weight_record_key = get_max_voter_weight_record_address( + ®istrar_cookie.account.realm, + ®istrar_cookie.account.governing_token_mint, + ); + + let data = anchor_lang::InstructionData::data( + &gpl_nft_voter::instruction::CreateMaxVoterWeightRecord {}, + ); + + let accounts = gpl_nft_voter::accounts::CreateMaxVoterWeightRecord { + governance_program_id: self.governance.program_id, + realm: registrar_cookie.account.realm, + realm_governing_token_mint: registrar_cookie.account.governing_token_mint, + max_voter_weight_record: max_voter_weight_record_key, + payer: self.bench.payer.pubkey(), + system_program: solana_sdk::system_program::id(), + }; + + let mut create_max_voter_weight_record_ix = Instruction { + program_id: gpl_nft_voter::id(), + accounts: anchor_lang::ToAccountMetas::to_account_metas(&accounts, None), + data, + }; + + instruction_override(&mut create_max_voter_weight_record_ix); + + self.bench + .process_transaction(&[create_max_voter_weight_record_ix], None) + .await?; + + let account = MaxVoterWeightRecord { + realm: registrar_cookie.account.realm, + governing_token_mint: registrar_cookie.account.governing_token_mint, + max_voter_weight: 0, + max_voter_weight_expiry: Some(0), + reserved: [0; 8], + }; + + Ok(MaxVoterWeightRecordCookie { + account, + address: max_voter_weight_record_key, + }) + } + + #[allow(dead_code)] + pub async fn update_voter_weight_record( + &self, + registrar_cookie: &RegistrarCookie, + voter_weight_record_cookie: &mut VoterWeightRecordCookie, + voter_weight_action: VoterWeightAction, + nft_cookies: &[&NftCookie], + ) -> Result<(), TransportError> { + let data = anchor_lang::InstructionData::data( + &gpl_nft_voter::instruction::UpdateVoterWeightRecord { + voter_weight_action, + }, + ); + + let accounts = gpl_nft_voter::accounts::UpdateVoterWeightRecord { + registrar: registrar_cookie.address, + voter_weight_record: voter_weight_record_cookie.address, + }; + + let mut account_metas = anchor_lang::ToAccountMetas::to_account_metas(&accounts, None); + + for nft_cookie in nft_cookies { + account_metas.push(AccountMeta::new_readonly(nft_cookie.address, false)); + account_metas.push(AccountMeta::new_readonly(nft_cookie.metadata, false)); + } + + let instructions = vec![Instruction { + program_id: gpl_nft_voter::id(), + accounts: account_metas, + data, + }]; + + self.bench.process_transaction(&instructions, None).await + } + + #[allow(dead_code)] + pub async fn relinquish_nft_vote( + &mut self, + registrar_cookie: &RegistrarCookie, + voter_weight_record_cookie: &VoterWeightRecordCookie, + proposal_cookie: &ProposalCookie, + voter_cookie: &WalletCookie, + voter_token_owner_record_cookie: &TokenOwnerRecordCookie, + nft_vote_record_cookies: &Vec, + ) -> Result<(), TransportError> { + let data = + anchor_lang::InstructionData::data(&gpl_nft_voter::instruction::RelinquishNftVote {}); + + let vote_record_key = vote_record::get_vote_record_address( + &self.governance.program_id, + &proposal_cookie.address, + &voter_token_owner_record_cookie.address, + ); + + let accounts = gpl_nft_voter::accounts::RelinquishNftVote { + registrar: registrar_cookie.address, + voter_weight_record: voter_weight_record_cookie.address, + governance: proposal_cookie.account.governance, + proposal: proposal_cookie.address, + governing_token_owner: voter_cookie.address, + vote_record: vote_record_key, + beneficiary: self.bench.payer.pubkey(), + }; + + let mut account_metas = anchor_lang::ToAccountMetas::to_account_metas(&accounts, None); + + for nft_vote_record_cookie in nft_vote_record_cookies { + account_metas.push(AccountMeta::new(nft_vote_record_cookie.address, false)); + } + + let relinquish_nft_vote_ix = Instruction { + program_id: gpl_nft_voter::id(), + accounts: account_metas, + data, + }; + + self.bench + .process_transaction(&[relinquish_nft_vote_ix], Some(&[&voter_cookie.signer])) + .await?; + + Ok(()) + } + + #[allow(dead_code)] + pub async fn with_collection( + &mut self, + registrar_cookie: &RegistrarCookie, + nft_collection_cookie: &NftCollectionCookie, + max_voter_weight_record_cookie: &MaxVoterWeightRecordCookie, + args: Option, + ) -> Result { + self.with_collection_using_ix( + registrar_cookie, + nft_collection_cookie, + max_voter_weight_record_cookie, + args, + NopOverride, + None, + ) + .await + } + + #[allow(dead_code)] + pub async fn with_collection_using_ix( + &mut self, + registrar_cookie: &RegistrarCookie, + nft_collection_cookie: &NftCollectionCookie, + max_voter_weight_record_cookie: &MaxVoterWeightRecordCookie, + args: Option, + instruction_override: F, + signers_override: Option<&[&Keypair]>, + ) -> Result { + let args = args.unwrap_or_default(); + + let data = + anchor_lang::InstructionData::data(&gpl_nft_voter::instruction::ConfigureCollection { + weight: args.weight, + size: args.size, + }); + + let accounts = gpl_nft_voter::accounts::ConfigureCollection { + registrar: registrar_cookie.address, + realm: registrar_cookie.account.realm, + realm_authority: registrar_cookie.realm_authority.pubkey(), + collection: nft_collection_cookie.mint, + max_voter_weight_record: max_voter_weight_record_cookie.address, + }; + + let mut configure_collection_ix = Instruction { + program_id: gpl_nft_voter::id(), + accounts: anchor_lang::ToAccountMetas::to_account_metas(&accounts, None), + data, + }; + + instruction_override(&mut configure_collection_ix); + + let default_signers = &[®istrar_cookie.realm_authority]; + let signers = signers_override.unwrap_or(default_signers); + + self.bench + .process_transaction(&[configure_collection_ix], Some(signers)) + .await?; + + let collection_config = CollectionConfig { + collection: nft_collection_cookie.mint, + size: args.size, + weight: args.weight, + reserved: [0; 8], + }; + + Ok(CollectionConfigCookie { collection_config }) + } + + /// Casts NFT Vote and spl-gov Vote + #[allow(dead_code)] + pub async fn cast_nft_vote( + &mut self, + registrar_cookie: &RegistrarCookie, + voter_weight_record_cookie: &VoterWeightRecordCookie, + max_voter_weight_record_cookie: &MaxVoterWeightRecordCookie, + proposal_cookie: &ProposalCookie, + nft_voter_cookie: &WalletCookie, + voter_token_owner_record_cookie: &TokenOwnerRecordCookie, + nft_cookies: &[&NftCookie], + args: Option, + ) -> Result, TransportError> { + let args = args.unwrap_or_default(); + + let data = anchor_lang::InstructionData::data(&gpl_nft_voter::instruction::CastNftVote { + proposal: proposal_cookie.address, + }); + + let accounts = gpl_nft_voter::accounts::CastNftVote { + registrar: registrar_cookie.address, + voter_weight_record: voter_weight_record_cookie.address, + governing_token_owner: nft_voter_cookie.address, + payer: self.bench.payer.pubkey(), + system_program: solana_sdk::system_program::id(), + }; + + let mut account_metas = anchor_lang::ToAccountMetas::to_account_metas(&accounts, None); + let mut nft_vote_record_cookies = vec![]; + + for nft_cookie in nft_cookies { + account_metas.push(AccountMeta::new_readonly(nft_cookie.address, false)); + account_metas.push(AccountMeta::new_readonly(nft_cookie.metadata, false)); + + let nft_vote_record_key = get_nft_vote_record_address( + &proposal_cookie.address, + &nft_cookie.mint_cookie.address, + ); + account_metas.push(AccountMeta::new(nft_vote_record_key, false)); + + let account = NftVoteRecord { + proposal: proposal_cookie.address, + nft_mint: nft_cookie.mint_cookie.address, + governing_token_owner: voter_weight_record_cookie.account.governing_token_owner, + account_discriminator: NftVoteRecord::ACCOUNT_DISCRIMINATOR, + reserved: [0; 8], + }; + + nft_vote_record_cookies.push(NftVoteRecordCookie { + address: nft_vote_record_key, + account, + }) + } + + let cast_nft_vote_ix = Instruction { + program_id: gpl_nft_voter::id(), + accounts: account_metas, + data, + }; + + let mut instruction = vec![cast_nft_vote_ix]; + + if args.cast_spl_gov_vote { + // spl-gov cast vote + let vote = Vote::Approve(vec![VoteChoice { + rank: 0, + weight_percentage: 100, + }]); + + let cast_vote_ix = cast_vote( + &self.governance.program_id, + ®istrar_cookie.account.realm, + &proposal_cookie.account.governance, + &proposal_cookie.address, + &proposal_cookie.account.token_owner_record, + &voter_token_owner_record_cookie.address, + &nft_voter_cookie.address, + &proposal_cookie.account.governing_token_mint, + &self.bench.payer.pubkey(), + Some(voter_weight_record_cookie.address), + Some(max_voter_weight_record_cookie.address), + vote, + ); + + instruction.push(cast_vote_ix); + } + + self.bench + .process_transaction(&instruction, Some(&[&nft_voter_cookie.signer])) + .await?; + + Ok(nft_vote_record_cookies) + } + + #[allow(dead_code)] + pub async fn get_registrar_account(&mut self, registrar: &Pubkey) -> Registrar { + self.bench.get_anchor_account::(*registrar).await + } + + #[allow(dead_code)] + pub async fn get_nf_vote_record_account(&mut self, nft_vote_record: &Pubkey) -> NftVoteRecord { + self.bench + .get_borsh_account::(nft_vote_record) + .await + } + + #[allow(dead_code)] + pub async fn get_max_voter_weight_record( + &self, + max_voter_weight_record: &Pubkey, + ) -> MaxVoterWeightRecord { + self.bench + .get_anchor_account(*max_voter_weight_record) + .await + } + + #[allow(dead_code)] + pub async fn get_voter_weight_record(&self, voter_weight_record: &Pubkey) -> VoterWeightRecord { + self.bench.get_anchor_account(*voter_weight_record).await + } +} diff --git a/programs/squads-voter/tests/program_test/program_test_bench.rs b/programs/squads-voter/tests/program_test/program_test_bench.rs new file mode 100644 index 00000000..150a6009 --- /dev/null +++ b/programs/squads-voter/tests/program_test/program_test_bench.rs @@ -0,0 +1,324 @@ +use std::cell::RefCell; + +use anchor_lang::{ + prelude::{Pubkey, Rent}, + AccountDeserialize, +}; + +use solana_program::{borsh::try_from_slice_unchecked, system_program}; +use solana_program_test::{ProgramTest, ProgramTestContext}; +use solana_sdk::{ + account::{Account, ReadableAccount}, + instruction::Instruction, + program_pack::Pack, + signature::Keypair, + signer::Signer, + system_instruction, + transaction::Transaction, + transport::TransportError, +}; + +use borsh::BorshDeserialize; + +use crate::program_test::tools::clone_keypair; + +pub struct MintCookie { + pub address: Pubkey, + pub mint_authority: Keypair, + pub freeze_authority: Option, +} +pub struct TokenAccountCookie { + pub address: Pubkey, +} + +#[derive(Debug)] +pub struct WalletCookie { + pub address: Pubkey, + pub account: Account, + + pub signer: Keypair, +} + +pub struct ProgramTestBench { + pub context: RefCell, + pub payer: Keypair, + pub rent: Rent, +} + +impl ProgramTestBench { + /// Create new bench given a ProgramTest instance populated with all of the + /// desired programs. + pub async fn start_new(program_test: ProgramTest) -> Self { + let mut context = program_test.start_with_context().await; + + let payer = clone_keypair(&context.payer); + + let rent = context.banks_client.get_rent().await.unwrap(); + + Self { + payer, + context: RefCell::new(context), + rent, + } + } + + #[allow(dead_code)] + pub async fn process_transaction( + &self, + instructions: &[Instruction], + signers: Option<&[&Keypair]>, + ) -> Result<(), TransportError> { + let mut context = self.context.borrow_mut(); + + let mut transaction = + Transaction::new_with_payer(&instructions, Some(&context.payer.pubkey())); + + let mut all_signers = vec![&context.payer]; + + if let Some(signers) = signers { + all_signers.extend_from_slice(signers); + } + + transaction.sign(&all_signers, context.last_blockhash); + + context + .banks_client + .process_transaction_with_commitment( + transaction, + solana_sdk::commitment_config::CommitmentLevel::Processed, + ) + .await + } + + pub async fn get_clock(&self) -> solana_program::clock::Clock { + self.context + .borrow_mut() + .banks_client + .get_sysvar::() + .await + .unwrap() + } + + #[allow(dead_code)] + pub async fn advance_clock(&self) { + let clock = self.get_clock().await; + self.context + .borrow_mut() + .warp_to_slot(clock.slot + 2) + .unwrap(); + } + + pub async fn with_mint(&self) -> Result { + let mint_keypair = Keypair::new(); + let mint_authority = Keypair::new(); + let freeze_authority = Keypair::new(); + + self.create_mint(&mint_keypair, &mint_authority.pubkey(), None) + .await?; + + Ok(MintCookie { + address: mint_keypair.pubkey(), + mint_authority, + freeze_authority: Some(freeze_authority), + }) + } + + #[allow(dead_code)] + pub async fn create_mint( + &self, + mint_keypair: &Keypair, + mint_authority: &Pubkey, + freeze_authority: Option<&Pubkey>, + ) -> Result<(), TransportError> { + let mint_rent = self.rent.minimum_balance(spl_token::state::Mint::LEN); + + let instructions = [ + system_instruction::create_account( + &self.context.borrow().payer.pubkey(), + &mint_keypair.pubkey(), + mint_rent, + spl_token::state::Mint::LEN as u64, + &spl_token::id(), + ), + spl_token::instruction::initialize_mint( + &spl_token::id(), + &mint_keypair.pubkey(), + mint_authority, + freeze_authority, + 0, + ) + .unwrap(), + ]; + + self.process_transaction(&instructions, Some(&[mint_keypair])) + .await + } + + #[allow(dead_code)] + pub async fn with_token_account( + &self, + token_mint: &Pubkey, + ) -> Result { + let token_account_keypair = Keypair::new(); + self.create_token_account(&token_account_keypair, token_mint, &self.payer.pubkey()) + .await?; + + Ok(TokenAccountCookie { + address: token_account_keypair.pubkey(), + }) + } + + #[allow(dead_code)] + pub async fn with_tokens( + &self, + mint_cookie: &MintCookie, + owner: &Pubkey, + amount: u64, + ) -> Result { + let token_account_keypair = Keypair::new(); + + self.create_token_account(&token_account_keypair, &mint_cookie.address, owner) + .await?; + + self.mint_tokens( + &mint_cookie.address, + &mint_cookie.mint_authority, + &token_account_keypair.pubkey(), + amount, + ) + .await?; + + Ok(TokenAccountCookie { + address: token_account_keypair.pubkey(), + }) + } + + pub async fn mint_tokens( + &self, + token_mint: &Pubkey, + token_mint_authority: &Keypair, + token_account: &Pubkey, + amount: u64, + ) -> Result<(), TransportError> { + let mint_instruction = spl_token::instruction::mint_to( + &spl_token::id(), + token_mint, + token_account, + &token_mint_authority.pubkey(), + &[], + amount, + ) + .unwrap(); + + self.process_transaction(&[mint_instruction], Some(&[token_mint_authority])) + .await + } + + #[allow(dead_code)] + pub async fn create_token_account( + &self, + token_account_keypair: &Keypair, + token_mint: &Pubkey, + owner: &Pubkey, + ) -> Result<(), TransportError> { + let rent = self + .context + .borrow_mut() + .banks_client + .get_rent() + .await + .unwrap(); + + let create_account_instruction = system_instruction::create_account( + &self.context.borrow().payer.pubkey(), + &token_account_keypair.pubkey(), + rent.minimum_balance(spl_token::state::Account::get_packed_len()), + spl_token::state::Account::get_packed_len() as u64, + &spl_token::id(), + ); + + let initialize_account_instruction = spl_token::instruction::initialize_account( + &spl_token::id(), + &token_account_keypair.pubkey(), + token_mint, + owner, + ) + .unwrap(); + + self.process_transaction( + &[create_account_instruction, initialize_account_instruction], + Some(&[token_account_keypair]), + ) + .await + } + + #[allow(dead_code)] + pub async fn with_wallet(&self) -> WalletCookie { + let account_rent = self.rent.minimum_balance(0); + let account_keypair = Keypair::new(); + + let create_account_ix = system_instruction::create_account( + &self.context.borrow().payer.pubkey(), + &account_keypair.pubkey(), + account_rent, + 0, + &system_program::id(), + ); + + self.process_transaction(&[create_account_ix], Some(&[&account_keypair])) + .await + .unwrap(); + + let account = Account { + lamports: account_rent, + data: vec![], + owner: system_program::id(), + executable: false, + rent_epoch: 0, + }; + + WalletCookie { + address: account_keypair.pubkey(), + account, + signer: account_keypair, + } + } + + #[allow(dead_code)] + pub async fn get_account(&self, address: &Pubkey) -> Option { + self.context + .borrow_mut() + .banks_client + .get_account(*address) + .await + .unwrap() + } + + #[allow(dead_code)] + pub async fn get_borsh_account(&self, address: &Pubkey) -> T { + self.get_account(address) + .await + .map(|a| try_from_slice_unchecked(&a.data).unwrap()) + .unwrap_or_else(|| panic!("GET-TEST-ACCOUNT-ERROR: Account {} not found", address)) + } + + #[allow(dead_code)] + pub async fn get_account_data(&self, address: Pubkey) -> Vec { + self.context + .borrow_mut() + .banks_client + .get_account(address) + .await + .unwrap() + .unwrap() + .data() + .to_vec() + } + + #[allow(dead_code)] + pub async fn get_anchor_account(&self, address: Pubkey) -> T { + let data = self.get_account_data(address).await; + let mut data_slice: &[u8] = &data; + AccountDeserialize::try_deserialize(&mut data_slice).unwrap() + } +} diff --git a/programs/squads-voter/tests/program_test/token_metadata_test.rs b/programs/squads-voter/tests/program_test/token_metadata_test.rs new file mode 100644 index 00000000..3ac0f97f --- /dev/null +++ b/programs/squads-voter/tests/program_test/token_metadata_test.rs @@ -0,0 +1,221 @@ +use std::{str::FromStr, sync::Arc}; + +use anchor_lang::prelude::Pubkey; +use mpl_token_metadata::state::Collection; +use solana_program_test::ProgramTest; +use solana_sdk::{signer::Signer, transport::TransportError}; + +use crate::program_test::program_test_bench::{MintCookie, ProgramTestBench, WalletCookie}; + +pub struct NftCookie { + pub address: Pubkey, + pub metadata: Pubkey, + pub mint_cookie: MintCookie, +} + +pub struct NftCollectionCookie { + pub mint: Pubkey, + pub metadata: Pubkey, + pub master_edition: Pubkey, +} + +pub struct CreateNftArgs { + pub verify_collection: bool, + pub amount: u64, +} + +impl Default for CreateNftArgs { + fn default() -> Self { + Self { + verify_collection: true, + amount: 1, + } + } +} + +pub struct TokenMetadataTest { + pub bench: Arc, + pub program_id: Pubkey, +} + +impl TokenMetadataTest { + pub fn program_id() -> Pubkey { + Pubkey::from_str("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s").unwrap() + } + + #[allow(dead_code)] + pub fn add_program(program_test: &mut ProgramTest) { + program_test.add_program("mpl_token_metadata", Self::program_id(), None); + } + + #[allow(dead_code)] + pub fn new(bench: Arc) -> Self { + TokenMetadataTest { + bench, + program_id: Self::program_id(), + } + } + + #[allow(dead_code)] + pub async fn with_nft_collection(&self) -> Result { + let update_authority = self.bench.context.borrow().payer.pubkey(); + let payer = self.bench.context.borrow().payer.pubkey(); + + // Create collection + let coll_mint_cookie = self.bench.with_mint().await?; + self.bench + .with_tokens(&coll_mint_cookie, &update_authority, 1) + .await?; + + let coll_metadata_seeds = &[ + b"metadata".as_ref(), + self.program_id.as_ref(), + &coll_mint_cookie.address.as_ref(), + ]; + let (coll_metadata_key, _) = + Pubkey::find_program_address(coll_metadata_seeds, &self.program_id); + + let coll_name = "NFT_C".to_string(); + let coll_symbol = "NFT_C".to_string(); + let coll_uri = "URI".to_string(); + + let create_coll_metadata_ix = mpl_token_metadata::instruction::create_metadata_accounts_v2( + self.program_id, + coll_metadata_key, + coll_mint_cookie.address, + coll_mint_cookie.mint_authority.pubkey(), + payer.clone(), + update_authority.clone(), + coll_name, + coll_symbol, + coll_uri, + None, + 10, + false, + false, + None, + None, + ); + + self.bench + .process_transaction( + &[create_coll_metadata_ix], + Some(&[&coll_mint_cookie.mint_authority]), + ) + .await?; + + let master_edition_seeds = &[ + b"metadata".as_ref(), + self.program_id.as_ref(), + coll_mint_cookie.address.as_ref(), + b"edition".as_ref(), + ]; + let (master_edition_key, _) = + Pubkey::find_program_address(master_edition_seeds, &self.program_id); + + let create_master_edition_ix = mpl_token_metadata::instruction::create_master_edition_v3( + self.program_id, + master_edition_key, + coll_mint_cookie.address, + update_authority, + coll_mint_cookie.mint_authority.pubkey(), + coll_metadata_key, + payer, + Some(0), + ); + + self.bench + .process_transaction( + &[create_master_edition_ix], + Some(&[&coll_mint_cookie.mint_authority]), + ) + .await?; + + Ok(NftCollectionCookie { + mint: coll_mint_cookie.address, + metadata: coll_metadata_key, + master_edition: master_edition_key, + }) + } + + #[allow(dead_code)] + pub async fn with_nft_v2( + &self, + nft_collection_cookie: &NftCollectionCookie, + nft_owner_cookie: &WalletCookie, + args: Option, + ) -> Result { + let CreateNftArgs { + verify_collection, + amount, + } = args.unwrap_or_default(); + + // Crate NFT + let mint_cookie = self.bench.with_mint().await?; + let nft_account_cookie = self + .bench + .with_tokens(&mint_cookie, &nft_owner_cookie.address, amount) + .await?; + + let metadata_seeds = &[ + b"metadata".as_ref(), + self.program_id.as_ref(), + &mint_cookie.address.as_ref(), + ]; + let (metadata_key, _) = Pubkey::find_program_address(metadata_seeds, &self.program_id); + + let name = "TestNFT".to_string(); + let symbol = "NFT".to_string(); + let uri = "URI".to_string(); + + let collection = Collection { + verified: false, + key: nft_collection_cookie.mint, + }; + + let create_metadata_ix = mpl_token_metadata::instruction::create_metadata_accounts_v2( + self.program_id, + metadata_key, + mint_cookie.address, + mint_cookie.mint_authority.pubkey(), + self.bench.payer.pubkey(), + self.bench.payer.pubkey(), + name, + symbol, + uri, + None, + 10, + false, + false, + Some(collection), + None, + ); + + self.bench + .process_transaction(&[create_metadata_ix], Some(&[&mint_cookie.mint_authority])) + .await?; + + if verify_collection { + let verify_collection = mpl_token_metadata::instruction::verify_collection( + self.program_id, + metadata_key, + self.bench.payer.pubkey(), + self.bench.payer.pubkey(), + nft_collection_cookie.mint, + nft_collection_cookie.metadata, + nft_collection_cookie.master_edition, + None, + ); + + self.bench + .process_transaction(&[verify_collection], None) + .await?; + } + + Ok(NftCookie { + address: nft_account_cookie.address, + metadata: metadata_key, + mint_cookie, + }) + } +} diff --git a/programs/squads-voter/tests/program_test/tools.rs b/programs/squads-voter/tests/program_test/tools.rs new file mode 100644 index 00000000..51f615d8 --- /dev/null +++ b/programs/squads-voter/tests/program_test/tools.rs @@ -0,0 +1,76 @@ +use anchor_lang::prelude::ERROR_CODE_OFFSET; +use gpl_nft_voter::error::NftVoterError; +use solana_program::instruction::InstructionError; +use solana_sdk::{signature::Keypair, transaction::TransactionError, transport::TransportError}; +use spl_governance_tools::error::GovernanceToolsError; + +pub fn clone_keypair(source: &Keypair) -> Keypair { + Keypair::from_bytes(&source.to_bytes()).unwrap() +} + +/// NOP (No Operation) Override function +#[allow(non_snake_case)] +pub fn NopOverride(_: &mut T) {} + +#[allow(dead_code)] +pub fn assert_nft_voter_err(banks_client_error: TransportError, nft_locker_error: NftVoterError) { + let tx_error = banks_client_error.unwrap(); + + match tx_error { + TransactionError::InstructionError(_, instruction_error) => match instruction_error { + InstructionError::Custom(e) => { + assert_eq!(e, nft_locker_error as u32 + ERROR_CODE_OFFSET) + } + _ => panic!("{:?} Is not InstructionError::Custom()", instruction_error), + }, + _ => panic!("{:?} Is not InstructionError", tx_error), + }; +} + +#[allow(dead_code)] +pub fn assert_gov_tools_err( + banks_client_error: TransportError, + gov_tools_error: GovernanceToolsError, +) { + let tx_error = banks_client_error.unwrap(); + + match tx_error { + TransactionError::InstructionError(_, instruction_error) => match instruction_error { + InstructionError::Custom(e) => { + assert_eq!(e, gov_tools_error as u32) + } + _ => panic!("{:?} Is not InstructionError::Custom()", instruction_error), + }, + _ => panic!("{:?} Is not InstructionError", tx_error), + }; +} + +#[allow(dead_code)] +pub fn assert_anchor_err( + banks_client_error: TransportError, + anchor_error: anchor_lang::error::ErrorCode, +) { + let tx_error = banks_client_error.unwrap(); + + match tx_error { + TransactionError::InstructionError(_, instruction_error) => match instruction_error { + InstructionError::Custom(e) => { + assert_eq!(e, anchor_error as u32) + } + _ => panic!("{:?} Is not InstructionError::Custom()", instruction_error), + }, + _ => panic!("{:?} Is not InstructionError", tx_error), + }; +} + +#[allow(dead_code)] +pub fn assert_ix_err(banks_client_error: TransportError, ix_error: InstructionError) { + let tx_error = banks_client_error.unwrap(); + + match tx_error { + TransactionError::InstructionError(_, instruction_error) => { + assert_eq!(instruction_error, ix_error); + } + _ => panic!("{:?} Is not InstructionError", tx_error), + }; +} diff --git a/programs/squads-voter/tests/relinquish_nft_vote.rs b/programs/squads-voter/tests/relinquish_nft_vote.rs new file mode 100644 index 00000000..2e890afe --- /dev/null +++ b/programs/squads-voter/tests/relinquish_nft_vote.rs @@ -0,0 +1,363 @@ +use crate::program_test::nft_voter_test::ConfigureCollectionArgs; +use gpl_nft_voter::error::NftVoterError; +use program_test::nft_voter_test::NftVoterTest; +use program_test::tools::assert_nft_voter_err; +use solana_program_test::*; +use solana_sdk::transport::TransportError; + +mod program_test; + +#[tokio::test] +async fn test_relinquish_nft_vote() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { weight: 1, size: 1 }), // Set Size == 1 to complete voting with just one vote + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let nft_cookie1 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + let nft_vote_record_cookies = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + None, + ) + .await?; + + // Act + + nft_voter_test + .relinquish_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &nft_vote_record_cookies, + ) + .await?; + + // Assert + + let voter_weight_record = nft_voter_test + .get_voter_weight_record(&voter_weight_record_cookie.address) + .await; + + assert_eq!(voter_weight_record.voter_weight_expiry, Some(0)); + assert_eq!(voter_weight_record.voter_weight, 0); + + // Check NftVoteRecord was disposed + let nft_vote_record = nft_voter_test + .bench + .get_account(&nft_vote_record_cookies[0].address) + .await; + + assert_eq!(None, nft_vote_record); + + Ok(()) +} + +#[tokio::test] +async fn test_relinquish_nft_vote_for_proposal_in_voting_state() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + None, + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let nft_cookie1 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + let nft_vote_record_cookies = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + None, + ) + .await?; + + // Relinquish Vote from spl-gov + nft_voter_test + .governance + .relinquish_vote( + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + ) + .await?; + + // Act + + nft_voter_test + .relinquish_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &nft_vote_record_cookies, + ) + .await?; + + // Assert + + let voter_weight_record = nft_voter_test + .get_voter_weight_record(&voter_weight_record_cookie.address) + .await; + + assert_eq!(voter_weight_record.voter_weight_expiry, Some(0)); + assert_eq!(voter_weight_record.voter_weight, 0); + + // Check NftVoteRecord was disposed + let nft_vote_record = nft_voter_test + .bench + .get_account(&nft_vote_record_cookies[0].address) + .await; + + assert_eq!(None, nft_vote_record); + + Ok(()) +} + +#[tokio::test] +async fn test_relinquish_nft_vote_for_proposal_in_voting_state_and_vote_record_exists_error( +) -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + None, + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let nft_cookie1 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + let nft_vote_record_cookies = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + None, + ) + .await?; + + // Act + + let err = nft_voter_test + .relinquish_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &nft_vote_record_cookies, + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, NftVoterError::VoteRecordMustBeWithdrawn); + + Ok(()) +} + +#[tokio::test] +async fn test_relinquish_nft_vote_with_invalid_voter_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { weight: 1, size: 1 }), // Set Size == 1 to complete voting with just one vote + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let voter_token_owner_record_cookie = nft_voter_test + .governance + .with_token_owner_record(&realm_cookie, &voter_cookie) + .await?; + + let voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let proposal_cookie = nft_voter_test + .governance + .with_proposal(&realm_cookie) + .await?; + + let nft_cookie1 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + let nft_vote_record_cookies = nft_voter_test + .cast_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &max_voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie, + &voter_token_owner_record_cookie, + &[&nft_cookie1], + None, + ) + .await?; + + // Try to use a different voter + let voter_cookie2 = nft_voter_test.bench.with_wallet().await; + + // Act + + let err = nft_voter_test + .relinquish_nft_vote( + ®istrar_cookie, + &voter_weight_record_cookie, + &proposal_cookie, + &voter_cookie2, + &voter_token_owner_record_cookie, + &nft_vote_record_cookies, + ) + .await + .err() + .unwrap(); + + // Assert + + assert_nft_voter_err(err, NftVoterError::InvalidTokenOwnerForVoterWeightRecord); + + Ok(()) +} diff --git a/programs/squads-voter/tests/update_voter_weight_record.rs b/programs/squads-voter/tests/update_voter_weight_record.rs new file mode 100644 index 00000000..d29809a2 --- /dev/null +++ b/programs/squads-voter/tests/update_voter_weight_record.rs @@ -0,0 +1,575 @@ +use crate::program_test::nft_voter_test::ConfigureCollectionArgs; +use gpl_nft_voter::error::NftVoterError; +use gpl_nft_voter::state::*; +use program_test::nft_voter_test::NftVoterTest; +use program_test::token_metadata_test::CreateNftArgs; +use program_test::tools::*; +use solana_program_test::*; +use solana_sdk::transport::TransportError; + +mod program_test; + +#[tokio::test] +async fn test_update_voter_weight_record() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + let _collection_config_cookie = nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let mut voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let nft1_cookie = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + nft_voter_test.bench.advance_clock().await; + let clock = nft_voter_test.bench.get_clock().await; + + // Act + nft_voter_test + .update_voter_weight_record( + ®istrar_cookie, + &mut voter_weight_record_cookie, + VoterWeightAction::CreateProposal, + &[&nft1_cookie], + ) + .await?; + + // Assert + + let voter_weight_record = nft_voter_test + .get_voter_weight_record(&voter_weight_record_cookie.address) + .await; + + assert_eq!(voter_weight_record.voter_weight, 10); + assert_eq!(voter_weight_record.voter_weight_expiry, Some(clock.slot)); + assert_eq!( + voter_weight_record.weight_action, + Some(VoterWeightAction::CreateProposal.into()) + ); + assert_eq!(voter_weight_record.weight_action_target, None); + + Ok(()) +} + +#[tokio::test] +async fn test_update_voter_weight_with_multiple_nfts() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + let _collection_config_cookie = nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let mut voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let nft_cookie1 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + let nft_cookie2 = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + nft_voter_test.bench.advance_clock().await; + let clock = nft_voter_test.bench.get_clock().await; + + // Act + nft_voter_test + .update_voter_weight_record( + ®istrar_cookie, + &mut voter_weight_record_cookie, + VoterWeightAction::CreateProposal, + &[&nft_cookie1, &nft_cookie2], + ) + .await?; + + // Assert + + let voter_weight_record = nft_voter_test + .get_voter_weight_record(&voter_weight_record_cookie.address) + .await; + + assert_eq!(voter_weight_record.voter_weight, 20); + assert_eq!(voter_weight_record.voter_weight_expiry, Some(clock.slot)); + assert_eq!( + voter_weight_record.weight_action, + Some(VoterWeightAction::CreateProposal.into()) + ); + assert_eq!(voter_weight_record.weight_action_target, None); + + Ok(()) +} + +#[tokio::test] +async fn test_update_voter_weight_with_cast_vote_not_allowed_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let mut voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let nft1_cookie = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + // Act + let err = nft_voter_test + .update_voter_weight_record( + ®istrar_cookie, + &mut voter_weight_record_cookie, + VoterWeightAction::CastVote, + &[&nft1_cookie], + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, NftVoterError::CastVoteIsNotAllowed); + + Ok(()) +} + +#[tokio::test] +async fn test_update_voter_weight_with_unverified_collection_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let mut voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + // Create NFT without verified collection + let nft1_cookie = nft_voter_test + .token_metadata + .with_nft_v2( + &nft_collection_cookie, + &voter_cookie, + Some(CreateNftArgs { + verify_collection: false, + ..Default::default() + }), + ) + .await?; + + // Act + let err = nft_voter_test + .update_voter_weight_record( + ®istrar_cookie, + &mut voter_weight_record_cookie, + VoterWeightAction::CreateGovernance, + &[&nft1_cookie], + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, NftVoterError::CollectionMustBeVerified); + + Ok(()) +} + +#[tokio::test] +async fn test_update_voter_weight_with_invalid_owner_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let mut voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let voter_cookie2 = nft_voter_test.bench.with_wallet().await; + + let nft1_cookie = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie2, None) + .await?; + + // Act + let err = nft_voter_test + .update_voter_weight_record( + ®istrar_cookie, + &mut voter_weight_record_cookie, + VoterWeightAction::CreateGovernance, + &[&nft1_cookie], + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, NftVoterError::VoterDoesNotOwnNft); + + Ok(()) +} + +#[tokio::test] +async fn test_update_voter_weight_with_invalid_collection_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let mut voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let nft_collection_cookie2 = nft_voter_test.token_metadata.with_nft_collection().await?; + + let nft1_cookie = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie2, &voter_cookie, None) + .await?; + + // Act + let err = nft_voter_test + .update_voter_weight_record( + ®istrar_cookie, + &mut voter_weight_record_cookie, + VoterWeightAction::CreateGovernance, + &[&nft1_cookie], + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, NftVoterError::CollectionNotFound); + + Ok(()) +} + +#[tokio::test] +async fn test_update_voter_weight_with_invalid_metadata_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let mut voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let mut nft1_cookie = nft_voter_test + .token_metadata + .with_nft_v2( + &nft_collection_cookie, + &voter_cookie, + Some(CreateNftArgs { + verify_collection: false, + ..Default::default() + }), + ) + .await?; + + let nft2_cookie = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + // Try to use verified NFT Metadata + nft1_cookie.metadata = nft2_cookie.metadata; + + // Act + let err = nft_voter_test + .update_voter_weight_record( + ®istrar_cookie, + &mut voter_weight_record_cookie, + VoterWeightAction::CreateGovernance, + &[&nft1_cookie], + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, NftVoterError::TokenMetadataDoesNotMatch); + + Ok(()) +} + +#[tokio::test] +async fn test_update_voter_weight_with_same_nft_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + None, + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let mut voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let nft_cookie = nft_voter_test + .token_metadata + .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) + .await?; + + // Act + let err = nft_voter_test + .update_voter_weight_record( + ®istrar_cookie, + &mut voter_weight_record_cookie, + VoterWeightAction::CreateProposal, + &[&nft_cookie, &nft_cookie], + ) + .await + .err() + .unwrap(); + + // Assert + + assert_nft_voter_err(err, NftVoterError::DuplicatedNftDetected); + + Ok(()) +} + +#[tokio::test] +async fn test_update_voter_weight_record_with_no_nft_error() -> Result<(), TransportError> { + // Arrange + let mut nft_voter_test = NftVoterTest::start_new().await; + + let realm_cookie = nft_voter_test.governance.with_realm().await?; + + let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + + let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; + + let max_voter_weight_record_cookie = nft_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + let _collection_config_cookie = nft_voter_test + .with_collection( + ®istrar_cookie, + &nft_collection_cookie, + &max_voter_weight_record_cookie, + Some(ConfigureCollectionArgs { + weight: 10, + size: 20, + }), + ) + .await?; + + let voter_cookie = nft_voter_test.bench.with_wallet().await; + + let mut voter_weight_record_cookie = nft_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let nft1_cookie = nft_voter_test + .token_metadata + .with_nft_v2( + &nft_collection_cookie, + &voter_cookie, + Some(CreateNftArgs { + amount: 0, + ..Default::default() + }), + ) + .await?; + + // Act + let err = nft_voter_test + .update_voter_weight_record( + ®istrar_cookie, + &mut voter_weight_record_cookie, + VoterWeightAction::CreateProposal, + &[&nft1_cookie], + ) + .await + .err() + .unwrap(); + + // Assert + assert_nft_voter_err(err, NftVoterError::InvalidNftAmount); + + Ok(()) +} From 3e9f5eb954e43950f2f6f758a1fdb156d02f312f Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Sat, 21 May 2022 22:40:01 +0300 Subject: [PATCH 02/11] wip: Update Squads instructions --- programs/squads-voter/src/error.rs | 52 +- .../src/instructions/cast_nft_vote.rs | 133 -- .../src/instructions/configure_collection.rs | 106 -- .../src/instructions/configure_squad.rs | 75 + .../src/instructions/create_registrar.rs | 22 +- programs/squads-voter/src/instructions/mod.rs | 10 +- .../src/instructions/relinquish_nft_vote.rs | 119 -- .../update_voter_weight_record.rs | 62 +- programs/squads-voter/src/lib.rs | 36 +- .../src/state/collection_config.rs | 28 - programs/squads-voter/src/state/idl_types.rs | 17 - programs/squads-voter/src/state/mod.rs | 9 +- .../squads-voter/src/state/nft_vote_record.rs | 84 -- programs/squads-voter/src/state/registrar.rs | 80 +- .../squads-voter/src/state/squad_config.rs | 18 + programs/squads-voter/src/tools/governance.rs | 19 - programs/squads-voter/src/tools/mod.rs | 3 - programs/squads-voter/src/tools/spl_token.rs | 15 - .../squads-voter/src/tools/token_metadata.rs | 31 - programs/squads-voter/tests/cast_nft_vote.rs | 1211 ----------------- .../squads-voter/tests/relinquish_nft_vote.rs | 363 ----- 21 files changed, 161 insertions(+), 2332 deletions(-) delete mode 100644 programs/squads-voter/src/instructions/cast_nft_vote.rs delete mode 100644 programs/squads-voter/src/instructions/configure_collection.rs create mode 100644 programs/squads-voter/src/instructions/configure_squad.rs delete mode 100644 programs/squads-voter/src/instructions/relinquish_nft_vote.rs delete mode 100644 programs/squads-voter/src/state/collection_config.rs delete mode 100644 programs/squads-voter/src/state/idl_types.rs delete mode 100644 programs/squads-voter/src/state/nft_vote_record.rs create mode 100644 programs/squads-voter/src/state/squad_config.rs delete mode 100644 programs/squads-voter/src/tools/governance.rs delete mode 100644 programs/squads-voter/src/tools/spl_token.rs delete mode 100644 programs/squads-voter/src/tools/token_metadata.rs delete mode 100644 programs/squads-voter/tests/cast_nft_vote.rs delete mode 100644 programs/squads-voter/tests/relinquish_nft_vote.rs diff --git a/programs/squads-voter/src/error.rs b/programs/squads-voter/src/error.rs index 4b03fb2b..857b7900 100644 --- a/programs/squads-voter/src/error.rs +++ b/programs/squads-voter/src/error.rs @@ -1,25 +1,19 @@ use anchor_lang::prelude::*; #[error_code] -pub enum NftVoterError { +pub enum SquadVoterError { #[msg("Invalid Realm Authority")] InvalidRealmAuthority, #[msg("Invalid Realm for Registrar")] InvalidRealmForRegistrar, - #[msg("Invalid Collection Size")] - InvalidCollectionSize, - #[msg("Invalid MaxVoterWeightRecord Realm")] InvalidMaxVoterWeightRecordRealm, #[msg("Invalid MaxVoterWeightRecord Mint")] InvalidMaxVoterWeightRecordMint, - #[msg("CastVote Is Not Allowed")] - CastVoteIsNotAllowed, - #[msg("Invalid VoterWeightRecord Realm")] InvalidVoterWeightRecordRealm, @@ -29,45 +23,9 @@ pub enum NftVoterError { #[msg("Invalid TokenOwner for VoterWeightRecord")] InvalidTokenOwnerForVoterWeightRecord, - #[msg("Collection must be verified")] - CollectionMustBeVerified, - - #[msg("Voter does not own NFT")] - VoterDoesNotOwnNft, - - #[msg("Collection not found")] - CollectionNotFound, - - #[msg("Missing Metadata collection")] - MissingMetadataCollection, - - #[msg("Token Metadata doesn't match")] - TokenMetadataDoesNotMatch, - - #[msg("Invalid account owner")] - InvalidAccountOwner, - - #[msg("Invalid token metadata account")] - InvalidTokenMetadataAccount, - - #[msg("Duplicated NFT detected")] - DuplicatedNftDetected, - - #[msg("Invalid NFT amount")] - InvalidNftAmount, - - #[msg("NFT already voted")] - NftAlreadyVoted, - - #[msg("Invalid Proposal for NftVoteRecord")] - InvalidProposalForNftVoteRecord, - - #[msg("Invalid TokenOwner for NftVoteRecord")] - InvalidTokenOwnerForNftVoteRecord, - - #[msg("VoteRecord must be withdrawn")] - VoteRecordMustBeWithdrawn, + #[msg("Squad not found")] + SquadNotFound, - #[msg("Invalid VoteRecord for NftVoteRecord")] - InvalidVoteRecordForNftVoteRecord, + #[msg("Duplicated Squad detected")] + DuplicatedSquadDetected, } diff --git a/programs/squads-voter/src/instructions/cast_nft_vote.rs b/programs/squads-voter/src/instructions/cast_nft_vote.rs deleted file mode 100644 index 853c35a5..00000000 --- a/programs/squads-voter/src/instructions/cast_nft_vote.rs +++ /dev/null @@ -1,133 +0,0 @@ -use crate::error::NftVoterError; -use crate::{id, state::*}; -use anchor_lang::prelude::*; -use anchor_lang::Accounts; -use itertools::Itertools; -use spl_governance_tools::account::create_and_serialize_account_signed; - -/// Casts NFT vote. The NFTs used for voting are tracked using NftVoteRecord accounts -/// This instruction updates VoterWeightRecord which is valid for the current Slot and the target Proposal only -/// and hance the instruction has to be executed inside the same transaction as spl-gov.CastVote -/// -/// CastNftVote is accumulative and can be invoked using several transactions if voter owns more than 5 NFTs to calculate total voter_weight -/// In this scenario only the last CastNftVote should be bundled with spl-gov.CastVote in the same transaction -/// -/// CastNftVote instruction and NftVoteRecord are not directional. They don't record vote choice (ex Yes/No) -/// VoteChoice is recorded by spl-gov in VoteRecord and this CastNftVote only tracks voting NFTs -/// -#[derive(Accounts)] -#[instruction(proposal: Pubkey)] -pub struct CastNftVote<'info> { - /// The NFT voting registrar - pub registrar: Account<'info, Registrar>, - - #[account( - mut, - constraint = voter_weight_record.realm == registrar.realm - @ NftVoterError::InvalidVoterWeightRecordRealm, - - constraint = voter_weight_record.governing_token_mint == registrar.governing_token_mint - @ NftVoterError::InvalidVoterWeightRecordMint, - )] - pub voter_weight_record: Account<'info, VoterWeightRecord>, - - /// The token owner who casts the vote - #[account( - address = voter_weight_record.governing_token_owner @ NftVoterError::InvalidTokenOwnerForVoterWeightRecord - )] - pub governing_token_owner: Signer<'info>, - - /// The account which pays for the transaction - #[account(mut)] - pub payer: Signer<'info>, - - pub system_program: Program<'info, System>, -} - -/// Casts vote with the NFT -pub fn cast_nft_vote<'a, 'b, 'c, 'info>( - ctx: Context<'a, 'b, 'c, 'info, CastNftVote<'info>>, - proposal: Pubkey, -) -> Result<()> { - let registrar = &ctx.accounts.registrar; - let governing_token_owner = &ctx.accounts.governing_token_owner.key(); - - let mut voter_weight = 0u64; - - // Ensure all voting nfts in the batch are unique - let mut unique_nft_mints = vec![]; - - let rent = Rent::get()?; - - for (nft_info, nft_metadata_info, nft_vote_record_info) in - ctx.remaining_accounts.iter().tuples() - { - let (nft_vote_weight, nft_mint) = resolve_nft_vote_weight_and_mint( - registrar, - governing_token_owner, - nft_info, - nft_metadata_info, - &mut unique_nft_mints, - )?; - - voter_weight = voter_weight.checked_add(nft_vote_weight as u64).unwrap(); - - // Create NFT vote record to ensure the same NFT hasn't been already used for voting - // Note: The correct PDA of the NftVoteRecord is validated in create_and_serialize_account_signed - // It ensures the NftVoteRecord is for ('nft-vote-record',proposal,nft_mint) seeds - require!( - nft_vote_record_info.data_is_empty(), - NftVoterError::NftAlreadyVoted - ); - - // Note: proposal.governing_token_mint must match voter_weight_record.governing_token_mint - // We don't verify it here because spl-gov does the check in cast_vote - // and it would reject voter_weight_record if governing_token_mint doesn't match - - // Note: Once the NFT plugin is enabled the governing_token_mint is used only as identity - // for the voting population and the tokens of that mint are no longer used - let nft_vote_record = NftVoteRecord { - account_discriminator: NftVoteRecord::ACCOUNT_DISCRIMINATOR, - proposal, - nft_mint, - governing_token_owner: *governing_token_owner, - reserved: [0; 8], - }; - - // Anchor doesn't natively support dynamic account creation using remaining_accounts - // and we have to take it on the manual drive - create_and_serialize_account_signed( - &ctx.accounts.payer.to_account_info(), - nft_vote_record_info, - &nft_vote_record, - &get_nft_vote_record_seeds(&proposal, &nft_mint), - &id(), - &ctx.accounts.system_program.to_account_info(), - &rent, - )?; - } - - let voter_weight_record = &mut ctx.accounts.voter_weight_record; - - if voter_weight_record.weight_action_target == Some(proposal) - && voter_weight_record.weight_action == Some(VoterWeightAction::CastVote) - { - // If cast_nft_vote is called for the same proposal then we keep accumulating the weight - // this way cast_nft_vote can be called multiple times in different transactions to allow voting with any number of NFTs - voter_weight_record.voter_weight = voter_weight_record - .voter_weight - .checked_add(voter_weight) - .unwrap(); - } else { - voter_weight_record.voter_weight = voter_weight; - } - - // The record is only valid as of the current slot - voter_weight_record.voter_weight_expiry = Some(Clock::get()?.slot); - - // The record is only valid for casting vote on the given Proposal - voter_weight_record.weight_action = Some(VoterWeightAction::CastVote); - voter_weight_record.weight_action_target = Some(proposal); - - Ok(()) -} diff --git a/programs/squads-voter/src/instructions/configure_collection.rs b/programs/squads-voter/src/instructions/configure_collection.rs deleted file mode 100644 index dab3b5aa..00000000 --- a/programs/squads-voter/src/instructions/configure_collection.rs +++ /dev/null @@ -1,106 +0,0 @@ -use anchor_lang::{ - account, - prelude::{Context, Signer}, - Accounts, -}; - -use anchor_lang::prelude::*; -use anchor_spl::token::Mint; -use spl_governance::state::realm; - -use crate::error::NftVoterError; -use crate::state::{max_voter_weight_record::MaxVoterWeightRecord, CollectionConfig, Registrar}; - -/// Configures NFT voting collection which defines what NFTs can be used for governances -/// and what weight they have -/// The instruction updates MaxVoterWeightRecord which is used by spl-gov to determine max voting power -/// used to calculate voting quorum -#[derive(Accounts)] -pub struct ConfigureCollection<'info> { - /// Registrar for which we configure this Collection - #[account(mut)] - pub registrar: Account<'info, Registrar>, - - #[account( - address = registrar.realm @ NftVoterError::InvalidRealmForRegistrar, - owner = registrar.governance_program_id - )] - /// CHECK: Owned by spl-governance instance specified in registrar.governance_program_id - pub realm: UncheckedAccount<'info>, - - /// Authority of the Realm must sign and match Realm.authority - pub realm_authority: Signer<'info>, - - // Collection which is going to be used for voting - pub collection: Account<'info, Mint>, - - #[account( - mut, - constraint = max_voter_weight_record.realm == registrar.realm - @ NftVoterError::InvalidMaxVoterWeightRecordRealm, - - constraint = max_voter_weight_record.governing_token_mint == registrar.governing_token_mint - @ NftVoterError::InvalidMaxVoterWeightRecordMint, - )] - pub max_voter_weight_record: Account<'info, MaxVoterWeightRecord>, -} - -pub fn configure_collection( - ctx: Context, - weight: u64, - size: u32, -) -> Result<()> { - require!(size > 0, NftVoterError::InvalidCollectionSize); - - let registrar = &mut ctx.accounts.registrar; - - let realm = realm::get_realm_data_for_governing_token_mint( - ®istrar.governance_program_id, - &ctx.accounts.realm, - ®istrar.governing_token_mint, - )?; - - require!( - realm.authority.unwrap() == ctx.accounts.realm_authority.key(), - NftVoterError::InvalidRealmAuthority - ); - - let collection = &ctx.accounts.collection; - - let collection_config = CollectionConfig { - collection: collection.key(), - weight, - reserved: [0; 8], - size, - }; - - let collection_idx = registrar - .collection_configs - .iter() - .position(|cc| cc.collection == collection.key()); - - if let Some(collection_idx) = collection_idx { - registrar.collection_configs[collection_idx] = collection_config; - } else { - // Note: In the current runtime version push() would throw an error if we exceed - // max_collections specified when the Registrar was created - registrar.collection_configs.push(collection_config); - } - - // TODO: if weight == 0 then remove the collection from config - // Currently if weight is set to 0 then the collection won't be removed but it won't have any governance power - - // Update MaxVoterWeightRecord based on max voting power of the collections - let max_voter_weight_record = &mut ctx.accounts.max_voter_weight_record; - - max_voter_weight_record.max_voter_weight = registrar - .collection_configs - .iter() - .try_fold(0u64, |sum, cc| sum.checked_add(cc.get_max_weight())) - .unwrap(); - - // The weight never expires and only changes when collections are configured - max_voter_weight_record.max_voter_weight_expiry = None; - - Ok(()) -} diff --git a/programs/squads-voter/src/instructions/configure_squad.rs b/programs/squads-voter/src/instructions/configure_squad.rs new file mode 100644 index 00000000..996b6cc5 --- /dev/null +++ b/programs/squads-voter/src/instructions/configure_squad.rs @@ -0,0 +1,75 @@ +use anchor_lang::{ + account, + prelude::{Context, Signer}, + Accounts, +}; + +use anchor_lang::prelude::*; +use anchor_spl::token::Mint; +use spl_governance::state::realm; + +use crate::error::SquadVoterError; +use crate::state::{Registrar, SquadConfig}; + +/// Creates or updates Squad configuration which defines what Squads can be used for governances +/// and what weight they have +#[derive(Accounts)] +pub struct ConfigureSquad<'info> { + /// Registrar for which we configure this Squad + #[account(mut)] + pub registrar: Account<'info, Registrar>, + + #[account( + address = registrar.realm @ SquadVoterError::InvalidRealmForRegistrar, + owner = registrar.governance_program_id + )] + /// CHECK: Owned by spl-governance instance specified in registrar.governance_program_id + pub realm: UncheckedAccount<'info>, + + /// Authority of the Realm must sign and match Realm.authority + pub realm_authority: Signer<'info>, + + // Squad which is going to be used for governance + pub squad: Account<'info, Mint>, +} + +pub fn configure_squad(ctx: Context, weight: u64) -> Result<()> { + let registrar = &mut ctx.accounts.registrar; + + let realm = realm::get_realm_data_for_governing_token_mint( + ®istrar.governance_program_id, + &ctx.accounts.realm, + ®istrar.governing_token_mint, + )?; + + require!( + realm.authority.unwrap() == ctx.accounts.realm_authority.key(), + SquadVoterError::InvalidRealmAuthority + ); + + let squad = &ctx.accounts.squad; + + let squad_config = SquadConfig { + squad: squad.key(), + weight, + reserved: [0; 8], + }; + + let squad_idx = registrar + .squads_configs + .iter() + .position(|cc| cc.squad == squad.key()); + + if let Some(squad_idx) = squad_idx { + registrar.squads_configs[squad_idx] = squad_config; + } else { + // Note: In the current runtime version push() would throw an error if we exceed + // max_squads specified when the Registrar was created + registrar.squads_configs.push(squad_config); + } + + // TODO: if weight == 0 then remove the Squad from config + // If weight is set to 0 then the Squad won't be removed but it won't have any governance power + + Ok(()) +} diff --git a/programs/squads-voter/src/instructions/create_registrar.rs b/programs/squads-voter/src/instructions/create_registrar.rs index efaa29f8..2c89304a 100644 --- a/programs/squads-voter/src/instructions/create_registrar.rs +++ b/programs/squads-voter/src/instructions/create_registrar.rs @@ -1,22 +1,22 @@ -use crate::error::NftVoterError; +use crate::error::SquadVoterError; use crate::state::*; use anchor_lang::prelude::*; use anchor_spl::token::Mint; use spl_governance::state::realm; -/// Creates Registrar storing NFT governance configuration for spl-gov Realm +/// Creates Registrar storing Squads governance configuration for spl-gov Realm /// This instruction should only be executed once per realm/governing_token_mint to create the account #[derive(Accounts)] -#[instruction(max_collections: u8)] +#[instruction(max_squads: u8)] pub struct CreateRegistrar<'info> { - /// The NFT voting Registrar + /// The Squads voting Registrar /// There can only be a single registrar per governance Realm and governing mint of the Realm #[account( init, seeds = [b"registrar".as_ref(),realm.key().as_ref(), governing_token_mint.key().as_ref()], bump, payer = payer, - space = Registrar::get_space(max_collections) + space = Registrar::get_space(max_squads) )] pub registrar: Account<'info, Registrar>, @@ -38,7 +38,7 @@ pub struct CreateRegistrar<'info> { /// Either the realm community mint or the council mint. /// It must match Realm.community_mint or Realm.config.council_mint /// - /// Note: Once the NFT plugin is enabled the governing_token_mint is used only as identity + /// Note: Once the Squads plugin is enabled the governing_token_mint is used only as identity /// for the voting population and the tokens of that are no longer used pub governing_token_mint: Account<'info, Mint>, @@ -51,14 +51,14 @@ pub struct CreateRegistrar<'info> { pub system_program: Program<'info, System>, } -/// Creates a new Registrar which stores NFT voting configuration for given Realm +/// Creates a new Registrar which stores Squads voting configuration for given Realm /// -/// To use the registrar, call ConfigureCollection to register NFT collections that may be +/// To use the registrar, call ConfigureSquad to register Squad that may be /// used for governance /// -/// max_collections is used allocate account size for the maximum number of governing NFT collections +/// max_squads is used allocate account size for the maximum number of governing Squads /// Note: Once Solana runtime supports account resizing the max value won't be required -pub fn create_registrar(ctx: Context, _max_collections: u8) -> Result<()> { +pub fn create_registrar(ctx: Context, _max_squads: u8) -> Result<()> { let registrar = &mut ctx.accounts.registrar; registrar.governance_program_id = ctx.accounts.governance_program_id.key(); registrar.realm = ctx.accounts.realm.key(); @@ -74,7 +74,7 @@ pub fn create_registrar(ctx: Context, _max_collections: u8) -> require!( realm.authority.unwrap() == ctx.accounts.realm_authority.key(), - NftVoterError::InvalidRealmAuthority + SquadVoterError::InvalidRealmAuthority ); Ok(()) diff --git a/programs/squads-voter/src/instructions/mod.rs b/programs/squads-voter/src/instructions/mod.rs index 518677c5..b0074432 100644 --- a/programs/squads-voter/src/instructions/mod.rs +++ b/programs/squads-voter/src/instructions/mod.rs @@ -1,5 +1,5 @@ -pub use configure_collection::*; -mod configure_collection; +pub use configure_squad::*; +mod configure_squad; pub use create_registrar::*; mod create_registrar; @@ -12,9 +12,3 @@ mod create_max_voter_weight_record; pub use update_voter_weight_record::*; mod update_voter_weight_record; - -pub use relinquish_nft_vote::*; -mod relinquish_nft_vote; - -pub use cast_nft_vote::*; -mod cast_nft_vote; diff --git a/programs/squads-voter/src/instructions/relinquish_nft_vote.rs b/programs/squads-voter/src/instructions/relinquish_nft_vote.rs deleted file mode 100644 index 3ef9796c..00000000 --- a/programs/squads-voter/src/instructions/relinquish_nft_vote.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::error::NftVoterError; -use crate::state::*; -use crate::state::{get_nft_vote_record_data_for_proposal_and_token_owner, Registrar}; -use crate::tools::governance::get_vote_record_address; -use anchor_lang::prelude::*; -use spl_governance::state::{enums::ProposalState, governance, proposal}; -use spl_governance_tools::account::dispose_account; - -/// Disposes NftVoteRecord and recovers the rent from the accounts -/// It can only be executed when voting on the target Proposal ended or voter withdrew vote from the Proposal -#[derive(Accounts)] -pub struct RelinquishNftVote<'info> { - /// The NFT voting Registrar - pub registrar: Account<'info, Registrar>, - - #[account( - mut, - constraint = voter_weight_record.realm == registrar.realm - @ NftVoterError::InvalidVoterWeightRecordRealm, - - constraint = voter_weight_record.governing_token_mint == registrar.governing_token_mint - @ NftVoterError::InvalidVoterWeightRecordMint, - )] - pub voter_weight_record: Account<'info, VoterWeightRecord>, - - /// CHECK: Owned by spl-governance instance specified in registrar.governance_program_id - /// Governance account the Proposal is for - #[account(owner = registrar.governance_program_id)] - pub governance: UncheckedAccount<'info>, - - /// CHECK: Owned by spl-governance instance specified in registrar.governance_program_id - #[account(owner = registrar.governance_program_id)] - pub proposal: UncheckedAccount<'info>, - - /// The token owner who cast the original vote - #[account( - address = voter_weight_record.governing_token_owner @ NftVoterError::InvalidTokenOwnerForVoterWeightRecord - )] - pub governing_token_owner: Signer<'info>, - - /// CHECK: Owned by spl-governance instance specified in registrar.governance_program_id - /// The account is used to validate that it doesn't exist and if it doesn't then Anchor owner check throws error - /// The check is disabled here and performed inside the instruction - /// #[account(owner = registrar.governance_program_id)] - pub vote_record: UncheckedAccount<'info>, - - /// CHECK: The beneficiary who receives lamports from the disposed NftVoterRecord accounts can be any account - #[account(mut)] - pub beneficiary: UncheckedAccount<'info>, -} - -pub fn relinquish_nft_vote(ctx: Context) -> Result<()> { - let registrar = &ctx.accounts.registrar; - - // Ensure the Governance belongs to Registrar.realm and is owned by Registrar.governance_program_id - let _governance = governance::get_governance_data_for_realm( - ®istrar.governance_program_id, - &ctx.accounts.governance, - ®istrar.realm, - )?; - - // Ensure the Proposal belongs to Governance from Registrar.realm and Registrar.governing_token_mint and is owned by Registrar.governance_program_id - let proposal = proposal::get_proposal_data_for_governance_and_governing_mint( - ®istrar.governance_program_id, - &ctx.accounts.proposal, - &ctx.accounts.governance.key(), - ®istrar.governing_token_mint, - )?; - - // If the Proposal is still in Voting state then we can only Relinquish the NFT votes if the Vote was withdrawn in spl-gov first - // When vote is withdrawn in spl-gov then VoteRecord is disposed and we have to assert it doesn't exist - // - // If the Proposal is in any other state then we can dispose NftVoteRecords without any additional Proposal checks - if proposal.state == ProposalState::Voting { - let vote_record_info = &ctx.accounts.vote_record.to_account_info(); - - // Ensure the given VoteRecord address matches the expected PDA - let vote_record_key = get_vote_record_address( - ®istrar.governance_program_id, - ®istrar.realm, - ®istrar.governing_token_mint, - &ctx.accounts.governing_token_owner.key(), - &ctx.accounts.proposal.key(), - ); - - require!( - vote_record_key == vote_record_info.key(), - NftVoterError::InvalidVoteRecordForNftVoteRecord - ); - - require!( - // VoteRecord doesn't exist if data is empty or account_type is 0 when the account was disposed in the same Tx - vote_record_info.data_is_empty() || vote_record_info.try_borrow_data().unwrap()[0] == 0, - NftVoterError::VoteRecordMustBeWithdrawn - ); - } - - // Dispose all NftVoteRecords - for nft_vote_record_info in ctx.remaining_accounts.iter() { - // Ensure NftVoteRecord is for the given Proposal and TokenOwner - let _nft_vote_record = get_nft_vote_record_data_for_proposal_and_token_owner( - nft_vote_record_info, - &ctx.accounts.proposal.key(), - &ctx.accounts.governing_token_owner.key(), - )?; - - dispose_account(nft_vote_record_info, &ctx.accounts.beneficiary); - } - - let voter_weight_record = &mut ctx.accounts.voter_weight_record; - - // Reset VoterWeightRecord and set expiry to expired to prevent it from being used - voter_weight_record.voter_weight = 0; - voter_weight_record.voter_weight_expiry = Some(0); - - voter_weight_record.weight_action_target = None; - - Ok(()) -} diff --git a/programs/squads-voter/src/instructions/update_voter_weight_record.rs b/programs/squads-voter/src/instructions/update_voter_weight_record.rs index ee9d74dd..40309f57 100644 --- a/programs/squads-voter/src/instructions/update_voter_weight_record.rs +++ b/programs/squads-voter/src/instructions/update_voter_weight_record.rs @@ -1,61 +1,49 @@ -use crate::error::NftVoterError; +use crate::error::SquadVoterError; use crate::state::*; use anchor_lang::prelude::*; -use itertools::Itertools; -/// Updates VoterWeightRecord to evaluate governance power for non voting use cases: CreateProposal, CreateGovernance etc... -/// This instruction updates VoterWeightRecord which is valid for the current Slot and the given target action only -/// and hance the instruction has to be executed inside the same transaction as the corresponding spl-gov instruction -/// -/// Note: UpdateVoterWeight is not cumulative the same way as CastNftVote and hence voter_weight for non voting scenarios -/// can only be used with max 5 NFTs due to Solana transaction size limit -/// It could be supported in future version by introducing bookkeeping accounts to track the NFTs -/// which were already used to calculate the total weight +/// Updates VoterWeightRecord to evaluate governance power for users and the Squads they belong to +/// This instruction updates VoterWeightRecord which is valid for the current Slot only +/// The instruction must be executed inside the same transaction as the corresponding spl-gov instruction #[derive(Accounts)] #[instruction(voter_weight_action:VoterWeightAction)] pub struct UpdateVoterWeightRecord<'info> { - /// The NFT voting Registrar + /// The Squads voting Registrar pub registrar: Account<'info, Registrar>, #[account( mut, constraint = voter_weight_record.realm == registrar.realm - @ NftVoterError::InvalidVoterWeightRecordRealm, + @ SquadVoterError::InvalidVoterWeightRecordRealm, constraint = voter_weight_record.governing_token_mint == registrar.governing_token_mint - @ NftVoterError::InvalidVoterWeightRecordMint, + @ SquadVoterError::InvalidVoterWeightRecordMint, )] pub voter_weight_record: Account<'info, VoterWeightRecord>, } -pub fn update_voter_weight_record( - ctx: Context, - voter_weight_action: VoterWeightAction, -) -> Result<()> { +pub fn update_voter_weight_record(ctx: Context) -> Result<()> { let registrar = &ctx.accounts.registrar; - let governing_token_owner = &ctx.accounts.voter_weight_record.governing_token_owner; - - // CastVote can't be evaluated using this instruction - require!( - voter_weight_action != VoterWeightAction::CastVote, - NftVoterError::CastVoteIsNotAllowed - ); + let _governing_token_owner = &ctx.accounts.voter_weight_record.governing_token_owner; let mut voter_weight = 0u64; - // Ensure all nfts are unique - let mut unique_nft_mints = vec![]; + let mut unique_squads = vec![]; + + for squad_info in ctx.remaining_accounts.iter() { + // Ensure the same Squad was not provided more than once + if unique_squads.contains(&squad_info.key) { + return Err(SquadVoterError::DuplicatedSquadDetected.into()); + } + unique_squads.push(squad_info.key); + + // TODO: Validate Squad membership for governing_token_owner - for (nft_info, nft_metadata_info) in ctx.remaining_accounts.iter().tuples() { - let (nft_vote_weight, _) = resolve_nft_vote_weight_and_mint( - registrar, - governing_token_owner, - nft_info, - nft_metadata_info, - &mut unique_nft_mints, - )?; + let squad_config = registrar.get_squad_config(squad_info.key)?; - voter_weight = voter_weight.checked_add(nft_vote_weight as u64).unwrap(); + voter_weight = voter_weight + .checked_add(squad_config.weight as u64) + .unwrap(); } let voter_weight_record = &mut ctx.accounts.voter_weight_record; @@ -65,8 +53,8 @@ pub fn update_voter_weight_record( // Record is only valid as of the current slot voter_weight_record.voter_weight_expiry = Some(Clock::get()?.slot); - // Set the action to make it specific and prevent being used for voting - voter_weight_record.weight_action = Some(voter_weight_action); + // Set action and target to None to indicate the weight is valid for any action + voter_weight_record.weight_action = None; voter_weight_record.weight_action_target = None; Ok(()) diff --git a/programs/squads-voter/src/lib.rs b/programs/squads-voter/src/lib.rs index 78cf1191..8b698646 100644 --- a/programs/squads-voter/src/lib.rs +++ b/programs/squads-voter/src/lib.rs @@ -9,19 +9,15 @@ pub mod state; pub mod tools; -use crate::state::*; - -declare_id!("GnftV5kLjd67tvHpNGyodwWveEKivz3ZWvvE3Z4xi2iw"); +declare_id!("GSqds6KYQf5tXEHwrDszu6AqkVXinFCKDwUfTLzp1jEH"); #[program] pub mod squads_voter { - use crate::state::VoterWeightAction; - use super::*; - pub fn create_registrar(ctx: Context, max_collections: u8) -> Result<()> { + pub fn create_registrar(ctx: Context, max_squads: u8) -> Result<()> { log_version(); - instructions::create_registrar(ctx, max_collections) + instructions::create_registrar(ctx, max_squads) } pub fn create_voter_weight_record( ctx: Context, @@ -34,32 +30,14 @@ pub mod squads_voter { log_version(); instructions::create_max_voter_weight_record(ctx) } - pub fn update_voter_weight_record( - ctx: Context, - voter_weight_action: VoterWeightAction, - ) -> Result<()> { - log_version(); - instructions::update_voter_weight_record(ctx, voter_weight_action) - } - pub fn relinquish_nft_vote(ctx: Context) -> Result<()> { + pub fn update_voter_weight_record(ctx: Context) -> Result<()> { log_version(); - instructions::relinquish_nft_vote(ctx) - } - pub fn configure_collection( - ctx: Context, - weight: u64, - size: u32, - ) -> Result<()> { - log_version(); - instructions::configure_collection(ctx, weight, size) + instructions::update_voter_weight_record(ctx) } - pub fn cast_nft_vote<'a, 'b, 'c, 'info>( - ctx: Context<'a, 'b, 'c, 'info, CastNftVote<'info>>, - proposal: Pubkey, - ) -> Result<()> { + pub fn configure_squad(ctx: Context, weight: u64) -> Result<()> { log_version(); - instructions::cast_nft_vote(ctx, proposal) + instructions::configure_squad(ctx, weight) } } diff --git a/programs/squads-voter/src/state/collection_config.rs b/programs/squads-voter/src/state/collection_config.rs deleted file mode 100644 index 56cd7e4e..00000000 --- a/programs/squads-voter/src/state/collection_config.rs +++ /dev/null @@ -1,28 +0,0 @@ -use anchor_lang::prelude::*; - -/// Configuration of an NFT collection used for governance power -#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy, PartialEq, Default)] -pub struct CollectionConfig { - /// The NFT collection used for governance - pub collection: Pubkey, - - /// The size of the NFT collection used to calculate max voter weight - /// Note: At the moment the size is not captured on Metaplex accounts - /// and it has to be manually updated on the Registrar - pub size: u32, - - /// Governance power weight of the collection - /// Each NFT in the collection has governance power = 1 * weight - /// Note: The weight is scaled accordingly to the governing_token_mint decimals - /// Ex: if the the mint has 2 decimal places then weight of 1 should be stored as 100 - pub weight: u64, - - /// Reserved for future upgrades - pub reserved: [u8; 8], -} - -impl CollectionConfig { - pub fn get_max_weight(&self) -> u64 { - (self.size as u64).checked_mul(self.weight).unwrap() - } -} diff --git a/programs/squads-voter/src/state/idl_types.rs b/programs/squads-voter/src/state/idl_types.rs deleted file mode 100644 index 06a57aa7..00000000 --- a/programs/squads-voter/src/state/idl_types.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! IDL only types which are required in IDL but not exported automatically by Anchor -use anchor_lang::prelude::*; - -/// NftVoteRecord exported to IDL without account_discriminator -/// TODO: Once we can support these accounts in Anchor via remaining_accounts then it should be possible to remove it -#[account] -pub struct NftVoteRecord { - /// Proposal which was voted on - pub proposal: Pubkey, - - /// The mint of the NFT which was used for the vote - pub nft_mint: Pubkey, - - /// The voter who casted this vote - /// It's a Realm member pubkey corresponding to TokenOwnerRecord.governing_token_owner - pub governing_token_owner: Pubkey, -} diff --git a/programs/squads-voter/src/state/mod.rs b/programs/squads-voter/src/state/mod.rs index 4084a580..9bf3426f 100644 --- a/programs/squads-voter/src/state/mod.rs +++ b/programs/squads-voter/src/state/mod.rs @@ -1,15 +1,10 @@ pub use registrar::*; pub mod registrar; -pub use collection_config::*; -pub mod collection_config; - -pub use nft_vote_record::*; -pub mod nft_vote_record; +pub use squad_config::*; +pub mod squad_config; pub mod max_voter_weight_record; pub use voter_weight_record::*; pub mod voter_weight_record; - -pub mod idl_types; diff --git a/programs/squads-voter/src/state/nft_vote_record.rs b/programs/squads-voter/src/state/nft_vote_record.rs deleted file mode 100644 index 25fcf101..00000000 --- a/programs/squads-voter/src/state/nft_vote_record.rs +++ /dev/null @@ -1,84 +0,0 @@ -use anchor_lang::prelude::*; -use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use solana_program::program_pack::IsInitialized; - -use spl_governance_tools::account::{get_account_data, AccountMaxSize}; - -use crate::{error::NftVoterError, id}; - -/// Vote record indicating the given NFT voted on the Proposal -/// The PDA of the record is ["nft-vote-record",proposal,nft_mint] -/// It guarantees uniques and ensures the same NFT can't vote twice -#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] -pub struct NftVoteRecord { - /// NftVoteRecord discriminator sha256("account:NftVoteRecord")[..8] - /// Note: The discriminator is used explicitly because NftVoteRecords - /// are created and consumed dynamically using remaining_accounts - /// and Anchor doesn't really support this scenario without going through lots of hoops - /// Once Anchor has better support for the scenario it shouldn't be necessary - pub account_discriminator: [u8; 8], - - /// Proposal which was voted on - pub proposal: Pubkey, - - /// The mint of the NFT which was used for the vote - pub nft_mint: Pubkey, - - /// The voter who casted this vote - /// It's a Realm member pubkey corresponding to TokenOwnerRecord.governing_token_owner - pub governing_token_owner: Pubkey, - - /// Reserved for future upgrades - pub reserved: [u8; 8], -} - -impl NftVoteRecord { - /// sha256("account:NftVoteRecord")[..8] - pub const ACCOUNT_DISCRIMINATOR: [u8; 8] = [137, 6, 55, 139, 251, 126, 254, 99]; -} - -impl AccountMaxSize for NftVoteRecord {} - -impl IsInitialized for NftVoteRecord { - fn is_initialized(&self) -> bool { - self.account_discriminator == NftVoteRecord::ACCOUNT_DISCRIMINATOR - } -} - -/// Returns NftVoteRecord PDA seeds -pub fn get_nft_vote_record_seeds<'a>(proposal: &'a Pubkey, nft_mint: &'a Pubkey) -> [&'a [u8]; 3] { - [b"nft-vote-record", proposal.as_ref(), nft_mint.as_ref()] -} - -/// Returns NftVoteRecord PDA address -pub fn get_nft_vote_record_address(proposal: &Pubkey, nft_mint: &Pubkey) -> Pubkey { - Pubkey::find_program_address(&get_nft_vote_record_seeds(proposal, nft_mint), &id()).0 -} - -/// Deserializes account and checks owner program -pub fn get_nft_vote_record_data(nft_vote_record_info: &AccountInfo) -> Result { - Ok(get_account_data::( - &id(), - nft_vote_record_info, - )?) -} - -pub fn get_nft_vote_record_data_for_proposal_and_token_owner( - nft_vote_record_info: &AccountInfo, - proposal: &Pubkey, - governing_token_owner: &Pubkey, -) -> Result { - let nft_vote_record = get_nft_vote_record_data(nft_vote_record_info)?; - - require!( - nft_vote_record.proposal == *proposal, - NftVoterError::InvalidProposalForNftVoteRecord - ); - - require!( - nft_vote_record.governing_token_owner == *governing_token_owner, - NftVoterError::InvalidTokenOwnerForNftVoteRecord - ); - - Ok(nft_vote_record) -} diff --git a/programs/squads-voter/src/state/registrar.rs b/programs/squads-voter/src/state/registrar.rs index 990b64ff..c7f53cd1 100644 --- a/programs/squads-voter/src/state/registrar.rs +++ b/programs/squads-voter/src/state/registrar.rs @@ -1,17 +1,12 @@ use crate::{ - error::NftVoterError, + error::SquadVoterError, id, - state::CollectionConfig, - tools::{ - anchor::{DISCRIMINATOR_SIZE, PUBKEY_SIZE}, - spl_token::get_spl_token_amount, - token_metadata::get_token_metadata_for_mint, - }, + state::SquadConfig, + tools::anchor::{DISCRIMINATOR_SIZE, PUBKEY_SIZE}, }; use anchor_lang::prelude::*; -use spl_governance::tools::spl_token::{get_spl_token_mint, get_spl_token_owner}; -/// Registrar which stores NFT voting configuration for the given Realm +/// Registrar which stores Squads voting configuration for the given Realm #[account] #[derive(Debug, PartialEq)] pub struct Registrar { @@ -27,19 +22,19 @@ pub struct Registrar { /// and the actual token of the mint is not used pub governing_token_mint: Pubkey, - /// MPL Collection used for voting - pub collection_configs: Vec, + /// Squads used for governance + pub squads_configs: Vec, /// Reserved for future upgrades pub reserved: [u8; 128], } impl Registrar { - pub fn get_space(max_collections: u8) -> usize { + pub fn get_space(max_squads: u8) -> usize { DISCRIMINATOR_SIZE + PUBKEY_SIZE * 3 + 4 - + max_collections as usize * (PUBKEY_SIZE + 4 + 8 + 8) + + max_squads as usize * (PUBKEY_SIZE + 4 + 8 + 8) + 128 } } @@ -58,58 +53,15 @@ pub fn get_registrar_address(realm: &Pubkey, governing_token_mint: &Pubkey) -> P } impl Registrar { - pub fn get_collection_config(&self, collection: Pubkey) -> Result<&CollectionConfig> { + pub fn get_squad_config(&self, squad: &Pubkey) -> Result<&SquadConfig> { return self - .collection_configs + .squads_configs .iter() - .find(|cc| cc.collection == collection) - .ok_or_else(|| NftVoterError::CollectionNotFound.into()); + .find(|cc| cc.squad == *squad) + .ok_or_else(|| SquadVoterError::SquadNotFound.into()); } } -/// Resolves vote weight and voting mint for the given NFT -pub fn resolve_nft_vote_weight_and_mint( - registrar: &Registrar, - governing_token_owner: &Pubkey, - nft_info: &AccountInfo, - nft_metadata_info: &AccountInfo, - unique_nft_mints: &mut Vec, -) -> Result<(u64, Pubkey)> { - let nft_owner = get_spl_token_owner(nft_info)?; - - // voter_weight_record.governing_token_owner must be the owner of the NFT - require!( - nft_owner == *governing_token_owner, - NftVoterError::VoterDoesNotOwnNft - ); - - let nft_mint = get_spl_token_mint(nft_info)?; - - // Ensure the same NFT was not provided more than once - if unique_nft_mints.contains(&nft_mint) { - return Err(NftVoterError::DuplicatedNftDetected.into()); - } - unique_nft_mints.push(nft_mint); - - // Ensure the token amount is exactly 1 - let nft_amount = get_spl_token_amount(nft_info)?; - - require!(nft_amount == 1, NftVoterError::InvalidNftAmount); - - let nft_metadata = get_token_metadata_for_mint(nft_metadata_info, &nft_mint)?; - - // The NFT must have a collection and the collection must be verified - let collection = nft_metadata - .collection - .ok_or(NftVoterError::MissingMetadataCollection)?; - - require!(collection.verified, NftVoterError::CollectionMustBeVerified); - - let collection_config = registrar.get_collection_config(collection.key)?; - - Ok((collection_config.weight, nft_mint)) -} - #[cfg(test)] mod test { @@ -124,10 +76,10 @@ mod test { governance_program_id: Pubkey::default(), realm: Pubkey::default(), governing_token_mint: Pubkey::default(), - collection_configs: vec![ - CollectionConfig::default(), - CollectionConfig::default(), - CollectionConfig::default(), + squads_configs: vec![ + SquadConfig::default(), + SquadConfig::default(), + SquadConfig::default(), ], reserved: [0; 128], }; diff --git a/programs/squads-voter/src/state/squad_config.rs b/programs/squads-voter/src/state/squad_config.rs new file mode 100644 index 00000000..95de23a2 --- /dev/null +++ b/programs/squads-voter/src/state/squad_config.rs @@ -0,0 +1,18 @@ +use anchor_lang::prelude::*; + +/// Configuration of a Squad used for governance power +#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy, PartialEq, Default)] +pub struct SquadConfig { + /// The Squad used for governance + pub squad: Pubkey, + + /// Governance power weight of the Squad + /// Membership in the Squad gives governance power equal to the weight + /// + /// Note: The weight is scaled accordingly to the governing_token_mint decimals + /// Ex: if the the mint has 2 decimal places then weight of 1 should be stored as 100 + pub weight: u64, + + /// Reserved for future upgrades + pub reserved: [u8; 8], +} diff --git a/programs/squads-voter/src/tools/governance.rs b/programs/squads-voter/src/tools/governance.rs deleted file mode 100644 index ccb25732..00000000 --- a/programs/squads-voter/src/tools/governance.rs +++ /dev/null @@ -1,19 +0,0 @@ -use anchor_lang::prelude::Pubkey; -use spl_governance::state::{token_owner_record, vote_record}; - -pub fn get_vote_record_address( - program_id: &Pubkey, - realm: &Pubkey, - governing_token_mint: &Pubkey, - governing_token_owner: &Pubkey, - proposal: &Pubkey, -) -> Pubkey { - let token_owner_record_key = token_owner_record::get_token_owner_record_address( - program_id, - realm, - governing_token_mint, - governing_token_owner, - ); - - vote_record::get_vote_record_address(program_id, proposal, &token_owner_record_key) -} diff --git a/programs/squads-voter/src/tools/mod.rs b/programs/squads-voter/src/tools/mod.rs index 13516dbb..37794aee 100644 --- a/programs/squads-voter/src/tools/mod.rs +++ b/programs/squads-voter/src/tools/mod.rs @@ -1,4 +1 @@ pub mod anchor; -pub mod governance; -pub mod spl_token; -pub mod token_metadata; diff --git a/programs/squads-voter/src/tools/spl_token.rs b/programs/squads-voter/src/tools/spl_token.rs deleted file mode 100644 index 49a97d5b..00000000 --- a/programs/squads-voter/src/tools/spl_token.rs +++ /dev/null @@ -1,15 +0,0 @@ -use anchor_lang::prelude::*; -use arrayref::array_ref; -use spl_governance::tools::spl_token::assert_is_valid_spl_token_account; - -/// Computationally cheap method to get amount from a token account -/// It reads amount without deserializing full account data -pub fn get_spl_token_amount(token_account_info: &AccountInfo) -> Result { - assert_is_valid_spl_token_account(token_account_info)?; - - // TokeAccount layout: mint(32), owner(32), amount(8), ... - let data = token_account_info.try_borrow_data()?; - let amount_bytes = array_ref![data, 64, 8]; - - Ok(u64::from_le_bytes(*amount_bytes)) -} diff --git a/programs/squads-voter/src/tools/token_metadata.rs b/programs/squads-voter/src/tools/token_metadata.rs deleted file mode 100644 index d5fe2384..00000000 --- a/programs/squads-voter/src/tools/token_metadata.rs +++ /dev/null @@ -1,31 +0,0 @@ -use anchor_lang::prelude::*; - -use mpl_token_metadata::state::Metadata; - -use crate::error::NftVoterError; - -pub fn get_token_metadata(account_info: &AccountInfo) -> Result { - if *account_info.owner != mpl_token_metadata::ID { - return Err(NftVoterError::InvalidAccountOwner.into()); - } - - let metadata = Metadata::from_account_info(account_info)?; - - // I'm not sure if this is needed but try_from_slice_checked in from_account_info - // ignores Key::Uninitialized and hence checking for the exact Key match here - if metadata.key != mpl_token_metadata::state::Key::MetadataV1 { - return Err(NftVoterError::InvalidTokenMetadataAccount.into()); - } - - Ok(metadata) -} - -pub fn get_token_metadata_for_mint(account_info: &AccountInfo, mint: &Pubkey) -> Result { - let token_metadata = get_token_metadata(account_info)?; - - if token_metadata.mint != *mint { - return Err(NftVoterError::TokenMetadataDoesNotMatch.into()); - } - - Ok(token_metadata) -} diff --git a/programs/squads-voter/tests/cast_nft_vote.rs b/programs/squads-voter/tests/cast_nft_vote.rs deleted file mode 100644 index 24cd8ea7..00000000 --- a/programs/squads-voter/tests/cast_nft_vote.rs +++ /dev/null @@ -1,1211 +0,0 @@ -use crate::program_test::nft_voter_test::ConfigureCollectionArgs; -use gpl_nft_voter::error::NftVoterError; -use gpl_nft_voter::state::*; -use program_test::token_metadata_test::CreateNftArgs; -use program_test::{nft_voter_test::*, tools::assert_nft_voter_err}; - -use solana_program_test::*; -use solana_sdk::transport::TransportError; - -mod program_test; - -#[tokio::test] -async fn test_cast_nft_vote() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let nft_cookie1 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - nft_voter_test.bench.advance_clock().await; - let clock = nft_voter_test.bench.get_clock().await; - - // Act - let nft_vote_record_cookies = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - None, - ) - .await?; - - // Assert - let nft_vote_record = nft_voter_test - .get_nf_vote_record_account(&nft_vote_record_cookies[0].address) - .await; - - assert_eq!(nft_vote_record_cookies[0].account, nft_vote_record); - - let voter_weight_record = nft_voter_test - .get_voter_weight_record(&voter_weight_record_cookie.address) - .await; - - assert_eq!(voter_weight_record.voter_weight, 10); - assert_eq!(voter_weight_record.voter_weight_expiry, Some(clock.slot)); - assert_eq!( - voter_weight_record.weight_action, - Some(VoterWeightAction::CastVote.into()) - ); - assert_eq!( - voter_weight_record.weight_action_target, - Some(proposal_cookie.address) - ); - - Ok(()) -} - -#[tokio::test] -async fn test_cast_nft_vote_with_multiple_nfts() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let nft_cookie1 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - let nft_cookie2 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - nft_voter_test.bench.advance_clock().await; - let clock = nft_voter_test.bench.get_clock().await; - - // Act - let nft_vote_record_cookies = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1, &nft_cookie2], - None, - ) - .await?; - - // Assert - let nft_vote_record1 = nft_voter_test - .get_nf_vote_record_account(&nft_vote_record_cookies[0].address) - .await; - - assert_eq!(nft_vote_record_cookies[0].account, nft_vote_record1); - - let nft_vote_record2 = nft_voter_test - .get_nf_vote_record_account(&nft_vote_record_cookies[1].address) - .await; - - assert_eq!(nft_vote_record_cookies[1].account, nft_vote_record2); - - let voter_weight_record = nft_voter_test - .get_voter_weight_record(&voter_weight_record_cookie.address) - .await; - - assert_eq!(voter_weight_record.voter_weight, 20); - assert_eq!(voter_weight_record.voter_weight_expiry, Some(clock.slot)); - assert_eq!( - voter_weight_record.weight_action, - Some(VoterWeightAction::CastVote.into()) - ); - assert_eq!( - voter_weight_record.weight_action_target, - Some(proposal_cookie.address) - ); - - Ok(()) -} - -#[tokio::test] -async fn test_cast_nft_vote_with_nft_already_voted_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - None, - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let nft_cookie1 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - None, - ) - .await?; - - nft_voter_test.bench.advance_clock().await; - - // Act - - let err = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - None, - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, SquadsVoterError::NftAlreadyVoted); - - Ok(()) -} - -#[tokio::test] -async fn test_cast_nft_vote_invalid_voter_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - None, - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let nft_cookie1 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - let voter_cookie2 = nft_voter_test.bench.with_wallet().await; - - // Act - - let err = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie2, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - None, - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, NftVoterError::InvalidTokenOwnerForVoterWeightRecord); - - Ok(()) -} - -#[tokio::test] -async fn test_cast_nft_vote_with_unverified_collection_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - // Create NFT without verified collection - let nft_cookie1 = nft_voter_test - .token_metadata - .with_nft_v2( - &nft_collection_cookie, - &voter_cookie, - Some(CreateNftArgs { - verify_collection: false, - ..Default::default() - }), - ) - .await?; - - // Act - let err = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - None, - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, NftVoterError::CollectionMustBeVerified); - - Ok(()) -} - -#[tokio::test] -async fn test_cast_nft_vote_with_invalid_owner_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let voter_cookie2 = nft_voter_test.bench.with_wallet().await; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let nft_cookie = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie2, None) - .await?; - - // Act - let err = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie], - None, - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, NftVoterError::VoterDoesNotOwnNft); - - Ok(()) -} - -#[tokio::test] -async fn test_cast_nft_vote_with_invalid_collection_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let nft_collection_cookie2 = nft_voter_test.token_metadata.with_nft_collection().await?; - - let nft_cookie = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie2, &voter_cookie, None) - .await?; - - // Act - let err = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie], - None, - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, NftVoterError::CollectionNotFound); - - Ok(()) -} - -#[tokio::test] -async fn test_cast_nft_vote_with_invalid_metadata_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let mut nft1_cookie = nft_voter_test - .token_metadata - .with_nft_v2( - &nft_collection_cookie, - &voter_cookie, - Some(CreateNftArgs { - verify_collection: false, - ..Default::default() - }), - ) - .await?; - - let nft2_cookie = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - // Try to use verified NFT Metadata - nft1_cookie.metadata = nft2_cookie.metadata; - - // Act - let err = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft1_cookie], - None, - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, NftVoterError::TokenMetadataDoesNotMatch); - - Ok(()) -} - -#[tokio::test] -async fn test_cast_nft_vote_with_same_nft_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - None, - ) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let nft_cookie = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - // Act - let err = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie, &nft_cookie], - None, - ) - .await - .err() - .unwrap(); - - // Assert - - assert_nft_voter_err(err, NftVoterError::DuplicatedNftDetected); - - Ok(()) -} - -#[tokio::test] -async fn test_cast_nft_vote_with_no_nft_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let nft_cookie1 = nft_voter_test - .token_metadata - .with_nft_v2( - &nft_collection_cookie, - &voter_cookie, - Some(CreateNftArgs { - amount: 0, - ..Default::default() - }), - ) - .await?; - - // Act - let err = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - None, - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, NftVoterError::InvalidNftAmount); - - Ok(()) -} - -#[tokio::test] -async fn test_cast_nft_vote_with_max_5_nfts() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let mut nft_cookies = vec![]; - - for _ in 0..5 { - nft_voter_test.bench.advance_clock().await; - let nft_cookie = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - nft_cookies.push(nft_cookie) - } - - nft_voter_test.bench.advance_clock().await; - let clock = nft_voter_test.bench.get_clock().await; - - // Act - let nft_vote_record_cookies = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &nft_cookies.iter().collect::>(), - None, - ) - .await?; - - // Assert - let nft_vote_record1 = nft_voter_test - .get_nf_vote_record_account(&nft_vote_record_cookies[0].address) - .await; - - assert_eq!(nft_vote_record_cookies[0].account, nft_vote_record1); - - let nft_vote_record2 = nft_voter_test - .get_nf_vote_record_account(&nft_vote_record_cookies[1].address) - .await; - - assert_eq!(nft_vote_record_cookies[1].account, nft_vote_record2); - - let voter_weight_record = nft_voter_test - .get_voter_weight_record(&voter_weight_record_cookie.address) - .await; - - assert_eq!(voter_weight_record.voter_weight, 50); - assert_eq!(voter_weight_record.voter_weight_expiry, Some(clock.slot)); - assert_eq!( - voter_weight_record.weight_action, - Some(VoterWeightAction::CastVote.into()) - ); - assert_eq!( - voter_weight_record.weight_action_target, - Some(proposal_cookie.address) - ); - - Ok(()) -} - -#[tokio::test] -async fn test_cast_nft_vote_using_multiple_instructions() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let nft_cookie1 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - nft_voter_test.bench.advance_clock().await; - let clock = nft_voter_test.bench.get_clock().await; - - let args = CastNftVoteArgs { - cast_spl_gov_vote: false, - }; - - nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - Some(args), - ) - .await?; - - let nft_cookie2 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - // Act - - nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie2], - None, - ) - .await?; - - // Assert - - let voter_weight_record = nft_voter_test - .get_voter_weight_record(&voter_weight_record_cookie.address) - .await; - - assert_eq!(voter_weight_record.voter_weight, 20); - assert_eq!(voter_weight_record.voter_weight_expiry, Some(clock.slot)); - assert_eq!( - voter_weight_record.weight_action, - Some(VoterWeightAction::CastVote.into()) - ); - assert_eq!( - voter_weight_record.weight_action_target, - Some(proposal_cookie.address) - ); - - Ok(()) -} - -#[tokio::test] -async fn test_cast_nft_vote_using_multiple_instructions_with_nft_already_voted_error( -) -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let nft_cookie1 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - let args = CastNftVoteArgs { - cast_spl_gov_vote: false, - }; - - nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - Some(args), - ) - .await?; - - // Act - - let err = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - None, - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, NftVoterError::NftAlreadyVoted); - - Ok(()) -} - -#[tokio::test] -async fn test_cast_nft_vote_using_multiple_instructions_with_attempted_sandwiched_relinquish( -) -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let nft_cookie1 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - let args = CastNftVoteArgs { - cast_spl_gov_vote: false, - }; - - // Cast vote with NFT - let nft_vote_record_cookies = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - Some(args), - ) - .await?; - - // Try relinquish NftVoteRecords to accumulate vote - nft_voter_test - .relinquish_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &nft_vote_record_cookies, - ) - .await?; - - // Act - - nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - None, - ) - .await?; - - // Assert - - let voter_weight_record = nft_voter_test - .get_voter_weight_record(&voter_weight_record_cookie.address) - .await; - - assert_eq!(voter_weight_record.voter_weight, 10); - - Ok(()) -} diff --git a/programs/squads-voter/tests/relinquish_nft_vote.rs b/programs/squads-voter/tests/relinquish_nft_vote.rs deleted file mode 100644 index 2e890afe..00000000 --- a/programs/squads-voter/tests/relinquish_nft_vote.rs +++ /dev/null @@ -1,363 +0,0 @@ -use crate::program_test::nft_voter_test::ConfigureCollectionArgs; -use gpl_nft_voter::error::NftVoterError; -use program_test::nft_voter_test::NftVoterTest; -use program_test::tools::assert_nft_voter_err; -use solana_program_test::*; -use solana_sdk::transport::TransportError; - -mod program_test; - -#[tokio::test] -async fn test_relinquish_nft_vote() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { weight: 1, size: 1 }), // Set Size == 1 to complete voting with just one vote - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let nft_cookie1 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - let nft_vote_record_cookies = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - None, - ) - .await?; - - // Act - - nft_voter_test - .relinquish_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &nft_vote_record_cookies, - ) - .await?; - - // Assert - - let voter_weight_record = nft_voter_test - .get_voter_weight_record(&voter_weight_record_cookie.address) - .await; - - assert_eq!(voter_weight_record.voter_weight_expiry, Some(0)); - assert_eq!(voter_weight_record.voter_weight, 0); - - // Check NftVoteRecord was disposed - let nft_vote_record = nft_voter_test - .bench - .get_account(&nft_vote_record_cookies[0].address) - .await; - - assert_eq!(None, nft_vote_record); - - Ok(()) -} - -#[tokio::test] -async fn test_relinquish_nft_vote_for_proposal_in_voting_state() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - None, - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let nft_cookie1 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - let nft_vote_record_cookies = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - None, - ) - .await?; - - // Relinquish Vote from spl-gov - nft_voter_test - .governance - .relinquish_vote( - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - ) - .await?; - - // Act - - nft_voter_test - .relinquish_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &nft_vote_record_cookies, - ) - .await?; - - // Assert - - let voter_weight_record = nft_voter_test - .get_voter_weight_record(&voter_weight_record_cookie.address) - .await; - - assert_eq!(voter_weight_record.voter_weight_expiry, Some(0)); - assert_eq!(voter_weight_record.voter_weight, 0); - - // Check NftVoteRecord was disposed - let nft_vote_record = nft_voter_test - .bench - .get_account(&nft_vote_record_cookies[0].address) - .await; - - assert_eq!(None, nft_vote_record); - - Ok(()) -} - -#[tokio::test] -async fn test_relinquish_nft_vote_for_proposal_in_voting_state_and_vote_record_exists_error( -) -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - None, - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let nft_cookie1 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - let nft_vote_record_cookies = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - None, - ) - .await?; - - // Act - - let err = nft_voter_test - .relinquish_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &nft_vote_record_cookies, - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, NftVoterError::VoteRecordMustBeWithdrawn); - - Ok(()) -} - -#[tokio::test] -async fn test_relinquish_nft_vote_with_invalid_voter_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { weight: 1, size: 1 }), // Set Size == 1 to complete voting with just one vote - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let voter_token_owner_record_cookie = nft_voter_test - .governance - .with_token_owner_record(&realm_cookie, &voter_cookie) - .await?; - - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let proposal_cookie = nft_voter_test - .governance - .with_proposal(&realm_cookie) - .await?; - - let nft_cookie1 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - let nft_vote_record_cookies = nft_voter_test - .cast_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &max_voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie, - &voter_token_owner_record_cookie, - &[&nft_cookie1], - None, - ) - .await?; - - // Try to use a different voter - let voter_cookie2 = nft_voter_test.bench.with_wallet().await; - - // Act - - let err = nft_voter_test - .relinquish_nft_vote( - ®istrar_cookie, - &voter_weight_record_cookie, - &proposal_cookie, - &voter_cookie2, - &voter_token_owner_record_cookie, - &nft_vote_record_cookies, - ) - .await - .err() - .unwrap(); - - // Assert - - assert_nft_voter_err(err, NftVoterError::InvalidTokenOwnerForVoterWeightRecord); - - Ok(()) -} From d28d717b33e9a97bdefc1103ce8fc1b9e9526879 Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Sat, 21 May 2022 23:10:23 +0300 Subject: [PATCH 03/11] chore: Create skeleton Squads instructions --- programs/squads-voter/src/instructions/mod.rs | 3 ++ .../update_max_voter_weight_record.rs | 53 +++++++++++++++++++ .../update_voter_weight_record.rs | 4 +- programs/squads-voter/src/lib.rs | 4 ++ programs/squads-voter/src/state/registrar.rs | 2 +- 5 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 programs/squads-voter/src/instructions/update_max_voter_weight_record.rs diff --git a/programs/squads-voter/src/instructions/mod.rs b/programs/squads-voter/src/instructions/mod.rs index b0074432..4a5c8b9f 100644 --- a/programs/squads-voter/src/instructions/mod.rs +++ b/programs/squads-voter/src/instructions/mod.rs @@ -12,3 +12,6 @@ mod create_max_voter_weight_record; pub use update_voter_weight_record::*; mod update_voter_weight_record; + +pub use update_max_voter_weight_record::*; +mod update_max_voter_weight_record; diff --git a/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs b/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs new file mode 100644 index 00000000..111d961a --- /dev/null +++ b/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs @@ -0,0 +1,53 @@ +use crate::error::SquadVoterError; +use crate::state::max_voter_weight_record::MaxVoterWeightRecord; +use crate::state::*; +use anchor_lang::prelude::*; + +/// Updates MaxVoterWeightRecord to evaluate governance power for users and the Squads they belong to +/// This instruction updates VoterWeightRecord which is valid for the current Slot only +/// The instruction must be executed inside the same transaction as the corresponding spl-gov instruction +#[derive(Accounts)] +#[instruction(voter_weight_action:VoterWeightAction)] +pub struct UpdateMaxVoterWeightRecord<'info> { + /// The Squads voting Registrar + pub registrar: Account<'info, Registrar>, + + #[account( + mut, + constraint = max_voter_weight_record.realm == registrar.realm + @ SquadVoterError::InvalidVoterWeightRecordRealm, + + constraint = max_voter_weight_record.governing_token_mint == registrar.governing_token_mint + @ SquadVoterError::InvalidVoterWeightRecordMint, + )] + pub max_voter_weight_record: Account<'info, MaxVoterWeightRecord>, +} + +pub fn update_max_voter_weight_record(ctx: Context) -> Result<()> { + let registrar = &ctx.accounts.registrar; + + let mut max_voter_weight = 0u64; + + for squad_config in registrar.squads_configs.iter() { + let _squad_info = ctx + .remaining_accounts + .iter() + .find(|ai| ai.key() == squad_config.squad) + .unwrap(); + + // TODO: Get the Squad size from squad_info + let squad_size = 10; + + max_voter_weight = max_voter_weight + .checked_add(squad_config.weight.checked_mul(squad_size).unwrap()) + .unwrap(); + } + + let voter_weight_record = &mut ctx.accounts.max_voter_weight_record; + voter_weight_record.max_voter_weight = max_voter_weight; + + // Record is only valid as of the current slot + voter_weight_record.max_voter_weight_expiry = Some(Clock::get()?.slot); + + Ok(()) +} diff --git a/programs/squads-voter/src/instructions/update_voter_weight_record.rs b/programs/squads-voter/src/instructions/update_voter_weight_record.rs index 40309f57..8a40e0c7 100644 --- a/programs/squads-voter/src/instructions/update_voter_weight_record.rs +++ b/programs/squads-voter/src/instructions/update_voter_weight_record.rs @@ -20,6 +20,8 @@ pub struct UpdateVoterWeightRecord<'info> { @ SquadVoterError::InvalidVoterWeightRecordMint, )] pub voter_weight_record: Account<'info, VoterWeightRecord>, + // + // Remaining Accounts: Squads } pub fn update_voter_weight_record(ctx: Context) -> Result<()> { @@ -37,7 +39,7 @@ pub fn update_voter_weight_record(ctx: Context) -> Resu } unique_squads.push(squad_info.key); - // TODO: Validate Squad membership for governing_token_owner + // TODO: Validate Squad membership for governing_token_owner and squad_info let squad_config = registrar.get_squad_config(squad_info.key)?; diff --git a/programs/squads-voter/src/lib.rs b/programs/squads-voter/src/lib.rs index 8b698646..e05f470f 100644 --- a/programs/squads-voter/src/lib.rs +++ b/programs/squads-voter/src/lib.rs @@ -34,6 +34,10 @@ pub mod squads_voter { log_version(); instructions::update_voter_weight_record(ctx) } + pub fn update_max_voter_weight_record(ctx: Context) -> Result<()> { + log_version(); + instructions::update_max_voter_weight_record(ctx) + } pub fn configure_squad(ctx: Context, weight: u64) -> Result<()> { log_version(); diff --git a/programs/squads-voter/src/state/registrar.rs b/programs/squads-voter/src/state/registrar.rs index c7f53cd1..905d335d 100644 --- a/programs/squads-voter/src/state/registrar.rs +++ b/programs/squads-voter/src/state/registrar.rs @@ -57,7 +57,7 @@ impl Registrar { return self .squads_configs .iter() - .find(|cc| cc.squad == *squad) + .find(|sc| sc.squad == *squad) .ok_or_else(|| SquadVoterError::SquadNotFound.into()); } } From 58ac1b9512c14ed627bdf6151daecc43c4ba32e7 Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Sun, 22 May 2022 01:18:49 +0300 Subject: [PATCH 04/11] wip: create_registrar tests --- programs/squads-voter/src/state/registrar.rs | 6 +- .../tests/configure_collection.rs | 413 ------------- .../tests/create_max_voter_weight_record.rs | 123 ---- .../squads-voter/tests/create_registrar.rs | 4 +- .../tests/create_voter_weight_record.rs | 126 ---- .../tests/fixtures/mpl_token_metadata.so | Bin 460888 -> 0 bytes .../squads-voter/tests/program_test/mod.rs | 1 - .../tests/program_test/nft_voter_test.rs | 253 ++------ .../tests/program_test/token_metadata_test.rs | 221 ------- .../squads-voter/tests/program_test/tools.rs | 4 +- .../tests/update_voter_weight_record.rs | 575 ------------------ 11 files changed, 40 insertions(+), 1686 deletions(-) delete mode 100644 programs/squads-voter/tests/configure_collection.rs delete mode 100644 programs/squads-voter/tests/create_max_voter_weight_record.rs delete mode 100644 programs/squads-voter/tests/create_voter_weight_record.rs delete mode 100755 programs/squads-voter/tests/fixtures/mpl_token_metadata.so delete mode 100644 programs/squads-voter/tests/program_test/token_metadata_test.rs delete mode 100644 programs/squads-voter/tests/update_voter_weight_record.rs diff --git a/programs/squads-voter/src/state/registrar.rs b/programs/squads-voter/src/state/registrar.rs index 905d335d..ed96b670 100644 --- a/programs/squads-voter/src/state/registrar.rs +++ b/programs/squads-voter/src/state/registrar.rs @@ -31,11 +31,7 @@ pub struct Registrar { impl Registrar { pub fn get_space(max_squads: u8) -> usize { - DISCRIMINATOR_SIZE - + PUBKEY_SIZE * 3 - + 4 - + max_squads as usize * (PUBKEY_SIZE + 4 + 8 + 8) - + 128 + DISCRIMINATOR_SIZE + PUBKEY_SIZE * 3 + 4 + max_squads as usize * (PUBKEY_SIZE + 8 + 8) + 128 } } diff --git a/programs/squads-voter/tests/configure_collection.rs b/programs/squads-voter/tests/configure_collection.rs deleted file mode 100644 index 4d83f5e3..00000000 --- a/programs/squads-voter/tests/configure_collection.rs +++ /dev/null @@ -1,413 +0,0 @@ -use gpl_nft_voter::error::NftVoterError; -use program_test::{ - nft_voter_test::NftVoterTest, - tools::{assert_anchor_err, assert_nft_voter_err}, -}; - -use solana_program_test::*; -use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; - -use crate::program_test::nft_voter_test::ConfigureCollectionArgs; - -mod program_test; - -#[tokio::test] -async fn test_configure_collection() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - // Act - let collection_config_cookie = nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - None, - ) - .await?; - - // Assert - let registrar = nft_voter_test - .get_registrar_account(®istrar_cookie.address) - .await; - - assert_eq!(registrar.collection_configs.len(), 1); - - assert_eq!( - registrar.collection_configs[0], - collection_config_cookie.collection_config - ); - - let max_voter_weight_record = nft_voter_test - .get_max_voter_weight_record(&max_voter_weight_record_cookie.address) - .await; - - assert_eq!(max_voter_weight_record.max_voter_weight_expiry, None); - assert_eq!( - max_voter_weight_record.max_voter_weight, - (registrar.collection_configs[0].weight as u32 * registrar.collection_configs[0].size) - as u64 - ); - - Ok(()) -} - -#[tokio::test] -async fn test_configure_multiple_collections() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie1 = nft_voter_test.token_metadata.with_nft_collection().await?; - let nft_collection_cookie2 = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - // Act - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie1, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { weight: 1, size: 5 }), - ) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie2, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 2, - size: 10, - }), - ) - .await?; - - // Assert - let registrar = nft_voter_test - .get_registrar_account(®istrar_cookie.address) - .await; - - assert_eq!(registrar.collection_configs.len(), 2); - - let max_voter_weight_record = nft_voter_test - .get_max_voter_weight_record(&max_voter_weight_record_cookie.address) - .await; - - assert_eq!(max_voter_weight_record.max_voter_weight_expiry, None); - assert_eq!(max_voter_weight_record.max_voter_weight, 25); - - Ok(()) -} - -#[tokio::test] -async fn test_configure_max_collections() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - // Act - - for _ in 0..registrar_cookie.max_collections { - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - None, - ) - .await?; - } - - // Assert - let registrar = nft_voter_test - .get_registrar_account(®istrar_cookie.address) - .await; - - assert_eq!( - registrar.collection_configs.len() as u8, - registrar_cookie.max_collections - ); - - let max_voter_weight_record = nft_voter_test - .get_max_voter_weight_record(&max_voter_weight_record_cookie.address) - .await; - - assert_eq!(max_voter_weight_record.max_voter_weight_expiry, None); - assert_eq!(max_voter_weight_record.max_voter_weight, 30); - - Ok(()) -} - -#[tokio::test] -async fn test_configure_existing_collection() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - None, - ) - .await?; - - // Act - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 2, - size: 10, - }), - ) - .await?; - - // Assert - let registrar = nft_voter_test - .get_registrar_account(®istrar_cookie.address) - .await; - - assert_eq!(registrar.collection_configs.len(), 1); - - let max_voter_weight_record = nft_voter_test - .get_max_voter_weight_record(&max_voter_weight_record_cookie.address) - .await; - - assert_eq!(max_voter_weight_record.max_voter_weight_expiry, None); - assert_eq!(max_voter_weight_record.max_voter_weight, 20); - - Ok(()) -} - -// TODO: Remove collection test - -#[tokio::test] -async fn test_configure_collection_with_invalid_realm_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - // Try to use a different Realm - let realm_cookie2 = nft_voter_test.governance.with_realm().await?; - - // Act - let err = nft_voter_test - .with_collection_using_ix( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - None, - |i| i.accounts[1].pubkey = realm_cookie2.address, // realm - None, - ) - .await - .err() - .unwrap(); - - // Assert - - assert_nft_voter_err(err, NftVoterError::InvalidRealmForRegistrar); - - Ok(()) -} - -#[tokio::test] -async fn test_configure_collection_with_realm_authority_must_sign_error( -) -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - // Act - let err = nft_voter_test - .with_collection_using_ix( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - None, - |i| i.accounts[2].is_signer = false, // realm_authority - Some(&[]), - ) - .await - .err() - .unwrap(); - - // Assert - - assert_anchor_err(err, anchor_lang::error::ErrorCode::AccountNotSigner); - - Ok(()) -} - -#[tokio::test] -async fn test_configure_collection_with_invalid_realm_authority_error() -> Result<(), TransportError> -{ - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - let realm_authority = Keypair::new(); - - // Act - let err = nft_voter_test - .with_collection_using_ix( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - None, - |i| i.accounts[2].pubkey = realm_authority.pubkey(), // realm_authority - Some(&[&realm_authority]), - ) - .await - .err() - .unwrap(); - - // Assert - - assert_nft_voter_err(err, NftVoterError::InvalidRealmAuthority); - - Ok(()) -} - -#[tokio::test] -async fn test_configure_collection_with_invalid_max_voter_weight_realm_error( -) -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let realm_cookie2 = nft_voter_test.governance.with_realm().await?; - let registrar_cookie2 = nft_voter_test.with_registrar(&realm_cookie2).await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie2) - .await?; - - // Act - let err = nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - None, - ) - .await - .err() - .unwrap(); - - // Assert - - assert_nft_voter_err(err, NftVoterError::InvalidMaxVoterWeightRecordRealm); - - Ok(()) -} - -#[tokio::test] -async fn test_configure_collection_with_invalid_max_voter_weight_mint_error( -) -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let mut realm_cookie = nft_voter_test.governance.with_realm().await?; - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - // Create Registrar for council mint - realm_cookie.account.community_mint = realm_cookie.account.config.council_mint.unwrap(); - let registrar_cookie2 = nft_voter_test.with_registrar(&realm_cookie).await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie2) - .await?; - - // Act - let err = nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - None, - ) - .await - .err() - .unwrap(); - - // Assert - - assert_nft_voter_err(err, NftVoterError::InvalidMaxVoterWeightRecordMint); - - Ok(()) -} diff --git a/programs/squads-voter/tests/create_max_voter_weight_record.rs b/programs/squads-voter/tests/create_max_voter_weight_record.rs deleted file mode 100644 index 2e5c7b35..00000000 --- a/programs/squads-voter/tests/create_max_voter_weight_record.rs +++ /dev/null @@ -1,123 +0,0 @@ -use program_test::{nft_voter_test::NftVoterTest, tools::assert_ix_err}; -use solana_program::instruction::InstructionError; -use solana_program_test::*; -use solana_sdk::transport::TransportError; - -mod program_test; - -#[tokio::test] -async fn test_create_max_voter_weight_record() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - // Act - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - // Assert - - let max_voter_weight_record = nft_voter_test - .get_max_voter_weight_record(&max_voter_weight_record_cookie.address) - .await; - - assert_eq!( - max_voter_weight_record_cookie.account, - max_voter_weight_record - ); - - Ok(()) -} - -#[tokio::test] -async fn test_create_max_voter_weight_record_with_invalid_realm_error() -> Result<(), TransportError> -{ - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let realm_cookie2 = nft_voter_test.governance.with_realm().await?; - - // Act - let err = nft_voter_test - .with_max_voter_weight_record_using_ix(®istrar_cookie, |i| { - i.accounts[2].pubkey = realm_cookie2.address // Realm - }) - .await - .err() - .unwrap(); - - // Assert - - // PDA doesn't match and hence the error is PrivilegeEscalation - assert_ix_err(err, InstructionError::PrivilegeEscalation); - - Ok(()) -} - -#[tokio::test] -async fn test_create_max_voter_weight_record_with_invalid_mint_error() -> Result<(), TransportError> -{ - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let realm_cookie2 = nft_voter_test.governance.with_realm().await?; - - // Act - let err = nft_voter_test - .with_max_voter_weight_record_using_ix(®istrar_cookie, |i| { - i.accounts[2].pubkey = realm_cookie2.address // Mint - }) - .await - .err() - .unwrap(); - - // Assert - - // PDA doesn't match and hence the error is PrivilegeEscalation - assert_ix_err(err, InstructionError::PrivilegeEscalation); - - Ok(()) -} - -#[tokio::test] -async fn test_create_max_voter_weight_record_with_already_exists_error( -) -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test.bench.advance_clock().await; - - // Act - let err = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await - .err() - .unwrap(); - - // Assert - - // InstructionError::Custom(0) is returned for TransactionError::AccountInUse - assert_ix_err(err, InstructionError::Custom(0)); - - Ok(()) -} diff --git a/programs/squads-voter/tests/create_registrar.rs b/programs/squads-voter/tests/create_registrar.rs index 29a9cc30..f4893671 100644 --- a/programs/squads-voter/tests/create_registrar.rs +++ b/programs/squads-voter/tests/create_registrar.rs @@ -1,7 +1,7 @@ mod program_test; use anchor_lang::prelude::Pubkey; -use gpl_nft_voter::error::NftVoterError; +use gpl_squads_voter::error::SquadVoterError; use program_test::nft_voter_test::NftVoterTest; use solana_program::instruction::InstructionError; @@ -45,7 +45,7 @@ async fn test_create_registrar_with_invalid_realm_authority_error() -> Result<() .err() .unwrap(); - assert_nft_voter_err(err, NftVoterError::InvalidRealmAuthority); + assert_nft_voter_err(err, SquadVoterError::InvalidRealmAuthority); Ok(()) } diff --git a/programs/squads-voter/tests/create_voter_weight_record.rs b/programs/squads-voter/tests/create_voter_weight_record.rs deleted file mode 100644 index c23d7858..00000000 --- a/programs/squads-voter/tests/create_voter_weight_record.rs +++ /dev/null @@ -1,126 +0,0 @@ -use program_test::nft_voter_test::NftVoterTest; -use program_test::tools::assert_ix_err; -use solana_program::instruction::InstructionError; -use solana_program_test::*; -use solana_sdk::transport::TransportError; - -mod program_test; - -#[tokio::test] -async fn test_create_voter_weight_record() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - // Act - let voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - // Assert - - let voter_weight_record = nft_voter_test - .get_voter_weight_record(&voter_weight_record_cookie.address) - .await; - - assert_eq!(voter_weight_record_cookie.account, voter_weight_record); - - Ok(()) -} - -#[tokio::test] -async fn test_create_voter_weight_record_with_invalid_realm_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let realm_cookie2 = nft_voter_test.governance.with_realm().await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - // Act - let err = nft_voter_test - .with_voter_weight_record_using_ix(®istrar_cookie, &voter_cookie, |i| { - i.accounts[2].pubkey = realm_cookie2.address // Realm - }) - .await - .err() - .unwrap(); - - // Assert - - // PDA doesn't match and hence the error is PrivilegeEscalation - assert_ix_err(err, InstructionError::PrivilegeEscalation); - - Ok(()) -} - -#[tokio::test] -async fn test_create_voter_weight_record_with_invalid_mint_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let realm_cookie2 = nft_voter_test.governance.with_realm().await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - // Act - let err = nft_voter_test - .with_voter_weight_record_using_ix(®istrar_cookie, &voter_cookie, |i| { - i.accounts[2].pubkey = realm_cookie2.address // Mint - }) - .await - .err() - .unwrap(); - - // Assert - - // PDA doesn't match and hence the error is PrivilegeEscalation - assert_ix_err(err, InstructionError::PrivilegeEscalation); - - Ok(()) -} - -#[tokio::test] -async fn test_create_voter_weight_record_with_already_exists_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - nft_voter_test.bench.advance_clock().await; - - // Act - let err = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await - .err() - .unwrap(); - - // Assert - - // InstructionError::Custom(0) is returned for TransactionError::AccountInUse - assert_ix_err(err, InstructionError::Custom(0)); - - Ok(()) -} diff --git a/programs/squads-voter/tests/fixtures/mpl_token_metadata.so b/programs/squads-voter/tests/fixtures/mpl_token_metadata.so deleted file mode 100755 index 7c8ae98f6868ca62844ffc214e3190d1c68b6f2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 460888 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 yyFOOAO9b?P}W)i diff --git a/programs/squads-voter/tests/program_test/mod.rs b/programs/squads-voter/tests/program_test/mod.rs index 60cbb5dd..a930ce3d 100644 --- a/programs/squads-voter/tests/program_test/mod.rs +++ b/programs/squads-voter/tests/program_test/mod.rs @@ -1,5 +1,4 @@ pub mod governance_test; pub mod nft_voter_test; pub mod program_test_bench; -pub mod token_metadata_test; pub mod tools; diff --git a/programs/squads-voter/tests/program_test/nft_voter_test.rs b/programs/squads-voter/tests/program_test/nft_voter_test.rs index 75c9e11e..895b7044 100644 --- a/programs/squads-voter/tests/program_test/nft_voter_test.rs +++ b/programs/squads-voter/tests/program_test/nft_voter_test.rs @@ -2,17 +2,11 @@ use std::sync::Arc; use anchor_lang::prelude::{AccountMeta, Pubkey}; -use gpl_nft_voter::state::max_voter_weight_record::{ +use gpl_squads_voter::state::max_voter_weight_record::{ get_max_voter_weight_record_address, MaxVoterWeightRecord, }; -use gpl_nft_voter::state::*; +use gpl_squads_voter::state::*; use solana_sdk::transport::TransportError; -use spl_governance::instruction::cast_vote; -use spl_governance::state::vote_record::{self, Vote, VoteChoice}; - -use gpl_nft_voter::state::{ - get_nft_vote_record_address, get_registrar_address, CollectionConfig, NftVoteRecord, Registrar, -}; use solana_program_test::ProgramTest; use solana_sdk::instruction::Instruction; @@ -22,9 +16,9 @@ use solana_sdk::signer::Signer; use crate::program_test::governance_test::GovernanceTest; use crate::program_test::program_test_bench::ProgramTestBench; -use crate::program_test::governance_test::{ProposalCookie, RealmCookie, TokenOwnerRecordCookie}; +use crate::program_test::governance_test::RealmCookie; use crate::program_test::program_test_bench::WalletCookie; -use crate::program_test::token_metadata_test::{NftCollectionCookie, NftCookie, TokenMetadataTest}; + use crate::program_test::tools::NopOverride; #[derive(Debug, PartialEq)] @@ -47,7 +41,7 @@ pub struct MaxVoterWeightRecordCookie { } pub struct CollectionConfigCookie { - pub collection_config: CollectionConfig, + pub collection_config: SquadConfig, } pub struct ConfigureCollectionArgs { @@ -61,35 +55,20 @@ impl Default for ConfigureCollectionArgs { } } -#[derive(Debug, PartialEq)] -pub struct NftVoteRecordCookie { +pub struct SquadCookie { pub address: Pubkey, - pub account: NftVoteRecord, -} - -pub struct CastNftVoteArgs { - pub cast_spl_gov_vote: bool, -} - -impl Default for CastNftVoteArgs { - fn default() -> Self { - Self { - cast_spl_gov_vote: true, - } - } } pub struct NftVoterTest { pub program_id: Pubkey, pub bench: Arc, pub governance: GovernanceTest, - pub token_metadata: TokenMetadataTest, } impl NftVoterTest { #[allow(dead_code)] pub fn add_program(program_test: &mut ProgramTest) { - program_test.add_program("gpl_nft_voter", gpl_nft_voter::id(), None); + program_test.add_program("gpl_squads_voter", gpl_squads_voter::id(), None); } #[allow(dead_code)] @@ -98,22 +77,19 @@ impl NftVoterTest { NftVoterTest::add_program(&mut program_test); GovernanceTest::add_program(&mut program_test); - TokenMetadataTest::add_program(&mut program_test); - let program_id = gpl_nft_voter::id(); + let program_id = gpl_squads_voter::id(); let bench = ProgramTestBench::start_new(program_test).await; let bench_rc = Arc::new(bench); let governance_bench = GovernanceTest::new(bench_rc.clone(), Some(program_id), Some(program_id)); - let token_metadata_bench = TokenMetadataTest::new(bench_rc.clone()); Self { program_id, bench: bench_rc, governance: governance_bench, - token_metadata: token_metadata_bench, } } @@ -136,15 +112,15 @@ impl NftVoterTest { let registrar_key = get_registrar_address(&realm_cookie.address, &realm_cookie.account.community_mint); - let max_collections = 10; + let max_squads = 10; let data = - anchor_lang::InstructionData::data(&gpl_nft_voter::instruction::CreateRegistrar { - max_collections, + anchor_lang::InstructionData::data(&gpl_squads_voter::instruction::CreateRegistrar { + max_squads, }); let accounts = anchor_lang::ToAccountMetas::to_account_metas( - &gpl_nft_voter::accounts::CreateRegistrar { + &gpl_squads_voter::accounts::CreateRegistrar { registrar: registrar_key, realm: realm_cookie.address, governance_program_id: self.governance.program_id, @@ -157,7 +133,7 @@ impl NftVoterTest { ); let mut create_registrar_ix = Instruction { - program_id: gpl_nft_voter::id(), + program_id: gpl_squads_voter::id(), accounts, data, }; @@ -175,7 +151,7 @@ impl NftVoterTest { governance_program_id: self.governance.program_id, realm: realm_cookie.address, governing_token_mint: realm_cookie.account.community_mint, - collection_configs: vec![], + squads_configs: vec![], reserved: [0; 128], }; @@ -183,7 +159,7 @@ impl NftVoterTest { address: registrar_key, account, realm_authority: realm_cookie.get_realm_authority(), - max_collections, + max_collections: max_squads, }) } @@ -213,16 +189,16 @@ impl NftVoterTest { registrar_cookie.account.governing_token_mint.as_ref(), governing_token_owner.as_ref(), ], - &gpl_nft_voter::id(), + &gpl_squads_voter::id(), ); let data = anchor_lang::InstructionData::data( - &gpl_nft_voter::instruction::CreateVoterWeightRecord { + &gpl_squads_voter::instruction::CreateVoterWeightRecord { governing_token_owner, }, ); - let accounts = gpl_nft_voter::accounts::CreateVoterWeightRecord { + let accounts = gpl_squads_voter::accounts::CreateVoterWeightRecord { governance_program_id: self.governance.program_id, realm: registrar_cookie.account.realm, realm_governing_token_mint: registrar_cookie.account.governing_token_mint, @@ -232,7 +208,7 @@ impl NftVoterTest { }; let mut create_voter_weight_record_ix = Instruction { - program_id: gpl_nft_voter::id(), + program_id: gpl_squads_voter::id(), accounts: anchor_lang::ToAccountMetas::to_account_metas(&accounts, None), data, }; @@ -281,10 +257,10 @@ impl NftVoterTest { ); let data = anchor_lang::InstructionData::data( - &gpl_nft_voter::instruction::CreateMaxVoterWeightRecord {}, + &gpl_squads_voter::instruction::CreateMaxVoterWeightRecord {}, ); - let accounts = gpl_nft_voter::accounts::CreateMaxVoterWeightRecord { + let accounts = gpl_squads_voter::accounts::CreateMaxVoterWeightRecord { governance_program_id: self.governance.program_id, realm: registrar_cookie.account.realm, realm_governing_token_mint: registrar_cookie.account.governing_token_mint, @@ -294,7 +270,7 @@ impl NftVoterTest { }; let mut create_max_voter_weight_record_ix = Instruction { - program_id: gpl_nft_voter::id(), + program_id: gpl_squads_voter::id(), accounts: anchor_lang::ToAccountMetas::to_account_metas(&accounts, None), data, }; @@ -324,29 +300,25 @@ impl NftVoterTest { &self, registrar_cookie: &RegistrarCookie, voter_weight_record_cookie: &mut VoterWeightRecordCookie, - voter_weight_action: VoterWeightAction, - nft_cookies: &[&NftCookie], + squads_cookies: &[&SquadCookie], ) -> Result<(), TransportError> { let data = anchor_lang::InstructionData::data( - &gpl_nft_voter::instruction::UpdateVoterWeightRecord { - voter_weight_action, - }, + &gpl_squads_voter::instruction::UpdateVoterWeightRecord {}, ); - let accounts = gpl_nft_voter::accounts::UpdateVoterWeightRecord { + let accounts = gpl_squads_voter::accounts::UpdateVoterWeightRecord { registrar: registrar_cookie.address, voter_weight_record: voter_weight_record_cookie.address, }; let mut account_metas = anchor_lang::ToAccountMetas::to_account_metas(&accounts, None); - for nft_cookie in nft_cookies { + for nft_cookie in squads_cookies { account_metas.push(AccountMeta::new_readonly(nft_cookie.address, false)); - account_metas.push(AccountMeta::new_readonly(nft_cookie.metadata, false)); } let instructions = vec![Instruction { - program_id: gpl_nft_voter::id(), + program_id: gpl_squads_voter::id(), accounts: account_metas, data, }]; @@ -354,66 +326,16 @@ impl NftVoterTest { self.bench.process_transaction(&instructions, None).await } - #[allow(dead_code)] - pub async fn relinquish_nft_vote( - &mut self, - registrar_cookie: &RegistrarCookie, - voter_weight_record_cookie: &VoterWeightRecordCookie, - proposal_cookie: &ProposalCookie, - voter_cookie: &WalletCookie, - voter_token_owner_record_cookie: &TokenOwnerRecordCookie, - nft_vote_record_cookies: &Vec, - ) -> Result<(), TransportError> { - let data = - anchor_lang::InstructionData::data(&gpl_nft_voter::instruction::RelinquishNftVote {}); - - let vote_record_key = vote_record::get_vote_record_address( - &self.governance.program_id, - &proposal_cookie.address, - &voter_token_owner_record_cookie.address, - ); - - let accounts = gpl_nft_voter::accounts::RelinquishNftVote { - registrar: registrar_cookie.address, - voter_weight_record: voter_weight_record_cookie.address, - governance: proposal_cookie.account.governance, - proposal: proposal_cookie.address, - governing_token_owner: voter_cookie.address, - vote_record: vote_record_key, - beneficiary: self.bench.payer.pubkey(), - }; - - let mut account_metas = anchor_lang::ToAccountMetas::to_account_metas(&accounts, None); - - for nft_vote_record_cookie in nft_vote_record_cookies { - account_metas.push(AccountMeta::new(nft_vote_record_cookie.address, false)); - } - - let relinquish_nft_vote_ix = Instruction { - program_id: gpl_nft_voter::id(), - accounts: account_metas, - data, - }; - - self.bench - .process_transaction(&[relinquish_nft_vote_ix], Some(&[&voter_cookie.signer])) - .await?; - - Ok(()) - } - #[allow(dead_code)] pub async fn with_collection( &mut self, registrar_cookie: &RegistrarCookie, - nft_collection_cookie: &NftCollectionCookie, - max_voter_weight_record_cookie: &MaxVoterWeightRecordCookie, + nft_collection_cookie: &SquadCookie, args: Option, ) -> Result { self.with_collection_using_ix( registrar_cookie, nft_collection_cookie, - max_voter_weight_record_cookie, args, NopOverride, None, @@ -425,8 +347,7 @@ impl NftVoterTest { pub async fn with_collection_using_ix( &mut self, registrar_cookie: &RegistrarCookie, - nft_collection_cookie: &NftCollectionCookie, - max_voter_weight_record_cookie: &MaxVoterWeightRecordCookie, + squad_cookie: &SquadCookie, args: Option, instruction_override: F, signers_override: Option<&[&Keypair]>, @@ -434,21 +355,19 @@ impl NftVoterTest { let args = args.unwrap_or_default(); let data = - anchor_lang::InstructionData::data(&gpl_nft_voter::instruction::ConfigureCollection { + anchor_lang::InstructionData::data(&gpl_squads_voter::instruction::ConfigureSquad { weight: args.weight, - size: args.size, }); - let accounts = gpl_nft_voter::accounts::ConfigureCollection { + let accounts = gpl_squads_voter::accounts::ConfigureSquad { registrar: registrar_cookie.address, realm: registrar_cookie.account.realm, realm_authority: registrar_cookie.realm_authority.pubkey(), - collection: nft_collection_cookie.mint, - max_voter_weight_record: max_voter_weight_record_cookie.address, + squad: squad_cookie.address, }; let mut configure_collection_ix = Instruction { - program_id: gpl_nft_voter::id(), + program_id: gpl_squads_voter::id(), accounts: anchor_lang::ToAccountMetas::to_account_metas(&accounts, None), data, }; @@ -462,9 +381,8 @@ impl NftVoterTest { .process_transaction(&[configure_collection_ix], Some(signers)) .await?; - let collection_config = CollectionConfig { - collection: nft_collection_cookie.mint, - size: args.size, + let collection_config = SquadConfig { + squad: squad_cookie.address, weight: args.weight, reserved: [0; 8], }; @@ -472,112 +390,11 @@ impl NftVoterTest { Ok(CollectionConfigCookie { collection_config }) } - /// Casts NFT Vote and spl-gov Vote - #[allow(dead_code)] - pub async fn cast_nft_vote( - &mut self, - registrar_cookie: &RegistrarCookie, - voter_weight_record_cookie: &VoterWeightRecordCookie, - max_voter_weight_record_cookie: &MaxVoterWeightRecordCookie, - proposal_cookie: &ProposalCookie, - nft_voter_cookie: &WalletCookie, - voter_token_owner_record_cookie: &TokenOwnerRecordCookie, - nft_cookies: &[&NftCookie], - args: Option, - ) -> Result, TransportError> { - let args = args.unwrap_or_default(); - - let data = anchor_lang::InstructionData::data(&gpl_nft_voter::instruction::CastNftVote { - proposal: proposal_cookie.address, - }); - - let accounts = gpl_nft_voter::accounts::CastNftVote { - registrar: registrar_cookie.address, - voter_weight_record: voter_weight_record_cookie.address, - governing_token_owner: nft_voter_cookie.address, - payer: self.bench.payer.pubkey(), - system_program: solana_sdk::system_program::id(), - }; - - let mut account_metas = anchor_lang::ToAccountMetas::to_account_metas(&accounts, None); - let mut nft_vote_record_cookies = vec![]; - - for nft_cookie in nft_cookies { - account_metas.push(AccountMeta::new_readonly(nft_cookie.address, false)); - account_metas.push(AccountMeta::new_readonly(nft_cookie.metadata, false)); - - let nft_vote_record_key = get_nft_vote_record_address( - &proposal_cookie.address, - &nft_cookie.mint_cookie.address, - ); - account_metas.push(AccountMeta::new(nft_vote_record_key, false)); - - let account = NftVoteRecord { - proposal: proposal_cookie.address, - nft_mint: nft_cookie.mint_cookie.address, - governing_token_owner: voter_weight_record_cookie.account.governing_token_owner, - account_discriminator: NftVoteRecord::ACCOUNT_DISCRIMINATOR, - reserved: [0; 8], - }; - - nft_vote_record_cookies.push(NftVoteRecordCookie { - address: nft_vote_record_key, - account, - }) - } - - let cast_nft_vote_ix = Instruction { - program_id: gpl_nft_voter::id(), - accounts: account_metas, - data, - }; - - let mut instruction = vec![cast_nft_vote_ix]; - - if args.cast_spl_gov_vote { - // spl-gov cast vote - let vote = Vote::Approve(vec![VoteChoice { - rank: 0, - weight_percentage: 100, - }]); - - let cast_vote_ix = cast_vote( - &self.governance.program_id, - ®istrar_cookie.account.realm, - &proposal_cookie.account.governance, - &proposal_cookie.address, - &proposal_cookie.account.token_owner_record, - &voter_token_owner_record_cookie.address, - &nft_voter_cookie.address, - &proposal_cookie.account.governing_token_mint, - &self.bench.payer.pubkey(), - Some(voter_weight_record_cookie.address), - Some(max_voter_weight_record_cookie.address), - vote, - ); - - instruction.push(cast_vote_ix); - } - - self.bench - .process_transaction(&instruction, Some(&[&nft_voter_cookie.signer])) - .await?; - - Ok(nft_vote_record_cookies) - } - #[allow(dead_code)] pub async fn get_registrar_account(&mut self, registrar: &Pubkey) -> Registrar { self.bench.get_anchor_account::(*registrar).await } - #[allow(dead_code)] - pub async fn get_nf_vote_record_account(&mut self, nft_vote_record: &Pubkey) -> NftVoteRecord { - self.bench - .get_borsh_account::(nft_vote_record) - .await - } - #[allow(dead_code)] pub async fn get_max_voter_weight_record( &self, diff --git a/programs/squads-voter/tests/program_test/token_metadata_test.rs b/programs/squads-voter/tests/program_test/token_metadata_test.rs deleted file mode 100644 index 3ac0f97f..00000000 --- a/programs/squads-voter/tests/program_test/token_metadata_test.rs +++ /dev/null @@ -1,221 +0,0 @@ -use std::{str::FromStr, sync::Arc}; - -use anchor_lang::prelude::Pubkey; -use mpl_token_metadata::state::Collection; -use solana_program_test::ProgramTest; -use solana_sdk::{signer::Signer, transport::TransportError}; - -use crate::program_test::program_test_bench::{MintCookie, ProgramTestBench, WalletCookie}; - -pub struct NftCookie { - pub address: Pubkey, - pub metadata: Pubkey, - pub mint_cookie: MintCookie, -} - -pub struct NftCollectionCookie { - pub mint: Pubkey, - pub metadata: Pubkey, - pub master_edition: Pubkey, -} - -pub struct CreateNftArgs { - pub verify_collection: bool, - pub amount: u64, -} - -impl Default for CreateNftArgs { - fn default() -> Self { - Self { - verify_collection: true, - amount: 1, - } - } -} - -pub struct TokenMetadataTest { - pub bench: Arc, - pub program_id: Pubkey, -} - -impl TokenMetadataTest { - pub fn program_id() -> Pubkey { - Pubkey::from_str("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s").unwrap() - } - - #[allow(dead_code)] - pub fn add_program(program_test: &mut ProgramTest) { - program_test.add_program("mpl_token_metadata", Self::program_id(), None); - } - - #[allow(dead_code)] - pub fn new(bench: Arc) -> Self { - TokenMetadataTest { - bench, - program_id: Self::program_id(), - } - } - - #[allow(dead_code)] - pub async fn with_nft_collection(&self) -> Result { - let update_authority = self.bench.context.borrow().payer.pubkey(); - let payer = self.bench.context.borrow().payer.pubkey(); - - // Create collection - let coll_mint_cookie = self.bench.with_mint().await?; - self.bench - .with_tokens(&coll_mint_cookie, &update_authority, 1) - .await?; - - let coll_metadata_seeds = &[ - b"metadata".as_ref(), - self.program_id.as_ref(), - &coll_mint_cookie.address.as_ref(), - ]; - let (coll_metadata_key, _) = - Pubkey::find_program_address(coll_metadata_seeds, &self.program_id); - - let coll_name = "NFT_C".to_string(); - let coll_symbol = "NFT_C".to_string(); - let coll_uri = "URI".to_string(); - - let create_coll_metadata_ix = mpl_token_metadata::instruction::create_metadata_accounts_v2( - self.program_id, - coll_metadata_key, - coll_mint_cookie.address, - coll_mint_cookie.mint_authority.pubkey(), - payer.clone(), - update_authority.clone(), - coll_name, - coll_symbol, - coll_uri, - None, - 10, - false, - false, - None, - None, - ); - - self.bench - .process_transaction( - &[create_coll_metadata_ix], - Some(&[&coll_mint_cookie.mint_authority]), - ) - .await?; - - let master_edition_seeds = &[ - b"metadata".as_ref(), - self.program_id.as_ref(), - coll_mint_cookie.address.as_ref(), - b"edition".as_ref(), - ]; - let (master_edition_key, _) = - Pubkey::find_program_address(master_edition_seeds, &self.program_id); - - let create_master_edition_ix = mpl_token_metadata::instruction::create_master_edition_v3( - self.program_id, - master_edition_key, - coll_mint_cookie.address, - update_authority, - coll_mint_cookie.mint_authority.pubkey(), - coll_metadata_key, - payer, - Some(0), - ); - - self.bench - .process_transaction( - &[create_master_edition_ix], - Some(&[&coll_mint_cookie.mint_authority]), - ) - .await?; - - Ok(NftCollectionCookie { - mint: coll_mint_cookie.address, - metadata: coll_metadata_key, - master_edition: master_edition_key, - }) - } - - #[allow(dead_code)] - pub async fn with_nft_v2( - &self, - nft_collection_cookie: &NftCollectionCookie, - nft_owner_cookie: &WalletCookie, - args: Option, - ) -> Result { - let CreateNftArgs { - verify_collection, - amount, - } = args.unwrap_or_default(); - - // Crate NFT - let mint_cookie = self.bench.with_mint().await?; - let nft_account_cookie = self - .bench - .with_tokens(&mint_cookie, &nft_owner_cookie.address, amount) - .await?; - - let metadata_seeds = &[ - b"metadata".as_ref(), - self.program_id.as_ref(), - &mint_cookie.address.as_ref(), - ]; - let (metadata_key, _) = Pubkey::find_program_address(metadata_seeds, &self.program_id); - - let name = "TestNFT".to_string(); - let symbol = "NFT".to_string(); - let uri = "URI".to_string(); - - let collection = Collection { - verified: false, - key: nft_collection_cookie.mint, - }; - - let create_metadata_ix = mpl_token_metadata::instruction::create_metadata_accounts_v2( - self.program_id, - metadata_key, - mint_cookie.address, - mint_cookie.mint_authority.pubkey(), - self.bench.payer.pubkey(), - self.bench.payer.pubkey(), - name, - symbol, - uri, - None, - 10, - false, - false, - Some(collection), - None, - ); - - self.bench - .process_transaction(&[create_metadata_ix], Some(&[&mint_cookie.mint_authority])) - .await?; - - if verify_collection { - let verify_collection = mpl_token_metadata::instruction::verify_collection( - self.program_id, - metadata_key, - self.bench.payer.pubkey(), - self.bench.payer.pubkey(), - nft_collection_cookie.mint, - nft_collection_cookie.metadata, - nft_collection_cookie.master_edition, - None, - ); - - self.bench - .process_transaction(&[verify_collection], None) - .await?; - } - - Ok(NftCookie { - address: nft_account_cookie.address, - metadata: metadata_key, - mint_cookie, - }) - } -} diff --git a/programs/squads-voter/tests/program_test/tools.rs b/programs/squads-voter/tests/program_test/tools.rs index 51f615d8..b63cf69c 100644 --- a/programs/squads-voter/tests/program_test/tools.rs +++ b/programs/squads-voter/tests/program_test/tools.rs @@ -1,5 +1,5 @@ use anchor_lang::prelude::ERROR_CODE_OFFSET; -use gpl_nft_voter::error::NftVoterError; +use gpl_squads_voter::error::SquadVoterError; use solana_program::instruction::InstructionError; use solana_sdk::{signature::Keypair, transaction::TransactionError, transport::TransportError}; use spl_governance_tools::error::GovernanceToolsError; @@ -13,7 +13,7 @@ pub fn clone_keypair(source: &Keypair) -> Keypair { pub fn NopOverride(_: &mut T) {} #[allow(dead_code)] -pub fn assert_nft_voter_err(banks_client_error: TransportError, nft_locker_error: NftVoterError) { +pub fn assert_nft_voter_err(banks_client_error: TransportError, nft_locker_error: SquadVoterError) { let tx_error = banks_client_error.unwrap(); match tx_error { diff --git a/programs/squads-voter/tests/update_voter_weight_record.rs b/programs/squads-voter/tests/update_voter_weight_record.rs deleted file mode 100644 index d29809a2..00000000 --- a/programs/squads-voter/tests/update_voter_weight_record.rs +++ /dev/null @@ -1,575 +0,0 @@ -use crate::program_test::nft_voter_test::ConfigureCollectionArgs; -use gpl_nft_voter::error::NftVoterError; -use gpl_nft_voter::state::*; -use program_test::nft_voter_test::NftVoterTest; -use program_test::token_metadata_test::CreateNftArgs; -use program_test::tools::*; -use solana_program_test::*; -use solana_sdk::transport::TransportError; - -mod program_test; - -#[tokio::test] -async fn test_update_voter_weight_record() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - let _collection_config_cookie = nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let mut voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let nft1_cookie = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - nft_voter_test.bench.advance_clock().await; - let clock = nft_voter_test.bench.get_clock().await; - - // Act - nft_voter_test - .update_voter_weight_record( - ®istrar_cookie, - &mut voter_weight_record_cookie, - VoterWeightAction::CreateProposal, - &[&nft1_cookie], - ) - .await?; - - // Assert - - let voter_weight_record = nft_voter_test - .get_voter_weight_record(&voter_weight_record_cookie.address) - .await; - - assert_eq!(voter_weight_record.voter_weight, 10); - assert_eq!(voter_weight_record.voter_weight_expiry, Some(clock.slot)); - assert_eq!( - voter_weight_record.weight_action, - Some(VoterWeightAction::CreateProposal.into()) - ); - assert_eq!(voter_weight_record.weight_action_target, None); - - Ok(()) -} - -#[tokio::test] -async fn test_update_voter_weight_with_multiple_nfts() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - let _collection_config_cookie = nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let mut voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let nft_cookie1 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - let nft_cookie2 = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - nft_voter_test.bench.advance_clock().await; - let clock = nft_voter_test.bench.get_clock().await; - - // Act - nft_voter_test - .update_voter_weight_record( - ®istrar_cookie, - &mut voter_weight_record_cookie, - VoterWeightAction::CreateProposal, - &[&nft_cookie1, &nft_cookie2], - ) - .await?; - - // Assert - - let voter_weight_record = nft_voter_test - .get_voter_weight_record(&voter_weight_record_cookie.address) - .await; - - assert_eq!(voter_weight_record.voter_weight, 20); - assert_eq!(voter_weight_record.voter_weight_expiry, Some(clock.slot)); - assert_eq!( - voter_weight_record.weight_action, - Some(VoterWeightAction::CreateProposal.into()) - ); - assert_eq!(voter_weight_record.weight_action_target, None); - - Ok(()) -} - -#[tokio::test] -async fn test_update_voter_weight_with_cast_vote_not_allowed_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let mut voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let nft1_cookie = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - // Act - let err = nft_voter_test - .update_voter_weight_record( - ®istrar_cookie, - &mut voter_weight_record_cookie, - VoterWeightAction::CastVote, - &[&nft1_cookie], - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, NftVoterError::CastVoteIsNotAllowed); - - Ok(()) -} - -#[tokio::test] -async fn test_update_voter_weight_with_unverified_collection_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let mut voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - // Create NFT without verified collection - let nft1_cookie = nft_voter_test - .token_metadata - .with_nft_v2( - &nft_collection_cookie, - &voter_cookie, - Some(CreateNftArgs { - verify_collection: false, - ..Default::default() - }), - ) - .await?; - - // Act - let err = nft_voter_test - .update_voter_weight_record( - ®istrar_cookie, - &mut voter_weight_record_cookie, - VoterWeightAction::CreateGovernance, - &[&nft1_cookie], - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, NftVoterError::CollectionMustBeVerified); - - Ok(()) -} - -#[tokio::test] -async fn test_update_voter_weight_with_invalid_owner_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let mut voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let voter_cookie2 = nft_voter_test.bench.with_wallet().await; - - let nft1_cookie = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie2, None) - .await?; - - // Act - let err = nft_voter_test - .update_voter_weight_record( - ®istrar_cookie, - &mut voter_weight_record_cookie, - VoterWeightAction::CreateGovernance, - &[&nft1_cookie], - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, NftVoterError::VoterDoesNotOwnNft); - - Ok(()) -} - -#[tokio::test] -async fn test_update_voter_weight_with_invalid_collection_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let mut voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let nft_collection_cookie2 = nft_voter_test.token_metadata.with_nft_collection().await?; - - let nft1_cookie = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie2, &voter_cookie, None) - .await?; - - // Act - let err = nft_voter_test - .update_voter_weight_record( - ®istrar_cookie, - &mut voter_weight_record_cookie, - VoterWeightAction::CreateGovernance, - &[&nft1_cookie], - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, NftVoterError::CollectionNotFound); - - Ok(()) -} - -#[tokio::test] -async fn test_update_voter_weight_with_invalid_metadata_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let mut voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let mut nft1_cookie = nft_voter_test - .token_metadata - .with_nft_v2( - &nft_collection_cookie, - &voter_cookie, - Some(CreateNftArgs { - verify_collection: false, - ..Default::default() - }), - ) - .await?; - - let nft2_cookie = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - // Try to use verified NFT Metadata - nft1_cookie.metadata = nft2_cookie.metadata; - - // Act - let err = nft_voter_test - .update_voter_weight_record( - ®istrar_cookie, - &mut voter_weight_record_cookie, - VoterWeightAction::CreateGovernance, - &[&nft1_cookie], - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, NftVoterError::TokenMetadataDoesNotMatch); - - Ok(()) -} - -#[tokio::test] -async fn test_update_voter_weight_with_same_nft_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - None, - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let mut voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let nft_cookie = nft_voter_test - .token_metadata - .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) - .await?; - - // Act - let err = nft_voter_test - .update_voter_weight_record( - ®istrar_cookie, - &mut voter_weight_record_cookie, - VoterWeightAction::CreateProposal, - &[&nft_cookie, &nft_cookie], - ) - .await - .err() - .unwrap(); - - // Assert - - assert_nft_voter_err(err, NftVoterError::DuplicatedNftDetected); - - Ok(()) -} - -#[tokio::test] -async fn test_update_voter_weight_record_with_no_nft_error() -> Result<(), TransportError> { - // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; - - let realm_cookie = nft_voter_test.governance.with_realm().await?; - - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; - - let nft_collection_cookie = nft_voter_test.token_metadata.with_nft_collection().await?; - - let max_voter_weight_record_cookie = nft_voter_test - .with_max_voter_weight_record(®istrar_cookie) - .await?; - - let _collection_config_cookie = nft_voter_test - .with_collection( - ®istrar_cookie, - &nft_collection_cookie, - &max_voter_weight_record_cookie, - Some(ConfigureCollectionArgs { - weight: 10, - size: 20, - }), - ) - .await?; - - let voter_cookie = nft_voter_test.bench.with_wallet().await; - - let mut voter_weight_record_cookie = nft_voter_test - .with_voter_weight_record(®istrar_cookie, &voter_cookie) - .await?; - - let nft1_cookie = nft_voter_test - .token_metadata - .with_nft_v2( - &nft_collection_cookie, - &voter_cookie, - Some(CreateNftArgs { - amount: 0, - ..Default::default() - }), - ) - .await?; - - // Act - let err = nft_voter_test - .update_voter_weight_record( - ®istrar_cookie, - &mut voter_weight_record_cookie, - VoterWeightAction::CreateProposal, - &[&nft1_cookie], - ) - .await - .err() - .unwrap(); - - // Assert - assert_nft_voter_err(err, NftVoterError::InvalidNftAmount); - - Ok(()) -} From af42a5b342e6f3972bf21ba50b821fa6c381ad21 Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Sun, 22 May 2022 01:24:12 +0300 Subject: [PATCH 05/11] chore: rename nft-voter artefacts to squads-voter --- programs/squads-voter/src/error.rs | 2 +- .../src/instructions/configure_squad.rs | 6 +-- .../src/instructions/create_registrar.rs | 4 +- .../update_max_voter_weight_record.rs | 6 +-- .../update_voter_weight_record.rs | 8 +-- programs/squads-voter/src/state/registrar.rs | 4 +- .../squads-voter/tests/create_registrar.rs | 50 +++++++++---------- .../squads-voter/tests/program_test/mod.rs | 2 +- ...nft_voter_test.rs => squads_voter_test.rs} | 6 +-- .../squads-voter/tests/program_test/tools.rs | 7 ++- 10 files changed, 49 insertions(+), 46 deletions(-) rename programs/squads-voter/tests/program_test/{nft_voter_test.rs => squads_voter_test.rs} (99%) diff --git a/programs/squads-voter/src/error.rs b/programs/squads-voter/src/error.rs index 857b7900..44b1d538 100644 --- a/programs/squads-voter/src/error.rs +++ b/programs/squads-voter/src/error.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; #[error_code] -pub enum SquadVoterError { +pub enum SquadsVoterError { #[msg("Invalid Realm Authority")] InvalidRealmAuthority, diff --git a/programs/squads-voter/src/instructions/configure_squad.rs b/programs/squads-voter/src/instructions/configure_squad.rs index 996b6cc5..94a8d7e8 100644 --- a/programs/squads-voter/src/instructions/configure_squad.rs +++ b/programs/squads-voter/src/instructions/configure_squad.rs @@ -8,7 +8,7 @@ use anchor_lang::prelude::*; use anchor_spl::token::Mint; use spl_governance::state::realm; -use crate::error::SquadVoterError; +use crate::error::SquadsVoterError; use crate::state::{Registrar, SquadConfig}; /// Creates or updates Squad configuration which defines what Squads can be used for governances @@ -20,7 +20,7 @@ pub struct ConfigureSquad<'info> { pub registrar: Account<'info, Registrar>, #[account( - address = registrar.realm @ SquadVoterError::InvalidRealmForRegistrar, + address = registrar.realm @ SquadsVoterError::InvalidRealmForRegistrar, owner = registrar.governance_program_id )] /// CHECK: Owned by spl-governance instance specified in registrar.governance_program_id @@ -44,7 +44,7 @@ pub fn configure_squad(ctx: Context, weight: u64) -> Result<()> require!( realm.authority.unwrap() == ctx.accounts.realm_authority.key(), - SquadVoterError::InvalidRealmAuthority + SquadsVoterError::InvalidRealmAuthority ); let squad = &ctx.accounts.squad; diff --git a/programs/squads-voter/src/instructions/create_registrar.rs b/programs/squads-voter/src/instructions/create_registrar.rs index 2c89304a..dfef89cd 100644 --- a/programs/squads-voter/src/instructions/create_registrar.rs +++ b/programs/squads-voter/src/instructions/create_registrar.rs @@ -1,4 +1,4 @@ -use crate::error::SquadVoterError; +use crate::error::SquadsVoterError; use crate::state::*; use anchor_lang::prelude::*; use anchor_spl::token::Mint; @@ -74,7 +74,7 @@ pub fn create_registrar(ctx: Context, _max_squads: u8) -> Resul require!( realm.authority.unwrap() == ctx.accounts.realm_authority.key(), - SquadVoterError::InvalidRealmAuthority + SquadsVoterError::InvalidRealmAuthority ); Ok(()) diff --git a/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs b/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs index 111d961a..fc4f073c 100644 --- a/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs +++ b/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs @@ -1,4 +1,4 @@ -use crate::error::SquadVoterError; +use crate::error::SquadsVoterError; use crate::state::max_voter_weight_record::MaxVoterWeightRecord; use crate::state::*; use anchor_lang::prelude::*; @@ -15,10 +15,10 @@ pub struct UpdateMaxVoterWeightRecord<'info> { #[account( mut, constraint = max_voter_weight_record.realm == registrar.realm - @ SquadVoterError::InvalidVoterWeightRecordRealm, + @ SquadsVoterError::InvalidVoterWeightRecordRealm, constraint = max_voter_weight_record.governing_token_mint == registrar.governing_token_mint - @ SquadVoterError::InvalidVoterWeightRecordMint, + @ SquadsVoterError::InvalidVoterWeightRecordMint, )] pub max_voter_weight_record: Account<'info, MaxVoterWeightRecord>, } diff --git a/programs/squads-voter/src/instructions/update_voter_weight_record.rs b/programs/squads-voter/src/instructions/update_voter_weight_record.rs index 8a40e0c7..dd31f7df 100644 --- a/programs/squads-voter/src/instructions/update_voter_weight_record.rs +++ b/programs/squads-voter/src/instructions/update_voter_weight_record.rs @@ -1,4 +1,4 @@ -use crate::error::SquadVoterError; +use crate::error::SquadsVoterError; use crate::state::*; use anchor_lang::prelude::*; @@ -14,10 +14,10 @@ pub struct UpdateVoterWeightRecord<'info> { #[account( mut, constraint = voter_weight_record.realm == registrar.realm - @ SquadVoterError::InvalidVoterWeightRecordRealm, + @ SquadsVoterError::InvalidVoterWeightRecordRealm, constraint = voter_weight_record.governing_token_mint == registrar.governing_token_mint - @ SquadVoterError::InvalidVoterWeightRecordMint, + @ SquadsVoterError::InvalidVoterWeightRecordMint, )] pub voter_weight_record: Account<'info, VoterWeightRecord>, // @@ -35,7 +35,7 @@ pub fn update_voter_weight_record(ctx: Context) -> Resu for squad_info in ctx.remaining_accounts.iter() { // Ensure the same Squad was not provided more than once if unique_squads.contains(&squad_info.key) { - return Err(SquadVoterError::DuplicatedSquadDetected.into()); + return Err(SquadsVoterError::DuplicatedSquadDetected.into()); } unique_squads.push(squad_info.key); diff --git a/programs/squads-voter/src/state/registrar.rs b/programs/squads-voter/src/state/registrar.rs index ed96b670..ec208f71 100644 --- a/programs/squads-voter/src/state/registrar.rs +++ b/programs/squads-voter/src/state/registrar.rs @@ -1,5 +1,5 @@ use crate::{ - error::SquadVoterError, + error::SquadsVoterError, id, state::SquadConfig, tools::anchor::{DISCRIMINATOR_SIZE, PUBKEY_SIZE}, @@ -54,7 +54,7 @@ impl Registrar { .squads_configs .iter() .find(|sc| sc.squad == *squad) - .ok_or_else(|| SquadVoterError::SquadNotFound.into()); + .ok_or_else(|| SquadsVoterError::SquadNotFound.into()); } } diff --git a/programs/squads-voter/tests/create_registrar.rs b/programs/squads-voter/tests/create_registrar.rs index f4893671..95989986 100644 --- a/programs/squads-voter/tests/create_registrar.rs +++ b/programs/squads-voter/tests/create_registrar.rs @@ -1,27 +1,27 @@ mod program_test; use anchor_lang::prelude::Pubkey; -use gpl_squads_voter::error::SquadVoterError; -use program_test::nft_voter_test::NftVoterTest; +use gpl_squads_voter::error::SquadsVoterError; +use program_test::squads_voter_test::SquadsVoterTest; use solana_program::instruction::InstructionError; use solana_program_test::*; use solana_sdk::{signature::Keypair, transport::TransportError}; -use program_test::tools::{assert_anchor_err, assert_ix_err, assert_nft_voter_err}; +use program_test::tools::{assert_anchor_err, assert_ix_err, assert_squads_voter_err}; #[tokio::test] async fn test_create_registrar() -> Result<(), TransportError> { // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; + let mut squads_voter_test = SquadsVoterTest::start_new().await; - let realm_cookie = nft_voter_test.governance.with_realm().await?; + let realm_cookie = squads_voter_test.governance.with_realm().await?; // Act - let registrar_cookie = nft_voter_test.with_registrar(&realm_cookie).await?; + let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; // Assert - let registrar = nft_voter_test + let registrar = squads_voter_test .get_registrar_account(®istrar_cookie.address) .await; @@ -33,19 +33,19 @@ async fn test_create_registrar() -> Result<(), TransportError> { #[tokio::test] async fn test_create_registrar_with_invalid_realm_authority_error() -> Result<(), TransportError> { // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; + let mut squads_voter_test = SquadsVoterTest::start_new().await; - let mut realm_cookie = nft_voter_test.governance.with_realm().await?; + let mut realm_cookie = squads_voter_test.governance.with_realm().await?; realm_cookie.realm_authority = Keypair::new(); // Act - let err = nft_voter_test + let err = squads_voter_test .with_registrar(&realm_cookie) .await .err() .unwrap(); - assert_nft_voter_err(err, SquadVoterError::InvalidRealmAuthority); + assert_squads_voter_err(err, SquadsVoterError::InvalidRealmAuthority); Ok(()) } @@ -54,13 +54,13 @@ async fn test_create_registrar_with_invalid_realm_authority_error() -> Result<() async fn test_create_registrar_with_realm_authority_must_sign_error() -> Result<(), TransportError> { // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; + let mut squads_voter_test = SquadsVoterTest::start_new().await; - let mut realm_cookie = nft_voter_test.governance.with_realm().await?; + let mut realm_cookie = squads_voter_test.governance.with_realm().await?; realm_cookie.realm_authority = Keypair::new(); // Act - let err = nft_voter_test + let err = squads_voter_test .with_registrar_using_ix( &realm_cookie, |i| i.accounts[4].is_signer = false, // realm_authority @@ -79,16 +79,16 @@ async fn test_create_registrar_with_realm_authority_must_sign_error() -> Result< async fn test_create_registrar_with_invalid_spl_gov_program_id_error() -> Result<(), TransportError> { // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; + let mut squads_voter_test = SquadsVoterTest::start_new().await; - let mut realm_cookie = nft_voter_test.governance.with_realm().await?; + let mut realm_cookie = squads_voter_test.governance.with_realm().await?; realm_cookie.realm_authority = Keypair::new(); // Try to use a different program id - let governance_program_id = nft_voter_test.program_id; + let governance_program_id = squads_voter_test.program_id; // Act - let err = nft_voter_test + let err = squads_voter_test .with_registrar_using_ix( &realm_cookie, |i| i.accounts[1].pubkey = governance_program_id, //governance_program_id @@ -106,13 +106,13 @@ async fn test_create_registrar_with_invalid_spl_gov_program_id_error() -> Result #[tokio::test] async fn test_create_registrar_with_invalid_realm_error() -> Result<(), TransportError> { // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; + let mut squads_voter_test = SquadsVoterTest::start_new().await; - let mut realm_cookie = nft_voter_test.governance.with_realm().await?; + let mut realm_cookie = squads_voter_test.governance.with_realm().await?; realm_cookie.realm_authority = Keypair::new(); // Act - let err = nft_voter_test + let err = squads_voter_test .with_registrar_using_ix( &realm_cookie, |i| i.accounts[2].pubkey = Pubkey::new_unique(), // realm @@ -132,15 +132,15 @@ async fn test_create_registrar_with_invalid_realm_error() -> Result<(), Transpor async fn test_create_registrar_with_invalid_governing_token_mint_error( ) -> Result<(), TransportError> { // Arrange - let mut nft_voter_test = NftVoterTest::start_new().await; + let mut squads_voter_test = SquadsVoterTest::start_new().await; - let mut realm_cookie = nft_voter_test.governance.with_realm().await?; + let mut realm_cookie = squads_voter_test.governance.with_realm().await?; realm_cookie.realm_authority = Keypair::new(); - let mint_cookie = nft_voter_test.bench.with_mint().await?; + let mint_cookie = squads_voter_test.bench.with_mint().await?; // Act - let err = nft_voter_test + let err = squads_voter_test .with_registrar_using_ix( &realm_cookie, |i| i.accounts[3].pubkey = mint_cookie.address, // governing_token_mint diff --git a/programs/squads-voter/tests/program_test/mod.rs b/programs/squads-voter/tests/program_test/mod.rs index a930ce3d..fffcc0ee 100644 --- a/programs/squads-voter/tests/program_test/mod.rs +++ b/programs/squads-voter/tests/program_test/mod.rs @@ -1,4 +1,4 @@ pub mod governance_test; -pub mod nft_voter_test; pub mod program_test_bench; +pub mod squads_voter_test; pub mod tools; diff --git a/programs/squads-voter/tests/program_test/nft_voter_test.rs b/programs/squads-voter/tests/program_test/squads_voter_test.rs similarity index 99% rename from programs/squads-voter/tests/program_test/nft_voter_test.rs rename to programs/squads-voter/tests/program_test/squads_voter_test.rs index 895b7044..6da9fecb 100644 --- a/programs/squads-voter/tests/program_test/nft_voter_test.rs +++ b/programs/squads-voter/tests/program_test/squads_voter_test.rs @@ -59,13 +59,13 @@ pub struct SquadCookie { pub address: Pubkey, } -pub struct NftVoterTest { +pub struct SquadsVoterTest { pub program_id: Pubkey, pub bench: Arc, pub governance: GovernanceTest, } -impl NftVoterTest { +impl SquadsVoterTest { #[allow(dead_code)] pub fn add_program(program_test: &mut ProgramTest) { program_test.add_program("gpl_squads_voter", gpl_squads_voter::id(), None); @@ -75,7 +75,7 @@ impl NftVoterTest { pub async fn start_new() -> Self { let mut program_test = ProgramTest::default(); - NftVoterTest::add_program(&mut program_test); + SquadsVoterTest::add_program(&mut program_test); GovernanceTest::add_program(&mut program_test); let program_id = gpl_squads_voter::id(); diff --git a/programs/squads-voter/tests/program_test/tools.rs b/programs/squads-voter/tests/program_test/tools.rs index b63cf69c..d6c698dc 100644 --- a/programs/squads-voter/tests/program_test/tools.rs +++ b/programs/squads-voter/tests/program_test/tools.rs @@ -1,5 +1,5 @@ use anchor_lang::prelude::ERROR_CODE_OFFSET; -use gpl_squads_voter::error::SquadVoterError; +use gpl_squads_voter::error::SquadsVoterError; use solana_program::instruction::InstructionError; use solana_sdk::{signature::Keypair, transaction::TransactionError, transport::TransportError}; use spl_governance_tools::error::GovernanceToolsError; @@ -13,7 +13,10 @@ pub fn clone_keypair(source: &Keypair) -> Keypair { pub fn NopOverride(_: &mut T) {} #[allow(dead_code)] -pub fn assert_nft_voter_err(banks_client_error: TransportError, nft_locker_error: SquadVoterError) { +pub fn assert_squads_voter_err( + banks_client_error: TransportError, + nft_locker_error: SquadsVoterError, +) { let tx_error = banks_client_error.unwrap(); match tx_error { From 458aef3515e763f2ec2d46fd13a702c7956d4661 Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Sun, 22 May 2022 01:29:05 +0300 Subject: [PATCH 06/11] chore: cleanup nft/collections names --- .../tests/program_test/squads_voter_test.rs | 53 ++++++++----------- .../squads-voter/tests/program_test/tools.rs | 4 +- 2 files changed, 25 insertions(+), 32 deletions(-) diff --git a/programs/squads-voter/tests/program_test/squads_voter_test.rs b/programs/squads-voter/tests/program_test/squads_voter_test.rs index 6da9fecb..a1bfa6c9 100644 --- a/programs/squads-voter/tests/program_test/squads_voter_test.rs +++ b/programs/squads-voter/tests/program_test/squads_voter_test.rs @@ -27,7 +27,7 @@ pub struct RegistrarCookie { pub account: Registrar, pub realm_authority: Keypair, - pub max_collections: u8, + pub max_squads: u8, } pub struct VoterWeightRecordCookie { @@ -40,18 +40,17 @@ pub struct MaxVoterWeightRecordCookie { pub account: MaxVoterWeightRecord, } -pub struct CollectionConfigCookie { - pub collection_config: SquadConfig, +pub struct SquadConfigCookie { + pub squad_config: SquadConfig, } -pub struct ConfigureCollectionArgs { +pub struct ConfigureSquadArgs { pub weight: u64, - pub size: u32, } -impl Default for ConfigureCollectionArgs { +impl Default for ConfigureSquadArgs { fn default() -> Self { - Self { weight: 1, size: 3 } + Self { weight: 1 } } } @@ -159,7 +158,7 @@ impl SquadsVoterTest { address: registrar_key, account, realm_authority: realm_cookie.get_realm_authority(), - max_collections: max_squads, + max_squads, }) } @@ -313,8 +312,8 @@ impl SquadsVoterTest { let mut account_metas = anchor_lang::ToAccountMetas::to_account_metas(&accounts, None); - for nft_cookie in squads_cookies { - account_metas.push(AccountMeta::new_readonly(nft_cookie.address, false)); + for squad_cookie in squads_cookies { + account_metas.push(AccountMeta::new_readonly(squad_cookie.address, false)); } let instructions = vec![Instruction { @@ -327,31 +326,25 @@ impl SquadsVoterTest { } #[allow(dead_code)] - pub async fn with_collection( + pub async fn with_squad( &mut self, registrar_cookie: &RegistrarCookie, - nft_collection_cookie: &SquadCookie, - args: Option, - ) -> Result { - self.with_collection_using_ix( - registrar_cookie, - nft_collection_cookie, - args, - NopOverride, - None, - ) - .await + squad_cookie: &SquadCookie, + args: Option, + ) -> Result { + self.with_squad_using_ix(registrar_cookie, squad_cookie, args, NopOverride, None) + .await } #[allow(dead_code)] - pub async fn with_collection_using_ix( + pub async fn with_squad_using_ix( &mut self, registrar_cookie: &RegistrarCookie, squad_cookie: &SquadCookie, - args: Option, + args: Option, instruction_override: F, signers_override: Option<&[&Keypair]>, - ) -> Result { + ) -> Result { let args = args.unwrap_or_default(); let data = @@ -366,28 +359,28 @@ impl SquadsVoterTest { squad: squad_cookie.address, }; - let mut configure_collection_ix = Instruction { + let mut configure_squad_ix = Instruction { program_id: gpl_squads_voter::id(), accounts: anchor_lang::ToAccountMetas::to_account_metas(&accounts, None), data, }; - instruction_override(&mut configure_collection_ix); + instruction_override(&mut configure_squad_ix); let default_signers = &[®istrar_cookie.realm_authority]; let signers = signers_override.unwrap_or(default_signers); self.bench - .process_transaction(&[configure_collection_ix], Some(signers)) + .process_transaction(&[configure_squad_ix], Some(signers)) .await?; - let collection_config = SquadConfig { + let squad_config = SquadConfig { squad: squad_cookie.address, weight: args.weight, reserved: [0; 8], }; - Ok(CollectionConfigCookie { collection_config }) + Ok(SquadConfigCookie { squad_config }) } #[allow(dead_code)] diff --git a/programs/squads-voter/tests/program_test/tools.rs b/programs/squads-voter/tests/program_test/tools.rs index d6c698dc..eb1959f5 100644 --- a/programs/squads-voter/tests/program_test/tools.rs +++ b/programs/squads-voter/tests/program_test/tools.rs @@ -15,14 +15,14 @@ pub fn NopOverride(_: &mut T) {} #[allow(dead_code)] pub fn assert_squads_voter_err( banks_client_error: TransportError, - nft_locker_error: SquadsVoterError, + squad_voter_error: SquadsVoterError, ) { let tx_error = banks_client_error.unwrap(); match tx_error { TransactionError::InstructionError(_, instruction_error) => match instruction_error { InstructionError::Custom(e) => { - assert_eq!(e, nft_locker_error as u32 + ERROR_CODE_OFFSET) + assert_eq!(e, squad_voter_error as u32 + ERROR_CODE_OFFSET) } _ => panic!("{:?} Is not InstructionError::Custom()", instruction_error), }, From 3e855557d80a037e086d4ebd2d17c58055830bca Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Sun, 22 May 2022 02:06:28 +0300 Subject: [PATCH 07/11] chore: create_voter_weight_record --- .../src/instructions/configure_squad.rs | 3 +- .../squads-voter/tests/configure_squad.rs | 371 ++++++++++++++++++ .../tests/create_voter_weight_record.rs | 124 ++++++ .../squads-voter/tests/program_test/mod.rs | 1 + .../tests/program_test/squads_test.rs | 46 +++ .../tests/program_test/squads_voter_test.rs | 18 +- 6 files changed, 554 insertions(+), 9 deletions(-) create mode 100644 programs/squads-voter/tests/configure_squad.rs create mode 100644 programs/squads-voter/tests/create_voter_weight_record.rs create mode 100644 programs/squads-voter/tests/program_test/squads_test.rs diff --git a/programs/squads-voter/src/instructions/configure_squad.rs b/programs/squads-voter/src/instructions/configure_squad.rs index 94a8d7e8..aa5feab9 100644 --- a/programs/squads-voter/src/instructions/configure_squad.rs +++ b/programs/squads-voter/src/instructions/configure_squad.rs @@ -5,7 +5,6 @@ use anchor_lang::{ }; use anchor_lang::prelude::*; -use anchor_spl::token::Mint; use spl_governance::state::realm; use crate::error::SquadsVoterError; @@ -30,7 +29,7 @@ pub struct ConfigureSquad<'info> { pub realm_authority: Signer<'info>, // Squad which is going to be used for governance - pub squad: Account<'info, Mint>, + pub squad: UncheckedAccount<'info>, } pub fn configure_squad(ctx: Context, weight: u64) -> Result<()> { diff --git a/programs/squads-voter/tests/configure_squad.rs b/programs/squads-voter/tests/configure_squad.rs new file mode 100644 index 00000000..26e70f27 --- /dev/null +++ b/programs/squads-voter/tests/configure_squad.rs @@ -0,0 +1,371 @@ +use solana_program_test::*; +use solana_sdk::transport::TransportError; + +use crate::program_test::squads_voter_test::{ConfigureSquadArgs, SquadsVoterTest}; + +mod program_test; + +#[tokio::test] +async fn test_configure_squad() -> Result<(), TransportError> { + // Arrange + let mut squads_voter_test = SquadsVoterTest::start_new().await; + + let realm_cookie = squads_voter_test.governance.with_realm().await?; + + let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + + let squad_cookie = squads_voter_test.squads.with_squad().await?; + + // Act + let squad_config_cookie = squads_voter_test + .with_squad_config(®istrar_cookie, &squad_cookie, None) + .await?; + + // // Assert + let registrar = squads_voter_test + .get_registrar_account(®istrar_cookie.address) + .await; + + assert_eq!(registrar.squads_configs.len(), 1); + + assert_eq!( + registrar.squads_configs[0], + squad_config_cookie.squad_config + ); + + Ok(()) +} + +#[tokio::test] +async fn test_configure_multiple_squads() -> Result<(), TransportError> { + // Arrange + let mut squads_voter_test = SquadsVoterTest::start_new().await; + + let realm_cookie = squads_voter_test.governance.with_realm().await?; + + let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + + let squad_cookie1 = squads_voter_test.squads.with_squad().await?; + let squad_cookie2 = squads_voter_test.squads.with_squad().await?; + + // Act + squads_voter_test + .with_squad_config( + ®istrar_cookie, + &squad_cookie1, + Some(ConfigureSquadArgs { weight: 1 }), + ) + .await?; + + squads_voter_test + .with_squad_config( + ®istrar_cookie, + &squad_cookie2, + Some(ConfigureSquadArgs { weight: 2 }), + ) + .await?; + + // Assert + let registrar = squads_voter_test + .get_registrar_account(®istrar_cookie.address) + .await; + + assert_eq!(registrar.squads_configs.len(), 2); + + Ok(()) +} + +// #[tokio::test] +// async fn test_configure_max_collections() -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await?; + +// // Act + +// for _ in 0..registrar_cookie.max_collections { +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// squads_voter_test +// .with_collection( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// None, +// ) +// .await?; +// } + +// // Assert +// let registrar = squads_voter_test +// .get_registrar_account(®istrar_cookie.address) +// .await; + +// assert_eq!( +// registrar.collection_configs.len() as u8, +// registrar_cookie.max_collections +// ); + +// let max_voter_weight_record = squads_voter_test +// .get_max_voter_weight_record(&max_voter_weight_record_cookie.address) +// .await; + +// assert_eq!(max_voter_weight_record.max_voter_weight_expiry, None); +// assert_eq!(max_voter_weight_record.max_voter_weight, 30); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_configure_existing_collection() -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await?; + +// squads_voter_test +// .with_collection( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// None, +// ) +// .await?; + +// // Act + +// squads_voter_test +// .with_collection( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// Some(ConfigureCollectionArgs { +// weight: 2, +// size: 10, +// }), +// ) +// .await?; + +// // Assert +// let registrar = squads_voter_test +// .get_registrar_account(®istrar_cookie.address) +// .await; + +// assert_eq!(registrar.collection_configs.len(), 1); + +// let max_voter_weight_record = squads_voter_test +// .get_max_voter_weight_record(&max_voter_weight_record_cookie.address) +// .await; + +// assert_eq!(max_voter_weight_record.max_voter_weight_expiry, None); +// assert_eq!(max_voter_weight_record.max_voter_weight, 20); + +// Ok(()) +// } + +// // TODO: Remove collection test + +// #[tokio::test] +// async fn test_configure_collection_with_invalid_realm_error() -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await?; + +// // Try to use a different Realm +// let realm_cookie2 = squads_voter_test.governance.with_realm().await?; + +// // Act +// let err = squads_voter_test +// .with_collection_using_ix( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// None, +// |i| i.accounts[1].pubkey = realm_cookie2.address, // realm +// None, +// ) +// .await +// .err() +// .unwrap(); + +// // Assert + +// assert_nft_voter_err(err, NftVoterError::InvalidRealmForRegistrar); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_configure_collection_with_realm_authority_must_sign_error( +// ) -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await?; + +// // Act +// let err = squads_voter_test +// .with_collection_using_ix( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// None, +// |i| i.accounts[2].is_signer = false, // realm_authority +// Some(&[]), +// ) +// .await +// .err() +// .unwrap(); + +// // Assert + +// assert_anchor_err(err, anchor_lang::error::ErrorCode::AccountNotSigner); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_configure_collection_with_invalid_realm_authority_error() -> Result<(), TransportError> +// { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await?; + +// let realm_authority = Keypair::new(); + +// // Act +// let err = squads_voter_test +// .with_collection_using_ix( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// None, +// |i| i.accounts[2].pubkey = realm_authority.pubkey(), // realm_authority +// Some(&[&realm_authority]), +// ) +// .await +// .err() +// .unwrap(); + +// // Assert + +// assert_nft_voter_err(err, NftVoterError::InvalidRealmAuthority); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_configure_collection_with_invalid_max_voter_weight_realm_error( +// ) -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// let realm_cookie2 = squads_voter_test.governance.with_realm().await?; +// let registrar_cookie2 = squads_voter_test.with_registrar(&realm_cookie2).await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie2) +// .await?; + +// // Act +// let err = squads_voter_test +// .with_collection( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// None, +// ) +// .await +// .err() +// .unwrap(); + +// // Assert + +// assert_nft_voter_err(err, NftVoterError::InvalidMaxVoterWeightRecordRealm); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_configure_collection_with_invalid_max_voter_weight_mint_error( +// ) -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let mut realm_cookie = squads_voter_test.governance.with_realm().await?; +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// // Create Registrar for council mint +// realm_cookie.account.community_mint = realm_cookie.account.config.council_mint.unwrap(); +// let registrar_cookie2 = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie2) +// .await?; + +// // Act +// let err = squads_voter_test +// .with_collection( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// None, +// ) +// .await +// .err() +// .unwrap(); + +// // Assert + +// assert_nft_voter_err(err, NftVoterError::InvalidMaxVoterWeightRecordMint); + +// Ok(()) +// } diff --git a/programs/squads-voter/tests/create_voter_weight_record.rs b/programs/squads-voter/tests/create_voter_weight_record.rs new file mode 100644 index 00000000..2a398c16 --- /dev/null +++ b/programs/squads-voter/tests/create_voter_weight_record.rs @@ -0,0 +1,124 @@ +use crate::program_test::squads_voter_test::SquadsVoterTest; +use solana_program_test::*; +use solana_sdk::transport::TransportError; + +mod program_test; + +#[tokio::test] +async fn test_create_voter_weight_record() -> Result<(), TransportError> { + // Arrange + let mut squads_voter_test = SquadsVoterTest::start_new().await; + + let realm_cookie = squads_voter_test.governance.with_realm().await?; + + let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + + let voter_cookie = squads_voter_test.bench.with_wallet().await; + + // Act + let voter_weight_record_cookie = squads_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + // Assert + + let voter_weight_record = squads_voter_test + .get_voter_weight_record(&voter_weight_record_cookie.address) + .await; + + assert_eq!(voter_weight_record_cookie.account, voter_weight_record); + + Ok(()) +} + +// #[tokio::test] +// async fn test_create_voter_weight_record_with_invalid_realm_error() -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let realm_cookie2 = squads_voter_test.governance.with_realm().await?; + +// let voter_cookie = squads_voter_test.bench.with_wallet().await; + +// // Act +// let err = squads_voter_test +// .with_voter_weight_record_using_ix(®istrar_cookie, &voter_cookie, |i| { +// i.accounts[2].pubkey = realm_cookie2.address // Realm +// }) +// .await +// .err() +// .unwrap(); + +// // Assert + +// // PDA doesn't match and hence the error is PrivilegeEscalation +// assert_ix_err(err, InstructionError::PrivilegeEscalation); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_create_voter_weight_record_with_invalid_mint_error() -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let realm_cookie2 = squads_voter_test.governance.with_realm().await?; + +// let voter_cookie = squads_voter_test.bench.with_wallet().await; + +// // Act +// let err = squads_voter_test +// .with_voter_weight_record_using_ix(®istrar_cookie, &voter_cookie, |i| { +// i.accounts[2].pubkey = realm_cookie2.address // Mint +// }) +// .await +// .err() +// .unwrap(); + +// // Assert + +// // PDA doesn't match and hence the error is PrivilegeEscalation +// assert_ix_err(err, InstructionError::PrivilegeEscalation); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_create_voter_weight_record_with_already_exists_error() -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let voter_cookie = squads_voter_test.bench.with_wallet().await; + +// squads_voter_test +// .with_voter_weight_record(®istrar_cookie, &voter_cookie) +// .await?; + +// squads_voter_test.bench.advance_clock().await; + +// // Act +// let err = squads_voter_test +// .with_voter_weight_record(®istrar_cookie, &voter_cookie) +// .await +// .err() +// .unwrap(); + +// // Assert + +// // InstructionError::Custom(0) is returned for TransactionError::AccountInUse +// assert_ix_err(err, InstructionError::Custom(0)); + +// Ok(()) +// } diff --git a/programs/squads-voter/tests/program_test/mod.rs b/programs/squads-voter/tests/program_test/mod.rs index fffcc0ee..6ee8cb84 100644 --- a/programs/squads-voter/tests/program_test/mod.rs +++ b/programs/squads-voter/tests/program_test/mod.rs @@ -1,4 +1,5 @@ pub mod governance_test; pub mod program_test_bench; +pub mod squads_test; pub mod squads_voter_test; pub mod tools; diff --git a/programs/squads-voter/tests/program_test/squads_test.rs b/programs/squads-voter/tests/program_test/squads_test.rs new file mode 100644 index 00000000..0d78fc52 --- /dev/null +++ b/programs/squads-voter/tests/program_test/squads_test.rs @@ -0,0 +1,46 @@ +use crate::program_test::program_test_bench::ProgramTestBench; +use anchor_lang::prelude::Pubkey; +use solana_program_test::ProgramTest; +use solana_sdk::transport::TransportError; +use std::{str::FromStr, sync::Arc}; + +pub struct SquadCookie { + pub address: Pubkey, +} + +pub struct SquadsTest { + pub bench: Arc, + pub program_id: Pubkey, +} + +impl SquadsTest { + pub fn program_id() -> Pubkey { + Pubkey::from_str("Sqds1ufWkcv5z7K4RPXnrVTNq6Yw3zhEuajPkfhLpek").unwrap() + } + + #[allow(dead_code)] + pub fn add_program(program_test: &mut ProgramTest) { + // TODO: Add squads_protocol program to fixtures and replace it's name + program_test.add_program("spl_governance", Self::program_id(), None); + } + + #[allow(dead_code)] + pub fn new(bench: Arc) -> Self { + SquadsTest { + bench, + program_id: Self::program_id(), + } + } + + #[allow(dead_code)] + pub async fn with_squad(&mut self) -> Result { + // TODO: Create Squad + + let squad_address = Pubkey::new_unique(); + let squad_cookie = SquadCookie { + address: squad_address, + }; + + Ok(squad_cookie) + } +} diff --git a/programs/squads-voter/tests/program_test/squads_voter_test.rs b/programs/squads-voter/tests/program_test/squads_voter_test.rs index a1bfa6c9..7812d6cf 100644 --- a/programs/squads-voter/tests/program_test/squads_voter_test.rs +++ b/programs/squads-voter/tests/program_test/squads_voter_test.rs @@ -21,6 +21,10 @@ use crate::program_test::program_test_bench::WalletCookie; use crate::program_test::tools::NopOverride; +use crate::program_test::squads_test::SquadsTest; + +use super::squads_test::SquadCookie; + #[derive(Debug, PartialEq)] pub struct RegistrarCookie { pub address: Pubkey, @@ -54,14 +58,11 @@ impl Default for ConfigureSquadArgs { } } -pub struct SquadCookie { - pub address: Pubkey, -} - pub struct SquadsVoterTest { pub program_id: Pubkey, pub bench: Arc, pub governance: GovernanceTest, + pub squads: SquadsTest, } impl SquadsVoterTest { @@ -76,6 +77,7 @@ impl SquadsVoterTest { SquadsVoterTest::add_program(&mut program_test); GovernanceTest::add_program(&mut program_test); + SquadsTest::add_program(&mut program_test); let program_id = gpl_squads_voter::id(); @@ -84,11 +86,13 @@ impl SquadsVoterTest { let governance_bench = GovernanceTest::new(bench_rc.clone(), Some(program_id), Some(program_id)); + let squads_bench = SquadsTest::new(bench_rc.clone()); Self { program_id, bench: bench_rc, governance: governance_bench, + squads: squads_bench, } } @@ -326,18 +330,18 @@ impl SquadsVoterTest { } #[allow(dead_code)] - pub async fn with_squad( + pub async fn with_squad_config( &mut self, registrar_cookie: &RegistrarCookie, squad_cookie: &SquadCookie, args: Option, ) -> Result { - self.with_squad_using_ix(registrar_cookie, squad_cookie, args, NopOverride, None) + self.with_squad_config_using_ix(registrar_cookie, squad_cookie, args, NopOverride, None) .await } #[allow(dead_code)] - pub async fn with_squad_using_ix( + pub async fn with_squad_config_using_ix( &mut self, registrar_cookie: &RegistrarCookie, squad_cookie: &SquadCookie, From d53eb0e6da0fe2d3cdc90e3fc1db8f753c245e35 Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Sun, 22 May 2022 02:07:56 +0300 Subject: [PATCH 08/11] chore: test_create_max_voter_weight_record --- .../tests/create_max_voter_weight_record.rs | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 programs/squads-voter/tests/create_max_voter_weight_record.rs diff --git a/programs/squads-voter/tests/create_max_voter_weight_record.rs b/programs/squads-voter/tests/create_max_voter_weight_record.rs new file mode 100644 index 00000000..9c893ba2 --- /dev/null +++ b/programs/squads-voter/tests/create_max_voter_weight_record.rs @@ -0,0 +1,122 @@ +use crate::program_test::squads_voter_test::SquadsVoterTest; +use solana_program_test::*; +use solana_sdk::transport::TransportError; + +mod program_test; + +#[tokio::test] +async fn test_create_max_voter_weight_record() -> Result<(), TransportError> { + // Arrange + let mut squads_voter_test = SquadsVoterTest::start_new().await; + + let realm_cookie = squads_voter_test.governance.with_realm().await?; + + let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + + // Act + let max_voter_weight_record_cookie = squads_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + // Assert + + let max_voter_weight_record = squads_voter_test + .get_max_voter_weight_record(&max_voter_weight_record_cookie.address) + .await; + + assert_eq!( + max_voter_weight_record_cookie.account, + max_voter_weight_record + ); + + Ok(()) +} + +// #[tokio::test] +// async fn test_create_max_voter_weight_record_with_invalid_realm_error() -> Result<(), TransportError> +// { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let realm_cookie2 = squads_voter_test.governance.with_realm().await?; + +// // Act +// let err = squads_voter_test +// .with_max_voter_weight_record_using_ix(®istrar_cookie, |i| { +// i.accounts[2].pubkey = realm_cookie2.address // Realm +// }) +// .await +// .err() +// .unwrap(); + +// // Assert + +// // PDA doesn't match and hence the error is PrivilegeEscalation +// assert_ix_err(err, InstructionError::PrivilegeEscalation); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_create_max_voter_weight_record_with_invalid_mint_error() -> Result<(), TransportError> +// { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let realm_cookie2 = squads_voter_test.governance.with_realm().await?; + +// // Act +// let err = squads_voter_test +// .with_max_voter_weight_record_using_ix(®istrar_cookie, |i| { +// i.accounts[2].pubkey = realm_cookie2.address // Mint +// }) +// .await +// .err() +// .unwrap(); + +// // Assert + +// // PDA doesn't match and hence the error is PrivilegeEscalation +// assert_ix_err(err, InstructionError::PrivilegeEscalation); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_create_max_voter_weight_record_with_already_exists_error( +// ) -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await?; + +// squads_voter_test.bench.advance_clock().await; + +// // Act +// let err = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await +// .err() +// .unwrap(); + +// // Assert + +// // InstructionError::Custom(0) is returned for TransactionError::AccountInUse +// assert_ix_err(err, InstructionError::Custom(0)); + +// Ok(()) +// } From 72cc63a5a9306d1fe329a2c92ad8b7631b3b0e9a Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Sun, 22 May 2022 02:24:35 +0300 Subject: [PATCH 09/11] chore: test_update_voter_weight_record --- .../update_voter_weight_record.rs | 1 - .../tests/program_test/squads_test.rs | 21 + .../tests/program_test/squads_voter_test.rs | 13 +- .../tests/update_voter_weight_record.rs | 558 ++++++++++++++++++ 4 files changed, 586 insertions(+), 7 deletions(-) create mode 100644 programs/squads-voter/tests/update_voter_weight_record.rs diff --git a/programs/squads-voter/src/instructions/update_voter_weight_record.rs b/programs/squads-voter/src/instructions/update_voter_weight_record.rs index dd31f7df..23c58b37 100644 --- a/programs/squads-voter/src/instructions/update_voter_weight_record.rs +++ b/programs/squads-voter/src/instructions/update_voter_weight_record.rs @@ -6,7 +6,6 @@ use anchor_lang::prelude::*; /// This instruction updates VoterWeightRecord which is valid for the current Slot only /// The instruction must be executed inside the same transaction as the corresponding spl-gov instruction #[derive(Accounts)] -#[instruction(voter_weight_action:VoterWeightAction)] pub struct UpdateVoterWeightRecord<'info> { /// The Squads voting Registrar pub registrar: Account<'info, Registrar>, diff --git a/programs/squads-voter/tests/program_test/squads_test.rs b/programs/squads-voter/tests/program_test/squads_test.rs index 0d78fc52..25f3e53d 100644 --- a/programs/squads-voter/tests/program_test/squads_test.rs +++ b/programs/squads-voter/tests/program_test/squads_test.rs @@ -8,6 +8,11 @@ pub struct SquadCookie { pub address: Pubkey, } +pub struct SquadMemberCookie { + pub address: Pubkey, + pub squad_address: Pubkey, +} + pub struct SquadsTest { pub bench: Arc, pub program_id: Pubkey, @@ -43,4 +48,20 @@ impl SquadsTest { Ok(squad_cookie) } + + #[allow(dead_code)] + pub async fn with_squad_member( + &mut self, + squad_cookie: &SquadCookie, + ) -> Result { + // TODO: Create Squad Member + + let squad_member = Pubkey::new_unique(); + let squad_member_cookie = SquadMemberCookie { + address: squad_member, + squad_address: squad_cookie.address, + }; + + Ok(squad_member_cookie) + } } diff --git a/programs/squads-voter/tests/program_test/squads_voter_test.rs b/programs/squads-voter/tests/program_test/squads_voter_test.rs index 7812d6cf..0414a7b3 100644 --- a/programs/squads-voter/tests/program_test/squads_voter_test.rs +++ b/programs/squads-voter/tests/program_test/squads_voter_test.rs @@ -21,9 +21,7 @@ use crate::program_test::program_test_bench::WalletCookie; use crate::program_test::tools::NopOverride; -use crate::program_test::squads_test::SquadsTest; - -use super::squads_test::SquadCookie; +use crate::program_test::squads_test::{SquadCookie, SquadMemberCookie, SquadsTest}; #[derive(Debug, PartialEq)] pub struct RegistrarCookie { @@ -303,7 +301,7 @@ impl SquadsVoterTest { &self, registrar_cookie: &RegistrarCookie, voter_weight_record_cookie: &mut VoterWeightRecordCookie, - squads_cookies: &[&SquadCookie], + squads_member_cookies: &[&SquadMemberCookie], ) -> Result<(), TransportError> { let data = anchor_lang::InstructionData::data( &gpl_squads_voter::instruction::UpdateVoterWeightRecord {}, @@ -316,8 +314,11 @@ impl SquadsVoterTest { let mut account_metas = anchor_lang::ToAccountMetas::to_account_metas(&accounts, None); - for squad_cookie in squads_cookies { - account_metas.push(AccountMeta::new_readonly(squad_cookie.address, false)); + for squad_member_cookie in squads_member_cookies { + account_metas.push(AccountMeta::new_readonly( + squad_member_cookie.squad_address, + false, + )); } let instructions = vec![Instruction { diff --git a/programs/squads-voter/tests/update_voter_weight_record.rs b/programs/squads-voter/tests/update_voter_weight_record.rs new file mode 100644 index 00000000..47c490cc --- /dev/null +++ b/programs/squads-voter/tests/update_voter_weight_record.rs @@ -0,0 +1,558 @@ +use crate::program_test::squads_voter_test::{ConfigureSquadArgs, SquadsVoterTest}; +use solana_program_test::*; +use solana_sdk::transport::TransportError; + +mod program_test; + +#[tokio::test] +async fn test_update_voter_weight_record() -> Result<(), TransportError> { + // Arrange + let mut squads_voter_test = SquadsVoterTest::start_new().await; + + let realm_cookie = squads_voter_test.governance.with_realm().await?; + + let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + + let squad_cookie = squads_voter_test.squads.with_squad().await?; + + let _squad_config_cookie = squads_voter_test + .with_squad_config( + ®istrar_cookie, + &squad_cookie, + Some(ConfigureSquadArgs { weight: 10 }), + ) + .await?; + + let voter_cookie = squads_voter_test.bench.with_wallet().await; + + let mut voter_weight_record_cookie = squads_voter_test + .with_voter_weight_record(®istrar_cookie, &voter_cookie) + .await?; + + let squad_member_cookie = squads_voter_test + .squads + .with_squad_member(&squad_cookie) + .await?; + + squads_voter_test.bench.advance_clock().await; + let clock = squads_voter_test.bench.get_clock().await; + + // Act + squads_voter_test + .update_voter_weight_record( + ®istrar_cookie, + &mut voter_weight_record_cookie, + &[&squad_member_cookie], + ) + .await?; + + // Assert + + let voter_weight_record = squads_voter_test + .get_voter_weight_record(&voter_weight_record_cookie.address) + .await; + + assert_eq!(voter_weight_record.voter_weight, 10); + assert_eq!(voter_weight_record.voter_weight_expiry, Some(clock.slot)); + assert_eq!(voter_weight_record.weight_action, None); + assert_eq!(voter_weight_record.weight_action_target, None); + + Ok(()) +} + +// #[tokio::test] +// async fn test_update_voter_weight_with_multiple_nfts() -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await?; + +// let _collection_config_cookie = squads_voter_test +// .with_collection( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// Some(ConfigureCollectionArgs { +// weight: 10, +// size: 20, +// }), +// ) +// .await?; + +// let voter_cookie = squads_voter_test.bench.with_wallet().await; + +// let mut voter_weight_record_cookie = squads_voter_test +// .with_voter_weight_record(®istrar_cookie, &voter_cookie) +// .await?; + +// let nft_cookie1 = squads_voter_test +// .token_metadata +// .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) +// .await?; + +// let nft_cookie2 = squads_voter_test +// .token_metadata +// .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) +// .await?; + +// squads_voter_test.bench.advance_clock().await; +// let clock = squads_voter_test.bench.get_clock().await; + +// // Act +// squads_voter_test +// .update_voter_weight_record( +// ®istrar_cookie, +// &mut voter_weight_record_cookie, +// VoterWeightAction::CreateProposal, +// &[&nft_cookie1, &nft_cookie2], +// ) +// .await?; + +// // Assert + +// let voter_weight_record = squads_voter_test +// .get_voter_weight_record(&voter_weight_record_cookie.address) +// .await; + +// assert_eq!(voter_weight_record.voter_weight, 20); +// assert_eq!(voter_weight_record.voter_weight_expiry, Some(clock.slot)); +// assert_eq!( +// voter_weight_record.weight_action, +// Some(VoterWeightAction::CreateProposal.into()) +// ); +// assert_eq!(voter_weight_record.weight_action_target, None); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_update_voter_weight_with_cast_vote_not_allowed_error() -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await?; + +// squads_voter_test +// .with_collection( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// Some(ConfigureCollectionArgs { +// weight: 10, +// size: 20, +// }), +// ) +// .await?; + +// let voter_cookie = squads_voter_test.bench.with_wallet().await; + +// let mut voter_weight_record_cookie = squads_voter_test +// .with_voter_weight_record(®istrar_cookie, &voter_cookie) +// .await?; + +// let nft1_cookie = squads_voter_test +// .token_metadata +// .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) +// .await?; + +// // Act +// let err = squads_voter_test +// .update_voter_weight_record( +// ®istrar_cookie, +// &mut voter_weight_record_cookie, +// VoterWeightAction::CastVote, +// &[&nft1_cookie], +// ) +// .await +// .err() +// .unwrap(); + +// // Assert +// assert_nft_voter_err(err, NftVoterError::CastVoteIsNotAllowed); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_update_voter_weight_with_unverified_collection_error() -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await?; + +// squads_voter_test +// .with_collection( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// Some(ConfigureCollectionArgs { +// weight: 10, +// size: 20, +// }), +// ) +// .await?; + +// let voter_cookie = squads_voter_test.bench.with_wallet().await; + +// let mut voter_weight_record_cookie = squads_voter_test +// .with_voter_weight_record(®istrar_cookie, &voter_cookie) +// .await?; + +// // Create NFT without verified collection +// let nft1_cookie = squads_voter_test +// .token_metadata +// .with_nft_v2( +// &nft_collection_cookie, +// &voter_cookie, +// Some(CreateNftArgs { +// verify_collection: false, +// ..Default::default() +// }), +// ) +// .await?; + +// // Act +// let err = squads_voter_test +// .update_voter_weight_record( +// ®istrar_cookie, +// &mut voter_weight_record_cookie, +// VoterWeightAction::CreateGovernance, +// &[&nft1_cookie], +// ) +// .await +// .err() +// .unwrap(); + +// // Assert +// assert_nft_voter_err(err, NftVoterError::CollectionMustBeVerified); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_update_voter_weight_with_invalid_owner_error() -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await?; + +// squads_voter_test +// .with_collection( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// Some(ConfigureCollectionArgs { +// weight: 10, +// size: 20, +// }), +// ) +// .await?; + +// let voter_cookie = squads_voter_test.bench.with_wallet().await; + +// let mut voter_weight_record_cookie = squads_voter_test +// .with_voter_weight_record(®istrar_cookie, &voter_cookie) +// .await?; + +// let voter_cookie2 = squads_voter_test.bench.with_wallet().await; + +// let nft1_cookie = squads_voter_test +// .token_metadata +// .with_nft_v2(&nft_collection_cookie, &voter_cookie2, None) +// .await?; + +// // Act +// let err = squads_voter_test +// .update_voter_weight_record( +// ®istrar_cookie, +// &mut voter_weight_record_cookie, +// VoterWeightAction::CreateGovernance, +// &[&nft1_cookie], +// ) +// .await +// .err() +// .unwrap(); + +// // Assert +// assert_nft_voter_err(err, NftVoterError::VoterDoesNotOwnNft); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_update_voter_weight_with_invalid_collection_error() -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await?; + +// squads_voter_test +// .with_collection( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// Some(ConfigureCollectionArgs { +// weight: 10, +// size: 20, +// }), +// ) +// .await?; + +// let voter_cookie = squads_voter_test.bench.with_wallet().await; + +// let mut voter_weight_record_cookie = squads_voter_test +// .with_voter_weight_record(®istrar_cookie, &voter_cookie) +// .await?; + +// let nft_collection_cookie2 = squads_voter_test.token_metadata.with_nft_collection().await?; + +// let nft1_cookie = squads_voter_test +// .token_metadata +// .with_nft_v2(&nft_collection_cookie2, &voter_cookie, None) +// .await?; + +// // Act +// let err = squads_voter_test +// .update_voter_weight_record( +// ®istrar_cookie, +// &mut voter_weight_record_cookie, +// VoterWeightAction::CreateGovernance, +// &[&nft1_cookie], +// ) +// .await +// .err() +// .unwrap(); + +// // Assert +// assert_nft_voter_err(err, NftVoterError::CollectionNotFound); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_update_voter_weight_with_invalid_metadata_error() -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await?; + +// squads_voter_test +// .with_collection( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// Some(ConfigureCollectionArgs { +// weight: 10, +// size: 20, +// }), +// ) +// .await?; + +// let voter_cookie = squads_voter_test.bench.with_wallet().await; + +// let mut voter_weight_record_cookie = squads_voter_test +// .with_voter_weight_record(®istrar_cookie, &voter_cookie) +// .await?; + +// let mut nft1_cookie = squads_voter_test +// .token_metadata +// .with_nft_v2( +// &nft_collection_cookie, +// &voter_cookie, +// Some(CreateNftArgs { +// verify_collection: false, +// ..Default::default() +// }), +// ) +// .await?; + +// let nft2_cookie = squads_voter_test +// .token_metadata +// .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) +// .await?; + +// // Try to use verified NFT Metadata +// nft1_cookie.metadata = nft2_cookie.metadata; + +// // Act +// let err = squads_voter_test +// .update_voter_weight_record( +// ®istrar_cookie, +// &mut voter_weight_record_cookie, +// VoterWeightAction::CreateGovernance, +// &[&nft1_cookie], +// ) +// .await +// .err() +// .unwrap(); + +// // Assert +// assert_nft_voter_err(err, NftVoterError::TokenMetadataDoesNotMatch); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_update_voter_weight_with_same_nft_error() -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await?; + +// squads_voter_test +// .with_collection( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// None, +// ) +// .await?; + +// let voter_cookie = squads_voter_test.bench.with_wallet().await; + +// let mut voter_weight_record_cookie = squads_voter_test +// .with_voter_weight_record(®istrar_cookie, &voter_cookie) +// .await?; + +// let nft_cookie = squads_voter_test +// .token_metadata +// .with_nft_v2(&nft_collection_cookie, &voter_cookie, None) +// .await?; + +// // Act +// let err = squads_voter_test +// .update_voter_weight_record( +// ®istrar_cookie, +// &mut voter_weight_record_cookie, +// VoterWeightAction::CreateProposal, +// &[&nft_cookie, &nft_cookie], +// ) +// .await +// .err() +// .unwrap(); + +// // Assert + +// assert_nft_voter_err(err, NftVoterError::DuplicatedNftDetected); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_update_voter_weight_record_with_no_nft_error() -> Result<(), TransportError> { +// // Arrange +// let mut squads_voter_test = NftVoterTest::start_new().await; + +// let realm_cookie = squads_voter_test.governance.with_realm().await?; + +// let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + +// let nft_collection_cookie = squads_voter_test.token_metadata.with_nft_collection().await?; + +// let max_voter_weight_record_cookie = squads_voter_test +// .with_max_voter_weight_record(®istrar_cookie) +// .await?; + +// let _collection_config_cookie = squads_voter_test +// .with_collection( +// ®istrar_cookie, +// &nft_collection_cookie, +// &max_voter_weight_record_cookie, +// Some(ConfigureCollectionArgs { +// weight: 10, +// size: 20, +// }), +// ) +// .await?; + +// let voter_cookie = squads_voter_test.bench.with_wallet().await; + +// let mut voter_weight_record_cookie = squads_voter_test +// .with_voter_weight_record(®istrar_cookie, &voter_cookie) +// .await?; + +// let nft1_cookie = squads_voter_test +// .token_metadata +// .with_nft_v2( +// &nft_collection_cookie, +// &voter_cookie, +// Some(CreateNftArgs { +// amount: 0, +// ..Default::default() +// }), +// ) +// .await?; + +// // Act +// let err = squads_voter_test +// .update_voter_weight_record( +// ®istrar_cookie, +// &mut voter_weight_record_cookie, +// VoterWeightAction::CreateProposal, +// &[&nft1_cookie], +// ) +// .await +// .err() +// .unwrap(); + +// // Assert +// assert_nft_voter_err(err, NftVoterError::InvalidNftAmount); + +// Ok(()) +// } From 81675659ddf8388a51e14043964f9bac3325796b Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Sun, 22 May 2022 02:36:55 +0300 Subject: [PATCH 10/11] chore: test_update_max_voter_weight_record --- .../update_max_voter_weight_record.rs | 1 - .../tests/program_test/squads_voter_test.rs | 31 ++++++++++ .../tests/update_max_voter_weight_record.rs | 60 +++++++++++++++++++ .../tests/update_voter_weight_record.rs | 2 +- 4 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 programs/squads-voter/tests/update_max_voter_weight_record.rs diff --git a/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs b/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs index fc4f073c..58dd4eea 100644 --- a/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs +++ b/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs @@ -7,7 +7,6 @@ use anchor_lang::prelude::*; /// This instruction updates VoterWeightRecord which is valid for the current Slot only /// The instruction must be executed inside the same transaction as the corresponding spl-gov instruction #[derive(Accounts)] -#[instruction(voter_weight_action:VoterWeightAction)] pub struct UpdateMaxVoterWeightRecord<'info> { /// The Squads voting Registrar pub registrar: Account<'info, Registrar>, diff --git a/programs/squads-voter/tests/program_test/squads_voter_test.rs b/programs/squads-voter/tests/program_test/squads_voter_test.rs index 0414a7b3..9bf4b553 100644 --- a/programs/squads-voter/tests/program_test/squads_voter_test.rs +++ b/programs/squads-voter/tests/program_test/squads_voter_test.rs @@ -330,6 +330,37 @@ impl SquadsVoterTest { self.bench.process_transaction(&instructions, None).await } + #[allow(dead_code)] + pub async fn update_max_voter_weight_record( + &self, + registrar_cookie: &RegistrarCookie, + max_voter_weight_record_cookie: &mut MaxVoterWeightRecordCookie, + squads_cookies: &[&SquadCookie], + ) -> Result<(), TransportError> { + let data = anchor_lang::InstructionData::data( + &gpl_squads_voter::instruction::UpdateMaxVoterWeightRecord {}, + ); + + let accounts = gpl_squads_voter::accounts::UpdateMaxVoterWeightRecord { + registrar: registrar_cookie.address, + max_voter_weight_record: max_voter_weight_record_cookie.address, + }; + + let mut account_metas = anchor_lang::ToAccountMetas::to_account_metas(&accounts, None); + + for squad_cookie in squads_cookies { + account_metas.push(AccountMeta::new_readonly(squad_cookie.address, false)); + } + + let instructions = vec![Instruction { + program_id: gpl_squads_voter::id(), + accounts: account_metas, + data, + }]; + + self.bench.process_transaction(&instructions, None).await + } + #[allow(dead_code)] pub async fn with_squad_config( &mut self, diff --git a/programs/squads-voter/tests/update_max_voter_weight_record.rs b/programs/squads-voter/tests/update_max_voter_weight_record.rs new file mode 100644 index 00000000..f06eb5d0 --- /dev/null +++ b/programs/squads-voter/tests/update_max_voter_weight_record.rs @@ -0,0 +1,60 @@ +use crate::program_test::squads_voter_test::{ConfigureSquadArgs, SquadsVoterTest}; +use solana_program_test::*; +use solana_sdk::transport::TransportError; + +mod program_test; + +#[tokio::test] +async fn test_update_max_voter_weight_record() -> Result<(), TransportError> { + // Arrange + let mut squads_voter_test = SquadsVoterTest::start_new().await; + + let realm_cookie = squads_voter_test.governance.with_realm().await?; + + let registrar_cookie = squads_voter_test.with_registrar(&realm_cookie).await?; + + let squad_cookie = squads_voter_test.squads.with_squad().await?; + + squads_voter_test + .with_squad_config( + ®istrar_cookie, + &squad_cookie, + Some(ConfigureSquadArgs { weight: 1 }), + ) + .await?; + + let mut max_voter_weight_record_cookie = squads_voter_test + .with_max_voter_weight_record(®istrar_cookie) + .await?; + + squads_voter_test.bench.advance_clock().await; + let clock = squads_voter_test.bench.get_clock().await; + + // Act + squads_voter_test + .update_max_voter_weight_record( + ®istrar_cookie, + &mut max_voter_weight_record_cookie, + &[&squad_cookie], + ) + .await?; + + // Assert + + let max_voter_weight_record = squads_voter_test + .get_max_voter_weight_record(&max_voter_weight_record_cookie.address) + .await; + + assert_eq!(max_voter_weight_record.max_voter_weight, 10); + assert_eq!( + max_voter_weight_record.max_voter_weight_expiry, + Some(clock.slot) + ); + assert_eq!(max_voter_weight_record.realm, realm_cookie.address); + assert_eq!( + max_voter_weight_record.governing_token_mint, + realm_cookie.account.community_mint + ); + + Ok(()) +} diff --git a/programs/squads-voter/tests/update_voter_weight_record.rs b/programs/squads-voter/tests/update_voter_weight_record.rs index 47c490cc..c3e1fd36 100644 --- a/programs/squads-voter/tests/update_voter_weight_record.rs +++ b/programs/squads-voter/tests/update_voter_weight_record.rs @@ -15,7 +15,7 @@ async fn test_update_voter_weight_record() -> Result<(), TransportError> { let squad_cookie = squads_voter_test.squads.with_squad().await?; - let _squad_config_cookie = squads_voter_test + squads_voter_test .with_squad_config( ®istrar_cookie, &squad_cookie, From eab35d779d0f8caea10477bcf265498ff870c182 Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Sun, 22 May 2022 02:52:23 +0300 Subject: [PATCH 11/11] chore: Update comments --- Cargo.lock | 2 -- programs/squads-voter/Cargo.toml | 2 -- programs/squads-voter/src/instructions/configure_squad.rs | 3 +++ programs/squads-voter/src/instructions/create_registrar.rs | 4 ++-- .../src/instructions/update_max_voter_weight_record.rs | 7 +++++-- .../src/instructions/update_voter_weight_record.rs | 3 ++- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 847717e9..8e8ec7b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1084,8 +1084,6 @@ dependencies = [ "anchor-spl", "arrayref", "borsh", - "itertools", - "mpl-token-metadata", "solana-program", "solana-program-test", "solana-sdk", diff --git a/programs/squads-voter/Cargo.toml b/programs/squads-voter/Cargo.toml index e09326d3..edd39fa1 100644 --- a/programs/squads-voter/Cargo.toml +++ b/programs/squads-voter/Cargo.toml @@ -20,8 +20,6 @@ default = [] arrayref = "0.3.6" anchor-lang = { version = "0.24.2", features = ["init-if-needed"] } anchor-spl = "0.24.2" -itertools = "0.10.2" -mpl-token-metadata = { version = "1.1.0", features = ["no-entrypoint"] } solana-program = "1.9.13" spl-governance = { version = "2.2.2", features = ["no-entrypoint"] } spl-governance-tools= "0.1.2" diff --git a/programs/squads-voter/src/instructions/configure_squad.rs b/programs/squads-voter/src/instructions/configure_squad.rs index aa5feab9..40cf1de1 100644 --- a/programs/squads-voter/src/instructions/configure_squad.rs +++ b/programs/squads-voter/src/instructions/configure_squad.rs @@ -29,6 +29,7 @@ pub struct ConfigureSquad<'info> { pub realm_authority: Signer<'info>, // Squad which is going to be used for governance + /// CHECK: Owned by squads-protocol pub squad: UncheckedAccount<'info>, } @@ -48,6 +49,8 @@ pub fn configure_squad(ctx: Context, weight: u64) -> Result<()> let squad = &ctx.accounts.squad; + // TODO: Assert Squad owned by squads-protocol + let squad_config = SquadConfig { squad: squad.key(), weight, diff --git a/programs/squads-voter/src/instructions/create_registrar.rs b/programs/squads-voter/src/instructions/create_registrar.rs index dfef89cd..896673e1 100644 --- a/programs/squads-voter/src/instructions/create_registrar.rs +++ b/programs/squads-voter/src/instructions/create_registrar.rs @@ -53,10 +53,10 @@ pub struct CreateRegistrar<'info> { /// Creates a new Registrar which stores Squads voting configuration for given Realm /// -/// To use the registrar, call ConfigureSquad to register Squad that may be +/// To use the registrar, call ConfigureSquad to register Squads which will be /// used for governance /// -/// max_squads is used allocate account size for the maximum number of governing Squads +/// max_squads is used to allocate account size for the maximum number of governing Squads /// Note: Once Solana runtime supports account resizing the max value won't be required pub fn create_registrar(ctx: Context, _max_squads: u8) -> Result<()> { let registrar = &mut ctx.accounts.registrar; diff --git a/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs b/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs index 58dd4eea..4d79d9c0 100644 --- a/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs +++ b/programs/squads-voter/src/instructions/update_max_voter_weight_record.rs @@ -3,8 +3,8 @@ use crate::state::max_voter_weight_record::MaxVoterWeightRecord; use crate::state::*; use anchor_lang::prelude::*; -/// Updates MaxVoterWeightRecord to evaluate governance power for users and the Squads they belong to -/// This instruction updates VoterWeightRecord which is valid for the current Slot only +/// Updates MaxVoterWeightRecord to evaluate max governance power for the configured Squads +/// This instruction updates MaxVoterWeightRecord which is valid for the current Slot only /// The instruction must be executed inside the same transaction as the corresponding spl-gov instruction #[derive(Accounts)] pub struct UpdateMaxVoterWeightRecord<'info> { @@ -20,6 +20,8 @@ pub struct UpdateMaxVoterWeightRecord<'info> { @ SquadsVoterError::InvalidVoterWeightRecordMint, )] pub max_voter_weight_record: Account<'info, MaxVoterWeightRecord>, + // + // Remaining Accounts: Squads } pub fn update_max_voter_weight_record(ctx: Context) -> Result<()> { @@ -34,6 +36,7 @@ pub fn update_max_voter_weight_record(ctx: Context) .find(|ai| ai.key() == squad_config.squad) .unwrap(); + // TODO: Assert squad_info is owned by squads-protocol program // TODO: Get the Squad size from squad_info let squad_size = 10; diff --git a/programs/squads-voter/src/instructions/update_voter_weight_record.rs b/programs/squads-voter/src/instructions/update_voter_weight_record.rs index 23c58b37..671500f5 100644 --- a/programs/squads-voter/src/instructions/update_voter_weight_record.rs +++ b/programs/squads-voter/src/instructions/update_voter_weight_record.rs @@ -20,7 +20,7 @@ pub struct UpdateVoterWeightRecord<'info> { )] pub voter_weight_record: Account<'info, VoterWeightRecord>, // - // Remaining Accounts: Squads + // Remaining Accounts: Squads Membership } pub fn update_voter_weight_record(ctx: Context) -> Result<()> { @@ -38,6 +38,7 @@ pub fn update_voter_weight_record(ctx: Context) -> Resu } unique_squads.push(squad_info.key); + // TODO: Assert squad_info is owned by squads-protocol program // TODO: Validate Squad membership for governing_token_owner and squad_info let squad_config = registrar.get_squad_config(squad_info.key)?;