diff --git a/Cargo.lock b/Cargo.lock index 74538d8ced..07ca333aee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4528,6 +4528,35 @@ dependencies = [ "tokio", ] +[[package]] +name = "subxt-core" +version = "0.34.0" +dependencies = [ + "base58", + "bitvec", + "blake2", + "derivative", + "derive_more", + "frame-metadata 16.0.0", + "hex", + "impl-serde", + "parity-scale-codec", + "primitive-types", + "scale-bits", + "scale-decode", + "scale-encode", + "scale-info", + "scale-value", + "serde", + "serde_json", + "sp-core", + "sp-core-hashing", + "sp-keyring", + "sp-runtime", + "subxt-metadata", + "url", +] + [[package]] name = "subxt-lightclient" version = "0.34.0" diff --git a/Cargo.toml b/Cargo.toml index 1a5a07ade5..3a5ec3bfbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "cli", "codegen", + "core", "lightclient", "testing/substrate-runner", "testing/test-runtime", @@ -18,7 +19,7 @@ members = [ # We exclude any crates that would depend on non mutually # exclusive feature flags and thus can't compile with the # workspace: -exclude = ["testing/wasm-rpc-tests", "testing/wasm-lightclient-tests", "signer/wasm-tests", "examples/wasm-example", "examples/parachain-example"] +exclude = ["testing/wasm-rpc-tests", "testing/wasm-lightclient-tests", "signer/wasm-tests", "examples/wasm-example", "examples/parachain-example", "core/no_std_tests"] resolver = "2" [workspace.package] @@ -63,6 +64,7 @@ color-eyre = "0.6.1" console_error_panic_hook = "0.1.7" darling = "0.20.3" derivative = "2.2.0" +derive_more = "0.99.17" either = "1.9.0" frame-metadata = { version = "16.0.0", default-features = false, features = ["current", "std"] } futures = { version = "0.3.30", default-features = false, features = ["std"] } @@ -126,6 +128,7 @@ sp-keyring = "31.0.0" # Subxt workspace crates: subxt = { version = "0.34.0", path = "subxt", default-features = false } +subxt-core = { version = "0.34.0", path = "core", default-features = false } subxt-macro = { version = "0.34.0", path = "macro" } subxt-metadata = { version = "0.34.0", path = "metadata" } subxt-codegen = { version = "0.34.0", path = "codegen" } diff --git a/RELEASING.md b/RELEASING.md index 37bfc50d2d..55405cbeac 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -79,6 +79,7 @@ We also assume that ongoing work done is being merged directly to the `master` b (cd lightclient && cargo publish) && \ (cd codegen && cargo publish) && \ (cd macro && cargo publish) && \ + (cd core && cargo publish) && \ (cd subxt && cargo publish) && \ (cd signer && cargo publish) && \ (cd cli && cargo publish); diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000000..8dbb4bf5f9 --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "subxt-core" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +publish = true + +license.workspace = true +readme = "README.md" +repository.workspace = true +documentation.workspace = true +homepage.workspace = true +description = "Sign extrinsics to be submitted by Subxt" +keywords = ["parity", "subxt", "extrinsic", "no-std"] + +[features] +default = ["std"] +std = [] +substrate-compat = ["sp-core", "sp-runtime"] + +[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 } +frame-metadata = { workspace = true } +subxt-metadata = { workspace = true } +derivative = { workspace = true } +derive_more = { workspace = true } +hex = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true, features = ["raw_value"] } + +# For ss58 encoding AccountId32 to serialize them properly: +base58 = { workspace = true } +blake2 = { workspace = true } + +# Provides some deserialization, types like U256/H256 and hashing impls like twox/blake256: +impl-serde = { workspace = true } +primitive-types = { workspace = true } +sp-core-hashing = { workspace = true } + +# For parsing urls to disallow insecure schemes +url = { workspace = true } + +# Included if the "substrate-compat" feature is enabled. +sp-core = { workspace = true, optional = true } +sp-runtime = { workspace = true, optional = true } + + +[dev-dependencies] +bitvec = { workspace = true } +codec = { workspace = true, features = ["derive", "bit-vec"] } +sp-core = { workspace = true } +sp-keyring = { workspace = true } +sp-runtime = { workspace = true } + + +[package.metadata.docs.rs] +defalt-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.playground] +defalt-features = true diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000000..745b0fea12 --- /dev/null +++ b/core/README.md @@ -0,0 +1,3 @@ +# Subxt-Core + +This library provides core functionality using in `subxt` and `subxt-signer`. It should be no-std compatible. \ No newline at end of file diff --git a/core/no_std_tests/.gitignore b/core/no_std_tests/.gitignore new file mode 100644 index 0000000000..c41cc9e35e --- /dev/null +++ b/core/no_std_tests/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/core/no_std_tests/Cargo.lock b/core/no_std_tests/Cargo.lock new file mode 100644 index 0000000000..d7ffa7cfd3 --- /dev/null +++ b/core/no_std_tests/Cargo.lock @@ -0,0 +1,938 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "frame-metadata" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "878babb0b136e731cc77ec2fd883ff02745ff21e6fb662729953d44923df009c" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "frame-metadata" +version = "16.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cf1549fba25a6fcac22785b61698317d958e96cac72a59102ea45b9ae64692" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "libc_alloc" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a835c038b748123287f9fb1743d565e7c635879997f43c345a18a026690364e" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parity-scale-codec" +version = "3.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" +dependencies = [ + "proc-macro-crate 2.0.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde", + "scale-info", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" +dependencies = [ + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "scale-bits" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "036575c29af9b6e4866ffb7fa055dbf623fe7a9cc159b33786de6013a6969d89" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "scale-decode" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7caaf753f8ed1ab4752c6afb20174f03598c664724e0e32628e161c21000ff76" +dependencies = [ + "derive_more", + "parity-scale-codec", + "primitive-types", + "scale-bits", + "scale-decode-derive", + "scale-info", + "smallvec", +] + +[[package]] +name = "scale-decode-derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3475108a1b62c7efd1b5c65974f30109a598b2f45f23c9ae030acb9686966db" +dependencies = [ + "darling", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scale-encode" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d70cb4b29360105483fac1ed567ff95d65224a14dd275b6303ed0a654c78de5" +dependencies = [ + "derive_more", + "parity-scale-codec", + "primitive-types", + "scale-bits", + "scale-encode-derive", + "scale-info", + "smallvec", +] + +[[package]] +name = "scale-encode-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "995491f110efdc6bea96d6a746140e32bfceb4ea47510750a5467295a4707a25" +dependencies = [ + "darling", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scale-info" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7d66a1128282b7ef025a8ead62a4a9fcf017382ec53b8ffbf4d7bf77bd3c60" +dependencies = [ + "bitvec", + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scale-value" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58223c7691bf0bd46b43c9aea6f0472d1067f378d574180232358d7c6e0a8089" +dependencies = [ + "base58", + "blake2", + "derive_more", + "either", + "frame-metadata 15.1.0", + "parity-scale-codec", + "scale-bits", + "scale-decode", + "scale-encode", + "scale-info", + "serde", + "yap", +] + +[[package]] +name = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" + +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_json" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "sp-core-hashing" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f4990add7b2cefdeca883c0efa99bb4d912cb2196120e1500c0cc099553b0" +dependencies = [ + "blake2b_simd", + "byteorder", + "digest", + "sha2", + "sha3", + "twox-hash", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "subxt-core" +version = "0.34.0" +dependencies = [ + "base58", + "blake2", + "derivative", + "derive_more", + "frame-metadata 16.0.0", + "impl-serde", + "parity-scale-codec", + "primitive-types", + "scale-bits", + "scale-decode", + "scale-encode", + "scale-info", + "scale-value", + "serde", + "serde_json", + "sp-core-hashing", + "url", +] + +[[package]] +name = "subxt-core-no-std-tests" +version = "0.0.0" +dependencies = [ + "hex", + "libc", + "libc_alloc", + "subxt-core", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "digest", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winnow" +version = "0.5.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4524214bc4629eba08d78ceb1d6507070cc0bcbbed23af74e19e6e924a24cf" diff --git a/core/no_std_tests/Cargo.toml b/core/no_std_tests/Cargo.toml new file mode 100644 index 0000000000..44799d165c --- /dev/null +++ b/core/no_std_tests/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "subxt-core-no-std-tests" +edition = "2021" +publish = false +version = "0.0.0" + +[dependencies] +subxt-core = { path = "../", default-features = false } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } + +libc = { version = "0.2", default-features = false } +libc_alloc = { version = "1.0.6" } + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" + +# this shouldn't be needed, it's in workspace.exclude, but still +# I get the complaint unless I add it... +[workspace] diff --git a/core/no_std_tests/rust-toolchain b/core/no_std_tests/rust-toolchain new file mode 100644 index 0000000000..07ade694b1 --- /dev/null +++ b/core/no_std_tests/rust-toolchain @@ -0,0 +1 @@ +nightly \ No newline at end of file diff --git a/core/no_std_tests/src/main.rs b/core/no_std_tests/src/main.rs new file mode 100644 index 0000000000..c5e37f1d02 --- /dev/null +++ b/core/no_std_tests/src/main.rs @@ -0,0 +1,71 @@ +// Copyright 2019-2020 +// Parity Technologies (UK) Ltd. Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![allow(internal_features)] +#![feature(lang_items, start)] +#![feature(alloc_error_handler)] +#![no_std] + +#[start] +fn start(_argc: isize, _argv: *const *const u8) -> isize { + main(); + 0 +} + +#[lang = "eh_personality"] +#[no_mangle] +pub extern "C" fn rust_eh_personality() {} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + unsafe { + libc::abort(); + } +} + +use libc_alloc::LibcAlloc; + +#[global_allocator] +static ALLOCATOR: LibcAlloc = LibcAlloc; + +////////////////////////////////////////////////////////////////////////////// + +// use subxt_core::utils::H256; + +// Note: Panics in this function will lead to `Aborted (core dumped)` and a non-zero exit status => suitable for CI tests. +fn main() { + let metadata_bytes: &[u8] = include_bytes!("../../../artifacts/polkadot_metadata_small.scale"); + + // 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::backend::RuntimeVersion { + // spec_version: 9370, + // transaction_version: 20, + // }; + + // // 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: &[u8] = include_bytes!("../polkadot_metadata_small.scale"); + // Metadata::decode(&mut &*bytes).unwrap() + // }; + + // // Create an offline client using the details obtained above: + // let api = OfflineClient::::new(genesis_hash, runtime_version, metadata); +} diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs new file mode 100644 index 0000000000..96b397d300 --- /dev/null +++ b/core/src/client/mod.rs @@ -0,0 +1,51 @@ +use derivative::Derivative; + +use crate::{config::Config, metadata::Metadata}; + +/// the base for a client should be: +/// - runtime version +/// - genesis hash +/// - metadata + +#[derive(Derivative)] +#[derivative(Debug(bound = ""))] +pub struct MinimalClient { + pub genesis_hash: C::Hash, + pub runtime_version: RuntimeVersion, + pub metadata: Metadata, + marker: std::marker::PhantomData, +} + +impl MinimalClient { + pub fn metadata(&self) -> Metadata { + self.metadata.clone() + } + + pub fn runtime_version(&self) -> RuntimeVersion { + self.runtime_version + } + + pub fn genesis_hash(&self) -> C::Hash { + self.genesis_hash + } +} + +/// Runtime version information needed to submit transactions. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RuntimeVersion { + /// Version of the runtime specification. A full-node will not attempt to use its native + /// runtime in substitute for the on-chain Wasm runtime unless all of `spec_name`, + /// `spec_version` and `authoring_version` are the same between Wasm and native. + pub spec_version: u32, + + /// All existing dispatches are fully compatible when this number doesn't change. If this + /// number changes, then `spec_version` must change, also. + /// + /// This number must change when an existing dispatchable (module ID, dispatch ID) is changed, + /// either through an alteration in its user-level semantics, a parameter + /// added/removed/changed, a dispatchable being removed, a module being removed, or a + /// dispatchable/module changing its index. + /// + /// It need *not* change when a new module is added or when a dispatchable is added. + pub transaction_version: u32, +} diff --git a/core/src/config/default_extrinsic_params.rs b/core/src/config/default_extrinsic_params.rs new file mode 100644 index 0000000000..dce83853bf --- /dev/null +++ b/core/src/config/default_extrinsic_params.rs @@ -0,0 +1,144 @@ +// 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 super::{signed_extensions, ExtrinsicParams}; +use super::{Config, Header}; + +/// The default [`super::ExtrinsicParams`] implementation understands common signed extensions +/// and how to apply them to a given chain. +pub type DefaultExtrinsicParams = signed_extensions::AnyOf< + T, + ( + signed_extensions::CheckSpecVersion, + signed_extensions::CheckTxVersion, + signed_extensions::CheckNonce, + signed_extensions::CheckGenesis, + signed_extensions::CheckMortality, + signed_extensions::ChargeAssetTxPayment, + signed_extensions::ChargeTransactionPayment, + ), +>; + +/// A builder that outputs the set of [`super::ExtrinsicParams::OtherParams`] required for +/// [`DefaultExtrinsicParams`]. This may expose methods that aren't applicable to the current +/// chain; such values will simply be ignored if so. +pub struct DefaultExtrinsicParamsBuilder { + /// `None` means the tx will be immortal. + mortality: Option>, + /// `None` means we'll use the native token. + tip_of_asset_id: Option, + tip: u128, + tip_of: u128, +} + +struct Mortality { + /// Block hash that mortality starts from + checkpoint_hash: Hash, + /// Block number that mortality starts from (must + // point to the same block as the hash above) + checkpoint_number: u64, + /// How many blocks the tx is mortal for + period: u64, +} + +impl Default for DefaultExtrinsicParamsBuilder { + fn default() -> Self { + Self { + mortality: None, + tip: 0, + tip_of: 0, + tip_of_asset_id: None, + } + } +} + +impl DefaultExtrinsicParamsBuilder { + /// Configure new extrinsic params. We default to providing no tip + /// and using an immortal transaction unless otherwise configured + pub fn new() -> Self { + Default::default() + } + + /// Make the transaction mortal, given a block header that it should be mortal from, + /// and the number of blocks (roughly; it'll be rounded to a power of two) that it will + /// be mortal for. + pub fn mortal(mut self, from_block: &T::Header, for_n_blocks: u64) -> Self { + self.mortality = Some(Mortality { + checkpoint_hash: from_block.hash(), + checkpoint_number: from_block.number().into(), + period: for_n_blocks, + }); + self + } + + /// Make the transaction mortal, given a block number and block hash (which must both point to + /// the same block) that it should be mortal from, and the number of blocks (roughly; it'll be + /// rounded to a power of two) that it will be mortal for. + /// + /// Prefer to use [`DefaultExtrinsicParamsBuilder::mortal()`], which ensures that the block hash + /// and number align. + pub fn mortal_unchecked( + mut self, + from_block_number: u64, + from_block_hash: T::Hash, + for_n_blocks: u64, + ) -> Self { + self.mortality = Some(Mortality { + checkpoint_hash: from_block_hash, + checkpoint_number: from_block_number, + period: for_n_blocks, + }); + self + } + + /// Provide a tip to the block author in the chain's native token. + pub fn tip(mut self, tip: u128) -> Self { + self.tip = tip; + self.tip_of = tip; + self.tip_of_asset_id = None; + self + } + + /// Provide a tip to the block author using the token denominated by the `asset_id` provided. This + /// is not applicable on chains which don't use the `ChargeAssetTxPayment` signed extension; in this + /// case, no tip will be given. + pub fn tip_of(mut self, tip: u128, asset_id: T::AssetId) -> Self { + self.tip = 0; + self.tip_of = tip; + self.tip_of_asset_id = Some(asset_id); + self + } + + /// Build the extrinsic parameters. + pub fn build(self) -> as ExtrinsicParams>::OtherParams { + let check_mortality_params = if let Some(mortality) = self.mortality { + signed_extensions::CheckMortalityParams::mortal( + mortality.period, + mortality.checkpoint_number, + mortality.checkpoint_hash, + ) + } else { + signed_extensions::CheckMortalityParams::immortal() + }; + + let charge_asset_tx_params = if let Some(asset_id) = self.tip_of_asset_id { + signed_extensions::ChargeAssetTxPaymentParams::tip_of(self.tip, asset_id) + } else { + signed_extensions::ChargeAssetTxPaymentParams::tip(self.tip) + }; + + let charge_transaction_params = + signed_extensions::ChargeTransactionPaymentParams::tip(self.tip); + + ( + (), + (), + (), + (), + check_mortality_params, + charge_asset_tx_params, + charge_transaction_params, + ) + } +} diff --git a/core/src/config/extrinsic_params.rs b/core/src/config/extrinsic_params.rs new file mode 100644 index 0000000000..378376b450 --- /dev/null +++ b/core/src/config/extrinsic_params.rs @@ -0,0 +1,86 @@ +// 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 module contains a trait which controls the parameters that must +//! be provided in order to successfully construct an extrinsic. +//! [`crate::config::DefaultExtrinsicParams`] provides a general-purpose +//! implementation of this that will work in many cases. + +use crate::client::MinimalClient; + +use super::Config; +use core::fmt::Debug; +use derive_more::Display; + +/// An error that can be emitted when trying to construct an instance of [`ExtrinsicParams`], +/// encode data from the instance, or match on signed extensions. +#[derive(Display, Debug)] +#[non_exhaustive] +pub enum ExtrinsicParamsError { + /// Cannot find a type id in the metadata. The context provides some additional + /// information about the source of the error (eg the signed extension name). + #[display(fmt = "Cannot find type id '{type_id} in the metadata (context: {context})")] + MissingTypeId { + /// Type ID. + type_id: u32, + /// Some arbitrary context to help narrow the source of the error. + context: &'static str, + }, + /// A signed extension in use on some chain was not provided. + #[display( + fmt = "The chain expects a signed extension with the name {_0}, but we did not provide one" + )] + UnknownSignedExtension(String), + /// Some custom error. + #[display(fmt = "Error constructing extrinsic parameters: {_0}")] + Custom(CustomExtrinsicParamsError), +} + +/// A custom error. +pub type CustomExtrinsicParamsError = Box; + +impl From for ExtrinsicParamsError { + fn from(value: std::convert::Infallible) -> Self { + match value {} + } +} +impl From for ExtrinsicParamsError { + fn from(value: CustomExtrinsicParamsError) -> Self { + ExtrinsicParamsError::Custom(value) + } +} + +/// This trait allows you to configure the "signed extra" and +/// "additional" parameters that are a part of the transaction payload +/// or the signer payload respectively. +pub trait ExtrinsicParams: ExtrinsicParamsEncoder + Sized + 'static { + /// These parameters can be provided to the constructor along with + /// some default parameters that `subxt` understands, in order to + /// help construct your [`ExtrinsicParams`] object. + type OtherParams; + + /// Construct a new instance of our [`ExtrinsicParams`]. + fn new( + nonce: u64, + client: &MinimalClient, + other_params: Self::OtherParams, + ) -> Result; +} + +/// This trait is expected to be implemented for any [`ExtrinsicParams`], and +/// defines how to encode the "additional" and "extra" params. Both functions +/// are optional and will encode nothing by default. +pub trait ExtrinsicParamsEncoder: 'static { + /// This is expected to SCALE encode the "signed extra" parameters + /// to some buffer that has been provided. These are the parameters + /// which are sent along with the transaction, as well as taken into + /// account when signing the transaction. + fn encode_extra_to(&self, _v: &mut Vec) {} + + /// This is expected to SCALE encode the "additional" parameters + /// to some buffer that has been provided. These parameters are _not_ + /// sent along with the transaction, but are taken into account when + /// signing it, meaning the client and node must agree on their values. + fn encode_additional_to(&self, _v: &mut Vec) {} +} diff --git a/core/src/config/mod.rs b/core/src/config/mod.rs new file mode 100644 index 0000000000..ddfa2466c7 --- /dev/null +++ b/core/src/config/mod.rs @@ -0,0 +1,154 @@ +// 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 module provides a [`Config`] type, which is used to define various +//! types that are important in order to speak to a particular chain. +//! [`SubstrateConfig`] provides a default set of these types suitable for the +//! default Substrate node implementation, and [`PolkadotConfig`] for a +//! Polkadot node. + +mod default_extrinsic_params; +mod extrinsic_params; + +pub mod polkadot; +pub mod signed_extensions; +pub mod substrate; + +use crate::macros::cfg_substrate_compat; +use codec::{Decode, Encode}; +use core::fmt::Debug; +use scale_decode::DecodeAsType; +use scale_encode::EncodeAsType; +use serde::{de::DeserializeOwned, Serialize}; + +pub use default_extrinsic_params::{DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder}; +pub use extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError}; +pub use polkadot::{PolkadotConfig, PolkadotExtrinsicParams, PolkadotExtrinsicParamsBuilder}; +pub use signed_extensions::SignedExtension; +pub use substrate::{SubstrateConfig, SubstrateExtrinsicParams, SubstrateExtrinsicParamsBuilder}; + +/// Runtime types. +// Note: the `Send + Sync + 'static` bound isn't strictly required, but currently deriving +// TypeInfo automatically applies a 'static bound to all generic types (including this one), +// And we want the compiler to infer `Send` and `Sync` OK for things which have `T: Config` +// rather than having to `unsafe impl` them ourselves. +pub trait Config: Sized + Send + Sync + 'static { + /// The output of the `Hasher` function. + type Hash: BlockHash; + + /// The account ID type. + type AccountId: Debug + Clone + Encode; + + /// The address type. + type Address: Debug + Encode + From; + + /// The signature type. + type Signature: Debug + Encode; + + /// The hashing system (algorithm) being used in the runtime (e.g. Blake2). + type Hasher: Debug + Hasher; + + /// The block header. + type Header: Debug + Header + Sync + Send + DeserializeOwned; + + /// This type defines the extrinsic extra and additional parameters. + type ExtrinsicParams: ExtrinsicParams; + + /// This is used to identify an asset in the `ChargeAssetTxPayment` signed extension. + type AssetId: Debug + Clone + Encode + DecodeAsType + EncodeAsType; +} + +/// given some [`Config`], this return the other params needed for its `ExtrinsicParams`. +pub type OtherParamsFor = <::ExtrinsicParams as ExtrinsicParams>::OtherParams; + +/// Block hashes must conform to a bunch of things to be used in Subxt. +pub trait BlockHash: + Debug + + Copy + + Send + + Sync + + Decode + + AsRef<[u8]> + + Serialize + + DeserializeOwned + + Encode + + PartialEq + + Eq + + std::hash::Hash +{ +} +impl BlockHash for T where + T: Debug + + Copy + + Send + + Sync + + Decode + + AsRef<[u8]> + + Serialize + + DeserializeOwned + + Encode + + PartialEq + + Eq + + std::hash::Hash +{ +} + +/// This represents the hasher used by a node to hash things like block headers +/// and extrinsics. +pub trait Hasher { + /// The type given back from the hash operation + type Output; + + /// Hash some bytes to the given output type. + fn hash(s: &[u8]) -> Self::Output; + + /// Hash some SCALE encodable type to the given output type. + fn hash_of(s: &S) -> Self::Output { + let out = s.encode(); + Self::hash(&out) + } +} + +/// This represents the block header type used by a node. +pub trait Header: Sized + Encode + Decode { + /// The block number type for this header. + type Number: Into; + /// The hasher used to hash this header. + type Hasher: Hasher; + + /// Return the block number of this header. + fn number(&self) -> Self::Number; + + /// Hash this header. + fn hash(&self) -> ::Output { + Self::Hasher::hash_of(self) + } +} + +cfg_substrate_compat! { + /// implement subxt's Hasher and Header traits for some substrate structs + mod substrate_impls { + use super::*; + + impl Header for T + where + ::Number: Into, + { + type Number = T::Number; + type Hasher = T::Hashing; + + fn number(&self) -> Self::Number { + *self.number() + } + } + + impl Hasher for T { + type Output = T::Output; + + fn hash(s: &[u8]) -> Self::Output { + ::hash(s) + } + } + } +} diff --git a/core/src/config/polkadot.rs b/core/src/config/polkadot.rs new file mode 100644 index 0000000000..04c3d70f27 --- /dev/null +++ b/core/src/config/polkadot.rs @@ -0,0 +1,33 @@ +// 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. + +//! Polkadot specific configuration + +use super::{Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder}; + +use super::SubstrateConfig; +pub use crate::utils::{AccountId32, MultiAddress, MultiSignature}; +pub use primitive_types::{H256, U256}; + +/// Default set of commonly used types by Polkadot nodes. +pub enum PolkadotConfig {} + +impl Config for PolkadotConfig { + type Hash = ::Hash; + type AccountId = ::AccountId; + type Address = MultiAddress; + type Signature = ::Signature; + type Hasher = ::Hasher; + type Header = ::Header; + type ExtrinsicParams = PolkadotExtrinsicParams; + type AssetId = u32; +} + +/// A struct representing the signed extra and additional parameters required +/// to construct a transaction for a polkadot node. +pub type PolkadotExtrinsicParams = DefaultExtrinsicParams; + +/// A builder which leads to [`PolkadotExtrinsicParams`] being constructed. +/// This is what you provide to methods like `sign_and_submit()`. +pub type PolkadotExtrinsicParamsBuilder = DefaultExtrinsicParamsBuilder; diff --git a/core/src/config/signed_extensions.rs b/core/src/config/signed_extensions.rs new file mode 100644 index 0000000000..6ba0284c40 --- /dev/null +++ b/core/src/config/signed_extensions.rs @@ -0,0 +1,497 @@ +// 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 module contains implementations for common signed extensions, each +//! of which implements [`SignedExtension`], and can be used in conjunction with +//! [`AnyOf`] to configure the set of signed extensions which are known about +//! when interacting with a chain. + +use super::extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError}; +use super::Config; +use crate::client::MinimalClient; +use crate::utils::Era; +use codec::{Compact, Encode}; +use core::fmt::Debug; +use derivative::Derivative; +use scale_decode::DecodeAsType; +use scale_info::PortableRegistry; + +use std::collections::HashMap; + +/// A single [`SignedExtension`] has a unique name, but is otherwise the +/// same as [`ExtrinsicParams`] in describing how to encode the extra and +/// additional data. +pub trait SignedExtension: ExtrinsicParams { + /// The type representing the `extra` bytes of a signed extension. + /// Decoding from this type should be symmetrical to the respective + /// `ExtrinsicParamsEncoder::encode_extra_to()` implementation of this signed extension. + type Decoded: DecodeAsType; + + /// This should return true if the signed extension matches the details given. + /// Often, this will involve just checking that the identifier given matches that of the + /// extension in question. + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool; +} + +/// The [`CheckSpecVersion`] signed extension. +pub struct CheckSpecVersion(u32); + +impl ExtrinsicParams for CheckSpecVersion { + type OtherParams = (); + + fn new( + _nonce: u64, + client: &MinimalClient, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckSpecVersion(client.runtime_version().spec_version)) + } +} + +impl ExtrinsicParamsEncoder for CheckSpecVersion { + fn encode_additional_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckSpecVersion { + type Decoded = (); + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckSpecVersion" + } +} + +/// The [`CheckNonce`] signed extension. +pub struct CheckNonce(Compact); + +impl ExtrinsicParams for CheckNonce { + type OtherParams = (); + + fn new( + nonce: u64, + _client: &MinimalClient, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckNonce(Compact(nonce))) + } +} + +impl ExtrinsicParamsEncoder for CheckNonce { + fn encode_extra_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckNonce { + type Decoded = u64; + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckNonce" + } +} + +/// The [`CheckTxVersion`] signed extension. +pub struct CheckTxVersion(u32); + +impl ExtrinsicParams for CheckTxVersion { + type OtherParams = (); + + fn new( + _nonce: u64, + client: &MinimalClient, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckTxVersion(client.runtime_version().transaction_version)) + } +} + +impl ExtrinsicParamsEncoder for CheckTxVersion { + fn encode_additional_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckTxVersion { + type Decoded = (); + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckTxVersion" + } +} + +/// The [`CheckGenesis`] signed extension. +pub struct CheckGenesis(T::Hash); + +impl ExtrinsicParams for CheckGenesis { + type OtherParams = (); + + fn new( + _nonce: u64, + client: &MinimalClient, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckGenesis(client.genesis_hash())) + } +} + +impl ExtrinsicParamsEncoder for CheckGenesis { + fn encode_additional_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckGenesis { + type Decoded = (); + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckGenesis" + } +} + +/// The [`CheckMortality`] signed extension. +pub struct CheckMortality { + era: Era, + checkpoint: T::Hash, +} + +/// Parameters to configure the [`CheckMortality`] signed extension. +pub struct CheckMortalityParams { + era: Era, + checkpoint: Option, +} + +impl Default for CheckMortalityParams { + fn default() -> Self { + Self { + era: Default::default(), + checkpoint: Default::default(), + } + } +} + +impl CheckMortalityParams { + /// Configure a mortal transaction. The `period` is (roughly) how many + /// blocks the transaction will be valid for. The `block_number` and + /// `block_hash` should both point to the same block, and are the block that + /// the transaction is mortal from. + pub fn mortal(period: u64, block_number: u64, block_hash: T::Hash) -> Self { + CheckMortalityParams { + era: Era::mortal(period, block_number), + checkpoint: Some(block_hash), + } + } + /// An immortal transaction. + pub fn immortal() -> Self { + CheckMortalityParams { + era: Era::Immortal, + checkpoint: None, + } + } +} + +impl ExtrinsicParams for CheckMortality { + type OtherParams = CheckMortalityParams; + + fn new( + _nonce: u64, + client: &MinimalClient, + other_params: Self::OtherParams, + ) -> Result { + Ok(CheckMortality { + era: other_params.era, + checkpoint: other_params.checkpoint.unwrap_or(client.genesis_hash()), + }) + } +} + +impl ExtrinsicParamsEncoder for CheckMortality { + fn encode_extra_to(&self, v: &mut Vec) { + self.era.encode_to(v); + } + fn encode_additional_to(&self, v: &mut Vec) { + self.checkpoint.encode_to(v); + } +} + +impl SignedExtension for CheckMortality { + type Decoded = Era; + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckMortality" + } +} + +/// The [`ChargeAssetTxPayment`] signed extension. +#[derive(Derivative, DecodeAsType)] +#[derivative(Clone(bound = "T::AssetId: Clone"), Debug(bound = "T::AssetId: Debug"))] +#[decode_as_type(trait_bounds = "T::AssetId: DecodeAsType")] +pub struct ChargeAssetTxPayment { + tip: Compact, + asset_id: Option, +} + +impl ChargeAssetTxPayment { + /// Tip to the extrinsic author in the native chain token. + pub fn tip(&self) -> u128 { + self.tip.0 + } + + /// Tip to the extrinsic author using the asset ID given. + pub fn asset_id(&self) -> Option<&T::AssetId> { + self.asset_id.as_ref() + } +} + +/// Parameters to configure the [`ChargeAssetTxPayment`] signed extension. +pub struct ChargeAssetTxPaymentParams { + tip: u128, + asset_id: Option, +} + +impl Default for ChargeAssetTxPaymentParams { + fn default() -> Self { + ChargeAssetTxPaymentParams { + tip: Default::default(), + asset_id: Default::default(), + } + } +} + +impl ChargeAssetTxPaymentParams { + /// Don't provide a tip to the extrinsic author. + pub fn no_tip() -> Self { + ChargeAssetTxPaymentParams { + tip: 0, + asset_id: None, + } + } + /// Tip the extrinsic author in the native chain token. + pub fn tip(tip: u128) -> Self { + ChargeAssetTxPaymentParams { + tip, + asset_id: None, + } + } + /// Tip the extrinsic author using the asset ID given. + pub fn tip_of(tip: u128, asset_id: T::AssetId) -> Self { + ChargeAssetTxPaymentParams { + tip, + asset_id: Some(asset_id), + } + } +} + +impl ExtrinsicParams for ChargeAssetTxPayment { + type OtherParams = ChargeAssetTxPaymentParams; + + fn new( + _nonce: u64, + _client: &MinimalClient, + other_params: Self::OtherParams, + ) -> Result { + Ok(ChargeAssetTxPayment { + tip: Compact(other_params.tip), + asset_id: other_params.asset_id, + }) + } +} + +impl ExtrinsicParamsEncoder for ChargeAssetTxPayment { + fn encode_extra_to(&self, v: &mut Vec) { + (self.tip, &self.asset_id).encode_to(v); + } +} + +impl SignedExtension for ChargeAssetTxPayment { + type Decoded = Self; + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "ChargeAssetTxPayment" + } +} + +/// The [`ChargeTransactionPayment`] signed extension. +#[derive(Clone, Debug, DecodeAsType)] +pub struct ChargeTransactionPayment { + tip: Compact, +} + +impl ChargeTransactionPayment { + /// Tip to the extrinsic author in the native chain token. + pub fn tip(&self) -> u128 { + self.tip.0 + } +} + +/// Parameters to configure the [`ChargeTransactionPayment`] signed extension. +#[derive(Default)] +pub struct ChargeTransactionPaymentParams { + tip: u128, +} + +impl ChargeTransactionPaymentParams { + /// Don't provide a tip to the extrinsic author. + pub fn no_tip() -> Self { + ChargeTransactionPaymentParams { tip: 0 } + } + /// Tip the extrinsic author in the native chain token. + pub fn tip(tip: u128) -> Self { + ChargeTransactionPaymentParams { tip } + } +} + +impl ExtrinsicParams for ChargeTransactionPayment { + type OtherParams = ChargeTransactionPaymentParams; + + fn new( + _nonce: u64, + _client: &MinimalClient, + other_params: Self::OtherParams, + ) -> Result { + Ok(ChargeTransactionPayment { + tip: Compact(other_params.tip), + }) + } +} + +impl ExtrinsicParamsEncoder for ChargeTransactionPayment { + fn encode_extra_to(&self, v: &mut Vec) { + self.tip.encode_to(v); + } +} + +impl SignedExtension for ChargeTransactionPayment { + type Decoded = Self; + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "ChargeTransactionPayment" + } +} + +/// This accepts a tuple of [`SignedExtension`]s, and will dynamically make use of whichever +/// ones are actually required for the chain in the correct order, ignoring the rest. This +/// is a sensible default, and allows for a single configuration to work across multiple chains. +pub struct AnyOf { + params: Vec>, + _marker: std::marker::PhantomData<(T, Params)>, +} + +macro_rules! impl_tuples { + ($($ident:ident $index:tt),+) => { + // We do some magic when the tuple is wrapped in AnyOf. We + // look at the metadata, and use this to select and make use of only the extensions + // that we actually need for the chain we're dealing with. + impl ExtrinsicParams for AnyOf + where + T: Config, + $($ident: SignedExtension,)+ + { + type OtherParams = ($($ident::OtherParams,)+); + + fn new( + nonce: u64, + client: &MinimalClient, + other_params: Self::OtherParams, + ) -> Result { + let metadata = client.metadata(); + let types = metadata.types(); + + // For each signed extension in the tuple, find the matching index in the metadata, if + // there is one, and add it to a map with that index as the key. + let mut exts_by_index = HashMap::new(); + $({ + for (idx, e) in metadata.extrinsic().signed_extensions().iter().enumerate() { + // Skip over any exts that have a match already: + if exts_by_index.contains_key(&idx) { + continue + } + // Break and record as soon as we find a match: + if $ident::matches(e.identifier(), e.extra_ty(), types) { + let ext = $ident::new(nonce, client, other_params.$index)?; + let boxed_ext: Box = Box::new(ext); + exts_by_index.insert(idx, boxed_ext); + break + } + } + })+ + + // Next, turn these into an ordered vec, erroring if we haven't matched on any exts yet. + let mut params = Vec::new(); + for (idx, e) in metadata.extrinsic().signed_extensions().iter().enumerate() { + let Some(ext) = exts_by_index.remove(&idx) else { + if is_type_empty(e.extra_ty(), types) { + continue + } else { + return Err(ExtrinsicParamsError::UnknownSignedExtension(e.identifier().to_owned())); + } + }; + params.push(ext); + } + + Ok(AnyOf { + params, + _marker: std::marker::PhantomData + }) + } + } + + impl ExtrinsicParamsEncoder for AnyOf + where + T: Config, + $($ident: SignedExtension,)+ + { + fn encode_extra_to(&self, v: &mut Vec) { + for ext in &self.params { + ext.encode_extra_to(v); + } + } + fn encode_additional_to(&self, v: &mut Vec) { + for ext in &self.params { + ext.encode_additional_to(v); + } + } + } + } +} + +#[rustfmt::skip] +const _: () = { + impl_tuples!(A 0); + impl_tuples!(A 0, B 1); + impl_tuples!(A 0, B 1, C 2); + impl_tuples!(A 0, B 1, C 2, D 3); + impl_tuples!(A 0, B 1, C 2, D 3, E 4); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, U 19); + impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, U 19, V 20); +}; + +/// Checks to see whether the type being given is empty, ie would require +/// 0 bytes to encode. +fn is_type_empty(type_id: u32, types: &scale_info::PortableRegistry) -> bool { + let Some(ty) = types.resolve(type_id) else { + // Can't resolve; type may not be empty. Not expected to hit this. + return false; + }; + + use scale_info::TypeDef; + match &ty.type_def { + TypeDef::Composite(c) => c.fields.iter().all(|f| is_type_empty(f.ty.id, types)), + TypeDef::Array(a) => a.len == 0 || is_type_empty(a.type_param.id, types), + TypeDef::Tuple(t) => t.fields.iter().all(|f| is_type_empty(f.id, types)), + // Explicitly list these in case any additions are made in the future. + TypeDef::BitSequence(_) + | TypeDef::Variant(_) + | TypeDef::Sequence(_) + | TypeDef::Compact(_) + | TypeDef::Primitive(_) => false, + } +} diff --git a/core/src/config/substrate.rs b/core/src/config/substrate.rs new file mode 100644 index 0000000000..a2eaaf4f63 --- /dev/null +++ b/core/src/config/substrate.rs @@ -0,0 +1,340 @@ +// 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. + +//! Substrate specific configuration + +use super::{Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder, Hasher, Header}; +use codec::{Decode, Encode}; +use serde::{Deserialize, Serialize}; + +pub use crate::utils::{AccountId32, MultiAddress, MultiSignature}; +pub use primitive_types::{H256, U256}; + +/// Default set of commonly used types by Substrate runtimes. +// Note: We only use this at the type level, so it should be impossible to +// create an instance of it. +pub enum SubstrateConfig {} + +impl Config for SubstrateConfig { + type Hash = H256; + type AccountId = AccountId32; + type Address = MultiAddress; + type Signature = MultiSignature; + type Hasher = BlakeTwo256; + type Header = SubstrateHeader; + type ExtrinsicParams = SubstrateExtrinsicParams; + type AssetId = u32; +} + +/// A struct representing the signed extra and additional parameters required +/// to construct a transaction for the default substrate node. +pub type SubstrateExtrinsicParams = DefaultExtrinsicParams; + +/// A builder which leads to [`SubstrateExtrinsicParams`] being constructed. +/// This is what you provide to methods like `sign_and_submit()`. +pub type SubstrateExtrinsicParamsBuilder = DefaultExtrinsicParamsBuilder; + +/// A type that can hash values using the blaks2_256 algorithm. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode)] +pub struct BlakeTwo256; + +impl Hasher for BlakeTwo256 { + type Output = H256; + fn hash(s: &[u8]) -> Self::Output { + sp_core_hashing::blake2_256(s).into() + } +} + +/// A generic Substrate header type, adapted from `sp_runtime::generic::Header`. +/// The block number and hasher can be configured to adapt this for other nodes. +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SubstrateHeader + TryFrom, H: Hasher> { + /// The parent hash. + pub parent_hash: H::Output, + /// The block number. + #[serde( + serialize_with = "serialize_number", + deserialize_with = "deserialize_number" + )] + #[codec(compact)] + pub number: N, + /// The state trie merkle root + pub state_root: H::Output, + /// The merkle root of the extrinsics. + pub extrinsics_root: H::Output, + /// A chain-specific digest of data useful for light clients or referencing auxiliary data. + pub digest: Digest, +} + +impl Header for SubstrateHeader +where + N: Copy + Into + Into + TryFrom + Encode, + H: Hasher + Encode, + SubstrateHeader: Encode + Decode, +{ + type Number = N; + type Hasher = H; + fn number(&self) -> Self::Number { + self.number + } +} + +/// Generic header digest. From `sp_runtime::generic::digest`. +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)] +pub struct Digest { + /// A list of digest items. + pub logs: Vec, +} + +/// Digest item that is able to encode/decode 'system' digest items and +/// provide opaque access to other items. From `sp_runtime::generic::digest`. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum DigestItem { + /// A pre-runtime digest. + /// + /// These are messages from the consensus engine to the runtime, although + /// the consensus engine can (and should) read them itself to avoid + /// code and state duplication. It is erroneous for a runtime to produce + /// these, but this is not (yet) checked. + /// + /// NOTE: the runtime is not allowed to panic or fail in an `on_initialize` + /// call if an expected `PreRuntime` digest is not present. It is the + /// responsibility of a external block verifier to check this. Runtime API calls + /// will initialize the block without pre-runtime digests, so initialization + /// cannot fail when they are missing. + PreRuntime(ConsensusEngineId, Vec), + + /// A message from the runtime to the consensus engine. This should *never* + /// be generated by the native code of any consensus engine, but this is not + /// checked (yet). + Consensus(ConsensusEngineId, Vec), + + /// Put a Seal on it. This is only used by native code, and is never seen + /// by runtimes. + Seal(ConsensusEngineId, Vec), + + /// Some other thing. Unsupported and experimental. + Other(Vec), + + /// An indication for the light clients that the runtime execution + /// environment is updated. + /// + /// Currently this is triggered when: + /// 1. Runtime code blob is changed or + /// 2. `heap_pages` value is changed. + RuntimeEnvironmentUpdated, +} + +// From sp_runtime::generic, DigestItem enum indexes are encoded using this: +#[repr(u32)] +#[derive(Encode, Decode)] +enum DigestItemType { + Other = 0u32, + Consensus = 4u32, + Seal = 5u32, + PreRuntime = 6u32, + RuntimeEnvironmentUpdated = 8u32, +} +impl Encode for DigestItem { + fn encode(&self) -> Vec { + let mut v = Vec::new(); + + match self { + Self::Consensus(val, data) => { + DigestItemType::Consensus.encode_to(&mut v); + (val, data).encode_to(&mut v); + } + Self::Seal(val, sig) => { + DigestItemType::Seal.encode_to(&mut v); + (val, sig).encode_to(&mut v); + } + Self::PreRuntime(val, data) => { + DigestItemType::PreRuntime.encode_to(&mut v); + (val, data).encode_to(&mut v); + } + Self::Other(val) => { + DigestItemType::Other.encode_to(&mut v); + val.encode_to(&mut v); + } + Self::RuntimeEnvironmentUpdated => { + DigestItemType::RuntimeEnvironmentUpdated.encode_to(&mut v); + } + } + + v + } +} +impl Decode for DigestItem { + fn decode(input: &mut I) -> Result { + let item_type: DigestItemType = Decode::decode(input)?; + match item_type { + DigestItemType::PreRuntime => { + let vals: (ConsensusEngineId, Vec) = Decode::decode(input)?; + Ok(Self::PreRuntime(vals.0, vals.1)) + } + DigestItemType::Consensus => { + let vals: (ConsensusEngineId, Vec) = Decode::decode(input)?; + Ok(Self::Consensus(vals.0, vals.1)) + } + DigestItemType::Seal => { + let vals: (ConsensusEngineId, Vec) = Decode::decode(input)?; + Ok(Self::Seal(vals.0, vals.1)) + } + DigestItemType::Other => Ok(Self::Other(Decode::decode(input)?)), + DigestItemType::RuntimeEnvironmentUpdated => Ok(Self::RuntimeEnvironmentUpdated), + } + } +} + +/// Consensus engine unique ID. From `sp_runtime::ConsensusEngineId`. +pub type ConsensusEngineId = [u8; 4]; + +impl serde::Serialize for DigestItem { + fn serialize(&self, seq: S) -> Result + where + S: serde::Serializer, + { + self.using_encoded(|bytes| impl_serde::serialize::serialize(bytes, seq)) + } +} + +impl<'a> serde::Deserialize<'a> for DigestItem { + fn deserialize(de: D) -> Result + where + D: serde::Deserializer<'a>, + { + let r = impl_serde::serialize::deserialize(de)?; + Decode::decode(&mut &r[..]) + .map_err(|e| serde::de::Error::custom(format!("Decode error: {e}"))) + } +} + +fn serialize_number>(val: &T, s: S) -> Result +where + S: serde::Serializer, +{ + let u256: U256 = (*val).into(); + serde::Serialize::serialize(&u256, s) +} + +fn deserialize_number<'a, D, T: TryFrom>(d: D) -> Result +where + D: serde::Deserializer<'a>, +{ + // At the time of writing, Smoldot gives back block numbers in numeric rather + // than hex format. So let's support deserializing from both here: + let number_or_hex = NumberOrHex::deserialize(d)?; + let u256 = number_or_hex.into_u256(); + TryFrom::try_from(u256).map_err(|_| serde::de::Error::custom("Try from failed")) +} + +/// A number type that can be serialized both as a number or a string that encodes a number in a +/// string. +/// +/// We allow two representations of the block number as input. Either we deserialize to the type +/// that is specified in the block type or we attempt to parse given hex value. +/// +/// The primary motivation for having this type is to avoid overflows when using big integers in +/// JavaScript (which we consider as an important RPC API consumer). +#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] +#[serde(untagged)] +pub enum NumberOrHex { + /// The number represented directly. + Number(u64), + /// Hex representation of the number. + Hex(U256), +} + +impl NumberOrHex { + /// Converts this number into an U256. + pub fn into_u256(self) -> U256 { + match self { + NumberOrHex::Number(n) => n.into(), + NumberOrHex::Hex(h) => h, + } + } +} + +impl From for U256 { + fn from(num_or_hex: NumberOrHex) -> U256 { + num_or_hex.into_u256() + } +} + +macro_rules! into_number_or_hex { + ($($t: ty)+) => { + $( + impl From<$t> for NumberOrHex { + fn from(x: $t) -> Self { + NumberOrHex::Number(x.into()) + } + } + )+ + } +} +into_number_or_hex!(u8 u16 u32 u64); + +impl From for NumberOrHex { + fn from(n: u128) -> Self { + NumberOrHex::Hex(n.into()) + } +} + +impl From for NumberOrHex { + fn from(n: U256) -> Self { + NumberOrHex::Hex(n) + } +} + +/// A quick helper to encode some bytes to hex. +fn to_hex(bytes: impl AsRef<[u8]>) -> String { + format!("0x{}", hex::encode(bytes.as_ref())) +} + +#[cfg(test)] +mod test { + use super::*; + + // Smoldot returns numeric block numbers in the header at the time of writing; + // ensure we can deserialize them properly. + #[test] + fn can_deserialize_numeric_block_number() { + let numeric_block_number_json = r#" + { + "digest": { + "logs": [] + }, + "extrinsicsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "number": 4, + "parentHash": "0xcb2690b2c85ceab55be03fc7f7f5f3857e7efeb7a020600ebd4331e10be2f7a5", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + "#; + + let header: SubstrateHeader = + serde_json::from_str(numeric_block_number_json).expect("valid block header"); + assert_eq!(header.number(), 4); + } + + // Substrate returns hex block numbers; ensure we can also deserialize those OK. + #[test] + fn can_deserialize_hex_block_number() { + let numeric_block_number_json = r#" + { + "digest": { + "logs": [] + }, + "extrinsicsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "number": "0x04", + "parentHash": "0xcb2690b2c85ceab55be03fc7f7f5f3857e7efeb7a020600ebd4331e10be2f7a5", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + "#; + + let header: SubstrateHeader = + serde_json::from_str(numeric_block_number_json).expect("valid block header"); + assert_eq!(header.number(), 4); + } +} diff --git a/core/src/dynamic.rs b/core/src/dynamic.rs new file mode 100644 index 0000000000..e0260b1920 --- /dev/null +++ b/core/src/dynamic.rs @@ -0,0 +1,83 @@ +// // 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 module provides the entry points to create dynamic +// //! transactions, storage and constant lookups. + +// use crate::metadata::{DecodeWithMetadata, Metadata}; +// use scale_decode::DecodeAsType; + +// 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 +// /// [`crate::metadata::DecodeWithMetadata`], and is used as a return type +// /// for dynamic requests. +// pub type DecodedValue = scale_value::Value; + +// // Submit dynamic transactions. +// pub use crate::tx::dynamic as tx; + +// // Lookup constants dynamically. +// pub use crate::constants::dynamic as constant; + +// // Lookup storage values dynamically. +// pub use crate::storage::dynamic as storage; + +// // 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. +// pub struct DecodedValueThunk { +// type_id: u32, +// metadata: Metadata, +// scale_bytes: Vec, +// } + +// impl DecodeWithMetadata for DecodedValueThunk { +// fn decode_with_metadata( +// bytes: &mut &[u8], +// type_id: u32, +// metadata: &Metadata, +// ) -> Result { +// let mut v = Vec::with_capacity(bytes.len()); +// v.extend_from_slice(bytes); +// *bytes = &[]; +// Ok(DecodedValueThunk { +// type_id, +// metadata: metadata.clone(), +// scale_bytes: v, +// }) +// } +// } + +// impl DecodedValueThunk { +// /// Return the SCALE encoded bytes handed back from the node. +// pub fn into_encoded(self) -> Vec { +// self.scale_bytes +// } +// /// Return the SCALE encoded bytes handed back from the node without taking ownership of them. +// pub fn encoded(&self) -> &[u8] { +// &self.scale_bytes +// } +// /// Decode the SCALE encoded storage entry into a dynamic [`DecodedValue`] type. +// pub fn to_value(&self) -> Result { +// let val = DecodedValue::decode_as_type( +// &mut &*self.scale_bytes, +// self.type_id, +// self.metadata.types(), +// )?; +// Ok(val) +// } +// /// decode the `DecodedValueThunk` into a concrete type. +// pub fn as_type(&self) -> Result { +// T::decode_as_type( +// &mut &self.scale_bytes[..], +// self.type_id, +// self.metadata.types(), +// ) +// } +// } diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 0000000000..d1106c42bc --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,22 @@ +// 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. + +//! # Subxt-core +//! +//! `#[no_std]` compatible core crate for subxt. + +pub mod client; +pub mod config; +pub mod dynamic; +pub mod metadata; +pub mod tx; +pub mod utils; + +pub use config::{ + BlockHash, Config, ExtrinsicParams, ExtrinsicParamsEncoder, PolkadotConfig, + PolkadotExtrinsicParams, SubstrateConfig, SubstrateExtrinsicParams, +}; + +#[macro_use] +mod macros; diff --git a/core/src/macros.rs b/core/src/macros.rs new file mode 100644 index 0000000000..1a61ee9354 --- /dev/null +++ b/core/src/macros.rs @@ -0,0 +1,17 @@ +macro_rules! cfg_feature { + ($feature:literal, $($item:item)*) => { + $( + #[cfg(feature = $feature)] + #[cfg_attr(docsrs, doc(cfg(feature = $feature)))] + $item + )* + } +} + +macro_rules! cfg_substrate_compat { + ($($item:item)*) => { + crate::macros::cfg_feature!("substrate-compat", $($item)*); + }; +} + +pub(crate) use {cfg_feature, cfg_substrate_compat}; diff --git a/core/src/metadata/decode_encode_traits.rs b/core/src/metadata/decode_encode_traits.rs new file mode 100644 index 0000000000..a1f5d019fb --- /dev/null +++ b/core/src/metadata/decode_encode_traits.rs @@ -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. + +use super::Metadata; + +/// This trait is implemented for all types that also implement [`scale_decode::DecodeAsType`]. +pub trait DecodeWithMetadata: Sized { + /// Given some metadata and a type ID, attempt to SCALE decode the provided bytes into `Self`. + fn decode_with_metadata( + bytes: &mut &[u8], + type_id: u32, + metadata: &Metadata, + ) -> Result; +} + +impl DecodeWithMetadata for T { + fn decode_with_metadata( + bytes: &mut &[u8], + type_id: u32, + metadata: &Metadata, + ) -> Result { + let val = T::decode_as_type(bytes, type_id, metadata.types())?; + Ok(val) + } +} + +/// This trait is implemented for all types that also implement [`scale_encode::EncodeAsType`]. +pub trait EncodeWithMetadata { + /// SCALE encode this type to bytes, possibly with the help of metadata. + fn encode_with_metadata( + &self, + type_id: u32, + metadata: &Metadata, + bytes: &mut Vec, + ) -> Result<(), scale_encode::Error>; +} + +impl EncodeWithMetadata for T { + /// SCALE encode this type to bytes, possibly with the help of metadata. + fn encode_with_metadata( + &self, + type_id: u32, + metadata: &Metadata, + bytes: &mut Vec, + ) -> Result<(), scale_encode::Error> { + self.encode_as_type_to(type_id, metadata.types(), bytes)?; + Ok(()) + } +} diff --git a/core/src/metadata/metadata_type.rs b/core/src/metadata/metadata_type.rs new file mode 100644 index 0000000000..99f930e54a --- /dev/null +++ b/core/src/metadata/metadata_type.rs @@ -0,0 +1,124 @@ +// 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 derive_more::Display; +use std::sync::Arc; + +/// A cheaply clone-able representation of the runtime metadata received from a node. +#[derive(Clone, Debug)] +pub struct Metadata { + inner: Arc, +} + +impl std::ops::Deref for Metadata { + type Target = subxt_metadata::Metadata; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl Metadata { + pub(crate) fn new(md: subxt_metadata::Metadata) -> Self { + Metadata { + inner: Arc::new(md), + } + } + + /// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found. + pub fn pallet_by_name_err( + &self, + name: &str, + ) -> Result { + self.pallet_by_name(name) + .ok_or_else(|| MetadataError::PalletNameNotFound(name.to_owned())) + } + + /// Identical to `metadata.pallet_by_index()`, but returns an error if the pallet is not found. + pub fn pallet_by_index_err( + &self, + index: u8, + ) -> Result { + self.pallet_by_index(index) + .ok_or(MetadataError::PalletIndexNotFound(index)) + } + + /// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found. + pub fn runtime_api_trait_by_name_err( + &self, + name: &str, + ) -> Result { + self.runtime_api_trait_by_name(name) + .ok_or_else(|| MetadataError::RuntimeTraitNotFound(name.to_owned())) + } +} + +impl From for Metadata { + fn from(md: subxt_metadata::Metadata) -> Self { + Metadata::new(md) + } +} + +impl TryFrom for Metadata { + type Error = subxt_metadata::TryFromError; + fn try_from(value: frame_metadata::RuntimeMetadataPrefixed) -> Result { + subxt_metadata::Metadata::try_from(value).map(Metadata::from) + } +} + +impl codec::Decode for Metadata { + fn decode(input: &mut I) -> Result { + subxt_metadata::Metadata::decode(input).map(Metadata::new) + } +} + +/// Something went wrong trying to access details in the metadata. +#[derive(Clone, Debug, PartialEq, Display)] +#[non_exhaustive] +pub enum MetadataError { + /// The DispatchError type isn't available in the metadata + #[display(fmt = "The DispatchError type isn't available")] + DispatchErrorNotFound, + /// Type not found in metadata. + #[display(fmt = "Type with ID {_0} not found")] + TypeNotFound(u32), + /// Pallet not found (index). + #[display(fmt = "Pallet with index {_0} not found")] + PalletIndexNotFound(u8), + /// Pallet not found (name). + #[display(fmt = "Pallet with name {_0} not found")] + PalletNameNotFound(String), + /// Variant not found. + #[display(fmt = "Variant with index {_0} not found")] + VariantIndexNotFound(u8), + /// Constant not found. + #[display(fmt = "Constant with name {_0} not found")] + ConstantNameNotFound(String), + /// Call not found. + #[display(fmt = "Call with name {_0} not found")] + CallNameNotFound(String), + /// Runtime trait not found. + #[display(fmt = "Runtime trait with name {_0} not found")] + RuntimeTraitNotFound(String), + /// Runtime method not found. + #[display(fmt = "Runtime method with name {_0} not found")] + RuntimeMethodNotFound(String), + /// Call type not found in metadata. + #[display(fmt = "Call type not found in pallet with index {_0}")] + CallTypeNotFoundInPallet(u8), + /// Event type not found in metadata. + #[display(fmt = "Event type not found in pallet with index {_0}")] + EventTypeNotFoundInPallet(u8), + /// Storage details not found in metadata. + #[display(fmt = "Storage details not found in pallet with name {_0}")] + StorageNotFoundInPallet(String), + /// Storage entry not found. + #[display(fmt = "Storage entry {_0} not found")] + StorageEntryNotFound(String), + /// The generated interface used is not compatible with the node. + #[display(fmt = "The generated code is not compatible with the node")] + IncompatibleCodegen, + /// Custom value not found. + #[display(fmt = "Custom value with name {_0} not found")] + CustomValueNameNotFound(String), +} diff --git a/core/src/metadata/mod.rs b/core/src/metadata/mod.rs new file mode 100644 index 0000000000..a6ef00ae6f --- /dev/null +++ b/core/src/metadata/mod.rs @@ -0,0 +1,14 @@ +// 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. + +//! Types representing the metadata obtained from a node. + +mod decode_encode_traits; +mod metadata_type; + +pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata}; +pub use metadata_type::Metadata; + +// Expose metadata types under a sub module in case somebody needs to reference them: +pub use subxt_metadata as types; diff --git a/core/src/tx/mod.rs b/core/src/tx/mod.rs new file mode 100644 index 0000000000..293cbd33c1 --- /dev/null +++ b/core/src/tx/mod.rs @@ -0,0 +1,2 @@ +pub mod signer; +pub mod tx_payload; diff --git a/core/src/tx/signer.rs b/core/src/tx/signer.rs new file mode 100644 index 0000000000..444aa46477 --- /dev/null +++ b/core/src/tx/signer.rs @@ -0,0 +1,100 @@ +// 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. + +//! A library to **sub**mit e**xt**rinsics to a +//! [substrate](https://github.com/paritytech/substrate) node via RPC. + +use crate::macros::cfg_substrate_compat; +use crate::Config; + +/// Signing transactions requires a [`Signer`]. This is responsible for +/// providing the "from" account that the transaction is being signed by, +/// as well as actually signing a SCALE encoded payload. +pub trait Signer { + /// Return the "from" account ID. + fn account_id(&self) -> T::AccountId; + + /// Return the "from" address. + fn address(&self) -> T::Address; + + /// Takes a signer payload for an extrinsic, and returns a signature based on it. + /// + /// Some signers may fail, for instance because the hardware on which the keys are located has + /// refused the operation. + fn sign(&self, signer_payload: &[u8]) -> T::Signature; +} + +cfg_substrate_compat! { + pub use pair_signer::PairSigner; +} + +// A signer suitable for substrate based chains. This provides compatibility with Substrate +// packages like sp_keyring and such, and so relies on sp_core and sp_runtime to be included. +#[cfg(feature = "substrate-compat")] +mod pair_signer { + use super::Signer; + use crate::Config; + use sp_core::Pair as PairT; + use sp_runtime::{ + traits::{IdentifyAccount, Verify}, + AccountId32 as SpAccountId32, MultiSignature as SpMultiSignature, + }; + + /// A [`Signer`] implementation that can be constructed from an [`sp_core::Pair`]. + #[derive(Clone, Debug)] + pub struct PairSigner { + account_id: T::AccountId, + signer: Pair, + } + + impl PairSigner + where + T: Config, + Pair: PairT, + // We go via an `sp_runtime::MultiSignature`. We can probably generalise this + // by implementing some of these traits on our built-in MultiSignature and then + // requiring them on all T::Signatures, to avoid any go-between. + ::Signer: From, + T::AccountId: From, + { + /// Creates a new [`Signer`] from an [`sp_core::Pair`]. + pub fn new(signer: Pair) -> Self { + let account_id = + ::Signer::from(signer.public()).into_account(); + Self { + account_id: account_id.into(), + signer, + } + } + + /// Returns the [`sp_core::Pair`] implementation used to construct this. + pub fn signer(&self) -> &Pair { + &self.signer + } + + /// Return the account ID. + pub fn account_id(&self) -> &T::AccountId { + &self.account_id + } + } + + impl Signer for PairSigner + where + T: Config, + Pair: PairT, + Pair::Signature: Into, + { + fn account_id(&self) -> T::AccountId { + self.account_id.clone() + } + + fn address(&self) -> T::Address { + self.account_id.clone().into() + } + + fn sign(&self, signer_payload: &[u8]) -> T::Signature { + self.signer.sign(signer_payload).into() + } + } +} diff --git a/core/src/tx/tx_payload.rs b/core/src/tx/tx_payload.rs new file mode 100644 index 0000000000..902420aca0 --- /dev/null +++ b/core/src/tx/tx_payload.rs @@ -0,0 +1,192 @@ +// // 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 module contains the trait and types used to represent +// //! transactions that can be submitted. + +// use crate::{ +// dynamic::Value, +// error::{Error, MetadataError}, +// metadata::Metadata, +// }; +// use codec::Encode; +// use scale_encode::EncodeAsFields; +// use scale_value::{Composite, ValueDef, Variant}; +// use std::{borrow::Cow, sync::Arc}; + +// /// This represents a transaction payload that can be submitted +// /// to a node. +// pub trait TxPayload { +// /// Encode call data to the provided output. +// fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec) -> Result<(), Error>; + +// /// Encode call data and return the output. This is a convenience +// /// wrapper around [`TxPayload::encode_call_data_to`]. +// fn encode_call_data(&self, metadata: &Metadata) -> Result, Error> { +// let mut v = Vec::new(); +// self.encode_call_data_to(metadata, &mut v)?; +// Ok(v) +// } + +// /// Returns the details needed to validate the call, which +// /// include a statically generated hash, the pallet name, +// /// and the call name. +// fn validation_details(&self) -> Option> { +// None +// } +// } + +// pub struct ValidationDetails<'a> { +// /// The pallet name. +// pub pallet_name: &'a str, +// /// The call name. +// pub call_name: &'a str, +// /// A hash (this is generated at compile time in our codegen) +// /// to compare against the runtime code. +// pub hash: [u8; 32], +// } + +// /// A transaction payload containing some generic `CallData`. +// #[derive(Clone, Debug)] +// pub struct Payload { +// pallet_name: Cow<'static, str>, +// call_name: Cow<'static, str>, +// call_data: CallData, +// validation_hash: Option<[u8; 32]>, +// } + +// /// A boxed transaction payload. +// // Dev Note: Arc used to enable easy cloning (given that we can't have dyn Clone). +// pub type BoxedPayload = Payload>; + +// /// The type of a payload typically used for dynamic transaction payloads. +// pub type DynamicPayload = Payload>; + +// impl Payload { +// /// Create a new [`Payload`]. +// pub fn new( +// pallet_name: impl Into, +// call_name: impl Into, +// call_data: CallData, +// ) -> Self { +// Payload { +// pallet_name: Cow::Owned(pallet_name.into()), +// call_name: Cow::Owned(call_name.into()), +// call_data, +// validation_hash: None, +// } +// } + +// /// Create a new [`Payload`] using static strings for the pallet and call name. +// /// This is only expected to be used from codegen. +// #[doc(hidden)] +// pub fn new_static( +// pallet_name: &'static str, +// call_name: &'static str, +// call_data: CallData, +// validation_hash: [u8; 32], +// ) -> Self { +// Payload { +// pallet_name: Cow::Borrowed(pallet_name), +// call_name: Cow::Borrowed(call_name), +// call_data, +// validation_hash: Some(validation_hash), +// } +// } + +// /// Box the payload. +// pub fn boxed(self) -> BoxedPayload +// where +// CallData: EncodeAsFields + Send + Sync + 'static, +// { +// BoxedPayload { +// pallet_name: self.pallet_name, +// call_name: self.call_name, +// call_data: Arc::new(self.call_data), +// validation_hash: self.validation_hash, +// } +// } + +// /// Do not validate this call prior to submitting it. +// pub fn unvalidated(self) -> Self { +// Self { +// validation_hash: None, +// ..self +// } +// } + +// /// Returns the call data. +// pub fn call_data(&self) -> &CallData { +// &self.call_data +// } + +// /// Returns the pallet name. +// pub fn pallet_name(&self) -> &str { +// &self.pallet_name +// } + +// /// Returns the call name. +// pub fn call_name(&self) -> &str { +// &self.call_name +// } +// } + +// impl Payload> { +// /// Convert the dynamic `Composite` payload into a [`Value`]. +// /// This is useful if you want to use this as an argument for a +// /// larger dynamic call that wants to use this as a nested call. +// pub fn into_value(self) -> Value<()> { +// let call = Value { +// context: (), +// value: ValueDef::Variant(Variant { +// name: self.call_name.into_owned(), +// values: self.call_data, +// }), +// }; + +// Value::unnamed_variant(self.pallet_name, [call]) +// } +// } + +// impl TxPayload for Payload { +// fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec) -> Result<(), Error> { +// let pallet = metadata.pallet_by_name_err(&self.pallet_name)?; +// let call = pallet +// .call_variant_by_name(&self.call_name) +// .ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?; + +// let pallet_index = pallet.index(); +// let call_index = call.index; + +// pallet_index.encode_to(out); +// call_index.encode_to(out); + +// let mut fields = call +// .fields +// .iter() +// .map(|f| scale_encode::Field::new(f.ty.id, f.name.as_deref())); + +// self.call_data +// .encode_as_fields_to(&mut fields, metadata.types(), out)?; +// Ok(()) +// } + +// fn validation_details(&self) -> Option> { +// self.validation_hash.map(|hash| ValidationDetails { +// pallet_name: &self.pallet_name, +// call_name: &self.call_name, +// hash, +// }) +// } +// } + +// /// Construct a transaction at runtime; essentially an alias to [`Payload::new()`] +// /// which provides a [`Composite`] value for the call data. +// pub fn dynamic( +// pallet_name: impl Into, +// call_name: impl Into, +// call_data: impl Into>, +// ) -> DynamicPayload { +// Payload::new(pallet_name, call_name, call_data.into()) +// } diff --git a/core/src/utils/account_id.rs b/core/src/utils/account_id.rs new file mode 100644 index 0000000000..b9ee3dda21 --- /dev/null +++ b/core/src/utils/account_id.rs @@ -0,0 +1,222 @@ +// 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. + +//! The "default" Substrate/Polkadot AccountId. This is used in codegen, as well as signing related bits. +//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_core::AccountId32` +//! for instance, to gain functionality without forcing a dependency on Substrate crates here. + +use codec::{Decode, Encode}; +use derive_more::Display; +use serde::{Deserialize, Serialize}; + +/// A 32-byte cryptographic identifier. This is a simplified version of Substrate's +/// `sp_core::crypto::AccountId32`. To obtain more functionality, convert this into +/// that type. +#[derive( + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + Debug, + scale_encode::EncodeAsType, + scale_decode::DecodeAsType, + scale_info::TypeInfo, +)] +pub struct AccountId32(pub [u8; 32]); + +impl AsRef<[u8]> for AccountId32 { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +impl AsRef<[u8; 32]> for AccountId32 { + fn as_ref(&self) -> &[u8; 32] { + &self.0 + } +} + +impl From<[u8; 32]> for AccountId32 { + fn from(x: [u8; 32]) -> Self { + AccountId32(x) + } +} + +impl AccountId32 { + // Return the ss58-check string for this key. Adapted from `sp_core::crypto`. We need this to + // serialize our account appropriately but otherwise don't care. + fn to_ss58check(&self) -> String { + // For serializing to a string to obtain the account nonce, we use the default substrate + // prefix (since we have no way to otherwise pick one). It doesn't really matter, since when + // it's deserialized back in system_accountNextIndex, we ignore this (so long as it's valid). + const SUBSTRATE_SS58_PREFIX: u8 = 42; + // prefix <= 63 just take up one byte at the start: + let mut v = vec![SUBSTRATE_SS58_PREFIX]; + // then push the account ID bytes. + v.extend(self.0); + // then push a 2 byte checksum of what we have so far. + let r = ss58hash(&v); + v.extend(&r[0..2]); + // then encode to base58. + use base58::ToBase58; + v.to_base58() + } + + // This isn't strictly needed, but to give our AccountId32 a little more usefulness, we also + // implement the logic needed to decode an AccountId32 from an SS58 encoded string. This is exposed + // via a `FromStr` impl. + fn from_ss58check(s: &str) -> Result { + const CHECKSUM_LEN: usize = 2; + let body_len = 32; + + use base58::FromBase58; + let data = s.from_base58().map_err(|_| FromSs58Error::BadBase58)?; + if data.len() < 2 { + return Err(FromSs58Error::BadLength); + } + let prefix_len = match data[0] { + 0..=63 => 1, + 64..=127 => 2, + _ => return Err(FromSs58Error::InvalidPrefix), + }; + if data.len() != prefix_len + body_len + CHECKSUM_LEN { + return Err(FromSs58Error::BadLength); + } + let hash = ss58hash(&data[0..body_len + prefix_len]); + let checksum = &hash[0..CHECKSUM_LEN]; + if data[body_len + prefix_len..body_len + prefix_len + CHECKSUM_LEN] != *checksum { + // Invalid checksum. + return Err(FromSs58Error::InvalidChecksum); + } + + let result = data[prefix_len..body_len + prefix_len] + .try_into() + .map_err(|_| FromSs58Error::BadLength)?; + Ok(AccountId32(result)) + } +} + +/// An error obtained from trying to interpret an SS58 encoded string into an AccountId32 +#[derive(Display, Clone, Copy, Eq, PartialEq, Debug)] +#[allow(missing_docs)] +pub enum FromSs58Error { + #[display(fmt = "Base 58 requirement is violated")] + BadBase58, + #[display(fmt = "Length is bad")] + BadLength, + #[display(fmt = "Invalid checksum")] + InvalidChecksum, + #[display(fmt = "Invalid SS58 prefix byte.")] + InvalidPrefix, +} + +#[cfg(feature = "std")] +impl std::error::Error for FromSs58Error {} + +// We do this just to get a checksum to help verify the validity of the address in to_ss58check +fn ss58hash(data: &[u8]) -> Vec { + use blake2::{Blake2b512, Digest}; + const PREFIX: &[u8] = b"SS58PRE"; + let mut ctx = Blake2b512::new(); + ctx.update(PREFIX); + ctx.update(data); + ctx.finalize().to_vec() +} + +impl Serialize for AccountId32 { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_ss58check()) + } +} + +impl<'de> Deserialize<'de> for AccountId32 { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + AccountId32::from_ss58check(&String::deserialize(deserializer)?) + .map_err(|e| serde::de::Error::custom(format!("{e:?}"))) + } +} + +impl std::fmt::Display for AccountId32 { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.to_ss58check()) + } +} + +impl std::str::FromStr for AccountId32 { + type Err = FromSs58Error; + fn from_str(s: &str) -> Result { + AccountId32::from_ss58check(s) + } +} + +// Improve compat with the substrate version if we're using those crates: +#[cfg(feature = "substrate-compat")] +mod substrate_impls { + use super::*; + + impl From for AccountId32 { + fn from(value: sp_runtime::AccountId32) -> Self { + Self(value.into()) + } + } + impl From for AccountId32 { + fn from(value: sp_core::sr25519::Public) -> Self { + let acc: sp_runtime::AccountId32 = value.into(); + acc.into() + } + } + impl From for AccountId32 { + fn from(value: sp_core::ed25519::Public) -> Self { + let acc: sp_runtime::AccountId32 = value.into(); + acc.into() + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + use sp_core::crypto::Ss58Codec; + use sp_keyring::AccountKeyring; + + #[test] + fn ss58_is_compatible_with_substrate_impl() { + let keyrings = vec![ + AccountKeyring::Alice, + AccountKeyring::Bob, + AccountKeyring::Charlie, + ]; + + for keyring in keyrings { + let substrate_account = keyring.to_account_id(); + // Avoid "From" impl hidden behind "substrate-compat" feature so that this test + // can work either way: + let local_account = AccountId32(substrate_account.clone().into()); + + // Both should encode to ss58 the same way: + let substrate_ss58 = substrate_account.to_ss58check(); + assert_eq!(substrate_ss58, local_account.to_ss58check()); + + // Both should decode from ss58 back to the same: + assert_eq!( + sp_core::crypto::AccountId32::from_ss58check(&substrate_ss58).unwrap(), + substrate_account + ); + assert_eq!( + AccountId32::from_ss58check(&substrate_ss58).unwrap(), + local_account + ); + } + } +} diff --git a/core/src/utils/bits.rs b/core/src/utils/bits.rs new file mode 100644 index 0000000000..ed830a0dea --- /dev/null +++ b/core/src/utils/bits.rs @@ -0,0 +1,266 @@ +// 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. + +//! Generic `scale_bits` over `bitvec`-like `BitOrder` and `BitFormat` types. + +use codec::{Compact, Input}; +use scale_bits::{ + scale::format::{Format, OrderFormat, StoreFormat}, + Bits, +}; +use scale_decode::IntoVisitor; +use std::marker::PhantomData; + +/// Associates `bitvec::store::BitStore` trait with corresponding, type-erased `scale_bits::StoreFormat` enum. +/// +/// Used to decode bit sequences by providing `scale_bits::StoreFormat` using +/// `bitvec`-like type type parameters. +pub trait BitStore { + /// Corresponding `scale_bits::StoreFormat` value. + const FORMAT: StoreFormat; + /// Number of bits that the backing store types holds. + const BITS: u32; +} +macro_rules! impl_store { + ($ty:ident, $wrapped:ty) => { + impl BitStore for $wrapped { + const FORMAT: StoreFormat = StoreFormat::$ty; + const BITS: u32 = <$wrapped>::BITS; + } + }; +} +impl_store!(U8, u8); +impl_store!(U16, u16); +impl_store!(U32, u32); +impl_store!(U64, u64); + +/// Associates `bitvec::order::BitOrder` trait with corresponding, type-erased `scale_bits::OrderFormat` enum. +/// +/// Used to decode bit sequences in runtime by providing `scale_bits::OrderFormat` using +/// `bitvec`-like type type parameters. +pub trait BitOrder { + /// Corresponding `scale_bits::OrderFormat` value. + const FORMAT: OrderFormat; +} +macro_rules! impl_order { + ($ty:ident) => { + #[doc = concat!("Type-level value that corresponds to `scale_bits::OrderFormat::", stringify!($ty), "` at run-time")] + #[doc = concat!(" and `bitvec::order::BitOrder::", stringify!($ty), "` at the type level.")] + #[derive(Clone, Debug, PartialEq, Eq)] + pub enum $ty {} + impl BitOrder for $ty { + const FORMAT: OrderFormat = OrderFormat::$ty; + } + }; +} +impl_order!(Lsb0); +impl_order!(Msb0); + +/// Constructs a run-time format parameters based on the corresponding type-level parameters. +fn bit_format() -> Format { + Format { + order: Order::FORMAT, + store: Store::FORMAT, + } +} + +/// `scale_bits::Bits` generic over the bit store (`u8`/`u16`/`u32`/`u64`) and bit order (LSB, MSB) +/// used for SCALE encoding/decoding. Uses `scale_bits::Bits`-default `u8` and LSB format underneath. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DecodedBits { + bits: Bits, + _marker: PhantomData<(Store, Order)>, +} + +impl DecodedBits { + /// Extracts the underlying `scale_bits::Bits` value. + pub fn into_bits(self) -> Bits { + self.bits + } + + /// References the underlying `scale_bits::Bits` value. + pub fn as_bits(&self) -> &Bits { + &self.bits + } +} + +impl core::iter::FromIterator for DecodedBits { + fn from_iter>(iter: T) -> Self { + DecodedBits { + bits: Bits::from_iter(iter), + _marker: PhantomData, + } + } +} + +impl codec::Decode for DecodedBits { + fn decode(input: &mut I) -> Result { + /// Equivalent of `BitSlice::MAX_BITS` on 32bit machine. + const ARCH32BIT_BITSLICE_MAX_BITS: u32 = 0x1fff_ffff; + + let Compact(bits) = >::decode(input)?; + // Otherwise it is impossible to store it on 32bit machine. + if bits > ARCH32BIT_BITSLICE_MAX_BITS { + return Err("Attempt to decode a BitVec with too many bits".into()); + } + // NOTE: Replace with `bits.div_ceil(Store::BITS)` if `int_roundings` is stabilised + let elements = (bits / Store::BITS) + u32::from(bits % Store::BITS != 0); + let bytes_in_elem = Store::BITS.saturating_div(u8::BITS); + let bytes_needed = (elements * bytes_in_elem) as usize; + + // NOTE: We could reduce allocations if it would be possible to directly + // decode from an `Input` type using a custom format (rather than default ) + // for the `Bits` type. + let mut storage = codec::Encode::encode(&Compact(bits)); + let prefix_len = storage.len(); + storage.reserve_exact(bytes_needed); + storage.extend(vec![0; bytes_needed]); + input.read(&mut storage[prefix_len..])?; + + let decoder = scale_bits::decode_using_format_from(&storage, bit_format::())?; + let bits = decoder.collect::, _>>()?; + let bits = Bits::from_iter(bits); + + Ok(DecodedBits { + bits, + _marker: PhantomData, + }) + } +} + +impl codec::Encode for DecodedBits { + fn size_hint(&self) -> usize { + self.bits.size_hint() + } + + fn encoded_size(&self) -> usize { + self.bits.encoded_size() + } + + fn encode(&self) -> Vec { + scale_bits::encode_using_format(self.bits.iter(), bit_format::()) + } +} + +#[doc(hidden)] +pub struct DecodedBitsVisitor(std::marker::PhantomData<(S, O)>); +impl scale_decode::Visitor for DecodedBitsVisitor { + type Value<'scale, 'info> = DecodedBits; + type Error = scale_decode::Error; + + fn unchecked_decode_as_type<'scale, 'info>( + self, + input: &mut &'scale [u8], + type_id: scale_decode::visitor::TypeId, + types: &'info scale_info::PortableRegistry, + ) -> scale_decode::visitor::DecodeAsTypeResult< + Self, + Result, Self::Error>, + > { + let res = scale_decode::visitor::decode_with_visitor( + input, + type_id.0, + types, + Bits::into_visitor(), + ) + .map(|bits| DecodedBits { + bits, + _marker: PhantomData, + }); + scale_decode::visitor::DecodeAsTypeResult::Decoded(res) + } +} +impl scale_decode::IntoVisitor for DecodedBits { + type Visitor = DecodedBitsVisitor; + fn into_visitor() -> Self::Visitor { + DecodedBitsVisitor(PhantomData) + } +} + +impl scale_encode::EncodeAsType for DecodedBits { + fn encode_as_type_to( + &self, + type_id: u32, + types: &scale_info::PortableRegistry, + out: &mut Vec, + ) -> Result<(), scale_encode::Error> { + self.bits.encode_as_type_to(type_id, types, out) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use core::fmt::Debug; + + use bitvec::vec::BitVec; + use codec::Decode as _; + + // NOTE: We don't use `bitvec::order` types in our implementation, since we + // don't want to depend on `bitvec`. Rather than reimplementing the unsafe + // trait on our types here for testing purposes, we simply convert and + // delegate to `bitvec`'s own types. + trait ToBitVec { + type Order: bitvec::order::BitOrder; + } + impl ToBitVec for Lsb0 { + type Order = bitvec::order::Lsb0; + } + impl ToBitVec for Msb0 { + type Order = bitvec::order::Msb0; + } + + fn scales_like_bitvec_and_roundtrips< + 'a, + Store: BitStore + bitvec::store::BitStore + PartialEq, + Order: BitOrder + ToBitVec + Debug + PartialEq, + >( + input: impl IntoIterator, + ) where + BitVec::Order>: codec::Encode + codec::Decode, + { + let input: Vec<_> = input.into_iter().copied().collect(); + + let decoded_bits = DecodedBits::::from_iter(input.clone()); + let bitvec = BitVec::::Order>::from_iter(input); + + let decoded_bits_encoded = codec::Encode::encode(&decoded_bits); + let bitvec_encoded = codec::Encode::encode(&bitvec); + assert_eq!(decoded_bits_encoded, bitvec_encoded); + + let decoded_bits_decoded = + DecodedBits::::decode(&mut &decoded_bits_encoded[..]) + .expect("SCALE-encoding DecodedBits to roundtrip"); + let bitvec_decoded = + BitVec::::Order>::decode(&mut &bitvec_encoded[..]) + .expect("SCALE-encoding BitVec to roundtrip"); + assert_eq!(decoded_bits, decoded_bits_decoded); + assert_eq!(bitvec, bitvec_decoded); + } + + #[test] + fn decoded_bitvec_scales_and_roundtrips() { + let test_cases = [ + vec![], + vec![true], + vec![false], + vec![true, false, true], + vec![true, false, true, false, false, false, false, false, true], + [vec![true; 5], vec![false; 5], vec![true; 1], vec![false; 3]].concat(), + [vec![true; 9], vec![false; 9], vec![true; 9], vec![false; 9]].concat(), + ]; + + for test_case in &test_cases { + scales_like_bitvec_and_roundtrips::(test_case); + scales_like_bitvec_and_roundtrips::(test_case); + scales_like_bitvec_and_roundtrips::(test_case); + scales_like_bitvec_and_roundtrips::(test_case); + scales_like_bitvec_and_roundtrips::(test_case); + scales_like_bitvec_and_roundtrips::(test_case); + scales_like_bitvec_and_roundtrips::(test_case); + scales_like_bitvec_and_roundtrips::(test_case); + } + } +} diff --git a/core/src/utils/era.rs b/core/src/utils/era.rs new file mode 100644 index 0000000000..c98ebe58f6 --- /dev/null +++ b/core/src/utils/era.rs @@ -0,0 +1,107 @@ +// 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 scale_decode::DecodeAsType; +use scale_encode::EncodeAsType; + +// Dev note: This and related bits taken from `sp_runtime::generic::Era` +/// An era to describe the longevity of a transaction. +#[derive( + PartialEq, + Default, + Eq, + Clone, + Copy, + Debug, + serde::Serialize, + serde::Deserialize, + DecodeAsType, + EncodeAsType, + scale_info::TypeInfo, +)] +pub enum Era { + /// The transaction is valid forever. The genesis hash must be present in the signed content. + #[default] + Immortal, + + /// The transaction will expire. Use [`Era::mortal`] to construct this with correct values. + /// + /// When used on `FRAME`-based runtimes, `period` cannot exceed `BlockHashCount` parameter + /// of `system` module. + Mortal { + /// The number of blocks that the tx will be valid for after the checkpoint block + /// hash found in the signer payload. + period: u64, + /// The phase in the period that this transaction's lifetime begins (and, importantly, + /// implies which block hash is included in the signature material). If the `period` is + /// greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that + /// `period` is. + phase: u64, + }, +} + +// E.g. with period == 4: +// 0 10 20 30 40 +// 0123456789012345678901234567890123456789012 +// |...| +// authored -/ \- expiry +// phase = 1 +// n = Q(current - phase, period) + phase +impl Era { + /// Create a new era based on a period (which should be a power of two between 4 and 65536 + /// inclusive) and a block number on which it should start (or, for long periods, be shortly + /// after the start). + /// + /// If using `Era` in the context of `FRAME` runtime, make sure that `period` + /// does not exceed `BlockHashCount` parameter passed to `system` module, since that + /// prunes old blocks and renders transactions immediately invalid. + pub fn mortal(period: u64, current: u64) -> Self { + let period = period + .checked_next_power_of_two() + .unwrap_or(1 << 16) + .clamp(4, 1 << 16); + let phase = current % period; + let quantize_factor = (period >> 12).max(1); + let quantized_phase = phase / quantize_factor * quantize_factor; + + Self::Mortal { + period, + phase: quantized_phase, + } + } +} + +// Both copied from `sp_runtime::generic::Era`; this is the wire interface and so +// it's really the most important bit here. +impl codec::Encode for Era { + fn encode_to(&self, output: &mut T) { + match self { + Self::Immortal => output.push_byte(0), + Self::Mortal { period, phase } => { + let quantize_factor = (*period >> 12).max(1); + let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16 + | ((phase / quantize_factor) << 4) as u16; + encoded.encode_to(output); + } + } + } +} +impl codec::Decode for Era { + fn decode(input: &mut I) -> Result { + let first = input.read_byte()?; + if first == 0 { + Ok(Self::Immortal) + } else { + let encoded = first as u64 + ((input.read_byte()? as u64) << 8); + let period = 2 << (encoded % (1 << 4)); + let quantize_factor = (period >> 12).max(1); + let phase = (encoded >> 4) * quantize_factor; + if period >= 4 && phase < period { + Ok(Self::Mortal { period, phase }) + } else { + Err("Invalid period and phase".into()) + } + } + } +} diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs new file mode 100644 index 0000000000..df58f9d27a --- /dev/null +++ b/core/src/utils/mod.rs @@ -0,0 +1,95 @@ +// 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. + +//! Miscellaneous utility helpers. + +mod account_id; +pub mod bits; +mod era; +mod multi_address; +mod multi_signature; +mod static_type; +mod unchecked_extrinsic; +mod wrapper_opaque; + +use codec::{Compact, Decode, Encode}; +use derivative::Derivative; +use url::Url; + +pub use account_id::AccountId32; +pub use era::Era; +pub use multi_address::MultiAddress; +pub use multi_signature::MultiSignature; +pub use static_type::Static; +pub use unchecked_extrinsic::UncheckedExtrinsic; +pub use wrapper_opaque::WrapperKeepOpaque; + +// Used in codegen +#[doc(hidden)] +pub use primitive_types::{H160, H256, H512}; + +/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of +/// the transaction payload +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Encoded(pub Vec); + +impl codec::Encode for Encoded { + fn encode(&self) -> Vec { + self.0.to_owned() + } +} + +/// Decodes a compact encoded value from the beginning of the provided bytes, +/// returning the value and any remaining bytes. +pub(crate) fn strip_compact_prefix(bytes: &[u8]) -> Result<(u64, &[u8]), codec::Error> { + let cursor = &mut &*bytes; + let val = >::decode(cursor)?; + Ok((val.0, *cursor)) +} + +/// A URL is considered secure if it uses a secure scheme ("https" or "wss") or is referring to localhost. +/// +/// Returns an error if the the string could not be parsed into a URL. +pub fn url_is_secure(url: &str) -> Result { + let url = Url::parse(url)?; + + let secure_scheme = url.scheme() == "https" || url.scheme() == "wss"; + let is_localhost = url.host().is_some_and(|e| match e { + url::Host::Domain(e) => e == "localhost", + url::Host::Ipv4(e) => e.is_loopback(), + url::Host::Ipv6(e) => e.is_loopback(), + }); + + Ok(secure_scheme || is_localhost) +} + +/// A version of [`std::marker::PhantomData`] that is also Send and Sync (which is fine +/// because regardless of the generic param, it is always possible to Send + Sync this +/// 0 size type). +#[derive(Derivative, Encode, Decode, scale_info::TypeInfo)] +#[derivative( + Clone(bound = ""), + PartialEq(bound = ""), + Debug(bound = ""), + Eq(bound = ""), + Default(bound = ""), + Hash(bound = "") +)] +#[scale_info(skip_type_params(T))] +#[doc(hidden)] +pub struct PhantomDataSendSync(core::marker::PhantomData); + +impl PhantomDataSendSync { + pub(crate) fn new() -> Self { + Self(core::marker::PhantomData) + } +} + +unsafe impl Send for PhantomDataSendSync {} +unsafe impl Sync for PhantomDataSendSync {} + +/// This represents a key-value collection and is SCALE compatible +/// with collections like BTreeMap. This has the same type params +/// as `BTreeMap` which allows us to easily swap the two during codegen. +pub type KeyedVec = Vec<(K, V)>; diff --git a/core/src/utils/multi_address.rs b/core/src/utils/multi_address.rs new file mode 100644 index 0000000000..9b1e556fa9 --- /dev/null +++ b/core/src/utils/multi_address.rs @@ -0,0 +1,72 @@ +// 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. + +//! The "default" Substrate/Polkadot Address type. This is used in codegen, as well as signing related bits. +//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_runtime::MultiAddress` +//! for instance, to gain functionality without forcing a dependency on Substrate crates here. + +use codec::{Decode, Encode}; + +/// A multi-format address wrapper for on-chain accounts. This is a simplified version of Substrate's +/// `sp_runtime::MultiAddress`. To obtain more functionality, convert this into that type (this conversion +/// functionality is provided via `From` impls if the `substrate-compat` feature is enabled). +#[derive( + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + Debug, + scale_encode::EncodeAsType, + scale_decode::DecodeAsType, + scale_info::TypeInfo, +)] +pub enum MultiAddress { + /// It's an account ID (pubkey). + Id(AccountId), + /// It's an account index. + Index(#[codec(compact)] AccountIndex), + /// It's some arbitrary raw bytes. + Raw(Vec), + /// It's a 32 byte representation. + Address32([u8; 32]), + /// Its a 20 byte representation. + Address20([u8; 20]), +} + +impl From for MultiAddress { + fn from(a: AccountId) -> Self { + Self::Id(a) + } +} + +// Improve compat with the substrate version if we're using those crates: +#[cfg(feature = "substrate-compat")] +mod substrate_impls { + use super::{super::AccountId32, *}; + + impl From for MultiAddress { + fn from(value: sp_runtime::AccountId32) -> Self { + let val: AccountId32 = value.into(); + val.into() + } + } + + impl From> for MultiAddress + where + Id: Into, + { + fn from(value: sp_runtime::MultiAddress) -> Self { + match value { + sp_runtime::MultiAddress::Id(v) => Self::Id(v.into()), + sp_runtime::MultiAddress::Index(v) => Self::Index(v), + sp_runtime::MultiAddress::Raw(v) => Self::Raw(v), + sp_runtime::MultiAddress::Address32(v) => Self::Address32(v), + sp_runtime::MultiAddress::Address20(v) => Self::Address20(v), + } + } + } +} diff --git a/core/src/utils/multi_signature.rs b/core/src/utils/multi_signature.rs new file mode 100644 index 0000000000..4ed9ea4bd5 --- /dev/null +++ b/core/src/utils/multi_signature.rs @@ -0,0 +1,58 @@ +// 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. + +//! The "default" Substrate/Polkadot Signature type. This is used in codegen, as well as signing related bits. +//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_runtime::MultiSignature` +//! for instance, to gain functionality without forcing a dependency on Substrate crates here. + +use codec::{Decode, Encode}; + +/// Signature container that can store known signature types. This is a simplified version of +/// `sp_runtime::MultiSignature`. To obtain more functionality, convert this into that type. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, scale_info::TypeInfo)] +pub enum MultiSignature { + /// An Ed25519 signature. + Ed25519([u8; 64]), + /// An Sr25519 signature. + Sr25519([u8; 64]), + /// An ECDSA/SECP256k1 signature (a 512-bit value, plus 8 bits for recovery ID). + Ecdsa([u8; 65]), +} + +// Improve compat with the substrate version if we're using those crates: +#[cfg(feature = "substrate-compat")] +mod substrate_impls { + use super::*; + + impl From for MultiSignature { + fn from(value: sp_runtime::MultiSignature) -> Self { + match value { + sp_runtime::MultiSignature::Ed25519(s) => Self::Ed25519(s.0), + sp_runtime::MultiSignature::Sr25519(s) => Self::Sr25519(s.0), + sp_runtime::MultiSignature::Ecdsa(s) => Self::Ecdsa(s.0), + } + } + } + + impl From for MultiSignature { + fn from(value: sp_core::ed25519::Signature) -> Self { + let sig: sp_runtime::MultiSignature = value.into(); + sig.into() + } + } + + impl From for MultiSignature { + fn from(value: sp_core::sr25519::Signature) -> Self { + let sig: sp_runtime::MultiSignature = value.into(); + sig.into() + } + } + + impl From for MultiSignature { + fn from(value: sp_core::ecdsa::Signature) -> Self { + let sig: sp_runtime::MultiSignature = value.into(); + sig.into() + } + } +} diff --git a/core/src/utils/static_type.rs b/core/src/utils/static_type.rs new file mode 100644 index 0000000000..2d13e61eba --- /dev/null +++ b/core/src/utils/static_type.rs @@ -0,0 +1,78 @@ +// 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 codec::{Decode, Encode}; +use scale_decode::{visitor::DecodeAsTypeResult, IntoVisitor, Visitor}; +use scale_encode::EncodeAsType; + +/// If the type inside this implements [`Encode`], this will implement [`scale_encode::EncodeAsType`]. +/// If the type inside this implements [`Decode`], this will implement [`scale_decode::DecodeAsType`]. +/// +/// In either direction, we ignore any type information and just attempt to encode/decode statically +/// via the [`Encode`] and [`Decode`] implementations. This can be useful as an adapter for types which +/// do not implement [`scale_encode::EncodeAsType`] and [`scale_decode::DecodeAsType`] themselves, but +/// it's best to avoid using it where possible as it will not take into account any type information, +/// and is thus more likely to encode or decode incorrectly. +#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] +pub struct Static(pub T); + +impl EncodeAsType for Static { + fn encode_as_type_to( + &self, + _type_id: u32, + _types: &scale_decode::PortableRegistry, + out: &mut Vec, + ) -> Result<(), scale_encode::Error> { + self.0.encode_to(out); + Ok(()) + } +} + +pub struct StaticDecodeAsTypeVisitor(std::marker::PhantomData); + +impl Visitor for StaticDecodeAsTypeVisitor { + type Value<'scale, 'info> = Static; + type Error = scale_decode::Error; + + fn unchecked_decode_as_type<'scale, 'info>( + self, + input: &mut &'scale [u8], + _type_id: scale_decode::visitor::TypeId, + _types: &'info scale_info::PortableRegistry, + ) -> DecodeAsTypeResult, Self::Error>> { + use scale_decode::{visitor::DecodeError, Error}; + let decoded = T::decode(input) + .map(Static) + .map_err(|e| Error::new(DecodeError::CodecError(e).into())); + DecodeAsTypeResult::Decoded(decoded) + } +} + +impl IntoVisitor for Static { + type Visitor = StaticDecodeAsTypeVisitor; + fn into_visitor() -> Self::Visitor { + StaticDecodeAsTypeVisitor(std::marker::PhantomData) + } +} + +// Make it easy to convert types into Static where required. +impl From for Static { + fn from(value: T) -> Self { + Static(value) + } +} + +// Static is just a marker type and should be as transparent as possible: +impl std::ops::Deref for Static { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for Static { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/core/src/utils/unchecked_extrinsic.rs b/core/src/utils/unchecked_extrinsic.rs new file mode 100644 index 0000000000..882b490bed --- /dev/null +++ b/core/src/utils/unchecked_extrinsic.rs @@ -0,0 +1,136 @@ +// 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. + +//! The "default" Substrate/Polkadot UncheckedExtrinsic. +//! This is used in codegen for runtime API calls. +//! +//! The inner bytes represent the encoded extrinsic expected by the +//! runtime APIs. Deriving `EncodeAsType` would lead to the inner +//! bytes to be re-encoded (length prefixed). + +use std::marker::PhantomData; + +use codec::{Decode, Encode}; +use scale_decode::{visitor::DecodeAsTypeResult, DecodeAsType, IntoVisitor, Visitor}; + +use super::{Encoded, Static}; + +/// The unchecked extrinsic from substrate. +#[derive(Clone, Debug, Eq, PartialEq, Encode)] +pub struct UncheckedExtrinsic( + Static, + #[codec(skip)] PhantomData<(Address, Call, Signature, Extra)>, +); + +impl UncheckedExtrinsic { + /// Construct a new [`UncheckedExtrinsic`]. + pub fn new(bytes: Vec) -> Self { + Self(Static(Encoded(bytes)), PhantomData) + } + + /// Get the bytes of the encoded extrinsic. + pub fn bytes(&self) -> &[u8] { + self.0 .0 .0.as_slice() + } +} + +impl Decode + for UncheckedExtrinsic +{ + fn decode(input: &mut I) -> Result { + // The bytes for an UncheckedExtrinsic are first a compact + // encoded length, and then the bytes following. This is the + // same encoding as a Vec, so easiest ATM is just to decode + // into that, and then encode the vec bytes to get our extrinsic + // bytes, which we save into an `Encoded` to preserve as-is. + let xt_vec: Vec = Decode::decode(input)?; + Ok(UncheckedExtrinsic::new(xt_vec)) + } +} + +impl scale_encode::EncodeAsType + for UncheckedExtrinsic +{ + fn encode_as_type_to( + &self, + type_id: u32, + types: &scale_info::PortableRegistry, + out: &mut Vec, + ) -> Result<(), scale_encode::Error> { + self.0.encode_as_type_to(type_id, types, out) + } +} + +impl From> + for UncheckedExtrinsic +{ + fn from(bytes: Vec) -> Self { + UncheckedExtrinsic::new(bytes) + } +} + +impl From> + for Vec +{ + fn from(bytes: UncheckedExtrinsic) -> Self { + bytes.0 .0 .0 + } +} + +pub struct UncheckedExtrinsicDecodeAsTypeVisitor( + PhantomData<(Address, Call, Signature, Extra)>, +); + +impl Visitor + for UncheckedExtrinsicDecodeAsTypeVisitor +{ + type Value<'scale, 'info> = UncheckedExtrinsic; + type Error = scale_decode::Error; + + fn unchecked_decode_as_type<'scale, 'info>( + self, + input: &mut &'scale [u8], + type_id: scale_decode::visitor::TypeId, + types: &'info scale_info::PortableRegistry, + ) -> DecodeAsTypeResult, Self::Error>> { + DecodeAsTypeResult::Decoded(Self::Value::decode_as_type(input, type_id.0, types)) + } +} + +impl IntoVisitor + for UncheckedExtrinsic +{ + type Visitor = UncheckedExtrinsicDecodeAsTypeVisitor; + + fn into_visitor() -> Self::Visitor { + UncheckedExtrinsicDecodeAsTypeVisitor(PhantomData) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + + #[test] + fn unchecked_extrinsic_encoding() { + // A tx is basically some bytes with a compact length prefix; ie an encoded vec: + let tx_bytes = vec![1u8, 2, 3].encode(); + + let unchecked_extrinsic = UncheckedExtrinsic::<(), (), (), ()>::new(tx_bytes.clone()); + let encoded_tx_bytes = unchecked_extrinsic.encode(); + + // The encoded representation must not alter the provided bytes. + assert_eq!(tx_bytes, encoded_tx_bytes); + + // However, for decoding we expect to be able to read the extrinsic from the wire + // which would be length prefixed. + let decoded_tx = UncheckedExtrinsic::<(), (), (), ()>::decode(&mut &tx_bytes[..]).unwrap(); + let decoded_tx_bytes = decoded_tx.bytes(); + let encoded_tx_bytes = decoded_tx.encode(); + + assert_eq!(decoded_tx_bytes, encoded_tx_bytes); + // Ensure we can decode the tx and fetch only the tx bytes. + assert_eq!(vec![1, 2, 3], encoded_tx_bytes); + } +} diff --git a/core/src/utils/wrapper_opaque.rs b/core/src/utils/wrapper_opaque.rs new file mode 100644 index 0000000000..9257405715 --- /dev/null +++ b/core/src/utils/wrapper_opaque.rs @@ -0,0 +1,243 @@ +// 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 super::PhantomDataSendSync; +use codec::{Compact, Decode, DecodeAll, Encode}; +use derivative::Derivative; +use scale_decode::{IntoVisitor, Visitor}; +use scale_encode::EncodeAsType; + +/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec`. +/// [`WrapperKeepOpaque`] stores the type only in its opaque format, aka as a `Vec`. To +/// access the real type `T` [`Self::try_decode`] needs to be used. +// Dev notes: +// +// - This is adapted from [here](https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs). +// - The encoded bytes will be a compact encoded length followed by that number of bytes. +// - However, the TypeInfo describes the type as a composite with first a compact encoded length and next the type itself. +// [`Encode`] and [`Decode`] impls will "just work" to take this into a `Vec`, but we need a custom [`EncodeAsType`] +// and [`Visitor`] implementation to encode and decode based on TypeInfo. +#[derive(Derivative, Encode, Decode)] +#[derivative( + Debug(bound = ""), + Clone(bound = ""), + PartialEq(bound = ""), + Eq(bound = ""), + Default(bound = ""), + Hash(bound = "") +)] +pub struct WrapperKeepOpaque { + data: Vec, + _phantom: PhantomDataSendSync, +} + +impl WrapperKeepOpaque { + /// Try to decode the wrapped type from the inner `data`. + /// + /// Returns `None` if the decoding failed. + pub fn try_decode(&self) -> Option + where + T: Decode, + { + T::decode_all(&mut &self.data[..]).ok() + } + + /// Returns the length of the encoded `T`. + pub fn encoded_len(&self) -> usize { + self.data.len() + } + + /// Returns the encoded data. + pub fn encoded(&self) -> &[u8] { + &self.data + } + + /// Create from the given encoded `data`. + pub fn from_encoded(data: Vec) -> Self { + Self { + data, + _phantom: PhantomDataSendSync::new(), + } + } + + /// Create from some raw value by encoding it. + pub fn from_value(value: T) -> Self + where + T: Encode, + { + Self { + data: value.encode(), + _phantom: PhantomDataSendSync::new(), + } + } +} + +impl EncodeAsType for WrapperKeepOpaque { + fn encode_as_type_to( + &self, + type_id: u32, + types: &scale_info::PortableRegistry, + out: &mut Vec, + ) -> Result<(), scale_encode::Error> { + use scale_encode::error::{Error, ErrorKind, Kind}; + + let Some(ty) = types.resolve(type_id) else { + return Err(Error::new(ErrorKind::TypeNotFound(type_id))); + }; + + // Do a basic check that the target shape lines up. + let scale_info::TypeDef::Composite(_) = &ty.type_def else { + return Err(Error::new(ErrorKind::WrongShape { + actual: Kind::Struct, + expected: type_id, + })); + }; + + // Check that the name also lines up. + if ty.path.ident().as_deref() != Some("WrapperKeepOpaque") { + return Err(Error::new(ErrorKind::WrongShape { + actual: Kind::Struct, + expected: type_id, + })); + } + + // Just blat the bytes out. + self.data.encode_to(out); + Ok(()) + } +} + +pub struct WrapperKeepOpaqueVisitor(std::marker::PhantomData); +impl Visitor for WrapperKeepOpaqueVisitor { + type Value<'scale, 'info> = WrapperKeepOpaque; + type Error = scale_decode::Error; + + fn visit_composite<'scale, 'info>( + self, + value: &mut scale_decode::visitor::types::Composite<'scale, 'info>, + _type_id: scale_decode::visitor::TypeId, + ) -> Result, Self::Error> { + use scale_decode::error::{Error, ErrorKind}; + + if value.path().ident().as_deref() != Some("WrapperKeepOpaque") { + return Err(Error::custom_str( + "Type to decode is not 'WrapperTypeKeepOpaque'", + )); + } + if value.remaining() != 2 { + return Err(Error::new(ErrorKind::WrongLength { + actual_len: value.remaining(), + expected_len: 2, + })); + } + + // The field to decode is a compact len followed by bytes. Decode the length, then grab the bytes. + let Compact(len) = value + .decode_item(Compact::::into_visitor()) + .expect("length checked")?; + let field = value.next().expect("length checked")?; + + // Sanity check that the compact length we decoded lines up with the number of bytes encoded in the next field. + if field.bytes().len() != len as usize { + return Err(Error::custom_str("WrapperTypeKeepOpaque compact encoded length doesn't line up with encoded byte len")); + } + + Ok(WrapperKeepOpaque { + data: field.bytes().to_vec(), + _phantom: PhantomDataSendSync::new(), + }) + } +} + +impl IntoVisitor for WrapperKeepOpaque { + type Visitor = WrapperKeepOpaqueVisitor; + fn into_visitor() -> Self::Visitor { + WrapperKeepOpaqueVisitor(std::marker::PhantomData) + } +} + +#[cfg(test)] +mod test { + use scale_decode::DecodeAsType; + + use super::*; + + // Copied from https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs + // and used for tests to check that we can work with the expected TypeInfo without needing to import + // the frame_support crate, which has quite a lot of dependencies. + impl scale_info::TypeInfo for WrapperKeepOpaque { + type Identity = Self; + fn type_info() -> scale_info::Type { + use scale_info::{build::Fields, meta_type, Path, Type, TypeParameter}; + + Type::builder() + .path(Path::new("WrapperKeepOpaque", module_path!())) + .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) + .composite( + Fields::unnamed() + .field(|f| f.compact::()) + .field(|f| f.ty::().type_name("T")), + ) + } + } + + /// Given a type definition, return type ID and registry representing it. + fn make_type() -> (u32, scale_info::PortableRegistry) { + let m = scale_info::MetaType::new::(); + let mut types = scale_info::Registry::new(); + let id = types.register_type(&m); + let portable_registry: scale_info::PortableRegistry = types.into(); + (id.id, portable_registry) + } + + fn roundtrips_like_scale_codec(t: T) + where + T: EncodeAsType + + DecodeAsType + + Encode + + Decode + + PartialEq + + std::fmt::Debug + + scale_info::TypeInfo + + 'static, + { + let (type_id, types) = make_type::(); + + let scale_codec_encoded = t.encode(); + let encode_as_type_encoded = t.encode_as_type(type_id, &types).unwrap(); + + assert_eq!( + scale_codec_encoded, encode_as_type_encoded, + "encoded bytes should match" + ); + + let decode_as_type_bytes = &mut &*scale_codec_encoded; + let decoded_as_type = T::decode_as_type(decode_as_type_bytes, type_id, &types) + .expect("decode-as-type decodes"); + + let decode_scale_codec_bytes = &mut &*scale_codec_encoded; + let decoded_scale_codec = T::decode(decode_scale_codec_bytes).expect("scale-codec decodes"); + + assert!( + decode_as_type_bytes.is_empty(), + "no bytes should remain in decode-as-type impl" + ); + assert!( + decode_scale_codec_bytes.is_empty(), + "no bytes should remain in codec-decode impl" + ); + + assert_eq!( + decoded_as_type, decoded_scale_codec, + "decoded values should match" + ); + } + + #[test] + fn wrapper_keep_opaque_roundtrips_ok() { + roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(123u64)); + roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(true)); + roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(vec![1u8, 2, 3, 4])); + } +} diff --git a/examples/parachain-example/Cargo.lock b/examples/parachain-example/Cargo.lock index c49f261820..00597ff970 100644 --- a/examples/parachain-example/Cargo.lock +++ b/examples/parachain-example/Cargo.lock @@ -2513,9 +2513,9 @@ dependencies = [ [[package]] name = "sp-core-hashing" -version = "13.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb8524f01591ee58b46cd83c9dbc0fcffd2fd730dabec4f59326cd58a00f17e2" +checksum = "1e0f4990add7b2cefdeca883c0efa99bb4d912cb2196120e1500c0cc099553b0" dependencies = [ "blake2b_simd", "byteorder", diff --git a/examples/wasm-example/Cargo.lock b/examples/wasm-example/Cargo.lock index 0e00f041a3..69f4740549 100644 --- a/examples/wasm-example/Cargo.lock +++ b/examples/wasm-example/Cargo.lock @@ -2540,9 +2540,9 @@ dependencies = [ [[package]] name = "sp-core-hashing" -version = "13.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb8524f01591ee58b46cd83c9dbc0fcffd2fd730dabec4f59326cd58a00f17e2" +checksum = "1e0f4990add7b2cefdeca883c0efa99bb4d912cb2196120e1500c0cc099553b0" dependencies = [ "blake2b_simd", "byteorder", @@ -2606,6 +2606,7 @@ dependencies = [ "subxt-macro", "subxt-metadata", "thiserror", + "tokio-stream", "tracing", "url", ] diff --git a/examples/wasm-example/Cargo.toml b/examples/wasm-example/Cargo.toml index 80d3be6ce7..2199dc1386 100644 --- a/examples/wasm-example/Cargo.toml +++ b/examples/wasm-example/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] futures = "0.3.28" -subxt = { path = "../../subxt", default-features = false, features = ["jsonrpsee", "web"], target_arch = "wasm32" } +subxt = { path = "../../subxt", default-features = false, features = ["jsonrpsee", "web", "unstable-light-client"], target_arch = "wasm32" } yew = { version = "0.20.0", features = ["csr"] } web-sys = "0.3.63" hex = "0.4.3"