Skip to main content

nym_validator_client/nyxd/contract_traits/
multisig_query_client.rs

1// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::nyxd::contract_traits::NymContractsProvider;
5use crate::nyxd::error::NyxdError;
6use crate::nyxd::CosmWasmClient;
7use async_trait::async_trait;
8use cw3::{
9    ProposalListResponse, ProposalResponse, VoteInfo, VoteListResponse, VoteResponse, VoterDetail,
10    VoterListResponse, VoterResponse,
11};
12use cw_utils::ThresholdResponse;
13use nym_multisig_contract_common::msg::QueryMsg as MultisigQueryMsg;
14use serde::Deserialize;
15
16#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
17#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
18pub trait MultisigQueryClient {
19    async fn query_multisig_contract<T>(&self, query: MultisigQueryMsg) -> Result<T, NyxdError>
20    where
21        for<'a> T: Deserialize<'a>;
22
23    async fn query_threshold(&self) -> Result<ThresholdResponse, NyxdError> {
24        self.query_multisig_contract(MultisigQueryMsg::Threshold {})
25            .await
26    }
27
28    async fn query_proposal(&self, proposal_id: u64) -> Result<ProposalResponse, NyxdError> {
29        self.query_multisig_contract(MultisigQueryMsg::Proposal { proposal_id })
30            .await
31    }
32
33    async fn list_proposals(
34        &self,
35        start_after: Option<u64>,
36        limit: Option<u32>,
37    ) -> Result<ProposalListResponse, NyxdError> {
38        self.query_multisig_contract(MultisigQueryMsg::ListProposals { start_after, limit })
39            .await
40    }
41
42    async fn reverse_proposals(
43        &self,
44        start_before: Option<u64>,
45        limit: Option<u32>,
46    ) -> Result<ProposalListResponse, NyxdError> {
47        self.query_multisig_contract(MultisigQueryMsg::ReverseProposals {
48            start_before,
49            limit,
50        })
51        .await
52    }
53
54    async fn query_vote(&self, proposal_id: u64, voter: String) -> Result<VoteResponse, NyxdError> {
55        self.query_multisig_contract(MultisigQueryMsg::Vote { proposal_id, voter })
56            .await
57    }
58
59    async fn list_votes(
60        &self,
61        proposal_id: u64,
62        start_after: Option<String>,
63        limit: Option<u32>,
64    ) -> Result<VoteListResponse, NyxdError> {
65        self.query_multisig_contract(MultisigQueryMsg::ListVotes {
66            proposal_id,
67            start_after,
68            limit,
69        })
70        .await
71    }
72
73    async fn query_voter(&self, address: String) -> Result<VoterResponse, NyxdError> {
74        self.query_multisig_contract(MultisigQueryMsg::Voter { address })
75            .await
76    }
77
78    async fn list_voters(
79        &self,
80        start_after: Option<String>,
81        limit: Option<u32>,
82    ) -> Result<VoterListResponse, NyxdError> {
83        self.query_multisig_contract(MultisigQueryMsg::ListVoters { start_after, limit })
84            .await
85    }
86
87    async fn query_config(&self) -> Result<(), NyxdError> {
88        unimplemented!("requires exporting state::Config type")
89    }
90}
91
92// extension trait to the query client to deal with the paged queries
93// (it didn't feel appropriate to combine it with the existing trait
94#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
95#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
96pub trait PagedMultisigQueryClient: MultisigQueryClient {
97    // can't use the macro due to different paging behaviour
98    async fn get_all_proposals(&self) -> Result<Vec<ProposalResponse>, NyxdError> {
99        let mut proposals = Vec::new();
100        let mut start_after = None;
101
102        loop {
103            let mut paged_response = self.list_proposals(start_after.take(), None).await?;
104
105            let last_id = paged_response.proposals.last().map(|prop| prop.id);
106            proposals.append(&mut paged_response.proposals);
107
108            if let Some(start_after_res) = last_id {
109                start_after = Some(start_after_res)
110            } else {
111                break;
112            }
113        }
114
115        Ok(proposals)
116    }
117
118    async fn get_all_voters(&self) -> Result<Vec<VoterDetail>, NyxdError> {
119        let mut voters = Vec::new();
120        let mut start_after = None;
121
122        loop {
123            let mut paged_response = self.list_voters(start_after.take(), None).await?;
124
125            let last_voter = paged_response.voters.last().map(|prop| prop.addr.clone());
126            voters.append(&mut paged_response.voters);
127
128            if let Some(start_after_res) = last_voter {
129                start_after = Some(start_after_res)
130            } else {
131                break;
132            }
133        }
134
135        Ok(voters)
136    }
137
138    async fn get_all_votes(&self, proposal_id: u64) -> Result<Vec<VoteInfo>, NyxdError> {
139        let mut votes = Vec::new();
140        let mut start_after = None;
141
142        loop {
143            let mut paged_response = self
144                .list_votes(proposal_id, start_after.take(), None)
145                .await?;
146
147            let last_voter = paged_response.votes.last().map(|vote| vote.voter.clone());
148            votes.append(&mut paged_response.votes);
149
150            if let Some(start_after_res) = last_voter {
151                start_after = Some(start_after_res)
152            } else {
153                break;
154            }
155        }
156
157        Ok(votes)
158    }
159}
160
161#[async_trait]
162impl<T> PagedMultisigQueryClient for T where T: MultisigQueryClient {}
163
164#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
165#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
166impl<C> MultisigQueryClient for C
167where
168    C: CosmWasmClient + NymContractsProvider + Send + Sync,
169{
170    async fn query_multisig_contract<T>(&self, query: MultisigQueryMsg) -> Result<T, NyxdError>
171    where
172        for<'a> T: Deserialize<'a>,
173    {
174        let multisig_contract_address = &self
175            .multisig_contract_address()
176            .ok_or_else(|| NyxdError::unavailable_contract_address("multisig contract"))?;
177        self.query_contract_smart(multisig_contract_address, &query)
178            .await
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185    use crate::nyxd::contract_traits::tests::IgnoreValue;
186
187    // it's enough that this compiles and clippy is happy about it
188    #[allow(dead_code)]
189    fn all_query_variants_are_covered<C: MultisigQueryClient + Send + Sync>(
190        client: C,
191        msg: MultisigQueryMsg,
192    ) {
193        match msg {
194            MultisigQueryMsg::Threshold {} => client.query_threshold().ignore(),
195            MultisigQueryMsg::Proposal { proposal_id } => {
196                client.query_proposal(proposal_id).ignore()
197            }
198            MultisigQueryMsg::ListProposals { start_after, limit } => {
199                client.list_proposals(start_after, limit).ignore()
200            }
201            MultisigQueryMsg::ReverseProposals {
202                start_before,
203                limit,
204            } => client.reverse_proposals(start_before, limit).ignore(),
205            MultisigQueryMsg::Vote { proposal_id, voter } => {
206                client.query_vote(proposal_id, voter).ignore()
207            }
208            MultisigQueryMsg::ListVotes {
209                proposal_id,
210                start_after,
211                limit,
212            } => client.list_votes(proposal_id, start_after, limit).ignore(),
213            MultisigQueryMsg::Voter { address } => client.query_voter(address).ignore(),
214            MultisigQueryMsg::ListVoters { start_after, limit } => {
215                client.list_voters(start_after, limit).ignore()
216            }
217            MultisigQueryMsg::Config {} => client.query_config().ignore(),
218        };
219    }
220}