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}