From 793c945fbd2de022f523c39a84ee02609ba423a9 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 3 Nov 2021 11:28:59 +0000 Subject: [PATCH] Generate runtime API from metadata (#294) * Remove test macro * Remove client crate * Create tests crate and move pallet specific tests there * Extract client, remove metadata and extra, more demolition * Update substrate dependencies to git dependencies * Remove Store stuff for now * Comment out some Call usages * Add back Runtime trait coped from original System trait * Make subxt lib compile * Delete old proc macros and copy over type generation from chameleon * WIP make transfer balance test pass * Change to subxt attribute macro * WIP provide user defined type substitutes * User defined type substitutes compile * WIP submitting transactions * WIP transfer balance test * Fix macro * Cargo fmt * WIP generating storage hashers * WIP add AccountData trait for fetching the nonce * Support single type storage map keys * WIP impl AccountInfo retrieval * Fix up storage struct generation * Implement AccountData triait directly on storage entry * Borrow storage map key and convert account id * Implement storage fetch client methods * Remove legacy metadata storage key construction * Rename CheckEra to CheckMortality * Substitute perthings types for compact impls * Fmt * Downgrade dyn-clone for cargo-contract compat * Scale-fo 1.0 * scale-info 1.0 * Remove special range handling * Restore wildcard type params * Frame metadata 14.0 * WIP decoding events * WIP more dynamically decoding events * Fmt * Decode events, handle errors * Uncomment some tests * Remove unused get_mod function * Fix some warnings * Fix some more warnings * Fix some more warnings * Add tests mod * Rename node-runtime tests mod to frame * Fix some warnings * Fmt * WIP generate storage client with getters * Storage client compiling * Generate storage client api * Fix up system account query account ids * WIP generating tx api fns * Only generate tx api fields when calls available * Fix tx api call fns * Fmt * WIP generate event structs * call functions not async * Derive Eq for comparison on generated types * Generate event structs * Fix call name * Fmt * Update node runtime metadata to substrate c000780db * Download latest substrate release for integration testing * Fix event decoding * Remove unused imports * Fix plain storage access, total_issuance pass * Fmt * Restore contracts tests * Backoff connecting to substrate node * Add required TypeInfo impls for local SignedExtension impls * Remove unnecessary assert formatting * Fix handling of DispatchError * Refactor contracts tests * Troubleshooting contract not found * Remove more client feature stuff * Fix dynamic event variant decoding, write consumed index to output * Fmt * Use substrate branch with heavy dependency removed * Remove sp-rcp dependency, define types locally * Ignore cargo timeing files * Use my branch for substrate test deps * Fix storage key type gen * Comment out fetching contract info * Add key iteration, extract storage client from main client * Debugging key generation * Use substrate master branch * Fix call test * Remove TypeSegmenter and dynclone dependency * Publicly expose Rpc mod * Unused import warnings * Add getter for runtime metadata * Add pallet and event indices for raw events * Add is_call and is_event convenience trait functions * Add missing docs * Refactor tests crate * Restore remaining client tests * Fmt * Fix warnings * Restore get_mod as test helper and fmt * Use client references for api calls * Fix api usages with methods * Use Bytes for RawEvent debug * Update metadata * Restoring some Balances tests * Populate runtime storage metadata * Restore balances lock test * Restore Balances error test * Fmt * Restore transfer subscription API * Staking test * Restore another staking test * Restore another staking test * Restore another staking test * Partially restore chill_works_for_controller_only staking test * Fix fetching Optional storage entries * Restore staking bond test * Restore remaining staking tests * Fmt * Restore sudo tests * Add some system tests * Fmt * Resolve some todos * Remove pass through rpc methods on Client, expose via rpc() getter * Remove more rpc pass through methods * Remove submit tx pass through rpc methods * Add some comments to SubmittableExtrinsic methods * Construct the runtime api from the client * Fmt * Use From trait instead of new for AccountData query * Rename subxt_proc_macro crate to subxt_macro * Fix AccountData From impl * Extract codegen crate from macro crate * Fmt * Replace chameleon hidden field name * Extract StructDef for generating structs * More refactoring of StructDef, moving towards sharing with typegen * Replace explicit tests crate with single implicit integration tests crate * Rename from substrate-subxt to subxt * Fix runtime path relative to root Cargo.toml * Move RpcClient creation to RpcClient * WIP get examples to compile * Rename Runtime to Config trait * WIP implementing default Config * WIP implementing default extrinsic extras * fix metadata constants (#299) * Move DefaultConfig definition and impl to macro * Extract type substitute parsing to ir mod * Extract calls, events and storage from api generation * Add some hardcoded type substitute defaults * Fmt * Add utility pallet tests (#300) * add batch call test example * add pallet utility tests * add utility module * fix warnings * Add polkadot runtime metadata for example * Fix system errors and fmt * Add subxt-cli crate * Add metadata and codegen subcommands * Make subxt-cli codegen command work * Fmt * Add polkadot codegen test * Comment about how to run codegen * Derive AsCompact for structs with single concrete unsigned int field * Fix bitvec codegen, adds as non optional dependency * Regenerate polkadot api with bitvec fix * Edition 2021 * Fix polkadot codegen with bitvec * Polkadot balance transfer is working * Fix fetch remote * Fix transfer_subscribe example * Fix submit_and_watch example * Fmt * Generate storage iter method for iterating over keys * Fmt * Fix existential deposit test * Fix staking tests * Add option for custom generated type derives * Add generated type derives for test runtime api * Fmt * Copy WrapperTypeOpaque from substrate, add Encode/Decode * Fmt * Extract type generator to module, separate & fix tests * Fully qualified primitive and prelude types * Fix up remaining type gen tests * Skip formatting of generated polkadot example code * Remove empty utility test file. * Newline * Update cli/src/main.rs Co-authored-by: David * Rename subxt-cli executable to subxt * Update src/client.rs Co-authored-by: David * Add some code docs to TypeGenerator. * Extract TypePath to own file * Extract type def generation to separate file * Renamed ModuleType to TypeDefGen * Fmt * Factor out type parameter from final_key * Fix some type paths * Resolve some todos * Resolve some panic todos in events * Add EventsDecodingError * Decode compact composite types with a single primitive field * Decode compact composite types with a single primitive field * Update src/metadata.rs Co-authored-by: Andrew Plaza * Remove Perbill compact substitute types * Remove todos regarding maintaining Rust code items, promoted to follow up issue. * Remove todo regarding overridding default config impl * Remove todo regarding overridding default Extra * Remove todo regarding AccountData storage type defintion * Remove todo regarding borrowing storage key arguments * Remove type substitution tests todo * Remove `Box` field name type hack todo * Remove Compact todo * Remove sudo todos * Remove BitVec implementation todo * Fmt * Add health warning to README * Fix up health warning Co-authored-by: Paulo Martins Co-authored-by: David Co-authored-by: Andrew Plaza --- .github/workflows/rust.yml | 2 +- .gitignore | 1 + .rustfmt.toml | 2 +- CHANGELOG.md | 134 +-- Cargo.toml | 44 +- FILE_TEMPLATE | 4 +- README.md | 6 +- cli/Cargo.toml | 32 + cli/src/main.rs | 157 ++++ client/.gitignore | 1 - client/Cargo.toml | 39 - client/src/lib.rs | 535 ------------ codegen/Cargo.toml | 21 + codegen/src/api/calls.rs | 101 +++ codegen/src/api/events.rs | 57 ++ codegen/src/api/mod.rs | 347 ++++++++ codegen/src/api/storage.rs | 223 +++++ codegen/src/derives.rs | 52 ++ codegen/src/ir.rs | 146 ++++ codegen/src/lib.rs | 31 + codegen/src/struct_def.rs | 142 ++++ codegen/src/types/mod.rs | 253 ++++++ codegen/src/types/tests.rs | 794 ++++++++++++++++++ codegen/src/types/type_def.rs | 325 +++++++ codegen/src/types/type_path.rs | 253 ++++++ examples/custom_type_derives.rs | 30 + examples/fetch_all_accounts.rs | 32 +- examples/fetch_remote.rs | 25 +- ...ansfer.rs => polkadot_balance_transfer.rs} | 31 +- examples/polkadot_metadata.scale | Bin 0 -> 269992 bytes examples/submit_and_watch.rs | 38 +- examples/transfer_subscribe.rs | 51 +- macro/Cargo.toml | 37 + macro/src/lib.rs | 55 ++ proc-macro/Cargo.toml | 44 - proc-macro/src/call.rs | 167 ---- proc-macro/src/event.rs | 96 --- proc-macro/src/lib.rs | 159 ---- proc-macro/src/module.rs | 271 ------ proc-macro/src/store.rs | 262 ------ proc-macro/src/test.rs | 505 ----------- proc-macro/src/utils.rs | 253 ------ proc-macro/tests/balances.rs | 145 ---- src/client.rs | 280 ++++++ src/config.rs | 105 +++ src/error.rs | 57 +- src/events.rs | 625 ++++++-------- src/extrinsic/extra.rs | 94 +-- src/extrinsic/mod.rs | 41 +- src/extrinsic/signer.rs | 21 +- src/frame/balances.rs | 317 ------- src/frame/contracts.rs | 293 ------- src/frame/mod.rs | 71 -- src/frame/session.rs | 100 --- src/frame/staking.rs | 435 ---------- src/frame/sudo.rs | 122 --- src/frame/system.rs | 215 ----- src/lib.rs | 648 +++----------- src/metadata.rs | 782 +++++------------ src/rpc.rs | 130 +-- src/runtimes.rs | 456 ---------- src/storage.rs | 297 +++++++ src/subscription.rs | 30 +- src/tests/mod.rs | 176 ---- tests/integration/client.rs | 124 +++ tests/integration/codegen/mod.rs | 25 + tests/integration/codegen/polkadot.rs | 1 + tests/integration/frame/balances.rs | 229 +++++ tests/integration/frame/contracts.rs | 212 +++++ tests/integration/frame/mod.rs | 23 + tests/integration/frame/staking.rs | 258 ++++++ tests/integration/frame/sudo.rs | 77 ++ tests/integration/frame/system.rs | 61 ++ tests/integration/main.rs | 27 + tests/integration/node_runtime.scale | Bin 0 -> 302446 bytes tests/integration/runtime.rs | 24 + tests/integration/utils/context.rs | 70 ++ tests/integration/utils/mod.rs | 21 + .../integration/utils}/node_proc.rs | 36 +- tests/node_runtime.scale | Bin 0 -> 301517 bytes 80 files changed, 5920 insertions(+), 6466 deletions(-) create mode 100644 cli/Cargo.toml create mode 100644 cli/src/main.rs delete mode 100644 client/.gitignore delete mode 100644 client/Cargo.toml delete mode 100644 client/src/lib.rs create mode 100644 codegen/Cargo.toml create mode 100644 codegen/src/api/calls.rs create mode 100644 codegen/src/api/events.rs create mode 100644 codegen/src/api/mod.rs create mode 100644 codegen/src/api/storage.rs create mode 100644 codegen/src/derives.rs create mode 100644 codegen/src/ir.rs create mode 100644 codegen/src/lib.rs create mode 100644 codegen/src/struct_def.rs create mode 100644 codegen/src/types/mod.rs create mode 100644 codegen/src/types/tests.rs create mode 100644 codegen/src/types/type_def.rs create mode 100644 codegen/src/types/type_path.rs create mode 100644 examples/custom_type_derives.rs rename examples/{kusama_balance_transfer.rs => polkadot_balance_transfer.rs} (57%) create mode 100644 examples/polkadot_metadata.scale create mode 100644 macro/Cargo.toml create mode 100644 macro/src/lib.rs delete mode 100644 proc-macro/Cargo.toml delete mode 100644 proc-macro/src/call.rs delete mode 100644 proc-macro/src/event.rs delete mode 100644 proc-macro/src/lib.rs delete mode 100644 proc-macro/src/module.rs delete mode 100644 proc-macro/src/store.rs delete mode 100644 proc-macro/src/test.rs delete mode 100644 proc-macro/src/utils.rs delete mode 100644 proc-macro/tests/balances.rs create mode 100644 src/client.rs create mode 100644 src/config.rs delete mode 100644 src/frame/balances.rs delete mode 100644 src/frame/contracts.rs delete mode 100644 src/frame/mod.rs delete mode 100644 src/frame/session.rs delete mode 100644 src/frame/staking.rs delete mode 100644 src/frame/sudo.rs delete mode 100644 src/frame/system.rs delete mode 100644 src/runtimes.rs create mode 100644 src/storage.rs delete mode 100644 src/tests/mod.rs create mode 100644 tests/integration/client.rs create mode 100644 tests/integration/codegen/mod.rs create mode 100644 tests/integration/codegen/polkadot.rs create mode 100644 tests/integration/frame/balances.rs create mode 100644 tests/integration/frame/contracts.rs create mode 100644 tests/integration/frame/mod.rs create mode 100644 tests/integration/frame/staking.rs create mode 100644 tests/integration/frame/sudo.rs create mode 100644 tests/integration/frame/system.rs create mode 100644 tests/integration/main.rs create mode 100644 tests/integration/node_runtime.scale create mode 100644 tests/integration/runtime.rs create mode 100644 tests/integration/utils/context.rs create mode 100644 tests/integration/utils/mod.rs rename {src/tests => tests/integration/utils}/node_proc.rs (91%) create mode 100644 tests/node_runtime.scale 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 b672dabecd..c331d1de90 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 0000000000000000000000000000000000000000..678aba31610e193f4911839737af3e81c12bb4af GIT binary patch literal 269992 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 YaJ$Kv6H@z%@ewzTnB0H=owwirf6dXWO8@`> literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cff019d32c4e1906b0c9afa4de90c857b29cd5a3 GIT binary patch literal 302446 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

%O~0Z)F6Hetmz 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..80692153a8cb67479eeb2a05a7e42334dc3bea02 GIT binary patch literal 301517 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 AbpQYW literal 0 HcmV?d00001