ic-query 0.90.0

Internet Computer query library for CloudEngine, NNS, SNS, ICRC, system canisters, and public network metadata
Documentation
use super::*;
use crate::sns::report::{
    SnsNeuronId, SnsPolicyObservationStatus,
    live::types::{SnsMetricsAccount, SnsMetricsSubaccount, SnsTopic},
};
use ic_platform::candid::contract::Principal;

#[test]
fn sns_neuron_conversion_preserves_fixed_size_native_fields() {
    let row = sns_neuron_row(neuron(SnsGovernanceDissolveState::DissolveDelaySeconds(
        86_400,
    )))
    .expect("convert neuron");

    assert_eq!(row.neuron_id, "01".repeat(32));
    assert_eq!(row.cached_neuron_stake_e8s, 100_000_000);
    assert_eq!(row.maturity_e8s_equivalent, 10_000_000);
    assert_eq!(row.staked_maturity_e8s_equivalent, Some(5_000_000));
    assert_eq!(row.created_timestamp_seconds, 1_700_000_000);
    assert_eq!(row.created_timestamp_seconds, 1_700_000_000);
    assert_eq!(row.source_nns_neuron_id, Some(42));
    assert_eq!(row.auto_stake_maturity, Some(true));
    assert_eq!(row.aging_since_timestamp_seconds, 1_700_000_100);
    assert_eq!(
        row.dissolve_state,
        Some(SnsNeuronDissolveState::DissolveDelaySeconds(86_400))
    );
    assert_eq!(row.voting_power_percentage_multiplier, 100);
    assert_eq!(row.vesting_period_seconds, Some(31_536_000));
    assert_eq!(row.neuron_fees_e8s, 10_000);
}

#[test]
fn sns_neuron_conversion_preserves_dissolved_timestamp_alternative() {
    let row = sns_neuron_row(neuron(
        SnsGovernanceDissolveState::WhenDissolvedTimestampSeconds(1_800_000_000),
    ))
    .expect("convert neuron");

    assert_eq!(
        row.dissolve_state,
        Some(SnsNeuronDissolveState::WhenDissolvedTimestampSeconds(
            1_800_000_000
        ))
    );
}

#[test]
fn exact_neuron_conversion_preserves_variable_native_evidence() {
    let detail = mainnet_sns_neuron(detail_neuron()).expect("convert detail");

    assert_eq!(
        detail.neuron,
        sns_neuron_row(neuron(SnsGovernanceDissolveState::DissolveDelaySeconds(
            86_400
        )))
        .expect("list and detail retain the same native fields")
    );
    assert_eq!(detail.neuron.neuron_id, "01".repeat(32));
    assert_eq!(
        detail.permissions[0].principal.as_deref(),
        Some("2vxsx-fae")
    );
    assert_eq!(detail.permissions[0].permission_types[0], 9);
    assert_eq!(
        crate::sns::sns_neuron_permission_name(detail.permissions[0].permission_types[1]),
        "unknown"
    );
    let disbursement = &detail.disburse_maturity_in_progress[0];
    let account = disbursement
        .account_to_disburse_to
        .as_ref()
        .expect("destination account");
    assert_eq!(account.owner.as_deref(), Some("2vxsx-fae"));
    assert_eq!(account.subaccount_hex.as_deref(), Some(&*"ab".repeat(32)));
    assert_eq!(detail.followees[0].followee_neuron_ids[0], "02".repeat(32));
    let topic = &detail.topic_followees.as_ref().expect("topics")[0];
    assert_eq!(topic.topic.as_deref(), Some("governance"));
    assert_eq!(topic.followees[0].alias.as_deref(), Some("lead"));
    assert_eq!(
        detail.derived_policy_observations().0,
        SnsPolicyObservationStatus::Violated
    );
    assert_eq!(
        detail.derived_policy_observations().1,
        SnsPolicyObservationStatus::Violated
    );
}

#[test]
fn neuron_projections_reject_missing_or_empty_ids_before_other_evidence() {
    for id in [None, Some(SnsNeuronId { id: Vec::new() })] {
        let missing = id.is_none();
        let mut list = neuron(SnsGovernanceDissolveState::DissolveDelaySeconds(0));
        list.id = id.clone();
        let mut detail = detail_neuron();
        detail.id = id.clone();
        detail.permissions[0].principal = None;
        for result in [
            sns_neuron_row(list).map(|_| ()),
            mainnet_sns_neuron(detail).map(|_| ()),
        ] {
            let error = result.expect_err("reject invalid neuron identity first");
            if missing {
                assert!(matches!(error, SnsHostError::MissingNeuronId));
            } else {
                assert!(matches!(error, SnsHostError::InvalidNeuronId));
            }
        }
    }
}

#[test]
fn exact_neuron_conversion_rejects_missing_permission_principal() {
    let mut neuron = detail_neuron();
    neuron.permissions[0].principal = None;

    assert!(matches!(
        mainnet_sns_neuron(neuron),
        Err(SnsHostError::MissingNeuronPermissionPrincipal {
            neuron_id,
            permission_index: 0,
        }) if neuron_id == "01".repeat(32)
    ));
}

fn neuron(dissolve_state: SnsGovernanceDissolveState) -> SnsGovernanceNeuron {
    SnsGovernanceNeuron {
        id: Some(SnsNeuronId { id: vec![1; 32] }),
        staked_maturity_e8s_equivalent: Some(5_000_000),
        maturity_e8s_equivalent: 10_000_000,
        cached_neuron_stake_e8s: 100_000_000,
        created_timestamp_seconds: 1_700_000_000,
        source_nns_neuron_id: Some(42),
        auto_stake_maturity: Some(true),
        aging_since_timestamp_seconds: 1_700_000_100,
        dissolve_state: Some(dissolve_state),
        voting_power_percentage_multiplier: 100,
        vesting_period_seconds: Some(31_536_000),
        neuron_fees_e8s: 10_000,
    }
}

fn detail_neuron() -> SnsGovernanceNeuronDetail {
    SnsGovernanceNeuronDetail {
        id: Some(SnsNeuronId { id: vec![1; 32] }),
        staked_maturity_e8s_equivalent: Some(5_000_000),
        permissions: vec![SnsGovernanceNeuronPermission {
            principal: Some(Principal::anonymous()),
            permission_type: vec![9, 11],
        }],
        maturity_e8s_equivalent: 10_000_000,
        cached_neuron_stake_e8s: 100_000_000,
        created_timestamp_seconds: 1_700_000_000,
        source_nns_neuron_id: Some(42),
        auto_stake_maturity: Some(true),
        aging_since_timestamp_seconds: 1_700_000_100,
        dissolve_state: Some(SnsGovernanceDissolveState::DissolveDelaySeconds(86_400)),
        voting_power_percentage_multiplier: 100,
        vesting_period_seconds: Some(31_536_000),
        disburse_maturity_in_progress: vec![SnsGovernanceMaturityDisbursement {
            timestamp_of_disbursement_seconds: 1_700_000_200,
            amount_e8s: 1_000,
            account_to_disburse_to: Some(SnsMetricsAccount {
                owner: Some(Principal::anonymous()),
                subaccount: Some(SnsMetricsSubaccount {
                    subaccount: vec![0xab; 32],
                }),
            }),
            finalize_disbursement_timestamp_seconds: Some(1_700_086_600),
        }],
        followees: vec![(
            1,
            SnsGovernanceFollowees {
                followees: vec![SnsNeuronId { id: vec![2; 32] }],
            },
        )],
        topic_followees: Some(SnsGovernanceTopicFollowees {
            topic_id_to_followees: vec![(
                5,
                SnsGovernanceFolloweesForTopic {
                    followees: vec![SnsGovernanceFollowee {
                        neuron_id: Some(SnsNeuronId { id: vec![3; 32] }),
                        alias: Some("lead".to_string()),
                    }],
                    topic: Some(SnsTopic::Governance),
                },
            )],
        }),
        neuron_fees_e8s: 10_000,
    }
}