Skip to content

Commit 467d1f6

Browse files
pandres95gui1117ggwpez
authoredMar 21, 2025··
Fix: [Referenda Tracks] Resolve representation issues that are breaking PJS apps (#7671)
This Pull Request fixes the following issues present with PJS derived from #2072 - Represents `Tracks` constant as a `Vec<(TrackId, TrackInfo)>` instead of `Vec<Track>`, so tracks iteration in PJS apps _Governance_ hook remains as how it was before, and ![Screenshot 2025-02-22 at 12 31 28 pm](https://github.com/user-attachments/assets/a075b0b2-8984-43cc-9e72-99996de0ae1b) - Encapsulates `[u8; N]` from `name` field in a `StringLike` structure that represents its `TypeInfo` as a `&str` and encodes as such. This fixes errors present in PJS apps that treat `name` as a javascript `string`. ![Screenshot 2025-02-22 at 12 31 18 pm](https://github.com/user-attachments/assets/361a4f10-47b7-496d-9591-bc37afcf5ee1) --------- Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
1 parent 855c47b commit 467d1f6

File tree

3 files changed

+121
-10
lines changed

3 files changed

+121
-10
lines changed
 

‎prdoc/pr_7671.prdoc

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
title: 'Fix: [Referenda Tracks] Resolve representation issues that are breaking PJS apps'
2+
3+
doc:
4+
- audience: Runtime Dev
5+
description: |-
6+
The PR #2072 introduces a change in the representation of the `name` field, from a `&str` to a `[u8; N]` array. This is because
7+
tracks can be retrieves from storage, and thus, a static string representation doesn't meet with the storage traits requirements.
8+
9+
This PR encapsulates this array into a `StringLike` structure that allows representing the value as a `str` for SCALE and metadata
10+
purposes. This is to avoid breaking changes.
11+
12+
This PR also reverts the representation of the `Tracks` constant as a tuple of `(TrackId, TrackInfo)` to accomplish the same
13+
purpose of avoid breaking changes to runtime users and clients.
14+
crates:
15+
- name: pallet-referenda
16+
bump: minor
17+
- name: collectives-westend-runtime
18+
bump: minor
19+
- name: kitchensink-runtime
20+
bump: minor
21+
- name: rococo-runtime
22+
bump: minor
23+
- name: westend-runtime
24+
bump: minor

‎substrate/frame/referenda/src/lib.rs

+29-6
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,11 @@ use self::branch::{BeginDecidingBranch, OneFewerDecidingBranch, ServiceBranch};
9797
pub use self::{
9898
pallet::*,
9999
types::{
100-
BalanceOf, BlockNumberFor, BoundedCallOf, CallOf, Curve, DecidingStatus, DecidingStatusOf,
101-
Deposit, InsertSorted, NegativeImbalanceOf, PalletsOriginOf, ReferendumIndex,
102-
ReferendumInfo, ReferendumInfoOf, ReferendumStatus, ReferendumStatusOf, ScheduleAddressOf,
103-
TallyOf, Track, TrackIdOf, TrackInfo, TrackInfoOf, TracksInfo, VotesOf,
100+
BalanceOf, BlockNumberFor, BoundedCallOf, CallOf, ConstTrackInfo, Curve, DecidingStatus,
101+
DecidingStatusOf, Deposit, InsertSorted, NegativeImbalanceOf, PalletsOriginOf,
102+
ReferendumIndex, ReferendumInfo, ReferendumInfoOf, ReferendumStatus, ReferendumStatusOf,
103+
ScheduleAddressOf, StringLike, TallyOf, Track, TrackIdOf, TrackInfo, TrackInfoOf,
104+
TracksInfo, VotesOf,
104105
},
105106
weights::WeightInfo,
106107
};
@@ -224,9 +225,31 @@ pub mod pallet {
224225

225226
#[pallet::extra_constants]
226227
impl<T: Config<I>, I: 'static> Pallet<T, I> {
228+
/// A list of tracks.
229+
///
230+
/// Note: if the tracks are dynamic, the value in the static metadata might be inaccurate.
227231
#[pallet::constant_name(Tracks)]
228-
fn tracks() -> Vec<Track<TrackIdOf<T, I>, BalanceOf<T, I>, BlockNumberFor<T, I>>> {
229-
T::Tracks::tracks().map(|t| t.into_owned()).collect()
232+
fn tracks() -> Vec<(TrackIdOf<T, I>, ConstTrackInfo<BalanceOf<T, I>, BlockNumberFor<T, I>>)>
233+
{
234+
T::Tracks::tracks()
235+
.map(|t| t.into_owned())
236+
.map(|Track { id, info }| {
237+
(
238+
id,
239+
ConstTrackInfo {
240+
name: StringLike(info.name),
241+
max_deciding: info.max_deciding,
242+
decision_deposit: info.decision_deposit,
243+
prepare_period: info.prepare_period,
244+
decision_period: info.decision_period,
245+
confirm_period: info.confirm_period,
246+
min_enactment_period: info.min_enactment_period,
247+
min_approval: info.min_approval,
248+
min_support: info.min_support,
249+
},
250+
)
251+
})
252+
.collect()
230253
}
231254
}
232255

‎substrate/frame/referenda/src/types.rs

+68-4
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919
2020
use super::*;
2121
use alloc::borrow::Cow;
22-
use codec::{Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen};
22+
use codec::{Compact, Decode, DecodeWithMemTracking, Encode, EncodeLike, Input, MaxEncodedLen};
2323
use core::fmt::Debug;
2424
use frame_support::{
2525
traits::{schedule::v3::Anon, Bounded},
2626
Parameter,
2727
};
28-
use scale_info::TypeInfo;
28+
use scale_info::{Type, TypeInfo};
2929
use sp_arithmetic::{Rounding::*, SignedRounding::*};
3030
use sp_runtime::{FixedI64, PerThing, RuntimeDebug};
3131

@@ -118,13 +118,61 @@ pub struct Deposit<AccountId, Balance> {
118118

119119
pub const DEFAULT_MAX_TRACK_NAME_LEN: usize = 25;
120120

121+
/// Helper structure to treat a `[u8; N]` array as a string.
122+
///
123+
/// This is a temporary fix (see [#7671](https://github.com/paritytech/polkadot-sdk/pull/7671)) in
124+
/// order to stop `polkadot.js` apps to fail when trying to decode the `name` field in `TrackInfo`.
125+
#[derive(Clone, Eq, DecodeWithMemTracking, PartialEq, Debug)]
126+
pub struct StringLike<const N: usize>(pub [u8; N]);
127+
128+
impl<const N: usize> TypeInfo for StringLike<N> {
129+
type Identity = <&'static str as TypeInfo>::Identity;
130+
131+
fn type_info() -> Type {
132+
<&str as TypeInfo>::type_info()
133+
}
134+
}
135+
136+
impl<const N: usize> MaxEncodedLen for StringLike<N> {
137+
fn max_encoded_len() -> usize {
138+
<Compact<u32> as MaxEncodedLen>::max_encoded_len().saturating_add(N)
139+
}
140+
}
141+
142+
impl<const N: usize> Encode for StringLike<N> {
143+
fn encode(&self) -> Vec<u8> {
144+
use codec::Compact;
145+
(Compact(N as u32), self.0).encode()
146+
}
147+
}
148+
149+
impl<const N: usize> Decode for StringLike<N> {
150+
fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
151+
let Compact(size): Compact<u32> = Decode::decode(input)?;
152+
if size != N as u32 {
153+
return Err("Invalid size".into());
154+
}
155+
156+
let bytes: [u8; N] = Decode::decode(input)?;
157+
Ok(Self(bytes))
158+
}
159+
}
160+
161+
/// Detailed information about the configuration of a referenda track. Used for internal storage.
162+
pub type TrackInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> =
163+
TrackDetails<Balance, Moment, [u8; N]>;
164+
165+
/// Detailed information about the configuration of a referenda track. Used for const querying.
166+
pub type ConstTrackInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> =
167+
TrackDetails<Balance, Moment, StringLike<N>>;
168+
121169
/// Detailed information about the configuration of a referenda track
122170
#[derive(
123171
Clone, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Eq, PartialEq, Debug,
124172
)]
125-
pub struct TrackInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> {
173+
pub struct TrackDetails<Balance, Moment, Name> {
126174
/// Name of this track.
127-
pub name: [u8; N],
175+
pub name: Name,
128176
/// A limit for the number of referenda on this track that can be being decided at once.
129177
/// For Root origin this should generally be just one.
130178
pub max_deciding: u32,
@@ -795,4 +843,20 @@ mod tests {
795843
Err("The tracks that were returned by `tracks` were not sorted by `Id`")
796844
);
797845
}
846+
847+
#[test]
848+
fn encoding_and_decoding_of_string_like_structure_works() {
849+
let string_like = StringLike::<13>(*b"hello, world!");
850+
let encoded: Vec<u8> = string_like.encode();
851+
852+
let decoded_as_vec: Vec<u8> =
853+
Decode::decode(&mut &encoded.clone()[..]).expect("decoding as Vec<u8> should work");
854+
assert_eq!(decoded_as_vec.len(), 13);
855+
let decoded_as_str: alloc::string::String =
856+
Decode::decode(&mut &encoded.clone()[..]).expect("decoding as str should work");
857+
assert_eq!(decoded_as_str.len(), 13);
858+
let decoded_as_string_like: StringLike<13> =
859+
Decode::decode(&mut &encoded.clone()[..]).expect("decoding as StringLike should work");
860+
assert_eq!(decoded_as_string_like.0.len(), 13);
861+
}
798862
}

0 commit comments

Comments
 (0)
Please sign in to comment.