mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 13:01:07 +00:00
First pass adding back the main APIs
This commit is contained in:
@@ -2,11 +2,15 @@ mod offline_client;
|
||||
mod online_client;
|
||||
|
||||
use crate::config::{Config, HashFor};
|
||||
use crate::constants::ConstantsClient;
|
||||
use crate::custom_values::CustomValuesClient;
|
||||
use crate::error::{EventsError, ExtrinsicError};
|
||||
use crate::events::Events;
|
||||
use crate::extrinsics::Extrinsics;
|
||||
use crate::runtime_apis::RuntimeApisClient;
|
||||
use crate::storage::StorageClient;
|
||||
use crate::transactions::TransactionsClient;
|
||||
use crate::view_functions::ViewFunctionsClient;
|
||||
use core::marker::PhantomData;
|
||||
use subxt_metadata::Metadata;
|
||||
|
||||
@@ -45,6 +49,24 @@ where
|
||||
StorageClient::new(self.client.clone())
|
||||
}
|
||||
|
||||
/// Access constants at this block.
|
||||
pub fn constants(&self) -> ConstantsClient<T, Client> {
|
||||
ConstantsClient::new(self.client.clone())
|
||||
}
|
||||
|
||||
pub fn custom_values(&self) -> CustomValuesClient<T, Client> {
|
||||
CustomValuesClient::new(self.client.clone())
|
||||
}
|
||||
|
||||
/// Access runtime APIs at this block.
|
||||
pub fn runtime_apis(&self) -> RuntimeApisClient<T, Client> {
|
||||
RuntimeApisClient::new(self.client.clone())
|
||||
}
|
||||
|
||||
pub fn view_functions(&self) -> ViewFunctionsClient<T, Client> {
|
||||
ViewFunctionsClient::new(self.client.clone())
|
||||
}
|
||||
|
||||
/// Obtain a reference to the metadata.
|
||||
pub fn metadata_ref(&self) -> &Metadata {
|
||||
self.client.metadata_ref()
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
use crate::client::OfflineClientAtBlockT;
|
||||
use crate::config::Config;
|
||||
use crate::error::ConstantError;
|
||||
use address::Address;
|
||||
use frame_decode::constants::ConstantTypeInfo;
|
||||
use scale_decode::IntoVisitor;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub mod address;
|
||||
|
||||
/// A client for working with storage entries.
|
||||
#[derive(Clone)]
|
||||
pub struct ConstantsClient<T, Client> {
|
||||
client: Client,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, Client> ConstantsClient<T, Client> {
|
||||
pub(crate) fn new(client: Client) -> Self {
|
||||
ConstantsClient {
|
||||
client,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, Client: OfflineClientAtBlockT<T>> ConstantsClient<T, Client> {
|
||||
/// Run the validation logic against some constant address you'd like to access. Returns `Ok(())`
|
||||
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
|
||||
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
|
||||
/// the pallet or constant in question do not exist at all).
|
||||
pub fn validate<Addr: Address>(&self, address: Addr) -> Result<(), ConstantError> {
|
||||
let metadata = self.client.metadata_ref();
|
||||
if let Some(actual_hash) = address.validation_hash() {
|
||||
let expected_hash = metadata
|
||||
.pallet_by_name(address.pallet_name())
|
||||
.ok_or_else(|| {
|
||||
ConstantError::PalletNameNotFound(address.pallet_name().to_string())
|
||||
})?
|
||||
.constant_hash(address.constant_name())
|
||||
.ok_or_else(|| ConstantError::ConstantNameNotFound {
|
||||
pallet_name: address.pallet_name().to_string(),
|
||||
constant_name: address.constant_name().to_owned(),
|
||||
})?;
|
||||
if actual_hash != expected_hash {
|
||||
return Err(ConstantError::IncompatibleCodegen);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Access the constant at the given address, returning the value defined by this address.
|
||||
pub fn entry<Addr: Address>(&self, address: Addr) -> Result<Addr::Target, ConstantError> {
|
||||
let metadata = self.client.metadata_ref();
|
||||
|
||||
// 1. Validate constant shape if hash given:
|
||||
self.validate(&address)?;
|
||||
|
||||
// 2. Attempt to decode the constant into the type given:
|
||||
let constant = frame_decode::constants::decode_constant(
|
||||
address.pallet_name(),
|
||||
address.constant_name(),
|
||||
metadata,
|
||||
metadata.types(),
|
||||
Addr::Target::into_visitor(),
|
||||
)
|
||||
.map_err(ConstantError::CouldNotDecodeConstant)?;
|
||||
|
||||
Ok(constant)
|
||||
}
|
||||
|
||||
/// Access the bytes of a constant by its address.
|
||||
pub fn entry_bytes<Addr: Address>(&self, address: Addr) -> Result<Vec<u8>, ConstantError> {
|
||||
// 1. Validate custom value shape if hash given:
|
||||
self.validate(&address)?;
|
||||
|
||||
// 2. Return the underlying bytes:
|
||||
let constant = self
|
||||
.client
|
||||
.metadata_ref()
|
||||
.constant_info(address.pallet_name(), address.constant_name())
|
||||
.map_err(|e| ConstantError::ConstantInfoError(e.into_owned()))?;
|
||||
Ok(constant.bytes.to_vec())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! Construct addresses to access constants with.
|
||||
|
||||
use derive_where::derive_where;
|
||||
use scale_decode::DecodeAsType;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// This represents a constant address. Anything implementing this trait
|
||||
/// can be used to fetch constants.
|
||||
pub trait Address {
|
||||
/// The target type of the value that lives at this address.
|
||||
type Target: DecodeAsType;
|
||||
|
||||
/// The name of the pallet that the constant lives under.
|
||||
fn pallet_name(&self) -> &str;
|
||||
|
||||
/// The name of the constant in a given pallet.
|
||||
fn constant_name(&self) -> &str;
|
||||
|
||||
/// An optional hash which, if present, will be checked against
|
||||
/// the node metadata to confirm that the return type matches what
|
||||
/// we are expecting.
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Any reference to an address is a valid address.
|
||||
impl<A: Address + ?Sized> Address for &'_ A {
|
||||
type Target = A::Target;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
A::pallet_name(*self)
|
||||
}
|
||||
|
||||
fn constant_name(&self) -> &str {
|
||||
A::constant_name(*self)
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
A::validation_hash(*self)
|
||||
}
|
||||
}
|
||||
|
||||
// (str, str) and similar are valid addresses.
|
||||
impl<A: AsRef<str>, B: AsRef<str>> Address for (A, B) {
|
||||
type Target = scale_value::Value;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
fn constant_name(&self) -> &str {
|
||||
self.1.as_ref()
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents the address of a constant.
|
||||
#[derive_where(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub struct StaticAddress<ReturnTy> {
|
||||
pallet_name: Cow<'static, str>,
|
||||
constant_name: Cow<'static, str>,
|
||||
constant_hash: Option<[u8; 32]>,
|
||||
_marker: core::marker::PhantomData<ReturnTy>,
|
||||
}
|
||||
|
||||
/// A dynamic lookup address to access a constant.
|
||||
pub type DynamicAddress<ReturnTy> = StaticAddress<ReturnTy>;
|
||||
|
||||
impl<ReturnTy> StaticAddress<ReturnTy> {
|
||||
/// Create a new [`StaticAddress`] to use to look up a constant.
|
||||
pub fn new(pallet_name: impl Into<String>, constant_name: impl Into<String>) -> Self {
|
||||
Self {
|
||||
pallet_name: Cow::Owned(pallet_name.into()),
|
||||
constant_name: Cow::Owned(constant_name.into()),
|
||||
constant_hash: None,
|
||||
_marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`StaticAddress`] that will be validated
|
||||
/// against node metadata using the hash given.
|
||||
#[doc(hidden)]
|
||||
pub fn new_static(
|
||||
pallet_name: &'static str,
|
||||
constant_name: &'static str,
|
||||
hash: [u8; 32],
|
||||
) -> Self {
|
||||
Self {
|
||||
pallet_name: Cow::Borrowed(pallet_name),
|
||||
constant_name: Cow::Borrowed(constant_name),
|
||||
constant_hash: Some(hash),
|
||||
_marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Do not validate this constant prior to accessing it.
|
||||
pub fn unvalidated(self) -> Self {
|
||||
Self {
|
||||
pallet_name: self.pallet_name,
|
||||
constant_name: self.constant_name,
|
||||
constant_hash: None,
|
||||
_marker: self._marker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<ReturnTy: DecodeAsType> Address for StaticAddress<ReturnTy> {
|
||||
type Target = ReturnTy;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
&self.pallet_name
|
||||
}
|
||||
|
||||
fn constant_name(&self) -> &str {
|
||||
&self.constant_name
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
self.constant_hash
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new dynamic constant lookup.
|
||||
pub fn dynamic<ReturnTy: DecodeAsType>(
|
||||
pallet_name: impl Into<String>,
|
||||
constant_name: impl Into<String>,
|
||||
) -> DynamicAddress<ReturnTy> {
|
||||
DynamicAddress::new(pallet_name, constant_name)
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
use crate::client::OfflineClientAtBlockT;
|
||||
use crate::config::Config;
|
||||
use crate::error::CustomValueError;
|
||||
use crate::utils::Maybe;
|
||||
use address::Address;
|
||||
use derive_where::derive_where;
|
||||
use frame_decode::custom_values::CustomValueTypeInfo;
|
||||
use scale_decode::IntoVisitor;
|
||||
|
||||
pub mod address;
|
||||
|
||||
/// A client for accessing custom values stored in the metadata.
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct CustomValuesClient<T, Client> {
|
||||
client: Client,
|
||||
marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, Client> CustomValuesClient<T, Client> {
|
||||
/// Create a new [`CustomValuesClient`].
|
||||
pub(crate) fn new(client: Client) -> Self {
|
||||
Self {
|
||||
client,
|
||||
marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, Client: OfflineClientAtBlockT<T>> CustomValuesClient<T, Client> {
|
||||
/// Run the validation logic against some custom value address you'd like to access. Returns `Ok(())`
|
||||
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
|
||||
/// Returns an error if the address was not valid (wrong name, type or raw bytes)
|
||||
pub fn validate<Addr: Address>(&self, address: Addr) -> Result<(), CustomValueError> {
|
||||
let metadata = self.client.metadata_ref();
|
||||
if let Some(actual_hash) = address.validation_hash() {
|
||||
let custom = metadata.custom();
|
||||
let custom_value = custom
|
||||
.get(address.name())
|
||||
.ok_or_else(|| CustomValueError::NotFound(address.name().into()))?;
|
||||
let expected_hash = custom_value.hash();
|
||||
if actual_hash != expected_hash {
|
||||
return Err(CustomValueError::IncompatibleCodegen);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Access a custom value by the address it is registered under. This can be just a [str] to get back a dynamic value,
|
||||
/// or a static address from the generated static interface to get a value of a static type returned.
|
||||
pub fn entry<Addr: Address<IsDecodable = Maybe>>(
|
||||
&self,
|
||||
address: Addr,
|
||||
) -> Result<Addr::Target, CustomValueError> {
|
||||
// 1. Validate custom value shape if hash given:
|
||||
self.validate(&address)?;
|
||||
|
||||
// 2. Attempt to decode custom value:
|
||||
let metadata = self.client.metadata_ref();
|
||||
let value = frame_decode::custom_values::decode_custom_value(
|
||||
address.name(),
|
||||
metadata,
|
||||
metadata.types(),
|
||||
Addr::Target::into_visitor(),
|
||||
)
|
||||
.map_err(CustomValueError::CouldNotDecodeCustomValue)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Access the bytes of a custom value by the address it is registered under.
|
||||
pub fn entry_bytes<Addr: Address>(&self, address: Addr) -> Result<Vec<u8>, CustomValueError> {
|
||||
// 1. Validate custom value shape if hash given:
|
||||
self.validate(&address)?;
|
||||
|
||||
// 2. Return the underlying bytes:
|
||||
let custom_value = self
|
||||
.client
|
||||
.metadata_ref()
|
||||
.custom_value_info(address.name())
|
||||
.map_err(|e| CustomValueError::NotFound(e.not_found))?;
|
||||
Ok(custom_value.bytes.to_vec())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! Construct addresses to access custom values with.
|
||||
|
||||
use derive_where::derive_where;
|
||||
use scale_decode::DecodeAsType;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Use this with [`Address::IsDecodable`].
|
||||
pub use crate::utils::{Maybe, No, NoMaybe};
|
||||
|
||||
/// This represents the address of a custom value in the metadata.
|
||||
/// Anything that implements it can be used to fetch custom values from the metadata.
|
||||
/// The trait is implemented by [`str`] for dynamic lookup and [`StaticAddress`] for static queries.
|
||||
pub trait Address {
|
||||
/// The type of the custom value.
|
||||
type Target: DecodeAsType;
|
||||
/// Should be set to `Yes` for Dynamic values and static values that have a valid type.
|
||||
/// Should be `No` for custom values, that have an invalid type id.
|
||||
type IsDecodable: NoMaybe;
|
||||
|
||||
/// the name (key) by which the custom value can be accessed in the metadata.
|
||||
fn name(&self) -> &str;
|
||||
|
||||
/// An optional hash which, if present, can be checked against node metadata.
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Any reference to an address is a valid address
|
||||
impl<A: Address + ?Sized> Address for &'_ A {
|
||||
type Target = A::Target;
|
||||
type IsDecodable = A::IsDecodable;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
A::name(*self)
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
A::validation_hash(*self)
|
||||
}
|
||||
}
|
||||
|
||||
// Support plain strings for looking up custom values.
|
||||
impl Address for str {
|
||||
type Target = scale_value::Value;
|
||||
type IsDecodable = Maybe;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A static address to a custom value.
|
||||
#[derive_where(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub struct StaticAddress<ReturnTy, IsDecodable> {
|
||||
name: Cow<'static, str>,
|
||||
hash: Option<[u8; 32]>,
|
||||
marker: core::marker::PhantomData<(ReturnTy, IsDecodable)>,
|
||||
}
|
||||
|
||||
/// A dynamic address to a custom value.
|
||||
pub type DynamicAddress<ReturnTy> = StaticAddress<ReturnTy, Maybe>;
|
||||
|
||||
impl<ReturnTy, IsDecodable> StaticAddress<ReturnTy, IsDecodable> {
|
||||
#[doc(hidden)]
|
||||
/// Creates a new StaticAddress.
|
||||
pub fn new_static(name: &'static str, hash: [u8; 32]) -> Self {
|
||||
Self {
|
||||
name: Cow::Borrowed(name),
|
||||
hash: Some(hash),
|
||||
marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`StaticAddress`]
|
||||
pub fn new(name: impl Into<String>) -> Self {
|
||||
Self {
|
||||
name: name.into().into(),
|
||||
hash: None,
|
||||
marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Do not validate this custom value prior to accessing it.
|
||||
pub fn unvalidated(self) -> Self {
|
||||
Self {
|
||||
name: self.name,
|
||||
hash: None,
|
||||
marker: self.marker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Target: DecodeAsType, IsDecodable: NoMaybe> Address for StaticAddress<Target, IsDecodable> {
|
||||
type Target = Target;
|
||||
type IsDecodable = IsDecodable;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
self.hash
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new dynamic custom value lookup.
|
||||
pub fn dynamic<ReturnTy: DecodeAsType>(
|
||||
custom_value_name: impl Into<String>,
|
||||
) -> DynamicAddress<ReturnTy> {
|
||||
DynamicAddress::new(custom_value_name)
|
||||
}
|
||||
+4
-6
@@ -35,20 +35,18 @@ mod only_used_in_docs_or_tests {
|
||||
pub mod backend;
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
pub mod constants;
|
||||
pub mod custom_values;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
pub mod extrinsics;
|
||||
pub mod runtime_apis;
|
||||
pub mod storage;
|
||||
pub mod transactions;
|
||||
pub mod utils;
|
||||
pub mod view_functions;
|
||||
// pub mod book;
|
||||
// pub mod blocks;
|
||||
// pub mod constants;
|
||||
// pub mod custom_values;
|
||||
// pub mod runtime_api;
|
||||
// pub mod storage;
|
||||
// pub mod tx;
|
||||
// pub mod view_functions;
|
||||
|
||||
// /// This module provides a [`Config`] type, which is used to define various
|
||||
// /// types that are important in order to speak to a particular chain.
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT};
|
||||
use crate::config::Config;
|
||||
use crate::error::RuntimeApiError;
|
||||
use derive_where::derive_where;
|
||||
use payload::Payload;
|
||||
use scale_decode::IntoVisitor;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub mod payload;
|
||||
|
||||
/// Execute runtime API calls.
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct RuntimeApisClient<T: Config, Client> {
|
||||
client: Client,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Config, Client> RuntimeApisClient<T, Client> {
|
||||
/// Create a new [`RuntimeApi`]
|
||||
pub(crate) fn new(client: Client) -> Self {
|
||||
Self {
|
||||
client,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Client> RuntimeApisClient<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OfflineClientAtBlockT<T>,
|
||||
{
|
||||
/// Run the validation logic against some runtime API payload you'd like to use. Returns `Ok(())`
|
||||
/// if the payload is valid (or if it's not possible to check since the payload has no validation hash).
|
||||
/// Return an error if the payload was not valid or something went wrong trying to validate it (ie
|
||||
/// the runtime API in question do not exist at all)
|
||||
pub fn validate<P: Payload>(&self, payload: P) -> Result<(), RuntimeApiError> {
|
||||
let Some(hash) = payload.validation_hash() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let metadata = self.client.metadata_ref();
|
||||
let trait_name = payload.trait_name();
|
||||
let method_name = payload.method_name();
|
||||
|
||||
let api_trait = metadata
|
||||
.runtime_api_trait_by_name(trait_name)
|
||||
.ok_or_else(|| RuntimeApiError::TraitNotFound(trait_name.to_string()))?;
|
||||
let api_method = api_trait.method_by_name(method_name).ok_or_else(|| {
|
||||
RuntimeApiError::MethodNotFound {
|
||||
trait_name: trait_name.to_string(),
|
||||
method_name: method_name.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
if hash != api_method.hash() {
|
||||
Err(RuntimeApiError::IncompatibleCodegen)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the name of the runtime API call from the payload.
|
||||
pub fn encode_name<P: Payload>(&self, payload: P) -> String {
|
||||
format!("{}_{}", payload.trait_name(), payload.method_name())
|
||||
}
|
||||
|
||||
/// Return the encoded call args from a runtime API payload.
|
||||
pub fn encode_args<P: Payload>(&self, payload: P) -> Result<Vec<u8>, RuntimeApiError> {
|
||||
let metadata = self.client.metadata_ref();
|
||||
let value = frame_decode::runtime_apis::encode_runtime_api_inputs(
|
||||
payload.trait_name(),
|
||||
payload.method_name(),
|
||||
payload.args(),
|
||||
metadata,
|
||||
metadata.types(),
|
||||
)
|
||||
.map_err(RuntimeApiError::CouldNotEncodeInputs)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Client> RuntimeApisClient<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientAtBlockT<T>,
|
||||
{
|
||||
/// Execute a raw runtime API call. This returns the raw bytes representing the result
|
||||
/// of this call. The caller is responsible for decoding the result.
|
||||
pub async fn call_raw<'a>(
|
||||
&self,
|
||||
function: &'a str,
|
||||
call_parameters: Option<&'a [u8]>,
|
||||
) -> Result<Vec<u8>, RuntimeApiError> {
|
||||
let client = &self.client;
|
||||
let block_hash = client.block_hash();
|
||||
let data = client
|
||||
.backend()
|
||||
.call(function, call_parameters, block_hash)
|
||||
.await
|
||||
.map_err(RuntimeApiError::CannotCallApi)?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// Execute a runtime API call.
|
||||
pub async fn call<P: Payload>(&self, payload: P) -> Result<P::ReturnType, RuntimeApiError> {
|
||||
let client = &self.client;
|
||||
let block_hash = client.block_hash();
|
||||
let metadata = client.metadata_ref();
|
||||
|
||||
// Validate the runtime API payload hash against the compile hash from codegen.
|
||||
self.validate(&payload)?;
|
||||
|
||||
// Encode the arguments of the runtime call.
|
||||
let call_name = self.encode_name(&payload);
|
||||
let call_args = self.encode_args(&payload)?;
|
||||
|
||||
// Make the call.
|
||||
let bytes = client
|
||||
.backend()
|
||||
.call(&call_name, Some(call_args.as_slice()), block_hash)
|
||||
.await
|
||||
.map_err(RuntimeApiError::CannotCallApi)?;
|
||||
|
||||
// Decode the response.
|
||||
let cursor = &mut &*bytes;
|
||||
let value = frame_decode::runtime_apis::decode_runtime_api_response(
|
||||
payload.trait_name(),
|
||||
payload.method_name(),
|
||||
cursor,
|
||||
metadata,
|
||||
metadata.types(),
|
||||
P::ReturnType::into_visitor(),
|
||||
)
|
||||
.map_err(RuntimeApiError::CouldNotDecodeResponse)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! This module contains the trait and types used to represent
|
||||
//! runtime API calls that can be made.
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use derive_where::derive_where;
|
||||
use frame_decode::runtime_apis::IntoEncodableValues;
|
||||
use scale_decode::DecodeAsType;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// This represents a runtime API payload that can be used to call a Runtime API on
|
||||
/// a chain and decode the response.
|
||||
pub trait Payload {
|
||||
/// Type of the arguments.
|
||||
type ArgsType: IntoEncodableValues;
|
||||
/// The return type of the function call.
|
||||
type ReturnType: DecodeAsType;
|
||||
|
||||
/// The runtime API trait name.
|
||||
fn trait_name(&self) -> &str;
|
||||
|
||||
/// The runtime API method name.
|
||||
fn method_name(&self) -> &str;
|
||||
|
||||
/// The input arguments.
|
||||
fn args(&self) -> &Self::ArgsType;
|
||||
|
||||
/// Returns the statically generated validation hash.
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Any reference to a payload is a valid payload.
|
||||
impl<P: Payload + ?Sized> Payload for &'_ P {
|
||||
type ArgsType = P::ArgsType;
|
||||
type ReturnType = P::ReturnType;
|
||||
|
||||
fn trait_name(&self) -> &str {
|
||||
P::trait_name(*self)
|
||||
}
|
||||
|
||||
fn method_name(&self) -> &str {
|
||||
P::method_name(*self)
|
||||
}
|
||||
|
||||
fn args(&self) -> &Self::ArgsType {
|
||||
P::args(*self)
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
P::validation_hash(*self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A runtime API payload containing the generic argument data
|
||||
/// and interpreting the result of the call as `ReturnTy`.
|
||||
///
|
||||
/// This can be created from static values (ie those generated
|
||||
/// via the `subxt` macro) or dynamic values via [`dynamic`].
|
||||
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsType)]
|
||||
pub struct StaticPayload<ArgsType, ReturnType> {
|
||||
trait_name: Cow<'static, str>,
|
||||
method_name: Cow<'static, str>,
|
||||
args: ArgsType,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
_marker: PhantomData<ReturnType>,
|
||||
}
|
||||
|
||||
/// A dynamic runtime API payload.
|
||||
pub type DynamicPayload<ArgsType, ReturnType> = StaticPayload<ArgsType, ReturnType>;
|
||||
|
||||
impl<ArgsType: IntoEncodableValues, ReturnType: DecodeAsType> Payload
|
||||
for StaticPayload<ArgsType, ReturnType>
|
||||
{
|
||||
type ArgsType = ArgsType;
|
||||
type ReturnType = ReturnType;
|
||||
|
||||
fn trait_name(&self) -> &str {
|
||||
&self.trait_name
|
||||
}
|
||||
|
||||
fn method_name(&self) -> &str {
|
||||
&self.method_name
|
||||
}
|
||||
|
||||
fn args(&self) -> &Self::ArgsType {
|
||||
&self.args
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
self.validation_hash
|
||||
}
|
||||
}
|
||||
|
||||
impl<ArgsType, ReturnTy> StaticPayload<ArgsType, ReturnTy> {
|
||||
/// Create a new [`StaticPayload`].
|
||||
pub fn new(
|
||||
trait_name: impl Into<String>,
|
||||
method_name: impl Into<String>,
|
||||
args: ArgsType,
|
||||
) -> Self {
|
||||
StaticPayload {
|
||||
trait_name: trait_name.into().into(),
|
||||
method_name: method_name.into().into(),
|
||||
args,
|
||||
validation_hash: None,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new static [`StaticPayload`] using static function name
|
||||
/// and scale-encoded argument data.
|
||||
///
|
||||
/// This is only expected to be used from codegen.
|
||||
#[doc(hidden)]
|
||||
pub fn new_static(
|
||||
trait_name: &'static str,
|
||||
method_name: &'static str,
|
||||
args: ArgsType,
|
||||
hash: [u8; 32],
|
||||
) -> StaticPayload<ArgsType, ReturnTy> {
|
||||
StaticPayload {
|
||||
trait_name: Cow::Borrowed(trait_name),
|
||||
method_name: Cow::Borrowed(method_name),
|
||||
args,
|
||||
validation_hash: Some(hash),
|
||||
_marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Do not validate this call prior to submitting it.
|
||||
pub fn unvalidated(self) -> Self {
|
||||
Self {
|
||||
validation_hash: None,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the trait name.
|
||||
pub fn trait_name(&self) -> &str {
|
||||
&self.trait_name
|
||||
}
|
||||
|
||||
/// Returns the method name.
|
||||
pub fn method_name(&self) -> &str {
|
||||
&self.method_name
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`DynamicPayload`].
|
||||
pub fn dynamic<ArgsType, ReturnType>(
|
||||
trait_name: impl Into<String>,
|
||||
method_name: impl Into<String>,
|
||||
args_data: ArgsType,
|
||||
) -> DynamicPayload<ArgsType, ReturnType> {
|
||||
DynamicPayload::new(trait_name, method_name, args_data)
|
||||
}
|
||||
+4
-4
@@ -21,7 +21,7 @@ pub use storage_key_value::StorageKeyValue;
|
||||
pub use storage_value::StorageValue;
|
||||
pub mod address;
|
||||
|
||||
/// A client for working with transactions.
|
||||
/// A client for working with storage entries.
|
||||
#[derive(Clone)]
|
||||
pub struct StorageClient<T, Client> {
|
||||
client: Client,
|
||||
@@ -81,7 +81,7 @@ impl<T: Config, Client: OfflineClientAtBlockT<T>> StorageClient<T, Client> {
|
||||
|
||||
/// Iterate over all of the storage entries listed in the metadata for the current block. This does **not** include well known
|
||||
/// storage entries like `:code` which are not listed in the metadata.
|
||||
pub fn entries(&self) -> impl Iterator<Item = StorageEntryRef<'_, Client, T>> {
|
||||
pub fn entries(&self) -> impl Iterator<Item = StorageEntryRef<'_, T, Client>> {
|
||||
let metadata = self.client.metadata_ref();
|
||||
Entry::tuples_of(metadata.storage_entries()).map(|(pallet_name, entry_name)| {
|
||||
StorageEntryRef {
|
||||
@@ -173,14 +173,14 @@ impl<T: Config, Client: OnlineClientAtBlockT<T>> StorageClient<T, Client> {
|
||||
}
|
||||
|
||||
/// Working with a specific storage entry.
|
||||
pub struct StorageEntryRef<'atblock, Client, T> {
|
||||
pub struct StorageEntryRef<'atblock, T, Client> {
|
||||
pallet_name: Cow<'atblock, str>,
|
||||
entry_name: Cow<'atblock, str>,
|
||||
client: &'atblock Client,
|
||||
marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'atblock, Client, T> StorageEntryRef<'atblock, Client, T>
|
||||
impl<'atblock, Client, T> StorageEntryRef<'atblock, T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OfflineClientAtBlockT<T>,
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT};
|
||||
use crate::config::Config;
|
||||
use crate::error::ViewFunctionError;
|
||||
use derive_where::derive_where;
|
||||
use payload::Payload;
|
||||
use scale_decode::IntoVisitor;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub mod payload;
|
||||
|
||||
/// The name of the Runtime API call which can execute
|
||||
const CALL_NAME: &str = "RuntimeViewFunction_execute_view_function";
|
||||
|
||||
/// Execute View Function calls.
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct ViewFunctionsClient<T: Config, Client> {
|
||||
client: Client,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Config, Client> ViewFunctionsClient<T, Client> {
|
||||
/// Create a new [`ViewFunctionsClient`]
|
||||
pub(crate) fn new(client: Client) -> Self {
|
||||
Self {
|
||||
client,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Client> ViewFunctionsClient<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OfflineClientAtBlockT<T>,
|
||||
{
|
||||
/// Run the validation logic against some View Function payload you'd like to use. Returns `Ok(())`
|
||||
/// if the payload is valid (or if it's not possible to check since the payload has no validation hash).
|
||||
/// Return an error if the payload was not valid or something went wrong trying to validate it (ie
|
||||
/// the View Function in question do not exist at all)
|
||||
pub fn validate<Call: Payload>(&self, payload: Call) -> Result<(), ViewFunctionError> {
|
||||
let Some(hash) = payload.validation_hash() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let metadata = self.client.metadata_ref();
|
||||
let pallet_name = payload.pallet_name();
|
||||
let function_name = payload.function_name();
|
||||
|
||||
let view_function = metadata
|
||||
.pallet_by_name(pallet_name)
|
||||
.ok_or_else(|| ViewFunctionError::PalletNotFound(pallet_name.to_string()))?
|
||||
.view_function_by_name(function_name)
|
||||
.ok_or_else(|| ViewFunctionError::ViewFunctionNotFound {
|
||||
pallet_name: pallet_name.to_string(),
|
||||
function_name: function_name.to_string(),
|
||||
})?;
|
||||
|
||||
if hash != view_function.hash() {
|
||||
Err(ViewFunctionError::IncompatibleCodegen)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode the bytes that will be passed to the "execute_view_function" Runtime API call,
|
||||
/// to execute the View Function represented by the given payload.
|
||||
pub fn encode_args<P: Payload>(&self, payload: P) -> Result<Vec<u8>, ViewFunctionError> {
|
||||
let metadata = self.client.metadata_ref();
|
||||
let inputs = frame_decode::view_functions::encode_view_function_inputs(
|
||||
payload.pallet_name(),
|
||||
payload.function_name(),
|
||||
payload.args(),
|
||||
metadata,
|
||||
metadata.types(),
|
||||
)
|
||||
.map_err(ViewFunctionError::CouldNotEncodeInputs)?;
|
||||
|
||||
Ok(inputs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Client> ViewFunctionsClient<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientAtBlockT<T>,
|
||||
{
|
||||
/// Execute a raw View function API call. This returns the raw bytes representing the result
|
||||
/// of this call. The caller is responsible for decoding the result.
|
||||
pub async fn call_raw<'a>(
|
||||
&self,
|
||||
call_parameters: Option<&'a [u8]>,
|
||||
) -> Result<Vec<u8>, ViewFunctionError> {
|
||||
let client = &self.client;
|
||||
let block_hash = client.block_hash();
|
||||
let data = client
|
||||
.backend()
|
||||
.call(CALL_NAME, call_parameters, block_hash)
|
||||
.await
|
||||
.map_err(ViewFunctionError::CannotCallApi)?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// Execute a View Function call.
|
||||
pub async fn call<P: Payload>(&self, payload: P) -> Result<P::ReturnType, ViewFunctionError> {
|
||||
let client = &self.client;
|
||||
let metadata = client.metadata_ref();
|
||||
let block_hash = client.block_hash();
|
||||
|
||||
// Validate the View Function payload hash against the compile hash from codegen.
|
||||
self.validate(&payload)?;
|
||||
|
||||
// Assemble the data to call the "execute_view_function" runtime API, which
|
||||
// then calls the relevant view function.
|
||||
let call_args = self.encode_args(&payload)?;
|
||||
|
||||
// Make the call.
|
||||
let bytes = client
|
||||
.backend()
|
||||
.call(CALL_NAME, Some(call_args.as_slice()), block_hash)
|
||||
.await
|
||||
.map_err(ViewFunctionError::CannotCallApi)?;
|
||||
|
||||
// Decode the response.
|
||||
let cursor = &mut &*bytes;
|
||||
let value = frame_decode::view_functions::decode_view_function_response(
|
||||
payload.pallet_name(),
|
||||
payload.function_name(),
|
||||
cursor,
|
||||
metadata,
|
||||
metadata.types(),
|
||||
P::ReturnType::into_visitor(),
|
||||
)
|
||||
.map_err(ViewFunctionError::CouldNotDecodeResponse)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! This module contains the trait and types used to represent
|
||||
//! View Function calls that can be made.
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use derive_where::derive_where;
|
||||
use frame_decode::view_functions::IntoEncodableValues;
|
||||
use scale_decode::DecodeAsType;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// This represents a View Function payload that can call into the runtime of node.
|
||||
///
|
||||
/// # Components
|
||||
///
|
||||
/// - associated return type
|
||||
///
|
||||
/// Resulting bytes of the call are interpreted into this type.
|
||||
///
|
||||
/// - query ID
|
||||
///
|
||||
/// The ID used to identify in the runtime which view function to call.
|
||||
///
|
||||
/// - encoded arguments
|
||||
///
|
||||
/// Each argument of the View Function must be scale-encoded.
|
||||
pub trait Payload {
|
||||
/// Type of the arguments for this call.
|
||||
type ArgsType: IntoEncodableValues;
|
||||
/// The return type of the function call.
|
||||
type ReturnType: DecodeAsType;
|
||||
|
||||
/// The View Function pallet name.
|
||||
fn pallet_name(&self) -> &str;
|
||||
|
||||
/// The View Function function name.
|
||||
fn function_name(&self) -> &str;
|
||||
|
||||
/// The arguments.
|
||||
fn args(&self) -> &Self::ArgsType;
|
||||
|
||||
/// Returns the statically generated validation hash.
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// A reference to a payload is a valid payload.
|
||||
impl<P: Payload + ?Sized> Payload for &'_ P {
|
||||
type ArgsType = P::ArgsType;
|
||||
type ReturnType = P::ReturnType;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
P::pallet_name(*self)
|
||||
}
|
||||
|
||||
fn function_name(&self) -> &str {
|
||||
P::function_name(*self)
|
||||
}
|
||||
|
||||
fn args(&self) -> &Self::ArgsType {
|
||||
P::args(*self)
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
P::validation_hash(*self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A View Function payload containing the generic argument data
|
||||
/// and interpreting the result of the call as `ReturnType`.
|
||||
///
|
||||
/// This can be created from static values (ie those generated
|
||||
/// via the `subxt` macro) or dynamic values via [`dynamic`].
|
||||
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsType)]
|
||||
pub struct StaticPayload<ArgsType, ReturnType> {
|
||||
pallet_name: Cow<'static, str>,
|
||||
function_name: Cow<'static, str>,
|
||||
args: ArgsType,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
_marker: PhantomData<ReturnType>,
|
||||
}
|
||||
|
||||
/// A dynamic View Function payload.
|
||||
pub type DynamicPayload<ArgsType, ReturnType> = StaticPayload<ArgsType, ReturnType>;
|
||||
|
||||
impl<ArgsType: IntoEncodableValues, ReturnType: DecodeAsType> Payload
|
||||
for StaticPayload<ArgsType, ReturnType>
|
||||
{
|
||||
type ArgsType = ArgsType;
|
||||
type ReturnType = ReturnType;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
&self.pallet_name
|
||||
}
|
||||
|
||||
fn function_name(&self) -> &str {
|
||||
&self.function_name
|
||||
}
|
||||
|
||||
fn args(&self) -> &Self::ArgsType {
|
||||
&self.args
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
self.validation_hash
|
||||
}
|
||||
}
|
||||
|
||||
impl<ReturnTy, ArgsType> StaticPayload<ArgsType, ReturnTy> {
|
||||
/// Create a new [`StaticPayload`] for a View Function call.
|
||||
pub fn new(
|
||||
pallet_name: impl Into<String>,
|
||||
function_name: impl Into<String>,
|
||||
args: ArgsType,
|
||||
) -> Self {
|
||||
StaticPayload {
|
||||
pallet_name: pallet_name.into().into(),
|
||||
function_name: function_name.into().into(),
|
||||
args,
|
||||
validation_hash: None,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new static [`StaticPayload`] for a View Function call
|
||||
/// using static function name and scale-encoded argument data.
|
||||
///
|
||||
/// This is only expected to be used from codegen.
|
||||
#[doc(hidden)]
|
||||
pub fn new_static(
|
||||
pallet_name: &'static str,
|
||||
function_name: &'static str,
|
||||
args: ArgsType,
|
||||
hash: [u8; 32],
|
||||
) -> StaticPayload<ArgsType, ReturnTy> {
|
||||
StaticPayload {
|
||||
pallet_name: Cow::Borrowed(pallet_name),
|
||||
function_name: Cow::Borrowed(function_name),
|
||||
args,
|
||||
validation_hash: Some(hash),
|
||||
_marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Do not validate this call prior to submitting it.
|
||||
pub fn unvalidated(self) -> Self {
|
||||
Self {
|
||||
validation_hash: None,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`DynamicPayload`] to call a View Function.
|
||||
pub fn dynamic<ArgsType, ReturnType>(
|
||||
pallet_name: impl Into<String>,
|
||||
function_name: impl Into<String>,
|
||||
args: ArgsType,
|
||||
) -> DynamicPayload<ArgsType, ReturnType> {
|
||||
DynamicPayload::new(pallet_name, function_name, args)
|
||||
}
|
||||
Reference in New Issue
Block a user