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) -> crate::error::Result<()> {
191 let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
192
193 match command {
194 TechReferendaCommands::Submit { preimage_hash, from, password, password_file } =>
195 submit_runtime_upgrade(&quantus_client, &preimage_hash, &from, password, password_file)
196 .await,
197 TechReferendaCommands::SubmitWithPreimage { wasm_file, from, password, password_file } =>
198 submit_runtime_upgrade_with_preimage(
199 &quantus_client,
200 &wasm_file,
201 &from,
202 password,
203 password_file,
204 )
205 .await,
206 TechReferendaCommands::List => list_proposals(&quantus_client).await,
207 TechReferendaCommands::Get { index } => get_proposal_details(&quantus_client, index).await,
208 TechReferendaCommands::Status { index } =>
209 get_proposal_status(&quantus_client, index).await,
210 TechReferendaCommands::PlaceDecisionDeposit { index, from, password, password_file } =>
211 place_decision_deposit(&quantus_client, index, &from, password, password_file).await,
212 TechReferendaCommands::Cancel { index, from, password, password_file } =>
213 cancel_proposal(&quantus_client, index, &from, password, password_file).await,
214 TechReferendaCommands::Kill { index, from, password, password_file } =>
215 kill_proposal(&quantus_client, index, &from, password, password_file).await,
216 TechReferendaCommands::Nudge { index, from, password, password_file } =>
217 nudge_proposal(&quantus_client, index, &from, password, password_file).await,
218 TechReferendaCommands::RefundSubmissionDeposit { index, from, password, password_file } =>
219 refund_submission_deposit(&quantus_client, index, &from, password, password_file).await,
220 TechReferendaCommands::RefundDecisionDeposit { index, from, password, password_file } =>
221 refund_decision_deposit(&quantus_client, index, &from, password, password_file).await,
222 TechReferendaCommands::Config => get_config(&quantus_client).await,
223 }
224}
225
226async fn submit_runtime_upgrade(
228 quantus_client: &crate::chain::client::QuantusClient,
229 preimage_hash: &str,
230 from: &str,
231 password: Option<String>,
232 password_file: Option<String>,
233) -> crate::error::Result<()> {
234 log_print!("📝 Submitting Runtime Upgrade Proposal to Tech Referenda");
235 log_print!(" 🔗 Preimage hash: {}", preimage_hash.bright_cyan());
236 log_print!(" 🔑 Submitted by: {}", from.bright_yellow());
237
238 let hash_str = preimage_hash.trim_start_matches("0x");
240 let preimage_hash_parsed: sp_core::H256 = sp_core::H256::from_str(hash_str)
241 .map_err(|_| QuantusError::Generic("Invalid preimage hash format".to_string()))?;
242
243 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
245
246 log_print!("🔍 Checking preimage status...");
248 let latest_block_hash = quantus_client.get_latest_block().await?;
249 let storage_at = quantus_client.client().storage().at(latest_block_hash);
250
251 let preimage_status = storage_at
252 .fetch(
253 &quantus_subxt::api::storage()
254 .preimage()
255 .request_status_for(preimage_hash_parsed),
256 )
257 .await
258 .map_err(|e| QuantusError::Generic(format!("Failed to fetch preimage status: {:?}", e)))?
259 .ok_or_else(|| QuantusError::Generic("Preimage not found on chain".to_string()))?;
260
261 let preimage_len = match preimage_status {
262 quantus_subxt::api::runtime_types::pallet_preimage::RequestStatus::Unrequested {
263 ticket: _,
264 len,
265 } => len,
266 quantus_subxt::api::runtime_types::pallet_preimage::RequestStatus::Requested {
267 maybe_ticket: _,
268 count: _,
269 maybe_len,
270 } => match maybe_len {
271 Some(len) => len,
272 None => return Err(QuantusError::Generic("Preimage length not available".to_string())),
273 },
274 };
275
276 log_print!("✅ Preimage found! Length: {} bytes", preimage_len);
277
278 type ProposalBounded =
280 quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded<
281 quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall,
282 quantus_subxt::api::runtime_types::qp_poseidon::PoseidonHasher,
283 >;
284
285 let preimage_hash_subxt: subxt::utils::H256 = preimage_hash_parsed;
286 let proposal: ProposalBounded =
287 ProposalBounded::Lookup { hash: preimage_hash_subxt, len: preimage_len };
288
289 let raw_origin_root =
290 quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root;
291 let origin_caller =
292 quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin_root);
293
294 let enactment =
295 quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After(
296 0u32,
297 );
298
299 log_print!("🔧 Creating TechReferenda::submit call...");
300 let submit_call =
301 quantus_subxt::api::tx()
302 .tech_referenda()
303 .submit(origin_caller, proposal, enactment);
304
305 let tx_hash = submit_transaction(quantus_client, &keypair, submit_call, None).await?;
306 log_print!(
307 "✅ {} Runtime upgrade proposal submitted! Hash: {:?}",
308 "SUCCESS".bright_green().bold(),
309 tx_hash
310 );
311
312 log_print!("💡 Use 'quantus tech-referenda list' to see active proposals");
313 Ok(())
314}
315
316async fn submit_runtime_upgrade_with_preimage(
318 quantus_client: &crate::chain::client::QuantusClient,
319 wasm_file: &PathBuf,
320 from: &str,
321 password: Option<String>,
322 password_file: Option<String>,
323) -> crate::error::Result<()> {
324 use qp_poseidon::PoseidonHasher;
325
326 log_print!("📝 Submitting Runtime Upgrade Proposal to Tech Referenda");
327 log_print!(" 📂 WASM file: {}", wasm_file.display().to_string().bright_cyan());
328 log_print!(" 🔑 Submitted by: {}", from.bright_yellow());
329
330 if !wasm_file.exists() {
331 return Err(QuantusError::Generic(format!("WASM file not found: {}", wasm_file.display())));
332 }
333
334 if let Some(ext) = wasm_file.extension() {
335 if ext != "wasm" {
336 log_verbose!("⚠️ Warning: File doesn't have .wasm extension");
337 }
338 }
339
340 let wasm_code = std::fs::read(wasm_file)
342 .map_err(|e| QuantusError::Generic(format!("Failed to read WASM file: {}", e)))?;
343
344 log_print!("📊 WASM file size: {} bytes", wasm_code.len());
345
346 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
348
349 let set_code_payload = quantus_subxt::api::tx().system().set_code(wasm_code.clone());
351 let metadata = quantus_client.client().metadata();
352 let encoded_call = <_ as subxt::tx::Payload>::encode_call_data(&set_code_payload, &metadata)
353 .map_err(|e| QuantusError::Generic(format!("Failed to encode call data: {:?}", e)))?;
354
355 log_verbose!("📝 Encoded call size: {} bytes", encoded_call.len());
356
357 let preimage_hash: sp_core::H256 =
359 <PoseidonHasher as sp_runtime::traits::Hash>::hash(&encoded_call);
360
361 log_print!("🔗 Preimage hash: {:?}", preimage_hash);
362
363 type PreimageBytes = quantus_subxt::api::preimage::calls::types::note_preimage::Bytes;
365 let bounded_bytes: PreimageBytes = encoded_call.clone();
366
367 log_print!("📝 Submitting preimage...");
368 let note_preimage_tx = quantus_subxt::api::tx().preimage().note_preimage(bounded_bytes);
369 let preimage_tx_hash =
370 submit_transaction(quantus_client, &keypair, note_preimage_tx, None).await?;
371 log_print!("✅ Preimage transaction submitted: {:?}", preimage_tx_hash);
372
373 log_print!("⏳ Waiting for preimage transaction confirmation...");
375
376 type ProposalBounded =
378 quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded<
379 quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall,
380 quantus_subxt::api::runtime_types::qp_poseidon::PoseidonHasher,
381 >;
382
383 let preimage_hash_subxt: subxt::utils::H256 = preimage_hash;
384 let proposal: ProposalBounded =
385 ProposalBounded::Lookup { hash: preimage_hash_subxt, len: encoded_call.len() as u32 };
386
387 let raw_origin_root =
388 quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root;
389 let origin_caller =
390 quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin_root);
391
392 let enactment =
393 quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After(
394 0u32,
395 );
396
397 log_print!("🔧 Creating TechReferenda::submit call...");
398 let submit_call =
399 quantus_subxt::api::tx()
400 .tech_referenda()
401 .submit(origin_caller, proposal, enactment);
402
403 let tx_hash = submit_transaction(quantus_client, &keypair, submit_call, None).await?;
404 log_print!(
405 "✅ {} Runtime upgrade proposal submitted! Hash: {:?}",
406 "SUCCESS".bright_green().bold(),
407 tx_hash
408 );
409
410 log_print!("💡 Use 'quantus tech-referenda list' to see active proposals");
411 Ok(())
412}
413
414async fn list_proposals(
416 quantus_client: &crate::chain::client::QuantusClient,
417) -> crate::error::Result<()> {
418 log_print!("📜 Active Tech Referenda Proposals");
419 log_print!("");
420
421 let addr = quantus_subxt::api::storage().tech_referenda().referendum_count();
422
423 let latest_block_hash = quantus_client.get_latest_block().await?;
425 let storage_at = quantus_client.client().storage().at(latest_block_hash);
426
427 let count = storage_at.fetch(&addr).await?;
428
429 if let Some(total) = count {
430 log_print!("📊 Total referenda created: {}", total);
431 if total == 0 {
432 log_print!("📭 No active proposals found");
433 return Ok(());
434 }
435 log_print!("🔍 Fetching recent referenda...");
436 for i in (0..total).rev().take(10) {
437 get_proposal_status(quantus_client, i).await?;
438 log_print!("----------------------------------------");
439 }
440 } else {
441 log_print!("📭 No referenda found - Tech Referenda may be empty");
442 }
443
444 Ok(())
445}
446
447async fn get_proposal_details(
449 quantus_client: &crate::chain::client::QuantusClient,
450 index: u32,
451) -> crate::error::Result<()> {
452 log_print!("📄 Tech Referendum #{} Details", index);
453 log_print!("");
454
455 let addr = quantus_subxt::api::storage().tech_referenda().referendum_info_for(index);
456
457 let latest_block_hash = quantus_client.get_latest_block().await?;
459 let storage_at = quantus_client.client().storage().at(latest_block_hash);
460
461 let info = storage_at.fetch(&addr).await?;
462
463 if let Some(referendum_info) = info {
464 log_print!("📋 Referendum Information (raw):");
465 log_print!("{:#?}", referendum_info);
466 } else {
467 log_print!("📭 Referendum #{} not found", index);
468 }
469 Ok(())
470}
471
472async fn get_proposal_status(
474 quantus_client: &crate::chain::client::QuantusClient,
475 index: u32,
476) -> crate::error::Result<()> {
477 use quantus_subxt::api::runtime_types::pallet_referenda::types::ReferendumInfo;
478
479 log_verbose!("📊 Fetching status for Tech Referendum #{}...", index);
480
481 let addr = quantus_subxt::api::storage().tech_referenda().referendum_info_for(index);
482
483 let latest_block_hash = quantus_client.get_latest_block().await?;
485 let storage_at = quantus_client.client().storage().at(latest_block_hash);
486
487 let info_res = storage_at.fetch(&addr).await;
488
489 match info_res {
490 Ok(Some(info)) => {
491 log_print!("📊 Status for Referendum #{}", index.to_string().bright_yellow());
492 match info {
493 ReferendumInfo::Ongoing(status) => {
494 log_print!(" - Status: {}", "Ongoing".bright_green());
495 log_print!(" - Track: {}", status.track);
496 log_print!(" - Submitted at: block {}", status.submitted);
497 log_print!(
498 " - Tally: Ayes: {}, Nays: {}",
499 status.tally.ayes,
500 status.tally.nays
501 );
502 log_verbose!(" - Full status: {:#?}", status);
503 },
504 ReferendumInfo::Approved(submitted, ..) => {
505 log_print!(" - Status: {}", "Approved".green());
506 log_print!(" - Submitted at block: {}", submitted);
507 },
508 ReferendumInfo::Rejected(submitted, ..) => {
509 log_print!(" - Status: {}", "Rejected".red());
510 log_print!(" - Submitted at block: {}", submitted);
511 },
512 ReferendumInfo::Cancelled(submitted, ..) => {
513 log_print!(" - Status: {}", "Cancelled".yellow());
514 log_print!(" - Submitted at block: {}", submitted);
515 },
516 ReferendumInfo::TimedOut(submitted, ..) => {
517 log_print!(" - Status: {}", "TimedOut".dimmed());
518 log_print!(" - Submitted at block: {}", submitted);
519 },
520 ReferendumInfo::Killed(submitted) => {
521 log_print!(" - Status: {}", "Killed".red().bold());
522 log_print!(" - Killed at block: {}", submitted);
523 },
524 }
525 },
526 Ok(None) => log_print!("📭 Referendum #{} not found", index),
527 Err(e) => log_error!("❌ Failed to fetch referendum #{}: {:?}", index, e),
528 }
529
530 Ok(())
531}
532
533async fn place_decision_deposit(
535 quantus_client: &crate::chain::client::QuantusClient,
536 index: u32,
537 from: &str,
538 password: Option<String>,
539 password_file: Option<String>,
540) -> crate::error::Result<()> {
541 log_print!("📋 Placing decision deposit for Tech Referendum #{}", index);
542 log_print!(" 🔑 Placed by: {}", from.bright_yellow());
543
544 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
545
546 let deposit_call = quantus_subxt::api::tx().tech_referenda().place_decision_deposit(index);
547 let tx_hash = submit_transaction(quantus_client, &keypair, deposit_call, None).await?;
548 log_success!("✅ Decision deposit placed! Hash: {:?}", tx_hash.to_string().bright_yellow());
549 Ok(())
550}
551
552async fn cancel_proposal(
554 quantus_client: &crate::chain::client::QuantusClient,
555 index: u32,
556 from: &str,
557 password: Option<String>,
558 password_file: Option<String>,
559) -> crate::error::Result<()> {
560 log_print!("❌ Cancelling Tech Referendum #{}", index);
561 log_print!(" 🔑 Cancelled by: {}", from.bright_yellow());
562
563 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
564
565 let inner =
566 quantus_subxt::api::Call::TechReferenda(quantus_subxt::api::tech_referenda::Call::cancel {
567 index,
568 });
569 let sudo_call = quantus_subxt::api::tx().sudo().sudo(inner);
570
571 let tx_hash = submit_transaction(quantus_client, &keypair, sudo_call, None).await?;
572 log_success!("✅ Referendum cancelled! Hash: {:?}", tx_hash.to_string().bright_yellow());
573 Ok(())
574}
575
576async fn kill_proposal(
578 quantus_client: &crate::chain::client::QuantusClient,
579 index: u32,
580 from: &str,
581 password: Option<String>,
582 password_file: Option<String>,
583) -> crate::error::Result<()> {
584 log_print!("💀 Killing Tech Referendum #{}", index);
585 log_print!(" 🔑 Killed by: {}", from.bright_yellow());
586
587 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
588
589 let inner =
590 quantus_subxt::api::Call::TechReferenda(quantus_subxt::api::tech_referenda::Call::kill {
591 index,
592 });
593 let sudo_call = quantus_subxt::api::tx().sudo().sudo(inner);
594
595 let tx_hash = submit_transaction(quantus_client, &keypair, sudo_call, None).await?;
596 log_success!("✅ Referendum killed! Hash: {:?}", tx_hash.to_string().bright_yellow());
597 Ok(())
598}
599
600async fn nudge_proposal(
602 quantus_client: &crate::chain::client::QuantusClient,
603 index: u32,
604 from: &str,
605 password: Option<String>,
606 password_file: Option<String>,
607) -> crate::error::Result<()> {
608 log_print!("🔄 Nudging Tech Referendum #{}", index);
609 log_print!(" 🔑 Nudged by: {}", from.bright_yellow());
610
611 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
612
613 let inner = quantus_subxt::api::Call::TechReferenda(
614 quantus_subxt::api::tech_referenda::Call::nudge_referendum { index },
615 );
616 let sudo_call = quantus_subxt::api::tx().sudo().sudo(inner);
617
618 let tx_hash = submit_transaction(quantus_client, &keypair, sudo_call, None).await?;
619 log_success!("✅ Referendum nudged! Hash: {:?}", tx_hash.to_string().bright_yellow());
620 Ok(())
621}
622
623async fn get_config(
625 quantus_client: &crate::chain::client::QuantusClient,
626) -> crate::error::Result<()> {
627 log_print!("⚙️ Tech Referenda Configuration");
628 log_print!("");
629
630 let constants = quantus_client.client().constants();
631 let tracks_addr = quantus_subxt::api::constants().tech_referenda().tracks();
632
633 match constants.at(&tracks_addr) {
634 Ok(tracks) => {
635 log_print!("{}", "📊 Track Configuration:".bold());
636 for (id, info) in tracks.iter() {
637 log_print!(" ------------------------------------");
638 log_print!(
639 " • {} #{}: {}",
640 "Track".bold(),
641 id,
642 info.name.to_string().bright_cyan()
643 );
644 log_print!(" • Max Deciding: {}", info.max_deciding);
645 log_print!(" • Decision Deposit: {}", info.decision_deposit);
646 log_print!(" • Prepare Period: {} blocks", info.prepare_period);
647 log_print!(" • Decision Period: {} blocks", info.decision_period);
648 log_print!(" • Confirm Period: {} blocks", info.confirm_period);
649 log_print!(" • Min Enactment Period: {} blocks", info.min_enactment_period);
650 }
651 log_print!(" ------------------------------------");
652 },
653 Err(e) => {
654 log_error!("❌ Failed to decode Tracks constant: {:?}", e);
655 log_print!("💡 It's possible the Tracks constant is not in the expected format.");
656 },
657 }
658
659 Ok(())
660}
661
662async fn refund_submission_deposit(
664 quantus_client: &crate::chain::client::QuantusClient,
665 index: u32,
666 from: &str,
667 password: Option<String>,
668 password_file: Option<String>,
669) -> crate::error::Result<()> {
670 log_print!("💰 Refunding submission deposit for Tech Referendum #{}", index);
671 log_print!(" 🔑 Refund to: {}", from.bright_yellow());
672
673 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
675
676 let refund_call = quantus_subxt::api::tx().tech_referenda().refund_submission_deposit(index);
678
679 let tx_hash = submit_transaction(quantus_client, &keypair, refund_call, None).await?;
680 log_print!(
681 "✅ {} Refund transaction submitted! Hash: {:?}",
682 "SUCCESS".bright_green().bold(),
683 tx_hash
684 );
685
686 log_success!("🎉 {} Submission deposit refunded!", "FINISHED".bright_green().bold());
687 log_print!("💡 Check your balance to confirm the refund");
688 Ok(())
689}
690
691async fn refund_decision_deposit(
693 quantus_client: &crate::chain::client::QuantusClient,
694 index: u32,
695 from: &str,
696 password: Option<String>,
697 password_file: Option<String>,
698) -> crate::error::Result<()> {
699 log_print!("💰 Refunding decision deposit for Tech Referendum #{}", index);
700 log_print!(" 🔑 Refund to: {}", from.bright_yellow());
701
702 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
704
705 let refund_call = quantus_subxt::api::tx().tech_referenda().refund_decision_deposit(index);
707
708 let tx_hash = submit_transaction(quantus_client, &keypair, refund_call, None).await?;
709 log_print!(
710 "✅ {} Refund transaction submitted! Hash: {:?}",
711 "SUCCESS".bright_green().bold(),
712 tx_hash
713 );
714
715 log_success!("🎉 {} Decision deposit refunded!", "FINISHED".bright_green().bold());
716 log_print!("💡 Check your balance to confirm the refund");
717 Ok(())
718}