## Substrate: Extending metadata with deprecation information Hi, in the past month a PR we've been working on adding deprecation info support inside `RuntimeMetadata`. #### [Click here to see the rust doc about `deprecated` attribute usage](https://doc.rust-lang.org/nightly/reference/attributes/diagnostics.html#the-deprecated-attribute) #### [Click here to see the relevant PR](https://github.com/paritytech/polkadot-sdk/pull/4851) #### [Click here to see the relevant issue](https://github.com/paritytech/polkadot-sdk/issues/4098) :::spoiler click me to see the motivation from the issue linked above ## Motivation As a dApp/tooling developer interacting with the blockchain through JS libraries and RPCs, there are no warnings or any information letting you know that a method you're using is deprecated. No such information is included in metadata or RPC responses, so there is no way to programmatically read this information. The only way to realize you're using a deprecated method is to follow the chain: A polkadot-sdk release including a PR that marks a given method as deprecated. A runtimes release that upgrades polkadot-sdk to this polkadot-sdk release. An OpenGov referendum that upgrades the runtime with this runtimes release. Finally, the approval and enactment of this referendum. Only following this chain of releases you can realize that a method you're using without any warning or issues starts being deprecated. It can fail for many reasons (either from insufficiently propagating deprecation/breaking-changes information down this list, or from developer failing to keep himself up to date). I posted some more thoughts here. Because of that, I think that there is a lack of deprecation information that can be consumed programmatically when running a program and interacting with the chain. ::: In this short doc I'll try to explain how the deprecation information will be represented in the `MetadataIR` and how it would be propagated forward along with some peculiar edge cases. ### High level overview With deprecation info support inside `MetadataIR` it's now possible to just mark relevant declarations with a `#[deprecated]` attribute and the information will be reflected in the metadata. Items that can be deprecated are listed below with examples. #### `Pallet` declaration - marked with `frame_support::pallet` attribute :::spoiler Usage example and produced information about deprecation inside metadata struct ```rust #[frame_support::pallet] #[deprecated = "test"] pub mod pallet { ... } ... pub struct PalletMetadataIR<T> { ... deprecation_info: DeprecationStatusIR::Deprecated { note: "test", since: None } } ``` ::: #### `Event` declaration - marked with `frame_support::pallet::event` attribute :::spoiler Usage example and produced information about deprecation inside metadata struct Full event deprecation: ```rust #[pallet::event] #[deprecated(note = "test")] pub enum Event<T: Config> { ExampleA, ExampleB, } ... pub struct PalletEventMetadataIR<T> { ... deprecation_info: DeprecationInfoIR::FullyDeprecated( DeprecationStatusIR::Deprecated { note: "test", since: None } ) } ``` Deprecating single/multiple variants of the Event enum: ```rust #[pallet::event] pub enum Event<T: Config> { ExampleA, #[deprecated(note = "test")] ExampleB, } ... pub struct PalletEventMetadataIR<T> { ... deprecation_info: DeprecationInfoIR::PartiallyDeprecated( [ // '1' is the index of the `ExampleB` variant in the declaration. // if codec::index or explicit discriminant are // defined we will use them instead. 1: DeprecationStatusIR::Deprecated { note: "test", since: None }, ] ) } ``` ::: #### `Error` declaration - marked with `frame_support::pallet::error` attribute :::spoiler Usage example and produced information about deprecation inside metadata struct Full Error deprecation: ```rust #[pallet::error] #[deprecated(note = "test")] pub enum Error<T> { ExampleA, ExampleB, } ... pub struct PalletErrorMetadataIR<T> { ... deprecation_info: DeprecationInfoIR::FullyDeprecated( DeprecationStatusIR::Deprecated { note: "test", since: None } ) } ``` Deprecating single/multiple variants of the Error enum: ```rust #[pallet::error] pub enum Error<T> { ExampleA, #[deprecated(note = "test")] ExampleB, } ... pub struct PalletErrorMetadataIR<T> { ... deprecation_info: DeprecationInfoIR::PartiallyDeprecated( [ // '1' is the index of the `ExampleB` variant in the declaration. // if codec::index or explicit discriminant are // defined we will use them instead. 1: DeprecationStatusIR::Deprecated { note: "test", since: None }, ] ) } ``` ::: #### `Call` declaration - marked with `frame_support::pallet::call` attribute :::spoiler Usage example and produced information about deprecation inside metadata struct ```rust #[pallet::call] impl<T: Config> Pallet<T> { #[pallet::call_index(0)] #[deprecated = "test"] pub fn foo( origin: OriginFor<T>, ) -> DispatchResultWithPostInfo { ... } ... } ... pub struct PalletCallMetadataIR<T> { ... deprecation_info: DeprecationInfoIR::PartiallyDeprecated( [ // '0' is the call index. 0: DeprecationStatusIR::Deprecated { note: "test", since: None }, ] ), } ``` ::: #### `StorageItem` declaration - marked with `frame_support::pallet::storage` attribute :::spoiler Usage example and produced information about deprecation inside metadata struct ```rust #[pallet::storage] #[deprecated(note = "test")] pub type OptionLinkedMap<T> = StorageMap<_, Blake2_128Concat, u32, u32, OptionQuery>; ... pub struct StorageEntryMetadataIR<T> { ... deprecation_info: DeprecationStatusIR::Deprecated { note: "test", since: None }, } ``` ::: #### `Constant` declaration - marked with `frame_support::pallet::storage` attribute :::spoiler Usage example and produced information about deprecation inside metadata struct ```rust #[pallet::config] pub trait Config: 'static { ... #[pallet::constant] #[deprecated = "this constant is deprecated"] type ExampleConstant: Get<()>; } ... pub struct PalletConstantMetadataIR<T> { ... pub deprecation_info: DeprecationStatusIR::Deprecated { note: "this constant is deprecated", since: None }, } ``` ::: #### `Runtime Apis` and their respective methods - marked with `sp_api::decl_runtime_apis` attribute :::spoiler Usage example and produced information about deprecation inside metadata struct ```rust sp_api::decl_runtime_apis! { #[deprecated] pub trait Api { #[deprecated(note = "example_method")] fn example_method(); } } ... pub struct RuntimeApiMetadataIR<T> { ... pub methods: [ pub struct RuntimeApiMethodMetadataIR<T> { pub name: "example_method", ... pub deprecation_info: DeprecationStatusIR::Deprecated { note: "example_method", since: None }, }, ], pub deprecation_info: DeprecationStatusIR::DeprecatedWithoutNote, } ``` ::: ### Limitations #### Enums and codec indexes For enums of `Event`s or `Error`s we use index of the variant inside the declaration or a discriminant/`codec::index` if they are present. In case of `codec::index` a loss of deprecation information might occur. Example provided below: ```rust #[pallet::event] pub enum Event<T: Config> { #[deprecated = "second"] A, #[deprecated = "first"] #[codec(index = 0)] B, } ``` Expected result would be a dictionary having both of the variants deprecated, but instead we get the result below ([see this issue on parity-scale-codec](https://github.com/paritytech/parity-scale-codec/issues/507)). ```rust DeprecationInfoIR::PartiallyDeprecated( [ 0: DeprecationStatusIR::Deprecated { note: "first", since: None }, ] ) ``` #### Enums and full/partial deprecation Currently its only allowed to deprecate either the whole enum or some of its variants individually. ```rust /// Ok #[pallet::event] pub enum Event<T: Config> { #[deprecated] A, #[deprecated] B, } /// Also Ok #[pallet::event] #[deprecated] pub enum Event<T: Config> { A, B, } /// Not ok #[pallet::event] #[deprecated] pub enum Event<T: Config> { #[deprecated] A, B, } ```