diff --git a/Cargo.lock b/Cargo.lock index 3e767d5b9..5690a0abf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2054,6 +2054,7 @@ dependencies = [ "thiserror", "tonic", "tracing", + "upgrade", ] [[package]] @@ -5833,6 +5834,22 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "upgrade" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "gears", + "ibc-proto 0.33.0", + "nutype", + "prost", + "serde", + "serde_json", + "strum", + "tracing", +] + [[package]] name = "url" version = "2.5.2" diff --git a/Cargo.toml b/Cargo.toml index 1107dc4cf..78861cacf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ members = [ "x/ibc-rs", "x/slashing", "x/staking", - "x/genutil", + "x/genutil", "x/upgrade", # new unsorted ] diff --git a/gaia-rs/src/lib.rs b/gaia-rs/src/lib.rs index a33dad0c6..9eacd0a74 100644 --- a/gaia-rs/src/lib.rs +++ b/gaia-rs/src/lib.rs @@ -58,7 +58,6 @@ pub mod config; pub mod genesis; pub mod message; pub mod modules; -pub mod params; pub mod query; pub mod rest; pub mod store_keys; diff --git a/gaia-rs/src/params.rs b/gaia-rs/src/params.rs deleted file mode 100644 index d744dc9a6..000000000 --- a/gaia-rs/src/params.rs +++ /dev/null @@ -1,168 +0,0 @@ -use auth::AuthParamsKeeper; -use bank::BankParamsKeeper; -use gears::{ - application::keepers::params::ParamsKeeper, - baseapp::BaseAppParamsKeeper, - context::InfallibleContextMut, - params::{ParamsDeserialize, ParamsSerialize}, - store::{database::Database, StoreKey}, -}; -use gov::{ - submission::{ - handler::{ParamChangeSubmissionHandler, SubmissionHandler, SubmissionHandlingError}, - param::ParameterChangeProposal, - text::{TextProposal, TextSubmissionHandler}, - }, - types::proposal::Proposal, - ProposalHandler, -}; -use staking::StakingParamsKeeper; - -use crate::store_keys::GaiaParamsStoreKey; - -#[derive(Debug)] -pub struct GaiaProposalHandler; - -impl ProposalHandler for GaiaProposalHandler { - fn handle, DB: Database, SK: StoreKey>( - &self, - proposal: &Proposal, - ctx: &mut CTX, - ) -> Result<(), SubmissionHandlingError> { - match proposal.content.type_url.as_str() { - ParameterChangeProposal::::TYPE_URL => { - let msg: ParameterChangeProposal = - ParameterChangeProposal::try_from(proposal.content.clone())?; - - for change in msg.changes { - match change.subspace.clone() { - space @ GaiaParamsStoreKey::Bank => ParamChangeSubmissionHandler::< - BankParamsKeeper, - >::handle( - change, ctx, &space - ), - space @ GaiaParamsStoreKey::Auth => ParamChangeSubmissionHandler::< - AuthParamsKeeper, - >::handle( - change, ctx, &space - ), - space @ GaiaParamsStoreKey::BaseApp => ParamChangeSubmissionHandler::< - BaseAppParamsKeeper, - >::handle( - change, ctx, &space - ), - space @ GaiaParamsStoreKey::Staking => ParamChangeSubmissionHandler::< - StakingParamsKeeper, - >::handle( - change, ctx, &space - ), - GaiaParamsStoreKey::IBC => Err(SubmissionHandlingError::Subspace), - GaiaParamsStoreKey::Capability => Err(SubmissionHandlingError::Subspace), - }?; - } - - Ok(()) - } - TextProposal::TYPE_URL => TextSubmissionHandler::::handle( - proposal.content.clone().try_into()?, - ctx, - &DUMMY_PARAMS, - ), - _ => Err(SubmissionHandlingError::InvalidProposal), - } - } - - fn check(proposal: &Proposal) -> bool { - match proposal.content.type_url.as_str() { - ParameterChangeProposal::::TYPE_URL => { - let msg: Result, gears::core::errors::CoreError> = - ParameterChangeProposal::try_from(proposal.content.clone()); - - match msg { - Ok(msg) => { - for change in msg.changes { - if !match change.subspace { - GaiaParamsStoreKey::Bank => { - BankParamsKeeper::::check_key(&change.key) - && BankParamsKeeper::::validate( - &change.key, - &change.value, - ) - } - GaiaParamsStoreKey::Auth => { - AuthParamsKeeper::::check_key(&change.key) - && AuthParamsKeeper::::validate( - &change.key, - &change.value, - ) - } - GaiaParamsStoreKey::BaseApp => { - BaseAppParamsKeeper::::check_key( - &change.key, - ) && BaseAppParamsKeeper::::validate( - &change.key, - &change.value, - ) - } - GaiaParamsStoreKey::Staking => { - StakingParamsKeeper::::check_key( - &change.key, - ) && StakingParamsKeeper::::validate( - &change.key, - &change.value, - ) - } - GaiaParamsStoreKey::IBC => false, - GaiaParamsStoreKey::Capability => false, - } { - return false; - } - } - - true - } - Err(_) => false, - } - } - TextProposal::TYPE_URL => true, - _ => false, - } - } -} - -const DUMMY_PARAMS: GaiaParamsStoreKey = GaiaParamsStoreKey::Auth; - -/// We need dummy keeper for textual propose which doesn't change any value, but need to satisfy api -#[derive(Debug, Default, Clone)] -struct DummyParamsKeeper; - -impl ParamsKeeper for DummyParamsKeeper { - type Param = DummyParams; - - fn psk(&self) -> &GaiaParamsStoreKey { - &DUMMY_PARAMS - } - - fn validate(_: impl AsRef<[u8]>, _: impl AsRef<[u8]>) -> bool { - true - } -} - -#[derive(Debug, Default, Clone)] -struct DummyParams; - -impl ParamsSerialize for DummyParams { - fn keys() -> std::collections::HashSet<&'static str> { - Default::default() - } - - fn to_raw(&self) -> Vec<(&'static str, Vec)> { - Default::default() - } -} - -impl ParamsDeserialize for DummyParams { - fn from_raw(_: std::collections::HashMap<&'static str, Vec>) -> Self { - Self - } -} diff --git a/gears/src/application/handlers/client.rs b/gears/src/application/handlers/client.rs index 2f723eda8..5a5374762 100644 --- a/gears/src/application/handlers/client.rs +++ b/gears/src/application/handlers/client.rs @@ -174,11 +174,11 @@ pub trait TxHandler { /// Handles query request, serialization and displaying it as `String` pub trait QueryHandler { - /// Query request which contains all information needed for request - type QueryRequest: Query; /// Additional context to use. \ /// In most cases you would expect this to be some sort of cli command type QueryCommands; + /// Query request which contains all information needed for request + type QueryRequest: Query; /// Serialized response from query request type QueryResponse: Serialize; diff --git a/gears/src/application/handlers/node.rs b/gears/src/application/handlers/node.rs index d4795dab5..dd38bbc97 100644 --- a/gears/src/application/handlers/node.rs +++ b/gears/src/application/handlers/node.rs @@ -13,7 +13,7 @@ use tendermint::types::{ }; use thiserror::Error; -pub trait ModuleInfo { +pub trait ModuleInfo: Clone + Sync + Send + 'static { const NAME: &'static str; } diff --git a/gears/src/baseapp/genesis.rs b/gears/src/baseapp/genesis.rs index 6494c506c..729426d39 100644 --- a/gears/src/baseapp/genesis.rs +++ b/gears/src/baseapp/genesis.rs @@ -1,6 +1,8 @@ use crate::types::{address::AccAddress, base::coins::UnsignedCoins}; use serde::{de::DeserializeOwned, Serialize}; +pub use null_genesis::NullGenesis; + #[derive(Debug, Clone, thiserror::Error)] #[error("cannot add account at existing address {0}")] pub struct GenesisError(pub AccAddress); @@ -15,3 +17,26 @@ pub trait Genesis: coins: UnsignedCoins, ) -> Result<(), GenesisError>; } + +mod null_genesis { + use super::*; + + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] + pub struct NullGenesis(); + + impl Default for NullGenesis { + fn default() -> Self { + Self() + } + } + + impl Genesis for NullGenesis { + fn add_genesis_account( + &mut self, + _: AccAddress, + _: UnsignedCoins, + ) -> Result<(), GenesisError> { + Ok(()) + } + } +} diff --git a/gears/src/error.rs b/gears/src/error.rs index 38f20880b..df3517b9f 100644 --- a/gears/src/error.rs +++ b/gears/src/error.rs @@ -3,10 +3,13 @@ use core_types::errors::CoreError; use cosmwasm_std::Decimal256RangeExceeded; use tendermint::{error::Error as TendermintError, types::time::timestamp::NewTimestampError}; -use crate::types::{ - base::errors::{CoinError, CoinsError}, - errors::DenomError, - tx::metadata::MetadataParseError, +use crate::{ + params::SubspaceParseError, + types::{ + base::errors::{CoinError, CoinsError}, + errors::DenomError, + tx::metadata::MetadataParseError, + }, }; pub const POISONED_LOCK: &str = "poisoned lock"; @@ -73,3 +76,15 @@ impl From for ProtobufError { unreachable!("who would return infallible error?") } } + +impl From for ProtobufError { + fn from(value: std::num::TryFromIntError) -> Self { + Self::Custom(anyhow::anyhow!("{value}")) + } +} + +impl From for ProtobufError { + fn from(value: SubspaceParseError) -> Self { + Self::Core(CoreError::DecodeGeneral(value.to_string())) + } +} diff --git a/gears/src/params/mod.rs b/gears/src/params/mod.rs index b7c30b453..ab0dcd6e6 100644 --- a/gears/src/params/mod.rs +++ b/gears/src/params/mod.rs @@ -53,10 +53,10 @@ pub fn infallible_subspace_mut< #[error("error parsing subpsace: {0}")] pub struct SubspaceParseError(pub String); -pub trait ParamsSubspaceKey: Hash + Eq + Clone + Send + Sync + 'static { - fn name(&self) -> &'static str; +pub trait ParamsSubspaceKey: std::fmt::Debug + Hash + Eq + Clone + Send + Sync + 'static { + fn name(&self) -> String; - fn from_subspace_str(val: &str) -> Result; + fn from_subspace_str(val: impl AsRef) -> Result; } // TODO:LATER For PR with xmod to change any params diff --git a/macros/key-derive/src/params_key.rs b/macros/key-derive/src/params_key.rs index dc1c8ae4b..512ecf22f 100644 --- a/macros/key-derive/src/params_key.rs +++ b/macros/key-derive/src/params_key.rs @@ -63,25 +63,26 @@ pub fn expand_params(input: DeriveInput) -> syn::Result { let _ = set.insert(to_string.clone()); - enum_variants.push(quote! { Self::#ident => #to_string }); + enum_variants + .push(quote! { Self::#ident => ::std::borrow::ToOwned::to_owned(#to_string) }); from_str_impls.push(quote! { #to_string => Self::#ident }); } let result = quote! { impl #crate_prefix ::params::ParamsSubspaceKey for #ident { - fn name(&self) -> &'static str + fn name(&self) -> String { match self{ #(#enum_variants),* } } - fn from_subspace_str(val: &str) -> ::std::result::Result { - let result = match val + fn from_subspace_str(val: impl ::std::convert::AsRef) -> ::std::result::Result { + let result = match ::std::convert::AsRef::as_ref(&val) { #(#from_str_impls),* - , _ => ::std::result::Result::Err(#crate_prefix::params::SubspaceParseError(::std::format!("missing valid key: {val} not found")))?, + , _ => ::std::result::Result::Err(#crate_prefix::params::SubspaceParseError(::std::format!("missing valid key: {} not found", ::std::convert::AsRef::as_ref(&val) )))?, }; ::std::result::Result::Ok(result) diff --git a/macros/protobuf-derive/proto.md b/macros/protobuf-derive/proto.md index c94422def..7eb1674ab 100644 --- a/macros/protobuf-derive/proto.md +++ b/macros/protobuf-derive/proto.md @@ -19,7 +19,11 @@ _Example_: `RawProto` or `inner::RawProto` **name** : _optional_, name of field that you want to use in raw structure. If not specified tries to use structure with name `Raw{NameOfStructure}` \ **optional** : _flag_ indicates that raw structure field type of `Option`. Exclusive to `repeated` flag. \ -**repeated** : _flag_ indicates that raw structure filed type of `Vec`. Exclusive to `optional` flag. +**repeated** : _flag_ indicates that raw structure filed type of `Vec`. Exclusive to `optional` flag. \ +**from** : _optional_ path to `fn` use to parse value *from* raw. \ +**from_ref** : _optional_ indicates that value should be passed by ref to parsing `fn`. Note makes no sense without `from` flag. \ +**into** : _optional_ path to `fn` use to parse value *into* raw. \ +**into_ref** : _optional_ indicates that value should be passed by ref to parsing `fn`. Note makes no sense without `into` flag. ## Example diff --git a/macros/protobuf-derive/src/proto.rs b/macros/protobuf-derive/src/proto.rs index e516eeeff..c284c25c5 100644 --- a/macros/protobuf-derive/src/proto.rs +++ b/macros/protobuf-derive/src/proto.rs @@ -19,11 +19,22 @@ struct ProtobufAttr { name: Option, #[darling(flatten, default)] opt: OptionalOrRepeated, + from: Option, + from_ref: Flag, + into: Option, + into_ref: Flag, } pub fn expand_raw_existing(input: DeriveInput) -> syn::Result { let ProtobufArg { raw, gears } = ProtobufArg::from_derive_input(&input)?; - let DeriveInput { ident, data, .. } = input; + let DeriveInput { + ident, + data, + generics, + .. + } = input; + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let crate_prefix = match gears.is_present() { true => quote! { crate }, @@ -41,14 +52,21 @@ pub fn expand_raw_existing(input: DeriveInput) -> syn::Result for #ident {} + impl #impl_generics #crate_prefix ::core::Protobuf<#raw> for #ident #ty_generics #where_clause {} }; match data { syn::Data::Struct(DataStruct { fields, .. }) => { let mut raw_fields = Vec::new(); for field in fields { - let ProtobufAttr { name, opt } = ProtobufAttr::from_attributes(&field.attrs)?; + let ProtobufAttr { + name, + opt, + from, + into, + from_ref, + into_ref, + } = ProtobufAttr::from_attributes(&field.attrs)?; let field_indent = field.ident.clone().ok_or(syn::Error::new( field.span(), @@ -62,18 +80,38 @@ pub fn expand_raw_existing(input: DeriveInput) -> syn::Result quote! { #var }, + None => quote! { ::std::convert::Into::into }, + }; + + let value_prefix = match into_ref.is_present() { + true => quote! { & }, + false => quote! {}, + }; + let result = match (field_kind, other_field_kind) { (FieldWrapper::Optional, FieldWrapper::Optional) => quote! { #other_name : match value.#field_ident { - ::std::option::Option::Some(var) => ::std::option::Option::Some( ::std::convert::Into::into(var)), + ::std::option::Option::Some(var) => ::std::option::Option::Some( #into_method ( #value_prefix var)), ::std::option::Option::None => ::std::option::Option::None, } }, @@ -95,7 +133,7 @@ pub fn expand_raw_existing(input: DeriveInput) -> syn::Result syn::Result quote! { - #other_name : ::std::option::Option::Some(::std::convert::Into::into(value.#field_ident)) + #other_name : ::std::option::Option::Some( #into_method ( #value_prefix value.#field_ident)) }, (FieldWrapper::None, FieldWrapper::Vec) => Err(syn::Error::new_spanned( field_ident, "Can't cast Vec to field", ))?, (FieldWrapper::None, FieldWrapper::None) => quote! { - #other_name : ::std::convert::Into::into(value.#field_ident) + #other_name : #into_method (#value_prefix value.#field_ident) }, }; @@ -124,8 +162,8 @@ pub fn expand_raw_existing(input: DeriveInput) -> syn::Result for #raw { - fn from(value: #ident) -> Self { + impl #impl_generics ::std::convert::From<#ident #ty_generics > for #raw #where_clause { + fn from(value: #ident #ty_generics) -> Self { Self { #(#from_fields_iter_gen),* @@ -137,11 +175,29 @@ pub fn expand_raw_existing(input: DeriveInput) -> syn::Result quote! { #var }, + None => quote! { ::std::convert::TryFrom::try_from }, + }; + + let value_prefix = match from_ref.is_present() { + true => quote! { & }, + false => quote! {}, + }; + let result = match (field_kind, other_field_kind) { (FieldWrapper::Optional, FieldWrapper::Optional) => quote! { #field_ident : match value.#other_name { - Some(var) => Some(::std::convert::TryFrom::try_from(var)?), + Some(var) => Some( #from_method (#value_prefix var)?), None => None, } }, @@ -149,18 +205,18 @@ pub fn expand_raw_existing(input: DeriveInput) -> syn::Result quote! { - #field_ident : ::std::option::Option::Some(::std::convert::TryFrom::try_from(value.#other_name)) + #field_ident : ::std::option::Option::Some( #from_method (#value_prefix value.#other_name)) }, (FieldWrapper::Vec, FieldWrapper::Optional) => Err( syn::Error::new_spanned(field_ident, "Can't cast Vec to Option"), )?, (FieldWrapper::Vec, FieldWrapper::Vec) => quote! { #field_ident : { - let mut buffer = std::vec::Vec::with_capacity(value.#other_name.len()); + let mut buffer = std::vec::Vec::with_capacity(#value_prefix value.#other_name.len()); for field in value.#other_name { - buffer.push( ::std::convert::TryFrom::try_from(field)?); + buffer.push( #from_method (field)?); } buffer @@ -176,7 +232,7 @@ pub fn expand_raw_existing(input: DeriveInput) -> syn::Result ::std::result::Result::Ok( ::std::convert::TryFrom::try_from(var)?), + ::std::option::Option::Some(var) => ::std::result::Result::Ok( #from_method (#value_prefix var)?), ::std::option::Option::None => ::std::result::Result::Err( #crate_prefix ::error::ProtobufError::MissingField( ::std::format!( "Missing field: {}", #other_name_str ))), }? } @@ -186,7 +242,7 @@ pub fn expand_raw_existing(input: DeriveInput) -> syn::Result quote! { - #field_ident : ::std::convert::TryFrom::try_from(value.#other_name)? + #field_ident : #from_method (#value_prefix value.#other_name)? }, }; @@ -198,7 +254,7 @@ pub fn expand_raw_existing(input: DeriveInput) -> syn::Result for #ident { + impl #impl_generics TryFrom<#raw> for #ident #ty_generics #where_clause { type Error = #crate_prefix ::error::ProtobufError; fn try_from(value: #raw) -> ::std::result::Result { diff --git a/macros/query-derive/src/lib.rs b/macros/query-derive/src/lib.rs index 537043a1e..eb5727d98 100644 --- a/macros/query-derive/src/lib.rs +++ b/macros/query-derive/src/lib.rs @@ -53,7 +53,12 @@ pub fn message_derive(input: TokenStream) -> TokenStream { } fn expand_macro(input: DeriveInput) -> syn::Result { - let DeriveInput { ident, data, .. } = &input; + let DeriveInput { + ident, + data, + generics, + .. + } = &input; let QueryAttr { kind, url, gears } = QueryAttr::from_derive_input(&input)?; let crate_prefix = match gears.is_present() { @@ -83,12 +88,14 @@ fn expand_macro(input: DeriveInput) -> syn::Result { } }; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + match data { syn::Data::Struct(_) => match kind { Kind::Request => { let url = match url { Some(url) => quote! { - impl #ident + impl #impl_generics #ident #ty_generics #where_clause { pub const QUERY_URL : &'static str = #url; } @@ -100,7 +107,7 @@ fn expand_macro(input: DeriveInput) -> syn::Result { }; let query_trait = quote! { - impl #crate_prefix ::baseapp::Query for #ident { + impl #impl_generics #crate_prefix ::baseapp::Query for #ident #ty_generics #where_clause { fn query_url(&self) -> &'static str { Self::QUERY_URL } @@ -122,7 +129,7 @@ fn expand_macro(input: DeriveInput) -> syn::Result { Kind::Response => { let url = match url { Some(_) => quote! { - impl #ident + impl #impl_generics #ident #ty_generics #where_clause { pub const QUERY_URL : &'static str = #url; } @@ -131,7 +138,7 @@ fn expand_macro(input: DeriveInput) -> syn::Result { }; let trait_impl = quote! { - impl #crate_prefix ::baseapp::QueryResponse for #ident { + impl #impl_generics #crate_prefix ::baseapp::QueryResponse for #ident #ty_generics #where_clause { fn into_bytes(self) -> std::vec::Vec { #crate_prefix ::core::Protobuf::encode_vec(&self) } @@ -176,7 +183,7 @@ fn expand_macro(input: DeriveInput) -> syn::Result { }); let gen = quote! { - impl #crate_prefix ::baseapp::Query for #ident { + impl #impl_generics #crate_prefix ::baseapp::Query for #ident #ty_generics #where_clause { fn query_url(&self) -> &'static str { match self { #(#query_url),* @@ -201,7 +208,7 @@ fn expand_macro(input: DeriveInput) -> syn::Result { }); let gen = quote! { - impl #crate_prefix ::baseapp::QueryResponse for #ident { + impl #impl_generics #crate_prefix ::baseapp::QueryResponse for #ident #ty_generics #where_clause { fn into_bytes(self) -> std::vec::Vec { match self { #(#into_bytes),* diff --git a/macros/tx-derive/src/enum_impl.rs b/macros/tx-derive/src/enum_impl.rs index 9961708cb..ac22634df 100644 --- a/macros/tx-derive/src/enum_impl.rs +++ b/macros/tx-derive/src/enum_impl.rs @@ -1,12 +1,13 @@ use darling::FromAttributes; use quote::quote; -use syn::{DataEnum, Ident}; +use syn::{DataEnum, Generics, Ident}; use crate::MessageAttr; pub fn expand_macro( DataEnum { variants, .. }: DataEnum, type_ident: Ident, + generics: Generics, crate_prefix: proc_macro2::TokenStream, ) -> syn::Result { let get_signers = variants.iter().map(|v| v.clone().ident).map(|i| { @@ -57,8 +58,10 @@ pub fn expand_macro( }) } + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let gen = quote! { - impl #crate_prefix::types::tx::TxMessage for #type_ident { + impl #impl_generics #crate_prefix::types::tx::TxMessage for #type_ident #ty_generics #where_clause { fn get_signers(&self) -> Vec<&#crate_prefix::types::address::AccAddress> { @@ -81,15 +84,15 @@ pub fn expand_macro( } - impl From<#type_ident> for #crate_prefix::core::any::google::Any { - fn from(msg: #type_ident) -> Self { + impl #impl_generics From<#type_ident #ty_generics> for #crate_prefix::core::any::google::Any #where_clause { + fn from(msg: #type_ident #ty_generics) -> Self { match msg { #(#into_any),* } } } - impl TryFrom<#crate_prefix::core::any::google::Any> for #type_ident { + impl #impl_generics TryFrom<#crate_prefix::core::any::google::Any> for #type_ident #ty_generics #where_clause { type Error = #crate_prefix::core::errors::CoreError; fn try_from(value: #crate_prefix::core::any::google::Any) -> Result { diff --git a/macros/tx-derive/src/lib.rs b/macros/tx-derive/src/lib.rs index 876b325c8..b7d3f6960 100644 --- a/macros/tx-derive/src/lib.rs +++ b/macros/tx-derive/src/lib.rs @@ -64,7 +64,12 @@ mod inner { url, amino_url, } = MessageArg::from_derive_input(&input)?; - let DeriveInput { ident, data, .. } = input; + let DeriveInput { + ident, + data, + generics, + .. + } = input; let crate_prefix = match gears.is_present() { true => quote! { crate }, @@ -73,9 +78,9 @@ mod inner { match data { syn::Data::Struct(data) => { - struct_impl::expand_macro(data, ident, crate_prefix, url, amino_url) + struct_impl::expand_macro(data, ident, generics, crate_prefix, url, amino_url) } - syn::Data::Enum(data) => enum_impl::expand_macro(data, ident, crate_prefix), + syn::Data::Enum(data) => enum_impl::expand_macro(data, ident, generics, crate_prefix), syn::Data::Union(_) => Err(syn::Error::new( proc_macro2::Span::call_site(), "TODO can't be derived for `Union`", diff --git a/macros/tx-derive/src/struct_impl.rs b/macros/tx-derive/src/struct_impl.rs index 1f9b1133d..8d10562d5 100644 --- a/macros/tx-derive/src/struct_impl.rs +++ b/macros/tx-derive/src/struct_impl.rs @@ -1,12 +1,13 @@ use darling::FromAttributes; use quote::quote; -use syn::{spanned::Spanned, DataStruct, Field, Ident}; +use syn::{spanned::Spanned, DataStruct, Field, Generics, Ident}; use crate::MessageAttr; pub fn expand_macro( DataStruct { fields, .. }: DataStruct, type_ident: Ident, + generics: Generics, crate_prefix: proc_macro2::TokenStream, url: String, amino_url: Option, @@ -33,19 +34,22 @@ pub fn expand_macro( None => (quote! {}, true), }; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let type_url_impl = quote! { - impl #type_ident + impl #impl_generics #type_ident #ty_generics #where_clause { pub const TYPE_URL : &'static str = #url; #amino_url } }; + let ty_generics_fish = ty_generics.as_turbofish(); let from_msg_to_any_impl = quote! { - impl ::std::convert::From<#type_ident> for #crate_prefix::core::any::google::Any { - fn from(msg: #type_ident) -> Self { + impl #impl_generics ::std::convert::From<#type_ident #ty_generics> for #crate_prefix::core::any::google::Any #where_clause { + fn from(msg: #type_ident #ty_generics) -> Self { #crate_prefix::core::any::google::Any { - type_url: #type_ident::TYPE_URL.to_string(), + type_url: #type_ident #ty_generics_fish ::TYPE_URL.to_string(), value: #crate_prefix::core::Protobuf::encode_vec(&msg), } } @@ -53,7 +57,7 @@ pub fn expand_macro( }; let try_from_any_to_msg_impl = quote! { - impl TryFrom<#crate_prefix::core::any::google::Any> for #type_ident { + impl #impl_generics TryFrom<#crate_prefix::core::any::google::Any> for #type_ident #ty_generics #where_clause { type Error = #crate_prefix::core::errors::CoreError; fn try_from(value: #crate_prefix::core::any::google::Any) -> ::std::result::Result { @@ -109,7 +113,7 @@ pub fn expand_macro( }; let tx_message_impl = quote! { - impl #crate_prefix::types::tx::TxMessage for #type_ident + impl #impl_generics #crate_prefix::types::tx::TxMessage for #type_ident #ty_generics #where_clause { #signers_impl diff --git a/x/gov/Cargo.toml b/x/gov/Cargo.toml index 0f0cc39c4..821d4e52d 100644 --- a/x/gov/Cargo.toml +++ b/x/gov/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] gears = { path = "../../gears", features = ["cli", "xmods", "governance" ] } +upgrade = { path = "../upgrade" } # unsorted anyhow = { workspace = true } diff --git a/x/gov/src/abci_handler.rs b/x/gov/src/abci_handler.rs index 7d701ad35..755d7cbf1 100644 --- a/x/gov/src/abci_handler.rs +++ b/x/gov/src/abci_handler.rs @@ -26,7 +26,9 @@ use gears::{ module::Module, }, }; +use serde::de::DeserializeOwned; +use crate::proposal::{Proposal, ProposalHandler}; use crate::{ errors::GovTxError, genesis::GovGenesisState, @@ -40,8 +42,7 @@ use crate::{ }, GovQuery, GovQueryResponse, }, - types::proposal::Proposal, - ProposalHandler, + types::proposal::ProposalModel, }; #[derive(Debug, Clone)] @@ -51,10 +52,11 @@ pub struct GovAbciHandler< M: Module, BK: GovernanceBankKeeper, STK: GovStakingKeeper, - PH: ProposalHandler, + P, + PH: ProposalHandler, SK>, MI, > { - keeper: GovKeeper, + keeper: GovKeeper, _marker: PhantomData, } @@ -64,11 +66,12 @@ impl< M: Module, BK: GovernanceBankKeeper, STK: GovStakingKeeper, - PH: ProposalHandler, + P, + PH: ProposalHandler, SK>, MI: ModuleInfo, - > GovAbciHandler + > GovAbciHandler { - pub fn new(keeper: GovKeeper) -> Self { + pub fn new(keeper: GovKeeper) -> Self { Self { keeper, _marker: PhantomData, @@ -82,19 +85,20 @@ impl< M: Module, BK: GovernanceBankKeeper, STK: GovStakingKeeper, - PH: ProposalHandler + Clone + Send + Sync + 'static, + P: Proposal + DeserializeOwned, + PH: ProposalHandler, SK> + Clone + Send + Sync + 'static, MI: ModuleInfo + Clone + Send + Sync + 'static, - > ABCIHandler for GovAbciHandler + > ABCIHandler for GovAbciHandler { type Message = GovMsg; - type Genesis = GovGenesisState; + type Genesis = GovGenesisState

; type StoreKey = SK; type QReq = GovQuery; - type QRes = GovQueryResponse; + type QRes = GovQueryResponse

; fn typed_query( &self, diff --git a/x/gov/src/client/cli/tx.rs b/x/gov/src/client/cli/tx.rs index 5c42fb5f0..392250f7a 100644 --- a/x/gov/src/client/cli/tx.rs +++ b/x/gov/src/client/cli/tx.rs @@ -56,6 +56,8 @@ pub struct ProposalCliCommand { pub enum ProposalCliSubcommand { Text(TextProposalCliCommand), ParamChange(ParamChangeProposalCliCommand), + SoftwareUpgrade(SoftwareUpgradeProposalCliCommand), + CancelSoftwareUpgrade(CancelSoftwareUpgradeProposalCliCommand), } #[derive(Args, Debug, Clone)] @@ -68,3 +70,14 @@ pub struct TextProposalCliCommand { pub struct ParamChangeProposalCliCommand { pub file: PathBuf, } + +#[derive(Args, Debug, Clone)] +pub struct SoftwareUpgradeProposalCliCommand { + pub file: PathBuf, +} + +#[derive(Args, Debug, Clone)] +pub struct CancelSoftwareUpgradeProposalCliCommand { + pub title: String, + pub description: String, +} diff --git a/x/gov/src/client/grpc.rs b/x/gov/src/client/grpc.rs index 306e2101c..e22cb2610 100644 --- a/x/gov/src/client/grpc.rs +++ b/x/gov/src/client/grpc.rs @@ -1,4 +1,7 @@ -use crate::query::{GovQuery, GovQueryResponse}; +use crate::{ + proposal::Proposal, + query::{GovQuery, GovQueryResponse}, +}; use gears::baseapp::{NodeQueryHandler, QueryRequest, QueryResponse}; use ibc_proto::cosmos::gov::v1beta1::{ query_server::{Query, QueryServer}, @@ -15,9 +18,9 @@ use tracing::info; const ERROR_STATE_MSG: &str = "An internal error occurred while querying the application state."; #[derive(Debug, Default)] -pub struct GovService { +pub struct GovService { app: QH, - _phantom: PhantomData<(QReq, QRes)>, + _phantom: PhantomData<(QReq, QRes, P)>, } #[tonic::async_trait] @@ -25,10 +28,11 @@ impl< QReq: Send + Sync + 'static, QRes: Send + Sync + 'static, QH: NodeQueryHandler, - > Query for GovService + P: Proposal, + > Query for GovService where QReq: QueryRequest + From, - QRes: QueryResponse + TryInto, + QRes: QueryResponse + TryInto, Error = Status>, { async fn proposal( &self, @@ -36,7 +40,7 @@ where ) -> Result, Status> { info!("Received a gRPC request gov::proposal"); let req = GovQuery::Proposal(request.into_inner().try_into()?); - let response: GovQueryResponse = self.app.typed_query(req)?.try_into()?; + let response: GovQueryResponse

= self.app.typed_query(req)?.try_into()?; if let GovQueryResponse::Proposal(response) = response { Ok(Response::new(response.into())) @@ -51,7 +55,7 @@ where ) -> Result, Status> { info!("Received a gRPC request gov::proposals"); let req = GovQuery::Proposals(request.into_inner().try_into()?); - let response: GovQueryResponse = self.app.typed_query(req)?.try_into()?; + let response: GovQueryResponse

= self.app.typed_query(req)?.try_into()?; if let GovQueryResponse::Proposals(response) = response { Ok(Response::new(response.into())) @@ -66,7 +70,7 @@ where ) -> Result, Status> { info!("Received a gRPC request gov::vote"); let req = GovQuery::Vote(request.into_inner().try_into()?); - let response: GovQueryResponse = self.app.typed_query(req)?.try_into()?; + let response: GovQueryResponse

= self.app.typed_query(req)?.try_into()?; if let GovQueryResponse::Vote(response) = response { Ok(Response::new(response.into())) @@ -81,7 +85,7 @@ where ) -> Result, Status> { info!("Received a gRPC request gov::votes"); let req = GovQuery::Votes(request.into_inner().try_into()?); - let response: GovQueryResponse = self.app.typed_query(req)?.try_into()?; + let response: GovQueryResponse

= self.app.typed_query(req)?.try_into()?; if let GovQueryResponse::Votes(response) = response { Ok(Response::new(response.into())) @@ -96,7 +100,7 @@ where ) -> Result, Status> { info!("Received a gRPC request gov::params"); let req = GovQuery::Params(request.into_inner().try_into()?); - let response: GovQueryResponse = self.app.typed_query(req)?.try_into()?; + let response: GovQueryResponse

= self.app.typed_query(req)?.try_into()?; if let GovQueryResponse::Params(response) = response { Ok(Response::new(response.into())) @@ -111,7 +115,7 @@ where ) -> Result, Status> { info!("Received a gRPC request gov::deposit"); let req = GovQuery::Deposit(request.into_inner().try_into()?); - let response: GovQueryResponse = self.app.typed_query(req)?.try_into()?; + let response: GovQueryResponse

= self.app.typed_query(req)?.try_into()?; if let GovQueryResponse::Deposit(response) = response { Ok(Response::new(response.into())) @@ -126,7 +130,7 @@ where ) -> Result, Status> { info!("Received a gRPC request gov::deposits"); let req = GovQuery::Deposits(request.into_inner().try_into()?); - let response: GovQueryResponse = self.app.typed_query(req)?.try_into()?; + let response: GovQueryResponse

= self.app.typed_query(req)?.try_into()?; if let GovQueryResponse::Deposits(response) = response { Ok(Response::new(response.into())) @@ -141,7 +145,7 @@ where ) -> Result, Status> { info!("Received a gRPC request gov::tally_result"); let req = GovQuery::Tally(request.into_inner().try_into()?); - let response: GovQueryResponse = self.app.typed_query(req)?.try_into()?; + let response: GovQueryResponse

= self.app.typed_query(req)?.try_into()?; if let GovQueryResponse::Tally(response) = response { Ok(Response::new(response.into())) @@ -151,10 +155,10 @@ where } } -pub fn new(app: QH) -> QueryServer> +pub fn new(app: QH) -> QueryServer> where QReq: QueryRequest + Send + Sync + 'static + From, - QRes: QueryResponse + Send + Sync + 'static + TryInto, + QRes: QueryResponse + Send + Sync + 'static + TryInto, Error = Status>, QH: NodeQueryHandler, { let gov_service = GovService { diff --git a/x/gov/src/client/mod.rs b/x/gov/src/client/mod.rs index 86b556b98..04511a7ec 100644 --- a/x/gov/src/client/mod.rs +++ b/x/gov/src/client/mod.rs @@ -1,8 +1,10 @@ +use std::marker::PhantomData; + pub mod cli; pub mod grpc; pub mod query_handler; pub mod rest; pub mod tx_handler; -#[derive(Debug, Clone)] -pub struct GovClientHandler; +#[derive(Debug, Clone, Default)] +pub struct GovClientHandler(PhantomData); diff --git a/x/gov/src/client/query_handler.rs b/x/gov/src/client/query_handler.rs index 80c635601..36bcbfe0b 100644 --- a/x/gov/src/client/query_handler.rs +++ b/x/gov/src/client/query_handler.rs @@ -1,18 +1,21 @@ use bytes::Bytes; use gears::{application::handlers::client::QueryHandler, core::Protobuf}; -use crate::query::{ - request::{ - QueryAllParamsRequest, QueryDepositRequest, QueryDepositsRequest, QueryParamsRequest, - QueryProposalRequest, QueryProposalsRequest, QueryProposerRequest, QueryTallyResultRequest, - QueryVoteRequest, QueryVotesRequest, +use crate::{ + proposal::Proposal, + query::{ + request::{ + QueryAllParamsRequest, QueryDepositRequest, QueryDepositsRequest, QueryParamsRequest, + QueryProposalRequest, QueryProposalsRequest, QueryProposerRequest, + QueryTallyResultRequest, QueryVoteRequest, QueryVotesRequest, + }, + response::{ + QueryAllParamsResponse, QueryDepositResponse, QueryParamsResponse, + QueryProposalResponse, QueryProposalsResponse, QueryProposerResponse, + QueryTallyResultResponse, QueryVoteResponse, QueryVotesResponse, + }, + GovQuery, GovQueryResponse, }, - response::{ - QueryAllParamsResponse, QueryDepositResponse, QueryParamsResponse, QueryProposalResponse, - QueryProposalsResponse, QueryProposerResponse, QueryTallyResultResponse, QueryVoteResponse, - QueryVotesResponse, - }, - GovQuery, GovQueryResponse, }; use super::{ @@ -20,12 +23,12 @@ use super::{ GovClientHandler, }; -impl QueryHandler for GovClientHandler { +impl QueryHandler for GovClientHandler { type QueryRequest = GovQuery; type QueryCommands = GovQueryCli; - type QueryResponse = GovQueryResponse; + type QueryResponse = GovQueryResponse; fn prepare_query_request( &self, diff --git a/x/gov/src/client/rest.rs b/x/gov/src/client/rest.rs index 035b203d2..9d83280cb 100644 --- a/x/gov/src/client/rest.rs +++ b/x/gov/src/client/rest.rs @@ -1,9 +1,12 @@ -use crate::query::{ - request::{ - ParamsQuery, QueryDepositsRequest, QueryParamsRequest, QueryProposalRequest, - QueryProposalsRequest, QueryTallyResultRequest, QueryVoteRequest, QueryVotesRequest, +use crate::{ + proposal::Proposal, + query::{ + request::{ + ParamsQuery, QueryDepositsRequest, QueryParamsRequest, QueryProposalRequest, + QueryProposalsRequest, QueryTallyResultRequest, QueryVoteRequest, QueryVotesRequest, + }, + GovQuery, GovQueryResponse, }, - GovQuery, GovQueryResponse, }; use axum::{ extract::{Path, Query, State}, @@ -18,8 +21,9 @@ use gears::{ pub async fn proposals< QReq: QueryRequest + From, - QRes: QueryResponse + TryInto, + QRes: QueryResponse + TryInto>, App: NodeQueryHandler, + P: Proposal, >( Query(pagination): Query, State(rest_state): State>, @@ -36,8 +40,9 @@ pub async fn proposals< pub async fn proposals_proposal_id< QReq: QueryRequest + From, - QRes: QueryResponse + TryInto, + QRes: QueryResponse + TryInto>, App: NodeQueryHandler, + P: Proposal, >( Path(proposal_id): Path, State(rest_state): State>, @@ -49,8 +54,9 @@ pub async fn proposals_proposal_id< pub async fn proposals_deposits< QReq: QueryRequest + From, - QRes: QueryResponse + TryInto, + QRes: QueryResponse + TryInto>, App: NodeQueryHandler, + P: Proposal, >( Path(proposal_id): Path, Query(pagination): Query, @@ -66,8 +72,9 @@ pub async fn proposals_deposits< pub async fn proposals_tally< QReq: QueryRequest + From, - QRes: QueryResponse + TryInto, + QRes: QueryResponse + TryInto>, App: NodeQueryHandler, + P: Proposal, >( Path(proposal_id): Path, State(rest_state): State>, @@ -79,8 +86,9 @@ pub async fn proposals_tally< pub async fn proposals_votes< QReq: QueryRequest + From, - QRes: QueryResponse + TryInto, + QRes: QueryResponse + TryInto>, App: NodeQueryHandler, + P: Proposal, >( Path(proposal_id): Path, Query(pagination): Query, @@ -96,8 +104,9 @@ pub async fn proposals_votes< pub async fn proposals_votes_voter< QReq: QueryRequest + From, - QRes: QueryResponse + TryInto, + QRes: QueryResponse + TryInto>, App: NodeQueryHandler, + P: Proposal, >( Path((proposal_id, voter)): Path<(u64, AccAddress)>, State(rest_state): State>, @@ -109,8 +118,9 @@ pub async fn proposals_votes_voter< pub async fn params_voting< QReq: QueryRequest + From, - QRes: QueryResponse + TryInto, + QRes: QueryResponse + TryInto>, App: NodeQueryHandler, + P: Proposal, >( State(rest_state): State>, ) -> Result, HTTPError> { @@ -123,8 +133,9 @@ pub async fn params_voting< pub async fn params_tally< QReq: QueryRequest + From, - QRes: QueryResponse + TryInto, + QRes: QueryResponse + TryInto>, App: NodeQueryHandler, + P: Proposal, >( State(rest_state): State>, ) -> Result, HTTPError> { @@ -137,8 +148,9 @@ pub async fn params_tally< pub async fn params_deposit< QReq: QueryRequest + From, - QRes: QueryResponse + TryInto, + QRes: QueryResponse + TryInto>, App: NodeQueryHandler, + P: Proposal, >( State(rest_state): State>, ) -> Result, HTTPError> { @@ -151,8 +163,9 @@ pub async fn params_deposit< pub fn get_router< QReq: QueryRequest + From, - QRes: QueryResponse + TryInto, + QRes: QueryResponse + TryInto>, App: NodeQueryHandler, + P: Proposal, >() -> Router> { Router::new() .route("/v1beta1/proposals", get(proposals)) diff --git a/x/gov/src/client/tx_handler.rs b/x/gov/src/client/tx_handler.rs index b03fad144..fa7d06f19 100644 --- a/x/gov/src/client/tx_handler.rs +++ b/x/gov/src/client/tx_handler.rs @@ -15,12 +15,19 @@ use crate::{ deposit::Deposit, proposal::MsgSubmitProposal, vote::Vote, weighted_vote::MsgVoteWeighted, GovMsg, }, - submission::{param::RawParameterChangeProposal, text::TextProposal}, + proposal::{ + param::RawParameterChangeProposal, + text::TextProposal, + upgrade::{CancelSoftwareUpgradeProposal, SoftwareUpgradeProposal}, + }, }; -use super::GovClientHandler; +use super::{ + cli::tx::{CancelSoftwareUpgradeProposalCliCommand, SoftwareUpgradeProposalCliCommand}, + GovClientHandler, +}; -impl TxHandler for GovClientHandler { +impl TxHandler for GovClientHandler { type Message = GovMsg; type TxCommands = GovTxCli; @@ -79,6 +86,23 @@ impl TxHandler for GovClientHandler { proposer: pubkey.get_address(), }) } + ProposalCliSubcommand::SoftwareUpgrade(SoftwareUpgradeProposalCliCommand { + file, + }) => GovMsg::Proposal(MsgSubmitProposal { + content: serde_json::from_slice::(&std::fs::read( + file, + )?)? + .into(), + initial_deposit, + proposer: pubkey.get_address(), + }), + ProposalCliSubcommand::CancelSoftwareUpgrade( + CancelSoftwareUpgradeProposalCliCommand { title, description }, + ) => GovMsg::Proposal(MsgSubmitProposal { + content: CancelSoftwareUpgradeProposal { title, description }.into(), + initial_deposit, + proposer: pubkey.get_address(), + }), }, }; Ok(command.into()) diff --git a/x/gov/src/errors.rs b/x/gov/src/errors.rs index bdf3f73b1..e063889d0 100644 --- a/x/gov/src/errors.rs +++ b/x/gov/src/errors.rs @@ -43,4 +43,6 @@ pub enum GovKeeperError { Gas(#[from] GasStoreErrors), #[error("{0}")] Time(String), + #[error("{0}")] + Custom(String), } diff --git a/x/gov/src/genesis.rs b/x/gov/src/genesis.rs index 73a31abc4..025ee1e40 100644 --- a/x/gov/src/genesis.rs +++ b/x/gov/src/genesis.rs @@ -2,24 +2,26 @@ use gears::{ baseapp::genesis::{Genesis, GenesisError}, types::{address::AccAddress, base::coins::UnsignedCoins}, }; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::{ msg::{deposit::Deposit, weighted_vote::MsgVoteWeighted}, params::GovParams, - types::proposal::Proposal, + types::proposal::ProposalModel, }; #[derive(Debug, Clone, Deserialize, Serialize)] -pub struct GovGenesisState { +pub struct GovGenesisState { pub starting_proposal_id: u64, pub deposits: Vec, pub votes: Vec, - pub proposals: Vec, + pub proposals: Vec>, pub params: GovParams, } -impl Genesis for GovGenesisState { +impl Genesis + for GovGenesisState

+{ fn add_genesis_account( &mut self, _address: AccAddress, @@ -29,7 +31,7 @@ impl Genesis for GovGenesisState { } } -impl Default for GovGenesisState { +impl Default for GovGenesisState { fn default() -> Self { Self { starting_proposal_id: 1, diff --git a/x/gov/src/keeper.rs b/x/gov/src/keeper.rs index b07932f72..9eb6c3854 100644 --- a/x/gov/src/keeper.rs +++ b/x/gov/src/keeper.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, marker::PhantomData, ops::Mul}; +use gears::core::errors::CoreError; use gears::extensions::gas::GasResultExt; use gears::{ application::keepers::params::ParamsKeeper, @@ -21,8 +22,11 @@ use gears::{ types::{delegation::StakingDelegation, validator::StakingValidator}, }, }; +use serde::de::DeserializeOwned; +use serde::Serialize; use strum::IntoEnumIterator; +use crate::proposal::{Proposal, ProposalHandler}; use crate::{ errors::{GovKeeperError, TallyError, SERDE_JSON_CONVERSION}, genesis::GovGenesisState, @@ -49,13 +53,12 @@ use crate::{ types::{ deposit_iter::DepositIterator, proposal::{ - active_iter::ActiveProposalIterator, inactive_iter::InactiveProposalIterator, Proposal, - ProposalStatus, ProposalsIterator, TallyResult, + active_iter::ActiveProposalIterator, inactive_iter::InactiveProposalIterator, + ProposalModel, ProposalStatus, ProposalsIterator, TallyResult, }, validator::ValidatorGovInfo, vote_iters::WeightedVoteIterator, }, - ProposalHandler, }; const PROPOSAL_ID_KEY: [u8; 1] = [0x03]; @@ -68,15 +71,17 @@ pub struct GovKeeper< M: Module, BK: GovernanceBankKeeper, STK: GovStakingKeeper, - PH: ProposalHandler, + P, + PH: ProposalHandler, SK>, > { store_key: SK, gov_params_keeper: GovParamsKeeper, gov_mod: M, bank_keeper: BK, staking_keeper: STK, - _bank_marker: PhantomData, proposal_handler: PH, + + _marker: PhantomData<(P, BK)>, } impl< @@ -85,8 +90,9 @@ impl< M: Module, BK: GovernanceBankKeeper, STK: GovStakingKeeper, - PH: ProposalHandler, - > GovKeeper + P: Proposal + DeserializeOwned, + PH: ProposalHandler, SK>, + > GovKeeper { pub fn new( store_key: SK, @@ -104,8 +110,8 @@ impl< gov_mod, bank_keeper, staking_keeper, - _bank_marker: PhantomData, proposal_handler, + _marker: PhantomData, } } @@ -118,7 +124,7 @@ impl< votes, proposals, params, - }: GovGenesisState, + }: GovGenesisState

, ) { { let mut store = ctx.kv_store_mut(&self.store_key); @@ -153,7 +159,7 @@ impl< match proposal.status { ProposalStatus::DepositPeriod => { store_mut.set( - Proposal::inactive_queue_key( + ProposalModel::

::inactive_queue_key( proposal.proposal_id, &proposal.deposit_end_time, ), @@ -161,7 +167,7 @@ impl< ); } ProposalStatus::VotingPeriod => store_mut.set( - Proposal::active_queue_key( + ProposalModel::

::active_queue_key( proposal.proposal_id, &proposal.deposit_end_time, ), @@ -205,7 +211,7 @@ impl< &self, ctx: &CTX, query: GovQuery, - ) -> Result { + ) -> Result, GasStoreErrors> { let result = match query { GovQuery::Deposit(QueryDepositRequest { proposal_id, @@ -307,7 +313,7 @@ impl< }) } GovQuery::Tally(QueryTallyResultRequest { proposal_id }) => { - let proposal = proposal_get(ctx, &self.store_key, proposal_id)?; + let proposal = proposal_get::<_, _, _, P>(ctx, &self.store_key, proposal_id)?; GovQueryResponse::Tally(QueryTallyResultResponse { tally: proposal.and_then(|this| this.final_tally_result), @@ -347,7 +353,7 @@ impl< amount, }: Deposit, ) -> Result { - let mut proposal = proposal_get(ctx, &self.store_key, proposal_id)? + let mut proposal = proposal_get::<_, _, _, P>(ctx, &self.store_key, proposal_id)? .ok_or(GovKeeperError::ProposalUnknown(proposal_id))?; match proposal.status { @@ -417,7 +423,7 @@ impl< ctx: &mut TxContext<'_, DB, SK>, vote: MsgVoteWeighted, ) -> Result<(), GovKeeperError> { - let proposal = proposal_get(ctx, &self.store_key, vote.proposal_id)? + let proposal = proposal_get::<_, _, _, P>(ctx, &self.store_key, vote.proposal_id)? .ok_or(GovKeeperError::ProposalUnknown(vote.proposal_id))?; match proposal.status { @@ -461,9 +467,11 @@ impl< .deposit .max_deposit_period; - let proposal = Proposal { + let proposal = ProposalModel { proposal_id, - content, + content: content + .try_into() + .map_err(|e: CoreError| GovKeeperError::Custom(e.to_string()))?, // TODO: Better way. Generic or smth else status: ProposalStatus::DepositPeriod, final_tally_result: None, submit_time, @@ -483,7 +491,10 @@ impl< let mut store = ctx.kv_store_mut(&self.store_key); store.set( - Proposal::inactive_queue_key(proposal.proposal_id, &proposal.deposit_end_time), + ProposalModel::

::inactive_queue_key( + proposal.proposal_id, + &proposal.deposit_end_time, + ), proposal.proposal_id.to_be_bytes(), )?; @@ -509,14 +520,14 @@ impl< { let inactive_iter = { let store = ctx.kv_store(&self.store_key); - InactiveProposalIterator::new(store.into(), &ctx.header.time) + InactiveProposalIterator::<'_, _, P>::new(store.into(), &ctx.header.time) .map(|this| this.map(|((proposal_id, _), _)| proposal_id)) .collect::>() }; for var in inactive_iter { let proposal_id = var.unwrap_gas(); - proposal_del(ctx, &self.store_key, proposal_id).unwrap_gas(); + proposal_del::<_, _, _, P>(ctx, &self.store_key, proposal_id).unwrap_gas(); deposit_del(ctx, self, proposal_id).unwrap_gas(); // TODO: HOOK https://github.com/cosmos/cosmos-sdk/blob/d3f09c222243bb3da3464969f0366330dcb977a8/x/gov/abci.go#L24-L25 @@ -566,7 +577,7 @@ impl< } match passes { - true if self.proposal_handler.handle(&proposal, ctx).is_ok() => { + true if self.proposal_handler.handle(proposal.clone(), ctx).is_ok() => { proposal.status = ProposalStatus::Passed } true => proposal.status = ProposalStatus::Failed, @@ -577,7 +588,7 @@ impl< proposal_set(ctx, &self.store_key, &proposal).unwrap_gas(); ctx.kv_store_mut(&self.store_key) - .delete(&Proposal::active_queue_key( + .delete(&ProposalModel::

::active_queue_key( proposal.proposal_id, &proposal.deposit_end_time, )); @@ -817,11 +828,11 @@ fn proposal_id_get>( )) } -fn proposal_get>( +fn proposal_get, P: DeserializeOwned>( ctx: &CTX, store_key: &SK, proposal_id: u64, -) -> Result, GasStoreErrors> { +) -> Result>, GasStoreErrors> { let key = [KEY_PROPOSAL_PREFIX.as_slice(), &proposal_id.to_be_bytes()].concat(); let store = ctx.kv_store(store_key); @@ -835,10 +846,15 @@ fn proposal_get>( } } -fn proposal_set>( +fn proposal_set< + DB: Database, + SK: StoreKey, + CTX: TransactionalContext, + P: DeserializeOwned + Serialize, +>( ctx: &mut CTX, store_key: &SK, - proposal: &Proposal, + proposal: &ProposalModel

, ) -> Result<(), GasStoreErrors> { let mut store = ctx.kv_store_mut(store_key); @@ -848,22 +864,27 @@ fn proposal_set>( ) } -fn proposal_del>( +fn proposal_del< + DB: Database, + SK: StoreKey, + CTX: TransactionalContext, + P: DeserializeOwned, +>( ctx: &mut CTX, store_key: &SK, proposal_id: u64, ) -> Result { - let proposal = proposal_get(ctx, store_key, proposal_id)?; + let proposal = proposal_get::<_, _, _, P>(ctx, store_key, proposal_id)?; if let Some(proposal) = proposal { let mut store = ctx.kv_store_mut(store_key); - store.delete(&Proposal::inactive_queue_key( + store.delete(&ProposalModel::

::inactive_queue_key( proposal_id, &proposal.deposit_end_time, ))?; - store.delete(&Proposal::active_queue_key( + store.delete(&ProposalModel::

::active_queue_key( proposal_id, &proposal.deposit_end_time, ))?; @@ -922,10 +943,11 @@ fn deposit_del< BK: GovernanceBankKeeper, STK: GovStakingKeeper, CTX: TransactionalContext, - PH: ProposalHandler, + P: Proposal, + PH: ProposalHandler, SK>, >( ctx: &mut CTX, - keeper: &GovKeeper, + keeper: &GovKeeper, proposal_id: u64, ) -> Result<(), GasStoreErrors> { let deposits = DepositIterator::new(ctx.kv_store(&keeper.store_key)) @@ -955,10 +977,11 @@ fn deposit_refund< BK: GovernanceBankKeeper, STK: GovStakingKeeper, CTX: TransactionalContext, - PH: ProposalHandler, + P: Proposal, + PH: ProposalHandler, SK>, >( ctx: &mut CTX, - keeper: &GovKeeper, + keeper: &GovKeeper, ) -> Result<(), GasStoreErrors> { for deposit in DepositIterator::new(ctx.kv_store(&keeper.store_key)) .map(|this| this.map(|(_, val)| val)) diff --git a/x/gov/src/lib.rs b/x/gov/src/lib.rs index 1b89a9c00..68578828b 100644 --- a/x/gov/src/lib.rs +++ b/x/gov/src/lib.rs @@ -1,27 +1,10 @@ -pub mod client; -use gears::{ - context::InfallibleContextMut, - params::ParamsSubspaceKey, - store::{database::Database, StoreKey}, -}; -use submission::handler::SubmissionHandlingError; - pub mod abci_handler; +pub mod client; pub mod errors; pub mod genesis; pub mod keeper; pub mod msg; pub mod params; +pub mod proposal; pub mod query; -pub mod submission; pub mod types; - -pub trait ProposalHandler { - fn handle, DB: Database, SK: StoreKey>( - &self, - proposal: &P, - ctx: &mut CTX, - ) -> Result<(), SubmissionHandlingError>; - - fn check(proposal: &P) -> bool; -} diff --git a/x/gov/src/msg/proposal.rs b/x/gov/src/msg/proposal.rs index 507f4f72b..3a334ba0e 100644 --- a/x/gov/src/msg/proposal.rs +++ b/x/gov/src/msg/proposal.rs @@ -16,7 +16,7 @@ mod inner { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct MsgSubmitProposal { - pub content: Any, + pub content: Any, // TODO: Generic? pub initial_deposit: UnsignedCoins, pub proposer: AccAddress, } diff --git a/x/gov/src/proposal/handler.rs b/x/gov/src/proposal/handler.rs new file mode 100644 index 000000000..0a5ef1091 --- /dev/null +++ b/x/gov/src/proposal/handler.rs @@ -0,0 +1,31 @@ +use gears::context::InfallibleContextMut; +use gears::core::errors::CoreError; +use gears::store::database::Database; +use gears::store::StoreKey; +use gears::types::store::gas::errors::GasStoreErrors; + +pub trait ProposalHandler { + fn handle, DB: Database>( + &self, + proposal: P, + ctx: &mut CTX, + ) -> Result<(), ProposalHandlingError>; + + fn check(proposal: &P) -> bool; +} + +#[derive(Debug, thiserror::Error)] +pub enum ProposalHandlingError { + #[error("Can't handle this proposal: decoding error")] + Decode(#[from] CoreError), + #[error("Can't handle this proposal: not supported subspace")] + Subspace, + #[error("Can't handle this proposal: no such keys in subspace")] + KeyNotFound, + #[error("Can't handle this proposal: invalid bytes")] + InvalidProposal, + #[error("Can't handle this proposal: {0}")] + Gas(#[from] GasStoreErrors), + #[error("{0}")] + Other(String), +} diff --git a/x/gov/src/proposal/mod.rs b/x/gov/src/proposal/mod.rs new file mode 100644 index 000000000..6cee63e63 --- /dev/null +++ b/x/gov/src/proposal/mod.rs @@ -0,0 +1,106 @@ +mod handler; +pub mod param; +pub mod text; +pub mod upgrade; + +use ::upgrade::{keeper::UpgradeKeeper, UpgradeHandler}; +use gears::{ + application::keepers::params::ParamsKeeper, core::errors::CoreError, derive::AppMessage, + params::ParamsSubspaceKey, store::StoreKey, +}; +pub use handler::*; +use ibc_proto::google::protobuf::Any; +use param::{ParamChangeProposalHandler, ParameterChangeProposal}; +use serde::Serialize; +use text::TextProposal; +use upgrade::{CancelSoftwareUpgradeProposal, SoftwareUpgradeProposal, UpgradeProposalHandler}; + +pub trait Proposal: + Clone + + std::fmt::Debug + + Send + + Sync + + serde::Serialize + + TryFrom + + Into + + 'static +{ +} + +#[derive(Debug, Clone, AppMessage)] +pub enum Proposals { + #[msg(url(path = TextProposal::TYPE_URL))] + Text(TextProposal), + #[msg(url(path = ParameterChangeProposal::::TYPE_URL))] + Params(ParameterChangeProposal), + #[msg(url(path = SoftwareUpgradeProposal::TYPE_URL))] + Upgrade(SoftwareUpgradeProposal), + #[msg(url(path = CancelSoftwareUpgradeProposal::TYPE_URL))] + CancelUpgrade(CancelSoftwareUpgradeProposal), +} + +impl Serialize for Proposals { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Proposals::Text(inner) => inner.serialize(serializer), + Proposals::Params(inner) => inner.serialize(serializer), + Proposals::Upgrade(inner) => inner.serialize(serializer), + Proposals::CancelUpgrade(inner) => inner.serialize(serializer), + } + } +} + +impl Proposal for Proposals {} + +pub struct ProposalsHandler { + params_handler: ParamChangeProposalHandler, + upgrade_handler: UpgradeProposalHandler, +} + +impl ProposalsHandler { + pub fn new(keeper: UpgradeKeeper) -> Self { + Self { + params_handler: ParamChangeProposalHandler::new(), + upgrade_handler: UpgradeProposalHandler::new(keeper), + } + } +} + +impl< + PSK: ParamsSubspaceKey, + SK: StoreKey, + PK: ParamsKeeper, + M: ::upgrade::Module, + UH: UpgradeHandler, + > ProposalHandler, SK> for ProposalsHandler +where + >>::Error: std::fmt::Display + std::fmt::Debug, +{ + fn handle< + CTX: gears::context::InfallibleContextMut, + DB: gears::store::database::Database, + >( + &self, + proposal: Proposals, + ctx: &mut CTX, + ) -> Result<(), ProposalHandlingError> { + match proposal { + Proposals::Text(_) => Ok(()), + Proposals::Params(proposal) => self.params_handler.handle(proposal, ctx), + Proposals::Upgrade(proposal) => self.upgrade_handler.handle(proposal, ctx), + Proposals::CancelUpgrade(proposal) => self.upgrade_handler.handle(proposal, ctx), + } + } + + fn check(proposal: &Proposals) -> bool { + match proposal { + Proposals::Params(proposal) => { + ParamChangeProposalHandler::::check(proposal) + } + _ => true, + } + } +} diff --git a/x/gov/src/proposal/param.rs b/x/gov/src/proposal/param.rs new file mode 100644 index 000000000..0725fca98 --- /dev/null +++ b/x/gov/src/proposal/param.rs @@ -0,0 +1,139 @@ +use std::marker::PhantomData; + +use gears::{ + application::keepers::params::ParamsKeeper, + derive::{AppMessage, Protobuf, Raw}, + params::ParamsSubspaceKey, + store::StoreKey, +}; +use ibc_proto::google::protobuf::Any; +use prost::Message; +use serde::{Deserialize, Serialize}; + +use super::handler::{ProposalHandler, ProposalHandlingError}; + +#[derive(Debug, Clone, PartialEq, Raw, Protobuf, AppMessage)] +#[raw(derive(Serialize, Deserialize, Clone, PartialEq))] +#[msg(url = "/cosmos.params.v1beta1/ParamChange")] +pub struct ParamChange { + #[raw(kind(string), raw = String)] + #[proto( + from = "PSK::from_subspace_str", + from_ref, + into = "PSK::name", + into_ref + )] + pub subspace: PSK, + #[raw(kind(bytes))] + #[proto(repeated)] + pub key: Vec, + #[raw(kind(bytes))] + #[proto(repeated)] + pub value: Vec, +} + +// Serde macro slightly dumb for such cases so I did it myself +impl serde::Serialize for ParamChange { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + RawParamChange::from(self.clone()).serialize(serializer) + } +} + +#[derive(Debug, Clone, PartialEq, Raw, Protobuf, AppMessage)] +#[raw(derive(Serialize, Deserialize, Clone, PartialEq))] +#[msg(url = "/cosmos.params.v1beta1/ParameterChangeProposal")] +pub struct ParameterChangeProposal { + #[raw(kind(string), raw = String)] + pub title: String, + #[raw(kind(string), raw = String)] + pub description: String, + #[raw(kind(message), raw = RawParamChange, repeated)] + #[proto(repeated)] + pub changes: Vec>, +} + +impl Serialize for ParameterChangeProposal { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + RawParameterChangeProposal::from(self.clone()).serialize(serializer) + } +} + +impl From for Any { + fn from(msg: RawParameterChangeProposal) -> Self { + Any { + type_url: "/cosmos.params.v1beta1/ParameterChangeProposal".to_owned(), + value: msg.encode_to_vec(), + } + } +} + +#[derive(Debug)] +pub struct ParamChangeProposalHandler(PhantomData<(PK, SK, PSK)>); + +impl Default for ParamChangeProposalHandler { + fn default() -> Self { + Self::new() + } +} + +impl ParamChangeProposalHandler { + pub fn new() -> Self { + Self(PhantomData) + } +} + +impl, SK: StoreKey> + ProposalHandler, SK> for ParamChangeProposalHandler +{ + fn handle< + CTX: gears::context::InfallibleContextMut, + DB: gears::store::database::Database, + >( + &self, + ParameterChangeProposal { + title: _, + description: _, + changes, + }: ParameterChangeProposal, + ctx: &mut CTX, + ) -> Result<(), super::handler::ProposalHandlingError> { + for ParamChange { + subspace, + key, + value, + } in changes + { + if !PK::check_key(&key) { + Err(ProposalHandlingError::KeyNotFound)? + } + + if !PK::validate(&key, &value) { + Err(ProposalHandlingError::InvalidProposal)? + } + + let mut store = gears::params::gas::subspace_mut(ctx, &subspace); + + store.raw_key_set(key, value)?; + } + + Ok(()) + } + + fn check( + ParameterChangeProposal { + title: _, + description: _, + changes, + }: &ParameterChangeProposal, + ) -> bool { + changes + .iter() + .all(|this| PK::check_key(&this.key) && PK::validate(&this.key, &this.value)) + } +} diff --git a/x/gov/src/proposal/text.rs b/x/gov/src/proposal/text.rs new file mode 100644 index 000000000..ee9aaea23 --- /dev/null +++ b/x/gov/src/proposal/text.rs @@ -0,0 +1,39 @@ +use std::marker::PhantomData; + +use gears::{ + context::InfallibleContextMut, + derive::{AppMessage, Protobuf}, + store::StoreKey, +}; +use serde::{Deserialize, Serialize}; + +use super::handler::{ProposalHandler, ProposalHandlingError}; + +mod inner { + pub use ibc_proto::cosmos::gov::v1beta1::TextProposal; +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Protobuf, AppMessage)] +#[proto(raw = "inner::TextProposal")] +#[msg(url = "/cosmos.params.v1beta1/TextProposal")] +pub struct TextProposal { + pub title: String, + pub description: String, +} + +#[derive(Debug, Default)] +pub struct TextSubmissionHandler(PhantomData); + +impl ProposalHandler for TextSubmissionHandler { + fn handle, DB: gears::store::database::Database>( + &self, + _proposal: TextProposal, + _ctx: &mut CTX, + ) -> Result<(), ProposalHandlingError> { + Ok(()) + } + + fn check(_proposal: &TextProposal) -> bool { + true + } +} diff --git a/x/gov/src/proposal/upgrade.rs b/x/gov/src/proposal/upgrade.rs new file mode 100644 index 000000000..5e015846c --- /dev/null +++ b/x/gov/src/proposal/upgrade.rs @@ -0,0 +1,125 @@ +use gears::{ + context::InfallibleContextMut, + derive::{AppMessage, Protobuf}, + store::StoreKey, +}; +use serde::{Deserialize, Serialize}; +use upgrade::{keeper::UpgradeKeeper, types::plan::Plan, Module, UpgradeHandler}; + +use super::handler::{ProposalHandler, ProposalHandlingError}; + +mod inner { + + // pub use ibc_proto::cosmos::upgrade::v1beta1::MsgCancelUpgrade; + // pub use ibc_proto::cosmos::upgrade::v1beta1::MsgSoftwareUpgrade; + // Deprecated, but we need to use it + pub use ibc_proto::cosmos::upgrade::v1beta1::CancelSoftwareUpgradeProposal; + pub use ibc_proto::cosmos::upgrade::v1beta1::SoftwareUpgradeProposal; +} + +// #[derive(Debug, Clone, Protobuf, Serialize, Deserialize, AppMessage)] +// #[proto(raw = "inner::MsgCancelUpgrade")] +// #[serde(try_from = "inner::MsgCancelUpgrade", into = "inner::MsgCancelUpgrade")] +// #[msg(url = "/cosmos.upgrade.v1beta1/MsgCancelUpgrade")] +// pub struct MsgCancelUpgrade { +// pub authority: AccAddress, +// } + +// #[derive(Debug, Clone, Protobuf, Serialize, Deserialize, AppMessage)] +// #[proto(raw = "inner::MsgSoftwareUpgrade")] +// #[serde( +// try_from = "inner::MsgSoftwareUpgrade", +// into = "inner::MsgSoftwareUpgrade" +// )] +// #[msg(url = "/cosmos.upgrade.v1beta1/MsgSoftwareUpgrade")] +// pub struct MsgSoftwareUpgrade { +// pub authority: AccAddress, +// #[proto(optional)] +// pub plan: Plan, +// } + +#[derive(Debug, Clone, Protobuf, Serialize, Deserialize, AppMessage)] +#[proto(raw = "inner::CancelSoftwareUpgradeProposal")] +#[serde( + try_from = "inner::CancelSoftwareUpgradeProposal", + into = "inner::CancelSoftwareUpgradeProposal" +)] +#[msg(url = "/cosmos.upgrade.v1beta1/CancelSoftwareUpgradeProposal")] +pub struct CancelSoftwareUpgradeProposal { + pub title: String, + pub description: String, +} + +#[derive(Debug, Clone, Protobuf, Serialize, Deserialize, AppMessage)] +#[proto(raw = "inner::SoftwareUpgradeProposal")] +#[serde( + try_from = "inner::SoftwareUpgradeProposal", + into = "inner::SoftwareUpgradeProposal" +)] +#[msg(url = "/cosmos.upgrade.v1beta1/SoftwareUpgradeProposal")] +pub struct SoftwareUpgradeProposal { + pub title: String, + pub description: String, + #[proto(optional)] + pub plan: Plan, +} + +#[derive(Debug)] +pub struct UpgradeProposalHandler { + keeper: UpgradeKeeper, +} + +impl UpgradeProposalHandler { + pub fn new(keeper: UpgradeKeeper) -> Self { + Self { keeper } + } +} + +impl ProposalHandler + for UpgradeProposalHandler +where + >>::Error: std::fmt::Display + std::fmt::Debug, +{ + fn handle, DB: gears::store::database::Database>( + &self, + SoftwareUpgradeProposal { + plan, + title: _, + description: _, + }: SoftwareUpgradeProposal, + ctx: &mut CTX, + ) -> Result<(), ProposalHandlingError> { + self.keeper + .schedule_upgrade(ctx, plan, true) + .map_err(|e| ProposalHandlingError::Other(e.to_string()))?; + + Ok(()) + } + + fn check(_proposal: &SoftwareUpgradeProposal) -> bool { + true + } +} + +impl ProposalHandler + for UpgradeProposalHandler +where + >>::Error: std::fmt::Display + std::fmt::Debug, +{ + fn handle, DB: gears::store::database::Database>( + &self, + CancelSoftwareUpgradeProposal { + title: _, + description: _, + }: CancelSoftwareUpgradeProposal, + ctx: &mut CTX, + ) -> Result<(), ProposalHandlingError> { + self.keeper.delete_upgrade_plan(ctx); + + Ok(()) + } + + fn check(_proposal: &CancelSoftwareUpgradeProposal) -> bool { + true + } +} diff --git a/x/gov/src/query/mod.rs b/x/gov/src/query/mod.rs index f30bf9052..a242a4e01 100644 --- a/x/gov/src/query/mod.rs +++ b/x/gov/src/query/mod.rs @@ -11,6 +11,8 @@ use response::{ }; use serde::{Deserialize, Serialize}; +use crate::proposal::Proposal; + pub mod request; pub mod response; @@ -37,13 +39,13 @@ impl QueryRequest for GovQuery { #[derive(Clone, PartialEq, Serialize, Deserialize, Debug, Query)] #[serde(untagged)] -pub enum GovQueryResponse { +pub enum GovQueryResponse { Deposit(QueryDepositResponse), Deposits(QueryDepositsResponse), Params(QueryParamsResponse), AllParams(QueryAllParamsResponse), - Proposal(QueryProposalResponse), - Proposals(QueryProposalsResponse), + Proposal(QueryProposalResponse), + Proposals(QueryProposalsResponse), Tally(QueryTallyResultResponse), Vote(QueryVoteResponse), Votes(QueryVotesResponse), diff --git a/x/gov/src/query/response.rs b/x/gov/src/query/response.rs index 47a46c048..5a9604b06 100644 --- a/x/gov/src/query/response.rs +++ b/x/gov/src/query/response.rs @@ -1,7 +1,7 @@ use gears::{ - core::errors::CoreError, - core::Protobuf, + core::{errors::CoreError, Protobuf}, derive::{Protobuf, Query}, + error::ProtobufError, types::{address::AccAddress, pagination::response::PaginationResponse}, }; use serde::{Deserialize, Serialize}; @@ -9,7 +9,8 @@ use serde::{Deserialize, Serialize}; use crate::{ msg::{deposit::Deposit, weighted_vote::MsgVoteWeighted}, params::{DepositParams, TallyParams, VotingParams}, - types::proposal::{Proposal, TallyResult}, + proposal::Proposal, + types::proposal::{ProposalModel, TallyResult}, }; mod inner { @@ -27,20 +28,20 @@ mod inner { #[derive(Clone, PartialEq, Serialize, Deserialize, Debug, Query, Protobuf)] #[proto(raw = "inner::QueryProposalResponse")] -pub struct QueryProposalResponse { +pub struct QueryProposalResponse { #[proto(optional)] - pub proposal: Option, + pub proposal: Option>, } #[derive(Clone, PartialEq, Serialize, Deserialize, Debug, Query)] // #[query(raw = "inner::QueryProposalsResponse")] -pub struct QueryProposalsResponse { - pub proposals: Vec, +pub struct QueryProposalsResponse { + pub proposals: Vec>, pub pagination: Option, } -impl TryFrom for QueryProposalsResponse { - type Error = CoreError; +impl TryFrom for QueryProposalsResponse { + type Error = ProtobufError; fn try_from( inner::QueryProposalsResponse { @@ -63,12 +64,12 @@ impl TryFrom for QueryProposalsResponse { } } -impl From for inner::QueryProposalsResponse { +impl From> for inner::QueryProposalsResponse { fn from( QueryProposalsResponse { proposals, pagination, - }: QueryProposalsResponse, + }: QueryProposalsResponse, ) -> Self { Self { proposals: proposals.into_iter().map(|this| this.into()).collect(), @@ -77,7 +78,7 @@ impl From for inner::QueryProposalsResponse { } } -impl Protobuf for QueryProposalsResponse {} +impl Protobuf for QueryProposalsResponse {} #[derive(Clone, PartialEq, Serialize, Deserialize, Debug, Query, Protobuf)] #[proto(raw = "inner::QueryVoteResponse")] diff --git a/x/gov/src/submission/handler.rs b/x/gov/src/submission/handler.rs deleted file mode 100644 index b89d457ce..000000000 --- a/x/gov/src/submission/handler.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::marker::PhantomData; - -use gears::context::TransactionalContext; -use gears::core::errors::CoreError; -use gears::params::gas::subspace_mut; -use gears::store::database::Database; -use gears::store::StoreKey; - -use gears::types::store::gas::errors::GasStoreErrors; -use gears::{ - application::keepers::params::ParamsKeeper, context::InfallibleContextMut, - params::ParamsSubspaceKey, -}; - -use super::param::ParamChange; - -pub trait SubmissionHandler, PSK: ParamsSubspaceKey, P> { - fn handle, DB: Database, SK: StoreKey>( - proposal: P, - ctx: &mut CTX, - subspace: &PSK, - ) -> Result<(), SubmissionHandlingError>; -} - -#[derive(Debug, thiserror::Error)] -pub enum SubmissionHandlingError { - #[error("Can't handle this proposal: decoding error")] - Decode(#[from] CoreError), - #[error("Can't handle this proposal: not supported subspace")] - Subspace, - #[error("Can't handle this proposal: no such keys in subspace")] - KeyNotFound, - #[error("Can't handle this proposal: invalid bytes")] - InvalidProposal, - #[error("Can't handle this proposal: {0}")] - Gas(#[from] GasStoreErrors), -} - -#[derive(Debug)] -pub struct ParamChangeSubmissionHandler(PhantomData); - -impl> SubmissionHandler> - for ParamChangeSubmissionHandler -{ - fn handle, DB: Database, SK: StoreKey>( - proposal: ParamChange, - ctx: &mut CTX, - subspace_key: &PSK, - ) -> Result<(), SubmissionHandlingError> { - if !PK::check_key(&proposal.key) { - Err(SubmissionHandlingError::KeyNotFound)? - } - - if !PK::validate(&proposal.key, &proposal.value) { - Err(SubmissionHandlingError::InvalidProposal)? - } - - let mut store = subspace_mut(ctx, subspace_key); - - store.raw_key_set(proposal.key, proposal.value)?; - - Ok(()) - } -} diff --git a/x/gov/src/submission/mod.rs b/x/gov/src/submission/mod.rs deleted file mode 100644 index f47a63805..000000000 --- a/x/gov/src/submission/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod handler; -pub mod param; -pub mod text; diff --git a/x/gov/src/submission/param.rs b/x/gov/src/submission/param.rs deleted file mode 100644 index ccaec35a4..000000000 --- a/x/gov/src/submission/param.rs +++ /dev/null @@ -1,181 +0,0 @@ -use bytes::Bytes; -use gears::{core::errors::CoreError, core::Protobuf, params::ParamsSubspaceKey}; -use ibc_proto::google::protobuf::Any; -use prost::Message; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, PartialEq, Message, Serialize, Deserialize)] -pub struct RawParamChange { - #[prost(string, tag = "1")] - pub subspace: String, - #[prost(bytes, tag = "2")] - pub key: Vec, - #[prost(bytes, tag = "3")] - pub value: Vec, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct ParamChange { - pub subspace: PSK, - pub key: Vec, - pub value: Vec, -} - -impl ParamChange { - pub const TYPE_URL: &'static str = "/cosmos.params.v1beta1/ParamChange"; -} - -impl Protobuf for ParamChange {} - -impl TryFrom for ParamChange { - type Error = CoreError; - - fn try_from( - RawParamChange { - subspace, - key, - value, - }: RawParamChange, - ) -> Result { - Ok(Self { - subspace: PSK::from_subspace_str(&subspace) - .map_err(|e| CoreError::DecodeGeneral(e.to_string()))?, - key, - value, - }) - } -} - -impl From> for RawParamChange { - fn from( - ParamChange { - subspace, - key, - value, - }: ParamChange, - ) -> Self { - Self { - subspace: subspace.name().to_owned(), - key, - value, - } - } -} - -impl TryFrom for ParamChange { - type Error = CoreError; - - fn try_from(value: Any) -> Result { - if value.type_url != Self::TYPE_URL { - Err(CoreError::DecodeGeneral( - "message type not recognized".into(), - ))? - } - ParamChange::decode::(value.value.into()) - .map_err(|e| CoreError::DecodeProtobuf(e.to_string())) - } -} - -impl From> for Any { - fn from(msg: ParamChange) -> Self { - Any { - type_url: ParamChange::::TYPE_URL.to_string(), - value: msg.encode_vec(), - } - } -} - -#[derive(Clone, PartialEq, Message, Serialize, Deserialize)] -pub struct RawParameterChangeProposal { - #[prost(string, tag = "1")] - pub title: String, - #[prost(string, tag = "2")] - pub description: String, - #[prost(repeated, message, tag = "3")] - pub changes: Vec, -} - -impl From for Any { - fn from(msg: RawParameterChangeProposal) -> Self { - Any { - type_url: "/cosmos.params.v1beta1/ParameterChangeProposal".to_owned(), - value: msg.encode_to_vec(), - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct ParameterChangeProposal { - pub title: String, - pub description: String, - pub changes: Vec>, -} - -impl ParameterChangeProposal { - pub const TYPE_URL: &'static str = "/cosmos.params.v1beta1/ParameterChangeProposal"; -} - -impl Protobuf for ParameterChangeProposal {} - -impl TryFrom for ParameterChangeProposal { - type Error = CoreError; - - fn try_from( - RawParameterChangeProposal { - title, - description, - changes, - }: RawParameterChangeProposal, - ) -> Result { - Ok(Self { - title, - description, - changes: { - let mut result = Vec::with_capacity(changes.len()); - for change in changes { - result.push(change.try_into()?) - } - result - }, - }) - } -} - -impl From> for RawParameterChangeProposal { - fn from( - ParameterChangeProposal { - title, - description, - changes, - }: ParameterChangeProposal, - ) -> Self { - Self { - title, - description, - changes: changes.into_iter().map(|e| e.into()).collect(), - } - } -} - -impl TryFrom for ParameterChangeProposal { - type Error = CoreError; - - fn try_from(value: Any) -> Result { - if value.type_url != Self::TYPE_URL { - Err(CoreError::DecodeGeneral( - "message type not recognized".into(), - ))? - } - Self::decode::(value.value.into()) - .map_err(|e| CoreError::DecodeProtobuf(e.to_string())) - } -} - -impl From> for Any { - fn from(msg: ParameterChangeProposal) -> Self { - Any { - type_url: ParameterChangeProposal::::TYPE_URL.to_string(), - value: msg.encode_vec(), - } - } -} diff --git a/x/gov/src/submission/text.rs b/x/gov/src/submission/text.rs deleted file mode 100644 index ae61c4482..000000000 --- a/x/gov/src/submission/text.rs +++ /dev/null @@ -1,90 +0,0 @@ -use std::marker::PhantomData; - -use bytes::Bytes; -use gears::{ - application::keepers::params::ParamsKeeper, context::InfallibleContextMut, - core::errors::CoreError, core::Protobuf, params::ParamsSubspaceKey, -}; -use ibc_proto::google::protobuf::Any; -use prost::Message; -use serde::{Deserialize, Serialize}; - -use super::handler::{SubmissionHandler, SubmissionHandlingError}; - -#[derive(Clone, PartialEq, Message)] -pub struct RawTextProposal { - #[prost(string, tag = "1")] - pub title: String, - #[prost(string, tag = "2")] - pub description: String, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct TextProposal { - pub title: String, - pub description: String, -} - -impl TextProposal { - pub const TYPE_URL: &'static str = "/cosmos.params.v1beta1/TextProposal"; -} - -impl Protobuf for TextProposal {} - -impl TryFrom for TextProposal { - type Error = CoreError; - - fn try_from( - RawTextProposal { title, description }: RawTextProposal, - ) -> Result { - Ok(Self { title, description }) - } -} - -impl From for RawTextProposal { - fn from(TextProposal { title, description }: TextProposal) -> Self { - Self { title, description } - } -} - -impl TryFrom for TextProposal { - type Error = CoreError; - - fn try_from(value: Any) -> Result { - if value.type_url != Self::TYPE_URL { - Err(CoreError::DecodeGeneral( - "message type not recognized".into(), - ))? - } - Self::decode::(value.value.into()) - .map_err(|e| CoreError::DecodeProtobuf(e.to_string())) - } -} - -impl From for Any { - fn from(msg: TextProposal) -> Self { - Any { - type_url: TextProposal::TYPE_URL.to_string(), - value: msg.encode_vec(), - } - } -} - -#[derive(Debug, Default)] -pub struct TextSubmissionHandler(PhantomData); - -impl> SubmissionHandler - for TextSubmissionHandler -{ - fn handle< - CTX: InfallibleContextMut, - DB: gears::store::database::Database, - SK: gears::store::StoreKey, - >( - _proposal: TextProposal, - _ctx: &mut CTX, - _keeper: &PSK, - ) -> Result<(), SubmissionHandlingError> { - Ok(()) - } -} diff --git a/x/gov/src/types/proposal/active_iter.rs b/x/gov/src/types/proposal/active_iter.rs index 560414196..af541d955 100644 --- a/x/gov/src/types/proposal/active_iter.rs +++ b/x/gov/src/types/proposal/active_iter.rs @@ -1,4 +1,4 @@ -use std::ops::Bound; +use std::{marker::PhantomData, ops::Bound}; use chrono::{DateTime, Utc}; use gears::{ @@ -6,34 +6,38 @@ use gears::{ tendermint::types::time::timestamp::Timestamp, types::store::{gas::errors::GasStoreErrors, kv::Store, range::StoreRange}, }; +use serde::de::DeserializeOwned; -use crate::errors::SERDE_JSON_CONVERSION; +use crate::{errors::SERDE_JSON_CONVERSION, proposal::Proposal}; -use super::{parse_proposal_key_bytes, Proposal}; +use super::{parse_proposal_key_bytes, ProposalModel}; #[derive(Debug)] -pub struct ActiveProposalIterator<'a, DB>(StoreRange<'a, DB>); +pub struct ActiveProposalIterator<'a, DB, P>(StoreRange<'a, DB>, PhantomData

); -impl<'a, DB: Database> ActiveProposalIterator<'a, DB> { - pub fn new(store: Store<'a, DB>, end_time: &Timestamp) -> ActiveProposalIterator<'a, DB> { +impl<'a, DB: Database, P: Proposal> ActiveProposalIterator<'a, DB, P> { + pub fn new(store: Store<'a, DB>, end_time: &Timestamp) -> ActiveProposalIterator<'a, DB, P> { Self( store.into_range(( - Bound::Included(Proposal::KEY_ACTIVE_QUEUE_PREFIX.to_vec()), + Bound::Included(ProposalModel::

::KEY_ACTIVE_QUEUE_PREFIX.to_vec()), Bound::Excluded( [ - Proposal::KEY_ACTIVE_QUEUE_PREFIX.as_slice(), + ProposalModel::

::KEY_ACTIVE_QUEUE_PREFIX.as_slice(), end_time.format_bytes_rounded().as_slice(), ] .concat() .to_vec(), ), )), + PhantomData, ) } } -impl<'a, DB: Database> Iterator for ActiveProposalIterator<'a, DB> { - type Item = Result<((u64, DateTime), Proposal), GasStoreErrors>; +impl<'a, DB: Database, P: Proposal + DeserializeOwned> Iterator + for ActiveProposalIterator<'a, DB, P> +{ + type Item = Result<((u64, DateTime), ProposalModel

), GasStoreErrors>; fn next(&mut self) -> Option { if let Some(var) = self.0.next() { diff --git a/x/gov/src/types/proposal/inactive_iter.rs b/x/gov/src/types/proposal/inactive_iter.rs index 54b18f09b..852f272e0 100644 --- a/x/gov/src/types/proposal/inactive_iter.rs +++ b/x/gov/src/types/proposal/inactive_iter.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, ops::Bound}; +use std::{borrow::Cow, marker::PhantomData, ops::Bound}; use chrono::{DateTime, Utc}; use gears::{ @@ -7,30 +7,33 @@ use gears::{ types::store::{gas::errors::GasStoreErrors, kv::Store, range::StoreRange}, }; -use super::{parse_proposal_key_bytes, Proposal}; +use crate::proposal::Proposal; + +use super::{parse_proposal_key_bytes, ProposalModel}; #[derive(Debug)] -pub struct InactiveProposalIterator<'a, DB>(StoreRange<'a, DB>); +pub struct InactiveProposalIterator<'a, DB, P>(StoreRange<'a, DB>, PhantomData

); -impl<'a, DB: Database> InactiveProposalIterator<'a, DB> { - pub fn new(store: Store<'a, DB>, end_time: &Timestamp) -> InactiveProposalIterator<'a, DB> { +impl<'a, DB: Database, P: Proposal> InactiveProposalIterator<'a, DB, P> { + pub fn new(store: Store<'a, DB>, end_time: &Timestamp) -> InactiveProposalIterator<'a, DB, P> { Self( store.into_range(( - Bound::Included(Proposal::KEY_INACTIVE_QUEUE_PREFIX.to_vec()), + Bound::Included(ProposalModel::

::KEY_INACTIVE_QUEUE_PREFIX.to_vec()), Bound::Excluded( [ - Proposal::KEY_INACTIVE_QUEUE_PREFIX.as_slice(), + ProposalModel::

::KEY_INACTIVE_QUEUE_PREFIX.as_slice(), end_time.format_bytes_rounded().as_slice(), ] .concat() .to_vec(), ), )), + PhantomData, ) } } -impl<'a, DB: Database> Iterator for InactiveProposalIterator<'a, DB> { +impl<'a, DB: Database, P: Proposal> Iterator for InactiveProposalIterator<'a, DB, P> { type Item = Result<((u64, DateTime), Cow<'a, Vec>), GasStoreErrors>; fn next(&mut self) -> Option { diff --git a/x/gov/src/types/proposal/mod.rs b/x/gov/src/types/proposal/mod.rs index 912a8f2e0..0a86edd03 100644 --- a/x/gov/src/types/proposal/mod.rs +++ b/x/gov/src/types/proposal/mod.rs @@ -1,8 +1,9 @@ -use std::{str::FromStr, sync::OnceLock}; +use std::{marker::PhantomData, str::FromStr, sync::OnceLock}; use chrono::{DateTime, SubsecRound, Utc}; use gears::{ core::{errors::CoreError, Protobuf}, + error::ProtobufError, store::database::Database, tendermint::types::time::timestamp::Timestamp, types::{ @@ -11,10 +12,9 @@ use gears::{ uint::Uint256, }, }; -use ibc_proto::google::protobuf::Any; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use crate::{errors::SERDE_JSON_CONVERSION, keeper::KEY_PROPOSAL_PREFIX}; +use crate::{errors::SERDE_JSON_CONVERSION, keeper::KEY_PROPOSAL_PREFIX, proposal::Proposal}; pub mod active_iter; pub mod inactive_iter; @@ -28,9 +28,9 @@ mod inner { const SORTABLE_DATE_TIME_FORMAT: &str = "%Y-%m-%dT&H:%M:%S.000000000"; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Proposal { +pub struct ProposalModel { pub proposal_id: u64, - pub content: Any, + pub content: T, pub status: ProposalStatus, pub final_tally_result: Option, pub submit_time: Timestamp, @@ -40,8 +40,8 @@ pub struct Proposal { pub voting_end_time: Option, } -impl TryFrom for Proposal { - type Error = CoreError; +impl TryFrom for ProposalModel { + type Error = ProtobufError; fn try_from( inner::Proposal { @@ -66,9 +66,11 @@ impl TryFrom for Proposal { Ok(Self { proposal_id, - content: content.ok_or(CoreError::MissingField( - "Proposal: field `content`".to_owned(), - ))?, + content: content + .ok_or(CoreError::MissingField( + "Proposal: field `content`".to_owned(), + ))? + .try_into()?, status: status.try_into()?, final_tally_result: match final_tally_result { Some(var) => Some(var.try_into()?), @@ -110,10 +112,9 @@ impl TryFrom for Proposal { } } -// TODO: -impl From for inner::Proposal { +impl From> for inner::Proposal { fn from( - Proposal { + ProposalModel { proposal_id, content, status, @@ -123,11 +124,11 @@ impl From for inner::Proposal { total_deposit, voting_start_time, voting_end_time, - }: Proposal, + }: ProposalModel, ) -> Self { Self { proposal_id, - content: Some(content), + content: Some(content.into()), status: status as i32, final_tally_result: final_tally_result.map(|e| e.into()), submit_time: Some(ibc_proto::google::protobuf::Timestamp { @@ -153,7 +154,7 @@ impl From for inner::Proposal { } } -impl Protobuf for Proposal {} +impl Protobuf for ProposalModel {} #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] pub struct TallyResult { @@ -208,7 +209,7 @@ impl From for inner::TallyResult { impl Protobuf for TallyResult {} -impl Proposal { +impl ProposalModel { const KEY_PREFIX: [u8; 1] = [0x00]; const KEY_ACTIVE_QUEUE_PREFIX: [u8; 1] = [0x01]; const KEY_INACTIVE_QUEUE_PREFIX: [u8; 1] = [0x02]; @@ -309,20 +310,20 @@ fn parse_proposal_key_bytes(bytes: impl AsRef<[u8]>) -> (u64, DateTime) { } #[derive(Debug)] -pub struct ProposalsIterator<'a, DB>(StoreRange<'a, DB>); +pub struct ProposalsIterator<'a, DB, P>(StoreRange<'a, DB>, PhantomData

); -impl<'a, DB: Database> ProposalsIterator<'a, DB> { - pub fn new(store: Store<'a, DB>) -> ProposalsIterator<'a, DB> { - let prefix = store.prefix_store(Proposal::KEY_PREFIX); +impl<'a, DB: Database, P: Proposal> ProposalsIterator<'a, DB, P> { + pub fn new(store: Store<'a, DB>) -> ProposalsIterator<'a, DB, P> { + let prefix = store.prefix_store(ProposalModel::

::KEY_PREFIX); let range = prefix.into_range(..); - ProposalsIterator(range) + ProposalsIterator(range, PhantomData) } } -impl<'a, DB: Database> Iterator for ProposalsIterator<'a, DB> { - type Item = Result<((u64, DateTime), Proposal), GasStoreErrors>; +impl<'a, DB: Database, P: Proposal + DeserializeOwned> Iterator for ProposalsIterator<'a, DB, P> { + type Item = Result<((u64, DateTime), ProposalModel

), GasStoreErrors>; fn next(&mut self) -> Option { if let Some(var) = self.0.next() { diff --git a/x/upgrade/Cargo.toml b/x/upgrade/Cargo.toml new file mode 100644 index 000000000..8d1b1b1e4 --- /dev/null +++ b/x/upgrade/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "upgrade" +version = "0.1.0" +edition = "2021" + +[lints] +workspace = true + +[dependencies] +#local +gears = { path = "../../gears", features = ["cli", "xmods", "governance" ] } + +#serialization +prost = { workspace = true } +serde = { workspace = true, default-features = false } +serde_json = { workspace = true } + +# utils +anyhow = { workspace = true } +tracing = { workspace = true } +strum = { workspace = true } + +# nutypes +ibc-proto = { workspace = true } +nutype = { workspace = true, features = ["serde"]} + +#clients +clap = { workspace = true } \ No newline at end of file diff --git a/x/upgrade/Readme.md b/x/upgrade/Readme.md new file mode 100644 index 000000000..e69de29bb diff --git a/x/upgrade/src/abci_handler.rs b/x/upgrade/src/abci_handler.rs new file mode 100644 index 000000000..beb12aaaf --- /dev/null +++ b/x/upgrade/src/abci_handler.rs @@ -0,0 +1,279 @@ +use std::{ + fmt::{Debug, Display}, + marker::PhantomData, +}; + +use gears::{ + application::handlers::node::{ABCIHandler, ModuleInfo}, + baseapp::{errors::QueryError, genesis::NullGenesis, QueryResponse}, + context::{query::QueryContext, QueryableContext}, + core::Protobuf, + params::ParamsSubspaceKey, + store::{database::Database, StoreKey}, + tendermint::types::request::query::RequestQuery, + types::tx::NullTxMsg, +}; +use prost::bytes::Bytes; +use tracing::info; + +use crate::{ + handler::UpgradeHandler, + keeper::{downgrade_verified, set_downgrade_verified, UpgradeKeeper}, + types::{ + query::{ + QueryAppliedPlanRequest, QueryAppliedPlanResponse, QueryCurrentPlanRequest, + QueryCurrentPlanResponse, QueryModuleVersionsRequest, QueryModuleVersionsResponse, + UpgradeQueryRequest, UpgradeQueryResponse, + }, + ModuleVersion, + }, + Module, +}; + +#[derive(Debug, Clone)] +pub struct UpgradeAbciHandler { + keeper: UpgradeKeeper, + _marker: PhantomData<(MI, SK, PSK, M)>, +} + +impl + ABCIHandler for UpgradeAbciHandler +where + >>::Error: Display + Debug, +{ + type Message = NullTxMsg; + + type Genesis = NullGenesis; + + type StoreKey = SK; + + type QReq = UpgradeQueryRequest; + + type QRes = UpgradeQueryResponse; + + fn typed_query( + &self, + ctx: &gears::context::query::QueryContext, + query: Self::QReq, + ) -> Self::QRes { + match query { + UpgradeQueryRequest::Plan(_) => Self::QRes::Plan(self.query_plan(ctx)), + UpgradeQueryRequest::Applied(query) => { + Self::QRes::Applied(self.query_applied(ctx, query)) + } + UpgradeQueryRequest::ModuleVersions(query) => { + Self::QRes::ModuleVersions(self.query_module_versions(ctx, query)) + } + } + } + + fn run_ante_checks( + &self, + _: &mut gears::context::tx::TxContext<'_, DB, Self::StoreKey>, + _: &gears::types::tx::raw::TxWithRaw, + _: bool, + ) -> Result<(), gears::application::handlers::node::TxError> { + Ok(()) + } + + fn msg( + &self, + _: &mut gears::context::tx::TxContext<'_, DB, Self::StoreKey>, + _: &Self::Message, + ) -> Result<(), gears::application::handlers::node::TxError> { + Ok(()) + } + + fn init_genesis( + &self, + _: &mut gears::context::init::InitContext<'_, DB, Self::StoreKey>, + _: Self::Genesis, + ) -> Vec { + Vec::new() + } + + fn query( + &self, + ctx: &gears::context::query::QueryContext, + RequestQuery { + data, + path, + height: _, + prove: _, + }: RequestQuery, + ) -> Result, gears::baseapp::errors::QueryError> { + let bytes = Bytes::from(data); + + let query = match path.as_str() { + QueryCurrentPlanRequest::QUERY_URL => { + Self::QReq::Plan(QueryCurrentPlanRequest::decode(bytes)?) + } + QueryAppliedPlanRequest::QUERY_URL => { + Self::QReq::Applied(QueryAppliedPlanRequest::decode(bytes)?) + } + QueryModuleVersionsRequest::QUERY_URL => { + Self::QReq::ModuleVersions(QueryModuleVersionsRequest::decode(bytes)?) + } + _ => Err(QueryError::PathNotFound)?, + }; + + Ok(ABCIHandler::typed_query(self, ctx, query).into_bytes()) + } + + fn begin_block<'b, DB: gears::store::database::Database>( + &self, + ctx: &mut gears::context::block::BlockContext<'_, DB, Self::StoreKey>, + request: gears::tendermint::request::RequestBeginBlock, + ) { + let plan = self.keeper.upgrade_plan(ctx); + + if !downgrade_verified() { + set_downgrade_verified(true); + let last_applied_plan = self.keeper.last_completed_upgrade(ctx); + + let is_none = plan.is_none(); + let should_execute = plan + .as_ref() + .map(|this| this.should_execute(ctx)) + .unwrap_or_default(); + + // This check will make sure that we are using a valid binary. + // It'll panic in these cases if there is no upgrade handler registered for the last applied upgrade. + // 1. If there is no scheduled upgrade. + // 2. If the plan is not ready. + // 3. If the plan is ready and skip upgrade height is set for current height. + if is_none + || !should_execute + || (should_execute && self.keeper.is_skip_height(ctx.height())) + { + match last_applied_plan { + Some(upg) if self.keeper.has_handler(&upg.name) => panic!( + "Wrong app version {}, upgrade handler is missing for {} upgrade plan", + request.header.version.app, upg.name, + ), + _ => (), + } + } + } + + let plan = match plan { + Some(plan) => plan, + None => return, + }; + + // To make sure clear upgrade is executed at the same block + if plan.should_execute(ctx) { + // If skip upgrade has been set for current height, we clear the upgrade plan + if self.keeper.is_skip_height(ctx.height()) { + info!( + "UPGRADE `{}` SKIPPED at {}: {}", + plan.name.as_ref(), + plan.height, + plan.info + ); + + self.keeper.delete_upgrade_plan(ctx); + return; + } + + if !self.keeper.has_handler(&plan.name) { + // TODO: store info https://github.com/cosmos/cosmos-sdk/blob/d3f09c222243bb3da3464969f0366330dcb977a8/x/upgrade/keeper/keeper.go#L375-L396 + + // We don't have an upgrade handler for this upgrade name, meaning this software is out of date so shutdown + let msg = format!( + "UPGRADE `{}` NEEDED at height: {}: {}", + plan.name.as_ref(), + plan.height, + plan.info + ); + let log_msg = msg.clone(); + + tracing::error!("{log_msg}"); + panic!("{msg}"); + } + + tracing::info!( + "applying upgrade `{}` at height: {}", + plan.name.as_ref(), + plan.height + ); + + // todo: why they need gas https://github.com/cosmos/cosmos-sdk/blob/d3f09c222243bb3da3464969f0366330dcb977a8/x/upgrade/abci.go#L75 + match self.keeper.apply_upgrade(ctx, plan) { + Ok(_) => return, + Err(err) => panic!("{err}"), + } + } + + if self.keeper.has_handler(&plan.name) { + let msg = format!( + "BINARY UPDATED BEFORE TRIGGER! UPGRADE `{}` - in binary but not executed on chain", + plan.name.as_ref() + ); + let log_msg = msg.clone(); + tracing::error!("{log_msg}"); + panic!("{msg}"); + } + } +} + +impl + UpgradeAbciHandler +where + >>::Error: Display + Debug, +{ + pub fn query_plan(&self, ctx: &QueryContext) -> QueryCurrentPlanResponse { + QueryCurrentPlanResponse { + plan: self.keeper.upgrade_plan(ctx), + } + } + + pub fn query_applied( + &self, + ctx: &QueryContext, + QueryAppliedPlanRequest { name }: QueryAppliedPlanRequest, + ) -> QueryAppliedPlanResponse { + QueryAppliedPlanResponse { + height: self.keeper.done_height(ctx, name).unwrap_or_default(), + } + } + + pub fn query_module_versions( + &self, + ctx: &QueryContext, + QueryModuleVersionsRequest { module_name }: QueryModuleVersionsRequest, + ) -> QueryModuleVersionsResponse { + let mut list = match module_name.is_empty() { + true => self + .keeper + .modules_version(ctx) + .into_iter() + .map(|(key, version)| ModuleVersion { + name: key.name().to_owned(), + version, + }) + .collect::>(), + false => { + match self + .keeper + .modules_version(ctx) + .into_iter() + .find(|this| this.0.name() == &module_name) + { + Some((key, version)) => [ModuleVersion { + name: key.name().to_owned(), + version, + }] + .to_vec(), + None => Vec::new(), + } + } + }; + + list.sort(); + + QueryModuleVersionsResponse { + module_versions: list, + } + } +} diff --git a/x/upgrade/src/client/cli/mod.rs b/x/upgrade/src/client/cli/mod.rs new file mode 100644 index 000000000..25359f67d --- /dev/null +++ b/x/upgrade/src/client/cli/mod.rs @@ -0,0 +1,68 @@ +use gears::{application::handlers::client::QueryHandler, core::Protobuf}; +use prost::bytes::Bytes; +use query::{UpgradeQueryCli, UpgradeQueryCliCommands}; + +use crate::types::query::{ + QueryAppliedPlanRequest, QueryAppliedPlanResponse, QueryCurrentPlanRequest, + QueryCurrentPlanResponse, QueryModuleVersionsRequest, QueryModuleVersionsResponse, + UpgradeQueryRequest, UpgradeQueryResponse, +}; + +pub mod query; + +#[derive(Debug, Clone)] +pub struct UpgradeClientHandler; + +impl QueryHandler for UpgradeClientHandler { + type QueryCommands = UpgradeQueryCli; + + type QueryRequest = UpgradeQueryRequest; + + type QueryResponse = UpgradeQueryResponse; + + fn prepare_query_request( + &self, + command: &Self::QueryCommands, + ) -> anyhow::Result { + let result = match &command.command { + UpgradeQueryCliCommands::Plan => Self::QueryRequest::Plan(QueryCurrentPlanRequest {}), + UpgradeQueryCliCommands::Applied { name } => { + Self::QueryRequest::Applied(QueryAppliedPlanRequest { + name: name.to_owned(), + }) + } + UpgradeQueryCliCommands::ModuleVersions { name } => { + Self::QueryRequest::ModuleVersions(QueryModuleVersionsRequest { + module_name: name + .as_ref() + .map(|this| this.to_owned()) + .unwrap_or_default(), + }) + } + }; + + Ok(result) + } + + fn handle_raw_response( + &self, + query_bytes: Vec, + command: &Self::QueryCommands, + ) -> anyhow::Result { + let bytes = Bytes::from(query_bytes); + + let result = match &command.command { + UpgradeQueryCliCommands::Plan => { + Self::QueryResponse::Plan(QueryCurrentPlanResponse::decode(bytes)?) + } + UpgradeQueryCliCommands::Applied { name: _ } => { + Self::QueryResponse::Applied(QueryAppliedPlanResponse::decode(bytes)?) + } + UpgradeQueryCliCommands::ModuleVersions { name: _ } => { + Self::QueryResponse::ModuleVersions(QueryModuleVersionsResponse::decode(bytes)?) + } + }; + + Ok(result) + } +} diff --git a/x/upgrade/src/client/cli/query.rs b/x/upgrade/src/client/cli/query.rs new file mode 100644 index 000000000..ff200ff04 --- /dev/null +++ b/x/upgrade/src/client/cli/query.rs @@ -0,0 +1,30 @@ +use clap::{Args, Subcommand}; + +#[derive(Args, Debug, Clone)] +pub struct UpgradeQueryCli { + #[command(subcommand)] + pub command: UpgradeQueryCliCommands, +} + +#[derive(Subcommand, Debug, Clone)] +pub enum UpgradeQueryCliCommands { + /// get upgrade plan (if one exists) + #[command(long_about = "Gets the currently scheduled upgrade plan, if one exists")] + Plan, + /// block header for height at which a completed upgrade was applied + #[command( + long_about = "If upgrade-name was previously executed on the chain, this returns the header for the block at which it was applied. This helps a client determine which binary was valid over a given range of blocks, as well as more context to understand past migrations." + )] + Applied { + /// name of the applied plan to query for + name: String, + }, + /// get the list of module versions + #[command( + long_about = "Gets a list of module names and their respective consensus versions. Following the command with a specific module name will return only that module's information." + )] + ModuleVersions { + /// name to query a specific module consensus version from state + name: Option, + }, +} diff --git a/x/upgrade/src/client/mod.rs b/x/upgrade/src/client/mod.rs new file mode 100644 index 000000000..4f773726a --- /dev/null +++ b/x/upgrade/src/client/mod.rs @@ -0,0 +1 @@ +pub mod cli; diff --git a/x/upgrade/src/handler.rs b/x/upgrade/src/handler.rs new file mode 100644 index 000000000..184cce649 --- /dev/null +++ b/x/upgrade/src/handler.rs @@ -0,0 +1,39 @@ +use std::{collections::HashMap, fmt::Debug}; + +use gears::context::InfallibleContextMut; + +use crate::types::plan::Plan; + +pub trait UpgradeHandler: Debug + Clone + Send + Sync + 'static { + fn name(&self) -> &'static str; + + fn handle, DB, SK, M>( + &self, + ctx: &mut CTX, + plan: &Plan, + versions: impl IntoIterator, + ) -> anyhow::Result>; +} + +pub mod dummy { + + use super::*; + + #[derive(Debug, Clone, strum::EnumIter)] + pub enum NullUpgradeHandler {} + + impl UpgradeHandler for NullUpgradeHandler { + fn name(&self) -> &'static str { + unreachable!() + } + + fn handle, DB, SK, M>( + &self, + _ctx: &mut CTX, + _plan: &Plan, + _versions: impl IntoIterator, + ) -> anyhow::Result> { + unreachable!() + } + } +} diff --git a/x/upgrade/src/keeper.rs b/x/upgrade/src/keeper.rs new file mode 100644 index 000000000..8e51ad140 --- /dev/null +++ b/x/upgrade/src/keeper.rs @@ -0,0 +1,327 @@ +use std::{ + collections::{HashMap, HashSet}, + fmt::{Debug, Display}, + marker::PhantomData, +}; + +use gears::{ + context::{InfallibleContext, InfallibleContextMut}, + core::Protobuf, + extensions::corruption::UnwrapCorrupt, + store::{database::Database, StoreKey}, +}; +use prost::bytes::Bytes; + +use crate::{ + handler::UpgradeHandler, + types::{plan::Plan, Upgrade}, + Module, +}; + +pub use downgrade_flag::*; + +/// specifies the Byte under which a pending upgrade plan is stored in the store +const PLAN_PREFIX: [u8; 1] = [0x0]; +/// is a prefix for to look up completed upgrade plan by name +const DONE_PREFIX: [u8; 1] = [0x1]; +/// is a prefix to look up module names (key) and versions (value) +const VERSION_MAP_PREFIX: [u8; 1] = [0x2]; +/// is a prefix to look up Protocol Version +const PROTOCOL_VERSION_BYTE_PREFIX: [u8; 1] = [0x3]; + +/// is the key under which upgraded ibc state is stored in the upgrade store +const UPGRADED_IBC_STATE_KEY: &[u8] = "upgradedIBCState".as_bytes(); +/// is the sub-key under which upgraded client state will be stored +const UPGRADED_CLIENT_KEY: &[u8] = "upgradedClient".as_bytes(); +/// is the sub-key under which upgraded consensus state will be stored +const UPGRADED_CONS_STATE_KEY: &[u8] = "upgradedConsState".as_bytes(); + +fn upgraded_client_key(height: u32) -> Vec { + [ + UPGRADED_IBC_STATE_KEY, + height.to_be_bytes().as_slice(), // TODO: Unsure in this + UPGRADED_CLIENT_KEY, + ] + .concat() +} + +fn upgraded_const_state_key(height: u32) -> Vec { + [ + UPGRADED_IBC_STATE_KEY, + height.to_be_bytes().as_slice(), // TODO: Unsure in this + UPGRADED_CONS_STATE_KEY, + ] + .concat() +} + +#[derive(Debug, Clone)] +pub struct UpgradeKeeper { + store_key: SK, + upgrade_handlers: HashMap<&'static str, UH>, + skip_heights: HashSet, // TODO: source https://github.com/cosmos/gaia/blob/189b57be735d64d0dbf0945717b49017a1beb11e/cmd/gaiad/cmd/root.go#L192-L195 + _modules_marker: PhantomData, +} + +impl UpgradeKeeper { + pub fn new(store_key: SK, skip_heights: impl IntoIterator) -> Self { + Self { + store_key, + upgrade_handlers: UH::iter().map(|this| (this.name(), this)).collect(), + skip_heights: skip_heights.into_iter().collect(), + _modules_marker: PhantomData, + } + } +} + +impl UpgradeKeeper { + pub fn new_unchecked( + store_key: SK, + upgrade_handlers: impl IntoIterator, + skip_heights: impl IntoIterator, + ) -> Self { + Self { + store_key, + upgrade_handlers: upgrade_handlers.into_iter().collect(), + skip_heights: skip_heights.into_iter().collect(), + _modules_marker: PhantomData, + } + } +} + +impl UpgradeKeeper +where + >>::Error: Display + Debug, +{ + pub fn schedule_upgrade>( + &self, + ctx: &mut CTX, + plan: Plan, + overwrite: bool, + ) -> anyhow::Result> { + if plan.height.get() <= ctx.height() { + Err(anyhow::anyhow!("upgrade cannot be scheduled in the past"))? + } + + if self.done_height(ctx, &plan.name).is_some() { + Err(anyhow::anyhow!( + "upgrade with name {} has already been completed", + plan.name.as_ref() + ))? + } + + let old_plan = match self.upgrade_plan(ctx) { + Some(old_plan) => match overwrite { + true => { + self.clear_ibc_state(ctx, old_plan.height.get()); + + ctx.infallible_store_mut(&self.store_key) + .set(PLAN_PREFIX, plan.encode_vec()); + + Some(old_plan) + } + false => Err(anyhow::anyhow!( + "upgrade with name {} already exists", + old_plan.name.as_ref() + ))?, + }, + None => { + ctx.infallible_store_mut(&self.store_key) + .set(PLAN_PREFIX, plan.encode_vec()); + + None + } + }; + + Ok(old_plan) + } + + pub(crate) fn apply_upgrade>( + &self, + ctx: &mut CTX, + plan: Plan, + ) -> anyhow::Result<()> { + let handler = self + .upgrade_handlers + .get(plan.name.as_ref()) + .ok_or(anyhow::anyhow!( + "Upgrade should never be called without first checking HasHandler" + ))?; + + let versions = self.modules_version(ctx); + + let updated = handler.handle(ctx, &plan, versions)?; + + self.set_modules_version(ctx, updated); + self.set_protocol_version(ctx, self.protocol_version(ctx) + 1); + + // TODO: protocol setter for baseapp https://github.com/cosmos/cosmos-sdk/blob/d3f09c222243bb3da3464969f0366330dcb977a8/x/upgrade/keeper/keeper.go#L350-L353 + + self.clear_ibc_state(ctx, plan.height.get()); + self.delete_upgrade_plan(ctx); + self.set_done(ctx, plan); + + Ok(()) + } + + pub(crate) fn done_height>( + &self, + ctx: &CTX, + name: impl AsRef, + ) -> Option { + let height = ctx + .infallible_store(&self.store_key) + .get(name.as_ref().as_bytes())?; + let height = u32::from_be_bytes(height.try_into().ok().unwrap_or_corrupt()); + + Some(height) + } + + fn set_done>(&self, ctx: &mut CTX, plan: Plan) { + let height = ctx.height(); + + ctx.infallible_store_mut(&self.store_key) + .prefix_store_mut(DONE_PREFIX) + .set(plan.name.into_inner().into_bytes(), height.to_be_bytes()); + } + + pub fn upgrade_plan>( + &self, + ctx: &CTX, + ) -> Option { + let store = ctx.infallible_store(&self.store_key); + + store + .get(&PLAN_PREFIX) + .map(|this| Protobuf::decode::(this.into()).unwrap_or_corrupt()) + } + + pub fn delete_upgrade_plan>( + &self, + ctx: &mut CTX, + ) -> bool { + let old_plan = self.upgrade_plan(ctx); + if let Some(old_plan) = old_plan { + self.clear_ibc_state(ctx, old_plan.height.get()); + } + + ctx.infallible_store_mut(&self.store_key) + .delete(&PLAN_PREFIX) + .is_some() + } + + fn clear_ibc_state>( + &self, + ctx: &mut CTX, + last_height: u32, + ) { + let mut store = ctx.infallible_store_mut(&self.store_key); + store.delete(&upgraded_client_key(last_height)); + store.delete(&upgraded_const_state_key(last_height)); + } + + pub fn last_completed_upgrade>( + &self, + ctx: &CTX, + ) -> Option { + // TODO: When revertable iterator will be available use it + let upgrade_bytes = ctx + .infallible_store(&self.store_key) + .prefix_store(DONE_PREFIX) + .into_range(..); + + let mut found = false; + let mut last_upgrade = Option::None; + for (key, value) in upgrade_bytes { + let upgrade = Upgrade::try_new(key.as_slice(), value.as_slice()).unwrap_or_corrupt(); + + if !found + || upgrade.block + >= last_upgrade + .as_ref() + .map(|this: &Upgrade| this.block) + .unwrap_or_default() + { + found = true; + last_upgrade = Some(upgrade) + } + } + + last_upgrade + } + + pub fn modules_version>( + &self, + ctx: &CTX, + ) -> HashMap { + ctx.infallible_store(&self.store_key) + .prefix_store(VERSION_MAP_PREFIX) + .into_range(..) + .map(|(key, value)| { + ( + M::try_from(key.as_slice().to_vec()).expect("unknown module version saved"), + u64::from_be_bytes(value.as_slice().try_into().ok().unwrap_or_corrupt()), + ) + }) + .collect::>() + } + + fn set_modules_version>( + &self, + ctx: &mut CTX, + modules: impl IntoIterator, + ) { + let modules = modules.into_iter().collect::>(); + + if modules.is_empty() { + let mut store = ctx + .infallible_store_mut(&self.store_key) + .prefix_store_mut(VERSION_MAP_PREFIX); + + for (module, version) in modules { + store.set( + module.name().as_bytes().to_owned(), + version.to_be_bytes().to_vec(), + ); + } + } + } + + fn protocol_version>(&self, ctx: &CTX) -> u64 { + ctx.infallible_store(&self.store_key) + .get(&PROTOCOL_VERSION_BYTE_PREFIX) + .map(|this| u64::from_be_bytes(this.as_slice().try_into().unwrap_or_corrupt())) + .unwrap_or_default() + } + + fn set_protocol_version>( + &self, + ctx: &mut CTX, + version: u64, + ) { + ctx.infallible_store_mut(&self.store_key) + .set(PROTOCOL_VERSION_BYTE_PREFIX, version.to_be_bytes()); + } + + pub fn is_skip_height(&self, height: u32) -> bool { + self.skip_heights.contains(&height) + } + + pub fn has_handler(&self, name: impl AsRef) -> bool { + self.upgrade_handlers.contains_key(name.as_ref()) + } +} + +mod downgrade_flag { + use std::sync::atomic::AtomicBool; + + /// tells if we've already sanity checked that this binary version isn't being used against an old state. + static DOWNGRADE_VERIFIED: AtomicBool = AtomicBool::new(false); + + pub fn downgrade_verified() -> bool { + DOWNGRADE_VERIFIED.load(std::sync::atomic::Ordering::SeqCst) + } + + pub fn set_downgrade_verified(val: bool) -> bool { + DOWNGRADE_VERIFIED.swap(val, std::sync::atomic::Ordering::SeqCst) + } +} diff --git a/x/upgrade/src/lib.rs b/x/upgrade/src/lib.rs new file mode 100644 index 000000000..cff515188 --- /dev/null +++ b/x/upgrade/src/lib.rs @@ -0,0 +1,13 @@ +pub mod client; +pub mod abci_handler; +mod handler; +pub mod keeper; +pub mod types; + +pub use crate::handler::*; + +pub trait Module: + Clone + Send + Sync + TryFrom> + std::cmp::Eq + std::hash::Hash + 'static +{ + fn name(&self) -> &'static str; +} diff --git a/x/upgrade/src/types/mod.rs b/x/upgrade/src/types/mod.rs new file mode 100644 index 000000000..c61b2c1ee --- /dev/null +++ b/x/upgrade/src/types/mod.rs @@ -0,0 +1,35 @@ +pub mod plan; +pub mod query; + +#[derive(Debug, Clone)] +pub struct Upgrade { + pub name: String, + pub block: u32, +} + +impl Upgrade { + pub fn try_new(block_bytes: impl AsRef<[u8]>, name_bytes: impl AsRef<[u8]>) -> Option { + let block = u32::from_be_bytes(block_bytes.as_ref().try_into().ok()?); + let name = String::from_utf8(name_bytes.as_ref()[1..].to_vec()).ok()?; + + Some(Self { name, block }) + } +} + +#[derive( + Debug, + Clone, + Hash, + PartialEq, + Eq, + PartialOrd, + Ord, + gears::derive::Protobuf, + serde::Serialize, + serde::Deserialize, +)] +#[proto(raw = "ibc_proto::cosmos::upgrade::v1beta1::ModuleVersion")] +pub struct ModuleVersion { + pub name: String, + pub version: u64, +} diff --git a/x/upgrade/src/types/plan.rs b/x/upgrade/src/types/plan.rs new file mode 100644 index 000000000..32dba4e19 --- /dev/null +++ b/x/upgrade/src/types/plan.rs @@ -0,0 +1,76 @@ +use std::num::NonZero; + +use gears::{ + context::QueryableContext, + core::{errors::CoreError, Protobuf}, + error::ProtobufError, +}; + +mod inner { + pub use ibc_proto::cosmos::upgrade::v1beta1::Plan; +} + +#[nutype::nutype( + validate(not_empty), + derive(Debug, Clone, Serialize, Deserialize, AsRef) +)] +pub struct PlanName(String); + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct Plan { + pub name: PlanName, + pub height: NonZero, + pub info: String, +} + +impl Plan { + pub fn should_execute, DB, SK>(&self, ctx: &CTX) -> bool { + self.height.get() <= ctx.height() + } +} + +impl From for inner::Plan { + fn from(Plan { name, height, info }: Plan) -> Self { + #[allow(deprecated)] + Self { + name: name.into_inner(), + time: None, + height: height.get().into(), + info, + upgraded_client_state: None, + } + } +} + +impl TryFrom for Plan { + type Error = ProtobufError; + + #[allow(deprecated)] + fn try_from( + inner::Plan { + name, + time, + height, + info, + upgraded_client_state, + }: inner::Plan, + ) -> Result { + if time.is_some() || upgraded_client_state.is_some() { + Err(anyhow::anyhow!( + "`time` and `upgraded_client_state` is deprecated" + ))? + } + + Ok(Self { + name: PlanName::try_new(name) + .map_err(|_| ProtobufError::MissingField("`name` is empty".to_owned()))?, + height: u32::try_from(height) + .map_err(|e| e.to_string()) + .and_then(|this| NonZero::new(this).ok_or("height can't be zero".to_owned())) + .map_err(|e| CoreError::DecodeGeneral(format!("invalid `height`: {e}")))?, + info, + }) + } +} + +impl Protobuf for Plan {} diff --git a/x/upgrade/src/types/query.rs b/x/upgrade/src/types/query.rs new file mode 100644 index 000000000..6b417b140 --- /dev/null +++ b/x/upgrade/src/types/query.rs @@ -0,0 +1,81 @@ +use gears::{ + baseapp::QueryRequest, + derive::{Protobuf, Query}, +}; +use serde::{Deserialize, Serialize}; + +use super::{plan::Plan, ModuleVersion}; + +#[derive(Debug, Clone, Query)] +pub enum UpgradeQueryRequest { + Plan(QueryCurrentPlanRequest), + Applied(QueryAppliedPlanRequest), + ModuleVersions(QueryModuleVersionsRequest), +} + +impl QueryRequest for UpgradeQueryRequest { + fn height(&self) -> u32 { + todo!() + } +} + +#[derive(Debug, Clone, Query, serde::Serialize, serde::Deserialize)] +pub enum UpgradeQueryResponse { + Plan(QueryCurrentPlanResponse), + Applied(QueryAppliedPlanResponse), + ModuleVersions(QueryModuleVersionsResponse), +} + +mod inner { + pub use ibc_proto::cosmos::upgrade::v1beta1::QueryAppliedPlanRequest; + pub use ibc_proto::cosmos::upgrade::v1beta1::QueryAppliedPlanResponse; + pub use ibc_proto::cosmos::upgrade::v1beta1::QueryCurrentPlanRequest; + pub use ibc_proto::cosmos::upgrade::v1beta1::QueryCurrentPlanResponse; + pub use ibc_proto::cosmos::upgrade::v1beta1::QueryModuleVersionsRequest; + pub use ibc_proto::cosmos::upgrade::v1beta1::QueryModuleVersionsResponse; + + /* + NOTE: these are deprecated + pub use ibc_proto::cosmos::upgrade::v1beta1::QueryUpgradedConsensusStateRequest; + pub use ibc_proto::cosmos::upgrade::v1beta1::QueryUpgradedConsensusStateResponse; + */ +} + +#[derive(Debug, Clone, Query, Protobuf, Serialize, Deserialize)] +#[proto(raw = "inner::QueryCurrentPlanRequest")] +#[query(url = "/cosmos.upgrade.v1beta1.QueryCurrentPlanRequest")] +pub struct QueryCurrentPlanRequest {} + +#[derive(Debug, Clone, Query, Protobuf, Serialize, Deserialize)] +#[proto(raw = "inner::QueryCurrentPlanResponse")] +pub struct QueryCurrentPlanResponse { + #[proto(optional)] + pub plan: Option, +} + +#[derive(Debug, Clone, Query, Protobuf, Serialize, Deserialize)] +#[proto(raw = "inner::QueryAppliedPlanRequest")] +#[query(url = "/cosmos.upgrade.v1beta1.QueryAppliedPlanRequest")] +pub struct QueryAppliedPlanRequest { + pub name: String, +} + +#[derive(Debug, Clone, Query, Protobuf, Serialize, Deserialize)] +#[proto(raw = "inner::QueryAppliedPlanResponse")] +pub struct QueryAppliedPlanResponse { + pub height: u32, +} + +#[derive(Debug, Clone, Query, Protobuf, Serialize, Deserialize)] +#[proto(raw = "inner::QueryModuleVersionsRequest")] +#[query(url = "/cosmos.upgrade.v1beta1.QueryModuleVersionsRequest")] +pub struct QueryModuleVersionsRequest { + pub module_name: String, +} + +#[derive(Debug, Clone, Query, Protobuf, Serialize, Deserialize)] +#[proto(raw = "inner::QueryModuleVersionsResponse")] +pub struct QueryModuleVersionsResponse { + #[proto(repeated)] + pub module_versions: Vec, +}