penumbra_sdk_dex/lp/trading_function.rs
1use anyhow::{anyhow, Result};
2use penumbra_sdk_asset::{asset, Value};
3use penumbra_sdk_num::{fixpoint::U128x128, Amount};
4use penumbra_sdk_proto::{penumbra::core::component::dex::v1 as pb, DomainType};
5use serde::{Deserialize, Serialize};
6use tracing::instrument;
7
8use crate::TradingPair;
9
10use super::Reserves;
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
13#[serde(try_from = "pb::TradingFunction", into = "pb::TradingFunction")]
14pub struct TradingFunction {
15 pub component: BareTradingFunction,
16 pub pair: TradingPair,
17}
18
19impl TradingFunction {
20 pub fn new(pair: TradingPair, fee: u32, p: Amount, q: Amount) -> Self {
21 Self {
22 component: BareTradingFunction::new(fee, p, q),
23 pair,
24 }
25 }
26
27 /// Checks that the specified input's asset type matches either end of this
28 /// the trading function's pair. Returns `true` if so, `false` otherwise.
29 pub fn matches_input(&self, input_id: asset::Id) -> bool {
30 input_id == self.pair.asset_1() || input_id == self.pair.asset_2()
31 }
32
33 /// Fills a trade of an input value against this position, returning the
34 /// unfilled amount of the input asset, the updated reserves, and the output
35 /// amount.
36 ///
37 /// # Errors
38 /// This method errors if:
39 /// - the asset type of the input does not match either end of the
40 /// `TradingPair`.
41 /// - an overflow occurs during execution.
42 pub fn fill(
43 &self,
44 input: Value,
45 reserves: &Reserves,
46 ) -> anyhow::Result<(Value, Reserves, Value)> {
47 tracing::debug!(?input, ?reserves, "filling trade");
48 if input.asset_id == self.pair.asset_1() {
49 let (unfilled, new_reserves, output) = self.component.fill(input.amount, reserves)?;
50 Ok((
51 Value {
52 amount: unfilled,
53 asset_id: self.pair.asset_1(),
54 },
55 new_reserves,
56 Value {
57 amount: output,
58 asset_id: self.pair.asset_2(),
59 },
60 ))
61 } else if input.asset_id == self.pair.asset_2() {
62 let flipped_reserves = reserves.flip();
63 let (unfilled, new_reserves, output) = self
64 .component
65 .flip()
66 .fill(input.amount, &flipped_reserves)?;
67 Ok((
68 Value {
69 amount: unfilled,
70 asset_id: self.pair.asset_2(),
71 },
72 new_reserves.flip(),
73 Value {
74 amount: output,
75 asset_id: self.pair.asset_1(),
76 },
77 ))
78 } else {
79 Err(anyhow!(
80 "input asset id {:?} did not match either end of trading pair {:?}",
81 input.asset_id,
82 self.pair
83 ))
84 }
85 }
86
87 /// Attempts to compute the input value required to produce the given output
88 /// value, returning the input value and updated reserves if successful.
89 /// Returns `None` if the output value exceeds the liquidity of the reserves.
90 ///
91 /// # Errors
92 /// This method errors if:
93 /// - The asset type of the output does not match either end of the reserves.
94 /// - An overflow occurs during the computation.
95 pub fn fill_output(
96 &self,
97 reserves: &Reserves,
98 output: Value,
99 ) -> anyhow::Result<Option<(Reserves, Value)>> {
100 if output.asset_id == self.pair.asset_2() {
101 Ok(self
102 .component
103 .fill_output(reserves, output.amount)?
104 .map(|(new_reserves, input)| {
105 (
106 new_reserves,
107 Value {
108 amount: input,
109 asset_id: self.pair.asset_1(),
110 },
111 )
112 }))
113 } else if output.asset_id == self.pair.asset_1() {
114 // Flip the reserves and the trading function...
115 let flipped_reserves = reserves.flip();
116 let flipped_function = self.component.flip();
117 Ok(flipped_function
118 .fill_output(&flipped_reserves, output.amount)?
119 .map(|(new_reserves, input)| {
120 (
121 // ... then flip the reserves back.
122 new_reserves.flip(),
123 Value {
124 amount: input,
125 asset_id: self.pair.asset_2(),
126 },
127 )
128 }))
129 } else {
130 Err(anyhow!(
131 "output asset id {:?} did not match either end of trading pair {:?}",
132 output.asset_id,
133 self.pair
134 ))
135 }
136 }
137
138 pub fn orient_end(&self, end: asset::Id) -> Option<BareTradingFunction> {
139 if end == self.pair.asset_2() {
140 Some(self.component.clone())
141 } else if end == self.pair.asset_1() {
142 Some(self.component.flip())
143 } else {
144 None
145 }
146 }
147
148 pub fn orient_start(&self, start: asset::Id) -> Option<BareTradingFunction> {
149 if start == self.pair.asset_1() {
150 Some(self.component.clone())
151 } else if start == self.pair.asset_2() {
152 Some(self.component.flip())
153 } else {
154 None
155 }
156 }
157}
158
159impl TryFrom<pb::TradingFunction> for TradingFunction {
160 type Error = anyhow::Error;
161
162 fn try_from(phi: pb::TradingFunction) -> Result<Self, Self::Error> {
163 Ok(Self {
164 component: phi
165 .component
166 .ok_or_else(|| anyhow::anyhow!("missing BareTradingFunction"))?
167 .try_into()?,
168 pair: phi
169 .pair
170 .ok_or_else(|| anyhow::anyhow!("missing TradingPair"))?
171 .try_into()?,
172 })
173 }
174}
175
176impl From<TradingFunction> for pb::TradingFunction {
177 fn from(phi: TradingFunction) -> Self {
178 Self {
179 component: Some(phi.component.into()),
180 pair: Some(phi.pair.into()),
181 }
182 }
183}
184
185impl DomainType for TradingFunction {
186 type Proto = pb::TradingFunction;
187}
188
189/// The data describing a trading function.
190///
191/// This implicitly treats the trading function as being between assets 1 and 2,
192/// without specifying what those assets are, to avoid duplicating data (each
193/// asset ID alone is twice the size of the trading function). Which assets correspond
194/// to asset 1 and 2 is given by the canonical ordering of the pair.
195///
196/// The trading function `phi(R) = p*R_1 + q*R_2` is a CFMM with a constant-sum,
197/// and a fee (`0 <= fee < 10_000`) expressed in basis points.
198///
199/// The valuations (`p`, `q`) for each asset inform the rate (or price) at which these
200/// assets trade against each other.
201#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
202#[serde(try_from = "pb::BareTradingFunction", into = "pb::BareTradingFunction")]
203pub struct BareTradingFunction {
204 /// The fee, expressed in basis points.
205 ///
206 /// The fee percentage of the trading function (`gamma`) is normalized
207 /// according to its maximum value (10_000 bps, i.e. 100%):
208 /// `gamma = (10_000 - fee) / 10_000`
209 pub fee: u32,
210 /// The valuation for the first asset of the pair, according to canonical ordering.
211 pub p: Amount,
212 /// The valuation for the second asset of the pair, according to canonical ordering.
213 pub q: Amount,
214}
215
216impl BareTradingFunction {
217 pub fn new(fee: u32, p: Amount, q: Amount) -> Self {
218 Self { fee, p, q }
219 }
220
221 pub fn flip(&self) -> Self {
222 Self {
223 fee: self.fee,
224 p: self.q,
225 q: self.p,
226 }
227 }
228
229 #[deprecated(note = "this method is not yet implemented")]
230 pub fn fill_input(&self, _reserves: &Reserves, _delta_1: Amount) -> Option<(Reserves, Amount)> {
231 unimplemented!()
232 }
233
234 /// Determine the amount of asset 1 that can be filled for a given amount of asset 2,
235 /// propagating rounding error to the input amount `delta_1` rather than the output amount `lambda_2`.
236 /// Returns `None` if the amount of asset 2 is greater than the reserves of asset 2.
237 ///
238 /// # Errors
239 /// This method returns an error if an overflow occurs when computing the fillable amount of asset 1.
240 #[instrument(skip(self, reserves, lambda_2))]
241 pub fn fill_output(
242 &self,
243 reserves: &Reserves,
244 lambda_2: Amount,
245 ) -> anyhow::Result<Option<(Reserves, Amount)>> {
246 if lambda_2 > reserves.r2 {
247 tracing::debug!(?reserves, ?lambda_2, "lambda_2 > r2, no fill possible");
248 return Ok(None);
249 }
250 // We must work backwards to infer what `delta_1` (input) correspond
251 // exactly to a fill of `lambda_2 = r2`.
252 // lambda_2 = effective_price * delta_1
253 // and since p,q != 0, effective_price != 0:
254 // delta_1 = r2 * effective_price^-1
255 let fillable_delta_1 = self.convert_to_delta_1(lambda_2.into())?;
256
257 // We burn the rouding error by apply `ceil` to delta_1:
258 //
259 // delta_1_star = Ceil(delta_1)
260 // TODO: round_up is now fallible
261 let fillable_delta_1_exact: Amount = fillable_delta_1
262 .round_up()
263 .expect("no overflow")
264 .try_into()
265 .expect("rounded up to integral value");
266
267 let new_reserves = Reserves {
268 r1: reserves.r1 + fillable_delta_1_exact,
269 // We checked that lambda_2 <= reserves.r2 above.
270 r2: reserves.r2 - lambda_2,
271 };
272 tracing::debug!(
273 ?reserves,
274 ?lambda_2,
275 %fillable_delta_1,
276 ?fillable_delta_1_exact,
277 ?new_reserves,
278 "computed reverse fill"
279 );
280 Ok(Some((new_reserves, fillable_delta_1_exact)))
281 }
282
283 /// Fills a trade of asset 1 to asset 2 against the given reserves,
284 /// returning the unfilled amount of asset 1, the updated reserves, and the
285 /// output amount of asset 2.
286 ///
287 /// # Errors
288 /// This method errors if an overflow occurs when computing the trade output amount,
289 /// or the fillable amount of asset 1.
290 pub fn fill(&self, delta_1: Amount, reserves: &Reserves) -> Result<(Amount, Reserves, Amount)> {
291 // We distinguish two cases, which only differ in their rounding
292 // behavior.
293 //
294 // If the desired fill is less than the original reserves, we want to
295 // work "forward" from the input amount `delta_1` to the output amount
296 // `lambda_2`, consuming exactly `delta_1` and rounding `lambda_2`
297 // (down, so that we're burning the rounding error).
298 //
299 // If the desired fill is greater than the original reserves, however,
300 // we want to work "backward" from the available reserves `R_2` (the
301 // "max fill") to the input amount `delta_1`, producing exactly
302 // `lambda_2 = R_2` output and rounding `delta_1` (up, so that we're
303 // burning the rounding error).
304 //
305 // We want to be sure that in either case, we only round once, and derive
306 // other quantities exactly from the rounded quantity. This ensures
307 // conservation of value.
308 //
309 // This also ensures that we cleanly fill the position, rather than
310 // leaving some dust amount of reserves in it. Otherwise, we might try
311 // executing against it again on a subsequent iteration, even though it
312 // was essentially filled.
313
314 // The effective price is the conversion rate between `2` and `1`:
315 // effective_price = (q/[gamma*p])
316 // effective_price_inv = gamma*(p/q)
317
318 // The trade output `lambda_2` is given by `effective_price * delta_1`, however, to avoid
319 // rounding loss, we prefer to first compute the numerator `(gamma * delta_1 * q)`, and then
320 // perform division.
321 let delta_1_fp = U128x128::from(delta_1);
322 let tentative_lambda_2 = self.convert_to_lambda_2(delta_1_fp)?;
323
324 if tentative_lambda_2 <= reserves.r2.into() {
325 // Observe that for the case when `tentative_lambda_2` equals
326 // `reserves.r1`, rounding it down does not change anything since
327 // `reserves.r1` is integral. Therefore `reserves.r1 - lambda_2 >= 0`.
328 let lambda_2: Amount = tentative_lambda_2
329 .round_down()
330 .try_into()
331 .expect("lambda_2 fits in an Amount");
332 let new_reserves = Reserves {
333 r1: reserves.r1 + delta_1,
334 r2: reserves.r2 - lambda_2,
335 };
336 Ok((0u64.into(), new_reserves, lambda_2))
337 } else {
338 let r2: U128x128 = reserves.r2.into();
339 // In this case, we don't have enough reserves to completely execute
340 // the fill. So we know that `lambda_2 = r2` or that the output will
341 // consist of all the reserves available.
342 //
343 // We must work backwards to infer what `delta_1` (input) correspond
344 // exactly to a fill of `lambda_2 = r2`.
345 //
346 // Normally, we would have:
347 //
348 // lambda_2 = effective_price * delta_1
349 // since lambda_2 = r2, we have:
350 //
351 // r2 = effective_price * delta_1, and since p,q != 0, effective_price != 0:
352 // delta_1 = r2 * effective_price^-1
353 let fillable_delta_1 = self.convert_to_delta_1(r2)?;
354
355 // We burn the rouding error by apply `ceil` to delta_1:
356 //
357 // delta_1_star = Ceil(delta_1)
358 // TODO: round_up is now fallible
359 let fillable_delta_1_exact: Amount = fillable_delta_1
360 .round_up()
361 .expect("no overflow")
362 .try_into()
363 .expect("fillable_delta_1 fits in an Amount");
364
365 // How to show that: `unfilled_amount >= 0`:
366 // In this branch, we have:
367 // lambda_2 > R_2, where lambda_2 = delta_1 * effective_price:
368 // delta_1 * effective_price > R_2, in other words:
369 // <=> delta_1 > R_2 * (effective_price)^-1, in other words:
370 // delta_1 > R_2 * effective_price_inv
371 //
372 // fillable_delta_1_exact = ceil(RHS) is integral (rounded), and
373 // delta_1 is integral by definition.
374 //
375 // Therefore, we have:
376 //
377 // delta_1 >= fillable_delta_1_exact, or in other words:
378 //
379 // unfilled_amount >= 0.
380 let unfilled_amount = delta_1 - fillable_delta_1_exact;
381
382 let new_reserves = Reserves {
383 r1: reserves.r1 + fillable_delta_1_exact,
384 r2: 0u64.into(),
385 };
386 Ok((unfilled_amount, new_reserves, reserves.r2))
387 }
388 }
389
390 /// Returns a byte key for this trading function with the property that the
391 /// lexicographic ordering on byte keys is the same as ordering the
392 /// corresponding trading functions by effective price.
393 ///
394 /// This allows trading functions to be indexed by price using a key-value store.
395 pub fn effective_price_key_bytes(&self) -> [u8; 32] {
396 self.effective_price().to_bytes()
397 }
398
399 /// Returns the inverse of the `effective_price`, in other words,
400 /// the exchange rate from `asset_1` to `asset_2`:
401 /// `delta_1 * effective_price_inv = lambda_2`
402 pub fn effective_price_inv(&self) -> U128x128 {
403 let p = U128x128::from(self.p);
404 let q = U128x128::from(self.q);
405
406 let price_ratio = (p / q).expect("q != 0 and p,q <= 2^60");
407 (price_ratio * self.gamma()).expect("2^-1 <= gamma <= 1")
408 }
409
410 /// Returns the exchange rate from `asset_2` to `asset_1, inclusive
411 /// of fees:
412 /// `lambda_2 * effective_price = delta_1`
413 pub fn effective_price(&self) -> U128x128 {
414 let p = U128x128::from(self.p);
415 let q = U128x128::from(self.q);
416
417 let price_ratio = (q / p).expect("p != 0 and p,q <= 2^60");
418 price_ratio.checked_div(&self.gamma()).expect("gamma != 0")
419 }
420
421 /// Converts an amount `delta_1` into `lambda_2`, using the inverse of the effective price.
422 pub fn convert_to_lambda_2(&self, delta_1: U128x128) -> anyhow::Result<U128x128> {
423 let lambda_2 = self.effective_price_inv() * delta_1;
424 Ok(lambda_2?)
425 }
426
427 /// Converts an amount of `lambda_2` into `delta_1`, using the effective price.
428 pub fn convert_to_delta_1(&self, lambda_2: U128x128) -> anyhow::Result<U128x128> {
429 let delta_1 = self.effective_price() * lambda_2;
430 Ok(delta_1?)
431 }
432
433 /// Returns `gamma` i.e. the fee percentage.
434 /// The fee is expressed in basis points (0 <= fee < 5000), where 5000bps = 50%.
435 ///
436 /// ## Bounds:
437 /// Since the fee `f` is bound by `0 <= < 5_000`, we have `1/2 <= gamma <= 1`.
438 ///
439 /// ## Examples:
440 ///
441 /// * A fee of 0% (0 bps) results in a discount factor of 1.
442 /// * A fee of 30 bps (30 bps) results in a discount factor of 0.997.
443 /// * A fee of 100% (10_000bps) results in a discount factor of 0.
444 pub fn gamma(&self) -> U128x128 {
445 (U128x128::from(10_000 - self.fee) / U128x128::from(10_000u64)).expect("10_000 != 0")
446 }
447
448 /// Compose two trading functions together
449 #[deprecated(note = "this method is not yet implemented")]
450 pub fn compose(&self, _phi: BareTradingFunction) -> BareTradingFunction {
451 unimplemented!()
452 }
453}
454
455impl DomainType for BareTradingFunction {
456 type Proto = pb::BareTradingFunction;
457}
458
459impl TryFrom<pb::BareTradingFunction> for BareTradingFunction {
460 type Error = anyhow::Error;
461
462 fn try_from(value: pb::BareTradingFunction) -> Result<Self, Self::Error> {
463 Ok(Self {
464 fee: value.fee,
465 p: value
466 .p
467 .ok_or_else(|| anyhow::anyhow!("missing p"))?
468 .try_into()?,
469 q: value
470 .q
471 .ok_or_else(|| anyhow::anyhow!("missing q"))?
472 .try_into()?,
473 })
474 }
475}
476
477impl From<BareTradingFunction> for pb::BareTradingFunction {
478 fn from(value: BareTradingFunction) -> Self {
479 Self {
480 fee: value.fee,
481 p: Some(value.p.into()),
482 q: Some(value.q.into()),
483 }
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use ark_ff::Zero;
490 use decaf377::Fq;
491 use penumbra_sdk_asset::asset::Id;
492
493 use super::*;
494
495 #[test]
496 /// Test that effective prices are encoded in a way that preserves their
497 /// numerical ordering. Numerical ordering should transfer over lexicographic order
498 /// of the encoded prices.
499 fn test_trading_function_to_bytes() {
500 let btf = BareTradingFunction {
501 fee: 0,
502 p: 2_000_000u32.into(),
503 q: 1_000_000u32.into(),
504 };
505
506 assert_eq!(btf.gamma(), U128x128::from(1u64));
507 assert_eq!(
508 btf.effective_price_inv(),
509 U128x128::ratio(btf.p, btf.q).unwrap()
510 );
511 let bytes1 = btf.effective_price_key_bytes();
512 let price1 = btf.effective_price();
513
514 let btf = BareTradingFunction {
515 fee: 100,
516 p: 2_000_000u32.into(),
517 q: 1_000_000u32.into(),
518 };
519
520 // Compares the `BareTradingFunction::gamma` to a scaled ratio (10^4)
521 let gamma_term =
522 U128x128::ratio::<Amount>(99_000_000u64.into(), 100_000_000u64.into()).unwrap();
523 assert_eq!(btf.gamma(), gamma_term,);
524
525 let price_without_fee = U128x128::ratio(btf.p, btf.q).unwrap();
526 let price_with_fee = (price_without_fee * gamma_term).unwrap();
527
528 assert_eq!(btf.effective_price_inv(), price_with_fee);
529 let bytes2 = btf.effective_price_key_bytes();
530 let price2 = btf.effective_price();
531
532 // Asserts that the lexicographic ordering of the encoded prices matches
533 // their ask price ordering (smaller = better).
534 //
535 // price1: trading function with 0 bps fee.
536 // price2: trading function with 100 bps fee.
537 // price1 is "better" than price2.
538 assert!(price1 < price2);
539 assert!(bytes1 < bytes2);
540 }
541
542 #[test]
543 /// Test that filling a position follows the asset conservation law,
544 /// meaning that the R + Delta = R + Lambda
545 ///
546 /// There is two branches of the `BareTradingFunction::fill` method that we
547 /// want to exercise. The first one is executed when there are enough reserves
548 /// available to perform the fill.
549 ///
550 /// The second case, is when the output is constrained by the available reserves.
551 fn fill_conserves_value() {
552 let btf = BareTradingFunction {
553 fee: 0,
554 p: 1_u32.into(),
555 q: 3_u32.into(),
556 };
557
558 // First, we want to test asset conservations in the case of a partial fill:
559 // D_1 = 10,000,000
560 // D_2 = 0
561 //
562 // price: p/q = 1/3, so you get 1 unit of asset 2 for 3 units of asset 1.
563 //
564 // L_1 = 0
565 // L_2 = 3_333_333 (D_1/3)
566
567 let old_reserves = Reserves {
568 r1: 1_000_000u64.into(),
569 r2: 100_000_000u64.into(),
570 };
571
572 let delta_1 = 10_000_000u64.into();
573 let delta_2 = 0u64.into();
574 let (lambda_1, new_reserves, lambda_2) = btf
575 .fill(delta_1, &old_reserves)
576 .expect("filling can't fail");
577 // Conservation of value:
578 assert_eq!(old_reserves.r1 + delta_1, new_reserves.r1 + lambda_1);
579 assert_eq!(old_reserves.r2 + delta_2, new_reserves.r2 + lambda_2);
580
581 // Exact amount checks:
582 assert_eq!(lambda_1, 0u64.into());
583 assert_eq!(lambda_2, 3_333_333u64.into());
584
585 // Here we test trying to swap or more output than what is available in
586 // the reserves:
587 // lambda_1 = delta_1/3
588 // lambda_2 = r2
589 let old_reserves = Reserves {
590 r1: 1_000_000u64.into(),
591 r2: 100_000_000u64.into(),
592 };
593 let delta_1 = 600_000_000u64.into();
594 let delta_2 = 0u64.into();
595
596 let (lambda_1, new_reserves, lambda_2) = btf
597 .fill(delta_1, &old_reserves)
598 .expect("filling can't fail");
599 // Conservation of value:
600 assert_eq!(old_reserves.r1 + delta_1, new_reserves.r1 + lambda_1);
601 assert_eq!(old_reserves.r2 + delta_2, new_reserves.r2 + lambda_2);
602
603 // Exact amount checks:
604 assert_eq!(lambda_1, 300_000_000u64.into());
605 assert_eq!(lambda_2, old_reserves.r2);
606 assert_eq!(new_reserves.r2, 0u64.into());
607 }
608
609 #[test]
610 fn fill_bad_rounding() {
611 let btf = BareTradingFunction {
612 fee: 0,
613 p: 12u32.into(),
614 q: 10u32.into(),
615 };
616
617 let old_reserves = Reserves {
618 r1: 0u64.into(),
619 r2: 120u64.into(),
620 };
621
622 let delta_1 = 100u64.into();
623 let (lambda_1, new_reserves, lambda_2) = btf
624 .fill(delta_1, &old_reserves)
625 .expect("filling can't fail");
626
627 // Conservation of value:
628 assert_eq!(old_reserves.r1 + delta_1, new_reserves.r1 + lambda_1);
629 assert_eq!(old_reserves.r2 + 0u64.into(), new_reserves.r2 + lambda_2);
630 // Exact amount checks:
631 assert_eq!(lambda_1, 0u64.into());
632 // We expect some lossy rounding here:
633 assert_eq!(lambda_2, 119u64.into());
634 }
635
636 #[test]
637 /// Test that the `convert_to_delta_1` and `convert_to_lambda_2` helper functions
638 /// are aligned with `effective_price` and `effective_price_inv` calculations.
639 fn test_conversion_helpers() {
640 let btf = BareTradingFunction {
641 fee: 150,
642 p: 12u32.into(),
643 q: 55u32.into(),
644 };
645
646 let one = U128x128::from(1u64);
647
648 assert_eq!(btf.effective_price(), btf.convert_to_delta_1(one).unwrap());
649 assert_eq!(
650 btf.effective_price_inv(),
651 btf.convert_to_lambda_2(one).unwrap()
652 );
653 }
654
655 #[test]
656 /// Test that the `TradingFunction` fills work correctly.
657 fn test_fill_trading_function() {
658 let a = Id(Fq::zero());
659 let b = Id(Fq::ONE);
660 let c = Id(Fq::ONE + Fq::ONE);
661
662 assert!(a < b);
663 assert!(b < c);
664
665 // First, we test that everything works well when we do a fill from A to B
666 // where id(A) < id(B).
667 let p = Amount::from(1u64);
668 let q = Amount::from(2u64);
669 let phi = TradingFunction::new(TradingPair::new(a, b), 0u32, p, q);
670 let reserves = Reserves {
671 r1: 0u64.into(),
672 r2: 100u64.into(),
673 };
674
675 let delta_1 = Value {
676 amount: 200u64.into(),
677 asset_id: a,
678 };
679
680 // TradingFunction::fill returns the unfilled amount, the new reserves, and the output:
681 let (lambda_1, new_reserves, lambda_2) = phi.fill(delta_1, &reserves).unwrap();
682
683 assert_eq!(lambda_1.amount, Amount::zero());
684 assert_eq!(lambda_1.asset_id, delta_1.asset_id);
685
686 assert_eq!(lambda_2.amount, reserves.r2);
687 assert_eq!(lambda_2.asset_id, b);
688
689 assert_eq!(new_reserves.r1, Amount::from(200u64));
690 assert_eq!(new_reserves.r2, Amount::zero());
691
692 // Now, we check that we fill correctly from B to A:
693 // where id(A) < id(B).
694 let delta_2 = Value {
695 amount: 50u64.into(),
696 asset_id: b,
697 };
698
699 let reserves = Reserves {
700 r1: 100u64.into(),
701 r2: 0u64.into(),
702 };
703
704 let (lambda_2, new_reserves, lambda_1) = phi.fill(delta_2, &reserves).unwrap();
705
706 assert_eq!(lambda_2.amount, Amount::zero());
707 assert_eq!(lambda_2.asset_id, b);
708
709 assert_eq!(lambda_1.amount, Amount::from(100u64));
710 assert_eq!(lambda_1.asset_id, a);
711
712 assert_eq!(new_reserves.r1, Amount::zero());
713 assert_eq!(new_reserves.r2, Amount::from(50u64));
714 }
715}