Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make working with nested queries a touch easier #714

Merged
merged 21 commits into from
Nov 15, 2022
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
Binary file modified artifacts/polkadot_metadata.scale
Binary file not shown.
79 changes: 79 additions & 0 deletions examples/examples/dynamic_multisig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.31-3711c6f9b2a.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.31/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```

use sp_keyring::AccountKeyring;
use subxt::{
dynamic::Value,
tx::PairSigner,
OnlineClient,
PolkadotConfig,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();

// My account.
let signer_account = AccountKeyring::Alice;
let signer_account_id = signer_account.to_account_id();
let signer = PairSigner::new(signer_account.pair());

// Transfer balance to this destination:
let dest = AccountKeyring::Bob.to_account_id();

// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;

// Create the inner balance transfer call.
let inner_tx = subxt::dynamic::tx(
"Balances",
"transfer",
vec![
Value::unnamed_variant("Id", [Value::from_bytes(&dest)]),
Value::u128(123_456_789_012_345),
],
);

// Now, build an outer call which this inner call will be a part of.
// This sets up the multisig arrangement.
//
// Note: Since this is a dynamic call, we can either use named or unnamed
// arguments (if unnamed, the order matters).
let tx = subxt::dynamic::tx(
"Multisig",
"as_multi",
vec![
("threshold", Value::u128(1)),
(
"other_signatories",
Value::unnamed_composite([Value::from_bytes(&signer_account_id)]),
),
("maybe_timepoint", Value::unnamed_variant("None", [])),
("call", inner_tx.into_value()),
(
"max_weight",
Value::named_composite([
("ref_time", Value::u128(10000000000)),
("proof_size", Value::u128(1)),
]),
),
],
);

// Submit it:
let encoded = hex::encode(&api.tx().call_data(&tx)?);
println!("Call data: {encoded}");
let tx_hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
println!("Submitted tx with hash {tx_hash}");

Ok(())
}
76 changes: 76 additions & 0 deletions examples/examples/multisig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.31-3711c6f9b2a.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.31/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```

use sp_keyring::AccountKeyring;
use subxt::{
tx::PairSigner,
OnlineClient,
PolkadotConfig,
};

#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();

// My account.
let signer_account = AccountKeyring::Alice;
let signer_account_id = signer_account.to_account_id();
let signer = PairSigner::new(signer_account.pair());

// Transfer balance to this destination:
let dest = AccountKeyring::Bob.to_account_id();

// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;

// Create the inner balance transfer call.
//
// Note: This call, being manually constructed, will have a specific pallet and call index
// which is determined by the generated code. If you're trying to submit this to a node which
// has the pallets/calls at different indexes, it will fail. See `dynamic_multisig.rs` for a
// workaround in this case which will work regardless of pallet and call indexes.
let inner_tx = polkadot::runtime_types::polkadot_runtime::RuntimeCall::Balances(
polkadot::runtime_types::pallet_balances::pallet::Call::transfer {
dest: dest.into(),
value: 123_456_789_012_345,
},
);

// Now, build an outer call which this inner call will be a part of.
// This sets up the multisig arrangement.
let tx = polkadot::tx().multisig().as_multi(
// threshold
1,
// other signatories
vec![signer_account_id],
// maybe timepoint
None,
// call
inner_tx,
// max weight
polkadot::runtime_types::sp_weights::weight_v2::Weight {
ref_time: 10000000000,
proof_size: 1,
},
);

// Submit the extrinsic with default params:
let encoded = hex::encode(&api.tx().call_data(&tx)?);
println!("Call data: {encoded}");
let tx_hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
println!("Submitted tx with hash {tx_hash}");

Ok(())
}
4 changes: 2 additions & 2 deletions subxt/src/tx/tx_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
{
let metadata = self.client.metadata();
let mut bytes = Vec::new();
call.encode_call_data(&metadata, &mut bytes)?;
call.encode_call_data_to(&metadata, &mut bytes)?;
Ok(bytes)
}

Expand All @@ -101,7 +101,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
// transaction protocol version (4) (is not signed, so no 1 bit at the front).
4u8.encode_to(&mut encoded_inner);
// encode call data after this byte.
call.encode_call_data(&self.client.metadata(), &mut encoded_inner)?;
call.encode_call_data_to(&self.client.metadata(), &mut encoded_inner)?;
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len())
Expand Down
60 changes: 52 additions & 8 deletions subxt/src/tx/tx_payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,31 @@ use crate::{
metadata::Metadata,
};
use codec::Encode;
use scale_value::{
Composite,
ValueDef,
Variant,
};
use std::borrow::Cow;

/// This represents a transaction payload that can be submitted
/// to a node.
pub trait TxPayload {
/// Encode call data to the provided output.
fn encode_call_data(
fn encode_call_data_to(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error>;

/// Encode call data and return the output. This is a convenience
/// wrapper around [`TxPayload::encode_call_data_to`].
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_call_data_to(metadata, &mut v)?;
Ok(v)
}

/// Returns the details needed to validate the call, which
/// include a statically generated hash, the pallet name,
/// and the call name.
Expand Down Expand Up @@ -83,7 +96,7 @@ impl<CallData> StaticTxPayload<CallData> {
}

impl<CallData: Encode> TxPayload for StaticTxPayload<CallData> {
fn encode_call_data(
fn encode_call_data_to(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
Expand Down Expand Up @@ -113,32 +126,63 @@ impl<CallData: Encode> TxPayload for StaticTxPayload<CallData> {
pub struct DynamicTxPayload<'a> {
pallet_name: Cow<'a, str>,
call_name: Cow<'a, str>,
fields: Vec<Value<()>>,
fields: Composite<()>,
}

impl<'a> DynamicTxPayload<'a> {
/// Return the pallet name.
pub fn pallet_name(&self) -> &str {
&self.pallet_name
}

/// Return the call name.
pub fn call_name(&self) -> &str {
&self.call_name
}

/// Convert the dynamic payload into a [`Value`]. This is useful
/// if you need to submit this as part of a larger call.
pub fn into_value(self) -> Value<()> {
let call = Value {
context: (),
value: ValueDef::Variant(Variant {
name: self.call_name.into_owned(),
values: self.fields,
}),
};

Value::unnamed_variant(self.pallet_name, [call])
}
}

/// Construct a new dynamic transaction payload to submit to a node.
pub fn dynamic<'a>(
pallet_name: impl Into<Cow<'a, str>>,
call_name: impl Into<Cow<'a, str>>,
fields: Vec<Value<()>>,
fields: impl Into<Composite<()>>,
) -> DynamicTxPayload<'a> {
DynamicTxPayload {
pallet_name: pallet_name.into(),
call_name: call_name.into(),
fields,
fields: fields.into(),
}
}

impl<'a> TxPayload for DynamicTxPayload<'a> {
fn encode_call_data(
fn encode_call_data_to(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error> {
let pallet = metadata.pallet(&self.pallet_name)?;
let call_id = pallet.call_ty_id().ok_or(MetadataError::CallNotFound)?;
let call_value =
Value::unnamed_variant(self.call_name.clone(), self.fields.clone());
let call_value = Value {
context: (),
value: ValueDef::Variant(Variant {
name: self.call_name.to_string(),
values: self.fields.clone(),
}),
};

pallet.index().encode_to(out);
scale_value::scale::encode_as_type(&call_value, call_id, metadata.types(), out)?;
Expand Down
Loading