From 16944ea1fcfb21d4c4b5ed6d5bb702a0fbd69e26 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Tue, 14 Feb 2023 19:30:11 +0200 Subject: [PATCH] XXX/Dynamic: Add dynamic Runtime API calls Signed-off-by: Alexandru Vasile --- examples/examples/runtime_calls.rs | 11 +- subxt/src/runtime_api/mod.rs | 2 +- subxt/src/runtime_api/runtime_payload.rs | 130 ++++++++++++++++++++++- subxt/src/runtime_api/runtime_types.rs | 41 ++++++- 4 files changed, 179 insertions(+), 5 deletions(-) diff --git a/examples/examples/runtime_calls.rs b/examples/examples/runtime_calls.rs index f194a3cd8e..f07fc3d15d 100644 --- a/examples/examples/runtime_calls.rs +++ b/examples/examples/runtime_calls.rs @@ -20,6 +20,8 @@ use subxt::{ }, SubstrateConfig, }, + dynamic::Value, + runtime_api::dynamic, tx::PairSigner, OnlineClient, }; @@ -49,10 +51,17 @@ async fn main() -> Result<(), Box> { let alice = AccountKeyring::Alice.to_account_id().into(); let api_tx = polkadot::runtime_api::AccountNonceApi::account_nonce(alice); println!("RuntimeApi payload: {:?}", api_tx); - let bytes = api.runtime_api().at(None).await?.call(api_tx).await?; println!("Result: {:?}", bytes); + let alice = AccountKeyring::Alice.to_account_id(); + let api_tx = dynamic::( + "AccountNonceApi_account_nonce", + vec![Value::from_bytes(&alice)], + ); + let bytes = api.runtime_api().at(None).await?.dyn_call(api_tx).await?; + println!("Result: {:?}", bytes); + // Send from Alice to Bob. let signer = PairSigner::new(AccountKeyring::Alice.pair()); let dest = AccountKeyring::Bob.to_account_id().into(); diff --git a/subxt/src/runtime_api/mod.rs b/subxt/src/runtime_api/mod.rs index 7cfca80f65..42f5ff7dc4 100644 --- a/subxt/src/runtime_api/mod.rs +++ b/subxt/src/runtime_api/mod.rs @@ -9,5 +9,5 @@ mod runtime_payload; mod runtime_types; pub use runtime_client::RuntimeApiClient; -pub use runtime_payload::RuntimeAPIPayload; +pub use runtime_payload::{RuntimeApiPayload, RuntimeAPIPayload, dynamic, DynamicRuntimeApiPayload, StaticRuntimeApiPayload}; pub use runtime_types::RuntimeApi; diff --git a/subxt/src/runtime_api/runtime_payload.rs b/subxt/src/runtime_api/runtime_payload.rs index c8c493d53f..ee5ca72cca 100644 --- a/subxt/src/runtime_api/runtime_payload.rs +++ b/subxt/src/runtime_api/runtime_payload.rs @@ -2,9 +2,23 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use std::marker::PhantomData; +use std::{ + borrow::Cow, + marker::PhantomData, +}; -/// Payload for a runtime API fn. +use codec::{ + Decode, + Encode, +}; +use scale_value::Composite; + +use crate::{ + Error, + Metadata, +}; + +/// Payload for calling into a runtime API function. #[derive(Debug)] pub struct RuntimeAPIPayload { func_name: &'static str, @@ -46,3 +60,115 @@ impl RuntimeAPIPayload { &self.data } } + +/// RuntimeApiPayload +pub trait RuntimeApiPayload { + /// The return type into which the result of the call is interpreted. + type Target; + + // TODO: Could do with some lifetimes. + /// The function name of the runtime API. + fn func_name(&self) -> String; + + /// Encode arguments to the provided output. + fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec) + -> Result<(), Error>; + + /// Encode arguments and return the output. This is a convenience + /// wrapper around [`RuntimeApiPayload::encode_params_to`]. + fn encode_args(&self, metadata: &Metadata) -> Result, Error> { + let mut v = Vec::new(); + self.encode_args_to(metadata, &mut v)?; + Ok(v) + } +} + +/// StaticRuntimeApiPayload +pub struct StaticRuntimeApiPayload { + func_name: &'static str, + data: ArgData, + _marker: PhantomData, +} + +impl RuntimeApiPayload for StaticRuntimeApiPayload +where + ArgData: Encode, + ReturnTy: Decode, +{ + type Target = ReturnTy; + + fn func_name(&self) -> String { + self.func_name.into() + } + + fn encode_args_to( + &self, + _metadata: &Metadata, + out: &mut Vec, + ) -> Result<(), Error> { + self.data.encode_to(out); + Ok(()) + } +} + +/// DynamicRuntimeApiPayload +pub struct DynamicRuntimeApiPayload { + func_name: &'static str, + fields: Composite<()>, + _marker: PhantomData, +} + +// impl<'a, ReturnTy> DynamicRuntimeApiPayload<'a, ReturnTy> { +// pub fn into_value(self) -> Value<()> { + +// } +// } + +/// Construct a dynamic runtime API call. +pub fn dynamic( + func_name: &'static str, + fields: impl Into>, +) -> DynamicRuntimeApiPayload { + DynamicRuntimeApiPayload { + func_name: func_name.into(), + fields: fields.into(), + _marker: PhantomData, + } +} + +impl RuntimeApiPayload for DynamicRuntimeApiPayload +where + ReturnTy: Decode, +{ + type Target = ReturnTy; + + fn encode_args_to( + &self, + metadata: &Metadata, + out: &mut Vec, + ) -> Result<(), Error> { + let args = match &self.fields { + // TODO: Composite::Named WIP. + Composite::Named(_) => panic!("Composite::Named unsupported yet."), + Composite::Unnamed(args) => args, + }; + + let fn_metadata = metadata.runtime_fn(&self.func_name)?; + let param_ty = fn_metadata.params_ty_ids(); + + if param_ty.len() != args.len() { + return Err(Error::Other( + "Provided different number of params than expected".into(), + )) + } + + for (value, ty) in args.iter().zip(param_ty) { + scale_value::scale::encode_as_type(value, *ty, metadata.types(), out)?; + } + Ok(()) + } + + fn func_name(&self) -> String { + self.func_name.to_owned().into() + } +} diff --git a/subxt/src/runtime_api/runtime_types.rs b/subxt/src/runtime_api/runtime_types.rs index 9b3b10f2ff..239fa56193 100644 --- a/subxt/src/runtime_api/runtime_types.rs +++ b/subxt/src/runtime_api/runtime_types.rs @@ -14,7 +14,13 @@ use std::{ marker::PhantomData, }; -use super::RuntimeAPIPayload; +use super::{ + runtime_payload::{ + DynamicRuntimeApiPayload, + RuntimeApiPayload, + }, + RuntimeAPIPayload, +}; /// Execute runtime API calls. #[derive(Derivative)] @@ -74,6 +80,7 @@ where let payload = payload; let function = payload.func_name(); let call_parameters = Some(payload.param_data()); + println!("StaticCall: {:?}", call_parameters); let data = client .rpc() @@ -84,4 +91,36 @@ where Ok(result) } } + + /// Dynamic call. + pub fn dyn_call( + &self, + payload: DynCall, + ) -> impl Future> + where + DynCall: RuntimeApiPayload, + ::Target: Decode, + { + let client = self.client.clone(); + let block_hash = self.block_hash; + + // Ensure that the returned future doesn't have a lifetime tied to api.runtime_api(), + // which is a temporary thing we'll be throwing away quickly: + async move { + let payload = payload; + let function = payload.func_name(); + + let bytes = payload.encode_args(&client.metadata())?; + let call_parameters = Some(bytes.as_slice()); + println!("DynCall: {:?}", call_parameters); + + let data = client + .rpc() + .state_call(&function, call_parameters, Some(block_hash)) + .await?; + + let result: DynCall::Target = Decode::decode(&mut &data.0[..])?; + Ok(result) + } + } }