multisig 0.1.0

Created with Anchor
Documentation
#![cfg(feature = "test-helpers")]

use litesvm::LiteSVM;
use multisig::{
    instructions::{
        AddGroupMemberInstructionArgs, CreateConfigProposalInstructionArgs,
        VoteOnConfigProposalInstructionArgs,
    },
    ConfigChange, ConfigType, Permissions, VoteChoice,
};
use multisig_sdk as sdk;
use solana_sdk::{pubkey::Pubkey, signer::Signer};

mod common;
use common::{add_multisig_program, read_group, send_tx, setup_asset_mint, setup_group, threshold};

fn create_config_proposal(
    svm: &mut LiteSVM,
    group_setup: &common::GroupSetup,
    config_change: ConfigChange,
    asset_address: Option<Pubkey>,
) -> Pubkey {
    let proposal_seed = Pubkey::new_unique();
    let proposal = sdk::proposal_pda(&group_setup.group, &proposal_seed).address;
    let create_args = CreateConfigProposalInstructionArgs {
        proposal_seed,
        timelock_offset: 0,
        proposal_deadline_timestamp: 1000,
        config_change,
    };
    let create_ix = sdk::create_config_proposal(
        create_args,
        group_setup.group,
        group_setup.payer.pubkey(),
        asset_address,
    );
    send_tx(svm, &group_setup.payer, vec![create_ix], &[]).expect("create config proposal");
    proposal
}

fn vote_config_group(
    svm: &mut LiteSVM,
    group_setup: &common::GroupSetup,
    proposal: Pubkey,
    vote: VoteChoice,
) {
    let payer_vote = sdk::vote_on_config_proposal(
        VoteOnConfigProposalInstructionArgs { vote },
        group_setup.group,
        proposal,
        group_setup.payer.pubkey(),
        None,
    );
    let member_vote = sdk::vote_on_config_proposal(
        VoteOnConfigProposalInstructionArgs { vote },
        group_setup.group,
        proposal,
        group_setup.members[0].pubkey(),
        None,
    );
    send_tx(
        svm,
        &group_setup.payer,
        vec![payer_vote, member_vote],
        &[&group_setup.members[0]],
    )
    .expect("vote group config proposal");
}

fn vote_config_asset(
    svm: &mut LiteSVM,
    group_setup: &common::GroupSetup,
    proposal: Pubkey,
    asset_address: Pubkey,
    vote: VoteChoice,
) {
    let payer_vote = sdk::vote_on_config_proposal(
        VoteOnConfigProposalInstructionArgs { vote },
        group_setup.group,
        proposal,
        group_setup.payer.pubkey(),
        Some(asset_address),
    );
    let member_vote = sdk::vote_on_config_proposal(
        VoteOnConfigProposalInstructionArgs { vote },
        group_setup.group,
        proposal,
        group_setup.members[0].pubkey(),
        Some(asset_address),
    );
    send_tx(
        svm,
        &group_setup.payer,
        vec![payer_vote, member_vote],
        &[&group_setup.members[0]],
    )
    .expect("vote asset config proposal");
}

fn execute_add_group_member(
    svm: &mut LiteSVM,
    group_setup: &common::GroupSetup,
    proposal: Pubkey,
    new_member: Pubkey,
) {
    let ix = sdk::add_group_member(
        AddGroupMemberInstructionArgs { new_member },
        group_setup.group,
        proposal,
        group_setup.payer.pubkey(),
        group_setup.payer.pubkey(),
    );
    send_tx(svm, &group_setup.payer, vec![ix], &[]).expect("execute add group member");
}

fn execute_change_group_config(
    svm: &mut LiteSVM,
    group_setup: &common::GroupSetup,
    proposal: Pubkey,
) {
    let ix = sdk::change_group_config(group_setup.group, proposal, group_setup.payer.pubkey());
    send_tx(svm, &group_setup.payer, vec![ix], &[]).expect("execute group config change");
}

fn execute_change_asset_config(
    svm: &mut LiteSVM,
    group_setup: &common::GroupSetup,
    asset_address: Pubkey,
    proposal: Pubkey,
) {
    let ix = sdk::change_asset_config(
        group_setup.group,
        asset_address,
        proposal,
        group_setup.payer.pubkey(),
    );
    send_tx(svm, &group_setup.payer, vec![ix], &[]).expect("execute asset config change");
}

fn close_config(svm: &mut LiteSVM, group_setup: &common::GroupSetup, proposal: Pubkey) {
    let close = sdk::close_config_proposal(group_setup.group, proposal, group_setup.payer.pubkey());
    send_tx(svm, &group_setup.payer, vec![close], &[]).expect("close config proposal");
}

#[test]
fn test_config_cycle_add_group_member() {
    let mut svm = LiteSVM::new();
    add_multisig_program(&mut svm).expect("program load");

    let group_setup = setup_group(&mut svm).expect("group setup");
    let before = read_group(&svm, group_setup.group).expect("read group before");
    let new_member = solana_sdk::signature::Keypair::new();

    let proposal = create_config_proposal(
        &mut svm,
        &group_setup,
        ConfigChange::AddGroupMember {
            member: new_member.pubkey(),
            weight: 1,
            permissions: Permissions::from_flags(true, true),
        },
        None,
    );
    vote_config_group(&mut svm, &group_setup, proposal, VoteChoice::For);
    execute_add_group_member(&mut svm, &group_setup, proposal, new_member.pubkey());

    let after = read_group(&svm, group_setup.group).expect("read group after");
    assert_eq!(after.member_count, before.member_count + 1);
    assert_eq!(after.proposal_index_after_stale, after.next_proposal_index);
}

#[test]
fn test_config_cycle_three_group_config_changes() {
    let mut svm = LiteSVM::new();
    add_multisig_program(&mut svm).expect("program load");

    let group_setup = setup_group(&mut svm).expect("group setup");
    let changes = [
        ConfigType::MinimumVoteCount(2),
        ConfigType::MinimumMemberCount(2),
        ConfigType::ChangeConfig(threshold(1, 2)),
    ];

    for config_type in changes {
        let proposal = create_config_proposal(
            &mut svm,
            &group_setup,
            ConfigChange::ChangeGroupConfig { config_type },
            None,
        );
        vote_config_group(&mut svm, &group_setup, proposal, VoteChoice::For);
        execute_change_group_config(&mut svm, &group_setup, proposal);
    }

    let group = read_group(&svm, group_setup.group).expect("read group");
    assert_eq!(group.proposal_index_after_stale, group.next_proposal_index);
}

#[test]
fn test_config_cycle_three_asset_config_changes() {
    let mut svm = LiteSVM::new();
    add_multisig_program(&mut svm).expect("program load");

    let group_setup = setup_group(&mut svm).expect("group setup");
    let asset_setup = setup_asset_mint(&mut svm, &group_setup).expect("asset setup");
    let changes = [
        ConfigType::Use(threshold(1, 2)),
        ConfigType::AddMember(threshold(1, 2)),
        ConfigType::RemoveMember(threshold(1, 2)),
    ];

    for config_type in changes {
        let proposal = create_config_proposal(
            &mut svm,
            &group_setup,
            ConfigChange::ChangeAssetConfig { config_type },
            Some(asset_setup.asset_address),
        );
        vote_config_asset(
            &mut svm,
            &group_setup,
            proposal,
            asset_setup.asset_address,
            VoteChoice::For,
        );
        execute_change_asset_config(&mut svm, &group_setup, asset_setup.asset_address, proposal);
    }

    let group = read_group(&svm, group_setup.group).expect("read group");
    assert_eq!(group.proposal_index_after_stale, group.next_proposal_index);
}

#[test]
fn test_config_declined_proposal_can_be_closed() {
    let mut svm = LiteSVM::new();
    add_multisig_program(&mut svm).expect("program load");

    let group_setup = setup_group(&mut svm).expect("group setup");
    let new_member = solana_sdk::signature::Keypair::new();
    let proposal = create_config_proposal(
        &mut svm,
        &group_setup,
        ConfigChange::AddGroupMember {
            member: new_member.pubkey(),
            weight: 1,
            permissions: Permissions::from_flags(true, true),
        },
        None,
    );

    vote_config_group(&mut svm, &group_setup, proposal, VoteChoice::Against);
    close_config(&mut svm, &group_setup, proposal);
}