paritytech / subxt

Interact with Substrate based nodes in Rust or WebAssembly
Other
407 stars 242 forks source link

proc-macro is broken for PhantomData #111

Closed Demi-Marie closed 4 years ago

Demi-Marie commented 4 years ago

Trying to use the procedural macro for raw storage (that is, storage not in a map) panics with an unwrap failure.

dvc94ch commented 4 years ago

Plain, map and double-map should work. Can you show how you're using the proc-macro. There are a few conventions to follow.

Demi-Marie commented 4 years ago
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of substrate-subxt.
//
// subxt is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// subxt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with substrate-subxt.  If not, see <http://www.gnu.org/licenses/>.
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
use sp_runtime::Perbill;
use std::path::PathBuf;
use structopt::StructOpt;

/// Output format
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum OutputFormat {
    /// Human-readable formatted text
    Text,
    /// Machine-parsable JSON output
    JSON,
    /// Spreadsheet-importable CSV output
    CSV,
}

impl std::str::FromStr for OutputFormat {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "JSON" => Ok(Self::JSON),
            "CSV" => Ok(Self::CSV),
            "Text" => Ok(Self::Text),
            _ => Err(format!("invalid output format {:?}", s)),
        }
    }
}

use codec::Encode;
use frame_support::Parameter;
use sp_core::storage::StorageKey;
use sp_runtime::traits::{MaybeDisplay, MaybeSerialize, Member};
use std::{fmt::Debug, marker::PhantomData};
use substrate_subxt::{system::System, Call, ClientBuilder, Metadata, MetadataError, Store};

/// The trait needed for this module.
pub trait Session: System {
    /// The validator account identifier type for the runtime.
    type ValidatorId: Parameter + Member + MaybeSerialize + Debug + MaybeDisplay + Ord + Default;

    /// The validator account identifier type for the runtime.
    type SessionIndex: Parameter + Member + MaybeSerialize + Debug + MaybeDisplay + Ord + Default;
}

const MODULE: &str = "Session";

/// The current set of validators.
#[derive(Encode, Store)]
pub struct ValidatorsStore<'a, T: Session> {
    /// The current set of validators.
    #[store(returns = Vec<<T as Session>::ValidatorId>)]
    pub account_id: PhantomData<&'a Vec<<T as Session>::ValidatorId>>,
}

/// The current set of validators.
#[cfg(any())]
#[derive(Encode)]
pub struct ValidatorsStore<'a, T: Session>(pub PhantomData<&'a Vec<<T as Session>::ValidatorId>>);
impl<'a, T: Session> Store<T> for ValidatorsStore<'a, T> {
    type Returns = Vec<<T as Session>::ValidatorId>;

    const FIELD: &'static str = "ValidatorsStore";
    const MODULE: &'static str = MODULE;

    fn key(&self, metadata: &Metadata) -> Result<StorageKey, MetadataError> {
        Ok(metadata
            .module(Self::MODULE)?
            .storage(Self::FIELD)?
            .plain()?
            .key())
    }
}

/// Current index of the session.
#[derive(Encode, Store)]
pub struct CurrentIndexStore<'a, T: Session> {
    /// Current index of the session.
    #[store(returns = <T as Session>::SessionIndex)]
    pub current_index: PhantomData<&'a T>,
}

/// True if the underlying economic identities or weighting behind the
/// validators has changed in the queued validator set.
#[derive(Encode, Store)]
pub struct QueuedChanged<'a, T: Session> {
    /// True if the underlying economic identities or weighting behind the
    /// validators has changed in the queued validator set.
    #[store(returns = bool)]
    pub queue_changed: PhantomData<&'a T>,
}

#[derive(Debug, StructOpt)]
#[structopt(name = "Ledgeracio", about = "Ledger CLI for staking")]
struct Ledgeracio {
    /// Enable verbose output
    #[structopt(short, long)]
    verbose: bool,
    /// File containing the PIN. Default is to prompt interactively for the PIN.
    ///
    /// If standard input is not a PTY, operations that require a PIN will error
    /// out if this option is not passed.
    #[structopt(short, long, parse(from_os_str))]
    pin_file: Option<PathBuf>,
    /// Dry run.  Do not execute the operation.
    #[structopt(short = "n", long)]
    dry_run: bool,
    /// USB device to use.  Default is to probe for devices.
    #[structopt(short, long)]
    device: Option<String>,
    /// Interactive mode.  Not yet implemented.  This is the default if no
    /// options are specified.
    #[structopt(short, long)]
    interactive: bool,
    /// Output format
    #[structopt(short, long, default_value = "Text")]
    format: OutputFormat,
    /// RPC host
    #[structopt(short, long, default_value = "wss://kusama-rpc.polkadot.io")]
    host: String,
    /// Network
    #[structopt(long, default_value = "Polkadot")]
    network: String,
    /// Subcommand
    #[structopt(subcommand)]
    cmd: Command,
}

#[derive(StructOpt, Debug)]
enum Command {
    /// Stash operations
    Stash(Stash),
    /// Validator operations
    Validator(Validator),
}

#[derive(StructOpt, Debug)]
enum Stash {
    /// Show the specified stash controller
    Show { index: u32 },
    /// Show the status of all stash controllers
    Status,
    /// Claim a validation payout
    Claim { index: Option<u32> },
    /// Submit a new validator set
    #[structopt(name = "submit-validator-set")]
    SubmitValidatorSet,
    /// Add a new controller key
    #[structopt(name = "add-controller-key")]
    AddControllerKey,
}

/// Counter for the number of eras that have passed.
pub type EraIndex = u32;

/// Preference of what happens regarding validation.
#[derive(PartialEq, Eq, Clone, Encode)]
pub struct ValidatorPrefs {
    /// Reward that validator takes up-front; only the rest is split between
    /// themselves and nominators.
    #[codec(compact)]
    pub commission: Perbill,
}

impl Default for ValidatorPrefs {
    fn default() -> Self {
        ValidatorPrefs {
            commission: Default::default(),
        }
    }
}

/// Claim a payout.
#[derive(PartialEq, Eq, Clone, Encode)]
struct PayoutStakersCall<'a, T: System> {
    pub validator_stash: &'a T::AccountId,
    pub era: EraIndex,
}

/// Claim a payout.
struct ValidateCall<'a> {
    pub validator_stash: &'a ValidatorPrefs,
}

impl<'a, T: System> Call<T> for PayoutStakersCall<'a, T> {
    const FUNCTION: &'static str = "payout_stakers";
    const MODULE: &'static str = "Staking";
}

#[derive(StructOpt, Debug)]
struct Count {
    count: u32,
}

#[derive(StructOpt, Debug)]
struct ValidatorIndex {
    index: u32,
}

#[derive(StructOpt, Debug)]
enum Validator {
    /// Show status of all Validator Controller keys
    Status { index: Option<u32> },
    /// Announce intention to validate
    Announce { index: u32 },
    /// Replace a session key
    ReplaceKey { index: u32 },
    /// Generate new controller keys
    GenerateKeys { count: u32 },
}

impl Session for substrate_subxt::KusamaRuntime {
    type SessionIndex = u32;
    type ValidatorId = <Self as System>::AccountId;
}

#[async_std::main]
async fn main() {
    let args = Ledgeracio::from_args();
    println!("{:?}", args);
    let builder: ClientBuilder<substrate_subxt::KusamaRuntime> = ClientBuilder::new();
    let client = builder.set_url(args.host).build().await.unwrap();
    println!(
        "Fetch result: {:#?}",
        client
            .fetch::<ValidatorsStore<substrate_subxt::KusamaRuntime>>(ValidatorsStore { account_id: PhantomData }, None)
            .await
            .unwrap()
            .unwrap()
    )
}
Demi-Marie commented 4 years ago

Even if I am using the proc macro incorrectly (which is quite possible), the error message is still very poor.

dvc94ch commented 4 years ago
#[derive(Encode, Store)]
pub struct ValidatorsStore<'a, T: Session> {
    /// The current set of validators.
    #[store(returns = Vec<<T as Session>::ValidatorId>)]
    pub _runtime: PhantomData<T>>,
}
Demi-Marie commented 4 years ago

Still panics:

error: proc-macro derive panicked
  --> src/main.rs:64:18
   |
64 | #[derive(Encode, Store)]
   |                  ^^^^^
   |
   = help: message: called `Result::unwrap()` on an `Err` value: Error("expected parentheses")

error: aborting due to previous error

And the source file:

// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of substrate-subxt.
//
// subxt is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// subxt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with substrate-subxt.  If not, see <http://www.gnu.org/licenses/>.
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
use sp_runtime::Perbill;
use std::path::PathBuf;
use structopt::StructOpt;

/// Output format
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum OutputFormat {
    /// Human-readable formatted text
    Text,
    /// Machine-parsable JSON output
    JSON,
    /// Spreadsheet-importable CSV output
    CSV,
}

impl std::str::FromStr for OutputFormat {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "JSON" => Ok(Self::JSON),
            "CSV" => Ok(Self::CSV),
            "Text" => Ok(Self::Text),
            _ => Err(format!("invalid output format {:?}", s)),
        }
    }
}

use codec::Encode;
use frame_support::Parameter;
use sp_core::storage::StorageKey;
use sp_runtime::traits::{MaybeDisplay, MaybeSerialize, Member};
use std::{fmt::Debug, marker::PhantomData};
use substrate_subxt::{system::System, Call, ClientBuilder, Metadata, MetadataError, Store};

/// The trait needed for this module.
pub trait Session: System {
    /// The validator account identifier type for the runtime.
    type ValidatorId: Parameter + Member + MaybeSerialize + Debug + MaybeDisplay + Ord + Default;

    /// The validator account identifier type for the runtime.
    type SessionIndex: Parameter + Member + MaybeSerialize + Debug + MaybeDisplay + Ord + Default;
}

const MODULE: &str = "Session";

/// The current set of validators.
#[derive(Encode, Store)]
pub struct ValidatorsStore<T: Session> {
    /// The current set of validators.
    #[store(returns = Vec<<T as Session>::ValidatorId>)]
    pub _runtime: PhantomData<T>,
}

/// Current index of the session.
#[derive(Encode, Store)]
pub struct CurrentIndexStore<'a, T: Session> {
    /// Current index of the session.
    #[store(returns = <T as Session>::SessionIndex)]
    pub current_index: PhantomData<&'a T>,
}

/// True if the underlying economic identities or weighting behind the
/// validators has changed in the queued validator set.
#[derive(Encode, Store)]
pub struct QueuedChanged<'a, T: Session> {
    /// True if the underlying economic identities or weighting behind the
    /// validators has changed in the queued validator set.
    #[store(returns = bool)]
    pub queue_changed: PhantomData<&'a T>,
}

#[derive(Debug, StructOpt)]
#[structopt(name = "Ledgeracio", about = "Ledger CLI for staking")]
struct Ledgeracio {
    /// Enable verbose output
    #[structopt(short, long)]
    verbose: bool,
    /// File containing the PIN. Default is to prompt interactively for the PIN.
    ///
    /// If standard input is not a PTY, operations that require a PIN will error
    /// out if this option is not passed.
    #[structopt(short, long, parse(from_os_str))]
    pin_file: Option<PathBuf>,
    /// Dry run.  Do not execute the operation.
    #[structopt(short = "n", long)]
    dry_run: bool,
    /// USB device to use.  Default is to probe for devices.
    #[structopt(short, long)]
    device: Option<String>,
    /// Interactive mode.  Not yet implemented.  This is the default if no
    /// options are specified.
    #[structopt(short, long)]
    interactive: bool,
    /// Output format
    #[structopt(short, long, default_value = "Text")]
    format: OutputFormat,
    /// RPC host
    #[structopt(short, long, default_value = "wss://kusama-rpc.polkadot.io")]
    host: String,
    /// Network
    #[structopt(long, default_value = "Polkadot")]
    network: String,
    /// Subcommand
    #[structopt(subcommand)]
    cmd: Command,
}

#[derive(StructOpt, Debug)]
enum Command {
    /// Stash operations
    Stash(Stash),
    /// Validator operations
    Validator(Validator),
}

#[derive(StructOpt, Debug)]
enum Stash {
    /// Show the specified stash controller
    Show { index: u32 },
    /// Show the status of all stash controllers
    Status,
    /// Claim a validation payout
    Claim { index: Option<u32> },
    /// Submit a new validator set
    #[structopt(name = "submit-validator-set")]
    SubmitValidatorSet,
    /// Add a new controller key
    #[structopt(name = "add-controller-key")]
    AddControllerKey,
}

/// Counter for the number of eras that have passed.
pub type EraIndex = u32;

/// Preference of what happens regarding validation.
#[derive(PartialEq, Eq, Clone, Encode)]
pub struct ValidatorPrefs {
    /// Reward that validator takes up-front; only the rest is split between
    /// themselves and nominators.
    #[codec(compact)]
    pub commission: Perbill,
}

impl Default for ValidatorPrefs {
    fn default() -> Self {
        ValidatorPrefs {
            commission: Default::default(),
        }
    }
}

/// Claim a payout.
#[derive(PartialEq, Eq, Clone, Encode)]
struct PayoutStakersCall<'a, T: System> {
    pub validator_stash: &'a T::AccountId,
    pub era: EraIndex,
}

/// Claim a payout.
struct ValidateCall<'a> {
    pub validator_stash: &'a ValidatorPrefs,
}

impl<'a, T: System> Call<T> for PayoutStakersCall<'a, T> {
    const FUNCTION: &'static str = "payout_stakers";
    const MODULE: &'static str = "Staking";
}

#[derive(StructOpt, Debug)]
struct Count {
    count: u32,
}

#[derive(StructOpt, Debug)]
struct ValidatorIndex {
    index: u32,
}

#[derive(StructOpt, Debug)]
enum Validator {
    /// Show status of all Validator Controller keys
    Status { index: Option<u32> },
    /// Announce intention to validate
    Announce { index: u32 },
    /// Replace a session key
    ReplaceKey { index: u32 },
    /// Generate new controller keys
    GenerateKeys { count: u32 },
}

impl Session for substrate_subxt::KusamaRuntime {
    type SessionIndex = u32;
    type ValidatorId = <Self as System>::AccountId;
}

#[async_std::main]
async fn main() {
    let args = Ledgeracio::from_args();
    println!("{:?}", args);
    let builder: ClientBuilder<substrate_subxt::KusamaRuntime> = ClientBuilder::new();
    let client = builder.set_url(args.host).build().await.unwrap();
    println!(
        "Fetch result: {:#?}",
        client
            .fetch::<Validators<substrate_subxt::KusamaRuntime>>(Validators(PhantomData), None)
            .await
            .unwrap()
            .unwrap()
    )
}
Demi-Marie commented 4 years ago

Cargo.toml:

[package]
name = "ledgeracio"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPLv3+"

[dependencies]
substrate-subxt = { git = "git+ssh://github.com/paritytech/substrate-subxt" }
structopt = "0.3.14"
substrate-subxt-proc-macro = { git = "git+ssh://github.com/paritytech/substrate-subxt" }
sp-core = "2.0.0-alpha.8"
codec = { package = "parity-scale-codec", version = "1.3.0" }
sp-runtime = "2.0.0-alpha.8"
frame-support = "2.0.0-alpha.8"
async-std = { version = "1.5.0", features = ["attributes"] }
dvc94ch commented 4 years ago

Well there's still a couple of things wrong. Why you have PhantomData<&'a T>? The lifetime is superfluous. You can look at the tests and examples, it's not that hard.

dvc94ch commented 4 years ago

Also this is wrong:

fetch::<Validators<substrate_subxt::KusamaRuntime>>(Validators(PhantomData)
client.validators().await.unwrap()
Demi-Marie commented 4 years ago

@dvc94ch The proc macro should still give a better error messages. I did look at the tests, but none of them use PhantomData.

dvc94ch commented 4 years ago

The balances and contract modules do. The runtime needs to be present as a type param. The proc macro filters PhantomData. The number of fields is used to find the storage type. So your examples will infer a map because they use PhantomData<&'a T>. Can I clone the example from somewhere?

dvc94ch commented 4 years ago

Also the diagnostics api isn't stabilized so errors in proc macros are annoying

Demi-Marie commented 4 years ago

This is fixed in the current version.