diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7b0e555e8e..17258b7094 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -25,7 +25,7 @@ jobs: - name: download-substrate run: | - curl "https://releases.parity.io/substrate/x86_64-debian:stretch/v3.0.0/substrate/substrate" --output substrate --location + curl "https://releases.parity.io/substrate/x86_64-debian:stretch/latest/substrate/substrate" --output substrate --location chmod +x ./substrate mkdir -p ~/.local/bin mv substrate ~/.local/bin diff --git a/.gitignore b/.gitignore index 693699042b..001b5bb5c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk Cargo.lock +cargo-timing* diff --git a/.rustfmt.toml b/.rustfmt.toml index 60f5650170..82af150637 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -43,7 +43,7 @@ trailing_comma = "Vertical" match_block_trailing_comma = false blank_lines_upper_bound = 1 blank_lines_lower_bound = 0 -edition = "2018" # changed +edition = "2021" # changed version = "One" merge_derives = true use_try_shorthand = true # changed diff --git a/CHANGELOG.md b/CHANGELOG.md index b8576b592f..4c275c8648 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,105 +9,105 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.15.0] - 2021-03-15 ### Added -- implement variant of subscription that returns finalized storage changes - [#237](https://github.com/paritytech/substrate-subxt/pull/237) -- implement session handling for unsubscribe in subxt-client - [#242](https://github.com/paritytech/substrate-subxt/pull/242) +- implement variant of subscription that returns finalized storage changes - [#237](https://github.com/paritytech/subxt/pull/237) +- implement session handling for unsubscribe in subxt-client - [#242](https://github.com/paritytech/subxt/pull/242) ### Changed -- update jsonrpsee [#251](https://github.com/paritytech/substrate-subxt/pull/251) -- return none if subscription returns early [#250](https://github.com/paritytech/substrate-subxt/pull/250) -- export ModuleError and RuntimeError for downstream usage - [#246](https://github.com/paritytech/substrate-subxt/pull/246) -- rpc client methods should be public for downstream usage - [#240](https://github.com/paritytech/substrate-subxt/pull/240) -- re-export WasmExecutionMethod for downstream usage - [#239](https://github.com/paritytech/substrate-subxt/pull/239) -- integration with jsonrpsee v2 - [#214](https://github.com/paritytech/substrate-subxt/pull/214) -- expose wasm execution method on subxt client config - [#230](https://github.com/paritytech/substrate-subxt/pull/230) -- Add hooks to register event types for decoding - [#227](https://github.com/paritytech/substrate-subxt/pull/227) -- Substrate 3.0 - [#232](https://github.com/paritytech/substrate-subxt/pull/232) +- update jsonrpsee [#251](https://github.com/paritytech/subxt/pull/251) +- return none if subscription returns early [#250](https://github.com/paritytech/subxt/pull/250) +- export ModuleError and RuntimeError for downstream usage - [#246](https://github.com/paritytech/subxt/pull/246) +- rpc client methods should be public for downstream usage - [#240](https://github.com/paritytech/subxt/pull/240) +- re-export WasmExecutionMethod for downstream usage - [#239](https://github.com/paritytech/subxt/pull/239) +- integration with jsonrpsee v2 - [#214](https://github.com/paritytech/subxt/pull/214) +- expose wasm execution method on subxt client config - [#230](https://github.com/paritytech/subxt/pull/230) +- Add hooks to register event types for decoding - [#227](https://github.com/paritytech/subxt/pull/227) +- Substrate 3.0 - [#232](https://github.com/paritytech/subxt/pull/232) ## [0.14.0] - 2021-02-05 -- Refactor event type decoding and declaration [#221](https://github.com/paritytech/substrate-subxt/pull/221) -- Add Balances Locks [#197](https://github.com/paritytech/substrate-subxt/pull/197) -- Add event Phase::Initialization [#215](https://github.com/paritytech/substrate-subxt/pull/215) -- Make type explicit [#217](https://github.com/paritytech/substrate-subxt/pull/217) -- Upgrade dependencies, bumps substrate to 2.0.1 [#219](https://github.com/paritytech/substrate-subxt/pull/219) -- Export extra types [#212](https://github.com/paritytech/substrate-subxt/pull/212) -- Enable retrieval of constants from rutnime metadata [#207](https://github.com/paritytech/substrate-subxt/pull/207) -- register type sizes for u64 and u128 [#200](https://github.com/paritytech/substrate-subxt/pull/200) -- Remove some substrate dependencies to improve compile time [#194](https://github.com/paritytech/substrate-subxt/pull/194) -- propagate 'RuntimeError's to 'decode_raw_bytes' caller [#189](https://github.com/paritytech/substrate-subxt/pull/189) -- Derive `Clone` for `PairSigner` [#184](https://github.com/paritytech/substrate-subxt/pull/184) +- Refactor event type decoding and declaration [#221](https://github.com/paritytech/subxt/pull/221) +- Add Balances Locks [#197](https://github.com/paritytech/subxt/pull/197) +- Add event Phase::Initialization [#215](https://github.com/paritytech/subxt/pull/215) +- Make type explicit [#217](https://github.com/paritytech/subxt/pull/217) +- Upgrade dependencies, bumps substrate to 2.0.1 [#219](https://github.com/paritytech/subxt/pull/219) +- Export extra types [#212](https://github.com/paritytech/subxt/pull/212) +- Enable retrieval of constants from rutnime metadata [#207](https://github.com/paritytech/subxt/pull/207) +- register type sizes for u64 and u128 [#200](https://github.com/paritytech/subxt/pull/200) +- Remove some substrate dependencies to improve compile time [#194](https://github.com/paritytech/subxt/pull/194) +- propagate 'RuntimeError's to 'decode_raw_bytes' caller [#189](https://github.com/paritytech/subxt/pull/189) +- Derive `Clone` for `PairSigner` [#184](https://github.com/paritytech/subxt/pull/184) ## [0.13.0] -- Make the contract call extrinsic work [#165](https://github.com/paritytech/substrate-subxt/pull/165) -- Update to Substrate 2.0.0 [#173](https://github.com/paritytech/substrate-subxt/pull/173) -- Display RawEvent data in hex [#168](https://github.com/paritytech/substrate-subxt/pull/168) -- Add SudoUncheckedWeightCall [#167](https://github.com/paritytech/substrate-subxt/pull/167) -- Add Add SetCodeWithoutChecksCall [#166](https://github.com/paritytech/substrate-subxt/pull/166) -- Improve contracts pallet tests [#163](https://github.com/paritytech/substrate-subxt/pull/163) -- Make Metadata types public [#162](https://github.com/paritytech/substrate-subxt/pull/162) -- Fix option decoding and add basic sanity test [#161](https://github.com/paritytech/substrate-subxt/pull/161) -- Add staking support [#160](https://github.com/paritytech/substrate-subxt/pull/161) -- Decode option event arg [#158](https://github.com/paritytech/substrate-subxt/pull/158) -- Remove unnecessary Sync bound [#172](https://github.com/paritytech/substrate-subxt/pull/172) +- Make the contract call extrinsic work [#165](https://github.com/paritytech/subxt/pull/165) +- Update to Substrate 2.0.0 [#173](https://github.com/paritytech/subxt/pull/173) +- Display RawEvent data in hex [#168](https://github.com/paritytech/subxt/pull/168) +- Add SudoUncheckedWeightCall [#167](https://github.com/paritytech/subxt/pull/167) +- Add Add SetCodeWithoutChecksCall [#166](https://github.com/paritytech/subxt/pull/166) +- Improve contracts pallet tests [#163](https://github.com/paritytech/subxt/pull/163) +- Make Metadata types public [#162](https://github.com/paritytech/subxt/pull/162) +- Fix option decoding and add basic sanity test [#161](https://github.com/paritytech/subxt/pull/161) +- Add staking support [#160](https://github.com/paritytech/subxt/pull/161) +- Decode option event arg [#158](https://github.com/paritytech/subxt/pull/158) +- Remove unnecessary Sync bound [#172](https://github.com/paritytech/subxt/pull/172) ## [0.12.0] -- Only return an error if the extrinsic failed. [#156](https://github.com/paritytech/substrate-subxt/pull/156) -- Update to rc6. [#155](https://github.com/paritytech/substrate-subxt/pull/155) -- Different assert. [#153](https://github.com/paritytech/substrate-subxt/pull/153) -- Add a method to fetch an unhashed key, close #100 [#152](https://github.com/paritytech/substrate-subxt/pull/152) -- Fix port number. [#151](https://github.com/paritytech/substrate-subxt/pull/151) -- Implement the `concat` in `twox_64_concat` [#150](https://github.com/paritytech/substrate-subxt/pull/150) -- Storage map iter [#148](https://github.com/paritytech/substrate-subxt/pull/148) +- Only return an error if the extrinsic failed. [#156](https://github.com/paritytech/subxt/pull/156) +- Update to rc6. [#155](https://github.com/paritytech/subxt/pull/155) +- Different assert. [#153](https://github.com/paritytech/subxt/pull/153) +- Add a method to fetch an unhashed key, close #100 [#152](https://github.com/paritytech/subxt/pull/152) +- Fix port number. [#151](https://github.com/paritytech/subxt/pull/151) +- Implement the `concat` in `twox_64_concat` [#150](https://github.com/paritytech/subxt/pull/150) +- Storage map iter [#148](https://github.com/paritytech/subxt/pull/148) ## [0.11.0] -- Fix build error, wabt 0.9.2 is yanked [#146](https://github.com/paritytech/substrate-subxt/pull/146) -- Rc5 [#143](https://github.com/paritytech/substrate-subxt/pull/143) -- Refactor: extract functions and types for creating extrinsics [#138](https://github.com/paritytech/substrate-subxt/pull/138) -- event subscription example [#140](https://github.com/paritytech/substrate-subxt/pull/140) -- Document the `Call` derive macro [#137](https://github.com/paritytech/substrate-subxt/pull/137) -- Document the #[module] macro [#135](https://github.com/paritytech/substrate-subxt/pull/135) -- Support authors api. [#134](https://github.com/paritytech/substrate-subxt/pull/134) +- Fix build error, wabt 0.9.2 is yanked [#146](https://github.com/paritytech/subxt/pull/146) +- Rc5 [#143](https://github.com/paritytech/subxt/pull/143) +- Refactor: extract functions and types for creating extrinsics [#138](https://github.com/paritytech/subxt/pull/138) +- event subscription example [#140](https://github.com/paritytech/subxt/pull/140) +- Document the `Call` derive macro [#137](https://github.com/paritytech/subxt/pull/137) +- Document the #[module] macro [#135](https://github.com/paritytech/subxt/pull/135) +- Support authors api. [#134](https://github.com/paritytech/subxt/pull/134) ## [0.10.1] - 2020-06-19 -- Release client v0.2.0 [#133](https://github.com/paritytech/substrate-subxt/pull/133) +- Release client v0.2.0 [#133](https://github.com/paritytech/subxt/pull/133) ## [0.10.0] - 2020-06-19 -- Upgrade to substrate rc4 release [#131](https://github.com/paritytech/substrate-subxt/pull/131) -- Support unsigned extrinsics. [#130](https://github.com/paritytech/substrate-subxt/pull/130) +- Upgrade to substrate rc4 release [#131](https://github.com/paritytech/subxt/pull/131) +- Support unsigned extrinsics. [#130](https://github.com/paritytech/subxt/pull/130) ## [0.9.0] - 2020-06-25 -- Events sub [#126](https://github.com/paritytech/substrate-subxt/pull/126) -- Improve error handling in proc-macros, handle DispatchError etc. [#123](https://github.com/paritytech/substrate-subxt/pull/123) -- Support embedded full/light node clients. [#91](https://github.com/paritytech/substrate-subxt/pull/91) -- Zero sized types [#121](https://github.com/paritytech/substrate-subxt/pull/121) -- Fix optional store items. [#120](https://github.com/paritytech/substrate-subxt/pull/120) -- Make signing fallable and asynchronous [#119](https://github.com/paritytech/substrate-subxt/pull/119) +- Events sub [#126](https://github.com/paritytech/subxt/pull/126) +- Improve error handling in proc-macros, handle DispatchError etc. [#123](https://github.com/paritytech/subxt/pull/123) +- Support embedded full/light node clients. [#91](https://github.com/paritytech/subxt/pull/91) +- Zero sized types [#121](https://github.com/paritytech/subxt/pull/121) +- Fix optional store items. [#120](https://github.com/paritytech/subxt/pull/120) +- Make signing fallable and asynchronous [#119](https://github.com/paritytech/subxt/pull/119) ## [0.8.0] - 2020-05-26 -- Update to Substrate release candidate [#116](https://github.com/paritytech/substrate-subxt/pull/116) -- Update to alpha.8 [#114](https://github.com/paritytech/substrate-subxt/pull/114) -- Refactors the api [#113](https://github.com/paritytech/substrate-subxt/pull/113) +- Update to Substrate release candidate [#116](https://github.com/paritytech/subxt/pull/116) +- Update to alpha.8 [#114](https://github.com/paritytech/subxt/pull/114) +- Refactors the api [#113](https://github.com/paritytech/subxt/pull/113) ## [0.7.0] - 2020-05-13 -- Split subxt [#102](https://github.com/paritytech/substrate-subxt/pull/102) -- Add support for RPC `state_getReadProof` [#106](https://github.com/paritytech/substrate-subxt/pull/106) -- Update to substrate alpha.7 release [#105](https://github.com/paritytech/substrate-subxt/pull/105) -- Double map and plain storage support, introduce macros [#93](https://github.com/paritytech/substrate-subxt/pull/93) -- Raw payload return SignedPayload struct [#92](https://github.com/paritytech/substrate-subxt/pull/92) +- Split subxt [#102](https://github.com/paritytech/subxt/pull/102) +- Add support for RPC `state_getReadProof` [#106](https://github.com/paritytech/subxt/pull/106) +- Update to substrate alpha.7 release [#105](https://github.com/paritytech/subxt/pull/105) +- Double map and plain storage support, introduce macros [#93](https://github.com/paritytech/subxt/pull/93) +- Raw payload return SignedPayload struct [#92](https://github.com/paritytech/subxt/pull/92) ## [0.6.0] - 2020-04-15 -- Raw extrinsic payloads in Client [#83](https://github.com/paritytech/substrate-subxt/pull/83) -- Custom extras [#89](https://github.com/paritytech/substrate-subxt/pull/89) -- Wrap and export BlockNumber [#87](https://github.com/paritytech/substrate-subxt/pull/87) +- Raw extrinsic payloads in Client [#83](https://github.com/paritytech/subxt/pull/83) +- Custom extras [#89](https://github.com/paritytech/subxt/pull/89) +- Wrap and export BlockNumber [#87](https://github.com/paritytech/subxt/pull/87) - All substrate dependencies upgraded to `alpha.6` ## [0.5.0] - 2020-03-25 diff --git a/Cargo.toml b/Cargo.toml index e121a91d3d..26a02c73da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,16 @@ [workspace] -members = [".", "client", "proc-macro"] +members = [".", "cli", "codegen", "macro"] [package] -name = "substrate-subxt" +name = "subxt" version = "0.15.0" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "GPL-3.0" readme = "README.md" -repository = "https://github.com/paritytech/substrate-subxt" -documentation = "https://docs.rs/substrate-subxt" +repository = "https://github.com/paritytech/subxt" +documentation = "https://docs.rs/subxt" homepage = "https://www.parity.io/" description = "Submit extrinsics (transactions) to a substrate node via RPC" keywords = ["parity", "substrate", "blockchain"] @@ -18,18 +18,16 @@ include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"] [features] default = ["tokio1"] -client = ["substrate-subxt-client"] # jsonrpsee can be configured to use tokio02 or tokio1. tokio02 = ["jsonrpsee-http-client/tokio02", "jsonrpsee-ws-client/tokio02"] tokio1 = ["jsonrpsee-http-client/tokio1", "jsonrpsee-ws-client/tokio1"] [dependencies] async-trait = "0.1.49" -codec = { package = "parity-scale-codec", version = "2.1", default-features = false, features = [ - "derive", - "full", -] } -dyn-clone = "1.0.4" +bitvec = { version = "0.20.1", default-features = false, features = ["alloc"] } +codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full", "bit-vec"] } +chameleon = "0.1.0" +scale-info = { version = "1.0.0", features = ["bit-vec"] } futures = "0.3.13" hex = "0.4.3" jsonrpsee-proc-macros = "0.3.0" @@ -43,20 +41,14 @@ serde_json = "1.0.64" thiserror = "1.0.24" url = "2.2.1" -substrate-subxt-client = { version = "0.7.0", path = "client", optional = true } -substrate-subxt-proc-macro = { version = "0.15.0", path = "proc-macro" } +subxt-macro = { version = "0.1.0", path = "macro" } -sp-application-crypto = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } -sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } -sp-rpc = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } -sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } -sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } -sp-version = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } +sp-arithmetic = { package = "sp-arithmetic", git = "https://github.com/paritytech/substrate/", branch = "master" } +sp-core = { package = "sp-core", git = "https://github.com/paritytech/substrate/", branch = "master" } +sp-runtime = { package = "sp-runtime", git = "https://github.com/paritytech/substrate/", branch = "master" } +sp-version = { package = "sp-version", git = "https://github.com/paritytech/substrate/", branch = "master" } -frame-metadata = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } -frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } -pallet-indices = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } -pallet-staking = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } +frame-metadata = "14.0.0" [dev-dependencies] assert_matches = "1.5.0" @@ -65,6 +57,6 @@ env_logger = "0.8.3" tempdir = "0.3.7" wabt = "0.10.0" which = "4.0.2" -sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } -frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } -pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } + +sp-keyring = { package = "sp-keyring", git = "https://github.com/paritytech/substrate/", branch = "master" } + diff --git a/FILE_TEMPLATE b/FILE_TEMPLATE index 5bec9f892d..36c00e99b5 100644 --- a/FILE_TEMPLATE +++ b/FILE_TEMPLATE @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,4 +12,4 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . diff --git a/README.md b/README.md index d44dff8efd..010b601fb3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ -# subxt · ![build](https://github.com/paritytech/substrate-subxt/workflows/Rust/badge.svg) [![Latest Version](https://img.shields.io/crates/v/substrate-subxt.svg)](https://crates.io/crates/substrate-subxt) [![Documentation](https://docs.rs/substrate-subxt/badge.svg)](https://docs.rs/substrate-subxt) +# subxt · ![build](https://github.com/paritytech/subxt/workflows/Rust/badge.svg) [![Latest Version](https://img.shields.io/crates/v/subxt.svg)](https://crates.io/crates/subxt) [![Documentation](https://docs.rs/subxt/badge.svg)](https://docs.rs/subxt) A library to **sub**mit e**xt**rinsics to a [substrate](https://github.com/paritytech/substrate) node via RPC. +### :warning: Health Warning :warning: considered *alpha* after recent changes, API still subject to change + +#### See https://github.com/paritytech/subxt/issues/309 for an overview of outstanding issues. + ## Usage See [examples](./examples). diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 0000000000..ea153c53cd --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "subxt-cli" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "subxt" +path = "src/main.rs" + +[dependencies] +# perform subxt codegen +subxt-codegen = { version = "0.1.0", path = "../codegen" } +# parse command line args +structopt = "0.3.25" +# make the request to a substrate node to get the metadata +ureq = { version = "2.2.0", features = ["json"] } +# colourful error reports +color-eyre = "0.5.11" +# serialize the metadata +serde = { version = "1.0.130", features = ["derive"] } +# serialize as json +serde_json = "1.0.68" +# hex encoded metadata to bytes +hex = "0.4.3" +# actual metadata types +frame-metadata = { version = "14.0.0", features = ["v14", "std"] } +# decode bytes into the metadata types +scale = { package = "parity-scale-codec", version = "2.3.0", default-features = false } +# handle urls to communicate with substrate nodes +url = { version = "2.2.2", features = ["serde"] } +# generate the item mod for codegen +syn = "1.0.80" \ No newline at end of file diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 0000000000..271a720903 --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,157 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use color_eyre::eyre::{ + self, + WrapErr, +}; +use frame_metadata::RuntimeMetadataPrefixed; +use scale::{ + Decode, + Input, +}; +use std::{ + fs, + io::{ + self, + Read, + Write, + }, + path::PathBuf, +}; +use structopt::StructOpt; + +/// Utilities for working with substrate metadata for subxt. +#[derive(Debug, StructOpt)] +struct Opts { + #[structopt(subcommand)] + command: Command, +} + +#[derive(Debug, StructOpt)] +enum Command { + /// Download metadata from a substrate node, for use with `subxt` codegen. + #[structopt(name = "metadata")] + Metadata { + /// the url of the substrate node to query for metadata + #[structopt( + name = "url", + long, + parse(try_from_str), + default_value = "http://localhost:9933" + )] + url: url::Url, + /// the format of the metadata to display: `json`, `hex` or `bytes` + #[structopt(long, short, default_value = "json")] + format: String, + }, + /// Generate runtime API client code from metadata. + /// + /// # Example (with code formatting) + /// + /// `subxt codegen | rustfmt --edition=2018 --emit=stdout` + Codegen { + /// the url of the substrate node to query for metadata for codegen. + #[structopt(name = "url", long, parse(try_from_str))] + url: Option, + /// the path to the encoded metadata file. + #[structopt(short, long, parse(from_os_str))] + file: Option, + }, +} + +fn main() -> color_eyre::Result<()> { + color_eyre::install()?; + let args = Opts::from_args(); + + match args.command { + Command::Metadata { url, format } => { + let (hex_data, bytes) = fetch_metadata(&url)?; + + match format.as_str() { + "json" => { + let metadata = + ::decode(&mut &bytes[..])?; + let json = serde_json::to_string_pretty(&metadata)?; + println!("{}", json); + Ok(()) + } + "hex" => { + println!("{}", hex_data); + Ok(()) + } + "bytes" => Ok(io::stdout().write_all(&bytes)?), + _ => { + Err(eyre::eyre!( + "Unsupported format `{}`, expected `json`, `hex` or `bytes`", + format + )) + } + } + } + Command::Codegen { url, file } => { + if let Some(file) = file.as_ref() { + if url.is_some() { + eyre::bail!("specify one of `--url` or `--file` but not both") + }; + + let mut file = fs::File::open(file)?; + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes)?; + codegen(&mut &bytes[..])?; + return Ok(()) + } + + let url = url.unwrap_or_else(|| { + url::Url::parse("http://localhost:9933").expect("default url is valid") + }); + let (_, bytes) = fetch_metadata(&url)?; + codegen(&mut &bytes[..])?; + return Ok(()) + } + } +} + +fn fetch_metadata(url: &url::Url) -> color_eyre::Result<(String, Vec)> { + let resp = ureq::post(url.as_str()) + .set("Content-Type", "application/json") + .send_json(ureq::json!({ + "jsonrpc": "2.0", + "method": "state_getMetadata", + "id": 1 + })) + .context("error fetching metadata from the substrate node")?; + let json: serde_json::Value = resp.into_json()?; + + let hex_data = json["result"] + .as_str() + .map(ToString::to_string) + .ok_or(eyre::eyre!("metadata result field should be a string"))?; + let bytes = hex::decode(hex_data.trim_start_matches("0x"))?; + + Ok((hex_data, bytes)) +} + +fn codegen(encoded: &mut I) -> color_eyre::Result<()> { + let metadata = ::decode(encoded)?; + let generator = subxt_codegen::RuntimeGenerator::new(metadata); + let item_mod = syn::parse_quote!( + pub mod api {} + ); + let runtime_api = generator.generate_runtime(item_mod, Default::default()); + println!("{}", runtime_api); + Ok(()) +} diff --git a/client/.gitignore b/client/.gitignore deleted file mode 100644 index 3005d75f6c..0000000000 --- a/client/.gitignore +++ /dev/null @@ -1 +0,0 @@ -dev-chain.json diff --git a/client/Cargo.toml b/client/Cargo.toml deleted file mode 100644 index 5ed4cfb3cc..0000000000 --- a/client/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "substrate-subxt-client" -version = "0.7.0" -authors = [ - "David Craven ", - "Parity Technologies ", -] -edition = "2018" - -license = "GPL-3.0" -repository = "https://github.com/paritytech/substrate-subxt" -documentation = "https://docs.rs/substrate-subxt-client" -homepage = "https://www.parity.io/" -description = "Embed a substrate node into your subxt application." -keywords = ["parity", "substrate", "blockchain"] - -[dependencies] -async-std = "1.8.0" -futures = { version = "0.3.9", features = ["compat"], package = "futures" } -futures01 = { package = "futures", version = "0.1.29" } -jsonrpsee-types = "0.3.0" -log = "0.4.13" -serde_json = "1.0.61" -thiserror = "1.0.23" - -sc-client-db = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } -sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } -sc-network = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10", default-features = false } -sc-service = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10", default-features = false } - -[target.'cfg(target_arch="x86_64")'.dependencies] -sc-service = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10", default-features = false, features = [ - "wasmtime", -] } - -[dev-dependencies] -async-std = { version = "1.8.0", features = ["attributes"] } -env_logger = "0.8.2" -tempdir = "0.3.7" diff --git a/client/src/lib.rs b/client/src/lib.rs deleted file mode 100644 index 76c55a936c..0000000000 --- a/client/src/lib.rs +++ /dev/null @@ -1,535 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -//! Client for embedding substrate nodes. - -#![deny(missing_docs)] - -use async_std::{ - sync::{ - Arc, - RwLock, - }, - task, -}; -use futures::{ - channel::{ - mpsc, - oneshot, - }, - compat::Stream01CompatExt, - future::{ - select, - FutureExt, - }, - sink::SinkExt, - stream::StreamExt, -}; -use futures01::sync::mpsc as mpsc01; -use jsonrpsee_types::{ - v2::{ - error::{ - JsonRpcError, - JsonRpcErrorCode, - }, - params::{ - Id, - JsonRpcParams, - JsonRpcSubscriptionParams, - SubscriptionId, - TwoPointZero, - }, - request::{ - JsonRpcCallSer, - JsonRpcInvalidRequest, - JsonRpcNotification, - JsonRpcNotificationSer, - }, - response::JsonRpcResponse, - }, - DeserializeOwned, - Error as JsonRpseeError, - FrontToBack, - JsonValue, - RequestMessage, - Subscription, - SubscriptionKind, - SubscriptionMessage, -}; -use sc_network::config::TransportConfig; -pub use sc_service::{ - config::{ - DatabaseConfig, - KeystoreConfig, - WasmExecutionMethod, - }, - Error as ServiceError, -}; -use sc_service::{ - config::{ - NetworkConfiguration, - TaskType, - TelemetryEndpoints, - }, - ChainSpec, - Configuration, - KeepBlocks, - RpcHandlers, - RpcSession, - TaskManager, -}; -use std::{ - collections::HashMap, - sync::atomic::{ - AtomicU64, - Ordering, - }, -}; -use thiserror::Error; - -const DEFAULT_CHANNEL_SIZE: usize = 16; - -/// Error thrown by the client. -#[derive(Debug, Error)] -pub enum SubxtClientError { - /// Failed to parse json rpc message. - #[error("{0}")] - Json(#[from] serde_json::Error), - /// Channel closed. - #[error("{0}")] - Mpsc(#[from] mpsc::SendError), -} - -/// Client for an embedded substrate node. -#[derive(Clone)] -pub struct SubxtClient { - to_back: mpsc::Sender, - next_id: Arc, -} - -impl SubxtClient { - /// Create a new client. - pub fn new(mut task_manager: TaskManager, rpc: RpcHandlers) -> Self { - let (to_back, from_front) = mpsc::channel(DEFAULT_CHANNEL_SIZE); - let subscriptions = - Arc::new(RwLock::new(HashMap::::new())); - - task::spawn( - select( - Box::pin(from_front.for_each(move |message: FrontToBack| { - let rpc = rpc.clone(); - let (to_front, from_back) = mpsc01::channel(DEFAULT_CHANNEL_SIZE); - let session = RpcSession::new(to_front.clone()); - - let subscriptions = subscriptions.clone(); - - async move { - match message { - FrontToBack::Notification(raw) => { - let _ = rpc.rpc_query(&session, &raw).await; - } - FrontToBack::Request(RequestMessage { - raw, - id, - send_back, - }) => { - let raw_response = rpc.rpc_query(&session, &raw).await; - let to_front = match read_jsonrpc_response( - raw_response, - Id::Number(id), - ) { - Some(Err(e)) => Err(e), - Some(Ok(rp)) => Ok(rp), - None => return, - }; - - send_back - .expect("request should have send_back") - .send(to_front) - .expect("failed to send request response"); - } - - FrontToBack::Subscribe(SubscriptionMessage { - raw, - subscribe_id, - unsubscribe_id, - unsubscribe_method, - send_back, - }) => { - let raw_response = rpc.rpc_query(&session, &raw).await; - let sub_id: SubscriptionId = match read_jsonrpc_response( - raw_response, - Id::Number(subscribe_id), - ) { - Some(Ok(rp)) => { - serde_json::from_value(rp) - .expect("infalliable; qed") - } - Some(Err(e)) => { - send_back - .send(Err(e)) - .expect("failed to send request response"); - return - } - None => return, - }; - - let (mut send_front_sub, send_back_sub) = - mpsc::channel(DEFAULT_CHANNEL_SIZE); - - send_back - .send(Ok((send_back_sub, sub_id.clone()))) - .expect("failed to send request response"); - - { - let mut subscriptions = subscriptions.write().await; - subscriptions.insert( - sub_id.clone(), - (unsubscribe_method, Id::Number(unsubscribe_id)), - ); - } - - task::spawn(async move { - let mut from_back = from_back.compat(); - let _session = session.clone(); - - while let Some(Ok(response)) = from_back.next().await - { - let notif = serde_json::from_str::< - JsonRpcNotification< - JsonRpcSubscriptionParams<_>, - >, - >( - &response - ) - .expect("failed to decode subscription notif"); - // ignore send error since the channel is probably closed - let _ = send_front_sub - .send(notif.params.result) - .await; - } - }); - } - - FrontToBack::SubscriptionClosed(sub_id) => { - let params: &[JsonValue] = &[sub_id.clone().into()]; - - let subscriptions = subscriptions.read().await; - if let Some((unsub_method, unsub_id)) = - subscriptions.get(&sub_id) - { - let message = - serde_json::to_string(&JsonRpcCallSer::new( - unsub_id.clone(), - unsub_method, - params.into(), - )) - .unwrap(); - let _ = rpc.rpc_query(&session, &message).await; - } - } - _ => (), - } - } - })), - Box::pin(async move { - task_manager.future().await.ok(); - }), - ) - .map(drop), - ); - - Self { - to_back, - next_id: Arc::new(AtomicU64::new(0)), - } - } - - /// Creates a new client from a config. - pub fn from_config( - config: SubxtClientConfig, - builder: impl Fn(Configuration) -> Result<(TaskManager, RpcHandlers), ServiceError>, - ) -> Result { - let config = config.into_service_config(); - let (task_manager, rpc_handlers) = (builder)(config)?; - Ok(Self::new(task_manager, rpc_handlers)) - } - - /// Send a JSONRPC notification. - pub async fn notification<'a>( - &self, - method: &'a str, - params: JsonRpcParams<'a>, - ) -> Result<(), JsonRpseeError> { - let msg = serde_json::to_string(&JsonRpcNotificationSer::new(method, params)) - .map_err(JsonRpseeError::ParseError)?; - self.to_back - .clone() - .send(FrontToBack::Notification(msg)) - .await - .map_err(|e| JsonRpseeError::Transport(Box::new(e))) - } - - /// Send a JSONRPC request. - pub async fn request<'a, T>( - &self, - method: &'a str, - params: JsonRpcParams<'a>, - ) -> Result - where - T: DeserializeOwned, - { - let (send_back_tx, send_back_rx) = oneshot::channel(); - - let id = self.next_id.fetch_add(1, Ordering::Relaxed); - let msg = - serde_json::to_string(&JsonRpcCallSer::new(Id::Number(id), method, params)) - .map_err(JsonRpseeError::ParseError)?; - self.to_back - .clone() - .send(FrontToBack::Request(RequestMessage { - raw: msg, - id, - send_back: Some(send_back_tx), - })) - .await - .map_err(|e| JsonRpseeError::Transport(Box::new(e)))?; - - let json_value = match send_back_rx.await { - Ok(Ok(v)) => v, - Ok(Err(err)) => return Err(err), - Err(err) => return Err(JsonRpseeError::Transport(Box::new(err))), - }; - serde_json::from_value(json_value).map_err(JsonRpseeError::ParseError) - } - - /// Send a subscription request to the server. - pub async fn subscribe<'a, N>( - &self, - subscribe_method: &'a str, - params: JsonRpcParams<'a>, - unsubscribe_method: &'a str, - ) -> Result, JsonRpseeError> - where - N: DeserializeOwned, - { - let sub_req_id = self.next_id.fetch_add(1, Ordering::Relaxed); - let unsub_req_id = self.next_id.fetch_add(1, Ordering::Relaxed); - let msg = serde_json::to_string(&JsonRpcCallSer::new( - Id::Number(sub_req_id), - subscribe_method, - params, - )) - .map_err(JsonRpseeError::ParseError)?; - - let (send_back_tx, send_back_rx) = oneshot::channel(); - self.to_back - .clone() - .send(FrontToBack::Subscribe(SubscriptionMessage { - raw: msg, - subscribe_id: sub_req_id, - unsubscribe_id: unsub_req_id, - unsubscribe_method: unsubscribe_method.to_owned(), - send_back: send_back_tx, - })) - .await - .map_err(JsonRpseeError::Internal)?; - - let (notifs_rx, id) = match send_back_rx.await { - Ok(Ok(val)) => val, - Ok(Err(err)) => return Err(err), - Err(err) => return Err(JsonRpseeError::Transport(Box::new(err))), - }; - Ok(Subscription::new( - self.to_back.clone(), - notifs_rx, - SubscriptionKind::Subscription(id), - )) - } -} - -/// Role of the node. -#[derive(Clone, Copy, Debug)] -pub enum Role { - /// Light client. - Light, - /// A full node (mainly used for testing purposes). - Authority(sp_keyring::AccountKeyring), -} - -impl From for sc_service::Role { - fn from(role: Role) -> Self { - match role { - Role::Light => Self::Light, - Role::Authority(_) => { - Self::Authority { - sentry_nodes: Default::default(), - } - } - } - } -} - -impl From for Option { - fn from(role: Role) -> Self { - match role { - Role::Light => None, - Role::Authority(key) => Some(key.to_seed()), - } - } -} - -/// Client configuration. -#[derive(Clone)] -pub struct SubxtClientConfig { - /// Name of the implementation. - pub impl_name: &'static str, - /// Version of the implementation. - pub impl_version: &'static str, - /// Author of the implementation. - pub author: &'static str, - /// Copyright start year. - pub copyright_start_year: i32, - /// Database configuration. - pub db: DatabaseConfig, - /// Keystore configuration. - pub keystore: KeystoreConfig, - /// Chain specification. - pub chain_spec: C, - /// Role of the node. - pub role: Role, - /// Enable telemetry on the given port. - pub telemetry: Option, - /// Wasm execution method - pub wasm_method: WasmExecutionMethod, -} - -impl SubxtClientConfig { - /// Creates a service configuration. - pub fn into_service_config(self) -> Configuration { - let mut network = NetworkConfiguration::new( - format!("{} (subxt client)", self.chain_spec.name()), - "unknown", - Default::default(), - None, - ); - network.boot_nodes = self.chain_spec.boot_nodes().to_vec(); - network.transport = TransportConfig::Normal { - enable_mdns: true, - allow_private_ipv4: true, - wasm_external_transport: None, - }; - let telemetry_endpoints = if let Some(port) = self.telemetry { - let endpoints = TelemetryEndpoints::new(vec![( - format!("/ip4/127.0.0.1/tcp/{}/ws", port), - 0, - )]) - .expect("valid config; qed"); - Some(endpoints) - } else { - None - }; - let service_config = Configuration { - network, - impl_name: self.impl_name.to_string(), - impl_version: self.impl_version.to_string(), - chain_spec: Box::new(self.chain_spec), - role: self.role.into(), - task_executor: (move |fut, ty| { - match ty { - TaskType::Async => task::spawn(fut), - TaskType::Blocking => task::spawn_blocking(|| task::block_on(fut)), - } - }) - .into(), - database: self.db, - keystore: self.keystore, - max_runtime_instances: 8, - announce_block: true, - dev_key_seed: self.role.into(), - telemetry_endpoints, - - telemetry_external_transport: Default::default(), - telemetry_handle: Default::default(), - telemetry_span: Default::default(), - default_heap_pages: Default::default(), - disable_grandpa: Default::default(), - disable_log_reloading: Default::default(), - execution_strategies: Default::default(), - force_authoring: Default::default(), - keep_blocks: KeepBlocks::All, - keystore_remote: Default::default(), - offchain_worker: Default::default(), - prometheus_config: Default::default(), - rpc_cors: Default::default(), - rpc_http: Default::default(), - rpc_ipc: Default::default(), - rpc_ws: Default::default(), - rpc_ws_max_connections: Default::default(), - rpc_methods: Default::default(), - state_cache_child_ratio: Default::default(), - state_cache_size: Default::default(), - tracing_receiver: Default::default(), - tracing_targets: Default::default(), - transaction_pool: Default::default(), - wasm_method: self.wasm_method, - base_path: Default::default(), - informant_output_format: Default::default(), - state_pruning: Default::default(), - transaction_storage: sc_client_db::TransactionStorageMode::BlockBody, - wasm_runtime_overrides: Default::default(), - }; - - log::info!("{}", service_config.impl_name); - log::info!("✌️ version {}", service_config.impl_version); - log::info!("❤️ by {}, {}", self.author, self.copyright_start_year); - log::info!( - "📋 Chain specification: {}", - service_config.chain_spec.name() - ); - log::info!("🏷 Node name: {}", service_config.network.node_name); - log::info!("👤 Role: {:?}", self.role); - - service_config - } -} - -fn read_jsonrpc_response( - maybe_msg: Option, - id: Id, -) -> Option> { - let msg: String = maybe_msg?; - // NOTE: `let res` is a workaround because rustc otherwise doesn't compile - // `msg` doesn't live long enough. - let res = match serde_json::from_str::>(&msg) { - Ok(rp) if rp.id == id => Some(Ok(rp.result)), - Ok(_) => Some(Err(JsonRpseeError::InvalidRequestId)), - Err(_) => { - match serde_json::from_str::>(&msg) { - Ok(err) => { - let err = JsonRpcError { - jsonrpc: TwoPointZero, - error: JsonRpcErrorCode::InvalidRequest.into(), - id: err.id, - }; - Some(Err(JsonRpseeError::Request(err.to_string()))) - } - Err(_) => None, - } - } - }; - res -} diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml new file mode 100644 index 0000000000..4fd6c5f084 --- /dev/null +++ b/codegen/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "subxt-codegen" +version = "0.1.0" +edition = "2021" + +[dependencies] +async-trait = "0.1.49" +codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full", "bit-vec"] } +darling = "0.13.0" +frame-metadata = "14.0" +heck = "0.3.2" +proc-macro2 = "1.0.24" +proc-macro-crate = "0.1.5" +proc-macro-error = "1.0.4" +quote = "1.0.8" +syn = "1.0.58" +scale-info = { version = "1.0.0", features = ["bit-vec"] } + +[dev-dependencies] +bitvec = { version = "0.20.1", default-features = false, features = ["alloc"] } +pretty_assertions = "0.6.1" diff --git a/codegen/src/api/calls.rs b/codegen/src/api/calls.rs new file mode 100644 index 0000000000..6d52f597af --- /dev/null +++ b/codegen/src/api/calls.rs @@ -0,0 +1,101 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use crate::types::TypeGenerator; +use frame_metadata::{ + PalletCallMetadata, + PalletMetadata, +}; +use heck::SnakeCase as _; +use proc_macro2::TokenStream as TokenStream2; +use proc_macro_error::abort_call_site; +use quote::{ + format_ident, + quote, +}; +use scale_info::form::PortableForm; + +pub fn generate_calls( + type_gen: &TypeGenerator, + pallet: &PalletMetadata, + call: &PalletCallMetadata, + types_mod_ident: &syn::Ident, +) -> TokenStream2 { + let struct_defs = + super::generate_structs_from_variants(type_gen, call.ty.id(), "Call"); + let (call_structs, call_fns): (Vec<_>, Vec<_>) = struct_defs + .iter() + .map(|struct_def| { + let (call_fn_args, call_args): (Vec<_>, Vec<_>) = struct_def + .named_fields() + .unwrap_or_else(|| { + abort_call_site!( + "Call variant for type {} must have all named fields", + call.ty.id() + ) + }) + .iter() + .map(|(name, ty)| (quote!( #name: #ty ), name)) + .unzip(); + + let pallet_name = &pallet.name; + let call_struct_name = &struct_def.name; + let function_name = struct_def.name.to_string().to_snake_case(); + let fn_name = format_ident!("{}", function_name); + + let call_struct = quote! { + #struct_def + + impl ::subxt::Call for #call_struct_name { + const PALLET: &'static str = #pallet_name; + const FUNCTION: &'static str = #function_name; + } + }; + let client_fn = quote! { + pub fn #fn_name( + &self, + #( #call_fn_args, )* + ) -> ::subxt::SubmittableExtrinsic { + let call = #call_struct_name { #( #call_args, )* }; + ::subxt::SubmittableExtrinsic::new(self.client, call) + } + }; + (call_struct, client_fn) + }) + .unzip(); + + quote! { + pub mod calls { + use super::#types_mod_ident; + #( #call_structs )* + + pub struct TransactionApi<'a, T: ::subxt::Config + ::subxt::ExtrinsicExtraData> { + client: &'a ::subxt::Client, + } + + impl<'a, T: ::subxt::Config> TransactionApi<'a, T> + where + T: ::subxt::Config + ::subxt::ExtrinsicExtraData, + { + pub fn new(client: &'a ::subxt::Client) -> Self { + Self { client } + } + + #( #call_fns )* + } + } + } +} diff --git a/codegen/src/api/events.rs b/codegen/src/api/events.rs new file mode 100644 index 0000000000..2cfe804273 --- /dev/null +++ b/codegen/src/api/events.rs @@ -0,0 +1,57 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use crate::types::TypeGenerator; +use frame_metadata::{ + PalletEventMetadata, + PalletMetadata, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use scale_info::form::PortableForm; + +pub fn generate_events( + type_gen: &TypeGenerator, + pallet: &PalletMetadata, + event: &PalletEventMetadata, + types_mod_ident: &syn::Ident, +) -> TokenStream2 { + let struct_defs = + super::generate_structs_from_variants(type_gen, event.ty.id(), "Event"); + let event_structs = struct_defs.iter().map(|struct_def| { + let pallet_name = &pallet.name; + let event_struct = &struct_def.name; + let event_name = struct_def.name.to_string(); + + quote! { + #struct_def + + impl ::subxt::Event for #event_struct { + const PALLET: &'static str = #pallet_name; + const EVENT: &'static str = #event_name; + } + } + }); + let event_type = type_gen.resolve_type_path(event.ty.id(), &[]); + + quote! { + pub type Event = #event_type; + pub mod events { + use super::#types_mod_ident; + #( #event_structs )* + } + } +} diff --git a/codegen/src/api/mod.rs b/codegen/src/api/mod.rs new file mode 100644 index 0000000000..bc46d02745 --- /dev/null +++ b/codegen/src/api/mod.rs @@ -0,0 +1,347 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +mod calls; +mod events; +mod storage; + +use super::GeneratedTypeDerives; +use crate::{ + ir, + struct_def::StructDef, + types::TypeGenerator, +}; +use codec::Decode; +use frame_metadata::{ + v14::RuntimeMetadataV14, + RuntimeMetadata, + RuntimeMetadataPrefixed, +}; +use heck::SnakeCase as _; +use proc_macro2::TokenStream as TokenStream2; +use proc_macro_error::abort_call_site; +use quote::{ + format_ident, + quote, +}; +use std::{ + collections::HashMap, + fs, + io::Read, + path, + string::ToString, +}; +use syn::{ + parse_quote, + punctuated::Punctuated, +}; + +pub fn generate_runtime_api

( + item_mod: syn::ItemMod, + path: P, + generated_type_derives: Option>, +) -> TokenStream2 +where + P: AsRef, +{ + let mut file = fs::File::open(&path).unwrap_or_else(|e| { + abort_call_site!("Failed to open {}: {}", path.as_ref().to_string_lossy(), e) + }); + + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes) + .unwrap_or_else(|e| abort_call_site!("Failed to read metadata file: {}", e)); + + let metadata = frame_metadata::RuntimeMetadataPrefixed::decode(&mut &bytes[..]) + .unwrap_or_else(|e| abort_call_site!("Failed to decode metadata: {}", e)); + + let mut derives = GeneratedTypeDerives::default(); + if let Some(user_derives) = generated_type_derives { + derives.append(user_derives.iter().cloned()) + } + + let generator = RuntimeGenerator::new(metadata); + generator.generate_runtime(item_mod, derives) +} + +pub struct RuntimeGenerator { + metadata: RuntimeMetadataV14, +} + +impl RuntimeGenerator { + pub fn new(metadata: RuntimeMetadataPrefixed) -> Self { + match metadata.1 { + RuntimeMetadata::V14(v14) => Self { metadata: v14 }, + _ => panic!("Unsupported metadata version {:?}", metadata.1), + } + } + + pub fn generate_runtime( + &self, + item_mod: syn::ItemMod, + derives: GeneratedTypeDerives, + ) -> TokenStream2 { + let item_mod_ir = ir::ItemMod::from(item_mod); + + // some hardcoded default type substitutes, can be overridden by user + let mut type_substitutes = [ + ( + "bitvec::order::Lsb0", + parse_quote!(::subxt::bitvec::order::Lsb0), + ), + ( + "bitvec::order::Msb0", + parse_quote!(::subxt::bitvec::order::Msb0), + ), + ( + "sp_core::crypto::AccountId32", + parse_quote!(::subxt::sp_core::crypto::AccountId32), + ), + ( + "primitive_types::H256", + parse_quote!(::subxt::sp_core::H256), + ), + ( + "sp_runtime::multiaddress::MultiAddress", + parse_quote!(::subxt::sp_runtime::MultiAddress), + ), + ( + "frame_support::traits::misc::WrapperKeepOpaque", + parse_quote!(::subxt::WrapperKeepOpaque), + ), + ] + .iter() + .map(|(path, substitute): &(&str, syn::TypePath)| { + (path.to_string(), substitute.clone()) + }) + .collect::>(); + + for (path, substitute) in item_mod_ir.type_substitutes().iter() { + type_substitutes.insert(path.to_string(), substitute.clone()); + } + + let type_gen = TypeGenerator::new( + &self.metadata.types, + "runtime_types", + type_substitutes, + derives.clone(), + ); + let types_mod = type_gen.generate_types_mod(); + let types_mod_ident = types_mod.ident(); + let pallets_with_mod_names = self + .metadata + .pallets + .iter() + .map(|pallet| { + ( + pallet, + format_ident!("{}", pallet.name.to_string().to_snake_case()), + ) + }) + .collect::>(); + let modules = pallets_with_mod_names.iter().map(|(pallet, mod_name)| { + let calls = if let Some(ref calls) = pallet.calls { + calls::generate_calls(&type_gen, pallet, calls, types_mod_ident) + } else { + quote!() + }; + + let event = if let Some(ref event) = pallet.event { + events::generate_events(&type_gen, pallet, event, types_mod_ident) + } else { + quote!() + }; + + let storage_mod = if let Some(ref storage) = pallet.storage { + storage::generate_storage(&type_gen, pallet, storage, types_mod_ident) + } else { + quote!() + }; + + quote! { + pub mod #mod_name { + use super::#types_mod_ident; + #calls + #event + #storage_mod + } + } + }); + + let outer_event_variants = self.metadata.pallets.iter().filter_map(|p| { + let variant_name = format_ident!("{}", p.name); + let mod_name = format_ident!("{}", p.name.to_string().to_snake_case()); + let index = proc_macro2::Literal::u8_unsuffixed(p.index); + + p.event.as_ref().map(|_| { + quote! { + #[codec(index = #index)] + #variant_name(#mod_name::Event), + } + }) + }); + + let outer_event = quote! { + #derives + pub enum Event { + #( #outer_event_variants )* + } + }; + + let mod_ident = item_mod_ir.ident; + let pallets_with_storage = + pallets_with_mod_names + .iter() + .filter_map(|(pallet, pallet_mod_name)| { + pallet.storage.as_ref().map(|_| pallet_mod_name) + }); + let pallets_with_calls = + pallets_with_mod_names + .iter() + .filter_map(|(pallet, pallet_mod_name)| { + pallet.calls.as_ref().map(|_| pallet_mod_name) + }); + + quote! { + #[allow(dead_code, unused_imports, non_camel_case_types)] + pub mod #mod_ident { + #outer_event + #( #modules )* + #types_mod + + /// Default configuration of common types for a target Substrate runtime. + #[derive(Clone, Debug, Default, Eq, PartialEq)] + pub struct DefaultConfig; + + impl ::subxt::Config for DefaultConfig { + type Index = u32; + type BlockNumber = u32; + type Hash = ::subxt::sp_core::H256; + type Hashing = ::subxt::sp_runtime::traits::BlakeTwo256; + type AccountId = ::subxt::sp_runtime::AccountId32; + type Address = ::subxt::sp_runtime::MultiAddress; + type Header = ::subxt::sp_runtime::generic::Header< + Self::BlockNumber, ::subxt::sp_runtime::traits::BlakeTwo256 + >; + type Signature = ::subxt::sp_runtime::MultiSignature; + type Extrinsic = ::subxt::sp_runtime::OpaqueExtrinsic; + } + + impl ::subxt::ExtrinsicExtraData for DefaultConfig { + type AccountData = AccountData; + type Extra = ::subxt::DefaultExtra; + } + + pub type AccountData = self::system::storage::Account; + + impl ::subxt::AccountData for AccountData { + fn nonce(result: &::Value) -> ::Index { + result.nonce + } + fn storage_entry(account_id: ::AccountId) -> Self { + Self(account_id) + } + } + + pub struct RuntimeApi> { + pub client: ::subxt::Client, + } + + impl ::core::convert::From<::subxt::Client> for RuntimeApi + where + T: ::subxt::Config + ::subxt::ExtrinsicExtraData, + { + fn from(client: ::subxt::Client) -> Self { + Self { client } + } + } + + impl<'a, T> RuntimeApi + where + T: ::subxt::Config + ::subxt::ExtrinsicExtraData, + { + pub fn storage(&'a self) -> StorageApi<'a, T> { + StorageApi { client: &self.client } + } + + pub fn tx(&'a self) -> TransactionApi<'a, T> { + TransactionApi { client: &self.client } + } + } + + pub struct StorageApi<'a, T> + where + T: ::subxt::Config + ::subxt::ExtrinsicExtraData, + { + client: &'a ::subxt::Client, + } + + impl<'a, T> StorageApi<'a, T> + where + T: ::subxt::Config + ::subxt::ExtrinsicExtraData, + { + #( + pub fn #pallets_with_storage(&self) -> #pallets_with_storage::storage::StorageApi<'a, T> { + #pallets_with_storage::storage::StorageApi::new(self.client) + } + )* + } + + pub struct TransactionApi<'a, T: ::subxt::Config + ::subxt::ExtrinsicExtraData> { + client: &'a ::subxt::Client, + } + + impl<'a, T> TransactionApi<'a, T> + where + T: ::subxt::Config + ::subxt::ExtrinsicExtraData, + { + #( + pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi<'a, T> { + #pallets_with_calls::calls::TransactionApi::new(self.client) + } + )* + } + } + } + } +} + +pub fn generate_structs_from_variants( + type_gen: &TypeGenerator, + type_id: u32, + error_message_type_name: &str, +) -> Vec { + let ty = type_gen.resolve_type(type_id); + if let scale_info::TypeDef::Variant(variant) = ty.type_def() { + variant + .variants() + .iter() + .map(|var| { + StructDef::new( + var.name(), + var.fields(), + Some(syn::parse_quote!(pub)), + type_gen, + ) + }) + .collect() + } else { + abort_call_site!( + "{} type should be an variant/enum type", + error_message_type_name + ) + } +} diff --git a/codegen/src/api/storage.rs b/codegen/src/api/storage.rs new file mode 100644 index 0000000000..22310f30ae --- /dev/null +++ b/codegen/src/api/storage.rs @@ -0,0 +1,223 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use crate::types::TypeGenerator; +use frame_metadata::{ + PalletMetadata, + PalletStorageMetadata, + StorageEntryMetadata, + StorageEntryModifier, + StorageEntryType, + StorageHasher, +}; +use heck::SnakeCase as _; +use proc_macro2::TokenStream as TokenStream2; +use proc_macro_error::abort_call_site; +use quote::{ + format_ident, + quote, +}; +use scale_info::{ + form::PortableForm, + TypeDef, +}; + +pub fn generate_storage( + type_gen: &TypeGenerator, + pallet: &PalletMetadata, + storage: &PalletStorageMetadata, + types_mod_ident: &syn::Ident, +) -> TokenStream2 { + let (storage_structs, storage_fns): (Vec<_>, Vec<_>) = storage + .entries + .iter() + .map(|entry| generate_storage_entry_fns(&type_gen, &pallet, entry)) + .unzip(); + + quote! { + pub mod storage { + use super::#types_mod_ident; + #( #storage_structs )* + + pub struct StorageApi<'a, T: ::subxt::Config> { + client: &'a ::subxt::Client, + } + + impl<'a, T: ::subxt::Config> StorageApi<'a, T> { + pub fn new(client: &'a ::subxt::Client) -> Self { + Self { client } + } + + #( #storage_fns )* + } + } + } +} + +fn generate_storage_entry_fns( + type_gen: &TypeGenerator, + pallet: &PalletMetadata, + storage_entry: &StorageEntryMetadata, +) -> (TokenStream2, TokenStream2) { + let entry_struct_ident = format_ident!("{}", storage_entry.name); + let (fields, entry_struct, constructor, key_impl) = match storage_entry.ty { + StorageEntryType::Plain(_) => { + let entry_struct = quote!( pub struct #entry_struct_ident; ); + let constructor = quote!( #entry_struct_ident ); + let key_impl = quote!(::subxt::StorageEntryKey::Plain); + (vec![], entry_struct, constructor, key_impl) + } + StorageEntryType::Map { + ref key, + ref hashers, + .. + } => { + let key_ty = type_gen.resolve_type(key.id()); + let hashers = hashers + .iter() + .map(|hasher| { + let hasher = match hasher { + StorageHasher::Blake2_128 => "Blake2_128", + StorageHasher::Blake2_256 => "Blake2_256", + StorageHasher::Blake2_128Concat => "Blake2_128Concat", + StorageHasher::Twox128 => "Twox128", + StorageHasher::Twox256 => "Twox256", + StorageHasher::Twox64Concat => "Twox64Concat", + StorageHasher::Identity => "Identity", + }; + let hasher = format_ident!("{}", hasher); + quote!( ::subxt::StorageHasher::#hasher ) + }) + .collect::>(); + match key_ty.type_def() { + TypeDef::Tuple(tuple) => { + let fields = tuple + .fields() + .iter() + .enumerate() + .map(|(i, f)| { + let field_name = format_ident!("_{}", syn::Index::from(i)); + let field_type = type_gen.resolve_type_path(f.id(), &[]); + (field_name, field_type) + }) + .collect::>(); + // toddo: [AJ] use unzip here? + let tuple_struct_fields = + fields.iter().map(|(_, field_type)| field_type); + let field_names = fields.iter().map(|(field_name, _)| field_name); + let entry_struct = quote! { + pub struct #entry_struct_ident( #( #tuple_struct_fields ),* ); + }; + let constructor = + quote!( #entry_struct_ident( #( #field_names ),* ) ); + let keys = (0..tuple.fields().len()).into_iter().zip(hashers).map( + |(field, hasher)| { + let index = syn::Index::from(field); + quote!( ::subxt::StorageMapKey::new(&self.#index, #hasher) ) + }, + ); + let key_impl = quote! { + ::subxt::StorageEntryKey::Map( + vec![ #( #keys ),* ] + ) + }; + (fields, entry_struct, constructor, key_impl) + } + _ => { + let ty_path = type_gen.resolve_type_path(key.id(), &[]); + let fields = vec![(format_ident!("_0"), ty_path.clone())]; + let entry_struct = quote! { + pub struct #entry_struct_ident( pub #ty_path ); + }; + let constructor = quote!( #entry_struct_ident(_0) ); + let hasher = hashers.get(0).unwrap_or_else(|| { + abort_call_site!("No hasher found for single key") + }); + let key_impl = quote! { + ::subxt::StorageEntryKey::Map( + vec![ ::subxt::StorageMapKey::new(&self.0, #hasher) ] + ) + }; + (fields, entry_struct, constructor, key_impl) + } + } + } + }; + let pallet_name = &pallet.name; + let storage_name = &storage_entry.name; + let fn_name = format_ident!("{}", storage_entry.name.to_snake_case()); + let fn_name_iter = format_ident!("{}_iter", fn_name); + let storage_entry_ty = match storage_entry.ty { + StorageEntryType::Plain(ref ty) => ty, + StorageEntryType::Map { ref value, .. } => value, + }; + let storage_entry_value_ty = type_gen.resolve_type_path(storage_entry_ty.id(), &[]); + let (return_ty, fetch) = match storage_entry.modifier { + StorageEntryModifier::Default => { + (quote!( #storage_entry_value_ty ), quote!(fetch_or_default)) + } + StorageEntryModifier::Optional => { + ( + quote!( ::core::option::Option<#storage_entry_value_ty> ), + quote!(fetch), + ) + } + }; + + let storage_entry_type = quote! { + #entry_struct + + impl ::subxt::StorageEntry for #entry_struct_ident { + const PALLET: &'static str = #pallet_name; + const STORAGE: &'static str = #storage_name; + type Value = #storage_entry_value_ty; + fn key(&self) -> ::subxt::StorageEntryKey { + #key_impl + } + } + }; + + let client_iter_fn = if matches!(storage_entry.ty, StorageEntryType::Map { .. }) { + quote! ( + pub async fn #fn_name_iter( + &self, + hash: ::core::option::Option, + ) -> ::core::result::Result<::subxt::KeyIter<'a, T, #entry_struct_ident>, ::subxt::Error> { + self.client.storage().iter(hash).await + } + ) + } else { + quote!() + }; + + let key_args = fields + .iter() + .map(|(field_name, field_type)| quote!( #field_name: #field_type )); + let client_fns = quote! { + pub async fn #fn_name( + &self, + #( #key_args, )* + hash: ::core::option::Option, + ) -> ::core::result::Result<#return_ty, ::subxt::Error> { + let entry = #constructor; + self.client.storage().#fetch(&entry, hash).await + } + + #client_iter_fn + }; + + (storage_entry_type, client_fns) +} diff --git a/codegen/src/derives.rs b/codegen/src/derives.rs new file mode 100644 index 0000000000..a27319b923 --- /dev/null +++ b/codegen/src/derives.rs @@ -0,0 +1,52 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use syn::punctuated::Punctuated; + +#[derive(Debug, Clone)] +pub struct GeneratedTypeDerives { + derives: Punctuated, +} + +impl GeneratedTypeDerives { + pub fn new(derives: Punctuated) -> Self { + Self { derives } + } + + pub fn append(&mut self, derives: impl Iterator) { + for derive in derives { + self.derives.push(derive) + } + } +} + +impl Default for GeneratedTypeDerives { + fn default() -> Self { + let mut derives = Punctuated::new(); + derives.push(syn::parse_quote!(::subxt::codec::Encode)); + derives.push(syn::parse_quote!(::subxt::codec::Decode)); + Self::new(derives) + } +} + +impl quote::ToTokens for GeneratedTypeDerives { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let derives = &self.derives; + tokens.extend(quote::quote! { + #[derive(#derives)] + }) + } +} diff --git a/codegen/src/ir.rs b/codegen/src/ir.rs new file mode 100644 index 0000000000..429d0d9acd --- /dev/null +++ b/codegen/src/ir.rs @@ -0,0 +1,146 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use proc_macro_error::abort; +use std::collections::HashMap; +use syn::{ + spanned::Spanned as _, + token, +}; + +#[derive(Debug, PartialEq, Eq)] +pub struct ItemMod { + // attrs: Vec, + vis: syn::Visibility, + mod_token: token::Mod, + pub ident: syn::Ident, + brace: token::Brace, + items: Vec, +} + +impl From for ItemMod { + fn from(module: syn::ItemMod) -> Self { + let (brace, items) = match module.content { + Some((brace, items)) => (brace, items), + None => { + abort!(module, "out-of-line subxt modules are not supported",) + } + }; + let items = items + .into_iter() + .map(>::from) + .collect::>(); + Self { + vis: module.vis, + mod_token: module.mod_token, + ident: module.ident, + brace, + items, + } + } +} + +impl ItemMod { + pub fn type_substitutes(&self) -> HashMap { + self.items + .iter() + .filter_map(|item| { + if let Item::Subxt(SubxtItem::TypeSubstitute { + generated_type_path, + substitute_with: substitute_type, + }) = item + { + Some((generated_type_path.clone(), substitute_type.clone())) + } else { + None + } + }) + .collect() + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Item { + Rust(syn::Item), + Subxt(SubxtItem), +} + +impl From for Item { + fn from(item: syn::Item) -> Self { + if let syn::Item::Use(ref use_) = item { + let substitute_attrs = use_ + .attrs + .iter() + .map(|attr| { + let meta = attr.parse_meta().unwrap_or_else(|e| { + abort!(attr.span(), "Error parsing attribute: {}", e) + }); + let substitute_type_args = + ::from_meta(&meta) + .unwrap_or_else(|e| { + abort!(attr.span(), "Error parsing attribute meta: {}", e) + }); + substitute_type_args + }) + .collect::>(); + if substitute_attrs.len() > 1 { + abort!( + use_.attrs[0].span(), + "Duplicate `substitute_type` attributes" + ) + } + if let Some(attr) = substitute_attrs.iter().next() { + let use_path = &use_.tree; + let substitute_with: syn::TypePath = syn::parse_quote!( #use_path ); + let type_substitute = SubxtItem::TypeSubstitute { + generated_type_path: attr.substitute_type().to_string(), + substitute_with, + }; + Self::Subxt(type_substitute) + } else { + Self::Rust(item) + } + } else { + Self::Rust(item) + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum SubxtItem { + TypeSubstitute { + generated_type_path: String, + substitute_with: syn::TypePath, + }, +} + +mod attrs { + use darling::FromMeta; + + #[derive(Debug, FromMeta)] + #[darling(rename_all = "snake_case")] + pub enum Subxt { + SubstituteType(String), + } + + impl Subxt { + pub fn substitute_type(&self) -> String { + match self { + Self::SubstituteType(path) => path.clone(), + } + } + } +} diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs new file mode 100644 index 0000000000..7c65ecdf5a --- /dev/null +++ b/codegen/src/lib.rs @@ -0,0 +1,31 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +//! Library to generate an API for a Substrate runtime from its metadata. + +mod api; +mod derives; +mod ir; +mod struct_def; +mod types; + +pub use self::{ + api::{ + generate_runtime_api, + RuntimeGenerator, + }, + derives::GeneratedTypeDerives, +}; diff --git a/codegen/src/struct_def.rs b/codegen/src/struct_def.rs new file mode 100644 index 0000000000..1db6c02b13 --- /dev/null +++ b/codegen/src/struct_def.rs @@ -0,0 +1,142 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use super::GeneratedTypeDerives; +use crate::types::{ + TypeGenerator, + TypePath, +}; +use heck::CamelCase as _; +use proc_macro2::TokenStream as TokenStream2; +use proc_macro_error::abort_call_site; +use quote::{ + format_ident, + quote, +}; +use scale_info::form::PortableForm; + +#[derive(Debug)] +pub struct StructDef { + pub name: syn::Ident, + pub fields: StructDefFields, + pub field_visibility: Option, + pub derives: GeneratedTypeDerives, +} + +#[derive(Debug)] +pub enum StructDefFields { + Named(Vec<(syn::Ident, TypePath)>), + Unnamed(Vec), +} + +impl StructDef { + pub fn new( + ident: &str, + fields: &[scale_info::Field], + field_visibility: Option, + type_gen: &TypeGenerator, + ) -> Self { + let name = format_ident!("{}", ident.to_camel_case()); + let fields = fields + .iter() + .map(|field| { + let name = field.name().map(|f| format_ident!("{}", f)); + let ty = type_gen.resolve_type_path(field.ty().id(), &[]); + (name, ty) + }) + .collect::>(); + + let named = fields.iter().all(|(name, _)| name.is_some()); + let unnamed = fields.iter().all(|(name, _)| name.is_none()); + + let fields = if named { + StructDefFields::Named( + fields + .iter() + .map(|(name, field)| { + let name = name.as_ref().unwrap_or_else(|| { + abort_call_site!("All fields should have a name") + }); + (name.clone(), field.clone()) + }) + .collect(), + ) + } else if unnamed { + StructDefFields::Unnamed( + fields.iter().map(|(_, field)| field.clone()).collect(), + ) + } else { + abort_call_site!( + "Struct '{}': Fields should either be all named or all unnamed.", + name, + ) + }; + + let derives = type_gen.derives().clone(); + + Self { + name, + fields, + field_visibility, + derives, + } + } + + pub fn named_fields(&self) -> Option<&[(syn::Ident, TypePath)]> { + if let StructDefFields::Named(ref fields) = self.fields { + Some(fields) + } else { + None + } + } +} + +impl quote::ToTokens for StructDef { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let visibility = &self.field_visibility; + let derives = &self.derives; + tokens.extend(match self.fields { + StructDefFields::Named(ref named_fields) => { + let fields = named_fields.iter().map(|(name, ty)| { + let compact_attr = + ty.is_compact().then(|| quote!( #[codec(compact)] )); + quote! { #compact_attr #visibility #name: #ty } + }); + let name = &self.name; + quote! { + #derives + pub struct #name { + #( #fields ),* + } + } + } + StructDefFields::Unnamed(ref unnamed_fields) => { + let fields = unnamed_fields.iter().map(|ty| { + let compact_attr = + ty.is_compact().then(|| quote!( #[codec(compact)] )); + quote! { #compact_attr #visibility #ty } + }); + let name = &self.name; + quote! { + #derives + pub struct #name ( + #( #fields ),* + ); + } + } + }) + } +} diff --git a/codegen/src/types/mod.rs b/codegen/src/types/mod.rs new file mode 100644 index 0000000000..73b8b9d062 --- /dev/null +++ b/codegen/src/types/mod.rs @@ -0,0 +1,253 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +#[cfg(test)] +mod tests; +mod type_def; +mod type_path; + +use super::GeneratedTypeDerives; +use proc_macro2::{ + Ident, + Span, + TokenStream, +}; +use quote::{ + quote, + ToTokens, +}; +use scale_info::{ + form::PortableForm, + PortableRegistry, + Type, + TypeDef, +}; +use std::collections::{ + BTreeMap, + HashMap, +}; + +pub use self::{ + type_def::TypeDefGen, + type_path::{ + TypeParameter, + TypePath, + TypePathSubstitute, + TypePathType, + }, +}; + +/// Generate a Rust module containing all types defined in the supplied [`PortableRegistry`]. +#[derive(Debug)] +pub struct TypeGenerator<'a> { + /// The name of the module which will contain the generated types. + types_mod_ident: Ident, + /// Registry of type definitions to be transformed into Rust type definitions. + type_registry: &'a PortableRegistry, + /// User defined overrides for generated types. + type_substitutes: HashMap, + /// Set of derives with which to annotate generated types. + derives: GeneratedTypeDerives, +} + +impl<'a> TypeGenerator<'a> { + /// Construct a new [`TypeGenerator`]. + pub fn new( + type_registry: &'a PortableRegistry, + root_mod: &'static str, + type_substitutes: HashMap, + derives: GeneratedTypeDerives, + ) -> Self { + let root_mod_ident = Ident::new(root_mod, Span::call_site()); + Self { + types_mod_ident: root_mod_ident, + type_registry, + type_substitutes, + derives, + } + } + + /// Generate a module containing all types defined in the supplied type registry. + pub fn generate_types_mod(&'a self) -> Module<'a> { + let mut root_mod = + Module::new(self.types_mod_ident.clone(), self.types_mod_ident.clone()); + + for (id, ty) in self.type_registry.types().iter().enumerate() { + if ty.ty().path().namespace().is_empty() { + // prelude types e.g. Option/Result have no namespace, so we don't generate them + continue + } + self.insert_type( + ty.ty().clone(), + id as u32, + ty.ty().path().namespace().to_vec(), + &self.types_mod_ident, + &mut root_mod, + ) + } + + root_mod + } + + fn insert_type( + &'a self, + ty: Type, + id: u32, + path: Vec, + root_mod_ident: &Ident, + module: &mut Module<'a>, + ) { + let joined_path = path.join("::"); + if self.type_substitutes.contains_key(&joined_path) { + return + } + + let segment = path.first().expect("path has at least one segment"); + let mod_ident = Ident::new(segment, Span::call_site()); + + let child_mod = module + .children + .entry(mod_ident.clone()) + .or_insert_with(|| Module::new(mod_ident, root_mod_ident.clone())); + + if path.len() == 1 { + child_mod + .types + .insert(ty.path().clone(), TypeDefGen { ty, type_gen: self }); + } else { + self.insert_type(ty, id, path[1..].to_vec(), root_mod_ident, child_mod) + } + } + + /// # Panics + /// + /// If no type with the given id found in the type registry. + pub fn resolve_type(&self, id: u32) -> Type { + self.type_registry + .resolve(id) + .unwrap_or_else(|| panic!("No type with id {} found", id)) + .clone() + } + + /// # Panics + /// + /// If no type with the given id found in the type registry. + pub fn resolve_type_path( + &self, + id: u32, + parent_type_params: &[TypeParameter], + ) -> TypePath { + if let Some(parent_type_param) = parent_type_params + .iter() + .find(|tp| tp.concrete_type_id == id) + { + return TypePath::Parameter(parent_type_param.clone()) + } + + let mut ty = self.resolve_type(id); + + if ty.path().ident() == Some("Cow".to_string()) { + ty = self.resolve_type( + ty.type_params()[0] + .ty() + .expect("type parameters to Cow are not expected to be skipped; qed") + .id(), + ) + } + + let params_type_ids = match ty.type_def() { + TypeDef::Array(arr) => vec![arr.type_param().id()], + TypeDef::Sequence(seq) => vec![seq.type_param().id()], + TypeDef::Tuple(tuple) => tuple.fields().iter().map(|f| f.id()).collect(), + TypeDef::Compact(compact) => vec![compact.type_param().id()], + TypeDef::BitSequence(seq) => { + vec![seq.bit_order_type().id(), seq.bit_store_type().id()] + } + _ => { + ty.type_params() + .iter() + .filter_map(|f| f.ty().map(|f| f.id())) + .collect() + } + }; + + let params = params_type_ids + .iter() + .map(|tp| self.resolve_type_path(*tp, parent_type_params)) + .collect::>(); + + let joined_path = ty.path().segments().join("::"); + if let Some(substitute_type_path) = self.type_substitutes.get(&joined_path) { + TypePath::Substitute(TypePathSubstitute { + path: substitute_type_path.clone(), + params, + }) + } else { + TypePath::Type(TypePathType { + ty, + params, + root_mod_ident: self.types_mod_ident.clone(), + }) + } + } + + /// Returns the derives with which all generated type will be decorated. + pub fn derives(&self) -> &GeneratedTypeDerives { + &self.derives + } +} + +#[derive(Debug)] +pub struct Module<'a> { + name: Ident, + root_mod: Ident, + children: BTreeMap>, + types: BTreeMap, TypeDefGen<'a>>, +} + +impl<'a> ToTokens for Module<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let name = &self.name; + let root_mod = &self.root_mod; + let modules = self.children.values(); + let types = self.types.values().clone(); + + tokens.extend(quote! { + pub mod #name { + use super::#root_mod; + + #( #modules )* + #( #types )* + } + }) + } +} + +impl<'a> Module<'a> { + pub fn new(name: Ident, root_mod: Ident) -> Self { + Self { + name, + root_mod, + children: BTreeMap::new(), + types: BTreeMap::new(), + } + } + + /// Returns the module ident. + pub fn ident(&self) -> &Ident { + &self.name + } +} diff --git a/codegen/src/types/tests.rs b/codegen/src/types/tests.rs new file mode 100644 index 0000000000..c46403e6fa --- /dev/null +++ b/codegen/src/types/tests.rs @@ -0,0 +1,794 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use super::*; +use pretty_assertions::assert_eq; +use scale_info::{ + meta_type, + Registry, + TypeInfo, +}; + +const MOD_PATH: &'static [&'static str] = &["subxt_codegen", "types", "tests"]; + +fn get_mod<'a>(module: &'a Module, path_segs: &[&'static str]) -> Option<&'a Module<'a>> { + let (mod_name, rest) = path_segs.split_first()?; + let mod_ident = Ident::new(mod_name, Span::call_site()); + let module = module.children.get(&mod_ident)?; + if rest.is_empty() { + Some(module) + } else { + get_mod(module, rest) + } +} + +#[test] +fn generate_struct_with_primitives() { + #[allow(unused)] + #[derive(TypeInfo)] + struct S { + a: bool, + b: u32, + c: char, + } + + let mut registry = Registry::new(); + registry.register_type(&meta_type::()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct S { + pub a: ::core::primitive::bool, + pub b: ::core::primitive::u32, + pub c: ::core::primitive::char, + } + } + } + .to_string() + ) +} + +#[test] +fn generate_struct_with_a_struct_field() { + #[allow(unused)] + #[derive(TypeInfo)] + struct Parent { + a: bool, + b: Child, + } + + #[allow(unused)] + #[derive(TypeInfo)] + struct Child { + a: i32, + } + + let mut registry = Registry::new(); + registry.register_type(&meta_type::()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Child { + pub a: ::core::primitive::i32, + } + + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Parent { + pub a: ::core::primitive::bool, + pub b: root::subxt_codegen::types::tests::Child, + } + } + } + .to_string() + ) +} + +#[test] +fn generate_tuple_struct() { + #[allow(unused)] + #[derive(TypeInfo)] + struct Parent(bool, Child); + + #[allow(unused)] + #[derive(TypeInfo)] + struct Child(i32); + + let mut registry = Registry::new(); + registry.register_type(&meta_type::()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Child(pub ::core::primitive::i32,); + + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Parent(pub ::core::primitive::bool, pub root::subxt_codegen::types::tests::Child,); + } + } + .to_string() + ) +} + +#[test] +fn derive_compact_as_for_uint_wrapper_structs() { + #[allow(unused)] + #[derive(TypeInfo)] + struct Su8 { + a: u8, + } + #[allow(unused)] + #[derive(TypeInfo)] + struct TSu8(u8); + #[allow(unused)] + #[derive(TypeInfo)] + struct Su16 { + a: u16, + } + #[allow(unused)] + #[derive(TypeInfo)] + struct TSu16(u16); + #[allow(unused)] + #[derive(TypeInfo)] + struct Su32 { + a: u32, + } + #[allow(unused)] + #[derive(TypeInfo)] + struct TSu32(u32); + #[allow(unused)] + #[derive(TypeInfo)] + struct Su64 { + a: u64, + } + #[allow(unused)] + #[derive(TypeInfo)] + struct TSu64(u64); + #[allow(unused)] + #[derive(TypeInfo)] + struct Su128 { + a: u128, + } + #[allow(unused)] + #[derive(TypeInfo)] + struct TSu128(u128); + + let mut registry = Registry::new(); + registry.register_type(&meta_type::()); + registry.register_type(&meta_type::()); + registry.register_type(&meta_type::()); + registry.register_type(&meta_type::()); + registry.register_type(&meta_type::()); + registry.register_type(&meta_type::()); + registry.register_type(&meta_type::()); + registry.register_type(&meta_type::()); + registry.register_type(&meta_type::()); + registry.register_type(&meta_type::()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + + #[derive(::subxt::codec::CompactAs)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Su128 { pub a: ::core::primitive::u128, } + + #[derive(::subxt::codec::CompactAs)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Su16 { pub a: ::core::primitive::u16, } + + #[derive(::subxt::codec::CompactAs)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Su32 { pub a: ::core::primitive::u32, } + + #[derive(::subxt::codec::CompactAs)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Su64 { pub a: ::core::primitive::u64, } + + #[derive(::subxt::codec::CompactAs)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Su8 { pub a: ::core::primitive::u8, } + + #[derive(::subxt::codec::CompactAs)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct TSu128(pub ::core::primitive::u128,); + + #[derive(::subxt::codec::CompactAs)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct TSu16(pub ::core::primitive::u16,); + + #[derive(::subxt::codec::CompactAs)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct TSu32(pub ::core::primitive::u32,); + + #[derive(::subxt::codec::CompactAs)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct TSu64(pub ::core::primitive::u64,); + + #[derive(::subxt::codec::CompactAs)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct TSu8(pub ::core::primitive::u8,); + } + } + .to_string() + ) +} + +#[test] +fn generate_enum() { + #[allow(unused)] + #[derive(TypeInfo)] + enum E { + A, + B(bool), + C { a: u32 }, + } + + let mut registry = Registry::new(); + registry.register_type(&meta_type::()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub enum E { + A, + B (::core::primitive::bool,), + C { a: ::core::primitive::u32, }, + } + } + } + .to_string() + ) +} + +#[test] +fn generate_array_field() { + #[allow(unused)] + #[derive(TypeInfo)] + struct S { + a: [u8; 32], + } + + let mut registry = Registry::new(); + registry.register_type(&meta_type::()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct S { + pub a: [::core::primitive::u8; 32usize], + } + } + } + .to_string() + ) +} + +#[test] +fn option_fields() { + #[allow(unused)] + #[derive(TypeInfo)] + struct S { + a: Option, + b: Option, + } + + let mut registry = Registry::new(); + registry.register_type(&meta_type::()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct S { + pub a: ::core::option::Option<::core::primitive::bool>, + pub b: ::core::option::Option<::core::primitive::u32>, + } + } + } + .to_string() + ) +} + +#[test] +fn box_fields_struct() { + use std::boxed::Box; + + #[allow(unused)] + #[derive(TypeInfo)] + struct S { + a: std::boxed::Box, + b: Box, + } + + let mut registry = Registry::new(); + registry.register_type(&meta_type::()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct S { + pub a: ::std::boxed::Box<::core::primitive::bool>, + pub b: ::std::boxed::Box<::core::primitive::u32>, + } + } + } + .to_string() + ) +} + +#[test] +fn box_fields_enum() { + use std::boxed::Box; + + #[allow(unused)] + #[derive(TypeInfo)] + enum E { + A(Box), + B { a: Box }, + } + + let mut registry = Registry::new(); + registry.register_type(&meta_type::()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub enum E { + A(::std::boxed::Box<::core::primitive::bool>,), + B { a: ::std::boxed::Box<::core::primitive::u32>, }, + } + } + } + .to_string() + ) +} + +#[test] +fn range_fields() { + #[allow(unused)] + #[derive(TypeInfo)] + struct S { + a: core::ops::Range, + b: core::ops::RangeInclusive, + } + + let mut registry = Registry::new(); + registry.register_type(&meta_type::()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct S { + pub a: ::core::ops::Range<::core::primitive::u32>, + pub b: ::core::ops::RangeInclusive<::core::primitive::u32>, + } + } + } + .to_string() + ) +} + +#[test] +fn generics() { + #[allow(unused)] + #[derive(TypeInfo)] + struct Foo { + a: T, + } + + #[allow(unused)] + #[derive(TypeInfo)] + struct Bar { + b: Foo, + c: Foo, + } + + let mut registry = Registry::new(); + registry.register_type(&meta_type::()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Bar { + pub b: root::subxt_codegen::types::tests::Foo<::core::primitive::u32>, + pub c: root::subxt_codegen::types::tests::Foo<::core::primitive::u8>, + } + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Foo<_0> { + pub a: _0, + } + } + } + .to_string() + ) +} + +#[test] +fn generics_nested() { + #[allow(unused)] + #[derive(TypeInfo)] + struct Foo { + a: T, + b: Option<(T, U)>, + } + + #[allow(unused)] + #[derive(TypeInfo)] + struct Bar { + b: Foo, + } + + let mut registry = Registry::new(); + registry.register_type(&meta_type::>()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Bar<_0> { + pub b: root::subxt_codegen::types::tests::Foo<_0, ::core::primitive::u32>, + } + + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Foo<_0, _1> { + pub a: _0, + pub b: ::core::option::Option<(_0, _1,)>, + } + } + } + .to_string() + ) +} + +#[test] +fn generate_bitvec() { + use bitvec::{ + order::{ + Lsb0, + Msb0, + }, + vec::BitVec, + }; + + #[allow(unused)] + #[derive(TypeInfo)] + struct S { + lsb: BitVec, + msb: BitVec, + } + + let mut registry = Registry::new(); + registry.register_type(&meta_type::()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct S { + pub lsb: ::subxt::bitvec::vec::BitVec, + pub msb: ::subxt::bitvec::vec::BitVec, + } + } + } + .to_string() + ) +} + +#[test] +fn generics_with_alias_adds_phantom_data_marker() { + trait Trait { + type Type; + } + + impl Trait for bool { + type Type = u32; + } + + type Foo = ::Type; + type Bar = (::Type, ::Type); + + #[allow(unused)] + #[derive(TypeInfo)] + struct NamedFields { + b: Foo, + } + + #[allow(unused)] + #[derive(TypeInfo)] + struct UnnamedFields(Bar); + + let mut registry = Registry::new(); + registry.register_type(&meta_type::>()); + registry.register_type(&meta_type::>()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + #[derive(::subxt::codec::CompactAs)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct NamedFields<_0> { + pub b: ::core::primitive::u32, + #[codec(skip)] pub __subxt_unused_type_params: ::core::marker::PhantomData<_0>, + } + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct UnnamedFields<_0, _1> ( + pub (::core::primitive::u32, ::core::primitive::u32,), + #[codec(skip)] pub ::core::marker::PhantomData<(_0, _1)>, + ); + } + } + .to_string() + ) +} + +#[test] +fn modules() { + mod modules { + pub mod a { + #[allow(unused)] + #[derive(scale_info::TypeInfo)] + pub struct Foo {} + + pub mod b { + #[allow(unused)] + #[derive(scale_info::TypeInfo)] + pub struct Bar { + a: super::Foo, + } + } + } + + pub mod c { + #[allow(unused)] + #[derive(scale_info::TypeInfo)] + pub struct Foo { + a: super::a::b::Bar, + } + } + } + + let mut registry = Registry::new(); + registry.register_type(&meta_type::()); + let portable_types: PortableRegistry = registry.into(); + + let type_gen = TypeGenerator::new( + &portable_types, + "root", + Default::default(), + Default::default(), + ); + let types = type_gen.generate_types_mod(); + let tests_mod = get_mod(&types, MOD_PATH).unwrap(); + + assert_eq!( + tests_mod.into_token_stream().to_string(), + quote! { + pub mod tests { + use super::root; + pub mod modules { + use super::root; + pub mod a { + use super::root; + + pub mod b { + use super::root; + + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Bar { + pub a: root::subxt_codegen::types::tests::modules::a::Foo, + } + } + + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Foo {} + } + + pub mod c { + use super::root; + + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode)] + pub struct Foo { + pub a: root::subxt_codegen::types::tests::modules::a::b::Bar, + } + } + } + } + } + .to_string() + ) +} diff --git a/codegen/src/types/type_def.rs b/codegen/src/types/type_def.rs new file mode 100644 index 0000000000..12071296f0 --- /dev/null +++ b/codegen/src/types/type_def.rs @@ -0,0 +1,325 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use super::{ + TypeGenerator, + TypeParameter, + TypePath, +}; +use proc_macro2::TokenStream; +use quote::{ + format_ident, + quote, +}; +use scale_info::{ + form::PortableForm, + Field, + Type, + TypeDef, + TypeDefPrimitive, +}; +use std::collections::HashSet; +use syn::parse_quote; + +/// Generates a Rust `struct` or `enum` definition based on the supplied [`scale-info::Type`]. +/// +/// Field type paths are resolved via the `TypeGenerator`, which contains the registry of all +/// generated types in the module. +#[derive(Debug)] +pub struct TypeDefGen<'a> { + /// The type generation context, allows resolving of type paths for the fields of the + /// generated type. + pub(super) type_gen: &'a TypeGenerator<'a>, + /// Contains the definition of the type to be generated. + pub(super) ty: Type, +} + +impl<'a> quote::ToTokens for TypeDefGen<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let type_params = self + .ty + .type_params() + .iter() + .enumerate() + .filter_map(|(i, tp)| { + match tp.ty() { + Some(ty) => { + let tp_name = format_ident!("_{}", i); + Some(TypeParameter { + concrete_type_id: ty.id(), + name: tp_name, + }) + } + None => None, + } + }) + .collect::>(); + + let type_name = self.ty.path().ident().map(|ident| { + let type_params = if !type_params.is_empty() { + quote! { < #( #type_params ),* > } + } else { + quote! {} + }; + let ty = format_ident!("{}", ident); + let path = parse_quote! { #ty #type_params}; + syn::Type::Path(path) + }); + + let derives = self.type_gen.derives(); + + match self.ty.type_def() { + TypeDef::Composite(composite) => { + let type_name = type_name.expect("structs should have a name"); + let (fields, _) = + self.composite_fields(composite.fields(), &type_params, true); + let derive_as_compact = if composite.fields().len() == 1 { + // any single field wrapper struct with a concrete unsigned int type can derive + // CompactAs. + let field = &composite.fields()[0]; + if !self + .ty + .type_params() + .iter() + .any(|tp| Some(tp.name()) == field.type_name()) + { + let ty = self.type_gen.resolve_type(field.ty().id()); + if matches!( + ty.type_def(), + TypeDef::Primitive( + TypeDefPrimitive::U8 + | TypeDefPrimitive::U16 + | TypeDefPrimitive::U32 + | TypeDefPrimitive::U64 + | TypeDefPrimitive::U128 + ) + ) { + Some(quote!( #[derive(::subxt::codec::CompactAs)] )) + } else { + None + } + } else { + None + } + } else { + None + }; + + let ty_toks = quote! { + #derive_as_compact + #derives + pub struct #type_name #fields + }; + tokens.extend(ty_toks); + } + TypeDef::Variant(variant) => { + let type_name = type_name.expect("variants should have a name"); + let mut variants = Vec::new(); + let mut used_type_params = HashSet::new(); + let type_params_set: HashSet<_> = type_params.iter().cloned().collect(); + + for v in variant.variants() { + let variant_name = format_ident!("{}", v.name()); + let (fields, unused_type_params) = if v.fields().is_empty() { + let unused = type_params_set.iter().cloned().collect::>(); + (quote! {}, unused) + } else { + self.composite_fields(v.fields(), &type_params, false) + }; + variants.push(quote! { #variant_name #fields }); + let unused_params_set = unused_type_params.iter().cloned().collect(); + let used_params = type_params_set.difference(&unused_params_set); + + for used_param in used_params { + used_type_params.insert(used_param.clone()); + } + } + + let unused_type_params = type_params_set + .difference(&used_type_params) + .cloned() + .collect::>(); + if !unused_type_params.is_empty() { + let phantom = Self::phantom_data(&unused_type_params); + variants.push(quote! { + __Ignore(#phantom) + }) + } + + let ty_toks = quote! { + #derives + pub enum #type_name { + #( #variants, )* + } + }; + tokens.extend(ty_toks); + } + _ => (), // all built-in types should already be in scope + } + } +} + +impl<'a> TypeDefGen<'a> { + fn composite_fields( + &self, + fields: &'a [Field], + type_params: &'a [TypeParameter], + is_struct: bool, + ) -> (TokenStream, Vec) { + let named = fields.iter().all(|f| f.name().is_some()); + let unnamed = fields.iter().all(|f| f.name().is_none()); + + fn unused_type_params<'a>( + type_params: &'a [TypeParameter], + types: impl Iterator, + ) -> Vec { + let mut used_type_params = HashSet::new(); + for ty in types { + ty.parent_type_params(&mut used_type_params) + } + let type_params_set: HashSet<_> = type_params.iter().cloned().collect(); + let mut unused = type_params_set + .difference(&used_type_params) + .cloned() + .collect::>(); + unused.sort(); + unused + } + + let ty_toks = |ty_name: &str, ty_path: &TypePath| { + if ty_name.contains("Box<") { + quote! { ::std::boxed::Box<#ty_path> } + } else { + quote! { #ty_path } + } + }; + + if named { + let fields = fields + .iter() + .map(|field| { + let name = format_ident!( + "{}", + field.name().expect("named field without a name") + ); + let ty = self + .type_gen + .resolve_type_path(field.ty().id(), type_params); + (name, ty, field.type_name()) + }) + .collect::>(); + + let mut fields_tokens = fields + .iter() + .map(|(name, ty, ty_name)| { + let field_type = match ty_name { + Some(ty_name) => { + let ty = ty_toks(ty_name, ty); + if is_struct { + quote! ( pub #name: #ty ) + } else { + quote! ( #name: #ty ) + } + } + None => { + quote! ( #name: #ty ) + } + }; + if ty.is_compact() { + quote!( #[codec(compact)] #field_type ) + } else { + quote!( #field_type ) + } + }) + .collect::>(); + + let unused_params = + unused_type_params(type_params, fields.iter().map(|(_, ty, _)| ty)); + + if is_struct && !unused_params.is_empty() { + let phantom = Self::phantom_data(&unused_params); + fields_tokens.push(quote! { + #[codec(skip)] pub __subxt_unused_type_params: #phantom + }) + } + + let fields = quote! { + { + #( #fields_tokens, )* + } + }; + (fields, unused_params) + } else if unnamed { + let type_paths = fields + .iter() + .map(|field| { + let ty = self + .type_gen + .resolve_type_path(field.ty().id(), type_params); + (ty, field.type_name()) + }) + .collect::>(); + let mut fields_tokens = type_paths + .iter() + .map(|(ty, ty_name)| { + match ty_name { + Some(ty_name) => { + let ty = ty_toks(ty_name, ty); + if is_struct { + quote! { pub #ty } + } else { + quote! { #ty } + } + } + None => { + quote! { #ty } + } + } + }) + .collect::>(); + + let unused_params = + unused_type_params(type_params, type_paths.iter().map(|(ty, _)| ty)); + + if is_struct && !unused_params.is_empty() { + let phantom_data = Self::phantom_data(&unused_params); + fields_tokens.push(quote! { #[codec(skip)] pub #phantom_data }) + } + + let fields = quote! { ( #( #fields_tokens, )* ) }; + let fields_tokens = if is_struct { + // add a semicolon for tuple structs + quote! { #fields; } + } else { + fields + }; + + (fields_tokens, unused_params) + } else { + panic!("Fields must be either all named or all unnamed") + } + } + + fn phantom_data(params: &[TypeParameter]) -> TokenStream { + let params = if params.len() == 1 { + let param = ¶ms[0]; + quote! { #param } + } else { + quote! { ( #( #params ), * ) } + }; + quote! ( ::core::marker::PhantomData<#params> ) + } +} diff --git a/codegen/src/types/type_path.rs b/codegen/src/types/type_path.rs new file mode 100644 index 0000000000..186619cbf0 --- /dev/null +++ b/codegen/src/types/type_path.rs @@ -0,0 +1,253 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use proc_macro2::{ + Ident, + TokenStream, +}; +use quote::{ + format_ident, + quote, +}; +use scale_info::{ + form::PortableForm, + Type, + TypeDef, + TypeDefPrimitive, +}; +use std::collections::HashSet; +use syn::parse_quote; + +#[derive(Clone, Debug)] +pub enum TypePath { + Parameter(TypeParameter), + Type(TypePathType), + Substitute(TypePathSubstitute), +} + +impl quote::ToTokens for TypePath { + fn to_tokens(&self, tokens: &mut TokenStream) { + let syn_type = self.to_syn_type(); + syn_type.to_tokens(tokens) + } +} + +impl TypePath { + pub(crate) fn to_syn_type(&self) -> syn::Type { + match self { + TypePath::Parameter(ty_param) => syn::Type::Path(parse_quote! { #ty_param }), + TypePath::Type(ty) => ty.to_syn_type(), + TypePath::Substitute(sub) => sub.to_syn_type(), + } + } + + pub(crate) fn is_compact(&self) -> bool { + matches!(self, Self::Type(ty) if ty.is_compact()) + } + + /// Returns the type parameters in a path which are inherited from the containing type. + /// + /// # Example + /// + /// ```rust + /// struct S { + /// a: Vec>, // the parent type param here is `T` + /// } + /// ``` + pub fn parent_type_params(&self, acc: &mut HashSet) { + match self { + Self::Parameter(type_parameter) => { + acc.insert(type_parameter.clone()); + } + Self::Type(type_path) => type_path.parent_type_params(acc), + Self::Substitute(sub) => sub.parent_type_params(acc), + } + } +} + +#[derive(Clone, Debug)] +pub struct TypePathType { + pub(super) ty: Type, + pub(super) params: Vec, + pub(super) root_mod_ident: Ident, +} + +impl TypePathType { + pub(crate) fn is_compact(&self) -> bool { + matches!(self.ty.type_def(), TypeDef::Compact(_)) + } + + fn to_syn_type(&self) -> syn::Type { + let params = &self.params; + match self.ty.type_def() { + TypeDef::Composite(_) | TypeDef::Variant(_) => { + let path_segments = self.ty.path().segments(); + + let ty_path: syn::TypePath = match path_segments { + [] => panic!("Type has no ident"), + [ident] => { + // paths to prelude types + match ident.as_str() { + "Option" => parse_quote!(::core::option::Option), + "Result" => parse_quote!(::core::result::Result), + "Cow" => parse_quote!(::std::borrow::Cow), + "BTreeMap" => parse_quote!(::std::collections::BTreeMap), + "BTreeSet" => parse_quote!(::std::collections::BTreeSet), + "Range" => parse_quote!(::core::ops::Range), + "RangeInclusive" => parse_quote!(::core::ops::RangeInclusive), + ident => panic!("Unknown prelude type '{}'", ident), + } + } + _ => { + // paths to generated types in the root types module + let mut ty_path = path_segments + .iter() + .map(|s| syn::PathSegment::from(format_ident!("{}", s))) + .collect::>(); + ty_path.insert( + 0, + syn::PathSegment::from(self.root_mod_ident.clone()), + ); + parse_quote!( #ty_path ) + } + }; + + let params = &self.params; + let path = if params.is_empty() { + parse_quote! { #ty_path } + } else { + parse_quote! { #ty_path< #( #params ),* > } + }; + syn::Type::Path(path) + } + TypeDef::Sequence(_) => { + let type_param = &self.params[0]; + let type_path = parse_quote! { ::std::vec::Vec<#type_param> }; + syn::Type::Path(type_path) + } + TypeDef::Array(array) => { + let array_type = &self.params[0]; + let array_len = array.len() as usize; + let array = parse_quote! { [#array_type; #array_len] }; + syn::Type::Array(array) + } + TypeDef::Tuple(_) => { + let tuple = parse_quote! { (#( # params, )* ) }; + syn::Type::Tuple(tuple) + } + TypeDef::Primitive(primitive) => { + let path = match primitive { + TypeDefPrimitive::Bool => parse_quote!(::core::primitive::bool), + TypeDefPrimitive::Char => parse_quote!(::core::primitive::char), + TypeDefPrimitive::Str => parse_quote!(::std::string::String), + TypeDefPrimitive::U8 => parse_quote!(::core::primitive::u8), + TypeDefPrimitive::U16 => parse_quote!(::core::primitive::u16), + TypeDefPrimitive::U32 => parse_quote!(::core::primitive::u32), + TypeDefPrimitive::U64 => parse_quote!(::core::primitive::u64), + TypeDefPrimitive::U128 => parse_quote!(::core::primitive::u128), + TypeDefPrimitive::U256 => unimplemented!("not a rust primitive"), + TypeDefPrimitive::I8 => parse_quote!(::core::primitive::i8), + TypeDefPrimitive::I16 => parse_quote!(::core::primitive::i16), + TypeDefPrimitive::I32 => parse_quote!(::core::primitive::i32), + TypeDefPrimitive::I64 => parse_quote!(::core::primitive::i64), + TypeDefPrimitive::I128 => parse_quote!(::core::primitive::i128), + TypeDefPrimitive::I256 => unimplemented!("not a rust primitive"), + }; + syn::Type::Path(path) + } + TypeDef::Compact(_) => { + let compact_type = &self.params[0]; + syn::Type::Path(parse_quote! ( #compact_type )) + } + TypeDef::BitSequence(_) => { + let bit_order_type = &self.params[0]; + let bit_store_type = &self.params[1]; + + let type_path = parse_quote! { ::subxt::bitvec::vec::BitVec<#bit_order_type, #bit_store_type> }; + + syn::Type::Path(type_path) + } + } + } + + /// Returns the type parameters in a path which are inherited from the containing type. + /// + /// # Example + /// + /// ```rust + /// struct S { + /// a: Vec>, // the parent type param here is `T` + /// } + /// ``` + fn parent_type_params(&self, acc: &mut HashSet) { + for p in &self.params { + p.parent_type_params(acc); + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct TypeParameter { + pub(super) concrete_type_id: u32, + pub(super) name: proc_macro2::Ident, +} + +impl quote::ToTokens for TypeParameter { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.name.to_tokens(tokens) + } +} + +#[derive(Clone, Debug)] +pub struct TypePathSubstitute { + pub(super) path: syn::TypePath, + pub(super) params: Vec, +} + +impl quote::ToTokens for TypePathSubstitute { + fn to_tokens(&self, tokens: &mut TokenStream) { + if self.params.is_empty() { + self.path.to_tokens(tokens) + } else { + let substitute_path = &self.path; + let params = &self.params; + tokens.extend(quote! { + #substitute_path< #( #params ),* > + }) + } + } +} + +impl TypePathSubstitute { + fn parent_type_params(&self, acc: &mut HashSet) { + for p in &self.params { + p.parent_type_params(acc); + } + } + + fn to_syn_type(&self) -> syn::Type { + if self.params.is_empty() { + syn::Type::Path(self.path.clone()) + } else { + let substitute_path = &self.path; + let params = &self.params; + parse_quote! ( #substitute_path< #( #params ),* > ) + } + } +} diff --git a/examples/custom_type_derives.rs b/examples/custom_type_derives.rs new file mode 100644 index 0000000000..ee3d25788f --- /dev/null +++ b/examples/custom_type_derives.rs @@ -0,0 +1,30 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +#[subxt::subxt( + runtime_metadata_path = "examples/polkadot_metadata.scale", + generated_type_derives = "Clone, Debug" +)] +pub mod polkadot {} + +use polkadot::runtime_types::frame_support::PalletId; + +#[async_std::main] +async fn main() -> Result<(), Box> { + let pallet_id = PalletId([1u8; 8]); + let _ = ::clone(&pallet_id); + Ok(()) +} diff --git a/examples/fetch_all_accounts.rs b/examples/fetch_all_accounts.rs index dde0094a61..d01cfb483c 100644 --- a/examples/fetch_all_accounts.rs +++ b/examples/fetch_all_accounts.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,22 +12,34 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . -use substrate_subxt::{ - system::AccountStoreExt, - ClientBuilder, - DefaultNodeRuntime, -}; +//! To run this example, a local polkadot node should be running. +//! +//! E.g. +//! ```bash +//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.11/polkadot" --output /usr/local/bin/polkadot --location +//! polkadot --dev --tmp +//! ``` + +use subxt::ClientBuilder; + +#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")] +pub mod polkadot {} #[async_std::main] async fn main() -> Result<(), Box> { env_logger::init(); - let client = ClientBuilder::::new().build().await?; - let mut iter = client.account_iter(None).await?; + let api = ClientBuilder::new() + .build() + .await? + .to_runtime_api::>(); + + let mut iter = api.storage().system().account_iter(None).await?; + while let Some((key, account)) = iter.next().await? { - println!("{:?}: {}", key, account.data.free); + println!("{}: {}", hex::encode(key), account.data.free); } Ok(()) } diff --git a/examples/fetch_remote.rs b/examples/fetch_remote.rs index cda160ed3e..a54004551d 100644 --- a/examples/fetch_remote.rs +++ b/examples/fetch_remote.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,25 +12,30 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . -use substrate_subxt::{ - ClientBuilder, - KusamaRuntime, -}; +use subxt::ClientBuilder; + +#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")] +pub mod polkadot {} #[async_std::main] async fn main() -> Result<(), Box> { env_logger::init(); - let client = ClientBuilder::::new() - .set_url("wss://kusama-rpc.polkadot.io") + let api = ClientBuilder::new() + .set_url("wss://rpc.polkadot.io") .build() - .await?; + .await? + .to_runtime_api::>(); let block_number = 1; - let block_hash = client.block_hash(Some(block_number.into())).await?; + let block_hash = api + .client + .rpc() + .block_hash(Some(block_number.into())) + .await?; if let Some(hash) = block_hash { println!("Block hash for block number {}: {}", block_number, hash); diff --git a/examples/kusama_balance_transfer.rs b/examples/polkadot_balance_transfer.rs similarity index 57% rename from examples/kusama_balance_transfer.rs rename to examples/polkadot_balance_transfer.rs index aa9b8a6b1a..ee3535173c 100644 --- a/examples/kusama_balance_transfer.rs +++ b/examples/polkadot_balance_transfer.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,16 +12,25 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . + +//! To run this example, a local polkadot node should be running. +//! +//! E.g. +//! ```bash +//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.11/polkadot" --output /usr/local/bin/polkadot --location +//! polkadot --dev --tmp +//! ``` use sp_keyring::AccountKeyring; -use substrate_subxt::{ - balances::*, +use subxt::{ ClientBuilder, - KusamaRuntime, PairSigner, }; +#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")] +pub mod polkadot {} + #[async_std::main] async fn main() -> Result<(), Box> { env_logger::init(); @@ -29,8 +38,16 @@ async fn main() -> Result<(), Box> { let signer = PairSigner::new(AccountKeyring::Alice.pair()); let dest = AccountKeyring::Bob.to_account_id().into(); - let client = ClientBuilder::::new().build().await?; - let hash = client.transfer(&signer, &dest, 10_000).await?; + let api = ClientBuilder::new() + .build() + .await? + .to_runtime_api::>(); + let hash = api + .tx() + .balances() + .transfer(dest, 10_000) + .sign_and_submit(&signer) + .await?; println!("Balance transfer extrinsic submitted: {}", hash); diff --git a/examples/polkadot_metadata.scale b/examples/polkadot_metadata.scale new file mode 100644 index 0000000000..678aba3161 Binary files /dev/null and b/examples/polkadot_metadata.scale differ diff --git a/examples/submit_and_watch.rs b/examples/submit_and_watch.rs index a71d12e880..8bc1d8d83a 100644 --- a/examples/submit_and_watch.rs +++ b/examples/submit_and_watch.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,19 +12,25 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . + +//! To run this example, a local polkadot node should be running. +//! +//! E.g. +//! ```bash +//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.11/polkadot" --output /usr/local/bin/polkadot --location +//! polkadot --dev --tmp +//! ``` use sp_keyring::AccountKeyring; -use substrate_subxt::{ - balances::{ - TransferCallExt, - TransferEventExt, - }, +use subxt::{ ClientBuilder, - DefaultNodeRuntime, PairSigner, }; +#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")] +pub mod polkadot {} + #[async_std::main] async fn main() -> Result<(), Box> { env_logger::init(); @@ -32,11 +38,19 @@ async fn main() -> Result<(), Box> { let signer = PairSigner::new(AccountKeyring::Alice.pair()); let dest = AccountKeyring::Bob.to_account_id().into(); - let client = ClientBuilder::::new().build().await?; - let result = client.transfer_and_watch(&signer, &dest, 10_000).await?; + let api = ClientBuilder::new() + .build() + .await? + .to_runtime_api::>(); + let result = api + .tx() + .balances() + .transfer(dest, 10_000) + .sign_and_submit_then_watch(&signer) + .await?; - if let Some(event) = result.transfer()? { - println!("Balance transfer success: value: {:?}", event.amount); + if let Some(event) = result.find_event::()? { + println!("Balance transfer success: value: {:?}", event.2); } else { println!("Failed to find Balances::Transfer Event"); } diff --git a/examples/transfer_subscribe.rs b/examples/transfer_subscribe.rs index 11487133ac..9b14fbcb1d 100644 --- a/examples/transfer_subscribe.rs +++ b/examples/transfer_subscribe.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,21 +12,26 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . + +//! To run this example, a local polkadot node should be running. +//! +//! E.g. +//! ```bash +//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.11/polkadot" --output /usr/local/bin/polkadot --location +//! polkadot --dev --tmp +//! ``` use sp_keyring::AccountKeyring; -use substrate_subxt::{ - balances::{ - TransferCallExt, - TransferEvent, - }, - sp_core::Decode, +use subxt::{ ClientBuilder, - DefaultNodeRuntime, EventSubscription, PairSigner, }; +#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")] +pub mod polkadot {} + #[async_std::main] async fn main() -> Result<(), Box> { env_logger::init(); @@ -34,16 +39,28 @@ async fn main() -> Result<(), Box> { let signer = PairSigner::new(AccountKeyring::Alice.pair()); let dest = AccountKeyring::Bob.to_account_id().into(); - let client = ClientBuilder::::new().build().await?; - let sub = client.subscribe_events().await?; - let decoder = client.events_decoder(); - let mut sub = EventSubscription::::new(sub, decoder); - sub.filter_event::>(); - client.transfer(&signer, &dest, 10_000).await?; + let api = ClientBuilder::new() + .build() + .await? + .to_runtime_api::>(); + + let sub = api.client.rpc().subscribe_events().await?; + let decoder = api.client.events_decoder(); + let mut sub = EventSubscription::::new(sub, decoder); + sub.filter_event::(); + + api.tx() + .balances() + .transfer(dest, 10_000) + .sign_and_submit(&signer) + .await?; + let raw = sub.next().await.unwrap().unwrap(); - let event = TransferEvent::::decode(&mut &raw.data[..]); + let event = ::decode( + &mut &raw.data[..], + ); if let Ok(e) = event { - println!("Balance transfer success: value: {:?}", e.amount); + println!("Balance transfer success: value: {:?}", e.2); } else { println!("Failed to subscribe to Balances::Transfer Event"); } diff --git a/macro/Cargo.toml b/macro/Cargo.toml new file mode 100644 index 0000000000..57a18f65b6 --- /dev/null +++ b/macro/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "subxt-macro" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +autotests = false + +license = "GPL-3.0" +repository = "https://github.com/paritytech/subxt" +documentation = "https://docs.rs/subxt" +homepage = "https://www.parity.io/" +description = "Generate types and helpers for interacting with Substrate runtimes." + +[lib] +proc-macro = true + +[dependencies] +async-trait = "0.1.49" +codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full"] } +darling = "0.13.0" +frame-metadata = "14.0" +heck = "0.3.2" +proc-macro2 = "1.0.24" +proc-macro-crate = "0.1.5" +proc-macro-error = "1.0.4" +quote = "1.0.8" +syn = "1.0.58" +scale-info = "1.0.0" + +subxt-codegen = { version = "0.1.0", path = "../codegen" } + +[dev-dependencies] +pretty_assertions = "0.6.1" +subxt = { path = ".." } +trybuild = "1.0.38" + +sp-keyring = { package = "sp-keyring", git = "https://github.com/paritytech/substrate/" } diff --git a/macro/src/lib.rs b/macro/src/lib.rs new file mode 100644 index 0000000000..41706634cf --- /dev/null +++ b/macro/src/lib.rs @@ -0,0 +1,55 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +extern crate proc_macro; + +use darling::FromMeta; +use proc_macro::TokenStream; +use proc_macro_error::proc_macro_error; +use syn::{ + parse_macro_input, + punctuated::Punctuated, +}; + +#[derive(Debug, FromMeta)] +struct RuntimeMetadataArgs { + runtime_metadata_path: String, + #[darling(default)] + generated_type_derives: Option, +} + +#[derive(Debug, FromMeta)] +struct GeneratedTypeDerives(Punctuated); + +#[proc_macro_attribute] +#[proc_macro_error] +pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { + let attr_args = parse_macro_input!(args as syn::AttributeArgs); + let item_mod = parse_macro_input!(input as syn::ItemMod); + + let args = match RuntimeMetadataArgs::from_list(&attr_args) { + Ok(v) => v, + Err(e) => return TokenStream::from(e.write_errors()), + }; + + let root = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into()); + let root_path = std::path::Path::new(&root); + let path = root_path.join(args.runtime_metadata_path); + + let generated_type_derives = args.generated_type_derives.map(|derives| derives.0); + + subxt_codegen::generate_runtime_api(item_mod, &path, generated_type_derives).into() +} diff --git a/proc-macro/Cargo.toml b/proc-macro/Cargo.toml deleted file mode 100644 index 3ee914577b..0000000000 --- a/proc-macro/Cargo.toml +++ /dev/null @@ -1,44 +0,0 @@ -[package] -name = "substrate-subxt-proc-macro" -version = "0.15.0" -authors = [ - "David Craven ", - "Parity Technologies ", -] -edition = "2018" -autotests = false - -license = "GPL-3.0" -repository = "https://github.com/paritytech/substrate-subxt" -documentation = "https://docs.rs/substrate-subxt" -homepage = "https://www.parity.io/" -description = "Derive calls, events, storage and tests for interacting Substrate modules with substrate-subxt" - -[lib] -proc-macro = true - -[dependencies] -async-trait = "0.1.49" -heck = "0.3.2" -proc-macro2 = "1.0.24" -proc-macro-crate = "0.1.5" -proc-macro-error = "1.0.4" -quote = "1.0.8" -syn = "1.0.58" -synstructure = "0.12.4" - -[dev-dependencies] -async-std = { version = "1.8.0", features = ["attributes"] } -codec = { package = "parity-scale-codec", version = "2.0.0", features = [ - "derive", -] } -env_logger = "0.8.2" -pretty_assertions = "0.6.1" -substrate-subxt = { path = ".." } -trybuild = "1.0.38" - -sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.10" } - -[[test]] -name = "balances" -path = "tests/balances.rs" diff --git a/proc-macro/src/call.rs b/proc-macro/src/call.rs deleted file mode 100644 index c4aebbf8e3..0000000000 --- a/proc-macro/src/call.rs +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -use crate::utils; -use heck::{ - CamelCase, - SnakeCase, -}; -use proc_macro2::TokenStream; -use quote::{ - format_ident, - quote, -}; -use synstructure::Structure; - -pub fn call(s: Structure) -> TokenStream { - let subxt = utils::use_crate("substrate-subxt"); - let ident = &s.ast().ident; - let generics = &s.ast().generics; - let params = utils::type_params(generics); - let module = utils::module_name(generics); - let call_name = utils::ident_to_name(ident, "Call").to_snake_case(); - let bindings = utils::bindings(&s); - let fields = utils::fields(&bindings); - let marker = utils::marker_field(&fields).unwrap_or_else(|| format_ident!("_")); - let filtered_fields = utils::filter_fields(&fields, &marker); - let args = utils::fields_to_args(&filtered_fields); - let build_struct = utils::build_struct(ident, &fields); - let call_trait = format_ident!("{}CallExt", call_name.to_camel_case()); - let call = format_ident!("{}", call_name); - let call_and_watch = format_ident!("{}_and_watch", call_name); - - quote! { - impl#generics #subxt::Call for #ident<#(#params),*> { - const MODULE: &'static str = MODULE; - const FUNCTION: &'static str = #call_name; - } - - /// Call extension trait. - #[async_trait::async_trait] - pub trait #call_trait { - /// Create and submit an extrinsic. - async fn #call<'a>( - &'a self, - signer: &'a (dyn #subxt::Signer + Send + Sync), - #args - ) -> Result; - - /// Create, submit and watch an extrinsic. - async fn #call_and_watch<'a>( - &'a self, - signer: &'a (dyn #subxt::Signer + Send + Sync), - #args - ) -> Result<#subxt::ExtrinsicSuccess, #subxt::Error>; - } - - #[async_trait::async_trait] - impl #call_trait for #subxt::Client - where - <>::Extra as #subxt::SignedExtension>::AdditionalSigned: Send + Sync, - { - async fn #call<'a>( - &'a self, - signer: &'a (dyn #subxt::Signer + Send + Sync), - #args - ) -> Result { - let #marker = core::marker::PhantomData::; - self.submit(#build_struct, signer).await - } - - async fn #call_and_watch<'a>( - &'a self, - signer: &'a (dyn #subxt::Signer + Send + Sync), - #args - ) -> Result<#subxt::ExtrinsicSuccess, #subxt::Error> { - let #marker = core::marker::PhantomData::; - self.watch(#build_struct, signer).await - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_transfer_call() { - let input = quote! { - #[derive(Call, Encode)] - pub struct TransferCall<'a, T: Balances> { - pub to: &'a ::Address, - #[codec(compact)] - pub amount: T::Balance, - } - }; - let expected = quote! { - impl<'a, T: Balances> substrate_subxt::Call for TransferCall<'a, T> { - const MODULE: &'static str = MODULE; - const FUNCTION: &'static str = "transfer"; - } - - /// Call extension trait. - #[async_trait::async_trait] - pub trait TransferCallExt { - /// Create and submit an extrinsic. - async fn transfer<'a>( - &'a self, - signer: &'a (dyn substrate_subxt::Signer + Send + Sync), - to: &'a ::Address, - amount: T::Balance, - ) -> Result; - - /// Create, submit and watch an extrinsic. - async fn transfer_and_watch<'a>( - &'a self, - signer: &'a (dyn substrate_subxt::Signer + Send + Sync), - to: &'a ::Address, - amount: T::Balance, - ) -> Result, substrate_subxt::Error>; - } - - #[async_trait::async_trait] - impl TransferCallExt for substrate_subxt::Client - where - <>::Extra as substrate_subxt::SignedExtension>::AdditionalSigned: Send + Sync, - { - async fn transfer<'a>( - &'a self, - signer: &'a (dyn substrate_subxt::Signer + Send + Sync), - to: &'a ::Address, - amount: T::Balance, - ) -> Result { - let _ = core::marker::PhantomData::; - self.submit(TransferCall { to, amount, }, signer).await - } - - async fn transfer_and_watch<'a>( - &'a self, - signer: &'a (dyn substrate_subxt::Signer + Send + Sync), - to: &'a ::Address, - amount: T::Balance, - ) -> Result, substrate_subxt::Error> { - let _ = core::marker::PhantomData::; - self.watch(TransferCall { to, amount, }, signer).await - } - } - }; - let derive_input = syn::parse2(input).unwrap(); - let s = Structure::new(&derive_input); - let result = call(s); - utils::assert_proc_macro(result, expected); - } -} diff --git a/proc-macro/src/event.rs b/proc-macro/src/event.rs deleted file mode 100644 index e653442b94..0000000000 --- a/proc-macro/src/event.rs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -use crate::utils; -use heck::{ - CamelCase, - SnakeCase, -}; -use proc_macro2::TokenStream; -use quote::{ - format_ident, - quote, -}; -use synstructure::Structure; - -pub fn event(s: Structure) -> TokenStream { - let subxt = utils::use_crate("substrate-subxt"); - let codec = utils::use_crate("parity-scale-codec"); - let ident = &s.ast().ident; - let generics = &s.ast().generics; - let module = utils::module_name(generics); - let event_name = utils::ident_to_name(ident, "Event").to_camel_case(); - let event = format_ident!("{}", event_name.to_snake_case()); - let event_trait = format_ident!("{}EventExt", event_name); - - quote! { - impl #subxt::Event for #ident { - const MODULE: &'static str = MODULE; - const EVENT: &'static str = #event_name; - } - - /// Event extension trait. - pub trait #event_trait { - /// Retrieves the event. - fn #event(&self) -> Result>, #codec::Error>; - } - - impl #event_trait for #subxt::ExtrinsicSuccess { - fn #event(&self) -> Result>, #codec::Error> { - self.find_event() - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_transfer_event() { - let input = quote! { - #[derive(Debug, Decode, Eq, Event, PartialEq)] - pub struct TransferEvent { - pub from: ::AccountId, - pub to: ::AccountId, - pub amount: T::Balance, - } - }; - let expected = quote! { - impl substrate_subxt::Event for TransferEvent { - const MODULE: &'static str = MODULE; - const EVENT: &'static str = "Transfer"; - } - - /// Event extension trait. - pub trait TransferEventExt { - /// Retrieves the event. - fn transfer(&self) -> Result>, codec::Error>; - } - - impl TransferEventExt for substrate_subxt::ExtrinsicSuccess { - fn transfer(&self) -> Result>, codec::Error> { - self.find_event() - } - } - }; - let derive_input = syn::parse2(input).unwrap(); - let s = Structure::new(&derive_input); - let result = event(s); - utils::assert_proc_macro(result, expected); - } -} diff --git a/proc-macro/src/lib.rs b/proc-macro/src/lib.rs deleted file mode 100644 index fee2ded501..0000000000 --- a/proc-macro/src/lib.rs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -extern crate proc_macro; - -mod call; -mod event; -mod module; -mod store; -mod test; -mod utils; - -use proc_macro::TokenStream; -use proc_macro_error::proc_macro_error; -use synstructure::{ - decl_derive, - Structure, -}; - -/// Register type sizes for [EventsDecoder](struct.EventsDecoder.html) and set the `MODULE`. -/// -/// The `module` macro registers the type sizes of the associated types of a trait so that [EventsDecoder](struct.EventsDecoder.html) -/// can decode events of that type when received from Substrate. It also sets the `MODULE` constant -/// to the name of the trait (must match the name of the Substrate pallet) that enables the [Call](), [Event]() and [Store]() macros to work. -/// -/// If you do not want an associated type to be registered, likely because you never expect it as part of a response payload to be decoded, use `#[module(ignore)]` on the type. -/// -/// Example: -/// -/// ```ignore -/// #[module] -/// pub trait Herd: Husbandry { -/// type Hooves: HoofCounter; -/// type Wool: WoollyAnimal; -/// #[module(ignore)] -/// type Digestion: EnergyProducer + std::fmt::Debug; -/// } -/// ``` -/// -/// The above will produce the following code: -/// -/// ```ignore -/// pub trait Herd: Husbandry { -/// type Hooves: HoofCounter; -/// type Wool: WoollyAnimal; -/// #[module(ignore)] -/// type Digestion: EnergyProducer + std::fmt::Debug; -/// } -/// -/// const MODULE: &str = "Herd"; -/// -/// // `EventTypeRegistry` extension trait. -/// pub trait HerdEventTypeRegistry { -/// // Registers this modules types. -/// fn with_herd(&mut self); -/// } -/// -/// impl EventTypeRegistry for -/// substrate_subxt::EventTypeRegistry -/// { -/// fn with_herd(&mut self) { -/// self.register_type_size::("Hooves"); -/// self.register_type_size::("Wool"); -/// } -/// } -/// ``` -/// -/// The following type sizes are registered by default: `bool, u8, u32, AccountId, AccountIndex, -/// AuthorityId, AuthorityIndex, AuthorityWeight, BlockNumber, DispatchInfo, Hash, Kind, -/// MemberCount, PhantomData, PropIndex, ProposalIndex, ReferendumIndex, SessionIndex, VoteThreshold` -#[proc_macro_attribute] -#[proc_macro_error] -pub fn module(args: TokenStream, input: TokenStream) -> TokenStream { - module::module(args.into(), input.into()).into() -} - -decl_derive!( - [Call] => - /// Derive macro that implements [substrate_subxt::Call](../substrate_subxt/trait.Call.html) for your struct - /// and defines&implements the calls as an extension trait. - /// - /// Use the `Call` derive macro in tandem with the [#module](../substrate_subxt/attr.module.html) macro to extend - /// your struct to enable calls to substrate and to decode events. The struct maps to the corresponding Substrate runtime call, e.g.: - /// - /// ```ignore - /// decl_module! { - /// /* … */ - /// pub fn fun_stuff(origin, something: Vec) -> DispatchResult { /* … */ } - /// /* … */ - /// } - ///``` - /// - /// Implements [substrate_subxt::Call](../substrate_subxt/trait.Call.html) and adds an extension trait that - /// provides two methods named as your struct. - /// - /// Example: - /// ```rust,ignore - /// pub struct MyRuntime; - /// - /// impl System for MyRuntime { /* … */ } - /// impl Balances for MyRuntime { /* … */ } - /// - /// #[module] - /// pub trait MyTrait: System + Balances {} - /// - /// #[derive(Call)] - /// pub struct FunStuffCall { - /// /// Runtime marker. - /// pub _runtime: PhantomData, - /// /// The argument passed to the call.. - /// pub something: Vec, - /// } - /// ``` - /// - /// When building a [Client](../substrate_subxt/struct.Client.html) parameterised to `MyRuntime`, you have access to - /// two new methods: `fun_stuff()` and `fun_stuff_and_watch()` by way of the derived `FunStuffExt` - /// trait. The `_and_watch` variant makes the call and waits for the result. The fields of the - /// input struct become arguments to the calls (ignoring the marker field). - /// - /// Under the hood the implementation calls [submit()](../substrate_subxt/struct.Client.html#method.submit) and - /// [watch()](../substrate_subxt/struct.Client.html#method.watch) respectively. - /// - /// *N.B.* You must use the `#[derive(Call)]` macro with `#[module]` in the same module or you will get errors - /// about undefined method with a name starting with `with_`. - - #[proc_macro_error] call -); -fn call(s: Structure) -> TokenStream { - call::call(s).into() -} - -decl_derive!([Event] => #[proc_macro_error] event); -fn event(s: Structure) -> TokenStream { - event::event(s).into() -} - -decl_derive!([Store, attributes(store)] => #[proc_macro_error] store); -fn store(s: Structure) -> TokenStream { - store::store(s).into() -} - -#[proc_macro] -#[proc_macro_error] -pub fn subxt_test(input: TokenStream) -> TokenStream { - test::test(input.into()).into() -} diff --git a/proc-macro/src/module.rs b/proc-macro/src/module.rs deleted file mode 100644 index cc14d5b3b4..0000000000 --- a/proc-macro/src/module.rs +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -use crate::utils; -use heck::SnakeCase; -use proc_macro2::TokenStream; -use proc_macro_error::abort; -use quote::{ - format_ident, - quote, -}; -use syn::parse::{ - Parse, - ParseStream, -}; - -mod kw { - use syn::custom_keyword; - - custom_keyword!(ignore); -} - -#[derive(Debug)] -enum ModuleAttr { - Ignore(kw::ignore), -} - -impl Parse for ModuleAttr { - fn parse(input: ParseStream) -> syn::Result { - Ok(Self::Ignore(input.parse()?)) - } -} - -type ModuleAttrs = utils::Attrs; - -fn ignore(attrs: &[syn::Attribute]) -> bool { - for attr in attrs { - if let Some(ident) = attr.path.get_ident() { - if ident == "module" { - let attrs: ModuleAttrs = syn::parse2(attr.tokens.clone()) - .map_err(|err| abort!("{}", err)) - .unwrap(); - if !attrs.attrs.is_empty() { - return true - } - } - } - } - false -} - -fn event_type_registry_trait_name(module: &syn::Ident) -> syn::Ident { - format_ident!("{}EventTypeRegistry", module.to_string()) -} - -fn with_module_ident(module: &syn::Ident) -> syn::Ident { - format_ident!("with_{}", module.to_string().to_snake_case()) -} - -type EventAttr = utils::UniAttr; -type EventAliasAttr = utils::UniAttr>; - -/// Parses the event type definition macros within #[module] -/// -/// It supports two ways to define the associated event type: -/// -/// ```ignore -/// #[module] -/// trait Pallet: System { -/// #![event_type(SomeType)] -/// #![event_alias(TypeNameAlias = SomeType)] -/// #![event_alias(SomeOtherAlias = TypeWithAssociatedTypes)] -/// } -/// ``` -fn parse_event_type_attr(attr: &syn::Attribute) -> Option<(String, syn::Type)> { - let ident = utils::path_to_ident(&attr.path); - if ident == "event_type" { - let attrs: EventAttr = syn::parse2(attr.tokens.clone()) - .map_err(|err| abort!("{}", err)) - .unwrap(); - let ty = attrs.attr; - let ident_str = quote!(#ty).to_string(); - Some((ident_str, ty)) - } else if ident == "event_alias" { - let attrs: EventAliasAttr = syn::parse2(attr.tokens.clone()) - .map_err(|err| abort!("{}", err)) - .unwrap(); - let ty = attrs.attr.value; - let ident_str = attrs.attr.key.to_string(); - Some((ident_str, ty)) - } else { - None - } -} - -/// Attribute macro that registers the type sizes used by the module; also sets the `MODULE` constant. -pub fn module(_args: TokenStream, tokens: TokenStream) -> TokenStream { - let input: Result = syn::parse2(tokens.clone()); - let mut input = if let Ok(input) = input { - input - } else { - // handle #[module(ignore)] by just returning the tokens - return tokens - }; - - // Parse the inner attributes `event_type` and `event_alias` and remove them from the macro - // outputs. - let (other_attrs, event_types): (Vec<_>, Vec<_>) = input - .attrs - .iter() - .cloned() - .partition(|attr| parse_event_type_attr(attr).is_none()); - input.attrs = other_attrs; - - let subxt = utils::use_crate("substrate-subxt"); - let module = &input.ident; - let module_name = module.to_string(); - let module_events_type_registry = event_type_registry_trait_name(module); - let with_module = with_module_ident(module); - - let associated_types = input.items.iter().filter_map(|item| { - if let syn::TraitItem::Type(ty) = item { - if ignore(&ty.attrs) { - return None - } - let ident = &ty.ident; - let ident_str = ident.to_string(); - Some(quote! { - self.register_type_size::(#ident_str); - }) - } else { - None - } - }); - let types = event_types.iter().map(|attr| { - let (ident_str, ty) = parse_event_type_attr(&attr).unwrap(); - quote! { - self.register_type_size::<#ty>(#ident_str); - } - }); - - quote! { - #input - - const MODULE: &str = #module_name; - - /// `EventTypeRegistry` extension trait. - pub trait #module_events_type_registry { - /// Registers this modules types. - fn #with_module(&mut self); - } - - impl #module_events_type_registry for - #subxt::EventTypeRegistry - { - fn #with_module(&mut self) { - #(#associated_types)* - #(#types)* - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_balance_module() { - let attr = quote!(#[module]); - let input = quote! { - pub trait Balances: System { - type Balance: frame_support::Parameter - + sp_runtime::traits::Member - + sp_runtime::traits::AtLeast32Bit - + codec::Codec - + Default - + Copy - + sp_runtime::traits::MaybeSerialize - + std::fmt::Debug - + From<::BlockNumber>; - } - }; - let expected = quote! { - pub trait Balances: System { - type Balance: frame_support::Parameter - + sp_runtime::traits::Member - + sp_runtime::traits::AtLeast32Bit - + codec::Codec - + Default - + Copy - + sp_runtime::traits::MaybeSerialize - + std::fmt::Debug - + From< ::BlockNumber>; - } - - const MODULE: &str = "Balances"; - - /// `EventTypeRegistry` extension trait. - pub trait BalancesEventTypeRegistry { - /// Registers this modules types. - fn with_balances(&mut self); - } - - impl BalancesEventTypeRegistry for - substrate_subxt::EventTypeRegistry - { - fn with_balances(&mut self) { - self.register_type_size::("Balance"); - } - } - }; - - let result = module(attr, input); - utils::assert_proc_macro(result, expected); - } - - #[test] - fn test_herd() { - let attr = quote!(#[module]); - let input = quote! { - pub trait Herd: Husbandry { - type Hoves: u8; - type Wool: bool; - #[module(ignore)] - type Digestion: EnergyProducer + fmt::Debug; - } - }; - let expected = quote! { - pub trait Herd: Husbandry { - type Hoves: u8; - type Wool: bool; - #[module(ignore)] - type Digestion: EnergyProducer + fmt::Debug; - } - - const MODULE: &str = "Herd"; - - /// `EventTypeRegistry` extension trait. - pub trait HerdEventTypeRegistry { - /// Registers this modules types. - fn with_herd(&mut self); - } - - impl HerdEventTypeRegistry for - substrate_subxt::EventTypeRegistry - { - fn with_herd(&mut self) { - self.register_type_size::("Hoves"); - self.register_type_size::("Wool"); - } - } - }; - - let result = module(attr, input); - utils::assert_proc_macro(result, expected); - } -} diff --git a/proc-macro/src/store.rs b/proc-macro/src/store.rs deleted file mode 100644 index 2a4d0cffb3..0000000000 --- a/proc-macro/src/store.rs +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -use crate::utils; -use heck::{ - CamelCase, - SnakeCase, -}; -use proc_macro2::TokenStream; -use proc_macro_error::abort; -use quote::{ - format_ident, - quote, -}; -use syn::parse::{ - Parse, - ParseStream, -}; -use synstructure::Structure; - -mod kw { - use syn::custom_keyword; - - custom_keyword!(returns); -} - -#[derive(Debug)] -enum StoreAttr { - Returns(utils::Attr), -} - -impl Parse for StoreAttr { - fn parse(input: ParseStream) -> syn::Result { - Ok(Self::Returns(input.parse()?)) - } -} - -type StoreAttrs = utils::Attrs; - -fn parse_returns_attr(attr: &syn::Attribute) -> Option<(syn::Type, syn::Type, bool)> { - let attrs: StoreAttrs = syn::parse2(attr.tokens.clone()) - .map_err(|err| abort!("{}", err)) - .unwrap(); - attrs.attrs.into_iter().next().map(|attr| { - let StoreAttr::Returns(attr) = attr; - let ty = attr.value; - if let Some(inner) = utils::parse_option(&ty) { - (ty, inner, false) - } else { - (ty.clone(), ty, true) - } - }) -} - -pub fn store(s: Structure) -> TokenStream { - let subxt = utils::use_crate("substrate-subxt"); - let ident = &s.ast().ident; - let generics = &s.ast().generics; - let params = utils::type_params(generics); - let module = utils::module_name(generics); - let store_name = utils::ident_to_name(ident, "Store").to_camel_case(); - let store = format_ident!("{}", store_name.to_snake_case()); - let store_iter = format_ident!("{}_iter", store_name.to_snake_case()); - let store_trait = format_ident!("{}StoreExt", store_name); - let bindings = utils::bindings(&s); - let fields = utils::fields(&bindings); - let marker = utils::marker_field(&fields).unwrap_or_else(|| format_ident!("_")); - let filtered_fields = utils::filter_fields(&fields, &marker); - let args = utils::fields_to_args(&filtered_fields); - let build_struct = utils::build_struct(ident, &fields); - let (ret, store_ret, uses_default) = bindings - .iter() - .filter_map(|bi| bi.ast().attrs.iter().filter_map(parse_returns_attr).next()) - .next() - .unwrap_or_else(|| { - abort!(ident, "#[store(returns = ..)] needs to be specified.") - }); - let fetch = if uses_default { - quote!(fetch_or_default) - } else { - quote!(fetch) - }; - let store_ty = format_ident!( - "{}", - match filtered_fields.len() { - 0 => "plain", - 1 => "map", - 2 => "double_map", - _ => { - abort!( - ident, - "Expected 0-2 fields but found {}", - filtered_fields.len() - ); - } - } - ); - let keys = filtered_fields - .iter() - .map(|(field, _)| quote!(&self.#field)); - let key_iter = quote!(#subxt::KeyIter>); - - quote! { - impl#generics #subxt::Store for #ident<#(#params),*> { - const MODULE: &'static str = MODULE; - const FIELD: &'static str = #store_name; - type Returns = #store_ret; - - fn prefix( - metadata: &#subxt::Metadata, - ) -> Result<#subxt::sp_core::storage::StorageKey, #subxt::MetadataError> { - Ok(metadata - .module(Self::MODULE)? - .storage(Self::FIELD)? - .prefix()) - } - - fn key( - &self, - metadata: &#subxt::Metadata, - ) -> Result<#subxt::sp_core::storage::StorageKey, #subxt::MetadataError> { - Ok(metadata - .module(Self::MODULE)? - .storage(Self::FIELD)? - .#store_ty()? - .key(#(#keys,)*)) - } - } - - /// Store extension trait. - #[async_trait::async_trait] - pub trait #store_trait { - /// Retrieve the store element. - async fn #store<'a>( - &'a self, - #args - hash: Option, - ) -> Result<#ret, #subxt::Error>; - - /// Iterate over the store element. - async fn #store_iter<'a>( - &'a self, - hash: Option, - ) -> Result<#key_iter, #subxt::Error>; - } - - #[async_trait::async_trait] - impl #store_trait for #subxt::Client { - async fn #store<'a>( - &'a self, - #args - hash: Option, - ) -> Result<#ret, #subxt::Error> { - let #marker = core::marker::PhantomData::; - self.#fetch(&#build_struct, hash).await - } - - async fn #store_iter<'a>( - &'a self, - hash: Option, - ) -> Result<#key_iter, #subxt::Error> { - self.iter(hash).await - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_account_store() { - let input = quote! { - #[derive(Encode, Store)] - pub struct AccountStore<'a, T: Balances> { - #[store(returns = AccountData)] - account_id: &'a ::AccountId, - } - }; - let expected = quote! { - impl<'a, T: Balances> substrate_subxt::Store for AccountStore<'a, T> { - const MODULE: &'static str = MODULE; - const FIELD: &'static str = "Account"; - type Returns = AccountData; - - fn prefix( - metadata: &substrate_subxt::Metadata, - ) -> Result { - Ok(metadata - .module(Self::MODULE)? - .storage(Self::FIELD)? - .prefix()) - } - - fn key( - &self, - metadata: &substrate_subxt::Metadata, - ) -> Result { - Ok(metadata - .module(Self::MODULE)? - .storage(Self::FIELD)? - .map()? - .key(&self.account_id,)) - } - } - - /// Store extension trait. - #[async_trait::async_trait] - pub trait AccountStoreExt { - /// Retrieve the store element. - async fn account<'a>( - &'a self, - account_id: &'a ::AccountId, - hash: Option, - ) -> Result, substrate_subxt::Error>; - /// Iterate over the store element. - async fn account_iter<'a>( - &'a self, - hash: Option, - ) -> Result>, substrate_subxt::Error>; - } - - #[async_trait::async_trait] - impl AccountStoreExt for substrate_subxt::Client { - async fn account<'a>( - &'a self, - account_id: &'a ::AccountId, - hash: Option, - ) -> Result, substrate_subxt::Error> - { - let _ = core::marker::PhantomData::; - self.fetch_or_default(&AccountStore { account_id, }, hash).await - } - - async fn account_iter<'a>( - &'a self, - hash: Option, - ) -> Result>, substrate_subxt::Error> { - self.iter(hash).await - } - } - }; - let derive_input = syn::parse2(input).unwrap(); - let s = Structure::new(&derive_input); - let result = store(s); - utils::assert_proc_macro(result, expected); - } -} diff --git a/proc-macro/src/test.rs b/proc-macro/src/test.rs deleted file mode 100644 index e0b6d78808..0000000000 --- a/proc-macro/src/test.rs +++ /dev/null @@ -1,505 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -use crate::utils; -use proc_macro2::TokenStream; -use proc_macro_error::abort; -use quote::{ - format_ident, - quote, -}; -use syn::{ - parse::{ - Parse, - ParseStream, - }, - punctuated::Punctuated, -}; - -mod kw { - use syn::custom_keyword; - - custom_keyword!(name); - custom_keyword!(runtime); - custom_keyword!(account); - custom_keyword!(prelude); - custom_keyword!(step); - custom_keyword!(state); - custom_keyword!(call); - custom_keyword!(event); - custom_keyword!(assert); -} - -#[derive(Debug)] -struct Item { - key: K, - colon: syn::token::Colon, - value: V, -} - -impl Parse for Item { - fn parse(input: ParseStream) -> syn::Result { - Ok(Self { - key: input.parse()?, - colon: input.parse()?, - value: input.parse()?, - }) - } -} - -#[derive(Debug)] -struct Items { - brace: syn::token::Brace, - items: Punctuated, -} - -impl Parse for Items { - fn parse(input: ParseStream) -> syn::Result { - let content; - let brace = syn::braced!(content in input); - let items = content.parse_terminated(I::parse)?; - Ok(Self { brace, items }) - } -} - -type ItemTest = Items; - -#[derive(Debug)] -enum TestItem { - Name(Item), - Runtime(Item>), - Account(Item), - State(Item), - Prelude(Item), - Step(Item), -} - -impl Parse for TestItem { - fn parse(input: ParseStream) -> syn::Result { - if input.peek(kw::name) { - Ok(TestItem::Name(input.parse()?)) - } else if input.peek(kw::runtime) { - Ok(TestItem::Runtime(input.parse()?)) - } else if input.peek(kw::account) { - Ok(TestItem::Account(input.parse()?)) - } else if input.peek(kw::state) { - Ok(TestItem::State(input.parse()?)) - } else if input.peek(kw::prelude) { - Ok(TestItem::Prelude(input.parse()?)) - } else { - Ok(TestItem::Step(input.parse()?)) - } - } -} - -type ItemStep = Items; - -#[derive(Debug)] -enum StepItem { - State(Item), - Call(Item), - Event(Item), - Assert(Item), -} - -impl Parse for StepItem { - fn parse(input: ParseStream) -> syn::Result { - if input.peek(kw::state) { - Ok(StepItem::State(input.parse()?)) - } else if input.peek(kw::call) { - Ok(StepItem::Call(input.parse()?)) - } else if input.peek(kw::event) { - Ok(StepItem::Event(input.parse()?)) - } else { - Ok(StepItem::Assert(input.parse()?)) - } - } -} - -type ItemState = Items; -type StateItem = Item; - -struct Test { - name: syn::Ident, - runtime: Box, - account: syn::Ident, - state: Option, - prelude: Option, - steps: Vec, -} - -impl From for Test { - fn from(test: ItemTest) -> Self { - let mut name = None; - let mut runtime = None; - let mut account = None; - let mut state = None; - let mut prelude = None; - let mut steps = vec![]; - - let span = test.brace.span; - for test_item in test.items { - match test_item { - TestItem::Name(item) => { - name = Some(item.value); - } - TestItem::Runtime(item) => { - runtime = Some(item.value); - } - TestItem::Account(item) => { - account = Some(item.value); - } - TestItem::State(item) => { - state = Some(item.value.into()); - } - TestItem::Prelude(item) => { - prelude = Some(item.value); - } - TestItem::Step(item) => { - steps.push(item.value.into()); - } - } - } - let subxt = utils::use_crate("substrate-subxt"); - let runtime = runtime - .unwrap_or_else(|| syn::parse2(quote!(#subxt::DefaultNodeRuntime)).unwrap()); - Self { - name: name.unwrap_or_else(|| abort!(span, "No name specified")), - account: account.unwrap_or_else(|| format_ident!("Alice")), - runtime, - state, - prelude, - steps, - } - } -} - -impl Test { - fn into_tokens(self) -> TokenStream { - let subxt = utils::use_crate("substrate-subxt"); - let sp_keyring = utils::use_crate("sp-keyring"); - let env_logger = utils::opt_crate("env_logger") - .map(|env_logger| quote!(#env_logger::try_init().ok();)); - let Test { - name, - runtime, - account, - state, - prelude, - steps, - } = self; - let prelude = prelude.map(|block| block.stmts).unwrap_or_default(); - let step = steps - .into_iter() - .map(|step| step.into_tokens(state.as_ref())); - quote! { - #[async_std::test] - #[ignore] - async fn #name() { - #env_logger - let client = #subxt::ClientBuilder::<#runtime>::new() - .build().await.unwrap(); - let signer = #subxt::PairSigner::new(#sp_keyring::AccountKeyring::#account.pair()); - - #[allow(unused)] - let alice = #sp_keyring::AccountKeyring::Alice.to_account_id(); - #[allow(unused)] - let bob = #sp_keyring::AccountKeyring::Bob.to_account_id(); - #[allow(unused)] - let charlie = #sp_keyring::AccountKeyring::Charlie.to_account_id(); - #[allow(unused)] - let dave = #sp_keyring::AccountKeyring::Dave.to_account_id(); - #[allow(unused)] - let eve = #sp_keyring::AccountKeyring::Eve.to_account_id(); - #[allow(unused)] - let ferdie = #sp_keyring::AccountKeyring::Ferdie.to_account_id(); - - #(#prelude)* - - #({ - #step - })* - } - } - } -} - -struct Step { - state: Option, - call: syn::Expr, - event_name: Vec, - event: Vec, - assert: Option, -} - -impl From for Step { - fn from(step: ItemStep) -> Self { - let mut state = None; - let mut call = None; - let mut event_name = vec![]; - let mut event = vec![]; - let mut assert = None; - - let span = step.brace.span; - for step_item in step.items { - match step_item { - StepItem::State(item) => { - state = Some(item.value.into()); - } - StepItem::Call(item) => { - call = Some(item.value); - } - StepItem::Event(item) => { - event_name.push(struct_name(&item.value)); - event.push(item.value); - } - StepItem::Assert(item) => { - assert = Some(item.value); - } - } - } - - Self { - state, - call: call.unwrap_or_else(|| abort!(span, "Step requires a call.")), - event_name, - event, - assert, - } - } -} - -impl Step { - fn into_tokens(self, test_state: Option<&State>) -> TokenStream { - let Step { - state, - call, - event_name, - event, - assert, - } = self; - let (pre, post) = state - .as_ref() - .or(test_state) - .map(|state| { - let State { - state_name, - state, - state_param, - } = state; - let state_struct = quote! { - struct State<#(#state_param),*> { - #(#state_name: #state_param,)* - } - }; - let build_struct = quote! { - #( - let #state_name = client.fetch_or_default(#state, None).await.unwrap(); - )* - State { #(#state_name),* } - }; - let pre = quote! { - #state_struct - let pre = { - #build_struct - }; - }; - let post = quote! { - let post = { - #build_struct - }; - }; - (pre, post) - }) - .unwrap_or_default(); - let expect_event = event_name.iter().map(|event| { - format!( - "failed to find event {}", - utils::path_to_ident(event).to_string() - ) - }); - let assert = assert.map(|block| block.stmts).unwrap_or_default(); - quote! { - #pre - - #[allow(unused)] - let result = client - .watch(#call, &signer) - .await - .unwrap(); - - #( - let event = result.find_event::<#event_name<_>>().unwrap().expect(#expect_event); - assert_eq!(event, #event); - )* - - #post - - #(#assert)* - } - } -} - -struct State { - state_name: Vec, - state: Vec, - state_param: Vec, -} - -impl From for State { - fn from(item_state: ItemState) -> Self { - let mut state_name = vec![]; - let mut state = vec![]; - for item in item_state.items { - state_name.push(item.key); - state.push(item.value); - } - let state_param = (b'A'..b'Z') - .map(|c| format_ident!("{}", (c as char).to_string())) - .take(state_name.len()) - .collect::>(); - Self { - state_name, - state, - state_param, - } - } -} - -fn struct_name(expr: &syn::Expr) -> syn::Path { - if let syn::Expr::Struct(syn::ExprStruct { path, .. }) = expr { - path.clone() - } else { - abort!(expr, "Expected a struct"); - } -} - -pub fn test(input: TokenStream) -> TokenStream { - let item_test: ItemTest = - syn::parse2(input).map_err(|err| abort!("{}", err)).unwrap(); - Test::from(item_test).into_tokens() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn transfer_test_case() { - let input = quote! {{ - name: test_transfer_balance, - runtime: KusamaRuntime, - account: Alice, - step: { - state: { - alice: AccountStore { account_id: &alice }, - bob: AccountStore { account_id: &bob }, - }, - call: TransferCall { - to: &bob, - amount: 10_000, - }, - event: TransferEvent { - from: alice.clone(), - to: bob.clone(), - amount: 10_000, - }, - assert: { - assert_eq!(pre.alice.free, post.alice.free - 10_000); - assert_eq!(pre.bob.free, post.bob.free + 10_000); - }, - }, - }}; - let expected = quote! { - #[async_std::test] - #[ignore] - async fn test_transfer_balance() { - env_logger::try_init().ok(); - let client = substrate_subxt::ClientBuilder::::new().build().await.unwrap(); - let signer = substrate_subxt::PairSigner::new(sp_keyring::AccountKeyring::Alice.pair()); - #[allow(unused)] - let alice = sp_keyring::AccountKeyring::Alice.to_account_id(); - #[allow(unused)] - let bob = sp_keyring::AccountKeyring::Bob.to_account_id(); - #[allow(unused)] - let charlie = sp_keyring::AccountKeyring::Charlie.to_account_id(); - #[allow(unused)] - let dave = sp_keyring::AccountKeyring::Dave.to_account_id(); - #[allow(unused)] - let eve = sp_keyring::AccountKeyring::Eve.to_account_id(); - #[allow(unused)] - let ferdie = sp_keyring::AccountKeyring::Ferdie.to_account_id(); - - { - struct State { - alice: A, - bob: B, - } - - let pre = { - let alice = client - .fetch_or_default(AccountStore { account_id: &alice }, None) - .await - .unwrap(); - let bob = client - .fetch_or_default(AccountStore { account_id: &bob }, None) - .await - .unwrap(); - State { alice, bob } - }; - - #[allow(unused)] - let result = client - .watch(TransferCall { - to: &bob, - amount: 10_000, - }, &signer) - .await - .unwrap(); - - let event = result.find_event::>() - .unwrap() - .expect("failed to find event TransferEvent"); - assert_eq!( - event, - TransferEvent { - from: alice.clone(), - to: bob.clone(), - amount: 10_000, - } - ); - - let post = { - let alice = client - .fetch_or_default(AccountStore { account_id: &alice }, None) - .await - .unwrap(); - let bob = client - .fetch_or_default(AccountStore { account_id: &bob }, None) - .await - .unwrap(); - State { alice, bob } - }; - - assert_eq!(pre.alice.free, post.alice.free - 10_000); - assert_eq!(pre.bob.free, post.bob.free + 10_000); - } - } - }; - let result = test(input); - utils::assert_proc_macro(result, expected); - } -} diff --git a/proc-macro/src/utils.rs b/proc-macro/src/utils.rs deleted file mode 100644 index ba644436a7..0000000000 --- a/proc-macro/src/utils.rs +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -use proc_macro2::{ - Span, - TokenStream, -}; -use quote::{ - format_ident, - quote, -}; -use syn::{ - parse::{ - Parse, - ParseStream, - }, - punctuated::Punctuated, -}; -use synstructure::{ - BindingInfo, - Structure, -}; - -pub fn use_crate(name: &str) -> syn::Ident { - opt_crate(name).unwrap_or_else(|| syn::Ident::new("crate", Span::call_site())) -} - -pub fn opt_crate(name: &str) -> Option { - proc_macro_crate::crate_name(name) - .ok() - .map(|krate| syn::Ident::new(&krate, Span::call_site())) -} - -pub fn bindings<'a>(s: &'a Structure) -> Vec<&'a BindingInfo<'a>> { - let mut bindings = vec![]; - for variant in s.variants() { - for binding in variant.bindings() { - bindings.push(binding); - } - } - bindings -} - -type Field = (syn::Ident, syn::Type); - -pub fn fields(bindings: &[&BindingInfo<'_>]) -> Vec { - bindings - .iter() - .enumerate() - .map(|(i, bi)| { - ( - bi.ast() - .ident - .clone() - .unwrap_or_else(|| format_ident!("key{}", i)), - bi.ast().ty.clone(), - ) - }) - .collect() -} - -pub fn marker_field(fields: &[Field]) -> Option { - fields - .iter() - .filter_map(|(field, ty)| { - if quote!(#ty).to_string() == quote!(PhantomData).to_string() { - Some(field) - } else { - None - } - }) - .next() - .cloned() -} - -pub fn filter_fields(fields: &[Field], field: &syn::Ident) -> Vec { - fields - .iter() - .filter_map(|(field2, ty)| { - if field2 != field { - Some((field2.clone(), ty.clone())) - } else { - None - } - }) - .collect() -} - -pub fn fields_to_args(fields: &[Field]) -> TokenStream { - let args = fields.iter().map(|(field, ty)| quote!(#field: #ty,)); - quote!(#(#args)*) -} - -pub fn build_struct(ident: &syn::Ident, fields: &[Field]) -> TokenStream { - let fields = fields.iter().map(|(field, _)| field); - quote!(#ident { #(#fields,)* }) -} - -pub fn ident_to_name(ident: &syn::Ident, ty: &str) -> String { - let name = ident.to_string(); - let name = name.trim_end_matches(ty); - if name.is_empty() { - ty.to_string() - } else { - name.to_string() - } -} - -pub fn module_name(generics: &syn::Generics) -> &syn::Path { - generics - .params - .iter() - .filter_map(|p| { - if let syn::GenericParam::Type(p) = p { - p.bounds - .iter() - .filter_map(|b| { - if let syn::TypeParamBound::Trait(t) = b { - Some(&t.path) - } else { - None - } - }) - .next() - } else { - None - } - }) - .next() - .unwrap() -} - -pub fn path_to_ident(path: &syn::Path) -> &syn::Ident { - &path.segments.iter().last().unwrap().ident -} - -pub fn type_params(generics: &syn::Generics) -> Vec { - generics - .params - .iter() - .filter_map(|g| { - match g { - syn::GenericParam::Type(p) => { - let ident = &p.ident; - Some(quote!(#ident)) - } - syn::GenericParam::Lifetime(p) => { - let lifetime = &p.lifetime; - Some(quote!(#lifetime)) - } - syn::GenericParam::Const(_) => None, - } - }) - .collect() -} - -pub fn parse_option(ty: &syn::Type) -> Option { - if let syn::Type::Path(ty_path) = ty { - if let Some(seg) = ty_path.path.segments.first() { - if &seg.ident == "Option" { - if let syn::PathArguments::AngleBracketed(args) = &seg.arguments { - if let Some(syn::GenericArgument::Type(ty)) = args.args.first() { - return Some(ty.clone()) - } - } - } - } - } - None -} - -#[derive(Debug)] -pub struct Attrs { - pub paren: syn::token::Paren, - pub attrs: Punctuated, -} - -impl Parse for Attrs { - fn parse(input: ParseStream) -> syn::Result { - let content; - let paren = syn::parenthesized!(content in input); - let attrs = content.parse_terminated(A::parse)?; - Ok(Self { paren, attrs }) - } -} - -#[derive(Debug)] -pub struct Attr { - pub key: K, - pub eq: syn::token::Eq, - pub value: V, -} - -impl Parse for Attr { - fn parse(input: ParseStream) -> syn::Result { - Ok(Self { - key: input.parse()?, - eq: input.parse()?, - value: input.parse()?, - }) - } -} - -#[derive(Debug)] -pub struct UniAttr { - pub paren: syn::token::Paren, - pub attr: A, -} - -impl Parse for UniAttr { - fn parse(input: ParseStream) -> syn::Result { - let content; - let paren = syn::parenthesized!(content in input); - let attr = content.parse()?; - Ok(Self { paren, attr }) - } -} - -#[cfg(test)] -pub(crate) fn assert_proc_macro( - result: proc_macro2::TokenStream, - expected: proc_macro2::TokenStream, -) { - let result = result.to_string(); - let expected = expected.to_string(); - pretty_assertions::assert_eq!(result, expected); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_option() { - let option_t: syn::Type = syn::parse2(quote!(Option)).unwrap(); - let t: syn::Type = syn::parse2(quote!(T)).unwrap(); - assert_eq!(parse_option(&option_t), Some(t.clone())); - assert_eq!(parse_option(&t), None); - } -} diff --git a/proc-macro/tests/balances.rs b/proc-macro/tests/balances.rs deleted file mode 100644 index 9dd87d8e75..0000000000 --- a/proc-macro/tests/balances.rs +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -#[macro_use] -extern crate substrate_subxt; - -use codec::{ - Codec, - Decode, - Encode, -}; -use sp_keyring::AccountKeyring; -use std::fmt::Debug; -use substrate_subxt::{ - sp_runtime::traits::{ - AtLeast32Bit, - MaybeSerialize, - Member, - }, - system::System, - ClientBuilder, - KusamaRuntime, - PairSigner, -}; - -#[module] -pub trait Balances: System { - type Balance: Member - + AtLeast32Bit - + Codec - + Default - + Copy - + MaybeSerialize - + Debug - + From<::BlockNumber>; -} - -#[derive(Clone, Decode, Default)] -pub struct AccountData { - pub free: Balance, - pub reserved: Balance, - pub misc_frozen: Balance, - pub fee_frozen: Balance, -} - -#[derive(Encode, Store)] -pub struct AccountStore<'a, T: Balances> { - #[store(returns = AccountData)] - pub account_id: &'a ::AccountId, -} - -#[derive(Call, Encode)] -pub struct TransferCall<'a, T: Balances> { - pub to: &'a ::Address, - #[codec(compact)] - pub amount: T::Balance, -} - -#[derive(Debug, Decode, Eq, Event, PartialEq)] -pub struct TransferEvent { - pub from: ::AccountId, - pub to: ::AccountId, - pub amount: T::Balance, -} - -impl Balances for KusamaRuntime { - type Balance = u128; -} - -subxt_test!({ - name: transfer_test_case, - runtime: KusamaRuntime, - account: Alice, - step: { - state: { - alice: &AccountStore { account_id: &alice }, - bob: &AccountStore { account_id: &bob }, - }, - call: TransferCall { - to: &bob, - amount: 10_000, - }, - event: TransferEvent { - from: alice.clone(), - to: bob.clone(), - amount: 10_000, - }, - assert: { - assert_eq!(pre.alice.free, post.alice.free - 10_000); - assert_eq!(pre.bob.free, post.bob.free + 10_000); - }, - }, -}); - -#[async_std::test] -#[ignore] -async fn transfer_balance_example() -> Result<(), Box> { - env_logger::init(); - let client = ClientBuilder::::new().build().await?; - let signer = PairSigner::new(AccountKeyring::Alice.pair()); - let alice = AccountKeyring::Alice.to_account_id(); - let bob = AccountKeyring::Bob.to_account_id(); - - let alice_account = client.account(&alice, None).await?; - let bob_account = client.account(&bob, None).await?; - let pre = (alice_account, bob_account); - - let _hash = client - .transfer(&signer, &bob.clone().into(), 10_000) - .await?; - - let result = client - .transfer_and_watch(&signer, &bob.clone().into(), 10_000) - .await?; - - assert_eq!( - result.transfer()?, - Some(TransferEvent { - from: alice.clone(), - to: bob.clone(), - amount: 10_000, - }) - ); - - let alice_account = client.account(&alice, None).await?; - let bob_account = client.account(&bob, None).await?; - let post = (alice_account, bob_account); - - assert_eq!(pre.0.free, post.0.free - 10_000); - assert_eq!(pre.1.free, post.1.free + 10_000); - Ok(()) -} diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000000..ae8a5a91c8 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,280 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use futures::future; +pub use sp_runtime::traits::SignedExtension; +pub use sp_version::RuntimeVersion; + +use crate::{ + events::EventsDecoder, + extrinsic::{ + self, + SignedExtra, + Signer, + UncheckedExtrinsic, + }, + rpc::{ + ExtrinsicSuccess, + Rpc, + RpcClient, + SystemProperties, + }, + storage::StorageClient, + AccountData, + Call, + Config, + Error, + ExtrinsicExtraData, + Metadata, +}; + +/// ClientBuilder for constructing a Client. +#[derive(Default)] +pub struct ClientBuilder { + url: Option, + client: Option, + page_size: Option, + accept_weak_inclusion: bool, +} + +impl ClientBuilder { + /// Creates a new ClientBuilder. + pub fn new() -> Self { + Self { + url: None, + client: None, + page_size: None, + accept_weak_inclusion: false, + } + } + + /// Sets the jsonrpsee client. + pub fn set_client>(mut self, client: C) -> Self { + self.client = Some(client.into()); + self + } + + /// Set the substrate rpc address. + pub fn set_url>(mut self, url: P) -> Self { + self.url = Some(url.into()); + self + } + + /// Set the page size. + pub fn set_page_size(mut self, size: u32) -> Self { + self.page_size = Some(size); + self + } + + /// Only check that transactions are InBlock on submit. + pub fn accept_weak_inclusion(mut self) -> Self { + self.accept_weak_inclusion = true; + self + } + + /// Creates a new Client. + pub async fn build(self) -> Result, Error> { + let client = if let Some(client) = self.client { + client + } else { + let url = self.url.as_deref().unwrap_or("ws://127.0.0.1:9944"); + RpcClient::try_from_url(url).await? + }; + let mut rpc = Rpc::new(client); + if self.accept_weak_inclusion { + rpc.accept_weak_inclusion(); + } + let (metadata, genesis_hash, runtime_version, properties) = future::join4( + rpc.metadata(), + rpc.genesis_hash(), + rpc.runtime_version(None), + rpc.system_properties(), + ) + .await; + let metadata = metadata?; + + let events_decoder = EventsDecoder::new(metadata.clone()); + + Ok(Client { + rpc, + genesis_hash: genesis_hash?, + metadata, + events_decoder, + properties: properties.unwrap_or_else(|_| Default::default()), + runtime_version: runtime_version?, + iter_page_size: self.page_size.unwrap_or(10), + }) + } +} + +/// Client to interface with a substrate node. +pub struct Client { + rpc: Rpc, + genesis_hash: T::Hash, + metadata: Metadata, + events_decoder: EventsDecoder, + properties: SystemProperties, + runtime_version: RuntimeVersion, + // _marker: PhantomData<(fn() -> T::Signature, T::Extra)>, + iter_page_size: u32, +} + +impl Clone for Client { + fn clone(&self) -> Self { + Self { + rpc: self.rpc.clone(), + genesis_hash: self.genesis_hash, + metadata: self.metadata.clone(), + events_decoder: self.events_decoder.clone(), + properties: self.properties.clone(), + runtime_version: self.runtime_version.clone(), + iter_page_size: self.iter_page_size, + } + } +} + +impl Client { + /// Returns the genesis hash. + pub fn genesis(&self) -> &T::Hash { + &self.genesis_hash + } + + /// Returns the chain metadata. + pub fn metadata(&self) -> &Metadata { + &self.metadata + } + + /// Returns the system properties + pub fn properties(&self) -> &SystemProperties { + &self.properties + } + + /// Returns the rpc client. + pub fn rpc(&self) -> &Rpc { + &self.rpc + } + + /// Create a client for accessing runtime storage + pub fn storage(&self) -> StorageClient { + StorageClient::new(&self.rpc, &self.metadata, self.iter_page_size) + } + + /// Convert the client to a runtime api wrapper for custom runtime access. + /// + /// The `subxt` proc macro will provide methods to submit extrinsics and read storage specific + /// to the target runtime. + pub fn to_runtime_api>(self) -> R { + self.into() + } + + /// Returns the events decoder. + pub fn events_decoder(&self) -> &EventsDecoder { + &self.events_decoder + } +} + +/// A constructed call ready to be signed and submitted. +pub struct SubmittableExtrinsic<'a, T: Config, C> { + client: &'a Client, + call: C, +} + +impl<'a, T, C> SubmittableExtrinsic<'a, T, C> +where + T: Config + ExtrinsicExtraData, + C: Call + Send + Sync, +{ + /// Create a new [`SubmittableExtrinsic`]. + pub fn new(client: &'a Client, call: C) -> Self { + Self { client, call } + } + + /// Creates and signs an extrinsic and submits it to the chain. + /// + /// Returns when the extrinsic has successfully been included in the block, together with any + /// events which were triggered by the extrinsic. + pub async fn sign_and_submit_then_watch( + self, + signer: &(dyn Signer + Send + Sync), + ) -> Result, Error> + where + <<>::Extra as SignedExtra>::Extra as SignedExtension>::AdditionalSigned: Send + Sync + 'static + { + let extrinsic = self.create_signed(signer).await?; + self.client + .rpc() + .submit_and_watch_extrinsic(extrinsic, self.client.events_decoder()) + .await + } + + /// Creates and signs an extrinsic and submits to the chain for block inclusion. + /// + /// Returns `Ok` with the extrinsic hash if it is valid extrinsic. + /// + /// # Note + /// + /// Success does not mean the extrinsic has been included in the block, just that it is valid + /// and has been included in the transaction pool. + pub async fn sign_and_submit( + self, + signer: &(dyn Signer + Send + Sync), + ) -> Result + where + <<>::Extra as SignedExtra>::Extra as SignedExtension>::AdditionalSigned: Send + Sync + 'static + { + let extrinsic = self.create_signed(signer).await?; + self.client.rpc().submit_extrinsic(extrinsic).await + } + + /// Creates a signed extrinsic. + pub async fn create_signed( + &self, + signer: &(dyn Signer + Send + Sync), + ) -> Result, Error> + where + <<>::Extra as SignedExtra>::Extra as SignedExtension>::AdditionalSigned: Send + Sync + 'static + { + let account_nonce = if let Some(nonce) = signer.nonce() { + nonce + } else { + let account_storage_entry = + <>::AccountData as AccountData>::storage_entry(signer.account_id().clone()); + let account_data = self + .client + .storage() + .fetch_or_default(&account_storage_entry, None) + .await?; + <>::AccountData as AccountData>::nonce( + &account_data, + ) + }; + let call = self + .client + .metadata() + .pallet(C::PALLET) + .and_then(|pallet| pallet.encode_call(&self.call))?; + + let signed = extrinsic::create_signed( + &self.client.runtime_version, + self.client.genesis_hash, + account_nonce, + call, + signer, + ) + .await?; + Ok(signed) + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000000..460cc4c5f0 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,105 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use crate::{ + SignedExtra, + StorageEntry, +}; +use codec::{ + Codec, + Encode, + EncodeLike, +}; +use core::fmt::Debug; +use sp_runtime::traits::{ + AtLeast32Bit, + Extrinsic, + Hash, + Header, + MaybeSerializeDeserialize, + Member, + Verify, +}; + +/// Runtime types. +pub trait Config: Clone + Sized + Send + Sync + 'static { + /// Account index (aka nonce) type. This stores the number of previous + /// transactions associated with a sender account. + type Index: Parameter + Member + Default + AtLeast32Bit + Copy + scale_info::TypeInfo; + + /// The block number type used by the runtime. + type BlockNumber: Parameter + + Member + + Default + + Copy + + core::hash::Hash + + core::str::FromStr; + + /// The output of the `Hashing` function. + type Hash: Parameter + + Member + + MaybeSerializeDeserialize + + Ord + + Default + + Copy + + std::hash::Hash + + AsRef<[u8]> + + AsMut<[u8]> + + scale_info::TypeInfo; + + /// The hashing system (algorithm) being used in the runtime (e.g. Blake2). + type Hashing: Hash; + + /// The user account identifier type for the runtime. + type AccountId: Parameter + Member; + + /// The address type. This instead of `::Source`. + type Address: Codec + Clone + PartialEq; + + /// The block header. + type Header: Parameter + + Header + + serde::de::DeserializeOwned; + + /// Signature type. + type Signature: Verify + Encode + Send + Sync + 'static; + + /// Extrinsic type within blocks. + type Extrinsic: Parameter + Extrinsic + Debug + MaybeSerializeDeserialize; +} + +/// Parameter trait copied from `substrate::frame_support` +pub trait Parameter: Codec + EncodeLike + Clone + Eq + Debug {} +impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + Debug {} + +/// Trait to fetch data about an account. +/// +/// Should be implemented on a type implementing `StorageEntry`, +/// usually generated by the `subxt` macro. +pub trait AccountData: StorageEntry { + /// Create a new storage entry key from the account id. + fn storage_entry(account_id: T::AccountId) -> Self; + /// Get the nonce from the storage entry value. + fn nonce(result: &::Value) -> T::Index; +} + +/// Trait to configure the extra data for an extrinsic. +pub trait ExtrinsicExtraData { + /// The type of the [`StorageEntry`] which can be used to retrieve an account nonce. + type AccountData: AccountData; + /// The type of extra data and additional signed data to be included in a transaction. + type Extra: SignedExtra + Send + Sync + 'static; +} diff --git a/src/error.rs b/src/error.rs index 9e60fc1557..5fbfa1897c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,8 +12,16 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . +use crate::{ + events::EventsDecodingError, + metadata::{ + InvalidMetadataError, + MetadataError, + }, + Metadata, +}; use jsonrpsee_types::Error as RequestError; use sp_core::crypto::SecretStringError; use sp_runtime::{ @@ -22,11 +30,6 @@ use sp_runtime::{ }; use thiserror::Error; -use crate::metadata::{ - Metadata, - MetadataError, -}; - /// Error enum. #[derive(Debug, Error)] pub enum Error { @@ -48,22 +51,18 @@ pub enum Error { /// Extrinsic validity error #[error("Transaction Validity Error: {0:?}")] Invalid(TransactionValidityError), - /// Metadata error. - #[error("Metadata error: {0}")] + /// Invalid metadata error + #[error("Invalid Metadata: {0}")] + InvalidMetadata(#[from] InvalidMetadataError), + /// Invalid metadata error + #[error("Metadata: {0}")] Metadata(#[from] MetadataError), - /// Unregistered type sizes. - #[error( - "The following types do not have a type size registered: \ - {0:?} \ - Use `ClientBuilder::register_type_size` to register missing type sizes." - )] - MissingTypeSizes(Vec), - /// Type size unavailable. - #[error("Type size unavailable while decoding event: {0:?}")] - TypeSizeUnavailable(String), /// Runtime error. #[error("Runtime error: {0}")] Runtime(#[from] RuntimeError), + /// Events decoding error. + #[error("Events decoding error: {0}")] + EventsDecoding(#[from] EventsDecodingError), /// Other error. #[error("Other error: {0}")] Other(String), @@ -98,7 +97,7 @@ impl From for Error { pub enum RuntimeError { /// Module error. #[error("Runtime module error: {0}")] - Module(ModuleError), + Module(PalletError), /// At least one consumer is remaining so the account cannot be destroyed. #[error("At least one consumer is remaining so the account cannot be destroyed.")] ConsumerRemaining, @@ -128,11 +127,11 @@ impl RuntimeError { error, message: _, } => { - let module = metadata.module_with_errors(index)?; - let error = module.error(error)?; - Ok(Self::Module(ModuleError { - module: module.name().to_string(), - error: error.to_string(), + let error = metadata.error(index, error)?; + Ok(Self::Module(PalletError { + pallet: error.pallet().to_string(), + error: error.error().to_string(), + description: error.description().to_vec(), })) } DispatchError::BadOrigin => Ok(Self::BadOrigin), @@ -150,10 +149,12 @@ impl RuntimeError { /// Module error. #[derive(Clone, Debug, Eq, Error, PartialEq)] -#[error("{error} from {module}")] -pub struct ModuleError { +#[error("{error} from {pallet}")] +pub struct PalletError { /// The module where the error originated. - pub module: String, + pub pallet: String, /// The actual error code. pub error: String, + /// The error description. + pub description: Vec, } diff --git a/src/events.rs b/src/events.rs index 95d530d14c..634ff10e5a 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,7 +12,7 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . use codec::{ Codec, @@ -20,115 +20,57 @@ use codec::{ Decode, Encode, Input, - Output, -}; -use dyn_clone::DynClone; -use sp_runtime::{ - DispatchError, - DispatchResult, -}; -use std::{ - collections::{ - hash_map::{ - Entry, - HashMap, - }, - HashSet, - }, - fmt, - marker::{ - PhantomData, - Send, - }, }; +use std::marker::PhantomData; use crate::{ - error::{ - Error, - RuntimeError, - }, metadata::{ - EventArg, - Metadata, + EventMetadata, + MetadataError, }, + Config, + Error, + Metadata, Phase, - Runtime, - System, + RuntimeError, }; +use scale_info::{ + TypeDef, + TypeDefPrimitive, +}; +use sp_core::Bytes; /// Raw bytes for an Event +#[derive(Debug)] pub struct RawEvent { - /// The name of the module from whence the Event originated - pub module: String, - /// The name of the Event + /// The name of the pallet from whence the Event originated. + pub pallet: String, + /// The index of the pallet from whence the Event originated. + pub pallet_index: u8, + /// The name of the pallet Event variant. pub variant: String, + /// The index of the pallet Event variant. + pub variant_index: u8, /// The raw Event data - pub data: Vec, -} - -impl std::fmt::Debug for RawEvent { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.debug_struct("RawEvent") - .field("module", &self.module) - .field("variant", &self.variant) - .field("data", &hex::encode(&self.data)) - .finish() - } -} - -pub trait TypeSegmenter: DynClone + Send + Sync { - /// Consumes an object from an input stream, and output the serialized bytes. - fn segment(&self, input: &mut &[u8], output: &mut Vec) -> Result<(), Error>; -} - -// derive object safe Clone impl for `Box` -dyn_clone::clone_trait_object!(TypeSegmenter); - -struct TypeMarker(PhantomData); -impl TypeSegmenter for TypeMarker -where - T: Codec + Send + Sync, -{ - fn segment(&self, input: &mut &[u8], output: &mut Vec) -> Result<(), Error> { - T::decode(input).map_err(Error::from)?.encode_to(output); - Ok(()) - } -} - -impl Clone for TypeMarker { - fn clone(&self) -> Self { - Self(Default::default()) - } -} - -impl Default for TypeMarker { - fn default() -> Self { - Self(Default::default()) - } + pub data: Bytes, } /// Events decoder. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct EventsDecoder { metadata: Metadata, - event_type_registry: EventTypeRegistry, -} - -impl Clone for EventsDecoder { - fn clone(&self) -> Self { - Self { - metadata: self.metadata.clone(), - event_type_registry: self.event_type_registry.clone(), - } - } + marker: PhantomData, } -impl EventsDecoder { +impl EventsDecoder +where + T: Config, +{ /// Creates a new `EventsDecoder`. - pub fn new(metadata: Metadata, event_type_registry: EventTypeRegistry) -> Self { + pub fn new(metadata: Metadata) -> Self { Self { metadata, - event_type_registry, + marker: Default::default(), } } @@ -136,28 +78,28 @@ impl EventsDecoder { pub fn decode_events(&self, input: &mut &[u8]) -> Result, Error> { let compact_len = >::decode(input)?; let len = compact_len.0 as usize; + log::debug!("decoding {} events", len); let mut r = Vec::new(); for _ in 0..len { // decode EventRecord let phase = Phase::decode(input)?; - let module_variant = input.read_byte()?; - - let module = self.metadata.module_with_events(module_variant)?; - let event_variant = input.read_byte()?; - let event_metadata = module.event(event_variant)?; - + let pallet_index = input.read_byte()?; + let variant_index = input.read_byte()?; log::debug!( - "received event '{}::{}' ({:?})", - module.name(), - event_metadata.name, - event_metadata.arguments() + "phase {:?}, pallet_index {}, event_variant: {}", + phase, + pallet_index, + variant_index ); + log::debug!("remaining input: {}", hex::encode(&input)); + + let event_metadata = self.metadata.event(pallet_index, variant_index)?; let mut event_data = Vec::::new(); let mut event_errors = Vec::::new(); - let result = self.decode_raw_bytes( - &event_metadata.arguments(), + let result = self.decode_raw_event( + &event_metadata, input, &mut event_data, &mut event_errors, @@ -167,13 +109,17 @@ impl EventsDecoder { log::debug!("raw bytes: {}", hex::encode(&event_data),); let event = RawEvent { - module: module.name().to_string(), - variant: event_metadata.name.clone(), - data: event_data, + pallet: event_metadata.pallet().to_string(), + pallet_index, + variant: event_metadata.event().to_string(), + variant_index, + data: event_data.into(), }; // topics come after the event data in EventRecord - let _topics = Vec::::decode(input)?; + let topics = Vec::::decode(input)?; + log::debug!("topics: {:?}", topics); + Raw::Event(event) } Err(err) => return Err(err), @@ -190,154 +136,200 @@ impl EventsDecoder { Ok(r) } - fn decode_raw_bytes( + fn decode_raw_event( &self, - args: &[EventArg], + event_metadata: &EventMetadata, input: &mut &[u8], - output: &mut W, + output: &mut Vec, errors: &mut Vec, ) -> Result<(), Error> { - for arg in args { - match arg { - EventArg::Vec(arg) => { - let len = >::decode(input)?; - len.encode_to(output); - for _ in 0..len.0 { - self.decode_raw_bytes(&[*arg.clone()], input, output, errors)? - } - } - EventArg::Option(arg) => { - match input.read_byte()? { - 0 => output.push_byte(0), - 1 => { - output.push_byte(1); - self.decode_raw_bytes(&[*arg.clone()], input, output, errors)? - } - _ => { - return Err(Error::Other( - "unexpected first byte decoding Option".into(), - )) - } - } - } - EventArg::Tuple(args) => { - self.decode_raw_bytes(args, input, output, errors)? - } - EventArg::Primitive(name) => { - let result = match name.as_str() { - "DispatchResult" => DispatchResult::decode(input)?, - "DispatchError" => Err(DispatchError::decode(input)?), - _ => { - if let Some(seg) = self.event_type_registry.resolve(name) { - let mut buf = Vec::::new(); - seg.segment(input, &mut buf)?; - output.write(&buf); - Ok(()) - } else { - return Err(Error::TypeSizeUnavailable(name.to_owned())) - } - } - }; - if let Err(error) = result { - // since the input may contain any number of args we propagate - // runtime errors to the caller for handling - errors.push(RuntimeError::from_dispatch(&self.metadata, error)?); - } + log::debug!( + "Decoding Event '{}::{}'", + event_metadata.pallet(), + event_metadata.event() + ); + for arg in event_metadata.variant().fields() { + let type_id = arg.ty().id(); + if event_metadata.pallet() == "System" + && event_metadata.event() == "ExtrinsicFailed" + { + let ty = self + .metadata + .resolve_type(type_id) + .ok_or(MetadataError::TypeNotFound(type_id))?; + + if ty.path().ident() == Some("DispatchError".to_string()) { + let dispatch_error = sp_runtime::DispatchError::decode(input)?; + log::info!("Dispatch Error {:?}", dispatch_error); + dispatch_error.encode_to(output); + let runtime_error = + RuntimeError::from_dispatch(&self.metadata, dispatch_error)?; + errors.push(runtime_error); + continue } } + self.decode_type(type_id, input, output)? } Ok(()) } -} -/// Registry for event types which cannot be directly inferred from the metadata. -#[derive(Default)] -pub struct EventTypeRegistry { - segmenters: HashMap>, - marker: PhantomData T>, -} - -impl Clone for EventTypeRegistry { - fn clone(&self) -> Self { - Self { - segmenters: self.segmenters.clone(), - marker: PhantomData, + fn decode_type( + &self, + type_id: u32, + input: &mut &[u8], + output: &mut Vec, + ) -> Result<(), Error> { + let ty = self + .metadata + .resolve_type(type_id) + .ok_or(MetadataError::TypeNotFound(type_id))?; + + fn decode_raw( + input: &mut &[u8], + output: &mut Vec, + ) -> Result<(), Error> { + let decoded = T::decode(input)?; + decoded.encode_to(output); + Ok(()) } - } -} - -impl fmt::Debug for EventTypeRegistry { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("EventTypeRegistry") - .field( - "segmenters", - &self.segmenters.keys().cloned().collect::(), - ) - .finish() - } -} - -impl EventTypeRegistry { - /// Create a new [`EventTypeRegistry`]. - pub fn new() -> Self { - let mut registry = Self { - segmenters: HashMap::new(), - marker: PhantomData, - }; - T::register_type_sizes(&mut registry); - registry - } - /// Register a type. - /// - /// # Panics - /// - /// If there is already a type size registered with this name. - pub fn register_type_size(&mut self, name: &str) - where - U: Codec + Send + Sync + 'static, - { - // A segmenter decodes a type from an input stream (&mut &[u8]) and returns te serialized - // type to the output stream (&mut Vec). - match self.segmenters.entry(name.to_string()) { - Entry::Occupied(_) => panic!("Already a type registered with key {}", name), - Entry::Vacant(entry) => entry.insert(Box::new(TypeMarker::::default())), - }; - } - - /// Check missing type sizes. - pub fn check_missing_type_sizes( - &self, - metadata: &Metadata, - ) -> Result<(), HashSet> { - let mut missing = HashSet::new(); - for module in metadata.modules_with_events() { - for event in module.events() { - for arg in event.arguments() { - for primitive in arg.primitives() { - if !self.segmenters.contains_key(&primitive) { - missing.insert(format!( - "{}::{}::{}", - module.name(), - event.name, - primitive - )); + match ty.type_def() { + TypeDef::Composite(composite) => { + for field in composite.fields() { + self.decode_type(field.ty().id(), input, output)? + } + Ok(()) + } + TypeDef::Variant(variant) => { + let variant_index = u8::decode(input)?; + variant_index.encode_to(output); + let variant = variant.variants().get(variant_index as usize).ok_or( + Error::Other(format!("Variant {} not found", variant_index)), + )?; + for field in variant.fields() { + self.decode_type(field.ty().id(), input, output)?; + } + Ok(()) + } + TypeDef::Sequence(seq) => { + let len = >::decode(input)?; + len.encode_to(output); + for _ in 0..len.0 { + self.decode_type(seq.type_param().id(), input, output)?; + } + Ok(()) + } + TypeDef::Array(arr) => { + for _ in 0..arr.len() { + self.decode_type(arr.type_param().id(), input, output)?; + } + Ok(()) + } + TypeDef::Tuple(tuple) => { + for field in tuple.fields() { + self.decode_type(field.id(), input, output)?; + } + Ok(()) + } + TypeDef::Primitive(primitive) => { + match primitive { + TypeDefPrimitive::Bool => decode_raw::(input, output), + TypeDefPrimitive::Char => { + Err(EventsDecodingError::UnsupportedPrimitive( + TypeDefPrimitive::Char, + ) + .into()) + } + TypeDefPrimitive::Str => decode_raw::(input, output), + TypeDefPrimitive::U8 => decode_raw::(input, output), + TypeDefPrimitive::U16 => decode_raw::(input, output), + TypeDefPrimitive::U32 => decode_raw::(input, output), + TypeDefPrimitive::U64 => decode_raw::(input, output), + TypeDefPrimitive::U128 => decode_raw::(input, output), + TypeDefPrimitive::U256 => { + Err(EventsDecodingError::UnsupportedPrimitive( + TypeDefPrimitive::U256, + ) + .into()) + } + TypeDefPrimitive::I8 => decode_raw::(input, output), + TypeDefPrimitive::I16 => decode_raw::(input, output), + TypeDefPrimitive::I32 => decode_raw::(input, output), + TypeDefPrimitive::I64 => decode_raw::(input, output), + TypeDefPrimitive::I128 => decode_raw::(input, output), + TypeDefPrimitive::I256 => { + Err(EventsDecodingError::UnsupportedPrimitive( + TypeDefPrimitive::I256, + ) + .into()) + } + } + } + TypeDef::Compact(_compact) => { + let inner = self + .metadata + .resolve_type(type_id) + .ok_or(MetadataError::TypeNotFound(type_id))?; + let mut decode_compact_primitive = |primitive: &TypeDefPrimitive| { + match primitive { + TypeDefPrimitive::U8 => decode_raw::>(input, output), + TypeDefPrimitive::U16 => { + decode_raw::>(input, output) + } + TypeDefPrimitive::U32 => { + decode_raw::>(input, output) + } + TypeDefPrimitive::U64 => { + decode_raw::>(input, output) + } + TypeDefPrimitive::U128 => { + decode_raw::>(input, output) + } + prim => { + Err(EventsDecodingError::InvalidCompactPrimitive( + prim.clone(), + ) + .into()) } } + }; + match inner.type_def() { + TypeDef::Primitive(primitive) => decode_compact_primitive(primitive), + TypeDef::Composite(composite) => { + match composite.fields() { + [field] => { + let field_ty = + self.metadata.resolve_type(field.ty().id()).ok_or( + MetadataError::TypeNotFound(field.ty().id()), + )?; + if let TypeDef::Primitive(primitive) = field_ty.type_def() + { + decode_compact_primitive(primitive) + } else { + Err(EventsDecodingError::InvalidCompactType("Composite type must have a single primitive field".into()).into()) + } + } + _ => { + Err(EventsDecodingError::InvalidCompactType( + "Composite type must have a single field".into(), + ) + .into()) + } + } + } + _ => { + Err(EventsDecodingError::InvalidCompactType( + "Compact type must be a primitive or a composite type".into(), + ) + .into()) + } } } + TypeDef::BitSequence(_bitseq) => { + // decode_raw:: + unimplemented!("BitVec decoding for events not implemented yet") + } } - - if !missing.is_empty() { - Err(missing) - } else { - Ok(()) - } - } - - /// Resolve a segmenter for a type by its name. - pub fn resolve(&self, name: &str) -> Option<&Box> { - self.segmenters.get(name) } } @@ -350,122 +342,47 @@ pub enum Raw { Error(RuntimeError), } -#[cfg(test)] -mod tests { - use super::*; - use frame_metadata::{ - DecodeDifferent, - ErrorMetadata, - EventMetadata, - ExtrinsicMetadata, - ModuleMetadata, - RuntimeMetadata, - RuntimeMetadataPrefixed, - RuntimeMetadataV13, - META_RESERVED, - }; - use std::convert::TryFrom; - - type TestRuntime = crate::NodeTemplateRuntime; - - #[test] - fn test_decode_option() { - let decoder = EventsDecoder::::new( - Metadata::default(), - EventTypeRegistry::new(), - ); - - let value = Some(0u8); - let input = value.encode(); - let mut output = Vec::::new(); - let mut errors = Vec::::new(); - - decoder - .decode_raw_bytes( - &[EventArg::Option(Box::new(EventArg::Primitive( - "u8".to_string(), - )))], - &mut &input[..], - &mut output, - &mut errors, - ) - .unwrap(); - - assert_eq!(output, vec![1, 0]); - } - - #[test] - fn test_decode_system_events_and_error() { - let decoder = EventsDecoder::::new( - Metadata::try_from(RuntimeMetadataPrefixed( - META_RESERVED, - RuntimeMetadata::V13(RuntimeMetadataV13 { - modules: DecodeDifferent::Decoded(vec![ModuleMetadata { - name: DecodeDifferent::Decoded("System".to_string()), - storage: None, - calls: None, - event: Some(DecodeDifferent::Decoded(vec![ - EventMetadata { - name: DecodeDifferent::Decoded( - "ExtrinsicSuccess".to_string(), - ), - arguments: DecodeDifferent::Decoded(vec![ - "DispatchInfo".to_string() - ]), - documentation: DecodeDifferent::Decoded(vec![]), - }, - EventMetadata { - name: DecodeDifferent::Decoded( - "ExtrinsicFailed".to_string(), - ), - arguments: DecodeDifferent::Decoded(vec![ - "DispatchError".to_string(), - "DispatchInfo".to_string(), - ]), - documentation: DecodeDifferent::Decoded(vec![]), - }, - ])), - constants: DecodeDifferent::Decoded(vec![]), - errors: DecodeDifferent::Decoded(vec![ - ErrorMetadata { - name: DecodeDifferent::Decoded( - "InvalidSpecName".to_string(), - ), - documentation: DecodeDifferent::Decoded(vec![]), - }, - ErrorMetadata { - name: DecodeDifferent::Decoded( - "SpecVersionNeedsToIncrease".to_string(), - ), - documentation: DecodeDifferent::Decoded(vec![]), - }, - ErrorMetadata { - name: DecodeDifferent::Decoded( - "FailedToExtractRuntimeVersion".to_string(), - ), - documentation: DecodeDifferent::Decoded(vec![]), - }, - ErrorMetadata { - name: DecodeDifferent::Decoded( - "NonDefaultComposite".to_string(), - ), - documentation: DecodeDifferent::Decoded(vec![]), - }, - ]), - index: 0, - }]), - extrinsic: ExtrinsicMetadata { - version: 0, - signed_extensions: vec![], - }, - }), - )) - .unwrap(), - EventTypeRegistry::new(), - ); - - // [(ApplyExtrinsic(0), Event(RawEvent { module: "System", variant: "ExtrinsicSuccess", data: "482d7c09000000000200" })), (ApplyExtrinsic(1), Error(Module(ModuleError { module: "System", error: "NonDefaultComposite" }))), (ApplyExtrinsic(2), Error(Module(ModuleError { module: "System", error: "NonDefaultComposite" })))] - let input = hex::decode("0c00000000000000482d7c0900000000020000000100000000010300035884723300000000000000000200000000010300035884723300000000000000").unwrap(); - decoder.decode_events(&mut &input[..]).unwrap(); - } +#[derive(Debug, thiserror::Error)] +pub enum EventsDecodingError { + /// Unsupported primitive type + #[error("Unsupported primitive type {0:?}")] + UnsupportedPrimitive(TypeDefPrimitive), + /// Invalid compact type, must be an unsigned int. + #[error("Invalid compact primitive {0:?}")] + InvalidCompactPrimitive(TypeDefPrimitive), + #[error("Invalid compact composite type {0}")] + InvalidCompactType(String), } + +// #[cfg(test)] +// mod tests { +// use super::*; +// use std::convert::TryFrom; +// +// type DefaultConfig = crate::NodeTemplateRuntime; +// +// #[test] +// fn test_decode_option() { +// let decoder = EventsDecoder::::new( +// Metadata::default(), +// ); +// +// let value = Some(0u8); +// let input = value.encode(); +// let mut output = Vec::::new(); +// let mut errors = Vec::::new(); +// +// decoder +// .decode_raw_bytes( +// &[EventArg::Option(Box::new(EventArg::Primitive( +// "u8".to_string(), +// )))], +// &mut &input[..], +// &mut output, +// &mut errors, +// ) +// .unwrap(); +// +// assert_eq!(output, vec![1, 0]); +// } +// } diff --git a/src/extrinsic/extra.rs b/src/extrinsic/extra.rs index 4847885301..62c66bd6f6 100644 --- a/src/extrinsic/extra.rs +++ b/src/extrinsic/extra.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,7 +12,7 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . use codec::{ Decode, @@ -22,22 +22,17 @@ use core::{ fmt::Debug, marker::PhantomData, }; +use scale_info::TypeInfo; use sp_runtime::{ generic::Era, traits::SignedExtension, transaction_validity::TransactionValidityError, }; -use crate::{ - frame::{ - balances::Balances, - system::System, - }, - runtimes::Runtime, -}; +use crate::Config; /// Extra type. -pub type Extra = <::Extra as SignedExtra>::Extra; +// pub type Extra = <::Extra as SignedExtra>::Extra; /// SignedExtra checks copied from substrate, in order to remove requirement to implement /// substrate's `frame_system::Trait` @@ -50,8 +45,9 @@ pub type Extra = <::Extra as SignedExtra>::Extra; /// returned via `additional_signed()`. /// Ensure the runtime version registered in the transaction is the same as at present. -#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] -pub struct CheckSpecVersion( +#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct CheckSpecVersion( pub PhantomData, /// Local version to be used for `AdditionalSigned` #[codec(skip)] @@ -60,7 +56,7 @@ pub struct CheckSpecVersion( impl SignedExtension for CheckSpecVersion where - T: System + Clone + Debug + Eq + Send + Sync, + T: Config + Clone + Debug + Eq + Send + Sync, { const IDENTIFIER: &'static str = "CheckSpecVersion"; type AccountId = u64; @@ -80,8 +76,9 @@ where /// /// This is modified from the substrate version to allow passing in of the version, which is /// returned via `additional_signed()`. -#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] -pub struct CheckTxVersion( +#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct CheckTxVersion( pub PhantomData, /// Local version to be used for `AdditionalSigned` #[codec(skip)] @@ -90,7 +87,7 @@ pub struct CheckTxVersion( impl SignedExtension for CheckTxVersion where - T: System + Clone + Debug + Eq + Send + Sync, + T: Config + Clone + Debug + Eq + Send + Sync, { const IDENTIFIER: &'static str = "CheckTxVersion"; type AccountId = u64; @@ -110,8 +107,9 @@ where /// /// This is modified from the substrate version to allow passing in of the genesis hash, which is /// returned via `additional_signed()`. -#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] -pub struct CheckGenesis( +#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct CheckGenesis( pub PhantomData, /// Local genesis hash to be used for `AdditionalSigned` #[codec(skip)] @@ -120,7 +118,7 @@ pub struct CheckGenesis( impl SignedExtension for CheckGenesis where - T: System + Clone + Debug + Eq + Send + Sync, + T: Config + Clone + Debug + Eq + Send + Sync, { const IDENTIFIER: &'static str = "CheckGenesis"; type AccountId = u64; @@ -141,8 +139,9 @@ where /// This is modified from the substrate version to allow passing in of the genesis hash, which is /// returned via `additional_signed()`. It assumes therefore `Era::Immortal` (The transaction is /// valid forever) -#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] -pub struct CheckEra( +#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct CheckMortality( /// The default structure for the Extra encoding pub (Era, PhantomData), /// Local genesis hash to be used for `AdditionalSigned` @@ -150,11 +149,11 @@ pub struct CheckEra( pub T::Hash, ); -impl SignedExtension for CheckEra +impl SignedExtension for CheckMortality where - T: System + Clone + Debug + Eq + Send + Sync, + T: Config + Clone + Debug + Eq + Send + Sync, { - const IDENTIFIER: &'static str = "CheckEra"; + const IDENTIFIER: &'static str = "CheckMortality"; type AccountId = u64; type Call = (); type AdditionalSigned = T::Hash; @@ -167,12 +166,13 @@ where } /// Nonce check and increment to give replay protection for transactions. -#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] -pub struct CheckNonce(#[codec(compact)] pub T::Index); +#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct CheckNonce(#[codec(compact)] pub T::Index); impl SignedExtension for CheckNonce where - T: System + Clone + Debug + Eq + Send + Sync, + T: Config + Clone + Debug + Eq + Send + Sync, { const IDENTIFIER: &'static str = "CheckNonce"; type AccountId = u64; @@ -187,12 +187,13 @@ where } /// Resource limit check. -#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] -pub struct CheckWeight(pub PhantomData); +#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct CheckWeight(pub PhantomData); impl SignedExtension for CheckWeight where - T: System + Clone + Debug + Eq + Send + Sync, + T: Config + Clone + Debug + Eq + Send + Sync, { const IDENTIFIER: &'static str = "CheckWeight"; type AccountId = u64; @@ -208,13 +209,11 @@ where /// Require the transactor pay for themselves and maybe include a tip to gain additional priority /// in the queue. -#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] -pub struct ChargeTransactionPayment(#[codec(compact)] pub T::Balance); +#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct ChargeTransactionPayment(#[codec(compact)] pub u128); -impl SignedExtension for ChargeTransactionPayment -where - T: Balances + Clone + Debug + Eq + Send + Sync, -{ +impl SignedExtension for ChargeTransactionPayment { const IDENTIFIER: &'static str = "ChargeTransactionPayment"; type AccountId = u64; type Call = (); @@ -228,7 +227,7 @@ where } /// Trait for implementing transaction extras for a runtime. -pub trait SignedExtra: SignedExtension { +pub trait SignedExtra: SignedExtension { /// The type the extras. type Extra: SignedExtension + Send + Sync; @@ -245,25 +244,24 @@ pub trait SignedExtra: SignedExtension { } /// Default `SignedExtra` for substrate runtimes. -#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)] -pub struct DefaultExtra { +#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct DefaultExtra { spec_version: u32, tx_version: u32, nonce: T::Index, genesis_hash: T::Hash, } -impl SignedExtra - for DefaultExtra -{ +impl SignedExtra for DefaultExtra { type Extra = ( CheckSpecVersion, CheckTxVersion, CheckGenesis, - CheckEra, + CheckMortality, CheckNonce, CheckWeight, - ChargeTransactionPayment, + ChargeTransactionPayment, ); fn new( @@ -285,17 +283,15 @@ impl SignedExtra CheckSpecVersion(PhantomData, self.spec_version), CheckTxVersion(PhantomData, self.tx_version), CheckGenesis(PhantomData, self.genesis_hash), - CheckEra((Era::Immortal, PhantomData), self.genesis_hash), + CheckMortality((Era::Immortal, PhantomData), self.genesis_hash), CheckNonce(self.nonce), CheckWeight(PhantomData), - ChargeTransactionPayment(::Balance::default()), + ChargeTransactionPayment(u128::default()), ) } } -impl SignedExtension - for DefaultExtra -{ +impl SignedExtension for DefaultExtra { const IDENTIFIER: &'static str = "DefaultExtra"; type AccountId = T::AccountId; type Call = (); diff --git a/src/extrinsic/mod.rs b/src/extrinsic/mod.rs index f1c669849e..542382bab5 100644 --- a/src/extrinsic/mod.rs +++ b/src/extrinsic/mod.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,7 +12,7 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . //! Create signed or unsigned extrinsics. @@ -22,14 +22,13 @@ mod signer; pub use self::{ extra::{ ChargeTransactionPayment, - CheckEra, CheckGenesis, + CheckMortality, CheckNonce, CheckSpecVersion, CheckTxVersion, CheckWeight, DefaultExtra, - Extra, SignedExtra, }, signer::{ @@ -42,22 +41,25 @@ use sp_runtime::traits::SignedExtension; use sp_version::RuntimeVersion; use crate::{ - frame::system::System, - runtimes::Runtime, + Config, Encoded, Error, + ExtrinsicExtraData, }; /// UncheckedExtrinsic type. pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic< - ::Address, + ::Address, Encoded, - ::Signature, - Extra, + ::Signature, + <>::Extra as SignedExtra>::Extra, >; /// SignedPayload type. -pub type SignedPayload = sp_runtime::generic::SignedPayload>; +pub type SignedPayload = sp_runtime::generic::SignedPayload< + Encoded, + <>::Extra as SignedExtra>::Extra, +>; /// Creates a signed extrinsic pub async fn create_signed( @@ -68,22 +70,19 @@ pub async fn create_signed( signer: &(dyn Signer + Send + Sync), ) -> Result, Error> where - T: Runtime, - <>::Extra as SignedExtension>::AdditionalSigned: + T: Config + ExtrinsicExtraData, + <<>::Extra as SignedExtra>::Extra as SignedExtension>::AdditionalSigned: Send + Sync, { let spec_version = runtime_version.spec_version; let tx_version = runtime_version.transaction_version; - let extra: T::Extra = T::Extra::new(spec_version, tx_version, nonce, genesis_hash); + let extra = >::Extra::new( + spec_version, + tx_version, + nonce, + genesis_hash, + ); let payload = SignedPayload::::new(call, extra.extra())?; let signed = signer.sign(payload).await?; Ok(signed) } - -/// Creates an unsigned extrinsic -pub fn create_unsigned(call: Encoded) -> UncheckedExtrinsic -where - T: Runtime, -{ - UncheckedExtrinsic::::new_unsigned(call) -} diff --git a/src/extrinsic/signer.rs b/src/extrinsic/signer.rs index 357ac36042..36c42e01a5 100644 --- a/src/extrinsic/signer.rs +++ b/src/extrinsic/signer.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,17 +12,20 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . //! A library to **sub**mit e**xt**rinsics to a //! [substrate](https://github.com/paritytech/substrate) node via RPC. use super::{ - SignedExtra, SignedPayload, UncheckedExtrinsic, }; -use crate::runtimes::Runtime; +use crate::{ + Config, + ExtrinsicExtraData, + SignedExtra, +}; use codec::Encode; use sp_core::Pair; use sp_runtime::traits::{ @@ -33,7 +36,7 @@ use sp_runtime::traits::{ /// Extrinsic signer. #[async_trait::async_trait] -pub trait Signer { +pub trait Signer> { /// Returns the account id. fn account_id(&self) -> &T::AccountId; @@ -52,7 +55,7 @@ pub trait Signer { /// Extrinsic signer using a private key. #[derive(Clone, Debug)] -pub struct PairSigner { +pub struct PairSigner { account_id: T::AccountId, nonce: Option, signer: P, @@ -60,7 +63,7 @@ pub struct PairSigner { impl PairSigner where - T: Runtime, + T: Config + ExtrinsicExtraData, T::Signature: From, ::Signer: From + IdentifyAccount, @@ -96,9 +99,9 @@ where #[async_trait::async_trait] impl Signer for PairSigner where - T: Runtime, + T: Config + ExtrinsicExtraData, T::AccountId: Into + 'static, - <>::Extra as SignedExtension>::AdditionalSigned: Send, + <<>::Extra as SignedExtra>::Extra as SignedExtension>::AdditionalSigned: Send + Sync + 'static, P: Pair + 'static, P::Signature: Into + 'static, { diff --git a/src/frame/balances.rs b/src/frame/balances.rs deleted file mode 100644 index f88102a81f..0000000000 --- a/src/frame/balances.rs +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -//! Implements support for the pallet_balances module. - -use crate::frame::system::System; -use codec::{ - Decode, - Encode, -}; -use core::marker::PhantomData; -use frame_support::{ - traits::LockIdentifier, - Parameter, -}; -use sp_runtime::traits::{ - AtLeast32Bit, - MaybeSerialize, - Member, -}; -use std::fmt::Debug; - -/// The subset of the `pallet_balances::Trait` that a client must implement. -#[module] -pub trait Balances: System { - /// The balance of an account. - type Balance: Parameter - + Member - + AtLeast32Bit - + codec::Codec - + Default - + Copy - + MaybeSerialize - + Debug - + From<::BlockNumber>; -} - -/// All balance information for an account. -#[derive(Clone, Debug, Eq, PartialEq, Default, Decode, Encode)] -pub struct AccountData { - /// Non-reserved part of the balance. There may still be restrictions on this, but it is the - /// total pool what may in principle be transferred, reserved and used for tipping. - /// - /// This is the only balance that matters in terms of most operations on tokens. It - /// alone is used to determine the balance when in the contract execution environment. - pub free: Balance, - /// Balance which is reserved and may not be used at all. - /// - /// This can still get slashed, but gets slashed last of all. - /// - /// This balance is a 'reserve' balance that other subsystems use in order to set aside tokens - /// that are still 'owned' by the account holder, but which are suspendable. - pub reserved: Balance, - /// The amount that `free` may not drop below when withdrawing for *anything except transaction - /// fee payment*. - pub misc_frozen: Balance, - /// The amount that `free` may not drop below when withdrawing specifically for transaction - /// fee payment. - pub fee_frozen: Balance, -} - -/// The total issuance of the balances module. -#[derive(Clone, Debug, Eq, PartialEq, Store, Encode)] -pub struct TotalIssuanceStore { - #[store(returns = T::Balance)] - /// Runtime marker. - pub _runtime: PhantomData, -} - -/// The locks of the balances module. -#[derive(Clone, Debug, Eq, PartialEq, Store, Encode, Decode)] -pub struct LocksStore<'a, T: Balances> { - #[store(returns = Vec>)] - /// Account to retrieve the balance locks for. - pub account_id: &'a T::AccountId, -} - -/// A single lock on a balance. There can be many of these on an account and they "overlap", so the -/// same balance is frozen by multiple locks. -#[derive(Clone, PartialEq, Eq, Encode, Decode)] -pub struct BalanceLock { - /// An identifier for this lock. Only one lock may be in existence for each identifier. - pub id: LockIdentifier, - /// The amount which the free balance may not drop below when this lock is in effect. - pub amount: Balance, - /// If true, then the lock remains in effect even for payment of transaction fees. - pub reasons: Reasons, -} - -impl Debug for BalanceLock { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.debug_struct("BalanceLock") - .field("id", &String::from_utf8_lossy(&self.id)) - .field("amount", &self.amount) - .field("reasons", &self.reasons) - .finish() - } -} - -/// Simplified reasons for withdrawing balance. -#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, Debug)] -pub enum Reasons { - /// Paying system transaction fees. - Fee, - /// Any reason other than paying system transaction fees. - Misc, - /// Any reason at all. - All, -} - -/// Transfer some liquid free balance to another account. -/// -/// `transfer` will set the `FreeBalance` of the sender and receiver. -/// It will decrease the total issuance of the system by the `TransferFee`. -/// If the sender's account is below the existential deposit as a result -/// of the transfer, the account will be reaped. -#[derive(Clone, Debug, PartialEq, Call, Encode)] -pub struct TransferCall<'a, T: Balances> { - /// Destination of the transfer. - pub to: &'a ::Address, - /// Amount to transfer. - #[codec(compact)] - pub amount: T::Balance, -} - -/// Transfer event. -#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct TransferEvent { - /// Account balance was transfered from. - pub from: ::AccountId, - /// Account balance was transfered to. - pub to: ::AccountId, - /// Amount of balance that was transfered. - pub amount: T::Balance, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - error::{ - Error, - ModuleError, - RuntimeError, - }, - extrinsic::{ - PairSigner, - Signer, - }, - subscription::EventSubscription, - system::AccountStoreExt, - tests::{ - test_node_process, - TestRuntime, - }, - }; - use sp_core::{ - sr25519::Pair, - Pair as _, - }; - use sp_keyring::AccountKeyring; - - #[async_std::test] - async fn test_basic_transfer() { - env_logger::try_init().ok(); - let alice = PairSigner::::new(AccountKeyring::Alice.pair()); - let bob = PairSigner::::new(AccountKeyring::Bob.pair()); - let bob_address = bob.account_id().clone().into(); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - - let alice_pre = client.account(alice.account_id(), None).await.unwrap(); - let bob_pre = client.account(bob.account_id(), None).await.unwrap(); - - let event = client - .transfer_and_watch(&alice, &bob_address, 10_000) - .await - .expect("sending an xt works") - .transfer() - .unwrap() - .unwrap(); - let expected_event = TransferEvent { - from: alice.account_id().clone(), - to: bob.account_id().clone(), - amount: 10_000, - }; - assert_eq!(event, expected_event); - - let alice_post = client.account(alice.account_id(), None).await.unwrap(); - let bob_post = client.account(bob.account_id(), None).await.unwrap(); - - assert!(alice_pre.data.free - 10_000 >= alice_post.data.free); - assert_eq!(bob_pre.data.free + 10_000, bob_post.data.free); - } - - #[async_std::test] - async fn test_state_total_issuance() { - env_logger::try_init().ok(); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - let total_issuance = client.total_issuance(None).await.unwrap(); - assert_ne!(total_issuance, 0); - } - - #[async_std::test] - async fn test_state_read_free_balance() { - env_logger::try_init().ok(); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - let account = AccountKeyring::Alice.to_account_id(); - let info = client.account(&account, None).await.unwrap(); - assert_ne!(info.data.free, 0); - } - - #[async_std::test] - async fn test_state_balance_lock() -> Result<(), crate::Error> { - use crate::frame::staking::{ - BondCallExt, - RewardDestination, - }; - - env_logger::try_init().ok(); - let bob = PairSigner::::new(AccountKeyring::Bob.pair()); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - - client - .bond_and_watch( - &bob, - &AccountKeyring::Charlie.to_account_id().into(), - 100_000_000_000_000, - RewardDestination::Stash, - ) - .await?; - - let locks = client - .locks(&AccountKeyring::Bob.to_account_id(), None) - .await?; - - assert_eq!( - locks, - vec![BalanceLock { - id: *b"staking ", - amount: 100_000_000_000_000, - reasons: Reasons::All, - }] - ); - - Ok(()) - } - - #[async_std::test] - async fn test_transfer_error() { - env_logger::try_init().ok(); - let alice = PairSigner::::new(AccountKeyring::Alice.pair()); - let alice_addr = alice.account_id().clone().into(); - let hans = PairSigner::::new(Pair::generate().0); - let hans_address = hans.account_id().clone().into(); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - client - .transfer_and_watch(&alice, &hans_address, 100_000_000_000_000_000) - .await - .unwrap(); - let res = client - .transfer_and_watch(&hans, &alice_addr, 100_000_000_000_000_000) - .await; - - if let Err(Error::Runtime(RuntimeError::Module(error))) = res { - let error2 = ModuleError { - module: "Balances".into(), - error: "InsufficientBalance".into(), - }; - assert_eq!(error, error2); - } else { - panic!("expected an error"); - } - } - - #[async_std::test] - async fn test_transfer_subscription() { - env_logger::try_init().ok(); - let alice = PairSigner::::new(AccountKeyring::Alice.pair()); - let bob = AccountKeyring::Bob.to_account_id(); - let bob_addr = bob.clone().into(); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - let sub = client.subscribe_events().await.unwrap(); - let decoder = client.events_decoder(); - let mut sub = EventSubscription::::new(sub, &decoder); - sub.filter_event::>(); - client.transfer(&alice, &bob_addr, 10_000).await.unwrap(); - let raw = sub.next().await.unwrap().unwrap(); - let event = TransferEvent::::decode(&mut &raw.data[..]).unwrap(); - assert_eq!( - event, - TransferEvent { - from: alice.account_id().clone(), - to: bob.clone(), - amount: 10_000, - } - ); - } -} diff --git a/src/frame/contracts.rs b/src/frame/contracts.rs deleted file mode 100644 index ae7e894262..0000000000 --- a/src/frame/contracts.rs +++ /dev/null @@ -1,293 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -//! Implements support for the pallet_contracts module. - -use crate::frame::{ - balances::Balances, - system::System, -}; -use codec::{ - Decode, - Encode, -}; - -/// Gas units are chosen to be represented by u64 so that gas metering -/// instructions can operate on them efficiently. -pub type Gas = u64; - -/// The subset of the `pallet_contracts::Trait` that a client must implement. -#[module] -pub trait Contracts: System + Balances {} - -/// Instantiates a new contract from the supplied `code` optionally transferring -/// some balance. -/// -/// This is the only function that can deploy new code to the chain. -/// -/// Instantiation is executed as follows: -/// -/// - The supplied `code` is instrumented, deployed, and a `code_hash` is created for that code. -/// - If the `code_hash` already exists on the chain the underlying `code` will be shared. -/// - The destination address is computed based on the sender, code_hash and the salt. -/// - The smart-contract account is created at the computed address. -/// - The `endowment` is transferred to the new account. -/// - The `deploy` function is executed in the context of the newly-created account. -#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] -pub struct InstantiateWithCodeCall<'a, T: Contracts> { - /// The balance to transfer from the `origin` to the newly created contract. - #[codec(compact)] - pub endowment: ::Balance, - /// The gas limit enforced when executing the constructor. - #[codec(compact)] - pub gas_limit: Gas, - /// The contract code to deploy in raw bytes. - pub code: &'a [u8], - /// The input data to pass to the contract constructor. - pub data: &'a [u8], - /// Used for the address derivation. - pub salt: &'a [u8], -} - -/// Instantiates a contract from a previously deployed wasm binary. -/// -/// This function is identical to [`InstantiateWithCodeCall`] but without the -/// code deployment step. Instead, the `code_hash` of an on-chain deployed wasm binary -/// must be supplied. -#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] -pub struct InstantiateCall<'a, T: Contracts> { - /// The balance to transfer from the `origin` to the newly created contract. - #[codec(compact)] - pub endowment: ::Balance, - /// The gas limit enforced when executing the constructor. - #[codec(compact)] - pub gas_limit: Gas, - /// Code hash of the already deployed on-chain deployed wasm binary. - pub code_hash: &'a ::Hash, - /// Data to initialize the contract with. - pub data: &'a [u8], - /// Used for the address derivation. - pub salt: &'a [u8], -} - -/// Makes a call to an account, optionally transferring some balance. -/// -/// * If the account is a smart-contract account, the associated code will be -/// executed and any value will be transferred. -/// * If the account is a regular account, any value will be transferred. -/// * If no account exists and the call value is not less than `existential_deposit`, -/// a regular account will be created and any value will be transferred. -#[derive(Clone, Debug, PartialEq, Call, Encode)] -pub struct CallCall<'a, T: Contracts> { - /// Address of the contract. - pub dest: &'a ::Address, - /// Value to transfer to the contract. - #[codec(compact)] - pub value: ::Balance, - /// Gas limit. - #[codec(compact)] - pub gas_limit: Gas, - /// Data to send to the contract. - pub data: &'a [u8], -} - -/// Code stored event. -#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct CodeStoredEvent { - /// Code hash of the contract. - pub code_hash: T::Hash, -} - -/// Instantiated event. -#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct InstantiatedEvent { - /// Caller that instantiated the contract. - pub caller: ::AccountId, - /// The address of the contract. - pub contract: ::AccountId, -} - -/// Contract execution event. -/// -/// Emitted upon successful execution of a contract, if any contract events were produced. -#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct ContractExecutionEvent { - /// Caller of the contract. - pub caller: ::AccountId, - /// SCALE encoded contract event data. - pub data: Vec, -} - -#[cfg(test)] -mod tests { - use sp_keyring::AccountKeyring; - - use super::*; - use crate::{ - tests::{ - test_node_process, - TestNodeProcess, - TestRuntime, - }, - Client, - Error, - ExtrinsicSuccess, - PairSigner, - }; - use sp_core::sr25519::Pair; - - struct TestContext { - node_process: TestNodeProcess, - signer: PairSigner, - } - - impl TestContext { - async fn init() -> Self { - env_logger::try_init().ok(); - - let node_process = test_node_process().await; - let signer = PairSigner::new(AccountKeyring::Alice.pair()); - - TestContext { - node_process, - signer, - } - } - - async fn instantiate_with_code( - &self, - ) -> Result, Error> { - const CONTRACT: &str = r#" - (module - (func (export "call")) - (func (export "deploy")) - ) - "#; - let code = wabt::wat2wasm(CONTRACT).expect("invalid wabt"); - - let result = self - .client() - .instantiate_with_code_and_watch( - &self.signer, - 100_000_000_000_000_000, // endowment - 500_000_000_000, // gas_limit - &code, - &[], // data - &[], // salt - ) - .await?; - let event = result.code_stored()?.ok_or_else(|| { - Error::Other("Failed to find a CodeStored event".into()) - })?; - log::info!("Code hash: {:?}", event.code_hash); - Ok(event) - } - - async fn instantiate( - &self, - code_hash: &::Hash, - data: &[u8], - salt: &[u8], - ) -> Result, Error> { - // call instantiate extrinsic - let result = self - .client() - .instantiate_and_watch( - &self.signer, - 100_000_000_000_000_000, // endowment - 500_000_000_000, // gas_limit - code_hash, - data, - salt, - ) - .await?; - - log::info!("Instantiate result: {:?}", result); - let instantiated = result.instantiated()?.ok_or_else(|| { - Error::Other("Failed to find a Instantiated event".into()) - })?; - - Ok(instantiated) - } - - async fn call( - &self, - contract: &::Address, - input_data: &[u8], - ) -> Result, Error> { - let result = self - .client() - .call_and_watch( - &self.signer, - contract, - 0, // value - 500_000_000, // gas_limit - input_data, - ) - .await?; - log::info!("Call result: {:?}", result); - Ok(result) - } - - fn client(&self) -> &Client { - self.node_process.client() - } - } - - #[async_std::test] - async fn tx_instantiate_with_code() { - let ctx = TestContext::init().await; - let code_stored = ctx.instantiate_with_code().await; - - assert!( - code_stored.is_ok(), - format!( - "Error calling instantiate_with_code and receiving CodeStored Event: {:?}", - code_stored - ) - ); - } - - #[async_std::test] - async fn tx_instantiate() { - let ctx = TestContext::init().await; - let code_stored = ctx.instantiate_with_code().await.unwrap(); - - let instantiated = ctx.instantiate(&code_stored.code_hash, &[], &[1u8]).await; - - assert!( - instantiated.is_ok(), - format!("Error instantiating contract: {:?}", instantiated) - ); - } - - #[async_std::test] - async fn tx_call() { - let ctx = TestContext::init().await; - let code_stored = ctx.instantiate_with_code().await.unwrap(); - - let instantiated = ctx - .instantiate(&code_stored.code_hash.into(), &[], &[1u8]) - .await - .unwrap(); - let executed = ctx.call(&instantiated.contract.into(), &[]).await; - - assert!( - executed.is_ok(), - format!("Error calling contract: {:?}", executed) - ); - } -} diff --git a/src/frame/mod.rs b/src/frame/mod.rs deleted file mode 100644 index 3f5781f098..0000000000 --- a/src/frame/mod.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -//! Implements support for built-in runtime modules. - -use crate::metadata::{ - Metadata, - MetadataError, -}; -use codec::{ - Decode, - Encode, -}; -use sp_core::storage::StorageKey; - -pub mod balances; -pub mod contracts; -pub mod session; -pub mod staking; -pub mod sudo; -pub mod system; - -/// Store trait. -pub trait Store: Encode { - /// Module name. - const MODULE: &'static str; - /// Field name. - const FIELD: &'static str; - /// Return type. - type Returns: Decode; - /// Returns the key prefix for storage maps - fn prefix(metadata: &Metadata) -> Result; - /// Returns the `StorageKey`. - fn key(&self, metadata: &Metadata) -> Result; - /// Returns the default value. - fn default(&self, metadata: &Metadata) -> Result { - Ok(metadata - .module(Self::MODULE)? - .storage(Self::FIELD)? - .default()?) - } -} - -/// Call trait. -pub trait Call: Encode { - /// Module name. - const MODULE: &'static str; - /// Function name. - const FUNCTION: &'static str; -} - -/// Event trait. -pub trait Event: Decode { - /// Module name. - const MODULE: &'static str; - /// Event name. - const EVENT: &'static str; -} diff --git a/src/frame/session.rs b/src/frame/session.rs deleted file mode 100644 index 2faa6ae979..0000000000 --- a/src/frame/session.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -//! Session support -use crate::frame::{ - balances::Balances, - system::System, -}; -use codec::Encode; -use frame_support::Parameter; -use sp_runtime::traits::{ - Member, - OpaqueKeys, -}; -use std::{ - fmt::Debug, - marker::PhantomData, -}; -use substrate_subxt_proc_macro::Store; - -/// Impls `Default::default` for some types that have a `_runtime` field of type -/// `PhantomData` as their only field. -macro_rules! default_impl { - ($name:ident) => { - impl Default for $name { - fn default() -> Self { - Self { - _runtime: PhantomData, - } - } - } - }; -} - -type IdentificationTuple = ( - ::ValidatorId, - pallet_staking::Exposure<::AccountId, ::Balance>, -); - -/// The trait needed for this module. -#[module] -pub trait Session: System + Balances { - #![event_alias(IdentificationTuple = IdentificationTuple)] - #![event_alias(OpaqueTimeSlot = Vec)] - #![event_alias(SessionIndex = u32)] - - /// The validator account identifier type for the runtime. - type ValidatorId: Parameter + Debug + Ord + Default + Send + Sync + 'static; - - /// The keys. - type Keys: OpaqueKeys + Member + Parameter + Default; -} - -/// The current set of validators. -#[derive(Encode, Store, Debug)] -pub struct ValidatorsStore { - #[store(returns = Vec<::ValidatorId>)] - /// Marker for the runtime - pub _runtime: PhantomData, -} - -/// The queued keys for the next session. -#[derive(Encode, Store, Debug)] -pub struct QueuedKeysStore { - #[store(returns = Vec<(::ValidatorId, T::Keys)>)] - /// Marker for the runtime - pub _runtime: PhantomData, -} - -/// The next session keys for a validator. -#[derive(Encode, Store, Debug)] -pub struct NextKeysStore<'a, T: Session> { - #[store(returns = Option<::Keys>)] - /// The validator account. - pub validator_id: &'a ::ValidatorId, -} - -default_impl!(ValidatorsStore); - -/// Set the session keys for a validator. -#[derive(Encode, Call, Debug)] -pub struct SetKeysCall { - /// The keys - pub keys: T::Keys, - /// The proof. This is not currently used and can be set to an empty vector. - pub proof: Vec, -} diff --git a/src/frame/staking.rs b/src/frame/staking.rs deleted file mode 100644 index 37cedffbce..0000000000 --- a/src/frame/staking.rs +++ /dev/null @@ -1,435 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -//! Implements support for the pallet_staking module. - -use super::balances::Balances; -use codec::{ - Decode, - Encode, -}; - -use std::{ - collections::BTreeMap, - fmt::Debug, - marker::PhantomData, -}; - -pub use pallet_staking::{ - ActiveEraInfo, - EraIndex, - Exposure, - Nominations, - RewardDestination, - RewardPoint, - StakingLedger, - ValidatorPrefs, -}; - -/// Rewards for the last `HISTORY_DEPTH` eras. -/// If reward hasn't been set or has been removed then 0 reward is returned. -#[derive(Clone, Encode, Decode, Debug, Store)] -pub struct ErasRewardPointsStore { - #[store(returns = EraRewardPoints)] - /// Era index - pub index: EraIndex, - /// Marker for the runtime - pub _phantom: PhantomData, -} - -/// Preference of what happens regarding validation. -#[derive(Clone, Encode, Decode, Debug, Call)] -pub struct SetPayeeCall { - /// The payee - pub payee: RewardDestination, - /// Marker for the runtime - pub _runtime: PhantomData, -} - -/// The subset of the `frame::Trait` that a client must implement. -#[module] -#[rustfmt::skip] -pub trait Staking: Balances { - #![event_alias(ElectionCompute = u8)] - #![event_type(EraIndex)] -} - -/// Number of eras to keep in history. -/// -/// Information is kept for eras in `[current_era - history_depth; current_era]`. -/// -/// Must be more than the number of eras delayed by session otherwise. -/// I.e. active era must always be in history. -/// I.e. `active_era > current_era - history_depth` must be guaranteed. -#[derive(Encode, Decode, Copy, Clone, Debug, Default, Store)] -pub struct HistoryDepthStore { - #[store(returns = u32)] - /// Marker for the runtime - pub _runtime: PhantomData, -} - -/// Map from all locked "stash" accounts to the controller account. -#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)] -pub struct BondedStore { - #[store(returns = Option)] - /// Tٗhe stash account - pub stash: T::AccountId, -} - -/// Map from all (unlocked) "controller" accounts to the info regarding the staking. -#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)] -pub struct LedgerStore { - #[store(returns = Option>)] - /// The controller account - pub controller: T::AccountId, -} - -/// Where the reward payment should be made. Keyed by stash. -#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)] -pub struct PayeeStore { - #[store(returns = RewardDestination)] - /// Tٗhe stash account - pub stash: T::AccountId, -} - -/// The map from (wannabe) validator stash key to the preferences of that validator. -#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Store)] -pub struct ValidatorsStore { - #[store(returns = ValidatorPrefs)] - /// Tٗhe stash account - pub stash: T::AccountId, -} - -/// The map from nominator stash key to the set of stash keys of all validators to nominate. -#[derive(Encode, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Store)] -pub struct NominatorsStore { - #[store(returns = Option>)] - /// Tٗhe stash account - pub stash: T::AccountId, -} - -/// The current era index. -/// -/// This is the latest planned era, depending on how the Session pallet queues the validator -/// set, it might be active or not. -#[derive(Encode, Copy, Clone, Debug, Store)] -pub struct CurrentEraStore { - #[store(returns = Option)] - /// Marker for the runtime - pub _runtime: PhantomData, -} - -/// Reward points of an era. Used to split era total payout between validators. -/// -/// This points will be used to reward validators and their respective nominators. -#[derive(PartialEq, Encode, Decode, Default, Debug)] -pub struct EraRewardPoints { - /// Total number of points. Equals the sum of reward points for each validator. - pub total: RewardPoint, - /// The reward points earned by a given validator. - pub individual: BTreeMap, -} - -/// Declare no desire to either validate or nominate. -/// -/// Effective at the beginning of the next era. -/// -/// The dispatch origin for this call must be _Signed_ by the controller, not the stash. -/// Can only be called when [`EraElectionStatus`] is `Closed`. -#[derive(Debug, Call, Encode)] -pub struct ChillCall { - /// Runtime marker - pub _runtime: PhantomData, -} - -impl Default for ChillCall { - fn default() -> Self { - Self { - _runtime: PhantomData, - } - } -} -impl Clone for ChillCall { - fn clone(&self) -> Self { - Self { - _runtime: self._runtime, - } - } -} -impl Copy for ChillCall {} - -/// Declare the desire to validate for the origin controller. -/// -/// Effective at the beginning of the next era. -/// -/// The dispatch origin for this call must be _Signed_ by the controller, not the stash. -/// Can only be called when [`EraElectionStatus`] is `Closed`. -#[derive(Clone, Debug, PartialEq, Call, Encode)] -pub struct ValidateCall { - /// Runtime marker - pub _runtime: PhantomData, - /// Validation preferences - pub prefs: ValidatorPrefs, -} - -/// Declare the desire to nominate `targets` for the origin controller. -/// -/// Effective at the beginning of the next era. -/// -/// The dispatch origin for this call must be _Signed_ by the controller, not the stash. -/// Can only be called when [`EraElectionStatus`] is `Closed`. -#[derive(Call, Encode, Debug)] -pub struct NominateCall { - /// The targets that are being nominated - pub targets: Vec, -} - -/// Take the origin account as a stash and lock up `value` of its balance. -/// `controller` will be the account that controls it. -#[derive(Call, Encode, Debug)] -pub struct BondCall<'a, T: Staking> { - /// Tٗhe controller account - pub controller: &'a T::Address, - /// Lock up `value` of its balance. - #[codec(compact)] - pub value: T::Balance, - /// Destination of Staking reward. - pub payee: RewardDestination, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - error::RuntimeError, - extrinsic::{ - PairSigner, - Signer, - }, - frame::balances::*, - tests::{ - test_node_process, - TestRuntime, - }, - Error, - ExtrinsicSuccess, - }; - use assert_matches::assert_matches; - use sp_core::{ - sr25519, - Pair, - }; - use sp_keyring::AccountKeyring; - - /// Helper function to generate a crypto pair from seed - fn get_from_seed(seed: &str) -> sr25519::Pair { - sr25519::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - } - - #[async_std::test] - async fn test_validate_with_controller_account() -> Result<(), Error> { - env_logger::try_init().ok(); - let alice = PairSigner::::new(AccountKeyring::Alice.pair()); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - let announce_validator = client - .validate_and_watch(&alice, ValidatorPrefs::default()) - .await; - assert_matches!(announce_validator, Ok(ExtrinsicSuccess {block: _, extrinsic: _, events}) => { - // TOOD: this is unsatisfying – can we do better? - assert_eq!(events.len(), 2); - }); - - Ok(()) - } - - #[async_std::test] - async fn test_validate_not_possible_for_stash_account() -> Result<(), Error> { - env_logger::try_init().ok(); - let alice_stash = - PairSigner::::new(get_from_seed("Alice//stash")); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - let announce_validator = client - .validate_and_watch(&alice_stash, ValidatorPrefs::default()) - .await; - assert_matches!(announce_validator, Err(Error::Runtime(RuntimeError::Module(module_err))) => { - assert_eq!(module_err.module, "Staking"); - assert_eq!(module_err.error, "NotController"); - }); - Ok(()) - } - - #[async_std::test] - async fn test_nominate_with_controller_account() -> Result<(), Error> { - env_logger::try_init().ok(); - let alice = PairSigner::::new(AccountKeyring::Alice.pair()); - let bob = PairSigner::::new(AccountKeyring::Bob.pair()); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - - let nomination = client - .nominate_and_watch(&alice, vec![bob.account_id().clone().into()]) - .await; - assert_matches!(nomination, Ok(ExtrinsicSuccess {block: _, extrinsic: _, events}) => { - // TOOD: this is unsatisfying – can we do better? - assert_eq!(events.len(), 2); - }); - Ok(()) - } - - #[async_std::test] - async fn test_nominate_not_possible_for_stash_account() -> Result<(), Error> { - env_logger::try_init().ok(); - let alice_stash = - PairSigner::::new(get_from_seed("Alice//stash")); - let bob = PairSigner::::new(AccountKeyring::Bob.pair()); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - - let nomination = client - .nominate_and_watch(&alice_stash, vec![bob.account_id().clone().into()]) - .await; - assert_matches!(nomination, Err(Error::Runtime(RuntimeError::Module(module_err))) => { - assert_eq!(module_err.module, "Staking"); - assert_eq!(module_err.error, "NotController"); - }); - Ok(()) - } - - #[async_std::test] - async fn test_chill_works_for_controller_only() -> Result<(), Error> { - env_logger::try_init().ok(); - let alice_stash = - PairSigner::::new(get_from_seed("Alice//stash")); - let bob_stash = - PairSigner::::new(get_from_seed("Bob//stash")); - let alice = PairSigner::::new(AccountKeyring::Alice.pair()); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - - // this will fail the second time, which is why this is one test, not two - client - .nominate_and_watch(&alice, vec![bob_stash.account_id().clone().into()]) - .await?; - let store = LedgerStore { - controller: alice.account_id().clone(), - }; - let StakingLedger { stash, .. } = client.fetch(&store, None).await?.unwrap(); - assert_eq!(alice_stash.account_id(), &stash); - let chill = client.chill_and_watch(&alice_stash).await; - - assert_matches!(chill, Err(Error::Runtime(RuntimeError::Module(module_err))) => { - assert_eq!(module_err.module, "Staking"); - assert_eq!(module_err.error, "NotController"); - }); - - let chill = client.chill_and_watch(&alice).await; - assert_matches!(chill, Ok(ExtrinsicSuccess {block: _, extrinsic: _, events}) => { - // TOOD: this is unsatisfying – can we do better? - assert_eq!(events.len(), 2); - }); - Ok(()) - } - - #[async_std::test] - async fn test_bond() -> Result<(), Error> { - env_logger::try_init().ok(); - let alice = PairSigner::::new(AccountKeyring::Alice.pair()); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - - let bond = client - .bond_and_watch( - &alice, - &AccountKeyring::Bob.to_account_id().into(), - 100_000_000_000_000, - RewardDestination::Stash, - ) - .await; - - assert_matches!(bond, Ok(ExtrinsicSuccess {block: _, extrinsic: _, events}) => { - // TOOD: this is unsatisfying – can we do better? - assert_eq!(events.len(), 3); - }); - - let bond_again = client - .bond_and_watch( - &alice, - &AccountKeyring::Bob.to_account_id().into(), - 100_000_000_000, - RewardDestination::Stash, - ) - .await; - - assert_matches!(bond_again, Err(Error::Runtime(RuntimeError::Module(module_err))) => { - assert_eq!(module_err.module, "Staking"); - assert_eq!(module_err.error, "AlreadyBonded"); - }); - - Ok(()) - } - - #[async_std::test] - async fn test_total_issuance_is_okay() -> Result<(), Error> { - env_logger::try_init().ok(); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - let total_issuance = client.total_issuance(None).await?; - assert!(total_issuance > 1u128 << 32); - Ok(()) - } - - #[async_std::test] - async fn test_history_depth_is_okay() -> Result<(), Error> { - env_logger::try_init().ok(); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - let history_depth = client.history_depth(None).await?; - assert_eq!(history_depth, 84); - Ok(()) - } - - #[async_std::test] - async fn test_current_era_is_okay() -> Result<(), Error> { - env_logger::try_init().ok(); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - let _current_era = client - .current_era(None) - .await? - .expect("current era always exists"); - Ok(()) - } - - #[async_std::test] - async fn test_era_reward_points_is_okay() -> Result<(), Error> { - env_logger::try_init().ok(); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - let store = ErasRewardPointsStore { - _phantom: PhantomData, - index: 0, - }; - - let current_era_result = client.fetch(&store, None).await?; - - assert_matches!(current_era_result, Some(_)); - - Ok(()) - } -} diff --git a/src/frame/sudo.rs b/src/frame/sudo.rs deleted file mode 100644 index 31fe409919..0000000000 --- a/src/frame/sudo.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -//! Implements support for the frame_sudo module. - -use crate::{ - frame::system::System, - Encoded, -}; -use codec::Encode; -use core::marker::PhantomData; -use frame_support::weights::Weight; - -/// The subset of the `frame_sudo::Trait` that a client must implement. -#[module] -pub trait Sudo: System {} - -/// Execute a transaction with sudo permissions. -#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] -pub struct SudoCall<'a, T: Sudo> { - /// Runtime marker. - pub _runtime: PhantomData, - /// Encoded transaction. - pub call: &'a Encoded, -} - -/// Execute a transaction with sudo permissions without checking the call weight. -#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] -pub struct SudoUncheckedWeightCall<'a, T: Sudo> { - /// Runtime marker. - pub _runtime: PhantomData, - /// Encoded transaction. - pub call: &'a Encoded, - /// Call weight. - /// - /// This argument is actually unused in runtime, you can pass any value of - /// `Weight` type when using this call. - pub weight: Weight, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - error::{ - Error, - RuntimeError, - }, - extrinsic::PairSigner, - frame::balances::TransferCall, - tests::{ - test_node_process, - TestRuntime, - }, - }; - use sp_keyring::AccountKeyring; - - #[async_std::test] - async fn test_sudo() { - env_logger::try_init().ok(); - let alice = PairSigner::::new(AccountKeyring::Alice.pair()); - let bob = AccountKeyring::Bob.to_account_id().clone().into(); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - - let call = client - .encode(TransferCall { - to: &bob, - amount: 10_000, - }) - .unwrap(); - - let res = client.sudo_and_watch(&alice, &call).await; - assert!( - if let Err(Error::Runtime(RuntimeError::BadOrigin)) = res { - true - } else { - false - } - ); - } - - #[async_std::test] - async fn test_sudo_unchecked_weight() { - env_logger::try_init().ok(); - let alice = PairSigner::::new(AccountKeyring::Alice.pair()); - let bob = AccountKeyring::Bob.to_account_id().into(); - let test_node_proc = test_node_process().await; - let client = test_node_proc.client(); - - let call = client - .encode(TransferCall { - to: &bob, - amount: 10_000, - }) - .unwrap(); - - let res = client - .sudo_unchecked_weight_and_watch(&alice, &call, 0u64) - .await; - assert!( - if let Err(Error::Runtime(RuntimeError::BadOrigin)) = res { - true - } else { - false - } - ); - } -} diff --git a/src/frame/system.rs b/src/frame/system.rs deleted file mode 100644 index a94904cdf3..0000000000 --- a/src/frame/system.rs +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -//! Implements support for the frame_system module. - -use codec::{ - Codec, - Decode, - Encode, -}; -use core::marker::PhantomData; -use frame_support::{ - weights::DispatchInfo, - Parameter, -}; -use serde::de::DeserializeOwned; -use sp_runtime::{ - traits::{ - AtLeast32Bit, - AtLeast32BitUnsigned, - Bounded, - CheckEqual, - Extrinsic, - Hash, - Header, - MaybeDisplay, - MaybeMallocSizeOf, - MaybeSerialize, - MaybeSerializeDeserialize, - Member, - SimpleBitOps, - }, - DispatchError, -}; -use std::fmt::Debug; - -/// The subset of the `frame::Trait` that a client must implement. -#[module] -pub trait System { - /// Account index (aka nonce) type. This stores the number of previous - /// transactions associated with a sender account. - type Index: Parameter - + Member - + MaybeSerialize - + Debug - + Default - + MaybeDisplay - + AtLeast32Bit - + Copy; - - /// The block number type used by the runtime. - type BlockNumber: Parameter - + Member - + MaybeMallocSizeOf - + MaybeSerializeDeserialize - + Debug - + MaybeDisplay - + AtLeast32BitUnsigned - + Default - + Bounded - + Copy - + std::hash::Hash - + std::str::FromStr; - - /// The output of the `Hashing` function. - type Hash: Parameter - + Member - + MaybeMallocSizeOf - + MaybeSerializeDeserialize - + Debug - + MaybeDisplay - + Ord - + SimpleBitOps - + Default - + Copy - + CheckEqual - + std::hash::Hash - + AsRef<[u8]> - + AsMut<[u8]>; - - /// The hashing system (algorithm) being used in the runtime (e.g. Blake2). - #[module(ignore)] - type Hashing: Hash; - - /// The user account identifier type for the runtime. - type AccountId: Parameter + Member + MaybeSerialize + MaybeDisplay + Ord + Default; - - /// The address type. This instead of `::Source`. - #[module(ignore)] - type Address: Codec + Clone + PartialEq + Debug + Send + Sync; - - /// The block header. - #[module(ignore)] - type Header: Parameter - + Header - + DeserializeOwned; - - /// Extrinsic type within blocks. - #[module(ignore)] - type Extrinsic: Parameter + Member + Extrinsic + Debug + MaybeSerializeDeserialize; - - /// Data to be associated with an account (other than nonce/transaction counter, which this - /// module does regardless). - type AccountData: Member + Codec + Clone + Default; -} - -/// Type used to encode the number of references an account has. -pub type RefCount = u32; - -/// Information of an account. -#[derive(Clone, Debug, Eq, PartialEq, Default, Decode, Encode)] -pub struct AccountInfo { - /// The number of transactions this account has sent. - pub nonce: T::Index, - /// The number of other modules that currently depend on this account's existence. The account - /// cannot be reaped until this is zero. - pub consumers: RefCount, - /// The number of other modules that allow this account to exist. The account may not be reaped - /// until this is zero. - pub providers: RefCount, - /// The additional data that belongs to this account. Used to store the balance(s) in a lot of - /// chains. - pub data: T::AccountData, -} - -/// Account field of the `System` module. -#[derive(Clone, Debug, Eq, PartialEq, Store, Encode)] -pub struct AccountStore<'a, T: System> { - #[store(returns = AccountInfo)] - /// Account to retrieve the `AccountInfo` for. - pub account_id: &'a T::AccountId, -} - -/// Arguments for updating the runtime code -#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] -pub struct SetCodeCall<'a, T: System> { - /// Runtime marker. - pub _runtime: PhantomData, - /// Runtime wasm blob. - pub code: &'a [u8], -} - -/// Arguments for updating the runtime code without checks -#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] -pub struct SetCodeWithoutChecksCall<'a, T: System> { - /// Runtime marker. - pub _runtime: PhantomData, - /// Runtime wasm blob. - pub code: &'a [u8], -} - -/// A phase of a block's execution. -#[derive(Clone, Debug, Eq, PartialEq, Decode)] -pub enum Phase { - /// Applying an extrinsic. - ApplyExtrinsic(u32), - /// Finalizing the block. - Finalization, - /// Initializing the block. - Initialization, -} - -/// An extrinsic completed successfully. -#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct ExtrinsicSuccessEvent { - /// Runtime marker. - pub _runtime: PhantomData, - /// The dispatch info. - pub info: DispatchInfo, -} - -/// An extrinsic failed. -#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct ExtrinsicFailedEvent { - /// Runtime marker. - pub _runtime: PhantomData, - /// The dispatch error. - pub error: DispatchError, - /// The dispatch info. - pub info: DispatchInfo, -} - -/// `:code` was updated. -#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct CodeUpdatedEvent { - /// Runtime marker. - pub _runtime: PhantomData, -} - -/// A new account was created. -#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct NewAccountEvent { - /// Created account id. - pub account: T::AccountId, -} - -/// An account was reaped. -#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct KilledAccountEvent { - /// Killed account id. - pub account: T::AccountId, -} diff --git a/src/lib.rs b/src/lib.rs index 961056e445..95119878b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,7 +12,7 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . //! A library to **sub**mit e**xt**rinsics to a //! [substrate](https://github.com/paritytech/substrate) node via RPC. @@ -40,68 +40,63 @@ )] #![allow(clippy::type_complexity)] -#[macro_use] -extern crate substrate_subxt_proc_macro; +pub use frame_metadata::StorageHasher; +pub use subxt_macro::subxt; +pub use bitvec; +pub use codec; +pub use sp_arithmetic; pub use sp_core; pub use sp_runtime; use codec::{ - Codec, Decode, + DecodeAll, + Encode, }; -use futures::future; -use jsonrpsee_http_client::HttpClientBuilder; -use jsonrpsee_types::Subscription; -use jsonrpsee_ws_client::WsClientBuilder; -use sp_core::{ - storage::{ - StorageChangeSet, - StorageData, - StorageKey, - }, - Bytes, -}; -pub use sp_runtime::traits::SignedExtension; -pub use sp_version::RuntimeVersion; -use std::{ +use core::{ + fmt::Debug, marker::PhantomData, - sync::Arc, }; +mod client; +mod config; mod error; mod events; pub mod extrinsic; -mod frame; mod metadata; -mod rpc; -mod runtimes; +pub mod rpc; +pub mod storage; mod subscription; -#[cfg(test)] -mod tests; pub use crate::{ + client::{ + Client, + ClientBuilder, + SubmittableExtrinsic, + }, + config::{ + AccountData, + Config, + ExtrinsicExtraData, + }, error::{ Error, - ModuleError, + PalletError, RuntimeError, }, events::{ - EventTypeRegistry, EventsDecoder, RawEvent, }, extrinsic::{ + DefaultExtra, PairSigner, SignedExtra, Signer, UncheckedExtrinsic, }, - frame::*, - metadata::{ - Metadata, - MetadataError, - }, + metadata::Metadata, rpc::{ BlockNumber, ExtrinsicSuccess, @@ -109,553 +104,102 @@ pub use crate::{ RpcClient, SystemProperties, }, - runtimes::*, + storage::{ + KeyIter, + StorageEntry, + StorageEntryKey, + StorageMapKey, + }, subscription::{ EventStorageSubscription, EventSubscription, FinalizedEventStorageSubscription, }, - substrate_subxt_proc_macro::*, -}; -use crate::{ - frame::system::{ - AccountStoreExt, - Phase, - System, - }, - rpc::{ - ChainBlock, - Rpc, - }, }; -/// ClientBuilder for constructing a Client. -#[derive(Default)] -pub struct ClientBuilder { - url: Option, - client: Option, - page_size: Option, - event_type_registry: EventTypeRegistry, - skip_type_sizes_check: bool, - accept_weak_inclusion: bool, -} +/// Call trait. +pub trait Call: Encode { + /// Pallet name. + const PALLET: &'static str; + /// Function name. + const FUNCTION: &'static str; -impl ClientBuilder { - /// Creates a new ClientBuilder. - pub fn new() -> Self { - Self { - url: None, - client: None, - page_size: None, - event_type_registry: EventTypeRegistry::new(), - skip_type_sizes_check: false, - accept_weak_inclusion: false, - } + /// Returns true if the given pallet and function names match this call. + fn is_call(pallet: &str, function: &str) -> bool { + Self::PALLET == pallet && Self::FUNCTION == function } +} - /// Sets the jsonrpsee client. - pub fn set_client>(mut self, client: C) -> Self { - self.client = Some(client.into()); - self - } - - /// Set the substrate rpc address. - pub fn set_url>(mut self, url: P) -> Self { - self.url = Some(url.into()); - self - } - - /// Set the page size. - pub fn set_page_size(mut self, size: u32) -> Self { - self.page_size = Some(size); - self - } - - /// Register a custom type segmenter, for consuming types in events where the size cannot - /// be inferred from the metadata. - /// - /// # Panics - /// - /// If there is already a type size registered with this name. - pub fn register_type_size(mut self, name: &str) -> Self - where - U: Codec + Send + Sync + 'static, - { - self.event_type_registry.register_type_size::(name); - self - } - - /// Disable the check for missing type sizes on `build`. - /// - /// *WARNING* can lead to runtime errors if receiving events with unknown types. - pub fn skip_type_sizes_check(mut self) -> Self { - self.skip_type_sizes_check = true; - self - } - - /// Only check that transactions are InBlock on submit. - pub fn accept_weak_inclusion(mut self) -> Self { - self.accept_weak_inclusion = true; - self - } - - /// Creates a new Client. - pub async fn build<'a>(self) -> Result, Error> { - let client = if let Some(client) = self.client { - client - } else { - let url = self.url.as_deref().unwrap_or("ws://127.0.0.1:9944"); - if url.starts_with("ws://") || url.starts_with("wss://") { - let client = WsClientBuilder::default() - .max_notifs_per_subscription(4096) - .build(url) - .await?; - RpcClient::WebSocket(Arc::new(client)) - } else { - let client = HttpClientBuilder::default().build(&url)?; - RpcClient::Http(Arc::new(client)) - } - }; - let mut rpc = Rpc::new(client); - if self.accept_weak_inclusion { - rpc.accept_weak_inclusion(); - } - let (metadata, genesis_hash, runtime_version, properties) = future::join4( - rpc.metadata(), - rpc.genesis_hash(), - rpc.runtime_version(None), - rpc.system_properties(), - ) - .await; - let metadata = metadata?; - - if let Err(missing) = self.event_type_registry.check_missing_type_sizes(&metadata) - { - if self.skip_type_sizes_check { - log::warn!( - "The following types do not have registered type segmenters: {:?} \ - If any events containing these types are received, this can cause a \ - `TypeSizeUnavailable` error and prevent decoding the actual event \ - being listened for.\ - \ - Use `ClientBuilder::register_type_size` to register missing type sizes.", - missing - ); - } else { - return Err(Error::MissingTypeSizes(missing.into_iter().collect())) - } - } - - let events_decoder = - EventsDecoder::new(metadata.clone(), self.event_type_registry); +/// Event trait. +pub trait Event: Decode { + /// Pallet name. + const PALLET: &'static str; + /// Event name. + const EVENT: &'static str; - Ok(Client { - rpc, - genesis_hash: genesis_hash?, - metadata, - events_decoder, - properties: properties.unwrap_or_else(|_| Default::default()), - runtime_version: runtime_version?, - _marker: PhantomData, - page_size: self.page_size.unwrap_or(10), - }) + /// Returns true if the given pallet and event names match this event. + fn is_event(pallet: &str, event: &str) -> bool { + Self::PALLET == pallet && Self::EVENT == event } } -/// Client to interface with a substrate node. -pub struct Client { - rpc: Rpc, - genesis_hash: T::Hash, - metadata: Metadata, - events_decoder: EventsDecoder, - properties: SystemProperties, - runtime_version: RuntimeVersion, - _marker: PhantomData<(fn() -> T::Signature, T::Extra)>, - page_size: u32, -} +/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of +/// the transaction payload +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Encoded(pub Vec); -impl Clone for Client { - fn clone(&self) -> Self { - Self { - rpc: self.rpc.clone(), - genesis_hash: self.genesis_hash, - metadata: self.metadata.clone(), - events_decoder: self.events_decoder.clone(), - properties: self.properties.clone(), - runtime_version: self.runtime_version.clone(), - _marker: PhantomData, - page_size: self.page_size, - } +impl codec::Encode for Encoded { + fn encode(&self) -> Vec { + self.0.to_owned() } } -/// Iterates over key value pairs in a map. -pub struct KeyIter> { - client: Client, - _marker: PhantomData, - count: u32, - hash: T::Hash, - start_key: Option, - buffer: Vec<(StorageKey, StorageData)>, +/// A phase of a block's execution. +#[derive(Clone, Debug, Eq, PartialEq, Decode)] +pub enum Phase { + /// Applying an extrinsic. + ApplyExtrinsic(u32), + /// Finalizing the block. + Finalization, + /// Initializing the block. + Initialization, } -impl> KeyIter { - /// Returns the next key value pair from a map. - pub async fn next(&mut self) -> Result, Error> { - loop { - if let Some((k, v)) = self.buffer.pop() { - return Ok(Some((k, Decode::decode(&mut &v.0[..])?))) - } else { - let keys = self - .client - .fetch_keys::(self.count, self.start_key.take(), Some(self.hash)) - .await?; - - if keys.is_empty() { - return Ok(None) - } - - self.start_key = keys.last().cloned(); - - let change_sets = self - .client - .rpc - .query_storage_at(&keys, Some(self.hash)) - .await?; - for change_set in change_sets { - for (k, v) in change_set.changes { - if let Some(v) = v { - self.buffer.push((k, v)); - } - } - } - debug_assert_eq!(self.buffer.len(), keys.len()); - } - } - } +/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec`. +/// +/// This type is similar to [`WrapperOpaque`], but it differs in the way it stores the type `T`. +/// While [`WrapperOpaque`] stores the decoded type, the [`WrapperKeepOpaque`] stores the type only +/// in its opaque format, aka as a `Vec`. To access the real type `T` [`Self::try_decode`] needs +/// to be used. +#[derive(Debug, Eq, PartialEq, Default, Clone, Decode, Encode)] +pub struct WrapperKeepOpaque { + data: Vec, + _phantom: PhantomData, } -impl Client { - /// Returns the genesis hash. - pub fn genesis(&self) -> &T::Hash { - &self.genesis_hash - } - - /// Returns the chain metadata. - pub fn metadata(&self) -> &Metadata { - &self.metadata - } - - /// Returns the system properties - pub fn properties(&self) -> &SystemProperties { - &self.properties - } - - /// Returns the rpc client. - pub fn rpc_client(&self) -> &RpcClient { - &self.rpc.client - } - - /// Fetch the value under an unhashed storage key - pub async fn fetch_unhashed( - &self, - key: StorageKey, - hash: Option, - ) -> Result, Error> { - if let Some(data) = self.rpc.storage(&key, hash).await? { - Ok(Some(Decode::decode(&mut &data.0[..])?)) - } else { - Ok(None) - } - } - - /// Fetch a StorageKey with an optional block hash. - pub async fn fetch>( - &self, - store: &F, - hash: Option, - ) -> Result, Error> { - let key = store.key(&self.metadata)?; - self.fetch_unhashed::(key, hash).await - } - - /// Fetch a StorageKey that has a default value with an optional block hash. - pub async fn fetch_or_default>( - &self, - store: &F, - hash: Option, - ) -> Result { - if let Some(data) = self.fetch(store, hash).await? { - Ok(data) - } else { - Ok(store.default(&self.metadata)?) - } - } - - /// Returns an iterator of key value pairs. - pub async fn iter>( - &self, - hash: Option, - ) -> Result, Error> { - let hash = if let Some(hash) = hash { - hash - } else { - self.block_hash(None) - .await? - .expect("didn't pass a block number; qed") - }; - Ok(KeyIter { - client: self.clone(), - hash, - count: self.page_size, - start_key: None, - buffer: Default::default(), - _marker: PhantomData, - }) - } - - /// Fetch up to `count` keys for a storage map in lexicographic order. +impl WrapperKeepOpaque { + /// Try to decode the wrapped type from the inner `data`. /// - /// Supports pagination by passing a value to `start_key`. - pub async fn fetch_keys>( - &self, - count: u32, - start_key: Option, - hash: Option, - ) -> Result, Error> { - let prefix = >::prefix(&self.metadata)?; - let keys = self - .rpc - .storage_keys_paged(Some(prefix), count, start_key, hash) - .await?; - Ok(keys) - } - - /// Query historical storage entries - pub async fn query_storage( - &self, - keys: Vec, - from: T::Hash, - to: Option, - ) -> Result::Hash>>, Error> { - self.rpc.query_storage(keys, from, to).await - } - - /// Get a header - pub async fn header(&self, hash: Option) -> Result, Error> - where - H: Into + 'static, - { - let header = self.rpc.header(hash.map(|h| h.into())).await?; - Ok(header) - } - - /// Get a block hash. By default returns the latest block hash - pub async fn block_hash( - &self, - block_number: Option, - ) -> Result, Error> { - let hash = self.rpc.block_hash(block_number).await?; - Ok(hash) - } - - /// Get a block hash of the latest finalized block - pub async fn finalized_head(&self) -> Result { - let head = self.rpc.finalized_head().await?; - Ok(head) - } - - /// Get a block - pub async fn block(&self, hash: Option) -> Result>, Error> - where - H: Into + 'static, - { - let block = self.rpc.block(hash.map(|h| h.into())).await?; - Ok(block) - } - - /// Get proof of storage entries at a specific block's state. - pub async fn read_proof( - &self, - keys: Vec, - hash: Option, - ) -> Result, Error> - where - H: Into + 'static, - { - let proof = self.rpc.read_proof(keys, hash.map(|h| h.into())).await?; - Ok(proof) - } - - /// Subscribe to events. - /// - /// *WARNING* these may not be included in the finalized chain, use - /// `subscribe_finalized_events` to ensure events are finalized. - pub async fn subscribe_events(&self) -> Result, Error> { - let events = self.rpc.subscribe_events().await?; - Ok(events) - } - - /// Subscribe to finalized events. - pub async fn subscribe_finalized_events( - &self, - ) -> Result, Error> { - let events = self.rpc.subscribe_finalized_events().await?; - Ok(events) - } - - /// Subscribe to new blocks. - pub async fn subscribe_blocks(&self) -> Result, Error> { - let headers = self.rpc.subscribe_blocks().await?; - Ok(headers) - } - - /// Subscribe to finalized blocks. - pub async fn subscribe_finalized_blocks( - &self, - ) -> Result, Error> { - let headers = self.rpc.subscribe_finalized_blocks().await?; - Ok(headers) - } - - /// Encodes a call. - pub fn encode>(&self, call: C) -> Result { - Ok(self - .metadata() - .module_with_calls(C::MODULE) - .and_then(|module| module.call(C::FUNCTION, call))?) - } - - /// Creates an unsigned extrinsic. - pub fn create_unsigned + Send + Sync>( - &self, - call: C, - ) -> Result, Error> { - let call = self.encode(call)?; - Ok(extrinsic::create_unsigned::(call)) + /// Returns `None` if the decoding failed. + pub fn try_decode(&self) -> Option { + T::decode_all(&mut &self.data[..]).ok() } - /// Creates a signed extrinsic. - pub async fn create_signed + Send + Sync>( - &self, - call: C, - signer: &(dyn Signer + Send + Sync), - ) -> Result, Error> - where - <>::Extra as SignedExtension>::AdditionalSigned: - Send + Sync, - { - let account_nonce = if let Some(nonce) = signer.nonce() { - nonce - } else { - self.account(signer.account_id(), None).await?.nonce - }; - let call = self.encode(call)?; - let signed = extrinsic::create_signed( - &self.runtime_version, - self.genesis_hash, - account_nonce, - call, - signer, - ) - .await?; - Ok(signed) + /// Returns the length of the encoded `T`. + pub fn encoded_len(&self) -> usize { + self.data.len() } - /// Returns the events decoder. - pub fn events_decoder(&self) -> &EventsDecoder { - &self.events_decoder + /// Returns the encoded data. + pub fn encoded(&self) -> &[u8] { + &self.data } - /// Create and submit an extrinsic and return corresponding Hash if successful - pub async fn submit_extrinsic( - &self, - extrinsic: UncheckedExtrinsic, - ) -> Result { - self.rpc.submit_extrinsic(extrinsic).await - } - - /// Create and submit an extrinsic and return corresponding Event if successful - pub async fn submit_and_watch_extrinsic( - &self, - extrinsic: UncheckedExtrinsic, - ) -> Result, Error> { - self.rpc - .submit_and_watch_extrinsic(extrinsic, &self.events_decoder) - .await - } - - /// Submits a transaction to the chain. - pub async fn submit + Send + Sync>( - &self, - call: C, - signer: &(dyn Signer + Send + Sync), - ) -> Result - where - <>::Extra as SignedExtension>::AdditionalSigned: - Send + Sync, - { - let extrinsic = self.create_signed(call, signer).await?; - self.submit_extrinsic(extrinsic).await - } - - /// Submits transaction to the chain and watch for events. - pub async fn watch + Send + Sync>( - &self, - call: C, - signer: &(dyn Signer + Send + Sync), - ) -> Result, Error> - where - <>::Extra as SignedExtension>::AdditionalSigned: - Send + Sync, - { - let extrinsic = self.create_signed(call, signer).await?; - self.submit_and_watch_extrinsic(extrinsic).await - } - - /// Insert a key into the keystore. - pub async fn insert_key( - &self, - key_type: String, - suri: String, - public: Bytes, - ) -> Result<(), Error> { - self.rpc.insert_key(key_type, suri, public).await - } - - /// Generate new session keys and returns the corresponding public keys. - pub async fn rotate_keys(&self) -> Result { - self.rpc.rotate_keys().await - } - - /// Checks if the keystore has private keys for the given session public keys. - /// - /// `session_keys` is the SCALE encoded session keys object from the runtime. - /// - /// Returns `true` iff all private keys could be found. - pub async fn has_session_keys(&self, session_keys: Bytes) -> Result { - self.rpc.has_session_keys(session_keys).await - } - - /// Checks if the keystore has private keys for the given public key and key type. - /// - /// Returns `true` if a private key could be found. - pub async fn has_key( - &self, - public_key: Bytes, - key_type: String, - ) -> Result { - self.rpc.has_key(public_key, key_type).await - } -} - -/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of -/// the transaction payload -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Encoded(pub Vec); - -impl codec::Encode for Encoded { - fn encode(&self) -> Vec { - self.0.to_owned() + /// Create from the given encoded `data`. + pub fn from_encoded(data: Vec) -> Self { + Self { + data, + _phantom: PhantomData, + } } } diff --git a/src/metadata.rs b/src/metadata.rs index c3c3645bbe..7aeb26cfa4 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,55 +12,52 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . use std::{ collections::HashMap, convert::TryFrom, - marker::PhantomData, - str::FromStr, }; -use codec::{ - Decode, - Encode, - Error as CodecError, -}; +use codec::Error as CodecError; use frame_metadata::{ - DecodeDifferent, + PalletConstantMetadata, RuntimeMetadata, + RuntimeMetadataLastVersion, RuntimeMetadataPrefixed, - StorageEntryModifier, - StorageEntryType, - StorageHasher, + StorageEntryMetadata, META_RESERVED, }; -use sp_core::storage::StorageKey; -use crate::Encoded; +use crate::{ + Call, + Encoded, +}; +use scale_info::{ + form::PortableForm, + Type, + Variant, +}; /// Metadata error. #[derive(Debug, thiserror::Error)] pub enum MetadataError { - /// Failed to parse metadata. - #[error("Error converting substrate metadata: {0}")] - Conversion(#[from] ConversionError), /// Module is not in metadata. - #[error("Module {0} not found")] - ModuleNotFound(String), - /// Module is not in metadata. - #[error("Module index {0} not found")] - ModuleIndexNotFound(u8), + #[error("Pallet {0} not found")] + PalletNotFound(String), + /// Pallet is not in metadata. + #[error("Pallet index {0} not found")] + PalletIndexNotFound(u8), /// Call is not in metadata. #[error("Call {0} not found")] CallNotFound(&'static str), /// Event is not in metadata. - #[error("Event {0} not found")] - EventNotFound(u8), + #[error("Pallet {0}, Event {0} not found")] + EventNotFound(u8, u8), /// Event is not in metadata. - #[error("Error {0} not found")] - ErrorNotFound(u8), + #[error("Pallet {0}, Error {0} not found")] + ErrorNotFound(u8, u8), /// Storage is not in metadata. #[error("Storage {0} not found")] StorageNotFound(&'static str), @@ -76,107 +73,91 @@ pub enum MetadataError { /// Constant is not in metadata. #[error("Constant {0} not found")] ConstantNotFound(&'static str), + #[error("Type {0} missing from type registry")] + TypeNotFound(u32), } /// Runtime metadata. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct Metadata { - modules: HashMap, - modules_with_calls: HashMap, - modules_with_events: HashMap, - modules_with_errors: HashMap, + metadata: RuntimeMetadataLastVersion, + pallets: HashMap, + events: HashMap<(u8, u8), EventMetadata>, + errors: HashMap<(u8, u8), ErrorMetadata>, } impl Metadata { - /// Returns `ModuleMetadata`. - pub fn module(&self, name: S) -> Result<&ModuleMetadata, MetadataError> - where - S: ToString, - { - let name = name.to_string(); - self.modules - .get(&name) - .ok_or(MetadataError::ModuleNotFound(name)) - } - - /// Returns `ModuleWithCalls`. - pub fn module_with_calls(&self, name: S) -> Result<&ModuleWithCalls, MetadataError> - where - S: ToString, - { - let name = name.to_string(); - self.modules_with_calls - .get(&name) - .ok_or(MetadataError::ModuleNotFound(name)) - } - - /// Returns Iterator of `ModuleWithEvents`. - pub fn modules_with_events(&self) -> impl Iterator { - self.modules_with_events.values() + /// Returns a reference to [`PalletMetadata`]. + pub fn pallet(&self, name: &'static str) -> Result<&PalletMetadata, MetadataError> { + self.pallets + .get(name) + .ok_or(MetadataError::PalletNotFound(name.to_string())) } - /// Returns `ModuleWithEvents`. - pub fn module_with_events( + /// Returns the metadata for the event at the given pallet and event indices. + pub fn event( &self, - module_index: u8, - ) -> Result<&ModuleWithEvents, MetadataError> { - self.modules_with_events - .values() - .find(|&module| module.index == module_index) - .ok_or(MetadataError::ModuleIndexNotFound(module_index)) + pallet_index: u8, + event_index: u8, + ) -> Result<&EventMetadata, MetadataError> { + let event = self + .events + .get(&(pallet_index, event_index)) + .ok_or(MetadataError::EventNotFound(pallet_index, event_index))?; + Ok(event) + } + + /// Returns the metadata for the error at the given pallet and error indices. + pub fn error( + &self, + pallet_index: u8, + error_index: u8, + ) -> Result<&ErrorMetadata, MetadataError> { + let error = self + .errors + .get(&(pallet_index, error_index)) + .ok_or(MetadataError::ErrorNotFound(pallet_index, error_index))?; + Ok(error) } - /// Returns `ModuleWithErrors`. - pub fn module_with_errors( - &self, - module_index: u8, - ) -> Result<&ModuleWithErrors, MetadataError> { - self.modules_with_errors - .values() - .find(|&module| module.index == module_index) - .ok_or(MetadataError::ModuleIndexNotFound(module_index)) + /// Resolve a type definition. + pub fn resolve_type(&self, id: u32) -> Option<&Type> { + self.metadata.types.resolve(id) } - /// Pretty print metadata. - pub fn pretty(&self) -> String { - let mut string = String::new(); - for (name, module) in &self.modules { - string.push_str(name.as_str()); - string.push('\n'); - for storage in module.storage.keys() { - string.push_str(" s "); - string.push_str(storage.as_str()); - string.push('\n'); - } - if let Some(module) = self.modules_with_calls.get(name) { - for call in module.calls.keys() { - string.push_str(" c "); - string.push_str(call.as_str()); - string.push('\n'); - } - } - if let Some(module) = self.modules_with_events.get(name) { - for event in module.events.values() { - string.push_str(" e "); - string.push_str(event.name.as_str()); - string.push('\n'); - } - } - } - string + /// Return the runtime metadata. + pub fn runtime_metadata(&self) -> &RuntimeMetadataLastVersion { + &self.metadata } } #[derive(Clone, Debug)] -pub struct ModuleMetadata { +pub struct PalletMetadata { index: u8, name: String, - storage: HashMap, - constants: HashMap, + calls: HashMap, + storage: HashMap>, + constants: HashMap>, } -impl ModuleMetadata { - pub fn storage(&self, key: &'static str) -> Result<&StorageMetadata, MetadataError> { +impl PalletMetadata { + pub fn encode_call(&self, call: &C) -> Result + where + C: Call, + { + let fn_index = self + .calls + .get(C::FUNCTION) + .ok_or(MetadataError::CallNotFound(C::FUNCTION))?; + let mut bytes = vec![self.index, *fn_index]; + bytes.extend(call.encode()); + Ok(Encoded(bytes)) + } + + pub fn storage( + &self, + key: &'static str, + ) -> Result<&StorageEntryMetadata, MetadataError> { self.storage .get(key) .ok_or(MetadataError::StorageNotFound(key)) @@ -186,7 +167,7 @@ impl ModuleMetadata { pub fn constant( &self, key: &'static str, - ) -> Result<&ModuleConstantMetadata, MetadataError> { + ) -> Result<&PalletConstantMetadata, MetadataError> { self.constants .get(key) .ok_or(MetadataError::ConstantNotFound(key)) @@ -194,484 +175,183 @@ impl ModuleMetadata { } #[derive(Clone, Debug)] -pub struct ModuleWithCalls { - index: u8, - calls: HashMap, +pub struct EventMetadata { + pallet: String, + event: String, + variant: Variant, } -impl ModuleWithCalls { - pub fn call( - &self, - function: &'static str, - params: T, - ) -> Result { - let fn_index = self - .calls - .get(function) - .ok_or(MetadataError::CallNotFound(function))?; - let mut bytes = vec![self.index, *fn_index]; - bytes.extend(params.encode()); - Ok(Encoded(bytes)) +impl EventMetadata { + /// Get the name of the pallet from which the event was emitted. + pub fn pallet(&self) -> &str { + &self.pallet } -} -#[derive(Clone, Debug)] -pub struct ModuleWithEvents { - index: u8, - name: String, - events: HashMap, -} - -impl ModuleWithEvents { - pub fn name(&self) -> &str { - &self.name - } - - pub fn events(&self) -> impl Iterator { - self.events.values() - } - - pub fn event(&self, index: u8) -> Result<&ModuleEventMetadata, MetadataError> { - self.events - .get(&index) - .ok_or(MetadataError::EventNotFound(index)) - } -} - -#[derive(Clone, Debug)] -pub struct ModuleWithErrors { - index: u8, - name: String, - errors: HashMap, -} - -impl ModuleWithErrors { - pub fn name(&self) -> &str { - &self.name + /// Get the name of the pallet event which was emitted. + pub fn event(&self) -> &str { + &self.event } - pub fn error(&self, index: u8) -> Result<&String, MetadataError> { - self.errors - .get(&index) - .ok_or(MetadataError::ErrorNotFound(index)) + /// Get the type def variant for the pallet event. + pub fn variant(&self) -> &Variant { + &self.variant } } #[derive(Clone, Debug)] -pub struct StorageMetadata { - module_prefix: String, - storage_prefix: String, - modifier: StorageEntryModifier, - ty: StorageEntryType, - default: Vec, +pub struct ErrorMetadata { + pallet: String, + error: String, + variant: Variant, } -impl StorageMetadata { - pub fn prefix(&self) -> StorageKey { - let mut bytes = sp_core::twox_128(self.module_prefix.as_bytes()).to_vec(); - bytes.extend(&sp_core::twox_128(self.storage_prefix.as_bytes())[..]); - StorageKey(bytes) +impl ErrorMetadata { + /// Get the name of the pallet from which the error originates. + pub fn pallet(&self) -> &str { + &self.pallet } - pub fn default(&self) -> Result { - Decode::decode(&mut &self.default[..]).map_err(MetadataError::DefaultError) + /// Get the name of the specific pallet error. + pub fn error(&self) -> &str { + &self.error } - pub fn hash(hasher: &StorageHasher, bytes: &[u8]) -> Vec { - match hasher { - StorageHasher::Identity => bytes.to_vec(), - StorageHasher::Blake2_128 => sp_core::blake2_128(bytes).to_vec(), - StorageHasher::Blake2_128Concat => { - // copied from substrate Blake2_128Concat::hash since StorageHasher is not public - sp_core::blake2_128(bytes) - .iter() - .chain(bytes) - .cloned() - .collect() - } - StorageHasher::Blake2_256 => sp_core::blake2_256(bytes).to_vec(), - StorageHasher::Twox128 => sp_core::twox_128(bytes).to_vec(), - StorageHasher::Twox256 => sp_core::twox_256(bytes).to_vec(), - StorageHasher::Twox64Concat => { - sp_core::twox_64(bytes) - .iter() - .chain(bytes) - .cloned() - .collect() - } - } - } - - pub fn hash_key(hasher: &StorageHasher, key: &K) -> Vec { - Self::hash(hasher, &key.encode()) - } - - pub fn plain(&self) -> Result { - match &self.ty { - StorageEntryType::Plain(_) => { - Ok(StoragePlain { - prefix: self.prefix().0, - }) - } - _ => Err(MetadataError::StorageTypeError), - } - } - - pub fn map(&self) -> Result, MetadataError> { - match &self.ty { - StorageEntryType::Map { hasher, .. } => { - Ok(StorageMap { - _marker: PhantomData, - prefix: self.prefix().0, - hasher: hasher.clone(), - }) - } - _ => Err(MetadataError::StorageTypeError), - } - } - - pub fn double_map( - &self, - ) -> Result, MetadataError> { - match &self.ty { - StorageEntryType::DoubleMap { - hasher, - key2_hasher, - .. - } => { - Ok(StorageDoubleMap { - _marker: PhantomData, - prefix: self.prefix().0, - hasher1: hasher.clone(), - hasher2: key2_hasher.clone(), - }) - } - _ => Err(MetadataError::StorageTypeError), - } - } -} - -#[derive(Clone, Debug)] -pub struct StoragePlain { - prefix: Vec, -} - -impl StoragePlain { - pub fn key(&self) -> StorageKey { - StorageKey(self.prefix.clone()) - } -} - -#[derive(Clone, Debug)] -pub struct StorageMap { - _marker: PhantomData, - prefix: Vec, - hasher: StorageHasher, -} - -impl StorageMap { - pub fn key(&self, key: &K) -> StorageKey { - let mut bytes = self.prefix.clone(); - bytes.extend(StorageMetadata::hash_key(&self.hasher, key)); - StorageKey(bytes) - } -} - -#[derive(Clone, Debug)] -pub struct StorageDoubleMap { - _marker: PhantomData<(K1, K2)>, - prefix: Vec, - hasher1: StorageHasher, - hasher2: StorageHasher, -} - -impl StorageDoubleMap { - pub fn key(&self, key1: &K1, key2: &K2) -> StorageKey { - let mut bytes = self.prefix.clone(); - bytes.extend(StorageMetadata::hash_key(&self.hasher1, key1)); - bytes.extend(StorageMetadata::hash_key(&self.hasher2, key2)); - StorageKey(bytes) - } -} - -#[derive(Clone, Debug)] -pub struct ModuleEventMetadata { - pub name: String, - arguments: Vec, -} - -impl ModuleEventMetadata { - pub fn arguments(&self) -> Vec { - self.arguments.to_vec() - } -} - -/// Naive representation of event argument types, supports current set of substrate EventArg types. -/// If and when Substrate uses `type-metadata`, this can be replaced. -/// -/// Used to calculate the size of a instance of an event variant without having the concrete type, -/// so the raw bytes can be extracted from the encoded `Vec>` (without `E` defined). -#[derive(Clone, Debug, Eq, PartialEq, Hash)] -pub enum EventArg { - Primitive(String), - Vec(Box), - Tuple(Vec), - Option(Box), -} - -impl FromStr for EventArg { - type Err = ConversionError; - - fn from_str(s: &str) -> Result { - if s.starts_with("Vec<") { - if s.ends_with('>') { - Ok(EventArg::Vec(Box::new(s[4..s.len() - 1].parse()?))) - } else { - Err(ConversionError::InvalidEventArg( - s.to_string(), - "Expected closing `>` for `Vec`", - )) - } - } else if s.starts_with("Option<") { - if s.ends_with('>') { - Ok(EventArg::Option(Box::new(s[7..s.len() - 1].parse()?))) - } else { - Err(ConversionError::InvalidEventArg( - s.to_string(), - "Expected closing `>` for `Option`", - )) - } - } else if s.starts_with('(') { - if s.ends_with(')') { - let mut args = Vec::new(); - for arg in s[1..s.len() - 1].split(',') { - let arg = arg.trim().parse()?; - args.push(arg) - } - Ok(EventArg::Tuple(args)) - } else { - Err(ConversionError::InvalidEventArg( - s.to_string(), - "Expecting closing `)` for tuple", - )) - } - } else { - Ok(EventArg::Primitive(s.to_string())) - } - } -} - -impl EventArg { - /// Returns all primitive types for this EventArg - pub fn primitives(&self) -> Vec { - match self { - EventArg::Primitive(p) => vec![p.clone()], - EventArg::Vec(arg) => arg.primitives(), - EventArg::Option(arg) => arg.primitives(), - EventArg::Tuple(args) => { - let mut primitives = Vec::new(); - for arg in args { - primitives.extend(arg.primitives()) - } - primitives - } - } - } -} - -#[derive(Clone, Debug)] -pub struct ModuleConstantMetadata { - name: String, - ty: String, - value: Vec, - documentation: Vec, -} - -impl ModuleConstantMetadata { - /// Name - pub fn name(&self) -> &String { - &self.name - } - - /// Constant value (decoded) - pub fn value(&self) -> Result { - Decode::decode(&mut &self.value[..]).map_err(MetadataError::ConstantValueError) - } - - /// Type (as defined in the runtime) - pub fn ty(&self) -> &String { - &self.ty - } - - /// Documentation - pub fn documentation(&self) -> &Vec { - &self.documentation + /// Get the description of the specific pallet error. + pub fn description(&self) -> &[String] { + self.variant.docs() } } #[derive(Debug, thiserror::Error)] -pub enum ConversionError { +pub enum InvalidMetadataError { #[error("Invalid prefix")] InvalidPrefix, #[error("Invalid version")] InvalidVersion, - #[error("Expected DecodeDifferent::Decoded")] - ExpectedDecoded, - #[error("Invalid event arg {0}")] - InvalidEventArg(String, &'static str), + #[error("Type {0} missing from type registry")] + MissingType(u32), + #[error("Type {0} was not a variant/enum type")] + TypeDefNotVariant(u32), } impl TryFrom for Metadata { - type Error = MetadataError; + type Error = InvalidMetadataError; fn try_from(metadata: RuntimeMetadataPrefixed) -> Result { if metadata.0 != META_RESERVED { - return Err(ConversionError::InvalidPrefix.into()) + return Err(InvalidMetadataError::InvalidPrefix.into()) } - let meta = match metadata.1 { - RuntimeMetadata::V13(meta) => meta, - _ => return Err(ConversionError::InvalidVersion.into()), + let metadata = match metadata.1 { + RuntimeMetadata::V14(meta) => meta, + _ => return Err(InvalidMetadataError::InvalidVersion.into()), }; - let mut modules = HashMap::new(); - let mut modules_with_calls = HashMap::new(); - let mut modules_with_events = HashMap::new(); - let mut modules_with_errors = HashMap::new(); - for module in convert(meta.modules)?.into_iter() { - let module_name = convert(module.name.clone())?; - - let mut constant_map = HashMap::new(); - for constant in convert(module.constants)?.into_iter() { - let constant_meta = convert_constant(constant)?; - constant_map.insert(constant_meta.name.clone(), constant_meta); - } - let mut storage_map = HashMap::new(); - if let Some(storage) = module.storage { - let storage = convert(storage)?; - let module_prefix = convert(storage.prefix)?; - for entry in convert(storage.entries)?.into_iter() { - let storage_prefix = convert(entry.name.clone())?; - let entry = convert_entry( - module_prefix.clone(), - storage_prefix.clone(), - entry, - )?; - storage_map.insert(storage_prefix, entry); - } - } - modules.insert( - module_name.clone(), - ModuleMetadata { - index: module.index, - name: module_name.clone(), - storage: storage_map, - constants: constant_map, - }, - ); - - if let Some(calls) = module.calls { - let mut call_map = HashMap::new(); - for (index, call) in convert(calls)?.into_iter().enumerate() { - let name = convert(call.name)?; - call_map.insert(name, index as u8); - } - modules_with_calls.insert( - module_name.clone(), - ModuleWithCalls { - index: module.index, - calls: call_map, - }, - ); - } - if let Some(events) = module.event { - let mut event_map = HashMap::new(); - for (index, event) in convert(events)?.into_iter().enumerate() { - event_map.insert(index as u8, convert_event(event)?); - } - modules_with_events.insert( - module_name.clone(), - ModuleWithEvents { - index: module.index, - name: module_name.clone(), - events: event_map, - }, - ); - } - let mut error_map = HashMap::new(); - for (index, error) in convert(module.errors)?.into_iter().enumerate() { - error_map.insert(index as u8, convert_error(error)?); + let get_type_def_variant = |type_id: u32| { + let ty = metadata + .types + .resolve(type_id) + .ok_or(InvalidMetadataError::MissingType(type_id))?; + if let scale_info::TypeDef::Variant(var) = ty.type_def() { + Ok(var) + } else { + Err(InvalidMetadataError::TypeDefNotVariant(type_id)) } - modules_with_errors.insert( - module_name.clone(), - ModuleWithErrors { - index: module.index, - name: module_name.clone(), - errors: error_map, - }, - ); - } - Ok(Metadata { - modules, - modules_with_calls, - modules_with_events, - modules_with_errors, + }; + let pallets = metadata + .pallets + .iter() + .map(|pallet| { + let calls = pallet.calls.as_ref().map_or(Ok(HashMap::new()), |call| { + let type_def_variant = get_type_def_variant(call.ty.id())?; + let calls = type_def_variant + .variants() + .iter() + .map(|v| (v.name().clone(), v.index())) + .collect(); + Ok(calls) + })?; + + let storage = pallet.storage.as_ref().map_or(HashMap::new(), |storage| { + storage + .entries + .iter() + .map(|entry| (entry.name.clone(), entry.clone())) + .collect() + }); + + let constants = pallet + .constants + .iter() + .map(|constant| (constant.name.clone(), constant.clone())) + .collect(); + + let pallet_metadata = PalletMetadata { + index: pallet.index, + name: pallet.name.to_string(), + calls, + storage, + constants, + }; + + Ok((pallet.name.to_string(), pallet_metadata)) + }) + .collect::>()?; + + let pallet_events = metadata + .pallets + .iter() + .filter_map(|pallet| { + pallet.event.as_ref().map(|event| { + let type_def_variant = get_type_def_variant(event.ty.id())?; + Ok((pallet, type_def_variant)) + }) + }) + .collect::, _>>()?; + let events = pallet_events + .iter() + .flat_map(|(pallet, type_def_variant)| { + type_def_variant.variants().iter().map(move |var| { + let key = (pallet.index, var.index()); + let value = EventMetadata { + pallet: pallet.name.clone(), + event: var.name().clone(), + variant: var.clone(), + }; + (key, value) + }) + }) + .collect(); + + let pallet_errors = metadata + .pallets + .iter() + .filter_map(|pallet| { + pallet.error.as_ref().map(|error| { + let type_def_variant = get_type_def_variant(error.ty.id())?; + Ok((pallet, type_def_variant)) + }) + }) + .collect::, _>>()?; + let errors = pallet_errors + .iter() + .flat_map(|(pallet, type_def_variant)| { + type_def_variant.variants().iter().map(move |var| { + let key = (pallet.index, var.index()); + let value = ErrorMetadata { + pallet: pallet.name.clone(), + error: var.name().clone(), + variant: var.clone(), + }; + (key, value) + }) + }) + .collect(); + + Ok(Self { + metadata, + pallets, + events, + errors, }) } } - -fn convert( - dd: DecodeDifferent, -) -> Result { - match dd { - DecodeDifferent::Decoded(value) => Ok(value), - _ => Err(ConversionError::ExpectedDecoded), - } -} - -fn convert_event( - event: frame_metadata::EventMetadata, -) -> Result { - let name = convert(event.name)?; - let mut arguments = Vec::new(); - for arg in convert(event.arguments)? { - let arg = arg.parse::()?; - arguments.push(arg); - } - Ok(ModuleEventMetadata { name, arguments }) -} - -fn convert_entry( - module_prefix: String, - storage_prefix: String, - entry: frame_metadata::StorageEntryMetadata, -) -> Result { - let default = convert(entry.default)?; - Ok(StorageMetadata { - module_prefix, - storage_prefix, - modifier: entry.modifier, - ty: entry.ty, - default, - }) -} - -fn convert_error( - error: frame_metadata::ErrorMetadata, -) -> Result { - convert(error.name) -} - -fn convert_constant( - constant: frame_metadata::ModuleConstantMetadata, -) -> Result { - let name = convert(constant.name)?; - let ty = convert(constant.ty)?; - let value = convert(constant.value)?; - let documentation = convert(constant.documentation)?; - Ok(ModuleConstantMetadata { - name, - ty, - value, - documentation, - }) -} diff --git a/src/rpc.rs b/src/rpc.rs index 9d9790b712..2be0a5cbc4 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,11 +12,13 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . + +//! RPC types and client for interacting with a substrate node. // jsonrpsee subscriptions are interminable. // Allows `while let status = subscription.next().await {}` -// Related: https://github.com/paritytech/substrate-subxt/issues/66 +// Related: https://github.com/paritytech/subxt/issues/66 #![allow(irrefutable_let_patterns)] use std::sync::Arc; @@ -31,7 +33,10 @@ use core::{ marker::PhantomData, }; use frame_metadata::RuntimeMetadataPrefixed; -use jsonrpsee_http_client::HttpClient; +use jsonrpsee_http_client::{ + HttpClient, + HttpClientBuilder, +}; use jsonrpsee_types::{ to_json_value, traits::{ @@ -43,7 +48,10 @@ use jsonrpsee_types::{ JsonValue, Subscription, }; -use jsonrpsee_ws_client::WsClient; +use jsonrpsee_ws_client::{ + WsClient, + WsClientBuilder, +}; use serde::{ Deserialize, Serialize, @@ -55,10 +63,7 @@ use sp_core::{ StorageKey, }, Bytes, -}; -use sp_rpc::{ - list::ListOrValue, - number::NumberOrHex, + U256, }; use sp_runtime::{ generic::{ @@ -75,22 +80,48 @@ use crate::{ EventsDecoder, RawEvent, }, - frame::{ - system::System, - Event, - }, - metadata::Metadata, - runtimes::Runtime, + storage::StorageKeyPrefix, subscription::{ EventStorageSubscription, EventSubscription, FinalizedEventStorageSubscription, SystemEvents, }, + Config, + Event, + Metadata, }; +/// A number type that can be serialized both as a number or a string that encodes a number in a +/// string. +/// +/// We allow two representations of the block number as input. Either we deserialize to the type +/// that is specified in the block type or we attempt to parse given hex value. +/// +/// The primary motivation for having this type is to avoid overflows when using big integers in +/// JavaScript (which we consider as an important RPC API consumer). +#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq)] +#[serde(untagged)] +pub enum NumberOrHex { + /// The number represented directly. + Number(u64), + /// Hex representation of the number. + Hex(U256), +} + +/// RPC list or value wrapper. +#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(untagged)] +pub enum ListOrValue { + /// A list of values of given type. + List(Vec), + /// A single value of given type. + Value(T), +} + +/// Alias for the type of a block returned by `chain_getBlock` pub type ChainBlock = - SignedBlock::Header, ::Extrinsic>>; + SignedBlock::Header, ::Extrinsic>>; /// Wrapper for NumberOrHex to allow custom From impls #[derive(Serialize)] @@ -153,9 +184,6 @@ pub enum TransactionStatus { Invalid, } -#[cfg(feature = "client")] -use substrate_subxt_client::SubxtClient; - /// Rpc client wrapper. /// This is workaround because adding generic types causes the macros to fail. #[derive(Clone)] @@ -165,12 +193,27 @@ pub enum RpcClient { /// JSONRPC client HTTP transport. // NOTE: Arc because `HttpClient` is not clone. Http(Arc), - #[cfg(feature = "client")] - /// Embedded substrate node. - Subxt(SubxtClient), } impl RpcClient { + /// Create a new [`RpcClient`] from the given URL. + /// + /// Infers the protocol from the URL, supports: + /// - Websockets (`ws://`, `wss://`) + /// - Http (`http://`, `https://`) + pub async fn try_from_url(url: &str) -> Result { + if url.starts_with("ws://") || url.starts_with("wss://") { + let client = WsClientBuilder::default() + .max_notifs_per_subscription(4096) + .build(url) + .await?; + Ok(RpcClient::WebSocket(Arc::new(client))) + } else { + let client = HttpClientBuilder::default().build(&url)?; + Ok(RpcClient::Http(Arc::new(client))) + } + } + /// Start a JSON-RPC request. pub async fn request<'a, T: DeserializeOwned + std::fmt::Debug>( &self, @@ -178,15 +221,13 @@ impl RpcClient { params: &[JsonValue], ) -> Result { let params = params.into(); + log::debug!("request {}: {:?}", method, params); let data = match self { Self::WebSocket(inner) => { inner.request(method, params).await.map_err(Into::into) } Self::Http(inner) => inner.request(method, params).await.map_err(Into::into), - #[cfg(feature = "client")] - Self::Subxt(inner) => inner.request(method, params).await.map_err(Into::into), }; - log::debug!("{}: {:?}", method, data); data } @@ -211,13 +252,6 @@ impl RpcClient { ) .into()) } - #[cfg(feature = "client")] - Self::Subxt(inner) => { - inner - .subscribe(subscribe_method, params, unsubscribe_method) - .await - .map_err(Into::into) - } } } } @@ -246,13 +280,6 @@ impl From> for RpcClient { } } -#[cfg(feature = "client")] -impl From for RpcClient { - fn from(client: SubxtClient) -> Self { - RpcClient::Subxt(client) - } -} - /// ReadProof struct returned by the RPC /// /// # Note @@ -269,14 +296,14 @@ pub struct ReadProof { } /// Client for substrate rpc interfaces -pub struct Rpc { +pub struct Rpc { /// Rpc client for sending requests. pub client: RpcClient, marker: PhantomData, accept_weak_inclusion: bool, } -impl Clone for Rpc { +impl Clone for Rpc { fn clone(&self) -> Self { Self { client: self.client.clone(), @@ -286,7 +313,8 @@ impl Clone for Rpc { } } -impl Rpc { +impl Rpc { + /// Create a new [`Rpc`] pub fn new(client: RpcClient) -> Self { Self { client, @@ -317,11 +345,12 @@ impl Rpc { /// If `start_key` is passed, return next keys in storage in lexicographic order. pub async fn storage_keys_paged( &self, - prefix: Option, + prefix: Option, count: u32, start_key: Option, hash: Option, ) -> Result, Error> { + let prefix = prefix.map(|p| p.to_storage_key()); let params = &[ to_json_value(prefix)?, to_json_value(count)?, @@ -338,7 +367,7 @@ impl Rpc { keys: Vec, from: T::Hash, to: Option, - ) -> Result::Hash>>, Error> { + ) -> Result>, Error> { let params = &[ to_json_value(keys)?, to_json_value(from)?, @@ -355,7 +384,7 @@ impl Rpc { &self, keys: &[StorageKey], at: Option, - ) -> Result::Hash>>, Error> { + ) -> Result>, Error> { let params = &[to_json_value(keys)?, to_json_value(at)?]; self.client .request("state_queryStorageAt", params) @@ -520,6 +549,7 @@ impl Rpc { Ok(xt_hash) } + /// Create and submit an extrinsic and return a subscription to the events triggered. pub async fn watch_extrinsic( &self, extrinsic: E, @@ -678,7 +708,7 @@ impl Rpc { /// Captures data for when an extrinsic is successfully included in a block #[derive(Debug)] -pub struct ExtrinsicSuccess { +pub struct ExtrinsicSuccess { /// Block hash. pub block: T::Hash, /// Extrinsic hash. @@ -687,20 +717,20 @@ pub struct ExtrinsicSuccess { pub events: Vec, } -impl ExtrinsicSuccess { +impl ExtrinsicSuccess { /// Find the Event for the given module/variant, with raw encoded event data. /// Returns `None` if the Event is not found. pub fn find_event_raw(&self, module: &str, variant: &str) -> Option<&RawEvent> { self.events .iter() - .find(|raw| raw.module == module && raw.variant == variant) + .find(|raw| raw.pallet == module && raw.variant == variant) } /// Find the Event for the given module/variant, attempting to decode the event data. /// Returns `None` if the Event is not found. /// Returns `Err` if the data fails to decode into the supplied type. - pub fn find_event>(&self) -> Result, CodecError> { - if let Some(event) = self.find_event_raw(E::MODULE, E::EVENT) { + pub fn find_event(&self) -> Result, CodecError> { + if let Some(event) = self.find_event_raw(E::PALLET, E::EVENT) { Ok(Some(E::decode(&mut &event.data[..])?)) } else { Ok(None) diff --git a/src/runtimes.rs b/src/runtimes.rs deleted file mode 100644 index 515c6d4248..0000000000 --- a/src/runtimes.rs +++ /dev/null @@ -1,456 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -use codec::Encode; -use sp_runtime::{ - generic::Header, - impl_opaque_keys, - traits::{ - BlakeTwo256, - IdentifyAccount, - Verify, - }, - MultiSignature, - OpaqueExtrinsic, -}; -use sp_std::prelude::*; - -/// BABE marker struct -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Babe; - -/// Application specific crypto types -/// -/// # Note -/// -/// These are redefined here to avoid dependencies on the substrate creates where they are defined. -/// They must be identical to the definitions in the target substrate version. -pub mod app { - use sp_application_crypto::{ - app_crypto, - ed25519, - key_types, - sr25519, - }; - - /// Authority discovery app crypto types - pub mod authority_discovery { - use super::*; - app_crypto!(sr25519, key_types::AUTHORITY_DISCOVERY); - } - /// Babe app crypto types - pub mod babe { - use super::*; - app_crypto!(sr25519, key_types::BABE); - } - /// Im online discovery app crypto types - pub mod im_online { - use super::*; - app_crypto!(ed25519, key_types::IM_ONLINE); - } - /// Grandpa app crypto types - pub mod grandpa { - use super::*; - app_crypto!(ed25519, key_types::GRANDPA); - } - /// Validator app crypto types - pub mod validator { - use super::*; - app_crypto!(ed25519, sp_core::crypto::KeyTypeId(*b"para")); - } -} - -impl sp_runtime::BoundToRuntimeAppPublic for Babe { - type Public = app::babe::Public; -} - -/// ImOnline marker struct -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct ImOnline; -impl sp_runtime::BoundToRuntimeAppPublic for ImOnline { - type Public = app::im_online::Public; -} - -/// GRANDPA marker struct -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Grandpa; -impl sp_runtime::BoundToRuntimeAppPublic for Grandpa { - type Public = app::grandpa::Public; -} - -/// Parachain marker struct -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Parachains; - -impl sp_runtime::BoundToRuntimeAppPublic for Parachains { - type Public = app::validator::Public; -} - -/// Authority discovery marker struct -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct AuthorityDiscovery; -impl sp_runtime::BoundToRuntimeAppPublic for AuthorityDiscovery { - type Public = app::authority_discovery::Public; -} - -impl_opaque_keys! { - /// Substrate base runtime keys - pub struct BasicSessionKeys { - /// GRANDPA session key - pub grandpa: Grandpa, - /// BABE session key - pub babe: Babe, - /// ImOnline session key - pub im_online: ImOnline, - /// Parachain validation session key - pub parachains: Parachains, - /// AuthorityDiscovery session key - pub authority_discovery: AuthorityDiscovery, - } -} - -impl_opaque_keys! { - /// Polkadot/Kusama runtime keys - pub struct SessionKeys { - /// GRANDPA session key - pub grandpa: Grandpa, - /// BABE session key - pub babe: Babe, - /// ImOnline session key - pub im_online: ImOnline, - /// ParachainValidator session key - pub parachain_validator: Parachains, - /// AuthorityDiscovery session key - pub authority_discovery: AuthorityDiscovery, - } -} - -use crate::{ - extrinsic::{ - DefaultExtra, - SignedExtra, - }, - frame::{ - balances::{ - AccountData, - Balances, - BalancesEventTypeRegistry, - }, - contracts::{ - Contracts, - ContractsEventTypeRegistry, - }, - session::{ - Session, - SessionEventTypeRegistry, - }, - staking::{ - Staking, - StakingEventTypeRegistry, - }, - sudo::{ - Sudo, - SudoEventTypeRegistry, - }, - system::{ - System, - SystemEventTypeRegistry, - }, - }, - EventTypeRegistry, -}; - -/// Runtime trait. -pub trait Runtime: System + Sized + Send + Sync + 'static { - /// Signature type. - type Signature: Verify + Encode + Send + Sync + 'static; - /// Transaction extras. - type Extra: SignedExtra + Send + Sync + 'static; - - /// Register type sizes for this runtime - fn register_type_sizes(event_type_registry: &mut EventTypeRegistry); -} - -/// Concrete type definitions compatible with those in the default substrate `node_runtime` -/// -/// # Note -/// -/// If the concrete types in the target substrate runtime differ from these, a custom Runtime -/// definition MUST be used to ensure type compatibility. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct DefaultNodeRuntime; - -impl Staking for DefaultNodeRuntime {} - -impl Runtime for DefaultNodeRuntime { - type Signature = MultiSignature; - type Extra = DefaultExtra; - - fn register_type_sizes(event_type_registry: &mut EventTypeRegistry) { - event_type_registry.with_system(); - event_type_registry.with_balances(); - event_type_registry.with_session(); - event_type_registry.with_staking(); - event_type_registry.with_contracts(); - event_type_registry.with_sudo(); - register_default_type_sizes(event_type_registry); - } -} - -impl System for DefaultNodeRuntime { - type Index = u32; - type BlockNumber = u32; - type Hash = sp_core::H256; - type Hashing = BlakeTwo256; - type AccountId = <::Signer as IdentifyAccount>::AccountId; - type Address = sp_runtime::MultiAddress; - type Header = Header; - type Extrinsic = OpaqueExtrinsic; - type AccountData = AccountData<::Balance>; -} - -impl Balances for DefaultNodeRuntime { - type Balance = u128; -} - -impl Session for DefaultNodeRuntime { - type ValidatorId = ::AccountId; - type Keys = BasicSessionKeys; -} - -impl Contracts for DefaultNodeRuntime {} - -impl Sudo for DefaultNodeRuntime {} - -/// Concrete type definitions compatible with the node template. -/// -/// # Note -/// -/// Main difference is `type Address = AccountId`. -/// Also the contracts module is not part of the node template runtime. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct NodeTemplateRuntime; - -impl Runtime for NodeTemplateRuntime { - type Signature = MultiSignature; - type Extra = DefaultExtra; - - fn register_type_sizes(event_type_registry: &mut EventTypeRegistry) { - event_type_registry.with_system(); - event_type_registry.with_balances(); - event_type_registry.with_session(); - event_type_registry.with_sudo(); - register_default_type_sizes(event_type_registry); - } -} - -impl System for NodeTemplateRuntime { - type Index = u32; - type BlockNumber = u32; - type Hash = sp_core::H256; - type Hashing = BlakeTwo256; - type AccountId = <::Signer as IdentifyAccount>::AccountId; - type Address = sp_runtime::MultiAddress; - type Header = Header; - type Extrinsic = OpaqueExtrinsic; - type AccountData = AccountData<::Balance>; -} - -impl Balances for NodeTemplateRuntime { - type Balance = u128; -} - -impl Session for NodeTemplateRuntime { - type ValidatorId = ::AccountId; - type Keys = BasicSessionKeys; -} - -impl Sudo for NodeTemplateRuntime {} - -/// Concrete type definitions compatible with the node template, with the -/// contracts pallet enabled. -/// -/// Inherits types from [`NodeTemplateRuntime`], but adds an implementation for -/// the contracts pallet trait. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct ContractsTemplateRuntime; - -impl Runtime for ContractsTemplateRuntime { - type Signature = ::Signature; - type Extra = DefaultExtra; - - fn register_type_sizes(event_type_registry: &mut EventTypeRegistry) { - event_type_registry.with_system(); - event_type_registry.with_balances(); - event_type_registry.with_contracts(); - event_type_registry.with_sudo(); - register_default_type_sizes(event_type_registry); - } -} - -impl System for ContractsTemplateRuntime { - type Index = ::Index; - type BlockNumber = ::BlockNumber; - type Hash = ::Hash; - type Hashing = ::Hashing; - type AccountId = ::AccountId; - type Address = ::Address; - type Header = ::Header; - type Extrinsic = ::Extrinsic; - type AccountData = ::AccountData; -} - -impl Balances for ContractsTemplateRuntime { - type Balance = ::Balance; -} - -impl Contracts for ContractsTemplateRuntime {} - -impl Sudo for ContractsTemplateRuntime {} - -/// Concrete type definitions compatible with those for kusama, v0.7 -/// -/// # Note -/// -/// Main difference is `type Address = AccountId`. -/// Also the contracts module is not part of the kusama runtime. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct KusamaRuntime; - -impl Runtime for KusamaRuntime { - type Signature = MultiSignature; - type Extra = DefaultExtra; - - fn register_type_sizes(event_type_registry: &mut EventTypeRegistry) { - event_type_registry.with_system(); - event_type_registry.with_balances(); - event_type_registry.with_session(); - event_type_registry.with_staking(); - register_default_type_sizes(event_type_registry); - } -} - -impl System for KusamaRuntime { - type Index = u32; - type BlockNumber = u32; - type Hash = sp_core::H256; - type Hashing = BlakeTwo256; - type AccountId = <::Signer as IdentifyAccount>::AccountId; - type Address = Self::AccountId; - type Header = Header; - type Extrinsic = OpaqueExtrinsic; - type AccountData = AccountData<::Balance>; -} - -impl Session for KusamaRuntime { - type ValidatorId = ::AccountId; - type Keys = SessionKeys; -} - -impl Staking for KusamaRuntime {} - -impl Balances for KusamaRuntime { - type Balance = u128; -} - -/// Identity of a Grandpa authority. -pub type AuthorityId = crate::runtimes::app::grandpa::Public; -/// The weight of an authority. -pub type AuthorityWeight = u64; -/// A list of Grandpa authorities with associated weights. -pub type AuthorityList = Vec<(AuthorityId, AuthorityWeight)>; - -/// Register default common runtime type sizes -pub fn register_default_type_sizes( - event_type_registry: &mut EventTypeRegistry, -) { - // for types which have all variants with no data, the size is just the index byte. - type CLikeEnum = u8; - - // primitives - event_type_registry.register_type_size::("bool"); - event_type_registry.register_type_size::("u8"); - event_type_registry.register_type_size::("u16"); - event_type_registry.register_type_size::("u32"); - event_type_registry.register_type_size::("u64"); - event_type_registry.register_type_size::("u128"); - - event_type_registry.register_type_size::<()>("PhantomData"); - event_type_registry - .register_type_size::<()>("sp_std::marker::PhantomData<(AccountId, Event)>"); - - // frame_support types - event_type_registry - .register_type_size::("DispatchInfo"); - event_type_registry - .register_type_size::("DispatchResult"); - event_type_registry - .register_type_size::("DispatchError"); - event_type_registry - .register_type_size::("Status"); - - // aliases etc. - event_type_registry.register_type_size::("ReferendumIndex"); - event_type_registry.register_type_size::<[u8; 16]>("Kind"); - - event_type_registry.register_type_size::("AccountIndex"); - event_type_registry.register_type_size::("AssetId"); - event_type_registry.register_type_size::("BountyIndex"); - event_type_registry.register_type_size::<(u8, u8)>("CallIndex"); - event_type_registry.register_type_size::<[u8; 32]>("CallHash"); - event_type_registry.register_type_size::("PropIndex"); - event_type_registry.register_type_size::("ProposalIndex"); - event_type_registry.register_type_size::("ProxyType"); - event_type_registry.register_type_size::("AuthorityIndex"); - event_type_registry.register_type_size::("MemberCount"); - event_type_registry.register_type_size::("RegistrarIndex"); - - event_type_registry.register_type_size::("VoteThreshold"); - event_type_registry - .register_type_size::<(T::BlockNumber, u32)>("TaskAddress"); - event_type_registry - .register_type_size::<(T::BlockNumber, u32)>("Timepoint"); - - event_type_registry.register_type_size::("AuthorityId"); - event_type_registry.register_type_size::("AuthorityWeight"); - event_type_registry - .register_type_size::>("AuthorityList"); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_register_default_runtime_type_sizes() { - EventTypeRegistry::::new(); - } - - #[test] - fn can_register_node_template_runtime_type_sizes() { - EventTypeRegistry::::new(); - } - - #[test] - fn can_register_contracts_template_runtime_type_sizes() { - EventTypeRegistry::::new(); - } - - #[test] - fn can_register_kusama_runtime_type_sizes() { - EventTypeRegistry::::new(); - } -} diff --git a/src/storage.rs b/src/storage.rs new file mode 100644 index 0000000000..e94d640926 --- /dev/null +++ b/src/storage.rs @@ -0,0 +1,297 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +//! For querying runtime storage. + +use codec::{ + Decode, + Encode, +}; +use sp_core::storage::{ + StorageChangeSet, + StorageData, + StorageKey, +}; +pub use sp_runtime::traits::SignedExtension; +pub use sp_version::RuntimeVersion; +use std::marker::PhantomData; + +use crate::{ + metadata::{ + Metadata, + MetadataError, + }, + rpc::Rpc, + Config, + Error, + StorageHasher, +}; + +/// Storage entry trait. +pub trait StorageEntry { + /// Pallet name. + const PALLET: &'static str; + /// Storage name. + const STORAGE: &'static str; + /// Type of the storage entry value. + type Value: Decode; + /// Get the key data for the storage. + fn key(&self) -> StorageEntryKey; +} + +/// The prefix of the key to a [`StorageEntry`] +pub struct StorageKeyPrefix(Vec); + +impl StorageKeyPrefix { + /// Create the storage key prefix for a [`StorageEntry`] + pub fn new() -> Self { + let mut bytes = sp_core::twox_128(T::PALLET.as_bytes()).to_vec(); + bytes.extend(&sp_core::twox_128(T::STORAGE.as_bytes())[..]); + Self(bytes) + } + + /// Convert the prefix into a [`StorageKey`] + pub fn to_storage_key(self) -> StorageKey { + StorageKey(self.0) + } +} + +/// Storage key. +pub enum StorageEntryKey { + /// Plain key. + Plain, + /// Map key(s). + Map(Vec), +} + +impl StorageEntryKey { + /// Construct the final [`sp_core::storage::StorageKey`] for the storage entry. + pub fn final_key(&self, prefix: StorageKeyPrefix) -> sp_core::storage::StorageKey { + let mut bytes = prefix.0; + if let Self::Map(map_keys) = self { + for map_key in map_keys { + bytes.extend(Self::hash(&map_key.hasher, &map_key.value)) + } + } + sp_core::storage::StorageKey(bytes) + } + + fn hash(hasher: &StorageHasher, bytes: &[u8]) -> Vec { + match hasher { + StorageHasher::Identity => bytes.to_vec(), + StorageHasher::Blake2_128 => sp_core::blake2_128(bytes).to_vec(), + StorageHasher::Blake2_128Concat => { + // copied from substrate Blake2_128Concat::hash since StorageHasher is not public + sp_core::blake2_128(bytes) + .iter() + .chain(bytes) + .cloned() + .collect() + } + StorageHasher::Blake2_256 => sp_core::blake2_256(bytes).to_vec(), + StorageHasher::Twox128 => sp_core::twox_128(bytes).to_vec(), + StorageHasher::Twox256 => sp_core::twox_256(bytes).to_vec(), + StorageHasher::Twox64Concat => { + sp_core::twox_64(bytes) + .iter() + .chain(bytes) + .cloned() + .collect() + } + } + } +} + +/// Storage key for a Map. +pub struct StorageMapKey { + value: Vec, + hasher: StorageHasher, +} + +impl StorageMapKey { + /// Create a new [`StorageMapKey`] with the encoded data and the hasher. + pub fn new(value: &T, hasher: StorageHasher) -> Self { + Self { + value: value.encode(), + hasher, + } + } +} + +/// Client for querying runtime storage. +#[derive(Clone)] +pub struct StorageClient<'a, T: Config> { + rpc: &'a Rpc, + metadata: &'a Metadata, + iter_page_size: u32, +} + +impl<'a, T: Config> StorageClient<'a, T> { + /// Create a new [`StorageClient`] + pub fn new(rpc: &'a Rpc, metadata: &'a Metadata, iter_page_size: u32) -> Self { + Self { + rpc, + metadata, + iter_page_size, + } + } + + /// Fetch the value under an unhashed storage key + pub async fn fetch_unhashed( + &self, + key: StorageKey, + hash: Option, + ) -> Result, Error> { + if let Some(data) = self.rpc.storage(&key, hash).await? { + Ok(Some(Decode::decode(&mut &data.0[..])?)) + } else { + Ok(None) + } + } + + /// Fetch the raw encoded value under the raw storage key. + pub async fn fetch_raw( + &self, + key: StorageKey, + hash: Option, + ) -> Result, Error> { + self.rpc.storage(&key, hash).await + } + + /// Fetch a StorageKey with an optional block hash. + pub async fn fetch( + &self, + store: &F, + hash: Option, + ) -> Result, Error> { + let prefix = StorageKeyPrefix::new::(); + let key = store.key().final_key(prefix); + self.fetch_unhashed::(key, hash).await + } + + /// Fetch a StorageKey that has a default value with an optional block hash. + pub async fn fetch_or_default( + &self, + store: &F, + hash: Option, + ) -> Result { + if let Some(data) = self.fetch(store, hash).await? { + Ok(data) + } else { + let pallet_metadata = self.metadata.pallet(F::PALLET)?; + let storage_metadata = pallet_metadata.storage(F::STORAGE)?; + let default = Decode::decode(&mut &storage_metadata.default[..]) + .map_err(MetadataError::DefaultError)?; + Ok(default) + } + } + + /// Query historical storage entries + pub async fn query_storage( + &self, + keys: Vec, + from: T::Hash, + to: Option, + ) -> Result>, Error> { + self.rpc.query_storage(keys, from, to).await + } + + /// Fetch up to `count` keys for a storage map in lexicographic order. + /// + /// Supports pagination by passing a value to `start_key`. + pub async fn fetch_keys( + &self, + count: u32, + start_key: Option, + hash: Option, + ) -> Result, Error> { + let prefix = StorageKeyPrefix::new::(); + let keys = self + .rpc + .storage_keys_paged(Some(prefix), count, start_key, hash) + .await?; + Ok(keys) + } + + /// Returns an iterator of key value pairs. + pub async fn iter( + &self, + hash: Option, + ) -> Result, Error> { + let hash = if let Some(hash) = hash { + hash + } else { + self.rpc + .block_hash(None) + .await? + .expect("didn't pass a block number; qed") + }; + Ok(KeyIter { + client: self.clone(), + hash, + count: self.iter_page_size, + start_key: None, + buffer: Default::default(), + _marker: PhantomData, + }) + } +} + +/// Iterates over key value pairs in a map. +pub struct KeyIter<'a, T: Config, F: StorageEntry> { + client: StorageClient<'a, T>, + _marker: PhantomData, + count: u32, + hash: T::Hash, + start_key: Option, + buffer: Vec<(StorageKey, StorageData)>, +} + +impl<'a, T: Config, F: StorageEntry> KeyIter<'a, T, F> { + /// Returns the next key value pair from a map. + pub async fn next(&mut self) -> Result, Error> { + loop { + if let Some((k, v)) = self.buffer.pop() { + return Ok(Some((k, Decode::decode(&mut &v.0[..])?))) + } else { + let keys = self + .client + .fetch_keys::(self.count, self.start_key.take(), Some(self.hash)) + .await?; + + if keys.is_empty() { + return Ok(None) + } + + self.start_key = keys.last().cloned(); + + let change_sets = self + .client + .rpc + .query_storage_at(&keys, Some(self.hash)) + .await?; + for change_set in change_sets { + for (k, v) in change_set.changes { + if let Some(v) = v { + self.buffer.push((k, v)); + } + } + } + debug_assert_eq!(self.buffer.len(), keys.len()); + } + } + } +} diff --git a/src/subscription.rs b/src/subscription.rs index 2fe7192e56..31ad50c3e8 100644 --- a/src/subscription.rs +++ b/src/subscription.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,7 +12,7 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . use jsonrpsee_types::{ DeserializeOwned, @@ -35,17 +35,15 @@ use crate::{ Raw, RawEvent, }, - frame::{ - system::Phase, - Event, - }, rpc::Rpc, - runtimes::Runtime, + Config, + Event, + Phase, }; /// Event subscription simplifies filtering a storage change set stream for /// events of interest. -pub struct EventSubscription<'a, T: Runtime> { +pub struct EventSubscription<'a, T: Config> { subscription: EventStorageSubscription, decoder: &'a EventsDecoder, block: Option, @@ -55,7 +53,7 @@ pub struct EventSubscription<'a, T: Runtime> { finished: bool, } -impl<'a, T: Runtime> EventSubscription<'a, T> { +impl<'a, T: Config> EventSubscription<'a, T> { /// Creates a new event subscription. pub fn new( subscription: EventStorageSubscription, @@ -84,8 +82,8 @@ impl<'a, T: Runtime> EventSubscription<'a, T> { } /// Filters events by type. - pub fn filter_event>(&mut self) { - self.event = Some((E::MODULE, E::EVENT)); + pub fn filter_event(&mut self) { + self.event = Some((E::PALLET, E::EVENT)); } /// Gets the next event. @@ -124,7 +122,7 @@ impl<'a, T: Runtime> EventSubscription<'a, T> { Raw::Error(err) => return Some(Err(err.into())), }; if let Some((module, variant)) = self.event { - if event.module != module || event.variant != variant { + if event.pallet != module || event.variant != variant { continue } } @@ -155,14 +153,14 @@ impl From for StorageKey { } /// Event subscription to only fetch finalized storage changes. -pub struct FinalizedEventStorageSubscription { +pub struct FinalizedEventStorageSubscription { rpc: Rpc, subscription: Subscription, storage_changes: VecDeque>, storage_key: StorageKey, } -impl FinalizedEventStorageSubscription { +impl FinalizedEventStorageSubscription { /// Creates a new finalized event storage subscription. pub fn new(rpc: Rpc, subscription: Subscription) -> Self { Self { @@ -193,14 +191,14 @@ impl FinalizedEventStorageSubscription { } /// Wrapper over imported and finalized event subscriptions. -pub enum EventStorageSubscription { +pub enum EventStorageSubscription { /// Events that are InBlock Imported(Subscription>), /// Events that are Finalized Finalized(FinalizedEventStorageSubscription), } -impl EventStorageSubscription { +impl EventStorageSubscription { /// Gets the next change_set from the subscription. pub async fn next(&mut self) -> Option> { match self { diff --git a/src/tests/mod.rs b/src/tests/mod.rs deleted file mode 100644 index 43f59ca7b6..0000000000 --- a/src/tests/mod.rs +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . - -mod node_proc; - -use super::*; -pub use node_proc::TestNodeProcess; -use sp_core::storage::{ - well_known_keys, - StorageKey, -}; -use sp_keyring::AccountKeyring; - -/// substrate node should be installed on the $PATH -const SUBSTRATE_NODE_PATH: &str = "substrate"; - -pub(crate) type TestRuntime = crate::DefaultNodeRuntime; - -pub(crate) async fn test_node_process_with( - key: AccountKeyring, -) -> TestNodeProcess { - if which::which(SUBSTRATE_NODE_PATH).is_err() { - panic!("A substrate binary should be installed on your path for integration tests. See https://github.com/paritytech/substrate-subxt/tree/master#integration-testing") - } - - let proc = TestNodeProcess::::build(SUBSTRATE_NODE_PATH) - .with_authority(key) - .scan_for_open_ports() - .spawn::() - .await; - proc.unwrap() -} - -pub(crate) async fn test_node_process() -> TestNodeProcess { - test_node_process_with(AccountKeyring::Alice).await -} - -#[async_std::test] -async fn test_insert_key() { - let test_node_process = test_node_process_with(AccountKeyring::Bob).await; - let client = test_node_process.client(); - let public = AccountKeyring::Alice.public().as_array_ref().to_vec(); - client - .insert_key( - "aura".to_string(), - "//Alice".to_string(), - public.clone().into(), - ) - .await - .unwrap(); - assert!(client - .has_key(public.clone().into(), "aura".to_string()) - .await - .unwrap()); -} - -#[async_std::test] -async fn test_tx_transfer_balance() { - let mut signer = PairSigner::new(AccountKeyring::Alice.pair()); - let dest = AccountKeyring::Bob.to_account_id().into(); - - let node_process = test_node_process().await; - let client = node_process.client(); - let nonce = client - .account(&AccountKeyring::Alice.to_account_id(), None) - .await - .unwrap() - .nonce; - signer.set_nonce(nonce); - client - .submit( - balances::TransferCall { - to: &dest, - amount: 10_000, - }, - &signer, - ) - .await - .unwrap(); - - // check that nonce is handled correctly - signer.increment_nonce(); - client - .submit( - balances::TransferCall { - to: &dest, - amount: 10_000, - }, - &signer, - ) - .await - .unwrap(); -} - -#[async_std::test] -async fn test_getting_hash() { - let node_process = test_node_process().await; - node_process.client().block_hash(None).await.unwrap(); -} - -#[async_std::test] -async fn test_getting_block() { - let node_process = test_node_process().await; - let client = node_process.client(); - let block_hash = client.block_hash(None).await.unwrap(); - client.block(block_hash).await.unwrap(); -} - -#[async_std::test] -async fn test_getting_read_proof() { - let node_process = test_node_process().await; - let client = node_process.client(); - let block_hash = client.block_hash(None).await.unwrap(); - client - .read_proof( - vec![ - StorageKey(well_known_keys::HEAP_PAGES.to_vec()), - StorageKey(well_known_keys::EXTRINSIC_INDEX.to_vec()), - ], - block_hash, - ) - .await - .unwrap(); -} - -#[async_std::test] -async fn test_chain_subscribe_blocks() { - let node_process = test_node_process().await; - let client = node_process.client(); - let mut blocks = client.subscribe_blocks().await.unwrap(); - blocks.next().await.unwrap(); -} - -#[async_std::test] -async fn test_chain_subscribe_finalized_blocks() { - let node_process = test_node_process().await; - let client = node_process.client(); - let mut blocks = client.subscribe_finalized_blocks().await.unwrap(); - blocks.next().await.unwrap(); -} - -#[async_std::test] -async fn test_fetch_keys() { - let node_process = test_node_process().await; - let client = node_process.client(); - let keys = client - .fetch_keys::>(4, None, None) - .await - .unwrap(); - assert_eq!(keys.len(), 4) -} - -#[async_std::test] -async fn test_iter() { - let node_process = test_node_process().await; - let client = node_process.client(); - let mut iter = client.iter::>(None).await.unwrap(); - let mut i = 0; - while let Some(_) = iter.next().await.unwrap() { - i += 1; - } - assert_eq!(i, 13); -} diff --git a/tests/integration/client.rs b/tests/integration/client.rs new file mode 100644 index 0000000000..70b5382597 --- /dev/null +++ b/tests/integration/client.rs @@ -0,0 +1,124 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use crate::{ + runtime::node_runtime::system, + test_node_process, + test_node_process_with, +}; + +use sp_core::storage::{ + well_known_keys, + StorageKey, +}; +use sp_keyring::AccountKeyring; + +#[async_std::test] +async fn insert_key() { + let test_node_process = test_node_process_with(AccountKeyring::Bob).await; + let client = test_node_process.client(); + let public = AccountKeyring::Alice.public().as_array_ref().to_vec(); + client + .rpc() + .insert_key( + "aura".to_string(), + "//Alice".to_string(), + public.clone().into(), + ) + .await + .unwrap(); + assert!(client + .rpc() + .has_key(public.clone().into(), "aura".to_string()) + .await + .unwrap()); +} + +#[async_std::test] +async fn fetch_block_hash() { + let node_process = test_node_process().await; + node_process.client().rpc().block_hash(None).await.unwrap(); +} + +#[async_std::test] +async fn fetch_block() { + let node_process = test_node_process().await; + let client = node_process.client(); + let block_hash = client.rpc().block_hash(None).await.unwrap(); + client.rpc().block(block_hash).await.unwrap(); +} + +#[async_std::test] +async fn fetch_read_proof() { + let node_process = test_node_process().await; + let client = node_process.client(); + let block_hash = client.rpc().block_hash(None).await.unwrap(); + client + .rpc() + .read_proof( + vec![ + StorageKey(well_known_keys::HEAP_PAGES.to_vec()), + StorageKey(well_known_keys::EXTRINSIC_INDEX.to_vec()), + ], + block_hash, + ) + .await + .unwrap(); +} + +#[async_std::test] +async fn chain_subscribe_blocks() { + let node_process = test_node_process().await; + let client = node_process.client(); + let mut blocks = client.rpc().subscribe_blocks().await.unwrap(); + blocks.next().await.unwrap(); +} + +#[async_std::test] +async fn chain_subscribe_finalized_blocks() { + let node_process = test_node_process().await; + let client = node_process.client(); + let mut blocks = client.rpc().subscribe_finalized_blocks().await.unwrap(); + blocks.next().await.unwrap(); +} + +#[async_std::test] +async fn fetch_keys() { + let node_process = test_node_process().await; + let client = node_process.client(); + let keys = client + .storage() + .fetch_keys::(4, None, None) + .await + .unwrap(); + assert_eq!(keys.len(), 4) +} + +#[async_std::test] +async fn test_iter() { + let node_process = test_node_process().await; + let client = node_process.client(); + let mut iter = client + .storage() + .iter::(None) + .await + .unwrap(); + let mut i = 0; + while let Some(_) = iter.next().await.unwrap() { + i += 1; + } + assert_eq!(i, 13); +} diff --git a/tests/integration/codegen/mod.rs b/tests/integration/codegen/mod.rs new file mode 100644 index 0000000000..1120941d7c --- /dev/null +++ b/tests/integration/codegen/mod.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +/// Checks that code generated by `subxt-cli codegen` compiles. Allows inspection of compiler errors +/// directly, more accurately than via the macro and `cargo expand`. +/// +/// Generate by: +/// +/// - run `polkadot --dev --tmp` node locally +/// - `cargo run --release -p subxt-cli -- codegen | rustfmt --edition=2018 --emit=stdout > tests/integration/codegen/polkadot.rs` +#[rustfmt::skip] +mod polkadot; diff --git a/tests/integration/codegen/polkadot.rs b/tests/integration/codegen/polkadot.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/integration/codegen/polkadot.rs @@ -0,0 +1 @@ + diff --git a/tests/integration/frame/balances.rs b/tests/integration/frame/balances.rs new file mode 100644 index 0000000000..e130a4bece --- /dev/null +++ b/tests/integration/frame/balances.rs @@ -0,0 +1,229 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use crate::{ + node_runtime::{ + balances, + runtime_types, + system, + DefaultConfig, + }, + test_context, +}; +use codec::Decode; +use sp_core::{ + sr25519::Pair, + Pair as _, +}; +use sp_keyring::AccountKeyring; +use subxt::{ + extrinsic::{ + PairSigner, + Signer, + }, + Error, + EventSubscription, + PalletError, + RuntimeError, +}; + +#[async_std::test] +async fn tx_basic_transfer() { + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let bob = PairSigner::::new(AccountKeyring::Bob.pair()); + let bob_address = bob.account_id().clone().into(); + let cxt = test_context().await; + let api = &cxt.api; + + let alice_pre = api + .storage() + .system() + .account(alice.account_id().clone().into(), None) + .await + .unwrap(); + let bob_pre = api + .storage() + .system() + .account(bob.account_id().clone().into(), None) + .await + .unwrap(); + + let result = api + .tx() + .balances() + .transfer(bob_address, 10_000) + .sign_and_submit_then_watch(&alice) + .await + .unwrap(); + let event = result + .find_event::() + .unwrap() + .unwrap(); + let _extrinsic_success = result + .find_event::() + .expect("Failed to decode ExtrinisicSuccess".into()) + .expect("Failed to find ExtrinisicSuccess"); + + let expected_event = balances::events::Transfer( + alice.account_id().clone(), + bob.account_id().clone(), + 10_000, + ); + assert_eq!(event, expected_event); + + let alice_post = api + .storage() + .system() + .account(alice.account_id().clone().into(), None) + .await + .unwrap(); + let bob_post = api + .storage() + .system() + .account(bob.account_id().clone().into(), None) + .await + .unwrap(); + + assert!(alice_pre.data.free - 10_000 >= alice_post.data.free); + assert_eq!(bob_pre.data.free + 10_000, bob_post.data.free); +} + +#[async_std::test] +async fn storage_total_issuance() { + let cxt = test_context().await; + let total_issuance = cxt + .api + .storage() + .balances() + .total_issuance(None) + .await + .unwrap(); + assert_ne!(total_issuance, 0); +} + +#[async_std::test] +async fn storage_balance_lock() -> Result<(), subxt::Error> { + let bob = PairSigner::::new(AccountKeyring::Bob.pair()); + let charlie = AccountKeyring::Charlie.to_account_id(); + let cxt = test_context().await; + + let result = cxt + .api + .tx() + .staking() + .bond( + charlie.into(), + 100_000_000_000_000, + runtime_types::pallet_staking::RewardDestination::Stash, + ) + .sign_and_submit_then_watch(&bob) + .await?; + + let success = result.find_event::()?; + assert!(success.is_some(), "No ExtrinsicSuccess Event found"); + + let locks = cxt + .api + .storage() + .balances() + .locks(AccountKeyring::Bob.to_account_id(), None) + .await?; + + assert_eq!( + locks.0, + vec![runtime_types::pallet_balances::BalanceLock { + id: *b"staking ", + amount: 100_000_000_000_000, + reasons: runtime_types::pallet_balances::Reasons::All, + }] + ); + + Ok(()) +} + +#[async_std::test] +async fn transfer_error() { + env_logger::try_init().ok(); + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let alice_addr = alice.account_id().clone().into(); + let hans = PairSigner::::new(Pair::generate().0); + let hans_address = hans.account_id().clone().into(); + let cxt = test_context().await; + + cxt.api + .tx() + .balances() + .transfer(hans_address, 100_000_000_000_000_000) + .sign_and_submit_then_watch(&alice) + .await + .unwrap(); + + let res = cxt + .api + .tx() + .balances() + .transfer(alice_addr, 100_000_000_000_000_000) + .sign_and_submit_then_watch(&hans) + .await; + + if let Err(Error::Runtime(RuntimeError::Module(error))) = res { + let error2 = PalletError { + pallet: "Balances".into(), + error: "InsufficientBalance".into(), + description: vec!["Balance too low to send value".to_string()], + }; + assert_eq!(error, error2); + } else { + panic!("expected an error"); + } +} + +#[async_std::test] +async fn transfer_subscription() { + env_logger::try_init().ok(); + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let bob = AccountKeyring::Bob.to_account_id(); + let bob_addr = bob.clone().into(); + let cxt = test_context().await; + let sub = cxt.client().rpc().subscribe_events().await.unwrap(); + let decoder = cxt.client().events_decoder(); + let mut sub = EventSubscription::::new(sub, &decoder); + sub.filter_event::(); + + cxt.api + .tx() + .balances() + .transfer(bob_addr, 10_000) + .sign_and_submit_then_watch(&alice) + .await + .unwrap(); + + let raw = sub.next().await.unwrap().unwrap(); + let event = balances::events::Transfer::decode(&mut &raw.data[..]).unwrap(); + assert_eq!( + event, + balances::events::Transfer(alice.account_id().clone(), bob.clone(), 10_000,) + ); +} + +#[async_std::test] +async fn constant_existential_deposit() { + let cxt = test_context().await; + let balances_metadata = cxt.client().metadata().pallet("Balances").unwrap(); + let constant_metadata = balances_metadata.constant("ExistentialDeposit").unwrap(); + let existential_deposit = u128::decode(&mut &constant_metadata.value[..]).unwrap(); + assert_eq!(existential_deposit, 100_000_000_000_000); +} diff --git a/tests/integration/frame/contracts.rs b/tests/integration/frame/contracts.rs new file mode 100644 index 0000000000..75e18d38d0 --- /dev/null +++ b/tests/integration/frame/contracts.rs @@ -0,0 +1,212 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use sp_keyring::AccountKeyring; + +use crate::{ + node_runtime::{ + contracts::{ + calls::TransactionApi, + events, + storage, + }, + system, + DefaultConfig, + }, + test_context, + TestContext, +}; +use sp_core::sr25519::Pair; +use sp_runtime::MultiAddress; +use subxt::{ + Client, + Config, + Error, + ExtrinsicSuccess, + PairSigner, +}; + +struct ContractsTestContext { + cxt: TestContext, + signer: PairSigner, +} + +type Hash = ::Hash; +type AccountId = ::AccountId; + +impl ContractsTestContext { + async fn init() -> Self { + let cxt = test_context().await; + let signer = PairSigner::new(AccountKeyring::Alice.pair()); + + Self { cxt, signer } + } + + fn client(&self) -> &Client { + &self.cxt.client() + } + + fn contracts_tx(&self) -> TransactionApi { + self.cxt.api.tx().contracts() + } + + async fn instantiate_with_code(&self) -> Result<(Hash, AccountId), Error> { + log::info!("instantiate_with_code:"); + const CONTRACT: &str = r#" + (module + (func (export "call")) + (func (export "deploy")) + ) + "#; + let code = wabt::wat2wasm(CONTRACT).expect("invalid wabt"); + + let result = self + .cxt + .api + .tx() + .contracts() + .instantiate_with_code( + 100_000_000_000_000_000, // endowment + 500_000_000_000, // gas_limit + code, + vec![], // data + vec![], // salt + ) + .sign_and_submit_then_watch(&self.signer) + .await?; + + let code_stored = result + .find_event::()? + .ok_or_else(|| Error::Other("Failed to find a CodeStored event".into()))?; + let instantiated = result + .find_event::()? + .ok_or_else(|| Error::Other("Failed to find a Instantiated event".into()))?; + let _extrinsic_success = result + .find_event::()? + .ok_or_else(|| { + Error::Other("Failed to find a ExtrinsicSuccess event".into()) + })?; + + log::info!(" Block hash: {:?}", result.block); + log::info!(" Code hash: {:?}", code_stored.code_hash); + log::info!(" Contract address: {:?}", instantiated.contract); + Ok((code_stored.code_hash, instantiated.contract)) + } + + async fn instantiate( + &self, + code_hash: Hash, + data: Vec, + salt: Vec, + ) -> Result { + // call instantiate extrinsic + let result = self + .contracts_tx() + .instantiate( + 100_000_000_000_000_000, // endowment + 500_000_000_000, // gas_limit + code_hash, + data, + salt, + ) + .sign_and_submit_then_watch(&self.signer) + .await?; + + log::info!("Instantiate result: {:?}", result); + let instantiated = result + .find_event::()? + .ok_or_else(|| Error::Other("Failed to find a Instantiated event".into()))?; + + Ok(instantiated.contract) + } + + async fn call( + &self, + contract: AccountId, + input_data: Vec, + ) -> Result, Error> { + log::info!("call: {:?}", contract); + let result = self + .contracts_tx() + .call( + MultiAddress::Id(contract), + 0, // value + 500_000_000, // gas_limit + input_data, + ) + .sign_and_submit_then_watch(&self.signer) + .await?; + + log::info!("Call result: {:?}", result); + Ok(result) + } +} + +#[async_std::test] +async fn tx_instantiate_with_code() { + let ctx = ContractsTestContext::init().await; + let result = ctx.instantiate_with_code().await; + + assert!( + result.is_ok(), + "Error calling instantiate_with_code and receiving CodeStored and Instantiated Events: {:?}", + result + ); +} + +#[async_std::test] +async fn tx_instantiate() { + let ctx = ContractsTestContext::init().await; + let (code_hash, _) = ctx.instantiate_with_code().await.unwrap(); + + let instantiated = ctx.instantiate(code_hash.into(), vec![], vec![1u8]).await; + + assert!( + instantiated.is_ok(), + "Error instantiating contract: {:?}", + instantiated + ); +} + +#[async_std::test] +async fn tx_call() { + let cxt = ContractsTestContext::init().await; + let (_, contract) = cxt.instantiate_with_code().await.unwrap(); + + let contract_info = cxt + .cxt + .api + .storage() + .contracts() + .contract_info_of(contract.clone(), None) + .await; + assert!(contract_info.is_ok()); + + let keys = cxt + .client() + .storage() + .fetch_keys::(5, None, None) + .await + .unwrap() + .iter() + .map(|key| hex::encode(&key.0)) + .collect::>(); + println!("keys post: {:?}", keys); + + let executed = cxt.call(contract, vec![]).await; + + assert!(executed.is_ok(), "Error calling contract: {:?}", executed); +} diff --git a/tests/integration/frame/mod.rs b/tests/integration/frame/mod.rs new file mode 100644 index 0000000000..8d18e46748 --- /dev/null +++ b/tests/integration/frame/mod.rs @@ -0,0 +1,23 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +//! Test interactions with some built-in FRAME pallets. + +mod balances; +mod contracts; +mod staking; +mod sudo; +mod system; diff --git a/tests/integration/frame/staking.rs b/tests/integration/frame/staking.rs new file mode 100644 index 0000000000..24071a0043 --- /dev/null +++ b/tests/integration/frame/staking.rs @@ -0,0 +1,258 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use crate::{ + node_runtime::{ + runtime_types::pallet_staking::{ + RewardDestination, + ValidatorPrefs, + }, + staking, + system, + DefaultConfig, + }, + test_context, +}; +use assert_matches::assert_matches; +use sp_core::{ + sr25519, + Pair, +}; +use sp_keyring::AccountKeyring; +use subxt::{ + extrinsic::{ + PairSigner, + Signer, + }, + Error, + RuntimeError, +}; + +/// Helper function to generate a crypto pair from seed +fn get_from_seed(seed: &str) -> sr25519::Pair { + sr25519::Pair::from_string(&format!("//{}", seed), None) + .expect("static values are valid; qed") +} + +fn default_validator_prefs() -> ValidatorPrefs { + ValidatorPrefs { + commission: sp_runtime::Perbill::default(), + blocked: false, + } +} + +#[async_std::test] +async fn validate_with_controller_account() -> Result<(), Error> { + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let cxt = test_context().await; + let result = cxt + .api + .tx() + .staking() + .validate(default_validator_prefs()) + .sign_and_submit_then_watch(&alice) + .await?; + + let success = result.find_event::()?; + assert!(success.is_some()); + + Ok(()) +} + +#[async_std::test] +async fn validate_not_possible_for_stash_account() -> Result<(), Error> { + let alice_stash = PairSigner::::new(get_from_seed("Alice//stash")); + let cxt = test_context().await; + let announce_validator = cxt + .api + .tx() + .staking() + .validate(default_validator_prefs()) + .sign_and_submit_then_watch(&alice_stash) + .await; + assert_matches!(announce_validator, Err(Error::Runtime(RuntimeError::Module(module_err))) => { + assert_eq!(module_err.pallet, "Staking"); + assert_eq!(module_err.error, "NotController"); + }); + Ok(()) +} + +#[async_std::test] +async fn nominate_with_controller_account() -> Result<(), Error> { + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let bob = PairSigner::::new(AccountKeyring::Bob.pair()); + let cxt = test_context().await; + + let result = cxt + .api + .tx() + .staking() + .nominate(vec![bob.account_id().clone().into()]) + .sign_and_submit_then_watch(&alice) + .await?; + + let success = result.find_event::()?; + assert!(success.is_some()); + + Ok(()) +} + +#[async_std::test] +async fn nominate_not_possible_for_stash_account() -> Result<(), Error> { + let alice_stash = + PairSigner::::new(get_from_seed("Alice//stash")); + let bob = PairSigner::::new(AccountKeyring::Bob.pair()); + let cxt = test_context().await; + + let nomination = cxt + .api + .tx() + .staking() + .nominate(vec![bob.account_id().clone().into()]) + .sign_and_submit_then_watch(&alice_stash) + .await; + + assert_matches!(nomination, Err(Error::Runtime(RuntimeError::Module(module_err))) => { + assert_eq!(module_err.pallet, "Staking"); + assert_eq!(module_err.error, "NotController"); + }); + Ok(()) +} + +#[async_std::test] +async fn chill_works_for_controller_only() -> Result<(), Error> { + let alice_stash = + PairSigner::::new(get_from_seed("Alice//stash")); + let bob_stash = + PairSigner::::new(get_from_seed("Bob//stash")); + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let cxt = test_context().await; + + // this will fail the second time, which is why this is one test, not two + cxt.api + .tx() + .staking() + .nominate(vec![bob_stash.account_id().clone().into()]) + .sign_and_submit_then_watch(&alice) + .await?; + + let ledger = cxt + .api + .storage() + .staking() + .ledger(alice.account_id().clone(), None) + .await? + .unwrap(); + assert_eq!(alice_stash.account_id(), &ledger.stash); + + let chill = cxt + .api + .tx() + .staking() + .chill() + .sign_and_submit_then_watch(&alice_stash) + .await; + + assert_matches!(chill, Err(Error::Runtime(RuntimeError::Module(module_err))) => { + assert_eq!(module_err.pallet, "Staking"); + assert_eq!(module_err.error, "NotController"); + }); + + let result = cxt + .api + .tx() + .staking() + .chill() + .sign_and_submit_then_watch(&alice) + .await?; + let chill = result.find_event::()?; + assert!(chill.is_some()); + Ok(()) +} + +#[async_std::test] +async fn tx_bond() -> Result<(), Error> { + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let cxt = test_context().await; + + let bond = cxt + .api + .tx() + .staking() + .bond( + AccountKeyring::Bob.to_account_id().into(), + 100_000_000_000_000, + RewardDestination::Stash, + ) + .sign_and_submit_then_watch(&alice) + .await; + + assert!(bond.is_ok()); + + let bond_again = cxt + .api + .tx() + .staking() + .bond( + AccountKeyring::Bob.to_account_id().into(), + 100_000_000_000_000, + RewardDestination::Stash, + ) + .sign_and_submit_then_watch(&alice) + .await; + + assert_matches!(bond_again, Err(Error::Runtime(RuntimeError::Module(module_err))) => { + assert_eq!(module_err.pallet, "Staking"); + assert_eq!(module_err.error, "AlreadyBonded"); + }); + + Ok(()) +} + +#[async_std::test] +async fn storage_history_depth() -> Result<(), Error> { + let cxt = test_context().await; + let history_depth = cxt.api.storage().staking().history_depth(None).await?; + assert_eq!(history_depth, 84); + Ok(()) +} + +#[async_std::test] +async fn storage_current_era() -> Result<(), Error> { + let cxt = test_context().await; + let _current_era = cxt + .api + .storage() + .staking() + .current_era(None) + .await? + .expect("current era always exists"); + Ok(()) +} + +#[async_std::test] +async fn storage_era_reward_points() -> Result<(), Error> { + let cxt = test_context().await; + let current_era_result = cxt + .api + .storage() + .staking() + .eras_reward_points(0, None) + .await; + assert!(current_era_result.is_ok()); + + Ok(()) +} diff --git a/tests/integration/frame/sudo.rs b/tests/integration/frame/sudo.rs new file mode 100644 index 0000000000..071f650437 --- /dev/null +++ b/tests/integration/frame/sudo.rs @@ -0,0 +1,77 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use crate::{ + node_runtime::{ + runtime_types, + sudo, + DefaultConfig, + }, + test_context, +}; +use assert_matches::assert_matches; +use sp_keyring::AccountKeyring; +use subxt::extrinsic::PairSigner; + +type Call = runtime_types::node_runtime::Call; +type BalancesCall = runtime_types::pallet_balances::pallet::Call; + +#[async_std::test] +async fn test_sudo() { + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let bob = AccountKeyring::Bob.to_account_id().clone().into(); + let cxt = test_context().await; + + let call = Call::Balances(BalancesCall::transfer { + dest: bob, + value: 10_000, + }); + + let res = cxt + .api + .tx() + .sudo() + .sudo(call) + .sign_and_submit_then_watch(&alice) + .await + .unwrap(); + let sudid = res.find_event::(); + assert_matches!(sudid, Ok(Some(_))) +} + +#[async_std::test] +async fn test_sudo_unchecked_weight() { + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let bob = AccountKeyring::Bob.to_account_id().into(); + let cxt = test_context().await; + + let call = Call::Balances(BalancesCall::transfer { + dest: bob, + value: 10_000, + }); + + let res = cxt + .api + .tx() + .sudo() + .sudo_unchecked_weight(call, 0) + .sign_and_submit_then_watch(&alice) + .await + .unwrap(); + + let sudid = res.find_event::(); + assert_matches!(sudid, Ok(Some(_))) +} diff --git a/tests/integration/frame/system.rs b/tests/integration/frame/system.rs new file mode 100644 index 0000000000..5f0205e598 --- /dev/null +++ b/tests/integration/frame/system.rs @@ -0,0 +1,61 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use crate::{ + node_runtime::{ + system, + DefaultConfig, + }, + test_context, +}; +use assert_matches::assert_matches; +use sp_keyring::AccountKeyring; +use subxt::extrinsic::{ + PairSigner, + Signer, +}; + +#[async_std::test] +async fn storage_account() { + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + + let cxt = test_context().await; + let account_info = cxt + .api + .storage() + .system() + .account(alice.account_id().clone().into(), None) + .await; + assert_matches!(account_info, Ok(_)) +} + +#[async_std::test] +async fn tx_remark_with_event() { + let alice = PairSigner::::new(AccountKeyring::Alice.pair()); + let cxt = test_context().await; + + let result = cxt + .api + .tx() + .system() + .remark_with_event(b"remarkable".to_vec()) + .sign_and_submit_then_watch(&alice) + .await + .unwrap(); + + let remarked = result.find_event::(); + assert_matches!(remarked, Ok(Some(_))); +} diff --git a/tests/integration/main.rs b/tests/integration/main.rs new file mode 100644 index 0000000000..40ee6f31b7 --- /dev/null +++ b/tests/integration/main.rs @@ -0,0 +1,27 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +mod codegen; +mod runtime; +mod utils; + +#[cfg(test)] +mod client; +#[cfg(test)] +mod frame; + +pub use runtime::node_runtime; +pub use utils::*; diff --git a/tests/integration/node_runtime.scale b/tests/integration/node_runtime.scale new file mode 100644 index 0000000000..cff019d32c Binary files /dev/null and b/tests/integration/node_runtime.scale differ diff --git a/tests/integration/runtime.rs b/tests/integration/runtime.rs new file mode 100644 index 0000000000..cccef567f4 --- /dev/null +++ b/tests/integration/runtime.rs @@ -0,0 +1,24 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +#[subxt::subxt( + runtime_metadata_path = "tests/integration/node_runtime.scale", + generated_type_derives = "Debug, Eq, PartialEq" +)] +pub mod node_runtime { + #[subxt(substitute_type = "sp_arithmetic::per_things::Perbill")] + use sp_runtime::Perbill; +} diff --git a/tests/integration/utils/context.rs b/tests/integration/utils/context.rs new file mode 100644 index 0000000000..321a568ca4 --- /dev/null +++ b/tests/integration/utils/context.rs @@ -0,0 +1,70 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +pub use crate::{ + node_runtime::{ + self, + DefaultConfig, + }, + TestNodeProcess, +}; + +use sp_keyring::AccountKeyring; +use subxt::Client; + +/// substrate node should be installed on the $PATH +const SUBSTRATE_NODE_PATH: &str = "substrate"; + +pub async fn test_node_process_with( + key: AccountKeyring, +) -> TestNodeProcess { + let path = std::env::var("SUBSTRATE_NODE_PATH").unwrap_or_else(|_| { + if which::which(SUBSTRATE_NODE_PATH).is_err() { + panic!("A substrate binary should be installed on your path for integration tests. \ + See https://github.com/paritytech/subxt/tree/master#integration-testing") + } + SUBSTRATE_NODE_PATH.to_string() + }); + + let proc = TestNodeProcess::::build(path.as_str()) + .with_authority(key) + .scan_for_open_ports() + .spawn::() + .await; + proc.unwrap() +} + +pub async fn test_node_process() -> TestNodeProcess { + test_node_process_with(AccountKeyring::Alice).await +} + +pub struct TestContext { + pub node_proc: TestNodeProcess, + pub api: node_runtime::RuntimeApi, +} + +impl TestContext { + pub fn client(&self) -> &Client { + &self.api.client + } +} + +pub async fn test_context() -> TestContext { + env_logger::try_init().ok(); + let node_proc = test_node_process_with(AccountKeyring::Alice).await; + let api = node_proc.client().clone().to_runtime_api(); + TestContext { node_proc, api } +} diff --git a/tests/integration/utils/mod.rs b/tests/integration/utils/mod.rs new file mode 100644 index 0000000000..d0e05c8234 --- /dev/null +++ b/tests/integration/utils/mod.rs @@ -0,0 +1,21 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +mod context; +mod node_proc; + +pub use context::*; +pub use node_proc::TestNodeProcess; diff --git a/src/tests/node_proc.rs b/tests/integration/utils/node_proc.rs similarity index 91% rename from src/tests/node_proc.rs rename to tests/integration/utils/node_proc.rs index 9f33d6985c..41505a0cc6 100644 --- a/src/tests/node_proc.rs +++ b/tests/integration/utils/node_proc.rs @@ -1,5 +1,5 @@ // Copyright 2019-2021 Parity Technologies (UK) Ltd. -// This file is part of substrate-subxt. +// This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -12,13 +12,8 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with substrate-subxt. If not, see . +// along with subxt. If not, see . -use crate::{ - Client, - ClientBuilder, - Runtime, -}; use sp_keyring::AccountKeyring; use std::{ ffi::{ @@ -34,16 +29,21 @@ use std::{ thread, time, }; +use subxt::{ + Client, + ClientBuilder, + Config, +}; /// Spawn a local substrate node for testing subxt. -pub struct TestNodeProcess { +pub struct TestNodeProcess { proc: process::Child, client: Client, } impl Drop for TestNodeProcess where - R: Runtime, + R: Config, { fn drop(&mut self) { let _ = self.kill(); @@ -52,7 +52,7 @@ where impl TestNodeProcess where - R: Runtime, + R: Config, { /// Construct a builder for spawning a test node process. pub fn build(program: S) -> TestNodeProcessBuilder @@ -119,7 +119,7 @@ impl TestNodeProcessBuilder { /// Spawn the substrate node at the given path, and wait for rpc to be initialized. pub async fn spawn(&self) -> Result, String> where - R: Runtime, + R: Config, { let mut cmd = process::Command::new(&self.node_path); cmd.env("RUST_LOG", "error").arg("--dev").arg("--tmp"); @@ -153,27 +153,23 @@ impl TestNodeProcessBuilder { ) })?; // wait for rpc to be initialized - const MAX_ATTEMPTS: u32 = 10; + const MAX_ATTEMPTS: u32 = 6; let mut attempts = 1; + let mut wait_secs = 1; let client = loop { - thread::sleep(time::Duration::from_secs(1)); + thread::sleep(time::Duration::from_secs(wait_secs)); log::info!( "Connecting to contracts enabled node, attempt {}/{}", attempts, MAX_ATTEMPTS ); - let result = ClientBuilder::::new() - .set_url(ws_url.clone()) - .build() - .await; + let result = ClientBuilder::new().set_url(ws_url.clone()).build().await; match result { Ok(client) => break Ok(client), - Err(crate::Error::MissingTypeSizes(e)) => { - break Err(crate::Error::MissingTypeSizes(e)) - } Err(err) => { if attempts < MAX_ATTEMPTS { attempts += 1; + wait_secs = wait_secs * 2; // backoff continue } break Err(err) diff --git a/tests/node_runtime.scale b/tests/node_runtime.scale new file mode 100644 index 0000000000..80692153a8 Binary files /dev/null and b/tests/node_runtime.scale differ