1use crate::{
3 chain::quantus_subxt, cli::common::submit_transaction, error::QuantusError, log_error,
4 log_print, log_success, log_verbose,
5};
6use clap::Subcommand;
7use colored::Colorize;
8use std::{path::PathBuf, str::FromStr};
9
10#[derive(Subcommand, Debug)]
12pub enum TechReferendaCommands {
13 Submit {
15 #[arg(long)]
17 preimage_hash: String,
18
19 #[arg(short, long)]
21 from: String,
22
23 #[arg(short, long)]
25 password: Option<String>,
26
27 #[arg(long)]
29 password_file: Option<String>,
30 },
31
32 SubmitWithPreimage {
34 #[arg(short, long)]
36 wasm_file: PathBuf,
37
38 #[arg(short, long)]
40 from: String,
41
42 #[arg(short, long)]
44 password: Option<String>,
45
46 #[arg(long)]
48 password_file: Option<String>,
49 },
50
51 List,
53
54 Get {
56 #[arg(short, long)]
58 index: u32,
59 },
60
61 Status {
63 #[arg(short, long)]
65 index: u32,
66 },
67
68 PlaceDecisionDeposit {
70 #[arg(short, long)]
72 index: u32,
73
74 #[arg(short, long)]
76 from: String,
77
78 #[arg(short, long)]
80 password: Option<String>,
81
82 #[arg(long)]
84 password_file: Option<String>,
85 },
86
87 Cancel {
89 #[arg(short, long)]
91 index: u32,
92
93 #[arg(short, long)]
95 from: String,
96
97 #[arg(short, long)]
99 password: Option<String>,
100
101 #[arg(long)]
103 password_file: Option<String>,
104 },
105
106 Kill {
108 #[arg(short, long)]
110 index: u32,
111
112 #[arg(short, long)]
114 from: String,
115
116 #[arg(short, long)]
118 password: Option<String>,
119
120 #[arg(long)]
122 password_file: Option<String>,
123 },
124
125 Nudge {
127 #[arg(short, long)]
129 index: u32,
130
131 #[arg(short, long)]
133 from: String,
134
135 #[arg(short, long)]
137 password: Option<String>,
138
139 #[arg(long)]
141 password_file: Option<String>,
142 },
143
144 RefundSubmissionDeposit {
146 #[arg(short, long)]
148 index: u32,
149
150 #[arg(short, long)]
152 from: String,
153
154 #[arg(short, long)]
156 password: Option<String>,
157
158 #[arg(long)]
160 password_file: Option<String>,
161 },
162
163 RefundDecisionDeposit {
165 #[arg(short, long)]
167 index: u32,
168
169 #[arg(short, long)]
171 from: String,
172
173 #[arg(short, long)]
175 password: Option<String>,
176
177 #[arg(long)]
179 password_file: Option<String>,
180 },
181
182 Config,
184}
185
186pub async fn handle_tech_referenda_command(
188 command: TechReferendaCommands,
189 node_url: &str,
190 finalized: bool,
191) -> crate::error::Result<()> {
192 let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
193
194 match command {
195 TechReferendaCommands::Submit { preimage_hash, from, password, password_file } =>
196 submit_runtime_upgrade(
197 &quantus_client,
198 &preimage_hash,
199 &from,
200 password,
201 password_file,
202 finalized,
203 )
204 .await,
205 TechReferendaCommands::SubmitWithPreimage { wasm_file, from, password, password_file } =>
206 submit_runtime_upgrade_with_preimage(
207 &quantus_client,
208 &wasm_file,
209 &from,
210 password,
211 password_file,
212 finalized,
213 )
214 .await,
215 TechReferendaCommands::List => list_proposals(&quantus_client).await,
216 TechReferendaCommands::Get { index } => get_proposal_details(&quantus_client, index).await,
217 TechReferendaCommands::Status { index } =>
218 get_proposal_status(&quantus_client, index).await,
219 TechReferendaCommands::PlaceDecisionDeposit { index, from, password, password_file } =>
220 place_decision_deposit(
221 &quantus_client,
222 index,
223 &from,
224 password,
225 password_file,
226 finalized,
227 )
228 .await,
229 TechReferendaCommands::Cancel { index, from, password, password_file } =>
230 cancel_proposal(&quantus_client, index, &from, password, password_file, finalized).await,
231 TechReferendaCommands::Kill { index, from, password, password_file } =>
232 kill_proposal(&quantus_client, index, &from, password, password_file, finalized).await,
233 TechReferendaCommands::Nudge { index, from, password, password_file } =>
234 nudge_proposal(&quantus_client, index, &from, password, password_file, finalized).await,
235 TechReferendaCommands::RefundSubmissionDeposit { index, from, password, password_file } =>
236 refund_submission_deposit(
237 &quantus_client,
238 index,
239 &from,
240 password,
241 password_file,
242 finalized,
243 )
244 .await,
245 TechReferendaCommands::RefundDecisionDeposit { index, from, password, password_file } =>
246 refund_decision_deposit(
247 &quantus_client,
248 index,
249 &from,
250 password,
251 password_file,
252 finalized,
253 )
254 .await,
255 TechReferendaCommands::Config => get_config(&quantus_client).await,
256 }
257}
258
259async fn submit_runtime_upgrade(
261 quantus_client: &crate::chain::client::QuantusClient,
262 preimage_hash: &str,
263 from: &str,
264 password: Option<String>,
265 password_file: Option<String>,
266 finalized: bool,
267) -> crate::error::Result<()> {
268 log_print!("📝 Submitting Runtime Upgrade Proposal to Tech Referenda");
269 log_print!(" 🔗 Preimage hash: {}", preimage_hash.bright_cyan());
270 log_print!(" 🔑 Submitted by: {}", from.bright_yellow());
271
272 let hash_str = preimage_hash.trim_start_matches("0x");
274 let preimage_hash_parsed: sp_core::H256 = sp_core::H256::from_str(hash_str)
275 .map_err(|_| QuantusError::Generic("Invalid preimage hash format".to_string()))?;
276
277 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
279
280 log_print!("🔍 Checking preimage status...");
282 let latest_block_hash = quantus_client.get_latest_block().await?;
283 let storage_at = quantus_client.client().storage().at(latest_block_hash);
284
285 let preimage_status = storage_at
286 .fetch(
287 &quantus_subxt::api::storage()
288 .preimage()
289 .request_status_for(preimage_hash_parsed),
290 )
291 .await
292 .map_err(|e| QuantusError::Generic(format!("Failed to fetch preimage status: {:?}", e)))?
293 .ok_or_else(|| QuantusError::Generic("Preimage not found on chain".to_string()))?;
294
295 let preimage_len = match preimage_status {
296 quantus_subxt::api::runtime_types::pallet_preimage::RequestStatus::Unrequested {
297 ticket: _,
298 len,
299 } => len,
300 quantus_subxt::api::runtime_types::pallet_preimage::RequestStatus::Requested {
301 maybe_ticket: _,
302 count: _,
303 maybe_len,
304 } => match maybe_len {
305 Some(len) => len,
306 None => return Err(QuantusError::Generic("Preimage length not available".to_string())),
307 },
308 };
309
310 log_print!("✅ Preimage found! Length: {} bytes", preimage_len);
311
312 type ProposalBounded =
314 quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded<
315 quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall,
316 quantus_subxt::api::runtime_types::qp_poseidon::PoseidonHasher,
317 >;
318
319 let preimage_hash_subxt: subxt::utils::H256 = preimage_hash_parsed;
320 let proposal: ProposalBounded =
321 ProposalBounded::Lookup { hash: preimage_hash_subxt, len: preimage_len };
322
323 let raw_origin_root =
324 quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root;
325 let origin_caller =
326 quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin_root);
327
328 let enactment =
329 quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After(
330 0u32,
331 );
332
333 log_print!("🔧 Creating TechReferenda::submit call...");
334 let submit_call =
335 quantus_subxt::api::tx()
336 .tech_referenda()
337 .submit(origin_caller, proposal, enactment);
338
339 let tx_hash =
340 submit_transaction(quantus_client, &keypair, submit_call, None, finalized).await?;
341 log_print!(
342 "✅ {} Runtime upgrade proposal submitted! Hash: {:?}",
343 "SUCCESS".bright_green().bold(),
344 tx_hash
345 );
346
347 log_print!("💡 Use 'quantus tech-referenda list' to see active proposals");
348 Ok(())
349}
350
351async fn submit_runtime_upgrade_with_preimage(
353 quantus_client: &crate::chain::client::QuantusClient,
354 wasm_file: &PathBuf,
355 from: &str,
356 password: Option<String>,
357 password_file: Option<String>,
358 finalized: bool,
359) -> crate::error::Result<()> {
360 use qp_poseidon::PoseidonHasher;
361
362 log_print!("📝 Submitting Runtime Upgrade Proposal to Tech Referenda");
363 log_print!(" 📂 WASM file: {}", wasm_file.display().to_string().bright_cyan());
364 log_print!(" 🔑 Submitted by: {}", from.bright_yellow());
365
366 if !wasm_file.exists() {
367 return Err(QuantusError::Generic(format!("WASM file not found: {}", wasm_file.display())));
368 }
369
370 if let Some(ext) = wasm_file.extension() {
371 if ext != "wasm" {
372 log_verbose!("⚠️ Warning: File doesn't have .wasm extension");
373 }
374 }
375
376 let wasm_code = std::fs::read(wasm_file)
378 .map_err(|e| QuantusError::Generic(format!("Failed to read WASM file: {}", e)))?;
379
380 log_print!("📊 WASM file size: {} bytes", wasm_code.len());
381
382 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
384
385 let set_code_payload = quantus_subxt::api::tx().system().set_code(wasm_code.clone());
387 let metadata = quantus_client.client().metadata();
388 let encoded_call = <_ as subxt::tx::Payload>::encode_call_data(&set_code_payload, &metadata)
389 .map_err(|e| QuantusError::Generic(format!("Failed to encode call data: {:?}", e)))?;
390
391 log_verbose!("📝 Encoded call size: {} bytes", encoded_call.len());
392
393 let preimage_hash: sp_core::H256 =
395 <PoseidonHasher as sp_runtime::traits::Hash>::hash(&encoded_call);
396
397 log_print!("🔗 Preimage hash: {:?}", preimage_hash);
398
399 type PreimageBytes = quantus_subxt::api::preimage::calls::types::note_preimage::Bytes;
401 let bounded_bytes: PreimageBytes = encoded_call.clone();
402
403 log_print!("📝 Submitting preimage...");
404 let note_preimage_tx = quantus_subxt::api::tx().preimage().note_preimage(bounded_bytes);
405 let preimage_tx_hash =
406 submit_transaction(quantus_client, &keypair, note_preimage_tx, None, finalized).await?;
407 log_print!("✅ Preimage transaction submitted: {:?}", preimage_tx_hash);
408
409 log_print!("⏳ Waiting for preimage transaction confirmation...");
411
412 type ProposalBounded =
414 quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded<
415 quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall,
416 quantus_subxt::api::runtime_types::qp_poseidon::PoseidonHasher,
417 >;
418
419 let preimage_hash_subxt: subxt::utils::H256 = preimage_hash;
420 let proposal: ProposalBounded =
421 ProposalBounded::Lookup { hash: preimage_hash_subxt, len: encoded_call.len() as u32 };
422
423 let raw_origin_root =
424 quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root;
425 let origin_caller =
426 quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin_root);
427
428 let enactment =
429 quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After(
430 0u32,
431 );
432
433 log_print!("🔧 Creating TechReferenda::submit call...");
434 let submit_call =
435 quantus_subxt::api::tx()
436 .tech_referenda()
437 .submit(origin_caller, proposal, enactment);
438
439 let tx_hash =
440 submit_transaction(quantus_client, &keypair, submit_call, None, finalized).await?;
441 log_print!(
442 "✅ {} Runtime upgrade proposal submitted! Hash: {:?}",
443 "SUCCESS".bright_green().bold(),
444 tx_hash
445 );
446
447 log_print!("💡 Use 'quantus tech-referenda list' to see active proposals");
448 Ok(())
449}
450
451async fn list_proposals(
453 quantus_client: &crate::chain::client::QuantusClient,
454) -> crate::error::Result<()> {
455 log_print!("📜 Active Tech Referenda Proposals");
456 log_print!("");
457
458 let addr = quantus_subxt::api::storage().tech_referenda().referendum_count();
459
460 let latest_block_hash = quantus_client.get_latest_block().await?;
462 let storage_at = quantus_client.client().storage().at(latest_block_hash);
463
464 let count = storage_at.fetch(&addr).await?;
465
466 if let Some(total) = count {
467 log_print!("📊 Total referenda created: {}", total);
468 if total == 0 {
469 log_print!("📭 No active proposals found");
470 return Ok(());
471 }
472 log_print!("🔍 Fetching recent referenda...");
473 for i in (0..total).rev().take(10) {
474 get_proposal_status(quantus_client, i).await?;
475 log_print!("----------------------------------------");
476 }
477 } else {
478 log_print!("📭 No referenda found - Tech Referenda may be empty");
479 }
480
481 Ok(())
482}
483
484async fn get_proposal_details(
486 quantus_client: &crate::chain::client::QuantusClient,
487 index: u32,
488) -> crate::error::Result<()> {
489 log_print!("📄 Tech Referendum #{} Details", index);
490 log_print!("");
491
492 let addr = quantus_subxt::api::storage().tech_referenda().referendum_info_for(index);
493
494 let latest_block_hash = quantus_client.get_latest_block().await?;
496 let storage_at = quantus_client.client().storage().at(latest_block_hash);
497
498 let info = storage_at.fetch(&addr).await?;
499
500 if let Some(referendum_info) = info {
501 log_print!("📋 Referendum Information (raw):");
502 log_print!("{:#?}", referendum_info);
503 } else {
504 log_print!("📭 Referendum #{} not found", index);
505 }
506 Ok(())
507}
508
509async fn get_proposal_status(
511 quantus_client: &crate::chain::client::QuantusClient,
512 index: u32,
513) -> crate::error::Result<()> {
514 use quantus_subxt::api::runtime_types::pallet_referenda::types::ReferendumInfo;
515
516 log_verbose!("📊 Fetching status for Tech Referendum #{}...", index);
517
518 let addr = quantus_subxt::api::storage().tech_referenda().referendum_info_for(index);
519
520 let latest_block_hash = quantus_client.get_latest_block().await?;
522 let storage_at = quantus_client.client().storage().at(latest_block_hash);
523
524 let info_res = storage_at.fetch(&addr).await;
525
526 match info_res {
527 Ok(Some(info)) => {
528 log_print!("📊 Status for Referendum #{}", index.to_string().bright_yellow());
529 match info {
530 ReferendumInfo::Ongoing(status) => {
531 log_print!(" - Status: {}", "Ongoing".bright_green());
532 log_print!(" - Track: {}", status.track);
533 log_print!(" - Submitted at: block {}", status.submitted);
534 log_print!(
535 " - Tally: Ayes: {}, Nays: {}",
536 status.tally.ayes,
537 status.tally.nays
538 );
539 log_verbose!(" - Full status: {:#?}", status);
540 },
541 ReferendumInfo::Approved(submitted, ..) => {
542 log_print!(" - Status: {}", "Approved".green());
543 log_print!(" - Submitted at block: {}", submitted);
544 },
545 ReferendumInfo::Rejected(submitted, ..) => {
546 log_print!(" - Status: {}", "Rejected".red());
547 log_print!(" - Submitted at block: {}", submitted);
548 },
549 ReferendumInfo::Cancelled(submitted, ..) => {
550 log_print!(" - Status: {}", "Cancelled".yellow());
551 log_print!(" - Submitted at block: {}", submitted);
552 },
553 ReferendumInfo::TimedOut(submitted, ..) => {
554 log_print!(" - Status: {}", "TimedOut".dimmed());
555 log_print!(" - Submitted at block: {}", submitted);
556 },
557 ReferendumInfo::Killed(submitted) => {
558 log_print!(" - Status: {}", "Killed".red().bold());
559 log_print!(" - Killed at block: {}", submitted);
560 },
561 }
562 },
563 Ok(None) => log_print!("📭 Referendum #{} not found", index),
564 Err(e) => log_error!("❌ Failed to fetch referendum #{}: {:?}", index, e),
565 }
566
567 Ok(())
568}
569
570async fn place_decision_deposit(
572 quantus_client: &crate::chain::client::QuantusClient,
573 index: u32,
574 from: &str,
575 password: Option<String>,
576 password_file: Option<String>,
577 finalized: bool,
578) -> crate::error::Result<()> {
579 log_print!("📋 Placing decision deposit for Tech Referendum #{}", index);
580 log_print!(" 🔑 Placed by: {}", from.bright_yellow());
581
582 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
583
584 let deposit_call = quantus_subxt::api::tx().tech_referenda().place_decision_deposit(index);
585 let tx_hash =
586 submit_transaction(quantus_client, &keypair, deposit_call, None, finalized).await?;
587 log_success!("✅ Decision deposit placed! Hash: {:?}", tx_hash.to_string().bright_yellow());
588 Ok(())
589}
590
591async fn cancel_proposal(
593 quantus_client: &crate::chain::client::QuantusClient,
594 index: u32,
595 from: &str,
596 password: Option<String>,
597 password_file: Option<String>,
598 finalized: bool,
599) -> crate::error::Result<()> {
600 log_print!("❌ Cancelling Tech Referendum #{}", index);
601 log_print!(" 🔑 Cancelled by: {}", from.bright_yellow());
602
603 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
604
605 let inner =
606 quantus_subxt::api::Call::TechReferenda(quantus_subxt::api::tech_referenda::Call::cancel {
607 index,
608 });
609 let sudo_call = quantus_subxt::api::tx().sudo().sudo(inner);
610
611 let tx_hash = submit_transaction(quantus_client, &keypair, sudo_call, None, finalized).await?;
612 log_success!("✅ Referendum cancelled! Hash: {:?}", tx_hash.to_string().bright_yellow());
613 Ok(())
614}
615
616async fn kill_proposal(
618 quantus_client: &crate::chain::client::QuantusClient,
619 index: u32,
620 from: &str,
621 password: Option<String>,
622 password_file: Option<String>,
623 finalized: bool,
624) -> crate::error::Result<()> {
625 log_print!("💀 Killing Tech Referendum #{}", index);
626 log_print!(" 🔑 Killed by: {}", from.bright_yellow());
627
628 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
629
630 let inner =
631 quantus_subxt::api::Call::TechReferenda(quantus_subxt::api::tech_referenda::Call::kill {
632 index,
633 });
634 let sudo_call = quantus_subxt::api::tx().sudo().sudo(inner);
635
636 let tx_hash = submit_transaction(quantus_client, &keypair, sudo_call, None, finalized).await?;
637 log_success!("✅ Referendum killed! Hash: {:?}", tx_hash.to_string().bright_yellow());
638 Ok(())
639}
640
641async fn nudge_proposal(
643 quantus_client: &crate::chain::client::QuantusClient,
644 index: u32,
645 from: &str,
646 password: Option<String>,
647 password_file: Option<String>,
648 finalized: bool,
649) -> crate::error::Result<()> {
650 log_print!("🔄 Nudging Tech Referendum #{}", index);
651 log_print!(" 🔑 Nudged by: {}", from.bright_yellow());
652
653 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
654
655 let inner = quantus_subxt::api::Call::TechReferenda(
656 quantus_subxt::api::tech_referenda::Call::nudge_referendum { index },
657 );
658 let sudo_call = quantus_subxt::api::tx().sudo().sudo(inner);
659
660 let tx_hash = submit_transaction(quantus_client, &keypair, sudo_call, None, finalized).await?;
661 log_success!("✅ Referendum nudged! Hash: {:?}", tx_hash.to_string().bright_yellow());
662 Ok(())
663}
664
665async fn get_config(
667 quantus_client: &crate::chain::client::QuantusClient,
668) -> crate::error::Result<()> {
669 log_print!("⚙️ Tech Referenda Configuration");
670 log_print!("");
671
672 let constants = quantus_client.client().constants();
673 let tracks_addr = quantus_subxt::api::constants().tech_referenda().tracks();
674
675 match constants.at(&tracks_addr) {
676 Ok(tracks) => {
677 log_print!("{}", "📊 Track Configuration:".bold());
678 for (id, info) in tracks.iter() {
679 log_print!(" ------------------------------------");
680 log_print!(
681 " • {} #{}: {}",
682 "Track".bold(),
683 id,
684 info.name.to_string().bright_cyan()
685 );
686 log_print!(" • Max Deciding: {}", info.max_deciding);
687 log_print!(" • Decision Deposit: {}", info.decision_deposit);
688 log_print!(" • Prepare Period: {} blocks", info.prepare_period);
689 log_print!(" • Decision Period: {} blocks", info.decision_period);
690 log_print!(" • Confirm Period: {} blocks", info.confirm_period);
691 log_print!(" • Min Enactment Period: {} blocks", info.min_enactment_period);
692 }
693 log_print!(" ------------------------------------");
694 },
695 Err(e) => {
696 log_error!("❌ Failed to decode Tracks constant: {:?}", e);
697 log_print!("💡 It's possible the Tracks constant is not in the expected format.");
698 },
699 }
700
701 Ok(())
702}
703
704async fn refund_submission_deposit(
706 quantus_client: &crate::chain::client::QuantusClient,
707 index: u32,
708 from: &str,
709 password: Option<String>,
710 password_file: Option<String>,
711 finalized: bool,
712) -> crate::error::Result<()> {
713 log_print!("💰 Refunding submission deposit for Tech Referendum #{}", index);
714 log_print!(" 🔑 Refund to: {}", from.bright_yellow());
715
716 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
718
719 let refund_call = quantus_subxt::api::tx().tech_referenda().refund_submission_deposit(index);
721
722 let tx_hash =
723 submit_transaction(quantus_client, &keypair, refund_call, None, finalized).await?;
724 log_print!(
725 "✅ {} Refund transaction submitted! Hash: {:?}",
726 "SUCCESS".bright_green().bold(),
727 tx_hash
728 );
729
730 log_success!("🎉 {} Submission deposit refunded!", "FINISHED".bright_green().bold());
731 log_print!("💡 Check your balance to confirm the refund");
732 Ok(())
733}
734
735async fn refund_decision_deposit(
737 quantus_client: &crate::chain::client::QuantusClient,
738 index: u32,
739 from: &str,
740 password: Option<String>,
741 password_file: Option<String>,
742 finalized: bool,
743) -> crate::error::Result<()> {
744 log_print!("💰 Refunding decision deposit for Tech Referendum #{}", index);
745 log_print!(" 🔑 Refund to: {}", from.bright_yellow());
746
747 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
749
750 let refund_call = quantus_subxt::api::tx().tech_referenda().refund_decision_deposit(index);
752
753 let tx_hash =
754 submit_transaction(quantus_client, &keypair, refund_call, None, finalized).await?;
755 log_print!(
756 "✅ {} Refund transaction submitted! Hash: {:?}",
757 "SUCCESS".bright_green().bold(),
758 tx_hash
759 );
760
761 log_success!("🎉 {} Decision deposit refunded!", "FINISHED".bright_green().bold());
762 log_print!("💡 Check your balance to confirm the refund");
763 Ok(())
764}