Skip to content

Commit 452f080

Browse files
committed
add comments and explanations
1 parent b9828dd commit 452f080

File tree

1 file changed

+44
-64
lines changed

1 file changed

+44
-64
lines changed

pyth-sdk/src/price.rs

Lines changed: 44 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -110,39 +110,25 @@ impl Price {
110110
/// Args
111111
/// deposits: u64, quantity of token deposited in the protocol
112112
/// deposits_endpoint: u64, deposits right endpoint for the affine combination
113-
/// discount_initial: u64, initial discount rate at 0 deposits (units given by discount_exponent)
114-
/// discount_final: u64, final discount rate at deposits_endpoint deposits (units given by discount_exponent)
113+
/// rate_discount_initial: u64, initial discounted rate at 0 deposits (units given by discount_exponent)
114+
/// rate_discount_final: u64, final discounted rate at deposits_endpoint deposits (units given by discount_exponent)
115115
/// discount_exponent: u64, the exponent to apply to the discounts above (e.g. if discount_final is 10 but meant to express 0.1/10%, exponent would be -2)
116116
/// note that if discount_initial is bigger than 100% per the discount exponent scale, then the initial valuation of the collateral will be higher than the oracle price
117-
///
118-
/// affine_combination yields us error <= 2/PD_SCALE for discount_interpolated
119-
/// We then multiply this with the price to yield price_discounted before scaling this back to the original expo
120-
/// Output of affine_combination has expo >= -18, price (self) has arbitrary expo
121-
/// Scaling this back to the original expo then has error bounded by the expo (10^expo).
122-
/// This is because reverting a potentially finer expo to a coarser grid has the potential to be off by
123-
/// the order of the atomic unit of the coarser grid.
124-
/// This scaling error combines with the previous error additively: Err <= 2/PD_SCALE + 10^expo
125-
///
126-
/// The practical error is based on the original expo:
127-
/// if it is big, then the 10^expo loss dominates;
128-
/// otherwise, the 2/PD_SCALE error dominates.
129-
///
130-
/// Thus, we expect the computed collateral valuation price to be no more than 2/PD_SCALE + 10^expo off of the mathematically true value
131-
/// For this reason, we encourage using this function with prices that have high expos, to minimize the potential error.
132-
pub fn get_collateral_valuation_price(&self, deposits: u64, deposits_endpoint: u64, discount_initial: u64, discount_final: u64, discount_exponent: i32) -> Option<Price> {
133-
if discount_initial < discount_final {
117+
pub fn get_collateral_valuation_price(&self, deposits: u64, deposits_endpoint: u64, rate_discount_initial: u64, rate_discount_final: u64, discount_exponent: i32) -> Option<Price> {
118+
// valuation price should not increase as amount of collateral grows, so rate_discount_initial should >= rate_discount_final
119+
if rate_discount_initial < rate_discount_final {
134120
return None;
135121
}
136122

137123
// get price versions of discounts
138124
let initial_percentage = Price {
139-
price: i64::try_from(discount_initial).ok()?,
125+
price: i64::try_from(rate_discount_initial).ok()?,
140126
conf: 0,
141127
expo: discount_exponent,
142128
publish_time: 0,
143129
};
144130
let final_percentage = Price {
145-
price: i64::try_from(discount_final).ok()?,
131+
price: i64::try_from(rate_discount_final).ok()?,
146132
conf: 0,
147133
expo: discount_exponent,
148134
publish_time: 0,
@@ -196,39 +182,25 @@ impl Price {
196182
/// Args
197183
/// borrows: u64, quantity of token borrowed from the protocol
198184
/// borrows_endpoint: u64, borrows right endpoint for the affine combination
199-
/// premium_initial: u64, initial premium at 0 borrows (units given by premium_exponent)
200-
/// premium_final: u64, final premium at borrows_endpoint borrows (units given by premium_exponent)
185+
/// rate_premium_initial: u64, initial premium at 0 borrows (units given by premium_exponent)
186+
/// rate_premium_final: u64, final premium at borrows_endpoint borrows (units given by premium_exponent)
201187
/// premium_exponent: u64, the exponent to apply to the premiums above (e.g. if premium_final is 50 but meant to express 0.05/5%, exponent would be -3)
202188
/// note that if premium_initial is less than 100% per the premium exponent scale, then the initial valuation of the borrow will be lower than the oracle price
203-
///
204-
/// affine_combination yields us error <= 2/PD_SCALE for premium_interpolated
205-
/// We then multiply this with the price to yield price_premium before scaling this back to the original expo
206-
/// Output of affine_combination has expo >= -18, price (self) has arbitrary expo
207-
/// Scaling this back to the original expo then has error bounded by the expo (10^expo).
208-
/// This is because reverting a potentially finer expo to a coarser grid has the potential to be off by
209-
/// the order of the atomic unit of the coarser grid.
210-
/// This scaling error combines with the previous error additively: Err <= 2/PD_SCALE + 10^expo
211-
///
212-
/// The practical error is based on the original expo:
213-
/// if it is big, then the 10^expo loss dominates;
214-
/// otherwise, the 2/PD_SCALE error dominates.
215-
///
216-
/// Thus, we expect the computed borrow valuation price to be no more than 2/PD_SCALE + 10^expo off of the mathematically true value
217-
/// For this reason, we encourage using this function with prices that have high expos, to minimize the potential error.
218-
pub fn get_borrow_valuation_price(&self, borrows: u64, borrows_endpoint: u64, premium_initial: u64, premium_final: u64, premium_exponent: i32) -> Option<Price> {
219-
if premium_initial > premium_final {
189+
pub fn get_borrow_valuation_price(&self, borrows: u64, borrows_endpoint: u64, rate_premium_initial: u64, rate_premium_final: u64, premium_exponent: i32) -> Option<Price> {
190+
// valuation price should not decrease as amount of borrow grows, so rate_premium_initial should <= rate_premium_final
191+
if rate_premium_initial > rate_premium_final {
220192
return None;
221193
}
222194

223195
// get price versions of premiums
224196
let initial_percentage = Price {
225-
price: i64::try_from(premium_initial).ok()?,
197+
price: i64::try_from(rate_premium_initial).ok()?,
226198
conf: 0,
227199
expo: premium_exponent,
228200
publish_time: 0,
229201
};
230202
let final_percentage = Price {
231-
price: i64::try_from(premium_final).ok()?,
203+
price: i64::try_from(rate_premium_final).ok()?,
232204
conf: 0,
233205
expo: premium_exponent,
234206
publish_time: 0,
@@ -308,7 +280,7 @@ impl Price {
308280
/// Scaling this back has error bounded by the expo (10^pre_add_expo).
309281
/// This is because reverting a potentially finer expo to a coarser grid has the potential to be off by
310282
/// the order of the atomic unit of the coarser grid.
311-
/// This scaling error combines with the previous error additively: Err <= 2/PD_SCALE + 2*10^pre_add_expo
283+
/// This scaling error combines with the previous error additively: Err <= 4x + 2*10^pre_add_expo
312284
/// But if pre_add_expo is reasonably small (<= -9), then other term will dominate
313285
pub fn affine_combination(x1: i64, y1: Price, x2: i64, y2: Price, x_query: i64, pre_add_expo: i32) -> Option<Price> {
314286
if x2 <= x1 {
@@ -324,15 +296,15 @@ impl Price {
324296
let delta_21 = x2.checked_sub(x1)?;
325297

326298
// get the relevant fractions of the deltas, with scaling
327-
// 4. compute D = A/C, Err(D) <= x = 1/PD_SCALE
299+
// 4. compute D = A/C, Err(D) <= x
328300
let frac_q1 = Price::fraction(delta_q1, delta_21)?;
329301
// 5. compute E = B/C, Err(E) <= x
330302
let frac_2q = Price::fraction(delta_2q, delta_21)?;
331303

332304
// calculate products for left and right
333-
// 6. compute F = y2 * D, Err(F) <= (1+x)^2 - 1
305+
// 6. compute F = y2 * D, Err(F) <= (1+x)^2 - 1 ~= 2x
334306
let mut left = y2.mul(&frac_q1)?;
335-
// 7. compute G = y1 * E, Err(G) <= (1+x)^2 - 1
307+
// 7. compute G = y1 * E, Err(G) <= (1+x)^2 - 1 ~= 2x
336308
let mut right = y1.mul(&frac_2q)?;
337309

338310
// Err(scaling) += 2*10^pre_add_expo
@@ -343,7 +315,7 @@ impl Price {
343315
return None;
344316
}
345317

346-
// 8. compute H = F + G, Err(H) ~= 2x + 2*10^pre_add_expo
318+
// 8. compute H = F + G, Err(H) ~= 4x + 2*10^pre_add_expo
347319
return left.add(&right);
348320
}
349321

@@ -1623,7 +1595,7 @@ mod test {
16231595
assert_eq!(result, None);
16241596
}
16251597

1626-
// constant, inbounds
1598+
// constant, in the bounds [x1, x2]
16271599
succeeds(
16281600
0,
16291601
pc(100, 0, -4),
@@ -1645,7 +1617,7 @@ mod test {
16451617
pc(10_000_000, 0, -9),
16461618
);
16471619

1648-
// increasing, inbounds
1620+
// increasing, in the bounds
16491621
succeeds(
16501622
0,
16511623
pc(90, 0, -4),
@@ -1667,7 +1639,7 @@ mod test {
16671639
pc(10_500_000, 0, -9)
16681640
);
16691641

1670-
// decreasing, inbounds
1642+
// decreasing, in the bounds
16711643
succeeds(
16721644
0,
16731645
pc(100, 0, -4),
@@ -1737,7 +1709,7 @@ mod test {
17371709
);
17381710

17391711
// test loss due to scaling
1740-
// lose more bc scale to higher expo
1712+
// lose more bc scaling to higher expo
17411713
succeeds(
17421714
0,
17431715
pc(0, 0, -2),
@@ -1747,7 +1719,7 @@ mod test {
17471719
-8,
17481720
pc(769230, 0, -8)
17491721
);
1750-
// lose less bc scale to lower expo
1722+
// lose less bc scaling to lower expo
17511723
succeeds(
17521724
0,
17531725
pc(0, 0, -2),
@@ -1757,7 +1729,7 @@ mod test {
17571729
-9,
17581730
pc(7692307, 0, -9)
17591731
);
1760-
// lose more bc need to increment expo more in scaling
1732+
// lose more bc need to increment expo more in scaling from original inputs
17611733
succeeds(
17621734
0,
17631735
pc(0, 0, -3),
@@ -1767,7 +1739,7 @@ mod test {
17671739
-9,
17681740
pc(7692307, 0, -9)
17691741
);
1770-
// lose less bc need to increment expo less in scaling
1742+
// lose less bc need to increment expo less in scaling from original inputs
17711743
succeeds(
17721744
0,
17731745
pc(0, 0, -2),
@@ -1889,7 +1861,7 @@ mod test {
18891861
);
18901862

18911863

1892-
// test w confidence (same at both endpoints)
1864+
// test w confidence (same at both endpoints)--expect linear change btwn x1 and x2 and growth in conf as distance from interval [x1, x2] increases
18931865
succeeds(
18941866
0,
18951867
pc(90, 10, -4),
@@ -1995,6 +1967,10 @@ mod test {
19951967
}
19961968

19971969
// quickcheck to confirm affine_combination introduces no error if normalization done explicitly on prices first
1970+
// this quickcheck calls affine_combination with two sets of almost identical inputs:
1971+
// the first set has potentially unnormalized prices, the second set simply has the normalized versions of those prices
1972+
// this set of checks should pass because normalization is automatically performed on the prices before they are multiplied
1973+
// this set of checks passing indicates that it doesn't matter whether the prices passed in are normalized
19981974
#[quickcheck]
19991975
fn quickcheck_affine_combination_normalize_prices(x1_inp: i32, p1: i32, x2_inp: i32, p2: i32, x_query_inp: i32) -> TestResult {
20001976
// generating xs and prices from i32 to limit the range to reasonable values and guard against overflow/bespoke constraint setting for quickcheck
@@ -2013,17 +1989,24 @@ mod test {
20131989
return TestResult::discard()
20141990
}
20151991

1992+
// original result
20161993
let result_orig = Price::affine_combination(x1, y1, x2, y2, x_query, pre_add_expo).unwrap();
20171994

20181995
let y1_norm = y1.normalize().unwrap();
20191996
let y2_norm = y2.normalize().unwrap();
20201997

1998+
// result with normalized price inputs
20211999
let result_norm = Price::affine_combination(x1, y1_norm, x2, y2_norm, x_query, pre_add_expo).unwrap();
20222000

2001+
// results should match exactly
20232002
TestResult::from_bool(result_norm == result_orig)
20242003
}
20252004

20262005
// quickcheck to confirm affine_combination introduces bounded error if close fraction x/y passed in first
2006+
// this quickcheck calls affine_combination with two sets of similar inputs:
2007+
// the first set has xs generated by the quickcheck generation process, leading to potentially inexact fractions that don't fit within 8 digits of precision
2008+
// the second set "normalizes" down to 8 digits of precision by setting x1 to 0, x2 to 100_000_000, and xquery proportionally
2009+
// based on the bounds described in the docstring of affine_combination, we expect error due to this to be leq 4*10^-7
20272010
#[quickcheck]
20282011
fn quickcheck_affine_combination_normalize_fractions(x1_inp: i32, p1: i32, x2_inp: i32, p2: i32, x_query_inp: i32) -> TestResult {
20292012
// generating xs and prices from i32 to limit the range to reasonable values and guard against overflow/bespoke constraint setting for quickcheck
@@ -2042,15 +2025,8 @@ mod test {
20422025
return TestResult::discard()
20432026
}
20442027

2045-
if (x_query > 5*x2) || (x_query < 2*x1 - x2) {
2046-
return TestResult::discard()
2047-
}
2048-
2049-
// require reasonable price/conf range
2050-
if (y1.price > 2*MAX_PD_V_I64) || (y1.price < 2*MIN_PD_V_I64) {
2051-
return TestResult::discard()
2052-
}
2053-
if (y2.price > 2*MAX_PD_V_I64) || (y2.price < 2*MIN_PD_V_I64) {
2028+
// constrain x_query to be within 5 interval lengths of x1 or x2
2029+
if (x_query > x2 + 5*(x2-x1)) || (x_query < x1 - 5*(x2-x1)) {
20542030
return TestResult::discard()
20552031
}
20562032

@@ -2072,14 +2048,18 @@ mod test {
20722048
x2_new = 100_000_000 as i64;
20732049
}
20742050

2051+
// original result
20752052
let result_orig = Price::affine_combination(x1, y1, x2, y2, x_query, pre_add_expo).unwrap().
20762053
scale_to_exponent(-7).unwrap();
20772054

2055+
// xs "normalized" result
20782056
let result_norm = Price::affine_combination(x1_new, y1, x2_new, y2, xq_new, pre_add_expo).unwrap().
20792057
scale_to_exponent(-7).unwrap();
20802058

2059+
// compute difference in prices
20812060
let price_diff = result_norm.add(&result_orig.cmul(-1, 0).unwrap()).unwrap();
20822061

2062+
// results should differ by less than 4*10^-7
20832063
TestResult::from_bool((price_diff.price < 4) && (price_diff.price > -4))
20842064
}
20852065

0 commit comments

Comments
 (0)