Skip to main content

ddk_payouts/
lib.rs

1pub mod enumeration;
2pub mod options;
3pub(crate) mod options_builder;
4pub mod parlay;
5
6use std::str::FromStr;
7
8use bitcoin::key::XOnlyPublicKey;
9use bitcoin::Amount;
10use ddk_manager::contract::numerical_descriptor::NumericalDescriptor;
11use ddk_manager::{
12    contract::{
13        contract_input::{ContractInput, ContractInputInfo, OracleInput},
14        ContractDescriptor,
15    },
16    payout_curve::{
17        PayoutFunction, PayoutFunctionPiece, PayoutPoint, PolynomialPayoutCurvePiece,
18        RoundingInterval, RoundingIntervals,
19    },
20};
21use ddk_trie::OracleNumericInfo;
22
23pub fn generate_payout_curve(
24    min_price: u64,
25    max_price: u64,
26    offer_collateral: Amount,
27    accept_collateral: Amount,
28    num_steps: u64,
29    max_value: u64,
30) -> anyhow::Result<PayoutFunction> {
31    let total_collateral = offer_collateral + accept_collateral;
32    let price_range = max_price - min_price;
33    let step_size = price_range / (num_steps - 1);
34
35    let mut points = Vec::with_capacity((num_steps).try_into().unwrap());
36
37    for i in 0..num_steps {
38        let price = if i == num_steps - 1 {
39            max_price // Ensure the last point is exactly at max_price
40        } else {
41            min_price + i * step_size
42        };
43
44        let payout = if i == num_steps - 1 {
45            total_collateral.to_sat() // Ensure the last payout is the total collateral
46        } else {
47            (i * total_collateral.to_sat()) / (num_steps - 1)
48        };
49        points.push(PayoutPoint {
50            event_outcome: price,
51            extra_precision: 0,
52            outcome_payout: Amount::from_sat(payout),
53        });
54    }
55
56    let final_payout_piece = points[points.len() - 1].clone();
57
58    // 20 digit oracle max value
59    let max_payout = PayoutPoint {
60        event_outcome: max_value,
61        extra_precision: 0,
62        outcome_payout: total_collateral,
63    };
64
65    let payout_curve_pieces = PolynomialPayoutCurvePiece::new(points)?;
66    let upper_limit = PolynomialPayoutCurvePiece::new(vec![final_payout_piece, max_payout])?;
67    Ok(PayoutFunction::new(vec![
68        PayoutFunctionPiece::PolynomialPayoutCurvePiece(payout_curve_pieces),
69        PayoutFunctionPiece::PolynomialPayoutCurvePiece(upper_limit),
70    ])?)
71}
72
73#[allow(clippy::too_many_arguments)]
74pub fn create_contract_input(
75    min_price: u64,
76    max_price: u64,
77    num_steps: u64,
78    offer_collateral: Amount,
79    accept_collateral: Amount,
80    fee_rate: u64,
81    oracle_pubkey: String,
82    event_id: String,
83) -> ContractInput {
84    let oracle_numeric_infos = OracleNumericInfo {
85        base: 2,
86        nb_digits: vec![20],
87    };
88
89    // Check the max value given the base and nb digits.
90    let max_value = oracle_numeric_infos
91        .base
92        .checked_pow(oracle_numeric_infos.nb_digits[0] as u32)
93        .unwrap() as u64
94        - 1;
95
96    let payout_curve = generate_payout_curve(
97        min_price,
98        max_price,
99        offer_collateral,
100        accept_collateral,
101        num_steps,
102        max_value,
103    )
104    .unwrap();
105    let rounding_intervals = RoundingIntervals {
106        intervals: vec![RoundingInterval {
107            begin_interval: 0,
108            rounding_mod: 1,
109        }],
110    };
111
112    let contract_descriptor = ContractDescriptor::Numerical(NumericalDescriptor {
113        payout_function: payout_curve,
114        rounding_intervals,
115        difference_params: None,
116        oracle_numeric_infos,
117    });
118
119    let oracles = OracleInput {
120        public_keys: vec![XOnlyPublicKey::from_str(&oracle_pubkey).unwrap()],
121        event_id,
122        threshold: 1,
123    };
124    let contract_infos = vec![ContractInputInfo {
125        contract_descriptor,
126        oracles,
127    }];
128    ContractInput {
129        offer_collateral,
130        accept_collateral,
131        fee_rate,
132        contract_flags: 0,
133        contract_infos,
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use bitcoin::Amount;
140
141    use crate::{create_contract_input, generate_payout_curve};
142
143    #[test]
144    fn payout_curve() {
145        let curve = generate_payout_curve(
146            13_000,
147            60_000,
148            Amount::from_sat(50_000),
149            Amount::from_sat(50_000),
150            10,
151            1045686,
152        );
153        assert!(curve.is_ok())
154    }
155
156    #[test]
157    fn create_contract_input_test() {
158        let oracle_pk =
159            "0d829c1cc556aa59060df5a9543c5357199ace5db9bcd5a8ddd6ee2fc7b6d174".to_string();
160        let event_id = "event".to_string();
161        let contract = create_contract_input(
162            0,
163            100_000,
164            3,
165            Amount::from_sat(50_000),
166            Amount::from_sat(50_000),
167            2,
168            oracle_pk,
169            event_id,
170        );
171
172        let json = serde_json::to_string(&contract).unwrap();
173        println!("{}", json)
174    }
175}