astroport_oracle/
contract.rs

1use crate::error::ContractError;
2use crate::migration::PRICE_LAST_V100;
3use crate::querier::{query_cumulative_prices, query_prices};
4use crate::state::{
5    get_precision, store_precisions, Config, PriceCumulativeLast, CONFIG, PRICE_LAST,
6};
7use astroport::asset::{Asset, AssetInfo};
8use astroport::oracle::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
9use astroport::pair::TWAP_PRECISION;
10use astroport::querier::query_pair_info;
11
12use cosmwasm_std::{
13    entry_point, to_json_binary, Binary, Decimal256, Deps, DepsMut, Env, MessageInfo, Response,
14    StdError, StdResult, Uint128, Uint256,
15};
16use cw2::{get_contract_version, set_contract_version};
17
18/// Contract name that is used for migration.
19const CONTRACT_NAME: &str = "astroport-oracle";
20/// Contract version that is used for migration.
21const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
22
23/// Time between two consecutive TWAP updates.
24pub const PERIOD: u64 = 86400;
25
26/// Creates a new contract with the specified parameters in the [`InstantiateMsg`].
27#[cfg_attr(not(feature = "library"), entry_point)]
28pub fn instantiate(
29    mut deps: DepsMut,
30    env: Env,
31    info: MessageInfo,
32    msg: InstantiateMsg,
33) -> Result<Response, ContractError> {
34    let factory_contract = deps.api.addr_validate(&msg.factory_contract)?;
35
36    for asset_info in &msg.asset_infos {
37        asset_info.check(deps.api)?;
38        store_precisions(deps.branch(), asset_info, &factory_contract)?;
39    }
40
41    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
42    let pair_info = query_pair_info(&deps.querier, &factory_contract, &msg.asset_infos)?;
43
44    let config = Config {
45        owner: info.sender,
46        factory: factory_contract,
47        asset_infos: msg.asset_infos,
48        pair: pair_info.clone(),
49    };
50    CONFIG.save(deps.storage, &config)?;
51
52    let prices = query_cumulative_prices(deps.querier, pair_info.contract_addr)?;
53    let average_prices = prices
54        .cumulative_prices
55        .iter()
56        .cloned()
57        .map(|(from, to, _)| (from, to, Decimal256::zero()))
58        .collect();
59
60    let price = PriceCumulativeLast {
61        cumulative_prices: prices.cumulative_prices,
62        average_prices,
63        block_timestamp_last: env.block.time.seconds(),
64    };
65    PRICE_LAST.save(deps.storage, &price)?;
66
67    Ok(Response::default())
68}
69
70/// Exposes all the execute functions available in the contract.
71///
72/// ## Variants
73/// * **ExecuteMsg::Update {}** Updates the local TWAP values for the assets in the Astroport pool.
74#[cfg_attr(not(feature = "library"), entry_point)]
75pub fn execute(
76    deps: DepsMut,
77    env: Env,
78    _info: MessageInfo,
79    msg: ExecuteMsg,
80) -> Result<Response, ContractError> {
81    match msg {
82        ExecuteMsg::Update {} => update(deps, env),
83    }
84}
85
86/// Updates the local TWAP values for the tokens in the target Astroport pool.
87pub fn update(deps: DepsMut, env: Env) -> Result<Response, ContractError> {
88    let config = CONFIG.load(deps.storage)?;
89    let price_last = PRICE_LAST.load(deps.storage)?;
90
91    let prices = query_cumulative_prices(deps.querier, config.pair.contract_addr)?;
92    let time_elapsed = env.block.time.seconds() - price_last.block_timestamp_last;
93
94    // Ensure that at least one full period has passed since the last update
95    if time_elapsed < PERIOD {
96        return Err(ContractError::WrongPeriod {});
97    }
98
99    let mut average_prices = vec![];
100    for (asset1_last, asset2_last, price_last) in price_last.cumulative_prices.iter() {
101        for (asset1, asset2, price) in prices.cumulative_prices.iter() {
102            if asset1.equal(asset1_last) && asset2.equal(asset2_last) {
103                average_prices.push((
104                    asset1.clone(),
105                    asset2.clone(),
106                    Decimal256::from_ratio(
107                        Uint256::from(price.wrapping_sub(*price_last)),
108                        time_elapsed,
109                    ),
110                ));
111            }
112        }
113    }
114
115    let prices = PriceCumulativeLast {
116        cumulative_prices: prices.cumulative_prices,
117        average_prices,
118        block_timestamp_last: env.block.time.seconds(),
119    };
120    PRICE_LAST.save(deps.storage, &prices)?;
121    Ok(Response::default())
122}
123
124/// Exposes all the queries available in the contract.
125///
126/// ## Queries
127/// * **QueryMsg::Consult { token, amount }** Validates assets and calculates a new average
128/// amount with updated precision
129#[cfg_attr(not(feature = "library"), entry_point)]
130pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
131    match msg {
132        QueryMsg::Consult { token, amount } => to_json_binary(&consult(deps, token, amount)?),
133    }
134}
135
136/// Multiplies a token amount by its latest TWAP value.
137/// * **token** token for which we multiply its TWAP value by an amount.
138///
139/// * **amount** amount of tokens we multiply the TWAP by.
140fn consult(
141    deps: Deps,
142    token: AssetInfo,
143    amount: Uint128,
144) -> Result<Vec<(AssetInfo, Uint256)>, StdError> {
145    let config = CONFIG.load(deps.storage)?;
146    let price_last = PRICE_LAST.load(deps.storage)?;
147
148    let mut average_prices = vec![];
149    for (from, to, value) in price_last.average_prices {
150        if from.equal(&token) {
151            average_prices.push((to, value));
152        }
153    }
154
155    if average_prices.is_empty() {
156        return Err(StdError::generic_err("Invalid Token"));
157    }
158
159    // Get the token's precision
160    let p = get_precision(deps.storage, &token)?;
161    let one = Uint128::new(10_u128.pow(p.into()));
162
163    average_prices
164        .iter()
165        .map(|(asset, price_average)| {
166            if price_average.is_zero() {
167                let price = query_prices(
168                    deps.querier,
169                    config.pair.contract_addr.clone(),
170                    Asset {
171                        info: token.clone(),
172                        amount: one,
173                    },
174                    Some(asset.clone()),
175                )?
176                .return_amount;
177                Ok((
178                    asset.clone(),
179                    Uint256::from(price).multiply_ratio(Uint256::from(amount), Uint256::from(one)),
180                ))
181            } else {
182                let price_precision = Uint256::from(10_u128.pow(TWAP_PRECISION.into()));
183                Ok((
184                    asset.clone(),
185                    Uint256::from(amount) * *price_average / price_precision,
186                ))
187            }
188        })
189        .collect::<Result<Vec<(AssetInfo, Uint256)>, StdError>>()
190}
191
192/// Manages the contract migration.
193#[cfg_attr(not(feature = "library"), entry_point)]
194pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
195    let contract_version = get_contract_version(deps.storage)?;
196
197    match contract_version.contract.as_ref() {
198        "astroport-oracle" => match contract_version.version.as_ref() {
199            "1.0.0" | "1.0.1" | "1.0.2" => {
200                let config = CONFIG.load(deps.storage)?;
201                let price_last_v100 = PRICE_LAST_V100.load(deps.storage)?;
202
203                let cumulative_prices = vec![
204                    (
205                        config.asset_infos[0].clone(),
206                        config.asset_infos[1].clone(),
207                        price_last_v100.price0_cumulative_last,
208                    ),
209                    (
210                        config.asset_infos[1].clone(),
211                        config.asset_infos[0].clone(),
212                        price_last_v100.price1_cumulative_last,
213                    ),
214                ];
215                let average_prices = vec![
216                    (
217                        config.asset_infos[0].clone(),
218                        config.asset_infos[1].clone(),
219                        price_last_v100.price_0_average,
220                    ),
221                    (
222                        config.asset_infos[1].clone(),
223                        config.asset_infos[0].clone(),
224                        price_last_v100.price_1_average,
225                    ),
226                ];
227
228                PRICE_LAST.save(
229                    deps.storage,
230                    &PriceCumulativeLast {
231                        cumulative_prices,
232                        average_prices,
233                        block_timestamp_last: price_last_v100.block_timestamp_last,
234                    },
235                )?;
236                for asset_info in &config.asset_infos {
237                    store_precisions(deps.branch(), asset_info, &config.factory)?;
238                }
239            }
240            _ => return Err(ContractError::MigrationError {}),
241        },
242        _ => return Err(ContractError::MigrationError {}),
243    }
244
245    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
246
247    Ok(Response::new()
248        .add_attribute("previous_contract_name", &contract_version.contract)
249        .add_attribute("previous_contract_version", &contract_version.version)
250        .add_attribute("new_contract_name", CONTRACT_NAME)
251        .add_attribute("new_contract_version", CONTRACT_VERSION))
252}