ddk_manager/contract/
contract_input.rs1use crate::error::Error;
4
5use super::ContractDescriptor;
6use bitcoin::Amount;
7use secp256k1_zkp::XOnlyPublicKey;
8#[cfg(feature = "use-serde")]
9use serde::{Deserialize, Serialize};
10
11const DUST_LIMIT: Amount = Amount::from_sat(1000);
12
13#[derive(Debug, Clone)]
15#[cfg_attr(
16 feature = "use-serde",
17 derive(Serialize, Deserialize),
18 serde(rename_all = "camelCase")
19)]
20pub struct OracleInput {
21 pub public_keys: Vec<XOnlyPublicKey>,
23 pub event_id: String,
26 pub threshold: u16,
29}
30
31impl OracleInput {
32 pub fn validate(&self) -> Result<(), Error> {
34 if self.public_keys.is_empty() {
35 return Err(Error::InvalidParameters(
36 "OracleInput must have at least one public key.".to_string(),
37 ));
38 }
39
40 if self.threshold > self.public_keys.len() as u16 {
41 return Err(Error::InvalidParameters(
42 "Threshold cannot be larger than number of oracles.".to_string(),
43 ));
44 }
45
46 if self.threshold == 0 {
47 return Err(Error::InvalidParameters(
48 "Threshold cannot be zero.".to_string(),
49 ));
50 }
51
52 Ok(())
53 }
54}
55
56#[derive(Debug, Clone)]
58#[cfg_attr(
59 feature = "use-serde",
60 derive(Serialize, Deserialize),
61 serde(rename_all = "camelCase")
62)]
63pub struct ContractInputInfo {
64 pub contract_descriptor: ContractDescriptor,
66 pub oracles: OracleInput,
68}
69
70#[derive(Debug, Clone)]
71#[cfg_attr(
72 feature = "use-serde",
73 derive(Serialize, Deserialize),
74 serde(rename_all = "camelCase")
75)]
76pub struct ContractInput {
78 pub offer_collateral: Amount,
80 pub accept_collateral: Amount,
82 pub fee_rate: u64,
84 pub contract_infos: Vec<ContractInputInfo>,
87}
88
89impl ContractInput {
90 pub fn validate(&self) -> Result<(), Error> {
92 if self.offer_collateral < DUST_LIMIT {
93 return Err(Error::InvalidParameters(
94 "Offer collateral must be greater than dust limit.".to_string(),
95 ));
96 }
97
98 let total_collateral = self.offer_collateral + self.accept_collateral;
99 if total_collateral < DUST_LIMIT {
100 return Err(Error::InvalidParameters(
101 "Total collateral must be greater than dust limit.".to_string(),
102 ));
103 }
104
105 if self.contract_infos.is_empty() {
106 return Err(Error::InvalidParameters(
107 "Need at least one contract info".to_string(),
108 ));
109 }
110
111 for contract_info in &self.contract_infos {
112 contract_info.oracles.validate()?;
113 }
114
115 ddk_dlc::util::validate_fee_rate(self.fee_rate)
116 .map_err(|_| Error::InvalidParameters("Fee rate too high.".to_string()))
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use ddk_dlc::{EnumerationPayout, Payout};
123 use secp256k1_zkp::{Keypair, SecretKey, SECP256K1};
124
125 use crate::contract::enum_descriptor::EnumDescriptor;
126
127 use super::*;
128
129 fn get_base_input() -> ContractInput {
130 ContractInput {
131 offer_collateral: Amount::from_sat(1000000),
132 accept_collateral: Amount::from_sat(2000000),
133 fee_rate: 1234,
134 contract_infos: vec![ContractInputInfo {
135 contract_descriptor: ContractDescriptor::Enum(EnumDescriptor {
136 outcome_payouts: vec![
137 EnumerationPayout {
138 outcome: "A".to_string(),
139 payout: Payout {
140 offer: Amount::from_sat(3000000),
141 accept: Amount::ZERO,
142 },
143 },
144 EnumerationPayout {
145 outcome: "B".to_string(),
146 payout: Payout {
147 offer: Amount::ZERO,
148 accept: Amount::from_sat(3000000),
149 },
150 },
151 ],
152 }),
153 oracles: OracleInput {
154 public_keys: vec![
155 XOnlyPublicKey::from_keypair(&Keypair::from_secret_key(
156 SECP256K1,
157 &SecretKey::from_slice(&secp256k1_zkp::constants::ONE).unwrap(),
158 ))
159 .0,
160 ],
161 event_id: "1234".to_string(),
162 threshold: 1,
163 },
164 }],
165 }
166 }
167
168 #[test]
169 fn valid_contract_input_is_valid() {
170 let input = get_base_input();
171 input.validate().expect("the contract input to be valid.");
172 }
173
174 #[test]
175 fn no_contract_info_contract_input_is_not_valid() {
176 let mut input = get_base_input();
177 input.contract_infos.clear();
178 input
179 .validate()
180 .expect_err("the contract input to be invalid.");
181 }
182
183 #[test]
184 fn invalid_fee_rate_contract_input_is_not_valid() {
185 let mut input = get_base_input();
186 input.fee_rate = 251 * 25;
187 input
188 .validate()
189 .expect_err("the contract input to be invalid.");
190 }
191
192 #[test]
193 fn no_public_keys_oracle_input_contract_input_is_not_valid() {
194 let mut input = get_base_input();
195 input.contract_infos[0].oracles.public_keys.clear();
196 input
197 .validate()
198 .expect_err("the contract input to be invalid.");
199 }
200
201 #[test]
202 fn invalid_oracle_info_threshold_oracle_input_contract_input_is_not_valid() {
203 let mut input = get_base_input();
204 input.contract_infos[0].oracles.threshold = 2;
205 input
206 .validate()
207 .expect_err("the contract input to be invalid.");
208 }
209
210 #[test]
211 fn invalid_oracle_info_threshold_zero() {
212 let mut input = get_base_input();
213 input.contract_infos[0].oracles.threshold = 0;
214 input
215 .validate()
216 .expect_err("the contract input to be invalid.");
217 }
218}