abstract_core/objects/
oracle.rs

1use std::collections::HashSet;
2
3use cosmwasm_schema::cw_serde;
4use cosmwasm_std::{Addr, Deps, DepsMut, Order, StdError, StdResult, Uint128};
5use cw_asset::{Asset, AssetInfo};
6use cw_storage_plus::{Bound, Map};
7
8use super::{
9    ans_host::AnsHost,
10    price_source::{AssetConversion, PriceSource, UncheckedPriceSource},
11    AssetEntry,
12};
13use crate::AbstractResult;
14
15pub type Complexity = u8;
16
17pub const LIST_SIZE_LIMIT: u8 = 15;
18const DEFAULT_PAGE_LIMIT: u8 = 5;
19
20/// Struct for calculating asset prices/values for a smart contract.
21pub struct Oracle<'a> {
22    /// map of human-readable asset names to their human-readable price source
23    pub config: Map<'static, &'a AssetEntry, UncheckedPriceSource>,
24    /// Assets map to get the complexity and value calculation of an asset.
25    assets: Map<'static, &'a AssetInfo, (PriceSource, Complexity)>,
26    /// Complexity rating used for efficient total value calculation
27    /// Vec > HashSet because it's faster for small sets
28    complexity: Map<'static, Complexity, Vec<AssetInfo>>,
29    /// Cache of asset values for efficient total value calculation
30    /// the amount set for an asset will be added to its balance.
31    /// Vec instead of HashMap because it's faster for small sets + AssetInfo does not implement `Hash`!
32    asset_equivalent_cache: Vec<(AssetInfo, Vec<(AssetInfo, Uint128)>)>,
33}
34
35impl<'a> Oracle<'a> {
36    pub const fn new() -> Self {
37        Oracle {
38            config: Map::new("oracle_config"),
39            assets: Map::new("assets"),
40            complexity: Map::new("complexity"),
41            asset_equivalent_cache: Vec::new(),
42        }
43    }
44
45    /// Updates the assets in the Oracle.
46    /// First adds the provided assets to the oracle, then removes the provided assets from the oracle.
47    pub fn update_assets(
48        &self,
49        mut deps: DepsMut,
50        ans: &AnsHost,
51        to_add: Vec<(AssetEntry, UncheckedPriceSource)>,
52        to_remove: Vec<AssetEntry>,
53    ) -> AbstractResult<()> {
54        let current_vault_size = self
55            .config
56            .keys(deps.storage, None, None, Order::Ascending)
57            .count();
58        let delta: i128 = to_add.len() as i128 - to_remove.len() as i128;
59        if current_vault_size as i128 + delta > LIST_SIZE_LIMIT as i128 {
60            return Err(crate::AbstractError::Std(StdError::generic_err(
61                "Oracle list size limit exceeded",
62            )));
63        }
64
65        let mut all: Vec<AssetEntry> = to_add
66            .iter()
67            .map(|(a, _)| a.clone())
68            .collect::<Vec<AssetEntry>>();
69        all.extend(to_remove.clone());
70        all.dedup();
71        if all.len() != to_add.len() + to_remove.len() {
72            return Err(crate::AbstractError::Std(StdError::generic_err(
73                "Duplicate assets in update",
74            )));
75        }
76
77        // add assets to oracle
78        self.add_assets(deps.branch(), ans, to_add)?;
79        // remove assets from oracle
80        self.remove_assets(deps.branch(), ans, to_remove)?;
81        // validate the oracle configuration
82        // Each asset must have a valid price source
83        // and there can only be one base asset.
84        self.validate(deps.as_ref())
85    }
86
87    /// Adds assets to the oracle
88    fn add_assets(
89        &self,
90        deps: DepsMut,
91        ans: &AnsHost,
92        assets: Vec<(AssetEntry, UncheckedPriceSource)>,
93    ) -> AbstractResult<()> {
94        // optimistically update config
95        // configuration check happens after all updates have been done.
96        for (key, data) in assets.iter() {
97            self.config.save(deps.storage, key, data)?;
98        }
99
100        let (assets, price_sources): (Vec<AssetEntry>, Vec<_>) = assets.into_iter().unzip();
101        let resolved_assets = ans.query_assets(&deps.querier, &assets)?;
102
103        let checked_price_sources = price_sources
104            .into_iter()
105            .enumerate()
106            .map(|(ix, price_source)| price_source.check(deps.as_ref(), ans, &assets[ix]))
107            .collect::<Result<Vec<PriceSource>, _>>()?;
108
109        let assets_and_sources = resolved_assets
110            .into_iter()
111            .zip(checked_price_sources)
112            .collect::<Vec<_>>();
113
114        // Now that we validated the input, assign a complexity to them and add them to the oracle
115
116        // Register asset
117        // Registration is expected to be done in increasing complexity
118        // So this will fail if a dependent asset is not registered first.
119        for (asset, price_source) in assets_and_sources {
120            // Get dependencies for this price source
121            let dependencies = price_source.dependencies(&asset);
122            self.assert_dependencies_exists(deps.as_ref(), &dependencies)?;
123            // get the complexity of the dependencies
124            // depending on the type of price source, the complexity is calculated differently
125            let complexity = self.asset_complexity(deps.as_ref(), &price_source, &dependencies)?;
126            // Add asset to complexity level
127            self.complexity.update(deps.storage, complexity, |v| {
128                let mut v = v.unwrap_or_default();
129                if v.contains(&asset) {
130                    return Err(StdError::generic_err(format!(
131                        "Asset {asset} already registered"
132                    )));
133                }
134                v.push(asset.clone());
135                Result::<_, StdError>::Ok(v)
136            })?;
137            self.assets.update(deps.storage, &asset, |v| {
138                if v.is_some() {
139                    return Err(StdError::generic_err(format!(
140                        "asset {asset} already registered"
141                    )));
142                }
143                Ok((price_source, complexity))
144            })?;
145        }
146
147        Ok(())
148    }
149
150    /// Removes assets from the oracle
151    fn remove_assets(
152        &self,
153        deps: DepsMut,
154        ans: &AnsHost,
155        assets: Vec<AssetEntry>,
156    ) -> AbstractResult<()> {
157        for asset in assets {
158            // assert asset was in config
159            if !self.config.has(deps.storage, &asset) {
160                return Err(StdError::generic_err(format!(
161                    "Asset {asset} not registered on oracle"
162                ))
163                .into());
164            }
165            // remove from config
166            self.config.remove(deps.storage, &asset);
167            // get its asset information
168            let asset = ans.query_asset(&deps.querier, &asset)?;
169            // get its complexity
170            let (_, complexity) = self.assets.load(deps.storage, &asset)?;
171            // remove from assets
172            self.assets.remove(deps.storage, &asset);
173            // remove from complexity level
174            self.complexity.update(deps.storage, complexity, |v| {
175                let mut v = v.unwrap_or_default();
176                v.retain(|a| a != &asset);
177                Result::<_, StdError>::Ok(v)
178            })?;
179        }
180        Ok(())
181    }
182
183    /// Returns the complexity of an asset
184    // Complexity logic:
185    // base: 0
186    // Pair: paired asset + 1
187    // LP: highest in pool + 1
188    // ValueAs: equal asset + 1
189    fn asset_complexity(
190        &self,
191        deps: Deps,
192        price_source: &PriceSource,
193        dependencies: &[AssetInfo],
194    ) -> AbstractResult<Complexity> {
195        match price_source {
196            PriceSource::None => Ok(0),
197            PriceSource::Pool { .. } => {
198                let compl = self.assets.load(deps.storage, &dependencies[0])?.1;
199                Ok(compl + 1)
200            }
201            PriceSource::LiquidityToken { .. } => {
202                let mut max = 0;
203                for dependency in dependencies {
204                    let (_, complexity) = self.assets.load(deps.storage, dependency)?;
205                    if complexity > max {
206                        max = complexity;
207                    }
208                }
209                Ok(max + 1)
210            }
211            PriceSource::ValueAs { asset, .. } => {
212                let (_, complexity) = self.assets.load(deps.storage, asset)?;
213                Ok(complexity + 1)
214            }
215        }
216    }
217
218    /// Calculates the value of a single asset by recursive conversion to underlying asset(s).
219    /// Does not make use of the cache to prevent querying the same price source multiple times.
220    pub fn asset_value(&self, deps: Deps, asset: Asset) -> AbstractResult<Uint128> {
221        // get the price source for the asset
222        let (price_source, _) = self.assets.load(deps.storage, &asset.info)?;
223        // get the conversions for this asset
224        let conversion_rates = price_source.conversion_rates(deps, &asset.info)?;
225        if conversion_rates.is_empty() {
226            // no conversion rates means this is the base asset, return the amount
227            return Ok(asset.amount);
228        }
229        // convert the asset into its underlying assets using the conversions
230        let converted_assets = AssetConversion::convert(&conversion_rates, asset.amount);
231        // recursively calculate the value of the underlying assets
232        converted_assets
233            .into_iter()
234            .map(|a| self.asset_value(deps, a))
235            .sum()
236    }
237
238    /// Calculates the total value of an account's assets by efficiently querying the configured price sources
239    ///
240    ///
241    /// ## Resolve the total value of an account given a base asset.
242    /// This process goes as follows
243    /// 1. Get the assets for the highest, not visited, complexity.
244    /// 2. For each asset query it's balance, get the conversion ratios associated with that asset and load its cached values.
245    /// 3. Using the conversion ratio convert the balance and cached values and save the resulting values in the cache for that lower complexity asset.
246    /// 4. Repeat until the base asset is reached. (complexity = 0)
247    pub fn account_value(&mut self, deps: Deps, account: &Addr) -> AbstractResult<AccountValue> {
248        // get the highest complexity
249        let start_complexity = self.highest_complexity(deps)?;
250        eprintln!("start complexity: {start_complexity}");
251        self.complexity_value_calculation(deps, start_complexity, account)
252    }
253
254    /// Calculates the values of assets for a given complexity level
255    fn complexity_value_calculation(
256        &mut self,
257        deps: Deps,
258        complexity: u8,
259        account: &Addr,
260    ) -> AbstractResult<AccountValue> {
261        let assets = self.complexity.load(deps.storage, complexity)?;
262        for asset in assets {
263            let (price_source, _) = self.assets.load(deps.storage, &asset)?;
264            // get the balance for this asset
265            let balance = asset.query_balance(&deps.querier, account)?;
266            eprintln!("{asset}: {balance} ");
267            // and the cached balances
268            let mut cached_balances = self.cached_balance(&asset).unwrap_or_default();
269            eprintln!("cached: {cached_balances:?}");
270            // add the balance to the cached balances
271            cached_balances.push((asset.clone(), balance));
272
273            // get the conversion rates for this asset
274            let conversion_rates = price_source.conversion_rates(deps, &asset)?;
275            if conversion_rates.is_empty() {
276                // no conversion rates means this is the base asset, construct the account value and return
277                let total: u128 = cached_balances
278                    .iter()
279                    .map(|(_, amount)| amount.u128())
280                    .sum::<u128>();
281
282                return Ok(AccountValue {
283                    total_value: Asset::new(asset, total),
284                    breakdown: cached_balances,
285                });
286            }
287            // convert the balance and cached values to this asset using the conversion rates
288            self.update_cache(cached_balances, conversion_rates)?;
289        }
290        // call recursively for the next complexity level
291        self.complexity_value_calculation(deps, complexity - 1, account)
292    }
293
294    /// Get the cached balance for an asset
295    /// Removes from cache if present
296    fn cached_balance(&mut self, asset: &AssetInfo) -> Option<Vec<(AssetInfo, Uint128)>> {
297        let asset_pos = self
298            .asset_equivalent_cache
299            .iter()
300            .position(|(asset_info, _)| asset_info == asset);
301        asset_pos.map(|ix| self.asset_equivalent_cache.swap_remove(ix).1)
302    }
303
304    /// for each balance, convert it to the equivalent value in the target asset(s) of lower complexity
305    /// update the cache of these target assets to include the re-valued balance of the source asset
306    fn update_cache(
307        &mut self,
308        source_asset_balances: Vec<(AssetInfo, Uint128)>,
309        conversions: Vec<AssetConversion>,
310    ) -> AbstractResult<()> {
311        eprintln!("updating cache with source asset balances: {source_asset_balances:?}");
312        for (source_asset, balance) in source_asset_balances {
313            // these balances are the equivalent to the source asset, just in a different denomination
314            let target_assets_balances = AssetConversion::convert(&conversions, balance);
315            // update the cache with these balances
316            for Asset {
317                info: target_asset,
318                amount: balance,
319            } in target_assets_balances
320            {
321                let cache = self
322                    .asset_equivalent_cache
323                    .iter_mut()
324                    .find(|(a, _)| a == &target_asset);
325                if let Some((_, cache)) = cache {
326                    cache.push((source_asset.clone(), balance));
327                } else {
328                    self.asset_equivalent_cache
329                        .push((target_asset, vec![(source_asset.clone(), balance)]));
330                }
331            }
332        }
333        eprintln!("cache updated: {:?}", self.asset_equivalent_cache);
334        Ok(())
335    }
336
337    /// Checks that the oracle is configured correctly.
338    pub fn validate(&self, deps: Deps) -> AbstractResult<()> {
339        // no need to validate config as its assets are validated on add operations
340
341        // fist check that a base asset is registered
342        let base_asset = self.base_asset(deps)?;
343
344        // Then start with lowest complexity assets and keep track of all the encountered assets.
345        // If an asset has a dependency that is not in the list of encountered assets
346        // then the oracle is not configured correctly.
347        let mut encountered_assets: HashSet<String> = HashSet::from([base_asset.to_string()]);
348        let max_complexity = self.highest_complexity(deps)?;
349        // if only base asset, just return
350        if max_complexity == 0 {
351            return Ok(());
352        }
353
354        let mut complexity = 1;
355        while complexity <= max_complexity {
356            let assets = self.complexity.load(deps.storage, complexity)?;
357
358            for asset in assets {
359                let (price_source, _) = self.assets.load(deps.storage, &asset)?;
360                let deps = price_source.dependencies(&asset);
361                for dep in &deps {
362                    if !encountered_assets.contains(&dep.to_string()) {
363                        return Err(StdError::generic_err(format!(
364                            "Asset {dep} is an oracle dependency but is not registered"
365                        ))
366                        .into());
367                    }
368                }
369                if !encountered_assets.insert(asset.to_string()) {
370                    return Err(StdError::generic_err(format!(
371                        "Asset {asset} is registered twice"
372                    ))
373                    .into());
374                };
375            }
376            complexity += 1;
377        }
378        Ok(())
379    }
380
381    /// Asserts that all dependencies of an asset are registered.
382    fn assert_dependencies_exists(
383        &self,
384        deps: Deps,
385        dependencies: &Vec<AssetInfo>,
386    ) -> AbstractResult<()> {
387        for dependency in dependencies {
388            let asset_info = self.assets.has(deps.storage, dependency);
389            if !asset_info {
390                return Err(crate::AbstractError::Std(StdError::generic_err(format!(
391                    "Asset {dependency} not registered on oracle"
392                ))));
393            }
394        }
395        Ok(())
396    }
397
398    // ### Queries ###
399
400    /// Page over the oracle assets
401    pub fn paged_asset_info(
402        &self,
403        deps: Deps,
404        last_asset: Option<AssetInfo>,
405        limit: Option<u8>,
406    ) -> AbstractResult<Vec<(AssetInfo, (PriceSource, Complexity))>> {
407        let limit = limit.unwrap_or(DEFAULT_PAGE_LIMIT).min(LIST_SIZE_LIMIT) as usize;
408        let start_bound = last_asset.as_ref().map(Bound::exclusive);
409
410        let res: Result<Vec<(AssetInfo, (PriceSource, Complexity))>, _> = self
411            .assets
412            .range(deps.storage, start_bound, None, Order::Ascending)
413            .take(limit)
414            .collect();
415
416        res.map_err(Into::into)
417    }
418
419    /// Page over the oracle's asset configuration
420    pub fn paged_asset_config(
421        &self,
422        deps: Deps,
423        last_asset: Option<AssetEntry>,
424        limit: Option<u8>,
425    ) -> AbstractResult<Vec<(AssetEntry, UncheckedPriceSource)>> {
426        let limit = limit.unwrap_or(DEFAULT_PAGE_LIMIT).min(LIST_SIZE_LIMIT) as usize;
427        let start_bound = last_asset.as_ref().map(Bound::exclusive);
428
429        let res: Result<Vec<(AssetEntry, UncheckedPriceSource)>, _> = self
430            .config
431            .range(deps.storage, start_bound, None, Order::Ascending)
432            .take(limit)
433            .collect();
434
435        res.map_err(Into::into)
436    }
437    /// Get the highest complexity present in the oracle
438    fn highest_complexity(&self, deps: Deps) -> AbstractResult<u8> {
439        Ok(self
440            .complexity
441            .keys(deps.storage, None, None, Order::Descending)
442            .take(1)
443            .collect::<StdResult<Vec<u8>>>()?[0])
444    }
445
446    /// get the configuration of an asset
447    pub fn asset_config(
448        &self,
449        deps: Deps,
450        asset: &AssetEntry,
451    ) -> AbstractResult<UncheckedPriceSource> {
452        self.config.load(deps.storage, asset).map_err(Into::into)
453    }
454
455    pub fn base_asset(&self, deps: Deps) -> AbstractResult<AssetInfo> {
456        let base_asset = self.complexity.load(deps.storage, 0);
457        let Ok(base_asset) = base_asset else {
458            return Err(StdError::generic_err("No base asset registered").into());
459        };
460        let base_asset_len = base_asset.len();
461        if base_asset_len != 1 {
462            return Err(StdError::generic_err(format!(
463                "{base_asset_len} base assets registered, must be 1"
464            ))
465            .into());
466        }
467        Ok(base_asset[0].clone())
468    }
469}
470
471#[cw_serde]
472pub struct AccountValue {
473    /// the total value of this account in the base denomination
474    pub total_value: Asset,
475    /// Vec of asset information and their value in the base asset denomination
476    pub breakdown: Vec<(AssetInfo, Uint128)>,
477}
478
479// TODO: See if we can change this to multi-indexed maps when documentation improves.
480
481// #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
482// struct OracleAsset {
483//     asset: AssetInfo,
484//     price_source: PriceSource,
485//     complexity: Complexity,
486// }
487
488// struct Foo<'a> {
489//     map: IndexedMap<'a, &'a str, OracleAsset, OracleIndexes<'a> >
490// }
491
492// impl<'a> Foo<'a> {
493//     fn new() -> Self {
494//         let indexes = OracleIndexes {
495//             complexity: MultiIndex::<'a>::new(
496//                 |_pk ,d: &OracleAsset| d.complexity,
497//                 "tokens",
498//                 "tokens__owner",
499//             ),
500//             asset: UniqueIndex::<'_,AssetInfo,_,()>::new(|d: &OracleAsset| d.asset, "asset"),
501//         };
502//         IndexedMap::new("or_assets", indexes)
503//         Self {  } }
504// }
505
506// struct OracleIndexes<'a> {
507//     pub asset: UniqueIndex<'a, &'a AssetInfo, OracleAsset, String>,
508//     pub complexity: MultiIndex<'a, u8, OracleAsset, String>,
509// }
510
511// impl<'a> IndexList<OracleAsset> for OracleIndexes<'a> {
512//     fn get_indexes(&'_ self) ->Box<dyn Iterator<Item = &'_ dyn Index<OracleAsset>> + '_> {
513//         let v: Vec<&dyn Index<_>> = vec![&self.asset, &self.complexity];
514//         Box::new(v.into_iter())
515//     }
516// }
517// pub fn oracle_asset_complexity<T>(_pk: &[u8], d: &OracleAsset) -> u8 {
518//     d.complexity
519// }
520
521#[cfg(test)]
522mod tests {
523    use abstract_testing::{prelude::*, MockAnsHost};
524    use cosmwasm_std::{coin, testing::*, Addr, Decimal};
525    use speculoos::prelude::*;
526
527    use super::*;
528    use crate::objects::DexAssetPairing;
529    type AResult = anyhow::Result<()>;
530
531    pub fn get_ans() -> AnsHost {
532        let addr = Addr::unchecked(TEST_ANS_HOST);
533
534        AnsHost::new(addr)
535    }
536
537    pub fn base_asset() -> (AssetEntry, UncheckedPriceSource) {
538        (AssetEntry::from(USD), UncheckedPriceSource::None)
539    }
540
541    pub fn asset_with_dep() -> (AssetEntry, UncheckedPriceSource) {
542        let asset = AssetEntry::from(EUR);
543        let price_source = UncheckedPriceSource::Pair(DexAssetPairing::new(
544            AssetEntry::new(EUR),
545            AssetEntry::new(USD),
546            TEST_DEX,
547        ));
548        (asset, price_source)
549    }
550
551    pub fn asset_as_half() -> (AssetEntry, UncheckedPriceSource) {
552        let asset = AssetEntry::from(EUR);
553        let price_source = UncheckedPriceSource::ValueAs {
554            asset: AssetEntry::new(USD),
555            multiplier: Decimal::percent(50),
556        };
557        (asset, price_source)
558    }
559
560    #[test]
561    fn add_base_asset() -> AResult {
562        let mut deps = mock_dependencies();
563        let mock_ans = MockAnsHost::new().with_defaults();
564        deps.querier = mock_ans.to_querier();
565        let ans = get_ans();
566
567        let oracle = Oracle::new();
568        // first asset can not have dependency
569        oracle
570            .update_assets(deps.as_mut(), &ans, vec![asset_with_dep()], vec![])
571            .unwrap_err();
572        // add base asset
573        oracle.update_assets(deps.as_mut(), &ans, vec![base_asset()], vec![])?;
574
575        // try add second base asset, fails
576        oracle
577            .update_assets(deps.as_mut(), &ans, vec![base_asset()], vec![])
578            .unwrap_err();
579        // add asset with dependency
580        oracle.update_assets(deps.as_mut(), &ans, vec![asset_with_dep()], vec![])?;
581
582        // ensure these assets were added
583        // Ensure that all assets have been added to the oracle
584        let assets = oracle
585            .config
586            .range(&deps.storage, None, None, Order::Ascending)
587            .collect::<StdResult<Vec<_>>>()?;
588
589        assert_that!(assets).has_length(2);
590        assert_that!(assets[0].0.as_str()).is_equal_to(EUR);
591        assert_that!(assets[0].1).is_equal_to(UncheckedPriceSource::Pair(DexAssetPairing::new(
592            AssetEntry::new(EUR),
593            AssetEntry::new(USD),
594            TEST_DEX,
595        )));
596        assert_that!(assets[1].0.as_str()).is_equal_to(USD);
597        assert_that!(assets[1].1).is_equal_to(UncheckedPriceSource::None);
598
599        // Ensure that all assets have been added to the complexity index
600        let complexity = oracle
601            .complexity
602            .range(&deps.storage, None, None, Order::Ascending)
603            .collect::<StdResult<Vec<_>>>()?;
604        // 2 assets, 1 base asset, 1 asset with dependency
605        assert_that!(complexity).has_length(2);
606
607        assert_that!(complexity[0].1).has_length(1);
608        assert_that!(complexity[1].1).has_length(1);
609
610        Ok(())
611    }
612
613    #[test]
614    fn query_base_value() -> AResult {
615        let mut deps = mock_dependencies();
616        let mock_ans = MockAnsHost::new().with_defaults();
617        deps.querier = mock_ans.to_querier();
618        deps.querier
619            .update_balance(MOCK_CONTRACT_ADDR, vec![coin(1000, USD)]);
620        let ans = get_ans();
621        let mut oracle = Oracle::new();
622
623        // add base asset
624        oracle.update_assets(deps.as_mut(), &ans, vec![base_asset()], vec![])?;
625
626        let value = oracle.account_value(deps.as_ref(), &Addr::unchecked(MOCK_CONTRACT_ADDR))?;
627        assert_that!(value.total_value.amount.u128()).is_equal_to(1000u128);
628
629        let base_asset = oracle.base_asset(deps.as_ref())?;
630        assert_that!(base_asset).is_equal_to(AssetInfo::native(USD));
631
632        // get the one-asset value of the base asset
633        let asset_value =
634            oracle.asset_value(deps.as_ref(), Asset::new(AssetInfo::native(USD), 1000u128))?;
635        assert_that!(asset_value.u128()).is_equal_to(1000u128);
636        Ok(())
637    }
638
639    #[test]
640    fn query_equivalent_asset_value() -> AResult {
641        let mut deps = mock_dependencies();
642        let mock_ans = MockAnsHost::new().with_defaults();
643        deps.querier = mock_ans.to_querier();
644        deps.querier
645            .update_balance(MOCK_CONTRACT_ADDR, vec![coin(1000, EUR)]);
646        let ans = get_ans();
647        let mut oracle = Oracle::new();
648        // fails because base asset is not set.
649        let res = oracle.update_assets(deps.as_mut(), &ans, vec![asset_as_half()], vec![]);
650        // match when adding better errors
651        assert_that!(res).is_err();
652        // fails, need to add base asset first, TODO: try removing this requirement when more tests are added.
653        oracle
654            .update_assets(
655                deps.as_mut(),
656                &ans,
657                vec![asset_as_half(), base_asset()],
658                vec![],
659            )
660            .unwrap_err();
661
662        // now in correct order
663        oracle.update_assets(
664            deps.as_mut(),
665            &ans,
666            vec![base_asset(), asset_as_half()],
667            vec![],
668        )?;
669
670        let value = oracle.account_value(deps.as_ref(), &Addr::unchecked(MOCK_CONTRACT_ADDR))?;
671        assert_that!(value.total_value.amount.u128()).is_equal_to(500u128);
672
673        // give the account some base asset
674        deps.querier
675            .update_balance(MOCK_CONTRACT_ADDR, vec![coin(1000, USD), coin(1000, EUR)]);
676
677        // assert that the value increases with 1000
678        let value = oracle.account_value(deps.as_ref(), &Addr::unchecked(MOCK_CONTRACT_ADDR))?;
679        assert_that!(value.total_value.amount.u128()).is_equal_to(1500u128);
680
681        // get the one-asset value of the base asset
682        let asset_value =
683            oracle.asset_value(deps.as_ref(), Asset::new(AssetInfo::native(USD), 1000u128))?;
684        assert_that!(asset_value.u128()).is_equal_to(1000u128);
685
686        // now for EUR
687        let asset_value =
688            oracle.asset_value(deps.as_ref(), Asset::new(AssetInfo::native(EUR), 1000u128))?;
689        assert_that!(asset_value.u128()).is_equal_to(500u128);
690        Ok(())
691    }
692
693    #[test]
694    fn reject_duplicate_entries() -> AResult {
695        let mut deps = mock_dependencies();
696        let mock_ans = MockAnsHost::new().with_defaults();
697        deps.querier = mock_ans.to_querier();
698        let ans = get_ans();
699        let oracle = Oracle::new();
700
701        // fails because base asset is not set.
702        let res = oracle.update_assets(
703            deps.as_mut(),
704            &ans,
705            vec![asset_as_half()],
706            vec![asset_as_half().0],
707        );
708        assert_that!(res).is_err();
709        Ok(())
710    }
711
712    // test for pair
713
714    // test for LP tokens
715
716    // test for max complexity
717}