1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use std::collections::{HashMap, HashSet};

#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{to_json_binary, Binary, Deps, Env, Order, StdError, StdResult};
use cw_storage_plus::Bound;
use itertools::Itertools;
use neutron_sdk::bindings::query::NeutronQuery;

use astroport_governance::emissions_controller::consts::MAX_PAGE_LIMIT;
use astroport_governance::emissions_controller::hub::{
    QueryMsg, SimulateTuneResponse, UserInfoResponse,
};

use crate::error::ContractError;
use crate::state::{CONFIG, OUTPOSTS, POOLS_WHITELIST, TUNE_INFO, USER_INFO, VOTED_POOLS};
use crate::utils::simulate_tune;

/// Expose available contract queries.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps<NeutronQuery>, env: Env, msg: QueryMsg) -> Result<Binary, ContractError> {
    match msg {
        QueryMsg::UserInfo { user, timestamp } => {
            let block_time = env.block.time.seconds();
            let timestamp = timestamp.unwrap_or(block_time);
            let user_info = match timestamp {
                timestamp if timestamp == block_time => USER_INFO.may_load(deps.storage, &user),
                timestamp => USER_INFO.may_load_at_height(deps.storage, &user, timestamp),
            }?
            .unwrap_or_default();

            let applied_votes = user_info
                .votes
                .iter()
                .filter_map(|(pool, weight)| {
                    let data = if timestamp == block_time {
                        VOTED_POOLS.may_load(deps.storage, pool)
                    } else {
                        VOTED_POOLS.may_load_at_height(deps.storage, pool, timestamp)
                    };

                    match data {
                        Ok(Some(pool_info)) if pool_info.init_ts <= user_info.vote_ts => {
                            Some(Ok((pool.clone(), *weight)))
                        }
                        Err(err) => Some(Err(err)),
                        _ => None,
                    }
                })
                .try_collect()?;

            let response = UserInfoResponse {
                vote_ts: user_info.vote_ts,
                voting_power: user_info.voting_power,
                votes: user_info.votes,
                applied_votes,
            };

            Ok(to_json_binary(&response)?)
        }
        QueryMsg::TuneInfo { timestamp } => {
            let block_time = env.block.time.seconds();
            let timestamp = timestamp.unwrap_or(block_time);
            let tune_info = match timestamp {
                timestamp if timestamp == block_time => TUNE_INFO.may_load(deps.storage),
                timestamp => TUNE_INFO.may_load_at_height(deps.storage, timestamp),
            }?
            .ok_or_else(|| StdError::generic_err(format!("Tune info not found at {timestamp}")))?;
            Ok(to_json_binary(&tune_info)?)
        }
        QueryMsg::Config {} => Ok(to_json_binary(&CONFIG.load(deps.storage)?)?),
        QueryMsg::VotedPool { pool, timestamp } => {
            let block_time = env.block.time.seconds();
            let timestamp = timestamp.unwrap_or(block_time);
            let voted_pool = match timestamp {
                timestamp if timestamp == block_time => VOTED_POOLS.may_load(deps.storage, &pool),
                timestamp => VOTED_POOLS.may_load_at_height(deps.storage, &pool, timestamp),
            }?
            .ok_or_else(|| StdError::generic_err(format!("Voted pool not found at {timestamp}")))?;
            Ok(to_json_binary(&voted_pool)?)
        }
        QueryMsg::VotedPools { limit, start_after } => {
            let limit = limit.unwrap_or(MAX_PAGE_LIMIT) as usize;
            let voted_pools = VOTED_POOLS
                .range(
                    deps.storage,
                    start_after.as_ref().map(|s| Bound::exclusive(s.as_str())),
                    None,
                    Order::Ascending,
                )
                .take(limit)
                .collect::<StdResult<Vec<_>>>()?;
            Ok(to_json_binary(&voted_pools)?)
        }
        QueryMsg::ListOutposts {} => {
            let outposts = OUTPOSTS
                .range(deps.storage, None, None, Order::Ascending)
                .collect::<StdResult<Vec<_>>>()?;
            Ok(to_json_binary(&outposts)?)
        }
        QueryMsg::QueryWhitelist { limit, start_after } => {
            let limit = limit.unwrap_or(MAX_PAGE_LIMIT) as usize;
            let pools_whitelist = POOLS_WHITELIST
                .load(deps.storage)?
                .into_iter()
                .skip_while(|pool| {
                    if let Some(start_after) = &start_after {
                        pool != start_after
                    } else {
                        false
                    }
                })
                .take(limit)
                .collect_vec();

            let pools_whitelist = if start_after.is_some() {
                &pools_whitelist[1..]
            } else {
                &pools_whitelist
            };

            Ok(to_json_binary(pools_whitelist)?)
        }
        QueryMsg::SimulateTune {} => {
            let deps = deps.into_empty();

            let voted_pools = VOTED_POOLS
                .keys(deps.storage, None, None, Order::Ascending)
                .collect::<StdResult<HashSet<_>>>()?;
            let outposts = OUTPOSTS
                .range(deps.storage, None, None, Order::Ascending)
                .collect::<StdResult<HashMap<_, _>>>()?;
            let config = CONFIG.load(deps.storage)?;

            let tune_result = simulate_tune(
                deps,
                &voted_pools,
                &outposts,
                env.block.time.seconds(),
                &config,
            )?;
            Ok(to_json_binary(&SimulateTuneResponse {
                new_emissions_state: tune_result.new_emissions_state,
                next_pools_grouped: tune_result.next_pools_grouped,
            })?)
        }
    }
}