1use sep_40_oracle::PriceData;
2use soroban_sdk::Vec;
3
4pub(crate) fn order_prices_by_timestamp(prices: &Vec<PriceData>) -> Vec<PriceData> {
5 let mut ordered_prices: Vec<PriceData> = Vec::new(prices.env());
6 for price in prices.iter() {
7 if ordered_prices.is_empty()
8 || price.timestamp
9 >= ordered_prices
10 .get_unchecked(ordered_prices.len() - 1)
11 .timestamp
12 {
13 ordered_prices.push_back(price.clone());
14 continue;
15 }
16 for (i, ordered_price) in ordered_prices.iter().enumerate() {
17 if price.timestamp < ordered_price.timestamp {
18 ordered_prices.insert(i as u32, price.clone());
19 break;
20 }
21 }
22 }
23 ordered_prices
24}
25
26pub(crate) fn fixed_div_floor(dividend: i128, divisor: i128, decimals: u32) -> Option<i128> {
27 if dividend <= 0 || divisor <= 0 {
28 return None;
29 }
30 let ashift = core::cmp::min(38 - dividend.ilog10(), decimals);
31 let bshift = core::cmp::max(decimals - ashift, 0);
32
33 let mut vdividend = dividend;
34 let mut vdivisor = divisor;
35 if ashift > 0 {
36 let svdividend = vdividend.checked_mul(10_i128.pow(ashift));
37 if svdividend.is_none() {
38 return None;
39 }
40 vdividend = svdividend?;
41 }
42 if bshift > 0 {
43 vdivisor /= 10_i128.pow(bshift);
44 }
45 if vdivisor <= 0 {
46 return None;
47 }
48 Some(vdividend / vdivisor)
49}
50
51pub fn twap(prices: &Vec<PriceData>) -> Option<i128> {
52 if prices.is_empty() {
53 return None;
54 }
55 let mut sum = 0i128;
56 let mut count = 0i128;
57 for price_data in prices.iter() {
58 if price_data.price > 0 && price_data.timestamp > 0 {
59 sum += price_data.price;
60 count += 1;
61 }
62 }
63 if count == 0 {
64 return None;
65 }
66 Some(sum / count)
67}
68
69pub fn x_twap(prices_a: &Vec<PriceData>, prices_b: &Vec<PriceData>, decimals: u32) -> Option<i128> {
70 if prices_a.is_empty() || prices_b.is_empty() {
71 return None;
72 }
73
74 let x_prices =
76 x_prices(prices_a, prices_b, decimals).unwrap_or_else(|| Vec::new(prices_a.env()));
77 if x_prices.is_empty() {
78 return None;
79 }
80
81 twap(&x_prices)
83}
84
85pub fn x_price(price_a: &PriceData, price_b: &PriceData, decimals: u32) -> Option<i128> {
86 if price_a.price <= 0 || price_b.price <= 0 {
87 return None;
88 }
89
90 fixed_div_floor(price_a.price, price_b.price, decimals)
91}
92
93pub fn x_prices(
94 prices_a: &Vec<PriceData>,
95 prices_b: &Vec<PriceData>,
96 decimals: u32,
97) -> Option<Vec<PriceData>> {
98 let mut result = Vec::new(prices_a.env());
99
100 let sorted_prices_a = order_prices_by_timestamp(prices_a);
101 let sorted_prices_b = order_prices_by_timestamp(prices_b);
102
103 for i in 0..prices_a.len().max(prices_b.len()) {
105 let mut price_a = sorted_prices_a.get(i).unwrap_or_else(|| PriceData {
107 price: 0,
108 timestamp: 0,
109 });
110 let mut price_b = sorted_prices_b.get(i).unwrap_or_else(|| PriceData {
111 price: 0,
112 timestamp: 0,
113 });
114
115 fn find_closest_price(target_timestamp: u64, sorted_prices: &Vec<PriceData>) -> PriceData {
117 let mut price = PriceData {
118 price: 0,
119 timestamp: 0,
120 };
121 for i in 0..sorted_prices.len() {
122 let candidate = sorted_prices.get_unchecked(i);
123 if candidate.timestamp > target_timestamp {
124 break;
125 }
126 price = candidate;
127 }
128 price
129 }
130
131 if price_a.timestamp > price_b.timestamp {
133 price_b = find_closest_price(price_a.timestamp, &sorted_prices_b);
134 } else if price_b.timestamp > price_a.timestamp {
135 price_a = find_closest_price(price_b.timestamp, &sorted_prices_a);
136 }
137
138 let cross_price = x_price(&price_a, &price_b, decimals);
140 if cross_price.is_none() {
141 continue;
143 }
144 result.push_back(PriceData {
145 price: cross_price.unwrap(),
146 timestamp: price_a.timestamp.max(price_b.timestamp),
147 });
148 }
149
150 Some(result)
151}
152
153mod tests;