Merge branch 'master' into tadeo-hepperle-rustfmt-config

This commit is contained in:
Tadeo hepperle
2023-05-10 14:24:09 +02:00
111 changed files with 25425 additions and 9762 deletions
Generated
+142 -109
View File
@@ -94,9 +94,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.3.0"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371"
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -133,9 +133,9 @@ dependencies = [
[[package]]
name = "anstyle-wincon"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd"
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
dependencies = [
"anstyle",
"windows-sys 0.48.0",
@@ -143,9 +143,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.70"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "array-bytes"
@@ -194,7 +194,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.14",
"syn 2.0.15",
]
[[package]]
@@ -353,9 +353,9 @@ dependencies = [
[[package]]
name = "bounded-collections"
version = "0.1.5"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a071c348a5ef6da1d3a87166b408170b46002382b1dda83992b5c2208cefb370"
checksum = "e3888522b497857eb606bf51695988dba7096941822c1bcf676e3a929a9ae7a0"
dependencies = [
"log",
"parity-scale-codec",
@@ -371,9 +371,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
[[package]]
name = "bumpalo"
version = "3.12.0"
version = "3.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
[[package]]
name = "byte-slice-cast"
@@ -458,9 +458,9 @@ dependencies = [
[[package]]
name = "clap"
version = "3.2.23"
version = "3.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
dependencies = [
"bitflags",
"clap_lex 0.2.4",
@@ -470,9 +470,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.2.4"
version = "4.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62"
checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938"
dependencies = [
"clap_builder",
"clap_derive",
@@ -481,9 +481,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.2.4"
version = "4.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749"
checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd"
dependencies = [
"anstream",
"anstyle",
@@ -502,7 +502,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.14",
"syn 2.0.15",
]
[[package]]
@@ -605,18 +605,18 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.6"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181"
checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
dependencies = [
"libc",
]
[[package]]
name = "cranelift-entity"
version = "0.93.1"
version = "0.93.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cf583f7b093f291005f9fb1323e2c37f6ee4c7909e39ce016b2e8360d461705"
checksum = "f42ea692c7b450ad18b8c9889661505d51c09ec4380cf1c2d278dbb2da22cae1"
dependencies = [
"serde",
]
@@ -640,7 +640,7 @@ dependencies = [
"atty",
"cast",
"ciborium",
"clap 3.2.23",
"clap 3.2.25",
"criterion-plot",
"itertools",
"lazy_static",
@@ -805,7 +805,7 @@ dependencies = [
"proc-macro2",
"quote",
"scratch",
"syn 2.0.14",
"syn 2.0.15",
]
[[package]]
@@ -822,7 +822,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.14",
"syn 2.0.15",
]
[[package]]
@@ -831,8 +831,18 @@ version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
dependencies = [
"darling_core",
"darling_macro",
"darling_core 0.14.4",
"darling_macro 0.14.4",
]
[[package]]
name = "darling"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944"
dependencies = [
"darling_core 0.20.1",
"darling_macro 0.20.1",
]
[[package]]
@@ -849,17 +859,42 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "darling_core"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.15",
]
[[package]]
name = "darling_macro"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
dependencies = [
"darling_core",
"darling_core 0.14.4",
"quote",
"syn 1.0.109",
]
[[package]]
name = "darling_macro"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
dependencies = [
"darling_core 0.20.1",
"quote",
"syn 2.0.15",
]
[[package]]
name = "derivative"
version = "2.2.0"
@@ -1148,7 +1183,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.14",
"syn 2.0.15",
]
[[package]]
@@ -1309,9 +1344,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f"
checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21"
dependencies = [
"bytes",
"fnv",
@@ -1477,9 +1512,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
version = "0.14.25"
version = "0.14.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899"
checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
dependencies = [
"bytes",
"futures-channel",
@@ -1628,7 +1663,7 @@ dependencies = [
"subxt",
"subxt-codegen",
"subxt-metadata",
"syn 1.0.109",
"syn 2.0.15",
"test-runtime",
"tokio",
"tracing",
@@ -1656,7 +1691,7 @@ checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
"rustix 0.37.11",
"rustix 0.37.19",
"windows-sys 0.48.0",
]
@@ -1795,9 +1830,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.141"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "libm"
@@ -1870,9 +1905,9 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "linux-raw-sys"
version = "0.3.1"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
[[package]]
name = "lock_api"
@@ -1923,7 +1958,7 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffc89ccdc6e10d6907450f753537ebc5c5d3460d2e4e62ea74bd571db62c0f9e"
dependencies = [
"rustix 0.37.11",
"rustix 0.37.19",
]
[[package]]
@@ -2511,7 +2546,7 @@ checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.14",
"syn 2.0.15",
]
[[package]]
@@ -2563,9 +2598,9 @@ dependencies = [
[[package]]
name = "rustc-demangle"
version = "0.1.22"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc-hash"
@@ -2581,9 +2616,9 @@ checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
[[package]]
name = "rustix"
version = "0.36.12"
version = "0.36.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0af200a3324fa5bcd922e84e9b55a298ea9f431a489f01961acdebc6e908f25"
checksum = "3a38f9520be93aba504e8ca974197f46158de5dcaa9fa04b57c57cd6a679d658"
dependencies = [
"bitflags",
"errno",
@@ -2595,15 +2630,15 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.37.11"
version = "0.37.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77"
checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys 0.3.1",
"linux-raw-sys 0.3.7",
"windows-sys 0.48.0",
]
@@ -2692,7 +2727,7 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b38741b2f78e4391b94eac6b102af0f6ea2b0f7fe65adb55d7f4004f507854db"
dependencies = [
"darling",
"darling 0.14.4",
"proc-macro-crate",
"proc-macro2",
"quote",
@@ -2719,7 +2754,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd983cf0a9effd76138554ead18a6de542d1af175ac12fd5e91836c5c0268082"
dependencies = [
"darling",
"darling 0.14.4",
"proc-macro-crate",
"proc-macro2",
"quote",
@@ -2728,9 +2763,9 @@ dependencies = [
[[package]]
name = "scale-info"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cfdffd972d76b22f3d7f81c8be34b2296afd3a25e0a547bd9abe340a4dbbe97"
checksum = "dfdef77228a4c05dc94211441595746732131ad7f6530c6c18f045da7b7ab937"
dependencies = [
"bitvec",
"cfg-if",
@@ -2742,9 +2777,9 @@ dependencies = [
[[package]]
name = "scale-info-derive"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61fa974aea2d63dd18a4ec3a49d59af9f34178c73a4f56d2f18205628d00681e"
checksum = "53012eae69e5aa5c14671942a5dd47de59d4cdcff8532a6dd0e081faf1119482"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@@ -2888,22 +2923,22 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0"
[[package]]
name = "serde"
version = "1.0.160"
version = "1.0.162"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.160"
version = "1.0.162"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.14",
"syn 2.0.15",
]
[[package]]
@@ -2968,9 +3003,9 @@ dependencies = [
[[package]]
name = "sha3"
version = "0.10.6"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9"
checksum = "54c2bb1a323307527314a36bfb73f24febb08ce2b8a554bf4ffd6f51ad15198c"
dependencies = [
"digest 0.10.6",
"keccak",
@@ -3422,9 +3457,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "ss58-registry"
version = "1.39.0"
version = "1.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecf0bd63593ef78eca595a7fc25e9a443ca46fe69fd472f8f09f5245cdcd769d"
checksum = "eb47a8ad42e5fc72d5b1eb104a5546937eaf39843499948bb666d6e93c62423b"
dependencies = [
"Inflector",
"num-format",
@@ -3502,6 +3537,7 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
name = "subxt"
version = "0.28.0"
dependencies = [
"assert_matches",
"base58",
"bitvec",
"blake2",
@@ -3539,7 +3575,7 @@ dependencies = [
name = "subxt-cli"
version = "0.28.0"
dependencies = [
"clap 4.2.4",
"clap 4.2.7",
"color-eyre",
"frame-metadata",
"hex",
@@ -3549,7 +3585,7 @@ dependencies = [
"serde_json",
"subxt-codegen",
"subxt-metadata",
"syn 1.0.109",
"syn 2.0.15",
"tokio",
]
@@ -3558,7 +3594,6 @@ name = "subxt-codegen"
version = "0.28.0"
dependencies = [
"bitvec",
"darling",
"frame-metadata",
"heck",
"hex",
@@ -3569,7 +3604,7 @@ dependencies = [
"quote",
"scale-info",
"subxt-metadata",
"syn 1.0.109",
"syn 2.0.15",
"thiserror",
"tokio",
]
@@ -3580,23 +3615,19 @@ version = "0.28.0"
dependencies = [
"futures",
"hex",
"parity-scale-codec",
"sp-core",
"sp-keyring",
"sp-runtime",
"subxt",
"tokio",
"tracing-subscriber 0.3.17",
]
[[package]]
name = "subxt-macro"
version = "0.28.0"
dependencies = [
"darling",
"darling 0.20.1",
"proc-macro-error",
"subxt-codegen",
"syn 1.0.109",
"syn 2.0.15",
]
[[package]]
@@ -3624,9 +3655,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.14"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf316d5356ed6847742d036f8a39c3b8435cac10bd528a4bd461928a6ab34d5"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote",
@@ -3641,9 +3672,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "target-lexicon"
version = "0.12.6"
version = "0.12.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5"
checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5"
[[package]]
name = "termcolor"
@@ -3658,8 +3689,10 @@ dependencies = [
name = "test-runtime"
version = "0.28.0"
dependencies = [
"hex",
"impl-serde",
"jsonrpsee",
"parity-scale-codec",
"serde",
"substrate-runner",
"subxt",
@@ -3690,7 +3723,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.14",
"syn 2.0.15",
]
[[package]]
@@ -3749,9 +3782,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.27.0"
version = "1.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f"
dependencies = [
"autocfg",
"bytes",
@@ -3761,18 +3794,18 @@ dependencies = [
"pin-project-lite",
"socket2",
"tokio-macros",
"windows-sys 0.45.0",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.0.0"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce"
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.14",
"syn 2.0.15",
]
[[package]]
@@ -3788,9 +3821,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.7"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
dependencies = [
"bytes",
"futures-core",
@@ -3838,13 +3871,13 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.23"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.15",
]
[[package]]
@@ -4243,9 +4276,9 @@ dependencies = [
[[package]]
name = "wasmtime"
version = "6.0.1"
version = "6.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6e89f9819523447330ffd70367ef4a18d8c832e24e8150fe054d1d912841632"
checksum = "76a222f5fa1e14b2cefc286f1b68494d7a965f4bf57ec04c59bb62673d639af6"
dependencies = [
"anyhow",
"bincode",
@@ -4268,18 +4301,18 @@ dependencies = [
[[package]]
name = "wasmtime-asm-macros"
version = "6.0.1"
version = "6.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bd3a5e46c198032da934469f3a6e48649d1f9142438e4fd4617b68a35644b8a"
checksum = "4407a7246e7d2f3d8fb1cf0c72fda8dbafdb6dd34d555ae8bea0e5ae031089cc"
dependencies = [
"cfg-if",
]
[[package]]
name = "wasmtime-environ"
version = "6.0.1"
version = "6.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a6db9fc52985ba06ca601f2ff0ff1f526c5d724c7ac267b47326304b0c97883"
checksum = "47b8b50962eae38ee319f7b24900b7cf371f03eebdc17400c1dc8575fc10c9a7"
dependencies = [
"anyhow",
"cranelift-entity",
@@ -4296,9 +4329,9 @@ dependencies = [
[[package]]
name = "wasmtime-jit"
version = "6.0.1"
version = "6.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b77e3a52cd84d0f7f18554afa8060cfe564ccac61e3b0802d3fd4084772fa5f6"
checksum = "ffaed4f9a234ba5225d8e64eac7b4a5d13b994aeb37353cde2cbeb3febda9eaa"
dependencies = [
"addr2line 0.17.0",
"anyhow",
@@ -4319,18 +4352,18 @@ dependencies = [
[[package]]
name = "wasmtime-jit-debug"
version = "6.0.1"
version = "6.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0245e8a9347017c7185a72e215218a802ff561545c242953c11ba00fccc930f"
checksum = "eed41cbcbf74ce3ff6f1d07d1b707888166dc408d1a880f651268f4f7c9194b2"
dependencies = [
"once_cell",
]
[[package]]
name = "wasmtime-jit-icache-coherence"
version = "6.0.1"
version = "6.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67d412e9340ab1c83867051d8d1d7c90aa8c9afc91da086088068e2734e25064"
checksum = "43a28ae1e648461bfdbb79db3efdaee1bca5b940872e4175390f465593a2e54c"
dependencies = [
"cfg-if",
"libc",
@@ -4339,9 +4372,9 @@ dependencies = [
[[package]]
name = "wasmtime-runtime"
version = "6.0.1"
version = "6.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d594e791b5fdd4dbaf8cf7ae62f2e4ff85018ce90f483ca6f42947688e48827d"
checksum = "e704b126e4252788ccfc3526d4d4511d4b23c521bf123e447ac726c14545217b"
dependencies = [
"anyhow",
"cc",
@@ -4354,7 +4387,7 @@ dependencies = [
"memoffset 0.6.5",
"paste",
"rand 0.8.5",
"rustix 0.36.12",
"rustix 0.36.13",
"wasmtime-asm-macros",
"wasmtime-environ",
"wasmtime-jit-debug",
@@ -4363,9 +4396,9 @@ dependencies = [
[[package]]
name = "wasmtime-types"
version = "6.0.1"
version = "6.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6688d6f96d4dbc1f89fab626c56c1778936d122b5f4ae7a57c2eb42b8d982e2"
checksum = "83e5572c5727c1ee7e8f28717aaa8400e4d22dcbd714ea5457d85b5005206568"
dependencies = [
"cranelift-entity",
"serde",
@@ -4602,9 +4635,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winnow"
version = "0.4.1"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
dependencies = [
"memchr",
]
@@ -4647,5 +4680,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.14",
"syn 2.0.15",
]
+65
View File
@@ -11,14 +11,79 @@ members = [
"metadata",
"subxt"
]
# This cannot be a workspace dependency, because it requires
# mutually exclusive jsonrpsee features to work, and workspaces
# will aggregate features used across crates:
exclude = ["testing/wasm-tests"]
resolver = "2"
[workspace.package]
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
version = "0.28.0"
rust-version = "1.64.0"
license = "Apache-2.0 OR GPL-3.0"
repository = "https://github.com/paritytech/subxt"
documentation = "https://docs.rs/subxt"
homepage = "https://www.parity.io/"
[workspace.dependencies]
assert_matches = "1.5.0"
base58 = { version = "0.2.0" }
bitvec = { version = "1", default-features = false }
blake2 = { version = "0.10.4", default-features = false }
clap = { version = "4.2.5", features = ["derive", "cargo"] }
criterion = "0.4"
codec = { package = "parity-scale-codec", version = "3.4.0", default-features = false }
color-eyre = "0.6.1"
console_error_panic_hook = "0.1.7"
darling = "0.20.0"
derivative = "2.2.0"
either = "1.8.1"
frame-metadata = { version = "15.1.0", features = ["v14", "v15-unstable", "std"] }
futures = { version = "0.3.27", default-features = false, features = ["std"] }
getrandom = "0.2"
hex = "0.4.3"
heck = "0.4.1"
impl-serde = { version = "0.4.0" }
jsonrpsee = { version = "0.16" }
parking_lot = "0.12.0"
pretty_assertions = "1.0.0"
primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "scale-info", "serde"] }
proc-macro2 = "1.0.55"
quote = "1.0.8"
regex = "1.8.1"
scale-info = "2.6.0"
scale-value = "0.7.0"
scale-bits = "0.3"
scale-decode = "0.5.0"
scale-encode = "0.1.0"
serde = { version = "1.0.162" }
serde_json = { version = "1.0.96" }
syn = { version = "2.0.15", features = ["full", "extra-traits"] }
thiserror = "1.0.40"
tokio = { version = "1.28", features = ["macros", "time", "rt-multi-thread"] }
tracing = "0.1.34"
tracing-wasm = "0.2.1"
tracing-subscriber = "0.3.17"
trybuild = "1.0.79"
proc-macro-error = "1.0.4"
wabt = "0.10.0"
wasm-bindgen-test = "0.3.24"
which = "4.4.0"
# Substrate crates:
sp-core = { version = "20.0.0", default-features = false }
sp-core-hashing = "8.0.0"
sp-keyring = "23.0.0"
sp-runtime = "23.0.0"
sp-version = "21.0.0"
# Subxt workspace crates:
subxt = { version = "0.28.0", path = "subxt" }
subxt-macro = { version = "0.28.0", path = "macro" }
subxt-metadata = { version = "0.28.0", path = "metadata" }
subxt-codegen = { version = "0.28.0", path = "codegen" }
test-runtime = { path = "testing/test-runtime" }
substrate-runner = { path = "testing/substrate-runner" }
Binary file not shown.
+13 -25
View File
@@ -1,6 +1,6 @@
[package]
name = "subxt-cli"
version = "0.28.0"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
@@ -17,27 +17,15 @@ name = "subxt"
path = "src/main.rs"
[dependencies]
# perform subxt codegen
subxt-codegen = { version = "0.28.0", path = "../codegen" }
# perform node compatibility
subxt-metadata = { version = "0.28.0", path = "../metadata" }
# parse command line args
clap = { version = "4.2.4", features = ["derive", "cargo"] }
# colourful error reports
color-eyre = "0.6.1"
# serialize the metadata
serde = { version = "1.0.159", features = ["derive"] }
# serialize as json
serde_json = "1.0.96"
# hex encoded metadata to bytes
hex = "0.4.3"
# actual metadata types
frame-metadata = { version = "15.1.0", features = ["v14", "v15-unstable", "std"] }
# decode bytes into the metadata types
scale = { package = "parity-scale-codec", version = "3.0.0", default-features = false }
# generate the item mod for codegen
syn = "1.0.109"
# communicate with the substrate nodes
jsonrpsee = { version = "0.16.0", features = ["async-client", "client-ws-transport", "http-client"] }
# async runtime
tokio = { version = "1.27", features = ["rt-multi-thread", "macros", "time"] }
subxt-codegen = { workspace = true }
subxt-metadata = { workspace = true }
clap = { workspace = true }
serde = { workspace = true, features = ["derive"] }
color-eyre = { workspace = true }
serde_json = { workspace = true }
hex = { workspace = true }
frame-metadata = { workspace = true }
codec = { package = "parity-scale-codec", workspace = true }
syn = { workspace = true }
jsonrpsee = { workspace = true, features = ["async-client", "client-ws-transport", "http-client"] }
tokio = { workspace = true }
+13 -1
View File
@@ -56,6 +56,11 @@ pub struct Opts {
/// Defaults to `false` (default trait derivations are provided).
#[clap(long)]
no_default_derives: bool,
/// Do not provide default substitutions for the generated types.
///
/// Defaults to `false` (default substitutions are provided).
#[clap(long)]
no_default_substitutions: bool,
}
fn derive_for_type_parser(src: &str) -> Result<(String, String), String> {
@@ -96,6 +101,7 @@ pub async fn run(opts: Opts) -> color_eyre::Result<()> {
opts.no_docs,
opts.runtime_types_only,
opts.no_default_derives,
opts.no_default_substitutions,
)?;
Ok(())
}
@@ -121,6 +127,7 @@ fn codegen(
no_docs: bool,
runtime_types_only: bool,
no_default_derives: bool,
no_default_substitutions: bool,
) -> color_eyre::Result<()> {
let item_mod = syn::parse_quote!(
pub mod api {}
@@ -155,7 +162,12 @@ fn codegen(
derives.extend_for_type(ty, vec![], std::iter::once(attribute.0));
}
let mut type_substitutes = TypeSubstitutes::new(&crate_path);
let mut type_substitutes = if no_default_substitutions {
TypeSubstitutes::new()
} else {
TypeSubstitutes::with_default_substitutes(&crate_path)
};
for (from_str, to_str) in substitute_types {
let from: syn::Path = syn::parse_str(&from_str)?;
let to: syn::Path = syn::parse_str(&to_str)?;
+32 -9
View File
@@ -3,14 +3,15 @@
// see LICENSE for license details.
use clap::Parser as ClapParser;
use codec::Decode;
use color_eyre::eyre::{self, WrapErr};
use frame_metadata::{
v15::RuntimeMetadataV15, RuntimeMetadata, RuntimeMetadataPrefixed, META_RESERVED,
};
use jsonrpsee::client_transport::ws::Uri;
use scale::Decode;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use subxt_codegen::utils::MetadataVersion;
use subxt_metadata::{get_metadata_hash, get_pallet_hash, metadata_v14_to_latest};
/// Verify metadata compatibility between substrate nodes.
@@ -25,16 +26,35 @@ pub struct Opts {
/// The validation will omit the full metadata check and focus instead on the pallet.
#[clap(long, value_parser)]
pallet: Option<String>,
/// Specify the metadata version.
///
/// - unstable:
///
/// Use the latest unstable metadata of the node.
///
/// - number
///
/// Use this specific metadata version.
///
/// Defaults to latest.
#[clap(long = "version", default_value = "latest")]
version: MetadataVersion,
}
pub async fn run(opts: Opts) -> color_eyre::Result<()> {
match opts.pallet {
Some(pallet) => handle_pallet_metadata(opts.nodes.as_slice(), pallet.as_str()).await,
None => handle_full_metadata(opts.nodes.as_slice()).await,
Some(pallet) => {
handle_pallet_metadata(opts.nodes.as_slice(), pallet.as_str(), opts.version).await
}
None => handle_full_metadata(opts.nodes.as_slice(), opts.version).await,
}
}
async fn handle_pallet_metadata(nodes: &[Uri], name: &str) -> color_eyre::Result<()> {
async fn handle_pallet_metadata(
nodes: &[Uri],
name: &str,
version: MetadataVersion,
) -> color_eyre::Result<()> {
#[derive(Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
struct CompatibilityPallet {
@@ -44,7 +64,7 @@ async fn handle_pallet_metadata(nodes: &[Uri], name: &str) -> color_eyre::Result
let mut compatibility: CompatibilityPallet = Default::default();
for node in nodes.iter() {
let metadata = fetch_runtime_metadata(node).await?;
let metadata = fetch_runtime_metadata(node, version).await?;
match metadata.pallets.iter().find(|pallet| pallet.name == name) {
Some(pallet_metadata) => {
@@ -73,10 +93,10 @@ async fn handle_pallet_metadata(nodes: &[Uri], name: &str) -> color_eyre::Result
Ok(())
}
async fn handle_full_metadata(nodes: &[Uri]) -> color_eyre::Result<()> {
async fn handle_full_metadata(nodes: &[Uri], version: MetadataVersion) -> color_eyre::Result<()> {
let mut compatibility_map: HashMap<String, Vec<String>> = HashMap::new();
for node in nodes.iter() {
let metadata = fetch_runtime_metadata(node).await?;
let metadata = fetch_runtime_metadata(node, version).await?;
let hash = get_metadata_hash(&metadata);
let hex_hash = hex::encode(hash);
println!("Node {node:?} has metadata hash {hex_hash:?}",);
@@ -96,8 +116,11 @@ async fn handle_full_metadata(nodes: &[Uri]) -> color_eyre::Result<()> {
Ok(())
}
async fn fetch_runtime_metadata(url: &Uri) -> color_eyre::Result<RuntimeMetadataV15> {
let bytes = subxt_codegen::utils::fetch_metadata_bytes(url).await?;
async fn fetch_runtime_metadata(
url: &Uri,
version: MetadataVersion,
) -> color_eyre::Result<RuntimeMetadataV15> {
let bytes = subxt_codegen::utils::fetch_metadata_bytes(url, version).await?;
let metadata = <RuntimeMetadataPrefixed as Decode>::decode(&mut &bytes[..])?;
if metadata.0 != META_RESERVED {
+1 -1
View File
@@ -4,9 +4,9 @@
use crate::utils::FileOrUrl;
use clap::Parser as ClapParser;
use codec::{Decode, Encode};
use color_eyre::eyre;
use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed};
use scale::{Decode, Encode};
use std::io::{self, Write};
use subxt_metadata::{metadata_v14_to_latest, retain_metadata_pallets};
+35 -7
View File
@@ -5,7 +5,7 @@
use clap::Args;
use color_eyre::eyre;
use std::{fs, io::Read, path::PathBuf};
use subxt_codegen::utils::Uri;
use subxt_codegen::utils::{MetadataVersion, Uri};
/// The source of the metadata.
#[derive(Debug, Args)]
@@ -16,29 +16,57 @@ pub struct FileOrUrl {
/// The path to the encoded metadata file.
#[clap(long, value_parser)]
file: Option<PathBuf>,
/// Specify the metadata version.
///
/// - unstable:
///
/// Use the latest unstable metadata of the node.
///
/// - number
///
/// Use this specific metadata version.
///
/// Defaults to 14.
#[clap(long)]
version: Option<MetadataVersion>,
}
impl FileOrUrl {
/// Fetch the metadata bytes.
pub async fn fetch(&self) -> color_eyre::Result<Vec<u8>> {
match (&self.file, &self.url) {
match (&self.file, &self.url, self.version) {
// Can't provide both --file and --url
(Some(_), Some(_)) => {
(Some(_), Some(_), _) => {
eyre::bail!("specify one of `--url` or `--file` but not both")
}
// Load from --file path
(Some(path), None) => {
(Some(path), None, None) => {
let mut file = fs::File::open(path)?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?;
Ok(bytes)
}
// Cannot load the metadata from the file and specify a version to fetch.
(Some(_), None, Some(_)) => {
// Note: we could provide the ability to convert between metadata versions
// but that would be involved because we'd need to convert
// from each metadata to the latest one and from the
// latest one to each metadata version. For now, disable the conversion.
eyre::bail!("`--file` is incompatible with `--version`")
}
// Fetch from --url
(None, Some(uri)) => Ok(subxt_codegen::utils::fetch_metadata_bytes(uri).await?),
(None, Some(uri), version) => Ok(subxt_codegen::utils::fetch_metadata_bytes(
uri,
version.unwrap_or_default(),
)
.await?),
// Default if neither is provided; fetch from local url
(None, None) => {
(None, None, version) => {
let uri = Uri::from_static("http://localhost:9933");
Ok(subxt_codegen::utils::fetch_metadata_bytes(&uri).await?)
Ok(
subxt_codegen::utils::fetch_metadata_bytes(&uri, version.unwrap_or_default())
.await?,
)
}
}
}
+16 -17
View File
@@ -1,6 +1,6 @@
[package]
name = "subxt-codegen"
version = "0.28.0"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
@@ -13,21 +13,20 @@ homepage.workspace = true
description = "Generate an API for interacting with a substrate node from FRAME metadata"
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full"] }
darling = "0.14.4"
frame-metadata = { version = "15.1.0", features = ["v14", "v15-unstable", "std"] }
heck = "0.4.1"
proc-macro2 = "1.0.55"
quote = "1.0.8"
syn = "1.0.109"
scale-info = "2.5.0"
subxt-metadata = { version = "0.28.0", path = "../metadata" }
jsonrpsee = { version = "0.16.0", features = ["async-client", "client-ws-transport", "http-client"] }
hex = "0.4.3"
tokio = { version = "1.27", features = ["macros", "rt-multi-thread"] }
thiserror = "1.0.40"
codec = { package = "parity-scale-codec", workspace = true, features = ["derive"] }
frame-metadata = { workspace = true }
heck = { workspace = true }
proc-macro2 = { workspace = true }
quote = { workspace = true }
syn = { workspace = true }
scale-info = { workspace = true }
subxt-metadata = { workspace = true }
jsonrpsee = { workspace = true, features = ["async-client", "client-ws-transport", "http-client"] }
hex = { workspace = true }
tokio = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies]
bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] }
scale-info = { version = "2.5.0", features = ["bit-vec"] }
pretty_assertions = "1.0.0"
bitvec = { workspace = true }
scale-info = { workspace = true, features = ["bit-vec"] }
pretty_assertions = { workspace = true }
+14 -3
View File
@@ -83,6 +83,11 @@ pub fn generate_calls(
// The call structure's documentation was stripped above.
let call_struct = quote! {
#struct_def
impl #crate_path::blocks::StaticExtrinsic for #struct_name {
const PALLET: &'static str = #pallet_name;
const CALL: &'static str = #call_name;
}
};
let client_fn = quote! {
@@ -90,11 +95,11 @@ pub fn generate_calls(
pub fn #fn_name(
&self,
#( #call_fn_args, )*
) -> #crate_path::tx::Payload<#struct_name> {
) -> #crate_path::tx::Payload<types::#struct_name> {
#crate_path::tx::Payload::new_static(
#pallet_name,
#call_name,
#struct_name { #( #call_args, )* },
types::#struct_name { #( #call_args, )* },
[#(#call_hash,)*]
)
}
@@ -106,6 +111,7 @@ pub fn generate_calls(
.into_iter()
.unzip();
let call_type = type_gen.resolve_type_path(call.ty.id);
let call_ty = type_gen.resolve_type(call.ty.id);
let docs = &call_ty.docs;
let docs = should_gen_docs
@@ -114,13 +120,18 @@ pub fn generate_calls(
Ok(quote! {
#docs
pub type Call = #call_type;
pub mod calls {
use super::root_mod;
use super::#types_mod_ident;
type DispatchError = #types_mod_ident::sp_runtime::DispatchError;
#( #call_structs )*
pub mod types {
use super::#types_mod_ident;
#( #call_structs )*
}
pub struct TransactionApi;
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use frame_metadata::v15::PalletMetadata;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use scale_info::form::PortableForm;
use crate::types::TypeGenerator;
use super::CodegenError;
/// Generate error type alias from the provided pallet metadata.
pub fn generate_error_type_alias(
type_gen: &TypeGenerator,
pallet: &PalletMetadata<PortableForm>,
should_gen_docs: bool,
) -> Result<TokenStream2, CodegenError> {
let Some(error) = &pallet.error else {
return Ok(quote!());
};
let error_type = type_gen.resolve_type_path(error.ty.id);
let error_ty = type_gen.resolve_type(error.ty.id);
let docs = &error_ty.docs;
let docs = should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
Ok(quote! {
#docs
pub type Error = #error_type;
})
}
+129 -5
View File
@@ -6,7 +6,9 @@
mod calls;
mod constants;
mod errors;
mod events;
mod runtime_apis;
mod storage;
use frame_metadata::v15::RuntimeMetadataV15;
@@ -17,7 +19,7 @@ use crate::error::CodegenError;
use crate::{
ir,
types::{CompositeDef, CompositeDefFields, TypeGenerator, TypeSubstitutes},
utils::{fetch_metadata_bytes_blocking, Uri},
utils::{fetch_metadata_bytes_blocking, MetadataVersion, Uri},
CratePath,
};
use codec::Decode;
@@ -94,7 +96,11 @@ pub fn generate_runtime_api_from_url(
should_gen_docs: bool,
runtime_types_only: bool,
) -> Result<TokenStream2, CodegenError> {
let bytes = fetch_metadata_bytes_blocking(url)?;
// Fetch latest unstable version, if that fails fall back to the latest stable.
let bytes = match fetch_metadata_bytes_blocking(url, MetadataVersion::Unstable) {
Ok(bytes) => bytes,
Err(_) => fetch_metadata_bytes_blocking(url, MetadataVersion::Latest)?,
};
generate_runtime_api_from_bytes(
item_mod,
@@ -219,8 +225,13 @@ impl RuntimeGenerator {
// Preserve any Rust items that were previously defined in the adorned module
#( #rust_items ) *
// Make it easy to access the root via `root_mod` at different levels:
use super::#mod_ident as root_mod;
// Make it easy to access the root items via `root_mod` at different levels
// without reaching out of this module.
#[allow(unused_imports)]
mod root_mod {
pub use super::*;
}
#types_mod
}
})
@@ -320,10 +331,13 @@ impl RuntimeGenerator {
should_gen_docs,
)?;
let errors = errors::generate_error_type_alias(&type_gen, pallet, should_gen_docs)?;
Ok(quote! {
pub mod #mod_name {
use super::root_mod;
use super::#types_mod_ident;
#errors
#calls
#event
#storage_mod
@@ -353,6 +367,26 @@ impl RuntimeGenerator {
}
};
let outer_extrinsic_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.calls.as_ref().map(|_| {
quote! {
#[codec(index = #index)]
#variant_name(#mod_name::Call),
}
})
});
let outer_extrinsic = quote! {
#default_derives
pub enum Call {
#( #outer_extrinsic_variants )*
}
};
let root_event_if_arms = self.metadata.pallets.iter().filter_map(|p| {
let variant_name_str = &p.name;
let variant_name = format_ident!("{}", variant_name_str);
@@ -371,6 +405,61 @@ impl RuntimeGenerator {
})
});
let root_extrinsic_if_arms = self.metadata.pallets.iter().filter_map(|p| {
let variant_name_str = &p.name;
let variant_name = format_ident!("{}", variant_name_str);
let mod_name = format_ident!("{}", variant_name_str.to_string().to_snake_case());
p.calls.as_ref().map(|_| {
// An 'if' arm for the RootExtrinsic impl to match this variant name:
quote! {
if pallet_name == #variant_name_str {
return Ok(Call::#variant_name(#mod_name::Call::decode_with_metadata(
&mut &*pallet_bytes,
pallet_ty,
metadata
)?));
}
}
})
});
let outer_error_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.error.as_ref().map(|_| {
quote! {
#[codec(index = #index)]
#variant_name(#mod_name::Error),
}
})
});
let outer_error = quote! {
#default_derives
pub enum Error {
#( #outer_error_variants )*
}
};
let root_error_if_arms = self.metadata.pallets.iter().filter_map(|p| {
let variant_name_str = &p.name;
let variant_name = format_ident!("{}", variant_name_str);
let mod_name = format_ident!("{}", variant_name_str.to_string().to_snake_case());
p.error.as_ref().map(|err|
{
let type_id = err.ty.id;
quote! {
if pallet_name == #variant_name_str {
let variant_error = #mod_name::Error::decode_with_metadata(cursor, #type_id, metadata)?;
return Ok(Error::#variant_name(variant_error));
}
}
}
)
});
let mod_ident = &item_mod_ir.ident;
let pallets_with_constants: Vec<_> = pallets_with_mod_names
.iter()
@@ -393,6 +482,14 @@ impl RuntimeGenerator {
let rust_items = item_mod_ir.rust_items();
let apis_mod = runtime_apis::generate_runtime_apis(
&self.metadata,
&type_gen,
types_mod_ident,
&crate_path,
should_gen_docs,
)?;
Ok(quote! {
#( #item_mod_attrs )*
#[allow(dead_code, unused_imports, non_camel_case_types)]
@@ -424,6 +521,27 @@ impl RuntimeGenerator {
}
}
#outer_extrinsic
impl #crate_path::blocks::RootExtrinsic for Call {
fn root_extrinsic(pallet_bytes: &[u8], pallet_name: &str, pallet_ty: u32, metadata: &#crate_path::Metadata) -> Result<Self, #crate_path::Error> {
use #crate_path::metadata::DecodeWithMetadata;
#( #root_extrinsic_if_arms )*
Err(#crate_path::ext::scale_decode::Error::custom(format!("Pallet name '{}' not found in root Call enum", pallet_name)).into())
}
}
#outer_error
impl #crate_path::error::RootError for Error {
fn root_error(pallet_bytes: &[u8], pallet_name: &str, metadata: &#crate_path::Metadata) -> Result<Self, #crate_path::Error> {
use #crate_path::metadata::DecodeWithMetadata;
let cursor = &mut &pallet_bytes[..];
#( #root_error_if_arms )*
Err(#crate_path::ext::scale_decode::Error::custom(format!("Pallet name '{}' not found in root Error enum", pallet_name)).into())
}
}
pub fn constants() -> ConstantsApi {
ConstantsApi
}
@@ -436,6 +554,12 @@ impl RuntimeGenerator {
TransactionApi
}
pub fn apis() -> runtime_apis::RuntimeApi {
runtime_apis::RuntimeApi
}
#apis_mod
pub struct ConstantsApi;
impl ConstantsApi {
#(
@@ -495,7 +619,7 @@ where
let ty = type_gen.resolve_type(type_id);
let scale_info::TypeDef::Variant(variant) = &ty.type_def else {
return Err(CodegenError::InvalidType(error_message_type_name.into()))
return Err(CodegenError::InvalidType(error_message_type_name.into()));
};
variant
+168
View File
@@ -0,0 +1,168 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{types::TypeGenerator, CodegenError, CratePath};
use frame_metadata::v15::{RuntimeApiMetadata, RuntimeMetadataV15};
use heck::ToSnakeCase as _;
use heck::ToUpperCamelCase as _;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use scale_info::form::PortableForm;
/// Generates runtime functions for the given API metadata.
fn generate_runtime_api(
metadata: &RuntimeMetadataV15,
api: &RuntimeApiMetadata<PortableForm>,
type_gen: &TypeGenerator,
types_mod_ident: &syn::Ident,
crate_path: &CratePath,
should_gen_docs: bool,
) -> Result<(TokenStream2, TokenStream2), CodegenError> {
// Trait name must remain as is (upper case) to identity the runtime call.
let trait_name = &api.name;
// The snake case for the trait name.
let trait_name_snake = format_ident!("{}", api.name.to_snake_case());
let docs = &api.docs;
let docs: TokenStream2 = should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
let structs_and_methods: Vec<_> = api.methods.iter().map(|method| {
let method_name = format_ident!("{}", method.name);
// Runtime function name is `TraitName_MethodName`.
let runtime_fn_name = format!("{}_{}", trait_name, method_name);
let docs = &method.docs;
let docs: TokenStream2 = should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
let inputs: Vec<_> = method.inputs.iter().map(|input| {
let name = format_ident!("{}", &input.name);
let ty = type_gen.resolve_type_path(input.ty.id);
let param = quote!(#name: #ty);
(param, name)
}).collect();
let params = inputs.iter().map(|(param, _)| param);
let param_names = inputs.iter().map(|(_, name)| name);
// From the method metadata generate a structure that holds
// all parameter types. This structure is used with metadata
// to encode parameters to the call via `encode_as_fields_to`.
let derives = type_gen.default_derives();
let struct_name = format_ident!("{}", method.name.to_upper_camel_case());
let struct_params = params.clone();
let struct_input = quote!(
#derives
pub struct #struct_name {
#( pub #struct_params, )*
}
);
let output = type_gen.resolve_type_path(method.output.id);
let Ok(call_hash) =
subxt_metadata::get_runtime_api_hash(metadata, trait_name, &method.name) else {
return Err(CodegenError::MissingRuntimeApiMetadata(
trait_name.into(),
method.name.clone(),
))
};
let method = quote!(
#docs
pub fn #method_name(&self, #( #params, )* ) -> #crate_path::runtime_api::Payload<types::#struct_name, #output> {
#crate_path::runtime_api::Payload::new_static(
#runtime_fn_name,
types::#struct_name { #( #param_names, )* },
[#(#call_hash,)*],
)
}
);
Ok((struct_input, method))
}).collect::<Result<_, _>>()?;
let trait_name = format_ident!("{}", trait_name);
let structs = structs_and_methods.iter().map(|(struct_, _)| struct_);
let methods = structs_and_methods.iter().map(|(_, method)| method);
let runtime_api = quote!(
pub mod #trait_name_snake {
use super::root_mod;
use super::#types_mod_ident;
#docs
pub struct #trait_name;
impl #trait_name {
#( #methods )*
}
pub mod types {
use super::#types_mod_ident;
#( #structs )*
}
}
);
// A getter for the `RuntimeApi` to get the trait structure.
let trait_getter = quote!(
pub fn #trait_name_snake(&self) -> #trait_name_snake::#trait_name {
#trait_name_snake::#trait_name
}
);
Ok((runtime_api, trait_getter))
}
/// Generate the runtime APIs.
pub fn generate_runtime_apis(
metadata: &RuntimeMetadataV15,
type_gen: &TypeGenerator,
types_mod_ident: &syn::Ident,
crate_path: &CratePath,
should_gen_docs: bool,
) -> Result<TokenStream2, CodegenError> {
let apis = &metadata.apis;
let runtime_fns: Vec<_> = apis
.iter()
.map(|api| {
generate_runtime_api(
metadata,
api,
type_gen,
types_mod_ident,
crate_path,
should_gen_docs,
)
})
.collect::<Result<_, _>>()?;
let runtime_apis_def = runtime_fns.iter().map(|(apis, _)| apis);
let runtime_apis_getters = runtime_fns.iter().map(|(_, getters)| getters);
Ok(quote! {
pub mod runtime_apis {
use super::root_mod;
use super::#types_mod_ident;
use #crate_path::ext::codec::Encode;
pub struct RuntimeApi;
impl RuntimeApi {
#( #runtime_apis_getters )*
}
#( #runtime_apis_def )*
}
})
}
+12
View File
@@ -42,6 +42,9 @@ pub enum CodegenError {
/// Metadata for call could not be found.
#[error("Metadata for call entry {0}_{1} could not be found. Make sure you are providing a valid substrate-based metadata")]
MissingCallMetadata(String, String),
/// Metadata for call could not be found.
#[error("Metadata for runtime API entry {0}_{1} could not be found. Make sure you are providing a valid substrate-based metadata")]
MissingRuntimeApiMetadata(String, String),
/// Call variant must have all named fields.
#[error("Call variant for type {0} must have all named fields. Make sure you are providing a valid substrate-based metadata")]
InvalidCallVariant(u32),
@@ -50,6 +53,11 @@ pub enum CodegenError {
"{0} type should be an variant/enum type. Make sure you are providing a valid substrate-based metadata"
)]
InvalidType(String),
/// Extrinsic call type could not be found.
#[error(
"Extrinsic call type could not be found. Make sure you are providing a valid substrate-based metadata"
)]
MissingCallType,
}
impl CodegenError {
@@ -77,10 +85,14 @@ impl CodegenError {
pub enum FetchMetadataError {
#[error("Cannot decode hex value: {0}")]
DecodeError(#[from] hex::FromHexError),
#[error("Cannot scale encode/decode value: {0}")]
CodecError(#[from] codec::Error),
#[error("Request error: {0}")]
RequestError(#[from] jsonrpsee::core::Error),
#[error("'{0}' not supported, supported URI schemes are http, https, ws or wss.")]
InvalidScheme(String),
#[error("Other error: {0}")]
Other(String),
}
/// Error attempting to do type substitution.
+1 -1
View File
@@ -34,7 +34,7 @@
//! // Default module derivatives.
//! let mut derives = DerivesRegistry::with_default_derives(&CratePath::default());
//! // Default type substitutes.
//! let substs = TypeSubstitutes::new(&CratePath::default());
//! let substs = TypeSubstitutes::with_default_substitutes(&CratePath::default());
//! // Generate the Runtime API.
//! let generator = subxt_codegen::RuntimeGenerator::new(metadata);
//! // Include metadata documentation in the Runtime API.
+1 -2
View File
@@ -11,7 +11,6 @@ mod type_def;
mod type_def_params;
mod type_path;
use darling::FromMeta;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef};
@@ -353,7 +352,7 @@ impl ToTokens for CratePath {
impl From<&str> for CratePath {
fn from(crate_path: &str) -> Self {
Self(syn::Path::from_string(crate_path).unwrap_or_else(|err| {
Self(syn::parse_str(crate_path).unwrap_or_else(|err| {
panic!("failed converting {crate_path:?} to `syn::Path`: {err:?}");
}))
}
+28 -12
View File
@@ -37,9 +37,25 @@ macro_rules! path_segments {
}
}
impl Default for TypeSubstitutes {
fn default() -> Self {
Self::new()
}
}
impl TypeSubstitutes {
/// Create a new set of type substitutes with some default substitutions in place.
pub fn new(crate_path: &CratePath) -> Self {
/// Creates a new `TypeSubstitutes` with no default derives.
pub fn new() -> Self {
Self {
substitutes: HashMap::new(),
}
}
/// Creates a new `TypeSubstitutes` with some default substitutions in place.
///
/// The `crate_path` denotes the `subxt` crate access path in the
/// generated code.
pub fn with_default_substitutes(crate_path: &CratePath) -> Self {
// Some hardcoded default type substitutes, can be overridden by user
let defaults = [
(
@@ -163,11 +179,11 @@ impl TypeSubstitutes {
src_path: &syn::Path,
target_path: &syn::Path,
) -> Result<TypeParamMapping, TypeSubstitutionError> {
let Some(syn::PathSegment { arguments: src_path_args, ..}) = src_path.segments.last() else {
return Err(TypeSubstitutionError::EmptySubstitutePath(src_path.span()))
let Some(syn::PathSegment { arguments: src_path_args, .. }) = src_path.segments.last() else {
return Err(TypeSubstitutionError::EmptySubstitutePath(src_path.span()));
};
let Some(syn::PathSegment { arguments: target_path_args, ..}) = target_path.segments.last() else {
return Err(TypeSubstitutionError::EmptySubstitutePath(target_path.span()))
let Some(syn::PathSegment { arguments: target_path_args, .. }) = target_path.segments.last() else {
return Err(TypeSubstitutionError::EmptySubstitutePath(target_path.span()));
};
// Get hold of the generic args for the "from" type, erroring if they aren't valid.
@@ -331,14 +347,14 @@ fn replace_path_params_recursively<I: Borrow<syn::Ident>, P: Borrow<TypePath>>(
) {
for segment in &mut path.segments {
let syn::PathArguments::AngleBracketed(args) = &mut segment.arguments else {
continue
continue;
};
for arg in &mut args.args {
let syn::GenericArgument::Type(ty) = arg else {
continue
continue;
};
let syn::Type::Path(path) = ty else {
continue
continue;
};
if let Some(ident) = get_ident_from_type_path(path) {
if let Some((_, replacement)) = params.iter().find(|(i, _)| ident == i.borrow()) {
@@ -356,7 +372,7 @@ fn replace_path_params_recursively<I: Borrow<syn::Ident>, P: Borrow<TypePath>>(
fn get_valid_to_substitution_type(arg: &syn::GenericArgument) -> Option<&syn::TypePath> {
let syn::GenericArgument::Type(syn::Type::Path(type_path)) = arg else {
// We are looking for a type, not a lifetime or anything else
return None
return None;
};
Some(type_path)
}
@@ -366,7 +382,7 @@ fn get_valid_to_substitution_type(arg: &syn::GenericArgument) -> Option<&syn::Ty
fn get_valid_from_substitution_type(arg: &syn::GenericArgument) -> Option<&syn::Ident> {
let syn::GenericArgument::Type(syn::Type::Path(type_path)) = arg else {
// We are looking for a type, not a lifetime or anything else
return None
return None;
};
get_ident_from_type_path(type_path)
}
@@ -387,7 +403,7 @@ fn get_ident_from_type_path(type_path: &syn::TypePath) -> Option<&syn::Ident> {
}
let Some(segment) = type_path.path.segments.last() else {
// Get the single ident (should be infallible)
return None
return None;
};
if !segment.arguments.is_empty() {
// The ident shouldn't have any of it's own generic args like A<B, C>
+89 -44
View File
@@ -38,7 +38,7 @@ fn generate_struct_with_primitives() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -63,7 +63,7 @@ fn generate_struct_with_primitives() {
}
}
}
.to_string()
.to_string()
)
}
@@ -90,7 +90,7 @@ fn generate_struct_with_a_struct_field() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -122,7 +122,7 @@ fn generate_struct_with_a_struct_field() {
}
}
}
.to_string()
.to_string()
)
}
@@ -144,7 +144,7 @@ fn generate_tuple_struct() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -153,8 +153,8 @@ fn generate_tuple_struct() {
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
assert_eq!(
tests_mod.into_token_stream().to_string(),
quote! {
tests_mod.into_token_stream().to_string(),
quote! {
pub mod tests {
use super::root;
@@ -171,8 +171,8 @@ fn generate_tuple_struct() {
pub struct Parent(pub ::core::primitive::bool, pub root::subxt_codegen::types::tests::Child,);
}
}
.to_string()
)
.to_string()
)
}
#[test]
@@ -235,7 +235,7 @@ fn derive_compact_as_for_uint_wrapper_structs() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -310,7 +310,7 @@ fn derive_compact_as_for_uint_wrapper_structs() {
pub struct TSu8(pub ::core::primitive::u8,);
}
}
.to_string()
.to_string()
)
}
@@ -332,7 +332,7 @@ fn generate_enum() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -359,7 +359,7 @@ fn generate_enum() {
}
}
}
.to_string()
.to_string()
)
}
@@ -396,7 +396,7 @@ fn compact_fields() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -438,7 +438,7 @@ fn compact_fields() {
pub struct TupleStruct(#[codec(compact)] pub ::core::primitive::u32,);
}
}
.to_string()
.to_string()
)
}
@@ -464,7 +464,7 @@ fn compact_generic_parameter() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -511,7 +511,7 @@ fn generate_array_field() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -533,7 +533,7 @@ fn generate_array_field() {
}
}
}
.to_string()
.to_string()
)
}
@@ -554,7 +554,7 @@ fn option_fields() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -577,7 +577,7 @@ fn option_fields() {
}
}
}
.to_string()
.to_string()
)
}
@@ -600,7 +600,7 @@ fn box_fields_struct() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -623,7 +623,7 @@ fn box_fields_struct() {
}
}
}
.to_string()
.to_string()
)
}
@@ -646,7 +646,7 @@ fn box_fields_enum() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -671,7 +671,7 @@ fn box_fields_enum() {
}
}
}
.to_string()
.to_string()
)
}
@@ -692,7 +692,7 @@ fn range_fields() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -715,7 +715,7 @@ fn range_fields() {
}
}
}
.to_string()
.to_string()
)
}
@@ -742,7 +742,7 @@ fn generics() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -772,7 +772,7 @@ fn generics() {
}
}
}
.to_string()
.to_string()
)
}
@@ -799,7 +799,7 @@ fn generics_nested() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -830,7 +830,7 @@ fn generics_nested() {
}
}
}
.to_string()
.to_string()
)
}
@@ -856,7 +856,7 @@ fn generate_bitvec() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -879,7 +879,7 @@ fn generate_bitvec() {
}
}
}
.to_string()
.to_string()
)
}
@@ -915,7 +915,7 @@ fn generics_with_alias_adds_phantom_data_marker() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -924,8 +924,8 @@ fn generics_with_alias_adds_phantom_data_marker() {
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
assert_eq!(
tests_mod.into_token_stream().to_string(),
quote! {
tests_mod.into_token_stream().to_string(),
quote! {
pub mod tests {
use super::root;
#[derive(::subxt_path::ext::codec::CompactAs, ::subxt_path::ext::codec::Decode, ::subxt_path::ext::codec::Encode, ::subxt_path::ext::scale_decode::DecodeAsType, ::subxt_path::ext::scale_encode::EncodeAsType, Debug)]
@@ -948,8 +948,8 @@ fn generics_with_alias_adds_phantom_data_marker() {
);
}
}
.to_string()
)
.to_string()
)
}
#[test]
@@ -986,7 +986,7 @@ fn modules() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -1037,7 +1037,7 @@ fn modules() {
}
}
}
.to_string()
.to_string()
)
}
@@ -1055,7 +1055,7 @@ fn dont_force_struct_names_camel_case() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
@@ -1076,7 +1076,7 @@ fn dont_force_struct_names_camel_case() {
pub struct AB;
}
}
.to_string()
.to_string()
)
}
@@ -1105,7 +1105,7 @@ fn apply_user_defined_derives_for_all_types() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
derives,
crate_path,
true,
@@ -1181,7 +1181,7 @@ fn apply_user_defined_derives_for_specific_types() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
derives,
crate_path,
true,
@@ -1249,7 +1249,7 @@ fn opt_out_from_default_derives() {
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(&crate_path),
TypeSubstitutes::with_default_substitutes(&crate_path),
derives,
crate_path,
true,
@@ -1276,3 +1276,48 @@ fn opt_out_from_default_derives() {
.to_string()
)
}
/// By default a BTreeMap would be replaced by a KeyedVec.
/// This test demonstrates that it does not happen if we opt out of default type substitutes.
#[test]
fn opt_out_from_default_substitutes() {
use std::collections::BTreeMap;
#[allow(unused)]
#[derive(TypeInfo)]
struct S {
map: BTreeMap<u8, u8>,
}
let mut registry = Registry::new();
registry.register_type(&meta_type::<S>());
let portable_types: PortableRegistry = registry.into();
let crate_path = "::subxt_path".into();
let type_gen = TypeGenerator::new(
&portable_types,
"root",
TypeSubstitutes::new(),
DerivesRegistry::with_default_derives(&crate_path),
crate_path,
true,
);
let types = type_gen.generate_types_mod().expect("Valid type mod; qed");
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_path::ext::codec::Decode, ::subxt_path::ext::codec::Encode, ::subxt_path::ext::scale_decode::DecodeAsType, ::subxt_path::ext::scale_encode::EncodeAsType, Debug)]
#[codec(crate = ::subxt_path::ext::codec)]
#[decode_as_type(crate_path = ":: subxt_path :: ext :: scale_decode")]
#[encode_as_type(crate_path = ":: subxt_path :: ext :: scale_encode")]
pub struct S {
pub map: ::std::collections::BTreeMap<:: core :: primitive :: u8,:: core :: primitive :: u8>,
}
}
}.to_string()
)
}
+178 -19
View File
@@ -3,6 +3,7 @@
// see LICENSE for license details.
use crate::error::FetchMetadataError;
use codec::{Decode, Encode};
use jsonrpsee::{
async_client::ClientBuilder,
client_transport::ws::{Uri, WsTransportClientBuilder},
@@ -12,14 +13,51 @@ use jsonrpsee::{
};
use std::time::Duration;
/// The metadata version that is fetched from the node.
#[derive(Default, Debug, Clone, Copy)]
pub enum MetadataVersion {
/// Latest stable version of the metadata.
#[default]
Latest,
/// Fetch a specified version of the metadata.
Version(u32),
/// Latest unstable version of the metadata.
Unstable,
}
// Note: Implementation needed for the CLI tool.
impl std::str::FromStr for MetadataVersion {
type Err = String;
fn from_str(input: &str) -> Result<Self, Self::Err> {
match input {
"unstable" => Ok(MetadataVersion::Unstable),
"latest" => Ok(MetadataVersion::Latest),
version => {
let num: u32 = version
.parse()
.map_err(|_| format!("Invalid metadata version specified {:?}", version))?;
Ok(MetadataVersion::Version(num))
}
}
}
}
/// Returns the metadata bytes from the provided URL, blocking the current thread.
pub fn fetch_metadata_bytes_blocking(url: &Uri) -> Result<Vec<u8>, FetchMetadataError> {
tokio_block_on(fetch_metadata_bytes(url))
pub fn fetch_metadata_bytes_blocking(
url: &Uri,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
tokio_block_on(fetch_metadata_bytes(url, version))
}
/// Returns the raw, 0x prefixed metadata hex from the provided URL, blocking the current thread.
pub fn fetch_metadata_hex_blocking(url: &Uri) -> Result<String, FetchMetadataError> {
tokio_block_on(fetch_metadata_hex(url))
pub fn fetch_metadata_hex_blocking(
url: &Uri,
version: MetadataVersion,
) -> Result<String, FetchMetadataError> {
tokio_block_on(fetch_metadata_hex(url, version))
}
// Block on some tokio runtime for sync contexts
@@ -32,26 +70,36 @@ fn tokio_block_on<T, Fut: std::future::Future<Output = T>>(fut: Fut) -> T {
}
/// Returns the metadata bytes from the provided URL.
pub async fn fetch_metadata_bytes(url: &Uri) -> Result<Vec<u8>, FetchMetadataError> {
let hex = fetch_metadata_hex(url).await?;
let bytes = hex::decode(hex.trim_start_matches("0x"))?;
Ok(bytes)
}
/// Returns the raw, 0x prefixed metadata hex from the provided URL.
pub async fn fetch_metadata_hex(url: &Uri) -> Result<String, FetchMetadataError> {
let hex_data = match url.scheme_str() {
Some("http") | Some("https") => fetch_metadata_http(url).await,
Some("ws") | Some("wss") => fetch_metadata_ws(url).await,
pub async fn fetch_metadata_bytes(
url: &Uri,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
let bytes = match url.scheme_str() {
Some("http") | Some("https") => fetch_metadata_http(url, version).await,
Some("ws") | Some("wss") => fetch_metadata_ws(url, version).await,
invalid_scheme => {
let scheme = invalid_scheme.unwrap_or("no scheme");
Err(FetchMetadataError::InvalidScheme(scheme.to_owned()))
}
}?;
Ok(bytes)
}
/// Returns the raw, 0x prefixed metadata hex from the provided URL.
pub async fn fetch_metadata_hex(
url: &Uri,
version: MetadataVersion,
) -> Result<String, FetchMetadataError> {
let bytes = fetch_metadata_bytes(url, version).await?;
let hex_data = format!("0x{}", hex::encode(bytes));
Ok(hex_data)
}
async fn fetch_metadata_ws(url: &Uri) -> Result<String, FetchMetadataError> {
async fn fetch_metadata_ws(
url: &Uri,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
let (sender, receiver) = WsTransportClientBuilder::default()
.build(url.to_string().parse::<Uri>().unwrap())
.await
@@ -62,13 +110,124 @@ async fn fetch_metadata_ws(url: &Uri) -> Result<String, FetchMetadataError> {
.max_notifs_per_subscription(4096)
.build_with_tokio(sender, receiver);
Ok(client.request("state_getMetadata", rpc_params![]).await?)
fetch_metadata(client, version).await
}
async fn fetch_metadata_http(url: &Uri) -> Result<String, FetchMetadataError> {
async fn fetch_metadata_http(
url: &Uri,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
let client = HttpClientBuilder::default()
.request_timeout(Duration::from_secs(180))
.build(url.to_string())?;
Ok(client.request("state_getMetadata", rpc_params![]).await?)
fetch_metadata(client, version).await
}
/// The innermost call to fetch metadata:
async fn fetch_metadata(
client: impl ClientT,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
const UNSTABLE_METADATA_VERSION: u32 = u32::MAX;
// Fetch metadata using the "new" state_call interface
async fn fetch_inner(
client: &impl ClientT,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
// Look up supported versions:
let supported_versions: Vec<u32> = {
let res: String = client
.request(
"state_call",
rpc_params!["Metadata_metadata_versions", "0x"],
)
.await?;
let raw_bytes = hex::decode(res.trim_start_matches("0x"))?;
Decode::decode(&mut &raw_bytes[..])?
};
// Return the version the user wants if it's supported:
let version = match version {
MetadataVersion::Latest => *supported_versions
.iter()
.filter(|&&v| v != UNSTABLE_METADATA_VERSION)
.max()
.ok_or_else(|| {
FetchMetadataError::Other("No valid metadata versions returned".to_string())
})?,
MetadataVersion::Unstable => {
if supported_versions.contains(&UNSTABLE_METADATA_VERSION) {
UNSTABLE_METADATA_VERSION
} else {
return Err(FetchMetadataError::Other(
"The node does not have an unstable metadata version available".to_string(),
));
}
}
MetadataVersion::Version(version) => {
if supported_versions.contains(&version) {
version
} else {
return Err(FetchMetadataError::Other(format!(
"The node does not have version {version} available"
)));
}
}
};
let bytes = version.encode();
let version: String = format!("0x{}", hex::encode(&bytes));
// Fetch the metadata at that version:
let metadata_string: String = client
.request(
"state_call",
rpc_params!["Metadata_metadata_at_version", &version],
)
.await?;
// Decode the metadata.
let metadata_bytes = hex::decode(metadata_string.trim_start_matches("0x"))?;
let metadata: Option<frame_metadata::OpaqueMetadata> =
Decode::decode(&mut &metadata_bytes[..])?;
let Some(metadata) = metadata else {
return Err(FetchMetadataError::Other(format!(
"The node does not have version {version} available"
)));
};
Ok(metadata.0)
}
// Fetch metadata using the "old" state_call interface
async fn fetch_inner_legacy(
client: &impl ClientT,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
if !matches!(
version,
MetadataVersion::Latest | MetadataVersion::Version(14)
) {
return Err(FetchMetadataError::Other(
"The node can only return version 14 metadata but you've asked for something else"
.to_string(),
));
}
// Fetch the metadata at that version:
let metadata_string: String = client
.request("state_call", rpc_params!["Metadata_metadata", "0x"])
.await?;
// Decode the metadata.
let metadata_bytes = hex::decode(metadata_string.trim_start_matches("0x"))?;
let metadata: frame_metadata::OpaqueMetadata = Decode::decode(&mut &metadata_bytes[..])?;
Ok(metadata.0)
}
// Fetch using the new interface, falling back to trying old one if there's an error.
match fetch_inner(&client, version).await {
Ok(s) => Ok(s),
Err(_) => fetch_inner_legacy(&client, version).await,
}
}
+1 -1
View File
@@ -11,5 +11,5 @@ pub use jsonrpsee::client_transport::ws::Uri;
pub use fetch_metadata::{
fetch_metadata_bytes, fetch_metadata_bytes_blocking, fetch_metadata_hex,
fetch_metadata_hex_blocking,
fetch_metadata_hex_blocking, MetadataVersion,
};
+6 -10
View File
@@ -1,6 +1,6 @@
[package]
name = "subxt-examples"
version = "0.28.0"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
@@ -13,12 +13,8 @@ homepage.workspace = true
description = "Subxt example usage"
[dev-dependencies]
subxt = { path = "../subxt" }
tokio = { version = "1.27", features = ["rt-multi-thread", "macros", "time"] }
futures = "0.3.27"
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full", "bit-vec"] }
hex = "0.4.3"
tracing-subscriber = "0.3.17"
sp-keyring = "23.0.0"
sp-core = { version = "20.0.0", default-features = false }
sp-runtime = "23.0.0"
subxt = { workspace = true }
tokio = { workspace = true }
futures = { workspace = true }
hex = { workspace = true }
sp-keyring = { workspace = true }
+3 -1
View File
@@ -1,3 +1,5 @@
# Subxt Examples
Take a look in the [examples](./examples) subfolder for various `subxt` usage examples.
Take a look in the [examples](./examples) subfolder for various `subxt` usage examples.
All examples form part of the `subxt` documentation; there should be no examples without corresponding links from the docs.
-40
View File
@@ -1,40 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use sp_keyring::AccountKeyring;
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Create a transaction to submit:
let tx = polkadot::tx()
.balances()
.transfer(dest, 123_456_789_012_345);
// Submit the transaction with default params:
let hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
println!("Balance transfer extrinsic submitted: {hash}");
Ok(())
}
@@ -0,0 +1,34 @@
use sp_keyring::AccountKeyring;
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};
// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a balance transfer extrinsic.
let dest = AccountKeyring::Bob.to_account_id().into();
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
// Submit the balance transfer extrinsic from Alice, and wait for it to be successful
// and in a finalized block. We get back the extrinsic events if all is well.
let from = PairSigner::new(AccountKeyring::Alice.pair());
let events = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
.await?
.wait_for_finalized_success()
.await?;
// Find a Transfer event and print it.
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
println!("Balance transfer success: {event:?}");
}
Ok(())
}
@@ -0,0 +1,58 @@
use futures::StreamExt;
use sp_keyring::AccountKeyring;
use subxt::{
tx::{PairSigner, TxStatus},
OnlineClient, PolkadotConfig,
};
// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a balance transfer extrinsic.
let dest = AccountKeyring::Bob.to_account_id().into();
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
// Submit the balance transfer extrinsic from Alice, and then monitor the
// progress of it.
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let mut balance_transfer_progress = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &signer)
.await?;
while let Some(status) = balance_transfer_progress.next().await {
match status? {
// It's finalized in a block!
TxStatus::Finalized(in_block) => {
println!(
"Transaction {:?} is finalized in block {:?}",
in_block.extrinsic_hash(),
in_block.block_hash()
);
// grab the events and fail if no ExtrinsicSuccess event seen:
let events = in_block.wait_for_success().await?;
// We can look for events (this uses the static interface; we can also iterate
// over them and dynamically decode them):
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
println!("Balance transfer success: {event:?}");
} else {
println!("Failed to find Balances::Transfer Event");
}
}
// Just log any other status we encounter:
other => {
println!("Status: {other:?}");
}
}
}
Ok(())
}
@@ -1,52 +1,28 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use sp_keyring::AccountKeyring;
use subxt::{
config::{
polkadot::{Era, PlainTip, PolkadotExtrinsicParamsBuilder as Params},
PolkadotConfig,
},
tx::PairSigner,
OnlineClient,
};
use subxt::config::polkadot::{Era, PlainTip, PolkadotExtrinsicParamsBuilder as Params};
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();
// Create a client to use:
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Create a transaction to submit:
let tx = polkadot::tx()
.balances()
.transfer(dest, 123_456_789_012_345);
// Build a balance transfer extrinsic.
let dest = AccountKeyring::Bob.to_account_id().into();
let tx = polkadot::tx().balances().transfer(dest, 10_000);
// Configure the transaction tip and era:
// Configure the transaction parameters; for Polkadot the tip and era:
let tx_params = Params::new()
.tip(PlainTip::new(20_000_000_000))
.tip(PlainTip::new(1_000))
.era(Era::Immortal, api.genesis_hash());
// submit the transaction:
let hash = api.tx().sign_and_submit(&tx, &signer, tx_params).await?;
println!("Balance transfer extrinsic submitted: {hash}");
let from = PairSigner::new(AccountKeyring::Alice.pair());
let hash = api.tx().sign_and_submit(&tx, &from, tx_params).await?;
println!("Balance transfer extrinsic submitted with hash : {hash}");
Ok(())
}
@@ -22,8 +22,6 @@ pub mod polkadot {}
/// pluck out the events that we care about.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
@@ -57,31 +55,44 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
while let Some(block) = block_sub.next().await {
let block = block?;
// Ask for the events for this block.
let events = block.events().await?;
let block_hash = block.hash();
println!(" Block {:?}", block_hash);
// We can dynamically decode events:
println!(" Dynamic event details: {block_hash:?}:");
for event in events.iter() {
let event = event?;
let is_balance_transfer = event
.as_event::<polkadot::balances::events::Transfer>()?
.is_some();
let pallet = event.pallet_name();
let variant = event.variant_name();
println!(" {pallet}::{variant} (is balance transfer? {is_balance_transfer})");
// Ask for the extrinsics for this block.
let extrinsics = block.body().await?.extrinsics();
let transfer_tx = extrinsics.find_first::<polkadot::balances::calls::types::Transfer>();
println!(" Transfer tx: {:?}", transfer_tx);
// Ask for the extrinsics for this block.
for extrinsic in extrinsics.iter() {
let extrinsic = extrinsic?;
println!(
" Extrinsic block index {:?}, pallet index {:?}, variant index {:?}",
extrinsic.index(),
extrinsic.pallet_index(),
extrinsic.variant_index()
);
let root_extrinsic = extrinsic.as_root_extrinsic::<polkadot::Call>();
println!(" As root extrinsic {:?}\n", root_extrinsic);
let pallet_extrinsic = extrinsic
.as_pallet_extrinsic::<polkadot::runtime_types::pallet_balances::pallet::Call>();
println!(
" Extrinsic as Balances Pallet call: {:?}\n",
pallet_extrinsic
);
let call = extrinsic.as_extrinsic::<polkadot::balances::calls::types::Transfer>()?;
println!(
" Extrinsic as polkadot::balances::calls::Transfer: {:?}\n\n",
call
);
}
// Or we can find the first transfer event, ignoring any others:
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(ev) = transfer_event {
println!(" - Balance transfer success: value: {:?}", ev.amount);
} else {
println!(" - No balance transfer event found in this block");
}
println!("\n");
}
Ok(())
@@ -1,15 +1,3 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.29-41a9d84b152.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.29/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use futures::StreamExt;
use subxt::{OnlineClient, PolkadotConfig};
@@ -18,14 +6,13 @@ pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Subscribe to all finalized blocks:
let mut blocks_sub = api.blocks().subscribe_finalized().await?;
// For each block, print a bunch of information about it:
while let Some(block) = blocks_sub.next().await {
let block = block?;
@@ -36,8 +23,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(" Hash: {block_hash}");
println!(" Extrinsics:");
// Log each of the extrinsic with it's associated events:
let body = block.body().await?;
for ext in body.extrinsics() {
for ext in body.extrinsics().iter() {
let ext = ext?;
let idx = ext.index();
let events = ext.events().await?;
let bytes_hex = format!("0x{}", hex::encode(ext.bytes()));
@@ -51,8 +40,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let pallet_name = evt.pallet_name();
let event_name = evt.variant_name();
let event_values = evt.field_values()?;
println!(" {pallet_name}_{event_name}");
println!(" {}", event_values);
}
}
}
@@ -1,39 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use futures::join;
use sp_keyring::AccountKeyring;
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = OnlineClient::<PolkadotConfig>::new().await?;
let addr = AccountKeyring::Bob.to_account_id().into();
// Construct storage addresses to access:
let staking_bonded = polkadot::storage().staking().bonded(&addr);
let staking_ledger = polkadot::storage().staking().ledger(&addr);
// For storage requests, we can join futures together to
// await multiple futures concurrently:
let a_fut = api.storage().at_latest().await?.fetch(&staking_bonded);
let b_fut = api.storage().at_latest().await?.fetch(&staking_ledger);
let (a, b) = join!(a_fut, b_fut);
println!("{a:?}, {b:?}");
Ok(())
}
+17
View File
@@ -0,0 +1,17 @@
use subxt::{OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// A dynamic query to obtain some contant:
let constant_query = subxt::dynamic::constant("System", "BlockLength");
// Obtain the value:
let value = api.constants().at(&constant_query)?;
println!("Constant bytes: {:?}", value.encoded());
println!("Constant value: {}", value.to_value()?);
Ok(())
}
+19
View File
@@ -0,0 +1,19 @@
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// A query to obtain some contant:
let constant_query = polkadot::constants().system().block_length();
// Obtain the value:
let value = api.constants().at(&constant_query)?;
println!("Block length: {value:?}");
Ok(())
}
-59
View File
@@ -1,59 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This example should compile but should fail to work, since we've modified the
//! config to not align with a Polkadot node.
use sp_keyring::AccountKeyring;
use subxt::{
config::{substrate::SubstrateExtrinsicParams, Config, SubstrateConfig},
tx::PairSigner,
OnlineClient,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
/// Custom [`Config`] impl where the default types for the target chain differ from the
/// [`DefaultConfig`]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct MyConfig;
impl Config for MyConfig {
// This is different from the default `u32`.
//
// *Note* that in this example it does differ from the actual `Index` type in the
// polkadot runtime used, so some operations will fail. Normally when using a custom `Config`
// impl types MUST match exactly those used in the actual runtime.
type Index = u64;
type Hash = <SubstrateConfig as Config>::Hash;
type Hasher = <SubstrateConfig as Config>::Hasher;
type Header = <SubstrateConfig as Config>::Header;
type AccountId = <SubstrateConfig as Config>::AccountId;
type Address = <SubstrateConfig as Config>::Address;
type Signature = <SubstrateConfig as Config>::Signature;
// ExtrinsicParams makes use of the index type, so we need to adjust it
// too to align with our modified index type, above:
type ExtrinsicParams = SubstrateExtrinsicParams<Self>;
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();
// Create a client to use:
let api = OnlineClient::<MyConfig>::new().await?;
// Create a transaction to submit:
let tx = polkadot::tx()
.balances()
.transfer(dest, 123_456_789_012_345);
// submit the transaction with default params:
let hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
println!("Balance transfer extrinsic submitted: {hash}");
Ok(())
}
-12
View File
@@ -1,12 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
// If you'd like to use metadata directly from a running node, you
// can provide a URL to that node here. HTTP or WebSocket URLs can be
// provided. Note that if the metadata cannot be retrieved from this
// node URL at compile time, compilation will fail.
#[subxt::subxt(runtime_metadata_url = "wss://rpc.polkadot.io:443")]
pub mod polkadot {}
fn main() {}
-32
View File
@@ -1,32 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
#![allow(clippy::redundant_clone)]
#[subxt::subxt(
runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
// We can add (certain) custom derives to the generated types by providing
// a comma separated list to the below attribute. Most useful for adding `Clone`.
// The derives that we can add ultimately is limited to the traits that the base
// types relied upon by the codegen implement.
derive_for_all_types = "Clone, PartialEq, Eq",
// To apply derives to specific generated types, add a `derive_for_type` per type,
// mapping the type path to the derives which should be added for that type only.
// Note that these derives will be in addition to those specified above in
// `derive_for_all_types`
derive_for_type(type = "frame_support::PalletId", derive = "Eq, Ord, PartialOrd"),
derive_for_type(type = "sp_runtime::ModuleError", derive = "Eq, Hash"),
)]
pub mod polkadot {}
use polkadot::runtime_types::frame_support::PalletId;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let pallet_id = PalletId([1u8; 8]);
let _ = pallet_id.clone();
Ok(())
}
-74
View File
@@ -1,74 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.31-3711c6f9b2a.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.31/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use sp_keyring::AccountKeyring;
use subxt::{dynamic::Value, tx::PairSigner, OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// My account.
let signer_account = AccountKeyring::Alice;
let signer_account_id = signer_account.to_account_id();
let signer = PairSigner::new(signer_account.pair());
// Transfer balance to this destination:
let dest = AccountKeyring::Bob.to_account_id();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Create the inner balance transfer call.
let inner_tx = subxt::dynamic::tx(
"Balances",
"transfer",
vec![
Value::unnamed_variant("Id", [Value::from_bytes(&dest)]),
Value::u128(123_456_789_012_345),
],
);
// Now, build an outer call which this inner call will be a part of.
// This sets up the multisig arrangement.
//
// Note: Since this is a dynamic call, we can either use named or unnamed
// arguments (if unnamed, the order matters).
let tx = subxt::dynamic::tx(
"Multisig",
"as_multi",
vec![
("threshold", Value::u128(1)),
(
"other_signatories",
Value::unnamed_composite([Value::from_bytes(&signer_account_id)]),
),
("maybe_timepoint", Value::unnamed_variant("None", [])),
("call", inner_tx.into_value()),
(
"max_weight",
Value::named_composite([
("ref_time", Value::u128(10000000000)),
("proof_size", Value::u128(1)),
]),
),
],
);
// Submit it:
let encoded = hex::encode(api.tx().call_data(&tx)?);
println!("Call data: {encoded}");
let tx_hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
println!("Submitted tx with hash {tx_hash}");
Ok(())
}
-85
View File
@@ -1,85 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
// This example showcases working with dynamic values rather than those that are generated via the subxt proc macro.
use sp_keyring::AccountKeyring;
use subxt::{dynamic::Value, tx::PairSigner, OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = OnlineClient::<PolkadotConfig>::new().await?;
// 1. Dynamic Balance Transfer (the dynamic equivalent to the balance_transfer example).
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id();
// Create a transaction to submit:
let tx = subxt::dynamic::tx(
"Balances",
"transfer",
vec![
// A value representing a MultiAddress<AccountId32, _>. We want the "Id" variant, and that
// will ultimately contain the bytes for our destination address (there is a new type wrapping
// the address, but our encoding will happily ignore such things and do it's best to line up what
// we provide with what it needs).
Value::unnamed_variant("Id", [Value::from_bytes(&dest)]),
// A value representing the amount we'd like to transfer.
Value::u128(123_456_789_012_345),
],
);
// submit the transaction with default params:
let hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
println!("Balance transfer extrinsic submitted: {hash}");
// 2. Dynamic constant access (the dynamic equivalent to the fetch_constants example).
let constant_address = subxt::dynamic::constant("Balances", "ExistentialDeposit");
let existential_deposit = api.constants().at(&constant_address)?.to_value()?;
println!("Existential Deposit: {existential_deposit}");
// 3. Dynamic storage access
let storage_address = subxt::dynamic::storage(
"System",
"Account",
vec![
// Something that encodes to an AccountId32 is what we need for the map key here:
Value::from_bytes(&dest),
],
);
let account = api
.storage()
.at_latest()
.await?
.fetch_or_default(&storage_address)
.await?
.to_value()?;
println!("Bob's account details: {account}");
// 4. Dynamic storage iteration (the dynamic equivalent to the fetch_all_accounts example).
let storage_address = subxt::dynamic::storage_root("System", "Account");
let mut iter = api
.storage()
.at_latest()
.await?
.iter(storage_address, 10)
.await?;
while let Some((key, account)) = iter.next().await? {
println!("{}: {}", hex::encode(key), account.to_value()?);
}
Ok(())
}
+60
View File
@@ -0,0 +1,60 @@
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Get events for the latest block:
let events = api.events().at_latest().await?;
// We can dynamically decode events:
println!("Dynamic event details:");
for event in events.iter() {
let event = event?;
let pallet = event.pallet_name();
let variant = event.variant_name();
let field_values = event.field_values()?;
println!("{pallet}::{variant}: {field_values}");
}
// Or we can attempt to statically decode them into the root Event type:
println!("Static event details:");
for event in events.iter() {
let event = event?;
if let Ok(ev) = event.as_root_event::<polkadot::Event>() {
println!("{ev:?}");
} else {
println!("<Cannot decode event>");
}
}
// Or we can attempt to decode them into a specific arbitraty pallet enum
// (We could also set the output type to Value to dynamically decode, here):
println!("Event details for Balances pallet:");
for event in events.iter() {
let event = event?;
if let Ok(ev) = event.as_pallet_event::<polkadot::balances::Event>() {
println!("{ev:?}");
} else {
continue;
}
}
// Or we can look for specific events which match our statically defined ones:
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(ev) = transfer_event {
println!(" - Balance transfer success: value: {:?}", ev.amount);
} else {
println!(" - No balance transfer event found in this block");
}
Ok(())
}
-32
View File
@@ -1,32 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let api = OnlineClient::<PolkadotConfig>::new().await?;
let address = polkadot::storage().system().account_root();
let mut iter = api.storage().at_latest().await?.iter(address, 10).await?;
while let Some((key, account)) = iter.next().await? {
println!("{}: {}", hex::encode(key), account.data.free);
}
Ok(())
}
-35
View File
@@ -1,35 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use subxt::{OnlineClient, PolkadotConfig};
// Generate the API from a static metadata path.
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a constant address to query:
let address = polkadot::constants().balances().existential_deposit();
// Look it up:
let existential_deposit = api.constants().at(&address)?;
println!("Existential Deposit: {existential_deposit}");
Ok(())
}
@@ -1,71 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use sp_core::{sr25519, Pair};
use sp_keyring::AccountKeyring;
use subxt::{utils::AccountId32, OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
let active_era_addr = polkadot::storage().staking().active_era();
let era = api
.storage()
.at_latest()
.await?
.fetch(&active_era_addr)
.await?
.unwrap();
println!(
"Staking active era: index: {:?}, start: {:?}",
era.index, era.start
);
let alice_id = AccountKeyring::Alice.to_account_id();
println!(" Alice account id: {alice_id:?}");
// Get Alice' Stash account ID
let alice_stash_id: AccountId32 = sr25519::Pair::from_string("//Alice//stash", None)
.expect("Could not obtain stash signer pair")
.public()
.into();
println!(" Alice//stash account id: {alice_stash_id:?}");
// Map from all locked "stash" accounts to the controller account.
let controller_acc_addr = polkadot::storage().staking().bonded(&alice_stash_id);
let controller_acc = api
.storage()
.at_latest()
.await?
.fetch(&controller_acc_addr)
.await?
.unwrap();
println!(" account controlled by: {controller_acc:?}");
let era_reward_addr = polkadot::storage().staking().eras_reward_points(era.index);
let era_result = api
.storage()
.at_latest()
.await?
.fetch(&era_reward_addr)
.await?;
println!("Era reward points: {era_result:?}");
Ok(())
}
@@ -1,33 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Each individual request will be validated against the static code that was
// used to construct it, if possible. We can also validate the entirety of the
// statically generated code against some client at a given point in time using
// this check. If it fails, then there is some breaking change between the metadata
// used to generate this static code, and the runtime metadata from a node that the
// client is using.
polkadot::validate_codegen(&api)?;
Ok(())
}
-72
View File
@@ -1,72 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.31-3711c6f9b2a.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.31/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use sp_keyring::AccountKeyring;
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// My account.
let signer_account = AccountKeyring::Alice;
let signer_account_id = signer_account.to_account_id();
let signer = PairSigner::new(signer_account.pair());
// Transfer balance to this destination:
let dest = AccountKeyring::Bob.to_account_id();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Create the inner balance transfer call.
//
// Note: This call, being manually constructed, will have a specific pallet and call index
// which is determined by the generated code. If you're trying to submit this to a node which
// has the pallets/calls at different indexes, it will fail. See `dynamic_multisig.rs` for a
// workaround in this case which will work regardless of pallet and call indexes.
let inner_tx = polkadot::runtime_types::polkadot_runtime::RuntimeCall::Balances(
polkadot::runtime_types::pallet_balances::pallet::Call::transfer {
dest: dest.into(),
value: 123_456_789_012_345,
},
);
// Now, build an outer call which this inner call will be a part of.
// This sets up the multisig arrangement.
let tx = polkadot::tx().multisig().as_multi(
// threshold
1,
// other signatories
vec![signer_account_id.into()],
// maybe timepoint
None,
// call
inner_tx,
// max weight
polkadot::runtime_types::sp_weights::weight_v2::Weight {
ref_time: 10000000000,
proof_size: 1,
},
);
// Submit the extrinsic with default params:
let encoded = hex::encode(api.tx().call_data(&tx)?);
println!("Call data: {encoded}");
let tx_hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
println!("Submitted tx with hash {tx_hash}");
Ok(())
}
-32
View File
@@ -1,32 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use subxt::{OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let api = OnlineClient::<PolkadotConfig>::new().await?;
let block_number = 1u32;
let block_hash = api.rpc().block_hash(Some(block_number.into())).await?;
if let Some(hash) = block_hash {
println!("Block hash for block number {block_number}: {hash}");
} else {
println!("Block number {block_number} not found.");
}
Ok(())
}
@@ -1,36 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use subxt::{config::Header, OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let api = OnlineClient::<PolkadotConfig>::from_url("wss://rpc.polkadot.io:443").await?;
// For non-finalised blocks use `.subscribe_blocks()`
let mut blocks = api.rpc().subscribe_finalized_block_headers().await?;
while let Some(Ok(block)) = blocks.next().await {
println!(
"block number: {} hash:{} parent:{} state root:{} extrinsics root:{}",
block.number,
block.hash(),
block.parent_hash,
block.state_root,
block.extrinsics_root
);
}
Ok(())
}
+31
View File
@@ -0,0 +1,31 @@
use sp_keyring::AccountKeyring;
use subxt::dynamic::Value;
use subxt::{config::PolkadotConfig, OnlineClient};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Create a dynamically runtime API payload that calls the
// `AccountNonceApi_account_nonce` function.
let account = AccountKeyring::Alice.to_account_id();
let runtime_api_call = subxt::dynamic::runtime_api_call(
"AccountNonceApi_account_nonce",
vec![Value::from_bytes(account)],
);
// Submit the call to get back a result.
let nonce = api
.runtime_api()
.at_latest()
.await?
.call(runtime_api_call)
.await?;
println!("Account nonce: {:#?}", nonce.to_value());
Ok(())
}
+22
View File
@@ -0,0 +1,22 @@
use subxt::ext::codec::Compact;
use subxt::ext::frame_metadata::RuntimeMetadataPrefixed;
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Use runtime APIs at the latest block:
let runtime_apis = api.runtime_api().at_latest().await?;
// Ask for metadata and decode it:
let (_, meta): (Compact<u32>, RuntimeMetadataPrefixed) =
runtime_apis.call_raw("Metadata_metadata", None).await?;
println!("{meta:?}");
Ok(())
}
+27
View File
@@ -0,0 +1,27 @@
use sp_keyring::AccountKeyring;
use subxt::{config::PolkadotConfig, OnlineClient};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Create a runtime API payload that calls into
// `AccountNonceApi_account_nonce` function.
let account = AccountKeyring::Alice.to_account_id().into();
let runtime_api_call = polkadot::apis().account_nonce_api().account_nonce(account);
// Submit the call and get back a result.
let nonce = api
.runtime_api()
.at_latest()
.await?
.call(runtime_api_call)
.await;
println!("AccountNonceApi_account_nonce for Alice: {:?}", nonce);
Ok(())
}
-62
View File
@@ -1,62 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! In some cases we are interested only in the `RuntimeCall` enum (or more generally, only in some
//! runtime types). We can ask `subxt` to generate only runtime types by passing a corresponding
//! flag.
//!
//! Here we present how to correctly create `Block` type for the Polkadot chain.
use sp_core::H256;
use sp_runtime::{
generic,
traits::{BlakeTwo256, Block as _, Header as _},
Digest,
};
use subxt::PolkadotConfig;
#[subxt::subxt(
runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
derive_for_all_types = "Clone, PartialEq, Eq",
runtime_types_only
)]
pub mod polkadot {}
type RuntimeCall = polkadot::runtime_types::polkadot_runtime::RuntimeCall;
type UncheckedExtrinsic = generic::UncheckedExtrinsic<
<PolkadotConfig as subxt::Config>::Address,
RuntimeCall,
<PolkadotConfig as subxt::Config>::Signature,
// Usually we are not interested in `SignedExtra`.
(),
>;
type Header = generic::Header<u32, BlakeTwo256>;
type Block = generic::Block<Header, UncheckedExtrinsic>;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Although we could build an online client, we do not have access to the full runtime API. For
// that, we would have to specify `runtime_types_only = false` (or just skipping it).
//
// let api = subxt::OnlineClient::<PolkadotConfig>::new().await?;
// let address = polkadot::constants().balances().existential_deposit(); <- this won't compile!
let polkadot_header = Header::new(
41,
H256::default(),
H256::default(),
H256::default(),
Digest::default(),
);
let polkadot_block = Block::new(polkadot_header, vec![]);
println!("{polkadot_block:?}");
Ok(())
}
@@ -0,0 +1,30 @@
use subxt::{
config::{substrate::SubstrateExtrinsicParams, Config, SubstrateConfig},
OnlineClient,
};
/// Define a custom config type (see the `subxt::config::Config` docs for
/// more information about each type):
enum MyConfig {}
impl Config for MyConfig {
// This is different from the default `u32`:
type Index = u64;
// We can point to the default types if we don't need to change things:
type Hash = <SubstrateConfig as Config>::Hash;
type Hasher = <SubstrateConfig as Config>::Hasher;
type Header = <SubstrateConfig as Config>::Header;
type AccountId = <SubstrateConfig as Config>::AccountId;
type Address = <SubstrateConfig as Config>::Address;
type Signature = <SubstrateConfig as Config>::Signature;
// ExtrinsicParams makes use of the index type, so we need to tweak it
// too to align with our modified index type, above:
type ExtrinsicParams = SubstrateExtrinsicParams<Self>;
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client which uses the custom config:
let _api = OnlineClient::<MyConfig>::new().await?;
Ok(())
}
@@ -1,7 +1,3 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use std::{
fmt::Write,
pin::Pin,
@@ -64,13 +60,8 @@ impl RpcClientT for MyLoggingClient {
}
}
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Instantiate our replacement RPC client.
let log = Arc::default();
let rpc_client = MyLoggingClient {
+37
View File
@@ -0,0 +1,37 @@
use subxt::ext::codec::Decode;
use subxt::ext::frame_metadata::RuntimeMetadataPrefixed;
use subxt::metadata::Metadata;
use subxt::utils::H256;
use subxt::{config::PolkadotConfig, OfflineClient};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// We need to obtain the following details for an OfflineClient to be instantiated:
// 1. Genesis hash (RPC call: chain_getBlockHash(0)):
let genesis_hash = {
let h = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3";
let bytes = hex::decode(h).unwrap();
H256::from_slice(&bytes)
};
// 2. A runtime version (system_version constant on a Substrate node has these):
let runtime_version = subxt::rpc::types::RuntimeVersion {
spec_version: 9370,
transaction_version: 20,
other: Default::default(),
};
// 3. Metadata (I'll load it from the downloaded metadata, but you can use
// `subxt metadata > file.scale` to download it):
let metadata = {
let bytes = std::fs::read("./artifacts/polkadot_metadata.scale").unwrap();
let metadata = RuntimeMetadataPrefixed::decode(&mut &*bytes).unwrap();
Metadata::try_from(metadata).unwrap()
};
// Create an offline client using the details obtained above:
let _api = OfflineClient::<PolkadotConfig>::new(genesis_hash, runtime_version, metadata);
Ok(())
}
+29
View File
@@ -0,0 +1,29 @@
use sp_keyring::AccountKeyring;
use subxt::{OnlineClient, PolkadotConfig};
// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a storage query to access account information.
let account = AccountKeyring::Alice.to_account_id().into();
let storage_query = polkadot::storage().system().account(&account);
// Use that query to `fetch` a result. This returns an `Option<_>`, which will be
// `None` if no value exists at the given address. You can also use `fetch_default`
// where applicable, which will return the default value if none exists.
let result = api
.storage()
.at_latest()
.await?
.fetch(&storage_query)
.await?;
println!("Alice has free balance: {}", result.unwrap().data.free);
Ok(())
}
@@ -0,0 +1,27 @@
use sp_keyring::AccountKeyring;
use subxt::dynamic::{At, Value};
use subxt::{OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a dynamic storage query to access account information.
let account = AccountKeyring::Alice.to_account_id();
let storage_query =
subxt::dynamic::storage("System", "Account", vec![Value::from_bytes(account)]);
// Use that query to `fetch` a result. Because the query is dynamic, we don't know what the result
// type will be either, and so we get a type back that can be decoded into a dynamic Value type.
let result = api
.storage()
.at_latest()
.await?
.fetch(&storage_query)
.await?;
let value = result.unwrap().to_value()?;
println!("Alice has free balance: {:?}", value.at("data").at("free"));
Ok(())
}
+15 -94
View File
@@ -1,108 +1,29 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use codec::{Decode, Encode};
use subxt::{OnlineClient, PolkadotConfig};
// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Create a client to use:
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Example 1. Iterate over (keys, value) using the storage client.
// This is the standard and most ergonomic approach.
{
let key_addr = polkadot::storage().xcm_pallet().version_notifiers_root();
// Build a storage query to iterate over account information.
let storage_query = polkadot::storage().system().account_root();
let mut iter = api.storage().at_latest().await?.iter(key_addr, 10).await?;
// Get back an iterator of results (here, we are fetching 10 items at
// a time from the node, but we always iterate over oen at a time).
let mut results = api
.storage()
.at_latest()
.await?
.iter(storage_query, 10)
.await?;
println!("\nExample 1. Obtained keys:");
while let Some((key, value)) = iter.next().await? {
println!("Key: 0x{}", hex::encode(key));
println!(" Value: {value}");
}
}
// Example 2. Iterate over fetched keys manually. Here, you forgo any static type
// safety and work directly with the bytes on either side.
{
let key_addr = polkadot::storage().xcm_pallet().version_notifiers_root();
// Fetch at most 10 keys from below the prefix XcmPallet' VersionNotifiers.
let keys = api
.storage()
.at_latest()
.await?
.fetch_keys(&key_addr.to_root_bytes(), 10, None)
.await?;
println!("Example 2. Obtained keys:");
for key in keys.iter() {
println!("Key: 0x{}", hex::encode(key));
if let Some(storage_data) = api.storage().at_latest().await?.fetch_raw(&key.0).await? {
// We know the return value to be `QueryId` (`u64`) from inspecting either:
// - polkadot code
// - polkadot.rs generated file under `version_notifiers()` fn
// - metadata in json format
let value = u64::decode(&mut &*storage_data)?;
println!(" Value: {value}");
}
}
}
// Example 3. Custom iteration over double maps. Here, we manually append one lookup
// key to the root and just iterate over the values underneath that.
{
let key_addr = polkadot::storage().xcm_pallet().version_notifiers_root();
// Obtain the root bytes (`twox_128("XcmPallet") ++ twox_128("VersionNotifiers")`).
let mut query_key = key_addr.to_root_bytes();
// We know that the first key is a u32 (the `XcmVersion`) and is hashed by twox64_concat.
// twox64_concat is just the result of running the twox_64 hasher on some value and concatenating
// the value itself after it:
query_key.extend(subxt::ext::sp_core::twox_64(&2u32.encode()));
query_key.extend(&2u32.encode());
// The final query key is essentially the result of:
// `twox_128("XcmPallet") ++ twox_128("VersionNotifiers") ++ twox_64(scale_encode(2u32)) ++ scale_encode(2u32)`
println!("\nExample 3\nQuery key: 0x{}", hex::encode(&query_key));
let keys = api
.storage()
.at_latest()
.await?
.fetch_keys(&query_key, 10, None)
.await?;
println!("Obtained keys:");
for key in keys.iter() {
println!("Key: 0x{}", hex::encode(key));
if let Some(storage_data) = api.storage().at_latest().await?.fetch_raw(&key.0).await? {
// We know the return value to be `QueryId` (`u64`) from inspecting either:
// - polkadot code
// - polkadot.rs generated file under `version_notifiers()` fn
// - metadata in json format
let value = u64::decode(&mut &storage_data[..])?;
println!(" Value: {value}");
}
}
while let Some((key, value)) = results.next().await? {
println!("Key: 0x{}", hex::encode(&key));
println!("Value: {:?}", value);
}
Ok(())
@@ -0,0 +1,25 @@
use subxt::{OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a dynamic storage query to iterate account information.
let storage_query = subxt::dynamic::storage_root("System", "Account");
// Use that query to return an iterator over the results.
let mut results = api
.storage()
.at_latest()
.await?
.iter(storage_query, 10)
.await?;
while let Some((key, value)) = results.next().await? {
println!("Key: 0x{}", hex::encode(&key));
println!("Value: {:?}", value.to_value()?);
}
Ok(())
}
-166
View File
@@ -1,166 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use futures::StreamExt;
use sp_keyring::AccountKeyring;
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
simple_transfer().await?;
simple_transfer_separate_events().await?;
handle_transfer_events().await?;
Ok(())
}
/// This is the highest level approach to using this API. We use `wait_for_finalized_success`
/// to wait for the transaction to make it into a finalized block, and also ensure that the
/// transaction was successful according to the associated events.
async fn simple_transfer() -> Result<(), Box<dyn std::error::Error>> {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();
let api = OnlineClient::<PolkadotConfig>::new().await?;
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
let balance_transfer = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &signer)
.await?
.wait_for_finalized_success()
.await?;
let transfer_event = balance_transfer.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
println!("Balance transfer success: {event:?}");
} else {
println!("Failed to find Balances::Transfer Event");
}
Ok(())
}
/// This is very similar to `simple_transfer`, except to show that we can handle
/// waiting for the transaction to be finalized separately from obtaining and checking
/// for success on the events.
async fn simple_transfer_separate_events() -> Result<(), Box<dyn std::error::Error>> {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();
let api = OnlineClient::<PolkadotConfig>::new().await?;
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
let balance_transfer = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &signer)
.await?
.wait_for_finalized()
.await?;
// Now we know it's been finalized, we can get hold of a couple of
// details, including events. Calling `wait_for_finalized_success` is
// equivalent to calling `wait_for_finalized` and then `wait_for_success`:
let _events = balance_transfer.wait_for_success().await?;
// Alternately, we could just `fetch_events`, which grabs all of the events like
// the above, but does not check for success, and leaves it up to you:
let events = balance_transfer.fetch_events().await?;
let failed_event = events.find_first::<polkadot::system::events::ExtrinsicFailed>()?;
if let Some(_ev) = failed_event {
// We found a failed event; the transfer didn't succeed.
println!("Balance transfer failed");
} else {
// We didn't find a failed event; the transfer succeeded. Find
// more details about it to report..
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
println!("Balance transfer success: {event:?}");
} else {
println!("Failed to find Balances::Transfer Event");
}
}
Ok(())
}
/// If we need more visibility into the state of the transaction, we can also ditch
/// `wait_for_finalized` entirely and stream the transaction progress events, handling
/// them more manually.
async fn handle_transfer_events() -> Result<(), Box<dyn std::error::Error>> {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();
let api = OnlineClient::<PolkadotConfig>::new().await?;
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
let mut balance_transfer_progress = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &signer)
.await?;
while let Some(ev) = balance_transfer_progress.next().await {
let ev = ev?;
use subxt::tx::TxStatus::*;
// Made it into a block, but not finalized.
if let InBlock(details) = ev {
println!(
"Transaction {:?} made it into block {:?}",
details.extrinsic_hash(),
details.block_hash()
);
let events = details.wait_for_success().await?;
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
println!("Balance transfer is now in block (but not finalized): {event:?}");
} else {
println!("Failed to find Balances::Transfer Event");
}
}
// Finalized!
else if let Finalized(details) = ev {
println!(
"Transaction {:?} is finalized in block {:?}",
details.extrinsic_hash(),
details.block_hash()
);
let events = details.wait_for_success().await?;
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
println!("Balance transfer success: {event:?}");
} else {
println!("Failed to find Balances::Transfer Event");
}
}
// Report other statuses we see.
else {
println!("Current transaction status: {ev:?}");
}
}
Ok(())
}
@@ -1,36 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use std::time::Duration;
use subxt::{OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Start a new tokio task to perform the runtime updates while
// utilizing the API for other use cases.
let update_client = api.updater();
tokio::spawn(async move {
let result = update_client.perform_runtime_updates().await;
println!("Runtime update failed with result={result:?}");
});
// If this client is kept in use a while, it'll update its metadata and such
// as needed when the node it's pointed at updates.
tokio::time::sleep(Duration::from_secs(10_000)).await;
Ok(())
}
+5 -6
View File
@@ -1,6 +1,6 @@
[package]
name = "subxt-macro"
version = "0.28.0"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
@@ -17,8 +17,7 @@ description = "Generate types and helpers for interacting with Substrate runtime
proc-macro = true
[dependencies]
darling = "0.14.4"
proc-macro-error = "1.0.4"
syn = "1.0.109"
subxt-codegen = { path = "../codegen", version = "0.28.0" }
darling = { workspace = true }
proc-macro-error = { workspace = true }
syn = { workspace = true }
subxt-codegen = { workspace = true }
+25 -119
View File
@@ -2,118 +2,11 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Generate a strongly typed API for interacting with a Substrate runtime from its metadata.
//!
//! Usage:
//!
//! Download metadata from a running Substrate node using `subxt-cli`:
//!
//! ```bash
//! subxt metadata > polkadot_metadata.scale
//! ```
//!
//! Annotate a Rust module with the `subxt` attribute referencing the aforementioned metadata file.
//!
//! ```ignore
//! #[subxt::subxt(
//! runtime_metadata_path = "polkadot_metadata.scale",
//! )]
//! pub mod polkadot {}
//! ```
//!
//! The `subxt` macro will populate the annotated module with all of the methods and types required
//! for submitting extrinsics and reading from storage for the given runtime.
//!
//! ## Substituting types
//!
//! In order to replace a generated type by a user-defined type, use `substitute_type`:
//!
//! ```ignore
//! #[subxt::subxt(
//! runtime_metadata_path = "polkadot_metadata.scale",
//! substitute_type(type = "sp_arithmetic::per_things::Perbill", with = "sp_runtime::Perbill")
//! )]
//! pub mod polkadot {}
//! ```
//!
//! This will replace the generated type and any usages with the specified type at the `use` import.
//! It is useful for using custom decoding for specific types, or to provide a type with foreign
//! trait implementations, or other specialized functionality.
//! ## Custom Derives
//!
//! By default all generated types are annotated with `scale::Encode` and `scale::Decode` derives.
//! However when using the generated types in the client, they may require additional derives to be
//! useful.
//!
//! ### Adding derives for all types
//!
//! Add `derive_for_all_types` with a comma separated list of the derives to apply to *all* types
//!
//! ```ignore
//! #[subxt::subxt(
//! runtime_metadata_path = "polkadot_metadata.scale",
//! derive_for_all_types = "Eq, PartialEq"
//! )]
//! pub mod polkadot {}
//! ```
//!
//! ### Adding derives for specific types
//!
//! Add `derive_for_type` for each specific type with a comma separated list of the derives to
//! apply for that type only.
//!
//! ```ignore
//! #[subxt::subxt(
//! runtime_metadata_path = "polkadot_metadata.scale",
//! derive_for_all_types = "Eq, PartialEq",
//! derive_for_type(type = "frame_support::PalletId", derive = "Ord, PartialOrd"),
//! derive_for_type(type = "sp_runtime::ModuleError", derive = "Hash"),
//! )]
//! pub mod polkadot {}
//! ```
//!
//! ### Custom crate path
//!
//! In order to specify a custom crate path to be used for the code generation:
//!
//! ```ignore
//! #[subxt::subxt(crate = "crate::path::to::subxt")]
//! pub mod polkadot {}
//! ```
//!
//! By default the path `::subxt` is used.
//!
//! ### Expose documentation
//!
//! In order to expose the documentation from the runtime metadata on the generated
//! code, users must specify the `generate_docs` flag:
//!
//! ```ignore
//! #[subxt::subxt(generate_docs)]
//! pub mod polkadot {}
//! ```
//!
//! By default the documentation is not generated.
//!
//! ### Runtime types generation
//!
//! In some cases, you may be interested only in the runtime types, like `RuntimeCall` enum. You can
//! limit code generation to just `runtime_types` module with `runtime_types_only` flag:
//!
//! ```ignore
//! #[subxt::subxt(runtime_types_only)]
//! // or equivalently
//! #[subxt::subxt(runtime_types_only = true)]
//! ```
#![deny(unused_crate_dependencies)]
extern crate proc_macro;
use std::str::FromStr;
use darling::FromMeta;
use darling::{ast::NestedMeta, FromMeta};
use proc_macro::TokenStream;
use proc_macro_error::{abort_call_site, proc_macro_error};
use subxt_codegen::{utils::Uri, CodegenError, DerivesRegistry, TypeSubstitutes};
@@ -152,33 +45,38 @@ struct RuntimeMetadataArgs {
runtime_types_only: bool,
#[darling(default)]
no_default_derives: bool,
#[darling(default)]
no_default_substitutions: bool,
}
#[derive(Debug, FromMeta)]
struct DeriveForType {
#[darling(rename = "type")]
ty: syn::TypePath,
path: syn::TypePath,
derive: Punctuated<syn::Path, syn::Token![,]>,
}
#[derive(Debug, FromMeta)]
struct AttributesForType {
#[darling(rename = "type")]
ty: syn::TypePath,
path: syn::TypePath,
attributes: Punctuated<OuterAttribute, syn::Token![,]>,
}
#[derive(Debug, FromMeta)]
struct SubstituteType {
#[darling(rename = "type")]
ty: syn::Path,
path: syn::Path,
with: syn::Path,
}
// Note: docs for this are in the subxt library; don't add any here as they will be appended.
#[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 attr_args = match NestedMeta::parse_meta_list(args.into()) {
Ok(v) => v,
Err(e) => {
return TokenStream::from(darling::Error::from(e).write_errors());
}
};
let item_mod = parse_macro_input!(input as syn::ItemMod);
let args = match RuntimeMetadataArgs::from_list(&attr_args) {
Ok(v) => v,
@@ -203,21 +101,29 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream {
);
for derives in &args.derive_for_type {
derives_registry.extend_for_type(derives.ty.clone(), derives.derive.iter().cloned(), vec![])
derives_registry.extend_for_type(
derives.path.clone(),
derives.derive.iter().cloned(),
vec![],
)
}
for attributes in &args.attributes_for_type {
derives_registry.extend_for_type(
attributes.ty.clone(),
attributes.path.clone(),
vec![],
attributes.attributes.iter().map(|a| a.0.clone()),
)
}
let mut type_substitutes = TypeSubstitutes::new(&crate_path);
let mut type_substitutes = if args.no_default_substitutions {
TypeSubstitutes::new()
} else {
TypeSubstitutes::with_default_substitutes(&crate_path)
};
let substitute_args_res: Result<(), _> = args.substitute_type.into_iter().try_for_each(|sub| {
sub.with
.try_into()
.and_then(|with| type_substitutes.insert(sub.ty, with))
.and_then(|with| type_substitutes.insert(sub.path, with))
});
if let Err(err) = substitute_args_res {
+6 -6
View File
@@ -1,6 +1,6 @@
[package]
name = "subxt-metadata"
version = "0.28.0"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
@@ -14,15 +14,15 @@ homepage.workspace = true
description = "Command line utilities for checking metadata compatibility between nodes."
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full"] }
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] }
frame-metadata = { version = "15.1.0", features = ["v14", "v15-unstable", "std"] }
scale-info = "2.5.0"
scale-info = "2.6.0"
sp-core-hashing = "8.0.0"
[dev-dependencies]
bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] }
criterion = "0.4"
scale-info = { version = "2.5.0", features = ["bit-vec"] }
bitvec = { workspace = true, features = ["alloc"] }
criterion = { workspace = true }
scale-info = { workspace = true, features = ["bit-vec"] }
[lib]
# Without this, libtest cli opts interfere with criteron benches:
+1 -1
View File
@@ -10,7 +10,7 @@ use frame_metadata::{v14::RuntimeMetadataV14, v15::RuntimeMetadataV15};
pub use retain::retain_metadata_pallets;
pub use validation::{
get_call_hash, get_constant_hash, get_metadata_hash, get_metadata_per_pallet_hash,
get_pallet_hash, get_storage_hash, NotFound,
get_pallet_hash, get_runtime_api_hash, get_runtime_trait_hash, get_storage_hash, NotFound,
};
/// Convert the metadata V14 to the latest metadata version.
+35 -1
View File
@@ -5,7 +5,7 @@
//! Utility functions to generate a subset of the metadata.
use frame_metadata::v15::{
ExtrinsicMetadata, PalletMetadata, RuntimeMetadataV15, StorageEntryType,
ExtrinsicMetadata, PalletMetadata, RuntimeApiMetadata, RuntimeMetadataV15, StorageEntryType,
};
use scale_info::{form::PortableForm, interner::UntrackedSymbol, TypeDef};
use std::{
@@ -105,6 +105,36 @@ fn update_extrinsic_types(
}
}
/// Collect all type IDs needed to represent the runtime APIs.
fn collect_runtime_api_types(
apis: &[RuntimeApiMetadata<PortableForm>],
type_ids: &mut HashSet<u32>,
) {
for api in apis {
for method in &api.methods {
for input in &method.inputs {
type_ids.insert(input.ty.id);
}
type_ids.insert(method.output.id);
}
}
}
/// Update all type IDs of the provided runtime APIs metadata using the new type IDs from the portable registry.
fn update_runtime_api_types(
apis: &mut [RuntimeApiMetadata<PortableForm>],
map_ids: &BTreeMap<u32, u32>,
) {
for api in apis {
for method in &mut api.methods {
for input in &mut method.inputs {
update_type(&mut input.ty, map_ids);
}
update_type(&mut method.output, map_ids);
}
}
}
/// Update the given type using the new type ID from the portable registry.
///
/// # Panics
@@ -191,6 +221,9 @@ where
// Keep the "runtime" type ID, since it's referenced in our metadata.
type_ids.insert(metadata.ty.id);
// Keep the runtime APIs types.
collect_runtime_api_types(&metadata.apis, &mut type_ids);
// Additionally, subxt depends on the `DispatchError` type existing; we use the same
// logic here that is used when building our `Metadata`.
let dispatch_error_ty = metadata
@@ -211,6 +244,7 @@ where
}
update_extrinsic_types(&mut metadata.extrinsic, &map_ids);
update_type(&mut metadata.ty, &map_ids);
update_runtime_api_types(&mut metadata.apis, &map_ids);
}
#[cfg(test)]
+124 -7
View File
@@ -5,7 +5,8 @@
//! Utility functions for metadata validation.
use frame_metadata::v15::{
ExtrinsicMetadata, PalletMetadata, RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType,
ExtrinsicMetadata, PalletMetadata, RuntimeApiMetadata, RuntimeApiMethodMetadata,
RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType,
};
use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant};
use std::collections::HashSet;
@@ -253,7 +254,7 @@ pub fn get_storage_hash(
.pallets
.iter()
.find(|p| p.name == pallet_name)
.ok_or(NotFound::Pallet)?;
.ok_or(NotFound::Root)?;
let storage = pallet.storage.as_ref().ok_or(NotFound::Item)?;
@@ -277,7 +278,7 @@ pub fn get_constant_hash(
.pallets
.iter()
.find(|p| p.name == pallet_name)
.ok_or(NotFound::Pallet)?;
.ok_or(NotFound::Root)?;
let constant = pallet
.constants
@@ -300,7 +301,7 @@ pub fn get_call_hash(
.pallets
.iter()
.find(|p| p.name == pallet_name)
.ok_or(NotFound::Pallet)?;
.ok_or(NotFound::Root)?;
let call_id = pallet.calls.as_ref().ok_or(NotFound::Item)?.ty.id;
@@ -321,6 +322,96 @@ pub fn get_call_hash(
Ok(hash)
}
fn get_runtime_method_hash(
metadata: &RuntimeMetadataV15,
trait_metadata: &RuntimeApiMetadata<PortableForm>,
method_metadata: &RuntimeApiMethodMetadata<PortableForm>,
visited_ids: &mut HashSet<u32>,
) -> [u8; 32] {
// The trait name is part of the runtime API call that is being
// generated for this method. Therefore the trait name is strongly
// connected to the method in the same way as a parameter is
// to the method.
let mut bytes = hash(trait_metadata.name.as_bytes());
bytes = xor(bytes, hash(method_metadata.name.as_bytes()));
for input in &method_metadata.inputs {
bytes = xor(bytes, hash(input.name.as_bytes()));
bytes = xor(
bytes,
get_type_hash(&metadata.types, input.ty.id, visited_ids),
);
}
bytes = xor(
bytes,
get_type_hash(&metadata.types, method_metadata.output.id, visited_ids),
);
bytes
}
/// Obtain the hash of a specific runtime trait.
pub fn get_runtime_trait_hash(
metadata: &RuntimeMetadataV15,
trait_metadata: &RuntimeApiMetadata<PortableForm>,
) -> [u8; 32] {
// Start out with any hash, the trait name is already part of the
// runtime method hash.
let mut bytes = hash(trait_metadata.name.as_bytes());
let mut visited_ids = HashSet::new();
let mut methods: Vec<_> = trait_metadata
.methods
.iter()
.map(|method_metadata| {
let bytes = get_runtime_method_hash(
metadata,
trait_metadata,
method_metadata,
&mut visited_ids,
);
(&*method_metadata.name, bytes)
})
.collect();
// Sort by method name to create a deterministic representation of the underlying metadata.
methods.sort_by_key(|&(name, _hash)| name);
// Note: Hash already takes into account the method name.
for (_, hash) in methods {
bytes = xor(bytes, hash);
}
bytes
}
/// Obtain the hash of a specific runtime API function, or an error if it's not found.
pub fn get_runtime_api_hash(
metadata: &RuntimeMetadataV15,
trait_name: &str,
method_name: &str,
) -> Result<[u8; 32], NotFound> {
let trait_metadata = metadata
.apis
.iter()
.find(|m| m.name == trait_name)
.ok_or(NotFound::Root)?;
let method_metadata = trait_metadata
.methods
.iter()
.find(|m| m.name == method_name)
.ok_or(NotFound::Item)?;
Ok(get_runtime_method_hash(
metadata,
trait_metadata,
method_metadata,
&mut HashSet::new(),
))
}
/// Obtain the hash representation of a `frame_metadata::v15::PalletMetadata`.
pub fn get_pallet_hash(
registry: &PortableRegistry,
@@ -404,6 +495,19 @@ pub fn get_metadata_hash(metadata: &RuntimeMetadataV15) -> [u8; 32] {
&mut visited_ids,
));
let mut apis: Vec<_> = metadata
.apis
.iter()
.map(|api| (&*api.name, get_runtime_trait_hash(metadata, api)))
.collect();
// Sort the runtime APIs by trait name to provide a deterministic output.
apis.sort_by_key(|&(name, _hash)| name);
for (_, hash) in apis.iter() {
bytes.extend(hash)
}
hash(&bytes)
}
@@ -449,11 +553,24 @@ pub fn get_metadata_per_pallet_hash<T: AsRef<str>>(
hash(&bytes)
}
/// An error returned if we attempt to get the hash for a specific call, constant
/// or storage item that doesn't exist.
/// An error returned if we attempt to get the hash for a specific call, constant,
/// storage or runtime API function does not exist.
///
/// The location of the specific item (call, constant, storage or runtime API function)
/// is stored with two indirections:
/// - Root
/// The root location of the item. For calls, constants, storage this represents the
/// pallet name. While for runtime API function this represents the trait name.
/// - Item
/// The actual item. For calls, constants, storage this represents the actual name.
/// While for runtime API functions this represents the method name.
#[derive(Clone, Debug)]
pub enum NotFound {
Pallet,
/// The root location of the item cannot be found.
/// - pallet name: for calls, constants, storage
/// - trait name: for runtime API functions
Root,
/// The actual item name cannot be found.
Item,
}
+47 -37
View File
@@ -1,6 +1,6 @@
[package]
name = "subxt"
version = "0.28.0"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
@@ -34,50 +34,60 @@ integration-tests = []
jsonrpsee-ws = ["jsonrpsee/async-client", "jsonrpsee/client-ws-transport"]
jsonrpsee-web = ["jsonrpsee/async-wasm-client", "jsonrpsee/client-web-transport"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full"] }
scale-info = "2.5.0"
scale-value = "0.7.0"
scale-bits = "0.3"
scale-decode = "0.5.0"
scale-encode = "0.1.0"
futures = { version = "0.3.27", default-features = false, features = ["std"] }
hex = "0.4.3"
jsonrpsee = { version = "0.16", optional = true, features = ["jsonrpsee-types"] }
serde = { version = "1.0.159", features = ["derive"] }
serde_json = { version = "1.0.96", features = ["raw_value"] }
thiserror = "1.0.40"
tracing = "0.1.34"
parking_lot = "0.12.0"
frame-metadata = { version = "15.1.0", features = ["v14", "v15-unstable", "std"] }
derivative = "2.2.0"
either = "1.8.1"
# Activate this to fetch and utilize the latest unstabl metadata from a node.
# The unstable metadata is subject to breaking changes and the subxt might
# fail to decode the metadata properly. Use this to experiment with the
# latest features exposed by the metadata.
unstable-metadata = []
subxt-macro = { version = "0.28.0", path = "../macro" }
subxt-metadata = { version = "0.28.0", path = "../metadata" }
[dependencies]
codec = { package = "parity-scale-codec", workspace = true, features = ["derive"] }
scale-info = { workspace = true }
scale-value = { workspace = true }
scale-bits = { workspace = true }
scale-decode = { workspace = true }
scale-encode = { workspace = true }
futures = { workspace = true }
hex = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["raw_value"] }
thiserror = { workspace = true }
tracing = { workspace = true }
parking_lot = { workspace = true }
frame-metadata = { workspace = true }
derivative = { workspace = true }
either = { workspace = true }
# Provides some deserialization, types like U256/H256 and hashing impls like twox/blake256:
impl-serde = { version = "0.4.0" }
primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "scale-info", "serde"] }
sp-core-hashing = "8.0.0"
impl-serde = { workspace = true }
primitive-types = { workspace = true }
sp-core-hashing = { workspace = true }
# For ss58 encoding AccountId32 to serialize them properly:
base58 = { version = "0.2.0" }
blake2 = { version = "0.10.4", default-features = false }
base58 = { workspace = true }
blake2 = { workspace = true }
# Included if one of the jsonrpsee features is enabled.
jsonrpsee = { workspace = true, optional = true, features = ["jsonrpsee-types"] }
# These are only included is "substrate-compat" is enabled.
sp-core = { version = "20.0.0", default-features = false, optional = true }
sp-runtime = { version = "23.0.0", optional = true }
sp-core = { workspace = true, optional = true }
sp-runtime = { workspace = true, optional = true }
# Other subxt crates we depend on.
subxt-macro = { workspace = true }
subxt-metadata = { workspace = true }
[target.wasm32-unknown-unknown.dependencies]
getrandom = { version = "0.2", features = ["js"] }
getrandom = { workspace = true, features = ["js"] }
[dev-dependencies]
bitvec = "1"
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full", "bit-vec"] }
scale-info = { version = "2.5.0", features = ["bit-vec"] }
tokio = { version = "1.27", features = ["macros", "time", "rt-multi-thread"] }
sp-core = { version = "20.0.0", default-features = false }
sp-runtime = "23.0.0"
sp-keyring = "23.0.0"
sp-version = "21.0.0"
bitvec = { workspace = true }
codec = { workspace = true, features = ["derive", "bit-vec"] }
scale-info = { workspace = true, features = ["bit-vec"] }
tokio = { workspace = true, features = ["macros", "time", "rt-multi-thread"] }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
sp-keyring = { workspace = true }
sp-version = { workspace = true }
assert_matches = { workspace = true }
+22 -154
View File
@@ -3,15 +3,16 @@
// see LICENSE for license details.
use crate::{
blocks::{extrinsic_types::ExtrinsicPartTypeIds, Extrinsics},
client::{OfflineClientT, OnlineClientT},
config::{Config, Hasher, Header},
config::{Config, Header},
error::{BlockError, Error},
events,
rpc::types::ChainBlockResponse,
runtime_api::RuntimeApi,
storage::Storage,
};
use derivative::Derivative;
use futures::lock::Mutex as AsyncMutex;
use std::sync::Arc;
@@ -26,7 +27,7 @@ pub struct Block<T: Config, C> {
// A cache for our events so we don't fetch them more than once when
// iterating over events for extrinsics.
type CachedEvents<T> = Arc<AsyncMutex<Option<events::Events<T>>>>;
pub(crate) type CachedEvents<T> = Arc<AsyncMutex<Option<events::Events<T>>>>;
impl<T, C> Block<T, C>
where
@@ -69,16 +70,17 @@ where
/// Fetch and return the block body.
pub async fn body(&self) -> Result<BlockBody<T, C>, Error> {
let ids = ExtrinsicPartTypeIds::new(self.client.metadata().runtime_metadata())?;
let block_hash = self.header.hash();
let block_details = match self.client.rpc().block(Some(block_hash)).await? {
Some(block) => block,
None => return Err(BlockError::not_found(block_hash).into()),
let Some(block_details) = self.client.rpc().block(Some(block_hash)).await? else {
return Err(BlockError::not_found(block_hash).into());
};
Ok(BlockBody::new(
self.client.clone(),
block_details,
self.cached_events.clone(),
ids,
))
}
@@ -99,6 +101,7 @@ pub struct BlockBody<T: Config, C> {
details: ChainBlockResponse<T>,
client: C,
cached_events: CachedEvents<T>,
ids: ExtrinsicPartTypeIds,
}
impl<T, C> BlockBody<T, C>
@@ -110,167 +113,32 @@ where
client: C,
details: ChainBlockResponse<T>,
cached_events: CachedEvents<T>,
ids: ExtrinsicPartTypeIds,
) -> Self {
Self {
details,
client,
cached_events,
ids,
}
}
/// Returns an iterator over the extrinsics in the block body.
pub fn extrinsics(&self) -> impl Iterator<Item = Extrinsic<'_, T, C>> {
self.details
.block
.extrinsics
.iter()
.enumerate()
.map(|(idx, e)| Extrinsic {
index: idx as u32,
bytes: &e.0,
client: self.client.clone(),
block_hash: self.details.block.header.hash(),
cached_events: self.cached_events.clone(),
_marker: std::marker::PhantomData,
})
}
}
/// A single extrinsic in a block.
pub struct Extrinsic<'a, T: Config, C> {
index: u32,
bytes: &'a [u8],
client: C,
block_hash: T::Hash,
cached_events: CachedEvents<T>,
_marker: std::marker::PhantomData<T>,
}
impl<'a, T, C> Extrinsic<'a, T, C>
where
T: Config,
C: OfflineClientT<T>,
{
/// The index of the extrinsic in the block.
pub fn index(&self) -> u32 {
self.index
}
/// The bytes of the extrinsic.
pub fn bytes(&self) -> &'a [u8] {
self.bytes
}
}
impl<'a, T, C> Extrinsic<'a, T, C>
where
T: Config,
C: OnlineClientT<T>,
{
/// The events associated with the extrinsic.
pub async fn events(&self) -> Result<ExtrinsicEvents<T>, Error> {
let events = get_events(&self.client, self.block_hash, &self.cached_events).await?;
let ext_hash = T::Hasher::hash_of(&self.bytes);
Ok(ExtrinsicEvents::new(ext_hash, self.index, events))
}
}
/// The events associated with a given extrinsic.
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub struct ExtrinsicEvents<T: Config> {
// The hash of the extrinsic (handy to expose here because
// this type is returned from TxProgress things in the most
// basic flows, so it's the only place people can access it
// without complicating things for themselves).
ext_hash: T::Hash,
// The index of the extrinsic:
idx: u32,
// All of the events in the block:
events: events::Events<T>,
}
impl<T: Config> ExtrinsicEvents<T> {
pub(crate) fn new(ext_hash: T::Hash, idx: u32, events: events::Events<T>) -> Self {
Self {
ext_hash,
idx,
events,
}
}
/// Return the hash of the block that the extrinsic is in.
pub fn block_hash(&self) -> T::Hash {
self.events.block_hash()
}
/// The index of the extrinsic that these events are produced from.
pub fn extrinsic_index(&self) -> u32 {
self.idx
}
/// Return the hash of the extrinsic.
pub fn extrinsic_hash(&self) -> T::Hash {
self.ext_hash
}
/// Return all of the events in the block that the extrinsic is in.
pub fn all_events_in_block(&self) -> &events::Events<T> {
&self.events
}
/// Iterate over all of the raw events associated with this transaction.
///
/// This works in the same way that [`events::Events::iter()`] does, with the
/// exception that it filters out events not related to the submitted extrinsic.
pub fn iter(&self) -> impl Iterator<Item = Result<events::EventDetails, Error>> + '_ {
self.events.iter().filter(|ev| {
ev.as_ref()
.map(|ev| ev.phase() == events::Phase::ApplyExtrinsic(self.idx))
.unwrap_or(true) // Keep any errors.
})
}
/// Find all of the transaction events matching the event type provided as a generic parameter.
///
/// This works in the same way that [`events::Events::find()`] does, with the
/// exception that it filters out events not related to the submitted extrinsic.
pub fn find<Ev: events::StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> + '_ {
self.iter().filter_map(|ev| {
ev.and_then(|ev| ev.as_event::<Ev>().map_err(Into::into))
.transpose()
})
}
/// Iterate through the transaction events using metadata to dynamically decode and skip
/// them, and return the first event found which decodes to the provided `Ev` type.
///
/// This works in the same way that [`events::Events::find_first()`] does, with the
/// exception that it ignores events not related to the submitted extrinsic.
pub fn find_first<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, Error> {
self.find::<Ev>().next().transpose()
}
/// Iterate through the transaction events using metadata to dynamically decode and skip
/// them, and return the last event found which decodes to the provided `Ev` type.
///
/// This works in the same way that [`events::Events::find_last()`] does, with the
/// exception that it ignores events not related to the submitted extrinsic.
pub fn find_last<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, Error> {
self.find::<Ev>().last().transpose()
}
/// Find an event in those associated with this transaction. Returns true if it was found.
///
/// This works in the same way that [`events::Events::has()`] does, with the
/// exception that it ignores events not related to the submitted extrinsic.
pub fn has<Ev: events::StaticEvent>(&self) -> Result<bool, Error> {
Ok(self.find::<Ev>().next().transpose()?.is_some())
// Dev note: The returned iterator is 'static + Send so that we can box it up and make
// use of it with our `FilterExtrinsic` stuff.
pub fn extrinsics(&self) -> Extrinsics<T, C> {
Extrinsics::new(
self.client.clone(),
self.details.block.extrinsics.clone(),
self.cached_events.clone(),
self.ids,
self.details.block.header.hash(),
)
}
}
// Return Events from the cache, or fetch from the node if needed.
async fn get_events<C, T>(
pub(crate) async fn get_events<C, T>(
client: &C,
block_hash: T::Hash,
cached_events: &AsyncMutex<Option<events::Events<T>>>,
+919
View File
@@ -0,0 +1,919 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
blocks::block_types::{get_events, CachedEvents},
client::{OfflineClientT, OnlineClientT},
config::{Config, Hasher},
error::{BlockError, Error},
events,
metadata::{DecodeWithMetadata, ExtrinsicMetadata},
rpc::types::ChainBlockExtrinsic,
Metadata,
};
use codec::Decode;
use derivative::Derivative;
use frame_metadata::v15::RuntimeMetadataV15;
use scale_decode::DecodeAsFields;
use std::{collections::HashMap, sync::Arc};
/// Trait to uniquely identify the extrinsic's identity from the runtime metadata.
///
/// Generated API structures that represent an extrinsic implement this trait.
///
/// The trait is utilized to decode emitted extrinsics from a block, via obtaining the
/// form of the `Extrinsic` from the metadata.
pub trait StaticExtrinsic: DecodeAsFields {
/// Pallet name.
const PALLET: &'static str;
/// Call name.
const CALL: &'static str;
/// Returns true if the given pallet and call names match this extrinsic.
fn is_extrinsic(pallet: &str, call: &str) -> bool {
Self::PALLET == pallet && Self::CALL == call
}
}
/// This trait is implemented on the statically generated root extrinsic type, so that we're able
/// to decode it properly via a pallet that impls `DecodeAsMetadata`. This is necessary
/// because the "root extrinsic" type is generated using pallet info but doesn't actually exist in the
/// metadata types, so we have no easy way to decode things into it via type information and need a
/// little help via codegen.
#[doc(hidden)]
pub trait RootExtrinsic: Sized {
/// Given details of the pallet extrinsic we want to decode, and the name of the pallet, try to hand
/// back a "root extrinsic".
fn root_extrinsic(
pallet_bytes: &[u8],
pallet_name: &str,
pallet_extrinsic_ty: u32,
metadata: &Metadata,
) -> Result<Self, Error>;
}
/// The body of a block.
pub struct Extrinsics<T: Config, C> {
client: C,
extrinsics: Vec<ChainBlockExtrinsic>,
cached_events: CachedEvents<T>,
ids: ExtrinsicPartTypeIds,
hash: T::Hash,
}
impl<T, C> Extrinsics<T, C>
where
T: Config,
C: OfflineClientT<T>,
{
pub(crate) fn new(
client: C,
extrinsics: Vec<ChainBlockExtrinsic>,
cached_events: CachedEvents<T>,
ids: ExtrinsicPartTypeIds,
hash: T::Hash,
) -> Self {
Self {
client,
extrinsics,
cached_events,
ids,
hash,
}
}
/// The number of extrinsics.
pub fn len(&self) -> usize {
self.extrinsics.len()
}
/// Are there no extrinsics in this block?
// Note: mainly here to satisfy clippy.
pub fn is_empty(&self) -> bool {
self.extrinsics.is_empty()
}
/// Return the block hash that these extrinsics are from.
pub fn block_hash(&self) -> T::Hash {
self.hash
}
/// Returns an iterator over the extrinsics in the block body.
// Dev note: The returned iterator is 'static + Send so that we can box it up and make
// use of it with our `FilterExtrinsic` stuff.
pub fn iter(
&self,
) -> impl Iterator<Item = Result<ExtrinsicDetails<T, C>, Error>> + Send + Sync + 'static {
let extrinsics = self.extrinsics.clone();
let num_extrinsics = self.extrinsics.len();
let client = self.client.clone();
let hash = self.hash;
let cached_events = self.cached_events.clone();
let ids = self.ids;
let mut index = 0;
std::iter::from_fn(move || {
if index == num_extrinsics {
None
} else {
match ExtrinsicDetails::decode_from(
index as u32,
extrinsics[index].0.clone().into(),
client.clone(),
hash,
cached_events.clone(),
ids,
) {
Ok(extrinsic_details) => {
index += 1;
Some(Ok(extrinsic_details))
}
Err(e) => {
index = num_extrinsics;
Some(Err(e))
}
}
}
})
}
/// Iterate through the extrinsics using metadata to dynamically decode and skip
/// them, and return only those which should decode to the provided `E` type.
/// If an error occurs, all subsequent iterations return `None`.
pub fn find<E: StaticExtrinsic>(&self) -> impl Iterator<Item = Result<E, Error>> + '_ {
self.iter().filter_map(|e| {
e.and_then(|e| e.as_extrinsic::<E>().map_err(Into::into))
.transpose()
})
}
/// Iterate through the extrinsics using metadata to dynamically decode and skip
/// them, and return the first extrinsic found which decodes to the provided `E` type.
pub fn find_first<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
self.find::<E>().next().transpose()
}
/// Iterate through the extrinsics using metadata to dynamically decode and skip
/// them, and return the last extrinsic found which decodes to the provided `Ev` type.
pub fn find_last<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
self.find::<E>().last().transpose()
}
/// Find an extrinsics that decodes to the type provided. Returns true if it was found.
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, Error> {
Ok(self.find::<E>().next().transpose()?.is_some())
}
}
/// A single extrinsic in a block.
pub struct ExtrinsicDetails<T: Config, C> {
/// The index of the extrinsic in the block.
index: u32,
/// Extrinsic bytes.
bytes: Arc<[u8]>,
/// True if the extrinsic payload is signed.
is_signed: bool,
/// The start index in the `bytes` from which the address is encoded.
address_start_idx: usize,
/// The end index of the address in the encoded `bytes`.
address_end_idx: usize,
/// The start index in the `bytes` from which the call is encoded.
call_start_idx: usize,
/// The pallet index.
pallet_index: u8,
/// The variant index.
variant_index: u8,
/// The block hash of this extrinsic (needed to fetch events).
block_hash: T::Hash,
/// Subxt client.
client: C,
/// Cached events.
cached_events: CachedEvents<T>,
/// Subxt metadata to fetch the extrinsic metadata.
metadata: Metadata,
_marker: std::marker::PhantomData<T>,
}
impl<T, C> ExtrinsicDetails<T, C>
where
T: Config,
C: OfflineClientT<T>,
{
// Attempt to dynamically decode a single extrinsic from the given input.
pub(crate) fn decode_from(
index: u32,
extrinsic_bytes: Arc<[u8]>,
client: C,
block_hash: T::Hash,
cached_events: CachedEvents<T>,
ids: ExtrinsicPartTypeIds,
) -> Result<ExtrinsicDetails<T, C>, Error> {
const SIGNATURE_MASK: u8 = 0b1000_0000;
const VERSION_MASK: u8 = 0b0111_1111;
const LATEST_EXTRINSIC_VERSION: u8 = 4;
let metadata = client.metadata();
// Extrinsic are encoded in memory in the following way:
// - first byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version)
// - signature: [unknown TBD with metadata].
// - extrinsic data
let first_byte: u8 = Decode::decode(&mut &extrinsic_bytes[..])?;
let version = first_byte & VERSION_MASK;
if version != LATEST_EXTRINSIC_VERSION {
return Err(BlockError::UnsupportedVersion(version).into());
}
let is_signed = first_byte & SIGNATURE_MASK != 0;
// Skip over the first byte which denotes the version and signing.
let cursor = &mut &extrinsic_bytes[1..];
let mut address_start_idx = 0;
let mut address_end_idx = 0;
if is_signed {
address_start_idx = extrinsic_bytes.len() - cursor.len();
// Skip over the address, signature and extra fields.
scale_decode::visitor::decode_with_visitor(
cursor,
ids.address,
&metadata.runtime_metadata().types,
scale_decode::visitor::IgnoreVisitor,
)
.map_err(scale_decode::Error::from)?;
address_end_idx = extrinsic_bytes.len() - cursor.len();
scale_decode::visitor::decode_with_visitor(
cursor,
ids.signature,
&metadata.runtime_metadata().types,
scale_decode::visitor::IgnoreVisitor,
)
.map_err(scale_decode::Error::from)?;
scale_decode::visitor::decode_with_visitor(
cursor,
ids.extra,
&metadata.runtime_metadata().types,
scale_decode::visitor::IgnoreVisitor,
)
.map_err(scale_decode::Error::from)?;
}
let call_start_idx = extrinsic_bytes.len() - cursor.len();
// Decode the pallet index, then the call variant.
let cursor = &mut &extrinsic_bytes[call_start_idx..];
let pallet_index: u8 = Decode::decode(cursor)?;
let variant_index: u8 = Decode::decode(cursor)?;
Ok(ExtrinsicDetails {
index,
bytes: extrinsic_bytes,
is_signed,
address_start_idx,
address_end_idx,
call_start_idx,
pallet_index,
variant_index,
block_hash,
client,
cached_events,
metadata,
_marker: std::marker::PhantomData,
})
}
/// Is the extrinsic signed?
pub fn is_signed(&self) -> bool {
self.is_signed
}
/// The index of the extrinsic in the block.
pub fn index(&self) -> u32 {
self.index
}
/// Return _all_ of the bytes representing this extrinsic, which include, in order:
/// - First byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version)
/// - SignatureType (if the payload is signed)
/// - Address
/// - Signature
/// - Extra fields
/// - Extrinsic call bytes
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
/// Return only the bytes representing this extrinsic call:
/// - First byte is the pallet index
/// - Second byte is the variant (call) index
/// - Followed by field bytes.
///
/// # Note
///
/// Please use [`Self::bytes`] if you want to get all extrinsic bytes.
pub fn call_bytes(&self) -> &[u8] {
&self.bytes[self.call_start_idx..]
}
/// Return the bytes representing the fields stored in this extrinsic.
///
/// # Note
///
/// This is a subset of [`Self::call_bytes`] that does not include the
/// first two bytes that denote the pallet index and the variant index.
pub fn field_bytes(&self) -> &[u8] {
// Note: this cannot panic because we checked the extrinsic bytes
// to contain at least two bytes.
&self.call_bytes()[2..]
}
/// Return only the bytes of the address that signed this extrinsic.
///
/// # Note
///
/// Returns `None` if the extrinsic is not signed.
pub fn address_bytes(&self) -> Option<&[u8]> {
self.is_signed
.then(|| &self.bytes[self.address_start_idx..self.address_end_idx])
}
/// The index of the pallet that the extrinsic originated from.
pub fn pallet_index(&self) -> u8 {
self.pallet_index
}
/// The index of the extrinsic variant that the extrinsic originated from.
pub fn variant_index(&self) -> u8 {
self.variant_index
}
/// The name of the pallet from whence the extrinsic originated.
pub fn pallet_name(&self) -> Result<&str, Error> {
Ok(self.extrinsic_metadata()?.pallet())
}
/// The name of the call (ie the name of the variant that it corresponds to).
pub fn variant_name(&self) -> Result<&str, Error> {
Ok(self.extrinsic_metadata()?.call())
}
/// Fetch the metadata for this extrinsic.
pub fn extrinsic_metadata(&self) -> Result<&ExtrinsicMetadata, Error> {
Ok(self
.metadata
.extrinsic(self.pallet_index(), self.variant_index())?)
}
/// Decode and provide the extrinsic fields back in the form of a [`scale_value::Composite`]
/// type which represents the named or unnamed fields that were
/// present in the extrinsic.
pub fn field_values(
&self,
) -> Result<scale_value::Composite<scale_value::scale::TypeId>, Error> {
let bytes = &mut self.field_bytes();
let extrinsic_metadata = self.extrinsic_metadata()?;
let decoded = <scale_value::Composite<scale_value::scale::TypeId>>::decode_as_fields(
bytes,
extrinsic_metadata.fields(),
&self.metadata.runtime_metadata().types,
)?;
Ok(decoded)
}
/// Attempt to statically decode these [`ExtrinsicDetails`] into a type representing the extrinsic
/// fields. This leans directly on [`codec::Decode`]. You can also attempt to decode the entirety
/// of the extrinsic using [`Self::as_root_extrinsic()`], which is more lenient because it's able
/// to lean on [`scale_decode::DecodeAsType`].
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
let extrinsic_metadata = self.extrinsic_metadata()?;
if extrinsic_metadata.pallet() == E::PALLET && extrinsic_metadata.call() == E::CALL {
let decoded = E::decode_as_fields(
&mut self.field_bytes(),
extrinsic_metadata.fields(),
self.metadata.types(),
)?;
Ok(Some(decoded))
} else {
Ok(None)
}
}
/// Attempt to decode these [`ExtrinsicDetails`] into a pallet extrinsic type (which includes
/// the pallet enum variants as well as the extrinsic fields). These extrinsics can be found in
/// the static codegen under a path like `pallet_name::Call`.
pub fn as_pallet_extrinsic<E: DecodeWithMetadata>(&self) -> Result<E, Error> {
let pallet = self.metadata.pallet(self.pallet_name()?)?;
let extrinsic_ty = pallet.call_ty_id().ok_or_else(|| {
Error::Metadata(crate::metadata::MetadataError::ExtrinsicNotFound(
pallet.index(),
self.variant_index(),
))
})?;
// Ignore the root enum index, so start 1 byte after that:
let decoded =
E::decode_with_metadata(&mut &self.call_bytes()[1..], extrinsic_ty, &self.metadata)?;
Ok(decoded)
}
/// Attempt to decode these [`ExtrinsicDetails`] into a root extrinsic type (which includes
/// the pallet and extrinsic enum variants as well as the extrinsic fields). A compatible
/// type for this is exposed via static codegen as a root level `Call` type.
pub fn as_root_extrinsic<E: RootExtrinsic>(&self) -> Result<E, Error> {
let pallet = self.metadata.pallet(self.pallet_name()?)?;
let pallet_extrinsic_ty = pallet.call_ty_id().ok_or_else(|| {
Error::Metadata(crate::metadata::MetadataError::ExtrinsicNotFound(
pallet.index(),
self.variant_index(),
))
})?;
// Ignore root enum index.
E::root_extrinsic(
&self.call_bytes()[1..],
self.pallet_name()?,
pallet_extrinsic_ty,
&self.metadata,
)
}
}
impl<T, C> ExtrinsicDetails<T, C>
where
T: Config,
C: OnlineClientT<T>,
{
/// The events associated with the extrinsic.
pub async fn events(&self) -> Result<ExtrinsicEvents<T>, Error> {
let events = get_events(&self.client, self.block_hash, &self.cached_events).await?;
let ext_hash = T::Hasher::hash_of(&self.bytes);
Ok(ExtrinsicEvents::new(ext_hash, self.index, events))
}
}
/// The type IDs extracted from the metadata that represent the
/// generic type parameters passed to the `UncheckedExtrinsic` from
/// the substrate-based chain.
#[derive(Debug, Copy, Clone)]
pub(crate) struct ExtrinsicPartTypeIds {
/// The address (source) of the extrinsic.
address: u32,
/// The extrinsic call type.
// Note: the call type can be used to skip over the extrinsic bytes to check
// they are in line with our metadata. This operation is currently postponed.
_call: u32,
/// The signature of the extrinsic.
signature: u32,
/// The extra parameters of the extrinsic.
extra: u32,
}
impl ExtrinsicPartTypeIds {
/// Extract the generic type parameters IDs from the extrinsic type.
pub(crate) fn new(metadata: &RuntimeMetadataV15) -> Result<Self, BlockError> {
const ADDRESS: &str = "Address";
const CALL: &str = "Call";
const SIGNATURE: &str = "Signature";
const EXTRA: &str = "Extra";
let id = metadata.extrinsic.ty.id;
let Some(ty) = metadata.types.resolve(id) else {
return Err(BlockError::MissingType);
};
let params: HashMap<_, _> = ty
.type_params
.iter()
.map(|ty_param| {
let Some(ty) = ty_param.ty else {
return Err(BlockError::MissingType);
};
Ok((ty_param.name.as_str(), ty.id))
})
.collect::<Result<_, _>>()?;
let Some(address) = params.get(ADDRESS) else {
return Err(BlockError::MissingType);
};
let Some(call) = params.get(CALL) else {
return Err(BlockError::MissingType);
};
let Some(signature) = params.get(SIGNATURE) else {
return Err(BlockError::MissingType);
};
let Some(extra) = params.get(EXTRA) else {
return Err(BlockError::MissingType);
};
Ok(ExtrinsicPartTypeIds {
address: *address,
_call: *call,
signature: *signature,
extra: *extra,
})
}
}
/// The events associated with a given extrinsic.
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub struct ExtrinsicEvents<T: Config> {
// The hash of the extrinsic (handy to expose here because
// this type is returned from TxProgress things in the most
// basic flows, so it's the only place people can access it
// without complicating things for themselves).
ext_hash: T::Hash,
// The index of the extrinsic:
idx: u32,
// All of the events in the block:
events: events::Events<T>,
}
impl<T: Config> ExtrinsicEvents<T> {
pub(crate) fn new(ext_hash: T::Hash, idx: u32, events: events::Events<T>) -> Self {
Self {
ext_hash,
idx,
events,
}
}
/// Return the hash of the block that the extrinsic is in.
pub fn block_hash(&self) -> T::Hash {
self.events.block_hash()
}
/// The index of the extrinsic that these events are produced from.
pub fn extrinsic_index(&self) -> u32 {
self.idx
}
/// Return the hash of the extrinsic.
pub fn extrinsic_hash(&self) -> T::Hash {
self.ext_hash
}
/// Return all of the events in the block that the extrinsic is in.
pub fn all_events_in_block(&self) -> &events::Events<T> {
&self.events
}
/// Iterate over all of the raw events associated with this transaction.
///
/// This works in the same way that [`events::Events::iter()`] does, with the
/// exception that it filters out events not related to the submitted extrinsic.
pub fn iter(&self) -> impl Iterator<Item = Result<events::EventDetails, Error>> + '_ {
self.events.iter().filter(|ev| {
ev.as_ref()
.map(|ev| ev.phase() == events::Phase::ApplyExtrinsic(self.idx))
.unwrap_or(true) // Keep any errors.
})
}
/// Find all of the transaction events matching the event type provided as a generic parameter.
///
/// This works in the same way that [`events::Events::find()`] does, with the
/// exception that it filters out events not related to the submitted extrinsic.
pub fn find<Ev: events::StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> + '_ {
self.iter().filter_map(|ev| {
ev.and_then(|ev| ev.as_event::<Ev>().map_err(Into::into))
.transpose()
})
}
/// Iterate through the transaction events using metadata to dynamically decode and skip
/// them, and return the first event found which decodes to the provided `Ev` type.
///
/// This works in the same way that [`events::Events::find_first()`] does, with the
/// exception that it ignores events not related to the submitted extrinsic.
pub fn find_first<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, Error> {
self.find::<Ev>().next().transpose()
}
/// Iterate through the transaction events using metadata to dynamically decode and skip
/// them, and return the last event found which decodes to the provided `Ev` type.
///
/// This works in the same way that [`events::Events::find_last()`] does, with the
/// exception that it ignores events not related to the submitted extrinsic.
pub fn find_last<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, Error> {
self.find::<Ev>().last().transpose()
}
/// Find an event in those associated with this transaction. Returns true if it was found.
///
/// This works in the same way that [`events::Events::has()`] does, with the
/// exception that it ignores events not related to the submitted extrinsic.
pub fn has<Ev: events::StaticEvent>(&self) -> Result<bool, Error> {
Ok(self.find::<Ev>().next().transpose()?.is_some())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{rpc::types::RuntimeVersion, OfflineClient, PolkadotConfig};
use assert_matches::assert_matches;
use codec::{Decode, Encode};
use frame_metadata::{
v15::{ExtrinsicMetadata, PalletCallMetadata, PalletMetadata, RuntimeMetadataV15},
RuntimeMetadataPrefixed,
};
use primitive_types::H256;
use scale_info::{meta_type, TypeInfo};
use scale_value::Value;
// Extrinsic needs to contain at least the generic type parameter "Call"
// for the metadata to be valid.
// The "Call" type from the metadata is used to decode extrinsics.
#[allow(unused)]
#[derive(TypeInfo)]
struct ExtrinsicType<Address, Call, Signature, Extra> {
pub signature: Option<(Address, Signature, Extra)>,
pub function: Call,
}
// Because this type is used to decode extrinsics, we expect this to be a TypeDefVariant.
// Each pallet must contain one single variant.
#[allow(unused)]
#[derive(
Encode,
Decode,
TypeInfo,
Clone,
Debug,
PartialEq,
Eq,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
)]
enum RuntimeCall {
Test(Pallet),
}
// We need this in order to be able to decode into a root extrinsic type:
impl RootExtrinsic for RuntimeCall {
fn root_extrinsic(
mut pallet_bytes: &[u8],
pallet_name: &str,
pallet_extrinsic_ty: u32,
metadata: &Metadata,
) -> Result<Self, Error> {
if pallet_name == "Test" {
return Ok(RuntimeCall::Test(Pallet::decode_with_metadata(
&mut pallet_bytes,
pallet_extrinsic_ty,
metadata,
)?));
}
panic!(
"Asked for pallet name '{pallet_name}', which isn't in our test RuntimeCall type"
)
}
}
// The calls of the pallet.
#[allow(unused)]
#[derive(
Encode,
Decode,
TypeInfo,
Clone,
Debug,
PartialEq,
Eq,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
)]
enum Pallet {
#[allow(unused)]
#[codec(index = 2)]
TestCall {
value: u128,
signed: bool,
name: String,
},
}
#[allow(unused)]
#[derive(
Encode,
Decode,
TypeInfo,
Clone,
Debug,
PartialEq,
Eq,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
)]
struct TestCallExtrinsic {
value: u128,
signed: bool,
name: String,
}
impl StaticExtrinsic for TestCallExtrinsic {
const PALLET: &'static str = "Test";
const CALL: &'static str = "TestCall";
}
/// Build fake metadata consisting the types needed to represent an extrinsic.
fn metadata() -> Metadata {
let pallets = vec![PalletMetadata {
name: "Test",
storage: None,
calls: Some(PalletCallMetadata {
ty: meta_type::<Pallet>(),
}),
event: None,
constants: vec![],
error: None,
index: 0,
docs: vec![],
}];
let extrinsic = ExtrinsicMetadata {
ty: meta_type::<ExtrinsicType<(), RuntimeCall, (), ()>>(),
version: 4,
signed_extensions: vec![],
};
let meta = RuntimeMetadataV15::new(pallets, extrinsic, meta_type::<()>(), vec![]);
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
Metadata::try_from(runtime_metadata).unwrap()
}
/// Build an offline client to work with the test metadata.
fn client(metadata: Metadata) -> OfflineClient<PolkadotConfig> {
// Create the encoded extrinsic bytes.
let rt_version = RuntimeVersion {
spec_version: 1,
transaction_version: 4,
other: Default::default(),
};
let block_hash = H256::random();
OfflineClient::new(block_hash, rt_version, metadata)
}
#[test]
fn extrinsic_metadata_consistency() {
let metadata = metadata();
// Except our metadata to contain the registered types.
let extrinsic = metadata
.extrinsic(0, 2)
.expect("metadata contains the RuntimeCall enum with this pallet");
assert_eq!(extrinsic.pallet(), "Test");
assert_eq!(extrinsic.call(), "TestCall");
}
#[test]
fn insufficient_extrinsic_bytes() {
let metadata = metadata();
let client = client(metadata.clone());
let ids = ExtrinsicPartTypeIds::new(metadata.runtime_metadata()).unwrap();
// Decode with empty bytes.
let result = ExtrinsicDetails::decode_from(
1,
vec![].into(),
client,
H256::random(),
Default::default(),
ids,
);
assert_matches!(result.err(), Some(crate::Error::Codec(_)));
}
#[test]
fn unsupported_version_extrinsic() {
let metadata = metadata();
let client = client(metadata.clone());
let ids = ExtrinsicPartTypeIds::new(metadata.runtime_metadata()).unwrap();
// Decode with invalid version.
let result = ExtrinsicDetails::decode_from(
1,
3u8.encode().into(),
client,
H256::random(),
Default::default(),
ids,
);
assert_matches!(
result.err(),
Some(crate::Error::Block(
crate::error::BlockError::UnsupportedVersion(3)
))
);
}
#[test]
fn statically_decode_extrinsic() {
let metadata = metadata();
let client = client(metadata.clone());
let ids = ExtrinsicPartTypeIds::new(metadata.runtime_metadata()).unwrap();
let tx = crate::tx::dynamic(
"Test",
"TestCall",
vec![
Value::u128(10),
Value::bool(true),
Value::string("SomeValue"),
],
);
let tx_encoded = client
.tx()
.create_unsigned(&tx)
.expect("Valid dynamic parameters are provided");
// Note: `create_unsigned` produces the extrinsic bytes by prefixing the extrinsic length.
// The length is handled deserializing `ChainBlockExtrinsic`, therefore the first byte is not needed.
let extrinsic = ExtrinsicDetails::decode_from(
1,
tx_encoded.encoded()[1..].into(),
client,
H256::random(),
Default::default(),
ids,
)
.expect("Valid extrinsic");
assert!(!extrinsic.is_signed());
assert_eq!(extrinsic.index(), 1);
assert_eq!(extrinsic.pallet_index(), 0);
assert_eq!(
extrinsic
.pallet_name()
.expect("Valid metadata contains pallet name"),
"Test"
);
assert_eq!(extrinsic.variant_index(), 2);
assert_eq!(
extrinsic
.variant_name()
.expect("Valid metadata contains variant name"),
"TestCall"
);
// Decode the extrinsic to the root enum.
let decoded_extrinsic = extrinsic
.as_root_extrinsic::<RuntimeCall>()
.expect("can decode extrinsic to root enum");
assert_eq!(
decoded_extrinsic,
RuntimeCall::Test(Pallet::TestCall {
value: 10,
signed: true,
name: "SomeValue".into(),
})
);
// Decode the extrinsic to the pallet enum.
let decoded_extrinsic = extrinsic
.as_pallet_extrinsic::<Pallet>()
.expect("can decode extrinsic to pallet enum");
assert_eq!(
decoded_extrinsic,
Pallet::TestCall {
value: 10,
signed: true,
name: "SomeValue".into(),
}
);
// Decode the extrinsic to the extrinsic variant.
let decoded_extrinsic = extrinsic
.as_extrinsic::<TestCallExtrinsic>()
.expect("can decode extrinsic to extrinsic variant")
.expect("value cannot be None");
assert_eq!(
decoded_extrinsic,
TestCallExtrinsic {
value: 10,
signed: true,
name: "SomeValue".into(),
}
);
}
}
+5 -1
View File
@@ -6,6 +6,10 @@
mod block_types;
mod blocks_client;
mod extrinsic_types;
pub use block_types::{Block, Extrinsic, ExtrinsicEvents};
pub use block_types::{Block, BlockBody};
pub use blocks_client::{subscribe_to_block_headers_filling_in_gaps, BlocksClient};
pub use extrinsic_types::{
ExtrinsicDetails, ExtrinsicEvents, Extrinsics, RootExtrinsic, StaticExtrinsic,
};
+96
View File
@@ -0,0 +1,96 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
// Dev note; I used the following command to normalize and wrap comments:
// rustfmt +nightly --config wrap_comments=true,comment_width=100,normalize_comments=true subxt/src/book/mod.rs
// It messed up comments in code blocks though, so be prepared to go and fix those.
//! # The Subxt Guide
//!
//! Subxt is a library for interacting with Substrate based nodes. It has a focus on **sub**mitting
//! e**xt**rinsics, hence the name, however it's also capable of reading blocks, storage, events and
//! constants from a node. The aim of this guide is to explain key concepts and get you started with
//! using Subxt.
//!
//! 1. [Features](#features-at-a-glance)
//! 2. [Limitations](#limitations)
//! 3. [Quick start](#quick-start)
//! 4. [Usage](#usage)
//!
//! ## Features at a glance
//!
//! Here's a quick overview of the features that Subxt has to offer:
//!
//! - Subxt allows you to generate a static, type safe interface to a node given some metadata; this
//! allows you to catch many errors at compile time rather than runtime.
//! - Subxt also makes heavy use of node metadata to encode/decode the data sent to/from it. This
//! allows it to target almost any node which can output the correct metadata, and allows it some
//! flexibility in encoding and decoding things to account for cross-node differences.
//! - Subxt has a pallet-oriented interface, meaning that code you write to talk to some pallet on
//! one node will often "Just Work" when pointed at different nodes that use the same pallet.
//! - Subxt can work offline; you can generate and sign transactions, access constants from node
//! metadata and more, without a network connection. This is all checked at compile time, so you
//! can be certain it won't try to establish a network connection if you don't want it to.
//! - Subxt can forego the statically generated interface and build transactions, storage queries
//! and constant queries using data provided at runtime, rather than queries constructed
//! statically.
//! - Subxt can be compiled to WASM to run in the browser, allowing it to back Rust based browser
//! apps, or even bind to JS apps.
//!
//! ## Limitations
//!
//! In various places, you can provide a block hash to access data at a particular block, for
//! instance:
//!
//! - [`crate::storage::StorageClient::at`]
//! - [`crate::events::EventsClient::at`]
//! - [`crate::blocks::BlocksClient::at`]
//! - [`crate::runtime_api::RuntimeApiClient::at`]
//!
//! However, Subxt is (by default) only capable of properly working with blocks that were produced
//! after the most recent runtime update. This is because it uses the most recent metadata given
//! back by a node to encode and decode things. It's possible to decode older blocks produced by a
//! runtime that emits compatible (currently, V14) metadata by manually setting the metadata used by
//! the client using [`crate::client::OnlineClient::set_metadata()`].
//!
//! Subxt does not support working with blocks produced prior to the runtime update that introduces
//! V14 metadata. It may have some success decoding older blocks using newer metadata, but may also
//! completely fail to do so.
//!
//! ## Quick start
//!
//! Here is a simple but complete example of using Subxt to transfer some tokens from the example
//! accounts, Alice to Bob:
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/examples/balance_transfer_basic.rs")]
//! ```
//!
//! This example assumes that a Polkadot node is running locally (Subxt endeavors to support all
//! recent releases). Typically, to use Subxt to talk to some custom Substrate node (for example a
//! parachain node), you'll want to:
//!
//! 1. [Generate an interface](setup::codegen).
//! 2. [Configure and instantiate the client](setup::client).
//!
//! Follow the above links to learn more about each step.
//!
//! ## Usage
//!
//! Once Subxt is configured, the next step is interacting with a node. Follow the links
//! below to learn more about how to use Subxt for each of the following things:
//!
//! - [Extrinsics](usage::extrinsics): Subxt can build and submit extrinsics, wait until they are in
//! blocks, and retrieve the associated events.
//! - [Storage](usage::storage): Subxt can query the node storage.
//! - [Events](usage::events): Subxt can read the events emitted for recent blocks.
//! - [Constants](usage::constants): Subxt can access the constant values stored in a node, which
//! remain the same for a given runtime version.
//! - [Blocks](usage::blocks): Subxt can load recent blocks or subscribe to new/finalized blocks,
//! reading the extrinsics, events and storage at these blocks.
//! - [Runtime APIs](usage::runtime_apis): Subxt can make calls into pallet runtime APIs to retrieve
//! data.
//!
pub mod setup;
pub mod usage;
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! # Configuring the Subxt client
//!
//! Subxt ships with two clients, an [offline client](crate::client::OfflineClient) and an [online
//! client](crate::client::OnlineClient). These are backed by the traits
//! [`crate::client::OfflineClientT`] and [`crate::client::OnlineClientT`], so in theory it's
//! possible for users to implement their own clients, although this isn't generally expected.
//!
//! Both clients are generic over a [`crate::config::Config`] trait, which is the way that we give
//! the client certain information about how to interact with a node that isn't otherwise available
//! or possible to include in the node metadata. Subxt ships out of the box with two default
//! implementations:
//!
//! - [`crate::config::PolkadotConfig`] for talking to Polkadot nodes, and
//! - [`crate::config::SubstrateConfig`] for talking to generic nodes built with Substrate.
//!
//! The latter will generally work in many cases, but will need modifying if the chain you'd like to
//! connect to has altered any of the details mentioned in [the trait](`crate::config::Config`).
//!
//! In the case of the [`crate::OnlineClient`], we have a few options to instantiate it:
//!
//! - [`crate::OnlineClient::new()`] to connect to a node running locally.
//! - [`crate::OnlineClient::from_url()`] to connect to a node at a specific URL.
//! - [`crate::OnlineClient::from_rpc_client()`] to instantiate the client with a custom RPC
//! implementation.
//!
//! The latter accepts anything that implements the low level [`crate::rpc::RpcClientT`] trait; this
//! allows you to decide how Subxt will attempt to talk to a node if you'd prefer something other
//! than the provided interfaces.
//!
//! ## Examples
//!
//! Defining some custom config based off the default Substrate config:
//!
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/setup_client_custom_config.rs")]
//! ```
//! Writing a custom [`crate::rpc::RpcClientT`] implementation:
//!
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/setup_client_custom_rpc.rs")]
//! ```
//! Creating an [`crate::OfflineClient`]:
//!
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/setup_client_offline.rs")]
//! ```
//!
+66
View File
@@ -0,0 +1,66 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! # Generating an interface
//!
//! The simplest way to use Subxt is to generate an interface to a chain that you'd like to interact
//! with. This generated interface allows you to build transactions and construct queries to access
//! data while leveraging the full type safety of the Rust compiler.
//!
//! ## The `#[subxt]` macro
//!
//! The most common way to generate the interface is to use the [`#[subxt]`](crate::subxt) macro.
//! Using this macro looks something like:
//!
//! ```rust,no_run
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//! ```
//!
//! The macro takes a path to some node metadata, and uses that to generate the interface you'll use
//! to talk to it. [Go here](crate::subxt) to learn more about the options available to the macro.
//!
//! To obtain this metadata you'll need for the above, you can use the `subxt` CLI tool to download it
//! from a node. The tool can be installed via `cargo`:
//!
//! ```shell
//! cargo install subxt-cli
//! ```
//!
//! And then it can be used to fetch metadata and save it to a file:
//!
//! ```shell
//! # Download and save all of the metadata:
//! subxt metadata > metadata.scale
//! # Download and save only the pallets you want to generate an interface for:
//! subxt metadata --pallets Balances,System > metadata.scale
//! ```
//!
//! Explicitly specifying pallets will cause the tool to strip out all unnecessary metadata and type
//! information, making the bundle much smaller in the event that you only need to generate an
//! interface for a subset of the available pallets on the node.
//!
//! ## The CLI tool
//!
//! Using the [`#[subxt]`](crate::subxt) macro carries some downsides:
//!
//! - Using it to generate an interface will have a small impact on compile times (though much less of
//! one if you only need a few pallets).
//! - IDE support for autocompletion and documentation when using the macro interface can be poor.
//! - It's impossible to manually look at the generated code to understand and debug things.
//!
//! If these are an issue, you can manually generate the same code that the macro generates under the hood
//! by using the `subxt codegen` command:
//!
//! ```shell
//! # Install the CLI tool if you haven't already:
//! cargo install subxt-cli
//! # Generate and format rust code, saving it to `interface.rs`:
//! subxt codegen | rustfmt > interface.rs
//! ```
//!
//! Use `subxt codegen --help` for more options; many of the options available via the macro are
//! also available via the CLI tool, such as the ability to substitute generated types for others,
//! or strip out docs from the generated code.
//!
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This modules contains details on setting up Subxt:
//!
//! - [Codegen](codegen)
//! - [Client](client)
//!
//! Alternately, [go back](super).
pub mod client;
pub mod codegen;
+39
View File
@@ -0,0 +1,39 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! # Blocks
//!
//! The [blocks API](crate::blocks::BlocksClient) in Subxt unifies many of the other interfaces, and
//! allows you to:
//!
//! - Access information about specific blocks (see [`crate::blocks::BlocksClient::at()`] and
//! [`crate::blocks::BlocksClient::at_latest()`]).
//! - Subscribe to [all](crate::blocks::BlocksClient::subscribe_all()),
//! [best](crate::blocks::BlocksClient::subscribe_best()) or
//! [finalized](crate::blocks::BlocksClient::subscribe_finalized()) blocks as they are produced.
//! Prefer to subscribe to finalized blocks unless you know what you're doing.
//!
//! In either case, you'll end up with [`crate::blocks::Block`]'s, from which you can access various
//! information about the block, such a the [header](crate::blocks::Block::header()), [block
//! number](crate::blocks::Block::number()) and [body](crate::blocks::Block::body()).
//! [`crate::blocks::Block`]'s also provide shortcuts to other Subxt APIs that will operate at the
//! given block:
//!
//! - [storage](crate::blocks::Block::storage()),
//! - [events](crate::blocks::Block::events())
//! - [runtime APIs](crate::blocks::Block::runtime_api())
//!
//! ## Example
//!
//! Given a block, you can [download the block body](crate::blocks::Block::body()) and iterate over
//! the extrinsics stored within it using [`crate::blocks::BlockBody::extrinsics()`].
//!
//! Here's an example in which we subscribe to blocks and print a bunch of information about each
//! one:
//!
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/blocks_subscribing.rs")]
//! ```
//!
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! # Constants
//!
//! There are various constants stored in a node; the types and values of these are defined in a
//! runtime, and can only change when the runtime is updated. Much like [`super::storage`], we can
//! query these using Subxt by taking the following steps:
//!
//! 1. [Constructing a constant query](#constructing-a-query).
//! 2. [Submitting the query to get back the associated value](#submitting-it).
//!
//! ## Constructing a constant query
//!
//! We can use the statically generated interface to build constant queries:
//!
//! ```rust,no_run
//! use sp_keyring::AccountKeyring;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! let constant_query = polkadot::constants().system().block_length();
//! ```
//!
//! Alternately, we can dynamically construct a constant query:
//!
//! ```rust,no_run
//! use sp_keyring::AccountKeyring;
//! use subxt::dynamic::Value;
//!
//! let account = AccountKeyring::Alice.to_account_id();
//! let storage_query = subxt::dynamic::constant("System", "BlockLength");
//! ```
//!
//! Static queries also have a static return type, so the constant is decoded appropriately. In
//! addition, they are validated at runtime to ensure that they align with the current node state.
//! Dynamic queries must be decoded into some static type manually, or into the dynamic
//! [`crate::dynamic::Value`] type.
//!
//! ## Submitting it
//!
//! Constant queries are handed to Subxt via [`crate::constants::ConstantsClient::at()`]. It's worth
//! noting that constant values are pulled directly out of the node metadata which Subxt has
//! already acquired, and so this function requires no network access and is available from a
//! [`crate::OfflineClient`].
//!
//! Here's an example using a static query:
//!
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/constants_static.rs")]
//! ```
//! And here's one using a dynamic query:
//!
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/constants_dynamic.rs")]
//! ```
//!
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! # Events
//!
//! In the process of adding extrinsics to a block, they are executed. When extrinsics are executed,
//! they normally produce events describing what's happening (at the very least, an event dictating whether
//! the extrinsic has succeeded or failed). The node may also emit some events of its own as the block is
//! processed.
//!
//! Events live in a single location in node storage which is overwritten at each block. Normal nodes tend to
//! keep a snapshot of the state at a small number of previous blocks, so you can sometimes access
//! older events by using [`crate::events::EventsClient::at()`] and providing an older block hash.
//!
//! When we submit extrinsics using Subxt, methods like [`crate::tx::TxProgress::wait_for_finalized_success()`]
//! return [`crate::blocks::ExtrinsicEvents`], which can be used to iterate and inspect the events produced
//! for a specific extrinsic. We can also access _all_ of the events produced in a single block using one of these
//! two interfaces:
//!
//! ```rust,no_run
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use subxt::client::OnlineClient;
//! use subxt::config::PolkadotConfig;
//!
//! // Create client:
//! let client = OnlineClient::<PolkadotConfig>::new().await?;
//!
//! // Get events from the latest block (use .at() to specify a block hash):
//! let events = client.blocks().at_latest().await?.events().await?;
//! // We can use this shorthand too:
//! let events = client.events().at_latest().await?;
//! # Ok(())
//! # }
//! ```
//!
//! Once we've loaded our events, we can iterate all events or search for specific events via
//! methods like [`crate::events::Events::iter()`] and [`crate::events::Events::find()`]. See
//! [`crate::events::Events`] and [`crate::events::EventDetails`] for more information.
//!
//! ## Example
//!
//! Here's an example which puts this all together:
//!
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/events.rs")]
//! ```
//!
+224
View File
@@ -0,0 +1,224 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! # Extrinsics
//!
//! Extrinsics define function calls and their parameters, and are the only way that you can change
//! the state of the blockchain. Submitting extrinsics to a node is one of the core features of
//! Subxt, and generally consists of the following steps:
//!
//! 1. [Constructing an extrinsic payload to submit](#constructing-an-extrinsic-payload).
//! 2. [Signing it](#signing-it).
//! 3. [Submitting it (optionally with some additional parameters)](#submitting-it).
//!
//! We'll look at each of these steps in turn.
//!
//! > As a side note, an _extrinsic_ is anything that can be added to a block, and a _transaction_
//! > is an extrinsic that is submitted from a particular user (and is therefore also signed). Subxt
//! > can construct unsigned extrinsics, but overwhelmingly you'll need to sign them, and so the
//! > documentation tends to use the terms _extrinsic_ and _transaction_ interchangeably.
//!
//! Furthermore, Subxt is capable of decoding extrinsics included in blocks.
//!
//! ## Constructing an extrinsic payload
//!
//! We can use the statically generated interface to build extrinsic payloads:
//!
//! ```rust,no_run
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! let remark = "Hello there".as_bytes().to_vec();
//! let extrinsic_payload = polkadot::tx().system().remark(remark);
//! ```
//!
//! > If you're not sure what types to import and use to build a given payload, you can use the
//! > `subxt` CLI tool to generate the interface by using something like `subxt codegen | rustfmt >
//! > interface.rs`, to see what types and things are available (or even just to use directly
//! > instead of the [`#[subxt]`](crate::subxt) macro).
//!
//! Alternately, we can dynamically construct an extrinsic payload. This will not be type checked or
//! validated until it's submitted:
//!
//! ```rust,no_run
//! use subxt::dynamic::Value;
//!
//! let extrinsic_payload = subxt::dynamic::tx("System", "remark", vec![
//! Value::from_bytes("Hello there")
//! ]);
//! ```
//!
//! The [`crate::dynamic::Value`] type is a dynamic type much like a `serde_json::Value` but instead
//! represents any type of data that can be SCALE encoded or decoded. It can be serialized,
//! deserialized and parsed from/to strings.
//!
//! A valid extrinsic payload is just something that implements the [`crate::tx::TxPayload`] trait;
//! you can implement this trait on your own custom types if the built-in ones are not suitable for
//! your needs.
//!
//! ## Signing it
//!
//! You'll normally need to sign an extrinsic to prove that it originated from an account that you
//! control. To do this, you will typically first create an [`crate::tx::Signer`], which tells Subxt
//! who the extrinsic is from, and takes care of signing the relevant details to prove this.
//!
//! Subxt provides a [`crate::tx::PairSigner`] which implements this trait (if the
//! `substrate-compat` feature is enabled) which accepts any valid [`sp_core::Pair`] and uses that
//! to sign transactions:
//!
//! ```rust
//! use subxt::tx::PairSigner;
//! use sp_core::Pair;
//! use subxt::config::PolkadotConfig;
//!
//! // Get hold of a `Signer` given a test account:
//! let pair = sp_keyring::AccountKeyring::Alice.pair();
//! let signer = PairSigner::<PolkadotConfig,_>::new(pair);
//!
//! // Or generate an `sr25519` keypair to use:
//! let (pair, _, _) = sp_core::sr25519::Pair::generate_with_phrase(Some("password"));
//! let signer = PairSigner::<PolkadotConfig,_>::new(pair);
//! ```
//!
//! See the [`sp_core::Pair`] docs for more ways to generate them.
//!
//! If this isn't suitable/available, you can either implement [`crate::tx::Signer`] yourself to use
//! custom signing logic, or you can use some external signing logic, like so:
//!
//! ```rust,no_run
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use subxt::client::OnlineClient;
//! use subxt::config::PolkadotConfig;
//! use subxt::dynamic::Value;
//!
//! // Create client:
//! let client = OnlineClient::<PolkadotConfig>::new().await?;
//!
//! // Create a dummy extrinsic payload to sign:
//! let payload = subxt::dynamic::tx("System", "remark", vec![
//! Value::from_bytes("Hello there")
//! ]);
//!
//! // Construct the extrinsic but don't sign it. You need to provide the nonce
//! // here, or can use `create_partial_signed` to fetch the correct nonce.
//! let partial_extrinsic = client.tx().create_partial_signed_with_nonce(
//! &payload,
//! 0,
//! Default::default()
//! )?;
//!
//! // Fetch the payload that needs to be signed:
//! let signer_payload = partial_extrinsic.signer_payload();
//!
//! // ... At this point, we can hand off the `signer_payload` to be signed externally.
//! // Ultimately we need to be given back a `signature` (or really, anything
//! // that can be SCALE encoded) and an `address`:
//! let signature;
//! let address;
//! # use subxt::tx::Signer;
//! # let pair = sp_keyring::AccountKeyring::Alice.pair();
//! # let signer = subxt::tx::PairSigner::<PolkadotConfig,_>::new(pair);
//! # signature = signer.sign(&signer_payload);
//! # address = signer.address();
//!
//! // Now we can build an extrinsic, which one can call `submit` or `submit_and_watch`
//! // on to submit to a node and optionally watch the status.
//! let extrinsic = partial_extrinsic.sign_with_address_and_signature(
//! &address,
//! &signature
//! );
//! # Ok(())
//! # }
//! ```
//!
//! ## Submitting it
//!
//! Once we are able to sign the extrinsic, we need to submit it.
//!
//! ### The high level API
//!
//! The highest level approach to doing this is to call
//! [`crate::tx::TxClient::sign_and_submit_then_watch_default`]. This hands back a
//! [`crate::tx::TxProgress`] struct which will monitor the transaction status. We can then call
//! [`crate::tx::TxProgress::wait_for_finalized_success()`] to wait for this transaction to make it
//! into a finalized block, check for an `ExtrinsicSuccess` event, and then hand back the events for
//! inspection. This looks like:
//!
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/balance_transfer_basic.rs")]
//! ```
//! ### Providing extrinsic parameters
//!
//! If you'd like to provide extrinsic parameters (such as mortality), you can use
//! [`crate::tx::TxClient::sign_and_submit_then_watch`] instead, and provide parameters for your
//! chain:
//!
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/balance_transfer_with_params.rs")]
//! ```
//! This example doesn't wait for the extrinsic to be included in a block; it just submits it and
//! hopes for the best!
//!
//! ### Custom handling of transaction status updates
//!
//! If you'd like more control or visibility over exactly which status updates are being emitted for
//! the transaction, you can monitor them as they are emitted and react however you choose:
//!
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/balance_transfer_status_stream.rs")]
//! ```
//! Take a look at the API docs for [`crate::tx::TxProgress`], [`crate::tx::TxStatus`] and
//! [`crate::tx::TxInBlock`] for more options.
//!
//! ## Decoding Extrinsics
//!
//! The block body is made up of extrinsics representing the generalization of the concept of transactions.
//! Extrinsics can contain any external data the underlying chain wishes to validate and track.
//!
//! The process of decoding extrinsics generally involves the following steps:
//!
//! 1. Retrieve a block from the chain: This can be done directly by providing a specific hash using [crate::blocks::BlocksClient::at()]
//! and [crate::blocks::BlocksClient::at_latest()], or indirectly by subscribing to the latest produced blocks of the chain using
//! [crate::blocks::BlocksClient::subscribe_finalized()].
//!
//! 2. Fetch the block's body using [crate::blocks::Block::body()].
//!
//! 3. Obtain the extrinsics from the block's body using [crate::blocks::BlockBody::extrinsics()].
//!
//! ```rust,no_run
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use subxt::client::OnlineClient;
//! use subxt::config::PolkadotConfig;
//!
//! // Create client:
//! let client = OnlineClient::<PolkadotConfig>::new().await?;
//!
//! // Get the lastest block (or use .at() to specify a block hash):
//! let block = client.blocks().at_latest().await?;
//!
//! // Get the block's body which contains the extrinsics.
//! let body = block.body().await?;
//!
//! // Fetch the extrinsics of the block's body.
//! let extrinsics = block.body().await?.extrinsics();
//! # Ok(())
//! # }
//! ```
//!
//! Once the extrinsics are loaded, similar to events, you can iterate through the extrinsics or search for specific extrinsics using methods
//! such as [crate::blocks::Extrinsics::iter()] and [crate::blocks::Extrinsics::find()]. For more information, refer to [crate::blocks::ExtrinsicDetails].
//!
//! ### Example
//!
//! Here's an example that demonstrates the usage of these concepts:
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/block_extrinsics.rs")]
//! ```
//!
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This modules contains examples of using Subxt; follow the links for more:
//!
//! - [Extrinsics](extrinsics)
//! - [Storage](storage)
//! - [Events](events)
//! - [Constants](constants)
//! - [Blocks](blocks)
//! - [Runtime APIs](runtime_apis)
//!
//! Alternately, [go back](super).
pub mod blocks;
pub mod constants;
pub mod events;
pub mod extrinsics;
pub mod runtime_apis;
pub mod storage;
+81
View File
@@ -0,0 +1,81 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! # Runtime API interface
//!
//! The Runtime API interface allows Subxt to call runtime APIs exposed by certain pallets in order
//! to obtain information. Much like [`super::storage`] and [`super::extrinsics`], Making a runtime
//! call to a node and getting the response back takes the following steps:
//!
//! 1. [Constructing a runtime call](#constructing-a-runtime-call)
//! 2. [Submitting it to get back the response](#submitting-it)
//!
//! **Note:** Runtime APIs are only available when using V15 metadata, which is currently unstable.
//! You'll need to use `subxt metadata --version unstable` command to download the unstable V15 metadata,
//! and activate the `unstable-metadata` feature in Subxt for it to also use this metadata from a node. The
//! metadata format is unstable because it may change and break compatibility with Subxt at any moment, so
//! use at your own risk.
//!
//! ## Constructing a runtime call
//!
//! We can use the statically generated interface to build runtime calls:
//!
//! ```rust,no_run
//! use sp_keyring::AccountKeyring;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! let runtime_call = polkadot::apis().metadata().metadata_versions();
//! ```
//!
//! Alternately, we can dynamically construct a runtime call:
//!
//! ```rust,no_run
//! use sp_keyring::AccountKeyring;
//! use subxt::dynamic::Value;
//!
//! let account = AccountKeyring::Alice.to_account_id();
//! let runtime_call = subxt::dynamic::runtime_api_call(
//! "Metadata_metadata_versions",
//! Vec::<Value<()>>::new()
//! );
//! ```
//!
//! All valid runtime calls implement [`crate::runtime_api::RuntimeApiPayload`], a trait which
//! describes how to encode the runtime call arguments and what return type to decode from the
//! response.
//!
//! ## Submitting it
//!
//! Runtime calls can be handed to [`crate::runtime_api::RuntimeApi::call()`], which will submit
//! them and hand back the associated response.
//!
//! ### Making a static Runtime API call
//!
//! The easiest way to make a runtime API call is to use the statically generated interface.
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/runtime_apis_static.rs")]
//! ```
//!
//! ### Making a dynamic Runtime API call
//!
//! If you'd prefer to construct the call at runtime, you can do this using the
//! [`crate::dynamic::runtime_api_call`] method.
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/runtime_apis_dynamic.rs")]
//! ```
//!
//! ### Making a raw call
//!
//! This is generally discouraged in favour of one of the above, but may be necessary (especially if
//! the node you're talking to does not yet serve V15 metadata). Here, you must manually encode
//! the argument bytes and manually provide a type for the response bytes to be decoded into.
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/runtime_apis_raw.rs")]
//! ```
//!
+110
View File
@@ -0,0 +1,110 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! # Storage
//!
//! A Substrate based chain has storage, whose values are determined by the extrinsics added to past
//! blocks. Subxt allows you to query the storage of a node, which consists of the following steps:
//!
//! 1. [Constructing a storage query](#constructing-a-storage-query).
//! 2. [Submitting the query to get back the associated values](#submitting-it).
//!
//! ## Constructing a storage query
//!
//! We can use the statically generated interface to build storage queries:
//!
//! ```rust,no_run
//! use sp_keyring::AccountKeyring;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! let account = AccountKeyring::Alice.to_account_id().into();
//! let storage_query = polkadot::storage().system().account(&account);
//! ```
//!
//! Alternately, we can dynamically construct a storage query. This will not be type checked or
//! validated until it's submitted:
//!
//! ```rust,no_run
//! use sp_keyring::AccountKeyring;
//! use subxt::dynamic::Value;
//!
//! let account = AccountKeyring::Alice.to_account_id();
//! let storage_query = subxt::dynamic::storage("System", "Account", vec![
//! Value::from_bytes(account)
//! ]);
//! ```
//!
//! As well as accessing specific entries, some storage locations can also be iterated over (such as
//! the map of account information). To do this, suffix `_root` onto the query constructor (this
//! will only be available on static constructors when iteration is actually possible):
//!
//! ```rust,no_run
//! use sp_keyring::AccountKeyring;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! // A static query capable of iterating over accounts:
//! let storage_query = polkadot::storage().system().account_root();
//! // A dynamic query to do the same:
//! let storage_query = subxt::dynamic::storage_root("System", "Account");
//! ```
//!
//! All valid storage queries implement [`crate::storage::StorageAddress`]. As well as describing
//! how to build a valid storage query, this trait also has some associated types that determine the
//! shape of the result you'll get back, and determine what you can do with it (ie, can you iterate
//! over storage entries using it).
//!
//! Static queries set appropriate values for these associated types, and can therefore only be used
//! where it makes sense. Dynamic queries don't know any better and can be used in more places, but
//! may fail at runtime instead if they are invalid in those places.
//!
//! ## Submitting it
//!
//! Storage queries can be handed to various functions in [`crate::storage::Storage`] in order to
//! obtain the associated values (also referred to as storage entries) back.
//!
//! ### Fetching storage entries
//!
//! The simplest way to access storage entries is to construct a query and then call either
//! [`crate::storage::Storage::fetch()`] or [`crate::storage::Storage::fetch_or_default()`] (the
//! latter will only work for storage queries that have a default value when empty):
//!
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/storage_fetch.rs")]
//! ```
//! For completeness, below is an example using a dynamic query instead. The return type from a
//! dynamic query is a [`crate::dynamic::DecodedValueThunk`], which can be decoded into a
//! [`crate::dynamic::Value`], or else the raw bytes can be accessed instead.
//!
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/storage_fetch_dynamic.rs")]
//! ```
//! ### Iterating storage entries
//!
//! Many storage entries are maps of values; as well as fetching individual values, it's possible to
//! iterate over all of the values stored at that location:
//!
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/storage_iterating.rs")]
//! ```
//! Here's the same logic but using dynamically constructed values instead:
//!
//!
//! ```rust,ignore
#![doc = include_str!("../../../../examples/examples/storage_iterating_dynamic.rs")]
//! ```
//! ### Advanced
//!
//! For more advanced use cases, have a look at [`crate::storage::Storage::fetch_raw`] and
//! [`crate::storage::Storage::fetch_keys`]. Both of these take raw bytes as arguments, which can be
//! obtained from a [`crate::storage::StorageAddress`] by using
//! [`crate::storage::StorageClient::address_bytes()`] or
//! [`crate::storage::StorageClient::address_root_bytes()`].
//!
+14 -7
View File
@@ -17,9 +17,7 @@ use crate::{
tx::TxClient,
Config, Metadata,
};
use codec::Compact;
use derivative::Derivative;
use frame_metadata::RuntimeMetadataPrefixed;
use futures::future;
use parking_lot::RwLock;
use std::sync::Arc;
@@ -136,10 +134,19 @@ impl<T: Config> OnlineClient<T> {
/// Fetch the metadata from substrate using the runtime API.
async fn fetch_metadata(rpc: &Rpc<T>) -> Result<Metadata, Error> {
let (_, meta) = rpc
.state_call::<(Compact<u32>, RuntimeMetadataPrefixed)>("Metadata_metadata", None, None)
.await?;
Ok(meta.try_into()?)
#[cfg(feature = "unstable-metadata")]
{
// Try to fetch the latest unstable metadata, if that fails fall back to
// fetching the latest stable metadata.
const V15_METADATA_VERSION: u32 = u32::MAX;
match rpc.metadata_at_version(V15_METADATA_VERSION).await {
Ok(bytes) => Ok(bytes),
Err(_) => rpc.metadata().await,
}
}
#[cfg(not(feature = "unstable-metadata"))]
rpc.metadata().await
}
/// Create an object which can be used to keep the runtime up to date
@@ -383,7 +390,7 @@ impl<T: Config> RuntimeUpdaterStream<T> {
Err(err) => return Some(Err(err)),
};
let metadata = match self.client.rpc().metadata(None).await {
let metadata = match self.client.rpc().metadata().await {
Ok(metadata) => metadata,
Err(err) => return Some(Err(err)),
};
+39 -11
View File
@@ -29,7 +29,7 @@ pub trait Config: 'static {
/// transactions associated with a sender account.
type Index: Debug + Copy + DeserializeOwned + Into<u64>;
/// The output of the `Hashing` function.
/// The output of the `Hasher` function.
type Hash: Debug
+ Copy
+ Send
@@ -54,7 +54,7 @@ pub trait Config: 'static {
type Hasher: Debug + Hasher<Output = Self::Hash>;
/// The block header.
type Header: Debug + Header<Hasher = Self::Hasher> + Send + DeserializeOwned;
type Header: Debug + Header<Hasher = Self::Hasher> + Sync + Send + DeserializeOwned;
/// This type defines the extrinsic extra and additional parameters.
type ExtrinsicParams: extrinsic_params::ExtrinsicParams<Self::Index, Self::Hash>;
@@ -95,15 +95,6 @@ pub trait Header: Sized + Encode {
/// Take a type implementing [`Config`] (eg [`SubstrateConfig`]), and some type which describes the
/// additional and extra parameters to pass to an extrinsic (see [`ExtrinsicParams`]),
/// and returns a type implementing [`Config`] with those new [`ExtrinsicParams`].
///
/// # Example
///
/// ```
/// use subxt::config::{ SubstrateConfig, WithExtrinsicParams, polkadot::PolkadotExtrinsicParams };
///
/// // This is how PolkadotConfig is implemented:
/// type PolkadotConfig = WithExtrinsicParams<SubstrateConfig, PolkadotExtrinsicParams<SubstrateConfig>>;
/// ```
pub struct WithExtrinsicParams<T: Config, E: extrinsic_params::ExtrinsicParams<T::Index, T::Hash>> {
_marker: std::marker::PhantomData<(T, E)>,
}
@@ -120,3 +111,40 @@ impl<T: Config, E: extrinsic_params::ExtrinsicParams<T::Index, T::Hash>> Config
type Header = T::Header;
type ExtrinsicParams = E;
}
/// implement subxt's Hasher and Header traits for some substrate structs
#[cfg(feature = "substrate-compat")]
mod substrate_impls {
use super::*;
use primitive_types::{H256, U256};
impl<N, H> Header for sp_runtime::generic::Header<N, H>
where
Self: Encode,
N: Copy + Into<U256> + Into<u64> + TryFrom<U256>,
H: sp_runtime::traits::Hash + Hasher,
{
type Number = N;
type Hasher = H;
fn number(&self) -> Self::Number {
self.number
}
}
impl Hasher for sp_core::Blake2Hasher {
type Output = H256;
fn hash(s: &[u8]) -> Self::Output {
<Self as sp_core::Hasher>::hash(s)
}
}
impl Hasher for sp_core::KeccakHasher {
type Output = H256;
fn hash(s: &[u8]) -> Self::Output {
<Self as sp_core::Hasher>::hash(s)
}
}
}
+19 -5
View File
@@ -4,15 +4,29 @@
//! Polkadot specific configuration
use super::{
extrinsic_params::{BaseExtrinsicParams, BaseExtrinsicParamsBuilder},
Config,
};
use codec::Encode;
use super::extrinsic_params::{BaseExtrinsicParams, BaseExtrinsicParamsBuilder};
pub use crate::utils::{AccountId32, MultiAddress, MultiSignature};
use crate::SubstrateConfig;
pub use primitive_types::{H256, U256};
/// Default set of commonly used types by Polkadot nodes.
pub type PolkadotConfig = super::WithExtrinsicParams<
super::SubstrateConfig,
PolkadotExtrinsicParams<super::SubstrateConfig>,
>;
pub enum PolkadotConfig {}
impl Config for PolkadotConfig {
type Index = <SubstrateConfig as Config>::Index;
type Hash = <SubstrateConfig as Config>::Hash;
type AccountId = <SubstrateConfig as Config>::AccountId;
type Address = MultiAddress<Self::AccountId, ()>;
type Signature = <SubstrateConfig as Config>::Signature;
type Hasher = <SubstrateConfig as Config>::Hasher;
type Header = <SubstrateConfig as Config>::Header;
type ExtrinsicParams = PolkadotExtrinsicParams<Self>;
}
/// A struct representing the signed extra and additional parameters required
/// to construct a transaction for a polkadot node.
+4 -1
View File
@@ -11,7 +11,7 @@ use crate::{
};
use scale_decode::DecodeAsType;
pub use scale_value::Value;
pub use scale_value::{At, Value};
/// A [`scale_value::Value`] type endowed with contextual information
/// regarding what type was used to decode each part of it. This implements
@@ -28,6 +28,9 @@ pub use crate::constants::dynamic as constant;
// Lookup storage values dynamically.
pub use crate::storage::{dynamic as storage, dynamic_root as storage_root};
// Execute runtime API function call dynamically.
pub use crate::runtime_api::dynamic as runtime_api_call;
/// This is the result of making a dynamic request to a node. From this,
/// we can return the raw SCALE bytes that we were handed back, or we can
/// complete the decoding of the bytes into a [`DecodedValue`] type.
+11 -1
View File
@@ -10,6 +10,9 @@ use core::fmt::Debug;
use scale_decode::visitor::DecodeAsTypeResult;
use std::borrow::Cow;
use super::Error;
use crate::error::RootError;
/// An error dispatching a transaction.
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
@@ -133,12 +136,13 @@ impl PartialEq for ModuleError {
self.raw == other.raw
}
}
impl Eq for ModuleError {}
impl std::fmt::Display for ModuleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Ok(details) = self.details() else {
return f.write_str("Unknown pallet error (pallet and error details cannot be retrieved)")
return f.write_str("Unknown pallet error (pallet and error details cannot be retrieved)");
};
let pallet = details.pallet();
@@ -159,6 +163,12 @@ impl ModuleError {
pub fn raw(&self) -> RawModuleError {
self.raw
}
/// Attempts to decode the ModuleError into a value implementing the trait `RootError`
/// where the actual type of value is the generated top level enum `Error`.
pub fn as_root_error<E: RootError>(&self) -> Result<E, Error> {
E::root_error(&self.raw.error, self.details()?.pallet(), &self.metadata)
}
}
/// The error details about a module error that has occurred.
+23 -1
View File
@@ -14,7 +14,7 @@ pub use dispatch_error::{
};
// Re-expose the errors we use from other crates here:
pub use crate::metadata::{InvalidMetadataError, MetadataError};
pub use crate::metadata::{InvalidMetadataError, Metadata, MetadataError};
pub use scale_decode::Error as DecodeError;
pub use scale_encode::Error as EncodeError;
@@ -102,6 +102,16 @@ pub enum BlockError {
/// An error containing the hash of the block that was not found.
#[error("Could not find a block with hash {0} (perhaps it was on a non-finalized fork?)")]
NotFound(String),
/// Extrinsic type ID cannot be resolved with the provided metadata.
#[error("Extrinsic type ID cannot be resolved with the provided metadata. Make sure this is a valid metadata")]
MissingType,
/// Unsupported signature.
#[error("Unsupported extrinsic version, only version 4 is supported currently")]
/// The extrinsic has an unsupported version.
UnsupportedVersion(u8),
/// Decoding error.
#[error("Cannot decode extrinsic: {0}")]
DecodingError(codec::Error),
}
impl BlockError {
@@ -162,3 +172,15 @@ pub enum StorageAddressError {
fields: usize,
},
}
/// This trait is implemented on the statically generated root ModuleError type
#[doc(hidden)]
pub trait RootError: Sized {
/// Given details of the pallet error we want to decode
fn root_error(
// typically a [u8; 4] encodes the error of a pallet
pallet_bytes: &[u8],
pallet_name: &str,
metadata: &Metadata,
) -> Result<Self, Error>;
}
+26 -2
View File
@@ -76,7 +76,7 @@ impl<T: Config> Events<T> {
/// .await?
/// .expect("didn't pass a block number; qed");
/// // Fetch the metadata of the given block.
/// let metadata = client.rpc().metadata(Some(block_hash)).await?;
/// let metadata = client.rpc().metadata_legacy(Some(block_hash)).await?;
/// // Fetch the events from the client.
/// let events = Events::new_from_client(metadata, block_hash, client);
/// # Ok(())
@@ -493,6 +493,30 @@ pub(crate) mod test_utils {
/// Build fake metadata consisting of a single pallet that knows
/// about the event type provided.
pub fn metadata<E: TypeInfo + 'static>() -> Metadata {
// Extrinsic needs to contain at least the generic type parameter "Call"
// for the metadata to be valid.
// The "Call" type from the metadata is used to decode extrinsics.
// In reality, the extrinsic type has "Call", "Address", "Extra", "Signature" generic types.
#[allow(unused)]
#[derive(TypeInfo)]
struct ExtrinsicType<Call> {
call: Call,
}
// Because this type is used to decode extrinsics, we expect this to be a TypeDefVariant.
// Each pallet must contain one single variant.
#[allow(unused)]
#[derive(TypeInfo)]
enum RuntimeCall {
PalletName(Pallet),
}
// The calls of the pallet.
#[allow(unused)]
#[derive(TypeInfo)]
enum Pallet {
#[allow(unused)]
SomeCall,
}
let pallets = vec![PalletMetadata {
name: "Test",
storage: None,
@@ -507,7 +531,7 @@ pub(crate) mod test_utils {
}];
let extrinsic = ExtrinsicMetadata {
ty: meta_type::<()>(),
ty: meta_type::<ExtrinsicType<RuntimeCall>>(),
version: 0,
signed_extensions: vec![],
};
+207 -105
View File
@@ -2,112 +2,13 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Subxt is a library to **sub**mit e**xt**rinsics to a [substrate](https://github.com/paritytech/substrate) node via RPC.
//! Subxt is a library for interacting with Substrate based nodes. Using it looks something like this:
//!
//! The generated Subxt API exposes the ability to:
//! - [Submit extrinsics](https://docs.substrate.io/v3/concepts/extrinsics/) (Calls)
//! - [Query storage](https://docs.substrate.io/v3/runtime/storage/) (Storage)
//! - [Query constants](https://docs.substrate.io/how-to-guides/v3/basics/configurable-constants/) (Constants)
//! - [Subscribe to events](https://docs.substrate.io/v3/runtime/events-and-errors/) (Events)
//!
//! # Initializing the API client
//!
//! To interact with a node, you'll need to construct a client.
//!
//! ```no_run
//! use subxt::{OnlineClient, PolkadotConfig};
//!
//! # #[tokio::main]
//! # async fn main() {
//! let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
//! # }
//! ```rust,ignore
#![doc = include_str!("../../examples/examples/balance_transfer_basic.rs")]
//! ```
//!
//! This default client connects to a locally running node, but can be configured to point anywhere.
//! Additionally, an [`crate::OfflineClient`] is available to perform operations that don't require a
//! network connection to a node.
//!
//! The client takes a type parameter, here [`crate::PolkadotConfig`], which bakes in assumptions about
//! the structure of extrinsics and the underlying types used by the node for things like block numbers.
//! If the node you'd like to interact with deviates from Polkadot or the default Substrate node in these
//! areas, you'll need to configure them by implementing the [`crate::config::Config`] type yourself.
//!
//! # Generating runtime types
//!
//! Subxt can optionally generate types at compile time to help you interact with a node. These types are
//! generated using metadata which can be downloaded from a node using the [subxt-cli](https://crates.io/crates/subxt-cli)
//! tool. These generated types provide a degree of type safety when interacting with a node that is compatible with
//! the metadata that they were generated using. We also do runtime checks in case the node you're talking to has
//! deviated from the types you're using to communicate with it (see below).
//!
//! To generate the types, use the `subxt` macro and point it at the metadata you've downloaded, like so:
//!
//! ```ignore
//! #[subxt::subxt(runtime_metadata_path = "metadata.scale")]
//! pub mod node_runtime { }
//! ```
//!
//! For more information, please visit the [subxt-codegen](https://docs.rs/subxt-codegen/latest/subxt_codegen/)
//! documentation.
//!
//! You can opt to skip this step and use dynamic queries to talk to nodes instead, which can be useful in some cases,
//! but doesn't provide any type safety.
//!
//! # Interacting with the API
//!
//! Once instantiated, a client exposes four core functions:
//! - `.tx()` for submitting extrinsics/transactions. See [`crate::tx::TxClient`] for more details, or see
//! the [balance_transfer](../examples/examples/balance_transfer.rs) example.
//! - `.storage()` for fetching and iterating over storage entries. See [`crate::storage::StorageClient`] for more details, or see
//! the [fetch_staking_details](../examples/examples/fetch_staking_details.rs) example.
//! - `.constants()` for getting hold of constants. See [`crate::constants::ConstantsClient`] for more details, or see
//! the [fetch_constants](../examples/examples/fetch_constants.rs) example.
//! - `.events()` for subscribing/obtaining events. See [`crate::events::EventsClient`] for more details, or see:
//! - [subscribe_all_events](../examples/examples/subscribe_all_events.rs): Subscribe to events emitted from blocks.
//! - [subscribe_one_event](../examples/examples/subscribe_one_event.rs): Subscribe and filter by one event.
//! - [subscribe_some_events](../examples/examples/subscribe_some_events.rs): Subscribe and filter event.
//!
//! # Static Metadata Validation
//!
//! If you use types generated by the [`crate::subxt`] macro, there is a chance that they will fall out of sync
//! with the actual state of the node you're trying to interact with.
//!
//! When you attempt to use any of these static types to interact with a node, Subxt will validate that they are
//! still compatible and issue an error if they have deviated.
//!
//! Additionally, you can validate that the entirety of the statically generated code aligns with a node like so:
//!
//! ```no_run
//! use subxt::{OnlineClient, PolkadotConfig};
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
//!
//! if let Err(_e) = polkadot::validate_codegen(&api) {
//! println!("Generated code is not up to date with node we're connected to");
//! }
//! # }
//! ```
//! ## Opting out of static validation
//!
//! The static types that are used to query/access information are validated by default, to make sure that they are
//! compatible with the node being queried. You can generally call `.unvalidated()` on these static types to
//! disable this validation.
//!
//! # Runtime Updates
//!
//! The node you're connected to may occasionally perform runtime updates while you're connected, which would ordinarily
//! leave the runtime state of the node out of sync with the information Subxt requires to do things like submit
//! transactions.
//!
//! If this is a concern, you can use the `UpdateClient` API to keep the `RuntimeVersion` and `Metadata` of the client
//! synced with the target node.
//!
//! Please visit the [subscribe_runtime_updates](../examples/examples/subscribe_runtime_updates.rs) example for more details.
//! Take a look at [the Subxt guide](book) to learn more about how to use Subxt.
#![deny(
bad_style,
@@ -132,13 +33,14 @@
)]
#![allow(clippy::type_complexity)]
// The guide is here.
pub mod book;
// Suppress an unused dependency warning because tokio is
// only used in example code snippets at the time of writing.
#[cfg(test)]
use tokio as _;
pub use subxt_macro::subxt;
// Used to enable the js feature for wasm.
#[cfg(target_arch = "wasm32")]
pub use getrandom as _;
@@ -184,3 +86,203 @@ pub mod ext {
#[cfg(feature = "substrate-compat")]
pub use sp_runtime;
}
/// Generate a strongly typed API for interacting with a Substrate runtime from its metadata.
///
/// # Metadata
///
/// First, you'll need to get hold of some metadata for the node you'd like to interact with. One
/// way to do this is by using the `subxt` CLI tool:
///
/// ```bash
/// # Install the CLI tool:
/// cargo install subxt-cli
/// # Use it to download metadata (in this case, from a node running locally)
/// subxt metadata > polkadot_metadata.scale
/// ```
///
/// Run `subxt metadata --help` for more options.
///
/// # Basic usage
///
/// Annotate a Rust module with the `subxt` attribute referencing the aforementioned metadata file.
///
/// ```rust,no_run
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
/// )]
/// mod polkadot {}
/// ```
///
/// The `subxt` macro will populate the annotated module with all of the methods and types required
/// for interacting with the runtime that the metadata came from via Subxt.
///
/// # Configuration
///
/// This macro supports a number of attributes to configure what is generated:
///
/// ## `crate = "..."`
///
/// Use this attribute to specify a custom path to the `subxt` crate:
///
/// ```rust
/// # pub extern crate subxt;
/// # pub mod path { pub mod to { pub use subxt; } }
/// # fn main() {}
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
/// crate = "crate::path::to::subxt"
/// )]
/// mod polkadot {}
/// ```
///
/// This is useful if you write a library which uses this macro, but don't want to force users to depend on `subxt`
/// at the top level too. By default the path `::subxt` is used.
///
/// ## `substitute_type(path = "...", with = "...")`
///
/// This attribute replaces any reference to the generated type at the path given by `path` with a
/// reference to the path given by `with`.
///
/// ```rust
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
/// substitute_type(path = "sp_arithmetic::per_things::Perbill", with = "crate::Foo")
/// )]
/// mod polkadot {}
///
/// # #[derive(
/// # scale_encode::EncodeAsType,
/// # scale_decode::DecodeAsType,
/// # codec::Encode,
/// # codec::Decode,
/// # Clone,
/// # Debug,
/// # )]
/// // In reality this needs some traits implementing on
/// // it to allow it to be used in place of Perbill:
/// pub struct Foo(u32);
/// # impl codec::CompactAs for Foo {
/// # type As = u32;
/// # fn encode_as(&self) -> &Self::As {
/// # &self.0
/// # }
/// # fn decode_from(x: Self::As) -> Result<Self, codec::Error> {
/// # Ok(Foo(x))
/// # }
/// # }
/// # impl From<codec::Compact<Foo>> for Foo {
/// # fn from(v: codec::Compact<Foo>) -> Foo {
/// # v.0
/// # }
/// # }
/// # fn main() {}
/// ```
///
/// If the type you're substituting contains generic parameters, you can "pattern match" on those, and
/// make use of them in the substituted type, like so:
///
/// ```rust,no_run
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
/// substitute_type(
/// path = "sp_runtime::multiaddress::MultiAddress<A, B>",
/// with = "::subxt::utils::Static<::sp_runtime::MultiAddress<A, B>>"
/// )
/// )]
/// mod polkadot {}
/// ```
///
/// The above is also an example of using the [`crate::utils::Static`] type to wrap some type which doesn't
/// on it's own implement [`scale_encode::EncodeAsType`] or [`scale_decode::DecodeAsType`], which are required traits
/// for any substitute type to implement by default.
///
/// ## `derive_for_all_types = "..."`
///
/// By default, all generated types derive a small set of traits. This attribute allows you to derive additional
/// traits on all generated types:
///
/// ```rust,no_run
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
/// derive_for_all_types = "Eq, PartialEq"
/// )]
/// mod polkadot {}
/// ```
///
/// Any substituted types (including the default substitutes) must also implement these traits in order to avoid errors
/// here.
///
/// ## `derive_for_type(path = "...", derive = "...")`
///
/// Unlike the above, which derives some trait on every generated type, this attribute allows you to derive traits only
/// for specific types. Note that any types which are used inside the specified type may also need to derive the same traits.
///
/// ```rust,no_run
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
/// derive_for_all_types = "Eq, PartialEq",
/// derive_for_type(path = "frame_support::PalletId", derive = "Ord, PartialOrd"),
/// derive_for_type(path = "sp_runtime::ModuleError", derive = "Hash"),
/// )]
/// mod polkadot {}
/// ```
///
/// ## `runtime_metadata_url = "..."`
///
/// This attribute can be used instead of `runtime_metadata_path` and will tell the macro to download metadata from a node running
/// at the provided URL, rather than a node running locally. This can be useful in CI, but is **not recommended** in production code,
/// since it runs at compile time and will cause compilation to fail if the node at the given address is unavailable or unresponsive.
///
/// ```rust,ignore
/// #[subxt::subxt(
/// runtime_metadata_url = "wss://rpc.polkadot.io:443"
/// )]
/// mod polkadot {}
/// ```
///
/// ## `generate_docs`
///
/// By default, documentation is not generated via the macro, since IDEs do not typically make use of it. This attribute
/// forces documentation to be generated, too.
///
/// ```rust,no_run
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
/// generate_docs
/// )]
/// mod polkadot {}
/// ```
///
/// ## `runtime_types_only`
///
/// By default, the macro will generate various interfaces to make using Subxt simpler in addition with any types that need
/// generating to make this possible. This attribute makes the codegen only generate the types and not the Subxt interface.
///
/// ```rust,no_run
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
/// runtime_types_only
/// )]
/// mod polkadot {}
/// ```
/// ## `no_default_derives`
///
/// By default, the macro will add all derives necessary for the generated code to play nicely with Subxt. Adding this attribute
/// removes all default derives.
///
/// ```rust,no_run
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
/// runtime_types_only,
/// no_default_derives,
/// derive_for_all_types="codec::Encode, codec::Decode"
/// )]
/// mod polkadot {}
/// ```
///
/// **Note**: At the moment, you must derive at least one of `codec::Encode` or `codec::Decode` or `scale_encode::EncodeAsType` or
/// `scale_decode::DecodeAsType` (because we add `#[codec(..)]` attributes on some fields/types during codegen), and you must use this
/// feature in conjunction with `runtime_types_only` (or manually specify a bunch of defaults to make codegen work properly when
/// generating the subxt interfaces).
pub use subxt_macro::subxt;
+12 -13
View File
@@ -5,23 +5,23 @@
use parking_lot::RwLock;
use std::{borrow::Cow, collections::HashMap};
/// A cache with the simple goal of storing 32 byte hashes against pallet+item keys
/// A cache with the simple goal of storing 32 byte hashes against root+item keys
#[derive(Default, Debug)]
pub struct HashCache {
inner: RwLock<HashMap<PalletItemKey<'static>, [u8; 32]>>,
inner: RwLock<HashMap<RootItemKey<'static>, [u8; 32]>>,
}
impl HashCache {
/// get a hash out of the cache by its pallet and item key. If the item doesn't exist,
/// get a hash out of the cache by its root and item key. If the item doesn't exist,
/// run the function provided to obtain a hash to insert (or bail with some error on failure).
pub fn get_or_insert<F, E>(&self, pallet: &str, item: &str, f: F) -> Result<[u8; 32], E>
pub fn get_or_insert<F, E>(&self, root: &str, item: &str, f: F) -> Result<[u8; 32], E>
where
F: FnOnce() -> Result<[u8; 32], E>,
{
let maybe_hash = self
.inner
.read()
.get(&PalletItemKey::new(pallet, item))
.get(&RootItemKey::new(root, item))
.copied();
if let Some(hash) = maybe_hash {
@@ -29,10 +29,9 @@ impl HashCache {
}
let hash = f()?;
self.inner.write().insert(
PalletItemKey::new(pallet.to_string(), item.to_string()),
hash,
);
self.inner
.write()
.insert(RootItemKey::new(root.to_string(), item.to_string()), hash);
Ok(hash)
}
@@ -41,14 +40,14 @@ impl HashCache {
/// This exists so that we can look items up in the cache using &strs, without having to allocate
/// Strings first (as you'd have to do to construct something like an `&(String,String)` key).
#[derive(Debug, PartialEq, Eq, Hash)]
struct PalletItemKey<'a> {
struct RootItemKey<'a> {
pallet: Cow<'a, str>,
item: Cow<'a, str>,
}
impl<'a> PalletItemKey<'a> {
impl<'a> RootItemKey<'a> {
fn new(pallet: impl Into<Cow<'a, str>>, item: impl Into<Cow<'a, str>>) -> Self {
PalletItemKey {
RootItemKey {
pallet: pallet.into(),
item: item.into(),
}
@@ -75,7 +74,7 @@ mod tests {
cache
.inner
.read()
.get(&PalletItemKey::new(pallet, item))
.get(&RootItemKey::new(pallet, item))
.unwrap(),
&value.unwrap()
);
+248 -4
View File
@@ -27,9 +27,15 @@ pub enum MetadataError {
/// Event is not in metadata.
#[error("Pallet {0}, Event {0} not found")]
EventNotFound(u8, u8),
/// Extrinsic is not in metadata.
#[error("Pallet {0}, Extrinsic {0} not found")]
ExtrinsicNotFound(u8, u8),
/// Event is not in metadata.
#[error("Pallet {0}, Error {0} not found")]
ErrorNotFound(u8, u8),
/// Runtime function is not in metadata.
#[error("Runtime function not found")]
RuntimeFnNotFound,
/// Storage is not in metadata.
#[error("Storage not found")]
StorageNotFound,
@@ -57,6 +63,9 @@ pub enum MetadataError {
/// Runtime storage metadata is incompatible with the static one.
#[error("Pallet {0} Storage {0} has incompatible metadata")]
IncompatibleStorageMetadata(String, String),
/// Runtime API metadata is incompatible with the static one.
#[error("Runtime API Trait {0} Method {0} has incompatible metadata")]
IncompatibleRuntimeApiMetadata(String, String),
/// Runtime metadata is not fully compatible with the static one.
#[error("Node metadata is not fully compatible")]
IncompatibleMetadata,
@@ -69,6 +78,8 @@ struct MetadataInner {
// Events are hashed by pallet an error index (decode oriented)
events: HashMap<(u8, u8), EventMetadata>,
// Extrinsics are hashed by pallet an error index (decode oriented)
extrinsics: HashMap<(u8, u8), ExtrinsicMetadata>,
// Errors are hashed by pallet and error index (decode oriented)
errors: HashMap<(u8, u8), ErrorMetadata>,
@@ -79,6 +90,9 @@ struct MetadataInner {
// an extrinsic fails.
dispatch_error_ty: Option<u32>,
// Runtime API metadata
runtime_apis: HashMap<String, RuntimeFnMetadata>,
// The hashes uniquely identify parts of the metadata; different
// hashes mean some type difference exists between static and runtime
// versions. We cache them here to avoid recalculating:
@@ -86,6 +100,7 @@ struct MetadataInner {
cached_call_hashes: HashCache,
cached_constant_hashes: HashCache,
cached_storage_hashes: HashCache,
cached_runtime_hashes: HashCache,
}
/// A representation of the runtime metadata received from a node.
@@ -95,6 +110,14 @@ pub struct Metadata {
}
impl Metadata {
/// Returns a reference to [`RuntimeFnMetadata`].
pub fn runtime_fn(&self, name: &str) -> Result<&RuntimeFnMetadata, MetadataError> {
self.inner
.runtime_apis
.get(name)
.ok_or(MetadataError::RuntimeFnNotFound)
}
/// Returns a reference to [`PalletMetadata`].
pub fn pallet(&self, name: &str) -> Result<&PalletMetadata, MetadataError> {
self.inner
@@ -117,6 +140,20 @@ impl Metadata {
Ok(event)
}
/// Returns the metadata for the extrinsic at the given pallet and call indices.
pub fn extrinsic(
&self,
pallet_index: u8,
call_index: u8,
) -> Result<&ExtrinsicMetadata, MetadataError> {
let event = self
.inner
.extrinsics
.get(&(pallet_index, call_index))
.ok_or(MetadataError::ExtrinsicNotFound(pallet_index, call_index))?;
Ok(event)
}
/// Returns the metadata for the error at the given pallet and error indices.
pub fn error(
&self,
@@ -158,7 +195,7 @@ impl Metadata {
.get_or_insert(pallet, storage, || {
subxt_metadata::get_storage_hash(&self.inner.metadata, pallet, storage).map_err(
|e| match e {
subxt_metadata::NotFound::Pallet => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::StorageNotFound,
},
)
@@ -172,7 +209,7 @@ impl Metadata {
.get_or_insert(pallet, constant, || {
subxt_metadata::get_constant_hash(&self.inner.metadata, pallet, constant).map_err(
|e| match e {
subxt_metadata::NotFound::Pallet => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::ConstantNotFound,
},
)
@@ -186,13 +223,27 @@ impl Metadata {
.get_or_insert(pallet, function, || {
subxt_metadata::get_call_hash(&self.inner.metadata, pallet, function).map_err(|e| {
match e {
subxt_metadata::NotFound::Pallet => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::CallNotFound,
}
})
})
}
/// Obtain the unique hash for a runtime API function.
pub fn runtime_api_hash(
&self,
trait_name: &str,
method_name: &str,
) -> Result<[u8; 32], MetadataError> {
self.inner
.cached_runtime_hashes
.get_or_insert(trait_name, method_name, || {
subxt_metadata::get_runtime_api_hash(&self.inner.metadata, trait_name, method_name)
.map_err(|_| MetadataError::RuntimeFnNotFound)
})
}
/// Obtain the unique hash for this metadata.
pub fn metadata_hash<T: AsRef<str>>(&self, pallets: &[T]) -> [u8; 32] {
if let Some(hash) = *self.inner.cached_metadata_hash.read() {
@@ -206,6 +257,42 @@ impl Metadata {
}
}
/// Metadata for a specific runtime API function.
#[derive(Clone, Debug)]
pub struct RuntimeFnMetadata {
/// The trait name of the runtime function.
trait_name: String,
/// The method name of the runtime function.
method_name: String,
/// The parameter name and type IDs interpreted as `scale_info::Field`
/// for ease of decoding.
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
/// The type ID of the return type.
return_id: u32,
}
impl RuntimeFnMetadata {
/// Get the parameters as fields.
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
&self.fields
}
/// Return the trait name of the runtime function.
pub fn trait_name(&self) -> &str {
&self.trait_name
}
/// Return the method name of the runtime function.
pub fn method_name(&self) -> &str {
&self.method_name
}
/// Get the type ID of the return type.
pub fn return_id(&self) -> u32 {
self.return_id
}
}
/// Metadata for a specific pallet.
#[derive(Clone, Debug)]
pub struct PalletMetadata {
@@ -318,6 +405,39 @@ impl EventMetadata {
}
}
/// Metadata for specific extrinsics.
#[derive(Clone, Debug)]
pub struct ExtrinsicMetadata {
// The pallet name is shared across every extrinsic, so put it
// behind an Arc to avoid lots of needless clones of it existing.
pallet: Arc<str>,
call: String,
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
docs: Vec<String>,
}
impl ExtrinsicMetadata {
/// Get the name of the pallet from which the extrinsic was emitted.
pub fn pallet(&self) -> &str {
&self.pallet
}
/// Get the name of the extrinsic call.
pub fn call(&self) -> &str {
&self.call
}
/// The names, type names & types of each field in the extrinsic.
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
&self.fields
}
/// Documentation for this extrinsic.
pub fn docs(&self) -> &[String] {
&self.docs
}
}
/// Details about a specific runtime error.
#[derive(Clone, Debug)]
pub struct ErrorMetadata {
@@ -358,6 +478,12 @@ pub enum InvalidMetadataError {
/// Type missing from type registry
#[error("Type {0} missing from type registry")]
MissingType(u32),
/// Type missing extrinsic "Call" type
#[error("Missing extrinsic Call type")]
MissingCallType,
/// The extrinsic variant expected to contain a single field.
#[error("Extrinsic variant at index {0} expected to contain a single field")]
InvalidExtrinsicVariant(u8),
/// Type was not a variant/enum type
#[error("Type {0} was not a variant/enum type")]
TypeDefNotVariant(u32),
@@ -376,6 +502,49 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
_ => return Err(InvalidMetadataError::InvalidVersion),
};
let runtime_apis: HashMap<String, RuntimeFnMetadata> = metadata
.apis
.iter()
.flat_map(|trait_metadata| {
let trait_name = &trait_metadata.name;
trait_metadata
.methods
.iter()
.map(|method_metadata| {
// Function named used by substrate to identify the runtime call.
let fn_name = format!("{}_{}", trait_name, method_metadata.name);
// Parameters mapped as `scale_info::Field` to allow dynamic decoding.
let fields: Vec<_> = method_metadata
.inputs
.iter()
.map(|input| {
let name = input.name.clone();
let ty = input.ty.id;
scale_info::Field {
name: Some(name),
ty: ty.into(),
type_name: None,
docs: Default::default(),
}
})
.collect();
let return_id = method_metadata.output.id;
let metadata = RuntimeFnMetadata {
fields,
return_id,
trait_name: trait_name.clone(),
method_name: method_metadata.name.clone(),
};
(fn_name, metadata)
})
.collect::<Vec<_>>()
})
.collect();
let get_type_def_variant = |type_id: u32| {
let ty = metadata
.types
@@ -485,17 +654,68 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
.find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
.map(|ty| ty.id);
let extrinsic_ty = metadata
.types
.resolve(metadata.extrinsic.ty.id)
.ok_or(InvalidMetadataError::MissingType(metadata.extrinsic.ty.id))?;
let Some(call_id) = extrinsic_ty.type_params
.iter()
.find(|ty| ty.name == "Call")
.and_then(|ty| ty.ty)
.map(|ty| ty.id) else {
return Err(InvalidMetadataError::MissingCallType);
};
let call_type_variants = get_type_def_variant(call_id)?;
let mut extrinsics = HashMap::<(u8, u8), ExtrinsicMetadata>::new();
for variant in &call_type_variants.variants {
let pallet_name: Arc<str> = variant.name.to_string().into();
let pallet_index = variant.index;
// Pallet variants must contain one single call variant.
// In the following form:
//
// enum RuntimeCall {
// Pallet(pallet_call)
// }
if variant.fields.len() != 1 {
return Err(InvalidMetadataError::InvalidExtrinsicVariant(pallet_index));
}
let Some(ty) = variant.fields.first() else {
return Err(InvalidMetadataError::InvalidExtrinsicVariant(pallet_index));
};
// Get the call variant.
let call_type_variant = get_type_def_variant(ty.ty.id)?;
for variant in &call_type_variant.variants {
extrinsics.insert(
(pallet_index, variant.index),
ExtrinsicMetadata {
pallet: pallet_name.clone(),
call: variant.name.to_string(),
fields: variant.fields.clone(),
docs: variant.docs.clone(),
},
);
}
}
Ok(Metadata {
inner: Arc::new(MetadataInner {
metadata,
pallets,
events,
extrinsics,
errors,
dispatch_error_ty,
runtime_apis,
cached_metadata_hash: Default::default(),
cached_call_hashes: Default::default(),
cached_constant_hashes: Default::default(),
cached_storage_hashes: Default::default(),
cached_runtime_hashes: Default::default(),
}),
})
}
@@ -511,6 +731,30 @@ mod tests {
use scale_info::{meta_type, TypeInfo};
fn load_metadata() -> Metadata {
// Extrinsic needs to contain at least the generic type parameter "Call"
// for the metadata to be valid.
// The "Call" type from the metadata is used to decode extrinsics.
// In reality, the extrinsic type has "Call", "Address", "Extra", "Signature" generic types.
#[allow(unused)]
#[derive(TypeInfo)]
struct ExtrinsicType<Call> {
call: Call,
}
// Because this type is used to decode extrinsics, we expect this to be a TypeDefVariant.
// Each pallet must contain one single variant.
#[allow(unused)]
#[derive(TypeInfo)]
enum RuntimeCall {
PalletName(Pallet),
}
// The calls of the pallet.
#[allow(unused)]
#[derive(TypeInfo)]
enum Pallet {
#[allow(unused)]
SomeCall,
}
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[derive(TypeInfo)]
@@ -549,7 +793,7 @@ mod tests {
let metadata = RuntimeMetadataV15::new(
vec![pallet],
ExtrinsicMetadata {
ty: meta_type::<()>(),
ty: meta_type::<ExtrinsicType<RuntimeCall>>(),
version: 0,
signed_extensions: vec![],
},
+2 -1
View File
@@ -12,7 +12,8 @@ mod metadata_type;
pub use metadata_location::MetadataLocation;
pub use metadata_type::{
ErrorMetadata, EventMetadata, InvalidMetadataError, Metadata, MetadataError, PalletMetadata,
ErrorMetadata, EventMetadata, ExtrinsicMetadata, InvalidMetadataError, Metadata, MetadataError,
PalletMetadata, RuntimeFnMetadata,
};
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
+48 -5
View File
@@ -143,8 +143,8 @@ impl<T: Config> Rpc<T> {
genesis_hash.ok_or_else(|| "Genesis hash not found".into())
}
/// Fetch the metadata
pub async fn metadata(&self, at: Option<T::Hash>) -> Result<Metadata, Error> {
/// Fetch the metadata via the legacy `state_getMetadata` RPC method.
pub async fn metadata_legacy(&self, at: Option<T::Hash>) -> Result<Metadata, Error> {
let bytes: types::Bytes = self
.client
.request("state_getMetadata", rpc_params![at])
@@ -347,13 +347,13 @@ impl<T: Config> Rpc<T> {
Ok(xt_hash)
}
/// Execute a runtime API call.
pub async fn state_call<Res: Decode>(
/// Execute a runtime API call via `state_call` RPC method.
pub async fn state_call_raw(
&self,
function: &str,
call_parameters: Option<&[u8]>,
at: Option<T::Hash>,
) -> Result<Res, Error> {
) -> Result<types::Bytes, Error> {
let call_parameters = call_parameters.unwrap_or_default();
let bytes: types::Bytes = self
.client
@@ -362,11 +362,54 @@ impl<T: Config> Rpc<T> {
rpc_params![function, to_hex(call_parameters), at],
)
.await?;
Ok(bytes)
}
/// Execute a runtime API call and decode the result.
pub async fn state_call<Res: Decode>(
&self,
function: &str,
call_parameters: Option<&[u8]>,
at: Option<T::Hash>,
) -> Result<Res, Error> {
let bytes = self.state_call_raw(function, call_parameters, at).await?;
let cursor = &mut &bytes[..];
let res: Res = Decode::decode(cursor)?;
Ok(res)
}
/// Execute runtime API call and return the specified runtime metadata version.
pub async fn metadata_at_version(&self, version: u32) -> Result<Metadata, Error> {
let param = version.encode();
let opaque: Option<frame_metadata::OpaqueMetadata> = self
.state_call("Metadata_metadata_at_version", Some(&param), None)
.await?;
let bytes = opaque.ok_or(Error::Other("Metadata version not found".into()))?;
let meta: RuntimeMetadataPrefixed = Decode::decode(&mut &bytes.0[..])?;
let metadata: Metadata = meta.try_into()?;
Ok(metadata)
}
/// Execute a runtime API call into `Metadata_metadata` method
/// to fetch the latest available metadata.
///
/// # Note
///
/// This returns the same output as [`Self::metadata`], but calls directly
/// into the runtime.
pub async fn metadata(&self) -> Result<Metadata, Error> {
let bytes: frame_metadata::OpaqueMetadata =
self.state_call("Metadata_metadata", None, None).await?;
let meta: RuntimeMetadataPrefixed = Decode::decode(&mut &bytes.0[..])?;
let metadata: Metadata = meta.try_into()?;
Ok(metadata)
}
/// Create and submit an extrinsic and return a subscription to the events triggered.
pub async fn watch_extrinsic<X: Encode>(
&self,
+2
View File
@@ -5,7 +5,9 @@
//! Types associated with executing runtime API calls.
mod runtime_client;
mod runtime_payload;
mod runtime_types;
pub use runtime_client::RuntimeApiClient;
pub use runtime_payload::{dynamic, DynamicRuntimeApiPayload, Payload, RuntimeApiPayload};
pub use runtime_types::RuntimeApi;
+152
View File
@@ -0,0 +1,152 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use core::marker::PhantomData;
use scale_encode::EncodeAsFields;
use scale_value::Composite;
use std::borrow::Cow;
use crate::dynamic::DecodedValueThunk;
use crate::{metadata::DecodeWithMetadata, Error, Metadata};
/// This represents a runtime API payload that can call into the runtime of node.
///
/// # Components
///
/// - associated return type
///
/// Resulting bytes of the call are interpreted into this type.
///
/// - runtime function name
///
/// The function name of the runtime API call. This is obtained by concatenating
/// the runtime trait name with the trait's method.
///
/// For example, the substrate runtime trait [Metadata](https://github.com/paritytech/substrate/blob/cb954820a8d8d765ce75021e244223a3b4d5722d/primitives/api/src/lib.rs#L745)
/// contains the `metadata_at_version` function. The corresponding runtime function
/// is `Metadata_metadata_at_version`.
///
/// - encoded arguments
///
/// Each argument of the runtime function must be scale-encoded.
pub trait RuntimeApiPayload {
/// The return type of the function call.
// Note: `DecodeWithMetadata` is needed to decode the function call result
// with the `subxt::Metadata.
type ReturnType: DecodeWithMetadata;
/// The runtime API function name.
fn fn_name(&self) -> &str;
/// Scale encode the arguments data.
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
/// Encode arguments data and return the output. This is a convenience
/// wrapper around [`RuntimeApiPayload::encode_args_to`].
fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_args_to(metadata, &mut v)?;
Ok(v)
}
/// Returns the statically generated validation hash.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// A runtime API payload containing the generic argument data
/// and interpreting the result of the call as `ReturnTy`.
///
/// This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive(Clone, Debug)]
pub struct Payload<ArgsData, ReturnTy> {
fn_name: Cow<'static, str>,
args_data: ArgsData,
validation_hash: Option<[u8; 32]>,
_marker: PhantomData<ReturnTy>,
}
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> RuntimeApiPayload
for Payload<ArgsData, ReturnTy>
{
type ReturnType = ReturnTy;
fn fn_name(&self) -> &str {
&self.fn_name
}
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let fn_metadata = metadata.runtime_fn(&self.fn_name)?;
self.args_data
.encode_as_fields_to(fn_metadata.fields(), metadata.types(), out)?;
Ok(())
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// A dynamic runtime API payload.
pub type DynamicRuntimeApiPayload = Payload<Composite<()>, DecodedValueThunk>;
impl<ReturnTy, ArgsData> Payload<ArgsData, ReturnTy> {
/// Create a new [`Payload`].
pub fn new(fn_name: impl Into<String>, args_data: ArgsData) -> Self {
Payload {
fn_name: Cow::Owned(fn_name.into()),
args_data,
validation_hash: None,
_marker: PhantomData,
}
}
/// Create a new static [`Payload`] using static function name
/// and scale-encoded argument data.
///
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
fn_name: &'static str,
args_data: ArgsData,
hash: [u8; 32],
) -> Payload<ArgsData, ReturnTy> {
Payload {
fn_name: Cow::Borrowed(fn_name),
args_data,
validation_hash: Some(hash),
_marker: std::marker::PhantomData,
}
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Returns the function name.
pub fn fn_name(&self) -> &str {
&self.fn_name
}
/// Returns the arguments data.
pub fn args_data(&self) -> &ArgsData {
&self.args_data
}
}
/// Create a new [`DynamicRuntimeApiPayload`].
pub fn dynamic(
fn_name: impl Into<String>,
args_data: impl Into<Composite<()>>,
) -> DynamicRuntimeApiPayload {
Payload::new(fn_name, args_data.into())
}
+56 -1
View File
@@ -2,11 +2,13 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{client::OnlineClientT, error::Error, Config};
use crate::{client::OnlineClientT, error::Error, metadata::DecodeWithMetadata, Config};
use codec::Decode;
use derivative::Derivative;
use std::{future::Future, marker::PhantomData};
use super::RuntimeApiPayload;
/// Execute runtime API calls.
#[derive(Derivative)]
#[derivative(Clone(bound = "Client: Clone"))]
@@ -50,4 +52,57 @@ where
Ok(data)
}
}
/// Execute a runtime API call.
pub fn call<Call: RuntimeApiPayload>(
&self,
payload: Call,
) -> impl Future<Output = Result<Call::ReturnType, Error>> {
let client = self.client.clone();
let block_hash = self.block_hash;
// Ensure that the returned future doesn't have a lifetime tied to api.runtime_api(),
// which is a temporary thing we'll be throwing away quickly:
async move {
let metadata = client.metadata();
let function = payload.fn_name();
// Check if the function is present in the runtime metadata.
let fn_metadata = metadata.runtime_fn(function)?;
// Return type ID used for dynamic decoding.
let return_id = fn_metadata.return_id();
// Validate the runtime API payload hash against the compile hash from codegen.
if let Some(static_hash) = payload.validation_hash() {
let runtime_hash = metadata
.runtime_api_hash(fn_metadata.trait_name(), fn_metadata.method_name())?;
if static_hash != runtime_hash {
return Err(
crate::metadata::MetadataError::IncompatibleRuntimeApiMetadata(
fn_metadata.trait_name().into(),
fn_metadata.method_name().into(),
)
.into(),
);
}
}
// Encode the arguments of the runtime call.
// For static payloads (codegen) this is pass-through, bytes are not altered.
// For dynamic payloads this relies on `scale_value::encode_as_fields_to`.
let params = payload.encode_args(&metadata)?;
let bytes = client
.rpc()
.state_call_raw(function, Some(params.as_slice()), Some(block_hash))
.await?;
let value = <Call::ReturnType as DecodeWithMetadata>::decode_with_metadata(
&mut &bytes[..],
return_id,
&metadata,
)?;
Ok(value)
}
}
}
+22 -22
View File
@@ -1,6 +1,6 @@
[package]
name = "integration-tests"
version = "0.28.0"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
@@ -16,24 +16,24 @@ description = "Subxt integration tests that rely on the Substrate binary"
default = ["subxt/integration-tests"]
[dev-dependencies]
assert_matches = "1.5.0"
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full", "bit-vec"] }
frame-metadata = { version = "15.1.0", features = ["v14", "v15-unstable", "std"] }
futures = "0.3.27"
hex = "0.4.3"
regex = "1.8.1"
scale-info = { version = "2.5.0", features = ["bit-vec"] }
sp-core = { version = "20.0.0", default-features = false }
sp-runtime = "23.0.0"
sp-keyring = "23.0.0"
syn = "1.0.109"
subxt = { version = "0.28.0", path = "../../subxt" }
subxt-codegen = { version = "0.28.0", path = "../../codegen" }
subxt-metadata = { version = "0.28.0", path = "../../metadata" }
test-runtime = { path = "../test-runtime" }
tokio = { version = "1.27", features = ["macros", "time"] }
tracing = "0.1.34"
tracing-subscriber = "0.3.17"
wabt = "0.10.0"
which = "4.4.0"
substrate-runner = { path = "../substrate-runner" }
assert_matches = { workspace = true }
codec = { package = "parity-scale-codec", workspace = true, features = ["derive", "bit-vec"] }
frame-metadata = { workspace = true }
futures = { workspace = true }
hex = { workspace = true }
regex = { workspace = true }
scale-info = { workspace = true, features = ["bit-vec"] }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
sp-keyring = { workspace = true }
syn = { workspace = true }
subxt = { workspace = true, features = ["unstable-metadata"] }
subxt-codegen = { workspace = true }
subxt-metadata = { workspace = true }
test-runtime = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
wabt = { workspace = true }
which = { workspace = true }
substrate-runner = { workspace = true }
+72 -2
View File
@@ -2,10 +2,12 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::test_context;
use crate::{pair_signer, test_context, utils::node_runtime};
use codec::Compact;
use frame_metadata::RuntimeMetadataPrefixed;
use futures::StreamExt;
use sp_keyring::AccountKeyring;
use subxt::blocks::BlocksClient;
// Check that we can subscribe to non-finalized blocks.
#[tokio::test]
@@ -113,8 +115,76 @@ async fn runtime_api_call() -> Result<(), subxt::Error> {
};
// Compare the runtime API call against the `state_getMetadata`.
let metadata = api.rpc().metadata(None).await?;
let metadata = api.rpc().metadata_legacy(None).await?;
let metadata = metadata.runtime_metadata();
assert_eq!(&metadata_call, metadata);
Ok(())
}
#[tokio::test]
async fn decode_extrinsics() {
let ctx = test_context().await;
let api = ctx.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = pair_signer(AccountKeyring::Bob.pair());
// Generate a block that has unsigned and signed transactions.
let tx = node_runtime::tx()
.balances()
.transfer(bob.account_id().clone().into(), 10_000);
let signed_extrinsic = api
.tx()
.create_signed(&tx, &alice, Default::default())
.await
.unwrap();
let in_block = signed_extrinsic
.submit_and_watch()
.await
.unwrap()
.wait_for_in_block()
.await
.unwrap();
let block_hash = in_block.block_hash();
let block = BlocksClient::new(api).at(block_hash).await.unwrap();
let extrinsics = block.body().await.unwrap().extrinsics();
assert_eq!(extrinsics.len(), 2);
assert_eq!(extrinsics.block_hash(), block_hash);
assert!(extrinsics
.has::<node_runtime::balances::calls::types::Transfer>()
.unwrap());
assert!(extrinsics
.find_first::<node_runtime::balances::calls::types::Transfer>()
.unwrap()
.is_some());
let block_extrinsics = extrinsics
.iter()
.map(|res| res.unwrap())
.collect::<Vec<_>>();
assert_eq!(block_extrinsics.len(), 2);
let timestamp = block_extrinsics.get(0).unwrap();
timestamp.as_root_extrinsic::<node_runtime::Call>().unwrap();
timestamp
.as_pallet_extrinsic::<node_runtime::runtime_types::pallet_timestamp::pallet::Call>()
.unwrap();
timestamp
.as_extrinsic::<node_runtime::timestamp::calls::types::Set>()
.unwrap();
assert!(!timestamp.is_signed());
let tx = block_extrinsics.get(1).unwrap();
tx.as_root_extrinsic::<node_runtime::Call>().unwrap();
tx.as_pallet_extrinsic::<node_runtime::runtime_types::pallet_balances::pallet::Call>()
.unwrap();
tx.as_extrinsic::<node_runtime::balances::calls::types::Transfer>()
.unwrap();
assert!(tx.is_signed());
}
+38 -1
View File
@@ -350,6 +350,43 @@ async fn submit_large_extrinsic() {
.unwrap();
}
#[tokio::test]
async fn decode_a_module_error() {
use node_runtime::runtime_types::pallet_assets::pallet as assets;
let ctx = test_context().await;
let api = ctx.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let alice_addr = alice.account_id().clone().into();
// Trying to work with an asset ID 1 which doesn't exist should return an
// "unknown" module error from the assets pallet.
let freeze_unknown_asset = node_runtime::tx().assets().freeze(1, alice_addr);
let err = api
.tx()
.sign_and_submit_then_watch_default(&freeze_unknown_asset, &alice)
.await
.unwrap()
.wait_for_finalized_success()
.await
.expect_err("an 'unknown asset' error");
let Error::Runtime(DispatchError::Module(module_err)) = err else {
panic!("Expected a ModuleError, got {err:?}");
};
// Decode the error into our generated Error type.
let decoded_err = module_err.as_root_error::<node_runtime::Error>().unwrap();
// Decoding should result in an Assets.Unknown error:
assert_eq!(
decoded_err,
node_runtime::Error::Assets(assets::Error::Unknown)
);
}
#[tokio::test]
async fn unsigned_extrinsic_is_same_shape_as_polkadotjs() {
let ctx = test_context().await;
@@ -401,7 +438,7 @@ async fn rpc_state_call() {
_ => panic!("Metadata V14 or V15 unavailable"),
};
// Compare the runtime API call against the `state_getMetadata`.
let metadata = api.rpc().metadata(None).await.unwrap();
let metadata = api.rpc().metadata_legacy(None).await.unwrap();
let metadata = metadata.runtime_metadata();
assert_eq!(&metadata_call, metadata);
}
@@ -40,6 +40,14 @@ fn metadata_docs() -> Vec<String> {
// Note: Extrinsics do not have associated documentation, but is implied by
// associated Type.
// Inspect the runtime API types and collect the documentation.
for api in metadata.apis {
docs.extend(api.docs);
for method in api.methods {
docs.extend(method.docs);
}
}
docs
}
@@ -53,7 +61,7 @@ fn generate_runtime_interface(crate_path: CratePath, should_gen_docs: bool) -> S
pub mod api {}
);
let derives = DerivesRegistry::with_default_derives(&crate_path);
let type_substitutes = TypeSubstitutes::new(&crate_path);
let type_substitutes = TypeSubstitutes::with_default_substitutes(&crate_path);
generator
.generate_runtime(
item_mod,
@@ -143,7 +151,7 @@ fn check_root_attrs_preserved() {
// Generate a runtime interface from the provided metadata.
let generator = RuntimeGenerator::new(metadata);
let derives = DerivesRegistry::with_default_derives(&CratePath::default());
let type_substitutes = TypeSubstitutes::new(&CratePath::default());
let type_substitutes = TypeSubstitutes::with_default_substitutes(&CratePath::default());
let generated_code = generator
.generate_runtime(
item_mod,
File diff suppressed because one or more lines are too long
+2
View File
@@ -18,6 +18,8 @@ mod frame;
#[cfg(test)]
mod metadata;
#[cfg(test)]
mod runtime_api;
#[cfg(test)]
mod storage;
#[cfg(test)]
@@ -96,10 +96,34 @@ fn default_pallet() -> PalletMetadata {
}
fn pallets_to_metadata(pallets: Vec<PalletMetadata>) -> RuntimeMetadataV15 {
// Extrinsic needs to contain at least the generic type parameter "Call"
// for the metadata to be valid.
// The "Call" type from the metadata is used to decode extrinsics.
// In reality, the extrinsic type has "Call", "Address", "Extra", "Signature" generic types.
#[allow(unused)]
#[derive(TypeInfo)]
struct ExtrinsicType<Call> {
call: Call,
}
// Because this type is used to decode extrinsics, we expect this to be a TypeDefVariant.
// Each pallet must contain one single variant.
#[allow(unused)]
#[derive(TypeInfo)]
enum RuntimeCall {
PalletName(Pallet),
}
// The calls of the pallet.
#[allow(unused)]
#[derive(TypeInfo)]
enum Pallet {
#[allow(unused)]
SomeCall,
}
RuntimeMetadataV15::new(
pallets,
ExtrinsicMetadata {
ty: meta_type::<()>(),
ty: meta_type::<ExtrinsicType<RuntimeCall>>(),
version: 0,
signed_extensions: vec![],
},

Some files were not shown because too many files have changed in this diff Show More