dlc_manager/
conversion_utils.rs

1use crate::contract::{
2    contract_info::ContractInfo,
3    enum_descriptor::EnumDescriptor,
4    numerical_descriptor::{DifferenceParams, NumericalDescriptor},
5    offered_contract::OfferedContract,
6    ContractDescriptor,
7};
8use crate::payout_curve::{
9    HyperbolaPayoutCurvePiece, PayoutFunction, PayoutFunctionPiece, PayoutPoint,
10    PolynomialPayoutCurvePiece, RoundingInterval, RoundingIntervals,
11};
12use bitcoin::{consensus::encode::Decodable, Amount, OutPoint, Transaction};
13use dlc::{EnumerationPayout, Payout, TxInputInfo};
14use dlc_messages::oracle_msgs::{
15    MultiOracleInfo, OracleInfo as SerOracleInfo, OracleParams, SingleOracleInfo,
16};
17use dlc_messages::FundingInput;
18use dlc_messages::{
19    contract_msgs::{
20        ContractDescriptor as SerContractDescriptor, ContractInfo as SerContractInfo,
21        ContractInfoInner, ContractOutcome, DisjointContractInfo, EnumeratedContractDescriptor,
22        HyperbolaPayoutCurvePiece as SerHyperbolaPayoutCurvePiece,
23        NumericOutcomeContractDescriptor, PayoutCurvePiece as SerPayoutCurvePiece,
24        PayoutFunction as SerPayoutFunction, PayoutFunctionPiece as SerPayoutFunctionPiece,
25        PayoutPoint as SerPayoutPoint, PolynomialPayoutCurvePiece as SerPolynomialPayoutCurvePiece,
26        RoundingInterval as SerRoundingInterval, RoundingIntervals as SerRoundingIntervals,
27        SingleContractInfo,
28    },
29    oracle_msgs::EventDescriptor,
30};
31use dlc_trie::OracleNumericInfo;
32use std::fmt;
33
34pub(crate) const BITCOIN_CHAINHASH: [u8; 32] = [
35    0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf,
36    0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f,
37];
38
39pub(crate) const PROTOCOL_VERSION: u32 = 1;
40
41#[derive(Debug)]
42pub enum Error {
43    BitcoinEncoding(bitcoin::consensus::encode::Error),
44    InvalidParameters,
45}
46
47impl fmt::Display for Error {
48    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49        match *self {
50            Error::BitcoinEncoding(_) => write!(f, "Invalid encoding"),
51            Error::InvalidParameters => write!(f, "Invalid parameters."),
52        }
53    }
54}
55
56#[cfg(feature = "std")]
57impl std::error::Error for Error {
58    fn cause(&self) -> Option<&dyn std::error::Error> {
59        match *self {
60            Error::BitcoinEncoding(ref e) => Some(e),
61            Error::InvalidParameters => None,
62        }
63    }
64}
65
66impl From<bitcoin::consensus::encode::Error> for Error {
67    fn from(e: bitcoin::consensus::encode::Error) -> Error {
68        Error::BitcoinEncoding(e)
69    }
70}
71
72pub fn get_tx_input_infos(
73    funding_inputs: &[FundingInput],
74) -> Result<(Vec<TxInputInfo>, Amount), Error> {
75    let mut input_amount = Amount::ZERO;
76    let mut inputs = Vec::new();
77
78    for fund_input in funding_inputs {
79        let tx = Transaction::consensus_decode(&mut fund_input.prev_tx.as_slice())?;
80        let vout = fund_input.prev_tx_vout;
81        let tx_out = tx
82            .output
83            .get(vout as usize)
84            .ok_or(Error::InvalidParameters)?;
85        input_amount += tx_out.value;
86        inputs.push(TxInputInfo {
87            outpoint: OutPoint {
88                txid: tx.compute_txid(),
89                vout,
90            },
91            max_witness_len: 107,
92            redeem_script: fund_input.redeem_script.clone(),
93            serial_id: fund_input.input_serial_id,
94        });
95    }
96
97    Ok((inputs, input_amount))
98}
99
100pub(crate) fn get_contract_info_and_announcements(
101    contract_info: &SerContractInfo,
102) -> Result<Vec<ContractInfo>, Error> {
103    let mut contract_infos = Vec::new();
104    let (total_collateral, inner_contract_infos) = match contract_info {
105        SerContractInfo::SingleContractInfo(single) => {
106            (single.total_collateral, vec![single.contract_info.clone()])
107        }
108        SerContractInfo::DisjointContractInfo(disjoint) => {
109            (disjoint.total_collateral, disjoint.contract_infos.clone())
110        }
111    };
112
113    for contract_info in inner_contract_infos {
114        let (descriptor, oracle_announcements, threshold) = match contract_info.contract_descriptor
115        {
116            SerContractDescriptor::EnumeratedContractDescriptor(enumerated) => {
117                let outcome_payouts = enumerated
118                    .payouts
119                    .iter()
120                    .map(|x| EnumerationPayout {
121                        outcome: x.outcome.clone(),
122                        payout: Payout {
123                            offer: x.offer_payout,
124                            accept: total_collateral - x.offer_payout,
125                        },
126                    })
127                    .collect();
128                let descriptor = ContractDescriptor::Enum(EnumDescriptor { outcome_payouts });
129                let mut threshold = 1;
130                let announcements = match contract_info.oracle_info {
131                    SerOracleInfo::Single(single) => vec![single.oracle_announcement],
132                    SerOracleInfo::Multi(multi) => {
133                        threshold = multi.threshold;
134                        multi.oracle_announcements
135                    }
136                };
137
138                if announcements
139                    .iter()
140                    .any(|x| match &x.oracle_event.event_descriptor {
141                        EventDescriptor::EnumEvent(_) => false,
142                        EventDescriptor::DigitDecompositionEvent(_) => true,
143                    })
144                {
145                    return Err(Error::InvalidParameters);
146                }
147
148                (descriptor, announcements, threshold)
149            }
150            SerContractDescriptor::NumericOutcomeContractDescriptor(numeric) => {
151                let threshold;
152                let mut difference_params: Option<DifferenceParams> = None;
153                let announcements = match contract_info.oracle_info {
154                    SerOracleInfo::Single(single) => {
155                        threshold = 1;
156                        vec![single.oracle_announcement]
157                    }
158                    SerOracleInfo::Multi(multi) => {
159                        threshold = multi.threshold;
160                        if let Some(params) = multi.oracle_params {
161                            difference_params = Some(DifferenceParams {
162                                max_error_exp: params.max_error_exp as usize,
163                                min_support_exp: params.min_fail_exp as usize,
164                                maximize_coverage: params.maximize_coverage,
165                            })
166                        }
167                        multi.oracle_announcements.clone()
168                    }
169                };
170                if announcements.is_empty() {
171                    return Err(Error::InvalidParameters);
172                }
173                let expected_base = if let EventDescriptor::DigitDecompositionEvent(d) =
174                    &announcements[0].oracle_event.event_descriptor
175                {
176                    d.base
177                } else {
178                    return Err(Error::InvalidParameters);
179                };
180                let nb_digits = announcements
181                    .iter()
182                    .map(|x| match &x.oracle_event.event_descriptor {
183                        EventDescriptor::DigitDecompositionEvent(d) => {
184                            if d.base == expected_base {
185                                Ok(d.nb_digits as usize)
186                            } else {
187                                Err(Error::InvalidParameters)
188                            }
189                        }
190                        _ => Err(Error::InvalidParameters),
191                    })
192                    .collect::<Result<Vec<_>, _>>()?;
193                let descriptor = ContractDescriptor::Numerical(NumericalDescriptor {
194                    payout_function: (&numeric.payout_function).into(),
195                    rounding_intervals: (&numeric.rounding_intervals).into(),
196                    difference_params,
197                    oracle_numeric_infos: OracleNumericInfo {
198                        base: expected_base as usize,
199                        nb_digits,
200                    },
201                });
202                (descriptor, announcements, threshold)
203            }
204        };
205        contract_infos.push(ContractInfo {
206            contract_descriptor: descriptor,
207            oracle_announcements,
208            threshold: threshold as usize,
209        });
210    }
211
212    Ok(contract_infos)
213}
214
215impl From<&OfferedContract> for SerContractInfo {
216    fn from(offered_contract: &OfferedContract) -> SerContractInfo {
217        let oracle_infos: Vec<SerOracleInfo> = offered_contract.into();
218        let mut contract_infos: Vec<ContractInfoInner> = offered_contract
219            .contract_info
220            .iter()
221            .zip(oracle_infos)
222            .map(|(c, o)| ContractInfoInner {
223                contract_descriptor: (&c.contract_descriptor).into(),
224                oracle_info: o,
225            })
226            .collect();
227        if contract_infos.len() == 1 {
228            SerContractInfo::SingleContractInfo(SingleContractInfo {
229                total_collateral: offered_contract.total_collateral,
230                contract_info: contract_infos.remove(0),
231            })
232        } else {
233            SerContractInfo::DisjointContractInfo(DisjointContractInfo {
234                total_collateral: offered_contract.total_collateral,
235                contract_infos,
236            })
237        }
238    }
239}
240
241impl From<&OfferedContract> for Vec<SerOracleInfo> {
242    fn from(offered_contract: &OfferedContract) -> Vec<SerOracleInfo> {
243        let mut infos = Vec::new();
244        for contract_info in &offered_contract.contract_info {
245            let announcements = &contract_info.oracle_announcements;
246            if announcements.len() == 1 {
247                infos.push(SerOracleInfo::Single(SingleOracleInfo {
248                    oracle_announcement: announcements[0].clone(),
249                }));
250            } else {
251                if let ContractDescriptor::Numerical(n) = &contract_info.contract_descriptor {
252                    if let Some(params) = &n.difference_params {
253                        infos.push(SerOracleInfo::Multi(MultiOracleInfo {
254                            threshold: contract_info.threshold as u16,
255                            oracle_announcements: announcements.clone(),
256                            oracle_params: Some(OracleParams {
257                                max_error_exp: params.max_error_exp as u16,
258                                min_fail_exp: params.min_support_exp as u16,
259                                maximize_coverage: params.maximize_coverage,
260                            }),
261                        }));
262                        continue;
263                    }
264                }
265                infos.push(SerOracleInfo::Multi(MultiOracleInfo {
266                    threshold: contract_info.threshold as u16,
267                    oracle_announcements: announcements.clone(),
268                    oracle_params: None,
269                }))
270            }
271        }
272
273        infos
274    }
275}
276
277impl From<&EnumDescriptor> for EnumeratedContractDescriptor {
278    fn from(enum_descriptor: &EnumDescriptor) -> EnumeratedContractDescriptor {
279        let payouts: Vec<ContractOutcome> = enum_descriptor
280            .outcome_payouts
281            .iter()
282            .map(|x| ContractOutcome {
283                outcome: x.outcome.clone(),
284                offer_payout: x.payout.offer,
285            })
286            .collect();
287        EnumeratedContractDescriptor { payouts }
288    }
289}
290
291impl From<&NumericalDescriptor> for NumericOutcomeContractDescriptor {
292    fn from(num_descriptor: &NumericalDescriptor) -> NumericOutcomeContractDescriptor {
293        NumericOutcomeContractDescriptor {
294            num_digits: *num_descriptor
295                .oracle_numeric_infos
296                .nb_digits
297                .iter()
298                .min()
299                .expect("to have at least a value") as u16,
300            payout_function: (&num_descriptor.payout_function).into(),
301            rounding_intervals: (&num_descriptor.rounding_intervals).into(),
302        }
303    }
304}
305
306impl From<&ContractDescriptor> for SerContractDescriptor {
307    fn from(descriptor: &ContractDescriptor) -> SerContractDescriptor {
308        match descriptor {
309            ContractDescriptor::Enum(e) => {
310                SerContractDescriptor::EnumeratedContractDescriptor(e.into())
311            }
312            ContractDescriptor::Numerical(n) => {
313                SerContractDescriptor::NumericOutcomeContractDescriptor(n.into())
314            }
315        }
316    }
317}
318
319impl From<&PayoutFunction> for SerPayoutFunction {
320    fn from(payout_function: &PayoutFunction) -> SerPayoutFunction {
321        SerPayoutFunction {
322            payout_function_pieces: payout_function
323                .payout_function_pieces
324                .iter()
325                .map(|x| {
326                    let (left, piece) = match x {
327                        PayoutFunctionPiece::PolynomialPayoutCurvePiece(p) => (
328                            (&p.payout_points[0]).into(),
329                            SerPayoutCurvePiece::PolynomialPayoutCurvePiece(
330                                SerPolynomialPayoutCurvePiece {
331                                    payout_points: p
332                                        .payout_points
333                                        .iter()
334                                        .skip(1)
335                                        .take(p.payout_points.len() - 2)
336                                        .map(|x| x.into())
337                                        .collect(),
338                                },
339                            ),
340                        ),
341                        PayoutFunctionPiece::HyperbolaPayoutCurvePiece(h) => (
342                            (&h.left_end_point).into(),
343                            SerPayoutCurvePiece::HyperbolaPayoutCurvePiece(h.into()),
344                        ),
345                    };
346                    SerPayoutFunctionPiece {
347                        end_point: left,
348                        payout_curve_piece: piece,
349                    }
350                })
351                .collect(),
352            last_endpoint: {
353                let last_piece = payout_function.payout_function_pieces.last().unwrap();
354                match last_piece {
355                    PayoutFunctionPiece::PolynomialPayoutCurvePiece(p) => {
356                        p.payout_points.last().unwrap().into()
357                    }
358                    PayoutFunctionPiece::HyperbolaPayoutCurvePiece(h) => {
359                        (&h.right_end_point).into()
360                    }
361                }
362            },
363        }
364    }
365}
366
367impl From<&SerPayoutFunction> for PayoutFunction {
368    fn from(payout_function: &SerPayoutFunction) -> PayoutFunction {
369        PayoutFunction {
370            payout_function_pieces: payout_function
371                .payout_function_pieces
372                .iter()
373                .zip(
374                    payout_function
375                        .payout_function_pieces
376                        .iter()
377                        .skip(1)
378                        .map(|x| &x.end_point)
379                        .chain(vec![&payout_function.last_endpoint]),
380                )
381                .map(|(x, y)| from_ser_payout_function_piece(x, y))
382                .collect(),
383        }
384    }
385}
386
387fn from_ser_payout_function_piece(
388    piece: &SerPayoutFunctionPiece,
389    right_end_point: &SerPayoutPoint,
390) -> PayoutFunctionPiece {
391    match &piece.payout_curve_piece {
392        SerPayoutCurvePiece::PolynomialPayoutCurvePiece(p) => {
393            PayoutFunctionPiece::PolynomialPayoutCurvePiece(PolynomialPayoutCurvePiece {
394                payout_points: vec![(&piece.end_point).into()]
395                    .into_iter()
396                    .chain(p.payout_points.iter().map(|x| x.into()))
397                    .chain(vec![(right_end_point).into()])
398                    .collect(),
399            })
400        }
401        SerPayoutCurvePiece::HyperbolaPayoutCurvePiece(h) => {
402            PayoutFunctionPiece::HyperbolaPayoutCurvePiece(HyperbolaPayoutCurvePiece {
403                left_end_point: (&piece.end_point).into(),
404                right_end_point: right_end_point.into(),
405                use_positive_piece: h.use_positive_piece,
406                translate_outcome: h.translate_outcome,
407                translate_payout: h.translate_payout,
408                a: h.a,
409                b: h.b,
410                c: h.c,
411                d: h.d,
412            })
413        }
414    }
415}
416
417impl From<&RoundingIntervals> for SerRoundingIntervals {
418    fn from(rounding_intervals: &RoundingIntervals) -> SerRoundingIntervals {
419        let intervals = rounding_intervals
420            .intervals
421            .iter()
422            .map(|x| x.into())
423            .collect();
424        SerRoundingIntervals { intervals }
425    }
426}
427
428impl From<&SerRoundingIntervals> for RoundingIntervals {
429    fn from(rounding_intervals: &SerRoundingIntervals) -> RoundingIntervals {
430        let intervals = rounding_intervals
431            .intervals
432            .iter()
433            .map(|x| x.into())
434            .collect();
435        RoundingIntervals { intervals }
436    }
437}
438
439impl From<&RoundingInterval> for SerRoundingInterval {
440    fn from(rounding_interval: &RoundingInterval) -> SerRoundingInterval {
441        SerRoundingInterval {
442            begin_interval: rounding_interval.begin_interval,
443            rounding_mod: rounding_interval.rounding_mod,
444        }
445    }
446}
447
448impl From<&SerRoundingInterval> for RoundingInterval {
449    fn from(rounding_interval: &SerRoundingInterval) -> RoundingInterval {
450        RoundingInterval {
451            begin_interval: rounding_interval.begin_interval,
452            rounding_mod: rounding_interval.rounding_mod,
453        }
454    }
455}
456
457impl From<&PayoutPoint> for SerPayoutPoint {
458    fn from(payout_point: &PayoutPoint) -> SerPayoutPoint {
459        SerPayoutPoint {
460            event_outcome: payout_point.event_outcome,
461            outcome_payout: payout_point.outcome_payout,
462            extra_precision: payout_point.extra_precision,
463        }
464    }
465}
466
467impl From<&SerPayoutPoint> for PayoutPoint {
468    fn from(payout_point: &SerPayoutPoint) -> PayoutPoint {
469        PayoutPoint {
470            event_outcome: payout_point.event_outcome,
471            outcome_payout: payout_point.outcome_payout,
472            extra_precision: payout_point.extra_precision,
473        }
474    }
475}
476
477impl From<&HyperbolaPayoutCurvePiece> for SerHyperbolaPayoutCurvePiece {
478    fn from(piece: &HyperbolaPayoutCurvePiece) -> SerHyperbolaPayoutCurvePiece {
479        SerHyperbolaPayoutCurvePiece {
480            use_positive_piece: piece.use_positive_piece,
481            translate_outcome: piece.translate_outcome,
482            translate_payout: piece.translate_payout,
483            a: piece.a,
484            b: piece.b,
485            c: piece.c,
486            d: piece.d,
487        }
488    }
489}
490
491impl From<&PolynomialPayoutCurvePiece> for SerPolynomialPayoutCurvePiece {
492    fn from(piece: &PolynomialPayoutCurvePiece) -> SerPolynomialPayoutCurvePiece {
493        SerPolynomialPayoutCurvePiece {
494            payout_points: piece
495                .payout_points
496                .iter()
497                .skip(1)
498                .take(piece.payout_points.len() - 2)
499                .map(|x| x.into())
500                .collect(),
501        }
502    }
503}
504
505impl From<&SerPolynomialPayoutCurvePiece> for PolynomialPayoutCurvePiece {
506    fn from(piece: &SerPolynomialPayoutCurvePiece) -> PolynomialPayoutCurvePiece {
507        PolynomialPayoutCurvePiece {
508            payout_points: piece.payout_points.iter().map(|x| x.into()).collect(),
509        }
510    }
511}
512
513impl From<&DifferenceParams> for OracleParams {
514    fn from(input: &DifferenceParams) -> OracleParams {
515        OracleParams {
516            max_error_exp: input.max_error_exp as u16,
517            min_fail_exp: input.min_support_exp as u16,
518            maximize_coverage: input.maximize_coverage,
519        }
520    }
521}
522
523impl From<&OracleParams> for DifferenceParams {
524    fn from(input: &OracleParams) -> DifferenceParams {
525        DifferenceParams {
526            max_error_exp: input.max_error_exp as usize,
527            min_support_exp: input.min_fail_exp as usize,
528            maximize_coverage: input.maximize_coverage,
529        }
530    }
531}
532
533#[cfg(test)]
534mod tests {
535    use super::*;
536
537    #[test]
538    fn payout_function_round_trip() {
539        let payout_function = PayoutFunction {
540            payout_function_pieces: vec![
541                PayoutFunctionPiece::PolynomialPayoutCurvePiece(PolynomialPayoutCurvePiece {
542                    payout_points: vec![
543                        PayoutPoint {
544                            event_outcome: 0,
545                            outcome_payout: Amount::ZERO,
546                            extra_precision: 0,
547                        },
548                        PayoutPoint {
549                            event_outcome: 9,
550                            outcome_payout: Amount::ZERO,
551                            extra_precision: 0,
552                        },
553                    ],
554                }),
555                PayoutFunctionPiece::PolynomialPayoutCurvePiece(PolynomialPayoutCurvePiece {
556                    payout_points: vec![
557                        PayoutPoint {
558                            event_outcome: 9,
559                            outcome_payout: Amount::ZERO,
560                            extra_precision: 0,
561                        },
562                        PayoutPoint {
563                            event_outcome: 10,
564                            outcome_payout: Amount::from_sat(10),
565                            extra_precision: 0,
566                        },
567                    ],
568                }),
569                PayoutFunctionPiece::PolynomialPayoutCurvePiece(PolynomialPayoutCurvePiece {
570                    payout_points: vec![
571                        PayoutPoint {
572                            event_outcome: 10,
573                            outcome_payout: Amount::from_sat(10),
574                            extra_precision: 0,
575                        },
576                        PayoutPoint {
577                            event_outcome: 20,
578                            outcome_payout: Amount::from_sat(10),
579                            extra_precision: 0,
580                        },
581                    ],
582                }),
583            ],
584        };
585        let ser_payout_function: SerPayoutFunction = (&payout_function).into();
586        let res: PayoutFunction = (&ser_payout_function).into();
587        assert_eq!(payout_function, res);
588    }
589}