cw_utils/
pagination.rs

1use cosmwasm_std::{Addr, Api, CanonicalAddr, StdResult};
2
3// this is used for pagination. Maybe we move it into the std lib one day?
4pub fn maybe_canonical(api: &dyn Api, human: Option<Addr>) -> StdResult<Option<CanonicalAddr>> {
5    human.map(|x| api.addr_canonicalize(x.as_ref())).transpose()
6}
7
8// This is used for pagination. Maybe we move it into the std lib one day?
9pub fn maybe_addr(api: &dyn Api, human: Option<String>) -> StdResult<Option<Addr>> {
10    human.map(|x| api.addr_validate(&x)).transpose()
11}
12
13// this will set the first key after the provided key, by appending a 0 byte
14pub fn calc_range_start(start_after: Option<Addr>) -> Option<Vec<u8>> {
15    start_after.map(|addr| {
16        let mut v: Vec<u8> = addr.as_bytes().into();
17        v.push(0);
18        v
19    })
20}
21
22// set the end to the canonicalized format (used for Order::Descending)
23pub fn calc_range_end(end_before: Option<Addr>) -> Option<Vec<u8>> {
24    end_before.map(|addr| addr.as_bytes().into())
25}
26
27// this will set the first key after the provided key, by appending a 0 byte
28pub fn calc_range_start_string(start_after: Option<String>) -> Option<Vec<u8>> {
29    start_after.map(|token_id| {
30        let mut v: Vec<u8> = token_id.into_bytes();
31        v.push(0);
32        v
33    })
34}
35
36#[cfg(test)]
37mod test {
38    use super::*;
39    use cosmwasm_std::{testing::mock_dependencies, Order};
40    use cw_storage_plus::{Bound, Map};
41
42    pub const HOLDERS: Map<&Addr, usize> = Map::new("some_data");
43    const LIMIT: usize = 30;
44
45    fn addr_from_i(i: usize) -> Addr {
46        Addr::unchecked(format!("addr{:0>8}", i))
47    }
48
49    #[test]
50    fn calc_range_start_works_as_expected() {
51        let total_elements_count = 100;
52        let mut deps = mock_dependencies();
53        for i in 0..total_elements_count {
54            let holder = (addr_from_i(i), i);
55            HOLDERS
56                .save(&mut deps.storage, &holder.0, &holder.1)
57                .unwrap();
58        }
59
60        for j in 0..4 {
61            let start_after = if j == 0 {
62                None
63            } else {
64                Some(addr_from_i(j * LIMIT - 1))
65            };
66
67            let start = calc_range_start(start_after).map(Bound::ExclusiveRaw);
68
69            let holders = HOLDERS
70                .keys(&deps.storage, start, None, Order::Ascending)
71                .take(LIMIT)
72                .collect::<StdResult<Vec<_>>>()
73                .unwrap();
74
75            for (i, holder) in holders.into_iter().enumerate() {
76                let global_index = j * LIMIT + i;
77                assert_eq!(holder, addr_from_i(global_index));
78            }
79        }
80    }
81
82    #[test]
83    fn calc_range_end_works_as_expected() {
84        let total_elements_count = 100;
85        let mut deps = mock_dependencies();
86        for i in 0..total_elements_count {
87            let holder = (addr_from_i(i), i);
88            HOLDERS
89                .save(&mut deps.storage, &holder.0, &holder.1)
90                .unwrap();
91        }
92
93        for j in 0..4 {
94            let end_before = Some(addr_from_i(total_elements_count - j * LIMIT));
95
96            let end = calc_range_end(end_before).map(Bound::ExclusiveRaw);
97
98            let holders = HOLDERS
99                .keys(&deps.storage, None, end, Order::Descending)
100                .take(LIMIT)
101                .collect::<StdResult<Vec<_>>>()
102                .unwrap();
103
104            for (i, holder) in holders.into_iter().enumerate() {
105                let global_index = total_elements_count - i - j * LIMIT - 1;
106                assert_eq!(holder, addr_from_i(global_index));
107            }
108        }
109    }
110
111    // TODO: add unit tests
112    #[ignore]
113    #[test]
114    fn add_more_tests() {}
115}