Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
9a458d3
fix(pallet-referenda): represent `Tracks` constant as `Vec<(TrackId, …
pandres95 Feb 22, 2025
f03a5a8
fix(pallet-referenda): define `StringLike` to represent a static arra…
pandres95 Feb 22, 2025
44b58c7
chore: add prdoc
pandres95 Feb 22, 2025
906a0a0
feat(pallet-referenda): apply suggestions from @ggwpez's review
pandres95 Feb 24, 2025
279cff2
fix(pallet-referenda): make clippy happy
pandres95 Feb 24, 2025
18662d0
Merge branch 'master' into fix/represent-tracks-constant-as-a-vec-id-…
pandres95 Feb 24, 2025
df8d03e
fix: make ci happy
pandres95 Feb 24, 2025
29d1650
Merge branch 'master' into fix/represent-tracks-constant-as-a-vec-id-…
pandres95 Feb 26, 2025
91b963b
chore(pallet-referenda): fmt/add docs for `StringLike`
pandres95 Mar 13, 2025
18393f3
chore(pallet-referenda): set different types for `TrackInfo` (storage…
pandres95 Mar 13, 2025
28ad818
chore(pallet-referenda): revert change of `str_array`
pandres95 Mar 13, 2025
1afaf8c
Merge branch 'master' into fix/represent-tracks-constant-as-a-vec-id-…
pandres95 Mar 13, 2025
65706fe
Merge branch 'master' into fix/represent-tracks-constant-as-a-vec-id-…
pandres95 Mar 13, 2025
0bc3716
chore(pallet-referenda):document `tracks` constant function.
pandres95 Mar 13, 2025
3130968
feat(pallet-referenda): test `StringLike` encoding and decoding.
pandres95 Mar 13, 2025
7ef3632
Merge branch 'master' into fix/represent-tracks-constant-as-a-vec-id-…
gui1117 Mar 20, 2025
b5762a7
Merge branch 'master' into fix/represent-tracks-constant-as-a-vec-id-…
ggwpez Mar 21, 2025
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_7671.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
72 changes: 68 additions & 4 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, DecodeWithMemTracking, 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,13 +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, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Eq, PartialEq, Debug,
)]
pub struct TrackInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> {
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 @@ -795,4 +843,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