mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-10 02:57:58 +00:00
Merge remote-tracking branch 'origin/master' into lexnv/light_client_support
This commit is contained in:
@@ -2,3 +2,5 @@
|
||||
**/*.rs.bk
|
||||
**/.DS_Store
|
||||
cargo-timing*
|
||||
/examples/wasm-example/dist
|
||||
/examples/wasm-example/target
|
||||
|
||||
Generated
+8
-8
@@ -2883,9 +2883,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.58"
|
||||
version = "1.0.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8"
|
||||
checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2912,9 +2912,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.27"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
|
||||
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -3061,9 +3061,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.8.2"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1a59b5d8e97dee33696bf13c5ba8ab85341c002922fba050069326b9c498974"
|
||||
checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -4461,9 +4461,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.28.1"
|
||||
version = "1.28.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105"
|
||||
checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
|
||||
+4
-4
@@ -15,7 +15,7 @@ members = [
|
||||
# This cannot be a workspace dependency, because it requires
|
||||
# mutually exclusive jsonrpsee features to work, and workspaces
|
||||
# will aggregate features used across crates:
|
||||
exclude = ["testing/wasm-tests"]
|
||||
exclude = ["testing/wasm-tests", "examples/wasm-example"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
@@ -51,9 +51,9 @@ jsonrpsee = { version = "0.16" }
|
||||
pretty_assertions = "1.0.0"
|
||||
primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "scale-info", "serde"] }
|
||||
proc-macro-error = "1.0.4"
|
||||
proc-macro2 = "1.0.58"
|
||||
quote = "1.0.27"
|
||||
regex = "1.8.1"
|
||||
proc-macro2 = "1.0.59"
|
||||
quote = "1.0.28"
|
||||
regex = "1.8.3"
|
||||
scale-info = "2.7.0"
|
||||
scale-value = "0.7.0"
|
||||
scale-bits = "0.3"
|
||||
|
||||
Generated
+2360
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
[workspace]
|
||||
|
||||
[package]
|
||||
name = "wasm-example"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.28"
|
||||
subxt = { path = "../../subxt", default-features = false, features = ["jsonrpsee-web"], target_arch = "wasm32" }
|
||||
yew = {version = "0.20.0", features = ["csr"] }
|
||||
web-sys = "0.3.63"
|
||||
hex = "0.4.3"
|
||||
@@ -0,0 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link data-trunk rel="scss" href="index.scss" />
|
||||
<title>Yew App</title>
|
||||
</head>
|
||||
</html>
|
||||
@@ -0,0 +1,57 @@
|
||||
$primary: #24cc85;
|
||||
$secondary: #1f624a;
|
||||
$dark: #242a35;
|
||||
|
||||
*{
|
||||
font-family: monospace;
|
||||
color: $dark;
|
||||
|
||||
}
|
||||
|
||||
html{
|
||||
background-color: $dark;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
h1{
|
||||
font-weight: bolder;
|
||||
color: $dark;
|
||||
}
|
||||
|
||||
body{
|
||||
width: 800px;
|
||||
max-width: 100%;
|
||||
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 16px;
|
||||
background-color: $primary;
|
||||
}
|
||||
|
||||
|
||||
p{
|
||||
white-space: pre-wrap;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
|
||||
background-color: $dark;
|
||||
color: white;
|
||||
}
|
||||
button{
|
||||
font-size: large;
|
||||
padding: 8px 16px;
|
||||
font-weight: bold;
|
||||
background-color: $dark;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
button:hover{
|
||||
background-color: $secondary;
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
//! This is a small WASM app using the Yew UI framework showcasing how to use Subxt's features in a WASM environment.
|
||||
//!
|
||||
//! To run the app locally use Trunk, a WASM bundler:
|
||||
//! ```
|
||||
//! cargo install --locked trunk
|
||||
//! ```
|
||||
//! Run the app locally:
|
||||
//! ```
|
||||
//! trunk serve --open
|
||||
//! ```
|
||||
//! You need to have a local polkadot/substrate node with it's JSON-RPC HTTP server running at 127.0.0.1:9933 in order for the examples to be working.
|
||||
//! Also make sure your browser supports WASM.
|
||||
use futures::{self, FutureExt};
|
||||
|
||||
use yew::prelude::*;
|
||||
mod services;
|
||||
|
||||
fn main() {
|
||||
yew::Renderer::<SubxtExamplesComponent>::new().render();
|
||||
}
|
||||
|
||||
struct SubxtExamplesComponent {
|
||||
operation_title: Option<AttrValue>,
|
||||
lines: Vec<AttrValue>,
|
||||
}
|
||||
|
||||
enum Message {
|
||||
Error(subxt::Error),
|
||||
Reload,
|
||||
Line(AttrValue),
|
||||
Lines(Vec<AttrValue>),
|
||||
ButtonClick(Button),
|
||||
}
|
||||
|
||||
enum Button {
|
||||
SubscribeFinalized,
|
||||
FetchConstant,
|
||||
FetchEvents,
|
||||
}
|
||||
|
||||
impl Component for SubxtExamplesComponent {
|
||||
type Message = Message;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
SubxtExamplesComponent {
|
||||
lines: Vec::new(),
|
||||
operation_title: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg {
|
||||
Message::Error(err) => {
|
||||
self.lines.push(err.to_string().into());
|
||||
}
|
||||
Message::Reload => {
|
||||
let window = web_sys::window().expect("Failed to access the window object");
|
||||
window
|
||||
.location()
|
||||
.reload()
|
||||
.expect("Failed to reload the page");
|
||||
}
|
||||
Message::Line(line) => {
|
||||
self.lines.push(line);
|
||||
}
|
||||
Message::Lines(mut lines) => {
|
||||
self.lines.append(&mut lines);
|
||||
}
|
||||
Message::ButtonClick(button) => match button {
|
||||
Button::SubscribeFinalized => {
|
||||
self.operation_title = Some("Subscribe to finalized blocks:".into());
|
||||
let cb: Callback<AttrValue> = ctx.link().callback(Message::Line);
|
||||
ctx.link()
|
||||
.send_future(services::subscribe_to_finalized_blocks(cb).map(|result| {
|
||||
let err = result.unwrap_err();
|
||||
Message::Error(err)
|
||||
}));
|
||||
}
|
||||
Button::FetchConstant => {
|
||||
self.operation_title =
|
||||
Some("Fetch the constant \"block_length\" of \"System\" pallet:".into());
|
||||
ctx.link()
|
||||
.send_future(services::fetch_constant_block_length().map(|result| {
|
||||
match result {
|
||||
Ok(value) => Message::Line(
|
||||
format!(
|
||||
"constant \"block_length\" of \"System\" pallet:\n {value}"
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
Err(err) => Message::Error(err),
|
||||
}
|
||||
}))
|
||||
}
|
||||
Button::FetchEvents => {
|
||||
self.operation_title = Some("Fetch events:".into());
|
||||
ctx.link()
|
||||
.send_future(services::fetch_events_dynamically().map(
|
||||
|result| match result {
|
||||
Ok(value) => {
|
||||
Message::Lines(value.into_iter().map(AttrValue::from).collect())
|
||||
}
|
||||
Err(err) => Message::Error(err),
|
||||
},
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let reload: Callback<MouseEvent> = ctx.link().callback(|_| Message::Reload);
|
||||
|
||||
let subscribe_finalized = ctx
|
||||
.link()
|
||||
.callback(|_| Message::ButtonClick(Button::SubscribeFinalized));
|
||||
|
||||
let fetch_constant = ctx
|
||||
.link()
|
||||
.callback(|_| Message::ButtonClick(Button::FetchConstant));
|
||||
|
||||
let fetch_events = ctx
|
||||
.link()
|
||||
.callback(|_| Message::ButtonClick(Button::FetchEvents));
|
||||
|
||||
html! {
|
||||
<div>
|
||||
if let Some(operation_title) = &self.operation_title{
|
||||
<button onclick={reload}>{"🡄 Back"}</button>
|
||||
<h1>{operation_title}</h1>
|
||||
if self.lines.is_empty(){
|
||||
<p>{"Loading..."}</p>
|
||||
}
|
||||
else{
|
||||
|
||||
}
|
||||
{ for self.lines.iter().map(|line| html! {<p> {line} </p>}) }
|
||||
}
|
||||
else{
|
||||
<>
|
||||
<h1>{"Subxt Examples"}</h1>
|
||||
<button onclick={subscribe_finalized}>{"Example: Subscribe to Finalized blocks"}</button>
|
||||
<button onclick={fetch_constant}>{"Example: Fetch constant value"}</button>
|
||||
<button onclick={fetch_events}>{"Example: Fetch events"}</button>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use futures::StreamExt;
|
||||
use std::fmt::Write;
|
||||
use subxt::{self, OnlineClient, PolkadotConfig};
|
||||
use yew::{AttrValue, Callback};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../../artifacts/polkadot_metadata_small.scale")]
|
||||
mod polkadot {}
|
||||
|
||||
pub(crate) async fn fetch_constant_block_length() -> Result<String, subxt::Error> {
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
let constant_query = polkadot::constants().system().block_length();
|
||||
|
||||
let value = api.constants().at(&constant_query)?;
|
||||
Ok(format!("{value:?}"))
|
||||
}
|
||||
|
||||
pub(crate) async fn fetch_events_dynamically() -> Result<Vec<String>, subxt::Error> {
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
let events = api.events().at_latest().await?;
|
||||
let mut event_strings = Vec::<String>::new();
|
||||
for event in events.iter() {
|
||||
let event = event?;
|
||||
let pallet = event.pallet_name();
|
||||
let variant = event.variant_name();
|
||||
let field_values = event.field_values()?;
|
||||
event_strings.push(format!("{pallet}::{variant}: {field_values}"));
|
||||
}
|
||||
Ok(event_strings)
|
||||
}
|
||||
|
||||
/// subscribes to finalized blocks. When a block is received, it is formatted as a string and sent via the callback.
|
||||
pub(crate) async fn subscribe_to_finalized_blocks(
|
||||
cb: Callback<AttrValue>,
|
||||
) -> Result<(), subxt::Error> {
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
// Subscribe to all finalized blocks:
|
||||
let mut blocks_sub = api.blocks().subscribe_finalized().await?;
|
||||
while let Some(block) = blocks_sub.next().await {
|
||||
let block = block?;
|
||||
let mut output = String::new();
|
||||
writeln!(output, "Block #{}:", block.header().number).ok();
|
||||
writeln!(output, " Hash: {}", block.hash()).ok();
|
||||
writeln!(output, " Extrinsics:").ok();
|
||||
let body = block.body().await?;
|
||||
for ext in body.extrinsics().iter() {
|
||||
let ext = ext?;
|
||||
let idx = ext.index();
|
||||
let events = ext.events().await?;
|
||||
let bytes_hex = format!("0x{}", hex::encode(ext.bytes()));
|
||||
|
||||
// See the API docs for more ways to decode extrinsics:
|
||||
let decoded_ext = ext.as_root_extrinsic::<polkadot::Call>();
|
||||
|
||||
writeln!(output, " Extrinsic #{idx}:").ok();
|
||||
writeln!(output, " Bytes: {bytes_hex}").ok();
|
||||
writeln!(output, " Decoded: {decoded_ext:?}").ok();
|
||||
writeln!(output, " Events:").ok();
|
||||
|
||||
for evt in events.iter() {
|
||||
let evt = evt?;
|
||||
|
||||
let pallet_name = evt.pallet_name();
|
||||
let event_name = evt.variant_name();
|
||||
let event_values = evt.field_values()?;
|
||||
|
||||
writeln!(output, " {pallet_name}_{event_name}").ok();
|
||||
writeln!(output, " {}", event_values).ok();
|
||||
}
|
||||
}
|
||||
cb.emit(output.into())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -33,7 +33,11 @@ fn bench_get_call_hash(c: &mut Criterion) {
|
||||
|
||||
for pallet in metadata.pallets() {
|
||||
let pallet_name = pallet.name();
|
||||
for variant in pallet.call_variants().unwrap() {
|
||||
let Some(variants) = pallet.call_variants() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for variant in variants {
|
||||
let call_name = &variant.name;
|
||||
let bench_name = format!("{pallet_name}/{call_name}");
|
||||
group.bench_function(&bench_name, |b| b.iter(|| pallet.call_hash(call_name)));
|
||||
|
||||
@@ -366,10 +366,7 @@ where
|
||||
|
||||
/// Fetch the metadata for this extrinsic.
|
||||
pub fn extrinsic_metadata(&self) -> Result<ExtrinsicMetadataDetails, Error> {
|
||||
let pallet = self
|
||||
.metadata
|
||||
.pallet_by_index(self.pallet_index())
|
||||
.ok_or_else(|| MetadataError::PalletIndexNotFound(self.pallet_index()))?;
|
||||
let pallet = self.metadata.pallet_by_index_err(self.pallet_index())?;
|
||||
let variant = pallet
|
||||
.call_variant_by_index(self.variant_index())
|
||||
.ok_or_else(|| MetadataError::VariantIndexNotFound(self.variant_index()))?;
|
||||
@@ -563,7 +560,7 @@ impl<T: Config> ExtrinsicEvents<T> {
|
||||
///
|
||||
/// This works in the same way that [`events::Events::iter()`] does, with the
|
||||
/// exception that it filters out events not related to the submitted extrinsic.
|
||||
pub fn iter(&self) -> impl Iterator<Item = Result<events::EventDetails, Error>> + '_ {
|
||||
pub fn iter(&self) -> impl Iterator<Item = Result<events::EventDetails<T>, Error>> + '_ {
|
||||
self.events.iter().filter(|ev| {
|
||||
ev.as_ref()
|
||||
.map(|ev| ev.phase() == events::Phase::ApplyExtrinsic(self.idx))
|
||||
|
||||
@@ -39,8 +39,7 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
|
||||
let expected_hash = self
|
||||
.client
|
||||
.metadata()
|
||||
.pallet_by_name(address.pallet_name())
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound(address.pallet_name().to_owned()))?
|
||||
.pallet_by_name_err(address.pallet_name())?
|
||||
.constant_hash(address.constant_name())
|
||||
.ok_or_else(|| {
|
||||
MetadataError::ConstantNameNotFound(address.constant_name().to_owned())
|
||||
@@ -65,10 +64,8 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
|
||||
self.validate(address)?;
|
||||
|
||||
// 2. Attempt to decode the constant into the type given:
|
||||
let pallet = metadata
|
||||
.pallet_by_name(address.pallet_name())
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound(address.pallet_name().to_owned()))?;
|
||||
let constant = pallet
|
||||
let constant = metadata
|
||||
.pallet_by_name_err(address.pallet_name())?
|
||||
.constant_by_name(address.constant_name())
|
||||
.ok_or_else(|| {
|
||||
MetadataError::ConstantNameNotFound(address.constant_name().to_owned())
|
||||
|
||||
@@ -154,11 +154,7 @@ impl std::fmt::Display for ModuleError {
|
||||
impl ModuleError {
|
||||
/// Return more details about this error.
|
||||
pub fn details(&self) -> Result<ModuleErrorDetails, MetadataError> {
|
||||
let pallet = self
|
||||
.metadata
|
||||
.pallet_by_index(self.raw.pallet_index)
|
||||
.ok_or(MetadataError::PalletIndexNotFound(self.raw.pallet_index))?;
|
||||
|
||||
let pallet = self.metadata.pallet_by_index_err(self.raw.pallet_index)?;
|
||||
let variant = pallet
|
||||
.error_variant_by_index(self.raw.error[0])
|
||||
.ok_or_else(|| MetadataError::VariantIndexNotFound(self.raw.error[0]))?;
|
||||
|
||||
@@ -121,7 +121,7 @@ impl<T: Config> Events<T> {
|
||||
// use of it with our `FilterEvents` stuff.
|
||||
pub fn iter(
|
||||
&self,
|
||||
) -> impl Iterator<Item = Result<EventDetails, Error>> + Send + Sync + 'static {
|
||||
) -> impl Iterator<Item = Result<EventDetails<T>, Error>> + Send + Sync + 'static {
|
||||
// The event bytes ignoring the compact encoded length on the front:
|
||||
let event_bytes = self.event_bytes.clone();
|
||||
let metadata = self.metadata.clone();
|
||||
@@ -133,12 +133,7 @@ impl<T: Config> Events<T> {
|
||||
if event_bytes.len() <= pos || num_events == index {
|
||||
None
|
||||
} else {
|
||||
match EventDetails::decode_from::<T>(
|
||||
metadata.clone(),
|
||||
event_bytes.clone(),
|
||||
pos,
|
||||
index,
|
||||
) {
|
||||
match EventDetails::decode_from(metadata.clone(), event_bytes.clone(), pos, index) {
|
||||
Ok(event_details) => {
|
||||
// Skip over decoded bytes in next iteration:
|
||||
pos += event_details.bytes().len();
|
||||
@@ -189,7 +184,7 @@ impl<T: Config> Events<T> {
|
||||
|
||||
/// The event details.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventDetails {
|
||||
pub struct EventDetails<T: Config> {
|
||||
phase: Phase,
|
||||
/// The index of the event in the list of events in a given block.
|
||||
index: u32,
|
||||
@@ -205,16 +200,17 @@ pub struct EventDetails {
|
||||
// end of everything (fields + topics)
|
||||
end_idx: usize,
|
||||
metadata: Metadata,
|
||||
topics: Vec<T::Hash>,
|
||||
}
|
||||
|
||||
impl EventDetails {
|
||||
impl<T: Config> EventDetails<T> {
|
||||
// Attempt to dynamically decode a single event from our events input.
|
||||
fn decode_from<T: Config>(
|
||||
fn decode_from(
|
||||
metadata: Metadata,
|
||||
all_bytes: Arc<[u8]>,
|
||||
start_idx: usize,
|
||||
index: u32,
|
||||
) -> Result<EventDetails, Error> {
|
||||
) -> Result<EventDetails<T>, Error> {
|
||||
let input = &mut &all_bytes[start_idx..];
|
||||
|
||||
let phase = Phase::decode(input)?;
|
||||
@@ -227,9 +223,7 @@ impl EventDetails {
|
||||
let event_fields_start_idx = all_bytes.len() - input.len();
|
||||
|
||||
// Get metadata for the event:
|
||||
let event_pallet = metadata
|
||||
.pallet_by_index(pallet_index)
|
||||
.ok_or(MetadataError::PalletIndexNotFound(pallet_index))?;
|
||||
let event_pallet = metadata.pallet_by_index_err(pallet_index)?;
|
||||
let event_variant = event_pallet
|
||||
.event_variant_by_index(variant_index)
|
||||
.ok_or(MetadataError::VariantIndexNotFound(variant_index))?;
|
||||
@@ -254,9 +248,8 @@ impl EventDetails {
|
||||
// the end of the field bytes.
|
||||
let event_fields_end_idx = all_bytes.len() - input.len();
|
||||
|
||||
// topics come after the event data in EventRecord. They aren't used for
|
||||
// anything at the moment, so just decode and throw them away.
|
||||
let _topics = Vec::<T::Hash>::decode(input)?;
|
||||
// topics come after the event data in EventRecord.
|
||||
let topics = Vec::<T::Hash>::decode(input)?;
|
||||
|
||||
// what bytes did we skip over in total, including topics.
|
||||
let end_idx = all_bytes.len() - input.len();
|
||||
@@ -271,6 +264,7 @@ impl EventDetails {
|
||||
end_idx,
|
||||
all_bytes,
|
||||
metadata,
|
||||
topics,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -387,6 +381,11 @@ impl EventDetails {
|
||||
&self.metadata,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the topics associated with this event.
|
||||
pub fn topics(&self) -> &[T::Hash] {
|
||||
&self.topics
|
||||
}
|
||||
}
|
||||
|
||||
/// Details for the given event plucked from the metadata.
|
||||
@@ -469,14 +468,21 @@ pub(crate) mod test_utils {
|
||||
topics: Vec<<SubstrateConfig as Config>::Hash>,
|
||||
}
|
||||
|
||||
impl<E: Encode> EventRecord<E> {
|
||||
/// Create a new event record with the given phase, event, and topics.
|
||||
pub fn new(phase: Phase, event: E, topics: Vec<<SubstrateConfig as Config>::Hash>) -> Self {
|
||||
Self {
|
||||
phase,
|
||||
event: AllEvents::Test(event),
|
||||
topics,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build an EventRecord, which encoded events in the format expected
|
||||
/// to be handed back from storage queries to System.Events.
|
||||
pub fn event_record<E: Encode>(phase: Phase, event: E) -> EventRecord<E> {
|
||||
EventRecord {
|
||||
phase,
|
||||
event: AllEvents::Test(event),
|
||||
topics: vec![],
|
||||
}
|
||||
EventRecord::new(phase, event, vec![])
|
||||
}
|
||||
|
||||
/// Build fake metadata consisting of a single pallet that knows
|
||||
@@ -566,10 +572,12 @@ pub(crate) mod test_utils {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
test_utils::{event_record, events, events_raw, AllEvents},
|
||||
test_utils::{event_record, events, events_raw, AllEvents, EventRecord},
|
||||
*,
|
||||
};
|
||||
use crate::SubstrateConfig;
|
||||
use codec::Encode;
|
||||
use primitive_types::H256;
|
||||
use scale_info::TypeInfo;
|
||||
use scale_value::Value;
|
||||
|
||||
@@ -598,7 +606,7 @@ mod tests {
|
||||
// Just for convenience, pass in the metadata type constructed
|
||||
// by the `metadata` function above to simplify caller code.
|
||||
metadata: &Metadata,
|
||||
actual: EventDetails,
|
||||
actual: EventDetails<SubstrateConfig>,
|
||||
expected: TestRawEventDetails,
|
||||
) {
|
||||
let types = &metadata.types();
|
||||
@@ -952,4 +960,36 @@ mod tests {
|
||||
);
|
||||
assert!(event_details.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn topics() {
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo, scale_decode::DecodeAsType)]
|
||||
enum Event {
|
||||
A(u8, bool, Vec<String>),
|
||||
}
|
||||
|
||||
// Create fake metadata that knows about our single event, above:
|
||||
let metadata = metadata::<Event>();
|
||||
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construct an Events object to iterate them:
|
||||
let event = Event::A(1, true, vec!["Hi".into()]);
|
||||
let topics = vec![H256::from_low_u64_le(123), H256::from_low_u64_le(456)];
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
vec![EventRecord::new(
|
||||
Phase::ApplyExtrinsic(123),
|
||||
event,
|
||||
topics.clone(),
|
||||
)],
|
||||
);
|
||||
|
||||
let ev = events
|
||||
.iter()
|
||||
.next()
|
||||
.expect("one event expected")
|
||||
.expect("event should be extracted OK");
|
||||
|
||||
assert_eq!(topics, ev.topics());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::error::MetadataError;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A cheaply clone-able representation of the runtime metadata received from a node.
|
||||
@@ -23,6 +24,33 @@ impl Metadata {
|
||||
inner: Arc::new(md),
|
||||
}
|
||||
}
|
||||
|
||||
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
|
||||
pub fn pallet_by_name_err(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<subxt_metadata::PalletMetadata, MetadataError> {
|
||||
self.pallet_by_name(name)
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound(name.to_owned()))
|
||||
}
|
||||
|
||||
/// Identical to `metadata.pallet_by_index()`, but returns an error if the pallet is not found.
|
||||
pub fn pallet_by_index_err(
|
||||
&self,
|
||||
index: u8,
|
||||
) -> Result<subxt_metadata::PalletMetadata, MetadataError> {
|
||||
self.pallet_by_index(index)
|
||||
.ok_or(MetadataError::PalletIndexNotFound(index))
|
||||
}
|
||||
|
||||
/// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found.
|
||||
pub fn runtime_api_trait_by_name_err(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<subxt_metadata::RuntimeApiMetadata, MetadataError> {
|
||||
self.runtime_api_trait_by_name(name)
|
||||
.ok_or_else(|| MetadataError::RuntimeTraitNotFound(name.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<subxt_metadata::Metadata> for Metadata {
|
||||
|
||||
@@ -88,10 +88,8 @@ impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> RuntimeApiPayload
|
||||
}
|
||||
|
||||
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
|
||||
let api_trait = metadata
|
||||
.runtime_api_trait_by_name(&self.trait_name)
|
||||
.ok_or_else(|| MetadataError::RuntimeTraitNotFound((*self.trait_name).to_owned()))?;
|
||||
let api_method = api_trait
|
||||
let api_method = metadata
|
||||
.runtime_api_trait_by_name_err(&self.trait_name)?
|
||||
.method_by_name(&self.method_name)
|
||||
.ok_or_else(|| MetadataError::RuntimeMethodNotFound((*self.method_name).to_owned()))?;
|
||||
|
||||
|
||||
@@ -70,11 +70,7 @@ where
|
||||
async move {
|
||||
let metadata = client.metadata();
|
||||
|
||||
let api_trait = metadata
|
||||
.runtime_api_trait_by_name(payload.trait_name())
|
||||
.ok_or_else(|| {
|
||||
MetadataError::RuntimeTraitNotFound(payload.trait_name().to_owned())
|
||||
})?;
|
||||
let api_trait = metadata.runtime_api_trait_by_name_err(payload.trait_name())?;
|
||||
let api_method = api_trait
|
||||
.method_by_name(payload.method_name())
|
||||
.ok_or_else(|| {
|
||||
|
||||
@@ -138,9 +138,7 @@ where
|
||||
}
|
||||
|
||||
fn append_entry_bytes(&self, metadata: &Metadata, bytes: &mut Vec<u8>) -> Result<(), Error> {
|
||||
let pallet = metadata
|
||||
.pallet_by_name(self.pallet_name())
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound(self.pallet_name().to_owned()))?;
|
||||
let pallet = metadata.pallet_by_name_err(self.pallet_name())?;
|
||||
let storage = pallet
|
||||
.storage()
|
||||
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(self.pallet_name().to_owned()))?;
|
||||
|
||||
@@ -9,7 +9,7 @@ use super::{
|
||||
|
||||
use crate::{
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
error::{Error, MetadataError},
|
||||
error::Error,
|
||||
Config,
|
||||
};
|
||||
use derivative::Derivative;
|
||||
@@ -44,9 +44,7 @@ where
|
||||
/// the pallet or storage entry in question do not exist at all).
|
||||
pub fn validate<Address: StorageAddress>(&self, address: &Address) -> Result<(), Error> {
|
||||
let metadata = self.client.metadata();
|
||||
let pallet_metadata = metadata
|
||||
.pallet_by_name(address.pallet_name())
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound(address.pallet_name().to_owned()))?;
|
||||
let pallet_metadata = metadata.pallet_by_name_err(address.pallet_name())?;
|
||||
validate_storage_address(address, pallet_metadata)
|
||||
}
|
||||
|
||||
|
||||
@@ -321,9 +321,7 @@ fn lookup_entry_details<'a>(
|
||||
entry_name: &str,
|
||||
metadata: &'a Metadata,
|
||||
) -> Result<(PalletMetadata<'a>, &'a StorageEntryMetadata), Error> {
|
||||
let pallet_metadata = metadata
|
||||
.pallet_by_name(pallet_name)
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound(pallet_name.to_owned()))?;
|
||||
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
|
||||
let storage_metadata = pallet_metadata
|
||||
.storage()
|
||||
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?;
|
||||
|
||||
@@ -50,8 +50,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
let expected_hash = self
|
||||
.client
|
||||
.metadata()
|
||||
.pallet_by_name(details.pallet_name)
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound(details.pallet_name.to_owned()))?
|
||||
.pallet_by_name_err(details.pallet_name)?
|
||||
.call_hash(details.call_name)
|
||||
.ok_or_else(|| MetadataError::CallNameNotFound(details.call_name.to_owned()))?;
|
||||
|
||||
@@ -358,10 +357,10 @@ where
|
||||
/// An address, and something representing a signature that can be SCALE encoded, are both
|
||||
/// needed in order to construct it. If you have a `Signer` to hand, you can use
|
||||
/// [`PartialExtrinsic::sign()`] instead.
|
||||
pub fn sign_with_address_and_signature<S: Encode>(
|
||||
pub fn sign_with_address_and_signature(
|
||||
&self,
|
||||
address: &T::Address,
|
||||
signature: &S,
|
||||
signature: &T::Signature,
|
||||
) -> SubmittableExtrinsic<T, C> {
|
||||
// Encode the extrinsic (into the format expected by protocol version 4)
|
||||
let extrinsic = {
|
||||
|
||||
@@ -141,9 +141,7 @@ impl Payload<Composite<()>> {
|
||||
|
||||
impl<CallData: EncodeAsFields> TxPayload for Payload<CallData> {
|
||||
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
|
||||
let pallet = metadata
|
||||
.pallet_by_name(&self.pallet_name)
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound((*self.pallet_name).to_owned()))?;
|
||||
let pallet = metadata.pallet_by_name_err(&self.pallet_name)?;
|
||||
let call = pallet
|
||||
.call_variant_by_name(&self.call_name)
|
||||
.ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
|
||||
|
||||
Reference in New Issue
Block a user