diff --git a/.gitignore b/.gitignore index e6b176b..dcbb90e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ *.hex # Circuit binaries are generated at build time by build.rs -/generated-bins/ \ No newline at end of file +/generated-bins/ +/generated-bins \ No newline at end of file diff --git a/build.rs b/build.rs index 6183c55..55e9731 100644 --- a/build.rs +++ b/build.rs @@ -53,12 +53,40 @@ fn main() { // verification (manifest_dir is inside target/package/ in that case). let project_bins = Path::new(&manifest_dir).join("generated-bins"); if !manifest_dir.contains("target/package/") { - let _ = std::fs::create_dir_all(&project_bins); - if let Ok(entries) = std::fs::read_dir(&build_output_dir) { - for entry in entries.flatten() { - let dest = project_bins.join(entry.file_name()); - let _ = std::fs::copy(entry.path(), dest); + // Prefer a symlink to avoid copying large prover binaries on every build. + // If symlink creation fails (e.g. on filesystems without symlink support), + // fall back to copying and surface errors. + #[cfg(unix)] + { + use std::os::unix::fs::symlink; + // Remove any existing dir/file/symlink at destination. + if let Ok(meta) = std::fs::symlink_metadata(&project_bins) { + if meta.is_dir() { + std::fs::remove_dir_all(&project_bins) + .expect("Failed to remove existing generated-bins directory"); + } else { + std::fs::remove_file(&project_bins) + .expect("Failed to remove existing generated-bins file/symlink"); + } } + if let Err(e) = symlink(&build_output_dir, &project_bins) { + println!( + "cargo:warning=[quantus-cli] Failed to symlink generated-bins ({}). Falling back to copy...", + e + ); + } else { + // Symlink created successfully; we're done. + return; + } + } + + std::fs::create_dir_all(&project_bins).expect("Failed to create generated-bins directory"); + let entries = std::fs::read_dir(&build_output_dir) + .expect("Failed to read generated-bins directory in OUT_DIR"); + for entry in entries { + let entry = entry.expect("Failed to read generated-bins entry"); + let dest = project_bins.join(entry.file_name()); + std::fs::copy(entry.path(), dest).expect("Failed to copy generated-bins file"); } } } diff --git a/src/chain/quantus_subxt.rs b/src/chain/quantus_subxt.rs index a7fa077..dc41a34 100644 --- a/src/chain/quantus_subxt.rs +++ b/src/chain/quantus_subxt.rs @@ -6,12 +6,11 @@ pub mod api { mod root_mod { pub use super::*; } - pub static PALLETS: [&str; 21usize] = [ + pub static PALLETS: [&str; 20usize] = [ "System", "Timestamp", "Balances", "TransactionPayment", - "Sudo", "QPoW", "MiningRewards", "Preimage", @@ -1432,10 +1431,10 @@ pub mod api { "query_call_info", types::QueryCallInfo { call, len }, [ - 218u8, 133u8, 148u8, 251u8, 115u8, 98u8, 41u8, 181u8, 220u8, 98u8, - 96u8, 5u8, 83u8, 71u8, 198u8, 107u8, 19u8, 120u8, 188u8, 127u8, 50u8, - 201u8, 132u8, 73u8, 13u8, 118u8, 124u8, 200u8, 48u8, 103u8, 188u8, - 219u8, + 251u8, 117u8, 187u8, 14u8, 196u8, 176u8, 208u8, 219u8, 61u8, 234u8, + 23u8, 241u8, 174u8, 156u8, 167u8, 160u8, 89u8, 42u8, 178u8, 155u8, + 190u8, 179u8, 124u8, 114u8, 251u8, 186u8, 67u8, 205u8, 239u8, 51u8, + 177u8, 90u8, ], ) } @@ -1453,10 +1452,10 @@ pub mod api { "query_call_fee_details", types::QueryCallFeeDetails { call, len }, [ - 222u8, 82u8, 209u8, 219u8, 210u8, 175u8, 13u8, 207u8, 13u8, 17u8, - 234u8, 166u8, 55u8, 25u8, 194u8, 92u8, 155u8, 69u8, 143u8, 95u8, 18u8, - 181u8, 70u8, 191u8, 64u8, 119u8, 244u8, 116u8, 44u8, 172u8, 251u8, - 66u8, + 213u8, 155u8, 136u8, 5u8, 157u8, 246u8, 224u8, 16u8, 227u8, 103u8, + 222u8, 116u8, 172u8, 213u8, 203u8, 43u8, 153u8, 98u8, 75u8, 158u8, + 131u8, 22u8, 186u8, 186u8, 116u8, 25u8, 242u8, 159u8, 130u8, 122u8, + 152u8, 125u8, ], ) } @@ -1841,9 +1840,6 @@ pub mod api { pub fn transaction_payment(&self) -> transaction_payment::storage::StorageApi { transaction_payment::storage::StorageApi } - pub fn sudo(&self) -> sudo::storage::StorageApi { - sudo::storage::StorageApi - } pub fn q_po_w(&self) -> q_po_w::storage::StorageApi { q_po_w::storage::StorageApi } @@ -1901,9 +1897,6 @@ pub mod api { pub fn balances(&self) -> balances::calls::TransactionApi { balances::calls::TransactionApi } - pub fn sudo(&self) -> sudo::calls::TransactionApi { - sudo::calls::TransactionApi - } pub fn preimage(&self) -> preimage::calls::TransactionApi { preimage::calls::TransactionApi } @@ -1959,9 +1952,9 @@ pub mod api { .hash(); runtime_metadata_hash == [ - 9u8, 252u8, 101u8, 200u8, 161u8, 142u8, 241u8, 130u8, 224u8, 189u8, 72u8, 23u8, - 15u8, 35u8, 16u8, 38u8, 233u8, 12u8, 177u8, 101u8, 41u8, 225u8, 185u8, 137u8, - 187u8, 153u8, 77u8, 45u8, 214u8, 214u8, 215u8, 198u8, + 95u8, 161u8, 29u8, 63u8, 6u8, 191u8, 169u8, 252u8, 9u8, 18u8, 126u8, 99u8, 29u8, + 6u8, 27u8, 29u8, 251u8, 189u8, 252u8, 166u8, 35u8, 141u8, 155u8, 35u8, 238u8, 85u8, + 244u8, 246u8, 49u8, 113u8, 167u8, 219u8, ] } pub mod system { @@ -3060,9 +3053,10 @@ pub mod api { "Events", (), [ - 219u8, 135u8, 2u8, 5u8, 35u8, 85u8, 207u8, 98u8, 136u8, 150u8, 109u8, - 92u8, 64u8, 218u8, 201u8, 111u8, 25u8, 157u8, 42u8, 17u8, 112u8, 204u8, - 180u8, 241u8, 138u8, 97u8, 146u8, 96u8, 223u8, 24u8, 107u8, 44u8, + 114u8, 203u8, 75u8, 252u8, 183u8, 122u8, 99u8, 127u8, 226u8, 41u8, + 125u8, 202u8, 177u8, 177u8, 136u8, 66u8, 221u8, 204u8, 240u8, 189u8, + 146u8, 54u8, 190u8, 211u8, 240u8, 69u8, 233u8, 62u8, 78u8, 177u8, 97u8, + 8u8, ], ) } @@ -5387,369 +5381,6 @@ pub mod api { } } } - pub mod sudo { - use super::{root_mod, runtime_types}; - #[doc = "Error for the Sudo pallet."] - pub type Error = runtime_types::pallet_sudo::pallet::Error; - #[doc = "Contains a variant per dispatchable extrinsic that this pallet has."] - pub type Call = runtime_types::pallet_sudo::pallet::Call; - pub mod calls { - use super::{root_mod, runtime_types}; - type DispatchError = runtime_types::sp_runtime::DispatchError; - pub mod types { - use super::runtime_types; - #[derive( - :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, - :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, - Debug, - )] - #[decode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode" - )] - #[encode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" - )] - #[doc = "Authenticates the sudo key and dispatches a function call with `Root` origin."] - pub struct Sudo { - pub call: ::subxt::ext::subxt_core::alloc::boxed::Box, - } - pub mod sudo { - use super::runtime_types; - pub type Call = runtime_types::quantus_runtime::RuntimeCall; - } - impl ::subxt::ext::subxt_core::blocks::StaticExtrinsic for Sudo { - const PALLET: &'static str = "Sudo"; - const CALL: &'static str = "sudo"; - } - #[derive( - :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, - :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, - Debug, - )] - #[decode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode" - )] - #[encode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" - )] - #[doc = "Authenticates the sudo key and dispatches a function call with `Root` origin."] - #[doc = "This function does not check the weight of the call, and instead allows the"] - #[doc = "Sudo user to specify the weight of the call."] - #[doc = ""] - #[doc = "The dispatch origin for this call must be _Signed_."] - pub struct SudoUncheckedWeight { - pub call: - ::subxt::ext::subxt_core::alloc::boxed::Box, - pub weight: sudo_unchecked_weight::Weight, - } - pub mod sudo_unchecked_weight { - use super::runtime_types; - pub type Call = runtime_types::quantus_runtime::RuntimeCall; - pub type Weight = runtime_types::sp_weights::weight_v2::Weight; - } - impl ::subxt::ext::subxt_core::blocks::StaticExtrinsic for SudoUncheckedWeight { - const PALLET: &'static str = "Sudo"; - const CALL: &'static str = "sudo_unchecked_weight"; - } - #[derive( - :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, - :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, - Debug, - )] - #[decode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode" - )] - #[encode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" - )] - #[doc = "Authenticates the current sudo key and sets the given AccountId (`new`) as the new sudo"] - #[doc = "key."] - pub struct SetKey { - pub new: set_key::New, - } - pub mod set_key { - use super::runtime_types; - pub type New = ::subxt::ext::subxt_core::utils::MultiAddress< - ::subxt::ext::subxt_core::utils::AccountId32, - (), - >; - } - impl ::subxt::ext::subxt_core::blocks::StaticExtrinsic for SetKey { - const PALLET: &'static str = "Sudo"; - const CALL: &'static str = "set_key"; - } - #[derive( - :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, - :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, - Debug, - )] - #[decode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode" - )] - #[encode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" - )] - #[doc = "Authenticates the sudo key and dispatches a function call with `Signed` origin from"] - #[doc = "a given account."] - #[doc = ""] - #[doc = "The dispatch origin for this call must be _Signed_."] - pub struct SudoAs { - pub who: sudo_as::Who, - pub call: ::subxt::ext::subxt_core::alloc::boxed::Box, - } - pub mod sudo_as { - use super::runtime_types; - pub type Who = ::subxt::ext::subxt_core::utils::MultiAddress< - ::subxt::ext::subxt_core::utils::AccountId32, - (), - >; - pub type Call = runtime_types::quantus_runtime::RuntimeCall; - } - impl ::subxt::ext::subxt_core::blocks::StaticExtrinsic for SudoAs { - const PALLET: &'static str = "Sudo"; - const CALL: &'static str = "sudo_as"; - } - #[derive( - :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, - :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, - Debug, - )] - #[decode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode" - )] - #[encode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" - )] - #[doc = "Permanently removes the sudo key."] - #[doc = ""] - #[doc = "**This cannot be un-done.**"] - pub struct RemoveKey; - impl ::subxt::ext::subxt_core::blocks::StaticExtrinsic for RemoveKey { - const PALLET: &'static str = "Sudo"; - const CALL: &'static str = "remove_key"; - } - } - pub struct TransactionApi; - impl TransactionApi { - #[doc = "Authenticates the sudo key and dispatches a function call with `Root` origin."] - pub fn sudo( - &self, - call: types::sudo::Call, - ) -> ::subxt::ext::subxt_core::tx::payload::StaticPayload { - ::subxt::ext::subxt_core::tx::payload::StaticPayload::new_static( - "Sudo", - "sudo", - types::Sudo { - call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), - }, - [ - 92u8, 222u8, 38u8, 218u8, 220u8, 120u8, 153u8, 82u8, 131u8, 21u8, - 133u8, 192u8, 226u8, 24u8, 106u8, 70u8, 156u8, 171u8, 231u8, 175u8, - 152u8, 51u8, 118u8, 15u8, 247u8, 6u8, 137u8, 153u8, 189u8, 181u8, - 207u8, 118u8, - ], - ) - } - #[doc = "Authenticates the sudo key and dispatches a function call with `Root` origin."] - #[doc = "This function does not check the weight of the call, and instead allows the"] - #[doc = "Sudo user to specify the weight of the call."] - #[doc = ""] - #[doc = "The dispatch origin for this call must be _Signed_."] - pub fn sudo_unchecked_weight( - &self, - call: types::sudo_unchecked_weight::Call, - weight: types::sudo_unchecked_weight::Weight, - ) -> ::subxt::ext::subxt_core::tx::payload::StaticPayload - { - ::subxt::ext::subxt_core::tx::payload::StaticPayload::new_static( - "Sudo", - "sudo_unchecked_weight", - types::SudoUncheckedWeight { - call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), - weight, - }, - [ - 18u8, 172u8, 114u8, 181u8, 239u8, 91u8, 227u8, 49u8, 11u8, 101u8, 81u8, - 116u8, 159u8, 79u8, 57u8, 170u8, 121u8, 12u8, 231u8, 89u8, 163u8, 95u8, - 8u8, 144u8, 157u8, 93u8, 162u8, 156u8, 198u8, 99u8, 4u8, 123u8, - ], - ) - } - #[doc = "Authenticates the current sudo key and sets the given AccountId (`new`) as the new sudo"] - #[doc = "key."] - pub fn set_key( - &self, - new: types::set_key::New, - ) -> ::subxt::ext::subxt_core::tx::payload::StaticPayload { - ::subxt::ext::subxt_core::tx::payload::StaticPayload::new_static( - "Sudo", - "set_key", - types::SetKey { new }, - [ - 9u8, 73u8, 39u8, 205u8, 188u8, 127u8, 143u8, 54u8, 128u8, 94u8, 8u8, - 227u8, 197u8, 44u8, 70u8, 93u8, 228u8, 196u8, 64u8, 165u8, 226u8, - 158u8, 101u8, 192u8, 22u8, 193u8, 102u8, 84u8, 21u8, 35u8, 92u8, 198u8, - ], - ) - } - #[doc = "Authenticates the sudo key and dispatches a function call with `Signed` origin from"] - #[doc = "a given account."] - #[doc = ""] - #[doc = "The dispatch origin for this call must be _Signed_."] - pub fn sudo_as( - &self, - who: types::sudo_as::Who, - call: types::sudo_as::Call, - ) -> ::subxt::ext::subxt_core::tx::payload::StaticPayload { - ::subxt::ext::subxt_core::tx::payload::StaticPayload::new_static( - "Sudo", - "sudo_as", - types::SudoAs { - who, - call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), - }, - [ - 25u8, 155u8, 115u8, 44u8, 183u8, 108u8, 169u8, 74u8, 11u8, 123u8, - 235u8, 102u8, 23u8, 199u8, 181u8, 104u8, 66u8, 183u8, 147u8, 133u8, - 155u8, 30u8, 203u8, 92u8, 78u8, 234u8, 38u8, 168u8, 178u8, 73u8, 108u8, - 50u8, - ], - ) - } - #[doc = "Permanently removes the sudo key."] - #[doc = ""] - #[doc = "**This cannot be un-done.**"] - pub fn remove_key( - &self, - ) -> ::subxt::ext::subxt_core::tx::payload::StaticPayload { - ::subxt::ext::subxt_core::tx::payload::StaticPayload::new_static( - "Sudo", - "remove_key", - types::RemoveKey {}, - [ - 133u8, 253u8, 54u8, 175u8, 202u8, 239u8, 5u8, 198u8, 180u8, 138u8, - 25u8, 28u8, 109u8, 40u8, 30u8, 56u8, 126u8, 100u8, 52u8, 205u8, 250u8, - 191u8, 61u8, 195u8, 172u8, 142u8, 184u8, 239u8, 247u8, 10u8, 211u8, - 79u8, - ], - ) - } - } - } - #[doc = "The `Event` enum of this pallet"] - pub type Event = runtime_types::pallet_sudo::pallet::Event; - pub mod events { - use super::runtime_types; - #[derive( - :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, - :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, - Debug, - )] - #[decode_as_type(crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode")] - #[encode_as_type(crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode")] - #[doc = "A sudo call just took place."] - pub struct Sudid { - pub sudo_result: sudid::SudoResult, - } - pub mod sudid { - use super::runtime_types; - pub type SudoResult = - ::core::result::Result<(), runtime_types::sp_runtime::DispatchError>; - } - impl ::subxt::ext::subxt_core::events::StaticEvent for Sudid { - const PALLET: &'static str = "Sudo"; - const EVENT: &'static str = "Sudid"; - } - #[derive( - :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, - :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, - Debug, - )] - #[decode_as_type(crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode")] - #[encode_as_type(crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode")] - #[doc = "The sudo key has been updated."] - pub struct KeyChanged { - pub old: key_changed::Old, - pub new: key_changed::New, - } - pub mod key_changed { - use super::runtime_types; - pub type Old = ::core::option::Option<::subxt::ext::subxt_core::utils::AccountId32>; - pub type New = ::subxt::ext::subxt_core::utils::AccountId32; - } - impl ::subxt::ext::subxt_core::events::StaticEvent for KeyChanged { - const PALLET: &'static str = "Sudo"; - const EVENT: &'static str = "KeyChanged"; - } - #[derive( - :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, - :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, - Debug, - )] - #[decode_as_type(crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode")] - #[encode_as_type(crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode")] - #[doc = "The key was permanently removed."] - pub struct KeyRemoved; - impl ::subxt::ext::subxt_core::events::StaticEvent for KeyRemoved { - const PALLET: &'static str = "Sudo"; - const EVENT: &'static str = "KeyRemoved"; - } - #[derive( - :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, - :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, - Debug, - )] - #[decode_as_type(crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode")] - #[encode_as_type(crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode")] - #[doc = "A [sudo_as](Pallet::sudo_as) call just took place."] - pub struct SudoAsDone { - pub sudo_result: sudo_as_done::SudoResult, - } - pub mod sudo_as_done { - use super::runtime_types; - pub type SudoResult = - ::core::result::Result<(), runtime_types::sp_runtime::DispatchError>; - } - impl ::subxt::ext::subxt_core::events::StaticEvent for SudoAsDone { - const PALLET: &'static str = "Sudo"; - const EVENT: &'static str = "SudoAsDone"; - } - } - pub mod storage { - use super::runtime_types; - pub mod types { - use super::runtime_types; - pub mod key { - use super::runtime_types; - pub type Key = ::subxt::ext::subxt_core::utils::AccountId32; - } - } - pub struct StorageApi; - impl StorageApi { - #[doc = " The `AccountId` of the sudo key."] - pub fn key( - &self, - ) -> ::subxt::ext::subxt_core::storage::address::StaticAddress< - (), - types::key::Key, - ::subxt::ext::subxt_core::utils::Yes, - (), - (), - > { - ::subxt::ext::subxt_core::storage::address::StaticAddress::new_static( - "Sudo", - "Key", - (), - [ - 72u8, 14u8, 225u8, 162u8, 205u8, 247u8, 227u8, 105u8, 116u8, 57u8, 4u8, - 31u8, 84u8, 137u8, 227u8, 228u8, 133u8, 245u8, 206u8, 227u8, 117u8, - 36u8, 252u8, 151u8, 107u8, 15u8, 180u8, 4u8, 4u8, 152u8, 195u8, 144u8, - ], - ) - } - } - } - } pub mod q_po_w { use super::{root_mod, runtime_types}; #[doc = "The `Event` enum of this pallet"] @@ -7009,10 +6640,9 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 166u8, 18u8, 39u8, 234u8, 251u8, 53u8, 21u8, 117u8, 134u8, 194u8, - 163u8, 196u8, 144u8, 31u8, 132u8, 122u8, 44u8, 9u8, 134u8, 32u8, 222u8, - 32u8, 173u8, 128u8, 182u8, 71u8, 87u8, 253u8, 109u8, 104u8, 222u8, - 32u8, + 209u8, 44u8, 38u8, 115u8, 192u8, 245u8, 230u8, 98u8, 4u8, 221u8, 48u8, + 218u8, 180u8, 45u8, 205u8, 242u8, 98u8, 60u8, 73u8, 209u8, 152u8, + 217u8, 39u8, 90u8, 46u8, 7u8, 50u8, 50u8, 214u8, 210u8, 178u8, 180u8, ], ) } @@ -7051,10 +6681,9 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 232u8, 133u8, 188u8, 74u8, 36u8, 170u8, 171u8, 99u8, 255u8, 226u8, - 174u8, 26u8, 109u8, 166u8, 144u8, 41u8, 219u8, 85u8, 170u8, 155u8, - 192u8, 22u8, 176u8, 97u8, 47u8, 17u8, 44u8, 223u8, 100u8, 65u8, 69u8, - 35u8, + 168u8, 23u8, 39u8, 121u8, 55u8, 131u8, 250u8, 35u8, 37u8, 194u8, 185u8, + 120u8, 150u8, 83u8, 251u8, 46u8, 33u8, 65u8, 144u8, 38u8, 77u8, 195u8, + 178u8, 91u8, 237u8, 254u8, 91u8, 211u8, 168u8, 181u8, 160u8, 137u8, ], ) } @@ -7090,10 +6719,9 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 128u8, 96u8, 130u8, 203u8, 5u8, 21u8, 127u8, 65u8, 92u8, 180u8, 92u8, - 228u8, 4u8, 71u8, 196u8, 224u8, 121u8, 194u8, 193u8, 213u8, 150u8, - 149u8, 253u8, 188u8, 121u8, 221u8, 209u8, 133u8, 46u8, 247u8, 221u8, - 29u8, + 72u8, 139u8, 81u8, 86u8, 46u8, 244u8, 58u8, 159u8, 117u8, 243u8, 134u8, + 98u8, 80u8, 240u8, 110u8, 81u8, 213u8, 76u8, 158u8, 96u8, 72u8, 193u8, + 249u8, 80u8, 251u8, 116u8, 166u8, 73u8, 81u8, 215u8, 75u8, 220u8, ], ) } @@ -7115,10 +6743,9 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 143u8, 66u8, 101u8, 79u8, 129u8, 219u8, 60u8, 16u8, 207u8, 159u8, - 231u8, 137u8, 114u8, 222u8, 149u8, 153u8, 60u8, 133u8, 35u8, 124u8, - 175u8, 66u8, 67u8, 97u8, 172u8, 207u8, 100u8, 13u8, 27u8, 169u8, 51u8, - 128u8, + 79u8, 224u8, 38u8, 251u8, 249u8, 207u8, 215u8, 115u8, 81u8, 65u8, 63u8, + 214u8, 84u8, 8u8, 50u8, 37u8, 55u8, 55u8, 61u8, 128u8, 25u8, 111u8, + 143u8, 130u8, 144u8, 164u8, 26u8, 145u8, 105u8, 180u8, 88u8, 94u8, ], ) } @@ -7466,7 +7093,7 @@ pub mod api { [::core::primitive::u8; 32usize], runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >, ::core::primitive::u32, runtime_types::quantus_runtime::OriginCaller, @@ -8113,9 +7740,9 @@ pub mod api { "batch", types::Batch { calls }, [ - 12u8, 35u8, 169u8, 238u8, 108u8, 124u8, 242u8, 241u8, 158u8, 144u8, - 55u8, 181u8, 164u8, 80u8, 109u8, 149u8, 149u8, 89u8, 202u8, 4u8, 65u8, - 16u8, 217u8, 49u8, 232u8, 146u8, 244u8, 123u8, 48u8, 8u8, 45u8, 101u8, + 241u8, 24u8, 68u8, 209u8, 219u8, 28u8, 161u8, 192u8, 127u8, 101u8, + 153u8, 143u8, 78u8, 9u8, 24u8, 122u8, 110u8, 250u8, 63u8, 159u8, 174u8, + 88u8, 85u8, 23u8, 195u8, 141u8, 225u8, 43u8, 138u8, 241u8, 44u8, 111u8, ], ) } @@ -8145,9 +7772,9 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 58u8, 129u8, 55u8, 87u8, 210u8, 249u8, 143u8, 136u8, 81u8, 237u8, 43u8, - 136u8, 156u8, 85u8, 92u8, 204u8, 228u8, 131u8, 218u8, 62u8, 54u8, 87u8, - 20u8, 248u8, 249u8, 118u8, 83u8, 233u8, 174u8, 3u8, 69u8, 110u8, + 165u8, 80u8, 171u8, 13u8, 254u8, 5u8, 209u8, 126u8, 47u8, 137u8, 90u8, + 96u8, 227u8, 196u8, 159u8, 55u8, 118u8, 49u8, 178u8, 50u8, 178u8, 34u8, + 131u8, 3u8, 41u8, 245u8, 119u8, 197u8, 221u8, 20u8, 174u8, 102u8, ], ) } @@ -8173,9 +7800,9 @@ pub mod api { "batch_all", types::BatchAll { calls }, [ - 160u8, 132u8, 211u8, 158u8, 79u8, 68u8, 196u8, 4u8, 17u8, 136u8, 198u8, - 11u8, 217u8, 69u8, 52u8, 19u8, 244u8, 95u8, 1u8, 43u8, 47u8, 107u8, - 71u8, 70u8, 129u8, 180u8, 96u8, 162u8, 243u8, 62u8, 255u8, 246u8, + 96u8, 62u8, 146u8, 237u8, 252u8, 60u8, 1u8, 202u8, 235u8, 81u8, 106u8, + 115u8, 226u8, 96u8, 214u8, 188u8, 216u8, 173u8, 83u8, 15u8, 78u8, 97u8, + 158u8, 66u8, 225u8, 63u8, 117u8, 196u8, 242u8, 185u8, 28u8, 145u8, ], ) } @@ -8198,9 +7825,10 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 70u8, 236u8, 128u8, 245u8, 79u8, 164u8, 36u8, 254u8, 249u8, 100u8, - 132u8, 160u8, 42u8, 245u8, 92u8, 122u8, 66u8, 71u8, 16u8, 194u8, 241u8, - 243u8, 146u8, 27u8, 3u8, 164u8, 155u8, 37u8, 133u8, 93u8, 15u8, 255u8, + 47u8, 109u8, 230u8, 121u8, 146u8, 49u8, 97u8, 31u8, 138u8, 224u8, + 206u8, 114u8, 57u8, 169u8, 75u8, 158u8, 2u8, 183u8, 138u8, 243u8, + 116u8, 32u8, 225u8, 61u8, 124u8, 106u8, 167u8, 238u8, 236u8, 20u8, + 202u8, 50u8, ], ) } @@ -8226,10 +7854,10 @@ pub mod api { "force_batch", types::ForceBatch { calls }, [ - 201u8, 43u8, 241u8, 144u8, 76u8, 120u8, 232u8, 97u8, 84u8, 126u8, - 227u8, 232u8, 69u8, 158u8, 222u8, 176u8, 144u8, 160u8, 104u8, 207u8, - 5u8, 106u8, 72u8, 119u8, 162u8, 214u8, 219u8, 131u8, 207u8, 153u8, - 24u8, 247u8, + 59u8, 222u8, 239u8, 78u8, 48u8, 183u8, 141u8, 107u8, 147u8, 104u8, + 49u8, 128u8, 119u8, 239u8, 68u8, 174u8, 68u8, 200u8, 166u8, 210u8, + 70u8, 69u8, 239u8, 174u8, 193u8, 143u8, 14u8, 45u8, 194u8, 153u8, + 198u8, 118u8, ], ) } @@ -8252,9 +7880,9 @@ pub mod api { weight, }, [ - 241u8, 184u8, 146u8, 128u8, 160u8, 33u8, 170u8, 130u8, 105u8, 26u8, - 128u8, 181u8, 154u8, 95u8, 76u8, 32u8, 133u8, 8u8, 115u8, 144u8, 198u8, - 25u8, 84u8, 96u8, 155u8, 30u8, 249u8, 235u8, 223u8, 158u8, 37u8, 13u8, + 86u8, 251u8, 42u8, 213u8, 7u8, 59u8, 69u8, 56u8, 170u8, 21u8, 184u8, + 32u8, 192u8, 34u8, 250u8, 122u8, 236u8, 31u8, 174u8, 104u8, 80u8, + 255u8, 209u8, 64u8, 251u8, 39u8, 162u8, 198u8, 163u8, 61u8, 73u8, 91u8, ], ) } @@ -8294,9 +7922,9 @@ pub mod api { fallback: ::subxt::ext::subxt_core::alloc::boxed::Box::new(fallback), }, [ - 199u8, 6u8, 145u8, 140u8, 251u8, 79u8, 237u8, 173u8, 162u8, 41u8, 31u8, - 94u8, 225u8, 34u8, 245u8, 153u8, 233u8, 225u8, 87u8, 190u8, 233u8, - 191u8, 3u8, 25u8, 216u8, 212u8, 30u8, 180u8, 168u8, 145u8, 54u8, 150u8, + 63u8, 128u8, 67u8, 20u8, 83u8, 111u8, 204u8, 20u8, 153u8, 162u8, 212u8, + 42u8, 117u8, 96u8, 212u8, 248u8, 70u8, 189u8, 85u8, 158u8, 136u8, + 110u8, 142u8, 61u8, 39u8, 54u8, 195u8, 118u8, 241u8, 6u8, 55u8, 130u8, ], ) } @@ -8319,10 +7947,9 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 222u8, 246u8, 25u8, 101u8, 155u8, 44u8, 93u8, 141u8, 239u8, 33u8, - 186u8, 124u8, 253u8, 4u8, 203u8, 161u8, 102u8, 220u8, 158u8, 48u8, - 81u8, 82u8, 9u8, 99u8, 50u8, 26u8, 210u8, 64u8, 165u8, 102u8, 227u8, - 84u8, + 167u8, 171u8, 21u8, 97u8, 73u8, 57u8, 83u8, 87u8, 141u8, 218u8, 49u8, + 79u8, 243u8, 165u8, 230u8, 26u8, 227u8, 116u8, 81u8, 203u8, 246u8, + 196u8, 209u8, 183u8, 16u8, 236u8, 9u8, 87u8, 169u8, 76u8, 73u8, 46u8, ], ) } @@ -8531,7 +8158,7 @@ pub mod api { pub type ProposalOrigin = runtime_types::quantus_runtime::OriginCaller; pub type Proposal = runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >; pub type EnactmentMoment = runtime_types::frame_support::traits::schedule::DispatchTime< @@ -9022,7 +8649,7 @@ pub mod api { pub type Track = ::core::primitive::u16; pub type Proposal = runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >; } impl ::subxt::ext::subxt_core::events::StaticEvent for Submitted { @@ -9116,7 +8743,7 @@ pub mod api { pub type Track = ::core::primitive::u16; pub type Proposal = runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >; pub type Tally = runtime_types::pallet_conviction_voting::types::Tally<::core::primitive::u128>; @@ -9373,7 +9000,7 @@ pub mod api { ::core::primitive::u32, runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >, ::core::primitive::u128, runtime_types::pallet_conviction_voting::types::Tally< @@ -10344,7 +9971,7 @@ pub mod api { ::core::primitive::u128, runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >, >; pub type Param0 = ::subxt::ext::subxt_core::utils::H256; @@ -12501,7 +12128,7 @@ pub mod api { pub type ProposalOrigin = runtime_types::quantus_runtime::OriginCaller; pub type Proposal = runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >; pub type EnactmentMoment = runtime_types::frame_support::traits::schedule::DispatchTime< @@ -12992,7 +12619,7 @@ pub mod api { pub type Track = ::core::primitive::u16; pub type Proposal = runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >; } impl ::subxt::ext::subxt_core::events::StaticEvent for Submitted { @@ -13086,7 +12713,7 @@ pub mod api { pub type Track = ::core::primitive::u16; pub type Proposal = runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >; pub type Tally = runtime_types::pallet_ranked_collective::Tally; } @@ -13337,7 +12964,7 @@ pub mod api { ::core::primitive::u32, runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >, ::core::primitive::u128, runtime_types::pallet_ranked_collective::Tally, @@ -14312,9 +13939,10 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 0u8, 144u8, 16u8, 19u8, 207u8, 225u8, 184u8, 92u8, 212u8, 17u8, 156u8, - 224u8, 97u8, 23u8, 96u8, 104u8, 207u8, 206u8, 163u8, 26u8, 91u8, 130u8, - 113u8, 130u8, 171u8, 57u8, 255u8, 83u8, 240u8, 20u8, 199u8, 126u8, + 145u8, 166u8, 24u8, 188u8, 62u8, 129u8, 16u8, 49u8, 10u8, 139u8, 33u8, + 24u8, 183u8, 248u8, 92u8, 239u8, 141u8, 11u8, 109u8, 166u8, 232u8, + 206u8, 123u8, 228u8, 213u8, 173u8, 87u8, 199u8, 92u8, 239u8, 240u8, + 30u8, ], ) } @@ -20420,7 +20048,11 @@ pub mod api { #[encode_as_type( crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" )] - #[doc = "Verify an aggregated wormhole proof and process all transfers in the batch"] + #[doc = "Verify an aggregated wormhole proof and process all transfers in the batch."] + #[doc = ""] + #[doc = "Returns `DispatchResultWithPostInfo` to allow weight correction on early failures."] + #[doc = "If validation fails before ZK verification, we return minimal weight."] + #[doc = "If ZK verification fails, we return full weight since the work was done."] pub struct VerifyAggregatedProof { pub proof_bytes: verify_aggregated_proof::ProofBytes, } @@ -20436,7 +20068,11 @@ pub mod api { } pub struct TransactionApi; impl TransactionApi { - #[doc = "Verify an aggregated wormhole proof and process all transfers in the batch"] + #[doc = "Verify an aggregated wormhole proof and process all transfers in the batch."] + #[doc = ""] + #[doc = "Returns `DispatchResultWithPostInfo` to allow weight correction on early failures."] + #[doc = "If validation fails before ZK verification, we return minimal weight."] + #[doc = "If ZK verification fails, we return full weight since the work was done."] pub fn verify_aggregated_proof( &self, proof_bytes: types::verify_aggregated_proof::ProofBytes, @@ -20553,6 +20189,12 @@ pub mod api { pub type TransferCount = ::core::primitive::u64; pub type Param0 = ::subxt::ext::subxt_core::utils::AccountId32; } + pub mod genesis_endowments_pending { + use super::runtime_types; + pub type GenesisEndowmentsPending = ::subxt::ext::subxt_core::alloc::vec::Vec< + (::subxt::ext::subxt_core::utils::AccountId32, ::core::primitive::u128), + >; + } } pub struct StorageApi; impl StorageApi { @@ -20709,6 +20351,32 @@ pub mod api { ], ) } + #[doc = " Genesis endowments pending event emission."] + #[doc = " Stores (to_address, amount) for each genesis endowment."] + #[doc = " These are processed in on_initialize at block 1 to emit NativeTransferred events,"] + #[doc = " then cleared. This ensures indexers like Subsquid can track genesis transfers."] + #[doc = ""] + #[doc = " Unbounded because it's only populated at genesis and cleared on block 1."] + pub fn genesis_endowments_pending( + &self, + ) -> ::subxt::ext::subxt_core::storage::address::StaticAddress< + (), + types::genesis_endowments_pending::GenesisEndowmentsPending, + ::subxt::ext::subxt_core::utils::Yes, + ::subxt::ext::subxt_core::utils::Yes, + (), + > { + ::subxt::ext::subxt_core::storage::address::StaticAddress::new_static( + "Wormhole", + "GenesisEndowmentsPending", + (), + [ + 92u8, 149u8, 109u8, 170u8, 10u8, 130u8, 163u8, 174u8, 224u8, 98u8, + 190u8, 125u8, 213u8, 240u8, 211u8, 23u8, 222u8, 20u8, 104u8, 45u8, 7u8, + 73u8, 225u8, 73u8, 21u8, 88u8, 91u8, 57u8, 207u8, 252u8, 25u8, 76u8, + ], + ) + } } } pub mod constants { @@ -25033,7 +24701,7 @@ pub mod api { >, proposal: runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >, enactment_moment: runtime_types::frame_support::traits::schedule::DispatchTime< @@ -25190,7 +24858,7 @@ pub mod api { track: ::core::primitive::u16, proposal: runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >, }, #[codec(index = 1)] @@ -25220,7 +24888,7 @@ pub mod api { track: ::core::primitive::u16, proposal: runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >, tally: runtime_types::pallet_conviction_voting::types::Tally< ::core::primitive::u128, @@ -25313,7 +24981,7 @@ pub mod api { track: ::core::primitive::u16, proposal: runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >, }, #[codec(index = 1)] @@ -25343,7 +25011,7 @@ pub mod api { track: ::core::primitive::u16, proposal: runtime_types::frame_support::traits::preimages::Bounded< runtime_types::quantus_runtime::RuntimeCall, - runtime_types::qp_poseidon::PoseidonHasher, + runtime_types::sp_runtime::traits::BlakeTwo256, >, tally: runtime_types::pallet_ranked_collective::Tally, }, @@ -26169,125 +25837,6 @@ pub mod api { pub __ignore: ::core::marker::PhantomData<(_4, _5, _2)>, } } - pub mod pallet_sudo { - use super::runtime_types; - pub mod pallet { - use super::runtime_types; - #[derive( - :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, - :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, - Debug, - )] - #[decode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode" - )] - #[encode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" - )] - #[doc = "Contains a variant per dispatchable extrinsic that this pallet has."] - pub enum Call { - #[codec(index = 0)] - #[doc = "Authenticates the sudo key and dispatches a function call with `Root` origin."] - sudo { - call: ::subxt::ext::subxt_core::alloc::boxed::Box< - runtime_types::quantus_runtime::RuntimeCall, - >, - }, - #[codec(index = 1)] - #[doc = "Authenticates the sudo key and dispatches a function call with `Root` origin."] - #[doc = "This function does not check the weight of the call, and instead allows the"] - #[doc = "Sudo user to specify the weight of the call."] - #[doc = ""] - #[doc = "The dispatch origin for this call must be _Signed_."] - sudo_unchecked_weight { - call: ::subxt::ext::subxt_core::alloc::boxed::Box< - runtime_types::quantus_runtime::RuntimeCall, - >, - weight: runtime_types::sp_weights::weight_v2::Weight, - }, - #[codec(index = 2)] - #[doc = "Authenticates the current sudo key and sets the given AccountId (`new`) as the new sudo"] - #[doc = "key."] - set_key { - new: ::subxt::ext::subxt_core::utils::MultiAddress< - ::subxt::ext::subxt_core::utils::AccountId32, - (), - >, - }, - #[codec(index = 3)] - #[doc = "Authenticates the sudo key and dispatches a function call with `Signed` origin from"] - #[doc = "a given account."] - #[doc = ""] - #[doc = "The dispatch origin for this call must be _Signed_."] - sudo_as { - who: ::subxt::ext::subxt_core::utils::MultiAddress< - ::subxt::ext::subxt_core::utils::AccountId32, - (), - >, - call: ::subxt::ext::subxt_core::alloc::boxed::Box< - runtime_types::quantus_runtime::RuntimeCall, - >, - }, - #[codec(index = 4)] - #[doc = "Permanently removes the sudo key."] - #[doc = ""] - #[doc = "**This cannot be un-done.**"] - remove_key, - } - #[derive( - :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, - :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, - Debug, - )] - #[decode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode" - )] - #[encode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" - )] - #[doc = "Error for the Sudo pallet."] - pub enum Error { - #[codec(index = 0)] - #[doc = "Sender must be the Sudo account."] - RequireSudo, - } - #[derive( - :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, - :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, - Debug, - )] - #[decode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode" - )] - #[encode_as_type( - crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" - )] - #[doc = "The `Event` enum of this pallet"] - pub enum Event { - #[codec(index = 0)] - #[doc = "A sudo call just took place."] - Sudid { - sudo_result: - ::core::result::Result<(), runtime_types::sp_runtime::DispatchError>, - }, - #[codec(index = 1)] - #[doc = "The sudo key has been updated."] - KeyChanged { - old: ::core::option::Option<::subxt::ext::subxt_core::utils::AccountId32>, - new: ::subxt::ext::subxt_core::utils::AccountId32, - }, - #[codec(index = 2)] - #[doc = "The key was permanently removed."] - KeyRemoved, - #[codec(index = 3)] - #[doc = "A [sudo_as](Pallet::sudo_as) call just took place."] - SudoAsDone { - sudo_result: - ::core::result::Result<(), runtime_types::sp_runtime::DispatchError>, - }, - } - } - } pub mod pallet_timestamp { use super::runtime_types; pub mod pallet { @@ -26758,7 +26307,11 @@ pub mod api { #[doc = "Contains a variant per dispatchable extrinsic that this pallet has."] pub enum Call { #[codec(index = 2)] - #[doc = "Verify an aggregated wormhole proof and process all transfers in the batch"] + #[doc = "Verify an aggregated wormhole proof and process all transfers in the batch."] + #[doc = ""] + #[doc = "Returns `DispatchResultWithPostInfo` to allow weight correction on early failures."] + #[doc = "If validation fails before ZK verification, we return minimal weight."] + #[doc = "If ZK verification fails, we return full weight since the work was done."] verify_aggregated_proof { proof_bytes: ::subxt::ext::subxt_core::alloc::vec::Vec<::core::primitive::u8>, @@ -26782,24 +26335,22 @@ pub mod api { #[codec(index = 1)] NullifierAlreadyUsed, #[codec(index = 2)] - VerifierNotAvailable, - #[codec(index = 3)] BlockNotFound, - #[codec(index = 4)] + #[codec(index = 3)] AggregatedVerifierNotAvailable, - #[codec(index = 5)] + #[codec(index = 4)] AggregatedProofDeserializationFailed, - #[codec(index = 6)] + #[codec(index = 5)] AggregatedVerificationFailed, - #[codec(index = 7)] + #[codec(index = 6)] InvalidAggregatedPublicInputs, - #[codec(index = 8)] + #[codec(index = 7)] #[doc = "The volume fee rate in the proof doesn't match the configured rate"] InvalidVolumeFeeRate, - #[codec(index = 9)] + #[codec(index = 8)] #[doc = "Transfer amount is below the minimum required"] TransferAmountBelowMinimum, - #[codec(index = 10)] + #[codec(index = 9)] #[doc = "Only native asset (asset_id = 0) is supported in this version"] NonNativeAssetNotSupported, } @@ -26906,17 +26457,6 @@ pub mod api { pub digest: runtime_types::sp_runtime::generic::digest::Digest, } } - pub mod qp_poseidon { - use super::runtime_types; - #[derive( - :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, - :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, - Debug, - )] - #[decode_as_type(crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode")] - #[encode_as_type(crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode")] - pub struct PoseidonHasher; - } pub mod qp_scheduler { use super::runtime_types; #[derive( @@ -27032,8 +26572,6 @@ pub mod api { Timestamp(runtime_types::pallet_timestamp::pallet::Call), #[codec(index = 2)] Balances(runtime_types::pallet_balances::pallet::Call), - #[codec(index = 4)] - Sudo(runtime_types::pallet_sudo::pallet::Call), #[codec(index = 7)] Preimage(runtime_types::pallet_preimage::pallet::Call), #[codec(index = 8)] @@ -27073,8 +26611,6 @@ pub mod api { System(runtime_types::frame_system::pallet::Error), #[codec(index = 2)] Balances(runtime_types::pallet_balances::pallet::Error), - #[codec(index = 4)] - Sudo(runtime_types::pallet_sudo::pallet::Error), #[codec(index = 7)] Preimage(runtime_types::pallet_preimage::pallet::Error), #[codec(index = 8)] @@ -27118,8 +26654,6 @@ pub mod api { Balances(runtime_types::pallet_balances::pallet::Event), #[codec(index = 3)] TransactionPayment(runtime_types::pallet_transaction_payment::pallet::Event), - #[codec(index = 4)] - Sudo(runtime_types::pallet_sudo::pallet::Event), #[codec(index = 5)] QPoW(runtime_types::pallet_qpow::pallet::Event), #[codec(index = 6)] @@ -27968,6 +27502,21 @@ pub mod api { DecodeError, } } + pub mod traits { + use super::runtime_types; + #[derive( + :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, + :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, + Debug, + )] + #[decode_as_type( + crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode" + )] + #[encode_as_type( + crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" + )] + pub struct BlakeTwo256; + } pub mod transaction_validity { use super::runtime_types; #[derive( diff --git a/src/cli/generic_call.rs b/src/cli/generic_call.rs index fe2049a..bce083d 100644 --- a/src/cli/generic_call.rs +++ b/src/cli/generic_call.rs @@ -79,9 +79,6 @@ pub async fn execute_generic_call( submit_system_remark(quantus_client, from_keypair, &args, tip_amount, execution_mode) .await?, - // Sudo pallet calls - ("Sudo", "sudo") => submit_sudo_call(quantus_client, from_keypair, &args).await?, - // TechCollective pallet calls ("TechCollective", "add_member") => submit_tech_collective_add_member(quantus_client, from_keypair, &args, execution_mode) @@ -117,7 +114,6 @@ pub async fn execute_generic_call( log_print!("💡 Supported pallets in SubXT:"); log_print!(" • Balances: transfer_allow_death, transfer_keep_alive"); log_print!(" • System: remark"); - log_print!(" • Sudo: sudo"); log_print!(" • TechCollective: add_member, remove_member, vote"); log_print!(" • ReversibleTransfers: schedule_transfer"); log_print!(" • Scheduler: schedule, cancel"); @@ -225,20 +221,6 @@ async fn submit_system_remark( .await } -/// Submit sudo call -async fn submit_sudo_call( - _quantus_client: &crate::chain::client::QuantusClient, - _from_keypair: &QuantumKeyPair, - _args: &[Value], -) -> crate::error::Result { - // For now, this is a placeholder - sudo calls need the inner call to be constructed - log_error!("❌ Sudo calls through generic call are complex - use specific sudo wrappers"); - log_print!("💡 Use dedicated subxt commands that already wrap calls in sudo"); - Err(QuantusError::Generic( - "Sudo calls not supported in generic call - use specific commands".to_string(), - )) -} - /// Submit tech collective add member async fn submit_tech_collective_add_member( quantus_client: &crate::chain::client::QuantusClient, @@ -264,21 +246,12 @@ async fn submit_tech_collective_add_member( let member_account_id_subxt = subxt::ext::subxt_core::utils::AccountId32::from(member_account_id_bytes); - // Wrap in sudo for privileged operation - let sudo_call = quantus_subxt::api::tx().sudo().sudo(quantus_subxt::api::Call::TechCollective( - quantus_subxt::api::tech_collective::Call::add_member { - who: subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id_subxt), - }, - )); + let call = quantus_subxt::api::tx() + .tech_collective() + .add_member(subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id_subxt)); - crate::cli::common::submit_transaction( - quantus_client, - from_keypair, - sudo_call, - None, - execution_mode, - ) - .await + crate::cli::common::submit_transaction(quantus_client, from_keypair, call, None, execution_mode) + .await } /// Submit tech collective remove member @@ -306,22 +279,13 @@ async fn submit_tech_collective_remove_member( let member_account_id_subxt = subxt::ext::subxt_core::utils::AccountId32::from(member_account_id_bytes); - // Wrap in sudo for privileged operation - let sudo_call = quantus_subxt::api::tx().sudo().sudo(quantus_subxt::api::Call::TechCollective( - quantus_subxt::api::tech_collective::Call::remove_member { - who: subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id_subxt), - min_rank: 0, // Default rank - }, - )); + let call = quantus_subxt::api::tx().tech_collective().remove_member( + subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id_subxt), + 0u16, // Default rank + ); - crate::cli::common::submit_transaction( - quantus_client, - from_keypair, - sudo_call, - None, - execution_mode, - ) - .await + crate::cli::common::submit_transaction(quantus_client, from_keypair, call, None, execution_mode) + .await } /// Submit tech collective vote diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 4a13545..f2b2aa3 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -14,6 +14,8 @@ pub mod multisend; pub mod multisig; pub mod preimage; pub mod recovery; +pub mod referenda; +pub mod referenda_decode; pub mod reversible; pub mod runtime; pub mod scheduler; @@ -21,6 +23,7 @@ pub mod send; pub mod storage; pub mod system; pub mod tech_collective; +pub mod tech_referenda; pub mod transfers; pub mod treasury; pub mod wallet; @@ -88,7 +91,7 @@ pub enum Commands { #[command(subcommand)] Scheduler(scheduler::SchedulerCommands), - /// Direct interaction with chain storage (Sudo required for set) + /// Direct interaction with chain storage (read-only) #[command(subcommand)] Storage(storage::StorageCommands), @@ -96,10 +99,18 @@ pub enum Commands { #[command(subcommand)] TechCollective(tech_collective::TechCollectiveCommands), - /// Tech Referenda management commands (for runtime upgrade proposals) + /// Preimage management commands #[command(subcommand)] Preimage(preimage::PreimageCommands), + /// Tech Referenda management commands (for runtime upgrade proposals) + #[command(subcommand)] + TechReferenda(tech_referenda::TechReferendaCommands), + + /// Standard Referenda management commands (public governance) + #[command(subcommand)] + Referenda(referenda::ReferendaCommands), + /// Treasury account info #[command(subcommand)] Treasury(treasury::TreasuryCommands), @@ -108,7 +119,7 @@ pub enum Commands { #[command(subcommand)] Transfers(transfers::TransfersCommands), - /// Runtime management commands (requires root/sudo permissions) + /// Runtime management commands (via governance where required) #[command(subcommand)] Runtime(runtime::RuntimeCommands), @@ -334,6 +345,15 @@ pub async fn execute_command( .await, Commands::Preimage(preimage_cmd) => preimage::handle_preimage_command(preimage_cmd, node_url, execution_mode).await, + Commands::TechReferenda(tech_referenda_cmd) => + tech_referenda::handle_tech_referenda_command( + tech_referenda_cmd, + node_url, + execution_mode, + ) + .await, + Commands::Referenda(referenda_cmd) => + referenda::handle_referenda_command(referenda_cmd, node_url, execution_mode).await, Commands::Treasury(treasury_cmd) => treasury::handle_treasury_command(treasury_cmd, node_url, execution_mode).await, Commands::Transfers(transfers_cmd) => diff --git a/src/cli/referenda.rs b/src/cli/referenda.rs new file mode 100644 index 0000000..797252a --- /dev/null +++ b/src/cli/referenda.rs @@ -0,0 +1,848 @@ +//! `quantus referenda` subcommand - manage standard Referenda proposals +use crate::{ + chain::quantus_subxt, cli::common::submit_transaction, error::QuantusError, log_error, + log_print, log_success, log_verbose, +}; +use clap::Subcommand; +use colored::Colorize; +use std::str::FromStr; + +/// Standard Referenda management commands +#[derive(Subcommand, Debug)] +pub enum ReferendaCommands { + /// Submit a simple proposal (System::remark) to test Referenda + SubmitRemark { + /// Message to include in the remark + #[arg(long)] + message: String, + + /// Wallet name to sign with + #[arg(short, long)] + from: String, + + /// Password for the wallet + #[arg(short, long)] + password: Option, + + /// Read password from file + #[arg(long)] + password_file: Option, + + /// Origin type: signed (default) or root + #[arg(long, default_value = "signed")] + origin: String, + }, + + /// Submit a proposal using existing preimage hash + Submit { + /// Preimage hash (must already exist on chain) + #[arg(long)] + preimage_hash: String, + + /// Wallet name to sign with + #[arg(short, long)] + from: String, + + /// Password for the wallet + #[arg(short, long)] + password: Option, + + /// Read password from file + #[arg(long)] + password_file: Option, + + /// Origin type: signed (default) or root + #[arg(long, default_value = "signed")] + origin: String, + }, + + /// List all active Referenda proposals + List, + + /// Get details of a specific Referendum + Get { + /// Referendum index + #[arg(short, long)] + index: u32, + + /// Decode and display the proposal call in human-readable format + #[arg(long)] + decode: bool, + }, + + /// Check the status of a Referendum + Status { + /// Referendum index + #[arg(short, long)] + index: u32, + }, + + /// Place a decision deposit for a Referendum + PlaceDecisionDeposit { + /// Referendum index + #[arg(short, long)] + index: u32, + + /// Wallet name to sign with + #[arg(short, long)] + from: String, + + /// Password for the wallet + #[arg(short, long)] + password: Option, + + /// Read password from file + #[arg(long)] + password_file: Option, + }, + + /// Vote on a Referendum (uses conviction voting) + Vote { + /// Referendum index + #[arg(short, long)] + index: u32, + + /// Vote aye (true) or nay (false) + #[arg(long)] + aye: bool, + + /// Conviction (0=None, 1=Locked1x, 2=Locked2x, up to 6=Locked6x) + #[arg(long, default_value = "0")] + conviction: u8, + + /// Amount to vote with + #[arg(long)] + amount: String, + + /// Wallet name to sign with + #[arg(short, long)] + from: String, + + /// Password for the wallet + #[arg(short, long)] + password: Option, + + /// Read password from file + #[arg(long)] + password_file: Option, + }, + + /// Refund submission deposit for a completed Referendum + RefundSubmissionDeposit { + /// Referendum index + #[arg(short, long)] + index: u32, + + /// Wallet name that submitted the referendum + #[arg(short, long)] + from: String, + + /// Password for the wallet + #[arg(short, long)] + password: Option, + + /// Read password from file + #[arg(long)] + password_file: Option, + }, + + /// Refund decision deposit for a completed Referendum + RefundDecisionDeposit { + /// Referendum index + #[arg(short, long)] + index: u32, + + /// Wallet name that placed the decision deposit + #[arg(short, long)] + from: String, + + /// Password for the wallet + #[arg(short, long)] + password: Option, + + /// Read password from file + #[arg(long)] + password_file: Option, + }, + + /// Get Referenda configuration + Config, +} + +/// Handle referenda commands +pub async fn handle_referenda_command( + command: ReferendaCommands, + node_url: &str, + execution_mode: crate::cli::common::ExecutionMode, +) -> crate::error::Result<()> { + let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?; + + match command { + ReferendaCommands::SubmitRemark { message, from, password, password_file, origin } => + submit_remark_proposal( + &quantus_client, + &message, + &from, + password, + password_file, + &origin, + execution_mode, + ) + .await, + ReferendaCommands::Submit { preimage_hash, from, password, password_file, origin } => + submit_proposal( + &quantus_client, + &preimage_hash, + &from, + password, + password_file, + &origin, + execution_mode, + ) + .await, + ReferendaCommands::List => list_proposals(&quantus_client).await, + ReferendaCommands::Get { index, decode } => + get_proposal_details(&quantus_client, index, decode).await, + ReferendaCommands::Status { index } => get_proposal_status(&quantus_client, index).await, + ReferendaCommands::PlaceDecisionDeposit { index, from, password, password_file } => + place_decision_deposit( + &quantus_client, + index, + &from, + password, + password_file, + execution_mode, + ) + .await, + ReferendaCommands::Vote { + index, + aye, + conviction, + amount, + from, + password, + password_file, + } => + vote_on_referendum( + &quantus_client, + index, + aye, + conviction, + &amount, + &from, + password, + password_file, + execution_mode, + ) + .await, + ReferendaCommands::RefundSubmissionDeposit { index, from, password, password_file } => + refund_submission_deposit( + &quantus_client, + index, + &from, + password, + password_file, + execution_mode, + ) + .await, + ReferendaCommands::RefundDecisionDeposit { index, from, password, password_file } => + refund_decision_deposit( + &quantus_client, + index, + &from, + password, + password_file, + execution_mode, + ) + .await, + ReferendaCommands::Config => get_config(&quantus_client).await, + } +} + +/// Submit a simple System::remark proposal +async fn submit_remark_proposal( + quantus_client: &crate::chain::client::QuantusClient, + message: &str, + from: &str, + password: Option, + password_file: Option, + origin_type: &str, + execution_mode: crate::cli::common::ExecutionMode, +) -> crate::error::Result<()> { + use sp_runtime::traits::{BlakeTwo256, Hash}; + + log_print!("📝 Submitting System::remark Proposal to Referenda"); + log_print!(" 💬 Message: {}", message.bright_cyan()); + log_print!(" 🔑 Submitted by: {}", from.bright_yellow()); + log_print!(" 🎯 Origin type: {}", origin_type.bright_magenta()); + + // Load wallet keypair + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + // Build System::remark call and encode it + let remark_bytes = message.as_bytes().to_vec(); + let remark_payload = quantus_subxt::api::tx().system().remark(remark_bytes.clone()); + let metadata = quantus_client.client().metadata(); + let encoded_call = <_ as subxt::tx::Payload>::encode_call_data(&remark_payload, &metadata) + .map_err(|e| QuantusError::Generic(format!("Failed to encode call data: {:?}", e)))?; + + log_verbose!("📝 Encoded call size: {} bytes", encoded_call.len()); + + // Must match `frame_system::Config::Hashing` (BlakeTwo256) — same key as `pallet_preimage`. + let preimage_hash: sp_core::H256 = BlakeTwo256::hash(&encoded_call); + + log_print!("🔗 Preimage hash: {:?}", preimage_hash); + + // Submit Preimage::note_preimage + type PreimageBytes = quantus_subxt::api::preimage::calls::types::note_preimage::Bytes; + let bounded_bytes: PreimageBytes = encoded_call.clone(); + + log_print!("📝 Submitting preimage..."); + let note_preimage_tx = quantus_subxt::api::tx().preimage().note_preimage(bounded_bytes); + let preimage_tx_hash = + submit_transaction(quantus_client, &keypair, note_preimage_tx, None, execution_mode) + .await?; + log_print!("✅ Preimage transaction submitted: {:?}", preimage_tx_hash); + + // Wait for preimage transaction confirmation + log_print!("⏳ Waiting for preimage transaction confirmation..."); + + // Build Referenda::submit call using Lookup preimage reference + type ProposalBounded = + quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded< + quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall, + quantus_subxt::api::runtime_types::sp_runtime::traits::BlakeTwo256, + >; + + let preimage_hash_subxt: subxt::utils::H256 = preimage_hash; + let proposal: ProposalBounded = + ProposalBounded::Lookup { hash: preimage_hash_subxt, len: encoded_call.len() as u32 }; + + // Create origin based on origin_type parameter + let account_id_sp = keypair.to_account_id_32(); + let account_id_subxt: subxt::ext::subxt_core::utils::AccountId32 = + subxt::ext::subxt_core::utils::AccountId32(*account_id_sp.as_ref()); + + let origin_caller = match origin_type.to_lowercase().as_str() { + "signed" => { + let raw_origin = + quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Signed( + account_id_subxt, + ); + quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin) + }, + "root" => { + let raw_origin = + quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root; + quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin) + }, + "none" => + return Err(QuantusError::Generic( + "Invalid origin type: none. Use 'signed' or 'root'.".to_string(), + )), + _ => + return Err(QuantusError::Generic(format!( + "Invalid origin type: {}. Must be 'signed' or 'root'", + origin_type + ))), + }; + + let enactment = + quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After( + 10u32, // Execute 10 blocks after approval + ); + + log_print!("🔧 Creating Referenda::submit call..."); + let submit_call = + quantus_subxt::api::tx().referenda().submit(origin_caller, proposal, enactment); + + let tx_hash = + submit_transaction(quantus_client, &keypair, submit_call, None, execution_mode).await?; + log_print!( + "✅ {} Referendum proposal submitted! Hash: {:?}", + "SUCCESS".bright_green().bold(), + tx_hash + ); + + log_print!("💡 Use 'quantus referenda list' to see active proposals"); + Ok(()) +} + +/// Submit a proposal using existing preimage hash +async fn submit_proposal( + quantus_client: &crate::chain::client::QuantusClient, + preimage_hash: &str, + from: &str, + password: Option, + password_file: Option, + origin_type: &str, + execution_mode: crate::cli::common::ExecutionMode, +) -> crate::error::Result<()> { + log_print!("📝 Submitting Proposal to Referenda"); + log_print!(" 🔗 Preimage hash: {}", preimage_hash.bright_cyan()); + log_print!(" 🔑 Submitted by: {}", from.bright_yellow()); + log_print!(" 🎯 Origin type: {}", origin_type.bright_magenta()); + + // Parse preimage hash + let hash_str = preimage_hash.trim_start_matches("0x"); + let preimage_hash_parsed: sp_core::H256 = sp_core::H256::from_str(hash_str) + .map_err(|_| QuantusError::Generic("Invalid preimage hash format".to_string()))?; + + // Load wallet keypair + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + // Check if preimage exists and get its length + log_print!("🔍 Checking preimage status..."); + let latest_block_hash = quantus_client.get_latest_block().await?; + let storage_at = quantus_client.client().storage().at(latest_block_hash); + + let preimage_status = storage_at + .fetch( + &quantus_subxt::api::storage() + .preimage() + .request_status_for(preimage_hash_parsed), + ) + .await + .map_err(|e| QuantusError::Generic(format!("Failed to fetch preimage status: {:?}", e)))? + .ok_or_else(|| QuantusError::Generic("Preimage not found on chain".to_string()))?; + + let preimage_len = match preimage_status { + quantus_subxt::api::runtime_types::pallet_preimage::RequestStatus::Unrequested { + ticket: _, + len, + } => len, + quantus_subxt::api::runtime_types::pallet_preimage::RequestStatus::Requested { + maybe_ticket: _, + count: _, + maybe_len, + } => match maybe_len { + Some(len) => len, + None => return Err(QuantusError::Generic("Preimage length not available".to_string())), + }, + }; + + log_print!("✅ Preimage found! Length: {} bytes", preimage_len); + + // Build Referenda::submit call + type ProposalBounded = + quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded< + quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall, + quantus_subxt::api::runtime_types::sp_runtime::traits::BlakeTwo256, + >; + + let preimage_hash_subxt: subxt::utils::H256 = preimage_hash_parsed; + let proposal: ProposalBounded = + ProposalBounded::Lookup { hash: preimage_hash_subxt, len: preimage_len }; + + // Create origin based on origin_type parameter + let account_id_sp = keypair.to_account_id_32(); + let account_id_subxt: subxt::ext::subxt_core::utils::AccountId32 = + subxt::ext::subxt_core::utils::AccountId32(*account_id_sp.as_ref()); + + let origin_caller = match origin_type.to_lowercase().as_str() { + "signed" => { + let raw_origin = + quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Signed( + account_id_subxt, + ); + quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin) + }, + "root" => { + let raw_origin = + quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root; + quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin) + }, + "none" => + return Err(QuantusError::Generic( + "Invalid origin type: none. Use 'signed' or 'root'.".to_string(), + )), + _ => + return Err(QuantusError::Generic(format!( + "Invalid origin type: {}. Must be 'signed' or 'root'", + origin_type + ))), + }; + + let enactment = + quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After( + 10u32, + ); + + log_print!("🔧 Creating Referenda::submit call..."); + let submit_call = + quantus_subxt::api::tx().referenda().submit(origin_caller, proposal, enactment); + + let tx_hash = + submit_transaction(quantus_client, &keypair, submit_call, None, execution_mode).await?; + log_print!( + "✅ {} Referendum proposal submitted! Hash: {:?}", + "SUCCESS".bright_green().bold(), + tx_hash + ); + + log_print!("💡 Use 'quantus referenda list' to see active proposals"); + Ok(()) +} + +/// List recent Referenda proposals +async fn list_proposals( + quantus_client: &crate::chain::client::QuantusClient, +) -> crate::error::Result<()> { + log_print!("📜 Active Referenda Proposals"); + log_print!(""); + + let addr = quantus_subxt::api::storage().referenda().referendum_count(); + + let latest_block_hash = quantus_client.get_latest_block().await?; + let storage_at = quantus_client.client().storage().at(latest_block_hash); + + let count = storage_at.fetch(&addr).await?; + + if let Some(total) = count { + log_print!("📊 Total referenda created: {}", total); + if total == 0 { + log_print!("📭 No active proposals found"); + return Ok(()); + } + log_print!("🔍 Fetching recent referenda..."); + for i in (0..total).rev().take(10) { + get_proposal_status(quantus_client, i).await?; + log_print!("----------------------------------------"); + } + } else { + log_print!("📭 No referenda found - Referenda may be empty"); + } + + Ok(()) +} + +/// Get details of a specific Referendum +async fn get_proposal_details( + quantus_client: &crate::chain::client::QuantusClient, + index: u32, + decode: bool, +) -> crate::error::Result<()> { + use quantus_subxt::api::runtime_types::pallet_referenda::types::ReferendumInfo; + + log_print!("📄 Referendum #{} Details", index); + log_print!(""); + + let addr = quantus_subxt::api::storage().referenda().referendum_info_for(index); + + let latest_block_hash = quantus_client.get_latest_block().await?; + let storage_at = quantus_client.client().storage().at(latest_block_hash); + + let info = storage_at.fetch(&addr).await?; + + if let Some(referendum_info) = info { + if decode { + // Try to decode the proposal + match &referendum_info { + ReferendumInfo::Ongoing(status) => { + log_print!("📊 {} Referendum #{}", "Ongoing".bright_green(), index); + log_print!(" 🛤️ Track: {}", status.track); + log_print!(" 📅 Submitted: Block #{}", status.submitted); + log_print!( + " 🗳️ Tally: Ayes: {}, Nays: {}, Support: {}", + status.tally.ayes, + status.tally.nays, + status.tally.support + ); + log_print!(""); + + // Extract preimage hash and length from proposal + if let quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded::Lookup { + hash, + len, + } = &status.proposal + { + log_print!("📝 Proposal Details:"); + log_print!(" 🔗 Preimage Hash: {:?}", hash); + log_print!(" 📏 Length: {} bytes", len); + log_print!(""); + + // Fetch and decode the preimage + match crate::cli::referenda_decode::decode_preimage(quantus_client, hash, *len).await { + Ok(decoded) => { + log_print!("✅ Decoded Proposal:"); + log_print!("{}", decoded); + }, + Err(e) => { + log_print!("⚠️ Could not decode proposal: {}", e); + log_print!(" Run 'quantus preimage get --hash {:?} --len {}' to see raw data", hash, len); + }, + } + } else { + log_print!("⚠️ Proposal is inline (not a preimage lookup)"); + } + }, + ReferendumInfo::Approved(..) => { + log_print!("📊 {} Referendum #{}", "Approved".green(), index); + log_print!( + " ℹ️ Proposal details no longer available (referendum finalized)" + ); + }, + ReferendumInfo::Rejected(..) => { + log_print!("📊 {} Referendum #{}", "Rejected".red(), index); + log_print!( + " ℹ️ Proposal details no longer available (referendum finalized)" + ); + }, + ReferendumInfo::Cancelled(..) => { + log_print!("📊 {} Referendum #{}", "Cancelled".yellow(), index); + log_print!( + " ℹ️ Proposal details no longer available (referendum finalized)" + ); + }, + ReferendumInfo::TimedOut(..) => { + log_print!("📊 {} Referendum #{}", "TimedOut".dimmed(), index); + log_print!( + " ℹ️ Proposal details no longer available (referendum finalized)" + ); + }, + ReferendumInfo::Killed(..) => { + log_print!("📊 {} Referendum #{}", "Killed".red().bold(), index); + log_print!(" ℹ️ Proposal details no longer available (referendum killed)"); + }, + } + } else { + // Raw output (original behavior) + log_print!("📋 Referendum Information (raw):"); + log_print!("{:#?}", referendum_info); + } + } else { + log_print!("📭 Referendum #{} not found", index); + } + Ok(()) +} + +/// Get the status of a Referendum +async fn get_proposal_status( + quantus_client: &crate::chain::client::QuantusClient, + index: u32, +) -> crate::error::Result<()> { + use quantus_subxt::api::runtime_types::pallet_referenda::types::ReferendumInfo; + + log_verbose!("📊 Fetching status for Referendum #{}...", index); + + let addr = quantus_subxt::api::storage().referenda().referendum_info_for(index); + + let latest_block_hash = quantus_client.get_latest_block().await?; + let storage_at = quantus_client.client().storage().at(latest_block_hash); + + let info_res = storage_at.fetch(&addr).await; + + match info_res { + Ok(Some(info)) => { + log_print!("📊 Status for Referendum #{}", index.to_string().bright_yellow()); + match info { + ReferendumInfo::Ongoing(status) => { + log_print!(" - Status: {}", "Ongoing".bright_green()); + log_print!(" - Track: {}", status.track); + log_print!(" - Submitted at: block {}", status.submitted); + log_print!( + " - Tally: Ayes: {}, Nays: {}", + status.tally.ayes, + status.tally.nays + ); + log_verbose!(" - Full status: {:#?}", status); + }, + ReferendumInfo::Approved(submitted, ..) => { + log_print!(" - Status: {}", "Approved".green()); + log_print!(" - Submitted at block: {}", submitted); + }, + ReferendumInfo::Rejected(submitted, ..) => { + log_print!(" - Status: {}", "Rejected".red()); + log_print!(" - Submitted at block: {}", submitted); + }, + ReferendumInfo::Cancelled(submitted, ..) => { + log_print!(" - Status: {}", "Cancelled".yellow()); + log_print!(" - Submitted at block: {}", submitted); + }, + ReferendumInfo::TimedOut(submitted, ..) => { + log_print!(" - Status: {}", "TimedOut".dimmed()); + log_print!(" - Submitted at block: {}", submitted); + }, + ReferendumInfo::Killed(submitted) => { + log_print!(" - Status: {}", "Killed".red().bold()); + log_print!(" - Killed at block: {}", submitted); + }, + } + }, + Ok(None) => log_print!("📭 Referendum #{} not found", index), + Err(e) => log_error!("❌ Failed to fetch referendum #{}: {:?}", index, e), + } + + Ok(()) +} + +/// Place a decision deposit for a Referendum +async fn place_decision_deposit( + quantus_client: &crate::chain::client::QuantusClient, + index: u32, + from: &str, + password: Option, + password_file: Option, + execution_mode: crate::cli::common::ExecutionMode, +) -> crate::error::Result<()> { + log_print!("📋 Placing decision deposit for Referendum #{}", index); + log_print!(" 🔑 Placed by: {}", from.bright_yellow()); + + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + let deposit_call = quantus_subxt::api::tx().referenda().place_decision_deposit(index); + let tx_hash = + submit_transaction(quantus_client, &keypair, deposit_call, None, execution_mode).await?; + log_success!("✅ Decision deposit placed! Hash: {:?}", tx_hash.to_string().bright_yellow()); + Ok(()) +} + +/// Vote on a Referendum +async fn vote_on_referendum( + quantus_client: &crate::chain::client::QuantusClient, + index: u32, + aye: bool, + conviction: u8, + amount: &str, + from: &str, + password: Option, + password_file: Option, + execution_mode: crate::cli::common::ExecutionMode, +) -> crate::error::Result<()> { + log_print!("🗳️ Voting on Referendum #{}", index); + log_print!(" 📊 Vote: {}", if aye { "AYE ✅".bright_green() } else { "NAY ❌".bright_red() }); + log_print!(" 💰 Amount: {}", amount.bright_cyan()); + log_print!(" 🔒 Conviction: {}", conviction); + log_print!(" 🔑 Signed by: {}", from.bright_yellow()); + + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + // Parse amount using chain decimals (12 for DEV) + let amount_value: u128 = crate::cli::send::parse_amount(quantus_client, amount).await?; + + // Validate conviction + if conviction > 6 { + return Err(QuantusError::Generic("Invalid conviction (must be 0-6)".to_string())); + } + + // Build vote + let vote = + quantus_subxt::api::runtime_types::pallet_conviction_voting::vote::AccountVote::Standard { + vote: quantus_subxt::api::runtime_types::pallet_conviction_voting::vote::Vote( + if aye { 128 } else { 0 } | conviction, + ), + balance: amount_value, + }; + + let vote_call = quantus_subxt::api::tx().conviction_voting().vote(index, vote); + let tx_hash = + submit_transaction(quantus_client, &keypair, vote_call, None, execution_mode).await?; + + log_print!( + "✅ {} Vote transaction submitted! Hash: {:?}", + "SUCCESS".bright_green().bold(), + tx_hash + ); + + log_success!("🎉 {} Vote submitted!", "FINISHED".bright_green().bold()); + Ok(()) +} + +/// Get Referenda configuration +async fn get_config( + quantus_client: &crate::chain::client::QuantusClient, +) -> crate::error::Result<()> { + log_print!("⚙️ Referenda Configuration"); + log_print!(""); + + let constants = quantus_client.client().constants(); + let tracks_addr = quantus_subxt::api::constants().referenda().tracks(); + + match constants.at(&tracks_addr) { + Ok(tracks) => { + log_print!("{}", "📊 Track Configuration:".bold()); + for (id, info) in tracks.iter() { + log_print!(" ------------------------------------"); + log_print!( + " • {} #{}: {}", + "Track".bold(), + id, + info.name.to_string().bright_cyan() + ); + log_print!(" • Max Deciding: {}", info.max_deciding); + log_print!(" • Decision Deposit: {}", info.decision_deposit); + log_print!(" • Prepare Period: {} blocks", info.prepare_period); + log_print!(" • Decision Period: {} blocks", info.decision_period); + log_print!(" • Confirm Period: {} blocks", info.confirm_period); + log_print!(" • Min Enactment Period: {} blocks", info.min_enactment_period); + } + log_print!(" ------------------------------------"); + }, + Err(e) => { + log_error!("❌ Failed to decode Tracks constant: {:?}", e); + log_print!("💡 It's possible the Tracks constant is not in the expected format."); + }, + } + + Ok(()) +} + +/// Refund submission deposit for a completed Referendum +async fn refund_submission_deposit( + quantus_client: &crate::chain::client::QuantusClient, + index: u32, + from: &str, + password: Option, + password_file: Option, + execution_mode: crate::cli::common::ExecutionMode, +) -> crate::error::Result<()> { + log_print!("💰 Refunding submission deposit for Referendum #{}", index); + log_print!(" 🔑 Refund to: {}", from.bright_yellow()); + + // Load wallet keypair + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + // Create refund_submission_deposit call + let refund_call = quantus_subxt::api::tx().referenda().refund_submission_deposit(index); + + let tx_hash = + submit_transaction(quantus_client, &keypair, refund_call, None, execution_mode).await?; + log_print!( + "✅ {} Refund transaction submitted! Hash: {:?}", + "SUCCESS".bright_green().bold(), + tx_hash + ); + + log_print!("💡 Check your balance to confirm the refund"); + Ok(()) +} + +/// Refund decision deposit for a completed Referendum +async fn refund_decision_deposit( + quantus_client: &crate::chain::client::QuantusClient, + index: u32, + from: &str, + password: Option, + password_file: Option, + execution_mode: crate::cli::common::ExecutionMode, +) -> crate::error::Result<()> { + log_print!("💰 Refunding decision deposit for Referendum #{}", index); + log_print!(" 🔑 Refund to: {}", from.bright_yellow()); + + // Load wallet keypair + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + // Create refund_decision_deposit call + let refund_call = quantus_subxt::api::tx().referenda().refund_decision_deposit(index); + + let tx_hash = + submit_transaction(quantus_client, &keypair, refund_call, None, execution_mode).await?; + log_print!( + "✅ {} Refund transaction submitted! Hash: {:?}", + "SUCCESS".bright_green().bold(), + tx_hash + ); + + log_print!("💡 Check your balance to confirm the refund"); + Ok(()) +} diff --git a/src/cli/referenda_decode.rs b/src/cli/referenda_decode.rs new file mode 100644 index 0000000..7140fa7 --- /dev/null +++ b/src/cli/referenda_decode.rs @@ -0,0 +1,243 @@ +//! Decoding utilities for referendum proposals + +use crate::error::QuantusError; +use codec::Decode; +use colored::Colorize; + +/// Decode preimage call data into human-readable format +pub async fn decode_preimage( + quantus_client: &crate::chain::client::QuantusClient, + hash: &subxt::utils::H256, + len: u32, +) -> crate::error::Result { + // Fetch preimage from storage + let latest_block_hash = quantus_client.get_latest_block().await?; + let storage_at = quantus_client.client().storage().at(latest_block_hash); + + let preimage_addr = crate::chain::quantus_subxt::api::storage() + .preimage() + .preimage_for((*hash, len)); + + let preimage_result = storage_at.fetch(&preimage_addr).await; + + let content = match preimage_result { + Ok(Some(bounded_vec)) => bounded_vec.0, + Ok(None) => + return Err(QuantusError::Generic(format!("Preimage not found for hash {:?}", hash))), + Err(e) => return Err(QuantusError::Generic(format!("Error fetching preimage: {:?}", e))), + }; + + // Decode using direct Decode trait (RuntimeCall implements it via DecodeAsType derive) + decode_runtime_call_direct(&content) +} + +/// Decode RuntimeCall directly using Decode trait +fn decode_runtime_call_direct(data: &[u8]) -> crate::error::Result { + // First, let's try to understand the call structure by reading indices + if data.len() < 3 { + return Err(QuantusError::Generic("Call data too short".to_string())); + } + + let pallet_index = data[0]; + let inner_index = data[1]; + let call_index = data[2]; + + match (pallet_index, inner_index, call_index) { + // System pallet (0, 0, X) + // Special case: if call_index looks like Compact (high value like 0xe8), + // it might be remark (call 0) where the call index byte is omitted + (0, 0, idx) if idx > 100 => { + // Likely remark (call 0) with Compact-encoded Vec starting at byte 2 + decode_system_remark_no_index(&data[2..]) + }, + (0, 0, _) => decode_system_call(&data[2..]), + + // TreasuryPallet (18, 5, X) where X is any spend variant (11, 15, 19, etc.) + // Different indices represent different value ranges/encodings + (18, 5, _) => decode_treasury_spend_call(&data[3..]), + + // Unknown + _ => Ok(format!( + " {} {} {} {}\n {} {} bytes\n {}:\n {}", + "Call Indices:".dimmed(), + pallet_index, + inner_index, + call_index, + "Args:".dimmed(), + data.len() - 3, + "Raw Hex".dimmed(), + hex::encode(&data[3..]).bright_green() + )), + } +} + +/// Decode System::remark when call index byte is omitted (call 0) +fn decode_system_remark_no_index(args: &[u8]) -> crate::error::Result { + // args starts directly with Compact-encoded Vec + let mut cursor = args; + let remark_bytes: Vec = Vec::decode(&mut cursor) + .map_err(|e| QuantusError::Generic(format!("Failed to decode remark: {:?}", e)))?; + let remark_str = String::from_utf8_lossy(&remark_bytes); + + Ok(format!( + " {} {}\n {} {}\n {}:\n {} \"{}\"", + "Pallet:".dimmed(), + "System".bright_cyan(), + "Call:".dimmed(), + "remark".bright_yellow(), + "Parameters".dimmed(), + "message:".dimmed(), + remark_str.bright_green() + )) +} + +/// Decode System pallet calls +fn decode_system_call(data_from_call: &[u8]) -> crate::error::Result { + if data_from_call.is_empty() { + return Err(QuantusError::Generic("Empty system call data".to_string())); + } + + let call_index = data_from_call[0]; + let args = &data_from_call[1..]; + + match call_index { + 0 => { + // remark - standard Vec + let mut cursor = args; + let remark_bytes: Vec = Vec::decode(&mut cursor) + .map_err(|e| QuantusError::Generic(format!("Failed to decode remark: {:?}", e)))?; + let remark_str = String::from_utf8_lossy(&remark_bytes); + + Ok(format!( + " {} {}\n {} {}\n {}:\n {} \"{}\"", + "Pallet:".dimmed(), + "System".bright_cyan(), + "Call:".dimmed(), + "remark".bright_yellow(), + "Parameters".dimmed(), + "message:".dimmed(), + remark_str.bright_green() + )) + }, + 1 => { + // remark_with_event - has different encoding, try decoding from byte 1 + let remark_str = if args.len() > 1 { + String::from_utf8_lossy(&args[1..]) + } else { + String::from_utf8_lossy(args) + }; + + Ok(format!( + " {} {}\n {} {}\n {}:\n {} \"{}\"", + "Pallet:".dimmed(), + "System".bright_cyan(), + "Call:".dimmed(), + "remark_with_event".bright_yellow(), + "Parameters".dimmed(), + "message:".dimmed(), + remark_str.bright_green() + )) + }, + 7 => { + // set_code + Ok(format!( + " {} {}\n {} {} {}\n {} {}", + "Pallet:".dimmed(), + "System".bright_cyan(), + "Call:".dimmed(), + "set_code".bright_yellow(), + "(Runtime Upgrade)".dimmed(), + "Parameters:".dimmed(), + "".bright_green() + )) + }, + _ => Ok(format!( + " {} {}\n {} {} (index {})", + "Pallet:".dimmed(), + "System".bright_cyan(), + "Call:".dimmed(), + "unknown".yellow(), + call_index + )), + } +} + +/// Decode TreasuryPallet::spend call arguments +/// The amount is stored as variable-length u128 in little-endian +fn decode_treasury_spend_call(args: &[u8]) -> crate::error::Result { + use sp_core::crypto::Ss58Codec; + + crate::log_verbose!("Decoding treasury spend, args length: {} bytes", args.len()); + crate::log_verbose!("Args hex: {}", hex::encode(args)); + + if args.len() < 34 { + return Err(QuantusError::Generic(format!( + "Args too short for treasury spend: {} bytes (expected 40-42)", + args.len() + ))); + } + + // Structure (discovered through empirical analysis): + // - asset_kind: Box<()> = 0 bytes (unit type has no encoding) + // - amount: u128 = variable bytes (7-8 bytes typically) as little-endian + // - beneficiary: Box = 32 bytes (no variant byte!) + // - valid_from: Option = 1 byte (0x00 for None) + + // The amount length varies based on the value: + // - Small values (< 256TB): 7 bytes + // - Larger values: 8+ bytes + // Total length is typically 40 bytes (7+32+1) or 42 bytes (8+32+1) or similar + + // Calculate amount bytes length: total - 32 (beneficiary) - 1 (valid_from) + let amount_bytes_len = args.len() - 32 - 1; + if !(1..=16).contains(&amount_bytes_len) { + return Err(QuantusError::Generic(format!( + "Invalid amount bytes length: {}", + amount_bytes_len + ))); + } + + // Decode amount: first N bytes as little-endian u128 + let mut amount_bytes_extended = [0u8; 16]; + amount_bytes_extended[..amount_bytes_len].copy_from_slice(&args[..amount_bytes_len]); + let amount = u128::from_le_bytes(amount_bytes_extended); + + // Decode beneficiary: starts after amount bytes, 32 bytes + let beneficiary_start = amount_bytes_len; + let account_bytes: [u8; 32] = args[beneficiary_start..beneficiary_start + 32] + .try_into() + .map_err(|_| QuantusError::Generic("Failed to extract beneficiary bytes".to_string()))?; + let sp_account = sp_core::crypto::AccountId32::from(account_bytes); + let ss58 = sp_account.to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(42)); + let beneficiary_str = format!("{} ({}...{})", ss58, &ss58[..8], &ss58[ss58.len() - 6..]); + + // Decode valid_from: last byte + let valid_from_byte = args[args.len() - 1]; + let valid_from_str = if valid_from_byte == 0 { + "None (immediate)".to_string() + } else { + format!("Some (byte: 0x{:02x})", valid_from_byte) + }; + + // Format amount in QUAN (1 QUAN = 10^12) + let quan = amount as f64 / 1_000_000_000_000.0; + + Ok(format!( + " {} {}\n {} {}\n {}:\n {} {} {} ({} raw)\n {} {}\n {} {}\n\n {} {}", + "Pallet:".dimmed(), + "TreasuryPallet".bright_cyan(), + "Call:".dimmed(), + "spend".bright_yellow(), + "Parameters".dimmed(), + "amount:".dimmed(), + quan.to_string().bright_green().bold(), + "QUAN".bright_green(), + amount, + "beneficiary:".dimmed(), + beneficiary_str.bright_green(), + "valid_from:".dimmed(), + valid_from_str.bright_green(), + "💡 Info:".cyan(), + "Vote YES if you approve this Treasury spend, NO to reject.".cyan() + )) +} diff --git a/src/cli/runtime.rs b/src/cli/runtime.rs index 08ed810..7c010c6 100644 --- a/src/cli/runtime.rs +++ b/src/cli/runtime.rs @@ -12,13 +12,13 @@ use subxt::OnlineClient; #[derive(Subcommand, Debug)] pub enum RuntimeCommands { - /// Update the runtime using a WASM file (requires root permissions) + /// Propose a runtime upgrade using a WASM file (via Tech Referenda; creates preimage first) Update { /// Path to the runtime WASM file #[arg(short, long)] wasm_file: PathBuf, - /// Wallet name to sign with (must have root/sudo permissions) + /// Wallet name to sign with (must be allowed to submit Tech Referenda) #[arg(short, long)] from: String, @@ -43,7 +43,7 @@ pub enum RuntimeCommands { }, } -/// Update runtime with sudo wrapper +/// Propose runtime upgrade via Tech Referenda (no sudo pallet) pub async fn update_runtime( quantus_client: &crate::chain::client::QuantusClient, wasm_code: Vec, @@ -55,6 +55,8 @@ pub async fn update_runtime( log_print!("📋 Current runtime version:"); log_print!(" • Use 'quantus system --runtime' to see current version"); + log_print!("📋 Upgrade path:"); + log_print!(" • This submits a Tech Referendum with Root origin (not an immediate root call)"); // Show confirmation prompt unless force is used if !force { @@ -64,9 +66,9 @@ pub async fn update_runtime( "WARNING:".bright_red().bold(), "Runtime update is a critical operation!" ); - log_print!(" • This will update the blockchain runtime immediately"); - log_print!(" • All nodes will need to upgrade to stay in sync"); - log_print!(" • This operation cannot be easily reversed"); + log_print!(" • This will submit a governance proposal to upgrade the runtime"); + log_print!(" • If approved and enacted, all nodes will need to upgrade to stay in sync"); + log_print!(" • Governance operations cannot be easily reversed"); log_print!(""); // Simple confirmation prompt @@ -83,18 +85,62 @@ pub async fn update_runtime( } } - // Create the System::set_code call using RuntimeCall type alias - let set_code_call = - quantus_subxt::api::Call::System(quantus_subxt::api::system::Call::set_code { - code: wasm_code, - }); + // Build a static payload for System::set_code and encode full call data (pallet + call + args) + use sp_runtime::traits::{BlakeTwo256, Hash}; + let set_code_payload = quantus_subxt::api::tx().system().set_code(wasm_code.clone()); + let metadata = quantus_client.client().metadata(); + let encoded_call = <_ as subxt::tx::Payload>::encode_call_data(&set_code_payload, &metadata) + .map_err(|e| QuantusError::Generic(format!("Failed to encode call data: {:?}", e)))?; - // Wrap with sudo for root permissions - let sudo_call = quantus_subxt::api::tx().sudo().sudo(set_code_call); - - // Submit transaction - log_print!("📡 Submitting runtime update transaction..."); + log_print!("📡 Submitting runtime upgrade proposal (preimage + referendum)..."); log_print!("⏳ This may take longer than usual due to WASM size..."); + log_verbose!("📝 Encoded call size: {} bytes", encoded_call.len()); + + let preimage_hash: sp_core::H256 = BlakeTwo256::hash(&encoded_call); + log_print!("🔗 Preimage hash: {:?}", preimage_hash); + + // Submit Preimage::note_preimage with bounded bytes + type PreimageBytes = quantus_subxt::api::preimage::calls::types::note_preimage::Bytes; + let bounded_bytes: PreimageBytes = encoded_call.clone(); + + log_print!("📝 Submitting preimage..."); + let note_preimage_tx = quantus_subxt::api::tx().preimage().note_preimage(bounded_bytes); + let preimage_tx_hash = crate::cli::common::submit_transaction( + quantus_client, + from_keypair, + note_preimage_tx, + None, + execution_mode, + ) + .await?; + log_success!("✅ Preimage transaction submitted: {:?}", preimage_tx_hash); + + // Build TechReferenda::submit call using Lookup preimage reference + type ProposalBounded = + quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded< + quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall, + quantus_subxt::api::runtime_types::sp_runtime::traits::BlakeTwo256, + >; + + let preimage_hash_subxt: subxt::utils::H256 = preimage_hash; + let proposal: ProposalBounded = + ProposalBounded::Lookup { hash: preimage_hash_subxt, len: encoded_call.len() as u32 }; + + let raw_origin_root = + quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root; + let origin_caller = + quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin_root); + + let enactment = + quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After( + 0u32, + ); + + log_print!("🔧 Creating TechReferenda::submit call..."); + let submit_call = + quantus_subxt::api::tx() + .tech_referenda() + .submit(origin_caller, proposal, enactment); if !execution_mode.finalized { log_print!( @@ -105,16 +151,13 @@ pub async fn update_runtime( let tx_hash = crate::cli::common::submit_transaction( quantus_client, from_keypair, - sudo_call, + submit_call, None, execution_mode, ) .await?; - log_success!( - "✅ SUCCESS Runtime update transaction submitted! Hash: 0x{}", - hex::encode(tx_hash) - ); + log_success!("✅ SUCCESS Runtime upgrade proposal submitted! Hash: 0x{}", hex::encode(tx_hash)); Ok(tx_hash) } diff --git a/src/cli/storage.rs b/src/cli/storage.rs index 2ad0ce0..ab4a1f1 100644 --- a/src/cli/storage.rs +++ b/src/cli/storage.rs @@ -7,13 +7,10 @@ use crate::{ log_error, log_print, log_success, log_verbose, }; use clap::Subcommand; -use codec::{Decode, Encode}; +use codec::Decode; use colored::Colorize; use serde::Deserialize; -use sp_core::{ - crypto::{AccountId32, Ss58Codec}, - twox_128, -}; +use sp_core::{crypto::AccountId32, twox_128}; use std::{collections::BTreeMap, str::FromStr}; use subxt::OnlineClient; @@ -52,7 +49,7 @@ fn validate_pallet_exists( Ok(()) } -/// Direct interaction with chain storage (Sudo required for set) +/// Direct interaction with chain storage (read-only) #[derive(Subcommand, Debug)] pub enum StorageCommands { /// Get a storage value from a pallet. @@ -151,41 +148,6 @@ pub enum StorageCommands { #[arg(long)] block: Option, }, - - /// Set a storage value on the chain. - /// - /// This requires sudo privileges. It constructs a `system.set_storage` call - /// and wraps it in a `sudo.sudo` extrinsic. The provided value should be - /// a hex-encoded SCALE representation of the value. - Set { - /// The name of the pallet (e.g., "Scheduler") - #[arg(long)] - pallet: String, - - /// The name of the storage item (e.g., "LastProcessedTimestamp") - #[arg(long)] - name: String, - - /// The new value. Can be a plain string if --type is used, otherwise a hex string. - #[arg(long)] - value: String, - - /// The type of the value to be encoded (e.g., "u64", "moment", "accountid") - #[arg(long)] - r#type: Option, - - /// The name of the wallet to sign the transaction with (must have sudo rights) - #[arg(long)] - wallet: String, - - /// The password for the wallet - #[arg(long)] - password: Option, - - /// Read password from file (for scripting) - #[arg(long)] - password_file: Option, - }, } /// Get block hash from block number or parse existing hash @@ -251,39 +213,6 @@ pub async fn get_storage_raw_at_block( Ok(result) } -/// Set storage value using sudo (requires sudo privileges) -pub async fn set_storage_value( - quantus_client: &crate::chain::client::QuantusClient, - from_keypair: &crate::wallet::QuantumKeyPair, - storage_key: Vec, - value_bytes: Vec, - execution_mode: crate::cli::common::ExecutionMode, -) -> crate::error::Result { - log_verbose!("✍️ Creating set_storage transaction..."); - - // Create the System::set_storage call using RuntimeCall type alias - let set_storage_call = - quantus_subxt::api::Call::System(quantus_subxt::api::system::Call::set_storage { - items: vec![(storage_key, value_bytes)], - }); - - // Wrap in Sudo::sudo call - let sudo_call = quantus_subxt::api::tx().sudo().sudo(set_storage_call); - - let tx_hash = crate::cli::common::submit_transaction( - quantus_client, - from_keypair, - sudo_call, - None, - execution_mode, - ) - .await?; - - log_verbose!("📋 Set storage transaction submitted: {:?}", tx_hash); - - Ok(tx_hash) -} - /// List all storage items in a pallet pub async fn list_storage_items( quantus_client: &crate::chain::client::QuantusClient, @@ -805,7 +734,7 @@ async fn get_storage_by_parts( pub async fn handle_storage_command( command: StorageCommands, node_url: &str, - execution_mode: crate::cli::common::ExecutionMode, + _execution_mode: crate::cli::common::ExecutionMode, ) -> crate::error::Result<()> { log_print!("🗄️ Storage"); @@ -847,82 +776,6 @@ pub async fn handle_storage_command( show_storage_stats(&quantus_client, pallet, detailed).await, StorageCommands::Iterate { pallet, name, limit, decode_as, block } => iterate_storage_entries(&quantus_client, &pallet, &name, limit, decode_as, block).await, - - StorageCommands::Set { pallet, name, value, wallet, password, password_file, r#type } => { - log_print!("✍️ Setting storage for {}::{}", pallet.bright_green(), name.bright_cyan()); - log_print!("\n{}", "🛑 This is a SUDO operation!".bright_red().bold()); - - // Validate pallet exists - validate_pallet_exists(quantus_client.client(), &pallet)?; - - // 1. Load wallet - let keypair = - crate::wallet::load_keypair_from_wallet(&wallet, password, password_file)?; - log_verbose!("🔐 Using wallet: {}", wallet.bright_green()); - - // 2. Encode the value based on the --type flag - let value_bytes = match r#type.as_deref() { - Some("u64") | Some("moment") => value - .parse::() - .map_err(|e| QuantusError::Generic(format!("Invalid u64 value: {e}")))? - .encode(), - Some("u128") | Some("balance") => value - .parse::() - .map_err(|e| QuantusError::Generic(format!("Invalid u128 value: {e}")))? - .encode(), - Some("accountid") | Some("accountid32") => AccountId32::from_ss58check(&value) - .map_err(|e| QuantusError::Generic(format!("Invalid AccountId value: {e:?}")))? - .encode(), - None => { - // Default to hex decoding if no type is specified - // Try to parse as H256 first, then fall back to hex decode - if value.starts_with("0x") && value.len() == 66 { - // 0x + 64 hex chars = 66 (32 bytes) - // Try to parse as H256 - let h256_value = subxt::utils::H256::from_str(&value).map_err(|e| { - QuantusError::Generic(format!("Invalid H256 value: {e}")) - })?; - h256_value.0.to_vec() - } else { - // Fall back to hex decode for other hex values - let value_hex = value.strip_prefix("0x").unwrap_or(&value); - hex::decode(value_hex) - .map_err(|e| QuantusError::Generic(format!("Invalid hex value: {e}")))? - } - }, - Some(unsupported) => - return Err(QuantusError::Generic(format!( - "Unsupported type for --type: {unsupported}" - ))), - }; - - log_verbose!("Encoded value bytes: 0x{}", hex::encode(&value_bytes).dimmed()); - - // 3. Construct the storage key - let storage_key = { - let mut key = twox_128(pallet.as_bytes()).to_vec(); - key.extend(&twox_128(name.as_bytes())); - key - }; - - // 4. Submit the set storage transaction - let tx_hash = set_storage_value( - &quantus_client, - &keypair, - storage_key, - value_bytes, - execution_mode, - ) - .await?; - - log_print!( - "✅ {} Set storage transaction submitted! Hash: {:?}", - "SUCCESS".bright_green().bold(), - tx_hash - ); - - Ok(()) - }, } } diff --git a/src/cli/tech_collective.rs b/src/cli/tech_collective.rs index 6234b6f..b9a9c41 100644 --- a/src/cli/tech_collective.rs +++ b/src/cli/tech_collective.rs @@ -83,13 +83,6 @@ pub enum TechCollectiveCommands { address: String, }, - /// Check who has sudo permissions in the network - CheckSudo { - /// Address to check if it's the sudo account (optional) - #[arg(short, long)] - address: Option, - }, - /// List active Tech Referenda ListReferenda, @@ -101,7 +94,7 @@ pub enum TechCollectiveCommands { }, } -/// Add a member to the Tech Collective using sudo +/// Add a member to the Tech Collective pub async fn add_member( quantus_client: &crate::chain::client::QuantusClient, from_keypair: &crate::wallet::QuantumKeyPair, @@ -121,20 +114,14 @@ pub async fn add_member( log_verbose!("✍️ Creating add_member transaction..."); - // Create the TechCollective::add_member call as RuntimeCall enum - let add_member_call = quantus_subxt::api::Call::TechCollective( - quantus_subxt::api::tech_collective::Call::add_member { - who: subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id), - }, - ); - - // Wrap in Sudo::sudo call - let sudo_call = quantus_subxt::api::tx().sudo().sudo(add_member_call); + let add_member_call = quantus_subxt::api::tx() + .tech_collective() + .add_member(subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id)); let tx_hash = crate::cli::common::submit_transaction( quantus_client, from_keypair, - sudo_call, + add_member_call, None, execution_mode, ) @@ -145,7 +132,7 @@ pub async fn add_member( Ok(tx_hash) } -/// Remove a member from the Tech Collective using sudo +/// Remove a member from the Tech Collective pub async fn remove_member( quantus_client: &crate::chain::client::QuantusClient, from_keypair: &crate::wallet::QuantumKeyPair, @@ -165,21 +152,15 @@ pub async fn remove_member( log_verbose!("✍️ Creating remove_member transaction..."); - // Create the TechCollective::remove_member call as RuntimeCall enum - let remove_member_call = quantus_subxt::api::Call::TechCollective( - quantus_subxt::api::tech_collective::Call::remove_member { - who: subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id), - min_rank: 0u16, // Use rank 0 as default - }, + let remove_member_call = quantus_subxt::api::tx().tech_collective().remove_member( + subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id), + 0u16, // Use rank 0 as default ); - // Wrap in Sudo::sudo call - let sudo_call = quantus_subxt::api::tx().sudo().sudo(remove_member_call); - let tx_hash = crate::cli::common::submit_transaction( quantus_client, from_keypair, - sudo_call, + remove_member_call, None, execution_mode, ) @@ -275,42 +256,43 @@ pub async fn get_member_count( Ok(count_data) } -/// Get list of all members +/// Get list of all members (rank 0 indices via `IndexToId`). +/// +/// We do **not** use `members_iter()`: subxt decodes iterable map keys into `keys: ()` for this +/// storage layout, and guessing AccountId from raw `key_bytes` is fragile. `MemberCount` + +/// `IndexToId(rank, index)` matches FRAME's ranked collective layout and matches RPC/state layout. pub async fn get_member_list( quantus_client: &crate::chain::client::QuantusClient, ) -> crate::error::Result> { - log_verbose!("🔍 Getting member list..."); + log_verbose!("🔍 Getting member list via MemberCount + IndexToId..."); - // Get the latest block hash to read from the latest state (not finalized) let latest_block_hash = quantus_client.get_latest_block().await?; - let storage_at = quantus_client.client().storage().at(latest_block_hash); - // Query all Members storage entries - let members_storage = quantus_subxt::api::storage().tech_collective().members_iter(); - - let mut members = Vec::new(); - let mut iter = storage_at.iter(members_storage).await.map_err(|e| { - QuantusError::NetworkError(format!("Failed to create members iterator: {e:?}")) - })?; - - while let Some(result) = iter.next().await { - match result { - Ok(storage_entry) => { - let key = storage_entry.key_bytes; - // The key contains the AccountId32 after the storage prefix - // TechCollective Members storage key format: prefix + AccountId32 - if key.len() >= 32 { - // Extract the last 32 bytes as AccountId32 - let account_bytes: [u8; 32] = - key[key.len() - 32..].try_into().unwrap_or([0u8; 32]); - let sp_account = AccountId32::from(account_bytes); - log_verbose!("Found member: {}", sp_account.to_quantus_ss58()); - members.push(sp_account); - } + let count_addr = quantus_subxt::api::storage().tech_collective().member_count(0u16); + let count = storage_at + .fetch(&count_addr) + .await + .map_err(|e| QuantusError::NetworkError(format!("Failed to fetch member count: {e:?}")))? + .unwrap_or(0); + + let mut members = Vec::with_capacity(count as usize); + for index in 0..count { + let id_addr = quantus_subxt::api::storage().tech_collective().index_to_id(0u16, index); + match storage_at.fetch(&id_addr).await { + Ok(Some(subxt_account)) => { + let account_bytes: [u8; 32] = *subxt_account.as_ref(); + let sp_account = AccountId32::from(account_bytes); + log_verbose!("Found member [{}]: {}", index, sp_account.to_quantus_ss58()); + members.push(sp_account); + }, + Ok(None) => { + log_verbose!("⚠️ IndexToId missing for rank 0 index {index} (count={count})"); }, Err(e) => { - log_verbose!("⚠️ Error reading member entry: {:?}", e); + return Err(QuantusError::NetworkError(format!( + "Failed to fetch IndexToId(0, {index}): {e:?}" + ))); }, } } @@ -319,35 +301,6 @@ pub async fn get_member_list( Ok(members) } -/// Get sudo account information -pub async fn get_sudo_account( - quantus_client: &crate::chain::client::QuantusClient, -) -> crate::error::Result> { - log_verbose!("🔍 Getting sudo account..."); - - // Query Sudo::Key storage - let storage_addr = quantus_subxt::api::storage().sudo().key(); - - // Get the latest block hash to read from the latest state (not finalized) - let latest_block_hash = quantus_client.get_latest_block().await?; - - let storage_at = quantus_client.client().storage().at(latest_block_hash); - - let sudo_account = storage_at - .fetch(&storage_addr) - .await - .map_err(|e| QuantusError::NetworkError(format!("Failed to fetch sudo account: {e:?}")))?; - - // Convert from subxt_core AccountId32 to sp_core AccountId32 - if let Some(subxt_account) = sudo_account { - let account_bytes: [u8; 32] = *subxt_account.as_ref(); - let sp_account = AccountId32::from(account_bytes); - Ok(Some(sp_account)) - } else { - Ok(None) - } -} - /// Handle tech collective subxt commands pub async fn handle_tech_collective_command( command: TechCollectiveCommands, @@ -445,7 +398,7 @@ pub async fn handle_tech_collective_command( } }, Err(e) => { - log_verbose!("⚠️ Failed to get member list: {:?}", e); + log_print!("⚠️ Failed to get member list: {e}"); // Fallback to member count match get_member_count(&quantus_client).await? { Some(count_data) => { @@ -469,9 +422,9 @@ pub async fn handle_tech_collective_command( log_print!(""); log_print!("💡 To check specific membership:"); log_print!(" quantus tech-collective is-member --address
"); - log_print!("💡 To add a member (requires sudo):"); + log_print!("💡 To add a member:"); log_print!( - " quantus tech-collective add-member --who
--from " + " quantus tech-collective add-member --who
--from " ); }, @@ -492,42 +445,6 @@ pub async fn handle_tech_collective_command( } }, - TechCollectiveCommands::CheckSudo { address } => { - log_print!("🏛️ Checking sudo permissions "); - - match get_sudo_account(&quantus_client).await? { - Some(sudo_account) => { - let sudo_address = sudo_account.to_quantus_ss58(); - log_verbose!("🔍 Found sudo account: {}", sudo_address); - log_success!("✅ Found sudo account: {}", sudo_address.bright_green()); - - // If an address was provided, check if it matches the sudo account - if let Some(check_address) = address { - log_verbose!("🔍 Checking if provided address is sudo..."); - - // Resolve address (could be wallet name or SS58 address) - let resolved_address = resolve_address(&check_address)?; - log_verbose!(" 👤 Address to check: {}", resolved_address); - - if sudo_address == resolved_address { - log_success!("✅ Provided address IS the sudo account!"); - } else { - log_print!("❌ Provided address is NOT the sudo account"); - log_verbose!("💡 Provided address: {}", resolved_address); - log_verbose!("💡 Actual sudo address: {}", sudo_address); - } - } else { - // No address provided, just show the sudo account - log_verbose!("💡 Use 'quantus tech-collective check-sudo --address
' to check if a specific address is sudo"); - } - }, - None => { - log_print!("📭 No sudo account found in network"); - log_verbose!("💡 The network may not have sudo configured"); - }, - } - }, - TechCollectiveCommands::ListReferenda => { log_print!("📜 Active Tech Referenda "); log_print!(""); diff --git a/src/cli/tech_referenda.rs b/src/cli/tech_referenda.rs new file mode 100644 index 0000000..ae8ef6a --- /dev/null +++ b/src/cli/tech_referenda.rs @@ -0,0 +1,755 @@ +//! `quantus tech-referenda` subcommand - manage Tech Referenda proposals +use crate::{ + chain::quantus_subxt, cli::common::submit_transaction, error::QuantusError, log_error, + log_print, log_success, log_verbose, +}; +use clap::Subcommand; +use colored::Colorize; +use std::{path::PathBuf, str::FromStr}; + +/// Tech Referenda management commands +#[derive(Subcommand, Debug)] +pub enum TechReferendaCommands { + /// Submit a runtime upgrade proposal to Tech Referenda (requires existing preimage) + Submit { + /// Preimage hash (must already exist on chain) + #[arg(long)] + preimage_hash: String, + + /// Wallet name to sign with (must be a Tech Collective member or root) + #[arg(short, long)] + from: String, + + /// Password for the wallet + #[arg(short, long)] + password: Option, + + /// Read password from file + #[arg(long)] + password_file: Option, + }, + + /// Submit a runtime upgrade proposal to Tech Referenda (creates preimage first) + SubmitWithPreimage { + /// Path to the runtime WASM file + #[arg(short, long)] + wasm_file: PathBuf, + + /// Wallet name to sign with (must be a Tech Collective member or root) + #[arg(short, long)] + from: String, + + /// Password for the wallet + #[arg(short, long)] + password: Option, + + /// Read password from file + #[arg(long)] + password_file: Option, + }, + + /// Submit a proposal to set Treasury `treasury_portion` (Permill) via Tech Referenda (creates + /// preimage first) + SubmitTreasuryPortion { + /// New treasury portion in Permill (parts per million): 0..=1_000_000 + /// + /// Example: 500_000 = 50% + #[arg(long, value_parser = clap::value_parser!(u32).range(0..=1_000_000))] + portion_permill: u32, + + /// Wallet name to sign with (must be a Tech Collective member or root) + #[arg(short, long)] + from: String, + + /// Password for the wallet + #[arg(short, long)] + password: Option, + + /// Read password from file + #[arg(long)] + password_file: Option, + }, + + /// List all active Tech Referenda proposals + List, + + /// Get details of a specific Tech Referendum + Get { + /// Referendum index + #[arg(short, long)] + index: u32, + }, + + /// Check the status of a Tech Referendum + Status { + /// Referendum index + #[arg(short, long)] + index: u32, + }, + + /// Place a decision deposit for a Tech Referendum + PlaceDecisionDeposit { + /// Referendum index + #[arg(short, long)] + index: u32, + + /// Wallet name to sign with + #[arg(short, long)] + from: String, + + /// Password for the wallet + #[arg(short, long)] + password: Option, + + /// Read password from file + #[arg(long)] + password_file: Option, + }, + + /// Refund submission deposit for a completed Tech Referendum + RefundSubmissionDeposit { + /// Referendum index + #[arg(short, long)] + index: u32, + + /// Wallet name that submitted the referendum + #[arg(short, long)] + from: String, + + /// Password for the wallet + #[arg(short, long)] + password: Option, + + /// Read password from file + #[arg(long)] + password_file: Option, + }, + + /// Refund decision deposit for a completed Tech Referendum + RefundDecisionDeposit { + /// Referendum index + #[arg(short, long)] + index: u32, + + /// Wallet name that placed the decision deposit + #[arg(short, long)] + from: String, + + /// Password for the wallet + #[arg(short, long)] + password: Option, + + /// Read password from file + #[arg(long)] + password_file: Option, + }, + + /// Get Tech Referenda configuration + Config, +} + +/// Handle tech referenda commands +pub async fn handle_tech_referenda_command( + command: TechReferendaCommands, + node_url: &str, + execution_mode: crate::cli::common::ExecutionMode, +) -> crate::error::Result<()> { + let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?; + + match command { + TechReferendaCommands::Submit { preimage_hash, from, password, password_file } => + submit_runtime_upgrade( + &quantus_client, + &preimage_hash, + &from, + password, + password_file, + execution_mode, + ) + .await, + TechReferendaCommands::SubmitWithPreimage { wasm_file, from, password, password_file } => + submit_runtime_upgrade_with_preimage( + &quantus_client, + &wasm_file, + &from, + password, + password_file, + execution_mode, + ) + .await, + TechReferendaCommands::SubmitTreasuryPortion { + portion_permill, + from, + password, + password_file, + } => + submit_treasury_portion_with_preimage( + &quantus_client, + portion_permill, + &from, + password, + password_file, + execution_mode, + ) + .await, + TechReferendaCommands::List => list_proposals(&quantus_client).await, + TechReferendaCommands::Get { index } => get_proposal_details(&quantus_client, index).await, + TechReferendaCommands::Status { index } => + get_proposal_status(&quantus_client, index).await, + TechReferendaCommands::PlaceDecisionDeposit { index, from, password, password_file } => + place_decision_deposit( + &quantus_client, + index, + &from, + password, + password_file, + execution_mode, + ) + .await, + TechReferendaCommands::RefundSubmissionDeposit { index, from, password, password_file } => + refund_submission_deposit( + &quantus_client, + index, + &from, + password, + password_file, + execution_mode, + ) + .await, + TechReferendaCommands::RefundDecisionDeposit { index, from, password, password_file } => + refund_decision_deposit( + &quantus_client, + index, + &from, + password, + password_file, + execution_mode, + ) + .await, + TechReferendaCommands::Config => get_config(&quantus_client).await, + } +} + +/// Submit a runtime upgrade proposal to Tech Referenda (uses existing preimage) +async fn submit_runtime_upgrade( + quantus_client: &crate::chain::client::QuantusClient, + preimage_hash: &str, + from: &str, + password: Option, + password_file: Option, + execution_mode: crate::cli::common::ExecutionMode, +) -> crate::error::Result<()> { + log_print!("📝 Submitting Runtime Upgrade Proposal to Tech Referenda"); + log_print!(" 🔗 Preimage hash: {}", preimage_hash.bright_cyan()); + log_print!(" 🔑 Submitted by: {}", from.bright_yellow()); + + // Parse preimage hash (trim 0x) + let hash_str = preimage_hash.trim_start_matches("0x"); + let preimage_hash_parsed: sp_core::H256 = sp_core::H256::from_str(hash_str) + .map_err(|_| QuantusError::Generic("Invalid preimage hash format".to_string()))?; + + // Load wallet keypair + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + // Check if preimage exists and get its length + log_print!("🔍 Checking preimage status..."); + let latest_block_hash = quantus_client.get_latest_block().await?; + let storage_at = quantus_client.client().storage().at(latest_block_hash); + + let preimage_status = storage_at + .fetch( + &quantus_subxt::api::storage() + .preimage() + .request_status_for(preimage_hash_parsed), + ) + .await + .map_err(|e| QuantusError::Generic(format!("Failed to fetch preimage status: {:?}", e)))? + .ok_or_else(|| QuantusError::Generic("Preimage not found on chain".to_string()))?; + + let preimage_len = match preimage_status { + quantus_subxt::api::runtime_types::pallet_preimage::RequestStatus::Unrequested { + ticket: _, + len, + } => len, + quantus_subxt::api::runtime_types::pallet_preimage::RequestStatus::Requested { + maybe_ticket: _, + count: _, + maybe_len, + } => match maybe_len { + Some(len) => len, + None => return Err(QuantusError::Generic("Preimage length not available".to_string())), + }, + }; + + log_print!("✅ Preimage found! Length: {} bytes", preimage_len); + + // Build TechReferenda::submit call using Lookup preimage reference + type ProposalBounded = + quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded< + quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall, + quantus_subxt::api::runtime_types::sp_runtime::traits::BlakeTwo256, + >; + + let preimage_hash_subxt: subxt::utils::H256 = preimage_hash_parsed; + let proposal: ProposalBounded = + ProposalBounded::Lookup { hash: preimage_hash_subxt, len: preimage_len }; + + let raw_origin_root = + quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root; + let origin_caller = + quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin_root); + + let enactment = + quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After( + 0u32, + ); + + log_print!("🔧 Creating TechReferenda::submit call..."); + let submit_call = + quantus_subxt::api::tx() + .tech_referenda() + .submit(origin_caller, proposal, enactment); + + let tx_hash = + submit_transaction(quantus_client, &keypair, submit_call, None, execution_mode).await?; + log_print!( + "✅ {} Runtime upgrade proposal submitted! Hash: {:?}", + "SUCCESS".bright_green().bold(), + tx_hash + ); + + log_print!("💡 Use 'quantus tech-referenda list' to see active proposals"); + Ok(()) +} + +/// Submit a runtime upgrade proposal to Tech Referenda (creates preimage first) +async fn submit_runtime_upgrade_with_preimage( + quantus_client: &crate::chain::client::QuantusClient, + wasm_file: &PathBuf, + from: &str, + password: Option, + password_file: Option, + execution_mode: crate::cli::common::ExecutionMode, +) -> crate::error::Result<()> { + use sp_runtime::traits::{BlakeTwo256, Hash}; + + log_print!("📝 Submitting Runtime Upgrade Proposal to Tech Referenda"); + log_print!(" 📂 WASM file: {}", wasm_file.display().to_string().bright_cyan()); + log_print!(" 🔑 Submitted by: {}", from.bright_yellow()); + + if !wasm_file.exists() { + return Err(QuantusError::Generic(format!("WASM file not found: {}", wasm_file.display()))); + } + + if let Some(ext) = wasm_file.extension() { + if ext != "wasm" { + log_verbose!("⚠️ Warning: File doesn't have .wasm extension"); + } + } + + // Read WASM file + let wasm_code = std::fs::read(wasm_file) + .map_err(|e| QuantusError::Generic(format!("Failed to read WASM file: {}", e)))?; + + log_print!("📊 WASM file size: {} bytes", wasm_code.len()); + + // Load wallet keypair + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + // Build a static payload for System::set_code and encode full call data (pallet + call + args) + let set_code_payload = quantus_subxt::api::tx().system().set_code(wasm_code.clone()); + let metadata = quantus_client.client().metadata(); + let encoded_call = <_ as subxt::tx::Payload>::encode_call_data(&set_code_payload, &metadata) + .map_err(|e| QuantusError::Generic(format!("Failed to encode call data: {:?}", e)))?; + + log_verbose!("📝 Encoded call size: {} bytes", encoded_call.len()); + + // Must match `frame_system::Config::Hashing` (BlakeTwo256) — same key as `pallet_preimage`. + let preimage_hash: sp_core::H256 = BlakeTwo256::hash(&encoded_call); + + log_print!("🔗 Preimage hash: {:?}", preimage_hash); + + // Submit Preimage::note_preimage with bounded bytes + type PreimageBytes = quantus_subxt::api::preimage::calls::types::note_preimage::Bytes; + let bounded_bytes: PreimageBytes = encoded_call.clone(); + + log_print!("📝 Submitting preimage..."); + let note_preimage_tx = quantus_subxt::api::tx().preimage().note_preimage(bounded_bytes); + let preimage_tx_hash = + submit_transaction(quantus_client, &keypair, note_preimage_tx, None, execution_mode) + .await?; + log_print!("✅ Preimage transaction submitted: {:?}", preimage_tx_hash); + + // Wait for preimage transaction confirmation + log_print!("⏳ Waiting for preimage transaction confirmation..."); + + // Build TechReferenda::submit call using Lookup preimage reference + type ProposalBounded = + quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded< + quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall, + quantus_subxt::api::runtime_types::sp_runtime::traits::BlakeTwo256, + >; + + let preimage_hash_subxt: subxt::utils::H256 = preimage_hash; + let proposal: ProposalBounded = + ProposalBounded::Lookup { hash: preimage_hash_subxt, len: encoded_call.len() as u32 }; + + let raw_origin_root = + quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root; + let origin_caller = + quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin_root); + + let enactment = + quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After( + 0u32, + ); + + log_print!("🔧 Creating TechReferenda::submit call..."); + let submit_call = + quantus_subxt::api::tx() + .tech_referenda() + .submit(origin_caller, proposal, enactment); + + let tx_hash = + submit_transaction(quantus_client, &keypair, submit_call, None, execution_mode).await?; + log_print!( + "✅ {} Runtime upgrade proposal submitted! Hash: {:?}", + "SUCCESS".bright_green().bold(), + tx_hash + ); + + log_print!("💡 Use 'quantus tech-referenda list' to see active proposals"); + Ok(()) +} + +/// Submit a Tech Referenda proposal to set the Treasury portion (creates preimage first) +async fn submit_treasury_portion_with_preimage( + quantus_client: &crate::chain::client::QuantusClient, + portion_permill: u32, + from: &str, + password: Option, + password_file: Option, + execution_mode: crate::cli::common::ExecutionMode, +) -> crate::error::Result<()> { + use sp_runtime::traits::{BlakeTwo256, Hash}; + + log_print!("📝 Submitting Treasury Portion Update Proposal to Tech Referenda"); + log_print!(" 📊 New portion (Permill): {}", portion_permill.to_string().bright_cyan()); + log_print!( + " 📊 New portion (%): {}", + format!("{:.2}%", (portion_permill as f64) / 10000.0).bright_cyan() + ); + log_print!(" 🔑 Submitted by: {}", from.bright_yellow()); + + // Load wallet keypair + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + // Build a static payload for TreasuryPallet::set_treasury_portion and encode full call data + // Note: runtime_types::Permill is a tuple struct (u32 parts-per-million). + let portion = + quantus_subxt::api::runtime_types::sp_arithmetic::per_things::Permill(portion_permill); + let set_portion_payload = + quantus_subxt::api::tx().treasury_pallet().set_treasury_portion(portion); + + let metadata = quantus_client.client().metadata(); + let encoded_call = <_ as subxt::tx::Payload>::encode_call_data(&set_portion_payload, &metadata) + .map_err(|e| QuantusError::Generic(format!("Failed to encode call data: {:?}", e)))?; + + log_verbose!("📝 Encoded call size: {} bytes", encoded_call.len()); + + // Must match `frame_system::Config::Hashing` (BlakeTwo256) — same key as `pallet_preimage`. + let preimage_hash: sp_core::H256 = BlakeTwo256::hash(&encoded_call); + log_print!("🔗 Preimage hash: {:?}", preimage_hash); + + // Submit Preimage::note_preimage with bounded bytes + type PreimageBytes = quantus_subxt::api::preimage::calls::types::note_preimage::Bytes; + let bounded_bytes: PreimageBytes = encoded_call.clone(); + + log_print!("📝 Submitting preimage..."); + let note_preimage_tx = quantus_subxt::api::tx().preimage().note_preimage(bounded_bytes); + let preimage_tx_hash = + submit_transaction(quantus_client, &keypair, note_preimage_tx, None, execution_mode) + .await?; + log_print!("✅ Preimage transaction submitted: {:?}", preimage_tx_hash); + + log_print!("⏳ Waiting for preimage transaction confirmation..."); + + // Build TechReferenda::submit call using Lookup preimage reference + type ProposalBounded = + quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded< + quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall, + quantus_subxt::api::runtime_types::sp_runtime::traits::BlakeTwo256, + >; + + let preimage_hash_subxt: subxt::utils::H256 = preimage_hash; + let proposal: ProposalBounded = + ProposalBounded::Lookup { hash: preimage_hash_subxt, len: encoded_call.len() as u32 }; + + let raw_origin_root = + quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root; + let origin_caller = + quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin_root); + + let enactment = + quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After( + 0u32, + ); + + log_print!("🔧 Creating TechReferenda::submit call..."); + let submit_call = + quantus_subxt::api::tx() + .tech_referenda() + .submit(origin_caller, proposal, enactment); + + let tx_hash = + submit_transaction(quantus_client, &keypair, submit_call, None, execution_mode).await?; + log_print!( + "✅ {} Treasury portion proposal submitted! Hash: {:?}", + "SUCCESS".bright_green().bold(), + tx_hash + ); + + log_print!("💡 Use 'quantus tech-referenda list' to see active proposals"); + Ok(()) +} + +/// List recent Tech Referenda proposals +async fn list_proposals( + quantus_client: &crate::chain::client::QuantusClient, +) -> crate::error::Result<()> { + log_print!("📜 Active Tech Referenda Proposals"); + log_print!(""); + + let addr = quantus_subxt::api::storage().tech_referenda().referendum_count(); + + // Get the latest block hash to read from the latest state (not finalized) + let latest_block_hash = quantus_client.get_latest_block().await?; + let storage_at = quantus_client.client().storage().at(latest_block_hash); + + let count = storage_at.fetch(&addr).await?; + + if let Some(total) = count { + log_print!("📊 Total referenda created: {}", total); + if total == 0 { + log_print!("📭 No active proposals found"); + return Ok(()); + } + log_print!("🔍 Fetching recent referenda..."); + for i in (0..total).rev().take(10) { + get_proposal_status(quantus_client, i).await?; + log_print!("----------------------------------------"); + } + } else { + log_print!("📭 No referenda found - Tech Referenda may be empty"); + } + + Ok(()) +} + +/// Get details of a specific Tech Referendum +async fn get_proposal_details( + quantus_client: &crate::chain::client::QuantusClient, + index: u32, +) -> crate::error::Result<()> { + log_print!("📄 Tech Referendum #{} Details", index); + log_print!(""); + + let addr = quantus_subxt::api::storage().tech_referenda().referendum_info_for(index); + + // Get the latest block hash to read from the latest state (not finalized) + let latest_block_hash = quantus_client.get_latest_block().await?; + let storage_at = quantus_client.client().storage().at(latest_block_hash); + + let info = storage_at.fetch(&addr).await?; + + if let Some(referendum_info) = info { + log_print!("📋 Referendum Information (raw):"); + log_print!("{:#?}", referendum_info); + } else { + log_print!("📭 Referendum #{} not found", index); + } + Ok(()) +} + +/// Get the status of a Tech Referendum +async fn get_proposal_status( + quantus_client: &crate::chain::client::QuantusClient, + index: u32, +) -> crate::error::Result<()> { + use quantus_subxt::api::runtime_types::pallet_referenda::types::ReferendumInfo; + + log_verbose!("📊 Fetching status for Tech Referendum #{}...", index); + + let addr = quantus_subxt::api::storage().tech_referenda().referendum_info_for(index); + + // Get the latest block hash to read from the latest state (not finalized) + let latest_block_hash = quantus_client.get_latest_block().await?; + let storage_at = quantus_client.client().storage().at(latest_block_hash); + + let info_res = storage_at.fetch(&addr).await; + + match info_res { + Ok(Some(info)) => { + log_print!("📊 Status for Referendum #{}", index.to_string().bright_yellow()); + match info { + ReferendumInfo::Ongoing(status) => { + log_print!(" - Status: {}", "Ongoing".bright_green()); + log_print!(" - Track: {}", status.track); + log_print!(" - Submitted at: block {}", status.submitted); + log_print!( + " - Tally: Ayes: {}, Nays: {}", + status.tally.ayes, + status.tally.nays + ); + log_verbose!(" - Full status: {:#?}", status); + }, + ReferendumInfo::Approved(submitted, ..) => { + log_print!(" - Status: {}", "Approved".green()); + log_print!(" - Submitted at block: {}", submitted); + }, + ReferendumInfo::Rejected(submitted, ..) => { + log_print!(" - Status: {}", "Rejected".red()); + log_print!(" - Submitted at block: {}", submitted); + }, + ReferendumInfo::Cancelled(submitted, ..) => { + log_print!(" - Status: {}", "Cancelled".yellow()); + log_print!(" - Submitted at block: {}", submitted); + }, + ReferendumInfo::TimedOut(submitted, ..) => { + log_print!(" - Status: {}", "TimedOut".dimmed()); + log_print!(" - Submitted at block: {}", submitted); + }, + ReferendumInfo::Killed(submitted) => { + log_print!(" - Status: {}", "Killed".red().bold()); + log_print!(" - Killed at block: {}", submitted); + }, + } + }, + Ok(None) => log_print!("📭 Referendum #{} not found", index), + Err(e) => log_error!("❌ Failed to fetch referendum #{}: {:?}", index, e), + } + + Ok(()) +} + +/// Place a decision deposit for a Tech Referendum +async fn place_decision_deposit( + quantus_client: &crate::chain::client::QuantusClient, + index: u32, + from: &str, + password: Option, + password_file: Option, + execution_mode: crate::cli::common::ExecutionMode, +) -> crate::error::Result<()> { + log_print!("📋 Placing decision deposit for Tech Referendum #{}", index); + log_print!(" 🔑 Placed by: {}", from.bright_yellow()); + + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + let deposit_call = quantus_subxt::api::tx().tech_referenda().place_decision_deposit(index); + let tx_hash = + submit_transaction(quantus_client, &keypair, deposit_call, None, execution_mode).await?; + log_success!("✅ Decision deposit placed! Hash: {:?}", tx_hash.to_string().bright_yellow()); + Ok(()) +} + +/// Get Tech Referenda configuration +async fn get_config( + quantus_client: &crate::chain::client::QuantusClient, +) -> crate::error::Result<()> { + log_print!("⚙️ Tech Referenda Configuration"); + log_print!(""); + + let constants = quantus_client.client().constants(); + let tracks_addr = quantus_subxt::api::constants().tech_referenda().tracks(); + + match constants.at(&tracks_addr) { + Ok(tracks) => { + log_print!("{}", "📊 Track Configuration:".bold()); + for (id, info) in tracks.iter() { + log_print!(" ------------------------------------"); + log_print!( + " • {} #{}: {}", + "Track".bold(), + id, + info.name.to_string().bright_cyan() + ); + log_print!(" • Max Deciding: {}", info.max_deciding); + log_print!(" • Decision Deposit: {}", info.decision_deposit); + log_print!(" • Prepare Period: {} blocks", info.prepare_period); + log_print!(" • Decision Period: {} blocks", info.decision_period); + log_print!(" • Confirm Period: {} blocks", info.confirm_period); + log_print!(" • Min Enactment Period: {} blocks", info.min_enactment_period); + } + log_print!(" ------------------------------------"); + }, + Err(e) => { + log_error!("❌ Failed to decode Tracks constant: {:?}", e); + log_print!("💡 It's possible the Tracks constant is not in the expected format."); + }, + } + + Ok(()) +} + +/// Refund submission deposit for a completed Tech Referendum +async fn refund_submission_deposit( + quantus_client: &crate::chain::client::QuantusClient, + index: u32, + from: &str, + password: Option, + password_file: Option, + execution_mode: crate::cli::common::ExecutionMode, +) -> crate::error::Result<()> { + log_print!("💰 Refunding submission deposit for Tech Referendum #{}", index); + log_print!(" 🔑 Refund to: {}", from.bright_yellow()); + + // Load wallet keypair + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + // Create refund_submission_deposit call for TechReferenda instance + let refund_call = quantus_subxt::api::tx().tech_referenda().refund_submission_deposit(index); + + let tx_hash = + submit_transaction(quantus_client, &keypair, refund_call, None, execution_mode).await?; + log_print!( + "✅ {} Refund transaction submitted! Hash: {:?}", + "SUCCESS".bright_green().bold(), + tx_hash + ); + + log_success!("🎉 {} Submission deposit refunded!", "FINISHED".bright_green().bold()); + log_print!("💡 Check your balance to confirm the refund"); + Ok(()) +} + +/// Refund decision deposit for a completed Tech Referendum +async fn refund_decision_deposit( + quantus_client: &crate::chain::client::QuantusClient, + index: u32, + from: &str, + password: Option, + password_file: Option, + execution_mode: crate::cli::common::ExecutionMode, +) -> crate::error::Result<()> { + log_print!("💰 Refunding decision deposit for Tech Referendum #{}", index); + log_print!(" 🔑 Refund to: {}", from.bright_yellow()); + + // Load wallet keypair + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + // Create refund_decision_deposit call for TechReferenda instance + let refund_call = quantus_subxt::api::tx().tech_referenda().refund_decision_deposit(index); + + let tx_hash = + submit_transaction(quantus_client, &keypair, refund_call, None, execution_mode).await?; + log_print!( + "✅ {} Refund transaction submitted! Hash: {:?}", + "SUCCESS".bright_green().bold(), + tx_hash + ); + + log_success!("🎉 {} Decision deposit refunded!", "FINISHED".bright_green().bold()); + log_print!("💡 Check your balance to confirm the refund"); + Ok(()) +} diff --git a/src/cli/wallet.rs b/src/cli/wallet.rs index ea636a9..703b52d 100644 --- a/src/cli/wallet.rs +++ b/src/cli/wallet.rs @@ -240,28 +240,43 @@ async fn fetch_pending_transfers_for_guardian( ) -> crate::error::Result<(u32, Vec<(String, u32)>)> { let latest = quantus_client.get_latest_block().await?; let storage = quantus_client.client().storage().at(latest); + let pending_iter = + quantus_subxt::api::storage().reversible_transfers().pending_transfers_iter(); + + let entrusted_ids: Vec<[u8; 32]> = entrusted_ss58 + .iter() + .map(|s| { + let id = SpAccountId32::from_ss58check(s).map_err(|e| { + QuantusError::Generic(format!("Invalid SS58 for pending lookup: {e:?}")) + })?; + Ok::<_, crate::error::QuantusError>(*id.as_ref()) + }) + .collect::, _>>()?; + + let mut counts: std::collections::HashMap<[u8; 32], u32> = + entrusted_ids.iter().map(|id| (*id, 0u32)).collect(); + + let mut iter = storage + .iter(pending_iter) + .await + .map_err(|e| QuantusError::NetworkError(format!("Pending transfers iter: {e:?}")))?; + + while let Some(result) = iter.next().await { + if let Ok(entry) = result { + let from_bytes: [u8; 32] = *entry.value.from.as_ref(); + if let Some(c) = counts.get_mut(&from_bytes) { + *c += 1; + } + } + } + let mut total = 0u32; let mut per_account = Vec::with_capacity(entrusted_ss58.len()); - - for ss58 in entrusted_ss58 { - let account_id_sp = SpAccountId32::from_ss58check(ss58).map_err(|e| { - QuantusError::Generic(format!("Invalid SS58 for pending lookup: {e:?}")) - })?; - let account_bytes: [u8; 32] = *account_id_sp.as_ref(); - let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_bytes); - - let addr = quantus_subxt::api::storage() - .reversible_transfers() - .pending_transfers_by_sender(account_id); - let value = storage.fetch(&addr).await.map_err(|e| { - QuantusError::NetworkError(format!("Fetch pending_transfers_by_sender: {e:?}")) - })?; - - let count = value.map(|bounded| bounded.0.len() as u32).unwrap_or(0); + for (ss58, id) in entrusted_ss58.iter().zip(entrusted_ids.iter()) { + let count = *counts.get(id).unwrap_or(&0); total += count; per_account.push((ss58.clone(), count)); } - Ok((total, per_account)) } diff --git a/src/config/mod.rs b/src/config/mod.rs index c6d7ac8..a5a4ff1 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -3,7 +3,7 @@ //! This module handles runtime compatibility information. /// List of runtime spec versions that this CLI is compatible with -pub const COMPATIBLE_RUNTIME_VERSIONS: &[u32] = &[115, 116]; +pub const COMPATIBLE_RUNTIME_VERSIONS: &[u32] = &[123]; /// Check if a runtime version is compatible with this CLI pub fn is_runtime_compatible(spec_version: u32) -> bool { diff --git a/src/quantus_metadata.scale b/src/quantus_metadata.scale index fb8eb49..74002ed 100644 Binary files a/src/quantus_metadata.scale and b/src/quantus_metadata.scale differ