mixnet_contract/mixnodes/
queries.rs

1// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use super::storage;
5use crate::constants::{
6    MIXNODE_BOND_DEFAULT_RETRIEVAL_LIMIT, MIXNODE_BOND_MAX_RETRIEVAL_LIMIT,
7    MIXNODE_DETAILS_DEFAULT_RETRIEVAL_LIMIT, MIXNODE_DETAILS_MAX_RETRIEVAL_LIMIT,
8    UNBONDED_MIXNODES_DEFAULT_RETRIEVAL_LIMIT, UNBONDED_MIXNODES_MAX_RETRIEVAL_LIMIT,
9};
10use crate::mixnodes::helpers::{
11    attach_mix_details, get_mixnode_details_by_id, get_mixnode_details_by_identity,
12    get_mixnode_details_by_owner,
13};
14use crate::rewards::storage as rewards_storage;
15use cosmwasm_std::{Deps, Order, StdResult, Storage};
16use cw_storage_plus::Bound;
17use mixnet_contract_common::mixnode::{
18    MixNodeBond, MixNodeDetails, MixnodeRewardingDetailsResponse, PagedMixnodesDetailsResponse,
19    PagedUnbondedMixnodesResponse, StakeSaturationResponse, UnbondedMixnodeResponse,
20};
21use mixnet_contract_common::{
22    IdentityKey, LayerDistribution, MixId, MixOwnershipResponse, MixnodeDetailsResponse,
23    PagedMixnodeBondsResponse,
24};
25
26pub fn query_mixnode_bonds_paged(
27    deps: Deps<'_>,
28    start_after: Option<MixId>,
29    limit: Option<u32>,
30) -> StdResult<PagedMixnodeBondsResponse> {
31    let limit = limit
32        .unwrap_or(MIXNODE_BOND_DEFAULT_RETRIEVAL_LIMIT)
33        .min(MIXNODE_BOND_MAX_RETRIEVAL_LIMIT) as usize;
34
35    let start = start_after.map(Bound::exclusive);
36
37    let nodes = storage::mixnode_bonds()
38        .range(deps.storage, start, None, Order::Ascending)
39        .take(limit)
40        .map(|res| res.map(|item| item.1))
41        .collect::<StdResult<Vec<MixNodeBond>>>()?;
42
43    let start_next_after = nodes.last().map(|node| node.mix_id);
44
45    Ok(PagedMixnodeBondsResponse::new(
46        nodes,
47        limit,
48        start_next_after,
49    ))
50}
51
52fn attach_node_details(
53    storage: &dyn Storage,
54    read_bond: StdResult<(MixId, MixNodeBond)>,
55) -> StdResult<MixNodeDetails> {
56    match read_bond {
57        Ok((_, bond)) => attach_mix_details(storage, bond),
58        Err(err) => Err(err),
59    }
60}
61
62pub fn query_mixnodes_details_paged(
63    deps: Deps<'_>,
64    start_after: Option<MixId>,
65    limit: Option<u32>,
66) -> StdResult<PagedMixnodesDetailsResponse> {
67    let limit = limit
68        .unwrap_or(MIXNODE_DETAILS_DEFAULT_RETRIEVAL_LIMIT)
69        .min(MIXNODE_DETAILS_MAX_RETRIEVAL_LIMIT) as usize;
70
71    let start = start_after.map(Bound::exclusive);
72
73    let nodes = storage::mixnode_bonds()
74        .range(deps.storage, start, None, Order::Ascending)
75        .take(limit)
76        .map(|res| attach_node_details(deps.storage, res))
77        .collect::<StdResult<Vec<MixNodeDetails>>>()?;
78
79    let start_next_after = nodes.last().map(|details| details.mix_id());
80
81    Ok(PagedMixnodesDetailsResponse::new(
82        nodes,
83        limit,
84        start_next_after,
85    ))
86}
87
88pub fn query_unbonded_mixnodes_paged(
89    deps: Deps<'_>,
90    start_after: Option<MixId>,
91    limit: Option<u32>,
92) -> StdResult<PagedUnbondedMixnodesResponse> {
93    let limit = limit
94        .unwrap_or(UNBONDED_MIXNODES_DEFAULT_RETRIEVAL_LIMIT)
95        .min(UNBONDED_MIXNODES_MAX_RETRIEVAL_LIMIT) as usize;
96
97    let start = start_after.map(Bound::exclusive);
98
99    let nodes = storage::unbonded_mixnodes()
100        .range(deps.storage, start, None, Order::Ascending)
101        .take(limit)
102        .collect::<StdResult<Vec<_>>>()?;
103
104    let start_next_after = nodes.last().map(|res| res.0);
105
106    Ok(PagedUnbondedMixnodesResponse::new(
107        nodes,
108        limit,
109        start_next_after,
110    ))
111}
112
113pub fn query_unbonded_mixnodes_by_owner_paged(
114    deps: Deps<'_>,
115    owner: String,
116    start_after: Option<MixId>,
117    limit: Option<u32>,
118) -> StdResult<PagedUnbondedMixnodesResponse> {
119    let owner = deps.api.addr_validate(&owner)?;
120
121    let limit = limit
122        .unwrap_or(UNBONDED_MIXNODES_DEFAULT_RETRIEVAL_LIMIT)
123        .min(UNBONDED_MIXNODES_MAX_RETRIEVAL_LIMIT) as usize;
124
125    let start = start_after.map(Bound::exclusive);
126
127    let nodes = storage::unbonded_mixnodes()
128        .idx
129        .owner
130        .prefix(owner)
131        .range(deps.storage, start, None, Order::Ascending)
132        .take(limit)
133        .collect::<StdResult<Vec<_>>>()?;
134
135    let start_next_after = nodes.last().map(|res| res.0);
136
137    Ok(PagedUnbondedMixnodesResponse::new(
138        nodes,
139        limit,
140        start_next_after,
141    ))
142}
143
144pub fn query_unbonded_mixnodes_by_identity_paged(
145    deps: Deps<'_>,
146    identity_key: String,
147    start_after: Option<MixId>,
148    limit: Option<u32>,
149) -> StdResult<PagedUnbondedMixnodesResponse> {
150    let limit = limit
151        .unwrap_or(UNBONDED_MIXNODES_DEFAULT_RETRIEVAL_LIMIT)
152        .min(UNBONDED_MIXNODES_MAX_RETRIEVAL_LIMIT) as usize;
153
154    let start = start_after.map(Bound::exclusive);
155
156    let nodes = storage::unbonded_mixnodes()
157        .idx
158        .identity_key
159        .prefix(identity_key)
160        .range(deps.storage, start, None, Order::Ascending)
161        .take(limit)
162        .collect::<StdResult<Vec<_>>>()?;
163
164    let start_next_after = nodes.last().map(|res| res.0);
165
166    Ok(PagedUnbondedMixnodesResponse::new(
167        nodes,
168        limit,
169        start_next_after,
170    ))
171}
172
173pub fn query_owned_mixnode(deps: Deps<'_>, address: String) -> StdResult<MixOwnershipResponse> {
174    let validated_addr = deps.api.addr_validate(&address)?;
175    let mixnode_details = get_mixnode_details_by_owner(deps.storage, validated_addr.clone())?;
176
177    Ok(MixOwnershipResponse {
178        address: validated_addr,
179        mixnode_details,
180    })
181}
182
183pub fn query_mixnode_details(deps: Deps<'_>, mix_id: MixId) -> StdResult<MixnodeDetailsResponse> {
184    let mixnode_details = get_mixnode_details_by_id(deps.storage, mix_id)?;
185
186    Ok(MixnodeDetailsResponse {
187        mix_id,
188        mixnode_details,
189    })
190}
191
192// TODO: change the return type to be consistent with the other details queries
193pub fn query_mixnode_details_by_identity(
194    deps: Deps<'_>,
195    mix_identity: IdentityKey,
196) -> StdResult<Option<MixNodeDetails>> {
197    get_mixnode_details_by_identity(deps.storage, mix_identity)
198}
199
200pub fn query_mixnode_rewarding_details(
201    deps: Deps<'_>,
202    mix_id: MixId,
203) -> StdResult<MixnodeRewardingDetailsResponse> {
204    let rewarding_details = rewards_storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)?;
205
206    Ok(MixnodeRewardingDetailsResponse {
207        mix_id,
208        rewarding_details,
209    })
210}
211
212pub fn query_unbonded_mixnode(deps: Deps<'_>, mix_id: MixId) -> StdResult<UnbondedMixnodeResponse> {
213    let unbonded_info = storage::unbonded_mixnodes().may_load(deps.storage, mix_id)?;
214
215    Ok(UnbondedMixnodeResponse {
216        mix_id,
217        unbonded_info,
218    })
219}
220
221pub fn query_stake_saturation(deps: Deps<'_>, mix_id: MixId) -> StdResult<StakeSaturationResponse> {
222    let mix_rewarding = match rewards_storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)? {
223        Some(mix_rewarding) => mix_rewarding,
224        None => {
225            return Ok(StakeSaturationResponse {
226                mix_id,
227                current_saturation: None,
228                uncapped_saturation: None,
229            })
230        }
231    };
232
233    let rewarding_params = rewards_storage::REWARDING_PARAMS.load(deps.storage)?;
234
235    Ok(StakeSaturationResponse {
236        mix_id,
237        current_saturation: Some(mix_rewarding.bond_saturation(&rewarding_params)),
238        uncapped_saturation: Some(mix_rewarding.uncapped_bond_saturation(&rewarding_params)),
239    })
240}
241
242pub(crate) fn query_layer_distribution(deps: Deps<'_>) -> StdResult<LayerDistribution> {
243    storage::LAYERS.load(deps.storage)
244}
245
246#[cfg(test)]
247pub(crate) mod tests {
248    use super::*;
249    use crate::interval::pending_events;
250    use crate::support::tests::fixtures::good_mixnode_pledge;
251    use crate::support::tests::test_helpers::TestSetup;
252    use crate::support::tests::{fixtures, test_helpers};
253    use cosmwasm_std::testing::mock_env;
254    use cosmwasm_std::Decimal;
255
256    #[cfg(test)]
257    mod mixnode_bonds {
258        use super::*;
259
260        #[test]
261        fn obeys_limits() {
262            let mut test = TestSetup::new();
263            test.add_dummy_mixnodes(1000);
264            let limit = 2;
265
266            let page1 = query_mixnode_bonds_paged(test.deps(), None, Some(limit)).unwrap();
267            assert_eq!(limit, page1.nodes.len() as u32);
268        }
269
270        #[test]
271        fn has_default_limit() {
272            let mut test = TestSetup::new();
273            test.add_dummy_mixnodes(1000);
274
275            // query without explicitly setting a limit
276            let page1 = query_mixnode_bonds_paged(test.deps(), None, None).unwrap();
277
278            assert_eq!(
279                MIXNODE_BOND_DEFAULT_RETRIEVAL_LIMIT,
280                page1.nodes.len() as u32
281            );
282        }
283
284        #[test]
285        fn has_max_limit() {
286            let mut test = TestSetup::new();
287            test.add_dummy_mixnodes(1000);
288
289            // query with a crazily high limit in an attempt to use too many resources
290            let crazy_limit = 1000;
291            let page1 = query_mixnode_bonds_paged(test.deps(), None, Some(crazy_limit)).unwrap();
292
293            // we default to a decent sized upper bound instead
294            assert_eq!(MIXNODE_BOND_MAX_RETRIEVAL_LIMIT, page1.nodes.len() as u32);
295        }
296
297        #[test]
298        fn pagination_works() {
299            // as we add mixnodes, we're always inserting them in ascending manner due to monotonically increasing id
300            let mut test = TestSetup::new();
301
302            test.add_dummy_mixnode("addr1", None);
303
304            let per_page = 2;
305            let page1 = query_mixnode_bonds_paged(test.deps(), None, Some(per_page)).unwrap();
306
307            // page should have 1 result on it
308            assert_eq!(1, page1.nodes.len());
309
310            // save another
311            test.add_dummy_mixnode("addr2", None);
312
313            // page1 should have 2 results on it
314            let page1 = query_mixnode_bonds_paged(test.deps(), None, Some(per_page)).unwrap();
315            assert_eq!(2, page1.nodes.len());
316
317            test.add_dummy_mixnode("addr3", None);
318
319            // page1 still has the same 2 results
320            let another_page1 =
321                query_mixnode_bonds_paged(test.deps(), None, Some(per_page)).unwrap();
322            assert_eq!(2, another_page1.nodes.len());
323            assert_eq!(page1, another_page1);
324
325            // retrieving the next page should start after the last key on this page
326            let start_after = page1.start_next_after.unwrap();
327            let page2 =
328                query_mixnode_bonds_paged(test.deps(), Some(start_after), Some(per_page)).unwrap();
329
330            assert_eq!(1, page2.nodes.len());
331
332            // save another one
333            test.add_dummy_mixnode("addr4", None);
334
335            let page2 =
336                query_mixnode_bonds_paged(test.deps(), Some(start_after), Some(per_page)).unwrap();
337
338            // now we have 2 pages, with 2 results on the second page
339            assert_eq!(2, page2.nodes.len());
340        }
341    }
342
343    #[cfg(test)]
344    mod mixnode_details {
345        use super::*;
346
347        #[test]
348        fn obeys_limits() {
349            let mut test = TestSetup::new();
350            test.add_dummy_mixnodes(1000);
351            let limit = 2;
352
353            let page1 = query_mixnodes_details_paged(test.deps(), None, Some(limit)).unwrap();
354            assert_eq!(limit, page1.nodes.len() as u32);
355        }
356
357        #[test]
358        fn has_default_limit() {
359            let mut test = TestSetup::new();
360            test.add_dummy_mixnodes(1000);
361
362            // query without explicitly setting a limit
363            let page1 = query_mixnodes_details_paged(test.deps(), None, None).unwrap();
364
365            assert_eq!(
366                MIXNODE_DETAILS_DEFAULT_RETRIEVAL_LIMIT,
367                page1.nodes.len() as u32
368            );
369        }
370
371        #[test]
372        fn has_max_limit() {
373            let mut test = TestSetup::new();
374            test.add_dummy_mixnodes(1000);
375
376            // query with a crazily high limit in an attempt to use too many resources
377            let crazy_limit = 1000;
378            let page1 = query_mixnodes_details_paged(test.deps(), None, Some(crazy_limit)).unwrap();
379
380            // we default to a decent sized upper bound instead
381            assert_eq!(
382                MIXNODE_DETAILS_MAX_RETRIEVAL_LIMIT,
383                page1.nodes.len() as u32
384            );
385        }
386
387        #[test]
388        fn pagination_works() {
389            // as we add mixnodes, we're always inserting them in ascending manner due to monotonically increasing id
390            let mut test = TestSetup::new();
391
392            test.add_dummy_mixnode("addr1", None);
393
394            let per_page = 2;
395            let page1 = query_mixnodes_details_paged(test.deps(), None, Some(per_page)).unwrap();
396
397            // page should have 1 result on it
398            assert_eq!(1, page1.nodes.len());
399
400            // save another
401            test.add_dummy_mixnode("addr2", None);
402
403            // page1 should have 2 results on it
404            let page1 = query_mixnodes_details_paged(test.deps(), None, Some(per_page)).unwrap();
405            assert_eq!(2, page1.nodes.len());
406
407            test.add_dummy_mixnode("addr3", None);
408
409            // page1 still has the same 2 results
410            let another_page1 =
411                query_mixnodes_details_paged(test.deps(), None, Some(per_page)).unwrap();
412            assert_eq!(2, another_page1.nodes.len());
413            assert_eq!(page1, another_page1);
414
415            // retrieving the next page should start after the last key on this page
416            let start_after = page1.start_next_after.unwrap();
417            let page2 =
418                query_mixnodes_details_paged(test.deps(), Some(start_after), Some(per_page))
419                    .unwrap();
420
421            assert_eq!(1, page2.nodes.len());
422
423            // save another one
424            test.add_dummy_mixnode("addr4", None);
425
426            let page2 =
427                query_mixnodes_details_paged(test.deps(), Some(start_after), Some(per_page))
428                    .unwrap();
429
430            // now we have 2 pages, with 2 results on the second page
431            assert_eq!(2, page2.nodes.len());
432        }
433    }
434
435    #[cfg(test)]
436    mod unbonded_mixnodes {
437        use super::*;
438        use cosmwasm_std::Addr;
439        use mixnet_contract_common::mixnode::UnbondedMixnode;
440
441        #[test]
442        fn obeys_limits() {
443            let mut deps = test_helpers::init_contract();
444            let _env = mock_env();
445            let rng = test_helpers::test_rng();
446            let limit = 2;
447
448            test_helpers::add_dummy_unbonded_mixnodes(rng, deps.as_mut(), 1000);
449            let page1 = query_unbonded_mixnodes_paged(deps.as_ref(), None, Some(limit)).unwrap();
450            assert_eq!(limit, page1.nodes.len() as u32);
451        }
452
453        #[test]
454        fn has_default_limit() {
455            let mut deps = test_helpers::init_contract();
456            let _env = mock_env();
457            let rng = test_helpers::test_rng();
458            test_helpers::add_dummy_unbonded_mixnodes(rng, deps.as_mut(), 1000);
459
460            // query without explicitly setting a limit
461            let page1 = query_unbonded_mixnodes_paged(deps.as_ref(), None, None).unwrap();
462
463            assert_eq!(
464                UNBONDED_MIXNODES_DEFAULT_RETRIEVAL_LIMIT,
465                page1.nodes.len() as u32
466            );
467        }
468
469        #[test]
470        fn has_max_limit() {
471            let mut deps = test_helpers::init_contract();
472            let _env = mock_env();
473            let rng = test_helpers::test_rng();
474            test_helpers::add_dummy_unbonded_mixnodes(rng, deps.as_mut(), 1000);
475
476            // query with a crazily high limit in an attempt to use too many resources
477            let crazy_limit = 1000;
478            let page1 =
479                query_unbonded_mixnodes_paged(deps.as_ref(), None, Some(crazy_limit)).unwrap();
480
481            // we default to a decent sized upper bound instead
482            assert_eq!(
483                UNBONDED_MIXNODES_MAX_RETRIEVAL_LIMIT,
484                page1.nodes.len() as u32
485            );
486        }
487
488        #[test]
489        fn pagination_works() {
490            fn add_unbonded(storage: &mut dyn Storage, id: MixId) {
491                storage::unbonded_mixnodes()
492                    .save(
493                        storage,
494                        id,
495                        &UnbondedMixnode {
496                            identity_key: format!("dummy{}", id),
497                            owner: Addr::unchecked(format!("dummy{}", id)),
498                            proxy: None,
499                            unbonding_height: 123,
500                        },
501                    )
502                    .unwrap();
503            }
504
505            // as we add mixnodes, we're always inserting them in ascending manner due to monotonically increasing id
506            let mut deps = test_helpers::init_contract();
507
508            add_unbonded(deps.as_mut().storage, 1);
509
510            let per_page = 2;
511            let page1 = query_unbonded_mixnodes_paged(deps.as_ref(), None, Some(per_page)).unwrap();
512
513            // page should have 1 result on it
514            assert_eq!(1, page1.nodes.len());
515
516            // save another
517            add_unbonded(deps.as_mut().storage, 2);
518
519            // page1 should have 2 results on it
520            let page1 = query_unbonded_mixnodes_paged(deps.as_ref(), None, Some(per_page)).unwrap();
521            assert_eq!(2, page1.nodes.len());
522
523            add_unbonded(deps.as_mut().storage, 3);
524
525            // page1 still has the same 2 results
526            let another_page1 =
527                query_unbonded_mixnodes_paged(deps.as_ref(), None, Some(per_page)).unwrap();
528            assert_eq!(2, another_page1.nodes.len());
529            assert_eq!(page1, another_page1);
530
531            // retrieving the next page should start after the last key on this page
532            let start_after = page1.start_next_after.unwrap();
533            let page2 =
534                query_unbonded_mixnodes_paged(deps.as_ref(), Some(start_after), Some(per_page))
535                    .unwrap();
536
537            assert_eq!(1, page2.nodes.len());
538
539            // save another one
540            add_unbonded(deps.as_mut().storage, 4);
541            let page2 =
542                query_unbonded_mixnodes_paged(deps.as_ref(), Some(start_after), Some(per_page))
543                    .unwrap();
544
545            // now we have 2 pages, with 2 results on the second page
546            assert_eq!(2, page2.nodes.len());
547        }
548    }
549
550    #[cfg(test)]
551    mod unbonded_mixnodes_by_owner {
552        use super::*;
553        use cosmwasm_std::Addr;
554        use mixnet_contract_common::mixnode::UnbondedMixnode;
555
556        fn add_unbonded_with_owner(storage: &mut dyn Storage, id: MixId, owner: &str) {
557            storage::unbonded_mixnodes()
558                .save(
559                    storage,
560                    id,
561                    &UnbondedMixnode {
562                        identity_key: format!("dummy{}", id),
563                        owner: Addr::unchecked(owner),
564                        proxy: None,
565                        unbonding_height: 123,
566                    },
567                )
568                .unwrap();
569        }
570
571        #[test]
572        fn obeys_limits() {
573            let mut deps = test_helpers::init_contract();
574            let _env = mock_env();
575            let rng = test_helpers::test_rng();
576            let limit = 2;
577            let owner = "owner";
578
579            test_helpers::add_dummy_unbonded_mixnodes_with_owner(rng, deps.as_mut(), owner, 1000);
580            let page1 = query_unbonded_mixnodes_by_owner_paged(
581                deps.as_ref(),
582                owner.into(),
583                None,
584                Some(limit),
585            )
586            .unwrap();
587            assert_eq!(limit, page1.nodes.len() as u32);
588        }
589
590        #[test]
591        fn has_default_limit() {
592            let mut deps = test_helpers::init_contract();
593            let _env = mock_env();
594            let rng = test_helpers::test_rng();
595            let owner = "owner";
596
597            test_helpers::add_dummy_unbonded_mixnodes_with_owner(rng, deps.as_mut(), owner, 1000);
598
599            // query without explicitly setting a limit
600            let page1 =
601                query_unbonded_mixnodes_by_owner_paged(deps.as_ref(), owner.into(), None, None)
602                    .unwrap();
603
604            assert_eq!(
605                UNBONDED_MIXNODES_DEFAULT_RETRIEVAL_LIMIT,
606                page1.nodes.len() as u32
607            );
608        }
609
610        #[test]
611        fn has_max_limit() {
612            let mut deps = test_helpers::init_contract();
613            let _env = mock_env();
614            let rng = test_helpers::test_rng();
615            let owner = "owner";
616
617            test_helpers::add_dummy_unbonded_mixnodes_with_owner(rng, deps.as_mut(), owner, 1000);
618
619            // query with a crazily high limit in an attempt to use too many resources
620            let crazy_limit = 1000;
621            let page1 = query_unbonded_mixnodes_by_owner_paged(
622                deps.as_ref(),
623                owner.into(),
624                None,
625                Some(crazy_limit),
626            )
627            .unwrap();
628
629            // we default to a decent sized upper bound instead
630            assert_eq!(
631                UNBONDED_MIXNODES_MAX_RETRIEVAL_LIMIT,
632                page1.nodes.len() as u32
633            );
634        }
635
636        #[test]
637        fn pagination_works() {
638            // as we add mixnodes, we're always inserting them in ascending manner due to monotonically increasing id
639            let mut deps = test_helpers::init_contract();
640            let owner = "owner";
641            add_unbonded_with_owner(deps.as_mut().storage, 1, owner);
642
643            let per_page = 2;
644            let page1 = query_unbonded_mixnodes_by_owner_paged(
645                deps.as_ref(),
646                owner.into(),
647                None,
648                Some(per_page),
649            )
650            .unwrap();
651
652            // page should have 1 result on it
653            assert_eq!(1, page1.nodes.len());
654
655            // save another
656            add_unbonded_with_owner(deps.as_mut().storage, 2, owner);
657
658            // page1 should have 2 results on it
659            let page1 = query_unbonded_mixnodes_by_owner_paged(
660                deps.as_ref(),
661                owner.into(),
662                None,
663                Some(per_page),
664            )
665            .unwrap();
666            assert_eq!(2, page1.nodes.len());
667
668            add_unbonded_with_owner(deps.as_mut().storage, 3, owner);
669
670            // page1 still has the same 2 results
671            let another_page1 = query_unbonded_mixnodes_by_owner_paged(
672                deps.as_ref(),
673                owner.into(),
674                None,
675                Some(per_page),
676            )
677            .unwrap();
678            assert_eq!(2, another_page1.nodes.len());
679            assert_eq!(page1, another_page1);
680
681            // retrieving the next page should start after the last key on this page
682            let start_after = page1.start_next_after.unwrap();
683            let page2 = query_unbonded_mixnodes_by_owner_paged(
684                deps.as_ref(),
685                owner.into(),
686                Some(start_after),
687                Some(per_page),
688            )
689            .unwrap();
690
691            assert_eq!(1, page2.nodes.len());
692
693            // save another one
694            add_unbonded_with_owner(deps.as_mut().storage, 4, owner);
695            let page2 = query_unbonded_mixnodes_by_owner_paged(
696                deps.as_ref(),
697                owner.into(),
698                Some(start_after),
699                Some(per_page),
700            )
701            .unwrap();
702
703            // now we have 2 pages, with 2 results on the second page
704            assert_eq!(2, page2.nodes.len());
705        }
706
707        #[test]
708        fn only_retrieves_nodes_with_specific_owner() {
709            let mut deps = test_helpers::init_contract();
710            let owner1 = "owner1";
711            let owner2 = "owner2";
712            let owner3 = "owner3";
713            let owner4 = "owner4";
714
715            add_unbonded_with_owner(deps.as_mut().storage, 1, owner1);
716            add_unbonded_with_owner(deps.as_mut().storage, 2, owner1);
717            add_unbonded_with_owner(deps.as_mut().storage, 3, owner2);
718            add_unbonded_with_owner(deps.as_mut().storage, 4, owner1);
719            add_unbonded_with_owner(deps.as_mut().storage, 5, owner3);
720            add_unbonded_with_owner(deps.as_mut().storage, 6, owner3);
721            add_unbonded_with_owner(deps.as_mut().storage, 7, owner4);
722            add_unbonded_with_owner(deps.as_mut().storage, 8, owner2);
723            add_unbonded_with_owner(deps.as_mut().storage, 9, owner1);
724            add_unbonded_with_owner(deps.as_mut().storage, 10, owner3);
725
726            let expected_ids1 = vec![1, 2, 4, 9];
727            let expected_ids2 = vec![3, 8];
728            let expected_ids3 = vec![5, 6, 10];
729            let expected_ids4 = vec![7];
730
731            let res1 =
732                query_unbonded_mixnodes_by_owner_paged(deps.as_ref(), owner1.into(), None, None)
733                    .unwrap()
734                    .nodes
735                    .into_iter()
736                    .map(|r| r.0)
737                    .collect::<Vec<_>>();
738            assert_eq!(res1, expected_ids1);
739
740            let res2 =
741                query_unbonded_mixnodes_by_owner_paged(deps.as_ref(), owner2.into(), None, None)
742                    .unwrap()
743                    .nodes
744                    .into_iter()
745                    .map(|r| r.0)
746                    .collect::<Vec<_>>();
747            assert_eq!(res2, expected_ids2);
748
749            let res3 =
750                query_unbonded_mixnodes_by_owner_paged(deps.as_ref(), owner3.into(), None, None)
751                    .unwrap()
752                    .nodes
753                    .into_iter()
754                    .map(|r| r.0)
755                    .collect::<Vec<_>>();
756            assert_eq!(res3, expected_ids3);
757
758            let res4 =
759                query_unbonded_mixnodes_by_owner_paged(deps.as_ref(), owner4.into(), None, None)
760                    .unwrap()
761                    .nodes
762                    .into_iter()
763                    .map(|r| r.0)
764                    .collect::<Vec<_>>();
765            assert_eq!(res4, expected_ids4);
766
767            let res5 = query_unbonded_mixnodes_by_owner_paged(
768                deps.as_ref(),
769                "doesnt-exist".into(),
770                None,
771                None,
772            )
773            .unwrap()
774            .nodes
775            .into_iter()
776            .map(|r| r.0)
777            .collect::<Vec<_>>();
778            assert!(res5.is_empty());
779        }
780    }
781
782    #[cfg(test)]
783    mod unbonded_mixnodes_by_identity {
784        use super::*;
785        use cosmwasm_std::Addr;
786        use mixnet_contract_common::mixnode::UnbondedMixnode;
787
788        fn add_unbonded_with_identity(storage: &mut dyn Storage, id: MixId, identity: &str) {
789            storage::unbonded_mixnodes()
790                .save(
791                    storage,
792                    id,
793                    &UnbondedMixnode {
794                        identity_key: identity.to_string(),
795                        owner: Addr::unchecked(format!("dummy{}", id)),
796                        proxy: None,
797                        unbonding_height: 123,
798                    },
799                )
800                .unwrap();
801        }
802
803        #[test]
804        fn obeys_limits() {
805            let mut deps = test_helpers::init_contract();
806            let _env = mock_env();
807            let rng = test_helpers::test_rng();
808            let limit = 2;
809            let identity = "foomp123";
810
811            test_helpers::add_dummy_unbonded_mixnodes_with_identity(
812                rng,
813                deps.as_mut(),
814                identity,
815                1000,
816            );
817            let page1 = query_unbonded_mixnodes_by_identity_paged(
818                deps.as_ref(),
819                identity.into(),
820                None,
821                Some(limit),
822            )
823            .unwrap();
824            assert_eq!(limit, page1.nodes.len() as u32);
825        }
826
827        #[test]
828        fn has_default_limit() {
829            let mut deps = test_helpers::init_contract();
830            let _env = mock_env();
831            let rng = test_helpers::test_rng();
832            let identity = "foomp123";
833            test_helpers::add_dummy_unbonded_mixnodes_with_identity(
834                rng,
835                deps.as_mut(),
836                identity,
837                1000,
838            );
839
840            // query without explicitly setting a limit
841            let page1 = query_unbonded_mixnodes_by_identity_paged(
842                deps.as_ref(),
843                identity.into(),
844                None,
845                None,
846            )
847            .unwrap();
848
849            assert_eq!(
850                UNBONDED_MIXNODES_DEFAULT_RETRIEVAL_LIMIT,
851                page1.nodes.len() as u32
852            );
853        }
854
855        #[test]
856        fn has_max_limit() {
857            let mut deps = test_helpers::init_contract();
858            let _env = mock_env();
859            let rng = test_helpers::test_rng();
860            let identity = "foomp123";
861            test_helpers::add_dummy_unbonded_mixnodes_with_identity(
862                rng,
863                deps.as_mut(),
864                identity,
865                1000,
866            );
867
868            // query with a crazily high limit in an attempt to use too many resources
869            let crazy_limit = 1000;
870            let page1 = query_unbonded_mixnodes_by_identity_paged(
871                deps.as_ref(),
872                identity.into(),
873                None,
874                Some(crazy_limit),
875            )
876            .unwrap();
877
878            // we default to a decent sized upper bound instead
879            assert_eq!(
880                UNBONDED_MIXNODES_MAX_RETRIEVAL_LIMIT,
881                page1.nodes.len() as u32
882            );
883        }
884
885        #[test]
886        fn pagination_works() {
887            // as we add mixnodes, we're always inserting them in ascending manner due to monotonically increasing id
888            let mut deps = test_helpers::init_contract();
889            let identity = "foomp123";
890
891            add_unbonded_with_identity(deps.as_mut().storage, 1, identity);
892
893            let per_page = 2;
894            let page1 = query_unbonded_mixnodes_by_identity_paged(
895                deps.as_ref(),
896                identity.into(),
897                None,
898                Some(per_page),
899            )
900            .unwrap();
901
902            // page should have 1 result on it
903            assert_eq!(1, page1.nodes.len());
904
905            // save another
906            add_unbonded_with_identity(deps.as_mut().storage, 2, identity);
907
908            // page1 should have 2 results on it
909            let page1 = query_unbonded_mixnodes_by_identity_paged(
910                deps.as_ref(),
911                identity.into(),
912                None,
913                Some(per_page),
914            )
915            .unwrap();
916            assert_eq!(2, page1.nodes.len());
917
918            add_unbonded_with_identity(deps.as_mut().storage, 3, identity);
919
920            // page1 still has the same 2 results
921            let another_page1 = query_unbonded_mixnodes_by_identity_paged(
922                deps.as_ref(),
923                identity.into(),
924                None,
925                Some(per_page),
926            )
927            .unwrap();
928            assert_eq!(2, another_page1.nodes.len());
929            assert_eq!(page1, another_page1);
930
931            // retrieving the next page should start after the last key on this page
932            let start_after = page1.start_next_after.unwrap();
933            let page2 = query_unbonded_mixnodes_by_identity_paged(
934                deps.as_ref(),
935                identity.into(),
936                Some(start_after),
937                Some(per_page),
938            )
939            .unwrap();
940
941            assert_eq!(1, page2.nodes.len());
942
943            // save another one
944            add_unbonded_with_identity(deps.as_mut().storage, 4, identity);
945            let page2 = query_unbonded_mixnodes_by_identity_paged(
946                deps.as_ref(),
947                identity.into(),
948                Some(start_after),
949                Some(per_page),
950            )
951            .unwrap();
952
953            // now we have 2 pages, with 2 results on the second page
954            assert_eq!(2, page2.nodes.len());
955        }
956
957        #[test]
958        fn only_retrieves_nodes_with_specific_identity_key() {
959            let mut deps = test_helpers::init_contract();
960            let identity1 = "identity1";
961            let identity2 = "identity2";
962            let identity3 = "identity3";
963            let identity4 = "identity4";
964
965            add_unbonded_with_identity(deps.as_mut().storage, 1, identity1);
966            add_unbonded_with_identity(deps.as_mut().storage, 2, identity1);
967            add_unbonded_with_identity(deps.as_mut().storage, 3, identity2);
968            add_unbonded_with_identity(deps.as_mut().storage, 4, identity1);
969            add_unbonded_with_identity(deps.as_mut().storage, 5, identity3);
970            add_unbonded_with_identity(deps.as_mut().storage, 6, identity3);
971            add_unbonded_with_identity(deps.as_mut().storage, 7, identity4);
972            add_unbonded_with_identity(deps.as_mut().storage, 8, identity2);
973            add_unbonded_with_identity(deps.as_mut().storage, 9, identity1);
974            add_unbonded_with_identity(deps.as_mut().storage, 10, identity3);
975
976            let expected_ids1 = vec![1, 2, 4, 9];
977            let expected_ids2 = vec![3, 8];
978            let expected_ids3 = vec![5, 6, 10];
979            let expected_ids4 = vec![7];
980
981            let res1 = query_unbonded_mixnodes_by_identity_paged(
982                deps.as_ref(),
983                identity1.into(),
984                None,
985                None,
986            )
987            .unwrap()
988            .nodes
989            .into_iter()
990            .map(|r| r.0)
991            .collect::<Vec<_>>();
992            assert_eq!(res1, expected_ids1);
993
994            let res2 = query_unbonded_mixnodes_by_identity_paged(
995                deps.as_ref(),
996                identity2.into(),
997                None,
998                None,
999            )
1000            .unwrap()
1001            .nodes
1002            .into_iter()
1003            .map(|r| r.0)
1004            .collect::<Vec<_>>();
1005            assert_eq!(res2, expected_ids2);
1006
1007            let res3 = query_unbonded_mixnodes_by_identity_paged(
1008                deps.as_ref(),
1009                identity3.into(),
1010                None,
1011                None,
1012            )
1013            .unwrap()
1014            .nodes
1015            .into_iter()
1016            .map(|r| r.0)
1017            .collect::<Vec<_>>();
1018            assert_eq!(res3, expected_ids3);
1019
1020            let res4 = query_unbonded_mixnodes_by_identity_paged(
1021                deps.as_ref(),
1022                identity4.into(),
1023                None,
1024                None,
1025            )
1026            .unwrap()
1027            .nodes
1028            .into_iter()
1029            .map(|r| r.0)
1030            .collect::<Vec<_>>();
1031            assert_eq!(res4, expected_ids4);
1032
1033            let res5 = query_unbonded_mixnodes_by_owner_paged(
1034                deps.as_ref(),
1035                "doesnt-exist".into(),
1036                None,
1037                None,
1038            )
1039            .unwrap()
1040            .nodes
1041            .into_iter()
1042            .map(|r| r.0)
1043            .collect::<Vec<_>>();
1044            assert!(res5.is_empty());
1045        }
1046    }
1047
1048    #[test]
1049    fn query_for_owned_mixnode() {
1050        let mut test = TestSetup::new();
1051
1052        let address = "mix-owner".to_string();
1053
1054        // when it doesnt exist
1055        let res = query_owned_mixnode(test.deps(), address.clone()).unwrap();
1056        assert!(res.mixnode_details.is_none());
1057        assert_eq!(address, res.address);
1058
1059        // when it [fully] exists
1060        let id = test.add_dummy_mixnode(&address, None);
1061        let res = query_owned_mixnode(test.deps(), address.clone()).unwrap();
1062        let details = res.mixnode_details.unwrap();
1063        assert_eq!(address, details.bond_information.owner);
1064        assert_eq!(
1065            good_mixnode_pledge()[0],
1066            details.bond_information.original_pledge
1067        );
1068        assert_eq!(address, res.address);
1069
1070        // when it partially exists, i.e. case when the operator unbonded, but there are still some pending delegates
1071        // TODO: perhaps this should work slightly differently, to return the underlying mixnode rewarding?
1072
1073        // manually adjust delegation info as to indicate the rewarding information shouldnt get removed
1074        let mut rewarding_details = details.rewarding_details;
1075        rewarding_details.delegates = Decimal::raw(12345);
1076        rewarding_details.unique_delegations = 1;
1077        rewards_storage::MIXNODE_REWARDING
1078            .save(test.deps_mut().storage, id, &rewarding_details)
1079            .unwrap();
1080
1081        pending_events::unbond_mixnode(test.deps_mut(), &mock_env(), 123, id).unwrap();
1082        let res = query_owned_mixnode(test.deps(), address.clone()).unwrap();
1083        assert!(res.mixnode_details.is_none());
1084        assert_eq!(address, res.address);
1085    }
1086
1087    #[test]
1088    fn query_for_mixnode_details() {
1089        let mut test = TestSetup::new();
1090
1091        // no node under this id
1092        let res = query_mixnode_details(test.deps(), 42).unwrap();
1093        assert!(res.mixnode_details.is_none());
1094        assert_eq!(42, res.mix_id);
1095
1096        // it exists
1097        let mix_id = test.add_dummy_mixnode("foomp", None);
1098        let res = query_mixnode_details(test.deps(), mix_id).unwrap();
1099        let details = res.mixnode_details.unwrap();
1100        assert_eq!(mix_id, details.bond_information.mix_id);
1101        assert_eq!(
1102            good_mixnode_pledge()[0],
1103            details.bond_information.original_pledge
1104        );
1105        assert_eq!(mix_id, res.mix_id);
1106    }
1107
1108    #[test]
1109    fn query_for_mixnode_details_by_identity() {
1110        let mut test = TestSetup::new();
1111
1112        // no node under this identity
1113        let res = query_mixnode_details_by_identity(test.deps(), "foomp".into()).unwrap();
1114        assert!(res.is_none());
1115
1116        // it exists
1117        let mix_id = test.add_dummy_mixnode("owner", None);
1118        // this was already tested to be working : )
1119        let expected = query_mixnode_details(test.deps(), mix_id)
1120            .unwrap()
1121            .mixnode_details
1122            .unwrap();
1123        let mix_identity = expected.bond_information.identity();
1124
1125        let res = query_mixnode_details_by_identity(test.deps(), mix_identity.into()).unwrap();
1126        assert_eq!(expected, res.unwrap());
1127    }
1128
1129    #[test]
1130    fn query_for_mixnode_rewarding_details() {
1131        let mut test = TestSetup::new();
1132
1133        // no node under this id
1134        let res = query_mixnode_rewarding_details(test.deps(), 42).unwrap();
1135        assert!(res.rewarding_details.is_none());
1136        assert_eq!(42, res.mix_id);
1137
1138        let mix_id = test.add_dummy_mixnode("foomp", None);
1139        let res = query_mixnode_rewarding_details(test.deps(), mix_id).unwrap();
1140        let details = res.rewarding_details.unwrap();
1141        assert_eq!(
1142            fixtures::mix_node_cost_params_fixture(),
1143            details.cost_params
1144        );
1145        assert_eq!(mix_id, res.mix_id);
1146    }
1147
1148    #[test]
1149    fn query_for_unbonded_mixnode() {
1150        let mut test = TestSetup::new();
1151
1152        let sender = "mix-owner";
1153
1154        // no node under this id
1155        let res = query_unbonded_mixnode(test.deps(), 42).unwrap();
1156        assert!(res.unbonded_info.is_none());
1157        assert_eq!(42, res.mix_id);
1158
1159        // add and unbond the mixnode
1160        let mix_id = test.add_dummy_mixnode(sender, None);
1161        pending_events::unbond_mixnode(test.deps_mut(), &mock_env(), 123, mix_id).unwrap();
1162
1163        let res = query_unbonded_mixnode(test.deps(), mix_id).unwrap();
1164        assert_eq!(res.unbonded_info.unwrap().owner, sender);
1165        assert_eq!(mix_id, res.mix_id);
1166    }
1167
1168    #[test]
1169    fn query_for_stake_saturation() {
1170        let mut test = TestSetup::new();
1171
1172        // no node under this id
1173        let res = query_stake_saturation(test.deps(), 42).unwrap();
1174        assert!(res.current_saturation.is_none());
1175        assert!(res.uncapped_saturation.is_none());
1176        assert_eq!(42, res.mix_id);
1177
1178        let rewarding_params = rewards_storage::REWARDING_PARAMS
1179            .load(test.deps().storage)
1180            .unwrap();
1181        let saturation_point = rewarding_params.interval.stake_saturation_point;
1182
1183        let mix_id = test.add_dummy_mixnode("foomp", None);
1184
1185        // below saturation point
1186        // there's only the base pledge without any delegation
1187        let expected =
1188            Decimal::from_atomics(good_mixnode_pledge()[0].amount, 0).unwrap() / saturation_point;
1189        let res = query_stake_saturation(test.deps(), mix_id).unwrap();
1190        assert_eq!(expected, res.current_saturation.unwrap());
1191        assert_eq!(expected, res.uncapped_saturation.unwrap());
1192        assert_eq!(mix_id, res.mix_id);
1193
1194        // exactly at saturation point
1195        let mut mix_rewarding = rewards_storage::MIXNODE_REWARDING
1196            .load(test.deps().storage, mix_id)
1197            .unwrap();
1198        mix_rewarding.operator = saturation_point;
1199        rewards_storage::MIXNODE_REWARDING
1200            .save(test.deps_mut().storage, mix_id, &mix_rewarding)
1201            .unwrap();
1202
1203        let res = query_stake_saturation(test.deps(), mix_id).unwrap();
1204        assert_eq!(Decimal::one(), res.current_saturation.unwrap());
1205        assert_eq!(Decimal::one(), res.uncapped_saturation.unwrap());
1206        assert_eq!(mix_id, res.mix_id);
1207
1208        // above the saturation point
1209        let mut mix_rewarding = rewards_storage::MIXNODE_REWARDING
1210            .load(test.deps().storage, mix_id)
1211            .unwrap();
1212        mix_rewarding.delegates = mix_rewarding.operator * Decimal::percent(150);
1213        rewards_storage::MIXNODE_REWARDING
1214            .save(test.deps_mut().storage, mix_id, &mix_rewarding)
1215            .unwrap();
1216
1217        let res = query_stake_saturation(test.deps(), mix_id).unwrap();
1218        assert_eq!(Decimal::one(), res.current_saturation.unwrap());
1219        assert_eq!(Decimal::percent(250), res.uncapped_saturation.unwrap());
1220        assert_eq!(mix_id, res.mix_id);
1221    }
1222}