diff --git a/subxt/src/client/online_client.rs b/subxt/src/client/online_client.rs index 0d264681c9..703777f26a 100644 --- a/subxt/src/client/online_client.rs +++ b/subxt/src/client/online_client.rs @@ -17,7 +17,7 @@ use crate::{ tx::TxClient, Config, Metadata, }; -use codec::{Compact, Decode}; +use codec::Compact; use derivative::Derivative; use frame_metadata::RuntimeMetadataPrefixed; use futures::future; @@ -136,10 +136,9 @@ impl OnlineClient { /// Fetch the metadata from substrate using the runtime API. async fn fetch_metadata(rpc: &Rpc) -> Result { - let bytes = rpc.state_call("Metadata_metadata", None, None).await?; - let cursor = &mut &*bytes; - let _ = >::decode(cursor)?; - let meta: RuntimeMetadataPrefixed = Decode::decode(cursor)?; + let (_, meta) = rpc + .state_call::<(Compact, RuntimeMetadataPrefixed)>("Metadata_metadata", None, None) + .await?; Ok(meta.try_into()?) } diff --git a/subxt/src/rpc/rpc.rs b/subxt/src/rpc/rpc.rs index 64f146de5d..b21b9d4311 100644 --- a/subxt/src/rpc/rpc.rs +++ b/subxt/src/rpc/rpc.rs @@ -31,16 +31,19 @@ //! # } //! ``` +use std::sync::Arc; + +use codec::{Decode, Encode}; +use frame_metadata::RuntimeMetadataPrefixed; +use serde::Serialize; + +use crate::{error::Error, utils::PhantomDataSendSync, Config, Metadata}; + use super::{ rpc_params, types::{self, ChainHeadEvent, FollowEvent}, RpcClient, RpcClientT, Subscription, }; -use crate::{error::Error, utils::PhantomDataSendSync, Config, Metadata}; -use codec::{Decode, Encode}; -use frame_metadata::RuntimeMetadataPrefixed; -use serde::Serialize; -use std::sync::Arc; /// Client for substrate rpc interfaces pub struct Rpc { @@ -151,25 +154,6 @@ impl Rpc { Ok(metadata) } - /// Execute a runtime API call. - pub async fn call( - &self, - function: String, - call_parameters: Option<&[u8]>, - at: Option, - ) -> Result { - let call_parameters = call_parameters.unwrap_or_default(); - - let bytes: types::Bytes = self - .client - .request( - "state_call", - rpc_params![function, to_hex(call_parameters), at], - ) - .await?; - Ok(bytes) - } - /// Fetch system properties pub async fn system_properties(&self) -> Result { self.client @@ -364,14 +348,13 @@ impl Rpc { } /// Execute a runtime API call. - pub async fn state_call( + pub async fn state_call( &self, function: &str, call_parameters: Option<&[u8]>, at: Option, - ) -> Result { + ) -> Result { let call_parameters = call_parameters.unwrap_or_default(); - let bytes: types::Bytes = self .client .request( @@ -379,7 +362,9 @@ impl Rpc { rpc_params![function, to_hex(call_parameters), at], ) .await?; - Ok(bytes) + let cursor = &mut &bytes[..]; + let res: Res = Decode::decode(cursor)?; + Ok(res) } /// Create and submit an extrinsic and return a subscription to the events triggered. diff --git a/subxt/src/runtime_api/runtime_types.rs b/subxt/src/runtime_api/runtime_types.rs index 70162e4f08..7bcd3436b9 100644 --- a/subxt/src/runtime_api/runtime_types.rs +++ b/subxt/src/runtime_api/runtime_types.rs @@ -3,6 +3,7 @@ // see LICENSE for license details. use crate::{client::OnlineClientT, error::Error, Config}; +use codec::Decode; use derivative::Derivative; use std::{future::Future, marker::PhantomData}; @@ -32,21 +33,21 @@ where Client: OnlineClientT, { /// Execute a raw runtime API call. - pub fn call_raw<'a>( + pub fn call_raw<'a, Res: Decode>( &self, function: &'a str, call_parameters: Option<&'a [u8]>, - ) -> impl Future, Error>> + 'a { + ) -> impl Future> + 'a { 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 data = client + let data: Res = client .rpc() .state_call(function, call_parameters, Some(block_hash)) .await?; - Ok(data.0) + Ok(data) } } } diff --git a/subxt/src/tx/tx_client.rs b/subxt/src/tx/tx_client.rs index 9eb9fa279a..0b73a964e1 100644 --- a/subxt/src/tx/tx_client.rs +++ b/subxt/src/tx/tx_client.rs @@ -2,17 +2,18 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use super::TxPayload; +use std::borrow::Cow; + +use codec::{Compact, Encode}; +use derivative::Derivative; + use crate::{ client::{OfflineClientT, OnlineClientT}, config::{Config, ExtrinsicParams, Hasher}, error::Error, - tx::{Signer as SignerT, TxProgress}, + tx::{Signer as SignerT, TxPayload, TxProgress}, utils::{Encoded, PhantomDataSendSync}, }; -use codec::{Compact, Encode}; -use derivative::Derivative; -use std::borrow::Cow; // This is returned from an API below, so expose it here. pub use crate::rpc::types::DryRunResult; @@ -465,4 +466,23 @@ where let dry_run_bytes = self.client.rpc().dry_run(self.encoded(), at).await?; dry_run_bytes.into_dry_run_result(&self.client.metadata()) } + + /// This returns an estimate for what the extrinsic is expected to cost to execute, less any tips. + /// The actual amount paid can vary from block to block based on node traffic and other factors. + pub async fn partial_fee_estimate(&self) -> Result { + let mut params = self.encoded().to_vec(); + (self.encoded().len() as u32).encode_to(&mut params); + // destructuring RuntimeDispatchInfo, see type information + // data layout: {weight_ref_time: Compact, weight_proof_size: Compact, class: u8, partial_fee: u128} + let (_, _, _, partial_fee) = self + .client + .rpc() + .state_call::<(Compact, Compact, u8, u128)>( + "TransactionPaymentApi_query_info", + Some(¶ms), + None, + ) + .await?; + Ok(partial_fee) + } } diff --git a/subxt/src/tx/tx_progress.rs b/subxt/src/tx/tx_progress.rs index 09a5a40119..97aa77e8c3 100644 --- a/subxt/src/tx/tx_progress.rs +++ b/subxt/src/tx/tx_progress.rs @@ -416,11 +416,7 @@ mod test { use crate::{ client::{OfflineClientT, OnlineClientT}, - config::{ - extrinsic_params::BaseExtrinsicParams, - polkadot::{PlainTip, PolkadotConfig}, - WithExtrinsicParams, - }, + config::{extrinsic_params::BaseExtrinsicParams, polkadot::PlainTip, WithExtrinsicParams}, error::RpcError, rpc::{types::SubstrateTxStatus, RpcSubscription, Subscription}, tx::TxProgress, @@ -429,15 +425,23 @@ mod test { use serde_json::value::RawValue; + type MockTxProgress = TxProgress; + type MockHash = , + > as Config>::Hash; + type MockSubstrateTxStatus = SubstrateTxStatus; + + /// a mock client to satisfy trait bounds in tests #[derive(Clone, Debug)] struct MockClient; - impl OfflineClientT for MockClient { + impl OfflineClientT for MockClient { fn metadata(&self) -> crate::Metadata { panic!("just a mock impl to satisfy trait bounds") } - fn genesis_hash(&self) -> ::Hash { + fn genesis_hash(&self) -> ::Hash { panic!("just a mock impl to satisfy trait bounds") } @@ -446,15 +450,8 @@ mod test { } } - type MockTxProgress = TxProgress; - type MockHash = , - > as Config>::Hash; - type MockSubstrateTxStatus = SubstrateTxStatus; - - impl OnlineClientT for MockClient { - fn rpc(&self) -> &crate::rpc::Rpc { + impl OnlineClientT for MockClient { + fn rpc(&self) -> &crate::rpc::Rpc { panic!("just a mock impl to satisfy trait bounds") } } diff --git a/testing/integration-tests/src/blocks/mod.rs b/testing/integration-tests/src/blocks/mod.rs index ed3fb4b5e5..d9c37c2e73 100644 --- a/testing/integration-tests/src/blocks/mod.rs +++ b/testing/integration-tests/src/blocks/mod.rs @@ -3,7 +3,7 @@ // see LICENSE for license details. use crate::test_context; -use codec::{Compact, Decode}; +use codec::Compact; use frame_metadata::RuntimeMetadataPrefixed; use futures::StreamExt; @@ -101,10 +101,9 @@ async fn runtime_api_call() -> Result<(), subxt::Error> { let block = sub.next().await.unwrap()?; let rt = block.runtime_api().await?; - let bytes = rt.call_raw("Metadata_metadata", None).await?; - let cursor = &mut &*bytes; - let _ = >::decode(cursor)?; - let meta: RuntimeMetadataPrefixed = Decode::decode(cursor)?; + let (_, meta) = rt + .call_raw::<(Compact, RuntimeMetadataPrefixed)>("Metadata_metadata", None) + .await?; let metadata_call = match meta.1 { frame_metadata::RuntimeMetadata::V14(metadata) => metadata, _ => panic!("Metadata V14 unavailable"), diff --git a/testing/integration-tests/src/client/mod.rs b/testing/integration-tests/src/client/mod.rs index 7b4fdb0d45..2dee4a2c66 100644 --- a/testing/integration-tests/src/client/mod.rs +++ b/testing/integration-tests/src/client/mod.rs @@ -388,15 +388,11 @@ async fn rpc_state_call() { let api = ctx.client(); // Call into the runtime of the chain to get the Metadata. - let metadata_bytes = api + let (_, meta) = api .rpc() - .state_call("Metadata_metadata", None, None) + .state_call::<(Compact, RuntimeMetadataPrefixed)>("Metadata_metadata", None, None) .await .unwrap(); - - let cursor = &mut &*metadata_bytes; - let _ = >::decode(cursor).unwrap(); - let meta: RuntimeMetadataPrefixed = Decode::decode(cursor).unwrap(); let metadata_call = match meta.1 { frame_metadata::RuntimeMetadata::V14(metadata) => metadata, _ => panic!("Metadata V14 unavailable"), @@ -582,3 +578,75 @@ async fn chainhead_unstable_unpin() { .await .is_err()); } + +/// taken from original type +#[derive(Encode, Decode, Debug, Clone, Eq, PartialEq)] +pub struct FeeDetails { + /// The minimum fee for a transaction to be included in a block. + pub inclusion_fee: Option, + /// tip + pub tip: u128, +} + +/// taken from original type +/// The base fee and adjusted weight and length fees constitute the _inclusion fee_. +#[derive(Encode, Decode, Debug, Clone, Eq, PartialEq)] +pub struct InclusionFee { + /// minimum amount a user pays for a transaction. + pub base_fee: u128, + /// amount paid for the encoded length (in bytes) of the transaction. + pub len_fee: u128, + /// + /// - `targeted_fee_adjustment`: This is a multiplier that can tune the final fee based on the + /// congestion of the network. + /// - `weight_fee`: This amount is computed based on the weight of the transaction. Weight + /// accounts for the execution time of a transaction. + /// + /// adjusted_weight_fee = targeted_fee_adjustment * weight_fee + pub adjusted_weight_fee: u128, +} + +#[tokio::test] +async fn partial_fee_estimate_correct() { + let ctx = test_context().await; + let api = ctx.client(); + + let alice = pair_signer(AccountKeyring::Alice.pair()); + let hans = pair_signer(Sr25519Pair::generate().0); + + let tx = node_runtime::tx() + .balances() + .transfer(hans.account_id().clone().into(), 1_000_000_000_000); + + let signed_extrinsic = api + .tx() + .create_signed(&tx, &alice, Default::default()) + .await + .unwrap(); + + // Method I: TransactionPaymentApi_query_info + let partial_fee_1 = signed_extrinsic.partial_fee_estimate().await.unwrap(); + + // Method II: TransactionPaymentApi_query_fee_details + calculations + let len_bytes: [u8; 4] = (signed_extrinsic.encoded().len() as u32).to_le_bytes(); + let encoded_with_len = [signed_extrinsic.encoded(), &len_bytes[..]].concat(); + let InclusionFee { + base_fee, + len_fee, + adjusted_weight_fee, + } = api + .rpc() + .state_call::( + "TransactionPaymentApi_query_fee_details", + Some(&encoded_with_len), + None, + ) + .await + .unwrap() + .inclusion_fee + .unwrap(); + let partial_fee_2 = base_fee + len_fee + adjusted_weight_fee; + + // Both methods should yield the same fee + assert_eq!(partial_fee_1, partial_fee_2); +}