mod live;
use super::{
MAINNET_GOVERNANCE_CANISTER_ID, NNS_PROPOSAL_FETCHED_BY, NnsProposalHostError,
assemble::{
NnsProposalListReportParts, NnsProposalReportParts, NnsProposalReportProvenance,
nns_proposal_list_report_from_parts, nns_proposal_report_from_parts,
},
enforce_mainnet_network,
model::{
NnsProposalBallotRow, NnsProposalListReport, NnsProposalListRequest, NnsProposalReport,
NnsProposalRequest, NnsProposalRewardStatus, NnsProposalRewardStatusFilter, NnsProposalRow,
NnsProposalStatus, NnsProposalStatusFilter, NnsProposalTally, NnsProposalTopic,
NnsProposalVote,
},
view::{
proposal_matches_proposer, proposal_matches_query, proposal_matches_topic,
sort_nns_proposal_rows,
},
wire::{NnsGovernanceBallot, NnsProposalInfo},
};
use crate::{
nns::{LiveNnsSource, NnsSourceRequest},
subnet_catalog::{MAINNET_NETWORK, format_utc_timestamp_secs},
};
pub fn build_nns_proposal_list_report(
request: &NnsProposalListRequest,
) -> Result<NnsProposalListReport, NnsProposalHostError> {
build_nns_proposal_list_report_with_source(request, &LiveNnsSource)
}
pub fn build_nns_proposal_report(
request: &NnsProposalRequest,
) -> Result<NnsProposalReport, NnsProposalHostError> {
build_nns_proposal_report_with_source(request, &LiveNnsSource)
}
pub fn build_nns_proposal_list_report_with_source(
request: &NnsProposalListRequest,
source: &dyn NnsProposalSource,
) -> Result<NnsProposalListReport, NnsProposalHostError> {
enforce_mainnet_network(&request.network)?;
let fetched_at = format_utc_timestamp_secs(request.now_unix_secs);
let fetch_request = NnsSourceRequest::new(
MAINNET_NETWORK,
&request.source_endpoint,
&fetched_at,
NNS_PROPOSAL_FETCHED_BY,
);
let mut proposals = source
.fetch_proposals(
&fetch_request,
request.limit,
request.before_proposal_id,
request.status,
request.reward_status,
)?
.into_iter()
.filter(|proposal| proposal_matches_proposer(proposal, request.proposer_neuron_id))
.filter(|proposal| proposal_matches_topic(proposal, request.topic))
.filter(|proposal| proposal_matches_query(proposal, request.query.as_deref()))
.collect::<Vec<_>>();
sort_nns_proposal_rows(&mut proposals, request.sort, request.sort_direction);
Ok(nns_proposal_list_report_from_parts(
NnsProposalListReportParts {
network: MAINNET_NETWORK.to_string(),
governance_canister_id: MAINNET_GOVERNANCE_CANISTER_ID.to_string(),
fetched_at,
source_endpoint: request.source_endpoint.clone(),
fetched_by: NNS_PROPOSAL_FETCHED_BY.to_string(),
provenance: NnsProposalReportProvenance::live(),
requested_limit: request.limit,
before_proposal_id: request.before_proposal_id,
status: request.status,
reward_status: request.reward_status,
topic: request.topic,
proposer_neuron_id: request.proposer_neuron_id,
query: request.query.clone(),
sort: request.sort,
sort_direction: request.sort_direction,
verbose: request.verbose,
proposals,
},
))
}
pub fn build_nns_proposal_report_with_source(
request: &NnsProposalRequest,
source: &dyn NnsProposalSource,
) -> Result<NnsProposalReport, NnsProposalHostError> {
enforce_mainnet_network(&request.network)?;
let fetched_at = format_utc_timestamp_secs(request.now_unix_secs);
let fetch_request = NnsSourceRequest::new(
MAINNET_NETWORK,
&request.source_endpoint,
&fetched_at,
NNS_PROPOSAL_FETCHED_BY,
);
let proposal = source.fetch_proposal(&fetch_request, request.proposal_id)?;
Ok(nns_proposal_report_from_parts(NnsProposalReportParts {
network: MAINNET_NETWORK.to_string(),
governance_canister_id: MAINNET_GOVERNANCE_CANISTER_ID.to_string(),
fetched_at,
source_endpoint: request.source_endpoint.clone(),
fetched_by: NNS_PROPOSAL_FETCHED_BY.to_string(),
provenance: NnsProposalReportProvenance::live(),
proposal_id: request.proposal_id,
show_ballots: request.show_ballots,
verbose: request.verbose,
proposal,
}))
}
pub trait NnsProposalSource {
fn fetch_proposals(
&self,
request: &NnsSourceRequest,
limit: u32,
before_proposal_id: Option<u64>,
status: NnsProposalStatusFilter,
reward_status: NnsProposalRewardStatusFilter,
) -> Result<Vec<NnsProposalRow>, NnsProposalHostError>;
fn fetch_proposal(
&self,
request: &NnsSourceRequest,
proposal_id: u64,
) -> Result<NnsProposalRow, NnsProposalHostError>;
}
pub(in crate::nns::proposals::report) fn nns_proposal_row_from_info(
info: NnsProposalInfo,
) -> NnsProposalRow {
let proposal = info.proposal;
let ballot_count = info.ballots.len();
let ballots = nns_proposal_ballot_rows(info.ballots);
NnsProposalRow {
proposal_id: info.id.map(|id| id.id),
proposer_neuron_id: info.proposer.map(|id| id.id),
topic: info.topic,
topic_text: NnsProposalTopic::from_code(info.topic),
status: info.status,
status_text: NnsProposalStatus::from_code(info.status),
reward_status: info.reward_status,
reward_status_text: NnsProposalRewardStatus::from_code(info.reward_status),
title: proposal
.as_ref()
.and_then(|proposal| proposal.title.clone()),
summary: proposal
.as_ref()
.map_or_else(String::new, |proposal| proposal.summary.clone()),
url: proposal
.as_ref()
.map_or_else(String::new, |proposal| proposal.url.clone()),
action_text: proposal
.as_ref()
.and_then(|proposal| proposal.action.as_ref())
.map(|action| action.as_str().to_string()),
reject_cost_e8s: info.reject_cost_e8s,
proposal_timestamp_seconds: info.proposal_timestamp_seconds,
proposed_at: format_utc_timestamp_secs(info.proposal_timestamp_seconds),
deadline_timestamp_seconds: info.deadline_timestamp_seconds,
deadline_at: info
.deadline_timestamp_seconds
.map(format_utc_timestamp_secs),
decided_timestamp_seconds: info.decided_timestamp_seconds,
decided_at: nonzero_timestamp_text(info.decided_timestamp_seconds),
executed_timestamp_seconds: info.executed_timestamp_seconds,
executed_at: nonzero_timestamp_text(info.executed_timestamp_seconds),
failed_timestamp_seconds: info.failed_timestamp_seconds,
failed_at: nonzero_timestamp_text(info.failed_timestamp_seconds),
reward_event_round: info.reward_event_round,
total_potential_voting_power: info.total_potential_voting_power,
latest_tally: info.latest_tally.map(|tally| NnsProposalTally {
timestamp_seconds: tally.timestamp_seconds,
yes: tally.yes,
no: tally.no,
total: tally.total,
}),
ballot_count,
ballots,
}
}
fn nns_proposal_ballot_rows(ballots: Vec<(u64, NnsGovernanceBallot)>) -> Vec<NnsProposalBallotRow> {
let mut rows = ballots
.into_iter()
.map(|(neuron_id, ballot)| NnsProposalBallotRow {
neuron_id,
vote: ballot.vote,
vote_text: NnsProposalVote::from_code(ballot.vote),
voting_power: ballot.voting_power,
})
.collect::<Vec<_>>();
rows.sort_by_key(|ballot| ballot.neuron_id);
rows
}
fn nonzero_timestamp_text(timestamp_seconds: u64) -> Option<String> {
(timestamp_seconds > 0).then(|| format_utc_timestamp_secs(timestamp_seconds))
}