dlc_manager/contract/
contract_input.rs

1//! #ContractInput
2
3use crate::error::Error;
4
5use super::ContractDescriptor;
6use bitcoin::Amount;
7use secp256k1_zkp::XOnlyPublicKey;
8#[cfg(feature = "use-serde")]
9use serde::{Deserialize, Serialize};
10
11/// Oracle information required for the initial creation of a contract.
12#[derive(Debug, Clone)]
13#[cfg_attr(
14    feature = "use-serde",
15    derive(Serialize, Deserialize),
16    serde(rename_all = "camelCase")
17)]
18pub struct OracleInput {
19    /// The set of public keys for each of the used oracles.
20    pub public_keys: Vec<XOnlyPublicKey>,
21    /// The id of the event being used for the contract. Note that at the moment
22    /// a single event id is used, while multiple ids would be preferable.
23    pub event_id: String,
24    /// The number of oracles that need to provide attestations satisfying the
25    /// contract conditions to be able to close the contract.
26    pub threshold: u16,
27}
28
29impl OracleInput {
30    /// Checks whether the data within the struct is consistent.
31    pub fn validate(&self) -> Result<(), Error> {
32        if self.public_keys.is_empty() {
33            return Err(Error::InvalidParameters(
34                "OracleInput must have at least one public key.".to_string(),
35            ));
36        }
37
38        if self.threshold > self.public_keys.len() as u16 {
39            return Err(Error::InvalidParameters(
40                "Threshold cannot be larger than number of oracles.".to_string(),
41            ));
42        }
43
44        if self.threshold == 0 {
45            return Err(Error::InvalidParameters(
46                "Threshold cannot be zero.".to_string(),
47            ));
48        }
49
50        Ok(())
51    }
52}
53
54/// Represents the contract specifications.
55#[derive(Debug, Clone)]
56#[cfg_attr(
57    feature = "use-serde",
58    derive(Serialize, Deserialize),
59    serde(rename_all = "camelCase")
60)]
61pub struct ContractInputInfo {
62    /// The contract conditions.
63    pub contract_descriptor: ContractDescriptor,
64    /// The oracle information.
65    pub oracles: OracleInput,
66}
67
68#[derive(Debug, Clone)]
69#[cfg_attr(
70    feature = "use-serde",
71    derive(Serialize, Deserialize),
72    serde(rename_all = "camelCase")
73)]
74/// Contains all the information necessary for the initialization of a DLC.
75pub struct ContractInput {
76    /// The collateral for the offering party.
77    pub offer_collateral: Amount,
78    /// The collateral for the accepting party.
79    pub accept_collateral: Amount,
80    /// The fee rate used to construct the transactions.
81    pub fee_rate: u64,
82    /// The set of contract that make up the DLC (a single DLC can be based
83    /// on multiple contracts).
84    pub contract_infos: Vec<ContractInputInfo>,
85}
86
87impl ContractInput {
88    /// Validate the contract input parameters
89    pub fn validate(&self) -> Result<(), Error> {
90        if self.contract_infos.is_empty() {
91            return Err(Error::InvalidParameters(
92                "Need at least one contract info".to_string(),
93            ));
94        }
95
96        for contract_info in &self.contract_infos {
97            contract_info.oracles.validate()?;
98        }
99
100        dlc::util::validate_fee_rate(self.fee_rate)
101            .map_err(|_| Error::InvalidParameters("Fee rate too high.".to_string()))
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use dlc::{EnumerationPayout, Payout};
108    use secp256k1_zkp::{Keypair, SecretKey, SECP256K1};
109
110    use crate::contract::enum_descriptor::EnumDescriptor;
111
112    use super::*;
113
114    fn get_base_input() -> ContractInput {
115        ContractInput {
116            offer_collateral: Amount::from_sat(1000000),
117            accept_collateral: Amount::from_sat(2000000),
118            fee_rate: 1234,
119            contract_infos: vec![ContractInputInfo {
120                contract_descriptor: ContractDescriptor::Enum(EnumDescriptor {
121                    outcome_payouts: vec![
122                        EnumerationPayout {
123                            outcome: "A".to_string(),
124                            payout: Payout {
125                                offer: Amount::from_sat(3000000),
126                                accept: Amount::ZERO,
127                            },
128                        },
129                        EnumerationPayout {
130                            outcome: "B".to_string(),
131                            payout: Payout {
132                                offer: Amount::ZERO,
133                                accept: Amount::from_sat(3000000),
134                            },
135                        },
136                    ],
137                }),
138                oracles: OracleInput {
139                    public_keys: vec![
140                        XOnlyPublicKey::from_keypair(&Keypair::from_secret_key(
141                            SECP256K1,
142                            &SecretKey::from_slice(&secp256k1_zkp::constants::ONE).unwrap(),
143                        ))
144                        .0,
145                    ],
146                    event_id: "1234".to_string(),
147                    threshold: 1,
148                },
149            }],
150        }
151    }
152
153    #[test]
154    fn valid_contract_input_is_valid() {
155        let input = get_base_input();
156        input.validate().expect("the contract input to be valid.");
157    }
158
159    #[test]
160    fn no_contract_info_contract_input_is_not_valid() {
161        let mut input = get_base_input();
162        input.contract_infos.clear();
163        input
164            .validate()
165            .expect_err("the contract input to be invalid.");
166    }
167
168    #[test]
169    fn invalid_fee_rate_contract_input_is_not_valid() {
170        let mut input = get_base_input();
171        input.fee_rate = 251 * 25;
172        input
173            .validate()
174            .expect_err("the contract input to be invalid.");
175    }
176
177    #[test]
178    fn no_public_keys_oracle_input_contract_input_is_not_valid() {
179        let mut input = get_base_input();
180        input.contract_infos[0].oracles.public_keys.clear();
181        input
182            .validate()
183            .expect_err("the contract input to be invalid.");
184    }
185
186    #[test]
187    fn invalid_oracle_info_threshold_oracle_input_contract_input_is_not_valid() {
188        let mut input = get_base_input();
189        input.contract_infos[0].oracles.threshold = 2;
190        input
191            .validate()
192            .expect_err("the contract input to be invalid.");
193    }
194
195    #[test]
196    fn invalid_oracle_info_threshold_zero() {
197        let mut input = get_base_input();
198        input.contract_infos[0].oracles.threshold = 0;
199        input
200            .validate()
201            .expect_err("the contract input to be invalid.");
202    }
203}