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}