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