diff --git a/Cargo.lock b/Cargo.lock index e73b717..dac1782 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3582,9 +3582,9 @@ dependencies = [ [[package]] name = "qp-dilithium-crypto" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c3fbdb9557a22cb876fc17c483d090e9556cbfdfc52beb0696143d4313d9" +checksum = "476cab75a58360464ef58fe3bfaa01b4a450a0895d32f028a1e17a0b9031657c" dependencies = [ "log", "parity-scale-codec", @@ -3754,17 +3754,15 @@ dependencies = [ [[package]] name = "qp-rusty-crystals-hdwallet" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91781bc0b96238c7e038a2d3157410388cb0458b05d42483851e79684e92f1a8" +checksum = "113fdac36387a857ab4e09fdc050098bc7b55bcd6e5a8ab755e7f44ef10741e6" dependencies = [ "bip39", - "bs58", "getrandom 0.2.17", "hex", "hex-literal", "hmac 0.12.1", - "k256", "qp-poseidon-core", "qp-rusty-crystals-dilithium", "serde", diff --git a/Cargo.toml b/Cargo.toml index c08da6e..af5f24a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,9 +50,9 @@ aes-gcm = "0.10" # AES-256-GCM (quantum-safe with 256-bit keys) # Quantus crypto dependencies (aligned with chain) qp-rusty-crystals-dilithium = { version = "2.1.0" } -qp-rusty-crystals-hdwallet = { version = "2.0.0" } +qp-rusty-crystals-hdwallet = { version = "2.1.0" } # Chain primitive (same branch as node); Cargo finds crate by name in repo -qp-dilithium-crypto = { version = "0.2.2", features = ["serde"] } +qp-dilithium-crypto = { version = "0.2.3", features = ["serde"] } qp-poseidon = { version = "1.1.0" } # HTTP client for Subsquid queries diff --git a/src/chain/quantus_subxt.rs b/src/chain/quantus_subxt.rs index a78d52d..12c1c58 100644 --- a/src/chain/quantus_subxt.rs +++ b/src/chain/quantus_subxt.rs @@ -782,26 +782,6 @@ pub mod api { use super::{root_mod, runtime_types}; pub struct QPoWApi; impl QPoWApi { - #[doc = " calculate hash of header with nonce using Bitcoin-style double Poseidon2"] - pub fn get_nonce_distance( - &self, - block_hash: types::get_nonce_distance::BlockHash, - nonce: types::get_nonce_distance::Nonce, - ) -> ::subxt::ext::subxt_core::runtime_api::payload::StaticPayload< - types::GetNonceDistance, - types::get_nonce_distance::output::Output, - > { - ::subxt::ext::subxt_core::runtime_api::payload::StaticPayload::new_static( - "QPoWApi", - "get_nonce_distance", - types::GetNonceDistance { block_hash, nonce }, - [ - 129u8, 114u8, 220u8, 23u8, 229u8, 124u8, 105u8, 65u8, 77u8, 91u8, 9u8, - 2u8, 2u8, 177u8, 124u8, 108u8, 143u8, 100u8, 174u8, 61u8, 29u8, 55u8, - 166u8, 162u8, 16u8, 61u8, 75u8, 213u8, 182u8, 125u8, 7u8, 120u8, - ], - ) - } #[doc = " Get the max possible reorg depth"] pub fn get_max_reorg_depth( &self, @@ -859,24 +839,6 @@ pub mod api { ], ) } - #[doc = " Get total work"] - pub fn get_total_work( - &self, - ) -> ::subxt::ext::subxt_core::runtime_api::payload::StaticPayload< - types::GetTotalWork, - types::get_total_work::output::Output, - > { - ::subxt::ext::subxt_core::runtime_api::payload::StaticPayload::new_static( - "QPoWApi", - "get_total_work", - types::GetTotalWork {}, - [ - 1u8, 91u8, 59u8, 140u8, 203u8, 250u8, 8u8, 65u8, 208u8, 35u8, 187u8, - 190u8, 255u8, 125u8, 190u8, 111u8, 216u8, 168u8, 83u8, 32u8, 37u8, - 203u8, 102u8, 226u8, 88u8, 207u8, 253u8, 59u8, 86u8, 72u8, 30u8, 171u8, - ], - ) - } #[doc = " Get block ema"] pub fn get_block_time_ema( &self, @@ -986,33 +948,28 @@ pub mod api { ], ) } + pub fn verify_and_get_achieved_difficulty( + &self, + block_hash: types::verify_and_get_achieved_difficulty::BlockHash, + nonce: types::verify_and_get_achieved_difficulty::Nonce, + ) -> ::subxt::ext::subxt_core::runtime_api::payload::StaticPayload< + types::VerifyAndGetAchievedDifficulty, + types::verify_and_get_achieved_difficulty::output::Output, + > { + ::subxt::ext::subxt_core::runtime_api::payload::StaticPayload::new_static( + "QPoWApi", + "verify_and_get_achieved_difficulty", + types::VerifyAndGetAchievedDifficulty { block_hash, nonce }, + [ + 200u8, 115u8, 180u8, 31u8, 14u8, 76u8, 235u8, 83u8, 133u8, 52u8, 192u8, + 59u8, 52u8, 48u8, 224u8, 34u8, 33u8, 53u8, 218u8, 132u8, 191u8, 10u8, + 17u8, 243u8, 227u8, 162u8, 171u8, 240u8, 49u8, 217u8, 172u8, 240u8, + ], + ) + } } pub mod types { use super::runtime_types; - pub mod get_nonce_distance { - use super::runtime_types; - pub type BlockHash = [::core::primitive::u8; 32usize]; - pub type Nonce = [::core::primitive::u8; 64usize]; - pub mod output { - use super::runtime_types; - pub type Output = runtime_types::primitive_types::U512; - } - } - #[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 GetNonceDistance { - pub block_hash: get_nonce_distance::BlockHash, - pub nonce: get_nonce_distance::Nonce, - } pub mod get_max_reorg_depth { use super::runtime_types; pub mod output { @@ -1070,25 +1027,6 @@ pub mod api { crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" )] pub struct GetDifficulty {} - pub mod get_total_work { - use super::runtime_types; - pub mod output { - use super::runtime_types; - pub type Output = runtime_types::primitive_types::U512; - } - } - #[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 GetTotalWork {} pub mod get_block_time_ema { use super::runtime_types; pub mod output { @@ -1213,6 +1151,31 @@ pub mod api { pub block_hash: verify_nonce_local_mining::BlockHash, pub nonce: verify_nonce_local_mining::Nonce, } + pub mod verify_and_get_achieved_difficulty { + use super::runtime_types; + pub type BlockHash = [::core::primitive::u8; 32usize]; + pub type Nonce = [::core::primitive::u8; 64usize]; + pub mod output { + use super::runtime_types; + pub type Output = + (::core::primitive::bool, runtime_types::primitive_types::U512); + } + } + #[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 VerifyAndGetAchievedDifficulty { + pub block_hash: verify_and_get_achieved_difficulty::BlockHash, + pub nonce: verify_and_get_achieved_difficulty::Nonce, + } } } pub mod account_nonce_api { @@ -1469,9 +1432,9 @@ pub mod api { "query_call_info", types::QueryCallInfo { call, len }, [ - 198u8, 65u8, 204u8, 71u8, 28u8, 170u8, 188u8, 123u8, 4u8, 169u8, 112u8, - 140u8, 13u8, 71u8, 97u8, 37u8, 125u8, 168u8, 73u8, 123u8, 116u8, 53u8, - 236u8, 206u8, 173u8, 112u8, 141u8, 87u8, 177u8, 37u8, 238u8, 57u8, + 59u8, 44u8, 99u8, 66u8, 11u8, 94u8, 211u8, 6u8, 243u8, 46u8, 231u8, + 78u8, 147u8, 43u8, 186u8, 119u8, 84u8, 5u8, 35u8, 154u8, 58u8, 175u8, + 180u8, 197u8, 176u8, 160u8, 53u8, 155u8, 145u8, 42u8, 23u8, 102u8, ], ) } @@ -1489,10 +1452,9 @@ pub mod api { "query_call_fee_details", types::QueryCallFeeDetails { call, len }, [ - 181u8, 192u8, 142u8, 93u8, 47u8, 223u8, 252u8, 28u8, 253u8, 247u8, - 255u8, 49u8, 206u8, 94u8, 24u8, 227u8, 178u8, 147u8, 3u8, 138u8, 204u8, - 67u8, 221u8, 34u8, 21u8, 249u8, 185u8, 109u8, 195u8, 213u8, 165u8, - 162u8, + 135u8, 58u8, 229u8, 112u8, 19u8, 128u8, 2u8, 114u8, 132u8, 7u8, 98u8, + 170u8, 14u8, 148u8, 33u8, 57u8, 125u8, 107u8, 77u8, 66u8, 33u8, 10u8, + 65u8, 28u8, 121u8, 233u8, 63u8, 109u8, 189u8, 9u8, 189u8, 122u8, ], ) } @@ -1995,9 +1957,9 @@ pub mod api { .hash(); runtime_metadata_hash == [ - 241u8, 104u8, 181u8, 101u8, 78u8, 126u8, 146u8, 140u8, 82u8, 117u8, 79u8, 128u8, - 3u8, 33u8, 86u8, 223u8, 167u8, 80u8, 94u8, 196u8, 191u8, 207u8, 240u8, 239u8, - 186u8, 203u8, 0u8, 147u8, 140u8, 8u8, 140u8, 64u8, + 235u8, 241u8, 231u8, 143u8, 238u8, 18u8, 134u8, 210u8, 142u8, 123u8, 23u8, 238u8, + 7u8, 116u8, 199u8, 36u8, 101u8, 103u8, 107u8, 11u8, 141u8, 9u8, 176u8, 251u8, + 185u8, 220u8, 173u8, 198u8, 201u8, 167u8, 78u8, 188u8, ] } pub mod system { @@ -3096,10 +3058,9 @@ pub mod api { "Events", (), [ - 129u8, 117u8, 78u8, 200u8, 45u8, 177u8, 108u8, 250u8, 68u8, 196u8, - 35u8, 140u8, 114u8, 113u8, 44u8, 193u8, 216u8, 27u8, 164u8, 117u8, - 173u8, 170u8, 119u8, 26u8, 55u8, 150u8, 155u8, 226u8, 61u8, 102u8, - 162u8, 127u8, + 16u8, 202u8, 95u8, 126u8, 59u8, 71u8, 99u8, 58u8, 52u8, 83u8, 147u8, + 29u8, 27u8, 91u8, 11u8, 178u8, 100u8, 183u8, 107u8, 163u8, 243u8, + 160u8, 155u8, 46u8, 55u8, 109u8, 210u8, 242u8, 84u8, 21u8, 0u8, 174u8, ], ) } @@ -5580,9 +5541,9 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 24u8, 141u8, 2u8, 72u8, 96u8, 108u8, 188u8, 185u8, 194u8, 189u8, 85u8, - 38u8, 202u8, 47u8, 99u8, 163u8, 41u8, 150u8, 193u8, 31u8, 229u8, 218u8, - 124u8, 107u8, 188u8, 18u8, 24u8, 48u8, 88u8, 224u8, 139u8, 146u8, + 224u8, 201u8, 98u8, 222u8, 12u8, 81u8, 22u8, 130u8, 86u8, 67u8, 141u8, + 83u8, 170u8, 107u8, 209u8, 158u8, 237u8, 186u8, 132u8, 61u8, 103u8, + 153u8, 37u8, 72u8, 59u8, 69u8, 181u8, 245u8, 166u8, 244u8, 62u8, 119u8, ], ) } @@ -5605,9 +5566,9 @@ pub mod api { weight, }, [ - 46u8, 22u8, 24u8, 76u8, 164u8, 139u8, 82u8, 44u8, 160u8, 220u8, 119u8, - 122u8, 176u8, 4u8, 184u8, 55u8, 219u8, 75u8, 226u8, 43u8, 51u8, 153u8, - 189u8, 146u8, 245u8, 96u8, 161u8, 218u8, 243u8, 35u8, 70u8, 249u8, + 171u8, 105u8, 36u8, 170u8, 167u8, 130u8, 207u8, 182u8, 35u8, 146u8, + 100u8, 18u8, 244u8, 232u8, 92u8, 75u8, 128u8, 183u8, 56u8, 211u8, 8u8, + 44u8, 56u8, 99u8, 239u8, 143u8, 82u8, 65u8, 6u8, 26u8, 231u8, 216u8, ], ) } @@ -5645,10 +5606,10 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 58u8, 78u8, 164u8, 136u8, 199u8, 77u8, 215u8, 140u8, 160u8, 177u8, - 127u8, 208u8, 164u8, 155u8, 35u8, 130u8, 92u8, 143u8, 46u8, 82u8, - 232u8, 4u8, 61u8, 213u8, 74u8, 103u8, 128u8, 81u8, 26u8, 137u8, 39u8, - 71u8, + 225u8, 229u8, 154u8, 137u8, 127u8, 113u8, 207u8, 149u8, 115u8, 222u8, + 119u8, 97u8, 241u8, 115u8, 146u8, 82u8, 97u8, 105u8, 97u8, 45u8, 237u8, + 82u8, 84u8, 145u8, 235u8, 153u8, 200u8, 108u8, 222u8, 27u8, 149u8, + 202u8, ], ) } @@ -5853,10 +5814,6 @@ pub mod api { use super::runtime_types; pub type CurrentDifficulty = runtime_types::primitive_types::U512; } - pub mod total_work { - use super::runtime_types; - pub type TotalWork = runtime_types::primitive_types::U512; - } pub mod block_time_ema { use super::runtime_types; pub type BlockTimeEma = ::core::primitive::u64; @@ -5925,26 +5882,6 @@ pub mod api { ], ) } - pub fn total_work( - &self, - ) -> ::subxt::ext::subxt_core::storage::address::StaticAddress< - (), - types::total_work::TotalWork, - ::subxt::ext::subxt_core::utils::Yes, - ::subxt::ext::subxt_core::utils::Yes, - (), - > { - ::subxt::ext::subxt_core::storage::address::StaticAddress::new_static( - "QPoW", - "TotalWork", - (), - [ - 184u8, 29u8, 54u8, 146u8, 220u8, 155u8, 103u8, 67u8, 21u8, 188u8, 53u8, - 160u8, 171u8, 107u8, 52u8, 211u8, 251u8, 52u8, 192u8, 227u8, 150u8, - 156u8, 172u8, 1u8, 233u8, 37u8, 49u8, 13u8, 213u8, 104u8, 10u8, 134u8, - ], - ) - } pub fn block_time_ema( &self, ) -> ::subxt::ext::subxt_core::storage::address::StaticAddress< @@ -6753,23 +6690,14 @@ pub mod api { #[encode_as_type( crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" )] - #[doc = "Anonymously schedule a task."] pub struct Schedule { pub when: schedule::When, - pub maybe_periodic: schedule::MaybePeriodic, pub priority: schedule::Priority, pub call: ::subxt::ext::subxt_core::alloc::boxed::Box, } pub mod schedule { use super::runtime_types; pub type When = ::core::primitive::u32; - pub type MaybePeriodic = ::core::option::Option<( - runtime_types::qp_scheduler::BlockNumberOrTimestamp< - ::core::primitive::u32, - ::core::primitive::u64, - >, - ::core::primitive::u32, - )>; pub type Priority = ::core::primitive::u8; pub type Call = runtime_types::quantus_runtime::RuntimeCall; } @@ -6816,11 +6744,9 @@ pub mod api { #[encode_as_type( crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" )] - #[doc = "Schedule a named task."] pub struct ScheduleNamed { pub id: schedule_named::Id, pub when: schedule_named::When, - pub maybe_periodic: schedule_named::MaybePeriodic, pub priority: schedule_named::Priority, pub call: ::subxt::ext::subxt_core::alloc::boxed::Box, } @@ -6828,13 +6754,6 @@ pub mod api { use super::runtime_types; pub type Id = [::core::primitive::u8; 32usize]; pub type When = ::core::primitive::u32; - pub type MaybePeriodic = ::core::option::Option<( - runtime_types::qp_scheduler::BlockNumberOrTimestamp< - ::core::primitive::u32, - ::core::primitive::u64, - >, - ::core::primitive::u32, - )>; pub type Priority = ::core::primitive::u8; pub type Call = runtime_types::quantus_runtime::RuntimeCall; } @@ -6876,10 +6795,8 @@ pub mod api { #[encode_as_type( crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" )] - #[doc = "Anonymously schedule a task after a delay."] pub struct ScheduleAfter { pub after: schedule_after::After, - pub maybe_periodic: schedule_after::MaybePeriodic, pub priority: schedule_after::Priority, pub call: ::subxt::ext::subxt_core::alloc::boxed::Box, } @@ -6889,13 +6806,6 @@ pub mod api { ::core::primitive::u32, ::core::primitive::u64, >; - pub type MaybePeriodic = ::core::option::Option<( - runtime_types::qp_scheduler::BlockNumberOrTimestamp< - ::core::primitive::u32, - ::core::primitive::u64, - >, - ::core::primitive::u32, - )>; pub type Priority = ::core::primitive::u8; pub type Call = runtime_types::quantus_runtime::RuntimeCall; } @@ -6914,11 +6824,9 @@ pub mod api { #[encode_as_type( crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" )] - #[doc = "Schedule a named task after a delay."] pub struct ScheduleNamedAfter { pub id: schedule_named_after::Id, pub after: schedule_named_after::After, - pub maybe_periodic: schedule_named_after::MaybePeriodic, pub priority: schedule_named_after::Priority, pub call: ::subxt::ext::subxt_core::alloc::boxed::Box, @@ -6930,13 +6838,6 @@ pub mod api { ::core::primitive::u32, ::core::primitive::u64, >; - pub type MaybePeriodic = ::core::option::Option<( - runtime_types::qp_scheduler::BlockNumberOrTimestamp< - ::core::primitive::u32, - ::core::primitive::u64, - >, - ::core::primitive::u32, - )>; pub type Priority = ::core::primitive::u8; pub type Call = runtime_types::quantus_runtime::RuntimeCall; } @@ -6960,10 +6861,9 @@ pub mod api { #[doc = "succeeds."] #[doc = ""] #[doc = "Tasks which need to be scheduled for a retry are still subject to weight metering and"] - #[doc = "agenda space, same as a regular task. If a periodic task fails, it will be scheduled"] - #[doc = "normally while the task is retrying."] + #[doc = "agenda space, same as a regular task."] #[doc = ""] - #[doc = "Tasks scheduled as a result of a retry for a periodic task are unnamed, non-periodic"] + #[doc = "Tasks scheduled as a result of a retry are unnamed"] #[doc = "clones of the original task. Their retry configuration will be derived from the"] #[doc = "original task's configuration, but will have a lower value for `remaining` than the"] #[doc = "original `total_retries`."] @@ -7007,10 +6907,9 @@ pub mod api { #[doc = "it succeeds."] #[doc = ""] #[doc = "Tasks which need to be scheduled for a retry are still subject to weight metering and"] - #[doc = "agenda space, same as a regular task. If a periodic task fails, it will be scheduled"] - #[doc = "normally while the task is retrying."] + #[doc = "agenda space, same as a regular task."] #[doc = ""] - #[doc = "Tasks scheduled as a result of a retry for a periodic task are unnamed, non-periodic"] + #[doc = "Tasks scheduled as a result of a retry are unnamed"] #[doc = "clones of the original task. Their retry configuration will be derived from the"] #[doc = "original task's configuration, but will have a lower value for `remaining` than the"] #[doc = "original `total_retries`."] @@ -7087,11 +6986,9 @@ pub mod api { } pub struct TransactionApi; impl TransactionApi { - #[doc = "Anonymously schedule a task."] pub fn schedule( &self, when: types::schedule::When, - maybe_periodic: types::schedule::MaybePeriodic, priority: types::schedule::Priority, call: types::schedule::Call, ) -> ::subxt::ext::subxt_core::tx::payload::StaticPayload { @@ -7100,14 +6997,13 @@ pub mod api { "schedule", types::Schedule { when, - maybe_periodic, priority, call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 232u8, 174u8, 107u8, 181u8, 152u8, 133u8, 51u8, 35u8, 111u8, 19u8, - 66u8, 80u8, 0u8, 148u8, 65u8, 239u8, 162u8, 250u8, 28u8, 124u8, 20u8, - 250u8, 177u8, 112u8, 35u8, 19u8, 32u8, 9u8, 166u8, 48u8, 69u8, 206u8, + 59u8, 142u8, 42u8, 82u8, 31u8, 67u8, 95u8, 118u8, 24u8, 55u8, 82u8, + 141u8, 2u8, 133u8, 213u8, 81u8, 84u8, 109u8, 6u8, 239u8, 46u8, 233u8, + 125u8, 136u8, 15u8, 160u8, 240u8, 29u8, 238u8, 12u8, 114u8, 8u8, ], ) } @@ -7128,12 +7024,10 @@ pub mod api { ], ) } - #[doc = "Schedule a named task."] pub fn schedule_named( &self, id: types::schedule_named::Id, when: types::schedule_named::When, - maybe_periodic: types::schedule_named::MaybePeriodic, priority: types::schedule_named::Priority, call: types::schedule_named::Call, ) -> ::subxt::ext::subxt_core::tx::payload::StaticPayload @@ -7144,14 +7038,13 @@ pub mod api { types::ScheduleNamed { id, when, - maybe_periodic, priority, call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 13u8, 254u8, 63u8, 126u8, 76u8, 11u8, 93u8, 141u8, 57u8, 125u8, 141u8, - 225u8, 225u8, 5u8, 181u8, 28u8, 1u8, 94u8, 156u8, 234u8, 29u8, 214u8, - 124u8, 169u8, 128u8, 135u8, 70u8, 217u8, 50u8, 249u8, 178u8, 84u8, + 132u8, 98u8, 238u8, 26u8, 202u8, 171u8, 103u8, 75u8, 126u8, 49u8, 0u8, + 201u8, 239u8, 60u8, 78u8, 218u8, 23u8, 147u8, 136u8, 131u8, 252u8, + 205u8, 89u8, 30u8, 83u8, 236u8, 25u8, 10u8, 149u8, 147u8, 56u8, 207u8, ], ) } @@ -7171,11 +7064,9 @@ pub mod api { ], ) } - #[doc = "Anonymously schedule a task after a delay."] pub fn schedule_after( &self, after: types::schedule_after::After, - maybe_periodic: types::schedule_after::MaybePeriodic, priority: types::schedule_after::Priority, call: types::schedule_after::Call, ) -> ::subxt::ext::subxt_core::tx::payload::StaticPayload @@ -7185,23 +7076,21 @@ pub mod api { "schedule_after", types::ScheduleAfter { after, - maybe_periodic, priority, call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 162u8, 125u8, 219u8, 73u8, 158u8, 117u8, 43u8, 87u8, 203u8, 66u8, 70u8, - 2u8, 216u8, 81u8, 36u8, 114u8, 236u8, 161u8, 11u8, 154u8, 173u8, 97u8, - 75u8, 215u8, 178u8, 154u8, 37u8, 132u8, 100u8, 230u8, 188u8, 208u8, + 123u8, 115u8, 44u8, 60u8, 131u8, 182u8, 245u8, 36u8, 29u8, 238u8, + 226u8, 239u8, 76u8, 4u8, 170u8, 52u8, 245u8, 83u8, 217u8, 193u8, 100u8, + 58u8, 144u8, 126u8, 232u8, 17u8, 219u8, 120u8, 99u8, 154u8, 255u8, + 116u8, ], ) } - #[doc = "Schedule a named task after a delay."] pub fn schedule_named_after( &self, id: types::schedule_named_after::Id, after: types::schedule_named_after::After, - maybe_periodic: types::schedule_named_after::MaybePeriodic, priority: types::schedule_named_after::Priority, call: types::schedule_named_after::Call, ) -> ::subxt::ext::subxt_core::tx::payload::StaticPayload @@ -7212,15 +7101,13 @@ pub mod api { types::ScheduleNamedAfter { id, after, - maybe_periodic, priority, call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 139u8, 221u8, 152u8, 183u8, 20u8, 207u8, 141u8, 46u8, 8u8, 172u8, - 154u8, 85u8, 106u8, 139u8, 217u8, 12u8, 190u8, 12u8, 4u8, 225u8, 103u8, - 127u8, 84u8, 158u8, 131u8, 218u8, 175u8, 114u8, 135u8, 111u8, 154u8, - 173u8, + 51u8, 123u8, 141u8, 168u8, 243u8, 27u8, 146u8, 86u8, 92u8, 47u8, 205u8, + 24u8, 66u8, 124u8, 97u8, 95u8, 171u8, 35u8, 140u8, 188u8, 58u8, 138u8, + 115u8, 200u8, 132u8, 253u8, 68u8, 16u8, 112u8, 132u8, 121u8, 68u8, ], ) } @@ -7229,10 +7116,9 @@ pub mod api { #[doc = "succeeds."] #[doc = ""] #[doc = "Tasks which need to be scheduled for a retry are still subject to weight metering and"] - #[doc = "agenda space, same as a regular task. If a periodic task fails, it will be scheduled"] - #[doc = "normally while the task is retrying."] + #[doc = "agenda space, same as a regular task."] #[doc = ""] - #[doc = "Tasks scheduled as a result of a retry for a periodic task are unnamed, non-periodic"] + #[doc = "Tasks scheduled as a result of a retry are unnamed"] #[doc = "clones of the original task. Their retry configuration will be derived from the"] #[doc = "original task's configuration, but will have a lower value for `remaining` than the"] #[doc = "original `total_retries`."] @@ -7258,10 +7144,9 @@ pub mod api { #[doc = "it succeeds."] #[doc = ""] #[doc = "Tasks which need to be scheduled for a retry are still subject to weight metering and"] - #[doc = "agenda space, same as a regular task. If a periodic task fails, it will be scheduled"] - #[doc = "normally while the task is retrying."] + #[doc = "agenda space, same as a regular task."] #[doc = ""] - #[doc = "Tasks scheduled as a result of a retry for a periodic task are unnamed, non-periodic"] + #[doc = "Tasks scheduled as a result of a retry are unnamed"] #[doc = "clones of the original task. Their retry configuration will be derived from the"] #[doc = "original task's configuration, but will have a lower value for `remaining` than the"] #[doc = "original `total_retries`."] @@ -7497,33 +7382,6 @@ pub mod api { )] #[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 given task was unable to be renewed since the agenda is full at that block."] - pub struct PeriodicFailed { - pub task: periodic_failed::Task, - pub id: periodic_failed::Id, - } - pub mod periodic_failed { - use super::runtime_types; - pub type Task = ( - runtime_types::qp_scheduler::BlockNumberOrTimestamp< - ::core::primitive::u32, - ::core::primitive::u64, - >, - ::core::primitive::u32, - ); - pub type Id = ::core::option::Option<[::core::primitive::u8; 32usize]>; - } - impl ::subxt::ext::subxt_core::events::StaticEvent for PeriodicFailed { - const PALLET: &'static str = "Scheduler"; - const EVENT: &'static str = "PeriodicFailed"; - } - #[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 given task was unable to be retried since the agenda is full at that block or there"] #[doc = "was not enough weight to reschedule it."] pub struct RetryFailed { @@ -7721,9 +7579,10 @@ pub mod api { "Agenda", (), [ - 167u8, 175u8, 28u8, 224u8, 44u8, 149u8, 114u8, 12u8, 119u8, 107u8, - 50u8, 64u8, 173u8, 39u8, 48u8, 85u8, 151u8, 68u8, 15u8, 145u8, 182u8, - 105u8, 30u8, 18u8, 132u8, 11u8, 249u8, 54u8, 47u8, 73u8, 51u8, 49u8, + 30u8, 205u8, 116u8, 231u8, 62u8, 200u8, 225u8, 69u8, 50u8, 106u8, + 175u8, 47u8, 182u8, 175u8, 231u8, 114u8, 176u8, 58u8, 24u8, 230u8, + 81u8, 228u8, 217u8, 72u8, 171u8, 222u8, 251u8, 218u8, 73u8, 28u8, + 239u8, 137u8, ], ) } @@ -7745,9 +7604,10 @@ pub mod api { "Agenda", ::subxt::ext::subxt_core::storage::address::StaticStorageKey::new(_0), [ - 167u8, 175u8, 28u8, 224u8, 44u8, 149u8, 114u8, 12u8, 119u8, 107u8, - 50u8, 64u8, 173u8, 39u8, 48u8, 85u8, 151u8, 68u8, 15u8, 145u8, 182u8, - 105u8, 30u8, 18u8, 132u8, 11u8, 249u8, 54u8, 47u8, 73u8, 51u8, 49u8, + 30u8, 205u8, 116u8, 231u8, 62u8, 200u8, 225u8, 69u8, 50u8, 106u8, + 175u8, 47u8, 182u8, 175u8, 231u8, 114u8, 176u8, 58u8, 24u8, 230u8, + 81u8, 228u8, 217u8, 72u8, 171u8, 222u8, 251u8, 218u8, 73u8, 28u8, + 239u8, 137u8, ], ) } @@ -7799,9 +7659,6 @@ pub mod api { ) } #[doc = " Lookup from a name to the block number and index of the task."] - #[doc = ""] - #[doc = " For v3 -> v4 the previously unbounded identities are Blake2-256 hashed to form the v4"] - #[doc = " identities."] pub fn lookup_iter( &self, ) -> ::subxt::ext::subxt_core::storage::address::StaticAddress< @@ -7823,9 +7680,6 @@ pub mod api { ) } #[doc = " Lookup from a name to the block number and index of the task."] - #[doc = ""] - #[doc = " For v3 -> v4 the previously unbounded identities are Blake2-256 hashed to form the v4"] - #[doc = " identities."] pub fn lookup( &self, _0: types::lookup::Param0, @@ -8248,9 +8102,9 @@ pub mod api { "batch", types::Batch { calls }, [ - 218u8, 227u8, 88u8, 252u8, 144u8, 164u8, 107u8, 191u8, 4u8, 6u8, 242u8, - 220u8, 160u8, 117u8, 131u8, 23u8, 30u8, 44u8, 210u8, 225u8, 129u8, - 119u8, 42u8, 236u8, 42u8, 77u8, 133u8, 77u8, 167u8, 238u8, 189u8, 18u8, + 175u8, 251u8, 47u8, 139u8, 42u8, 213u8, 29u8, 121u8, 126u8, 160u8, + 31u8, 182u8, 18u8, 35u8, 57u8, 165u8, 44u8, 5u8, 237u8, 46u8, 120u8, + 177u8, 32u8, 135u8, 177u8, 20u8, 87u8, 229u8, 64u8, 13u8, 127u8, 38u8, ], ) } @@ -8280,10 +8134,10 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 61u8, 204u8, 36u8, 196u8, 16u8, 224u8, 252u8, 119u8, 160u8, 206u8, - 178u8, 6u8, 162u8, 252u8, 50u8, 119u8, 243u8, 74u8, 88u8, 93u8, 168u8, - 135u8, 247u8, 31u8, 113u8, 228u8, 106u8, 61u8, 76u8, 137u8, 49u8, - 152u8, + 163u8, 103u8, 246u8, 62u8, 75u8, 102u8, 59u8, 101u8, 118u8, 106u8, + 209u8, 68u8, 186u8, 60u8, 209u8, 233u8, 79u8, 179u8, 41u8, 65u8, 154u8, + 146u8, 145u8, 126u8, 183u8, 212u8, 239u8, 26u8, 76u8, 22u8, 239u8, + 228u8, ], ) } @@ -8309,9 +8163,9 @@ pub mod api { "batch_all", types::BatchAll { calls }, [ - 95u8, 55u8, 212u8, 217u8, 21u8, 7u8, 172u8, 181u8, 131u8, 146u8, 194u8, - 179u8, 219u8, 165u8, 223u8, 70u8, 125u8, 117u8, 103u8, 207u8, 97u8, - 4u8, 2u8, 153u8, 7u8, 107u8, 218u8, 218u8, 238u8, 159u8, 132u8, 169u8, + 150u8, 227u8, 254u8, 10u8, 63u8, 29u8, 209u8, 16u8, 78u8, 200u8, 2u8, + 1u8, 45u8, 134u8, 126u8, 227u8, 213u8, 206u8, 28u8, 11u8, 248u8, 34u8, + 208u8, 245u8, 115u8, 226u8, 51u8, 80u8, 157u8, 19u8, 214u8, 7u8, ], ) } @@ -8334,9 +8188,10 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 45u8, 253u8, 46u8, 68u8, 166u8, 17u8, 170u8, 35u8, 107u8, 183u8, 69u8, - 167u8, 62u8, 147u8, 100u8, 67u8, 144u8, 182u8, 141u8, 227u8, 20u8, - 175u8, 1u8, 229u8, 103u8, 16u8, 162u8, 14u8, 144u8, 57u8, 62u8, 154u8, + 26u8, 167u8, 149u8, 52u8, 49u8, 181u8, 232u8, 189u8, 214u8, 103u8, + 142u8, 80u8, 167u8, 149u8, 40u8, 66u8, 198u8, 220u8, 22u8, 169u8, + 249u8, 194u8, 40u8, 241u8, 212u8, 216u8, 96u8, 42u8, 107u8, 229u8, + 250u8, 169u8, ], ) } @@ -8362,9 +8217,9 @@ pub mod api { "force_batch", types::ForceBatch { calls }, [ - 143u8, 1u8, 248u8, 160u8, 18u8, 153u8, 38u8, 72u8, 212u8, 148u8, 146u8, - 7u8, 79u8, 129u8, 146u8, 207u8, 173u8, 174u8, 48u8, 237u8, 195u8, 6u8, - 40u8, 26u8, 146u8, 94u8, 85u8, 89u8, 187u8, 161u8, 10u8, 186u8, + 214u8, 226u8, 85u8, 46u8, 75u8, 28u8, 254u8, 199u8, 162u8, 194u8, + 192u8, 69u8, 103u8, 7u8, 126u8, 201u8, 242u8, 24u8, 85u8, 53u8, 69u8, + 91u8, 182u8, 111u8, 205u8, 250u8, 109u8, 233u8, 52u8, 9u8, 171u8, 11u8, ], ) } @@ -8387,9 +8242,9 @@ pub mod api { weight, }, [ - 45u8, 178u8, 212u8, 106u8, 190u8, 63u8, 70u8, 5u8, 15u8, 114u8, 128u8, - 212u8, 20u8, 232u8, 8u8, 28u8, 100u8, 12u8, 94u8, 250u8, 250u8, 221u8, - 169u8, 206u8, 52u8, 75u8, 152u8, 194u8, 133u8, 223u8, 85u8, 213u8, + 164u8, 25u8, 18u8, 51u8, 125u8, 4u8, 172u8, 80u8, 236u8, 99u8, 192u8, + 159u8, 103u8, 231u8, 83u8, 188u8, 32u8, 54u8, 11u8, 101u8, 127u8, 14u8, + 78u8, 134u8, 146u8, 112u8, 165u8, 108u8, 89u8, 10u8, 184u8, 131u8, ], ) } @@ -8429,9 +8284,10 @@ pub mod api { fallback: ::subxt::ext::subxt_core::alloc::boxed::Box::new(fallback), }, [ - 182u8, 227u8, 213u8, 214u8, 220u8, 74u8, 1u8, 211u8, 42u8, 249u8, - 237u8, 97u8, 126u8, 112u8, 93u8, 63u8, 5u8, 120u8, 216u8, 157u8, 132u8, - 123u8, 231u8, 38u8, 7u8, 139u8, 15u8, 126u8, 104u8, 63u8, 54u8, 76u8, + 92u8, 81u8, 197u8, 222u8, 38u8, 170u8, 79u8, 51u8, 116u8, 62u8, 50u8, + 58u8, 150u8, 250u8, 110u8, 71u8, 27u8, 159u8, 43u8, 203u8, 156u8, + 187u8, 239u8, 196u8, 87u8, 161u8, 156u8, 23u8, 146u8, 234u8, 101u8, + 3u8, ], ) } @@ -8454,9 +8310,9 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 209u8, 114u8, 253u8, 16u8, 26u8, 99u8, 196u8, 135u8, 161u8, 52u8, 48u8, - 224u8, 235u8, 129u8, 247u8, 215u8, 88u8, 253u8, 178u8, 0u8, 82u8, - 173u8, 148u8, 199u8, 98u8, 250u8, 50u8, 6u8, 106u8, 190u8, 52u8, 214u8, + 130u8, 99u8, 92u8, 78u8, 42u8, 115u8, 255u8, 247u8, 224u8, 97u8, 137u8, + 129u8, 227u8, 245u8, 204u8, 255u8, 128u8, 231u8, 43u8, 191u8, 72u8, + 241u8, 154u8, 16u8, 23u8, 72u8, 130u8, 40u8, 219u8, 191u8, 118u8, 34u8, ], ) } @@ -10123,6 +9979,8 @@ pub mod api { #[doc = "account by transferring the entire balance to themselves."] #[doc = ""] #[doc = "This is an emergency function for when the high security account may be compromised."] + #[doc = "It cancels all pending transfers first (applying volume fees), then transfers"] + #[doc = "the remaining free balance to the guardian."] pub struct RecoverFunds { pub account: recover_funds::Account, } @@ -10297,6 +10155,8 @@ pub mod api { #[doc = "account by transferring the entire balance to themselves."] #[doc = ""] #[doc = "This is an emergency function for when the high security account may be compromised."] + #[doc = "It cancels all pending transfers first (applying volume fees), then transfers"] + #[doc = "the remaining free balance to the guardian."] pub fn recover_funds( &self, account: types::recover_funds::Account, @@ -10479,11 +10339,6 @@ pub mod api { >; pub type Param0 = ::subxt::ext::subxt_core::utils::H256; } - pub mod account_pending_index { - use super::runtime_types; - pub type AccountPendingIndex = ::core::primitive::u32; - pub type Param0 = ::subxt::ext::subxt_core::utils::AccountId32; - } pub mod pending_transfers_by_sender { use super::runtime_types; pub type PendingTransfersBySender = @@ -10492,14 +10347,6 @@ pub mod api { >; pub type Param0 = ::subxt::ext::subxt_core::utils::AccountId32; } - pub mod pending_transfers_by_recipient { - use super::runtime_types; - pub type PendingTransfersByRecipient = - runtime_types::bounded_collections::bounded_vec::BoundedVec< - ::subxt::ext::subxt_core::utils::H256, - >; - pub type Param0 = ::subxt::ext::subxt_core::utils::AccountId32; - } pub mod interceptor_index { use super::runtime_types; pub type InterceptorIndex = @@ -10611,57 +10458,7 @@ pub mod api { ], ) } - #[doc = " Indexes pending transaction IDs per account for efficient lookup and cancellation."] - #[doc = " Also enforces the maximum pending transactions limit per account."] - pub fn account_pending_index_iter( - &self, - ) -> ::subxt::ext::subxt_core::storage::address::StaticAddress< - (), - types::account_pending_index::AccountPendingIndex, - (), - ::subxt::ext::subxt_core::utils::Yes, - ::subxt::ext::subxt_core::utils::Yes, - > { - ::subxt::ext::subxt_core::storage::address::StaticAddress::new_static( - "ReversibleTransfers", - "AccountPendingIndex", - (), - [ - 142u8, 255u8, 15u8, 41u8, 210u8, 84u8, 93u8, 230u8, 194u8, 31u8, 164u8, - 88u8, 155u8, 106u8, 130u8, 110u8, 199u8, 137u8, 153u8, 99u8, 154u8, - 210u8, 108u8, 136u8, 70u8, 141u8, 242u8, 255u8, 246u8, 19u8, 247u8, - 136u8, - ], - ) - } - #[doc = " Indexes pending transaction IDs per account for efficient lookup and cancellation."] - #[doc = " Also enforces the maximum pending transactions limit per account."] - pub fn account_pending_index( - &self, - _0: types::account_pending_index::Param0, - ) -> ::subxt::ext::subxt_core::storage::address::StaticAddress< - ::subxt::ext::subxt_core::storage::address::StaticStorageKey< - types::account_pending_index::Param0, - >, - types::account_pending_index::AccountPendingIndex, - ::subxt::ext::subxt_core::utils::Yes, - ::subxt::ext::subxt_core::utils::Yes, - (), - > { - ::subxt::ext::subxt_core::storage::address::StaticAddress::new_static( - "ReversibleTransfers", - "AccountPendingIndex", - ::subxt::ext::subxt_core::storage::address::StaticStorageKey::new(_0), - [ - 142u8, 255u8, 15u8, 41u8, 210u8, 84u8, 93u8, 230u8, 194u8, 31u8, 164u8, - 88u8, 155u8, 106u8, 130u8, 110u8, 199u8, 137u8, 153u8, 99u8, 154u8, - 210u8, 108u8, 136u8, 70u8, 141u8, 242u8, 255u8, 246u8, 19u8, 247u8, - 136u8, - ], - ) - } #[doc = " Maps sender accounts to their list of pending transaction IDs."] - #[doc = " This allows users to query all their outgoing pending transfers."] pub fn pending_transfers_by_sender_iter( &self, ) -> ::subxt::ext::subxt_core::storage::address::StaticAddress< @@ -10683,7 +10480,6 @@ pub mod api { ) } #[doc = " Maps sender accounts to their list of pending transaction IDs."] - #[doc = " This allows users to query all their outgoing pending transfers."] pub fn pending_transfers_by_sender( &self, _0: types::pending_transfers_by_sender::Param0, @@ -10707,55 +10503,6 @@ pub mod api { ], ) } - #[doc = " Maps recipient accounts to their list of pending incoming transaction IDs."] - #[doc = " This allows users to query all their incoming pending transfers."] - pub fn pending_transfers_by_recipient_iter( - &self, - ) -> ::subxt::ext::subxt_core::storage::address::StaticAddress< - (), - types::pending_transfers_by_recipient::PendingTransfersByRecipient, - (), - ::subxt::ext::subxt_core::utils::Yes, - ::subxt::ext::subxt_core::utils::Yes, - > { - ::subxt::ext::subxt_core::storage::address::StaticAddress::new_static( - "ReversibleTransfers", - "PendingTransfersByRecipient", - (), - [ - 63u8, 141u8, 24u8, 239u8, 201u8, 143u8, 36u8, 152u8, 35u8, 110u8, - 112u8, 157u8, 29u8, 61u8, 221u8, 79u8, 209u8, 192u8, 183u8, 29u8, - 145u8, 166u8, 238u8, 156u8, 131u8, 203u8, 124u8, 233u8, 210u8, 201u8, - 91u8, 212u8, - ], - ) - } - #[doc = " Maps recipient accounts to their list of pending incoming transaction IDs."] - #[doc = " This allows users to query all their incoming pending transfers."] - pub fn pending_transfers_by_recipient( - &self, - _0: types::pending_transfers_by_recipient::Param0, - ) -> ::subxt::ext::subxt_core::storage::address::StaticAddress< - ::subxt::ext::subxt_core::storage::address::StaticStorageKey< - types::pending_transfers_by_recipient::Param0, - >, - types::pending_transfers_by_recipient::PendingTransfersByRecipient, - ::subxt::ext::subxt_core::utils::Yes, - ::subxt::ext::subxt_core::utils::Yes, - (), - > { - ::subxt::ext::subxt_core::storage::address::StaticAddress::new_static( - "ReversibleTransfers", - "PendingTransfersByRecipient", - ::subxt::ext::subxt_core::storage::address::StaticStorageKey::new(_0), - [ - 63u8, 141u8, 24u8, 239u8, 201u8, 143u8, 36u8, 152u8, 35u8, 110u8, - 112u8, 157u8, 29u8, 61u8, 221u8, 79u8, 209u8, 192u8, 183u8, 29u8, - 145u8, 166u8, 238u8, 156u8, 131u8, 203u8, 124u8, 233u8, 210u8, 201u8, - 91u8, 212u8, - ], - ) - } #[doc = " Maps interceptor accounts to the list of accounts they can intercept for."] #[doc = " This allows the UI to efficiently query all accounts for which a given account is an"] #[doc = " interceptor."] @@ -10834,15 +10581,15 @@ pub mod api { use super::runtime_types; pub struct ConstantsApi; impl ConstantsApi { - #[doc = " Maximum pending reversible transactions allowed per account. Used for BoundedVec."] - pub fn max_pending_per_account( + #[doc = " Maximum number of accounts an interceptor can intercept for. Used for BoundedVec."] + pub fn max_interceptor_accounts( &self, ) -> ::subxt::ext::subxt_core::constants::address::StaticAddress< ::core::primitive::u32, > { ::subxt::ext::subxt_core::constants::address::StaticAddress::new_static( "ReversibleTransfers", - "MaxPendingPerAccount", + "MaxInterceptorAccounts", [ 98u8, 252u8, 116u8, 72u8, 26u8, 180u8, 225u8, 83u8, 200u8, 157u8, 125u8, 151u8, 53u8, 76u8, 168u8, 26u8, 10u8, 9u8, 98u8, 68u8, 9u8, @@ -10851,15 +10598,15 @@ pub mod api { ], ) } - #[doc = " Maximum number of accounts an interceptor can intercept for. Used for BoundedVec."] - pub fn max_interceptor_accounts( + #[doc = " Maximum pending reversible transactions allowed per account."] + pub fn max_pending_per_account( &self, ) -> ::subxt::ext::subxt_core::constants::address::StaticAddress< ::core::primitive::u32, > { ::subxt::ext::subxt_core::constants::address::StaticAddress::new_static( "ReversibleTransfers", - "MaxInterceptorAccounts", + "MaxPendingPerAccount", [ 98u8, 252u8, 116u8, 72u8, 26u8, 180u8, 225u8, 83u8, 200u8, 157u8, 125u8, 151u8, 53u8, 76u8, 168u8, 26u8, 10u8, 9u8, 98u8, 68u8, 9u8, @@ -14555,10 +14302,10 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 74u8, 28u8, 170u8, 7u8, 84u8, 125u8, 208u8, 229u8, 206u8, 35u8, 213u8, - 58u8, 187u8, 211u8, 153u8, 234u8, 193u8, 109u8, 97u8, 114u8, 59u8, - 238u8, 20u8, 171u8, 27u8, 139u8, 40u8, 12u8, 219u8, 199u8, 220u8, - 245u8, + 138u8, 215u8, 99u8, 222u8, 139u8, 128u8, 44u8, 8u8, 70u8, 23u8, 174u8, + 102u8, 142u8, 62u8, 176u8, 98u8, 216u8, 41u8, 138u8, 101u8, 201u8, + 105u8, 99u8, 164u8, 144u8, 176u8, 94u8, 228u8, 191u8, 175u8, 145u8, + 30u8, ], ) } @@ -19522,10 +19269,8 @@ pub mod api { #[doc = "- A deposit (refundable - returned immediately on execution/cancellation)"] #[doc = "- A fee (non-refundable, burned immediately)"] #[doc = ""] - #[doc = "**Auto-cleanup:** Before creating a new proposal, ALL proposer's expired"] - #[doc = "proposals are automatically removed. This is the primary cleanup mechanism."] - #[doc = ""] - #[doc = "**For threshold=1:** If the multisig threshold is 1, the proposal executes immediately."] + #[doc = "**For threshold=1:** The proposal is created with `Approved` status immediately"] + #[doc = "and can be executed via `execute()` without additional approvals."] #[doc = ""] #[doc = "**Weight:** Charged upfront for worst-case (high-security path with decode)."] #[doc = "Refunded to actual cost on success based on whether HS path was taken."] @@ -19622,11 +19367,14 @@ pub mod api { #[doc = "Remove expired proposals and return deposits to proposers"] #[doc = ""] #[doc = "Can only be called by signers of the multisig."] - #[doc = "Only removes Active proposals that have expired (past expiry block)."] + #[doc = "Removes Active or Approved proposals that have expired (past expiry block)."] #[doc = "Executed and Cancelled proposals are automatically cleaned up immediately."] #[doc = ""] + #[doc = "Approved+expired proposals can become stuck if proposer is unavailable (e.g. lost"] + #[doc = "keys, compromise). Allowing any signer to remove them prevents permanent deposit"] + #[doc = "lockup and enables multisig dissolution."] + #[doc = ""] #[doc = "The deposit is always returned to the original proposer, not the caller."] - #[doc = "This allows any signer to help clean up storage even if proposer is inactive."] pub struct RemoveExpired { pub multisig_address: remove_expired::MultisigAddress, pub proposal_id: remove_expired::ProposalId, @@ -19655,10 +19403,10 @@ pub mod api { #[doc = ""] #[doc = "This is a batch operation that removes all expired proposals where:"] #[doc = "- Caller is the proposer"] - #[doc = "- Proposal is Active and past expiry block"] + #[doc = "- Proposal is Active or Approved and past expiry block"] #[doc = ""] #[doc = "Note: Executed and Cancelled proposals are automatically cleaned up immediately,"] - #[doc = "so only Active+Expired proposals need manual cleanup."] + #[doc = "so only Active+Expired and Approved+Expired proposals need manual cleanup."] #[doc = ""] #[doc = "Returns all proposal deposits to the proposer in a single transaction."] pub struct ClaimDeposits { @@ -19792,10 +19540,8 @@ pub mod api { #[doc = "- A deposit (refundable - returned immediately on execution/cancellation)"] #[doc = "- A fee (non-refundable, burned immediately)"] #[doc = ""] - #[doc = "**Auto-cleanup:** Before creating a new proposal, ALL proposer's expired"] - #[doc = "proposals are automatically removed. This is the primary cleanup mechanism."] - #[doc = ""] - #[doc = "**For threshold=1:** If the multisig threshold is 1, the proposal executes immediately."] + #[doc = "**For threshold=1:** The proposal is created with `Approved` status immediately"] + #[doc = "and can be executed via `execute()` without additional approvals."] #[doc = ""] #[doc = "**Weight:** Charged upfront for worst-case (high-security path with decode)."] #[doc = "Refunded to actual cost on success based on whether HS path was taken."] @@ -19867,11 +19613,14 @@ pub mod api { #[doc = "Remove expired proposals and return deposits to proposers"] #[doc = ""] #[doc = "Can only be called by signers of the multisig."] - #[doc = "Only removes Active proposals that have expired (past expiry block)."] + #[doc = "Removes Active or Approved proposals that have expired (past expiry block)."] #[doc = "Executed and Cancelled proposals are automatically cleaned up immediately."] #[doc = ""] + #[doc = "Approved+expired proposals can become stuck if proposer is unavailable (e.g. lost"] + #[doc = "keys, compromise). Allowing any signer to remove them prevents permanent deposit"] + #[doc = "lockup and enables multisig dissolution."] + #[doc = ""] #[doc = "The deposit is always returned to the original proposer, not the caller."] - #[doc = "This allows any signer to help clean up storage even if proposer is inactive."] pub fn remove_expired( &self, multisig_address: types::remove_expired::MultisigAddress, @@ -19893,10 +19642,10 @@ pub mod api { #[doc = ""] #[doc = "This is a batch operation that removes all expired proposals where:"] #[doc = "- Caller is the proposer"] - #[doc = "- Proposal is Active and past expiry block"] + #[doc = "- Proposal is Active or Approved and past expiry block"] #[doc = ""] #[doc = "Note: Executed and Cancelled proposals are automatically cleaned up immediately,"] - #[doc = "so only Active+Expired proposals need manual cleanup."] + #[doc = "so only Active+Expired and Approved+Expired proposals need manual cleanup."] #[doc = ""] #[doc = "Returns all proposal deposits to the proposer in a single transaction."] pub fn claim_deposits( @@ -24068,10 +23817,8 @@ pub mod api { #[doc = "- A deposit (refundable - returned immediately on execution/cancellation)"] #[doc = "- A fee (non-refundable, burned immediately)"] #[doc = ""] - #[doc = "**Auto-cleanup:** Before creating a new proposal, ALL proposer's expired"] - #[doc = "proposals are automatically removed. This is the primary cleanup mechanism."] - #[doc = ""] - #[doc = "**For threshold=1:** If the multisig threshold is 1, the proposal executes immediately."] + #[doc = "**For threshold=1:** The proposal is created with `Approved` status immediately"] + #[doc = "and can be executed via `execute()` without additional approvals."] #[doc = ""] #[doc = "**Weight:** Charged upfront for worst-case (high-security path with decode)."] #[doc = "Refunded to actual cost on success based on whether HS path was taken."] @@ -24109,11 +23856,14 @@ pub mod api { #[doc = "Remove expired proposals and return deposits to proposers"] #[doc = ""] #[doc = "Can only be called by signers of the multisig."] - #[doc = "Only removes Active proposals that have expired (past expiry block)."] + #[doc = "Removes Active or Approved proposals that have expired (past expiry block)."] #[doc = "Executed and Cancelled proposals are automatically cleaned up immediately."] #[doc = ""] + #[doc = "Approved+expired proposals can become stuck if proposer is unavailable (e.g. lost"] + #[doc = "keys, compromise). Allowing any signer to remove them prevents permanent deposit"] + #[doc = "lockup and enables multisig dissolution."] + #[doc = ""] #[doc = "The deposit is always returned to the original proposer, not the caller."] - #[doc = "This allows any signer to help clean up storage even if proposer is inactive."] remove_expired { multisig_address: ::subxt::ext::subxt_core::utils::AccountId32, proposal_id: ::core::primitive::u32, @@ -24123,10 +23873,10 @@ pub mod api { #[doc = ""] #[doc = "This is a batch operation that removes all expired proposals where:"] #[doc = "- Caller is the proposer"] - #[doc = "- Proposal is Active and past expiry block"] + #[doc = "- Proposal is Active or Approved and past expiry block"] #[doc = ""] #[doc = "Note: Executed and Cancelled proposals are automatically cleaned up immediately,"] - #[doc = "so only Active+Expired proposals need manual cleanup."] + #[doc = "so only Active+Expired and Approved+Expired proposals need manual cleanup."] #[doc = ""] #[doc = "Returns all proposal deposits to the proposer in a single transaction."] claim_deposits { @@ -25920,6 +25670,8 @@ pub mod api { #[doc = "account by transferring the entire balance to themselves."] #[doc = ""] #[doc = "This is an emergency function for when the high security account may be compromised."] + #[doc = "It cancels all pending transfers first (applying volume fees), then transfers"] + #[doc = "the remaining free balance to the guardian."] recover_funds { account: ::subxt::ext::subxt_core::utils::AccountId32 }, } #[derive( @@ -26108,16 +25860,8 @@ pub mod api { #[doc = "Contains a variant per dispatchable extrinsic that this pallet has."] pub enum Call { #[codec(index = 0)] - #[doc = "Anonymously schedule a task."] schedule { when: ::core::primitive::u32, - maybe_periodic: ::core::option::Option<( - runtime_types::qp_scheduler::BlockNumberOrTimestamp< - ::core::primitive::u32, - ::core::primitive::u64, - >, - ::core::primitive::u32, - )>, priority: ::core::primitive::u8, call: ::subxt::ext::subxt_core::alloc::boxed::Box< runtime_types::quantus_runtime::RuntimeCall, @@ -26133,17 +25877,9 @@ pub mod api { index: ::core::primitive::u32, }, #[codec(index = 2)] - #[doc = "Schedule a named task."] schedule_named { id: [::core::primitive::u8; 32usize], when: ::core::primitive::u32, - maybe_periodic: ::core::option::Option<( - runtime_types::qp_scheduler::BlockNumberOrTimestamp< - ::core::primitive::u32, - ::core::primitive::u64, - >, - ::core::primitive::u32, - )>, priority: ::core::primitive::u8, call: ::subxt::ext::subxt_core::alloc::boxed::Box< runtime_types::quantus_runtime::RuntimeCall, @@ -26153,39 +25889,23 @@ pub mod api { #[doc = "Cancel a named scheduled task."] cancel_named { id: [::core::primitive::u8; 32usize] }, #[codec(index = 4)] - #[doc = "Anonymously schedule a task after a delay."] schedule_after { after: runtime_types::qp_scheduler::BlockNumberOrTimestamp< ::core::primitive::u32, ::core::primitive::u64, >, - maybe_periodic: ::core::option::Option<( - runtime_types::qp_scheduler::BlockNumberOrTimestamp< - ::core::primitive::u32, - ::core::primitive::u64, - >, - ::core::primitive::u32, - )>, priority: ::core::primitive::u8, call: ::subxt::ext::subxt_core::alloc::boxed::Box< runtime_types::quantus_runtime::RuntimeCall, >, }, #[codec(index = 5)] - #[doc = "Schedule a named task after a delay."] schedule_named_after { id: [::core::primitive::u8; 32usize], after: runtime_types::qp_scheduler::BlockNumberOrTimestamp< ::core::primitive::u32, ::core::primitive::u64, >, - maybe_periodic: ::core::option::Option<( - runtime_types::qp_scheduler::BlockNumberOrTimestamp< - ::core::primitive::u32, - ::core::primitive::u64, - >, - ::core::primitive::u32, - )>, priority: ::core::primitive::u8, call: ::subxt::ext::subxt_core::alloc::boxed::Box< runtime_types::quantus_runtime::RuntimeCall, @@ -26197,10 +25917,9 @@ pub mod api { #[doc = "succeeds."] #[doc = ""] #[doc = "Tasks which need to be scheduled for a retry are still subject to weight metering and"] - #[doc = "agenda space, same as a regular task. If a periodic task fails, it will be scheduled"] - #[doc = "normally while the task is retrying."] + #[doc = "agenda space, same as a regular task."] #[doc = ""] - #[doc = "Tasks scheduled as a result of a retry for a periodic task are unnamed, non-periodic"] + #[doc = "Tasks scheduled as a result of a retry are unnamed"] #[doc = "clones of the original task. Their retry configuration will be derived from the"] #[doc = "original task's configuration, but will have a lower value for `remaining` than the"] #[doc = "original `total_retries`."] @@ -26224,10 +25943,9 @@ pub mod api { #[doc = "it succeeds."] #[doc = ""] #[doc = "Tasks which need to be scheduled for a retry are still subject to weight metering and"] - #[doc = "agenda space, same as a regular task. If a periodic task fails, it will be scheduled"] - #[doc = "normally while the task is retrying."] + #[doc = "agenda space, same as a regular task."] #[doc = ""] - #[doc = "Tasks scheduled as a result of a retry for a periodic task are unnamed, non-periodic"] + #[doc = "Tasks scheduled as a result of a retry are unnamed"] #[doc = "clones of the original task. Their retry configuration will be derived from the"] #[doc = "original task's configuration, but will have a lower value for `remaining` than the"] #[doc = "original `total_retries`."] @@ -26373,18 +26091,6 @@ pub mod api { id: ::core::option::Option<[::core::primitive::u8; 32usize]>, }, #[codec(index = 6)] - #[doc = "The given task was unable to be renewed since the agenda is full at that block."] - PeriodicFailed { - task: ( - runtime_types::qp_scheduler::BlockNumberOrTimestamp< - ::core::primitive::u32, - ::core::primitive::u64, - >, - ::core::primitive::u32, - ), - id: ::core::option::Option<[::core::primitive::u8; 32usize]>, - }, - #[codec(index = 7)] #[doc = "The given task was unable to be retried since the agenda is full at that block or there"] #[doc = "was not enough weight to reschedule it."] RetryFailed { @@ -26397,7 +26103,7 @@ pub mod api { ), id: ::core::option::Option<[::core::primitive::u8; 32usize]>, }, - #[codec(index = 8)] + #[codec(index = 7)] #[doc = "The given task can never be executed since it is overweight."] PermanentlyOverweight { task: ( @@ -26434,13 +26140,9 @@ pub mod api { pub maybe_id: ::core::option::Option<_0>, pub priority: ::core::primitive::u8, pub call: _1, - pub maybe_periodic: ::core::option::Option<( - runtime_types::qp_scheduler::BlockNumberOrTimestamp<_2, _5>, - _2, - )>, pub origin: _3, #[codec(skip)] - pub __ignore: ::core::marker::PhantomData<_4>, + pub __ignore: ::core::marker::PhantomData<(_4, _5, _2)>, } } pub mod pallet_sudo { @@ -27045,39 +26747,30 @@ pub mod api { #[doc = "The `Error` enum of this pallet."] pub enum Error { #[codec(index = 0)] - InvalidProof, - #[codec(index = 1)] - ProofDeserializationFailed, - #[codec(index = 2)] - VerificationFailed, - #[codec(index = 3)] InvalidPublicInputs, - #[codec(index = 4)] + #[codec(index = 1)] NullifierAlreadyUsed, - #[codec(index = 5)] + #[codec(index = 2)] VerifierNotAvailable, - #[codec(index = 6)] - InvalidStorageRoot, - #[codec(index = 7)] - StorageRootMismatch, - #[codec(index = 8)] + #[codec(index = 3)] BlockNotFound, - #[codec(index = 9)] - InvalidBlockNumber, - #[codec(index = 10)] + #[codec(index = 4)] AggregatedVerifierNotAvailable, - #[codec(index = 11)] + #[codec(index = 5)] AggregatedProofDeserializationFailed, - #[codec(index = 12)] + #[codec(index = 6)] AggregatedVerificationFailed, - #[codec(index = 13)] + #[codec(index = 7)] InvalidAggregatedPublicInputs, - #[codec(index = 14)] + #[codec(index = 8)] #[doc = "The volume fee rate in the proof doesn't match the configured rate"] InvalidVolumeFeeRate, - #[codec(index = 15)] + #[codec(index = 9)] #[doc = "Transfer amount is below the minimum required"] TransferAmountBelowMinimum, + #[codec(index = 10)] + #[doc = "Only native asset (asset_id = 0) is supported in this version"] + NonNativeAssetNotSupported, } #[derive( :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 2d3e436..68bbdbb 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; @@ -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), @@ -353,6 +364,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..a1300e2 --- /dev/null +++ b/src/cli/referenda.rs @@ -0,0 +1,849 @@ +//! `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 qp_poseidon::PoseidonHasher; + + 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()); + + // Compute preimage hash using Poseidon + let preimage_hash: sp_core::H256 = + ::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::qp_poseidon::PoseidonHasher, + >; + + 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::qp_poseidon::PoseidonHasher, + >; + + 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/reversible.rs b/src/cli/reversible.rs index 63d5ed0..83940e5 100644 --- a/src/cli/reversible.rs +++ b/src/cli/reversible.rs @@ -389,11 +389,8 @@ async fn list_pending_transactions( // Determine which address to query let target_address = match (address, wallet_name) { (Some(addr), _) => { - // Validate the provided address - SpAccountId32::from_ss58check(&addr).map_err(|e| { - crate::error::QuantusError::Generic(format!("Invalid address: {e:?}")) - })?; - addr + // --address accepts SS58 or wallet name + resolve_address(&addr)? }, (None, Some(wallet)) => { // Load wallet and get its address @@ -416,105 +413,66 @@ async fn list_pending_transactions( log_verbose!("🔍 Querying pending transfers for: {}", target_address); - // Query pending transfers by sender (outgoing) - let sender_storage_address = crate::chain::quantus_subxt::api::storage() - .reversible_transfers() - .pending_transfers_by_sender(account_id.clone()); - - // Get the latest block hash to read from the latest state (not finalized) let latest_block_hash = quantus_client.get_latest_block().await?; - - let outgoing_transfers = quantus_client - .client() - .storage() - .at(latest_block_hash) - .fetch(&sender_storage_address) - .await - .map_err(|e| crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}")))?; - - // Query pending transfers by recipient (incoming) - let recipient_storage_address = crate::chain::quantus_subxt::api::storage() + let storage_at = quantus_client.client().storage().at(latest_block_hash); + let pending_iter = crate::chain::quantus_subxt::api::storage() .reversible_transfers() - .pending_transfers_by_recipient(account_id); + .pending_transfers_iter(); + + let mut outgoing = Vec::new(); + let mut incoming = Vec::new(); + let mut iter = storage_at.iter(pending_iter).await.map_err(|e| { + crate::error::QuantusError::NetworkError(format!("Storage iter error: {e:?}")) + })?; - let incoming_transfers = quantus_client - .client() - .storage() - .at(latest_block_hash) - .fetch(&recipient_storage_address) - .await - .map_err(|e| crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}")))?; + while let Some(result) = iter.next().await { + match result { + Ok(entry) => { + // Key is storage prefix + tx_id (H256, 32 bytes). Extract last 32 bytes. + let tx_id_hex = if entry.key_bytes.len() >= 32 { + hex::encode(&entry.key_bytes[entry.key_bytes.len() - 32..]) + } else { + hex::encode(&entry.key_bytes) + }; + let transfer = entry.value; + if transfer.from == account_id { + outgoing.push((tx_id_hex, transfer)); + } else if transfer.to == account_id { + incoming.push((tx_id_hex, transfer)); + } + }, + Err(e) => { + log_verbose!("âš ī¸ Error reading pending transfer: {:?}", e); + }, + } + } let mut total_transfers = 0; - // Display outgoing transfers - if let Some(outgoing_hashes) = outgoing_transfers { - if !outgoing_hashes.0.is_empty() { - log_print!("📤 Outgoing pending transfers:"); - for (i, hash) in outgoing_hashes.0.iter().enumerate() { - total_transfers += 1; - log_print!(" {}. 0x{}", i + 1, hex::encode(hash.as_ref())); - - // Try to get transfer details - let transfer_storage_address = crate::chain::quantus_subxt::api::storage() - .reversible_transfers() - .pending_transfers(*hash); - - if let Ok(Some(transfer_details)) = quantus_client - .client() - .storage() - .at(latest_block_hash) - .fetch(&transfer_storage_address) - .await - .map_err(|e| { - crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}")) - }) { - let formatted_amount = format_amount(transfer_details.amount); - log_print!(" 👤 To: {}", transfer_details.to.to_quantus_ss58()); - log_print!(" 💰 Amount: {}", formatted_amount); - log_print!( - " 🔄 Interceptor: {}", - transfer_details.interceptor.to_quantus_ss58() - ); - } - } + if !outgoing.is_empty() { + log_print!("📤 Outgoing pending transfers:"); + for (i, (tx_id_hex, transfer)) in outgoing.iter().enumerate() { + total_transfers += 1; + log_print!(" {}. 0x{}", i + 1, tx_id_hex); + let formatted_amount = format_amount(transfer.amount); + log_print!(" 👤 To: {}", transfer.to.to_quantus_ss58()); + log_print!(" 💰 Amount: {}", formatted_amount); + log_print!(" 🔄 Interceptor: {}", transfer.interceptor.to_quantus_ss58()); } } - // Display incoming transfers - if let Some(incoming_hashes) = incoming_transfers { - if !incoming_hashes.0.is_empty() { - if total_transfers > 0 { - log_print!(""); - } - log_print!("đŸ“Ĩ Incoming pending transfers:"); - for (i, hash) in incoming_hashes.0.iter().enumerate() { - total_transfers += 1; - log_print!(" {}. 0x{}", i + 1, hex::encode(hash.as_ref())); - - // Try to get transfer details - let transfer_storage_address = crate::chain::quantus_subxt::api::storage() - .reversible_transfers() - .pending_transfers(*hash); - - if let Ok(Some(transfer_details)) = quantus_client - .client() - .storage() - .at(latest_block_hash) - .fetch(&transfer_storage_address) - .await - .map_err(|e| { - crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}")) - }) { - let formatted_amount = format_amount(transfer_details.amount); - log_print!(" 👤 From: {}", transfer_details.from.to_quantus_ss58()); - log_print!(" 💰 Amount: {}", formatted_amount); - log_print!( - " 🔄 Interceptor: {}", - transfer_details.interceptor.to_quantus_ss58() - ); - } - } + if !incoming.is_empty() { + if total_transfers > 0 { + log_print!(""); + } + log_print!("đŸ“Ĩ Incoming pending transfers:"); + for (i, (tx_id_hex, transfer)) in incoming.iter().enumerate() { + total_transfers += 1; + log_print!(" {}. 0x{}", i + 1, tx_id_hex); + let formatted_amount = format_amount(transfer.amount); + log_print!(" 👤 From: {}", transfer.from.to_quantus_ss58()); + log_print!(" 💰 Amount: {}", formatted_amount); + log_print!(" 🔄 Interceptor: {}", transfer.interceptor.to_quantus_ss58()); } } diff --git a/src/cli/scheduler.rs b/src/cli/scheduler.rs index e89a1cd..89055f0 100644 --- a/src/cli/scheduler.rs +++ b/src/cli/scheduler.rs @@ -121,13 +121,11 @@ async fn schedule_remark( // When: after N blocks (u32) let when_u32: u32 = after; - let maybe_periodic = None; let priority: u8 = 0; // Submit schedule extrinsic let keypair = crate::wallet::load_keypair_from_wallet(from, None, None)?; - let schedule_tx = - api::tx().scheduler().schedule(when_u32, maybe_periodic, priority, runtime_call); + let schedule_tx = api::tx().scheduler().schedule(when_u32, priority, runtime_call); let tx_hash = crate::cli::common::submit_transaction( quantus_client, &keypair, diff --git a/src/cli/tech_referenda.rs b/src/cli/tech_referenda.rs new file mode 100644 index 0000000..6fc8860 --- /dev/null +++ b/src/cli/tech_referenda.rs @@ -0,0 +1,771 @@ +//! `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, + }, + + /// 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, + }, + + /// Cancel a Tech Referendum (requires root permissions) + Cancel { + /// Referendum index to cancel + #[arg(short, long)] + index: u32, + + /// Wallet name to sign with (must have root permissions) + #[arg(short, long)] + from: String, + + /// Password for the wallet + #[arg(short, long)] + password: Option, + + /// Read password from file + #[arg(long)] + password_file: Option, + }, + + /// Kill a Tech Referendum (requires root permissions) + Kill { + /// Referendum index to kill + #[arg(short, long)] + index: u32, + + /// Wallet name to sign with (must have root permissions) + #[arg(short, long)] + from: String, + + /// Password for the wallet + #[arg(short, long)] + password: Option, + + /// Read password from file + #[arg(long)] + password_file: Option, + }, + + /// Nudge a Tech Referendum to next phase (sudo origin) + Nudge { + /// Referendum index to nudge + #[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::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::Cancel { index, from, password, password_file } => + cancel_proposal(&quantus_client, index, &from, password, password_file, execution_mode) + .await, + TechReferendaCommands::Kill { index, from, password, password_file } => + kill_proposal(&quantus_client, index, &from, password, password_file, execution_mode) + .await, + TechReferendaCommands::Nudge { index, from, password, password_file } => + nudge_proposal(&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::qp_poseidon::PoseidonHasher, + >; + + 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 qp_poseidon::PoseidonHasher; + + 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()); + + // Compute preimage hash using Poseidon (runtime uses PoseidonHasher) + let preimage_hash: sp_core::H256 = + ::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::qp_poseidon::PoseidonHasher, + >; + + 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(()) +} + +/// 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(()) +} + +/// Cancel a Tech Referendum (sudo) +async fn cancel_proposal( + 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!("❌ Cancelling Tech Referendum #{}", index); + log_print!(" 🔑 Cancelled by: {}", from.bright_yellow()); + + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + let inner = + quantus_subxt::api::Call::TechReferenda(quantus_subxt::api::tech_referenda::Call::cancel { + index, + }); + let sudo_call = quantus_subxt::api::tx().sudo().sudo(inner); + + let tx_hash = + submit_transaction(quantus_client, &keypair, sudo_call, None, execution_mode).await?; + log_success!("✅ Referendum cancelled! Hash: {:?}", tx_hash.to_string().bright_yellow()); + Ok(()) +} + +/// Kill a Tech Referendum (sudo) +async fn kill_proposal( + 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!("💀 Killing Tech Referendum #{}", index); + log_print!(" 🔑 Killed by: {}", from.bright_yellow()); + + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + let inner = + quantus_subxt::api::Call::TechReferenda(quantus_subxt::api::tech_referenda::Call::kill { + index, + }); + let sudo_call = quantus_subxt::api::tx().sudo().sudo(inner); + + let tx_hash = + submit_transaction(quantus_client, &keypair, sudo_call, None, execution_mode).await?; + log_success!("✅ Referendum killed! Hash: {:?}", tx_hash.to_string().bright_yellow()); + Ok(()) +} + +/// Nudge a Tech Referendum to next phase (sudo) +async fn nudge_proposal( + 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!("🔄 Nudging Tech Referendum #{}", index); + log_print!(" 🔑 Nudged by: {}", from.bright_yellow()); + + let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?; + + let inner = quantus_subxt::api::Call::TechReferenda( + quantus_subxt::api::tech_referenda::Call::nudge_referendum { index }, + ); + let sudo_call = quantus_subxt::api::tx().sudo().sudo(inner); + + let tx_hash = + submit_transaction(quantus_client, &keypair, sudo_call, None, execution_mode).await?; + log_success!("✅ Referendum nudged! 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..d846972 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] = &[118, 119]; /// 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 a26ebd3..c213df4 100644 Binary files a/src/quantus_metadata.scale and b/src/quantus_metadata.scale differ diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 0837d2d..e784dbb 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -773,8 +773,10 @@ mod tests { let (wallet_manager, _temp_dir) = create_test_wallet_manager().await; let test_mnemonic = "orchard answer curve patient visual flower maze noise retreat penalty cage small earth domain scan pitch bottom crunch theme club client swap slice raven"; - let expected_address = "qzoog56PJKvDwqo9GwkzRN74kxEgDEspxu5zVA62y18ttt3tG"; // default derivation path index 0 - let expected_address_no_derive = "qzofkFbmnEYLX6iHwqJ9uKYXFi7ypQwcBBMxcYYLVD17vGpsm"; + // Addresses derived from mnemonic via DEFAULT_DERIVATION_PATH (m/44'/189189'/0'/0'/0') + // and no-derivation path (m/44'/189189'/0'). Update if qp-rusty-crystals-hdwallet changes. + let expected_address = "qznibgrYxiVTcBVLAoRPHgMqkXk16t1FfzGYcE4f4dVnJC3U8"; + let expected_address_no_derive = "qzkpGYFNw3LshweJnW5PgkNrZcUxXyoHFQHqBjJoZ1MeiQThC"; let imported_wallet = wallet_manager .import_wallet("imported-test-wallet", test_mnemonic, Some("import-password"))