substrate icon indicating copy to clipboard operation
substrate copied to clipboard

Improve Usage of Constants in Pallets

Open shawntabrizi opened this issue 3 years ago • 13 comments

At the moment, all configurations in the runtime use trait Get<T> in order to pass those configurations from the runtime definition to the pallet.

However, many of these configurations are assumed to be constant, while others are not. At the Configuration API level, we provide no distinction for these differences, and thus are prone to errors caused by runtime misconfigurations.

To improve this, I propose the following changes:

  • [ ] Add a ConstGet<T> trait, with documentation that any use of this trait must be implemented by a true constant value.
  • [ ] Generate a tiny bit of macro code which will wrap a const in a trait into a ConstGet<T> so it can be used within the runtime for things like BoundedVec
  • [ ] Change BoundedVec to use ConstGet<T> rather than just Get<T>
  • [ ] Go through pallets and migrate all those which can easily convert Get<T> into const where it makes sense.
  • [ ] Those that do not migrate easily, migrate to ConstGet<T> where it makes sense.
  • [ ] Update paramter_types! macro to generate ConstGet<T> on const, but not things like static and storage

shawntabrizi avatar Oct 07 '21 22:10 shawntabrizi

The hard part of this exercise is to properly identify which Get<T> functions should be turned into const, ConstGet<T> or stay Get<T>.

Unfortunately we cannot migrate everything which SHOULD be a const into that since const generic features are not fully stabilized: https://github.com/paritytech/substrate/pull/9865

shawntabrizi avatar Oct 07 '21 22:10 shawntabrizi

Hi @shawntabrizi can i take on this issue

Doordashcon avatar Oct 27 '21 15:10 Doordashcon

@Doordashcon Yes, please go ahead! If you need any help, please ping me or Shawn in the Substrate Technical channel on Element/Discord.

KiChjang avatar Oct 27 '21 16:10 KiChjang

@KiChjang sure thing

Doordashcon avatar Oct 27 '21 16:10 Doordashcon

@KiChjang want to submit task by task?

Doordashcon avatar Nov 04 '21 10:11 Doordashcon

I think the first two tasks can be done in 1 PR. Make sure your PR targets paritytech/substrate, not your fork

shawntabrizi avatar Nov 04 '21 13:11 shawntabrizi

is this the general idea for the second task?

#[impl_const]
trait ConstantId {
  const ID: i32;
}

the attribute-like macro #[impl_const] will implement ConstGet<T> for associated constants in ConstantId

impl ConstGet<i32> for i32 {
  fn get() -> i32
}

Doordashcon avatar Nov 09 '21 09:11 Doordashcon

@Doordashcon yes, but ideally within the trait itself?

trait ConstantId {
  #[impl_const_get]
  const ID: i32;
}

Is that possible?

shawntabrizi avatar Nov 11 '21 14:11 shawntabrizi

I don't think the macro would know where it is called, so it cannot know about the ConstantId trait, which is a needed element for the expansion.

Another approach would be to keep the attribute on the trait definition, but require a helper annotation on const

#[apply(derive_Gets!)]
trait ConstantId {
    #[Get]
    const ID: i32;
}

And have the outer macro skip the associated items with no #[Get] applied to them.

I would use a crate called macro_use_attribute with the alias apply which allows the use of declarative macros as proc_macro attributes or derives.

Doordashcon avatar Nov 12 '21 20:11 Doordashcon

Generate a tiny bit of macro code which will wrap a const in a trait into a ConstGet<T> so it can be used within the runtime for things like BoundedVec

This syntax:

trait ConstantId {
  #[impl_const_get]
  const ID: i32;
}

is actually doable in FRAME, assuming that ConstantId means the pallet's Config trait. Even if ConstantId means something else, we can make it so that any associated constant defined inside of a trait under #[frame_support::pallet] would parse for an attribute above itself.

KiChjang avatar Nov 13 '21 03:11 KiChjang

The syntax

trait ConstantId {
    #[impl_const_get]
    const ID: i32;
}

would —within a sane implementation— be unable to generate an impl … block since that's not a valid assoc item for trait ConstantId definition:

//! Expanded code

trait ConstantId {
    const ID: i32;

    impl … // Uh oh
}

While there exists a contrived workaround to allow for such an impl … declaration nested inside the const ID: i32, I highly recommend that the trait just take an external annotation:

//! Expansion: incorrect Rust

#[preprocessor_name] // <- actual proc-macro attribute   <-------------------------------------------------+
trait ConstantId {                                                                                      // |
    #[const_get] // <- fake proc-macro attr / inert attr that shall just act as a syntactical marker for --+
    const ID: i32;
}

It's way easier to implement (and the result is more readable). In fact, for a non-generic trait, with only associated constants, that shall be picked or ignored by the "preprocessor", then such a preprocessor can be implemented with a macro_rules! macro, that can then be, if so desired, #[apply]ed as an attribute.

danielhenrymantilla avatar Nov 15 '21 18:11 danielhenrymantilla

@danielhenrymantilla That is my point, we already have #[frame_support::pallet] as a preprocessor for all pallet definitions.

See the directory under frame/support/procedural/src/pallet for more information.

KiChjang avatar Nov 16 '21 01:11 KiChjang

We needed this feature in our pallet, the workaround we found is using traits, but is not optimal:

trait ConstGet<T> {
    const VALUE: T;
}

struct Two;
impl ConstGet<usize> for Two {
    const VALUE: usize = 2;
}

struct Four;
impl ConstGet<usize> for Four {
    const VALUE: usize = 4;
}

struct FixedArray<const N: usize>([u32; N]);
impl <const N: usize> FixedArray<N> {
    fn new() -> Self {
        Self([0;N])
    }
}

type TwoByteArray = FixedArray::<{Two::VALUE}>;
type FourByteArray = FixedArray::<{Four::VALUE}>;

fn main() {
    let a = TwoByteArray::new();
    let b = FourByteArray::new();
    println!("a: {:?}", a.0);
    println!("b: {:?}", b.0);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=daee4712ad219a12546465b8ddf774bf

Then the Config definition is something like this:

/// Configuration trait.
#[pallet::config]
pub trait Config: frame_system::Config {
    ...
    // Constant array size
    type ArraySize: ConstGet<usize>;
}

Lohann avatar Jan 19 '22 20:01 Lohann