1#![allow(non_snake_case)]
6
7use crate::imports::*;
8use crate::result::Result as ClientResult;
9use borsh::{BorshDeserialize, BorshSerialize};
10use kaspa_consensus_core::errors::tx::PopulateGenesisCovenantsError;
11use kaspa_consensus_core::tx::{CovenantBinding as CoreCovenantBinding, GenesisCovenantGroup as CoreGenesisCovenantGroup};
12use kaspa_hashes::Hash;
13use kaspa_wasm_core::types::NumberArray;
14use workflow_wasm::error::Error as WasmError;
15
16#[wasm_bindgen(typescript_custom_section)]
17const TS_COVENANT_BINDING: &'static str = r#"
18/**
19 * A covenant binding binds a transaction output to the covenant and input authorizing its creation.
20 *
21 * @category Consensus
22 */
23export interface ICovenantBinding {
24 authorizingInput: number;
25 covenantId: HexString;
26}
27"#;
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, BorshSerialize, BorshDeserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct CovenantBindingInner {
32 pub authorizing_input: u16,
33 pub covenant_id: Hash,
34}
35
36impl CovenantBindingInner {
37 pub fn new(authorizing_input: u16, covenant_id: Hash) -> Self {
38 Self { authorizing_input, covenant_id }
39 }
40}
41
42#[derive(Debug, Serialize, Deserialize, Clone, CastFromJs)]
43#[serde(rename_all = "camelCase")]
44#[wasm_bindgen(inspectable)]
45pub struct CovenantBinding {
46 inner: Arc<Mutex<CovenantBindingInner>>,
47}
48
49impl BorshSerialize for CovenantBinding {
50 fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
51 let inner = self.inner.lock().unwrap();
52 BorshSerialize::serialize(&*inner, writer)
53 }
54}
55
56impl BorshDeserialize for CovenantBinding {
57 fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
58 let inner = CovenantBindingInner::deserialize_reader(reader)?;
59 Ok(Self { inner: Arc::new(Mutex::new(inner)) })
60 }
61}
62
63impl CovenantBinding {
64 pub fn inner(&self) -> MutexGuard<'_, CovenantBindingInner> {
65 self.inner.lock().unwrap()
66 }
67}
68
69#[wasm_bindgen]
70impl CovenantBinding {
71 #[wasm_bindgen(constructor)]
72 pub fn new(authorizing_input: u16, covenant_id: Hash) -> Self {
73 let inner = Arc::new(Mutex::new(CovenantBindingInner::new(authorizing_input, covenant_id)));
74 Self { inner }
75 }
76
77 #[wasm_bindgen(setter, js_name = authorizingInput)]
78 pub fn set_authorizing_input(&mut self, v: u16) {
79 self.inner().authorizing_input = v;
80 }
81
82 #[wasm_bindgen(getter, js_name = authorizingInput)]
83 pub fn get_authorizing_input(&self) -> u16 {
84 self.inner().authorizing_input
85 }
86
87 #[wasm_bindgen(setter, js_name = covenantId)]
88 pub fn set_covenant_id(&mut self, v: Hash) {
89 self.inner().covenant_id = v;
90 }
91
92 #[wasm_bindgen(getter, js_name = covenantId)]
93 pub fn get_covenant_id(&self) -> Hash {
94 self.inner().covenant_id
95 }
96
97 #[wasm_bindgen(js_name = toJSON)]
98 pub fn to_js_object(&self) -> Result<Object, WasmError> {
99 let obj = Object::new();
100 obj.set("authorizingInput", &self.get_authorizing_input().into())?;
101 obj.set("covenantId", &self.get_covenant_id().to_string().into())?;
102 Ok(obj)
103 }
104}
105
106impl From<CoreCovenantBinding> for CovenantBinding {
107 fn from(covenant: CoreCovenantBinding) -> Self {
108 CovenantBinding::new(covenant.authorizing_input, covenant.covenant_id)
109 }
110}
111
112impl From<CovenantBinding> for CoreCovenantBinding {
113 fn from(covenant: CovenantBinding) -> Self {
114 CoreCovenantBinding::new(covenant.get_authorizing_input(), covenant.get_covenant_id())
115 }
116}
117
118impl TryCastFromJs for CovenantBinding {
119 type Error = WasmError;
120
121 fn try_cast_from<'a, R>(value: &'a R) -> Result<Cast<'a, Self>, Self::Error>
122 where
123 R: AsRef<JsValue> + 'a,
124 {
125 Self::resolve(value, || {
126 let Some(object) = Object::try_from(value.as_ref()) else { return Err(Self::Error::NotAnObject) };
127 let authorizing_input = object.get_u16("authorizingInput")?;
128 let covenant_id = object.get_value("covenantId")?.try_into_owned()?;
129 Ok(CovenantBinding::new(authorizing_input, covenant_id))
130 })
131 }
132}
133
134#[wasm_bindgen(typescript_custom_section)]
135const TS_GENESIS_COVENANT_GROUP: &'static str = r#"
136/**
137 * A genesis covenant group for bulk covenant binding population.
138 *
139 * @category Consensus
140 */
141export interface IGenesisCovenantGroup {
142 authorizingInput: number;
143 outputs: number[];
144}
145"#;
146
147#[wasm_bindgen]
148extern "C" {
149 #[wasm_bindgen(extends = js_sys::Array, typescript_type = "(IGenesisCovenantGroup | GenesisCovenantGroup)[]")]
153 pub type GenesisCovenantGroupArrayT;
154}
155
156impl TryFrom<&GenesisCovenantGroupArrayT> for Vec<CoreGenesisCovenantGroup> {
157 type Error = Error;
158 fn try_from(value: &GenesisCovenantGroupArrayT) -> Result<Self, Self::Error> {
159 if value.is_array() {
160 value.iter().map(|v| GenesisCovenantGroup::try_owned_from(v).map(|gcp| gcp.inner().clone()).map_err(Error::from)).collect()
161 } else {
162 Err(PopulateGenesisCovenantsError::InvalidGenesisCovenantGroupArray.into())
163 }
164 }
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, CastFromJs)]
173#[serde(rename_all = "camelCase")]
174#[wasm_bindgen(inspectable)]
175pub struct GenesisCovenantGroup {
176 inner: CoreGenesisCovenantGroup,
177}
178
179impl GenesisCovenantGroup {
180 pub fn new(authorizing_input: u16, outputs: Vec<u32>) -> Self {
181 Self { inner: CoreGenesisCovenantGroup::new(authorizing_input, outputs) }
182 }
183
184 pub fn inner(&self) -> &CoreGenesisCovenantGroup {
185 &self.inner
186 }
187
188 pub fn outputs(&self) -> Vec<u32> {
189 self.inner.outputs.clone()
190 }
191
192 pub fn set_outputs(&mut self, outputs: Vec<u32>) {
193 self.inner.outputs = outputs
194 }
195}
196
197impl From<CoreGenesisCovenantGroup> for GenesisCovenantGroup {
198 fn from(inner: CoreGenesisCovenantGroup) -> Self {
199 Self { inner }
200 }
201}
202
203impl From<GenesisCovenantGroup> for CoreGenesisCovenantGroup {
204 fn from(wrapper: GenesisCovenantGroup) -> Self {
205 wrapper.inner
206 }
207}
208
209#[wasm_bindgen]
210impl GenesisCovenantGroup {
211 #[wasm_bindgen(constructor)]
212 pub fn ctor(authorizing_input: u16, outputs: NumberArray) -> ClientResult<Self> {
213 let outputs: Vec<u32> = serde_wasm_bindgen::from_value(outputs.into())?;
214 Ok(Self { inner: CoreGenesisCovenantGroup::new(authorizing_input, outputs) })
215 }
216
217 #[wasm_bindgen(getter, js_name = authorizingInput)]
218 pub fn authorizing_input(&self) -> u16 {
219 self.inner.authorizing_input
220 }
221
222 #[wasm_bindgen(setter, js_name = authorizingInput)]
223 pub fn set_authorizing_input(&mut self, value: u16) {
224 self.inner.authorizing_input = value;
225 }
226
227 #[wasm_bindgen(setter = outputs, js_name = outputs)]
228 pub fn js_set_outputs(&mut self, outputs: NumberArray) -> ClientResult<()> {
229 let outputs: Vec<u32> = serde_wasm_bindgen::from_value(outputs.into())?;
230 self.inner.outputs = outputs;
231 Ok(())
232 }
233
234 #[wasm_bindgen(getter, js_name = outputs)]
235 pub fn js_get_outputs(&self) -> NumberArray {
236 serde_wasm_bindgen::to_value(&self.inner.outputs)
237 .expect("serializing genesis covenant outputs should not fail")
238 .unchecked_into()
239 }
240
241 #[wasm_bindgen(js_name = "toJSON")]
242 pub fn to_js_object(&self) -> Result<Object, WasmError> {
243 let obj = Object::new();
244 obj.set("authorizingInput", &self.inner.authorizing_input.into())?;
245 obj.set("outputs", &js_sys::Array::from_iter(self.inner.outputs.iter().map(|&v| JsValue::from(v))))?;
246 Ok(obj)
247 }
248
249 #[wasm_bindgen(js_name = "toString")]
250 pub fn js_to_string(&self) -> Result<js_sys::JsString, WasmError> {
251 Ok(js_sys::JSON::stringify(&self.to_js_object()?.into())?)
252 }
253}
254
255impl TryCastFromJs for GenesisCovenantGroup {
256 type Error = WasmError;
257
258 fn try_cast_from<'a, R>(value: &'a R) -> Result<Cast<'a, Self>, Self::Error>
259 where
260 R: AsRef<JsValue> + 'a,
261 {
262 Self::resolve(value, || {
263 let Some(object) = Object::try_from(value.as_ref()) else {
264 return Err(Self::Error::NotAnObject);
265 };
266 let authorizing_input = object.get_u16("authorizingInput")?;
267 let outputs: Vec<u32> =
268 serde_wasm_bindgen::from_value(object.get_value("outputs")?).map_err(|err| Self::Error::from(JsValue::from(err)))?;
269 Ok(Self { inner: CoreGenesisCovenantGroup::new(authorizing_input, outputs) })
270 })
271 }
272}
273
274#[cfg(all(test, target_arch = "wasm32"))]
275mod tests {
276 use super::*;
277 use kaspa_hashes::HASH_SIZE;
278 use std::str::FromStr;
279 use wasm_bindgen::JsValue;
280 use wasm_bindgen_test::wasm_bindgen_test;
281
282 fn to_js_array(values: &[u32]) -> js_sys::Array {
287 let arr = js_sys::Array::new();
288 for &v in values {
289 arr.push(&JsValue::from(v));
290 }
291 arr
292 }
293
294 fn construct_genesis_covenant_group(authorizing_input: u16, outputs: &[u32]) -> GenesisCovenantGroup {
296 GenesisCovenantGroup::ctor(authorizing_input, to_js_array(outputs).unchecked_into()).expect("constructor should succeed")
297 }
298
299 fn construct_genesis_covenant_group_plain(authorizing_input: u16, outputs: &[u32]) -> JsValue {
301 let obj = Object::new();
302 obj.set("authorizingInput", &JsValue::from(authorizing_input)).expect("set authorizingInput");
303 obj.set("outputs", &to_js_array(outputs).into()).expect("set outputs");
304 obj.into()
305 }
306
307 fn get_genesis_covenant_group_outputs(group: &GenesisCovenantGroup) -> Vec<u32> {
309 serde_wasm_bindgen::from_value(group.outputs().into()).expect("outputs should deserialize")
310 }
311
312 #[wasm_bindgen_test]
313 fn test_genesis_covenant_group_construction() {
314 let group = construct_genesis_covenant_group(1, &[0, 1, 2]);
315
316 assert_eq!(group.authorizing_input(), 1);
317 assert_eq!(get_genesis_covenant_group_outputs(&group), vec![0, 1, 2]);
318 }
319
320 #[wasm_bindgen_test]
321 fn test_genesis_covenant_group_authorizing_input_setter() {
322 let mut group = construct_genesis_covenant_group(0, &[0, 1]);
323
324 group.set_authorizing_input(7);
325 assert_eq!(group.authorizing_input(), 7);
326 }
327
328 #[wasm_bindgen_test]
329 fn test_genesis_covenant_group_outputs_setter() {
330 let mut group = construct_genesis_covenant_group(0, &[0, 1]);
331
332 group.js_set_outputs(to_js_array(&[3, 4, 5]).unchecked_into()).expect("set_outputs should succeed");
333
334 assert_eq!(get_genesis_covenant_group_outputs(&group), vec![3, 4, 5]);
335 }
336
337 #[wasm_bindgen_test]
338 fn test_genesis_covenant_group_to_json() {
339 let group = construct_genesis_covenant_group(2, &[0, 1]);
340
341 let obj = group.to_js_object().expect("to_js_object should succeed");
342 let auth_input = js_sys::Reflect::get(&obj, &JsValue::from_str("authorizingInput")).expect("should have authorizingInput");
343 assert_eq!(auth_input.as_f64().expect("should be a number"), 2.0);
344
345 let outputs = js_sys::Reflect::get(&obj, &JsValue::from_str("outputs")).expect("should have outputs");
346 let outputs_array: &js_sys::Array = outputs.dyn_ref::<js_sys::Array>().expect("outputs should be an array");
347 assert_eq!(outputs_array.length(), 2);
348 assert_eq!(outputs_array.get(0).as_f64().expect("should be a number"), 0.0);
349 assert_eq!(outputs_array.get(1).as_f64().expect("should be a number"), 1.0);
350
351 let json_str: String = group.js_to_string().expect("js_to_string should succeed").into();
353 let parsed: serde_json::Value = serde_json::from_str(&json_str).expect("should be valid JSON");
354 assert_eq!(parsed["authorizingInput"], 2);
355 assert_eq!(parsed["outputs"], serde_json::json!([0, 1]));
356 }
357
358 #[wasm_bindgen_test]
359 fn test_genesis_covenant_group_try_cast_from_plain_object() {
360 let js_val = construct_genesis_covenant_group_plain(1, &[0, 2]);
361 let group = GenesisCovenantGroup::try_owned_from(&js_val).expect("try_cast_from should succeed");
362 assert_eq!(group.inner().authorizing_input, 1);
363 assert_eq!(group.inner().outputs, vec![0, 2]);
364 }
365
366 #[wasm_bindgen_test]
367 fn test_genesis_covenant_group_array_t_from_plain_objects() {
368 let arr = js_sys::Array::new();
369 arr.push(&construct_genesis_covenant_group_plain(0, &[0, 1]));
370 arr.push(&construct_genesis_covenant_group_plain(1, &[2, 3, 4]));
371
372 let typed_arr: &GenesisCovenantGroupArrayT = arr.unchecked_ref();
373 let groups: Vec<CoreGenesisCovenantGroup> = Vec::try_from(typed_arr).expect("should convert from plain objects");
374
375 assert_eq!(groups.len(), 2);
376 assert_eq!(groups[0].authorizing_input, 0);
377 assert_eq!(groups[0].outputs, vec![0, 1]);
378 assert_eq!(groups[1].authorizing_input, 1);
379 assert_eq!(groups[1].outputs, vec![2, 3, 4]);
380 }
381
382 #[wasm_bindgen_test]
383 fn test_genesis_covenant_group_array_t_from_wasm_instances() {
384 let g0 = construct_genesis_covenant_group(5, &[10, 11]);
385 let g1 = construct_genesis_covenant_group(6, &[20]);
386
387 let arr = js_sys::Array::new();
388 arr.push(&JsValue::from(g0));
389 arr.push(&JsValue::from(g1));
390
391 let typed_arr: &GenesisCovenantGroupArrayT = arr.unchecked_ref();
392 let groups: Vec<CoreGenesisCovenantGroup> = Vec::try_from(typed_arr).expect("should convert from WASM instances");
393
394 assert_eq!(groups.len(), 2);
395 assert_eq!(groups[0].authorizing_input, 5);
396 assert_eq!(groups[0].outputs, vec![10, 11]);
397 assert_eq!(groups[1].authorizing_input, 6);
398 assert_eq!(groups[1].outputs, vec![20]);
399 }
400
401 #[wasm_bindgen_test]
405 fn test_covenant_binding_construction() {
406 let covenant_id = Hash::from_bytes([0xab; HASH_SIZE]);
407 let binding = CovenantBinding::new(0, covenant_id);
408
409 assert_eq!(binding.get_authorizing_input(), 0);
410 assert_eq!(binding.get_covenant_id(), covenant_id);
411 }
412
413 #[wasm_bindgen_test]
414 fn test_covenant_binding_construction_plain() {
415 let covenant_id = Hash::from_bytes([0xab; HASH_SIZE]);
416
417 let obj = Object::new();
418 obj.set("authorizingInput", &JsValue::from(0)).expect("set authorizingInput");
419 obj.set("covenantId", &JsValue::from_str(&covenant_id.to_string())).expect("set covenantId");
420
421 let binding = CovenantBinding::try_owned_from(obj).expect("try_cast_from failed");
422
423 assert_eq!(binding.get_authorizing_input(), 0);
424 assert_eq!(binding.get_covenant_id(), covenant_id);
425 }
426
427 #[wasm_bindgen_test]
428 fn test_covenant_binding_to_json() {
429 let covenant_id_in = Hash::from_bytes([0xab; HASH_SIZE]);
430 let binding = CovenantBinding::new(0, covenant_id_in);
431
432 let obj = binding.to_js_object().expect("to_js_object failed");
433
434 let authorizing_input_out = obj.get_u16("authorizingInput").expect("failed to get authorizingInput");
435 let covenant_id_out =
436 Hash::from_str(&obj.get_string("covenantId").expect("failed to get covenantId")).expect("failed to create Hash from str");
437
438 assert_eq!(0, authorizing_input_out);
439 assert_eq!(covenant_id_in, covenant_id_out);
440 }
441
442 #[wasm_bindgen_test]
443 fn test_covenant_binding_authorizing_input_setter() {
444 let covenant_id = Hash::from_bytes([0xab; HASH_SIZE]);
445 let mut binding = CovenantBinding::new(0, covenant_id);
446
447 let new_input = 1;
448
449 binding.set_authorizing_input(new_input);
450
451 assert_eq!(binding.get_authorizing_input(), new_input);
452 }
453
454 #[wasm_bindgen_test]
455 fn test_covenant_binding_covenant_id_setter() {
456 let covenant_id = Hash::from_bytes([0xab; HASH_SIZE]);
457 let mut binding = CovenantBinding::new(0, covenant_id);
458
459 let new_covenant_id = Hash::from_bytes([0xac; HASH_SIZE]);
460
461 binding.set_covenant_id(new_covenant_id);
462
463 assert_eq!(binding.get_covenant_id(), new_covenant_id);
464 }
465}