subxt_core/runtime_api/
payload.rs

1// Copyright 2019-2024 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or GPL-3.0.
3// see LICENSE for license details.
4
5//! This module contains the trait and types used to represent
6//! runtime API calls that can be made.
7
8use alloc::borrow::Cow;
9use alloc::borrow::ToOwned;
10use alloc::string::String;
11use alloc::vec::Vec;
12use core::marker::PhantomData;
13use derive_where::derive_where;
14use scale_encode::EncodeAsFields;
15use scale_value::Composite;
16
17use crate::dynamic::DecodedValueThunk;
18use crate::error::MetadataError;
19use crate::Error;
20
21use crate::metadata::{DecodeWithMetadata, Metadata};
22
23/// This represents a runtime API payload that can call into the runtime of node.
24///
25/// # Components
26///
27/// - associated return type
28///
29/// Resulting bytes of the call are interpreted into this type.
30///
31/// - runtime function name
32///
33/// The function name of the runtime API call. This is obtained by concatenating
34/// the runtime trait name with the trait's method.
35///
36/// For example, the substrate runtime trait [Metadata](https://github.com/paritytech/substrate/blob/cb954820a8d8d765ce75021e244223a3b4d5722d/primitives/api/src/lib.rs#L745)
37/// contains the `metadata_at_version` function. The corresponding runtime function
38/// is `Metadata_metadata_at_version`.
39///
40/// - encoded arguments
41///
42/// Each argument of the runtime function must be scale-encoded.
43pub trait Payload {
44    /// The return type of the function call.
45    // Note: `DecodeWithMetadata` is needed to decode the function call result
46    // with the `subxt::Metadata.
47    type ReturnType: DecodeWithMetadata;
48
49    /// The runtime API trait name.
50    fn trait_name(&self) -> &str;
51
52    /// The runtime API method name.
53    fn method_name(&self) -> &str;
54
55    /// Scale encode the arguments data.
56    fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
57
58    /// Encode arguments data and return the output. This is a convenience
59    /// wrapper around [`Payload::encode_args_to`].
60    fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
61        let mut v = Vec::new();
62        self.encode_args_to(metadata, &mut v)?;
63        Ok(v)
64    }
65
66    /// Returns the statically generated validation hash.
67    fn validation_hash(&self) -> Option<[u8; 32]> {
68        None
69    }
70}
71
72/// A runtime API payload containing the generic argument data
73/// and interpreting the result of the call as `ReturnTy`.
74///
75/// This can be created from static values (ie those generated
76/// via the `subxt` macro) or dynamic values via [`dynamic`].
77#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsData)]
78pub struct DefaultPayload<ArgsData, ReturnTy> {
79    trait_name: Cow<'static, str>,
80    method_name: Cow<'static, str>,
81    args_data: ArgsData,
82    validation_hash: Option<[u8; 32]>,
83    _marker: PhantomData<ReturnTy>,
84}
85
86/// A statically generated runtime API payload.
87pub type StaticPayload<ArgsData, ReturnTy> = DefaultPayload<ArgsData, ReturnTy>;
88/// A dynamic runtime API payload.
89pub type DynamicPayload = DefaultPayload<Composite<()>, DecodedValueThunk>;
90
91impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
92    for DefaultPayload<ArgsData, ReturnTy>
93{
94    type ReturnType = ReturnTy;
95
96    fn trait_name(&self) -> &str {
97        &self.trait_name
98    }
99
100    fn method_name(&self) -> &str {
101        &self.method_name
102    }
103
104    fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
105        let api_method = metadata
106            .runtime_api_trait_by_name_err(&self.trait_name)?
107            .method_by_name(&self.method_name)
108            .ok_or_else(|| MetadataError::RuntimeMethodNotFound((*self.method_name).to_owned()))?;
109        let mut fields = api_method
110            .inputs()
111            .map(|input| scale_encode::Field::named(input.ty, &input.name));
112
113        self.args_data
114            .encode_as_fields_to(&mut fields, metadata.types(), out)?;
115        Ok(())
116    }
117
118    fn validation_hash(&self) -> Option<[u8; 32]> {
119        self.validation_hash
120    }
121}
122
123impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
124    /// Create a new [`DefaultPayload`].
125    pub fn new(
126        trait_name: impl Into<String>,
127        method_name: impl Into<String>,
128        args_data: ArgsData,
129    ) -> Self {
130        DefaultPayload {
131            trait_name: Cow::Owned(trait_name.into()),
132            method_name: Cow::Owned(method_name.into()),
133            args_data,
134            validation_hash: None,
135            _marker: PhantomData,
136        }
137    }
138
139    /// Create a new static [`DefaultPayload`] using static function name
140    /// and scale-encoded argument data.
141    ///
142    /// This is only expected to be used from codegen.
143    #[doc(hidden)]
144    pub fn new_static(
145        trait_name: &'static str,
146        method_name: &'static str,
147        args_data: ArgsData,
148        hash: [u8; 32],
149    ) -> DefaultPayload<ArgsData, ReturnTy> {
150        DefaultPayload {
151            trait_name: Cow::Borrowed(trait_name),
152            method_name: Cow::Borrowed(method_name),
153            args_data,
154            validation_hash: Some(hash),
155            _marker: core::marker::PhantomData,
156        }
157    }
158
159    /// Do not validate this call prior to submitting it.
160    pub fn unvalidated(self) -> Self {
161        Self {
162            validation_hash: None,
163            ..self
164        }
165    }
166
167    /// Returns the trait name.
168    pub fn trait_name(&self) -> &str {
169        &self.trait_name
170    }
171
172    /// Returns the method name.
173    pub fn method_name(&self) -> &str {
174        &self.method_name
175    }
176
177    /// Returns the arguments data.
178    pub fn args_data(&self) -> &ArgsData {
179        &self.args_data
180    }
181}
182
183/// Create a new [`DynamicPayload`].
184pub fn dynamic(
185    trait_name: impl Into<String>,
186    method_name: impl Into<String>,
187    args_data: impl Into<Composite<()>>,
188) -> DynamicPayload {
189    DefaultPayload::new(trait_name, method_name, args_data.into())
190}