ddk_manager/contract/
contract_input.rs

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