Skip to content

Commit

Permalink
Merge branch 'arshavir/NNS1-3154-2' into 'master'
Browse files Browse the repository at this point in the history
feat(nns): Limit to `5_000` #swap participants from the Neurons' Fund

This MR limits the maximum number of swap participants from the Neurons' Fund to `5_000`.

< [Previous MR in this series](https://gitlab.com/dfinity-lab/public/ic/-/merge_requests/20216) | 

See merge request dfinity-lab/public/ic!20141
  • Loading branch information
aterga committed Jul 3, 2024
2 parents 844ab79 + 9e05300 commit e4eeb33
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 3 deletions.
33 changes: 30 additions & 3 deletions rs/nns/governance/src/governance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,18 @@ pub const KNOWN_NEURON_NAME_MAX_LEN: usize = 200;
/// Max character length for the field "description" in KnownNeuronData.
pub const KNOWN_NEURON_DESCRIPTION_MAX_LEN: usize = 3000;

// The number of seconds between automated Node Provider reward events
// Currently 1/12 of a year: 2629800 = 86400 * 365.25 / 12
/// The number of seconds between automated Node Provider reward events
/// Currently 1/12 of a year: 2629800 = 86400 * 365.25 / 12
const NODE_PROVIDER_REWARD_PERIOD_SECONDS: u64 = 2629800;

const VALID_MATURITY_MODULATION_BASIS_POINTS_RANGE: RangeInclusive<i32> = -500..=500;

/// Maximum allowed number of Neurons' Fund participants that may participate in an SNS swap.
/// Given the maximum number of SNS neurons per swap participant (a.k.a. neuron basket count),
/// this constant can be used to obtain an upper bound for the number of SNS neurons created
/// for the Neurons' Fund participants. See also `MAX_SNS_NEURONS_PER_BASKET`.
const MAX_NEURONS_FUND_PARTICIPANTS: u64 = 5_000;

impl NetworkEconomics {
/// The multiplier applied to minimum_icp_xdr_rate to convert the XDR unit to basis_points
pub const ICP_XDR_RATE_TO_BASIS_POINT_MULTIPLIER: u64 = 100;
Expand Down Expand Up @@ -7225,6 +7231,24 @@ impl Governance {
swap_participation_limits,
neurons_fund,
)?;
// Check that the maximum number of Neurons' Fund participants is not too high. Otherwise,
// the SNS may be unable to distribute SNS tokens to all participants after the swap.
{
let maximum_neurons_fund_participants = initial_neurons_fund_participation
.snapshot()
.neurons()
.len() as u64;
if maximum_neurons_fund_participants > MAX_NEURONS_FUND_PARTICIPANTS {
return Err(GovernanceError::new_with_message(
ErrorType::InvalidProposal,
format!(
"The maximum number of Neurons' Fund participants ({}) must not exceed \
MAX_NEURONS_FUND_PARTICIPANTS ({}).",
maximum_neurons_fund_participants, MAX_NEURONS_FUND_PARTICIPANTS,
),
));
};
}
let constraints = initial_neurons_fund_participation.compute_constraints()?;
let initial_neurons_fund_participation_snapshot =
initial_neurons_fund_participation.snapshot_cloned();
Expand All @@ -7234,7 +7258,10 @@ impl Governance {
if self.get_proposal_data(*proposal_id).is_none() {
return Err(GovernanceError::new_with_message(
ErrorType::InvalidProposal,
"Expected ProposalData.neurons_fund_data to be unset.",
format!(
"ProposalData must be present for proposal {:?}.",
proposal_id
),
));
}
self.neuron_store
Expand Down
1 change: 1 addition & 0 deletions rs/nns/governance/src/governance/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use lazy_static::lazy_static;
use maplit::{btreemap, hashmap};
use std::convert::TryFrom;

mod neurons_fund;
mod stake_maturity;

#[test]
Expand Down
156 changes: 156 additions & 0 deletions rs/nns/governance/src/governance/tests/neurons_fund.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use super::*;
use crate::init::GovernanceCanisterInitPayloadBuilder;
use crate::pb::v1::create_service_nervous_system::SwapParameters;
use crate::test_utils::{MockEnvironment, StubCMC, StubIcpLedger};
use assert_matches::assert_matches;
use ic_nervous_system_common::E8;
use ic_nervous_system_proto::pb::v1 as pb;
use maplit::btreemap;
use test_data::CREATE_SERVICE_NERVOUS_SYSTEM_WITH_MATCHED_FUNDING;

#[test]
fn proposal_passes_if_not_too_many_nf_neurons_can_occur() {
let proposal_id = ProposalId { id: 123 };
let create_service_nervous_system = CREATE_SERVICE_NERVOUS_SYSTEM_WITH_MATCHED_FUNDING.clone();
let mut governance_proto = GovernanceCanisterInitPayloadBuilder::new()
.with_test_neurons_fund_neurons(500_000 * E8)
.build();
governance_proto.proposals = btreemap! {
123 => ProposalData {
id: Some(proposal_id),
..ProposalData::default()
}
};
let mut governance = Governance::new(
governance_proto,
Box::<MockEnvironment>::default(),
Box::new(StubIcpLedger {}),
Box::new(StubCMC {}),
);
// Run code under test
governance
.draw_maturity_from_neurons_fund(&proposal_id, &create_service_nervous_system)
.unwrap();
}

#[test]
fn proposal_fails_if_too_many_nf_neurons_can_occur() {
let num_neurons_fund_neurons = 5_001;
let maturity_equivalent_icp_e8s = u64::MAX / num_neurons_fund_neurons;
let proposal_id = ProposalId { id: 123 };
let create_service_nervous_system = {
let create_service_nervous_system =
CREATE_SERVICE_NERVOUS_SYSTEM_WITH_MATCHED_FUNDING.clone();
let swap_parameters = SwapParameters {
// This avoids all neurons having less than `minimum_participant_icp` in terms of
// their proportional maturity amounts eligible for SNS swap participation.
minimum_participant_icp: Some(pb::Tokens {
e8s: Some(E8 / 100),
}),
..create_service_nervous_system
.swap_parameters
.clone()
.unwrap()
};
CreateServiceNervousSystem {
swap_parameters: Some(swap_parameters),
..create_service_nervous_system
}
};
let mut governance_proto = {
let proto_neuron = GovernanceCanisterInitPayloadBuilder::new()
.with_test_neurons_fund_neurons(maturity_equivalent_icp_e8s)
.build()
.neurons
.into_iter()
.find_map(|(_, neuron)| {
if neuron.joined_community_fund_timestamp_seconds.unwrap() > 0 {
Some(neuron)
} else {
None
}
})
.unwrap();
let neurons = (0..num_neurons_fund_neurons)
.map(|id| NeuronProto {
id: Some(NeuronId { id }),
..proto_neuron.clone()
})
.collect();
GovernanceCanisterInitPayloadBuilder::new()
.with_additional_neurons(neurons)
.build()
};
governance_proto.proposals = btreemap! {
123 => ProposalData {
id: Some(proposal_id),
..ProposalData::default()
}
};
let mut governance = Governance::new(
governance_proto,
Box::<MockEnvironment>::default(),
Box::new(StubIcpLedger {}),
Box::new(StubCMC {}),
);
// Run code under test
let err = governance
.draw_maturity_from_neurons_fund(&proposal_id, &create_service_nervous_system)
.unwrap_err();

let expected_error_sub_message = format!(
"The maximum number of Neurons' Fund participants ({}) \
must not exceed MAX_NEURONS_FUND_PARTICIPANTS ({}).",
num_neurons_fund_neurons, MAX_NEURONS_FUND_PARTICIPANTS,
);
assert_matches!(err, GovernanceError {
error_type,
error_message,
} => {
assert_eq!(ErrorType::try_from(error_type).unwrap(), ErrorType::InvalidProposal);
assert!(
error_message.contains(&expected_error_sub_message),
"Observed error:\n{}\ndoes not contain expected substring `{}`.",
error_message,
expected_error_sub_message
);
});
}

#[test]
fn proposal_fails_if_no_nf_neurons_exist() {
let proposal_id = ProposalId { id: 123 };
let create_service_nervous_system = CREATE_SERVICE_NERVOUS_SYSTEM_WITH_MATCHED_FUNDING.clone();
let mut governance_proto = GovernanceCanisterInitPayloadBuilder::new().build();
governance_proto.proposals = btreemap! {
123 => ProposalData {
id: Some(proposal_id),
..ProposalData::default()
}
};
let mut governance = Governance::new(
governance_proto,
Box::<MockEnvironment>::default(),
Box::new(StubIcpLedger {}),
Box::new(StubCMC {}),
);
// Run code under test
let err = governance
.draw_maturity_from_neurons_fund(&proposal_id, &create_service_nervous_system)
.unwrap_err();

let expected_error_sub_message = "Cannot compute Neurons' Fund participation \
intervals, as total_maturity_equivalent_icp_e8s = 0";
assert_matches!(err, GovernanceError {
error_type,
error_message,
} => {
assert_eq!(ErrorType::try_from(error_type).unwrap(), ErrorType::InvalidProposal);
assert!(
error_message.contains(expected_error_sub_message),
"Observed error:\n{}\ndoes not contain expected substring `{}`.",
error_message,
expected_error_sub_message
);
});
}

0 comments on commit e4eeb33

Please sign in to comment.