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
20pub struct Oracle<'a> {
22 pub config: Map<'static, &'a AssetEntry, UncheckedPriceSource>,
24 assets: Map<'static, &'a AssetInfo, (PriceSource, Complexity)>,
26 complexity: Map<'static, Complexity, Vec<AssetInfo>>,
29 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 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 self.add_assets(deps.branch(), ans, to_add)?;
79 self.remove_assets(deps.branch(), ans, to_remove)?;
81 self.validate(deps.as_ref())
85 }
86
87 fn add_assets(
89 &self,
90 deps: DepsMut,
91 ans: &AnsHost,
92 assets: Vec<(AssetEntry, UncheckedPriceSource)>,
93 ) -> AbstractResult<()> {
94 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 for (asset, price_source) in assets_and_sources {
120 let dependencies = price_source.dependencies(&asset);
122 self.assert_dependencies_exists(deps.as_ref(), &dependencies)?;
123 let complexity = self.asset_complexity(deps.as_ref(), &price_source, &dependencies)?;
126 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 fn remove_assets(
152 &self,
153 deps: DepsMut,
154 ans: &AnsHost,
155 assets: Vec<AssetEntry>,
156 ) -> AbstractResult<()> {
157 for asset in assets {
158 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 self.config.remove(deps.storage, &asset);
167 let asset = ans.query_asset(&deps.querier, &asset)?;
169 let (_, complexity) = self.assets.load(deps.storage, &asset)?;
171 self.assets.remove(deps.storage, &asset);
173 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 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 pub fn asset_value(&self, deps: Deps, asset: Asset) -> AbstractResult<Uint128> {
221 let (price_source, _) = self.assets.load(deps.storage, &asset.info)?;
223 let conversion_rates = price_source.conversion_rates(deps, &asset.info)?;
225 if conversion_rates.is_empty() {
226 return Ok(asset.amount);
228 }
229 let converted_assets = AssetConversion::convert(&conversion_rates, asset.amount);
231 converted_assets
233 .into_iter()
234 .map(|a| self.asset_value(deps, a))
235 .sum()
236 }
237
238 pub fn account_value(&mut self, deps: Deps, account: &Addr) -> AbstractResult<AccountValue> {
248 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 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 let balance = asset.query_balance(&deps.querier, account)?;
266 eprintln!("{asset}: {balance} ");
267 let mut cached_balances = self.cached_balance(&asset).unwrap_or_default();
269 eprintln!("cached: {cached_balances:?}");
270 cached_balances.push((asset.clone(), balance));
272
273 let conversion_rates = price_source.conversion_rates(deps, &asset)?;
275 if conversion_rates.is_empty() {
276 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 self.update_cache(cached_balances, conversion_rates)?;
289 }
290 self.complexity_value_calculation(deps, complexity - 1, account)
292 }
293
294 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 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 let target_assets_balances = AssetConversion::convert(&conversions, balance);
315 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 pub fn validate(&self, deps: Deps) -> AbstractResult<()> {
339 let base_asset = self.base_asset(deps)?;
343
344 let mut encountered_assets: HashSet<String> = HashSet::from([base_asset.to_string()]);
348 let max_complexity = self.highest_complexity(deps)?;
349 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 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 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 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 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 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 pub total_value: Asset,
475 pub breakdown: Vec<(AssetInfo, Uint128)>,
477}
478
479#[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 oracle
570 .update_assets(deps.as_mut(), &ans, vec![asset_with_dep()], vec![])
571 .unwrap_err();
572 oracle.update_assets(deps.as_mut(), &ans, vec![base_asset()], vec![])?;
574
575 oracle
577 .update_assets(deps.as_mut(), &ans, vec![base_asset()], vec![])
578 .unwrap_err();
579 oracle.update_assets(deps.as_mut(), &ans, vec![asset_with_dep()], vec![])?;
581
582 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 let complexity = oracle
601 .complexity
602 .range(&deps.storage, None, None, Order::Ascending)
603 .collect::<StdResult<Vec<_>>>()?;
604 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 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 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 let res = oracle.update_assets(deps.as_mut(), &ans, vec![asset_as_half()], vec![]);
650 assert_that!(res).is_err();
652 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 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 deps.querier
675 .update_balance(MOCK_CONTRACT_ADDR, vec![coin(1000, USD), coin(1000, EUR)]);
676
677 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 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 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 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 }