Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions prdoc/pr_7991.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
title: 'Fix: [Referenda Tracks] Resolve representation issues that are breaking PJS apps'

doc:
- audience: Runtime Dev
description: |-
The PR #2072 introduces a change in the representation of the `name` field, from a `&str` to a `[u8; N]` array. This is because
tracks can be retrieves from storage, and thus, a static string representation doesn't meet with the storage traits requirements.

This PR encapsulates this array into a `StringLike` structure that allows representing the value as a `str` for SCALE and metadata
purposes. This is to avoid breaking changes.

This PR also reverts the representation of the `Tracks` constant as a tuple of `(TrackId, TrackInfo)` to accomplish the same
purpose of avoid breaking changes to runtime users and clients.
crates:
- name: pallet-referenda
bump: minor
- name: collectives-westend-runtime
bump: minor
- name: kitchensink-runtime
bump: minor
- name: rococo-runtime
bump: minor
- name: westend-runtime
bump: minor
35 changes: 29 additions & 6 deletions substrate/frame/referenda/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,11 @@ use self::branch::{BeginDecidingBranch, OneFewerDecidingBranch, ServiceBranch};
pub use self::{
pallet::*,
types::{
BalanceOf, BlockNumberFor, BoundedCallOf, CallOf, Curve, DecidingStatus, DecidingStatusOf,
Deposit, InsertSorted, NegativeImbalanceOf, PalletsOriginOf, ReferendumIndex,
ReferendumInfo, ReferendumInfoOf, ReferendumStatus, ReferendumStatusOf, ScheduleAddressOf,
TallyOf, Track, TrackIdOf, TrackInfo, TrackInfoOf, TracksInfo, VotesOf,
BalanceOf, BlockNumberFor, BoundedCallOf, CallOf, ConstTrackInfo, Curve, DecidingStatus,
DecidingStatusOf, Deposit, InsertSorted, NegativeImbalanceOf, PalletsOriginOf,
ReferendumIndex, ReferendumInfo, ReferendumInfoOf, ReferendumStatus, ReferendumStatusOf,
ScheduleAddressOf, StringLike, TallyOf, Track, TrackIdOf, TrackInfo, TrackInfoOf,
TracksInfo, VotesOf,
},
weights::WeightInfo,
};
Expand Down Expand Up @@ -224,9 +225,31 @@ pub mod pallet {

#[pallet::extra_constants]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// A list of tracks.
///
/// Note: if the tracks are dynamic, the value in the static metadata might be inaccurate.
#[pallet::constant_name(Tracks)]
fn tracks() -> Vec<Track<TrackIdOf<T, I>, BalanceOf<T, I>, BlockNumberFor<T, I>>> {
T::Tracks::tracks().map(|t| t.into_owned()).collect()
fn tracks() -> Vec<(TrackIdOf<T, I>, ConstTrackInfo<BalanceOf<T, I>, BlockNumberFor<T, I>>)>
{
T::Tracks::tracks()
.map(|t| t.into_owned())
.map(|Track { id, info }| {
(
id,
ConstTrackInfo {
name: StringLike(info.name),
max_deciding: info.max_deciding,
decision_deposit: info.decision_deposit,
prepare_period: info.prepare_period,
decision_period: info.decision_period,
confirm_period: info.confirm_period,
min_enactment_period: info.min_enactment_period,
min_approval: info.min_approval,
min_support: info.min_support,
},
)
})
.collect()
}
}

Expand Down
78 changes: 72 additions & 6 deletions substrate/frame/referenda/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@

use super::*;
use alloc::borrow::Cow;
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use codec::{Compact, Decode, DecodeWithMemTracking, Encode, EncodeLike, Input, MaxEncodedLen};
use core::fmt::Debug;
use frame_support::{
traits::{schedule::v3::Anon, Bounded},
Parameter,
};
use scale_info::TypeInfo;
use scale_info::{Type, TypeInfo};
use sp_arithmetic::{Rounding::*, SignedRounding::*};
use sp_runtime::{FixedI64, PerThing, RuntimeDebug};

Expand Down Expand Up @@ -118,11 +118,61 @@ pub struct Deposit<AccountId, Balance> {

pub const DEFAULT_MAX_TRACK_NAME_LEN: usize = 25;

/// Helper structure to treat a `[u8; N]` array as a string.
///
/// This is a temporary fix (see [#7671](https://github.com/paritytech/polkadot-sdk/pull/7671)) in
/// order to stop `polkadot.js` apps to fail when trying to decode the `name` field in `TrackInfo`.
#[derive(Clone, Eq, DecodeWithMemTracking, PartialEq, Debug)]
pub struct StringLike<const N: usize>(pub [u8; N]);

impl<const N: usize> TypeInfo for StringLike<N> {
type Identity = <&'static str as TypeInfo>::Identity;

fn type_info() -> Type {
<&str as TypeInfo>::type_info()
}
}

impl<const N: usize> MaxEncodedLen for StringLike<N> {
fn max_encoded_len() -> usize {
<Compact<u32> as MaxEncodedLen>::max_encoded_len().saturating_add(N)
}
}

impl<const N: usize> Encode for StringLike<N> {
fn encode(&self) -> Vec<u8> {
use codec::Compact;
(Compact(N as u32), self.0).encode()
}
}

impl<const N: usize> Decode for StringLike<N> {
fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
let Compact(size): Compact<u32> = Decode::decode(input)?;
if size != N as u32 {
return Err("Invalid size".into());
}

let bytes: [u8; N] = Decode::decode(input)?;
Ok(Self(bytes))
}
}

/// Detailed information about the configuration of a referenda track. Used for internal storage.
pub type TrackInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> =
TrackDetails<Balance, Moment, [u8; N]>;

/// Detailed information about the configuration of a referenda track. Used for const querying.
pub type ConstTrackInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> =
TrackDetails<Balance, Moment, StringLike<N>>;

/// Detailed information about the configuration of a referenda track
#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, Eq, PartialEq, Debug)]
pub struct TrackInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> {
#[derive(
Clone, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Eq, PartialEq, Debug,
)]
pub struct TrackDetails<Balance, Moment, Name> {
/// Name of this track.
pub name: [u8; N],
pub name: Name,
/// A limit for the number of referenda on this track that can be being decided at once.
/// For Root origin this should generally be just one.
pub max_deciding: u32,
Expand Down Expand Up @@ -324,7 +374,7 @@ impl<

/// Type for describing a curve over the 2-dimensional space of axes between 0-1, as represented
/// by `(Perbill, Perbill)`.
#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)]
#[derive(Clone, Eq, PartialEq, Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen)]
#[cfg_attr(not(feature = "std"), derive(RuntimeDebug))]
pub enum Curve {
/// Linear curve starting at `(0, ceil)`, proceeding linearly to `(length, floor)`, then
Expand Down Expand Up @@ -791,4 +841,20 @@ mod tests {
Err("The tracks that were returned by `tracks` were not sorted by `Id`")
);
}

#[test]
fn encoding_and_decoding_of_string_like_structure_works() {
let string_like = StringLike::<13>(*b"hello, world!");
let encoded: Vec<u8> = string_like.encode();

let decoded_as_vec: Vec<u8> =
Decode::decode(&mut &encoded.clone()[..]).expect("decoding as Vec<u8> should work");
assert_eq!(decoded_as_vec.len(), 13);
let decoded_as_str: alloc::string::String =
Decode::decode(&mut &encoded.clone()[..]).expect("decoding as str should work");
assert_eq!(decoded_as_str.len(), 13);
let decoded_as_string_like: StringLike<13> =
Decode::decode(&mut &encoded.clone()[..]).expect("decoding as StringLike should work");
assert_eq!(decoded_as_string_like.0.len(), 13);
}
}
Loading