mars_core/
oracle.rs

1use std::fmt;
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use cosmwasm_std::{Addr, Api, StdResult, Uint128};
7
8use crate::math::decimal::Decimal;
9
10#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
11#[serde(rename_all = "snake_case")]
12pub enum PriceSource<A> {
13    /// Returns a fixed value; used for UST
14    Fixed { price: Decimal },
15    /// Native Terra stablecoins transaction rate quoted in UST
16    Native { denom: String },
17    /// Astroport spot price quoted in UST
18    ///
19    /// NOTE: `pair_address` must point to an astroport pair consists of the asset of intereset and UST
20    AstroportSpot {
21        /// Address of the Astroport pair
22        pair_address: A,
23    },
24    /// Astroport TWAP price quoted in UST
25    ///
26    /// NOTE: `pair_address` must point to an astroport pair consists of the asset of intereset and UST
27    AstroportTwap {
28        /// Address of the Astroport pair
29        pair_address: A,
30        /// Address of the asset of interest
31        ///
32        /// NOTE: Spot price in intended for CW20 tokens. Terra native tokens should use Fixed or
33        /// Native price sources.
34        window_size: u64,
35        /// When calculating averaged price, we take the most recent TWAP snapshot and find a second
36        /// snapshot in the range of window_size +/- tolerance. For example, if window size is 5 minutes
37        /// and tolerance is 1 minute, we look for snapshots that are 4 - 6 minutes back in time from
38        /// the most recent snapshot.
39        ///
40        /// If there are multiple snapshots within the range, we take the one that is closest to the
41        /// desired window size.
42        tolerance: u64,
43    },
44    /// Astroport liquidity token
45    ///
46    /// NOTE: Astroport's pair contract does not have a query command to check the address of the LP
47    /// token associated with a pair. Therefore, we can't implement relevant checks in the contract.
48    /// The owner must make sure the addresses supplied are accurate
49    AstroportLiquidityToken {
50        /// Address of the asset of interest
51        pair_address: A,
52    },
53}
54
55impl<A> fmt::Display for PriceSource<A> {
56    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
57        let label = match self {
58            PriceSource::Fixed { .. } => "fixed",
59            PriceSource::Native { .. } => "native",
60            PriceSource::AstroportSpot { .. } => "astroport_spot",
61            PriceSource::AstroportTwap { .. } => "astroport_twap",
62            PriceSource::AstroportLiquidityToken { .. } => "astroport_liquidity_token",
63        };
64        write!(f, "{}", label)
65    }
66}
67
68pub type PriceSourceUnchecked = PriceSource<String>;
69pub type PriceSourceChecked = PriceSource<Addr>;
70
71impl PriceSourceUnchecked {
72    pub fn to_checked(&self, api: &dyn Api) -> StdResult<PriceSourceChecked> {
73        Ok(match self {
74            PriceSourceUnchecked::Fixed { price } => PriceSourceChecked::Fixed { price: *price },
75            PriceSourceUnchecked::Native { denom } => PriceSourceChecked::Native {
76                denom: denom.clone(),
77            },
78            PriceSourceUnchecked::AstroportSpot { pair_address } => {
79                PriceSourceChecked::AstroportSpot {
80                    pair_address: api.addr_validate(pair_address)?,
81                }
82            }
83            PriceSourceUnchecked::AstroportTwap {
84                pair_address,
85                window_size,
86                tolerance,
87            } => PriceSourceChecked::AstroportTwap {
88                pair_address: api.addr_validate(pair_address)?,
89                window_size: *window_size,
90                tolerance: *tolerance,
91            },
92            PriceSourceUnchecked::AstroportLiquidityToken { pair_address } => {
93                PriceSourceChecked::AstroportLiquidityToken {
94                    pair_address: api.addr_validate(pair_address)?,
95                }
96            }
97        })
98    }
99}
100
101/// Contract global configuration
102#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
103pub struct Config {
104    pub owner: Addr,
105}
106
107#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
108pub struct AstroportTwapSnapshot {
109    /// Timestamp of the most recent TWAP data update
110    pub timestamp: u64,
111    /// Cumulative price of the asset retrieved by the most recent TWAP data update
112    pub price_cumulative: Uint128,
113}
114
115pub mod msg {
116    use schemars::JsonSchema;
117    use serde::{Deserialize, Serialize};
118
119    use super::PriceSourceUnchecked;
120    use crate::asset::Asset;
121
122    #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
123    pub struct InstantiateMsg {
124        pub owner: String,
125    }
126
127    #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
128    #[serde(rename_all = "snake_case")]
129    pub enum ExecuteMsg {
130        /// Update contract config
131        UpdateConfig { owner: Option<String> },
132        /// Specify parameters to query asset price
133        SetAsset {
134            asset: Asset,
135            price_source: PriceSourceUnchecked,
136        },
137        /// Fetch cumulative prices from Astroport pairs and record in contract storage
138        RecordTwapSnapshots { assets: Vec<Asset> },
139    }
140
141    #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
142    #[serde(rename_all = "snake_case")]
143    pub enum QueryMsg {
144        /// Query contract config. Returns `Config`
145        Config {},
146        /// Get asset's price source. Returns `AssetConfigChecked`
147        AssetPriceSource { asset: Asset },
148        /// Query asset price given an asset; returns `mars_core::math::decimal::Decimal`
149        AssetPrice { asset: Asset },
150        /// Query asset price given it's internal reference; returns `mars_core::math::decimal::Decimal`
151        ///
152        /// NOTE: meant to be used by protocol contracts only
153        AssetPriceByReference { asset_reference: Vec<u8> },
154    }
155}
156
157pub mod helpers {
158    use cosmwasm_std::{to_binary, Addr, QuerierWrapper, QueryRequest, StdResult, WasmQuery};
159
160    use crate::asset::AssetType;
161    use crate::math::decimal::Decimal;
162
163    use super::msg::QueryMsg;
164
165    pub fn query_price(
166        querier: QuerierWrapper,
167        oracle_address: Addr,
168        asset_label: &str,
169        asset_reference: Vec<u8>,
170        asset_type: AssetType,
171    ) -> StdResult<Decimal> {
172        // For UST, we skip the query and just return 1 to save gas
173        if asset_type == AssetType::Native && asset_label == "uusd" {
174            Ok(Decimal::one())
175        } else {
176            querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
177                contract_addr: oracle_address.into(),
178                msg: to_binary(&QueryMsg::AssetPriceByReference { asset_reference })?,
179            }))
180        }
181    }
182}