diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 530c7cd8df..904027bde2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -9,7 +9,6 @@ on: description: "Branch or tag to use for the Docker image tag and ref to checkout (optional)" required: false default: "" - push: branches: - devnet-ready @@ -35,6 +34,13 @@ jobs: echo "tag=$branch_or_tag" >> $GITHUB_ENV echo "ref=$branch_or_tag" >> $GITHUB_ENV + # Check if this is a tagged release (not devnet-ready/devnet/testnet) + if [[ "${{ github.event_name }}" == "release" && "$branch_or_tag" != "devnet-ready" && "$branch_or_tag" != "devnet" && "$branch_or_tag" != "testnet" ]]; then + echo "latest_tag=true" >> $GITHUB_ENV + else + echo "latest_tag=false" >> $GITHUB_ENV + fi + - name: Checkout code uses: actions/checkout@v4 with: @@ -58,5 +64,7 @@ jobs: with: context: . push: true + platforms: linux/amd64,linux/arm64 tags: | ghcr.io/${{ github.repository }}:${{ env.tag }} + ${{ env.latest_tag == 'true' && format('ghcr.io/{0}:latest', github.repository) || '' }} diff --git a/Cargo.lock b/Cargo.lock index 70dcd3d377..6fe43451ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,11 +23,11 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ - "gimli 0.31.0", + "gimli 0.31.1", ] [[package]] @@ -77,7 +77,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom", + "getrandom 0.2.15", "once_cell", "version_check", ] @@ -89,7 +89,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy", @@ -106,9 +106,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-tzdata" @@ -127,9 +127,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -142,43 +142,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "approx" @@ -200,7 +201,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -567,7 +568,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -583,7 +584,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -607,7 +608,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", "synstructure 0.13.1", ] @@ -630,7 +631,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -652,9 +653,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock", "cfg-if", @@ -663,7 +664,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 0.38.37", + "rustix 0.38.44", "slab", "tracing", "windows-sys 0.59.0", @@ -675,20 +676,20 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 5.3.1", + "event-listener 5.4.0", "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -732,13 +733,13 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -753,11 +754,11 @@ version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ - "addr2line 0.24.1", + "addr2line 0.24.2", "cfg-if", "libc", "miniz_oxide", - "object 0.36.4", + "object 0.36.7", "rustc-demangle", "windows-targets 0.52.6", ] @@ -819,13 +820,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", - "prettyplease 0.2.22", + "prettyplease 0.2.29", "proc-macro2", "quote", "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -852,9 +853,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "bitvec" @@ -913,9 +914,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" dependencies = [ "arrayref", "arrayvec", @@ -944,9 +945,9 @@ dependencies = [ [[package]] name = "bounded-collections" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32385ecb91a31bddaf908e8dcf4a15aef1bcd3913cc03ebfad02ff6d568abc1" +checksum = "3d077619e9c237a5d1875166f5e8033e8f6bff0c96f8caf81e1c2d7738c431bf" dependencies = [ "log", "parity-scale-codec", @@ -980,9 +981,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byte-slice-cast" @@ -998,9 +999,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytemuck" -version = "1.18.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" [[package]] name = "byteorder" @@ -1010,9 +1011,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "bzip2-sys" @@ -1046,9 +1047,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -1061,10 +1062,10 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.23", + "semver 1.0.25", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1075,9 +1076,9 @@ checksum = "fd6c0e7b807d60291f42f33f58480c0bfafe28ed08286446f45e463728cf9c1c" [[package]] name = "cc" -version = "1.1.24" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "jobserver", "libc", @@ -1150,9 +1151,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1222,9 +1223,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.19" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", @@ -1232,9 +1233,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.19" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -1245,21 +1246,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "codespan-reporting" @@ -1268,14 +1269,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "combine" @@ -1289,13 +1290,13 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" +checksum = "24f165e7b643266ea80cb858aed492ad9280e3e05ce24d4a99d7d7b889b6a4d9" dependencies = [ "strum 0.26.3", "strum_macros 0.26.4", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] @@ -1315,15 +1316,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "unicode-width", - "windows-sys 0.52.0", + "once_cell", + "unicode-width 0.2.0", + "windows-sys 0.59.0", ] [[package]] @@ -1347,7 +1348,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom", + "getrandom 0.2.15", "once_cell", "tiny-keccak", ] @@ -1406,9 +1407,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -1537,9 +1538,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1565,15 +1566,15 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-bigint" @@ -1651,51 +1652,66 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "cxx" -version = "1.0.128" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ccead7d199d584d139148b04b4a368d1ec7556a1d9ea2548febb1b9d49f9a4" +checksum = "0fc894913dccfed0f84106062c284fa021c3ba70cb1d78797d6f5165d4492e45" dependencies = [ "cc", + "cxxbridge-cmd", "cxxbridge-flags", "cxxbridge-macro", + "foldhash", "link-cplusplus", ] [[package]] name = "cxx-build" -version = "1.0.128" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77953e99f01508f89f55c494bfa867171ef3a6c8cea03d26975368f2121a5c1" +checksum = "503b2bfb6b3e8ce7f95d865a67419451832083d3186958290cee6c53e39dfcfe" dependencies = [ "cc", "codespan-reporting", - "once_cell", "proc-macro2", "quote", "scratch", - "syn 2.0.90", + "syn 2.0.96", +] + +[[package]] +name = "cxxbridge-cmd" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d2cb64a95b4b5a381971482235c4db2e0208302a962acdbe314db03cbbe2fb" +dependencies = [ + "clap", + "codespan-reporting", + "proc-macro2", + "quote", + "syn 2.0.96", ] [[package]] name = "cxxbridge-flags" -version = "1.0.128" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65777e06cc48f0cb0152024c77d6cf9e4bdb4408e7b48bea993d42fa0f5b02b6" +checksum = "5f797b0206463c9c2a68ed605ab28892cca784f1ef066050f4942e3de26ad885" [[package]] name = "cxxbridge-macro" -version = "1.0.128" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98532a60dedaebc4848cb2cba5023337cc9ea3af16a5b062633fabfd9f18fb60" +checksum = "e79010a2093848e65a3e0f7062d3f02fb2ef27f866416dfe436fccfa73d3bb59" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "rustversion", + "syn 2.0.96", ] [[package]] @@ -1719,7 +1735,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -1730,7 +1746,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -1748,15 +1764,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" [[package]] name = "data-encoding-macro" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" +checksum = "5b16d9d0d88a5273d830dac8b78ceb217ffc9b1d5404e5597a3542515329405b" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1764,9 +1780,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" +checksum = "1145d32e826a7748b69ee8fc62d3e6355ff7f1051df53141e7048162fc90481b" dependencies = [ "data-encoding", "syn 1.0.109", @@ -1839,7 +1855,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -1852,7 +1868,27 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.90", + "syn 2.0.96", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", ] [[package]] @@ -1941,23 +1977,23 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "docify" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2f138ad521dc4a2ced1a4576148a6a610b4c5923933b062a263130a6802ce" +checksum = "a772b62b1837c8f060432ddcc10b17aae1453ef17617a99bc07789252d2a5896" dependencies = [ "docify_macros", ] [[package]] name = "docify_macros" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a081e51fb188742f5a7a1164ad752121abcb22874b21e2c3b0dd040c515fdad" +checksum = "60e6be249b0a462a14784a99b19bf35a667bb5e09de611738bb7362fa4c95ff7" dependencies = [ "common-path", "derive-syn-parse", @@ -1965,7 +2001,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.90", + "syn 2.0.96", "termcolor", "toml 0.8.19", "walkdir", @@ -1991,9 +2027,9 @@ checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" [[package]] name = "dyn-clonable" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +checksum = "a36efbb9bfd58e1723780aa04b61aba95ace6a05d9ffabfdb0b43672552f0805" dependencies = [ "dyn-clonable-impl", "dyn-clone", @@ -2001,13 +2037,13 @@ dependencies = [ [[package]] name = "dyn-clonable-impl" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +checksum = "7e8671d54058979a37a26f3511fbf8d198ba1aa35ffb202c42587d918d77213a" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.96", ] [[package]] @@ -2102,9 +2138,9 @@ dependencies = [ [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "enum-as-inner" @@ -2127,27 +2163,27 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "enumflags2" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -2177,12 +2213,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2193,9 +2229,9 @@ checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" dependencies = [ "crunchy", "fixed-hash", - "impl-codec", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", + "impl-serde 0.4.0", "scale-info", "tiny-keccak", ] @@ -2226,12 +2262,12 @@ checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ "ethbloom", "fixed-hash", - "impl-codec", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", - "primitive-types", + "impl-serde 0.4.0", + "primitive-types 0.12.2", "scale-info", - "uint", + "uint 0.9.5", ] [[package]] @@ -2242,9 +2278,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", @@ -2253,11 +2289,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ - "event-listener 5.3.1", + "event-listener 5.4.0", "pin-project-lite", ] @@ -2275,7 +2311,7 @@ dependencies = [ "evm-runtime", "log", "parity-scale-codec", - "primitive-types", + "primitive-types 0.12.2", "rlp", "scale-info", "serde", @@ -2289,7 +2325,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1da6cedc5cedb4208e59467106db0d1f50db01b920920589f8e672c02fdc04f" dependencies = [ "parity-scale-codec", - "primitive-types", + "primitive-types 0.12.2", "scale-info", "serde", ] @@ -2303,7 +2339,7 @@ dependencies = [ "environmental", "evm-core", "evm-runtime", - "primitive-types", + "primitive-types 0.12.2", ] [[package]] @@ -2315,7 +2351,7 @@ dependencies = [ "auto_impl", "environmental", "evm-core", - "primitive-types", + "primitive-types 0.12.2", "sha3", ] @@ -2337,10 +2373,10 @@ dependencies = [ "blake2 0.10.6", "file-guard", "fs-err", - "prettyplease 0.2.22", + "prettyplease 0.2.29", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -2357,9 +2393,9 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fc-api" @@ -2386,7 +2422,7 @@ dependencies = [ "sp-block-builder", "sp-consensus", "sp-runtime", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2492,7 +2528,7 @@ dependencies = [ "sp-storage 21.0.0", "sp-timestamp", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -2535,7 +2571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182f7dbc2ef73d9ef67351c5fbbea084729c48362d3ce9dd44c28e32e277fe5" dependencies = [ "libc", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2648,9 +2684,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "foreign-types" @@ -2691,7 +2727,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" dependencies = [ "nonempty", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2700,7 +2736,7 @@ version = "1.0.0-dev" source = "git+https://github.com/opentensor/frontier?rev=635bdac882#635bdac882333afed827053f31ef56ab739f7a2e" dependencies = [ "hex", - "impl-serde", + "impl-serde 0.4.0", "libsecp256k1", "log", "parity-scale-codec", @@ -2864,7 +2900,7 @@ dependencies = [ "sp-storage 21.0.0", "sp-trie", "sp-wasm-interface 21.0.1", - "thiserror", + "thiserror 1.0.69", "thousands", ] @@ -2971,7 +3007,7 @@ dependencies = [ "proc-macro2", "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -2984,7 +3020,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -2996,7 +3032,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -3007,7 +3043,7 @@ checksum = "68672b9ec6fe72d259d3879dc212c5e42e977588cdac830c76f54d9f492aeb58" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -3017,7 +3053,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -3172,9 +3208,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.3.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" dependencies = [ "futures-core", "pin-project-lite", @@ -3188,7 +3224,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -3284,7 +3320,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", ] [[package]] @@ -3330,15 +3378,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "governor" @@ -3383,7 +3431,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.6.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -3392,17 +3440,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.1.0", - "indexmap 2.6.0", + "http 1.2.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -3426,7 +3474,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3494,11 +3542,11 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.2", ] [[package]] @@ -3587,11 +3635,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3618,9 +3666,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -3645,7 +3693,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.1.0", + "http 1.2.0", ] [[package]] @@ -3656,16 +3704,16 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -3681,9 +3729,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", @@ -3696,7 +3744,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.5.8", "tokio", "tower-service", "tracing", @@ -3705,15 +3753,15 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.6", - "http 1.1.0", + "h2 0.4.7", + "http 1.2.0", "http-body 1.0.1", "httparse", "httpdate", @@ -3731,7 +3779,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.32", "log", "rustls 0.21.12", "rustls-native-certs", @@ -3747,9 +3795,9 @@ checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", - "hyper 1.5.0", + "hyper 1.6.0", "pin-project-lite", "tokio", "tower-service", @@ -3778,6 +3826,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -3807,12 +3973,23 @@ dependencies = [ [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -3827,9 +4004,9 @@ dependencies = [ [[package]] name = "if-watch" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e" +checksum = "cdf9d64cfcf380606e64f9a0bcf493616b65331199f984151a6fa11a7b3cde38" dependencies = [ "async-io", "core-foundation", @@ -3838,6 +4015,10 @@ dependencies = [ "if-addrs", "ipnet", "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", "rtnetlink", "system-configuration", "tokio", @@ -3855,7 +4036,7 @@ dependencies = [ "bytes", "futures", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.32", "log", "rand", "tokio", @@ -3872,6 +4053,26 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67aa010c1e3da95bf151bd8b4c059b2ed7e75387cdb969b4f8f2723a43f9941" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-num-traits" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803d15461ab0dcc56706adf266158acbc44ccf719bf7d0af30705f58b90a4b8c" +dependencies = [ + "integer-sqrt", + "num-traits", + "uint 0.10.0", +] + [[package]] name = "impl-rlp" version = "0.3.0" @@ -3890,15 +4091,24 @@ dependencies = [ "serde", ] +[[package]] +name = "impl-serde" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b" +dependencies = [ + "serde", +] + [[package]] name = "impl-trait-for-tuples" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.96", ] [[package]] @@ -3933,9 +4143,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -3991,7 +4201,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.7", + "socket2 0.5.8", "widestring", "windows-sys 0.48.0", "winreg", @@ -3999,19 +4209,19 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi 0.4.0", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4049,9 +4259,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" @@ -4064,18 +4274,19 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "jsonrpsee" -version = "0.24.7" +version = "0.24.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5c71d8c1a731cc4227c2f698d377e7848ca12c8a48866fc5e6951c43a4db843" +checksum = "834af00800e962dee8f7bfc0f60601de215e73e78e5497d733a2919da837d3c8" dependencies = [ "jsonrpsee-core", "jsonrpsee-proc-macros", @@ -4087,51 +4298,51 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.24.7" +version = "0.24.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2882f6f8acb9fdaec7cefc4fd607119a9bd709831df7d7672a1d3b644628280" +checksum = "76637f6294b04e747d68e69336ef839a3493ca62b35bf488ead525f7da75c5bb" dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", "jsonrpsee-types", "parking_lot 0.12.3", "rand", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", ] [[package]] name = "jsonrpsee-proc-macros" -version = "0.24.7" +version = "0.24.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06c01ae0007548e73412c08e2285ffe5d723195bf268bce67b1b77c3bb2a14d" +checksum = "6fcae0c6c159e11541080f1f829873d8f374f81eda0abc67695a13fc8dc1a580" dependencies = [ "heck 0.5.0", "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "jsonrpsee-server" -version = "0.24.7" +version = "0.24.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82ad8ddc14be1d4290cd68046e7d1d37acd408efed6d3ca08aefcc3ad6da069c" +checksum = "66b7a3df90a1a60c3ed68e7ca63916b53e9afa928e33531e87f61a9c8e9ae87b" dependencies = [ "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.6.0", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", @@ -4140,7 +4351,7 @@ dependencies = [ "serde", "serde_json", "soketto", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tokio-util", @@ -4150,14 +4361,14 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.24.7" +version = "0.24.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a178c60086f24cc35bb82f57c651d0d25d99c4742b4d335de04e97fa1f08a8a1" +checksum = "ddb81adb1a5ae9182df379e374a79e24e992334e7346af4d065ae5b2acb8d4c6" dependencies = [ - "http 1.1.0", + "http 1.2.0", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -4236,25 +4447,25 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libp2p" @@ -4266,7 +4477,7 @@ dependencies = [ "either", "futures", "futures-timer", - "getrandom", + "getrandom 0.2.15", "instant", "libp2p-allow-block-list", "libp2p-connection-limits", @@ -4290,7 +4501,7 @@ dependencies = [ "multiaddr 0.18.2", "pin-project", "rw-stream-sink", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -4331,7 +4542,7 @@ dependencies = [ "libp2p-identity", "log", "multiaddr 0.18.2", - "multihash 0.19.2", + "multihash 0.19.3", "multistream-select", "once_cell", "parking_lot 0.12.3", @@ -4340,7 +4551,7 @@ dependencies = [ "rand", "rw-stream-sink", "smallvec", - "thiserror", + "thiserror 1.0.69", "unsigned-varint 0.7.2", "void", ] @@ -4380,24 +4591,24 @@ dependencies = [ "quick-protobuf", "quick-protobuf-codec", "smallvec", - "thiserror", + "thiserror 1.0.69", "void", ] [[package]] name = "libp2p-identity" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cca1eb2bc1fd29f099f3daaab7effd01e1a54b7c577d0ed082521034d912e8" +checksum = "257b5621d159b32282eac446bed6670c39c7dc68a200a992d8f056afa0066f6d" dependencies = [ "bs58 0.5.1", "ed25519-dalek", "hkdf", - "multihash 0.19.2", + "multihash 0.19.3", "quick-protobuf", "rand", "sha2 0.10.8", - "thiserror", + "thiserror 1.0.69", "tracing", "zeroize", ] @@ -4425,8 +4636,8 @@ dependencies = [ "rand", "sha2 0.10.8", "smallvec", - "thiserror", - "uint", + "thiserror 1.0.69", + "uint 0.9.5", "unsigned-varint 0.7.2", "void", ] @@ -4446,7 +4657,7 @@ dependencies = [ "log", "rand", "smallvec", - "socket2 0.5.7", + "socket2 0.5.8", "tokio", "trust-dns-proto 0.22.0", "void", @@ -4482,14 +4693,14 @@ dependencies = [ "libp2p-identity", "log", "multiaddr 0.18.2", - "multihash 0.19.2", + "multihash 0.19.3", "once_cell", "quick-protobuf", "rand", "sha2 0.10.8", "snow", "static_assertions", - "thiserror", + "thiserror 1.0.69", "x25519-dalek", "zeroize", ] @@ -4531,8 +4742,8 @@ dependencies = [ "rand", "ring 0.16.20", "rustls 0.21.12", - "socket2 0.5.7", - "thiserror", + "socket2 0.5.8", + "thiserror 1.0.69", "tokio", ] @@ -4587,7 +4798,7 @@ dependencies = [ "proc-macro-warning 0.4.2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -4603,7 +4814,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "log", - "socket2 0.5.7", + "socket2 0.5.8", "tokio", ] @@ -4621,7 +4832,7 @@ dependencies = [ "ring 0.16.20", "rustls 0.21.12", "rustls-webpki", - "thiserror", + "thiserror 1.0.69", "x509-parser 0.15.1", "yasna", ] @@ -4672,7 +4883,7 @@ dependencies = [ "pin-project-lite", "rw-stream-sink", "soketto", - "thiserror", + "thiserror 1.0.69", "url", "webpki-roots", ] @@ -4686,7 +4897,7 @@ dependencies = [ "futures", "libp2p-core", "log", - "thiserror", + "thiserror 1.0.69", "yamux", ] @@ -4696,9 +4907,9 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", ] [[package]] @@ -4777,9 +4988,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.20" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" dependencies = [ "cc", "pkg-config", @@ -4803,18 +5014,18 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linked_hash_set" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" +checksum = "bae85b5be22d9843c80e5fc80e9b64c8a3b1f98f867c709956eca3efff4e92e2" dependencies = [ "linked-hash-map", ] [[package]] name = "linregress" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de04dcecc58d366391f9920245b85ffa684558a5ef6e7736e754347c3aea9c2" +checksum = "a9eda9dcf4f2a99787827661f312ac3219292549c2ee992bf9a6248ffb066bf7" dependencies = [ "nalgebra", ] @@ -4827,9 +5038,9 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "lioness" @@ -4843,6 +5054,12 @@ dependencies = [ "keystream", ] +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "litep2p" version = "0.6.2" @@ -4857,7 +5074,7 @@ dependencies = [ "futures", "futures-timer", "hex-literal", - "indexmap 2.6.0", + "indexmap 2.7.1", "libc", "mockall 0.12.1", "multiaddr 0.17.1", @@ -4878,17 +5095,17 @@ dependencies = [ "simple-dns", "smallvec", "snow", - "socket2 0.5.7", + "socket2 0.5.8", "static_assertions", "str0m", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tokio-tungstenite", "tokio-util", "tracing", "trust-dns-resolver", - "uint", + "uint 0.9.5", "unsigned-varint 0.8.0", "url", "webpki", @@ -4910,9 +5127,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "lru" @@ -4943,9 +5160,9 @@ dependencies = [ [[package]] name = "lz4" -version = "1.28.0" +version = "1.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d1febb2b4a79ddd1980eede06a8f7902197960aa0383ffcfdd62fe723036725" +checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4" dependencies = [ "lz4-sys", ] @@ -4978,7 +5195,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -4992,7 +5209,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -5003,7 +5220,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -5014,7 +5231,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -5060,7 +5277,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.37", + "rustix 0.38.44", ] [[package]] @@ -5133,22 +5350,21 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -5173,7 +5389,7 @@ dependencies = [ "rand_chacha", "rand_distr", "subtle 2.6.1", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -5203,7 +5419,7 @@ dependencies = [ "fragile", "lazy_static", "mockall_derive 0.12.1", - "predicates 3.1.2", + "predicates 3.1.3", "predicates-tree", ] @@ -5228,7 +5444,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -5261,7 +5477,7 @@ dependencies = [ "data-encoding", "libp2p-identity", "multibase", - "multihash 0.19.2", + "multihash 0.19.3", "percent-encoding", "serde", "static_assertions", @@ -5316,9 +5532,9 @@ dependencies = [ [[package]] name = "multihash" -version = "0.19.2" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc41f430805af9d1cf4adae4ed2149c759b877b01d909a1f40256188d09345d2" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" dependencies = [ "core2", "unsigned-varint 0.8.0", @@ -5344,6 +5560,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + [[package]] name = "multistream-select" version = "0.13.0" @@ -5360,13 +5582,12 @@ dependencies = [ [[package]] name = "nalgebra" -version = "0.32.6" +version = "0.33.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" +checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" dependencies = [ "approx", "matrixmultiply", - "nalgebra-macros", "num-complex", "num-rational", "num-traits", @@ -5374,17 +5595,6 @@ dependencies = [ "typenum 1.17.0", ] -[[package]] -name = "nalgebra-macros" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "names" version = "0.14.0" @@ -5396,9 +5606,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" dependencies = [ "libc", "log", @@ -5426,21 +5636,20 @@ dependencies = [ [[package]] name = "netlink-packet-core" -version = "0.4.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" dependencies = [ "anyhow", "byteorder", - "libc", "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" -version = "0.12.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" dependencies = [ "anyhow", "bitflags 1.3.2", @@ -5459,29 +5668,28 @@ dependencies = [ "anyhow", "byteorder", "paste", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "netlink-proto" -version = "0.10.0" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" +checksum = "72452e012c2f8d612410d89eea01e2d9b56205274abb35d53f60200b2ec41d60" dependencies = [ "bytes", "futures", "log", "netlink-packet-core", "netlink-sys", - "thiserror", - "tokio", + "thiserror 2.0.11", ] [[package]] name = "netlink-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" +checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23" dependencies = [ "bytes", "futures", @@ -5498,15 +5706,15 @@ checksum = "a4a43439bf756eed340bdf8feba761e2d50c7d47175d87545cd5cbe4a137c4d1" dependencies = [ "cc", "libc", - "thiserror", + "thiserror 1.0.69", "winapi", ] [[package]] name = "nix" -version = "0.24.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", @@ -5597,7 +5805,8 @@ dependencies = [ "substrate-prometheus-endpoint", "subtensor-custom-rpc", "subtensor-custom-rpc-runtime-api", - "thiserror", + "subtensor-runtime-common", + "thiserror 1.0.69", ] [[package]] @@ -5605,7 +5814,6 @@ name = "node-subtensor-runtime" version = "4.0.0-dev" dependencies = [ "ark-serialize", - "ed25519-dalek", "fp-account", "fp-evm", "fp-rpc", @@ -5619,7 +5827,7 @@ dependencies = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", - "getrandom", + "getrandom 0.2.15", "hex", "log", "pallet-admin-utils", @@ -5641,7 +5849,7 @@ dependencies = [ "pallet-membership", "pallet-multisig", "pallet-preimage", - "pallet-proxy", + "pallet-proxy 38.0.0", "pallet-registry", "pallet-safe-mode", "pallet-scheduler", @@ -5650,7 +5858,7 @@ dependencies = [ "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", - "pallet-utility", + "pallet-utility 38.0.0", "parity-scale-codec", "precompile-utils", "rand_chacha", @@ -5676,6 +5884,8 @@ dependencies = [ "substrate-wasm-builder", "subtensor-custom-rpc-runtime-api", "subtensor-macros", + "subtensor-precompiles", + "subtensor-runtime-common", "tle", "w3f-bls", ] @@ -5842,7 +6052,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -5868,9 +6078,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -5895,12 +6105,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.1" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -5920,7 +6127,7 @@ version = "0.10.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "foreign-types", "libc", @@ -5937,20 +6144,20 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.4.0+3.4.0" +version = "300.4.1+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" +checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" dependencies = [ "cc", ] @@ -6315,6 +6522,23 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "pallet-proxy" +version = "38.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-utility 38.0.0", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "subtensor-macros", +] + [[package]] name = "pallet-proxy" version = "38.0.0" @@ -6346,6 +6570,20 @@ dependencies = [ "subtensor-macros", ] +[[package]] +name = "pallet-root-testing" +version = "14.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", +] + [[package]] name = "pallet-safe-mode" version = "19.0.0" @@ -6356,8 +6594,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "pallet-proxy", - "pallet-utility", + "pallet-proxy 38.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", + "pallet-utility 38.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", "parity-scale-codec", "scale-info", "sp-arithmetic", @@ -6424,7 +6662,7 @@ dependencies = [ "pallet-preimage", "pallet-scheduler", "pallet-transaction-payment", - "pallet-utility", + "pallet-utility 38.0.0", "parity-scale-codec", "parity-util-mem", "rand", @@ -6527,6 +6765,25 @@ dependencies = [ "sp-weights", ] +[[package]] +name = "pallet-utility" +version = "38.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-collective", + "pallet-root-testing", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "subtensor-macros", +] + [[package]] name = "pallet-utility" version = "38.0.0" @@ -6616,7 +6873,7 @@ dependencies = [ "lru 0.8.1", "parity-util-mem-derive", "parking_lot 0.12.3", - "primitive-types", + "primitive-types 0.12.2", "smallvec", "winapi", ] @@ -6687,7 +6944,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", "smallvec", "windows-targets 0.52.6", ] @@ -6748,20 +7005,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.13" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror", + "thiserror 2.0.11", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.13" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -6769,22 +7026,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.13" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "pest_meta" -version = "2.7.13" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", @@ -6798,34 +7055,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.6.0", + "indexmap 2.7.1", ] [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -6858,7 +7115,7 @@ dependencies = [ "libc", "log", "polkavm-assembler", - "polkavm-common", + "polkavm-common 0.9.0", "polkavm-linux-raw", ] @@ -6880,25 +7137,52 @@ dependencies = [ "log", ] +[[package]] +name = "polkavm-common" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ff33982a807d8567645d4784b9b5d7ab87bcb494f534a57cadd9012688e102" + [[package]] name = "polkavm-derive" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae8c4bea6f3e11cd89bb18bcdddac10bd9a24015399bd1c485ad68a985a19606" dependencies = [ - "polkavm-derive-impl-macro", + "polkavm-derive-impl-macro 0.9.0", +] + +[[package]] +name = "polkavm-derive" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2eb703f3b6404c13228402e98a5eae063fd16b8f58afe334073ec105ee4117e" +dependencies = [ + "polkavm-derive-impl-macro 0.18.0", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" +dependencies = [ + "polkavm-common 0.9.0", + "proc-macro2", + "quote", + "syn 2.0.96", ] [[package]] name = "polkavm-derive-impl" -version = "0.9.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" +checksum = "2f2116a92e6e96220a398930f4c8a6cda1264206f3e2034fc9982bfd93f261f7" dependencies = [ - "polkavm-common", + "polkavm-common 0.18.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -6907,8 +7191,18 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ - "polkavm-derive-impl", - "syn 2.0.90", + "polkavm-derive-impl 0.9.0", + "syn 2.0.96", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c16669ddc7433e34c1007d31080b80901e3e8e523cb9d4b441c3910cf9294b" +dependencies = [ + "polkavm-derive-impl 0.18.1", + "syn 2.0.96", ] [[package]] @@ -6921,7 +7215,7 @@ dependencies = [ "hashbrown 0.14.5", "log", "object 0.32.2", - "polkavm-common", + "polkavm-common 0.9.0", "regalloc2 0.9.3", "rustc-demangle", ] @@ -6934,15 +7228,15 @@ checksum = "26e85d3456948e650dff0cfc85603915847faf893ed1e66b020bb82ef4557120" [[package]] name = "polling" -version = "3.7.3" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.37", + "rustix 0.38.44", "tracing", "windows-sys 0.59.0", ] @@ -6972,9 +7266,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "powerfmt" @@ -7022,7 +7316,7 @@ source = "git+https://github.com/opentensor/frontier?rev=635bdac882#635bdac88233 dependencies = [ "case", "num_enum", - "prettyplease 0.2.22", + "prettyplease 0.2.29", "proc-macro2", "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", @@ -7045,9 +7339,9 @@ dependencies = [ [[package]] name = "predicates" -version = "3.1.2" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" dependencies = [ "anstyle", "predicates-core", @@ -7055,15 +7349,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" [[package]] name = "predicates-tree" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" dependencies = [ "predicates-core", "termtree", @@ -7081,12 +7375,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.22" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -7096,11 +7390,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", - "impl-codec", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", + "impl-serde 0.4.0", "scale-info", - "uint", + "uint 0.9.5", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "impl-codec 0.7.0", + "impl-num-traits", + "uint 0.10.0", ] [[package]] @@ -7109,7 +7415,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ - "thiserror", + "thiserror 1.0.69", "toml 0.5.11", ] @@ -7154,7 +7460,7 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -7165,14 +7471,14 @@ checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -7193,7 +7499,7 @@ dependencies = [ "quote", "regex", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -7207,7 +7513,7 @@ dependencies = [ "lazy_static", "memchr", "parking_lot 0.12.3", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -7230,7 +7536,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -7264,7 +7570,7 @@ dependencies = [ "itertools 0.10.5", "lazy_static", "log", - "multimap", + "multimap 0.8.3", "petgraph", "prettyplease 0.1.25", "prost 0.11.9", @@ -7285,14 +7591,14 @@ dependencies = [ "heck 0.5.0", "itertools 0.12.1", "log", - "multimap", + "multimap 0.10.0", "once_cell", "petgraph", - "prettyplease 0.2.22", + "prettyplease 0.2.29", "prost 0.12.6", "prost-types 0.12.6", "regex", - "syn 2.0.90", + "syn 2.0.96", "tempfile", ] @@ -7319,7 +7625,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -7342,24 +7648,24 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" +checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" dependencies = [ "cc", ] [[package]] name = "quanta" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" dependencies = [ "crossbeam-utils", "libc", "once_cell", "raw-cpuid", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "web-sys", "winapi", ] @@ -7388,7 +7694,7 @@ dependencies = [ "asynchronous-codec", "bytes", "quick-protobuf", - "thiserror", + "thiserror 1.0.69", "unsigned-varint 0.7.2", ] @@ -7404,7 +7710,7 @@ dependencies = [ "quinn-udp 0.3.2", "rustc-hash 1.1.0", "rustls 0.20.9", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "webpki", @@ -7423,7 +7729,7 @@ dependencies = [ "quinn-udp 0.4.1", "rustc-hash 1.1.0", "rustls 0.21.12", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -7440,7 +7746,7 @@ dependencies = [ "rustc-hash 1.1.0", "rustls 0.20.9", "slab", - "thiserror", + "thiserror 1.0.69", "tinyvec", "tracing", "webpki", @@ -7458,7 +7764,7 @@ dependencies = [ "rustc-hash 1.1.0", "rustls 0.21.12", "slab", - "thiserror", + "thiserror 1.0.69", "tinyvec", "tracing", ] @@ -7484,16 +7790,16 @@ checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ "bytes", "libc", - "socket2 0.5.7", + "socket2 0.5.8", "tracing", "windows-sys 0.48.0", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -7531,7 +7837,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -7555,11 +7861,11 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.2.0" +version = "11.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" +checksum = "c6928fa44c097620b706542d428957635951bade7143269085389d42c8a4927e" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -7611,11 +7917,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -7624,9 +7930,9 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -7646,7 +7952,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -7676,13 +7982,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -7697,9 +8003,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -7755,15 +8061,14 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.8" +version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", - "spin 0.9.8", "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -7828,16 +8133,19 @@ dependencies = [ [[package]] name = "rtnetlink" -version = "0.10.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" +checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" dependencies = [ "futures", "log", + "netlink-packet-core", "netlink-packet-route", + "netlink-packet-utils", "netlink-proto", + "netlink-sys", "nix", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -7865,9 +8173,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustc-hex" @@ -7890,7 +8198,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.23", + "semver 1.0.25", ] [[package]] @@ -7918,15 +8226,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "errno", "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", ] [[package]] @@ -7947,7 +8255,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.8", + "ring 0.17.13", "rustls-webpki", "sct", ] @@ -7979,15 +8287,15 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.8", + "ring 0.17.13", "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "rw-stream-sink" @@ -8002,9 +8310,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "safe-math" @@ -8026,9 +8334,9 @@ dependencies = [ [[package]] name = "safe_arch" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" dependencies = [ "bytemuck", ] @@ -8050,7 +8358,7 @@ dependencies = [ "log", "sp-core", "sp-wasm-interface 21.0.1", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -8125,7 +8433,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -8165,7 +8473,7 @@ dependencies = [ "sp-panic-handler", "sp-runtime", "sp-version", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -8243,7 +8551,7 @@ dependencies = [ "sp-runtime", "sp-state-machine", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -8272,7 +8580,7 @@ dependencies = [ "sp-keystore", "sp-runtime", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -8308,7 +8616,7 @@ dependencies = [ "sp-keystore", "sp-runtime", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -8365,7 +8673,7 @@ dependencies = [ "sp-keystore", "sp-runtime", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -8385,7 +8693,7 @@ dependencies = [ "sp-blockchain", "sp-core", "sp-runtime", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -8420,7 +8728,7 @@ dependencies = [ "sp-runtime", "sp-timestamp", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -8478,7 +8786,7 @@ dependencies = [ "sc-allocator", "sp-maybe-compressed-blob", "sp-wasm-interface 21.0.1", - "thiserror", + "thiserror 1.0.69", "wasm-instrument", ] @@ -8539,7 +8847,7 @@ dependencies = [ "sp-application-crypto", "sp-core", "sp-keystore", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -8568,7 +8876,7 @@ dependencies = [ "sp-keystore", "sp-mixnet", "sp-runtime", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -8613,7 +8921,7 @@ dependencies = [ "sp-core", "sp-runtime", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "unsigned-varint 0.7.2", @@ -8677,7 +8985,7 @@ dependencies = [ "sp-blockchain", "sp-core", "sp-runtime", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -8712,7 +9020,7 @@ dependencies = [ "sp-core", "sp-runtime", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", ] @@ -8747,9 +9055,9 @@ dependencies = [ "litep2p", "log", "multiaddr 0.18.2", - "multihash 0.19.2", + "multihash 0.19.3", "rand", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -8763,7 +9071,7 @@ dependencies = [ "fnv", "futures", "futures-timer", - "hyper 0.14.30", + "hyper 0.14.32", "hyper-rustls", "log", "num_cpus", @@ -8845,7 +9153,7 @@ dependencies = [ "sp-rpc", "sp-runtime", "sp-version", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -8857,9 +9165,9 @@ dependencies = [ "forwarded-header-value", "futures", "governor", - "http 1.1.0", + "http 1.2.0", "http-body-util", - "hyper 1.5.0", + "hyper 1.6.0", "ip_network", "jsonrpsee", "log", @@ -8899,7 +9207,7 @@ dependencies = [ "sp-rpc", "sp-runtime", "sp-version", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", ] @@ -8962,7 +9270,7 @@ dependencies = [ "static_init", "substrate-prometheus-endpoint", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "tracing-futures", @@ -8984,7 +9292,7 @@ name = "sc-sysinfo" version = "38.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" dependencies = [ - "derive_more", + "derive_more 0.99.18", "futures", "libc", "log", @@ -9016,7 +9324,7 @@ dependencies = [ "sc-utils", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "wasm-timer", ] @@ -9043,10 +9351,10 @@ dependencies = [ "sp-rpc", "sp-runtime", "sp-tracing 17.0.1", - "thiserror", + "thiserror 1.0.69", "tracing", "tracing-log", - "tracing-subscriber 0.3.18", + "tracing-subscriber 0.3.19", ] [[package]] @@ -9057,7 +9365,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -9084,7 +9392,7 @@ dependencies = [ "sp-tracing 17.0.1", "sp-transaction-pool", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9100,7 +9408,7 @@ dependencies = [ "sp-blockchain", "sp-core", "sp-runtime", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9134,7 +9442,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" dependencies = [ - "derive_more", + "derive_more 0.99.18", "parity-scale-codec", "scale-bits", "scale-type-resolver", @@ -9143,13 +9451,13 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.3" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "bitvec", "cfg-if", - "derive_more", + "derive_more 1.0.0", "parity-scale-codec", "scale-info-derive", "serde", @@ -9157,14 +9465,14 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.3" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.96", ] [[package]] @@ -9175,18 +9483,18 @@ checksum = "f0cded6518aa0bd6c1be2b88ac81bf7044992f0f154bfbabd5ad34f43512abcb" [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "schnellru" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" +checksum = "356285bbf17bea63d9e52e96bd18f039672ac92b55b8cb997d6162a2a37d1649" dependencies = [ "ahash 0.8.11", "cfg-if", @@ -9230,7 +9538,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.8", + "ring 0.17.13", "untrusted 0.9.0", ] @@ -9246,7 +9554,7 @@ dependencies = [ "log", "rand", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9297,7 +9605,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "core-foundation", "core-foundation-sys", "libc", @@ -9306,9 +9614,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -9334,9 +9642,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" dependencies = [ "serde", ] @@ -9355,9 +9663,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -9392,20 +9700,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -9459,7 +9767,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -9583,9 +9891,9 @@ dependencies = [ [[package]] name = "simba" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" dependencies = [ "approx", "num-complex", @@ -9600,7 +9908,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cae9a3fcdadafb6d97f4c0e007e4247b114ee0f119f650c3cbf3a8b3a1479694" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -9653,7 +9961,7 @@ dependencies = [ "chacha20poly1305", "curve25519-dalek", "rand_core", - "ring 0.17.8", + "ring 0.17.13", "rustc_version 0.4.1", "sha2 0.10.8", "subtle 2.6.1", @@ -9671,9 +9979,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -9681,14 +9989,14 @@ dependencies = [ [[package]] name = "soketto" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37468c595637c10857701c990f93a40ce0e357cedb0953d1c26c8d8027f9bb53" +checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" dependencies = [ "base64 0.22.1", "bytes", "futures", - "http 1.1.0", + "http 1.2.0", "httparse", "log", "rand", @@ -9714,7 +10022,7 @@ dependencies = [ "sp-state-machine", "sp-trie", "sp-version", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9728,7 +10036,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -9791,7 +10099,7 @@ dependencies = [ "sp-database", "sp-runtime", "sp-state-machine", - "thiserror", + "thiserror 1.0.69", "tracing", ] @@ -9807,7 +10115,7 @@ dependencies = [ "sp-inherents", "sp-runtime", "sp-state-machine", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9887,7 +10195,7 @@ dependencies = [ "futures", "hash-db", "hash256-std-hasher", - "impl-serde", + "impl-serde 0.4.0", "itertools 0.11.0", "k256", "libsecp256k1", @@ -9897,7 +10205,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "paste", - "primitive-types", + "primitive-types 0.12.2", "rand", "scale-info", "schnorrkel", @@ -9912,7 +10220,7 @@ dependencies = [ "sp-storage 21.0.0", "ss58-registry", "substrate-bip39", - "thiserror", + "thiserror 1.0.69", "tracing", "w3f-bls", "zeroize", @@ -9921,7 +10229,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" -source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" +source = "git+https://github.com/paritytech/polkadot-sdk#80e30ec3cdccae8e9099bd67840ff8737b043496" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -9992,7 +10300,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable dependencies = [ "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -10011,23 +10319,23 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" +source = "git+https://github.com/paritytech/polkadot-sdk#80e30ec3cdccae8e9099bd67840ff8737b043496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" +source = "git+https://github.com/paritytech/polkadot-sdk#80e30ec3cdccae8e9099bd67840ff8737b043496" dependencies = [ "environmental", "parity-scale-codec", @@ -10066,7 +10374,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -10080,7 +10388,7 @@ dependencies = [ "libsecp256k1", "log", "parity-scale-codec", - "polkavm-derive", + "polkavm-derive 0.9.1", "rustversion", "secp256k1", "sp-core", @@ -10121,7 +10429,7 @@ name = "sp-maybe-compressed-blob" version = "11.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" dependencies = [ - "thiserror", + "thiserror 1.0.69", "zstd 0.12.4", ] @@ -10205,13 +10513,13 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" +source = "git+https://github.com/paritytech/polkadot-sdk#80e30ec3cdccae8e9099bd67840ff8737b043496" dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", - "polkavm-derive", - "primitive-types", + "polkavm-derive 0.18.0", + "primitive-types 0.13.1", "sp-externalities 0.25.0", "sp-runtime-interface-proc-macro 17.0.0", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk)", @@ -10229,8 +10537,8 @@ dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", - "polkavm-derive", - "primitive-types", + "polkavm-derive 0.9.1", + "primitive-types 0.12.2", "sp-externalities 0.29.0", "sp-runtime-interface-proc-macro 18.0.0", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", @@ -10243,14 +10551,14 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" +source = "git+https://github.com/paritytech/polkadot-sdk#80e30ec3cdccae8e9099bd67840ff8737b043496" dependencies = [ "Inflector", "expander", "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -10263,7 +10571,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -10308,7 +10616,7 @@ dependencies = [ "sp-externalities 0.29.0", "sp-panic-handler", "sp-trie", - "thiserror", + "thiserror 1.0.69", "tracing", "trie-db", ] @@ -10333,7 +10641,7 @@ dependencies = [ "sp-externalities 0.29.0", "sp-runtime", "sp-runtime-interface 28.0.0", - "thiserror", + "thiserror 1.0.69", "x25519-dalek", ] @@ -10345,14 +10653,14 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" +source = "git+https://github.com/paritytech/polkadot-sdk#80e30ec3cdccae8e9099bd67840ff8737b043496" [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" +source = "git+https://github.com/paritytech/polkadot-sdk#80e30ec3cdccae8e9099bd67840ff8737b043496" dependencies = [ - "impl-serde", + "impl-serde 0.5.0", "parity-scale-codec", "ref-cast", "serde", @@ -10364,7 +10672,7 @@ name = "sp-storage" version = "21.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" dependencies = [ - "impl-serde", + "impl-serde 0.4.0", "parity-scale-codec", "ref-cast", "serde", @@ -10380,18 +10688,18 @@ dependencies = [ "parity-scale-codec", "sp-inherents", "sp-runtime", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" +source = "git+https://github.com/paritytech/polkadot-sdk#80e30ec3cdccae8e9099bd67840ff8737b043496" dependencies = [ "parity-scale-codec", "tracing", "tracing-core", - "tracing-subscriber 0.3.18", + "tracing-subscriber 0.3.19", ] [[package]] @@ -10402,7 +10710,7 @@ dependencies = [ "parity-scale-codec", "tracing", "tracing-core", - "tracing-subscriber 0.3.18", + "tracing-subscriber 0.3.19", ] [[package]] @@ -10445,7 +10753,7 @@ dependencies = [ "schnellru", "sp-core", "sp-externalities 0.29.0", - "thiserror", + "thiserror 1.0.69", "tracing", "trie-db", "trie-root", @@ -10456,7 +10764,7 @@ name = "sp-version" version = "37.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" dependencies = [ - "impl-serde", + "impl-serde 0.4.0", "parity-scale-codec", "parity-wasm", "scale-info", @@ -10465,7 +10773,7 @@ dependencies = [ "sp-runtime", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", "sp-version-proc-macro", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -10476,13 +10784,13 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" +source = "git+https://github.com/paritytech/polkadot-sdk#80e30ec3cdccae8e9099bd67840ff8737b043496" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -10550,21 +10858,11 @@ dependencies = [ "der", ] -[[package]] -name = "sqlformat" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" -dependencies = [ - "nom", - "unicode_categories", -] - [[package]] name = "sqlx" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" +checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f" dependencies = [ "sqlx-core", "sqlx-macros", @@ -10573,37 +10871,31 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" +checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0" dependencies = [ - "atoi", - "byteorder", "bytes", "crc", "crossbeam-queue", "either", - "event-listener 5.3.1", - "futures-channel", + "event-listener 5.4.0", "futures-core", "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.14.5", - "hashlink 0.9.1", - "hex", - "indexmap 2.6.0", + "hashbrown 0.15.2", + "hashlink 0.10.0", + "indexmap 2.7.1", "log", "memchr", "native-tls", "once_cell", - "paste", "percent-encoding", "serde", "sha2 0.10.8", "smallvec", - "sqlformat", - "thiserror", + "thiserror 2.0.11", "tokio", "tokio-stream", "tracing", @@ -10612,22 +10904,22 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" +checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "sqlx-macros-core" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" +checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad" dependencies = [ "dotenvy", "either", @@ -10641,7 +10933,7 @@ dependencies = [ "sha2 0.10.8", "sqlx-core", "sqlx-sqlite", - "syn 2.0.90", + "syn 2.0.96", "tempfile", "tokio", "url", @@ -10649,9 +10941,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" +checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" dependencies = [ "atoi", "flume", @@ -10672,9 +10964,9 @@ dependencies = [ [[package]] name = "ss58-registry" -version = "1.50.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43fce22ed1df64d04b262351c8f9d5c6da4f76f79f25ad15529792f893fad25d" +checksum = "19409f13998e55816d1c728395af0b52ec066206341d939e22e7766df9b494b8" dependencies = [ "Inflector", "num-format", @@ -10733,9 +11025,9 @@ dependencies = [ [[package]] name = "static_init_macro" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" +checksum = "1389c88ddd739ec6d3f8f83343764a0e944cd23cfbf126a9796a714b0b6edd6f" dependencies = [ "cfg_aliases", "memchr", @@ -10760,7 +11052,7 @@ dependencies = [ "sctp-proto", "serde", "sha-1", - "thiserror", + "thiserror 1.0.69", "tracing", ] @@ -10808,7 +11100,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -10865,11 +11157,11 @@ version = "0.17.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" dependencies = [ "http-body-util", - "hyper 1.5.0", + "hyper 1.6.0", "hyper-util", "log", "prometheus", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -10912,7 +11204,7 @@ dependencies = [ "quote", "rayon", "subtensor-linting", - "syn 2.0.90", + "syn 2.0.96", "walkdir", ] @@ -10950,7 +11242,7 @@ dependencies = [ "proc-macro2", "procedural-fork", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -10960,7 +11252,42 @@ dependencies = [ "ahash 0.8.11", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", +] + +[[package]] +name = "subtensor-precompiles" +version = "0.1.0" +dependencies = [ + "ed25519-dalek", + "fp-evm", + "frame-support", + "frame-system", + "log", + "pallet-admin-utils", + "pallet-balances", + "pallet-evm", + "pallet-evm-precompile-modexp", + "pallet-evm-precompile-sha3fips", + "pallet-evm-precompile-simple", + "pallet-proxy 38.0.0", + "pallet-subtensor", + "precompile-utils", + "sp-core", + "sp-runtime", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", + "subtensor-runtime-common", +] + +[[package]] +name = "subtensor-runtime-common" +version = "0.1.0" +dependencies = [ + "frame-support", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", ] [[package]] @@ -10969,7 +11296,7 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", - "semver 1.0.23", + "semver 1.0.25", "toml_edit", ] @@ -10998,9 +11325,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -11027,25 +11354,25 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.8.0", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -11065,14 +11392,15 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.13.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", + "getrandom 0.3.1", "once_cell", - "rustix 0.38.37", + "rustix 0.38.44", "windows-sys 0.59.0", ] @@ -11087,38 +11415,58 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ - "rustix 0.38.37", + "rustix 0.38.44", "windows-sys 0.59.0", ] [[package]] name = "termtree" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -11158,9 +11506,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -11179,9 +11527,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -11196,11 +11544,21 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -11241,9 +11599,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.40.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -11252,20 +11610,20 @@ dependencies = [ "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.7", + "socket2 0.5.8", "tokio-macros", "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -11280,9 +11638,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -11307,9 +11665,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -11355,7 +11713,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", @@ -11383,9 +11741,9 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "bytes", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", "pin-project-lite", @@ -11407,9 +11765,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -11419,20 +11777,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -11470,9 +11828,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -11528,7 +11886,7 @@ dependencies = [ "rand", "smallvec", "socket2 0.4.10", - "thiserror", + "thiserror 1.0.69", "tinyvec", "tokio", "tracing", @@ -11553,7 +11911,7 @@ dependencies = [ "once_cell", "rand", "smallvec", - "thiserror", + "thiserror 1.0.69", "tinyvec", "tokio", "tracing", @@ -11575,7 +11933,7 @@ dependencies = [ "rand", "resolv-conf", "smallvec", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "trust-dns-proto 0.23.2", @@ -11608,7 +11966,7 @@ dependencies = [ "rand", "rustls 0.21.12", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", ] @@ -11658,17 +12016,29 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-bidi" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-normalization" @@ -11686,16 +12056,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] -name = "unicode-xid" -version = "0.2.6" +name = "unicode-width" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] -name = "unicode_categories" -version = "0.1.1" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -11743,12 +12113,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna 1.0.3", "percent-encoding", ] @@ -11758,6 +12128,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -11766,9 +12148,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -11808,7 +12190,7 @@ dependencies = [ "rand_core", "sha2 0.10.8", "sha3", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -11837,49 +12219,59 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11887,22 +12279,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-instrument" @@ -11924,7 +12319,7 @@ dependencies = [ "strum 0.24.1", "strum_macros 0.24.3", "tempfile", - "thiserror", + "thiserror 1.0.69", "wasm-opt-cxx-sys", "wasm-opt-sys", ] @@ -12051,7 +12446,7 @@ dependencies = [ "log", "object 0.30.4", "target-lexicon", - "thiserror", + "thiserror 1.0.69", "wasmparser", "wasmtime-cranelift-shared", "wasmtime-environ", @@ -12086,7 +12481,7 @@ dependencies = [ "object 0.30.4", "serde", "target-lexicon", - "thiserror", + "thiserror 1.0.69", "wasmparser", "wasmtime-types", ] @@ -12169,15 +12564,15 @@ checksum = "a4f6fffd2a1011887d57f07654dd112791e872e3ff4a2e626aee8059ee17f06f" dependencies = [ "cranelift-entity", "serde", - "thiserror", + "thiserror 1.0.69", "wasmparser", ] [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -12189,7 +12584,7 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.17.8", + "ring 0.17.13", "untrusted 0.9.0", ] @@ -12208,14 +12603,14 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.37", + "rustix 0.38.44", ] [[package]] name = "wide" -version = "0.7.28" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690" +checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22" dependencies = [ "bytemuck", "safe_arch", @@ -12260,28 +12655,38 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.51.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538" dependencies = [ - "windows-core 0.51.1", - "windows-targets 0.48.5", + "windows-core 0.53.0", + "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.52.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "9dcc5b895a6377f1ab9fa55acedab1fd5ac0db66ad1e6c7f47e28a22e446a5dd" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ "windows-targets 0.52.6", ] @@ -12517,9 +12922,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "ad699df48212c6cc6eb4435f35500ac6fd3b9913324f938aea302022ce19d310" dependencies = [ "memchr", ] @@ -12534,6 +12939,27 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyz" version = "0.5.1" @@ -12568,7 +12994,7 @@ dependencies = [ "nom", "oid-registry 0.6.1", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -12585,7 +13011,7 @@ dependencies = [ "nom", "oid-registry 0.7.1", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -12597,14 +13023,14 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] name = "xml-rs" -version = "0.8.22" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" +checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" [[package]] name = "xmltree" @@ -12639,6 +13065,30 @@ dependencies = [ "time", ] +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure 0.13.1", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -12657,7 +13107,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure 0.13.1", ] [[package]] @@ -12677,7 +13148,29 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ef5d64b0d6..15dd760a57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,21 +23,14 @@ rayon = "1.10" [workspace] members = [ - "node", - "pallets/commitments", - "pallets/subtensor", - "pallets/admin-utils", - "pallets/collective", - "pallets/registry", - "primitives/*", - "runtime", - "support/tools", - "support/macros", - "support/linting", - "support/procedural-fork", - "pallets/drand", + "common", + "node", + "pallets/*", + "precompiles", + "primitives/*", + "runtime", + "support/*", ] -exclude = ["support/procedural-fork"] resolver = "2" [workspace.lints.clippy] @@ -46,14 +39,29 @@ arithmetic-side-effects = "deny" type_complexity = "allow" unwrap-used = "deny" manual_inspect = "allow" -useless_conversion = "allow" # until polkadot is patched +useless_conversion = "allow" # until polkadot is patched [workspace.dependencies] +pallet-admin-utils = { default-features = false, path = "pallets/admin-utils" } +pallet-collective = { default-features = false, path = "pallets/collective" } +pallet-commitments = { default-features = false, path = "pallets/commitments" } +pallet-registry = { default-features = false, path = "pallets/registry" } +pallet-subtensor = { default-features = false, path = "pallets/subtensor" } +subtensor-custom-rpc = { default-features = false, path = "pallets/subtensor/rpc" } +subtensor-custom-rpc-runtime-api = { default-features = false, path = "pallets/subtensor/runtime-api" } +subtensor-precompiles = { default-features = false, path = "precompiles" } +subtensor-runtime-common = { default-features = false, path = "common" } +node-subtensor-runtime = { default-features = false, path = "runtime" } + async-trait = "0.1" cargo-husky = { version = "1", default-features = false } clap = "4.5.4" -codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -ed25519-dalek = { version = "2.1.0", default-features = false, features = ["alloc"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ + "derive", +] } +ed25519-dalek = { version = "2.1.0", default-features = false, features = [ + "alloc", +] } enumflags2 = "0.7.9" futures = "0.3.30" hex = { version = "0.4", default-features = false } @@ -64,7 +72,9 @@ memmap2 = "0.9.4" ndarray = { version = "0.15.6", default-features = false } parity-util-mem = "0.12.0" rand = "0.8.5" -scale-codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = ["derive"] } +scale-codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = [ + "derive", +] } scale-info = { version = "2.11.2", default-features = false } serde = { version = "1.0.214", default-features = false } serde-tuple-vec-map = { version = "1.0.1", default-features = false } @@ -74,11 +84,11 @@ serde_with = { version = "=2.0.0", default-features = false } smallvec = "1.13.2" litep2p = { git = "https://github.com/paritytech/litep2p", tag = "v0.7.0" } syn = { version = "2.0.87", features = [ - "full", - "visit-mut", - "visit", - "extra-traits", - "parsing", + "full", + "visit-mut", + "visit", + "extra-traits", + "parsing", ] } quote = "1" proc-macro2 = { version = "1", features = ["span-locations"] } @@ -105,7 +115,7 @@ pallet-insecure-randomness-collective-flip = { git = "https://github.com/parityt pallet-membership = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } -pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } +pallet-proxy = { path = "pallets/proxy", default-features = false } pallet-safe-mode = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } pallet-scheduler = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } @@ -113,7 +123,8 @@ pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409" } pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } -pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } +pallet-utility = { path = "pallets/utility", default-features = false } +pallet-root-testing = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409" } sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409" } @@ -181,7 +192,9 @@ fc-consensus = { git = "https://github.com/opentensor/frontier", rev = "635bdac8 fp-consensus = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false } fp-dynamic-fee = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false } fc-api = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false } -fc-rpc = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false, features = ["rpc-binary-search-estimate"]} +fc-rpc = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false, features = [ + "rpc-binary-search-estimate", +] } fc-rpc-core = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false } fc-mapping-sync = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false } precompile-utils = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false } @@ -199,21 +212,34 @@ pallet-hotfix-sufficients = { git = "https://github.com/opentensor/frontier", re #DRAND pallet-drand = { path = "pallets/drand", default-features = false } -sp-crypto-ec-utils = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", features = ["bls12-381"] } -getrandom = { version = "0.2.15", features = ["custom"], default-features = false } -sp-keystore = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } +sp-crypto-ec-utils = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", features = [ + "bls12-381", +] } +getrandom = { version = "0.2.15", features = [ + "custom", +], default-features = false } +sp-keystore = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } w3f-bls = { version = "=0.1.3", default-features = false } -ark-crypto-primitives = { version = "0.4.0", default-features = false, features = [ "r1cs", "snark" ] } -ark-scale = { version = "0.0.11", default-features = false, features = ["hazmat"] } +ark-crypto-primitives = { version = "0.4.0", default-features = false, features = [ + "r1cs", + "snark", +] } +ark-scale = { version = "0.0.11", default-features = false, features = [ + "hazmat", +] } sp-ark-bls12-381 = { git = "https://github.com/paritytech/substrate-curves", default-features = false } -ark-bls12-381 = { version = "0.4.0", features = ["curve"], default-features = false } -ark-serialize = { version = "0.4.0", features = [ "derive" ], default-features = false } +ark-bls12-381 = { version = "0.4.0", features = [ + "curve", +], default-features = false } +ark-serialize = { version = "0.4.0", features = [ + "derive", +], default-features = false } ark-ff = { version = "0.4.0", default-features = false } ark-ec = { version = "0.4.0", default-features = false } ark-std = { version = "0.4.0", default-features = false } anyhow = "1.0.81" sha2 = { version = "0.10.8", default-features = false } -rand_chacha = { version = "0.3.1", default-features = false } +rand_chacha = { version = "0.3.1", default-features = false } tle = { git = "https://github.com/ideal-lab5/timelock", rev = "5416406cfd32799e31e1795393d4916894de4468", default-features = false } frame-metadata = "16" @@ -232,11 +258,11 @@ codegen-units = 1 [features] default = [] try-runtime = [ - "node-subtensor/try-runtime", - "node-subtensor-runtime/try-runtime", + "node-subtensor/try-runtime", + "node-subtensor-runtime/try-runtime", ] runtime-benchmarks = [ - "node-subtensor/runtime-benchmarks", - "node-subtensor-runtime/runtime-benchmarks", + "node-subtensor/runtime-benchmarks", + "node-subtensor-runtime/runtime-benchmarks", ] metadata-hash = ["node-subtensor-runtime/metadata-hash"] diff --git a/Dockerfile b/Dockerfile index edceab0e8c..2439d5b2a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG BASE_IMAGE=ubuntu:24.04 +ARG BASE_IMAGE=ubuntu:latest FROM $BASE_IMAGE AS builder SHELL ["/bin/bash", "-c"] @@ -18,18 +18,22 @@ RUN apt-get update && \ apt-get install -y curl build-essential protobuf-compiler clang git pkg-config libssl-dev && \ rm -rf /var/lib/apt/lists/* -RUN set -o pipefail && curl https://sh.rustup.rs -sSf | sh -s -- -y -ENV PATH="/root/.cargo/bin:${PATH}" -RUN rustup update stable -RUN rustup target add wasm32-unknown-unknown --toolchain stable - # Copy entire repository COPY . /build WORKDIR /build +# Install Rust +RUN set -o pipefail && curl https://sh.rustup.rs -sSf | sh -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" +RUN rustup toolchain install +RUN rustup target add wasm32-unknown-unknown + # Build the project RUN cargo build -p node-subtensor --profile production --features="metadata-hash" --locked +# Slim down image +RUN rm -rf /root/.cargo + # Verify the binary was produced RUN test -e /build/target/production/node-subtensor diff --git a/build.rs b/build.rs index 1abd7456b4..854778873e 100644 --- a/build.rs +++ b/build.rs @@ -31,6 +31,7 @@ fn main() { // Parse each rust file with syn and run the linting suite on it in parallel rust_files.par_iter().for_each_with(tx.clone(), |tx, file| { + let is_test = file.display().to_string().contains("test"); let Ok(content) = fs::read_to_string(file) else { return; }; @@ -63,6 +64,10 @@ fn main() { track_lint(ForbidKeysRemoveCall::lint(&parsed_file)); track_lint(RequireFreezeStruct::lint(&parsed_file)); track_lint(RequireExplicitPalletIndex::lint(&parsed_file)); + + if is_test { + track_lint(ForbidSaturatingMath::lint(&parsed_file)); + } }); // Collect and print all errors after the parallel processing is done diff --git a/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 0000000000..d0b43cdc1b --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "subtensor-runtime-common" +version = "0.1.0" +edition = "2024" +authors = ["Opentensor Foundation "] +homepage = "https://opentensor.ai/" +publish = false +repository = "https://github.com/opentensor/subtensor/" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +frame-support = { workspace = true } +scale-info = { workspace = true } +sp-runtime = { workspace = true } +sp-core = { workspace = true } + +[lints] +workspace = true + +[features] +default = ["std"] +fast-blocks = [] +std = [ + "codec/std", + "frame-support/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", +] diff --git a/common/src/lib.rs b/common/src/lib.rs new file mode 100644 index 0000000000..75b18e3b14 --- /dev/null +++ b/common/src/lib.rs @@ -0,0 +1,86 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{ + MultiSignature, + traits::{IdentifyAccount, Verify}, +}; + +/// Balance of an account. +pub type Balance = u64; + +/// An index to a block. +pub type BlockNumber = u32; + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent to the +/// public key of our transaction signing scheme. +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +/// Index of a transaction in the chain. +pub type Index = u32; + +/// A hash of some data used by the chain. +pub type Hash = sp_core::H256; + +pub type Nonce = u32; + +/// Transfers below SMALL_TRANSFER_LIMIT are considered small transfers +pub const SMALL_TRANSFER_LIMIT: Balance = 500_000_000; // 0.5 TAO + +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, MaxEncodedLen, TypeInfo, +)] +pub enum ProxyType { + Any, + Owner, // Subnet owner Calls + NonCritical, + NonTransfer, + Senate, + NonFungibile, // Nothing involving moving TAO + Triumvirate, + Governance, // Both above governance + Staking, + Registration, + Transfer, + SmallTransfer, + RootWeights, + ChildKeys, + SudoUncheckedSetCode, +} + +impl Default for ProxyType { + // allow all Calls; required to be most permissive + fn default() -> Self { + Self::Any + } +} + +pub mod time { + use super::*; + + /// This determines the average expected block time that we are targeting. Blocks will be + /// produced at a minimum duration defined by `SLOT_DURATION`. `SLOT_DURATION` is picked up by + /// `pallet_timestamp` which is in turn picked up by `pallet_aura` to implement `fn + /// slot_duration()`. + /// + /// Change this to adjust the block time. + #[cfg(not(feature = "fast-blocks"))] + pub const MILLISECS_PER_BLOCK: u64 = 12000; + + /// Fast blocks for development + #[cfg(feature = "fast-blocks")] + pub const MILLISECS_PER_BLOCK: u64 = 250; + + // NOTE: Currently it is not possible to change the slot duration after the chain has started. + // Attempting to do so will brick block production. + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + + // Time is measured by number of blocks. + pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); + pub const HOURS: BlockNumber = MINUTES * 60; + pub const DAYS: BlockNumber = HOURS * 24; +} diff --git a/evm-tests/.gitignore b/evm-tests/.gitignore new file mode 100644 index 0000000000..661f94a6e0 --- /dev/null +++ b/evm-tests/.gitignore @@ -0,0 +1,3 @@ +node_modules +.papi +.env diff --git a/evm-tests/README.md b/evm-tests/README.md new file mode 100644 index 0000000000..7d01034bd8 --- /dev/null +++ b/evm-tests/README.md @@ -0,0 +1,27 @@ +# type-test + +test with ts + +## polkadot api + +```bash +npx papi add devnet -w ws://10.0.0.11:9944 +``` + +## get the new metadata + +```bash +sh get-metadata.sh +``` + +## run all tests + +```bash +yarn run test +``` + +## To run a particular test case, you can pass an argument with the name or part of the name. For example: + +```bash +yarn run test -- -g "Can set subnet parameter" +``` diff --git a/evm-tests/get-metadata.sh b/evm-tests/get-metadata.sh new file mode 100644 index 0000000000..6d7727009d --- /dev/null +++ b/evm-tests/get-metadata.sh @@ -0,0 +1,3 @@ +rm -rf .papi +npx papi add devnet -w ws://localhost:9944 + diff --git a/evm-tests/local.test.ts b/evm-tests/local.test.ts new file mode 100644 index 0000000000..9eb24d4327 --- /dev/null +++ b/evm-tests/local.test.ts @@ -0,0 +1,53 @@ +import * as assert from "assert"; +import { getAliceSigner, getClient, getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { SUB_LOCAL_URL, } from "../src/config"; +import { devnet } from "@polkadot-api/descriptors" +import { PolkadotSigner, TypedApi } from "polkadot-api"; +import { convertPublicKeyToSs58, convertH160ToSS58 } from "../src/address-utils" +import { ethers } from "ethers" +import { INEURON_ADDRESS, INeuronABI } from "../src/contracts/neuron" +import { generateRandomEthersWallet } from "../src/utils" +import { forceSetBalanceToEthAddress, forceSetBalanceToSs58Address, addNewSubnetwork, burnedRegister } from "../src/subtensor" + +describe("Test neuron precompile Serve Axon Prometheus", () => { + // init eth part + // const wallet1 = generateRandomEthersWallet(); + // const wallet2 = generateRandomEthersWallet(); + // const wallet3 = generateRandomEthersWallet(); + + // init substrate part + + // const coldkey = getRandomSubstrateKeypair(); + + let api: TypedApi + + // sudo account alice as signer + let alice: PolkadotSigner; + before(async () => { + // init variables got from await and async + const subClient = await getClient(SUB_LOCAL_URL) + api = await getDevnetApi() + // alice = await getAliceSigner(); + + // await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) + // await forceSetBalanceToEthAddress(api, wallet1.address) + // await forceSetBalanceToEthAddress(api, wallet2.address) + // await forceSetBalanceToEthAddress(api, wallet3.address) + + + let index = 0; + while (index < 30) { + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) + let netuid = await addNewSubnetwork(api, hotkey, coldkey) + } + + + }) + + it("Serve Axon", async () => { + + }); +}); \ No newline at end of file diff --git a/evm-tests/package.json b/evm-tests/package.json new file mode 100644 index 0000000000..a96a2c4a0c --- /dev/null +++ b/evm-tests/package.json @@ -0,0 +1,31 @@ +{ + "scripts": { + "test": "mocha --timeout 999999 --require ts-node/register test/*test.ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@polkadot-api/descriptors": "file:.papi/descriptors", + "@polkadot-labs/hdkd": "^0.0.10", + "@polkadot-labs/hdkd-helpers": "^0.0.11", + "@polkadot/api": "15.1.1", + "crypto": "^1.0.1", + "dotenv": "16.4.7", + "ethers": "^6.13.5", + "polkadot-api": "^1.9.5", + "viem": "2.23.4" + }, + "devDependencies": { + "@types/bun": "^1.1.13", + "@types/chai": "^5.0.1", + "@types/mocha": "^10.0.10", + "assert": "^2.1.0", + "chai": "^5.2.0", + "mocha": "^11.1.0", + "prettier": "^3.3.3", + "ts-node": "^10.9.2", + "typescript": "^5.7.2", + "vite": "^5.4.8" + } +} diff --git a/evm-tests/src/address-utils.ts b/evm-tests/src/address-utils.ts new file mode 100644 index 0000000000..ed3abc5008 --- /dev/null +++ b/evm-tests/src/address-utils.ts @@ -0,0 +1,77 @@ +import { Address } from "viem" +import { encodeAddress } from "@polkadot/util-crypto"; +import { ss58Address } from "@polkadot-labs/hdkd-helpers"; +import { hexToU8a } from "@polkadot/util"; +import { blake2AsU8a, decodeAddress } from "@polkadot/util-crypto"; +import { Binary } from "polkadot-api"; +import { SS58_PREFIX } from "./config" + +export function toViemAddress(address: string): Address { + let addressNoPrefix = address.replace("0x", "") + return `0x${addressNoPrefix}` +} + +export function convertH160ToSS58(ethAddress: string) { + // get the public key + const hash = convertH160ToPublicKey(ethAddress); + + // Convert the hash to SS58 format + const ss58Address = encodeAddress(hash, SS58_PREFIX); + return ss58Address; +} + +export function convertPublicKeyToSs58(publickey: Uint8Array) { + return ss58Address(publickey, SS58_PREFIX); +} + +export function convertH160ToPublicKey(ethAddress: string) { + const prefix = "evm:"; + const prefixBytes = new TextEncoder().encode(prefix); + const addressBytes = hexToU8a( + ethAddress.startsWith("0x") ? ethAddress : `0x${ethAddress}` + ); + const combined = new Uint8Array(prefixBytes.length + addressBytes.length); + + // Concatenate prefix and Ethereum address + combined.set(prefixBytes); + combined.set(addressBytes, prefixBytes.length); + + // Hash the combined data (the public key) + const hash = blake2AsU8a(combined); + return hash; +} + +export function ss58ToEthAddress(ss58Address: string) { + // Decode the SS58 address to a Uint8Array public key + const publicKey = decodeAddress(ss58Address); + + // Take the first 20 bytes of the hashed public key for the Ethereum address + const ethereumAddressBytes = publicKey.slice(0, 20); + + // Convert the 20 bytes into an Ethereum H160 address format (Hex string) + const ethereumAddress = '0x' + Buffer.from(ethereumAddressBytes).toString('hex'); + + return ethereumAddress; +} + +export function ss58ToH160(ss58Address: string): Binary { + // Decode the SS58 address to a Uint8Array public key + const publicKey = decodeAddress(ss58Address); + + // Take the first 20 bytes of the hashed public key for the Ethereum address + const ethereumAddressBytes = publicKey.slice(0, 20); + + + return new Binary(ethereumAddressBytes); +} + +export function ethAddressToH160(ethAddress: string): Binary { + // Decode the SS58 address to a Uint8Array public key + const publicKey = hexToU8a(ethAddress); + + // Take the first 20 bytes of the hashed public key for the Ethereum address + // const ethereumAddressBytes = publicKey.slice(0, 20); + + + return new Binary(publicKey); +} \ No newline at end of file diff --git a/evm-tests/src/balance-math.ts b/evm-tests/src/balance-math.ts new file mode 100644 index 0000000000..8d6e86bd5a --- /dev/null +++ b/evm-tests/src/balance-math.ts @@ -0,0 +1,26 @@ +import assert from "assert" + +export const TAO = BigInt(1000000000) // 10^9 +export const ETH_PER_RAO = BigInt(1000000000) // 10^9 +export const GWEI = BigInt(1000000000) // 10^9 +export const MAX_TX_FEE = BigInt(21000000) * GWEI // 100 times EVM to EVM transfer fee + +export function bigintToRao(value: bigint) { + return TAO * value +} + +export function tao(value: number) { + return TAO * BigInt(value) +} + +export function raoToEth(value: bigint) { + return ETH_PER_RAO * value +} + +export function compareEthBalanceWithTxFee(balance1: bigint, balance2: bigint) { + if (balance1 > balance2) { + assert((balance1 - balance2) < MAX_TX_FEE) + } else { + assert((balance2 - balance1) < MAX_TX_FEE) + } +} diff --git a/evm-tests/src/config.ts b/evm-tests/src/config.ts new file mode 100644 index 0000000000..601c89c8c1 --- /dev/null +++ b/evm-tests/src/config.ts @@ -0,0 +1,38 @@ +export const ETH_LOCAL_URL = 'http://localhost:9944' +export const SUB_LOCAL_URL = 'ws://localhost:9944' +export const SS58_PREFIX = 42; +// set the tx timeout as 2 second when eable the fast-blocks feature. +export const TX_TIMEOUT = 2000; + +export const IED25519VERIFY_ADDRESS = "0x0000000000000000000000000000000000000402"; +export const IEd25519VerifyABI = [ + { + inputs: [ + { internalType: "bytes32", name: "message", type: "bytes32" }, + { internalType: "bytes32", name: "publicKey", type: "bytes32" }, + { internalType: "bytes32", name: "r", type: "bytes32" }, + { internalType: "bytes32", name: "s", type: "bytes32" }, + ], + name: "verify", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "pure", + type: "function", + }, +]; + +export const IBALANCETRANSFER_ADDRESS = "0x0000000000000000000000000000000000000800"; +export const IBalanceTransferABI = [ + { + inputs: [ + { + internalType: "bytes32", + name: "data", + type: "bytes32", + }, + ], + name: "transfer", + outputs: [], + stateMutability: "payable", + type: "function", + }, +]; \ No newline at end of file diff --git a/evm-tests/src/contracts/bridgeToken.ts b/evm-tests/src/contracts/bridgeToken.ts new file mode 100644 index 0000000000..f8b3ea4d03 --- /dev/null +++ b/evm-tests/src/contracts/bridgeToken.ts @@ -0,0 +1,631 @@ +export const BRIDGE_TOKEN_CONTRACT_ABI = [ + { + "inputs": [ + { + "internalType": "string", + "name": "name_", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol_", + "type": "string" + }, + { + "internalType": "address", + "name": "admin", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [], + "name": "UnauthorizedHandler", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burnFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "isAdmin", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +]; + +export const BRIDGE_TOKEN_CONTRACT_BYTECODE = "0x60806040523480156200001157600080fd5b5060405162000fac38038062000fac8339810160408190526200003491620001ea565b8282600362000044838262000308565b50600462000053828262000308565b5062000065915060009050826200006f565b50505050620003d4565b60008281526005602090815260408083206001600160a01b038516845290915281205460ff16620001185760008381526005602090815260408083206001600160a01b03861684529091529020805460ff19166001179055620000cf3390565b6001600160a01b0316826001600160a01b0316847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45060016200011c565b5060005b92915050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200014a57600080fd5b81516001600160401b038082111562000167576200016762000122565b604051601f8301601f19908116603f0116810190828211818310171562000192576200019262000122565b8160405283815260209250866020858801011115620001b057600080fd5b600091505b83821015620001d45785820183015181830184015290820190620001b5565b6000602085830101528094505050505092915050565b6000806000606084860312156200020057600080fd5b83516001600160401b03808211156200021857600080fd5b620002268783880162000138565b945060208601519150808211156200023d57600080fd5b506200024c8682870162000138565b604086015190935090506001600160a01b03811681146200026c57600080fd5b809150509250925092565b600181811c908216806200028c57607f821691505b602082108103620002ad57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111562000303576000816000526020600020601f850160051c81016020861015620002de5750805b601f850160051c820191505b81811015620002ff57828155600101620002ea565b5050505b505050565b81516001600160401b0381111562000324576200032462000122565b6200033c8162000335845462000277565b84620002b3565b602080601f8311600181146200037457600084156200035b5750858301515b600019600386901b1c1916600185901b178555620002ff565b600085815260208120601f198616915b82811015620003a55788860151825594840194600190910190840162000384565b5085821015620003c45787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b610bc880620003e46000396000f3fe608060405234801561001057600080fd5b506004361061012c5760003560e01c806340c10f19116100ad57806395d89b411161007157806395d89b4114610288578063a217fddf14610290578063a9059cbb14610298578063d547741f146102ab578063dd62ed3e146102be57600080fd5b806340c10f191461021357806342966c681461022657806370a082311461023957806379cc67901461026257806391d148541461027557600080fd5b8063248a9ca3116100f4578063248a9ca3146101a657806324d7806c146101c95780632f2ff15d146101dc578063313ce567146101f157806336568abe1461020057600080fd5b806301ffc9a71461013157806306fdde0314610159578063095ea7b31461016e57806318160ddd1461018157806323b872dd14610193575b600080fd5b61014461013f3660046109ab565b6102f7565b60405190151581526020015b60405180910390f35b61016161032e565b60405161015091906109dc565b61014461017c366004610a47565b6103c0565b6002545b604051908152602001610150565b6101446101a1366004610a71565b6103d8565b6101856101b4366004610aad565b60009081526005602052604090206001015490565b6101446101d7366004610ac6565b6103fc565b6101ef6101ea366004610ae1565b610408565b005b60405160128152602001610150565b6101ef61020e366004610ae1565b610433565b6101ef610221366004610a47565b61046b565b6101ef610234366004610aad565b610480565b610185610247366004610ac6565b6001600160a01b031660009081526020819052604090205490565b6101ef610270366004610a47565b61048d565b610144610283366004610ae1565b6104a2565b6101616104cd565b610185600081565b6101446102a6366004610a47565b6104dc565b6101ef6102b9366004610ae1565b6104ea565b6101856102cc366004610b0d565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b60006001600160e01b03198216637965db0b60e01b148061032857506301ffc9a760e01b6001600160e01b03198316145b92915050565b60606003805461033d90610b37565b80601f016020809104026020016040519081016040528092919081815260200182805461036990610b37565b80156103b65780601f1061038b576101008083540402835291602001916103b6565b820191906000526020600020905b81548152906001019060200180831161039957829003601f168201915b5050505050905090565b6000336103ce81858561050f565b5060019392505050565b6000336103e685828561051c565b6103f1858585610599565b506001949350505050565b600061032881836104a2565b600082815260056020526040902060010154610423816105f8565b61042d8383610602565b50505050565b6001600160a01b038116331461045c5760405163334bd91960e11b815260040160405180910390fd5b6104668282610696565b505050565b6000610476816105f8565b6104668383610703565b61048a338261073d565b50565b6000610498816105f8565b610466838361073d565b60009182526005602090815260408084206001600160a01b0393909316845291905290205460ff1690565b60606004805461033d90610b37565b6000336103ce818585610599565b600082815260056020526040902060010154610505816105f8565b61042d8383610696565b6104668383836001610773565b6001600160a01b03838116600090815260016020908152604080832093861683529290522054600019811461042d578181101561058a57604051637dc7a0d960e11b81526001600160a01b038416600482015260248101829052604481018390526064015b60405180910390fd5b61042d84848484036000610773565b6001600160a01b0383166105c357604051634b637e8f60e11b815260006004820152602401610581565b6001600160a01b0382166105ed5760405163ec442f0560e01b815260006004820152602401610581565b610466838383610848565b61048a8133610972565b600061060e83836104a2565b61068e5760008381526005602090815260408083206001600160a01b03861684529091529020805460ff191660011790556106463390565b6001600160a01b0316826001600160a01b0316847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4506001610328565b506000610328565b60006106a283836104a2565b1561068e5760008381526005602090815260408083206001600160a01b0386168085529252808320805460ff1916905551339286917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a4506001610328565b6001600160a01b03821661072d5760405163ec442f0560e01b815260006004820152602401610581565b61073960008383610848565b5050565b6001600160a01b03821661076757604051634b637e8f60e11b815260006004820152602401610581565b61073982600083610848565b6001600160a01b03841661079d5760405163e602df0560e01b815260006004820152602401610581565b6001600160a01b0383166107c757604051634a1406b160e11b815260006004820152602401610581565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561042d57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161083a91815260200190565b60405180910390a350505050565b6001600160a01b0383166108735780600260008282546108689190610b71565b909155506108e59050565b6001600160a01b038316600090815260208190526040902054818110156108c65760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610581565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b03821661090157600280548290039055610920565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161096591815260200190565b60405180910390a3505050565b61097c82826104a2565b6107395760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401610581565b6000602082840312156109bd57600080fd5b81356001600160e01b0319811681146109d557600080fd5b9392505050565b60006020808352835180602085015260005b81811015610a0a578581018301518582016040015282016109ee565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114610a4257600080fd5b919050565b60008060408385031215610a5a57600080fd5b610a6383610a2b565b946020939093013593505050565b600080600060608486031215610a8657600080fd5b610a8f84610a2b565b9250610a9d60208501610a2b565b9150604084013590509250925092565b600060208284031215610abf57600080fd5b5035919050565b600060208284031215610ad857600080fd5b6109d582610a2b565b60008060408385031215610af457600080fd5b82359150610b0460208401610a2b565b90509250929050565b60008060408385031215610b2057600080fd5b610b2983610a2b565b9150610b0460208401610a2b565b600181811c90821680610b4b57607f821691505b602082108103610b6b57634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561032857634e487b7160e01b600052601160045260246000fdfea2646970667358221220e179fc58c926e64cb6e87416f8ca64c117044e3195b184afe45038857606c15364736f6c63430008160033" diff --git a/evm-tests/src/contracts/incremental.sol b/evm-tests/src/contracts/incremental.sol new file mode 100644 index 0000000000..2b3bc2fd49 --- /dev/null +++ b/evm-tests/src/contracts/incremental.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.2 <0.9.0; + +contract Storage { + uint256 number; + + /** + * @dev Store value in variable + * @param num value to store + */ + function store(uint256 num) public { + number = num; + } + + /** + * @dev Return value + * @return value of 'number' + */ + function retrieve() public view returns (uint256) { + return number; + } +} diff --git a/evm-tests/src/contracts/incremental.ts b/evm-tests/src/contracts/incremental.ts new file mode 100644 index 0000000000..b19909e491 --- /dev/null +++ b/evm-tests/src/contracts/incremental.ts @@ -0,0 +1,39 @@ +export const INCREMENTAL_CONTRACT_ABI = [ + { + "inputs": [], + "name": "retrieve", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "num", + "type": "uint256" + } + ], + "name": "store", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +]; + +/* +"compiler": { + "version": "0.8.26+commit.8a97fa7a" + }, +*/ + +export const INCREMENTAL_CONTRACT_BYTECODE = "6080604052348015600e575f80fd5b506101438061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c80632e64cec1146100385780636057361d14610056575b5f80fd5b610040610072565b60405161004d919061009b565b60405180910390f35b610070600480360381019061006b91906100e2565b61007a565b005b5f8054905090565b805f8190555050565b5f819050919050565b61009581610083565b82525050565b5f6020820190506100ae5f83018461008c565b92915050565b5f80fd5b6100c181610083565b81146100cb575f80fd5b50565b5f813590506100dc816100b8565b92915050565b5f602082840312156100f7576100f66100b4565b5b5f610104848285016100ce565b9150509291505056fea26469706673582212209a0dd35336aff1eb3eeb11db76aa60a1427a12c1b92f945ea8c8d1dfa337cf2264736f6c634300081a0033" + + + diff --git a/evm-tests/src/contracts/metagraph.ts b/evm-tests/src/contracts/metagraph.ts new file mode 100644 index 0000000000..d0c3bf5154 --- /dev/null +++ b/evm-tests/src/contracts/metagraph.ts @@ -0,0 +1,391 @@ +export const IMETAGRAPH_ADDRESS = "0x0000000000000000000000000000000000000802"; + +export const IMetagraphABI = [ + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getAxon", + outputs: [ + { + components: [ + { + internalType: "uint64", + name: "block", + type: "uint64", + }, + { + internalType: "uint32", + name: "version", + type: "uint32", + }, + { + internalType: "uint128", + name: "ip", + type: "uint128", + }, + { + internalType: "uint16", + name: "port", + type: "uint16", + }, + { + internalType: "uint8", + name: "ip_type", + type: "uint8", + }, + { + internalType: "uint8", + name: "protocol", + type: "uint8", + }, + ], + internalType: "struct AxonInfo", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getColdkey", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getConsensus", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getDividends", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getEmission", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getHotkey", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getIncentive", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getIsActive", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getLastUpdate", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getRank", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getStake", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getTrust", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getUidCount", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getValidatorStatus", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "uid", + type: "uint16", + }, + ], + name: "getVtrust", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, +]; \ No newline at end of file diff --git a/evm-tests/src/contracts/neuron.ts b/evm-tests/src/contracts/neuron.ts new file mode 100644 index 0000000000..4a8fb47e4c --- /dev/null +++ b/evm-tests/src/contracts/neuron.ts @@ -0,0 +1,235 @@ +export const INEURON_ADDRESS = "0x0000000000000000000000000000000000000804"; + +export const INeuronABI = [ + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "bytes32", + name: "commitHash", + type: "bytes32", + }, + ], + name: "commitWeights", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16[]", + name: "uids", + type: "uint16[]", + }, + { + internalType: "uint16[]", + name: "values", + type: "uint16[]", + }, + { + internalType: "uint16[]", + name: "salt", + type: "uint16[]", + }, + { + internalType: "uint64", + name: "versionKey", + type: "uint64", + }, + ], + name: "revealWeights", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16[]", + name: "dests", + type: "uint16[]", + }, + { + internalType: "uint16[]", + name: "weights", + type: "uint16[]", + }, + { + internalType: "uint64", + name: "versionKey", + type: "uint64", + }, + ], + name: "setWeights", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint32", + name: "version", + type: "uint32", + }, + { + internalType: "uint128", + name: "ip", + type: "uint128", + }, + { + internalType: "uint16", + name: "port", + type: "uint16", + }, + { + internalType: "uint8", + name: "ipType", + type: "uint8", + }, + { + internalType: "uint8", + name: "protocol", + type: "uint8", + }, + { + internalType: "uint8", + name: "placeholder1", + type: "uint8", + }, + { + internalType: "uint8", + name: "placeholder2", + type: "uint8", + }, + ], + name: "serveAxon", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint32", + name: "version", + type: "uint32", + }, + { + internalType: "uint128", + name: "ip", + type: "uint128", + }, + { + internalType: "uint16", + name: "port", + type: "uint16", + }, + { + internalType: "uint8", + name: "ipType", + type: "uint8", + }, + { + internalType: "uint8", + name: "protocol", + type: "uint8", + }, + { + internalType: "uint8", + name: "placeholder1", + type: "uint8", + }, + { + internalType: "uint8", + name: "placeholder2", + type: "uint8", + }, + { + internalType: "bytes", + name: "certificate", + type: "bytes", + }, + ], + name: "serveAxonTls", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint32", + name: "version", + type: "uint32", + }, + { + internalType: "uint128", + name: "ip", + type: "uint128", + }, + { + internalType: "uint16", + name: "port", + type: "uint16", + }, + { + internalType: "uint8", + name: "ipType", + type: "uint8", + }, + ], + name: "servePrometheus", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "bytes32", + name: "hotkey", + type: "bytes32", + }, + ], + name: "burnedRegister", + outputs: [], + stateMutability: "payable", + type: "function", + }, +]; \ No newline at end of file diff --git a/evm-tests/src/contracts/staking.ts b/evm-tests/src/contracts/staking.ts new file mode 100644 index 0000000000..9a30d307ba --- /dev/null +++ b/evm-tests/src/contracts/staking.ts @@ -0,0 +1,243 @@ +export const ISTAKING_ADDRESS = "0x0000000000000000000000000000000000000801"; +export const ISTAKING_V2_ADDRESS = "0x0000000000000000000000000000000000000805"; + +export const IStakingABI = [ + { + inputs: [ + { + internalType: "bytes32", + name: "delegate", + type: "bytes32", + }, + ], + name: "addProxy", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "hotkey", + type: "bytes32", + }, + { + internalType: "uint256", + name: "netuid", + type: "uint256", + }, + ], + name: "addStake", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "delegate", + type: "bytes32", + }, + ], + name: "removeProxy", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "hotkey", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "coldkey", + type: "bytes32", + }, + { + internalType: "uint256", + name: "netuid", + type: "uint256", + }, + ], + name: "getStake", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "hotkey", + type: "bytes32", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "uint256", + name: "netuid", + type: "uint256", + }, + ], + name: "removeStake", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +]; + +export const IStakingV2ABI = [ + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + } + ], + "name": "addProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "addStake", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "coldkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "getStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "coldkey", + "type": "bytes32" + } + ], + "name": "getTotalColdkeyStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + } + ], + "name": "getTotalHotkeyStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + } + ], + "name": "removeProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "removeStake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +]; \ No newline at end of file diff --git a/evm-tests/src/contracts/subnet.ts b/evm-tests/src/contracts/subnet.ts new file mode 100644 index 0000000000..9b6fe00596 --- /dev/null +++ b/evm-tests/src/contracts/subnet.ts @@ -0,0 +1,889 @@ +export const ISUBNET_ADDRESS = "0x0000000000000000000000000000000000000803"; + +export const ISubnetABI = [ + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getAdjustmentAlpha", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getAlphaValues", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getBondsMovingAverage", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getCommitRevealWeightsEnabled", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getDifficulty", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + name: "getImmunityPeriod", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + name: "getKappa", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getMaxBurn", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getMaxDifficulty", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getMaxWeightLimit", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getMinAllowedWeights", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getMinBurn", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getMinDifficulty", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getNetworkRegistrationAllowed", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + name: "getRho", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getServingRateLimit", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getWeightsSetRateLimit", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getWeightsVersionKey", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "activityCutoff", + type: "uint16", + }, + ], + name: "setActivityCutoff", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getActivityCutoff", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint64", + name: "adjustmentAlpha", + type: "uint64", + }, + ], + name: "setAdjustmentAlpha", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "alphaLow", + type: "uint16", + }, + { + internalType: "uint16", + name: "alphaHigh", + type: "uint16", + }, + ], + name: "setAlphaValues", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint64", + name: "bondsMovingAverage", + type: "uint64", + }, + ], + name: "setBondsMovingAverage", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "bool", + name: "commitRevealWeightsEnabled", + type: "bool", + }, + ], + name: "setCommitRevealWeightsEnabled", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getCommitRevealWeightsInterval", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint64", + name: "commitRevealWeightsInterval", + type: "uint64", + }, + ], + name: "setCommitRevealWeightsInterval", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint64", + name: "difficulty", + type: "uint64", + }, + ], + name: "setDifficulty", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "immunityPeriod", + type: "uint16", + }, + ], + name: "setImmunityPeriod", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "kappa", + type: "uint16", + }, + ], + name: "setKappa", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getLiquidAlphaEnabled", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "bool", + name: "liquidAlphaEnabled", + type: "bool", + }, + ], + name: "setLiquidAlphaEnabled", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint64", + name: "maxBurn", + type: "uint64", + }, + ], + name: "setMaxBurn", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint64", + name: "maxDifficulty", + type: "uint64", + }, + ], + name: "setMaxDifficulty", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "maxWeightLimit", + type: "uint16", + }, + ], + name: "setMaxWeightLimit", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "minAllowedWeights", + type: "uint16", + }, + ], + name: "setMinAllowedWeights", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint64", + name: "minBurn", + type: "uint64", + }, + ], + name: "setMinBurn", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint64", + name: "minDifficulty", + type: "uint64", + }, + ], + name: "setMinDifficulty", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + ], + name: "getNetworkPowRegistrationAllowed", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "bool", + name: "networkPowRegistrationAllowed", + type: "bool", + }, + ], + name: "setNetworkPowRegistrationAllowed", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "bool", + name: "networkRegistrationAllowed", + type: "bool", + }, + ], + name: "setNetworkRegistrationAllowed", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint16", + name: "rho", + type: "uint16", + }, + ], + name: "setRho", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint64", + name: "servingRateLimit", + type: "uint64", + }, + ], + name: "setServingRateLimit", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint64", + name: "weightsSetRateLimit", + type: "uint64", + }, + ], + name: "setWeightsSetRateLimit", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "netuid", + type: "uint16", + }, + { + internalType: "uint64", + name: "weightsVersionKey", + type: "uint64", + }, + ], + name: "setWeightsVersionKey", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "hotkey", + type: "bytes32", + }, + ], + name: "registerNetwork", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "hotkey", + type: "bytes32" + }, + { + internalType: "string", + name: "subnetName", + type: "string" + }, + { + internalType: "string", + name: "githubRepo", + type: "string" + }, + { + internalType: "string", + name: "subnetContact", + type: "string" + }, + { + internalType: "string", + name: "subnetUrl", + type: "string" + }, + { + internalType: "string", + name: "discord", + type: "string" + }, + { + internalType: "string", + name: "description", + type: "string" + }, + { + internalType: "string", + name: "additional", + type: "string" + } + ], + name: "registerNetwork", + outputs: [], + stateMutability: "payable", + type: "function" + }, +]; \ No newline at end of file diff --git a/evm-tests/src/contracts/withdraw.sol b/evm-tests/src/contracts/withdraw.sol new file mode 100644 index 0000000000..3945661e09 --- /dev/null +++ b/evm-tests/src/contracts/withdraw.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +contract Withdraw { + constructor() {} + + function withdraw(uint256 value) public payable { + payable(msg.sender).transfer(value); + } + + receive() external payable {} +} diff --git a/evm-tests/src/contracts/withdraw.ts b/evm-tests/src/contracts/withdraw.ts new file mode 100644 index 0000000000..46fe66bf24 --- /dev/null +++ b/evm-tests/src/contracts/withdraw.ts @@ -0,0 +1,31 @@ +export const WITHDRAW_CONTRACT_ABI = [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +]; + +// "compiler": { +// "version": "0.8.26+commit.8a97fa7a" +// }, + +export const WITHDRAW_CONTRACT_BYTECODE = "6080604052348015600e575f80fd5b506101148061001c5f395ff3fe608060405260043610601e575f3560e01c80632e1a7d4d146028576024565b36602457005b5f80fd5b603e6004803603810190603a919060b8565b6040565b005b3373ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f193505050501580156082573d5f803e3d5ffd5b5050565b5f80fd5b5f819050919050565b609a81608a565b811460a3575f80fd5b50565b5f8135905060b2816093565b92915050565b5f6020828403121560ca5760c96086565b5b5f60d58482850160a6565b9150509291505056fea2646970667358221220f43400858bfe4fcc0bf3c1e2e06d3a9e6ced86454a00bd7e4866b3d4d64e46bb64736f6c634300081a0033" + diff --git a/evm-tests/src/eth.ts b/evm-tests/src/eth.ts new file mode 100644 index 0000000000..ea3ebb9976 --- /dev/null +++ b/evm-tests/src/eth.ts @@ -0,0 +1,17 @@ + +import { ethers, Provider, TransactionRequest, Wallet } from "ethers"; +export async function estimateTransactionCost(provider: Provider, tx: TransactionRequest) { + const feeData = await provider.getFeeData(); + const estimatedGas = BigInt(await provider.estimateGas(tx)); + const gasPrice = feeData.gasPrice || feeData.maxFeePerGas; + if (gasPrice === null) + return estimatedGas + else + return estimatedGas * BigInt(gasPrice); +} + +export function getContract(contractAddress: string, abi: {}[], wallet: Wallet) { + const contract = new ethers.Contract(contractAddress, abi, wallet); + return contract + +} \ No newline at end of file diff --git a/evm-tests/src/substrate.ts b/evm-tests/src/substrate.ts new file mode 100644 index 0000000000..ddfdfb626d --- /dev/null +++ b/evm-tests/src/substrate.ts @@ -0,0 +1,274 @@ +import * as assert from "assert"; +import { devnet, MultiAddress } from '@polkadot-api/descriptors'; +import { createClient, TypedApi, Transaction, PolkadotSigner, Binary } from 'polkadot-api'; +import { getWsProvider } from 'polkadot-api/ws-provider/web'; +import { sr25519CreateDerive } from "@polkadot-labs/hdkd" +import { convertPublicKeyToSs58 } from "../src/address-utils" +import { DEV_PHRASE, entropyToMiniSecret, mnemonicToEntropy, KeyPair } from "@polkadot-labs/hdkd-helpers" +import { getPolkadotSigner } from "polkadot-api/signer" +import { randomBytes } from 'crypto'; +import { Keyring } from '@polkadot/keyring'; +import { SS58_PREFIX, TX_TIMEOUT } from "./config"; + +let api: TypedApi | undefined = undefined + +// define url string as type to extend in the future +// export type ClientUrlType = 'ws://localhost:9944' | 'wss://test.finney.opentensor.ai:443' | 'wss://dev.chain.opentensor.ai:443' | 'wss://archive.chain.opentensor.ai'; +export type ClientUrlType = 'ws://localhost:9944' + +export async function getClient(url: ClientUrlType) { + const provider = getWsProvider(url); + const client = createClient(provider); + return client +} + +export async function getDevnetApi() { + if (api === undefined) { + let client = await getClient('ws://localhost:9944') + api = client.getTypedApi(devnet) + } + return api +} + +export function getAlice() { + const entropy = mnemonicToEntropy(DEV_PHRASE) + const miniSecret = entropyToMiniSecret(entropy) + const derive = sr25519CreateDerive(miniSecret) + const hdkdKeyPair = derive("//Alice") + + return hdkdKeyPair +} + +export function getAliceSigner() { + const alice = getAlice() + const polkadotSigner = getPolkadotSigner( + alice.publicKey, + "Sr25519", + alice.sign, + ) + + return polkadotSigner +} + +export function getRandomSubstrateSigner() { + const keypair = getRandomSubstrateKeypair(); + return getSignerFromKeypair(keypair) +} + +export function getSignerFromKeypair(keypair: KeyPair) { + const polkadotSigner = getPolkadotSigner( + keypair.publicKey, + "Sr25519", + keypair.sign, + ) + return polkadotSigner +} + +export function getRandomSubstrateKeypair() { + const seed = randomBytes(32); + const miniSecret = entropyToMiniSecret(seed) + const derive = sr25519CreateDerive(miniSecret) + const hdkdKeyPair = derive("") + + return hdkdKeyPair +} + +export async function getBalance(api: TypedApi) { + const value = await api.query.Balances.Account.getValue("") + return value +} + +export async function getNonce(api: TypedApi, ss58Address: string): Promise { + const value = await api.query.System.Account.getValue(ss58Address); + return value.nonce +} + +export async function getNonceChangePromise(api: TypedApi, ss58Address: string) { + // api.query.System.Account.getValue() + const initValue = await api.query.System.Account.getValue(ss58Address); + return new Promise((resolve, reject) => { + const subscription = api.query.System.Account.watchValue(ss58Address).subscribe({ + next(value) { + if (value.nonce > initValue.nonce) { + subscription.unsubscribe(); + // Resolve the promise when the transaction is finalized + resolve(); + } + }, + + error(err: Error) { + console.error("Transaction failed:", err); + subscription.unsubscribe(); + // Reject the promise in case of an error + reject(err); + }, + complete() { + console.log("Subscription complete"); + } + }) + + setTimeout(() => { + subscription.unsubscribe(); + console.log('unsubscribed!'); + resolve() + }, TX_TIMEOUT); + + }) +} + +export function convertPublicKeyToMultiAddress(publicKey: Uint8Array, ss58Format: number = SS58_PREFIX): MultiAddress { + // Create a keyring instance + const keyring = new Keyring({ type: 'sr25519', ss58Format }); + + // Add the public key to the keyring + const address = keyring.encodeAddress(publicKey); + + return MultiAddress.Id(address); +} + + +export async function waitForTransactionCompletion(api: TypedApi, tx: Transaction<{}, string, string, void>, signer: PolkadotSigner,) { + const transactionPromise = await getTransactionWatchPromise(tx, signer) + return transactionPromise + + // If we can't always get the finalized event, then add nonce subscribe as other evidence for tx is finalized. + // Don't need it based on current testing. + // const ss58Address = convertPublicKeyToSs58(signer.publicKey) + // const noncePromise = await getNonceChangePromise(api, ss58Address) + + // return new Promise((resolve, reject) => { + // Promise.race([transactionPromise, noncePromise]) + // .then(resolve) + // .catch(reject); + // }) +} + +export async function getTransactionWatchPromise(tx: Transaction<{}, string, string, void>, signer: PolkadotSigner,) { + return new Promise((resolve, reject) => { + // store the txHash, then use it in timeout. easier to know which tx is not finalized in time + let txHash = "" + const subscription = tx.signSubmitAndWatch(signer).subscribe({ + next(value) { + console.log("Event:", value); + txHash = value.txHash + + // TODO investigate why finalized not for each extrinsic + if (value.type === "finalized") { + console.log("Transaction is finalized in block:", value.txHash); + subscription.unsubscribe(); + // Resolve the promise when the transaction is finalized + resolve(); + + } + }, + error(err) { + console.error("Transaction failed:", err); + subscription.unsubscribe(); + // Reject the promise in case of an error + reject(err); + + }, + complete() { + console.log("Subscription complete"); + } + }); + + setTimeout(() => { + subscription.unsubscribe(); + console.log('unsubscribed because of timeout for tx {}', txHash); + reject() + }, TX_TIMEOUT); + }); +} + +export async function waitForFinalizedBlock(api: TypedApi) { + const currentBlockNumber = await api.query.System.Number.getValue() + return new Promise((resolve, reject) => { + + const subscription = api.query.System.Number.watchValue().subscribe({ + // TODO check why the block number event just get once + next(value: number) { + console.log("Event block number is :", value); + + if (value > currentBlockNumber + 6) { + console.log("Transaction is finalized in block:", value); + subscription.unsubscribe(); + + resolve(); + + } + + }, + error(err: Error) { + console.error("Transaction failed:", err); + subscription.unsubscribe(); + // Reject the promise in case of an error + reject(err); + + }, + complete() { + console.log("Subscription complete"); + } + }); + + setTimeout(() => { + subscription.unsubscribe(); + console.log('unsubscribed!'); + resolve() + }, 2000); + }); +} + +// second solution to wait for transaction finalization. pass the raw data to avoid the complex transaction type definition +export async function waitForTransactionCompletion2(api: TypedApi, raw: Binary, signer: PolkadotSigner,) { + const tx = await api.txFromCallData(raw); + return new Promise((resolve, reject) => { + const subscription = tx.signSubmitAndWatch(signer).subscribe({ + next(value) { + console.log("Event:", value); + + if (value.type === "txBestBlocksState") { + console.log("Transaction is finalized in block:", value.txHash); + subscription.unsubscribe(); + // Resolve the promise when the transaction is finalized + resolve(); + + } + }, + error(err: Error) { + console.error("Transaction failed:", err); + subscription.unsubscribe(); + // Reject the promise in case of an error + reject(err); + + }, + complete() { + console.log("Subscription complete"); + } + }); + }); +} + +export async function waitForNonceChange(api: TypedApi, ss58Address: string) { + const initNonce = await getNonce(api, ss58Address) + while (true) { + const currentNonce = await getNonce(api, ss58Address) + if (currentNonce > initNonce) { + break + } + + await new Promise(resolve => setTimeout(resolve, 200)); + } +} + + +// other approach to convert public key to ss58 +// export function convertPublicKeyToSs58(publicKey: Uint8Array, ss58Format: number = 42): string { +// // Create a keyring instance +// const keyring = new Keyring({ type: 'sr25519', ss58Format }); + +// // Add the public key to the keyring +// const address = keyring.encodeAddress(publicKey); + +// return address +// } \ No newline at end of file diff --git a/evm-tests/src/subtensor.ts b/evm-tests/src/subtensor.ts new file mode 100644 index 0000000000..48dc5c83c7 --- /dev/null +++ b/evm-tests/src/subtensor.ts @@ -0,0 +1,345 @@ +import * as assert from "assert"; +import { devnet, MultiAddress } from '@polkadot-api/descriptors'; +import { TypedApi, TxCallData } from 'polkadot-api'; +import { KeyPair } from "@polkadot-labs/hdkd-helpers" +import { getAliceSigner, waitForTransactionCompletion, getSignerFromKeypair } from './substrate' +import { convertH160ToSS58, convertPublicKeyToSs58 } from './address-utils' +import { tao } from './balance-math' + +// create a new subnet and return netuid +export async function addNewSubnetwork(api: TypedApi, hotkey: KeyPair, coldkey: KeyPair) { + const alice = getAliceSigner() + const totalNetworks = await api.query.SubtensorModule.TotalNetworks.getValue() + + const rateLimit = await api.query.SubtensorModule.NetworkRateLimit.getValue() + if (rateLimit !== BigInt(0)) { + const internalCall = api.tx.AdminUtils.sudo_set_network_rate_limit({ rate_limit: BigInt(0) }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + } + + const signer = getSignerFromKeypair(coldkey) + const registerNetworkTx = api.tx.SubtensorModule.register_network({ hotkey: convertPublicKeyToSs58(hotkey.publicKey) }) + await waitForTransactionCompletion(api, registerNetworkTx, signer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + + assert.equal(totalNetworks + 1, await api.query.SubtensorModule.TotalNetworks.getValue()) + return totalNetworks +} + +// force set balance for a ss58 address +export async function forceSetBalanceToSs58Address(api: TypedApi, ss58Address: string) { + const alice = getAliceSigner() + const balance = tao(1e8) + const internalCall = api.tx.Balances.force_set_balance({ who: MultiAddress.Id(ss58Address), new_free: balance }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + + const balanceOnChain = (await api.query.System.Account.getValue(ss58Address)).data.free + // check the balance except for sudo account becasue of tx fee + if (ss58Address !== convertPublicKeyToSs58(alice.publicKey)) { + assert.equal(balance, balanceOnChain) + } +} + +// set balance for an eth address +export async function forceSetBalanceToEthAddress(api: TypedApi, ethAddress: string) { + const ss58Address = convertH160ToSS58(ethAddress) + await forceSetBalanceToSs58Address(api, ss58Address) +} + +export async function setCommitRevealWeightsEnabled(api: TypedApi, netuid: number, enabled: boolean) { + const value = await api.query.SubtensorModule.CommitRevealWeightsEnabled.getValue(netuid) + if (value === enabled) { + return; + } + + const alice = getAliceSigner() + const internalCall = api.tx.AdminUtils.sudo_set_commit_reveal_weights_enabled({ netuid: netuid, enabled: enabled }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + assert.equal(enabled, await api.query.SubtensorModule.CommitRevealWeightsEnabled.getValue(netuid)) +} + +export async function setWeightsSetRateLimit(api: TypedApi, netuid: number, rateLimit: bigint) { + const value = await api.query.SubtensorModule.WeightsSetRateLimit.getValue(netuid) + if (value === rateLimit) { + return; + } + + const alice = getAliceSigner() + const internalCall = api.tx.AdminUtils.sudo_set_weights_set_rate_limit({ netuid: netuid, weights_set_rate_limit: rateLimit }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + assert.equal(rateLimit, await api.query.SubtensorModule.WeightsSetRateLimit.getValue(netuid)) +} + +// tempo is u16 in rust, but we just number in js. so value should be less than u16::Max +export async function setTempo(api: TypedApi, netuid: number, tempo: number) { + const value = await api.query.SubtensorModule.Tempo.getValue(netuid) + console.log("init avlue is ", value) + if (value === tempo) { + return; + } + + const alice = getAliceSigner() + const internalCall = api.tx.AdminUtils.sudo_set_tempo({ netuid: netuid, tempo: tempo }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + assert.equal(tempo, await api.query.SubtensorModule.Tempo.getValue(netuid)) +} + +export async function setCommitRevealWeightsInterval(api: TypedApi, netuid: number, interval: bigint) { + const value = await api.query.SubtensorModule.RevealPeriodEpochs.getValue(netuid) + if (value === interval) { + return; + } + + const alice = getAliceSigner() + const internalCall = api.tx.AdminUtils.sudo_set_commit_reveal_weights_interval({ netuid: netuid, interval: interval }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + assert.equal(interval, await api.query.SubtensorModule.RevealPeriodEpochs.getValue(netuid)) +} + + +export async function forceSetChainID(api: TypedApi, chainId: bigint) { + const value = await api.query.EVMChainId.ChainId.getValue() + if (value === chainId) { + return; + } + + const alice = getAliceSigner() + const internalCall = api.tx.AdminUtils.sudo_set_evm_chain_id({ chain_id: chainId }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + assert.equal(chainId, await api.query.EVMChainId.ChainId.getValue()) +} + +export async function disableWhiteListCheck(api: TypedApi, disabled: boolean) { + const value = await api.query.EVM.DisableWhitelistCheck.getValue() + if (value === disabled) { + return; + } + + const alice = getAliceSigner() + const internalCall = api.tx.EVM.disable_whitelist({ disabled: disabled }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + assert.equal(disabled, await api.query.EVM.DisableWhitelistCheck.getValue()) +} + +export async function burnedRegister(api: TypedApi, netuid: number, ss58Address: string, keypair: KeyPair) { + const uids = await api.query.SubtensorModule.SubnetworkN.getValue(netuid) + const signer = getSignerFromKeypair(keypair) + const tx = api.tx.SubtensorModule.burned_register({ hotkey: ss58Address, netuid: netuid }) + await waitForTransactionCompletion(api, tx, signer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + assert.equal(uids + 1, await api.query.SubtensorModule.SubnetworkN.getValue(netuid)) +} + + +export async function sendProxyCall(api: TypedApi, calldata: TxCallData, ss58Address: string, keypair: KeyPair) { + const signer = getSignerFromKeypair(keypair) + const tx = api.tx.Proxy.proxy({ + call: calldata, + real: MultiAddress.Id(ss58Address), + force_proxy_type: undefined + }); + await waitForTransactionCompletion(api, tx, signer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); +} + + +export async function setTxRateLimit(api: TypedApi, txRateLimit: bigint) { + const value = await api.query.SubtensorModule.TxRateLimit.getValue() + if (value === txRateLimit) { + return; + } + const alice = getAliceSigner() + + const internalCall = api.tx.AdminUtils.sudo_set_tx_rate_limit({ tx_rate_limit: txRateLimit }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + assert.equal(txRateLimit, await api.query.SubtensorModule.TxRateLimit.getValue()) +} + +export async function setMaxAllowedValidators(api: TypedApi, netuid: number, maxAllowedValidators: number) { + const value = await api.query.SubtensorModule.MaxAllowedValidators.getValue(netuid) + if (value === maxAllowedValidators) { + return; + } + + const alice = getAliceSigner() + + const internalCall = api.tx.AdminUtils.sudo_set_max_allowed_validators({ + netuid: netuid, + max_allowed_validators: maxAllowedValidators + }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + assert.equal(maxAllowedValidators, await api.query.SubtensorModule.MaxAllowedValidators.getValue(netuid)) +} + +export async function setSubnetOwnerCut(api: TypedApi, subnetOwnerCut: number) { + const value = await api.query.SubtensorModule.SubnetOwnerCut.getValue() + if (value === subnetOwnerCut) { + return; + } + + const alice = getAliceSigner() + + const internalCall = api.tx.AdminUtils.sudo_set_subnet_owner_cut({ + subnet_owner_cut: subnetOwnerCut + }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + assert.equal(subnetOwnerCut, await api.query.SubtensorModule.SubnetOwnerCut.getValue()) +} + +export async function setActivityCutoff(api: TypedApi, netuid: number, activityCutoff: number) { + const value = await api.query.SubtensorModule.ActivityCutoff.getValue(netuid) + if (value === activityCutoff) { + return; + } + + const alice = getAliceSigner() + + const internalCall = api.tx.AdminUtils.sudo_set_activity_cutoff({ + netuid: netuid, + activity_cutoff: activityCutoff + }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + assert.equal(activityCutoff, await api.query.SubtensorModule.ActivityCutoff.getValue(netuid)) +} + +export async function setMaxAllowedUids(api: TypedApi, netuid: number, maxAllowedUids: number) { + const value = await api.query.SubtensorModule.MaxAllowedUids.getValue(netuid) + if (value === maxAllowedUids) { + return; + } + + const alice = getAliceSigner() + + const internalCall = api.tx.AdminUtils.sudo_set_max_allowed_uids({ + netuid: netuid, + max_allowed_uids: maxAllowedUids + }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + assert.equal(maxAllowedUids, await api.query.SubtensorModule.MaxAllowedUids.getValue(netuid)) +} + +export async function setMinDelegateTake(api: TypedApi, minDelegateTake: number) { + const value = await api.query.SubtensorModule.MinDelegateTake.getValue() + if (value === minDelegateTake) { + return; + } + + const alice = getAliceSigner() + + const internalCall = api.tx.AdminUtils.sudo_set_min_delegate_take({ + take: minDelegateTake + }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + assert.equal(minDelegateTake, await api.query.SubtensorModule.MinDelegateTake.getValue()) +} + +export async function becomeDelegate(api: TypedApi, ss58Address: string, keypair: KeyPair) { + const singer = getSignerFromKeypair(keypair) + + const tx = api.tx.SubtensorModule.become_delegate({ + hotkey: ss58Address + }) + await waitForTransactionCompletion(api, tx, singer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); +} + +export async function addStake(api: TypedApi, netuid: number, ss58Address: string, amount_staked: bigint, keypair: KeyPair) { + const singer = getSignerFromKeypair(keypair) + let tx = api.tx.SubtensorModule.add_stake({ + netuid: netuid, + hotkey: ss58Address, + amount_staked: amount_staked + }) + + await waitForTransactionCompletion(api, tx, singer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + +} + +export async function setWeight(api: TypedApi, netuid: number, dests: number[], weights: number[], version_key: bigint, keypair: KeyPair) { + const singer = getSignerFromKeypair(keypair) + let tx = api.tx.SubtensorModule.set_weights({ + netuid: netuid, + dests: dests, + weights: weights, + version_key: version_key + }) + + await waitForTransactionCompletion(api, tx, singer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + +} + +export async function rootRegister(api: TypedApi, ss58Address: string, keypair: KeyPair) { + const singer = getSignerFromKeypair(keypair) + let tx = api.tx.SubtensorModule.root_register({ + hotkey: ss58Address + }) + + await waitForTransactionCompletion(api, tx, singer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + +} \ No newline at end of file diff --git a/evm-tests/src/utils.ts b/evm-tests/src/utils.ts new file mode 100644 index 0000000000..36e922b49e --- /dev/null +++ b/evm-tests/src/utils.ts @@ -0,0 +1,55 @@ +import { defineChain, http, publicActions, createPublicClient } from "viem" +import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts' +import { ethers } from "ethers" +import { ETH_LOCAL_URL } from "./config" + +export type ClientUrlType = 'http://localhost:9944'; + +export const chain = (id: number, url: string) => defineChain({ + id: id, + name: 'bittensor', + network: 'bittensor', + nativeCurrency: { + name: 'tao', + symbol: 'TAO', + decimals: 9, + }, + rpcUrls: { + default: { + http: [url], + }, + }, + testnet: true, +}) + + +export async function getPublicClient(url: ClientUrlType) { + const wallet = createPublicClient({ + chain: chain(42, url), + transport: http(), + + }) + + return wallet.extend(publicActions) +} + +/** + * Generates a random Ethereum wallet + * @returns wallet keyring + */ +export function generateRandomEthWallet() { + let privateKey = generatePrivateKey().toString(); + privateKey = privateKey.replace('0x', ''); + + const account = privateKeyToAccount(`0x${privateKey}`) + return account +} + + +export function generateRandomEthersWallet() { + const account = ethers.Wallet.createRandom(); + const provider = new ethers.JsonRpcProvider(ETH_LOCAL_URL); + + const wallet = new ethers.Wallet(account.privateKey, provider); + return wallet; +} \ No newline at end of file diff --git a/evm-tests/test/ed25519.precompile.verify.test.ts b/evm-tests/test/ed25519.precompile.verify.test.ts new file mode 100644 index 0000000000..fcd79ec9d7 --- /dev/null +++ b/evm-tests/test/ed25519.precompile.verify.test.ts @@ -0,0 +1,122 @@ +import { IED25519VERIFY_ADDRESS, IEd25519VerifyABI, ETH_LOCAL_URL } from '../src/config' +import { getPublicClient } from "../src/utils"; +import { toHex, toBytes, keccak256, PublicClient } from 'viem' +import { Keyring } from "@polkadot/keyring"; +import * as assert from "assert"; + +describe("Verfication of ed25519 signature", () => { + // init eth part + let ethClient: PublicClient; + + before(async () => { + ethClient = await getPublicClient(ETH_LOCAL_URL); + }); + + it("Verification of ed25519 works", async () => { + const keyring = new Keyring({ type: "ed25519" }); + const alice = keyring.addFromUri("//Alice"); + + // Use this example: https://github.com/gztensor/evm-demo/blob/main/docs/ed25519verify-precompile.md + // const keyring = new Keyring({ type: "ed25519" }); + // const myAccount = keyring.addFromUri("//Alice"); + + ////////////////////////////////////////////////////////////////////// + // Generate a signature + + // Your message to sign + const message = "Sign this message"; + const messageU8a = new TextEncoder().encode(message); + const messageHex = toHex(messageU8a); // Convert message to hex string + const messageHash = keccak256(messageHex); // Hash the message to fit into bytes32 + console.log(`messageHash = ${messageHash}`); + const hashedMessageBytes = toBytes(messageHash); + console.log(`hashedMessageBytes = ${hashedMessageBytes}`); + + // Sign the message + const signature = await alice.sign(hashedMessageBytes); + console.log(`Signature: ${toHex(signature)}`); + + // Verify the signature locally + const isValid = alice.verify( + hashedMessageBytes, + signature, + alice.publicKey + ); + console.log(`Is the signature valid? ${isValid}`); + + ////////////////////////////////////////////////////////////////////// + // Verify the signature using the precompile contract + + const publicKeyBytes = toHex(alice.publicKey); + console.log(`publicKeyBytes = ${publicKeyBytes}`); + + // Split signture into Commitment (R) and response (s) + let r = signature.slice(0, 32); // Commitment, a.k.a. "r" - first 32 bytes + let s = signature.slice(32, 64); // Response, a.k.a. "s" - second 32 bytes + let rBytes = toHex(r); + let sBytes = toHex(s); + + const isPrecompileValid = await ethClient.readContract({ + address: IED25519VERIFY_ADDRESS, + abi: IEd25519VerifyABI, + functionName: "verify", + args: [messageHash, + publicKeyBytes, + rBytes, + sBytes] + + }); + + console.log( + `Is the signature valid according to the smart contract? ${isPrecompileValid}` + ); + assert.equal(isPrecompileValid, true) + + ////////////////////////////////////////////////////////////////////// + // Verify the signature for bad data using the precompile contract + + let brokenHashedMessageBytes = hashedMessageBytes; + brokenHashedMessageBytes[0] = (brokenHashedMessageBytes[0] + 1) % 0xff; + const brokenMessageHash = toHex(brokenHashedMessageBytes); + console.log(`brokenMessageHash = ${brokenMessageHash}`); + + const isPrecompileValidBadData = await ethClient.readContract({ + address: IED25519VERIFY_ADDRESS, + abi: IEd25519VerifyABI, + functionName: "verify", + args: [brokenMessageHash, + publicKeyBytes, + rBytes, + sBytes] + + }); + + console.log( + `Is the signature valid according to the smart contract for broken data? ${isPrecompileValidBadData}` + ); + assert.equal(isPrecompileValidBadData, false) + + ////////////////////////////////////////////////////////////////////// + // Verify the bad signature for good data using the precompile contract + + let brokenR = r; + brokenR[0] = (brokenR[0] + 1) % 0xff; + rBytes = toHex(r); + const isPrecompileValidBadSignature = await ethClient.readContract({ + address: IED25519VERIFY_ADDRESS, + abi: IEd25519VerifyABI, + functionName: "verify", + args: [messageHash, + publicKeyBytes, + rBytes, + sBytes] + + }); + + console.log( + `Is the signature valid according to the smart contract for broken signature? ${isPrecompileValidBadSignature}` + ); + assert.equal(isPrecompileValidBadSignature, false) + + }); +}); \ No newline at end of file diff --git a/evm-tests/test/eth.bridgeToken.deploy.test.ts b/evm-tests/test/eth.bridgeToken.deploy.test.ts new file mode 100644 index 0000000000..94ebcd1260 --- /dev/null +++ b/evm-tests/test/eth.bridgeToken.deploy.test.ts @@ -0,0 +1,69 @@ +import * as assert from "assert"; +import * as chai from "chai"; + +import { getDevnetApi } from "../src/substrate" +import { generateRandomEthersWallet, getPublicClient } from "../src/utils"; +import { ETH_LOCAL_URL } from "../src/config"; +import { devnet } from "@polkadot-api/descriptors" +import { PublicClient } from "viem"; +import { TypedApi } from "polkadot-api"; +import { BRIDGE_TOKEN_CONTRACT_ABI, BRIDGE_TOKEN_CONTRACT_BYTECODE } from "../src/contracts/bridgeToken"; +import { toViemAddress } from "../src/address-utils"; +import { forceSetBalanceToEthAddress, disableWhiteListCheck } from "../src/subtensor"; +import { ethers } from "ethers" +describe("bridge token contract deployment", () => { + // init eth part + const wallet = generateRandomEthersWallet(); + let publicClient: PublicClient; + + // init substrate part + let api: TypedApi + + before(async () => { + // init variables got from await and async + publicClient = await getPublicClient(ETH_LOCAL_URL) + api = await getDevnetApi() + + await forceSetBalanceToEthAddress(api, wallet.address) + await disableWhiteListCheck(api, true) + }); + + it("Can deploy bridge token smart contract", async () => { + const contractFactory = new ethers.ContractFactory(BRIDGE_TOKEN_CONTRACT_ABI, BRIDGE_TOKEN_CONTRACT_BYTECODE, wallet) + const contract = await contractFactory.deploy("name", + "symbol", wallet.address) + await contract.waitForDeployment() + assert.notEqual(contract.target, undefined) + + const contractAddress = contract.target.toString() + + const code = await publicClient.getCode({ address: toViemAddress(contractAddress) }) + if (code === undefined) { + throw new Error("code not available") + } + assert.ok(code.length > 100) + assert.ok(code.includes("0x60806040523480156")) + }); + + it("Can deploy bridge token contract with gas limit", async () => { + const contractFactory = new ethers.ContractFactory(BRIDGE_TOKEN_CONTRACT_ABI, BRIDGE_TOKEN_CONTRACT_BYTECODE, wallet) + const successful_gas_limit = "12345678"; + const contract = await contractFactory.deploy("name", + "symbol", wallet.address, + { + gasLimit: successful_gas_limit, + } + ) + await contract.waitForDeployment() + assert.notEqual(contract.target, undefined) + + const contractAddress = contract.target.toString() + + const code = await publicClient.getCode({ address: toViemAddress(contractAddress) }) + if (code === undefined) { + throw new Error("code not available") + } + assert.ok(code.length > 100) + assert.ok(code.includes("0x60806040523480156")) + }); +}); \ No newline at end of file diff --git a/evm-tests/test/eth.chain-id.test.ts b/evm-tests/test/eth.chain-id.test.ts new file mode 100644 index 0000000000..09174c1212 --- /dev/null +++ b/evm-tests/test/eth.chain-id.test.ts @@ -0,0 +1,76 @@ + +import * as assert from "assert"; +import * as chai from "chai"; + +import { getDevnetApi, waitForTransactionCompletion, getRandomSubstrateKeypair } from "../src/substrate" +import { generateRandomEthWallet, getPublicClient } from "../src/utils"; +import { convertPublicKeyToSs58 } from "../src/address-utils" +import { ETH_LOCAL_URL } from "../src/config"; +import { devnet } from "@polkadot-api/descriptors" +import { getPolkadotSigner } from "polkadot-api/signer"; +import { PublicClient } from "viem"; +import { TypedApi } from "polkadot-api"; +import { forceSetBalanceToSs58Address, forceSetChainID } from "../src/subtensor"; + +describe("Test the EVM chain ID", () => { + // init eth part + const wallet = generateRandomEthWallet(); + let ethClient: PublicClient; + + // init substrate part + const keyPair = getRandomSubstrateKeypair(); + let api: TypedApi; + + // init other variable + const initChainId = 42; + + before(async () => { + // init variables got from await and async + ethClient = await getPublicClient(ETH_LOCAL_URL); + api = await getDevnetApi() + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(keyPair.publicKey)) + + }); + + it("EVM chain id update is ok", async () => { + let chainId = await ethClient.getChainId(); + // init chain id should be 42 + assert.equal(chainId, initChainId); + + const newChainId = BigInt(100) + await forceSetChainID(api, newChainId) + + chainId = await ethClient.getChainId(); + assert.equal(chainId, newChainId); + + await forceSetChainID(api, BigInt(initChainId)) + + chainId = await ethClient.getChainId(); + // back to original value for other tests. and we can run it repeatedly + assert.equal(chainId, initChainId); + + }); + + it("EVM chain id is the same, only sudo can change it.", async () => { + let chainId = await ethClient.getChainId(); + // init chain id should be 42 + assert.equal(chainId, initChainId); + + // invalide signer for set chain ID + let signer = getPolkadotSigner( + keyPair.publicKey, + "Sr25519", + keyPair.sign, + ) + + let tx = api.tx.AdminUtils.sudo_set_evm_chain_id({ chain_id: BigInt(100) }) + await waitForTransactionCompletion(api, tx, signer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + + // extrinsic should be failed and chain ID not updated. + chainId = await ethClient.getChainId(); + assert.equal(chainId, 42); + + }); +}); \ No newline at end of file diff --git a/evm-tests/test/eth.incremental.deploy.test.ts b/evm-tests/test/eth.incremental.deploy.test.ts new file mode 100644 index 0000000000..c22187538d --- /dev/null +++ b/evm-tests/test/eth.incremental.deploy.test.ts @@ -0,0 +1,61 @@ + + +import * as assert from "assert"; +import * as chai from "chai"; + +import { getDevnetApi } from "../src/substrate" +import { generateRandomEthersWallet, getPublicClient } from "../src/utils"; +import { ETH_LOCAL_URL } from "../src/config"; +import { devnet } from "@polkadot-api/descriptors" +import { PublicClient } from "viem"; +import { TypedApi } from "polkadot-api"; +import { INCREMENTAL_CONTRACT_ABI, INCREMENTAL_CONTRACT_BYTECODE } from "../src/contracts/incremental"; +import { toViemAddress } from "../src/address-utils"; +import { ethers } from "ethers" +import { disableWhiteListCheck, forceSetBalanceToEthAddress } from "../src/subtensor"; + +describe("bridge token contract deployment", () => { + // init eth part + const wallet = generateRandomEthersWallet(); + let publicClient: PublicClient; + + // init substrate part + let api: TypedApi + + before(async () => { + publicClient = await getPublicClient(ETH_LOCAL_URL) + api = await getDevnetApi() + + await forceSetBalanceToEthAddress(api, wallet.address) + await disableWhiteListCheck(api, true) + }); + + it("Can deploy incremental smart contract", async () => { + const contractFactory = new ethers.ContractFactory(INCREMENTAL_CONTRACT_ABI, INCREMENTAL_CONTRACT_BYTECODE, wallet) + const contract = await contractFactory.deploy() + await contract.waitForDeployment() + + const value = await publicClient.readContract({ + abi: INCREMENTAL_CONTRACT_ABI, + address: toViemAddress(contract.target.toString()), + functionName: "retrieve", + args: [] + }) + assert.equal(value, 0) + + const newValue = 1234 + + const deployContract = new ethers.Contract(contract.target.toString(), INCREMENTAL_CONTRACT_ABI, wallet) + const storeTx = await deployContract.store(newValue) + await storeTx.wait() + + const newValueAfterStore = await publicClient.readContract({ + abi: INCREMENTAL_CONTRACT_ABI, + address: toViemAddress(contract.target.toString()), + functionName: "retrieve", + args: [] + }) + + assert.equal(newValue, newValueAfterStore) + }); +}); diff --git a/evm-tests/test/eth.substrate-transfer.test.ts b/evm-tests/test/eth.substrate-transfer.test.ts new file mode 100644 index 0000000000..9e3a2b2050 --- /dev/null +++ b/evm-tests/test/eth.substrate-transfer.test.ts @@ -0,0 +1,412 @@ +import * as assert from "assert"; + +import { getDevnetApi, waitForTransactionCompletion, getRandomSubstrateSigner, } from "../src/substrate" +import { getPublicClient } from "../src/utils"; +import { ETH_LOCAL_URL, IBALANCETRANSFER_ADDRESS, IBalanceTransferABI } from "../src/config"; +import { devnet, MultiAddress } from "@polkadot-api/descriptors" +import { PublicClient } from "viem"; +import { TypedApi, Binary, FixedSizeBinary } from "polkadot-api"; +import { generateRandomEthersWallet } from "../src/utils"; +import { tao, raoToEth, bigintToRao, compareEthBalanceWithTxFee } from "../src/balance-math"; +import { toViemAddress, convertPublicKeyToSs58, convertH160ToSS58, ss58ToH160, ss58ToEthAddress, ethAddressToH160 } from "../src/address-utils" +import { ethers } from "ethers" +import { estimateTransactionCost, getContract } from "../src/eth" + +import { WITHDRAW_CONTRACT_ABI, WITHDRAW_CONTRACT_BYTECODE } from "../src/contracts/withdraw" + +import { forceSetBalanceToEthAddress, forceSetBalanceToSs58Address, disableWhiteListCheck } from "../src/subtensor"; + +describe("Balance transfers between substrate and EVM", () => { + const gwei = BigInt("1000000000"); + // init eth part + const wallet = generateRandomEthersWallet(); + const wallet2 = generateRandomEthersWallet(); + let publicClient: PublicClient; + const provider = new ethers.JsonRpcProvider(ETH_LOCAL_URL); + // init substrate part + const signer = getRandomSubstrateSigner(); + let api: TypedApi + + before(async () => { + + publicClient = await getPublicClient(ETH_LOCAL_URL) + api = await getDevnetApi() + + await forceSetBalanceToEthAddress(api, wallet.address) + await forceSetBalanceToEthAddress(api, wallet2.address) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(signer.publicKey)) + await disableWhiteListCheck(api, true) + }); + + it("Can transfer token from EVM to EVM", async () => { + const senderBalance = await publicClient.getBalance({ address: toViemAddress(wallet.address) }) + const receiverBalance = await publicClient.getBalance({ address: toViemAddress(wallet2.address) }) + const transferBalance = raoToEth(tao(1)) + const tx = { + to: wallet2.address, + value: transferBalance.toString() + } + const txFee = await estimateTransactionCost(provider, tx) + + const txResponse = await wallet.sendTransaction(tx) + await txResponse.wait(); + + + const senderBalanceAfterTransfer = await publicClient.getBalance({ address: toViemAddress(wallet.address) }) + const receiverBalanceAfterTranser = await publicClient.getBalance({ address: toViemAddress(wallet2.address) }) + + assert.equal(senderBalanceAfterTransfer, senderBalance - transferBalance - txFee) + assert.equal(receiverBalance, receiverBalanceAfterTranser - transferBalance) + }); + + it("Can transfer token from Substrate to EVM", async () => { + const ss58Address = convertH160ToSS58(wallet.address) + const senderBalance = (await api.query.System.Account.getValue(ss58Address)).data.free + const receiverBalance = await publicClient.getBalance({ address: toViemAddress(wallet.address) }) + const transferBalance = tao(1) + + const tx = api.tx.Balances.transfer_keep_alive({ value: transferBalance, dest: MultiAddress.Id(ss58Address) }) + await waitForTransactionCompletion(api, tx, signer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + + + const senderBalanceAfterTransfer = (await api.query.System.Account.getValue(ss58Address)).data.free + const receiverBalanceAfterTranser = await publicClient.getBalance({ address: toViemAddress(wallet.address) }) + + assert.equal(senderBalanceAfterTransfer, senderBalance + transferBalance) + assert.equal(receiverBalance, receiverBalanceAfterTranser - raoToEth(transferBalance)) + }); + + it("Can transfer token from EVM to Substrate", async () => { + const contract = getContract(IBALANCETRANSFER_ADDRESS, IBalanceTransferABI, wallet) + const senderBalance = await publicClient.getBalance({ address: toViemAddress(wallet.address) }) + const receiverBalance = (await api.query.System.Account.getValue(convertPublicKeyToSs58(signer.publicKey))).data.free + const transferBalance = raoToEth(tao(1)) + + const tx = await contract.transfer(signer.publicKey, { value: transferBalance.toString() }) + await tx.wait() + + + const senderBalanceAfterTransfer = await publicClient.getBalance({ address: toViemAddress(wallet.address) }) + const receiverBalanceAfterTranser = (await api.query.System.Account.getValue(convertPublicKeyToSs58(signer.publicKey))).data.free + + compareEthBalanceWithTxFee(senderBalanceAfterTransfer, senderBalance - transferBalance) + assert.equal(receiverBalance, receiverBalanceAfterTranser - tao(1)) + }); + + it("Transfer from EVM to substrate using evm::withdraw", async () => { + const ss58Address = convertPublicKeyToSs58(signer.publicKey) + const senderBalance = (await api.query.System.Account.getValue(ss58Address)).data.free + const ethAddresss = ss58ToH160(ss58Address); + + // transfer token to mirror eth address + const ethTransfer = { + to: ss58ToEthAddress(ss58Address), + value: raoToEth(tao(2)).toString() + } + + const txResponse = await wallet.sendTransaction(ethTransfer) + await txResponse.wait(); + + const tx = api.tx.EVM.withdraw({ address: ethAddresss, value: tao(1) }) + const txFee = (await tx.getPaymentInfo(ss58Address)).partial_fee + + await waitForTransactionCompletion(api, tx, signer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + + const senderBalanceAfterWithdraw = (await api.query.System.Account.getValue(ss58Address)).data.free + + assert.equal(senderBalance, senderBalanceAfterWithdraw - tao(1) + txFee) + }); + + it("Transfer from EVM to substrate using evm::call", async () => { + const ss58Address = convertPublicKeyToSs58(signer.publicKey) + const ethAddresss = ss58ToH160(ss58Address); + + // transfer token to mirror eth address + const ethTransfer = { + to: ss58ToEthAddress(ss58Address), + value: raoToEth(tao(2)).toString() + } + + const txResponse = await wallet.sendTransaction(ethTransfer) + await txResponse.wait(); + + const source: FixedSizeBinary<20> = ethAddresss; + const target = ethAddressToH160(wallet.address) + const receiverBalance = await publicClient.getBalance({ address: toViemAddress(wallet.address) }) + + // all these parameter value are tricky, any change could make the call failed + const tx = api.tx.EVM.call({ + source: source, + target: target, + // it is U256 in the extrinsic. + value: [raoToEth(tao(1)), tao(0), tao(0), tao(0)], + gas_limit: BigInt(1000000), + // it is U256 in the extrinsic. + max_fee_per_gas: [BigInt(10e9), BigInt(0), BigInt(0), BigInt(0)], + max_priority_fee_per_gas: undefined, + input: Binary.fromText(""), + nonce: undefined, + access_list: [] + }) + // txFee not accurate + const txFee = (await tx.getPaymentInfo(ss58Address)).partial_fee + + await waitForTransactionCompletion(api, tx, signer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + + + const receiverBalanceAfterCall = await publicClient.getBalance({ address: toViemAddress(wallet.address) }) + assert.equal(receiverBalanceAfterCall, receiverBalance + raoToEth(tao(1))) + }); + + it("Forward value in smart contract", async () => { + + + const contractFactory = new ethers.ContractFactory(WITHDRAW_CONTRACT_ABI, WITHDRAW_CONTRACT_BYTECODE, wallet) + const contract = await contractFactory.deploy() + await contract.waitForDeployment() + + const code = await publicClient.getCode({ address: toViemAddress(contract.target.toString()) }) + if (code === undefined) { + throw new Error("code length is wrong for deployed contract") + } + assert.ok(code.length > 100) + + // transfer 2 TAO to contract + const ethTransfer = { + to: contract.target.toString(), + value: raoToEth(tao(2)).toString() + } + + const txResponse = await wallet.sendTransaction(ethTransfer) + await txResponse.wait(); + + const contractBalance = await publicClient.getBalance({ address: toViemAddress(contract.target.toString()) }) + const callerBalance = await publicClient.getBalance({ address: toViemAddress(wallet.address) }) + + const contractForCall = new ethers.Contract(contract.target.toString(), WITHDRAW_CONTRACT_ABI, wallet) + + const withdrawTx = await contractForCall.withdraw( + raoToEth(tao(1)).toString() + ); + + await withdrawTx.wait(); + + const contractBalanceAfterWithdraw = await publicClient.getBalance({ address: toViemAddress(contract.target.toString()) }) + const callerBalanceAfterWithdraw = await publicClient.getBalance({ address: toViemAddress(wallet.address) }) + + compareEthBalanceWithTxFee(callerBalanceAfterWithdraw, callerBalance + raoToEth(tao(1))) + assert.equal(contractBalance, contractBalanceAfterWithdraw + raoToEth(tao(1))) + }); + + it("Transfer full balance", async () => { + const ethBalance = await publicClient.getBalance({ address: toViemAddress(wallet.address) }) + const receiverBalance = await publicClient.getBalance({ address: toViemAddress(wallet2.address) }) + const tx = { + to: wallet2.address, + value: ethBalance.toString(), + }; + const txPrice = await estimateTransactionCost(provider, tx); + const finalTx = { + to: wallet2.address, + value: (ethBalance - txPrice).toString(), + }; + try { + // transfer should be failed since substrate requires existial balance to keep account + const txResponse = await wallet.sendTransaction(finalTx) + await txResponse.wait(); + } catch (error) { + if (error instanceof Error) { + assert.equal((error as any).code, "INSUFFICIENT_FUNDS") + assert.equal(error.toString().includes("insufficient funds"), true) + } + } + + const receiverBalanceAfterTransfer = await publicClient.getBalance({ address: toViemAddress(wallet2.address) }) + assert.equal(receiverBalance, receiverBalanceAfterTransfer) + }) + + it("Transfer more than owned balance should fail", async () => { + const ethBalance = await publicClient.getBalance({ address: toViemAddress(wallet.address) }) + const receiverBalance = await publicClient.getBalance({ address: toViemAddress(wallet2.address) }) + const tx = { + to: wallet2.address, + value: (ethBalance + raoToEth(tao(1))).toString(), + }; + + try { + // transfer should be failed since substrate requires existial balance to keep account + const txResponse = await wallet.sendTransaction(tx) + await txResponse.wait(); + } catch (error) { + if (error instanceof Error) { + assert.equal((error as any).code, "INSUFFICIENT_FUNDS") + assert.equal(error.toString().includes("insufficient funds"), true) + } + } + + const receiverBalanceAfterTransfer = await publicClient.getBalance({ address: toViemAddress(wallet2.address) }) + assert.equal(receiverBalance, receiverBalanceAfterTransfer) + }); + + it("Transfer more than u64::max in substrate equivalent should receive error response", async () => { + const receiverBalance = await publicClient.getBalance({ address: toViemAddress(wallet2.address) }) + try { + const tx = { + to: wallet2.address, + value: raoToEth(BigInt(2) ** BigInt(64)).toString(), + }; + // transfer should be failed since substrate requires existial balance to keep account + const txResponse = await wallet.sendTransaction(tx) + await txResponse.wait(); + } catch (error) { + if (error instanceof Error) { + assert.equal((error as any).code, "INSUFFICIENT_FUNDS") + assert.equal(error.toString().includes("insufficient funds"), true) + } + } + + const contract = getContract(IBALANCETRANSFER_ADDRESS, IBalanceTransferABI, wallet) + try { + const tx = await contract.transfer(signer.publicKey, { value: raoToEth(BigInt(2) ** BigInt(64)).toString() }) + await tx.await() + } catch (error) { + if (error instanceof Error) { + console.log(error.toString()) + assert.equal(error.toString().includes("revert data"), true) + } + } + + try { + const dest = convertH160ToSS58(wallet2.address) + const tx = api.tx.Balances.transfer_keep_alive({ value: bigintToRao(BigInt(2) ** BigInt(64)), dest: MultiAddress.Id(dest) }) + await waitForTransactionCompletion(api, tx, signer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + } catch (error) { + if (error instanceof Error) { + console.log(error.toString()) + assert.equal(error.toString().includes("Cannot convert"), true) + } + } + + try { + const dest = ethAddressToH160(wallet2.address) + const tx = api.tx.EVM.withdraw({ value: bigintToRao(BigInt(2) ** BigInt(64)), address: dest }) + await waitForTransactionCompletion(api, tx, signer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + } catch (error) { + if (error instanceof Error) { + assert.equal(error.toString().includes("Cannot convert"), true) + } + } + + try { + const source = ethAddressToH160(wallet.address) + const target = ethAddressToH160(wallet2.address) + const tx = api.tx.EVM.call({ + source: source, + target: target, + // it is U256 in the extrinsic, the value is more than u64::MAX + value: [raoToEth(tao(1)), tao(0), tao(0), tao(1)], + gas_limit: BigInt(1000000), + // it is U256 in the extrinsic. + max_fee_per_gas: [BigInt(10e9), BigInt(0), BigInt(0), BigInt(0)], + max_priority_fee_per_gas: undefined, + input: Binary.fromText(""), + nonce: undefined, + access_list: [] + }) + await waitForTransactionCompletion(api, tx, signer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + } catch (error) { + if (error instanceof Error) { + console.log(error.toString()) + assert.equal((error as any).code, "INSUFFICIENT_FUNDS") + assert.equal(error.toString().includes("insufficient funds"), true) + } + } + + const receiverBalanceAfterTransfer = await publicClient.getBalance({ address: toViemAddress(wallet2.address) }) + assert.equal(receiverBalance, receiverBalanceAfterTransfer) + }); + + it("Gas price should be 10 GWei", async () => { + const feeData = await provider.getFeeData(); + assert.equal(feeData.gasPrice, BigInt(10000000000)); + }); + + + it("max_fee_per_gas and max_priority_fee_per_gas affect transaction fee properly", async () => { + + const testCases = [ + [10, 0, 21000 * 10 * 1e9], + [10, 10, 21000 * 10 * 1e9], + [11, 0, 21000 * 10 * 1e9], + [11, 1, (21000 * 10 + 21000) * 1e9], + [11, 2, (21000 * 10 + 21000) * 1e9], + ]; + + for (let i in testCases) { + const tc = testCases[i]; + const actualFee = await transferAndGetFee( + wallet, wallet2, publicClient, + gwei * BigInt(tc[0]), + gwei * BigInt(tc[1]) + ); + assert.equal(actualFee, BigInt(tc[2])) + } + }); + + it("Low max_fee_per_gas gets transaction rejected", async () => { + try { + await transferAndGetFee(wallet, wallet2, publicClient, gwei * BigInt(9), BigInt(0)) + } catch (error) { + if (error instanceof Error) { + console.log(error.toString()) + assert.equal(error.toString().includes("gas price less than block base fee"), true) + } + } + }); + + it("max_fee_per_gas lower than max_priority_fee_per_gas gets transaction rejected", async () => { + try { + await transferAndGetFee(wallet, wallet2, publicClient, gwei * BigInt(10), gwei * BigInt(11)) + } catch (error) { + if (error instanceof Error) { + assert.equal(error.toString().includes("priorityFee cannot be more than maxFee"), true) + } + } + }); +}); + +async function transferAndGetFee(wallet: ethers.Wallet, wallet2: ethers.Wallet, client: PublicClient, max_fee_per_gas: BigInt, max_priority_fee_per_gas: BigInt) { + + const ethBalanceBefore = await client.getBalance({ address: toViemAddress(wallet.address) }) + // Send TAO + const tx = { + to: wallet2.address, + value: raoToEth(tao(1)).toString(), + // EIP-1559 transaction parameters + maxPriorityFeePerGas: max_priority_fee_per_gas.toString(), + maxFeePerGas: max_fee_per_gas.toString(), + gasLimit: 21000, + }; + + // Send the transaction + const txResponse = await wallet.sendTransaction(tx); + await txResponse.wait() + + // Check balances + const ethBalanceAfter = await client.getBalance({ address: toViemAddress(wallet.address) }) + const fee = ethBalanceBefore - ethBalanceAfter - raoToEth(tao(1)) + + return fee; +} \ No newline at end of file diff --git a/evm-tests/test/metagraph.precompile.test.ts b/evm-tests/test/metagraph.precompile.test.ts new file mode 100644 index 0000000000..94c0df8861 --- /dev/null +++ b/evm-tests/test/metagraph.precompile.test.ts @@ -0,0 +1,147 @@ +import * as assert from "assert"; + +import { getAliceSigner, getClient, getDevnetApi, waitForTransactionCompletion, convertPublicKeyToMultiAddress, getRandomSubstrateKeypair, getSignerFromKeypair } from "../src/substrate" +import { getPublicClient, } from "../src/utils"; +import { ETH_LOCAL_URL, SUB_LOCAL_URL, } from "../src/config"; +import { devnet } from "@polkadot-api/descriptors" +import { PublicClient } from "viem"; +import { PolkadotSigner, TypedApi } from "polkadot-api"; +import { toViemAddress, convertPublicKeyToSs58 } from "../src/address-utils" +import { IMetagraphABI, IMETAGRAPH_ADDRESS } from "../src/contracts/metagraph" + +describe("Test the EVM chain ID", () => { + // init substrate part + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + let publicClient: PublicClient; + + let api: TypedApi + + // sudo account alice as signer + let alice: PolkadotSigner; + + // init other variable + let subnetId = 0; + + before(async () => { + // init variables got from await and async + publicClient = await getPublicClient(ETH_LOCAL_URL) + const subClient = await getClient(SUB_LOCAL_URL) + api = await getDevnetApi() + alice = await getAliceSigner(); + + { + const multiAddress = convertPublicKeyToMultiAddress(hotkey.publicKey) + const internalCall = api.tx.Balances.force_set_balance({ who: multiAddress, new_free: BigInt(1e12) }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + } + + { + const multiAddress = convertPublicKeyToMultiAddress(coldkey.publicKey) + const internalCall = api.tx.Balances.force_set_balance({ who: multiAddress, new_free: BigInt(1e12) }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionCompletion(api, tx, alice) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + } + + const signer = getSignerFromKeypair(coldkey) + const registerNetworkTx = api.tx.SubtensorModule.register_network({ hotkey: convertPublicKeyToSs58(hotkey.publicKey) }) + await waitForTransactionCompletion(api, registerNetworkTx, signer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + + let totalNetworks = await api.query.SubtensorModule.TotalNetworks.getValue() + assert.ok(totalNetworks > 1) + subnetId = totalNetworks - 1 + + let uid_count = + await api.query.SubtensorModule.SubnetworkN.getValue(subnetId) + if (uid_count === 0) { + const tx = api.tx.SubtensorModule.burned_register({ hotkey: convertPublicKeyToSs58(hotkey.publicKey), netuid: subnetId }) + await waitForTransactionCompletion(api, tx, signer) + .then(() => { }) + .catch((error) => { console.log(`transaction error ${error}`) }); + } + }) + + it("Metagraph data access via precompile contract is ok", async () => { + const uid = 0 + const uid_count = await publicClient.readContract({ + abi: IMetagraphABI, + address: toViemAddress(IMETAGRAPH_ADDRESS), + functionName: "getUidCount", + args: [subnetId] + }) + // back to original value for other tests. and we can run it repeatedly + assert.ok(uid_count != undefined); + + // const axon = api.query.SubtensorModule.Axons.getValue() + + const axon = await publicClient.readContract({ + abi: IMetagraphABI, + address: toViemAddress(IMETAGRAPH_ADDRESS), + functionName: "getAxon", + args: [subnetId, uid] + }) + + assert.ok(axon != undefined); + if (axon instanceof Object) { + assert.ok(axon != undefined); + if ("block" in axon) { + assert.ok(axon.block != undefined); + } else { + throw new Error("block not included in axon") + } + + if ("version" in axon) { + assert.ok(axon.version != undefined); + } else { + throw new Error("version not included in axon") + } + + if ("ip" in axon) { + assert.ok(axon.ip != undefined); + } else { + throw new Error("ip not included in axon") + } + + if ("port" in axon) { + assert.ok(axon.port != undefined); + } else { + throw new Error("port not included in axon") + } + + if ("ip_type" in axon) { + assert.ok(axon.ip_type != undefined); + } else { + throw new Error("ip_type not included in axon") + } + + if ("protocol" in axon) { + assert.ok(axon.protocol != undefined); + } else { + throw new Error("protocol not included in axon") + } + } + + const methodList = ["getEmission", "getVtrust", "getValidatorStatus", "getLastUpdate", "getIsActive", + "getHotkey", "getColdkey" + ] + for (const method of methodList) { + const value = await publicClient.readContract({ + abi: IMetagraphABI, + address: toViemAddress(IMETAGRAPH_ADDRESS), + functionName: method, + args: [subnetId, uid] + }) + + assert.ok(value != undefined); + } + }); +}); \ No newline at end of file diff --git a/evm-tests/test/neuron.precompile.emission-check.test.ts b/evm-tests/test/neuron.precompile.emission-check.test.ts new file mode 100644 index 0000000000..ac609c1e27 --- /dev/null +++ b/evm-tests/test/neuron.precompile.emission-check.test.ts @@ -0,0 +1,72 @@ +import * as assert from "assert"; + +import { getAliceSigner, getClient, getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { getPublicClient, } from "../src/utils"; +import { ETH_LOCAL_URL, SUB_LOCAL_URL, } from "../src/config"; +import { devnet } from "@polkadot-api/descriptors" +import { PublicClient } from "viem"; +import { PolkadotSigner, TypedApi } from "polkadot-api"; +import { convertPublicKeyToSs58, } from "../src/address-utils" +import { ethers } from "ethers" +import { INEURON_ADDRESS, INeuronABI } from "../src/contracts/neuron" +import { generateRandomEthersWallet } from "../src/utils" +import { forceSetBalanceToSs58Address, forceSetBalanceToEthAddress, addNewSubnetwork } from "../src/subtensor" + +describe("Test the EVM chain ID", () => { + // init eth part + const wallet = generateRandomEthersWallet(); + + // init substrate part + const hotkey = getRandomSubstrateKeypair(); + const hotkey2 = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + let publicClient: PublicClient; + + let api: TypedApi + + // sudo account alice as signer + let alice: PolkadotSigner; + + before(async () => { + // init variables got from await and async + publicClient = await getPublicClient(ETH_LOCAL_URL) + const subClient = await getClient(SUB_LOCAL_URL) + api = await getDevnetApi() + alice = await getAliceSigner(); + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey2.publicKey)) + + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) + await forceSetBalanceToEthAddress(api, wallet.address) + + const netuid = await addNewSubnetwork(api, hotkey2, coldkey) + console.log("test on subnet ", netuid) + }) + + it("Burned register and check emission", async () => { + let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 + const uid = await api.query.SubtensorModule.SubnetworkN.getValue(netuid) + const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet); + + const tx = await contract.burnedRegister( + netuid, + hotkey.publicKey + ); + await tx.wait(); + + const uidAfterNew = await api.query.SubtensorModule.SubnetworkN.getValue(netuid) + assert.equal(uid + 1, uidAfterNew) + + const key = await api.query.SubtensorModule.Keys.getValue(netuid, uid) + assert.equal(key, convertPublicKeyToSs58(hotkey.publicKey)) + + let i = 0; + while (i < 10) { + const emission = await api.query.SubtensorModule.PendingEmission.getValue(netuid) + + console.log("emission is ", emission); + await new Promise((resolve) => setTimeout(resolve, 2000)); + i += 1; + } + }) +}); \ No newline at end of file diff --git a/evm-tests/test/neuron.precompile.reveal-weights.test.ts b/evm-tests/test/neuron.precompile.reveal-weights.test.ts new file mode 100644 index 0000000000..85125f0956 --- /dev/null +++ b/evm-tests/test/neuron.precompile.reveal-weights.test.ts @@ -0,0 +1,142 @@ +import * as assert from "assert"; +import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { devnet } from "@polkadot-api/descriptors" +import { PolkadotSigner, TypedApi } from "polkadot-api"; +import { convertPublicKeyToSs58, convertH160ToSS58 } from "../src/address-utils" +import { Vec, Tuple, VecFixed, u16, u8, u64 } from "@polkadot/types-codec"; +import { TypeRegistry } from "@polkadot/types"; +import { ethers } from "ethers" +import { INEURON_ADDRESS, INeuronABI } from "../src/contracts/neuron" +import { generateRandomEthersWallet } from "../src/utils" +import { convertH160ToPublicKey } from "../src/address-utils" +import { blake2AsU8a } from "@polkadot/util-crypto" +import { + forceSetBalanceToEthAddress, forceSetBalanceToSs58Address, addNewSubnetwork, setCommitRevealWeightsEnabled, setWeightsSetRateLimit, burnedRegister, + setTempo, setCommitRevealWeightsInterval +} from "../src/subtensor" + +// hardcode some values for reveal hash +const uids = [1]; +const values = [5]; +const salt = [9]; +const version_key = 0; + +function getCommitHash(netuid: number, address: string) { + const registry = new TypeRegistry(); + let publicKey = convertH160ToPublicKey(address); + + const tupleData = new Tuple( + registry, + [ + VecFixed.with(u8, 32), + u16, + Vec.with(u16), + Vec.with(u16), + Vec.with(u16), + u64, + ], + [publicKey, netuid, uids, values, salt, version_key] + ); + + const hash = blake2AsU8a(tupleData.toU8a()); + return hash; +} + +describe("Test neuron precompile reveal weights", () => { + // init eth part + const wallet = generateRandomEthersWallet(); + + // init substrate part + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + + let api: TypedApi + + // sudo account alice as signer + let alice: PolkadotSigner; + before(async () => { + // init variables got from await and async + api = await getDevnetApi() + alice = await getAliceSigner(); + + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(alice.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) + await forceSetBalanceToEthAddress(api, wallet.address) + let netuid = await addNewSubnetwork(api, hotkey, coldkey) + + console.log("test the case on subnet ", netuid) + + // enable commit reveal feature + await setCommitRevealWeightsEnabled(api, netuid, true) + // set it as 0, we can set the weight anytime + await setWeightsSetRateLimit(api, netuid, BigInt(0)) + + const ss58Address = convertH160ToSS58(wallet.address) + await burnedRegister(api, netuid, ss58Address, coldkey) + + const uid = await api.query.SubtensorModule.Uids.getValue( + netuid, + ss58Address + ) + // eth wallet account should be the first neuron in the subnet + assert.equal(uid, uids[0]) + }) + + it("EVM neuron commit weights via call precompile", async () => { + let totalNetworks = await api.query.SubtensorModule.TotalNetworks.getValue() + const subnetId = totalNetworks - 1 + const commitHash = getCommitHash(subnetId, wallet.address) + const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet); + const tx = await contract.commitWeights(subnetId, commitHash) + await tx.wait() + + const ss58Address = convertH160ToSS58(wallet.address) + + const weightsCommit = await api.query.SubtensorModule.WeightCommits.getValue(subnetId, ss58Address) + if (weightsCommit === undefined) { + throw new Error("submit weights failed") + } + assert.ok(weightsCommit.length > 0) + }) + + it("EVM neuron reveal weights via call precompile", async () => { + let totalNetworks = await api.query.SubtensorModule.TotalNetworks.getValue() + const netuid = totalNetworks - 1 + const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet); + // set tempo or epoch large, then enough time to reveal weight + await setTempo(api, netuid, 60000) + // set interval epoch as 0, we can reveal at the same epoch + await setCommitRevealWeightsInterval(api, netuid, BigInt(0)) + + const tx = await contract.revealWeights( + netuid, + uids, + values, + salt, + version_key + ); + await tx.wait() + const ss58Address = convertH160ToSS58(wallet.address) + + // check the weight commit is removed after reveal successfully + const weightsCommit = await api.query.SubtensorModule.WeightCommits.getValue(netuid, ss58Address) + assert.equal(weightsCommit, undefined) + + // check the weight is set after reveal with correct uid + const neuron_uid = await api.query.SubtensorModule.Uids.getValue( + netuid, + ss58Address + ) + + const weights = await api.query.SubtensorModule.Weights.getValue(netuid, neuron_uid) + + if (weights === undefined) { + throw new Error("weights not available onchain") + } + for (const weight of weights) { + assert.equal(weight[0], neuron_uid) + assert.ok(weight[1] !== undefined) + } + }) +}); \ No newline at end of file diff --git a/evm-tests/test/neuron.precompile.serve.axon-prometheus.test.ts b/evm-tests/test/neuron.precompile.serve.axon-prometheus.test.ts new file mode 100644 index 0000000000..aee84f130c --- /dev/null +++ b/evm-tests/test/neuron.precompile.serve.axon-prometheus.test.ts @@ -0,0 +1,162 @@ +import * as assert from "assert"; +import { getAliceSigner, getClient, getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { SUB_LOCAL_URL, } from "../src/config"; +import { devnet } from "@polkadot-api/descriptors" +import { PolkadotSigner, TypedApi } from "polkadot-api"; +import { convertPublicKeyToSs58, convertH160ToSS58 } from "../src/address-utils" +import { ethers } from "ethers" +import { INEURON_ADDRESS, INeuronABI } from "../src/contracts/neuron" +import { generateRandomEthersWallet } from "../src/utils" +import { forceSetBalanceToEthAddress, forceSetBalanceToSs58Address, addNewSubnetwork, burnedRegister } from "../src/subtensor" + +describe("Test neuron precompile Serve Axon Prometheus", () => { + // init eth part + const wallet1 = generateRandomEthersWallet(); + const wallet2 = generateRandomEthersWallet(); + const wallet3 = generateRandomEthersWallet(); + + // init substrate part + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + + let api: TypedApi + + // sudo account alice as signer + let alice: PolkadotSigner; + before(async () => { + // init variables got from await and async + const subClient = await getClient(SUB_LOCAL_URL) + api = await getDevnetApi() + alice = await getAliceSigner(); + + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(alice.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) + await forceSetBalanceToEthAddress(api, wallet1.address) + await forceSetBalanceToEthAddress(api, wallet2.address) + await forceSetBalanceToEthAddress(api, wallet3.address) + let netuid = await addNewSubnetwork(api, hotkey, coldkey) + + console.log("test the case on subnet ", netuid) + + await burnedRegister(api, netuid, convertH160ToSS58(wallet1.address), coldkey) + await burnedRegister(api, netuid, convertH160ToSS58(wallet2.address), coldkey) + await burnedRegister(api, netuid, convertH160ToSS58(wallet3.address), coldkey) + }) + + it("Serve Axon", async () => { + let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 + const version = 0; + const ip = 1; + const port = 2; + const ipType = 4; + const protocol = 0; + const placeholder1 = 8; + const placeholder2 = 9; + + const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet1); + + const tx = await contract.serveAxon( + netuid, + version, + ip, + port, + ipType, + protocol, + placeholder1, + placeholder2 + ); + await tx.wait(); + + const axon = await api.query.SubtensorModule.Axons.getValue( + netuid, + convertH160ToSS58(wallet1.address) + ) + assert.notEqual(axon?.block, undefined) + assert.equal(axon?.version, version) + assert.equal(axon?.ip, ip) + assert.equal(axon?.port, port) + assert.equal(axon?.ip_type, ipType) + assert.equal(axon?.protocol, protocol) + assert.equal(axon?.placeholder1, placeholder1) + assert.equal(axon?.placeholder2, placeholder2) + }); + + it("Serve Axon TLS", async () => { + let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 + const version = 0; + const ip = 1; + const port = 2; + const ipType = 4; + const protocol = 0; + const placeholder1 = 8; + const placeholder2 = 9; + // certificate length is 65 + const certificate = new Uint8Array([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, 65, + ]); + + const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet2); + + const tx = await contract.serveAxonTls( + netuid, + version, + ip, + port, + ipType, + protocol, + placeholder1, + placeholder2, + certificate + ); + await tx.wait(); + + const axon = await api.query.SubtensorModule.Axons.getValue( + netuid, + convertH160ToSS58(wallet2.address)) + + assert.notEqual(axon?.block, undefined) + assert.equal(axon?.version, version) + assert.equal(axon?.ip, ip) + assert.equal(axon?.port, port) + assert.equal(axon?.ip_type, ipType) + assert.equal(axon?.protocol, protocol) + assert.equal(axon?.placeholder1, placeholder1) + assert.equal(axon?.placeholder2, placeholder2) + }); + + it("Serve Prometheus", async () => { + let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 + const version = 0; + const ip = 1; + const port = 2; + const ipType = 4; + + const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet3); + + const tx = await contract.servePrometheus( + netuid, + version, + ip, + port, + ipType + ); + await tx.wait(); + + const prometheus = ( + await api.query.SubtensorModule.Prometheus.getValue( + netuid, + convertH160ToSS58(wallet3.address) + ) + ) + + assert.notEqual(prometheus?.block, undefined) + assert.equal(prometheus?.version, version) + assert.equal(prometheus?.ip, ip) + assert.equal(prometheus?.port, port) + assert.equal(prometheus?.ip_type, ipType) + }); +}); \ No newline at end of file diff --git a/evm-tests/test/neuron.precompile.set-weights.test.ts b/evm-tests/test/neuron.precompile.set-weights.test.ts new file mode 100644 index 0000000000..393c2b97b8 --- /dev/null +++ b/evm-tests/test/neuron.precompile.set-weights.test.ts @@ -0,0 +1,65 @@ +import * as assert from "assert"; + +import { getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { devnet } from "@polkadot-api/descriptors" +import { TypedApi } from "polkadot-api"; +import { convertH160ToSS58, convertPublicKeyToSs58, } from "../src/address-utils" +import { ethers } from "ethers" +import { INEURON_ADDRESS, INeuronABI } from "../src/contracts/neuron" +import { generateRandomEthersWallet } from "../src/utils" +import { + forceSetBalanceToSs58Address, forceSetBalanceToEthAddress, addNewSubnetwork, burnedRegister, setCommitRevealWeightsEnabled, + setWeightsSetRateLimit +} from "../src/subtensor" + +describe("Test neuron precompile contract, set weights function", () => { + // init eth part + const wallet = generateRandomEthersWallet(); + + // init substrate part + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + + let api: TypedApi + + before(async () => { + api = await getDevnetApi() + + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) + + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) + await forceSetBalanceToEthAddress(api, wallet.address) + + const netuid = await addNewSubnetwork(api, hotkey, coldkey) + console.log("test on subnet ", netuid) + + await burnedRegister(api, netuid, convertH160ToSS58(wallet.address), coldkey) + const uid = await api.query.SubtensorModule.Uids.getValue(netuid, convertH160ToSS58(wallet.address)) + assert.notEqual(uid, undefined) + // disable reveal and enable direct set weights + await setCommitRevealWeightsEnabled(api, netuid, false) + await setWeightsSetRateLimit(api, netuid, BigInt(0)) + }) + + it("Set weights is ok", async () => { + let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 + const uid = await api.query.SubtensorModule.Uids.getValue(netuid, convertH160ToSS58(wallet.address)) + + const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet); + const dests = [1]; + const weights = [2]; + const version_key = 0; + + const tx = await contract.setWeights(netuid, dests, weights, version_key); + + await tx.wait(); + const weightsOnChain = await api.query.SubtensorModule.Weights.getValue(netuid, uid) + + weightsOnChain.forEach((weight, _) => { + const uidInWeight = weight[0]; + const value = weight[1]; + assert.equal(uidInWeight, uid) + assert.ok(value > 0) + }); + }) +}); \ No newline at end of file diff --git a/evm-tests/test/staking.precompile.add-remove.test.ts b/evm-tests/test/staking.precompile.add-remove.test.ts new file mode 100644 index 0000000000..5387e62428 --- /dev/null +++ b/evm-tests/test/staking.precompile.add-remove.test.ts @@ -0,0 +1,326 @@ +import * as assert from "assert"; +import { getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { devnet } from "@polkadot-api/descriptors" +import { PolkadotSigner, TypedApi } from "polkadot-api"; +import { convertPublicKeyToSs58, convertH160ToSS58 } from "../src/address-utils" +import { raoToEth, tao } from "../src/balance-math" +import { ethers } from "ethers" +import { generateRandomEthersWallet, getPublicClient } from "../src/utils" +import { convertH160ToPublicKey } from "../src/address-utils" +import { + forceSetBalanceToEthAddress, forceSetBalanceToSs58Address, addNewSubnetwork, burnedRegister, + sendProxyCall, +} from "../src/subtensor" +import { ETH_LOCAL_URL } from "../src/config"; +import { ISTAKING_ADDRESS, ISTAKING_V2_ADDRESS, IStakingABI, IStakingV2ABI } from "../src/contracts/staking" +import { PublicClient } from "viem"; + +describe("Test neuron precompile reveal weights", () => { + // init eth part + const wallet1 = generateRandomEthersWallet(); + const wallet2 = generateRandomEthersWallet(); + let publicClient: PublicClient; + // init substrate part + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const proxy = getRandomSubstrateKeypair(); + + let api: TypedApi + + // sudo account alice as signer + let alice: PolkadotSigner; + before(async () => { + publicClient = await getPublicClient(ETH_LOCAL_URL) + // init variables got from await and async + api = await getDevnetApi() + + // await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(alice.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(proxy.publicKey)) + await forceSetBalanceToEthAddress(api, wallet1.address) + await forceSetBalanceToEthAddress(api, wallet2.address) + let netuid = await addNewSubnetwork(api, hotkey, coldkey) + + console.log("test the case on subnet ", netuid) + + await burnedRegister(api, netuid, convertH160ToSS58(wallet1.address), coldkey) + await burnedRegister(api, netuid, convertH160ToSS58(wallet2.address), coldkey) + }) + + it("Can add stake", async () => { + let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 + // ETH unit + let stakeBalance = raoToEth(tao(20)) + const stakeBefore = await api.query.SubtensorModule.Alpha.getValue(convertPublicKeyToSs58(hotkey.publicKey), convertH160ToSS58(wallet1.address), netuid) + const contract = new ethers.Contract(ISTAKING_ADDRESS, IStakingABI, wallet1); + const tx = await contract.addStake(hotkey.publicKey, netuid, { value: stakeBalance.toString() }) + await tx.wait() + + const stakeFromContract = BigInt( + await contract.getStake(hotkey.publicKey, convertH160ToPublicKey(wallet1.address), netuid) + ); + + assert.ok(stakeFromContract > stakeBefore) + const stakeAfter = await api.query.SubtensorModule.Alpha.getValue(convertPublicKeyToSs58(hotkey.publicKey), convertH160ToSS58(wallet1.address), netuid) + assert.ok(stakeAfter > stakeBefore) + }) + + it("Can add stake V2", async () => { + let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 + // the unit in V2 is RAO, not ETH + let stakeBalance = tao(20) + const stakeBefore = await api.query.SubtensorModule.Alpha.getValue(convertPublicKeyToSs58(hotkey.publicKey), convertH160ToSS58(wallet2.address), netuid) + const contract = new ethers.Contract(ISTAKING_V2_ADDRESS, IStakingV2ABI, wallet2); + const tx = await contract.addStake(hotkey.publicKey, stakeBalance.toString(), netuid) + await tx.wait() + + const stakeFromContract = BigInt( + await contract.getStake(hotkey.publicKey, convertH160ToPublicKey(wallet2.address), netuid) + ); + + assert.ok(stakeFromContract > stakeBefore) + const stakeAfter = await api.query.SubtensorModule.Alpha.getValue(convertPublicKeyToSs58(hotkey.publicKey), convertH160ToSS58(wallet2.address), netuid) + assert.ok(stakeAfter > stakeBefore) + }) + + it("Can not add stake if subnet doesn't exist", async () => { + // wrong netuid + let netuid = 12345; + let stakeBalance = raoToEth(tao(20)) + const stakeBefore = await api.query.SubtensorModule.Alpha.getValue(convertPublicKeyToSs58(hotkey.publicKey), convertH160ToSS58(wallet1.address), netuid) + const contract = new ethers.Contract(ISTAKING_ADDRESS, IStakingABI, wallet1); + try { + const tx = await contract.addStake(hotkey.publicKey, netuid, { value: stakeBalance.toString() }) + await tx.wait() + assert.fail("Transaction should have failed"); + } catch (error) { + // Transaction failed as expected + } + + const stakeFromContract = BigInt( + await contract.getStake(hotkey.publicKey, convertH160ToPublicKey(wallet1.address), netuid) + ); + assert.equal(stakeFromContract, stakeBefore) + const stakeAfter = await api.query.SubtensorModule.Alpha.getValue(convertPublicKeyToSs58(hotkey.publicKey), convertH160ToSS58(wallet1.address), netuid) + assert.equal(stakeAfter, stakeBefore) + }); + + it("Can not add stake V2 if subnet doesn't exist", async () => { + // wrong netuid + let netuid = 12345; + // the unit in V2 is RAO, not ETH + let stakeBalance = tao(20) + const stakeBefore = await api.query.SubtensorModule.Alpha.getValue(convertPublicKeyToSs58(hotkey.publicKey), convertH160ToSS58(wallet2.address), netuid) + const contract = new ethers.Contract(ISTAKING_V2_ADDRESS, IStakingV2ABI, wallet2); + + try { + const tx = await contract.addStake(hotkey.publicKey, stakeBalance.toString(), netuid); + await tx.wait(); + assert.fail("Transaction should have failed"); + } catch (error) { + // Transaction failed as expected + } + + const stakeFromContract = BigInt( + await contract.getStake(hotkey.publicKey, convertH160ToPublicKey(wallet2.address), netuid) + ); + assert.equal(stakeFromContract, stakeBefore) + const stakeAfter = await api.query.SubtensorModule.Alpha.getValue(convertPublicKeyToSs58(hotkey.publicKey), convertH160ToSS58(wallet2.address), netuid) + assert.equal(stakeAfter, stakeBefore) + }) + + it("Can get stake via contract read method", async () => { + let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 + + // TODO need check how to pass bytes32 as parameter of readContract + // const value = await publicClient.readContract({ + // address: ISTAKING_ADDRESS, + // abi: IStakingABI, + // functionName: "getStake", + // args: [hotkey.publicKey, // Convert to bytes32 format + // convertH160ToPublicKey(wallet1.address), + // netuid] + // }) + // if (value === undefined || value === null) { + // throw new Error("value of getStake from contract is undefined") + // } + // const intValue = BigInt(value.toString()) + + const contractV1 = new ethers.Contract(ISTAKING_ADDRESS, IStakingABI, wallet1); + const stakeFromContractV1 = BigInt( + await contractV1.getStake(hotkey.publicKey, convertH160ToPublicKey(wallet1.address), netuid) + ); + + const contractV2 = new ethers.Contract(ISTAKING_V2_ADDRESS, IStakingV2ABI, wallet1); + // unit from contract V2 is RAO, not ETH + const stakeFromContractV2 = Number( + await contractV2.getStake(hotkey.publicKey, convertH160ToPublicKey(wallet1.address), netuid) + ); + + assert.equal(stakeFromContractV1, tao(stakeFromContractV2)) + + }) + + it("Can remove stake", async () => { + let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 + const contract = new ethers.Contract( + ISTAKING_ADDRESS, + IStakingABI, + wallet1 + ); + + const stakeBeforeRemove = BigInt( + await contract.getStake(hotkey.publicKey, convertH160ToPublicKey(wallet1.address), netuid) + ); + + let stakeBalance = raoToEth(tao(10)) + const tx = await contract.removeStake(hotkey.publicKey, stakeBalance, netuid) + await tx.wait() + + const stakeAfterRemove = BigInt( + await contract.getStake(hotkey.publicKey, convertH160ToPublicKey(wallet1.address), netuid) + ); + assert.ok(stakeAfterRemove < stakeBeforeRemove) + + }) + + it("Can remove stake V2", async () => { + let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 + const contract = new ethers.Contract( + ISTAKING_V2_ADDRESS, + IStakingV2ABI, + wallet2 + ); + + const stakeBeforeRemove = BigInt( + await contract.getStake(hotkey.publicKey, convertH160ToPublicKey(wallet2.address), netuid) + ); + + let stakeBalance = tao(10) + const tx = await contract.removeStake(hotkey.publicKey, stakeBalance, netuid) + await tx.wait() + + const stakeAfterRemove = BigInt( + await contract.getStake(hotkey.publicKey, convertH160ToPublicKey(wallet2.address), netuid) + ); + + assert.ok(stakeAfterRemove < stakeBeforeRemove) + }) + + it("Can add/remove proxy", async () => { + let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 + // add/remove are done in a single test case, because we can't use the same private/public key + // between substrate and EVM, but to test the remove part, we must predefine the proxy first. + // it makes `remove` being dependent on `add`, because we should use `addProxy` from contract + // to prepare the proxy for `removeProxy` testing - the proxy is specified for the + // caller/origin. + + // first, check we don't have proxies + const ss58Address = convertH160ToSS58(wallet1.address); + // the result include two items array, first one is delegate info, second one is balance + const initProxies = await api.query.Proxy.Proxies.getValue(ss58Address); + assert.equal(initProxies[0].length, 0); + + // intialize the contract + const contract = new ethers.Contract( + ISTAKING_ADDRESS, + IStakingABI, + wallet1 + ); + + // test "add" + let tx = await contract.addProxy(proxy.publicKey); + await tx.wait(); + + const proxiesAfterAdd = await api.query.Proxy.Proxies.getValue(ss58Address); + + assert.equal(proxiesAfterAdd[0][0].delegate, convertPublicKeyToSs58(proxy.publicKey)) + + let stakeBefore = await api.query.SubtensorModule.Alpha.getValue( + convertPublicKeyToSs58(hotkey.publicKey), + ss58Address, + netuid + ) + + const call = api.tx.SubtensorModule.add_stake({ + hotkey: convertPublicKeyToSs58(hotkey.publicKey), + netuid: netuid, + amount_staked: tao(1) + }) + await sendProxyCall(api, call.decodedCall, ss58Address, proxy) + + let stakeAfter = await api.query.SubtensorModule.Alpha.getValue( + convertPublicKeyToSs58(hotkey.publicKey), + ss58Address, + netuid + ) + + assert.ok(stakeAfter > stakeBefore) + // test "remove" + tx = await contract.removeProxy(proxy.publicKey); + await tx.wait(); + + const proxiesAfterRemove = await api.query.Proxy.Proxies.getValue(ss58Address); + assert.equal(proxiesAfterRemove[0].length, 0) + }); + + it("Can add/remove proxy V2", async () => { + let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 + // add/remove are done in a single test case, because we can't use the same private/public key + // between substrate and EVM, but to test the remove part, we must predefine the proxy first. + // it makes `remove` being dependent on `add`, because we should use `addProxy` from contract + // to prepare the proxy for `removeProxy` testing - the proxy is specified for the + // caller/origin. + + // first, check we don't have proxies + const ss58Address = convertH160ToSS58(wallet1.address); + // the result include two items array, first one is delegate info, second one is balance + const initProxies = await api.query.Proxy.Proxies.getValue(ss58Address); + assert.equal(initProxies[0].length, 0); + + // intialize the contract + // const signer = new ethers.Wallet(fundedEthWallet.privateKey, provider); + const contract = new ethers.Contract( + ISTAKING_V2_ADDRESS, + IStakingV2ABI, + wallet1 + ); + + // test "add" + let tx = await contract.addProxy(proxy.publicKey); + await tx.wait(); + + const proxiesAfterAdd = await api.query.Proxy.Proxies.getValue(ss58Address); + + assert.equal(proxiesAfterAdd[0][0].delegate, convertPublicKeyToSs58(proxy.publicKey)) + + let stakeBefore = await api.query.SubtensorModule.Alpha.getValue( + convertPublicKeyToSs58(hotkey.publicKey), + ss58Address, + netuid + ) + + const call = api.tx.SubtensorModule.add_stake({ + hotkey: convertPublicKeyToSs58(hotkey.publicKey), + netuid: netuid, + amount_staked: tao(1) + }) + + await sendProxyCall(api, call.decodedCall, ss58Address, proxy) + + let stakeAfter = await api.query.SubtensorModule.Alpha.getValue( + convertPublicKeyToSs58(hotkey.publicKey), + ss58Address, + netuid + ) + + assert.ok(stakeAfter > stakeBefore) + // test "remove" + tx = await contract.removeProxy(proxy.publicKey); + await tx.wait(); + + const proxiesAfterRemove = await api.query.Proxy.Proxies.getValue(ss58Address); + assert.equal(proxiesAfterRemove[0].length, 0) + }); +}); diff --git a/evm-tests/test/staking.precompile.reward.test.ts b/evm-tests/test/staking.precompile.reward.test.ts new file mode 100644 index 0000000000..3600a6d08d --- /dev/null +++ b/evm-tests/test/staking.precompile.reward.test.ts @@ -0,0 +1,105 @@ +import * as assert from "assert"; +import { getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { devnet } from "@polkadot-api/descriptors" +import { TypedApi } from "polkadot-api"; +import { convertPublicKeyToSs58 } from "../src/address-utils" +import { tao } from "../src/balance-math" +import { + forceSetBalanceToSs58Address, addNewSubnetwork, burnedRegister, + setTxRateLimit, setTempo, setWeightsSetRateLimit, setSubnetOwnerCut, setMaxAllowedUids, + setMinDelegateTake, becomeDelegate, setActivityCutoff, addStake, setWeight, rootRegister +} from "../src/subtensor" + +describe("Test neuron precompile reveal weights", () => { + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + + const validator = getRandomSubstrateKeypair(); + const miner = getRandomSubstrateKeypair(); + const nominator = getRandomSubstrateKeypair(); + + let api: TypedApi + + before(async () => { + const root_netuid = 0; + const root_tempo = 1; // neet root epoch to happen before subnet tempo + const subnet_tempo = 1; + api = await getDevnetApi() + + // await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(alice.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(validator.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(miner.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(nominator.publicKey)) + // await forceSetBalanceToEthAddress(api, wallet1.address) + // await forceSetBalanceToEthAddress(api, wallet2.address) + let netuid = await addNewSubnetwork(api, hotkey, coldkey) + + console.log("test the case on subnet ", netuid) + + await setTxRateLimit(api, BigInt(0)) + await setTempo(api, root_netuid, root_tempo) + await setTempo(api, netuid, subnet_tempo) + await setWeightsSetRateLimit(api, netuid, BigInt(0)) + + await burnedRegister(api, netuid, convertPublicKeyToSs58(validator.publicKey), coldkey) + await burnedRegister(api, netuid, convertPublicKeyToSs58(miner.publicKey), coldkey) + await burnedRegister(api, netuid, convertPublicKeyToSs58(nominator.publicKey), coldkey) + await setSubnetOwnerCut(api, 0) + await setActivityCutoff(api, netuid, 65535) + await setMaxAllowedUids(api, netuid, 65535) + await setMinDelegateTake(api, 0) + await becomeDelegate(api, convertPublicKeyToSs58(validator.publicKey), coldkey) + await becomeDelegate(api, convertPublicKeyToSs58(miner.publicKey), coldkey) + }) + + it("Staker receives rewards", async () => { + let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 + + await addStake(api, netuid, convertPublicKeyToSs58(miner.publicKey), tao(1), coldkey) + await addStake(api, netuid, convertPublicKeyToSs58(nominator.publicKey), tao(1), coldkey) + + await addStake(api, netuid, convertPublicKeyToSs58(validator.publicKey), tao(100), coldkey) + + const miner_alpha_before_emission = await api.query.SubtensorModule.Alpha.getValue( + convertPublicKeyToSs58(miner.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid + ) + + await setWeight(api, netuid, [0, 1], [0xffff, 0xffff], BigInt(0), validator) + await rootRegister(api, convertPublicKeyToSs58(validator.publicKey), coldkey) + + let index = 0; + while (index < 60) { + const pending = await api.query.SubtensorModule.PendingEmission.getValue(netuid); + if (pending > 0) { + console.log("pending amount is ", pending); + break; + } + + await new Promise((resolve) => setTimeout(resolve, 1000)); + console.log("wait for the pendingEmission update"); + index += 1; + } + + index = 0; + while (index < 60) { + let miner_current_alpha = await api.query.SubtensorModule.Alpha.getValue( + convertPublicKeyToSs58(miner.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid + ) + + if (miner_current_alpha > miner_alpha_before_emission) { + console.log("miner got reward"); + break; + } + + await new Promise((resolve) => setTimeout(resolve, 1000)); + console.log(" waiting for emission"); + index += 1; + } + }) +}) diff --git a/evm-tests/test/subnet.precompile.hyperparameter.test.ts b/evm-tests/test/subnet.precompile.hyperparameter.test.ts new file mode 100644 index 0000000000..1805b85ce9 --- /dev/null +++ b/evm-tests/test/subnet.precompile.hyperparameter.test.ts @@ -0,0 +1,442 @@ +import * as assert from "assert"; + +import { getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { devnet } from "@polkadot-api/descriptors" +import { TypedApi } from "polkadot-api"; +import { convertPublicKeyToSs58 } from "../src/address-utils" +import { generateRandomEthersWallet } from "../src/utils"; +import { ISubnetABI, ISUBNET_ADDRESS } from "../src/contracts/subnet" +import { ethers } from "ethers" +import { forceSetBalanceToEthAddress, forceSetBalanceToSs58Address } from "../src/subtensor" + +describe("Test the Subnet precompile contract", () => { + // init eth part + const wallet = generateRandomEthersWallet(); + // init substrate part + + const hotkey1 = getRandomSubstrateKeypair(); + const hotkey2 = getRandomSubstrateKeypair(); + let api: TypedApi + + before(async () => { + // init variables got from await and async + api = await getDevnetApi() + + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey1.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey2.publicKey)) + await forceSetBalanceToEthAddress(api, wallet.address) + }) + + it("Can register network without identity info", async () => { + const totalNetwork = await api.query.SubtensorModule.TotalNetworks.getValue() + + const contract = new ethers.Contract(ISUBNET_ADDRESS, ISubnetABI, wallet); + const tx = await contract.registerNetwork(hotkey1.publicKey); + await tx.wait(); + + const totalNetworkAfterAdd = await api.query.SubtensorModule.TotalNetworks.getValue() + assert.ok(totalNetwork + 1 === totalNetworkAfterAdd) + }); + + it("Can register network with identity info", async () => { + const totalNetwork = await api.query.SubtensorModule.TotalNetworks.getValue() + + const contract = new ethers.Contract(ISUBNET_ADDRESS, ISubnetABI, wallet); + const tx = await contract.registerNetwork(hotkey2.publicKey, + "name", + "repo", + "contact", + "subnetUrl", + "discord", + "description", + "additional" + ); + await tx.wait(); + + const totalNetworkAfterAdd = await api.query.SubtensorModule.TotalNetworks.getValue() + assert.ok(totalNetwork + 1 === totalNetworkAfterAdd) + }); + + it("Can set subnet parameter", async () => { + + const totalNetwork = await api.query.SubtensorModule.TotalNetworks.getValue() + const contract = new ethers.Contract(ISUBNET_ADDRESS, ISubnetABI, wallet); + const netuid = totalNetwork - 1; + + // servingRateLimit hyperparameter + { + const newValue = 100; + const tx = await contract.setServingRateLimit(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.ServingRateLimit.getValue(netuid) + + + let valueFromContract = Number( + await contract.getServingRateLimit(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + // minDifficulty hyperparameter + // + // disabled: only by sudo + // + // newValue = 101; + // tx = await contract.setMinDifficulty(netuid, newValue); + // await tx.wait(); + + // await usingApi(async (api) => { + // onchainValue = Number( + // await api.query.subtensorModule.minDifficulty(netuid) + // ); + // }); + + // valueFromContract = Number(await contract.getMinDifficulty(netuid)); + + // expect(valueFromContract).to.eq(newValue); + // expect(valueFromContract).to.eq(onchainValue); + + // maxDifficulty hyperparameter + + { + const newValue = 102; + const tx = await contract.setMaxDifficulty(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.MaxDifficulty.getValue(netuid) + + + let valueFromContract = Number( + await contract.getMaxDifficulty(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + // weightsVersionKey hyperparameter + { + const newValue = 103; + const tx = await contract.setWeightsVersionKey(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.WeightsVersionKey.getValue(netuid) + + + let valueFromContract = Number( + await contract.getWeightsVersionKey(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + // weightsSetRateLimit hyperparameter + { + const newValue = 104; + const tx = await contract.setWeightsSetRateLimit(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.WeightsSetRateLimit.getValue(netuid) + + + let valueFromContract = Number( + await contract.getWeightsSetRateLimit(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + // adjustmentAlpha hyperparameter + { + const newValue = 105; + const tx = await contract.setAdjustmentAlpha(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.AdjustmentAlpha.getValue(netuid) + + + let valueFromContract = Number( + await contract.getAdjustmentAlpha(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + // maxWeightLimit hyperparameter + { + const newValue = 106; + const tx = await contract.setMaxWeightLimit(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.MaxWeightsLimit.getValue(netuid) + + + let valueFromContract = Number( + await contract.getMaxWeightLimit(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + // immunityPeriod hyperparameter + { + const newValue = 107; + const tx = await contract.setImmunityPeriod(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.ImmunityPeriod.getValue(netuid) + + + let valueFromContract = Number( + await contract.getImmunityPeriod(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + // minAllowedWeights hyperparameter + { + const newValue = 108; + const tx = await contract.setMinAllowedWeights(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.MinAllowedWeights.getValue(netuid) + + + let valueFromContract = Number( + await contract.getMinAllowedWeights(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + // kappa hyperparameter + { + const newValue = 109; + const tx = await contract.setKappa(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.Kappa.getValue(netuid) + + + let valueFromContract = Number( + await contract.getKappa(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + // rho hyperparameter + { + const newValue = 110; + const tx = await contract.setRho(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.Rho.getValue(netuid) + + + let valueFromContract = Number( + await contract.getRho(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + // activityCutoff hyperparameter + { + const newValue = 111; + const tx = await contract.setActivityCutoff(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.ActivityCutoff.getValue(netuid) + + + let valueFromContract = Number( + await contract.getActivityCutoff(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + // networkRegistrationAllowed hyperparameter + { + const newValue = true; + const tx = await contract.setNetworkRegistrationAllowed(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.NetworkRegistrationAllowed.getValue(netuid) + + + let valueFromContract = Boolean( + await contract.getNetworkRegistrationAllowed(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + // networkPowRegistrationAllowed hyperparameter + { + const newValue = true; + const tx = await contract.setNetworkPowRegistrationAllowed(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.NetworkPowRegistrationAllowed.getValue(netuid) + + + let valueFromContract = Boolean( + await contract.getNetworkPowRegistrationAllowed(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + // minBurn hyperparameter. only sudo can set it now + // newValue = 112; + + // tx = await contract.setMinBurn(netuid, newValue); + // await tx.wait(); + + // await usingApi(async (api) => { + // onchainValue = Number( + // await api.query.subtensorModule.minBurn(netuid) + // ); + // }); + + // valueFromContract = Number(await contract.getMinBurn(netuid)); + + // expect(valueFromContract).to.eq(newValue); + // expect(valueFromContract).to.eq(onchainValue); + + // maxBurn hyperparameter + { + const newValue = 113; + const tx = await contract.setMaxBurn(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.MaxBurn.getValue(netuid) + + + let valueFromContract = Number( + await contract.getMaxBurn(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + + // difficulty hyperparameter (disabled: sudo only) + // newValue = 114; + + // tx = await contract.setDifficulty(netuid, newValue); + // await tx.wait(); + + // await usingApi(async (api) => { + // onchainValue = Number( + // await api.query.subtensorModule.difficulty(netuid) + // ); + // }); + + // valueFromContract = Number(await contract.getDifficulty(netuid)); + + // expect(valueFromContract).to.eq(newValue); + // expect(valueFromContract).to.eq(onchainValue); + + // bondsMovingAverage hyperparameter + { + const newValue = 115; + const tx = await contract.setBondsMovingAverage(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.BondsMovingAverage.getValue(netuid) + + + let valueFromContract = Number( + await contract.getBondsMovingAverage(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + + // commitRevealWeightsEnabled hyperparameter + { + const newValue = true; + const tx = await contract.setCommitRevealWeightsEnabled(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.CommitRevealWeightsEnabled.getValue(netuid) + + + let valueFromContract = Boolean( + await contract.getCommitRevealWeightsEnabled(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + // liquidAlphaEnabled hyperparameter + { + const newValue = true; + const tx = await contract.setLiquidAlphaEnabled(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.LiquidAlphaOn.getValue(netuid) + + + let valueFromContract = Boolean( + await contract.getLiquidAlphaEnabled(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + + // alphaValues hyperparameter + { + const newValue = [118, 52429]; + const tx = await contract.setAlphaValues(netuid, newValue[0], newValue[1]); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.AlphaValues.getValue(netuid) + + let value = await contract.getAlphaValues(netuid) + let valueFromContract = [Number(value[0]), Number(value[1])] + + assert.equal(valueFromContract[0], newValue[0]) + assert.equal(valueFromContract[1], newValue[1]) + assert.equal(valueFromContract[0], onchainValue[0]); + assert.equal(valueFromContract[1], onchainValue[1]); + } + + // commitRevealWeightsInterval hyperparameter + { + const newValue = 119; + const tx = await contract.setCommitRevealWeightsInterval(netuid, newValue); + await tx.wait(); + + let onchainValue = await api.query.SubtensorModule.RevealPeriodEpochs.getValue(netuid) + + let valueFromContract = Number( + await contract.getCommitRevealWeightsInterval(netuid) + ); + + assert.equal(valueFromContract, newValue) + assert.equal(valueFromContract, onchainValue); + } + }) +}); \ No newline at end of file diff --git a/evm-tests/tsconfig.json b/evm-tests/tsconfig.json new file mode 100644 index 0000000000..c9c555d96f --- /dev/null +++ b/evm-tests/tsconfig.json @@ -0,0 +1,111 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/node/Cargo.toml b/node/Cargo.toml index f97425ba9a..6cea8f6950 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -66,7 +66,7 @@ pallet-transaction-payment = { workspace = true } pallet-commitments = { path = "../pallets/commitments" } pallet-drand = { workspace = true } sp-crypto-ec-utils = { workspace = true } -sp-keystore = { workspace = true, default-features = false } +sp-keystore = { workspace = true, default-features = false } # These dependencies are used for the subtensor's RPCs @@ -105,9 +105,10 @@ thiserror = { workspace = true } num-traits = { version = "0.2", features = ["std"] } # Local Dependencies -node-subtensor-runtime = { path = "../runtime" } -subtensor-custom-rpc = { path = "../pallets/subtensor/rpc" } -subtensor-custom-rpc-runtime-api = { path = "../pallets/subtensor/runtime-api" } +node-subtensor-runtime = { workspace = true, features = ["std"] } +subtensor-runtime-common = { workspace = true, features = ["std"] } +subtensor-custom-rpc = { workspace = true, features = ["std"] } +subtensor-custom-rpc-runtime-api = { workspace = true, features = ["std"] } [build-dependencies] substrate-build-script-utils = { workspace = true } @@ -133,7 +134,7 @@ runtime-benchmarks = [ "sc-service/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "pallet-commitments/runtime-benchmarks", - "pallet-drand/runtime-benchmarks" + "pallet-drand/runtime-benchmarks", ] pow-faucet = [] @@ -145,7 +146,7 @@ try-runtime = [ "pallet-transaction-payment/try-runtime", "sp-runtime/try-runtime", "pallet-commitments/try-runtime", - "pallet-drand/try-runtime" + "pallet-drand/try-runtime", ] metadata-hash = ["node-subtensor-runtime/metadata-hash"] diff --git a/node/src/benchmarking.rs b/node/src/benchmarking.rs index cee1cb4ac8..40031ac1aa 100644 --- a/node/src/benchmarking.rs +++ b/node/src/benchmarking.rs @@ -7,13 +7,14 @@ use crate::client::FullClient; use node_subtensor_runtime as runtime; use node_subtensor_runtime::check_nonce; use node_subtensor_runtime::pallet_subtensor; -use runtime::{AccountId, Balance, BalancesCall, SystemCall}; +use runtime::{BalancesCall, SystemCall}; use sc_cli::Result; use sc_client_api::BlockBackend; use sp_core::{Encode, Pair}; use sp_inherents::{InherentData, InherentDataProvider}; use sp_keyring::Sr25519Keyring; use sp_runtime::{OpaqueExtrinsic, SaturatedConversion}; +use subtensor_runtime_common::{AccountId, Balance, Signature}; use std::{sync::Arc, time::Duration}; @@ -161,7 +162,7 @@ pub fn create_benchmark_extrinsic( runtime::UncheckedExtrinsic::new_signed( call.clone(), sp_runtime::AccountId32::from(sender.public()).into(), - runtime::Signature::Sr25519(signature), + Signature::Sr25519(signature), extra.clone(), ) } diff --git a/node/src/chain_spec/mod.rs b/node/src/chain_spec/mod.rs index 25942658d5..e8efbb1647 100644 --- a/node/src/chain_spec/mod.rs +++ b/node/src/chain_spec/mod.rs @@ -6,7 +6,7 @@ pub mod finney; pub mod localnet; pub mod testnet; -use node_subtensor_runtime::{AccountId, Block, Signature, WASM_BINARY}; +use node_subtensor_runtime::{Block, WASM_BINARY}; use sc_chain_spec_derive::ChainSpecExtension; use sc_service::ChainType; use sp_consensus_aura::sr25519::AuthorityId as AuraId; @@ -18,6 +18,7 @@ use sp_runtime::traits::{IdentifyAccount, Verify}; use std::collections::HashSet; use std::env; use std::str::FromStr; +use subtensor_runtime_common::{AccountId, Signature}; // The URL for the telemetry server. // const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; diff --git a/node/src/rpc.rs b/node/src/rpc.rs index 39a63c602e..0d4cd355de 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -13,7 +13,6 @@ pub use fc_rpc::EthBlockDataCacheTask; pub use fc_rpc_core::types::{FeeHistoryCache, FeeHistoryCacheLimit, FilterPool}; use fc_storage::StorageOverride; use jsonrpsee::RpcModule; -use node_subtensor_runtime::Hash; use node_subtensor_runtime::opaque::Block; use sc_consensus_manual_seal::EngineCommand; use sc_network::service::traits::NetworkService; @@ -24,6 +23,7 @@ use sc_transaction_pool_api::TransactionPool; use sp_core::H256; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::Block as BlockT; +use subtensor_runtime_common::Hash; use crate::{ client::{FullBackend, FullClient}, diff --git a/node/src/service.rs b/node/src/service.rs index a0b7cb1c6a..4ce041b0a4 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -796,7 +796,7 @@ fn run_manual_seal_authorship( ) -> Result<(), sp_inherents::Error> { TIMESTAMP.with(|x| { let mut x_ref = x.borrow_mut(); - *x_ref = x_ref.saturating_add(node_subtensor_runtime::SLOT_DURATION); + *x_ref = x_ref.saturating_add(subtensor_runtime_common::time::SLOT_DURATION); inherent_data.put_data(sp_timestamp::INHERENT_IDENTIFIER, &*x.borrow()) }) } diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index af9b68051f..c5794e0279 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -272,13 +272,5 @@ mod benchmarks { _(RawOrigin::Root, 1u16/*netuid*/, true/*enabled*/)/*set_commit_reveal_weights_enabled*/; } - #[benchmark] - fn sudo_set_network_max_stake() { - pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); - - #[extrinsic_call] - _(RawOrigin::Root, 1u16/*netuid*/, 1_000_000_000_000_000u64/*max_stake*/)/*sudo_set_network_max_stake*/; - } - //impl_benchmark_test_suite!(AdminUtils, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 7bc9e986e0..0ca6fee2c8 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -25,9 +25,13 @@ pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_support::traits::tokens::Balance; - use frame_support::{dispatch::DispatchResult, pallet_prelude::StorageMap}; + use frame_support::{ + dispatch::{DispatchResult, RawOrigin}, + pallet_prelude::StorageMap, + }; use frame_system::pallet_prelude::*; use pallet_evm_chain_id::{self, ChainId}; + use pallet_subtensor::utils::rate_limiting::TransactionType; use sp_runtime::BoundedVec; use substrate_fixed::types::I96F32; @@ -249,12 +253,35 @@ pub mod pallet { netuid: u16, weights_version_key: u64, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin.clone(), netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); + + if let Ok(RawOrigin::Signed(who)) = origin.into() { + // SN Owner + // Ensure the origin passes the rate limit. + ensure!( + pallet_subtensor::Pallet::::passes_rate_limit_on_subnet( + &TransactionType::SetWeightsVersionKey, + &who, + netuid, + ), + pallet_subtensor::Error::::TxRateLimitExceeded + ); + + // Set last transaction block + let current_block = pallet_subtensor::Pallet::::get_current_block_as_u64(); + pallet_subtensor::Pallet::::set_last_transaction_block_on_subnet( + &who, + netuid, + &TransactionType::SetWeightsVersionKey, + current_block, + ); + } + pallet_subtensor::Pallet::::set_weights_version_key(netuid, weights_version_key); log::debug!( "WeightsVersionKeySet( netuid: {:?} weights_version_key: {:?} ) ", @@ -910,12 +937,8 @@ pub mod pallet { DispatchClass::Operational, Pays::No ))] - pub fn sudo_set_subnet_limit(origin: OriginFor, max_subnets: u16) -> DispatchResult { + pub fn sudo_set_subnet_limit(origin: OriginFor, _max_subnets: u16) -> DispatchResult { ensure_root(origin)?; - pallet_subtensor::Pallet::::set_max_subnets(max_subnets); - - log::debug!("SubnetLimit( max_subnets: {:?} ) ", max_subnets); - Ok(()) } @@ -1155,22 +1178,11 @@ pub mod pallet { #[pallet::weight((0, DispatchClass::Operational, Pays::No))] pub fn sudo_set_network_max_stake( origin: OriginFor, - netuid: u16, - max_stake: u64, + _netuid: u16, + _max_stake: u64, ) -> DispatchResult { // Ensure the call is made by the root account ensure_root(origin)?; - - // Set the new maximum stake for the specified network - pallet_subtensor::Pallet::::set_network_max_stake(netuid, max_stake); - - // Log the change - log::trace!( - "NetworkMaxStakeSet( netuid: {:?}, max_stake: {:?} )", - netuid, - max_stake - ); - Ok(()) } @@ -1399,6 +1411,65 @@ pub mod pallet { log::debug!("SubnetMovingAlphaSet( alpha: {:?} )", alpha); Ok(()) } + + /// Change the SubnetOwnerHotkey for a given subnet. + /// + /// # Arguments + /// * `origin` - The origin of the call, which must be the subnet owner. + /// * `netuid` - The unique identifier for the subnet. + /// * `hotkey` - The new hotkey for the subnet owner. + /// + /// # Errors + /// * `BadOrigin` - If the caller is not the subnet owner or root account. + /// + /// # Weight + /// Weight is handled by the `#[pallet::weight]` attribute. + #[pallet::call_index(64)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_set_subnet_owner_hotkey( + origin: OriginFor, + netuid: u16, + hotkey: T::AccountId, + ) -> DispatchResult { + pallet_subtensor::Pallet::::ensure_subnet_owner(origin.clone(), netuid)?; + pallet_subtensor::Pallet::::set_subnet_owner_hotkey(netuid, &hotkey); + + log::debug!( + "SubnetOwnerHotkeySet( netuid: {:?}, hotkey: {:?} )", + netuid, + hotkey + ); + Ok(()) + } + + /// + /// + /// # Arguments + /// * `origin` - The origin of the call, which must be the root account. + /// * `ema_alpha_period` - Number of blocks for EMA price to halve + /// + /// # Errors + /// * `BadOrigin` - If the caller is not the root account. + /// + /// # Weight + /// Weight is handled by the `#[pallet::weight]` attribute. + #[pallet::call_index(65)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_set_ema_price_halving_period( + origin: OriginFor, + netuid: u16, + ema_halving: u64, + ) -> DispatchResult { + ensure_root(origin)?; + pallet_subtensor::EMAPriceHalvingBlocks::::set(netuid, ema_halving); + + log::debug!( + "EMAPriceHalvingBlocks( netuid: {:?}, ema_halving: {:?} )", + netuid, + ema_halving + ); + Ok(()) + } } } diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 40e29e54dd..fc0d016198 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -123,17 +123,18 @@ parameter_types! { pub const InitialNetworkMinLockCost: u64 = 100_000_000_000; pub const InitialSubnetOwnerCut: u16 = 0; // 0%. 100% of rewards go to validators + miners. pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks. - pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets. + // pub const InitialSubnetLimit: u16 = 10; // (DEPRECATED) pub const InitialNetworkRateLimit: u64 = 0; pub const InitialKeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn // pub const InitialHotkeyEmissionTempo: u64 = 1; // (DEPRECATED) - pub const InitialNetworkMaxStake: u64 = u64::MAX; // Maximum possible value for u64, this make the make stake infinity + // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. + pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks } impl pallet_subtensor::Config for Test { @@ -188,18 +189,16 @@ impl pallet_subtensor::Config for Test { type InitialNetworkMinLockCost = InitialNetworkMinLockCost; type InitialSubnetOwnerCut = InitialSubnetOwnerCut; type InitialNetworkLockReductionInterval = InitialNetworkLockReductionInterval; - type InitialSubnetLimit = InitialSubnetLimit; type InitialNetworkRateLimit = InitialNetworkRateLimit; type KeySwapCost = InitialKeySwapCost; type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; - // type InitialHotkeyEmissionTempo = InitialHotkeyEmissionTempo; // (DEPRECATED) - type InitialNetworkMaxStake = InitialNetworkMaxStake; type Preimages = (); type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; + type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 6c879635e3..ed7ddcbcb2 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -5,7 +5,7 @@ use frame_support::{ traits::Hooks, }; use frame_system::Config; -use pallet_subtensor::Error as SubtensorError; +use pallet_subtensor::{Error as SubtensorError, SubnetOwner, Tempo, WeightsVersionKeyRateLimit}; // use pallet_subtensor::{migrations, Event}; use pallet_subtensor::Event; use sp_consensus_grandpa::AuthorityId as GrandpaId; @@ -162,6 +162,107 @@ fn test_sudo_set_weights_version_key() { }); } +#[test] +fn test_sudo_set_weights_version_key_rate_limit() { + new_test_ext().execute_with(|| { + let netuid: u16 = 1; + let to_be_set: u64 = 10; + + let sn_owner = U256::from(1); + add_network(netuid, 10); + // Set the Subnet Owner + SubnetOwner::::insert(netuid, sn_owner); + + let rate_limit = WeightsVersionKeyRateLimit::::get(); + let tempo: u16 = Tempo::::get(netuid); + + let rate_limit_period = rate_limit * (tempo as u64); + + assert_ok!(AdminUtils::sudo_set_weights_version_key( + <::RuntimeOrigin>::signed(sn_owner), + netuid, + to_be_set + )); + assert_eq!(SubtensorModule::get_weights_version_key(netuid), to_be_set); + + // Try to set again with + // Assert rate limit not passed + assert!(!SubtensorModule::passes_rate_limit_on_subnet( + &pallet_subtensor::utils::rate_limiting::TransactionType::SetWeightsVersionKey, + &sn_owner, + netuid + )); + + // Try transaction + assert_noop!( + AdminUtils::sudo_set_weights_version_key( + <::RuntimeOrigin>::signed(sn_owner), + netuid, + to_be_set + 1 + ), + pallet_subtensor::Error::::TxRateLimitExceeded + ); + + // Wait for rate limit to pass + run_to_block(rate_limit_period + 2); + assert!(SubtensorModule::passes_rate_limit_on_subnet( + &pallet_subtensor::utils::rate_limiting::TransactionType::SetWeightsVersionKey, + &sn_owner, + netuid + )); + + // Try transaction + assert_ok!(AdminUtils::sudo_set_weights_version_key( + <::RuntimeOrigin>::signed(sn_owner), + netuid, + to_be_set + 1 + )); + assert_eq!( + SubtensorModule::get_weights_version_key(netuid), + to_be_set + 1 + ); + }); +} + +#[test] +fn test_sudo_set_weights_version_key_rate_limit_root() { + // root should not be effected by rate limit + new_test_ext().execute_with(|| { + let netuid: u16 = 1; + let to_be_set: u64 = 10; + + let sn_owner = U256::from(1); + add_network(netuid, 10); + // Set the Subnet Owner + SubnetOwner::::insert(netuid, sn_owner); + + let rate_limit = WeightsVersionKeyRateLimit::::get(); + let tempo: u16 = Tempo::::get(netuid); + + let rate_limit_period = rate_limit * (tempo as u64); + // Verify the rate limit is more than 0 blocks + assert!(rate_limit_period > 0); + + assert_ok!(AdminUtils::sudo_set_weights_version_key( + <::RuntimeOrigin>::root(), + netuid, + to_be_set + )); + assert_eq!(SubtensorModule::get_weights_version_key(netuid), to_be_set); + + // Try transaction + assert_ok!(AdminUtils::sudo_set_weights_version_key( + <::RuntimeOrigin>::signed(sn_owner), + netuid, + to_be_set + 1 + )); + assert_eq!( + SubtensorModule::get_weights_version_key(netuid), + to_be_set + 1 + ); + }); +} + #[test] fn test_sudo_set_weights_set_rate_limit() { new_test_ext().execute_with(|| { @@ -850,30 +951,6 @@ fn test_sudo_set_rao_recycled() { }); } -#[test] -fn test_sudo_set_subnet_limit() { - new_test_ext().execute_with(|| { - let netuid: u16 = 1; - let to_be_set: u16 = 10; - add_network(netuid, 10); - - let init_value: u16 = SubtensorModule::get_max_subnets(); - assert_eq!( - AdminUtils::sudo_set_subnet_limit( - <::RuntimeOrigin>::signed(U256::from(1)), - to_be_set - ), - Err(DispatchError::BadOrigin) - ); - assert_eq!(SubtensorModule::get_max_subnets(), init_value); - assert_ok!(AdminUtils::sudo_set_subnet_limit( - <::RuntimeOrigin>::root(), - to_be_set - )); - assert_eq!(SubtensorModule::get_max_subnets(), to_be_set); - }); -} - #[test] fn test_sudo_set_network_lock_reduction_interval() { new_test_ext().execute_with(|| { @@ -1466,3 +1543,171 @@ fn test_sudo_root_sets_subnet_moving_alpha() { assert_eq!(pallet_subtensor::SubnetMovingAlpha::::get(), alpha); }); } + +#[test] +fn test_sets_a_lower_value_clears_small_nominations() { + new_test_ext().execute_with(|| { + let hotkey: U256 = U256::from(3); + let owner_coldkey: U256 = U256::from(1); + let staker_coldkey: U256 = U256::from(2); + + let initial_nominator_min_required_stake = 10u64; + let nominator_min_required_stake_0 = 5u64; + let nominator_min_required_stake_1 = 20u64; + + assert!(nominator_min_required_stake_0 < nominator_min_required_stake_1); + assert!(nominator_min_required_stake_0 < initial_nominator_min_required_stake); + + let to_stake = initial_nominator_min_required_stake + 1; + + assert!(to_stake > initial_nominator_min_required_stake); + assert!(to_stake > nominator_min_required_stake_0); // Should stay when set + assert!(to_stake < nominator_min_required_stake_1); // Should be removed when set + + // Create network + let netuid = 2; + add_network(netuid, 10); + + // Register a neuron + register_ok_neuron(netuid, hotkey, owner_coldkey, 0); + + assert_ok!(AdminUtils::sudo_set_nominator_min_required_stake( + RuntimeOrigin::root(), + initial_nominator_min_required_stake + )); + assert_eq!( + SubtensorModule::get_nominator_min_required_stake(), + initial_nominator_min_required_stake + ); + + // Stake to the hotkey as staker_coldkey + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &staker_coldkey, + netuid, + to_stake, + ); + + assert_ok!(AdminUtils::sudo_set_nominator_min_required_stake( + RuntimeOrigin::root(), + nominator_min_required_stake_0 + )); + assert_eq!( + SubtensorModule::get_nominator_min_required_stake(), + nominator_min_required_stake_0 + ); + + // Check this nomination is not cleared + assert!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &staker_coldkey, + netuid + ) > 0 + ); + + assert_ok!(AdminUtils::sudo_set_nominator_min_required_stake( + RuntimeOrigin::root(), + nominator_min_required_stake_1 + )); + assert_eq!( + SubtensorModule::get_nominator_min_required_stake(), + nominator_min_required_stake_1 + ); + + // Check this nomination is cleared + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &staker_coldkey, + netuid + ), + 0 + ); + }); +} + +#[test] +fn test_sudo_set_subnet_owner_hotkey() { + new_test_ext().execute_with(|| { + let netuid: u16 = 1; + + let coldkey: U256 = U256::from(1); + let hotkey: U256 = U256::from(2); + let new_hotkey: U256 = U256::from(3); + + let coldkey_origin = <::RuntimeOrigin>::signed(coldkey); + let root = RuntimeOrigin::root(); + let random_account = RuntimeOrigin::signed(U256::from(123456)); + + pallet_subtensor::SubnetOwner::::insert(netuid, coldkey); + pallet_subtensor::SubnetOwnerHotkey::::insert(netuid, hotkey); + assert_eq!( + pallet_subtensor::SubnetOwnerHotkey::::get(netuid), + hotkey + ); + + assert_ok!(AdminUtils::sudo_set_subnet_owner_hotkey( + coldkey_origin, + netuid, + new_hotkey + )); + + assert_eq!( + pallet_subtensor::SubnetOwnerHotkey::::get(netuid), + new_hotkey + ); + + assert_noop!( + AdminUtils::sudo_set_subnet_owner_hotkey(random_account, netuid, new_hotkey), + DispatchError::BadOrigin + ); + + assert_noop!( + AdminUtils::sudo_set_subnet_owner_hotkey(root, netuid, new_hotkey), + DispatchError::BadOrigin + ); + }); +} + +// cargo test --package pallet-admin-utils --lib -- tests::test_sudo_set_ema_halving --exact --show-output +#[test] +fn test_sudo_set_ema_halving() { + new_test_ext().execute_with(|| { + let netuid: u16 = 1; + let to_be_set: u64 = 10; + add_network(netuid, 10); + + let value_before: u64 = pallet_subtensor::EMAPriceHalvingBlocks::::get(netuid); + assert_eq!( + AdminUtils::sudo_set_ema_price_halving_period( + <::RuntimeOrigin>::signed(U256::from(1)), + netuid, + to_be_set + ), + Err(DispatchError::BadOrigin) + ); + let value_after_0: u64 = pallet_subtensor::EMAPriceHalvingBlocks::::get(netuid); + assert_eq!(value_after_0, value_before); + + let owner = U256::from(10); + pallet_subtensor::SubnetOwner::::insert(netuid, owner); + assert_eq!( + AdminUtils::sudo_set_ema_price_halving_period( + <::RuntimeOrigin>::signed(owner), + netuid, + to_be_set + ), + Err(DispatchError::BadOrigin) + ); + let value_after_1: u64 = pallet_subtensor::EMAPriceHalvingBlocks::::get(netuid); + assert_eq!(value_after_1, value_before); + assert_ok!(AdminUtils::sudo_set_ema_price_halving_period( + <::RuntimeOrigin>::root(), + netuid, + to_be_set + )); + let value_after_2: u64 = pallet_subtensor::EMAPriceHalvingBlocks::::get(netuid); + assert_eq!(value_after_2, to_be_set); + }); +} diff --git a/pallets/proxy/Cargo.toml b/pallets/proxy/Cargo.toml new file mode 100644 index 0000000000..f3a97dfedf --- /dev/null +++ b/pallets/proxy/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "pallet-proxy" +version = "38.0.0" +authors = ["Bittensor Nucleus Team"] +edition = "2021" +license = "Apache-2.0" +homepage = "https://bittensor.com" +description = "FRAME proxying pallet" +readme = "README.md" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { features = ["max-encoded-len"], workspace = true } +scale-info = { features = ["derive"], workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support.workspace = true +frame-system.workspace = true +sp-io.workspace = true +sp-runtime.workspace = true +subtensor-macros.workspace = true + +[dev-dependencies] +pallet-balances = { default-features = true, workspace = true } +pallet-utility = { default-features = true, workspace = true } +sp-core = { default-features = true, workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-utility/runtime-benchmarks" +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", + "pallet-balances/try-runtime", + "pallet-utility/try-runtime" +] diff --git a/pallets/proxy/README.md b/pallets/proxy/README.md new file mode 100644 index 0000000000..290c49c050 --- /dev/null +++ b/pallets/proxy/README.md @@ -0,0 +1,26 @@ +# Proxy Module +A module allowing accounts to give permission to other accounts to dispatch types of calls from +their signed origin. + +The accounts to which permission is delegated may be required to announce the action that they +wish to execute some duration prior to execution happens. In this case, the target account may +reject the announcement and in doing so, veto the execution. + +- [`Config`](https://docs.rs/pallet-proxy/latest/pallet_proxy/pallet/trait.Config.html) +- [`Call`](https://docs.rs/pallet-proxy/latest/pallet_proxy/pallet/enum.Call.html) + +## Overview + +## Interface + +### Dispatchable Functions + +[`Call`]: ./enum.Call.html +[`Config`]: ./trait.Config.html + +License: Apache-2.0 + + +## Release + +Polkadot SDK stable2409 diff --git a/pallets/proxy/src/benchmarking.rs b/pallets/proxy/src/benchmarking.rs new file mode 100644 index 0000000000..f519c0f0c3 --- /dev/null +++ b/pallets/proxy/src/benchmarking.rs @@ -0,0 +1,261 @@ +// This file is part of Substrate. +// +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 +// +// 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. + +// Benchmarks for Proxy Pallet + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use crate::Pallet as Proxy; +use alloc::{boxed::Box, vec}; +use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller}; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; +use sp_runtime::traits::{Bounded, CheckedDiv}; + +const SEED: u32 = 0; + +fn assert_last_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn half_max_balance() -> BalanceOf { + BalanceOf::::max_value() + .checked_div(&BalanceOf::::from(2_u32)) + .unwrap_or_else(BalanceOf::::max_value) +} + +fn add_proxies(n: u32, maybe_who: Option) -> Result<(), &'static str> { + let caller = maybe_who.unwrap_or_else(whitelisted_caller); + T::Currency::make_free_balance_be(&caller, half_max_balance::()); + for i in 0..n { + let real = T::Lookup::unlookup(account("target", i, SEED)); + + Proxy::::add_proxy( + RawOrigin::Signed(caller.clone()).into(), + real, + T::ProxyType::default(), + BlockNumberFor::::zero(), + )?; + } + Ok(()) +} + +fn add_announcements( + n: u32, + maybe_who: Option, + maybe_real: Option, +) -> Result<(), &'static str> { + let caller = maybe_who.unwrap_or_else(|| account("caller", 0, SEED)); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + T::Currency::make_free_balance_be(&caller, half_max_balance::()); + let real = if let Some(real) = maybe_real { + real + } else { + let real = account("real", 0, SEED); + T::Currency::make_free_balance_be(&real, half_max_balance::()); + Proxy::::add_proxy( + RawOrigin::Signed(real.clone()).into(), + caller_lookup, + T::ProxyType::default(), + BlockNumberFor::::zero(), + )?; + real + }; + let real_lookup = T::Lookup::unlookup(real); + for _ in 0..n { + Proxy::::announce( + RawOrigin::Signed(caller.clone()).into(), + real_lookup.clone(), + T::CallHasher::hash_of(&("add_announcement", n)), + )?; + } + Ok(()) +} + +benchmarks! { + proxy { + let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; + let caller: T::AccountId = account("target", p.saturating_sub(1), SEED); + T::Currency::make_free_balance_be(&caller, half_max_balance::()); + let real: T::AccountId = whitelisted_caller(); + let real_lookup = T::Lookup::unlookup(real); + let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); + }: _(RawOrigin::Signed(caller), real_lookup, Some(T::ProxyType::default()), Box::new(call)) + verify { + assert_last_event::(Event::ProxyExecuted { result: Ok(()) }.into()) + } + + proxy_announced { + let a in 0 .. T::MaxPending::get().saturating_sub(1); + let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; + let caller: T::AccountId = account("pure", 0, SEED); + let delegate: T::AccountId = account("target", p.saturating_sub(1), SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + T::Currency::make_free_balance_be(&delegate, half_max_balance::()); + let real: T::AccountId = whitelisted_caller(); + let real_lookup = T::Lookup::unlookup(real); + let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); + Proxy::::announce( + RawOrigin::Signed(delegate.clone()).into(), + real_lookup.clone(), + T::CallHasher::hash_of(&call), + )?; + add_announcements::(a, Some(delegate.clone()), None)?; + }: _(RawOrigin::Signed(caller), delegate_lookup, real_lookup, Some(T::ProxyType::default()), Box::new(call)) + verify { + assert_last_event::(Event::ProxyExecuted { result: Ok(()) }.into()) + } + + remove_announcement { + let a in 0 .. T::MaxPending::get().saturating_sub(1); + let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; + let caller: T::AccountId = account("target", p.saturating_sub(1), SEED); + T::Currency::make_free_balance_be(&caller, half_max_balance::()); + let real: T::AccountId = whitelisted_caller(); + let real_lookup = T::Lookup::unlookup(real); + let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); + Proxy::::announce( + RawOrigin::Signed(caller.clone()).into(), + real_lookup.clone(), + T::CallHasher::hash_of(&call), + )?; + add_announcements::(a, Some(caller.clone()), None)?; + }: _(RawOrigin::Signed(caller.clone()), real_lookup, T::CallHasher::hash_of(&call)) + verify { + let (announcements, _) = Announcements::::get(&caller); + assert_eq!(announcements.len() as u32, a); + } + + reject_announcement { + let a in 0 .. T::MaxPending::get().saturating_sub(1); + let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; + let caller: T::AccountId = account("target", p.saturating_sub(1), SEED); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + T::Currency::make_free_balance_be(&caller, half_max_balance::()); + let real: T::AccountId = whitelisted_caller(); + let real_lookup = T::Lookup::unlookup(real.clone()); + let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); + Proxy::::announce( + RawOrigin::Signed(caller.clone()).into(), + real_lookup, + T::CallHasher::hash_of(&call), + )?; + add_announcements::(a, Some(caller.clone()), None)?; + }: _(RawOrigin::Signed(real), caller_lookup, T::CallHasher::hash_of(&call)) + verify { + let (announcements, _) = Announcements::::get(&caller); + assert_eq!(announcements.len() as u32, a); + } + + announce { + let a in 0 .. T::MaxPending::get().saturating_sub(1); + let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; + let caller: T::AccountId = account("target", p.saturating_sub(1), SEED); + T::Currency::make_free_balance_be(&caller, half_max_balance::()); + let real: T::AccountId = whitelisted_caller(); + let real_lookup = T::Lookup::unlookup(real.clone()); + add_announcements::(a, Some(caller.clone()), None)?; + let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); + let call_hash = T::CallHasher::hash_of(&call); + }: _(RawOrigin::Signed(caller.clone()), real_lookup, call_hash) + verify { + assert_last_event::(Event::Announced { real, proxy: caller, call_hash }.into()); + } + + add_proxy { + let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; + let caller: T::AccountId = whitelisted_caller(); + let real = T::Lookup::unlookup(account("target", T::MaxProxies::get(), SEED)); + }: _( + RawOrigin::Signed(caller.clone()), + real, + T::ProxyType::default(), + BlockNumberFor::::zero() + ) + verify { + let (proxies, _) = Proxies::::get(caller); + assert_eq!(proxies.len() as u32, p.saturating_add(1)); + } + + remove_proxy { + let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; + let caller: T::AccountId = whitelisted_caller(); + let delegate = T::Lookup::unlookup(account("target", 0, SEED)); + }: _( + RawOrigin::Signed(caller.clone()), + delegate, + T::ProxyType::default(), + BlockNumberFor::::zero() + ) + verify { + let (proxies, _) = Proxies::::get(caller); + assert_eq!(proxies.len() as u32, p.saturating_sub(1)); + } + + remove_proxies { + let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; + let caller: T::AccountId = whitelisted_caller(); + }: _(RawOrigin::Signed(caller.clone())) + verify { + let (proxies, _) = Proxies::::get(caller); + assert_eq!(proxies.len() as u32, 0); + } + + create_pure { + let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; + let caller: T::AccountId = whitelisted_caller(); + }: _( + RawOrigin::Signed(caller.clone()), + T::ProxyType::default(), + BlockNumberFor::::zero(), + 0 + ) + verify { + let pure_account = Pallet::::pure_account(&caller, &T::ProxyType::default(), 0, None); + assert_last_event::(Event::PureCreated { + pure: pure_account, + who: caller, + proxy_type: T::ProxyType::default(), + disambiguation_index: 0, + }.into()); + } + + kill_pure { + let p in 0 .. (T::MaxProxies::get().saturating_sub(2)); + + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + Pallet::::create_pure( + RawOrigin::Signed(whitelisted_caller()).into(), + T::ProxyType::default(), + BlockNumberFor::::zero(), + 0 + )?; + let height = system::Pallet::::block_number(); + let ext_index = system::Pallet::::extrinsic_index().unwrap_or(0); + let pure_account = Pallet::::pure_account(&caller, &T::ProxyType::default(), 0, None); + + add_proxies::(p, Some(pure_account.clone()))?; + ensure!(Proxies::::contains_key(&pure_account), "pure proxy not created"); + }: _(RawOrigin::Signed(pure_account.clone()), caller_lookup, T::ProxyType::default(), 0, height, ext_index) + verify { + assert!(!Proxies::::contains_key(&pure_account)); + } + + impl_benchmark_test_suite!(Proxy, crate::tests::new_test_ext(), crate::tests::Test); +} diff --git a/pallets/proxy/src/lib.rs b/pallets/proxy/src/lib.rs new file mode 100644 index 0000000000..3f45951190 --- /dev/null +++ b/pallets/proxy/src/lib.rs @@ -0,0 +1,891 @@ +// This file is part of Substrate. +// +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 +// +// 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. + +//! # Proxy Pallet +//! A pallet allowing accounts to give permission to other accounts to dispatch types of calls from +//! their signed origin. +//! +//! The accounts to which permission is delegated may be required to announce the action that they +//! wish to execute some duration prior to execution happens. In this case, the target account may +//! reject the announcement and in doing so, veto the execution. +//! +//! - [`Config`] +//! - [`Call`] + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +mod benchmarking; +mod tests; +pub mod weights; + +extern crate alloc; + +use alloc::{boxed::Box, vec}; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::pallet_prelude::{Pays, Weight}; +use frame_support::{ + dispatch::GetDispatchInfo, + ensure, + traits::{Currency, Get, InstanceFilter, IsSubType, IsType, OriginTrait, ReservableCurrency}, + BoundedVec, +}; +use frame_system::{self as system, ensure_signed, pallet_prelude::BlockNumberFor}; +pub use pallet::*; +use scale_info::{prelude::cmp::Ordering, TypeInfo}; +use sp_io::hashing::blake2_256; +use sp_runtime::{ + traits::{Dispatchable, Hash, Saturating, StaticLookup, TrailingZeroInput, Zero}, + DispatchError, DispatchResult, RuntimeDebug, +}; +use subtensor_macros::freeze_struct; +pub use weights::WeightInfo; + +type CallHashOf = <::CallHasher as Hash>::Output; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + +/// The parameters under which a particular account has a proxy relationship with some other +/// account. +#[derive( + Encode, + Decode, + Clone, + Copy, + Eq, + PartialEq, + Ord, + PartialOrd, + RuntimeDebug, + MaxEncodedLen, + TypeInfo, +)] +#[freeze_struct("a37bb67fe5520678")] +pub struct ProxyDefinition { + /// The account which may act on behalf of another. + pub delegate: AccountId, + /// A value defining the subset of calls that it is allowed to make. + pub proxy_type: ProxyType, + /// The number of blocks that an announcement must be in place for before the corresponding + /// call may be dispatched. If zero, then no announcement is needed. + pub delay: BlockNumber, +} + +/// Details surrounding a specific instance of an announcement to make a call. +#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +#[freeze_struct("4c1b5c8c3bc489ad")] +pub struct Announcement { + /// The account which made the announcement. + real: AccountId, + /// The hash of the call to be made. + call_hash: Hash, + /// The height at which the announcement was made. + height: BlockNumber, +} + +#[frame_support::pallet] +pub mod pallet { + use super::{DispatchResult, *}; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + /// Configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The overarching call type. + type RuntimeCall: Parameter + + Dispatchable + + GetDispatchInfo + + From> + + IsSubType> + + IsType<::RuntimeCall>; + + /// The currency mechanism. + type Currency: ReservableCurrency; + + /// A kind of proxy; specified with the proxy and passed in to the `IsProxyable` filter. + /// The instance filter determines whether a given call may be proxied under this type. + /// + /// IMPORTANT: `Default` must be provided and MUST BE the the *most permissive* value. + type ProxyType: Parameter + + Member + + Ord + + PartialOrd + + InstanceFilter<::RuntimeCall> + + Default + + MaxEncodedLen; + + /// The base amount of currency needed to reserve for creating a proxy. + /// + /// This is held for an additional storage item whose value size is + /// `sizeof(Balance)` bytes and whose key size is `sizeof(AccountId)` bytes. + #[pallet::constant] + type ProxyDepositBase: Get>; + + /// The amount of currency needed per proxy added. + /// + /// This is held for adding 32 bytes plus an instance of `ProxyType` more into a + /// pre-existing storage value. Thus, when configuring `ProxyDepositFactor` one should take + /// into account `32 + proxy_type.encode().len()` bytes of data. + #[pallet::constant] + type ProxyDepositFactor: Get>; + + /// The maximum amount of proxies allowed for a single account. + #[pallet::constant] + type MaxProxies: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// The maximum amount of time-delayed announcements that are allowed to be pending. + #[pallet::constant] + type MaxPending: Get; + + /// The type of hash used for hashing the call. + type CallHasher: Hash; + + /// The base amount of currency needed to reserve for creating an announcement. + /// + /// This is held when a new storage item holding a `Balance` is created (typically 16 + /// bytes). + #[pallet::constant] + type AnnouncementDepositBase: Get>; + + /// The amount of currency needed per announcement made. + /// + /// This is held for adding an `AccountId`, `Hash` and `BlockNumber` (typically 68 bytes) + /// into a pre-existing storage value. + #[pallet::constant] + type AnnouncementDepositFactor: Get>; + } + + #[pallet::call] + impl Pallet { + /// Dispatch the given `call` from an account that the sender is authorised for through + /// `add_proxy`. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `real`: The account that the proxy will make a call on behalf of. + /// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call. + /// - `call`: The call to be made by the `real` account. + #[pallet::call_index(0)] + #[pallet::weight({ + let di = call.get_dispatch_info(); + let inner_call_weight = match di.pays_fee { + Pays::Yes => di.weight, + Pays::No => Weight::zero(), + }; + let base_weight = T::WeightInfo::proxy(T::MaxProxies::get()) + .saturating_add(T::DbWeight::get().reads_writes(1, 1)); + (base_weight.saturating_add(inner_call_weight), di.class) + })] + pub fn proxy( + origin: OriginFor, + real: AccountIdLookupOf, + force_proxy_type: Option, + call: Box<::RuntimeCall>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let real = T::Lookup::lookup(real)?; + let def = Self::find_proxy(&real, &who, force_proxy_type)?; + ensure!(def.delay.is_zero(), Error::::Unannounced); + + Self::do_proxy(def, real, *call); + + Ok(()) + } + + /// Register a proxy account for the sender that is able to make calls on its behalf. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `proxy`: The account that the `caller` would like to make a proxy. + /// - `proxy_type`: The permissions allowed for this proxy account. + /// - `delay`: The announcement period required of the initial proxy. Will generally be + /// zero. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::add_proxy(T::MaxProxies::get()))] + pub fn add_proxy( + origin: OriginFor, + delegate: AccountIdLookupOf, + proxy_type: T::ProxyType, + delay: BlockNumberFor, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + Self::add_proxy_delegate(&who, delegate, proxy_type, delay) + } + + /// Unregister a proxy account for the sender. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `proxy`: The account that the `caller` would like to remove as a proxy. + /// - `proxy_type`: The permissions currently enabled for the removed proxy account. + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::remove_proxy(T::MaxProxies::get()))] + pub fn remove_proxy( + origin: OriginFor, + delegate: AccountIdLookupOf, + proxy_type: T::ProxyType, + delay: BlockNumberFor, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + Self::remove_proxy_delegate(&who, delegate, proxy_type, delay) + } + + /// Unregister all proxy accounts for the sender. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// WARNING: This may be called on accounts created by `pure`, however if done, then + /// the unreserved fees will be inaccessible. **All access to this account will be lost.** + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::remove_proxies(T::MaxProxies::get()))] + pub fn remove_proxies(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::remove_all_proxy_delegates(&who); + Ok(()) + } + + /// Spawn a fresh new account that is guaranteed to be otherwise inaccessible, and + /// initialize it with a proxy of `proxy_type` for `origin` sender. + /// + /// Requires a `Signed` origin. + /// + /// - `proxy_type`: The type of the proxy that the sender will be registered as over the + /// new account. This will almost always be the most permissive `ProxyType` possible to + /// allow for maximum flexibility. + /// - `index`: A disambiguation index, in case this is called multiple times in the same + /// transaction (e.g. with `utility::batch`). Unless you're using `batch` you probably just + /// want to use `0`. + /// - `delay`: The announcement period required of the initial proxy. Will generally be + /// zero. + /// + /// Fails with `Duplicate` if this has already been called in this transaction, from the + /// same sender, with the same parameters. + /// + /// Fails if there are insufficient funds to pay for deposit. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::create_pure(T::MaxProxies::get()))] + pub fn create_pure( + origin: OriginFor, + proxy_type: T::ProxyType, + delay: BlockNumberFor, + index: u16, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + let pure = Self::pure_account(&who, &proxy_type, index, None); + ensure!(!Proxies::::contains_key(&pure), Error::::Duplicate); + + let proxy_def = ProxyDefinition { + delegate: who.clone(), + proxy_type: proxy_type.clone(), + delay, + }; + let bounded_proxies: BoundedVec<_, T::MaxProxies> = vec![proxy_def] + .try_into() + .map_err(|_| Error::::TooMany)?; + + let deposit = T::ProxyDepositBase::get().saturating_add(T::ProxyDepositFactor::get()); + T::Currency::reserve(&who, deposit)?; + + Proxies::::insert(&pure, (bounded_proxies, deposit)); + Self::deposit_event(Event::PureCreated { + pure, + who, + proxy_type, + disambiguation_index: index, + }); + + Ok(()) + } + + /// Removes a previously spawned pure proxy. + /// + /// WARNING: **All access to this account will be lost.** Any funds held in it will be + /// inaccessible. + /// + /// Requires a `Signed` origin, and the sender account must have been created by a call to + /// `pure` with corresponding parameters. + /// + /// - `spawner`: The account that originally called `pure` to create this account. + /// - `index`: The disambiguation index originally passed to `pure`. Probably `0`. + /// - `proxy_type`: The proxy type originally passed to `pure`. + /// - `height`: The height of the chain when the call to `pure` was processed. + /// - `ext_index`: The extrinsic index in which the call to `pure` was processed. + /// + /// Fails with `NoPermission` in case the caller is not a previously created pure + /// account whose `pure` call has corresponding parameters. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::kill_pure(T::MaxProxies::get()))] + pub fn kill_pure( + origin: OriginFor, + spawner: AccountIdLookupOf, + proxy_type: T::ProxyType, + index: u16, + #[pallet::compact] height: BlockNumberFor, + #[pallet::compact] ext_index: u32, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let spawner = T::Lookup::lookup(spawner)?; + + let when = (height, ext_index); + let proxy = Self::pure_account(&spawner, &proxy_type, index, Some(when)); + ensure!(proxy == who, Error::::NoPermission); + + let (_, deposit) = Proxies::::take(&who); + T::Currency::unreserve(&spawner, deposit); + + Ok(()) + } + + /// Publish the hash of a proxy-call that will be made in the future. + /// + /// This must be called some number of blocks before the corresponding `proxy` is attempted + /// if the delay associated with the proxy relationship is greater than zero. + /// + /// No more than `MaxPending` announcements may be made at any one time. + /// + /// This will take a deposit of `AnnouncementDepositFactor` as well as + /// `AnnouncementDepositBase` if there are no other pending announcements. + /// + /// The dispatch origin for this call must be _Signed_ and a proxy of `real`. + /// + /// Parameters: + /// - `real`: The account that the proxy will make a call on behalf of. + /// - `call_hash`: The hash of the call to be made by the `real` account. + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::announce(T::MaxPending::get(), T::MaxProxies::get()))] + pub fn announce( + origin: OriginFor, + real: AccountIdLookupOf, + call_hash: CallHashOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let real = T::Lookup::lookup(real)?; + Proxies::::get(&real) + .0 + .into_iter() + .find(|x| x.delegate == who) + .ok_or(Error::::NotProxy)?; + + let announcement = Announcement { + real: real.clone(), + call_hash, + height: system::Pallet::::block_number(), + }; + + Announcements::::try_mutate(&who, |(ref mut pending, ref mut deposit)| { + pending + .try_push(announcement) + .map_err(|_| Error::::TooMany)?; + Self::rejig_deposit( + &who, + *deposit, + T::AnnouncementDepositBase::get(), + T::AnnouncementDepositFactor::get(), + pending.len(), + ) + .map(|d| { + d.expect("Just pushed; pending.len() > 0; rejig_deposit returns Some; qed") + }) + .map(|d| *deposit = d) + })?; + Self::deposit_event(Event::Announced { + real, + proxy: who, + call_hash, + }); + + Ok(()) + } + + /// Remove a given announcement. + /// + /// May be called by a proxy account to remove a call they previously announced and return + /// the deposit. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `real`: The account that the proxy will make a call on behalf of. + /// - `call_hash`: The hash of the call to be made by the `real` account. + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::remove_announcement( + T::MaxPending::get(), + T::MaxProxies::get() + ))] + pub fn remove_announcement( + origin: OriginFor, + real: AccountIdLookupOf, + call_hash: CallHashOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let real = T::Lookup::lookup(real)?; + Self::edit_announcements(&who, |ann| ann.real != real || ann.call_hash != call_hash)?; + + Ok(()) + } + + /// Remove the given announcement of a delegate. + /// + /// May be called by a target (proxied) account to remove a call that one of their delegates + /// (`delegate`) has announced they want to execute. The deposit is returned. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `delegate`: The account that previously announced the call. + /// - `call_hash`: The hash of the call to be made. + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::reject_announcement( + T::MaxPending::get(), + T::MaxProxies::get() + ))] + pub fn reject_announcement( + origin: OriginFor, + delegate: AccountIdLookupOf, + call_hash: CallHashOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + Self::edit_announcements(&delegate, |ann| { + ann.real != who || ann.call_hash != call_hash + })?; + + Ok(()) + } + + /// Dispatch the given `call` from an account that the sender is authorized for through + /// `add_proxy`. + /// + /// Removes any corresponding announcement(s). + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `real`: The account that the proxy will make a call on behalf of. + /// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call. + /// - `call`: The call to be made by the `real` account. + #[pallet::call_index(9)] + #[pallet::weight({ + let di = call.get_dispatch_info(); + (T::WeightInfo::proxy_announced(T::MaxPending::get(), T::MaxProxies::get()) + // AccountData for inner call origin accountdata. + .saturating_add(T::DbWeight::get().reads_writes(1, 1)) + .saturating_add(di.weight), + di.class) + })] + pub fn proxy_announced( + origin: OriginFor, + delegate: AccountIdLookupOf, + real: AccountIdLookupOf, + force_proxy_type: Option, + call: Box<::RuntimeCall>, + ) -> DispatchResult { + ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + let real = T::Lookup::lookup(real)?; + let def = Self::find_proxy(&real, &delegate, force_proxy_type)?; + + let call_hash = T::CallHasher::hash_of(&call); + let now = system::Pallet::::block_number(); + Self::edit_announcements(&delegate, |ann| { + ann.real != real + || ann.call_hash != call_hash + || now.saturating_sub(ann.height) < def.delay + }) + .map_err(|_| Error::::Unannounced)?; + + Self::do_proxy(def, real, *call); + + Ok(()) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A proxy was executed correctly, with the given. + ProxyExecuted { result: DispatchResult }, + /// A pure account has been created by new proxy with given + /// disambiguation index and proxy type. + PureCreated { + pure: T::AccountId, + who: T::AccountId, + proxy_type: T::ProxyType, + disambiguation_index: u16, + }, + /// An announcement was placed to make a call in the future. + Announced { + real: T::AccountId, + proxy: T::AccountId, + call_hash: CallHashOf, + }, + /// A proxy was added. + ProxyAdded { + delegator: T::AccountId, + delegatee: T::AccountId, + proxy_type: T::ProxyType, + delay: BlockNumberFor, + }, + /// A proxy was removed. + ProxyRemoved { + delegator: T::AccountId, + delegatee: T::AccountId, + proxy_type: T::ProxyType, + delay: BlockNumberFor, + }, + } + + #[pallet::error] + pub enum Error { + /// There are too many proxies registered or too many announcements pending. + TooMany, + /// Proxy registration not found. + NotFound, + /// Sender is not a proxy of the account to be proxied. + NotProxy, + /// A call which is incompatible with the proxy type's filter was attempted. + Unproxyable, + /// Account is already a proxy. + Duplicate, + /// Call may not be made by proxy because it may escalate its privileges. + NoPermission, + /// Announcement, if made at all, was made too recently. + Unannounced, + /// Cannot add self as proxy. + NoSelfProxy, + } + + /// The set of account proxies. Maps the account which has delegated to the accounts + /// which are being delegated to, together with the amount held on deposit. + #[pallet::storage] + pub type Proxies = StorageMap< + _, + Twox64Concat, + T::AccountId, + ( + BoundedVec< + ProxyDefinition>, + T::MaxProxies, + >, + BalanceOf, + ), + ValueQuery, + >; + + /// The announcements made by the proxy (key). + #[pallet::storage] + pub type Announcements = StorageMap< + _, + Twox64Concat, + T::AccountId, + ( + BoundedVec, BlockNumberFor>, T::MaxPending>, + BalanceOf, + ), + ValueQuery, + >; +} + +impl Pallet { + /// Public function to proxies storage. + pub fn proxies( + account: T::AccountId, + ) -> ( + BoundedVec>, T::MaxProxies>, + BalanceOf, + ) { + Proxies::::get(account) + } + + /// Public function to announcements storage. + pub fn announcements( + account: T::AccountId, + ) -> ( + BoundedVec, BlockNumberFor>, T::MaxPending>, + BalanceOf, + ) { + Announcements::::get(account) + } + + /// Calculate the address of an pure account. + /// + /// - `who`: The spawner account. + /// - `proxy_type`: The type of the proxy that the sender will be registered as over the + /// new account. This will almost always be the most permissive `ProxyType` possible to + /// allow for maximum flexibility. + /// - `index`: A disambiguation index, in case this is called multiple times in the same + /// transaction (e.g. with `utility::batch`). Unless you're using `batch` you probably just + /// want to use `0`. + /// - `maybe_when`: The block height and extrinsic index of when the pure account was + /// created. None to use current block height and extrinsic index. + pub fn pure_account( + who: &T::AccountId, + proxy_type: &T::ProxyType, + index: u16, + maybe_when: Option<(BlockNumberFor, u32)>, + ) -> T::AccountId { + let (height, ext_index) = maybe_when.unwrap_or_else(|| { + ( + system::Pallet::::block_number(), + system::Pallet::::extrinsic_index().unwrap_or_default(), + ) + }); + let entropy = ( + b"modlpy/proxy____", + who, + height, + ext_index, + proxy_type, + index, + ) + .using_encoded(blake2_256); + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") + } + + /// Register a proxy account for the delegator that is able to make calls on its behalf. + /// + /// Parameters: + /// - `delegator`: The delegator account. + /// - `delegatee`: The account that the `delegator` would like to make a proxy. + /// - `proxy_type`: The permissions allowed for this proxy account. + /// - `delay`: The announcement period required of the initial proxy. Will generally be + /// zero. + pub fn add_proxy_delegate( + delegator: &T::AccountId, + delegatee: T::AccountId, + proxy_type: T::ProxyType, + delay: BlockNumberFor, + ) -> DispatchResult { + ensure!(delegator != &delegatee, Error::::NoSelfProxy); + Proxies::::try_mutate(delegator, |(ref mut proxies, ref mut deposit)| { + let proxy_def = ProxyDefinition { + delegate: delegatee.clone(), + proxy_type: proxy_type.clone(), + delay, + }; + let i = proxies + .binary_search(&proxy_def) + .err() + .ok_or(Error::::Duplicate)?; + proxies + .try_insert(i, proxy_def) + .map_err(|_| Error::::TooMany)?; + let new_deposit = Self::deposit(proxies.len() as u32); + match new_deposit.cmp(deposit) { + Ordering::Greater => { + T::Currency::reserve(delegator, new_deposit.saturating_sub(*deposit))?; + } + Ordering::Less => { + T::Currency::unreserve(delegator, deposit.saturating_sub(new_deposit)); + } + Ordering::Equal => (), + } + *deposit = new_deposit; + Self::deposit_event(Event::::ProxyAdded { + delegator: delegator.clone(), + delegatee, + proxy_type, + delay, + }); + Ok(()) + }) + } + + /// Unregister a proxy account for the delegator. + /// + /// Parameters: + /// - `delegator`: The delegator account. + /// - `delegatee`: The account that the `delegator` would like to make a proxy. + /// - `proxy_type`: The permissions allowed for this proxy account. + /// - `delay`: The announcement period required of the initial proxy. Will generally be + /// zero. + pub fn remove_proxy_delegate( + delegator: &T::AccountId, + delegatee: T::AccountId, + proxy_type: T::ProxyType, + delay: BlockNumberFor, + ) -> DispatchResult { + Proxies::::try_mutate_exists(delegator, |x| { + let (mut proxies, old_deposit) = x.take().ok_or(Error::::NotFound)?; + let proxy_def = ProxyDefinition { + delegate: delegatee.clone(), + proxy_type: proxy_type.clone(), + delay, + }; + let i = proxies + .binary_search(&proxy_def) + .ok() + .ok_or(Error::::NotFound)?; + proxies.remove(i); + let new_deposit = Self::deposit(proxies.len() as u32); + match new_deposit.cmp(&old_deposit) { + Ordering::Greater => { + T::Currency::reserve(delegator, new_deposit.saturating_sub(old_deposit))?; + } + Ordering::Less => { + T::Currency::unreserve(delegator, old_deposit.saturating_sub(new_deposit)); + } + Ordering::Equal => (), + } + if !proxies.is_empty() { + *x = Some((proxies, new_deposit)) + } + Self::deposit_event(Event::::ProxyRemoved { + delegator: delegator.clone(), + delegatee, + proxy_type, + delay, + }); + Ok(()) + }) + } + + pub fn deposit(num_proxies: u32) -> BalanceOf { + if num_proxies == 0 { + Zero::zero() + } else { + T::ProxyDepositBase::get() + .saturating_add(T::ProxyDepositFactor::get().saturating_mul(num_proxies.into())) + } + } + + fn rejig_deposit( + who: &T::AccountId, + old_deposit: BalanceOf, + base: BalanceOf, + factor: BalanceOf, + len: usize, + ) -> Result>, DispatchError> { + let new_deposit = if len == 0 { + BalanceOf::::zero() + } else { + base.saturating_add(factor.saturating_mul((len as u32).into())) + }; + match new_deposit.cmp(&old_deposit) { + Ordering::Greater => { + T::Currency::reserve(who, new_deposit.saturating_sub(old_deposit))?; + } + Ordering::Less => { + T::Currency::unreserve(who, old_deposit.saturating_sub(new_deposit)); + } + Ordering::Equal => (), + } + Ok(if len == 0 { None } else { Some(new_deposit) }) + } + + fn edit_announcements< + F: FnMut(&Announcement, BlockNumberFor>) -> bool, + >( + delegate: &T::AccountId, + mut f: F, + ) -> DispatchResult { + Announcements::::try_mutate_exists(delegate, |x| { + let (mut pending, old_deposit) = x.take().ok_or(Error::::NotFound)?; + let orig_pending_len = pending.len(); + pending.retain(&mut f); + ensure!(orig_pending_len > pending.len(), Error::::NotFound); + *x = Self::rejig_deposit( + delegate, + old_deposit, + T::AnnouncementDepositBase::get(), + T::AnnouncementDepositFactor::get(), + pending.len(), + )? + .map(|deposit| (pending, deposit)); + Ok(()) + }) + } + + pub fn find_proxy( + real: &T::AccountId, + delegate: &T::AccountId, + force_proxy_type: Option, + ) -> Result>, DispatchError> { + let f = |x: &ProxyDefinition>| -> bool { + &x.delegate == delegate && force_proxy_type.as_ref().is_none_or(|y| &x.proxy_type == y) + }; + Ok(Proxies::::get(real) + .0 + .into_iter() + .find(f) + .ok_or(Error::::NotProxy)?) + } + + fn do_proxy( + def: ProxyDefinition>, + real: T::AccountId, + call: ::RuntimeCall, + ) { + // This is a freshly authenticated new account, the origin restrictions doesn't apply. + let mut origin: T::RuntimeOrigin = frame_system::RawOrigin::Signed(real).into(); + origin.add_filter(move |c: &::RuntimeCall| { + let c = ::RuntimeCall::from_ref(c); + // We make sure the proxy call does access this pallet to change modify proxies. + match c.is_sub_type() { + // Proxy call cannot add or remove a proxy with more permissions than it already + // has. + Some(Call::add_proxy { ref proxy_type, .. }) + | Some(Call::remove_proxy { ref proxy_type, .. }) + if !def.proxy_type.is_superset(proxy_type) => + { + false + } + // Proxy call cannot remove all proxies or kill pure proxies unless it has full + // permissions. + Some(Call::remove_proxies { .. }) | Some(Call::kill_pure { .. }) + if def.proxy_type != T::ProxyType::default() => + { + false + } + _ => def.proxy_type.filter(c), + } + }); + let e = call.dispatch(origin); + Self::deposit_event(Event::ProxyExecuted { + result: e.map(|_| ()).map_err(|e| e.error), + }); + } + + /// Removes all proxy delegates for a given delegator. + /// + /// Parameters: + /// - `delegator`: The delegator account. + pub fn remove_all_proxy_delegates(delegator: &T::AccountId) { + let (_, old_deposit) = Proxies::::take(delegator); + T::Currency::unreserve(delegator, old_deposit); + } +} diff --git a/pallets/proxy/src/tests.rs b/pallets/proxy/src/tests.rs new file mode 100644 index 0000000000..04bd0bf566 --- /dev/null +++ b/pallets/proxy/src/tests.rs @@ -0,0 +1,965 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +// Tests for Proxy Pallet + +#![cfg(test)] + +use super::*; + +use crate as proxy; +use alloc::{vec, vec::Vec}; +use codec::{Decode, Encode}; +use frame_support::{ + assert_noop, assert_ok, derive_impl, + traits::{ConstU32, ConstU64, Contains}, +}; +use sp_core::H256; +use sp_runtime::{traits::BlakeTwo256, BuildStorage, DispatchError, RuntimeDebug}; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system = 1, + Balances: pallet_balances = 2, + Proxy: proxy = 3, + Utility: pallet_utility = 4, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type BaseCallFilter = BaseFilter; + type AccountData = pallet_balances::AccountData; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type ReserveIdentifier = [u8; 8]; + type AccountStore = System; +} + +impl pallet_utility::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = (); +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + RuntimeDebug, + MaxEncodedLen, + scale_info::TypeInfo, +)] +pub enum ProxyType { + Any, + JustTransfer, + JustUtility, +} +impl Default for ProxyType { + fn default() -> Self { + Self::Any + } +} +impl InstanceFilter for ProxyType { + fn filter(&self, c: &RuntimeCall) -> bool { + match self { + ProxyType::Any => true, + ProxyType::JustTransfer => { + matches!( + c, + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) + ) + } + ProxyType::JustUtility => matches!(c, RuntimeCall::Utility { .. }), + } + } + fn is_superset(&self, o: &Self) -> bool { + self == &ProxyType::Any || self == o + } +} +pub struct BaseFilter; +impl Contains for BaseFilter { + fn contains(c: &RuntimeCall) -> bool { + match *c { + // Remark is used as a no-op call in the benchmarking + RuntimeCall::System(SystemCall::remark { .. }) => true, + RuntimeCall::System(_) => false, + _ => true, + } + } +} +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = ProxyType; + type ProxyDepositBase = ConstU64<1>; + type ProxyDepositFactor = ConstU64<1>; + type MaxProxies = ConstU32<4>; + type WeightInfo = (); + type CallHasher = BlakeTwo256; + type MaxPending = ConstU32<2>; + type AnnouncementDepositBase = ConstU64<1>; + type AnnouncementDepositFactor = ConstU64<1>; +} + +use super::{Call as ProxyCall, Event as ProxyEvent}; +use frame_system::Call as SystemCall; +use pallet_balances::{Call as BalancesCall, Event as BalancesEvent}; +use pallet_utility::{Call as UtilityCall, Event as UtilityEvent}; + +type SystemError = frame_system::Error; + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Expected to not panic"); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 3)], + } + .assimilate_storage(&mut t) + .expect("Expected to not panic"); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +fn last_events(n: usize) -> Vec { + system::Pallet::::events() + .into_iter() + .rev() + .take(n) + .rev() + .map(|e| e.event) + .collect() +} + +fn expect_events(e: Vec) { + assert_eq!(last_events(e.len()), e); +} + +fn call_transfer(dest: u64, value: u64) -> RuntimeCall { + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value }) +} + +#[test] +fn announcement_works() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 3, + ProxyType::Any, + 1 + )); + System::assert_last_event( + ProxyEvent::ProxyAdded { + delegator: 1, + delegatee: 3, + proxy_type: ProxyType::Any, + delay: 1, + } + .into(), + ); + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(2), + 3, + ProxyType::Any, + 1 + )); + assert_eq!(Balances::reserved_balance(3), 0); + + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 1, [1; 32].into())); + let announcements = Announcements::::get(3); + assert_eq!( + announcements.0, + vec![Announcement { + real: 1, + call_hash: [1; 32].into(), + height: 1 + }] + ); + assert_eq!(Balances::reserved_balance(3), announcements.1); + + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 2, [2; 32].into())); + let announcements = Announcements::::get(3); + assert_eq!( + announcements.0, + vec![ + Announcement { + real: 1, + call_hash: [1; 32].into(), + height: 1 + }, + Announcement { + real: 2, + call_hash: [2; 32].into(), + height: 1 + }, + ] + ); + assert_eq!(Balances::reserved_balance(3), announcements.1); + + assert_noop!( + Proxy::announce(RuntimeOrigin::signed(3), 2, [3; 32].into()), + Error::::TooMany + ); + }); +} + +#[test] +fn remove_announcement_works() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 3, + ProxyType::Any, + 1 + )); + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(2), + 3, + ProxyType::Any, + 1 + )); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 1, [1; 32].into())); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 2, [2; 32].into())); + let e = Error::::NotFound; + assert_noop!( + Proxy::remove_announcement(RuntimeOrigin::signed(3), 1, [0; 32].into()), + e + ); + assert_ok!(Proxy::remove_announcement( + RuntimeOrigin::signed(3), + 1, + [1; 32].into() + )); + let announcements = Announcements::::get(3); + assert_eq!( + announcements.0, + vec![Announcement { + real: 2, + call_hash: [2; 32].into(), + height: 1 + }] + ); + assert_eq!(Balances::reserved_balance(3), announcements.1); + }); +} + +#[test] +fn reject_announcement_works() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 3, + ProxyType::Any, + 1 + )); + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(2), + 3, + ProxyType::Any, + 1 + )); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 1, [1; 32].into())); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 2, [2; 32].into())); + let e = Error::::NotFound; + assert_noop!( + Proxy::reject_announcement(RuntimeOrigin::signed(1), 3, [0; 32].into()), + e + ); + let e = Error::::NotFound; + assert_noop!( + Proxy::reject_announcement(RuntimeOrigin::signed(4), 3, [1; 32].into()), + e + ); + assert_ok!(Proxy::reject_announcement( + RuntimeOrigin::signed(1), + 3, + [1; 32].into() + )); + let announcements = Announcements::::get(3); + assert_eq!( + announcements.0, + vec![Announcement { + real: 2, + call_hash: [2; 32].into(), + height: 1 + }] + ); + assert_eq!(Balances::reserved_balance(3), announcements.1); + }); +} + +#[test] +fn announcer_must_be_proxy() { + new_test_ext().execute_with(|| { + assert_noop!( + Proxy::announce(RuntimeOrigin::signed(2), 1, H256::zero()), + Error::::NotProxy + ); + }); +} + +#[test] +fn calling_proxy_doesnt_remove_announcement() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 2, + ProxyType::Any, + 0 + )); + + let call = Box::new(call_transfer(6, 1)); + let call_hash = BlakeTwo256::hash_of(&call); + + assert_ok!(Proxy::announce(RuntimeOrigin::signed(2), 1, call_hash)); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(2), 1, None, call)); + + // The announcement is not removed by calling proxy. + let announcements = Announcements::::get(2); + assert_eq!( + announcements.0, + vec![Announcement { + real: 1, + call_hash, + height: 1 + }] + ); + }); +} + +#[test] +fn delayed_requires_pre_announcement() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 2, + ProxyType::Any, + 1 + )); + let call = Box::new(call_transfer(6, 1)); + let e = Error::::Unannounced; + assert_noop!( + Proxy::proxy(RuntimeOrigin::signed(2), 1, None, call.clone()), + e + ); + let e = Error::::Unannounced; + assert_noop!( + Proxy::proxy_announced(RuntimeOrigin::signed(0), 2, 1, None, call.clone()), + e + ); + let call_hash = BlakeTwo256::hash_of(&call); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(2), 1, call_hash)); + system::Pallet::::set_block_number(2); + assert_ok!(Proxy::proxy_announced( + RuntimeOrigin::signed(0), + 2, + 1, + None, + call.clone() + )); + }); +} + +#[test] +fn proxy_announced_removes_announcement_and_returns_deposit() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 3, + ProxyType::Any, + 1 + )); + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(2), + 3, + ProxyType::Any, + 1 + )); + let call = Box::new(call_transfer(6, 1)); + let call_hash = BlakeTwo256::hash_of(&call); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 1, call_hash)); + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 2, call_hash)); + // Too early to execute announced call + let e = Error::::Unannounced; + assert_noop!( + Proxy::proxy_announced(RuntimeOrigin::signed(0), 3, 1, None, call.clone()), + e + ); + + system::Pallet::::set_block_number(2); + assert_ok!(Proxy::proxy_announced( + RuntimeOrigin::signed(0), + 3, + 1, + None, + call.clone() + )); + let announcements = Announcements::::get(3); + assert_eq!( + announcements.0, + vec![Announcement { + real: 2, + call_hash, + height: 1 + }] + ); + assert_eq!(Balances::reserved_balance(3), announcements.1); + }); +} + +#[test] +fn filtering_works() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 1000); + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 2, + ProxyType::Any, + 0 + )); + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 3, + ProxyType::JustTransfer, + 0 + )); + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 4, + ProxyType::JustUtility, + 0 + )); + + let call = Box::new(call_transfer(6, 1)); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(2), + 1, + None, + call.clone() + )); + System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into()); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(3), + 1, + None, + call.clone() + )); + System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into()); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(4), + 1, + None, + call.clone() + )); + System::assert_last_event( + ProxyEvent::ProxyExecuted { + result: Err(SystemError::CallFiltered.into()), + } + .into(), + ); + + let derivative_id = Utility::derivative_account_id(1, 0); + Balances::make_free_balance_be(&derivative_id, 1000); + let inner = Box::new(call_transfer(6, 1)); + + let call = Box::new(RuntimeCall::Utility(UtilityCall::as_derivative { + index: 0, + call: inner.clone(), + })); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(2), + 1, + None, + call.clone() + )); + System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into()); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(3), + 1, + None, + call.clone() + )); + System::assert_last_event( + ProxyEvent::ProxyExecuted { + result: Err(SystemError::CallFiltered.into()), + } + .into(), + ); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(4), + 1, + None, + call.clone() + )); + System::assert_last_event( + ProxyEvent::ProxyExecuted { + result: Err(SystemError::CallFiltered.into()), + } + .into(), + ); + + let call = Box::new(RuntimeCall::Utility(UtilityCall::batch { + calls: vec![*inner], + })); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(2), + 1, + None, + call.clone() + )); + expect_events(vec![ + UtilityEvent::BatchCompleted.into(), + ProxyEvent::ProxyExecuted { result: Ok(()) }.into(), + ]); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(3), + 1, + None, + call.clone() + )); + System::assert_last_event( + ProxyEvent::ProxyExecuted { + result: Err(SystemError::CallFiltered.into()), + } + .into(), + ); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(4), + 1, + None, + call.clone() + )); + expect_events(vec![ + UtilityEvent::BatchInterrupted { + index: 0, + error: SystemError::CallFiltered.into(), + } + .into(), + ProxyEvent::ProxyExecuted { result: Ok(()) }.into(), + ]); + + let inner = Box::new(RuntimeCall::Proxy(ProxyCall::new_call_variant_add_proxy( + 5, + ProxyType::Any, + 0, + ))); + let call = Box::new(RuntimeCall::Utility(UtilityCall::batch { + calls: vec![*inner], + })); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(2), + 1, + None, + call.clone() + )); + expect_events(vec![ + UtilityEvent::BatchCompleted.into(), + ProxyEvent::ProxyExecuted { result: Ok(()) }.into(), + ]); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(3), + 1, + None, + call.clone() + )); + System::assert_last_event( + ProxyEvent::ProxyExecuted { + result: Err(SystemError::CallFiltered.into()), + } + .into(), + ); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(4), + 1, + None, + call.clone() + )); + expect_events(vec![ + UtilityEvent::BatchInterrupted { + index: 0, + error: SystemError::CallFiltered.into(), + } + .into(), + ProxyEvent::ProxyExecuted { result: Ok(()) }.into(), + ]); + + let call = Box::new(RuntimeCall::Proxy(ProxyCall::remove_proxies {})); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(3), + 1, + None, + call.clone() + )); + System::assert_last_event( + ProxyEvent::ProxyExecuted { + result: Err(SystemError::CallFiltered.into()), + } + .into(), + ); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(4), + 1, + None, + call.clone() + )); + System::assert_last_event( + ProxyEvent::ProxyExecuted { + result: Err(SystemError::CallFiltered.into()), + } + .into(), + ); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(2), + 1, + None, + call.clone() + )); + expect_events(vec![ + BalancesEvent::::Unreserved { who: 1, amount: 5 }.into(), + ProxyEvent::ProxyExecuted { result: Ok(()) }.into(), + ]); + }); +} + +#[test] +fn add_remove_proxies_works() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 2, + ProxyType::Any, + 0 + )); + assert_noop!( + Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::Any, 0), + Error::::Duplicate + ); + assert_eq!(Balances::reserved_balance(1), 2); + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 2, + ProxyType::JustTransfer, + 0 + )); + assert_eq!(Balances::reserved_balance(1), 3); + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 3, + ProxyType::Any, + 0 + )); + assert_eq!(Balances::reserved_balance(1), 4); + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 4, + ProxyType::JustUtility, + 0 + )); + assert_eq!(Balances::reserved_balance(1), 5); + assert_noop!( + Proxy::add_proxy(RuntimeOrigin::signed(1), 4, ProxyType::Any, 0), + Error::::TooMany + ); + assert_noop!( + Proxy::remove_proxy(RuntimeOrigin::signed(1), 3, ProxyType::JustTransfer, 0), + Error::::NotFound + ); + assert_ok!(Proxy::remove_proxy( + RuntimeOrigin::signed(1), + 4, + ProxyType::JustUtility, + 0 + )); + System::assert_last_event( + ProxyEvent::ProxyRemoved { + delegator: 1, + delegatee: 4, + proxy_type: ProxyType::JustUtility, + delay: 0, + } + .into(), + ); + assert_eq!(Balances::reserved_balance(1), 4); + assert_ok!(Proxy::remove_proxy( + RuntimeOrigin::signed(1), + 3, + ProxyType::Any, + 0 + )); + assert_eq!(Balances::reserved_balance(1), 3); + System::assert_last_event( + ProxyEvent::ProxyRemoved { + delegator: 1, + delegatee: 3, + proxy_type: ProxyType::Any, + delay: 0, + } + .into(), + ); + assert_ok!(Proxy::remove_proxy( + RuntimeOrigin::signed(1), + 2, + ProxyType::Any, + 0 + )); + assert_eq!(Balances::reserved_balance(1), 2); + System::assert_last_event( + ProxyEvent::ProxyRemoved { + delegator: 1, + delegatee: 2, + proxy_type: ProxyType::Any, + delay: 0, + } + .into(), + ); + assert_ok!(Proxy::remove_proxy( + RuntimeOrigin::signed(1), + 2, + ProxyType::JustTransfer, + 0 + )); + assert_eq!(Balances::reserved_balance(1), 0); + System::assert_last_event( + ProxyEvent::ProxyRemoved { + delegator: 1, + delegatee: 2, + proxy_type: ProxyType::JustTransfer, + delay: 0, + } + .into(), + ); + assert_noop!( + Proxy::add_proxy(RuntimeOrigin::signed(1), 1, ProxyType::Any, 0), + Error::::NoSelfProxy + ); + }); +} + +#[test] +fn cannot_add_proxy_without_balance() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(5), + 3, + ProxyType::Any, + 0 + )); + assert_eq!(Balances::reserved_balance(5), 2); + assert_noop!( + Proxy::add_proxy(RuntimeOrigin::signed(5), 4, ProxyType::Any, 0), + DispatchError::ConsumerRemaining, + ); + }); +} + +#[test] +fn proxying_works() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 2, + ProxyType::JustTransfer, + 0 + )); + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 3, + ProxyType::Any, + 0 + )); + + let call = Box::new(call_transfer(6, 1)); + assert_noop!( + Proxy::proxy(RuntimeOrigin::signed(4), 1, None, call.clone()), + Error::::NotProxy + ); + assert_noop!( + Proxy::proxy( + RuntimeOrigin::signed(2), + 1, + Some(ProxyType::Any), + call.clone() + ), + Error::::NotProxy + ); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(2), + 1, + None, + call.clone() + )); + System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into()); + assert_eq!(Balances::free_balance(6), 1); + + let call = Box::new(RuntimeCall::System(SystemCall::set_code { code: vec![] })); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(3), + 1, + None, + call.clone() + )); + System::assert_last_event( + ProxyEvent::ProxyExecuted { + result: Err(SystemError::CallFiltered.into()), + } + .into(), + ); + + let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_keep_alive { + dest: 6, + value: 1, + })); + assert_ok!( + RuntimeCall::Proxy(super::Call::new_call_variant_proxy(1, None, call.clone())) + .dispatch(RuntimeOrigin::signed(2)) + ); + System::assert_last_event( + ProxyEvent::ProxyExecuted { + result: Err(SystemError::CallFiltered.into()), + } + .into(), + ); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(3), + 1, + None, + call.clone() + )); + System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into()); + assert_eq!(Balances::free_balance(6), 2); + }); +} + +#[test] +fn pure_works() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 11); // An extra one for the ED. + assert_ok!(Proxy::create_pure( + RuntimeOrigin::signed(1), + ProxyType::Any, + 0, + 0 + )); + let anon = Proxy::pure_account(&1, &ProxyType::Any, 0, None); + System::assert_last_event( + ProxyEvent::PureCreated { + pure: anon, + who: 1, + proxy_type: ProxyType::Any, + disambiguation_index: 0, + } + .into(), + ); + + // other calls to pure allowed as long as they're not exactly the same. + assert_ok!(Proxy::create_pure( + RuntimeOrigin::signed(1), + ProxyType::JustTransfer, + 0, + 0 + )); + assert_ok!(Proxy::create_pure( + RuntimeOrigin::signed(1), + ProxyType::Any, + 0, + 1 + )); + let anon2 = Proxy::pure_account(&2, &ProxyType::Any, 0, None); + assert_ok!(Proxy::create_pure( + RuntimeOrigin::signed(2), + ProxyType::Any, + 0, + 0 + )); + assert_noop!( + Proxy::create_pure(RuntimeOrigin::signed(1), ProxyType::Any, 0, 0), + Error::::Duplicate + ); + System::set_extrinsic_index(1); + assert_ok!(Proxy::create_pure( + RuntimeOrigin::signed(1), + ProxyType::Any, + 0, + 0 + )); + System::set_extrinsic_index(0); + System::set_block_number(2); + assert_ok!(Proxy::create_pure( + RuntimeOrigin::signed(1), + ProxyType::Any, + 0, + 0 + )); + + let call = Box::new(call_transfer(6, 1)); + assert_ok!(Balances::transfer_allow_death( + RuntimeOrigin::signed(3), + anon, + 5 + )); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call)); + System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into()); + assert_eq!(Balances::free_balance(6), 1); + + let call = Box::new(RuntimeCall::Proxy(ProxyCall::new_call_variant_kill_pure( + 1, + ProxyType::Any, + 0, + 1, + 0, + ))); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(2), + anon2, + None, + call.clone() + )); + let de = DispatchError::from(Error::::NoPermission).stripped(); + System::assert_last_event(ProxyEvent::ProxyExecuted { result: Err(de) }.into()); + assert_noop!( + Proxy::kill_pure(RuntimeOrigin::signed(1), 1, ProxyType::Any, 0, 1, 0), + Error::::NoPermission + ); + assert_eq!(Balances::free_balance(1), 1); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(1), + anon, + None, + call.clone() + )); + assert_eq!(Balances::free_balance(1), 3); + assert_noop!( + Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call.clone()), + Error::::NotProxy + ); + }); +} diff --git a/pallets/proxy/src/weights.rs b/pallets/proxy/src/weights.rs new file mode 100644 index 0000000000..3093298e3e --- /dev/null +++ b/pallets/proxy/src/weights.rs @@ -0,0 +1,415 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Autogenerated weights for `pallet_proxy` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// ./target/production/substrate-node +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_proxy +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./substrate/frame/proxy/src/weights.rs +// --header=./substrate/HEADER-APACHE2 +// --template=./substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_proxy`. +pub trait WeightInfo { + fn proxy(p: u32, ) -> Weight; + fn proxy_announced(a: u32, p: u32, ) -> Weight; + fn remove_announcement(a: u32, p: u32, ) -> Weight; + fn reject_announcement(a: u32, p: u32, ) -> Weight; + fn announce(a: u32, p: u32, ) -> Weight; + fn add_proxy(p: u32, ) -> Weight; + fn remove_proxy(p: u32, ) -> Weight; + fn remove_proxies(p: u32, ) -> Weight; + fn create_pure(p: u32, ) -> Weight; + fn kill_pure(p: u32, ) -> Weight; +} + +/// Weights for `pallet_proxy` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `306 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 18_280_000 picoseconds. + Weight::from_parts(19_655_145, 4706) + // Standard Error: 2_345 + .saturating_add(Weight::from_parts(36_306, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + } + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn proxy_announced(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `633 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 41_789_000 picoseconds. + Weight::from_parts(41_812_078, 5698) + // Standard Error: 3_694 + .saturating_add(Weight::from_parts(163_029, 0).saturating_mul(a.into())) + // Standard Error: 3_817 + .saturating_add(Weight::from_parts(79_539, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn remove_announcement(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `403 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 22_475_000 picoseconds. + Weight::from_parts(22_666_821, 5698) + // Standard Error: 1_797 + .saturating_add(Weight::from_parts(170_629, 0).saturating_mul(a.into())) + // Standard Error: 1_857 + .saturating_add(Weight::from_parts(18_799, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn reject_announcement(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `403 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 22_326_000 picoseconds. + Weight::from_parts(22_654_227, 5698) + // Standard Error: 1_859 + .saturating_add(Weight::from_parts(168_822, 0).saturating_mul(a.into())) + // Standard Error: 1_921 + .saturating_add(Weight::from_parts(21_839, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn announce(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `420 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 31_551_000 picoseconds. + Weight::from_parts(32_205_445, 5698) + // Standard Error: 4_089 + .saturating_add(Weight::from_parts(167_596, 0).saturating_mul(a.into())) + // Standard Error: 4_225 + .saturating_add(Weight::from_parts(67_833, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn add_proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `161 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 21_495_000 picoseconds. + Weight::from_parts(22_358_457, 4706) + // Standard Error: 1_606 + .saturating_add(Weight::from_parts(64_322, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn remove_proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `161 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 21_495_000 picoseconds. + Weight::from_parts(22_579_308, 4706) + // Standard Error: 2_571 + .saturating_add(Weight::from_parts(62_404, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn remove_proxies(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `161 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 20_541_000 picoseconds. + Weight::from_parts(21_456_750, 4706) + // Standard Error: 1_697 + .saturating_add(Weight::from_parts(45_387, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn create_pure(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `173` + // Estimated: `4706` + // Minimum execution time: 22_809_000 picoseconds. + Weight::from_parts(23_878_644, 4706) + // Standard Error: 1_600 + .saturating_add(Weight::from_parts(10_149, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[0, 30]`. + fn kill_pure(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `198 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 20_993_000 picoseconds. + Weight::from_parts(22_067_418, 4706) + // Standard Error: 1_673 + .saturating_add(Weight::from_parts(52_703, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `306 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 18_280_000 picoseconds. + Weight::from_parts(19_655_145, 4706) + // Standard Error: 2_345 + .saturating_add(Weight::from_parts(36_306, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + } + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn proxy_announced(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `633 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 41_789_000 picoseconds. + Weight::from_parts(41_812_078, 5698) + // Standard Error: 3_694 + .saturating_add(Weight::from_parts(163_029, 0).saturating_mul(a.into())) + // Standard Error: 3_817 + .saturating_add(Weight::from_parts(79_539, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn remove_announcement(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `403 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 22_475_000 picoseconds. + Weight::from_parts(22_666_821, 5698) + // Standard Error: 1_797 + .saturating_add(Weight::from_parts(170_629, 0).saturating_mul(a.into())) + // Standard Error: 1_857 + .saturating_add(Weight::from_parts(18_799, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn reject_announcement(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `403 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 22_326_000 picoseconds. + Weight::from_parts(22_654_227, 5698) + // Standard Error: 1_859 + .saturating_add(Weight::from_parts(168_822, 0).saturating_mul(a.into())) + // Standard Error: 1_921 + .saturating_add(Weight::from_parts(21_839, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn announce(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `420 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 31_551_000 picoseconds. + Weight::from_parts(32_205_445, 5698) + // Standard Error: 4_089 + .saturating_add(Weight::from_parts(167_596, 0).saturating_mul(a.into())) + // Standard Error: 4_225 + .saturating_add(Weight::from_parts(67_833, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn add_proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `161 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 21_495_000 picoseconds. + Weight::from_parts(22_358_457, 4706) + // Standard Error: 1_606 + .saturating_add(Weight::from_parts(64_322, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn remove_proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `161 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 21_495_000 picoseconds. + Weight::from_parts(22_579_308, 4706) + // Standard Error: 2_571 + .saturating_add(Weight::from_parts(62_404, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn remove_proxies(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `161 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 20_541_000 picoseconds. + Weight::from_parts(21_456_750, 4706) + // Standard Error: 1_697 + .saturating_add(Weight::from_parts(45_387, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn create_pure(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `173` + // Estimated: `4706` + // Minimum execution time: 22_809_000 picoseconds. + Weight::from_parts(23_878_644, 4706) + // Standard Error: 1_600 + .saturating_add(Weight::from_parts(10_149, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[0, 30]`. + fn kill_pure(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `198 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 20_993_000 picoseconds. + Weight::from_parts(22_067_418, 4706) + // Standard Error: 1_673 + .saturating_add(Weight::from_parts(52_703, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index d36602bbbb..b2633fa381 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -70,31 +70,6 @@ impl Pallet { false } - /// Sets the emission values for each netuid - /// - pub fn set_emission_values(netuids: &[u16], emission: Vec) -> Result<(), &'static str> { - log::debug!( - "set_emission_values: netuids: {:?} emission:{:?}", - netuids, - emission - ); - - // Be careful this function can fail. - if Self::contains_invalid_root_uids(netuids) { - log::error!("set_emission_values: contains_invalid_root_uids"); - return Err("Invalid netuids"); - } - if netuids.len() != emission.len() { - log::error!("set_emission_values: netuids.len() != emission.len()"); - return Err("netuids and emission must have the same length"); - } - for (netuid_i, emission_i) in netuids.iter().zip(emission) { - log::debug!("set netuid:{:?} emission:{:?}", netuid_i, emission_i); - EmissionValues::::insert(*netuid_i, emission_i); - } - Ok(()) - } - /// Retrieves weight matrix associated with the root network. /// Weights represent the preferences for each subnetwork. /// @@ -577,7 +552,6 @@ impl Pallet { MaxAllowedUids::::remove(netuid); ImmunityPeriod::::remove(netuid); ActivityCutoff::::remove(netuid); - EmissionValues::::remove(netuid); MaxWeightsLimit::::remove(netuid); MinAllowedWeights::::remove(netuid); RegistrationsThisInterval::::remove(netuid); @@ -647,55 +621,6 @@ impl Pallet { lock_cost } - /// This function is used to determine which subnet to prune when the total number of networks has reached the limit. - /// It iterates over all the networks and finds the oldest subnet with the minimum emission value that is not in the immunity period. - /// - /// # Returns: - /// * 'u16': - /// - The uid of the network to be pruned. - /// - pub fn get_subnet_to_prune() -> u16 { - let mut netuids: Vec = vec![]; - let current_block = Self::get_current_block_as_u64(); - - // Even if we don't have a root subnet, this still works - for netuid in NetworksAdded::::iter_keys_from(NetworksAdded::::hashed_key_for(0)) { - if current_block.saturating_sub(Self::get_network_registered_block(netuid)) - < Self::get_network_immunity_period() - { - continue; - } - - // This iterator seems to return them in order anyways, so no need to sort by key - netuids.push(netuid); - } - - // Now we sort by emission, and then by subnet creation time. - netuids.sort_by(|a, b| { - use sp_std::cmp::Ordering; - - match Self::get_emission_value(*b).cmp(&Self::get_emission_value(*a)) { - Ordering::Equal => { - if Self::get_network_registered_block(*b) - < Self::get_network_registered_block(*a) - { - Ordering::Less - } else { - Ordering::Equal - } - } - v => v, - } - }); - - log::debug!("Netuids Order: {:?}", netuids); - - match netuids.last() { - Some(netuid) => *netuid, - None => 0, - } - } - pub fn get_network_registered_block(netuid: u16) -> u64 { NetworkRegisteredAt::::get(netuid) } diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 50c691819f..7836423868 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -266,8 +266,6 @@ impl Pallet { pending_swapped, owner_cut ); - // Setup. - let zero: I96F32 = asfloat!(0.0); // Run the epoch. let hotkey_emission: Vec<(T::AccountId, u64, u64)> = @@ -297,6 +295,25 @@ impl Pallet { log::debug!("incentives: {:?}", incentives); log::debug!("dividends: {:?}", dividends); + Self::distribute_dividends_and_incentives( + netuid, + pending_tao, + owner_cut, + incentives, + dividends, + ); + } + + pub fn distribute_dividends_and_incentives( + netuid: u16, + pending_tao: u64, + owner_cut: u64, + incentives: BTreeMap, + dividends: BTreeMap, + ) { + // Setup. + let zero: I96F32 = asfloat!(0.0); + // Accumulate root divs and alpha_divs. For each hotkey we compute their // local and root dividend proportion based on their alpha_stake/root_stake let mut total_root_divs: I96F32 = asfloat!(0); @@ -375,8 +392,19 @@ impl Pallet { // Distribute mining incentives. for (hotkey, incentive) in incentives { - // Increase stake for miner. log::debug!("incentives: hotkey: {:?}", incentive); + + if let Ok(owner_hotkey) = SubnetOwnerHotkey::::try_get(netuid) { + if hotkey == owner_hotkey { + log::debug!( + "incentives: hotkey: {:?} is SN owner hotkey, skipping {:?}", + hotkey, + incentive + ); + continue; // Skip/burn miner-emission for SN owner hotkey. + } + } + // Increase stake for miner. Self::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey.clone(), &Owner::::get(hotkey.clone()), @@ -497,6 +525,7 @@ impl Pallet { // Calculate the hotkey's share of the validator emission based on its childkey take let validating_emission: I96F32 = I96F32::saturating_from_num(dividends); + let mut remaining_emission: I96F32 = validating_emission; let childkey_take_proportion: I96F32 = I96F32::saturating_from_num(Self::get_childkey_take(hotkey, netuid)) .safe_div(I96F32::saturating_from_num(u16::MAX)); @@ -507,23 +536,12 @@ impl Pallet { ); // NOTE: Only the validation emission should be split amongst parents. - // Reserve childkey take - let child_emission_take: I96F32 = childkey_take_proportion - .saturating_mul(I96F32::saturating_from_num(validating_emission)); - let remaining_emission: I96F32 = validating_emission.saturating_sub(child_emission_take); - log::debug!( - "Child emission take: {:?} for hotkey {:?}", - child_emission_take, - hotkey - ); - log::debug!( - "Remaining emission: {:?} for hotkey {:?}", - remaining_emission, - hotkey - ); + // Grab the owner of the childkey. + let childkey_owner = Self::get_owning_coldkey_for_hotkey(hotkey); // Initialize variables to track emission distribution let mut to_parents: u64 = 0; + let mut total_child_emission_take: I96F32 = I96F32::saturating_from_num(0); // Initialize variables to calculate total stakes from parents let mut total_contribution: I96F32 = I96F32::saturating_from_num(0); @@ -580,33 +598,66 @@ impl Pallet { // Distribute emission to parents based on their contributions. // Deduct childkey take from parent contribution. for (parent, contribution) in parent_contributions { - // Sum up the total emission for this parent + let parent_owner = Self::get_owning_coldkey_for_hotkey(&parent); + + // Get the stake contribution of this parent key of the total stake. let emission_factor: I96F32 = contribution .checked_div(total_contribution) .unwrap_or(I96F32::saturating_from_num(0)); - let parent_emission: u64 = - (remaining_emission.saturating_mul(emission_factor)).saturating_to_num::(); + + // Get the parent's portion of the validating emission based on their contribution. + let mut parent_emission: I96F32 = validating_emission.saturating_mul(emission_factor); + // Remove this emission from the remaining emission. + remaining_emission = remaining_emission.saturating_sub(parent_emission); + + // Get the childkey take for this parent. + let child_emission_take: I96F32 = if parent_owner == childkey_owner { + // The parent is from the same coldkey, so we don't remove any childkey take. + I96F32::saturating_from_num(0) + } else { + childkey_take_proportion + .saturating_mul(I96F32::saturating_from_num(parent_emission)) + }; + + // Remove the childkey take from the parent's emission. + parent_emission = parent_emission.saturating_sub(child_emission_take); + + // Add the childkey take to the total childkey take tracker. + total_child_emission_take = + total_child_emission_take.saturating_add(child_emission_take); + + log::debug!( + "Child emission take: {:?} for hotkey {:?}", + child_emission_take, + hotkey + ); + log::debug!( + "Parent emission: {:?} for hotkey {:?}", + parent_emission, + hotkey + ); + log::debug!("remaining emission: {:?}", remaining_emission); // Add the parent's emission to the distribution list - dividend_tuples.push((parent.clone(), parent_emission)); + dividend_tuples.push((parent.clone(), parent_emission.saturating_to_num::())); // Keep track of total emission distributed to parents - to_parents = to_parents.saturating_add(parent_emission); + to_parents = to_parents.saturating_add(parent_emission.saturating_to_num::()); log::debug!( - "Parent contribution for parent {:?} with contribution: {:?}, of total: {:?} of emission: {:?} gets: {:?}", + "Parent contribution for parent {:?} with contribution: {:?}, of total: {:?} ({:?}), of emission: {:?} gets: {:?}", parent, contribution, total_contribution, - remaining_emission, - parent_emission + emission_factor, + validating_emission, + parent_emission, ); } // Calculate the final emission for the hotkey itself. // This includes the take left from the parents and the self contribution. let child_emission = remaining_emission - .saturating_add(child_emission_take) - .saturating_to_num::() - .saturating_sub(to_parents); + .saturating_add(total_child_emission_take) + .saturating_to_num::(); // Add the hotkey's own emission to the distribution list dividend_tuples.push((hotkey.clone(), child_emission)); diff --git a/pallets/subtensor/src/epoch/math.rs b/pallets/subtensor/src/epoch/math.rs index 9818b06a48..436dba84a0 100644 --- a/pallets/subtensor/src/epoch/math.rs +++ b/pallets/subtensor/src/epoch/math.rs @@ -569,6 +569,33 @@ pub fn inplace_mask_diag(matrix: &mut [Vec]) { }); } +// Mask out the diagonal of the input matrix in-place, except for the diagonal entry at except_index. +#[allow(dead_code)] +pub fn inplace_mask_diag_except_index(matrix: &mut [Vec], except_index: u16) { + let Some(first_row) = matrix.first() else { + return; + }; + if first_row.is_empty() { + return; + } + assert_eq!(matrix.len(), first_row.len()); + + let diag_at_index = matrix + .get(except_index as usize) + .and_then(|row| row.get(except_index as usize)) + .cloned(); + + inplace_mask_diag(matrix); + + matrix.get_mut(except_index as usize).map(|row| { + row.get_mut(except_index as usize).map(|value| { + if let Some(diag_at_index) = diag_at_index { + *value = diag_at_index; + } + }) + }); +} + // Return a new sparse matrix that replaces masked rows with an empty vector placeholder. #[allow(dead_code)] pub fn mask_rows_sparse( @@ -604,6 +631,29 @@ pub fn mask_diag_sparse(sparse_matrix: &[Vec<(u16, I32F32)>]) -> Vec], + except_index: u16, +) -> Vec> { + sparse_matrix + .iter() + .enumerate() + .map(|(i, sparse_row)| { + sparse_row + .iter() + .filter(|(j, _)| { + // Is not a diagonal OR is the diagonal at except_index + i != (*j as usize) || (i == except_index as usize && *j == except_index) + }) + .copied() + .collect() + }) + .collect() +} + // Remove cells from sparse matrix where the mask function of two vectors is true. #[allow(dead_code, clippy::indexing_slicing)] pub fn vec_mask_sparse_matrix( diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index a53c8d562f..27dd17fa0b 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -111,6 +111,9 @@ impl Pallet { // == Weights == // ============= + // Get owner uid. + let owner_uid: Option = Self::get_owner_uid(netuid); + // Access network weights row unnormalized. let mut weights: Vec> = Self::get_weights(netuid); log::trace!("W:\n{:?}\n", &weights); @@ -119,7 +122,13 @@ impl Pallet { inplace_mask_rows(&validator_forbids, &mut weights); log::trace!("W (permit): {:?}", &weights); - // Remove self-weight by masking diagonal. + // Remove self-weight by masking diagonal; keep owner_uid self-weight. + if let Some(owner_uid) = owner_uid { + inplace_mask_diag_except_index(&mut weights, owner_uid); + } else { + inplace_mask_diag(&mut weights); + } + inplace_mask_diag(&mut weights); log::trace!("W (permit+diag):\n{:?}\n", &weights); @@ -454,6 +463,8 @@ impl Pallet { // == Weights == // ============= + let owner_uid: Option = Self::get_owner_uid(netuid); + // Access network weights row unnormalized. let mut weights: Vec> = Self::get_weights_sparse(netuid); log::trace!("Weights: {:?}", &weights); @@ -462,8 +473,12 @@ impl Pallet { weights = mask_rows_sparse(&validator_forbids, &weights); log::trace!("Weights (permit): {:?}", &weights); - // Remove self-weight by masking diagonal. - weights = mask_diag_sparse(&weights); + // Remove self-weight by masking diagonal; keep owner_uid self-weight. + if let Some(owner_uid) = owner_uid { + weights = mask_diag_sparse_except_index(&weights, owner_uid); + } else { + weights = mask_diag_sparse(&weights); + } log::trace!("Weights (permit+diag): {:?}", &weights); // Remove weights referring to deregistered neurons. diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 0ef0029f9f..23272a0993 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -396,6 +396,11 @@ pub mod pallet { 0 } #[pallet::type_value] + /// Default EMA price halving blocks + pub fn DefaultEMAPriceMovingBlocks() -> u64 { + T::InitialEmaPriceHalvingPeriod::get() + } + #[pallet::type_value] /// Default registrations this block. pub fn DefaultBurn() -> u64 { T::InitialBurn::get() @@ -501,11 +506,6 @@ pub mod pallet { T::InitialSubnetOwnerCut::get() } #[pallet::type_value] - /// Default value for subnet limit. - pub fn DefaultSubnetLimit() -> u16 { - T::InitialSubnetLimit::get() - } - #[pallet::type_value] /// Default value for network rate limit. pub fn DefaultNetworkRateLimit() -> u64 { if cfg!(feature = "pow-faucet") { @@ -514,9 +514,10 @@ pub mod pallet { T::InitialNetworkRateLimit::get() } #[pallet::type_value] - /// Default value for emission values. - pub fn DefaultEmissionValues() -> u64 { - 0 + /// Default value for weights version key rate limit. + /// In units of tempos. + pub fn DefaultWeightsVersionKeyRateLimit() -> u64 { + 5 // 5 tempos } #[pallet::type_value] /// Default value for pending emission. @@ -728,11 +729,6 @@ pub mod pallet { pub fn DefaultAlphaValues() -> (u16, u16) { (45875, 58982) } - #[pallet::type_value] - /// Default value for network max stake. - pub fn DefaultNetworkMaxStake() -> u64 { - T::InitialNetworkMaxStake::get() - } #[pallet::type_value] /// Default value for coldkey swap schedule duration @@ -743,7 +739,11 @@ pub mod pallet { #[pallet::type_value] /// Default value for applying pending items (e.g. childkeys). pub fn DefaultPendingCooldown() -> u64 { - 1 + if cfg!(feature = "fast-blocks") { + return 15; + } + + 7_200 } #[pallet::type_value] @@ -1055,9 +1055,6 @@ pub mod pallet { pub type MaxRegistrationsPerBlock = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxRegistrationsPerBlock>; #[pallet::storage] - /// --- ITEM( maximum_number_of_networks ) - pub type SubnetLimit = StorageValue<_, u16, ValueQuery, DefaultSubnetLimit>; - #[pallet::storage] /// --- ITEM( total_number_of_existing_networks ) pub type TotalNetworks = StorageValue<_, u16, ValueQuery>; #[pallet::storage] @@ -1091,6 +1088,10 @@ pub mod pallet { pub type NetworkRateLimit = StorageValue<_, u64, ValueQuery, DefaultNetworkRateLimit>; #[pallet::storage] // --- ITEM( nominator_min_required_stake ) pub type NominatorMinRequiredStake = StorageValue<_, u64, ValueQuery, DefaultZeroU64>; + #[pallet::storage] + /// ITEM( weights_version_key_rate_limit ) --- Rate limit in tempos. + pub type WeightsVersionKeyRateLimit = + StorageValue<_, u64, ValueQuery, DefaultWeightsVersionKeyRateLimit>; /// ============================ /// ==== Subnet Locks ===== @@ -1152,10 +1153,6 @@ pub mod pallet { pub type NetworkRegisteredAt = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultNetworkRegisteredAt>; #[pallet::storage] - /// --- MAP ( netuid ) --> emission_values - pub type EmissionValues = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultEmissionValues>; - #[pallet::storage] /// --- MAP ( netuid ) --> pending_emission pub type PendingEmission = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultPendingEmission>; @@ -1302,6 +1299,10 @@ pub mod pallet { pub type RegistrationsThisBlock = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultRegistrationsThisBlock>; #[pallet::storage] + /// --- MAP ( netuid ) --> Halving time of average moving price. + pub type EMAPriceHalvingBlocks = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultEMAPriceMovingBlocks>; + #[pallet::storage] /// --- MAP ( netuid ) --> global_RAO_recycled_for_registration pub type RAORecycledForRegistration = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultRAORecycledForRegistration>; @@ -1324,10 +1325,6 @@ pub mod pallet { /// MAP ( netuid ) --> (alpha_low, alpha_high) pub type AlphaValues = StorageMap<_, Identity, u16, (u16, u16), ValueQuery, DefaultAlphaValues>; - /// MAP ( netuid ) --> max stake allowed on a subnet. - #[pallet::storage] - pub type NetworkMaxStake = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultNetworkMaxStake>; /// ======================================= /// ==== Subnetwork Consensus Storage ==== @@ -1578,15 +1575,21 @@ pub mod pallet { } /// Returns the transaction priority for stake operations. - pub fn get_priority_staking(coldkey: &T::AccountId, hotkey: &T::AccountId) -> u64 { + pub fn get_priority_staking( + coldkey: &T::AccountId, + hotkey: &T::AccountId, + stake_amount: u64, + ) -> u64 { match LastColdkeyHotkeyStakeBlock::::get(coldkey, hotkey) { Some(last_stake_block) => { let current_block_number = Self::get_current_block_as_u64(); let default_priority = current_block_number.saturating_sub(last_stake_block); - default_priority.saturating_add(u32::MAX as u64) + default_priority + .saturating_add(u32::MAX as u64) + .saturating_add(stake_amount) } - None => 0, + None => stake_amount, } } @@ -1715,8 +1718,12 @@ where Pallet::::get_priority_set_weights(who, netuid) } - pub fn get_priority_staking(coldkey: &T::AccountId, hotkey: &T::AccountId) -> u64 { - Pallet::::get_priority_staking(coldkey, hotkey) + pub fn get_priority_staking( + coldkey: &T::AccountId, + hotkey: &T::AccountId, + stake_amount: u64, + ) -> u64 { + Pallet::::get_priority_staking(coldkey, hotkey, stake_amount) } pub fn check_weights_min_stake(who: &T::AccountId, netuid: u16) -> bool { @@ -1931,7 +1938,7 @@ where *amount_staked, false, ), - Self::get_priority_staking(who, hotkey), + Self::get_priority_staking(who, hotkey, *amount_staked), ) } Some(Call::add_stake_limit { @@ -1961,7 +1968,7 @@ where max_amount, *allow_partial, ), - Self::get_priority_staking(who, hotkey), + Self::get_priority_staking(who, hotkey, *amount_staked), ) } Some(Call::remove_stake { @@ -1979,7 +1986,7 @@ where *amount_unstaked, false, ), - Self::get_priority_staking(who, hotkey), + Self::get_priority_staking(who, hotkey, *amount_unstaked), ) } Some(Call::remove_stake_limit { @@ -2002,7 +2009,7 @@ where max_amount, *allow_partial, ), - Self::get_priority_staking(who, hotkey), + Self::get_priority_staking(who, hotkey, *amount_unstaked), ) } Some(Call::move_stake { @@ -2033,7 +2040,7 @@ where None, false, ), - Self::get_priority_staking(who, origin_hotkey), + Self::get_priority_staking(who, origin_hotkey, *alpha_amount), ) } Some(Call::transfer_stake { @@ -2064,7 +2071,7 @@ where None, true, ), - Self::get_priority_staking(who, hotkey), + Self::get_priority_staking(who, hotkey, *alpha_amount), ) } Some(Call::swap_stake { @@ -2094,7 +2101,7 @@ where None, false, ), - Self::get_priority_staking(who, hotkey), + Self::get_priority_staking(who, hotkey, *alpha_amount), ) } Some(Call::swap_stake_limit { @@ -2133,7 +2140,7 @@ where Some(*allow_partial), false, ), - Self::get_priority_staking(who, hotkey), + Self::get_priority_staking(who, hotkey, *alpha_amount), ) } Some(Call::register { netuid, .. } | Call::burned_register { netuid, .. }) => { @@ -2229,9 +2236,13 @@ where self, who: &Self::AccountId, call: &Self::Call, - _info: &DispatchInfoOf, - _len: usize, + info: &DispatchInfoOf, + len: usize, ) -> Result { + // We need to perform same checks as Self::validate so that + // the validation is performed during Executive::apply_extrinsic as well. + // this prevents inclusion of invalid tx in a block by malicious block author. + self.validate(who, call, info, len)?; match call.is_sub_type() { Some(Call::add_stake { .. }) => { let transaction_fee = 100000; diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 72a4c5f0d3..af448c8771 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -180,9 +180,6 @@ mod config { /// Initial lock reduction interval. #[pallet::constant] type InitialNetworkLockReductionInterval: Get; - /// Initial max allowed subnets - #[pallet::constant] - type InitialSubnetLimit: Get; /// Initial network creation rate limit #[pallet::constant] type InitialNetworkRateLimit: Get; @@ -198,9 +195,6 @@ mod config { /// A flag to indicate if Liquid Alpha is enabled. #[pallet::constant] type LiquidAlphaOn: Get; - /// Initial network max stake. - #[pallet::constant] - type InitialNetworkMaxStake: Get; // /// Initial hotkey emission tempo. // #[pallet::constant] // type InitialHotkeyEmissionTempo: Get; @@ -213,5 +207,8 @@ mod config { /// Initial TAO weight. #[pallet::constant] type InitialTaoWeight: Get; + /// Initial EMA price halving period + #[pallet::constant] + type InitialEmaPriceHalvingPeriod: Get; } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 40890b018a..834aa901fa 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -65,8 +65,6 @@ mod events { AxonServed(u16, T::AccountId), /// the prometheus server information is added to the network. PrometheusServed(u16, T::AccountId), - /// emission ratios for all networks is set. - EmissionValuesSet(), /// a hotkey has become a delegate. DelegateAdded(T::AccountId, T::AccountId, u16), /// the default take is set. @@ -124,7 +122,7 @@ mod events { /// the network minimum locking cost is set. NetworkMinLockCostSet(u64), /// the maximum number of subnets is set - SubnetLimitSet(u16), + // SubnetLimitSet(u16), /// the lock cost reduction is set NetworkLockCostReductionIntervalSet(u64), /// the take for a delegate is decreased. @@ -193,8 +191,8 @@ mod events { SetChildren(T::AccountId, u16, Vec<(u64, T::AccountId)>), // /// The hotkey emission tempo has been set // HotkeyEmissionTempoSet(u64), - /// The network maximum stake has been set - NetworkMaxStakeSet(u16, u64), + // /// The network maximum stake has been set + // NetworkMaxStakeSet(u16, u64), /// The identity of a coldkey has been set ChainIdentitySet(T::AccountId), /// The identity of a subnet has been set @@ -271,5 +269,11 @@ mod events { /// Parameters: /// (netuid, bool) TransferToggle(u16, bool), + + /// The owner hotkey for a subnet has been set. + /// + /// Parameters: + /// (netuid, new_hotkey) + SubnetOwnerHotkeySet(u16, T::AccountId), } } diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 812b840f50..df9dffabca 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -79,7 +79,9 @@ mod hooks { // Set the min difficulty across all subnets to a new minimum .saturating_add(migrations::migrate_set_min_difficulty::migrate_set_min_difficulty::()) // Remove Stake map entries - .saturating_add(migrations::migrate_remove_stake_map::migrate_remove_stake_map::()); + .saturating_add(migrations::migrate_remove_stake_map::migrate_remove_stake_map::()) + // Remove unused maps entries + .saturating_add(migrations::migrate_remove_unused_maps_and_values::migrate_remove_unused_maps_and_values::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs b/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs index c917c7cab1..d12a58ed37 100644 --- a/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs +++ b/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs @@ -103,7 +103,6 @@ pub fn migrate_delete_subnet_21() -> Weight { MaxAllowedUids::::remove(netuid); ImmunityPeriod::::remove(netuid); ActivityCutoff::::remove(netuid); - EmissionValues::::remove(netuid); MaxWeightsLimit::::remove(netuid); MinAllowedWeights::::remove(netuid); RegistrationsThisInterval::::remove(netuid); diff --git a/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs b/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs index 2176963574..0e85c56554 100644 --- a/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs +++ b/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs @@ -106,7 +106,6 @@ pub fn migrate_delete_subnet_3() -> Weight { MaxAllowedUids::::remove(netuid); ImmunityPeriod::::remove(netuid); ActivityCutoff::::remove(netuid); - EmissionValues::::remove(netuid); MaxWeightsLimit::::remove(netuid); MinAllowedWeights::::remove(netuid); RegistrationsThisInterval::::remove(netuid); diff --git a/pallets/subtensor/src/migrations/migrate_rao.rs b/pallets/subtensor/src/migrations/migrate_rao.rs index 3b2de079e5..623faa1661 100644 --- a/pallets/subtensor/src/migrations/migrate_rao.rs +++ b/pallets/subtensor/src/migrations/migrate_rao.rs @@ -3,7 +3,6 @@ use alloc::string::String; use frame_support::IterableStorageMap; use frame_support::{traits::Get, weights::Weight}; use sp_runtime::format; -use substrate_fixed::types::I96F32; use super::*; @@ -89,12 +88,12 @@ pub fn migrate_rao() -> Weight { let remaining_lock = lock.saturating_sub(pool_initial_tao); // Refund the owner for the remaining lock. - SubnetMovingPrice::::insert( - netuid, - I96F32::from_num(EmissionValues::::get(netuid)) - .checked_div(I96F32::from_num(1_000_000_000)) - .unwrap_or(I96F32::from_num(0.0)), - ); + // SubnetMovingPrice::::insert( + // netuid, + // I96F32::from_num(EmissionValues::::get(netuid)) + // .checked_div(I96F32::from_num(1_000_000_000)) + // .unwrap_or(I96F32::from_num(0.0)), + // ); Pallet::::add_balance_to_coldkey_account(&owner, remaining_lock); SubnetLocked::::insert(netuid, 0); // Clear lock amount. SubnetTAO::::insert(netuid, pool_initial_tao); diff --git a/pallets/subtensor/src/migrations/migrate_remove_unused_maps_and_values.rs b/pallets/subtensor/src/migrations/migrate_remove_unused_maps_and_values.rs new file mode 100644 index 0000000000..e61ff66f04 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_remove_unused_maps_and_values.rs @@ -0,0 +1,71 @@ +use super::*; +use crate::HasMigrationRun; +use frame_support::{traits::Get, weights::Weight}; +use scale_info::prelude::string::String; +use sp_io::{ + KillStorageResult, + hashing::twox_128, + storage::{clear, clear_prefix}, +}; + +fn remove_prefix(old_map: &str, weight: &mut Weight) { + let mut prefix = Vec::new(); + prefix.extend_from_slice(&twox_128("SubtensorModule".as_bytes())); + prefix.extend_from_slice(&twox_128(old_map.as_bytes())); + + let removal_results = clear_prefix(&prefix, Some(u32::MAX)); + + let removed_entries_count = match removal_results { + KillStorageResult::AllRemoved(removed) => removed as u64, + KillStorageResult::SomeRemaining(removed) => { + log::info!("Failed To Remove Some Items During migration"); + removed as u64 + } + }; + + log::info!( + "Removed {:?} entries from {:?} map.", + removed_entries_count, + old_map + ); + + *weight = (*weight).saturating_add(T::DbWeight::get().writes(removed_entries_count)); +} + +pub fn migrate_remove_unused_maps_and_values() -> Weight { + let migration_name = b"migrate_remove_unused_maps_and_values".to_vec(); + let mut weight = T::DbWeight::get().reads(1); + + if HasMigrationRun::::get(&migration_name) { + log::info!( + "Migration '{:?}' has already run. Skipping.", + migration_name + ); + return weight; + } + + log::info!( + "Running migration '{}'", + String::from_utf8_lossy(&migration_name) + ); + + // Remove EmissionValues entries + remove_prefix::("EmissionValues", &mut weight); + + // Remove NetworkMaxStake + remove_prefix::("NetworkMaxStake", &mut weight); + + // Remove SubnetLimit + clear(b"SubtensorModule::SubnetLimit"); + + // Mark Migration as Completed + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + "Migration '{:?}' completed successfully.", + String::from_utf8_lossy(&migration_name) + ); + + weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index b8754c07bf..6af6ad2a56 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -10,6 +10,7 @@ pub mod migrate_init_total_issuance; pub mod migrate_populate_owned_hotkeys; pub mod migrate_rao; pub mod migrate_remove_stake_map; +pub mod migrate_remove_unused_maps_and_values; pub mod migrate_set_min_burn; pub mod migrate_set_min_difficulty; pub mod migrate_stake_threshold; diff --git a/pallets/subtensor/src/rpc_info/dynamic_info.rs b/pallets/subtensor/src/rpc_info/dynamic_info.rs index e197e1b57c..b363931d9c 100644 --- a/pallets/subtensor/src/rpc_info/dynamic_info.rs +++ b/pallets/subtensor/src/rpc_info/dynamic_info.rs @@ -54,7 +54,7 @@ impl Pallet { tempo: Tempo::::get(netuid).into(), last_step: last_step.into(), blocks_since_last_step: blocks_since_last_step.into(), - emission: EmissionValues::::get(netuid).into(), + emission: 0.into(), alpha_in: SubnetAlphaIn::::get(netuid).into(), alpha_out: SubnetAlphaOut::::get(netuid).into(), tao_in: SubnetTAO::::get(netuid).into(), diff --git a/pallets/subtensor/src/rpc_info/metagraph.rs b/pallets/subtensor/src/rpc_info/metagraph.rs index 5404f0bff8..3d5ee7537b 100644 --- a/pallets/subtensor/src/rpc_info/metagraph.rs +++ b/pallets/subtensor/src/rpc_info/metagraph.rs @@ -173,10 +173,10 @@ impl Pallet { blocks_since_last_step: blocks_since_last_step.into(), // blocks since last epoch. // Subnet emission terms - subnet_emission: EmissionValues::::get(netuid).into(), // subnet emission via stao - alpha_in: SubnetAlphaIn::::get(netuid).into(), // amount of alpha in reserve - alpha_out: SubnetAlphaOut::::get(netuid).into(), // amount of alpha outstanding - tao_in: SubnetTAO::::get(netuid).into(), // amount of tao injected per block + subnet_emission: 0.into(), // DEPRECATED + alpha_in: SubnetAlphaIn::::get(netuid).into(), // amount of alpha in reserve + alpha_out: SubnetAlphaOut::::get(netuid).into(), // amount of alpha outstanding + tao_in: SubnetTAO::::get(netuid).into(), // amount of tao injected per block alpha_out_emission: SubnetAlphaOutEmission::::get(netuid).into(), // amount injected in alpha reserves per block alpha_in_emission: SubnetAlphaInEmission::::get(netuid).into(), // amount injected outstanding per block tao_in_emission: SubnetTaoInEmission::::get(netuid).into(), // amount of tao injected per block diff --git a/pallets/subtensor/src/rpc_info/subnet_info.rs b/pallets/subtensor/src/rpc_info/subnet_info.rs index 46644e7467..6e9e722295 100644 --- a/pallets/subtensor/src/rpc_info/subnet_info.rs +++ b/pallets/subtensor/src/rpc_info/subnet_info.rs @@ -102,7 +102,6 @@ impl Pallet { let blocks_since_last_step = Self::get_blocks_since_last_step(netuid); let tempo = Self::get_tempo(netuid); let network_modality = >::get(netuid); - let emission_values = Self::get_emission_value(netuid); let burn: Compact = Self::get_burn_as_u64(netuid).into(); // DEPRECATED let network_connect: Vec<[u16; 2]> = Vec::<[u16; 2]>::new(); @@ -126,7 +125,7 @@ impl Pallet { tempo: tempo.into(), network_modality: network_modality.into(), network_connect, - emission_values: emission_values.into(), + emission_values: 0.into(), burn, owner: Self::get_subnet_owner(netuid), }) @@ -172,7 +171,6 @@ impl Pallet { let blocks_since_last_step = Self::get_blocks_since_last_step(netuid); let tempo = Self::get_tempo(netuid); let network_modality = >::get(netuid); - let emission_value = Self::get_emission_value(netuid); let burn: Compact = Self::get_burn_as_u64(netuid).into(); let identity: Option = SubnetIdentitiesV2::::get(netuid); @@ -198,7 +196,7 @@ impl Pallet { tempo: tempo.into(), network_modality: network_modality.into(), network_connect, - emission_value: emission_value.into(), + emission_value: 0.into(), burn, owner: Self::get_subnet_owner(netuid), identity, diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index ed607eec54..e599262cef 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -1,5 +1,5 @@ use super::*; -use sp_core::Get; +use substrate_fixed::types::I96F32; impl Pallet { /// ---- The implementation for the extrinsic add_stake: Adds stake to a hotkey account. @@ -58,13 +58,19 @@ impl Pallet { )?; // 3. Ensure the remove operation from the coldkey is a success. - let tao_staked: u64 = - Self::remove_balance_from_coldkey_account(&coldkey, stake_to_be_added)?; + let tao_staked: I96F32 = + Self::remove_balance_from_coldkey_account(&coldkey, stake_to_be_added)?.into(); // 4. Swap the stake into alpha on the subnet and increase counters. // Emit the staking event. let fee = DefaultStakingFee::::get(); - Self::stake_into_subnet(&hotkey, &coldkey, netuid, tao_staked, fee); + Self::stake_into_subnet( + &hotkey, + &coldkey, + netuid, + tao_staked.saturating_to_num::(), + fee, + ); // Ok and return. Ok(()) @@ -148,12 +154,19 @@ impl Pallet { } // 5. Ensure the remove operation from the coldkey is a success. - let tao_staked: u64 = Self::remove_balance_from_coldkey_account(&coldkey, possible_stake)?; + let tao_staked: I96F32 = + Self::remove_balance_from_coldkey_account(&coldkey, possible_stake)?.into(); // 6. Swap the stake into alpha on the subnet and increase counters. // Emit the staking event. let fee = DefaultStakingFee::::get(); - Self::stake_into_subnet(&hotkey, &coldkey, netuid, tao_staked, fee); + Self::stake_into_subnet( + &hotkey, + &coldkey, + netuid, + tao_staked.saturating_to_num::(), + fee, + ); // Ok and return. Ok(()) diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index 9620664b5c..cfe3b8cde1 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -1,7 +1,7 @@ use super::*; use safe_math::*; use sp_core::Get; -use substrate_fixed::types::U64F64; +use substrate_fixed::types::{I96F32, U64F64}; impl Pallet { /// Moves stake from one hotkey to another across subnets. @@ -330,13 +330,26 @@ impl Pallet { check_transfer_toggle, )?; + // Calculate the amount that should be moved in this operation + let move_amount = if alpha_amount < max_amount { + alpha_amount + } else { + max_amount + }; + // Unstake from the origin subnet, returning TAO (or a 1:1 equivalent). - let fee = DefaultStakingFee::::get().safe_div(2); + let fee = Self::calculate_staking_fee( + origin_netuid, + origin_hotkey, + I96F32::saturating_from_num(alpha_amount), + ) + .safe_div(2); + let tao_unstaked = Self::unstake_from_subnet( origin_hotkey, origin_coldkey, origin_netuid, - alpha_amount, + move_amount, fee, ); diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index c1db7012c3..235d075e6f 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -1,5 +1,5 @@ use super::*; -use sp_core::Get; +use substrate_fixed::types::I96F32; impl Pallet { /// ---- The implementation for the extrinsic remove_stake: Removes stake from a hotkey account and adds it onto a coldkey. @@ -58,7 +58,11 @@ impl Pallet { )?; // 3. Swap the alpba to tao and update counters for this subnet. - let fee = DefaultStakingFee::::get(); + let fee = Self::calculate_staking_fee( + netuid, + &hotkey, + I96F32::saturating_from_num(alpha_unstaked), + ); let tao_unstaked: u64 = Self::unstake_from_subnet(&hotkey, &coldkey, netuid, alpha_unstaked, fee); @@ -109,8 +113,6 @@ impl Pallet { origin: T::RuntimeOrigin, hotkey: T::AccountId, ) -> dispatch::DispatchResult { - let fee = DefaultStakingFee::::get(); - // 1. We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information. let coldkey = ensure_signed(origin)?; log::info!("do_unstake_all( origin:{:?} hotkey:{:?} )", coldkey, hotkey); @@ -126,20 +128,26 @@ impl Pallet { log::debug!("All subnet netuids: {:?}", netuids); // 4. Iterate through all subnets and remove stake. - for netuid in netuids.iter() { + for netuid in netuids.into_iter() { // Ensure that the hotkey has enough stake to withdraw. let alpha_unstaked = - Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, *netuid); + Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + let fee = Self::calculate_staking_fee( + netuid, + &hotkey, + I96F32::saturating_from_num(alpha_unstaked), + ); + if alpha_unstaked > 0 { // Swap the alpha to tao and update counters for this subnet. let tao_unstaked: u64 = - Self::unstake_from_subnet(&hotkey, &coldkey, *netuid, alpha_unstaked, fee); + Self::unstake_from_subnet(&hotkey, &coldkey, netuid, alpha_unstaked, fee); // Add the balance to the coldkey. If the above fails we will not credit this coldkey. Self::add_balance_to_coldkey_account(&coldkey, tao_unstaked); // If the stake is below the minimum, we clear the nomination from storage. - Self::clear_small_nomination_if_required(&hotkey, &coldkey, *netuid); + Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid); } } @@ -177,8 +185,6 @@ impl Pallet { origin: T::RuntimeOrigin, hotkey: T::AccountId, ) -> dispatch::DispatchResult { - let fee = DefaultStakingFee::::get(); - // 1. We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information. let coldkey = ensure_signed(origin)?; log::info!("do_unstake_all( origin:{:?} hotkey:{:?} )", coldkey, hotkey); @@ -195,22 +201,28 @@ impl Pallet { // 4. Iterate through all subnets and remove stake. let mut total_tao_unstaked: u64 = 0; - for netuid in netuids.iter() { + for netuid in netuids.into_iter() { // If not Root network. - if *netuid != Self::get_root_netuid() { + if netuid != Self::get_root_netuid() { // Ensure that the hotkey has enough stake to withdraw. let alpha_unstaked = - Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, *netuid); + Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + let fee = Self::calculate_staking_fee( + netuid, + &hotkey, + I96F32::saturating_from_num(alpha_unstaked), + ); + if alpha_unstaked > 0 { // Swap the alpha to tao and update counters for this subnet. - let tao_unstaked: u64 = - Self::unstake_from_subnet(&hotkey, &coldkey, *netuid, alpha_unstaked, fee); + let tao_unstaked = + Self::unstake_from_subnet(&hotkey, &coldkey, netuid, alpha_unstaked, fee); // Increment total total_tao_unstaked = total_tao_unstaked.saturating_add(tao_unstaked); // If the stake is below the minimum, we clear the nomination from storage. - Self::clear_small_nomination_if_required(&hotkey, &coldkey, *netuid); + Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid); } } } @@ -302,8 +314,12 @@ impl Pallet { )?; // 4. Swap the alpha to tao and update counters for this subnet. - let fee = DefaultStakingFee::::get(); - let tao_unstaked: u64 = + let fee = Self::calculate_staking_fee( + netuid, + &hotkey, + I96F32::saturating_from_num(alpha_unstaked), + ); + let tao_unstaked = Self::unstake_from_subnet(&hotkey, &coldkey, netuid, possible_alpha, fee); // 5. We add the balance to the coldkey. If the above fails we will not credit this coldkey. diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index 0a3659b6ac..8be22c32f9 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -57,7 +57,19 @@ impl Pallet { } } pub fn update_moving_price(netuid: u16) { - let alpha: I96F32 = SubnetMovingAlpha::::get(); + let blocks_since_registration = I96F32::saturating_from_num( + Self::get_current_block_as_u64().saturating_sub(NetworkRegisteredAt::::get(netuid)), + ); + + // Use halving time hyperparameter. The meaning of this parameter can be best explained under + // the assumption of a constant price and SubnetMovingAlpha == 0.5: It is how many blocks it + // will take in order for the distance between current EMA of price and current price to shorten + // by half. + let halving_time = EMAPriceHalvingBlocks::::get(netuid); + let alpha: I96F32 = + SubnetMovingAlpha::::get().saturating_mul(blocks_since_registration.safe_div( + blocks_since_registration.saturating_add(I96F32::saturating_from_num(halving_time)), + )); let minus_alpha: I96F32 = I96F32::saturating_from_num(1.0).saturating_sub(alpha); let current_price: I96F32 = alpha .saturating_mul(Self::get_alpha_price(netuid).min(I96F32::saturating_from_num(1.0))); @@ -546,8 +558,12 @@ impl Pallet { amount: u64, ) -> u64 { let mut alpha_share_pool = Self::get_alpha_share_pool(hotkey.clone(), netuid); + // We expect to add a positive amount here. let actual_alpha = alpha_share_pool.update_value_for_one(coldkey, amount as i64); - actual_alpha.unsigned_abs() + + // We should return a positive amount, or 0 if the operation failed. + // e.g. the stake was removed due to precision issues. + actual_alpha.max(0).unsigned_abs() } pub fn try_increase_stake_for_hotkey_and_coldkey_on_subnet( @@ -576,6 +592,8 @@ impl Pallet { amount: u64, ) -> u64 { let mut alpha_share_pool = Self::get_alpha_share_pool(hotkey.clone(), netuid); + + // We expect a negative value here let mut actual_alpha = 0; if let Ok(value) = alpha_share_pool.try_get_value(coldkey) { if value >= amount { @@ -583,7 +601,11 @@ impl Pallet { alpha_share_pool.update_value_for_one(coldkey, (amount as i64).neg()); } } - actual_alpha.unsigned_abs() + + // Get the negation of the removed alpha, and clamp at 0. + // This ensures we return a positive value, but only if + // `actual_alpha` was negative (i.e. a decrease in stake). + actual_alpha.neg().max(0).unsigned_abs() } /// Calculates Some(Alpha) returned from pool by staking operation @@ -789,7 +811,7 @@ impl Pallet { /// Stakes TAO into a subnet for a given hotkey and coldkey pair. /// /// We update the pools associated with a subnet as well as update hotkey alpha shares. - pub fn stake_into_subnet( + pub(crate) fn stake_into_subnet( hotkey: &T::AccountId, coldkey: &T::AccountId, netuid: u16, @@ -1049,6 +1071,28 @@ impl Pallet { Ok(()) } + + pub(crate) fn calculate_staking_fee( + netuid: u16, + hotkey: &T::AccountId, + alpha_estimate: I96F32, + ) -> u64 { + if (netuid == Self::get_root_netuid()) || (SubnetMechanism::::get(netuid)) == 0 { + DefaultStakingFee::::get() + } else { + let fee = alpha_estimate + .saturating_mul( + I96F32::saturating_from_num(AlphaDividendsPerSubnet::::get(netuid, &hotkey)) + .safe_div(I96F32::saturating_from_num(TotalHotkeyAlpha::::get( + &hotkey, netuid, + ))), + ) + .saturating_mul(Self::get_alpha_price(netuid)) // fee needs to be in TAO + .saturating_to_num::(); + + fee.max(DefaultStakingFee::::get()) + } + } } /////////////////////////////////////////// diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 505905af98..87704c558a 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -25,37 +25,6 @@ impl Pallet { TotalNetworks::::get() } - /// Fetches the max number of subnet - /// - /// This function retrieves the max number of subnet. - /// - /// # Returns: - /// * 'u16': The max number of subnet - /// - pub fn get_max_subnets() -> u16 { - SubnetLimit::::get() - } - - /// Sets the max number of subnet - /// - /// This function sets the max number of subnet. - /// - pub fn set_max_subnets(limit: u16) { - SubnetLimit::::put(limit); - Self::deposit_event(Event::SubnetLimitSet(limit)); - } - - /// Returns the emission value for the given subnet. - /// - /// This function retrieves the emission value for the given subnet. - /// - /// # Returns: - /// * 'u64': The emission value for the given subnet. - /// - pub fn get_subnet_emission_value(netuid: u16) -> u64 { - EmissionValues::::get(netuid) - } - /// Returns true if the subnetwork exists. /// /// This function checks if a subnetwork with the given UID exists. @@ -321,9 +290,6 @@ impl Pallet { if !ActivityCutoff::::contains_key(netuid) { ActivityCutoff::::insert(netuid, ActivityCutoff::::get(netuid)); } - if !EmissionValues::::contains_key(netuid) { - EmissionValues::::insert(netuid, EmissionValues::::get(netuid)); - } if !MaxWeightsLimit::::contains_key(netuid) { MaxWeightsLimit::::insert(netuid, MaxWeightsLimit::::get(netuid)); } diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index 74070faaca..fa336c687b 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -158,56 +158,63 @@ impl Pallet { OwnedHotkeys::::insert(coldkey, hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - // 3. Swap total hotkey alpha for all subnets. + // 3. Swap total hotkey alpha for all subnets it exists on. // TotalHotkeyAlpha( hotkey, netuid ) -> alpha -- the total alpha that the hotkey has on a specific subnet. - let all_netuids: Vec = Self::get_all_subnet_netuids(); - for netuid in all_netuids { - let old_total_hotkey_alpha = TotalHotkeyAlpha::::get(old_hotkey, netuid); - let new_total_hotkey_alpha = TotalHotkeyAlpha::::get(new_hotkey, netuid); - TotalHotkeyAlpha::::remove(old_hotkey, netuid); - TotalHotkeyAlpha::::insert( - new_hotkey, - netuid, - old_total_hotkey_alpha.saturating_add(new_total_hotkey_alpha), - ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - } - - // 4. Swap total hotkey shares on all subnets + TotalHotkeyAlpha::::iter_prefix(old_hotkey) + .drain() + .for_each(|(netuid, old_alpha)| { + let new_total_hotkey_alpha = TotalHotkeyAlpha::::get(new_hotkey, netuid); + TotalHotkeyAlpha::::insert( + new_hotkey, + netuid, + old_alpha.saturating_add(new_total_hotkey_alpha), + ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + }); + + // 4. Swap total hotkey shares on all subnets it exists on. // TotalHotkeyShares( hotkey, netuid ) -> alpha -- the total alpha that the hotkey has on a specific subnet. - let all_netuids: Vec = Self::get_all_subnet_netuids(); - for netuid in all_netuids { - let old_total_hotkey_shares = TotalHotkeyShares::::get(old_hotkey, netuid); - let new_total_hotkey_shares = TotalHotkeyShares::::get(new_hotkey, netuid); - TotalHotkeyShares::::remove(old_hotkey, netuid); - TotalHotkeyShares::::insert( - new_hotkey, - netuid, - old_total_hotkey_shares.saturating_add(new_total_hotkey_shares), - ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - } + TotalHotkeyShares::::iter_prefix(old_hotkey) + .drain() + .for_each(|(netuid, old_shares)| { + let new_total_hotkey_shares = TotalHotkeyShares::::get(new_hotkey, netuid); + TotalHotkeyShares::::insert( + new_hotkey, + netuid, + old_shares.saturating_add(new_total_hotkey_shares), + ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + }); // 5. Swap LastTxBlock // LastTxBlock( hotkey ) --> u64 -- the last transaction block for the hotkey. + let last_tx_block: u64 = LastTxBlock::::get(old_hotkey); LastTxBlock::::remove(old_hotkey); - LastTxBlock::::insert(new_hotkey, Self::get_current_block_as_u64()); - weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 2)); + LastTxBlock::::insert(new_hotkey, last_tx_block); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); // 6. Swap LastTxBlockDelegateTake // LastTxBlockDelegateTake( hotkey ) --> u64 -- the last transaction block for the hotkey delegate take. + let last_tx_block_delegate_take: u64 = LastTxBlockDelegateTake::::get(old_hotkey); LastTxBlockDelegateTake::::remove(old_hotkey); - LastTxBlockDelegateTake::::insert(new_hotkey, Self::get_current_block_as_u64()); + LastTxBlockDelegateTake::::insert(new_hotkey, last_tx_block_delegate_take); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + // 7. Swap LastTxBlockChildKeyTake + // LastTxBlockChildKeyTake( hotkey ) --> u64 -- the last transaction block for the hotkey child key take. + let last_tx_block_child_key_take: u64 = LastTxBlockChildKeyTake::::get(old_hotkey); + LastTxBlockChildKeyTake::::remove(old_hotkey); + LastTxBlockChildKeyTake::::insert(new_hotkey, last_tx_block_child_key_take); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - // 7. Swap Senate members. + // 8. Swap Senate members. // Senate( hotkey ) --> ? if T::SenateMembers::is_member(old_hotkey) { T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } - // 8. Swap delegates. + // 9. Swap delegates. // Delegates( hotkey ) -> take value -- the hotkey delegate take value. if Delegates::::contains_key(old_hotkey) { let old_delegate_take = Delegates::::get(old_hotkey); diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index b16836e8f8..21ddd453f5 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -1421,128 +1421,6 @@ fn test_do_revoke_children_multiple_complex_scenario() { }); } -// 35: Test getting network max stake -// This test verifies the functionality of getting the network max stake: -// - Checks the default max stake value -// - Sets a new max stake value -// - Verifies that the new value is retrieved correctly -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_get_network_max_stake --exact --show-output --nocapture -#[test] -fn test_get_network_max_stake() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let default_max_stake = SubtensorModule::get_network_max_stake(netuid); - - // Check that the default value is set correctly - assert_eq!(default_max_stake, u64::MAX); - - // Set a new max stake value - let new_max_stake: u64 = 1_000_000; - SubtensorModule::set_network_max_stake(netuid, new_max_stake); - - // Check that the new value is retrieved correctly - assert_eq!( - SubtensorModule::get_network_max_stake(netuid), - new_max_stake - ); - }); -} - -// 36: Test setting network max stake -// This test verifies the functionality of setting the network max stake: -// - Checks the initial max stake value -// - Sets a new max stake value -// - Verifies that the new value is set correctly -// - Checks that the appropriate event is emitted -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_set_network_max_stake --exact --show-output --nocapture -#[test] -fn test_set_network_max_stake() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let initial_max_stake = SubtensorModule::get_network_max_stake(netuid); - - // Set a new max stake value - let new_max_stake: u64 = 500_000; - SubtensorModule::set_network_max_stake(netuid, new_max_stake); - - // Check that the new value is set correctly - assert_eq!( - SubtensorModule::get_network_max_stake(netuid), - new_max_stake - ); - assert_ne!( - SubtensorModule::get_network_max_stake(netuid), - initial_max_stake - ); - - // Check that the event is emitted - System::assert_last_event(Event::NetworkMaxStakeSet(netuid, new_max_stake).into()); - }); -} - -// 37: Test setting network max stake for multiple networks -// This test verifies the functionality of setting different max stake values for multiple networks: -// - Sets different max stake values for two networks -// - Verifies that the values are set correctly for each network -// - Checks that the values are different between networks -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_set_network_max_stake_multiple_networks --exact --show-output --nocapture -#[test] -fn test_set_network_max_stake_multiple_networks() { - new_test_ext(1).execute_with(|| { - let netuid1: u16 = 1; - let netuid2: u16 = 2; - - // Set different max stake values for two networks - let max_stake1: u64 = 1_000_000; - let max_stake2: u64 = 2_000_000; - SubtensorModule::set_network_max_stake(netuid1, max_stake1); - SubtensorModule::set_network_max_stake(netuid2, max_stake2); - - // Check that the values are set correctly for each network - assert_eq!(SubtensorModule::get_network_max_stake(netuid1), max_stake1); - assert_eq!(SubtensorModule::get_network_max_stake(netuid2), max_stake2); - assert_ne!( - SubtensorModule::get_network_max_stake(netuid1), - SubtensorModule::get_network_max_stake(netuid2) - ); - }); -} - -// 38: Test updating network max stake -// This test verifies the functionality of updating an existing network max stake value: -// - Sets an initial max stake value -// - Updates the max stake value -// - Verifies that the value is updated correctly -// - Checks that the appropriate event is emitted for the update -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_set_network_max_stake_update --exact --show-output --nocapture -#[test] -fn test_set_network_max_stake_update() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - - // Set an initial max stake value - let initial_max_stake: u64 = 1_000_000; - SubtensorModule::set_network_max_stake(netuid, initial_max_stake); - - // Update the max stake value - let updated_max_stake: u64 = 1_500_000; - SubtensorModule::set_network_max_stake(netuid, updated_max_stake); - - // Check that the value is updated correctly - assert_eq!( - SubtensorModule::get_network_max_stake(netuid), - updated_max_stake - ); - assert_ne!( - SubtensorModule::get_network_max_stake(netuid), - initial_max_stake - ); - - // Check that the event is emitted for the update - System::assert_last_event(Event::NetworkMaxStakeSet(netuid, updated_max_stake).into()); - }); -} - // 39: Test children stake values // This test verifies the correct distribution of stake among parent and child neurons: // - Sets up a network with a parent neuron and multiple child neurons @@ -2003,9 +1881,8 @@ fn test_get_stake_for_hotkey_on_subnet_edge_cases() { register_ok_neuron(netuid, child1, coldkey, 0); register_ok_neuron(netuid, child2, coldkey, 0); - // Set network max stake - let network_max_stake: u64 = 500_000_000_000_000; // 500_000 TAO - SubtensorModule::set_network_max_stake(netuid, network_max_stake); + // Set above old value of network max stake + let network_max_stake: u64 = 600_000_000_000_000; // Increase stake to the network max SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( @@ -3906,3 +3783,210 @@ fn test_do_set_child_as_sn_owner_not_enough_stake() { ); }); } + +// Test dividend distribution for children with same coldkey Owner +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_dividend_distribution_with_children_same_coldkey_owner --exact --show-output +#[test] +fn test_dividend_distribution_with_children_same_coldkey_owner() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + add_network(netuid, 1, 0); + // Set SN owner cut to 0 + SubtensorModule::set_subnet_owner_cut(0_u16); + + // Define hotkeys and coldkeys + let hotkey_a: U256 = U256::from(1); + let hotkey_b: U256 = U256::from(2); + let coldkey_a: U256 = U256::from(100); // Only one coldkey + + // Register neurons with decreasing stakes + register_ok_neuron(netuid, hotkey_a, coldkey_a, 0); + register_ok_neuron(netuid, hotkey_b, coldkey_a, 0); + + // Add initial stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey_a, 1_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_a, 1_000); + + // Swap to alpha + let total_tao: I96F32 = I96F32::from_num(300_000 + 100_000); + let total_alpha: I96F32 = I96F32::from_num(SubtensorModule::swap_tao_for_alpha( + netuid, + total_tao.saturating_to_num::(), + )); + + // Set the stakes directly + // This avoids needing to swap tao to alpha, impacting the initial stake distribution. + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_a, + &coldkey_a, + netuid, + (total_alpha * I96F32::from_num(300_000) / total_tao).saturating_to_num::(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_b, + &coldkey_a, + netuid, + (total_alpha * I96F32::from_num(100_000) / total_tao).saturating_to_num::(), + ); + + // Get old stakes + let stake_a: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_a); + let stake_b: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_b); + + // Assert initial stake is correct + let rel_stake_a = I96F32::from_num(stake_a) / total_tao; + let rel_stake_b = I96F32::from_num(stake_b) / total_tao; + + log::info!("rel_stake_a: {:?}", rel_stake_a); // 0.75 -> 3/4 + log::info!("rel_stake_b: {:?}", rel_stake_b); // 0.25 -> 1/4 + assert_eq!(rel_stake_a, I96F32::from_num(300_000) / total_tao); + assert_eq!(rel_stake_b, I96F32::from_num(100_000) / total_tao); + + // Set parent-child relationships + // A -> B (50% of A's stake) + mock_set_children(&coldkey_a, &hotkey_a, netuid, &[(u64::MAX / 2, hotkey_b)]); + + // Set CHK take rate to 1/9 + let chk_take: I96F32 = I96F32::from_num(1_f64 / 9_f64); + let chk_take_u16: u16 = (chk_take * I96F32::from_num(u16::MAX)).saturating_to_num::(); + ChildkeyTake::::insert(hotkey_b, netuid, chk_take_u16); + + // Set the weight of root TAO to be 0%, so only alpha is effective. + SubtensorModule::set_tao_weight(0); + + let hardcoded_emission: I96F32 = I96F32::from_num(1_000_000); // 1 million (adjust as needed) + + let hotkey_emission: Vec<(U256, u64, u64)> = + SubtensorModule::epoch(netuid, hardcoded_emission.saturating_to_num::()); + log::info!("hotkey_emission: {:?}", hotkey_emission); + let total_emission: I96F32 = hotkey_emission + .iter() + .map(|(_, _, emission)| I96F32::from_num(*emission)) + .sum(); + + // Verify emissions match expected from CHK arrangements + let em_eps: I96F32 = I96F32::from_num(1e-4); // 4 decimal places + // A's pending emission: + assert!( + ((I96F32::from_num(hotkey_emission[0].2) / total_emission) - + I96F32::from_num(3_f64 / 4_f64 * 1_f64 / 2_f64)).abs() // 3/4 * 1/2 = 3/8; 50% -> B + <= em_eps, + "A should have pending emission of 3/8 of total emission" + ); + // B's pending emission: + assert!( + ((I96F32::from_num(hotkey_emission[1].2) / total_emission) - + (I96F32::from_num(1_f64 / 4_f64 + 3_f64 / 4_f64 * 1_f64 / 2_f64))).abs() // 1/4 + 3/4 * 1/2 = 5/8; 50% from A + <= em_eps, + "B should have pending emission of 5/8 of total emission: {:?}", + I96F32::from_num(hotkey_emission[1].2) / total_emission + ); + + // Get the distribution of dividends including the Parent/Child relationship. + let dividends_a = SubtensorModule::get_dividends_distribution( + &hotkey_a, + netuid, + hardcoded_emission.saturating_to_num::(), + ); + let dividends_b = SubtensorModule::get_dividends_distribution( + &hotkey_b, + netuid, + hardcoded_emission.saturating_to_num::(), + ); + log::info!("dividends_a: {:?}", dividends_a); + log::info!("dividends_b: {:?}", dividends_b); + + // We expect A should have no impact from B, as they have the same owner. + assert_eq!(dividends_a.len(), 1); + assert_eq!(dividends_a[0].0, hotkey_a); + assert_eq!( + dividends_a[0].1, + hardcoded_emission.saturating_to_num::() + ); + assert_abs_diff_eq!( + dividends_a + .iter() + .map(|(_, emission)| *emission) + .sum::(), + hardcoded_emission.saturating_to_num::(), + epsilon = (hardcoded_emission / 1000).saturating_to_num::() + ); + + // Expect only 2 dividends. Parent key A and child key B. + assert_eq!(dividends_b.len(), 2); // A and B + assert_eq!(dividends_b[0].0, hotkey_a); + assert_eq!(dividends_b[1].0, hotkey_b); + + // We expect B's coldkey to have no increase in dividends from A, as they have the same owner. + // And therefore, B should get no CHK_TAKE. + + // A should also have no decrease because there is no CHK_TAKE. + let total_stake_b = rel_stake_b + rel_stake_a * 1 / 2; + let expected_b_b: u64 = + (rel_stake_b / total_stake_b * hardcoded_emission).saturating_to_num::(); + + assert_abs_diff_eq!( + dividends_b[1].1, + expected_b_b, + epsilon = (hardcoded_emission / 1000).saturating_to_num::(), + ); + + let expected_b_a: u64 = + ((rel_stake_a * 1 / 2) / total_stake_b * hardcoded_emission).saturating_to_num::(); + assert_eq!(dividends_b[0].0, hotkey_a); + assert_abs_diff_eq!( + dividends_b[0].1, + expected_b_a, + epsilon = (hardcoded_emission / 1000).saturating_to_num::() + ); + assert_abs_diff_eq!( + dividends_b + .iter() + .map(|(_, emission)| *emission) + .sum::(), + hardcoded_emission.saturating_to_num::(), + epsilon = (hardcoded_emission / 1000).saturating_to_num::() + ); + }); +} + +#[test] +fn test_pending_cooldown_one_day() { + let curr_block = 1; + + let expected_cooldown = if cfg!(feature = "fast-blocks") { + 15 + } else { + 7_200 + }; + + new_test_ext(curr_block).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child1 = U256::from(3); + let child2 = U256::from(4); + let netuid: u16 = 1; + let proportion1: u64 = 1000; + let proportion2: u64 = 2000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set multiple children + mock_schedule_children( + &coldkey, + &hotkey, + netuid, + &[(proportion1, child1), (proportion2, child2)], + ); + + // Verify pending map + let pending_children = PendingChildKeys::::get(netuid, hotkey); + assert_eq!( + pending_children.0, + vec![(proportion1, child1), (proportion2, child2)] + ); + assert_eq!(pending_children.1, curr_block + expected_cooldown); + }); +} diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 94cb786e48..9f59fe338e 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -2,6 +2,7 @@ use super::mock::*; use crate::*; +use alloc::collections::BTreeMap; use approx::assert_abs_diff_eq; use frame_support::assert_ok; use sp_core::U256; @@ -190,11 +191,16 @@ fn test_coinbase_moving_prices() { SubnetAlphaIn::::insert(netuid, 1_000_000); SubnetMechanism::::insert(netuid, 1); SubnetMovingPrice::::insert(netuid, I96F32::from_num(1)); + NetworkRegisteredAt::::insert(netuid, 1); + // Updating the moving price keeps it the same. assert_eq!( SubtensorModule::get_moving_alpha_price(netuid), I96F32::from_num(1) ); + // Skip some blocks so that EMA price is not slowed down + System::set_block_number(7_200_000); + SubtensorModule::update_moving_price(netuid); assert_eq!( SubtensorModule::get_moving_alpha_price(netuid), @@ -206,29 +212,78 @@ fn test_coinbase_moving_prices() { SubnetMovingAlpha::::set(I96F32::from_num(1.0)); // Run moving 1 times. SubtensorModule::update_moving_price(netuid); - // Assert price is == 100% of the real price. - assert_eq!( - SubtensorModule::get_moving_alpha_price(netuid), - I96F32::from_num(1.0) - ); + // Assert price is ~ 100% of the real price. + assert!(I96F32::from_num(1.0) - SubtensorModule::get_moving_alpha_price(netuid) < 0.05); // Set price to zero. SubnetMovingPrice::::insert(netuid, I96F32::from_num(0)); SubnetMovingAlpha::::set(I96F32::from_num(0.1)); - // Run moving 6 times. - SubtensorModule::update_moving_price(netuid); - SubtensorModule::update_moving_price(netuid); - SubtensorModule::update_moving_price(netuid); - SubtensorModule::update_moving_price(netuid); - SubtensorModule::update_moving_price(netuid); - SubtensorModule::update_moving_price(netuid); + + // EMA price 28 days after registration + System::set_block_number(7_200 * 28); + + // Run moving 14 times. + for _ in 0..14 { + SubtensorModule::update_moving_price(netuid); + } + // Assert price is > 50% of the real price. - assert_eq!( - SubtensorModule::get_moving_alpha_price(netuid), - I96F32::from_num(0.468559) + assert!( + (I96F32::from_num(0.512325) - SubtensorModule::get_moving_alpha_price(netuid)).abs() + < 0.001 ); }); } +// Test moving price updates slow down at the beginning. +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_update_moving_price_initial --exact --show-output --nocapture +#[test] +fn test_update_moving_price_initial() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + add_network(netuid, 1, 0); + // Set current price to 1.0 + SubnetTAO::::insert(netuid, 1_000_000); + SubnetAlphaIn::::insert(netuid, 1_000_000); + SubnetMechanism::::insert(netuid, 1); + SubnetMovingAlpha::::set(I96F32::from_num(0.5)); + SubnetMovingPrice::::insert(netuid, I96F32::from_num(0)); + + // Registered recently + System::set_block_number(510); + NetworkRegisteredAt::::insert(netuid, 500); + + SubtensorModule::update_moving_price(netuid); + + let new_price = SubnetMovingPrice::::get(netuid); + assert!(new_price.to_num::() < 0.001); + }); +} + +// Test moving price updates slow down at the beginning. +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_update_moving_price_after_time --exact --show-output --nocapture +#[test] +fn test_update_moving_price_after_time() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + add_network(netuid, 1, 0); + // Set current price to 1.0 + SubnetTAO::::insert(netuid, 1_000_000); + SubnetAlphaIn::::insert(netuid, 1_000_000); + SubnetMechanism::::insert(netuid, 1); + SubnetMovingAlpha::::set(I96F32::from_num(0.5)); + SubnetMovingPrice::::insert(netuid, I96F32::from_num(0)); + + // Registered long time ago + System::set_block_number(144_000_500); + NetworkRegisteredAt::::insert(netuid, 500); + + SubtensorModule::update_moving_price(netuid); + + let new_price = SubnetMovingPrice::::get(netuid); + assert!((new_price.to_num::() - 0.5).abs() < 0.001); + }); +} + // Test basic alpha issuance in coinbase mechanism. // This test verifies that: // - Alpha issuance is initialized to 0 for new subnets @@ -934,32 +989,33 @@ fn test_get_root_children_drain() { // Set TAO weight to 1. SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1. // Create keys. - let cold = U256::from(0); - let alice = U256::from(1); - let bob = U256::from(2); + let cold_alice = U256::from(0); + let cold_bob = U256::from(1); + let alice = U256::from(2); + let bob = U256::from(3); // Register Alice and Bob to the root network and alpha subnet. - register_ok_neuron(alpha, alice, cold, 0); - register_ok_neuron(alpha, bob, cold, 0); + register_ok_neuron(alpha, alice, cold_alice, 0); + register_ok_neuron(alpha, bob, cold_bob, 0); assert_ok!(SubtensorModule::root_register( - RuntimeOrigin::signed(cold).clone(), + RuntimeOrigin::signed(cold_alice).clone(), alice, )); assert_ok!(SubtensorModule::root_register( - RuntimeOrigin::signed(cold).clone(), + RuntimeOrigin::signed(cold_bob).clone(), bob, )); // Add stake for Alice and Bob on root. let alice_root_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &alice, - &cold, + &cold_alice, root, alice_root_stake, ); let bob_root_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &bob, - &cold, + &cold_bob, root, alice_root_stake, ); @@ -967,14 +1023,14 @@ fn test_get_root_children_drain() { let alice_alpha_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &alice, - &cold, + &cold_alice, alpha, alice_alpha_stake, ); let bob_alpha_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &bob, - &cold, + &cold_bob, alpha, bob_alpha_stake, ); @@ -1056,32 +1112,33 @@ fn test_get_root_children_drain_half_proportion() { // Set TAO weight to 1. SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1. // Create keys. - let cold = U256::from(0); - let alice = U256::from(1); - let bob = U256::from(2); + let cold_alice = U256::from(0); + let cold_bob = U256::from(1); + let alice = U256::from(2); + let bob = U256::from(3); // Register Alice and Bob to the root network and alpha subnet. - register_ok_neuron(alpha, alice, cold, 0); - register_ok_neuron(alpha, bob, cold, 0); + register_ok_neuron(alpha, alice, cold_alice, 0); + register_ok_neuron(alpha, bob, cold_bob, 0); assert_ok!(SubtensorModule::root_register( - RuntimeOrigin::signed(cold).clone(), + RuntimeOrigin::signed(cold_alice).clone(), alice, )); assert_ok!(SubtensorModule::root_register( - RuntimeOrigin::signed(cold).clone(), + RuntimeOrigin::signed(cold_bob).clone(), bob, )); // Add stake for Alice and Bob on root. let alice_root_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &alice, - &cold, + &cold_alice, root, alice_root_stake, ); let bob_root_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &bob, - &cold, + &cold_bob, root, alice_root_stake, ); @@ -1089,14 +1146,14 @@ fn test_get_root_children_drain_half_proportion() { let alice_alpha_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &alice, - &cold, + &cold_alice, alpha, alice_alpha_stake, ); let bob_alpha_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &bob, - &cold, + &cold_bob, alpha, bob_alpha_stake, ); @@ -1138,32 +1195,33 @@ fn test_get_root_children_drain_with_take() { // Set TAO weight to 1. SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1. // Create keys. - let cold = U256::from(0); - let alice = U256::from(1); - let bob = U256::from(2); + let cold_alice = U256::from(0); + let cold_bob = U256::from(1); + let alice = U256::from(2); + let bob = U256::from(3); // Register Alice and Bob to the root network and alpha subnet. - register_ok_neuron(alpha, alice, cold, 0); - register_ok_neuron(alpha, bob, cold, 0); + register_ok_neuron(alpha, alice, cold_alice, 0); + register_ok_neuron(alpha, bob, cold_bob, 0); assert_ok!(SubtensorModule::root_register( - RuntimeOrigin::signed(cold).clone(), + RuntimeOrigin::signed(cold_alice).clone(), alice, )); assert_ok!(SubtensorModule::root_register( - RuntimeOrigin::signed(cold).clone(), + RuntimeOrigin::signed(cold_bob).clone(), bob, )); // Add stake for Alice and Bob on root. let alice_root_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &alice, - &cold, + &cold_alice, root, alice_root_stake, ); let bob_root_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &bob, - &cold, + &cold_bob, root, alice_root_stake, ); @@ -1171,14 +1229,14 @@ fn test_get_root_children_drain_with_take() { let alice_alpha_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &alice, - &cold, + &cold_alice, alpha, alice_alpha_stake, ); let bob_alpha_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &bob, - &cold, + &cold_bob, alpha, bob_alpha_stake, ); @@ -1215,32 +1273,33 @@ fn test_get_root_children_drain_with_half_take() { // Set TAO weight to 1. SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1. // Create keys. - let cold = U256::from(0); - let alice = U256::from(1); - let bob = U256::from(2); + let cold_alice = U256::from(0); + let cold_bob = U256::from(1); + let alice = U256::from(2); + let bob = U256::from(3); // Register Alice and Bob to the root network and alpha subnet. - register_ok_neuron(alpha, alice, cold, 0); - register_ok_neuron(alpha, bob, cold, 0); + register_ok_neuron(alpha, alice, cold_alice, 0); + register_ok_neuron(alpha, bob, cold_bob, 0); assert_ok!(SubtensorModule::root_register( - RuntimeOrigin::signed(cold).clone(), + RuntimeOrigin::signed(cold_alice).clone(), alice, )); assert_ok!(SubtensorModule::root_register( - RuntimeOrigin::signed(cold).clone(), + RuntimeOrigin::signed(cold_bob).clone(), bob, )); // Add stake for Alice and Bob on root. let alice_root_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &alice, - &cold, + &cold_alice, root, alice_root_stake, ); let bob_root_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &bob, - &cold, + &cold_bob, root, alice_root_stake, ); @@ -1248,14 +1307,14 @@ fn test_get_root_children_drain_with_half_take() { let alice_alpha_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &alice, - &cold, + &cold_alice, alpha, alice_alpha_stake, ); let bob_alpha_stake: u64 = 1_000_000_000; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &bob, - &cold, + &cold_bob, alpha, bob_alpha_stake, ); @@ -1381,3 +1440,51 @@ fn test_get_root_children_drain_with_half_take() { // ); // }); // } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_incentive_to_subnet_owner_is_burned --exact --show-output --nocapture +#[test] +fn test_incentive_to_subnet_owner_is_burned() { + new_test_ext(1).execute_with(|| { + let subnet_owner_ck = U256::from(0); + let subnet_owner_hk = U256::from(1); + + let other_ck = U256::from(2); + let other_hk = U256::from(3); + + let netuid = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); + + let pending_tao: u64 = 1_000_000_000; + let owner_cut: u64 = 0; + let mut incentives: BTreeMap = BTreeMap::new(); + let mut dividends: BTreeMap = BTreeMap::new(); + + // Give incentive to other_hk + incentives.insert(other_hk, 10_000_000); + + // Give incentives to subnet_owner_hk + incentives.insert(subnet_owner_hk, 10_000_000); + + // Verify stake before + let subnet_owner_stake_before = + SubtensorModule::get_stake_for_hotkey_on_subnet(&subnet_owner_hk, netuid); + assert_eq!(subnet_owner_stake_before, 0); + let other_stake_before = SubtensorModule::get_stake_for_hotkey_on_subnet(&other_hk, netuid); + assert_eq!(other_stake_before, 0); + + // Distribute dividends and incentives + SubtensorModule::distribute_dividends_and_incentives( + netuid, + pending_tao, + owner_cut, + incentives, + dividends, + ); + + // Verify stake after + let subnet_owner_stake_after = + SubtensorModule::get_stake_for_hotkey_on_subnet(&subnet_owner_hk, netuid); + assert_eq!(subnet_owner_stake_after, 0); + let other_stake_after = SubtensorModule::get_stake_for_hotkey_on_subnet(&other_hk, netuid); + assert!(other_stake_after > 0); + }); +} diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index 4c24769c27..38b104ac2b 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -582,11 +582,6 @@ fn test_1_graph() { )); // SubtensorModule::set_weights_for_testing( netuid, i as u16, vec![ ( 0, u16::MAX )]); // doesn't set update status // SubtensorModule::set_bonds_for_testing( netuid, uid, vec![ ( 0, u16::MAX )]); // rather, bonds are calculated in epoch - SubtensorModule::set_emission_values(&[netuid], vec![1_000_000_000]).unwrap(); - assert_eq!( - SubtensorModule::get_subnet_emission_value(netuid), - 1_000_000_000 - ); SubtensorModule::epoch(netuid, 1_000_000_000); assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey), @@ -597,10 +592,6 @@ fn test_1_graph() { assert_eq!(SubtensorModule::get_consensus_for_uid(netuid, uid), 0); assert_eq!(SubtensorModule::get_incentive_for_uid(netuid, uid), 0); assert_eq!(SubtensorModule::get_dividends_for_uid(netuid, uid), 0); - assert_eq!( - SubtensorModule::get_emission_for_uid(netuid, uid), - 1_000_000_000 - ); }); } @@ -2285,19 +2276,19 @@ fn test_compute_alpha_values() { // exp_val = exp(0.0 - 1.0 * 0.1) = exp(-0.1) // alpha[0] = 1 / (1 + exp(-0.1)) ~ 0.9048374180359595 let exp_val_0 = I32F32::from_num(0.9048374180359595); - let expected_alpha_0 = I32F32::from_num(1.0) / I32F32::from_num(1.0).saturating_add(exp_val_0); + let expected_alpha_0 = I32F32::from_num(1.0) / (I32F32::from_num(1.0) + exp_val_0); // For consensus[1] = 0.5: // exp_val = exp(0.0 - 1.0 * 0.5) = exp(-0.5) // alpha[1] = 1 / (1 + exp(-0.5)) ~ 0.6065306597126334 let exp_val_1 = I32F32::from_num(0.6065306597126334); - let expected_alpha_1 = I32F32::from_num(1.0) / I32F32::from_num(1.0).saturating_add(exp_val_1); + let expected_alpha_1 = I32F32::from_num(1.0) / (I32F32::from_num(1.0) + exp_val_1); // For consensus[2] = 0.9: // exp_val = exp(0.0 - 1.0 * 0.9) = exp(-0.9) // alpha[2] = 1 / (1 + exp(-0.9)) ~ 0.4065696597405991 let exp_val_2 = I32F32::from_num(0.4065696597405991); - let expected_alpha_2 = I32F32::from_num(1.0) / I32F32::from_num(1.0).saturating_add(exp_val_2); + let expected_alpha_2 = I32F32::from_num(1.0) / (I32F32::from_num(1.0) + exp_val_2); // Define an epsilon for approximate equality checks. let epsilon = I32F32::from_num(1e-6); @@ -2329,13 +2320,13 @@ fn test_compute_alpha_values_256_miners() { for (i, &c) in consensus.iter().enumerate() { // Use saturating subtraction and multiplication - let exponent = b.saturating_sub(a.saturating_mul(c)); + let exponent = b - (a * c); // Use safe_exp instead of exp let exp_val = safe_exp(exponent); // Use saturating addition and division - let expected_alpha = I32F32::from_num(1.0) / I32F32::from_num(1.0).saturating_add(exp_val); + let expected_alpha = I32F32::from_num(1.0) / (I32F32::from_num(1.0) + exp_val); // Assert that the computed alpha values match the expected values within the epsilon. assert_approx_eq(alpha[i], expected_alpha, epsilon); @@ -2782,6 +2773,61 @@ fn test_blocks_since_last_step() { }); } +#[test] +fn test_can_set_self_weight_as_subnet_owner() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey: U256 = U256::from(1); + let subnet_owner_hotkey: U256 = U256::from(1 + 456); + + let other_hotkey: U256 = U256::from(2); + + let stake = 5_000_000_000_000; // 5k TAO + let to_emit: u64 = 1_000_000_000; // 1 TAO + + // Create subnet + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + // Register the other hotkey + register_ok_neuron(netuid, other_hotkey, subnet_owner_coldkey, 0); + + // Add stake to owner hotkey. + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &subnet_owner_hotkey, + &subnet_owner_coldkey, + netuid, + stake, + ); + + // Give vpermits to owner hotkey ONLY + ValidatorPermit::::insert(netuid, vec![true, false]); + + // Set weight of 50% to each hotkey. + // This includes a self-weight + let fifty_percent: u16 = u16::MAX / 2; + Weights::::insert(netuid, 0, vec![(0, fifty_percent), (1, fifty_percent)]); + + step_block(1); + // Set updated so weights are valid + LastUpdate::::insert(netuid, vec![2, 0]); + + // Run epoch + let hotkey_emission: Vec<(U256, u64, u64)> = SubtensorModule::epoch(netuid, to_emit); + + // hotkey_emission is [(hotkey, incentive, dividend)] + assert_eq!(hotkey_emission.len(), 2); + assert_eq!(hotkey_emission[0].0, subnet_owner_hotkey); + assert_eq!(hotkey_emission[1].0, other_hotkey); + + log::debug!("hotkey_emission: {:?}", hotkey_emission); + // Both should have received incentive emission + assert!(hotkey_emission[0].1 > 0); + assert!(hotkey_emission[1].1 > 0); + + // Their incentive should be equal + assert_eq!(hotkey_emission[0].1, hotkey_emission[1].1); + }); +} + // Map the retention graph for consensus guarantees with an single epoch on a graph with 512 nodes, // of which the first 64 are validators, the graph is split into a major and minor set, each setting // specific weight on itself and the complement on the other. diff --git a/pallets/subtensor/src/tests/math.rs b/pallets/subtensor/src/tests/math.rs index 19bab75b4e..036e2015ab 100644 --- a/pallets/subtensor/src/tests/math.rs +++ b/pallets/subtensor/src/tests/math.rs @@ -58,7 +58,13 @@ fn assert_sparse_mat_compare( ) { assert!(ma.len() == mb.len()); for row in 0..ma.len() { - assert!(ma[row].len() == mb[row].len()); + assert!( + ma[row].len() == mb[row].len(), + "row: {}, ma: {:?}, mb: {:?}", + row, + ma[row], + mb[row] + ); for j in 0..ma[row].len() { assert!(ma[row][j].0 == mb[row][j].0); // u16 assert_float_compare(ma[row][j].1, mb[row][j].1, epsilon) // I32F32 @@ -1034,6 +1040,27 @@ fn test_math_inplace_mask_diag() { ); } +#[test] +fn test_math_inplace_mask_diag_except_index() { + let vector: Vec = vec![1., 2., 3., 4., 5., 6., 7., 8., 9.]; + let rows = 3; + + for i in 0..rows { + let mut target: Vec = vec![0., 2., 3., 4., 0., 6., 7., 8., 0.]; + let row = i * rows; + let col = i; + target[row + col] = vector[row + col]; + + let mut mat = vec_to_mat_fixed(&vector, rows, false); + inplace_mask_diag_except_index(&mut mat, i as u16); + assert_mat_compare( + &mat, + &vec_to_mat_fixed(&target, rows, false), + I32F32::from_num(0), + ); + } +} + #[test] fn test_math_mask_rows_sparse() { let input: Vec = vec![1., 2., 3., 4., 5., 6., 7., 8., 9.]; @@ -1105,6 +1132,58 @@ fn test_math_mask_diag_sparse() { ); } +#[test] +fn test_math_mask_diag_sparse_except_index() { + let rows = 3; + + let vector: Vec = vec![1., 2., 3., 4., 5., 6., 7., 8., 9.]; + let mat = vec_to_sparse_mat_fixed(&vector, rows, false); + + for i in 0..rows { + let mut target: Vec = vec![0., 2., 3., 4., 0., 6., 7., 8., 0.]; + let row = i * rows; + let col = i; + target[row + col] = vector[row + col]; + + let result = mask_diag_sparse_except_index(&mat, i as u16); + let target_as_mat = vec_to_sparse_mat_fixed(&target, rows, false); + + assert_sparse_mat_compare(&result, &target_as_mat, I32F32::from_num(0)); + } + + let vector: Vec = vec![1., 0., 0., 0., 5., 0., 0., 0., 9.]; + let mat = vec_to_sparse_mat_fixed(&vector, rows, false); + + for i in 0..rows { + let mut target: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let row = i * rows; + let col = i; + target[row + col] = vector[row + col]; + + let result = mask_diag_sparse_except_index(&mat, i as u16); + let target_as_mat = vec_to_sparse_mat_fixed(&target, rows, false); + assert_eq!(result.len(), target_as_mat.len()); + + assert_sparse_mat_compare(&result, &target_as_mat, I32F32::from_num(0)); + } + + for i in 0..rows { + let vector: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat = vec_to_sparse_mat_fixed(&vector, rows, false); + + let mut target: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let row = i * rows; + let col = i; + target[row + col] = vector[row + col]; + + let result = mask_diag_sparse_except_index(&mat, i as u16); + let target_as_mat = vec_to_sparse_mat_fixed(&target, rows, false); + assert_eq!(result.len(), target_as_mat.len()); + + assert_sparse_mat_compare(&result, &target_as_mat, I32F32::from_num(0)); + } +} + #[test] fn test_math_vec_mask_sparse_matrix() { let vector: Vec = vec![1., 2., 3., 4., 5., 6., 7., 8., 9.]; diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index aae6aa60ef..0d979a6126 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -175,16 +175,16 @@ parameter_types! { pub const InitialNetworkMinLockCost: u64 = 100_000_000_000; pub const InitialSubnetOwnerCut: u16 = 0; // 0%. 100% of rewards go to validators + miners. pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks. - pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets. pub const InitialNetworkRateLimit: u64 = 0; pub const InitialKeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn - pub const InitialNetworkMaxStake: u64 = u64::MAX; // Maximum possible value for u64 + // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. + pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks } // Configure collective pallet for council @@ -398,17 +398,16 @@ impl crate::Config for Test { type InitialNetworkMinLockCost = InitialNetworkMinLockCost; type InitialSubnetOwnerCut = InitialSubnetOwnerCut; type InitialNetworkLockReductionInterval = InitialNetworkLockReductionInterval; - type InitialSubnetLimit = InitialSubnetLimit; type InitialNetworkRateLimit = InitialNetworkRateLimit; type KeySwapCost = InitialKeySwapCost; type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; - type InitialNetworkMaxStake = InitialNetworkMaxStake; type Preimages = Preimage; type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; + type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; } pub struct OriginPrivilegeCmp; diff --git a/pallets/subtensor/src/tests/registration.rs b/pallets/subtensor/src/tests/registration.rs index 988f1de662..50d409561d 100644 --- a/pallets/subtensor/src/tests/registration.rs +++ b/pallets/subtensor/src/tests/registration.rs @@ -1571,11 +1571,6 @@ fn test_full_pass_through() { assert_eq!(SubtensorModule::get_tempo(netuid1), tempo1); assert_eq!(SubtensorModule::get_tempo(netuid2), tempo2); - // Check their emission value. - assert_eq!(SubtensorModule::get_emission_value(netuid0), 0); - assert_eq!(SubtensorModule::get_emission_value(netuid1), 0); - assert_eq!(SubtensorModule::get_emission_value(netuid2), 0); - // Set their max allowed uids. SubtensorModule::set_max_allowed_uids(netuid0, 2); SubtensorModule::set_max_allowed_uids(netuid1, 2); diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index 5d9db9f4e8..38c440fe71 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -2219,11 +2219,71 @@ fn test_remove_stake_fee_goes_to_subnet_tao() { }); } +// cargo test --package pallet-subtensor --lib -- tests::staking::test_remove_stake_fee_realistic_values --exact --show-output --nocapture #[test] -fn test_stake_below_min_validate() { - // Testing the signed extension validate function - // correctly filters the `add_stake` transaction. +fn test_remove_stake_fee_realistic_values() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let hotkey = U256::from(2); + let coldkey = U256::from(3); + let alpha_to_unstake = 111_180_000_000; + let alpha_divs = 2_816_190; + + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + + // Mock a realistic scenario: + // Subnet 1 has 3896 TAO and 128_011 Alpha in reserves, which + // makes its price ~0.03. + // A hotkey has 111 Alpha stake and is unstaking all Alpha. + // Alpha dividends of this hotkey are ~0.0028 + // This makes fee be equal ~0.0028 Alpha ~= 84000 rao + let tao_reserve: U96F32 = U96F32::from_num(3_896_056_559_708_u64); + let alpha_in: U96F32 = U96F32::from_num(128_011_331_299_964_u64); + SubnetTAO::::insert(netuid, tao_reserve.to_num::()); + SubnetAlphaIn::::insert(netuid, alpha_in.to_num::()); + AlphaDividendsPerSubnet::::insert(netuid, hotkey, alpha_divs); + let current_price = SubtensorModule::get_alpha_price(netuid).to_num::(); + + // Add stake first time to init TotalHotkeyAlpha + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + netuid, + alpha_to_unstake, + ); + + // Estimate fees + let expected_fee: f64 = current_price * alpha_divs as f64; + + // Remove stake to measure fee + let balance_before = SubtensorModule::get_coldkey_balance(&coldkey); + let expected_tao_no_fee = + SubtensorModule::sim_swap_alpha_for_tao(netuid, alpha_to_unstake).unwrap(); + + assert_ok!(SubtensorModule::remove_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + alpha_to_unstake + )); + + // Calculate expected fee + let balance_after = SubtensorModule::get_coldkey_balance(&coldkey); + let actual_fee = expected_tao_no_fee as f64 - (balance_after - balance_before) as f64; + log::info!("Actual fee: {:?}", actual_fee); + + assert_abs_diff_eq!( + actual_fee as u64, + expected_fee as u64, + epsilon = expected_fee as u64 / 1000 + ); + }); +} +#[test] +fn test_stake_below_min_validate() { new_test_ext(0).execute_with(|| { let subnet_owner_coldkey = U256::from(1001); let subnet_owner_hotkey = U256::from(1002); @@ -3885,3 +3945,65 @@ fn test_remove_99_9989_per_cent_stake_leaves_a_little() { assert_abs_diff_eq!(new_alpha, (alpha as f64 * 0.01) as u64, epsilon = 10); }); } + +#[test] +fn test_move_stake_limit_partial() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let stake_amount = 150_000_000_000; + let move_amount = 150_000_000_000; + + // add network + let origin_netuid: u16 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let destination_netuid: u16 = + add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + register_ok_neuron(origin_netuid, hotkey, coldkey, 192213123); + register_ok_neuron(destination_netuid, hotkey, coldkey, 192213123); + + // Give the neuron some stake to remove + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + origin_netuid, + stake_amount, + ); + + // Forse-set alpha in and tao reserve to make price equal 1.5 on both origin and destination, + // but there's much more liquidity on destination, so its price wouldn't go up when restaked + let tao_reserve: U96F32 = U96F32::from_num(150_000_000_000_u64); + let alpha_in: U96F32 = U96F32::from_num(100_000_000_000_u64); + SubnetTAO::::insert(origin_netuid, tao_reserve.to_num::()); + SubnetAlphaIn::::insert(origin_netuid, alpha_in.to_num::()); + SubnetTAO::::insert(destination_netuid, (tao_reserve * 100_000).to_num::()); + SubnetAlphaIn::::insert(destination_netuid, (alpha_in * 100_000).to_num::()); + let current_price: U96F32 = + U96F32::from_num(SubtensorModule::get_alpha_price(origin_netuid)); + assert_eq!(current_price, U96F32::from_num(1.5)); + + // The relative price between origin and destination subnets is 1. + // Setup limit relative price so that it doesn't drop by more than 1% from current price + let limit_price = 990_000_000; + + // Move stake with slippage safety - executes partially + assert_ok!(SubtensorModule::swap_stake_limit( + RuntimeOrigin::signed(coldkey), + hotkey, + origin_netuid, + destination_netuid, + move_amount, + limit_price, + true, + )); + + let new_alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + origin_netuid, + ); + + assert_abs_diff_eq!(new_alpha, 149_000_000_000, epsilon = 100_000_000,); + }); +} diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 8f7b024e4c..ad1fd68526 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -14,6 +14,7 @@ use frame_support::traits::schedule::DispatchTime; use frame_support::traits::schedule::v3::Named as ScheduleNamed; use sp_core::{Get, H256, U256}; use sp_runtime::DispatchError; +use substrate_fixed::types::I96F32; // // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_total_hotkey_coldkey_stakes_this_interval --exact --nocapture // #[test] @@ -537,7 +538,9 @@ fn test_swap_concurrent_modifications() { let netuid: u16 = 1; let initial_stake = 1_000_000_000_000; let additional_stake = 500_000_000_000; - let fee = DefaultStakingFee::::get(); + let initial_stake_alpha = + I96F32::from(initial_stake).saturating_mul(SubtensorModule::get_alpha_price(netuid)); + let fee = SubtensorModule::calculate_staking_fee(netuid, &hotkey, initial_stake_alpha); // Setup initial state add_network(netuid, 1, 1); @@ -588,7 +591,6 @@ fn test_swap_concurrent_modifications() { &mut weight )); - let eps = 500; // RAO assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, @@ -596,7 +598,7 @@ fn test_swap_concurrent_modifications() { netuid ), stake_before_swap + additional_stake - fee, - epsilon = eps + epsilon = (stake_before_swap + additional_stake - fee) / 1000 ); assert!(!Alpha::::contains_key((hotkey, old_coldkey, netuid))); }); diff --git a/pallets/subtensor/src/tests/swap_hotkey.rs b/pallets/subtensor/src/tests/swap_hotkey.rs index dab1675074..63413b0667 100644 --- a/pallets/subtensor/src/tests/swap_hotkey.rs +++ b/pallets/subtensor/src/tests/swap_hotkey.rs @@ -116,56 +116,6 @@ fn test_swap_total_hotkey_stake() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_last_tx_block --exact --nocapture -#[test] -fn test_swap_last_tx_block() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let mut weight = Weight::zero(); - - LastTxBlock::::insert(old_hotkey, 1000); - assert_ok!(SubtensorModule::perform_hotkey_swap( - &old_hotkey, - &new_hotkey, - &coldkey, - &mut weight - )); - - assert!(!LastTxBlock::::contains_key(old_hotkey)); - assert_eq!( - LastTxBlock::::get(new_hotkey), - SubtensorModule::get_current_block_as_u64() - ); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_last_tx_block_delegate_take --exact --nocapture -#[test] -fn test_swap_last_tx_block_delegate_take() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let mut weight = Weight::zero(); - - crate::LastTxBlockDelegateTake::::insert(old_hotkey, 1000); - assert_ok!(SubtensorModule::perform_hotkey_swap( - &old_hotkey, - &new_hotkey, - &coldkey, - &mut weight - )); - - assert!(!LastTxBlockDelegateTake::::contains_key(old_hotkey)); - assert_eq!( - LastTxBlockDelegateTake::::get(new_hotkey), - SubtensorModule::get_current_block_as_u64() - ); - }); -} - // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_senate_members --exact --nocapture #[test] fn test_swap_senate_members() { @@ -1387,3 +1337,39 @@ fn test_swap_hotkey_is_sn_owner_hotkey() { assert_eq!(SubnetOwnerHotkey::::get(netuid), new_hotkey); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_swap_rate_limits --exact --nocapture +#[test] +fn test_swap_hotkey_swap_rate_limits() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + let last_tx_block = 123; + let delegate_take_block = 4567; + let child_key_take_block = 8910; + + // Set the last tx block for the old hotkey + LastTxBlock::::insert(old_hotkey, last_tx_block); + // Set the last delegate take block for the old hotkey + LastTxBlockDelegateTake::::insert(old_hotkey, delegate_take_block); + // Set last childkey take block for the old hotkey + LastTxBlockChildKeyTake::::insert(old_hotkey, child_key_take_block); + + // Perform the swap + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + + // Check for new hotkey + assert_eq!(LastTxBlock::::get(new_hotkey), last_tx_block); + assert_eq!( + LastTxBlockDelegateTake::::get(new_hotkey), + delegate_take_block + ); + assert_eq!( + LastTxBlockChildKeyTake::::get(new_hotkey), + child_key_take_block + ); + }); +} diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index bd093a76b5..19d07248d2 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -1,7 +1,7 @@ use super::*; use crate::{ Error, - system::{ensure_root, ensure_signed_or_root, pallet_prelude::BlockNumberFor}, + system::{ensure_root, ensure_signed, ensure_signed_or_root, pallet_prelude::BlockNumberFor}, }; use safe_math::*; use sp_core::Get; @@ -23,6 +23,15 @@ impl Pallet { } } + pub fn ensure_subnet_owner(o: T::RuntimeOrigin, netuid: u16) -> Result<(), DispatchError> { + let coldkey = ensure_signed(o); + match coldkey { + Ok(who) if SubnetOwner::::get(netuid) == who => Ok(()), + Ok(_) => Err(DispatchError::BadOrigin), + Err(x) => Err(x.into()), + } + } + // ======================== // ==== Global Setters ==== // ======================== @@ -204,9 +213,6 @@ impl Pallet { pub fn get_tempo(netuid: u16) -> u16 { Tempo::::get(netuid) } - pub fn get_emission_value(netuid: u16) -> u64 { - EmissionValues::::get(netuid) - } pub fn get_pending_emission(netuid: u16) -> u64 { PendingEmission::::get(netuid) } @@ -682,38 +688,6 @@ impl Pallet { LiquidAlphaOn::::get(netuid) } - /// Retrieves the maximum stake allowed for a given network. - /// - /// # Arguments - /// - /// * `netuid` - The unique identifier of the network. - /// - /// # Returns - /// - /// * `u64` - The maximum stake allowed for the specified network. - pub fn get_network_max_stake(netuid: u16) -> u64 { - NetworkMaxStake::::get(netuid) - } - - /// Sets the maximum stake allowed for a given network. - /// - /// # Arguments - /// - /// * `netuid` - The unique identifier of the network. - /// * `max_stake` - The new maximum stake value to set. - /// - /// # Effects - /// - /// * Updates the NetworkMaxStake storage. - /// * Emits a NetworkMaxStakeSet event. - pub fn set_network_max_stake(netuid: u16, max_stake: u64) { - // Update the NetworkMaxStake storage with the new max_stake value - NetworkMaxStake::::insert(netuid, max_stake); - - // Emit an event to notify listeners about the change - Self::deposit_event(Event::NetworkMaxStakeSet(netuid, max_stake)); - } - /// Set the duration for coldkey swap /// /// # Arguments @@ -743,4 +717,28 @@ impl Pallet { DissolveNetworkScheduleDuration::::set(duration); Self::deposit_event(Event::DissolveNetworkScheduleDurationSet(duration)); } + + /// Set the owner hotkey for a subnet. + /// + /// # Arguments + /// + /// * `netuid` - The unique identifier for the subnet. + /// * `hotkey` - The new hotkey for the subnet owner. + /// + /// # Effects + /// + /// * Update the SubnetOwnerHotkey storage. + /// * Emits a SubnetOwnerHotkeySet event. + pub fn set_subnet_owner_hotkey(netuid: u16, hotkey: &T::AccountId) { + SubnetOwnerHotkey::::insert(netuid, hotkey.clone()); + Self::deposit_event(Event::SubnetOwnerHotkeySet(netuid, hotkey.clone())); + } + + // Get the uid of the Owner Hotkey for a subnet. + pub fn get_owner_uid(netuid: u16) -> Option { + match SubnetOwnerHotkey::::try_get(netuid) { + Ok(owner_hotkey) => Uids::::get(netuid, &owner_hotkey), + Err(_) => None, + } + } } diff --git a/pallets/subtensor/src/utils/rate_limiting.rs b/pallets/subtensor/src/utils/rate_limiting.rs index 8b30f9665b..c37a78d2e4 100644 --- a/pallets/subtensor/src/utils/rate_limiting.rs +++ b/pallets/subtensor/src/utils/rate_limiting.rs @@ -7,6 +7,7 @@ pub enum TransactionType { SetChildkeyTake, Unknown, RegisterNetwork, + SetWeightsVersionKey, } /// Implement conversion from TransactionType to u16 @@ -17,6 +18,7 @@ impl From for u16 { TransactionType::SetChildkeyTake => 1, TransactionType::Unknown => 2, TransactionType::RegisterNetwork => 3, + TransactionType::SetWeightsVersionKey => 4, } } } @@ -28,6 +30,7 @@ impl From for TransactionType { 0 => TransactionType::SetChildren, 1 => TransactionType::SetChildkeyTake, 3 => TransactionType::RegisterNetwork, + 4 => TransactionType::SetWeightsVersionKey, _ => TransactionType::Unknown, } } @@ -41,14 +44,18 @@ impl Pallet { match tx_type { TransactionType::SetChildren => 150, // 30 minutes TransactionType::SetChildkeyTake => TxChildkeyTakeRateLimit::::get(), - TransactionType::Unknown => 0, // Default to no limit for unknown types (no limit) TransactionType::RegisterNetwork => NetworkRateLimit::::get(), + + TransactionType::Unknown => 0, // Default to no limit for unknown types (no limit) + _ => 0, } } - pub fn get_rate_limit_on_subnet(tx_type: &TransactionType, _netuid: u16) -> u64 { + pub fn get_rate_limit_on_subnet(tx_type: &TransactionType, netuid: u16) -> u64 { #[allow(clippy::match_single_binding)] match tx_type { + TransactionType::SetWeightsVersionKey => (Tempo::::get(netuid) as u64) + .saturating_mul(WeightsVersionKeyRateLimit::::get()), _ => Self::get_rate_limit(tx_type), } } diff --git a/pallets/utility/Cargo.toml b/pallets/utility/Cargo.toml new file mode 100644 index 0000000000..6d217ebd4b --- /dev/null +++ b/pallets/utility/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "pallet-utility" +version = "38.0.0" +edition = "2021" +license = "Apache-2.0" +description = "FRAME utilities pallet" +readme = "README.md" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +frame-benchmarking = { workspace = true, default-features = false, optional = true } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-io = { workspace = true, default-features = false} +sp-runtime = { workspace = true, default-features = false} +subtensor-macros = { workspace = true } + +[dev-dependencies] +pallet-balances = { default-features = true, workspace = true } +pallet-collective = { default-features = false, path = "../collective" } +pallet-timestamp = { default-features = true, workspace = true } +sp-core = { default-features = true, workspace = true } +pallet-root-testing = { workspace = true, default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "pallet-collective/std", + "pallet-root-testing/std" +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-collective/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks" +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", + "pallet-balances/try-runtime", + "pallet-collective/try-runtime", + "pallet-root-testing/try-runtime", + "pallet-timestamp/try-runtime" +] diff --git a/pallets/utility/README.md b/pallets/utility/README.md new file mode 100644 index 0000000000..5366951a89 --- /dev/null +++ b/pallets/utility/README.md @@ -0,0 +1,43 @@ +# Utility Module +A stateless module with helpers for dispatch management which does no re-authentication. + +- [`utility::Config`](https://docs.rs/pallet-utility/latest/pallet_utility/pallet/trait.Config.html) +- [`Call`](https://docs.rs/pallet-utility/latest/pallet_utility/pallet/enum.Call.html) + +## Overview + +This module contains two basic pieces of functionality: +- Batch dispatch: A stateless operation, allowing any origin to execute multiple calls in a + single dispatch. This can be useful to amalgamate proposals, combining `set_code` with + corresponding `set_storage`s, for efficient multiple payouts with just a single signature + verify, or in combination with one of the other two dispatch functionality. +- Pseudonymal dispatch: A stateless operation, allowing a signed origin to execute a call from + an alternative signed origin. Each account has 2 * 2**16 possible "pseudonyms" (alternative + account IDs) and these can be stacked. This can be useful as a key management tool, where you + need multiple distinct accounts (e.g. as controllers for many staking accounts), but where + it's perfectly fine to have each of them controlled by the same underlying keypair. + Derivative accounts are, for the purposes of proxy filtering considered exactly the same as + the origin and are thus hampered with the origin's filters. + +Since proxy filters are respected in all dispatches of this module, it should never need to be +filtered by any proxy. + +## Interface + +### Dispatchable Functions + +#### For batch dispatch +- `batch` - Dispatch multiple calls from the sender's origin. + +#### For pseudonymal dispatch +- `as_derivative` - Dispatch a call from a derivative signed origin. + +[`Call`]: ./enum.Call.html +[`Config`]: ./trait.Config.html + +License: Apache-2.0 + + +## Release + +Polkadot SDK stable2409 diff --git a/pallets/utility/src/benchmarking.rs b/pallets/utility/src/benchmarking.rs new file mode 100644 index 0000000000..6980552c36 --- /dev/null +++ b/pallets/utility/src/benchmarking.rs @@ -0,0 +1,91 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +// Benchmarks for Utility Pallet + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use alloc::{vec, vec::Vec}; +use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller}; +use frame_system::RawOrigin; + +const SEED: u32 = 0; + +fn assert_last_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +benchmarks! { + where_clause { where ::PalletsOrigin: Clone } + batch { + let c in 0 .. 1000; + let mut calls: Vec<::RuntimeCall> = Vec::new(); + for i in 0 .. c { + let call = frame_system::Call::remark { remark: vec![] }.into(); + calls.push(call); + } + let caller = whitelisted_caller(); + }: _(RawOrigin::Signed(caller), calls) + verify { + assert_last_event::(Event::BatchCompleted.into()) + } + + as_derivative { + let caller = account("caller", SEED, SEED); + let call = Box::new(frame_system::Call::remark { remark: vec![] }.into()); + // Whitelist caller account from further DB operations. + let caller_key = frame_system::Account::::hashed_key_for(&caller); + frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); + }: _(RawOrigin::Signed(caller), SEED as u16, call) + + batch_all { + let c in 0 .. 1000; + let mut calls: Vec<::RuntimeCall> = Vec::new(); + for i in 0 .. c { + let call = frame_system::Call::remark { remark: vec![] }.into(); + calls.push(call); + } + let caller = whitelisted_caller(); + }: _(RawOrigin::Signed(caller), calls) + verify { + assert_last_event::(Event::BatchCompleted.into()) + } + + dispatch_as { + let caller = account("caller", SEED, SEED); + let call = Box::new(frame_system::Call::remark { remark: vec![] }.into()); + let origin: T::RuntimeOrigin = RawOrigin::Signed(caller).into(); + let pallets_origin: ::PalletsOrigin = origin.caller().clone(); + let pallets_origin = Into::::into(pallets_origin); + }: _(RawOrigin::Root, Box::new(pallets_origin), call) + + force_batch { + let c in 0 .. 1000; + let mut calls: Vec<::RuntimeCall> = Vec::new(); + for i in 0 .. c { + let call = frame_system::Call::remark { remark: vec![] }.into(); + calls.push(call); + } + let caller = whitelisted_caller(); + }: _(RawOrigin::Signed(caller), calls) + verify { + assert_last_event::(Event::BatchCompleted.into()) + } + + impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); +} diff --git a/pallets/utility/src/lib.rs b/pallets/utility/src/lib.rs new file mode 100644 index 0000000000..2677f744b6 --- /dev/null +++ b/pallets/utility/src/lib.rs @@ -0,0 +1,521 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! # Utility Pallet +//! A stateless pallet with helpers for dispatch management which does no re-authentication. +//! +//! - [`Config`] +//! - [`Call`] +//! +//! ## Overview +//! +//! This pallet contains two basic pieces of functionality: +//! - Batch dispatch: A stateless operation, allowing any origin to execute multiple calls in a +//! single dispatch. This can be useful to amalgamate proposals, combining `set_code` with +//! corresponding `set_storage`s, for efficient multiple payouts with just a single signature +//! verify, or in combination with one of the other two dispatch functionality. +//! - Pseudonymal dispatch: A stateless operation, allowing a signed origin to execute a call from +//! an alternative signed origin. Each account has 2 * 2**16 possible "pseudonyms" (alternative +//! account IDs) and these can be stacked. This can be useful as a key management tool, where you +//! need multiple distinct accounts (e.g. as controllers for many staking accounts), but where +//! it's perfectly fine to have each of them controlled by the same underlying keypair. Derivative +//! accounts are, for the purposes of proxy filtering considered exactly the same as the origin +//! and are thus hampered with the origin's filters. +//! +//! Since proxy filters are respected in all dispatches of this pallet, it should never need to be +//! filtered by any proxy. +//! +//! ## Interface +//! +//! ### Dispatchable Functions +//! +//! #### For batch dispatch +//! * `batch` - Dispatch multiple calls from the sender's origin. +//! +//! #### For pseudonymal dispatch +//! * `as_derivative` - Dispatch a call from a derivative signed origin. + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +mod benchmarking; +mod tests; +pub mod weights; + +extern crate alloc; + +use alloc::{boxed::Box, vec::Vec}; +use codec::{Decode, Encode}; +use frame_support::{ + dispatch::{extract_actual_weight, GetDispatchInfo, PostDispatchInfo}, + traits::{IsSubType, OriginTrait, UnfilteredDispatchable}, +}; +use sp_core::TypeId; +use sp_io::hashing::blake2_256; +use sp_runtime::traits::{BadOrigin, Dispatchable, TrailingZeroInput}; +pub use weights::WeightInfo; + +use subtensor_macros::freeze_struct; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{dispatch::DispatchClass, pallet_prelude::*}; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + /// Configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From + IsType<::RuntimeEvent>; + + /// The overarching call type. + type RuntimeCall: Parameter + + Dispatchable + + GetDispatchInfo + + From> + + UnfilteredDispatchable + + IsSubType> + + IsType<::RuntimeCall>; + + /// The caller origin, overarching type of all pallets origins. + type PalletsOrigin: Parameter + + Into<::RuntimeOrigin> + + IsType<<::RuntimeOrigin as frame_support::traits::OriginTrait>::PalletsOrigin>; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Batch of dispatches did not complete fully. Index of first failing dispatch given, as + /// well as the error. + BatchInterrupted { index: u32, error: DispatchError }, + /// Batch of dispatches completed fully with no error. + BatchCompleted, + /// Batch of dispatches completed but has errors. + BatchCompletedWithErrors, + /// A single item within a Batch of dispatches has completed with no error. + ItemCompleted, + /// A single item within a Batch of dispatches has completed with error. + ItemFailed { error: DispatchError }, + /// A call was dispatched. + DispatchedAs { result: DispatchResult }, + } + + // Align the call size to 1KB. As we are currently compiling the runtime for native/wasm + // the `size_of` of the `Call` can be different. To ensure that this don't leads to + // mismatches between native/wasm or to different metadata for the same runtime, we + // algin the call size. The value is chosen big enough to hopefully never reach it. + const CALL_ALIGN: u32 = 1024; + + #[pallet::extra_constants] + impl Pallet { + /// The limit on the number of batched calls. + fn batched_calls_limit() -> u32 { + let allocator_limit = sp_core::MAX_POSSIBLE_ALLOCATION; + let size = core::mem::size_of::<::RuntimeCall>() as u32; + + let align_up = size.saturating_add(CALL_ALIGN.saturating_sub(1)); + let call_size = align_up + .checked_div(CALL_ALIGN) + .unwrap_or(0) + .saturating_mul(CALL_ALIGN); + + let margin_factor: u32 = 3; + + let after_margin = allocator_limit.checked_div(margin_factor).unwrap_or(0); + + after_margin.checked_div(call_size).unwrap_or(0) + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + // If you hit this error, you need to try to `Box` big dispatchable parameters. + assert!( + core::mem::size_of::<::RuntimeCall>() as u32 <= CALL_ALIGN, + "Call enum size should be smaller than {} bytes.", + CALL_ALIGN, + ); + } + } + + #[pallet::error] + pub enum Error { + /// Too many calls batched. + TooManyCalls, + } + + #[pallet::call] + impl Pallet { + /// Send a batch of dispatch calls. + /// + /// May be called from any origin except `None`. + /// + /// - `calls`: The calls to be dispatched from the same origin. The number of call must not + /// exceed the constant: `batched_calls_limit` (available in constant metadata). + /// + /// If origin is root then the calls are dispatched without checking origin filter. (This + /// includes bypassing `frame_system::Config::BaseCallFilter`). + /// + /// ## Complexity + /// - O(C) where C is the number of calls to be batched. + /// + /// This will return `Ok` in all circumstances. To determine the success of the batch, an + /// event is deposited. If a call failed and the batch was interrupted, then the + /// `BatchInterrupted` event is deposited, along with the number of successful calls made + /// and the error of the failed call. If all were successful, then the `BatchCompleted` + /// event is deposited. + #[pallet::call_index(0)] + #[pallet::weight({ + let (dispatch_weight, dispatch_class) = Pallet::::weight_and_dispatch_class(calls); + let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch(calls.len() as u32)); + (dispatch_weight, dispatch_class) + })] + pub fn batch( + origin: OriginFor, + calls: Vec<::RuntimeCall>, + ) -> DispatchResultWithPostInfo { + // Do not allow the `None` origin. + if ensure_none(origin.clone()).is_ok() { + return Err(BadOrigin.into()); + } + + let is_root = ensure_root(origin.clone()).is_ok(); + let calls_len = calls.len(); + ensure!( + calls_len <= Self::batched_calls_limit() as usize, + Error::::TooManyCalls + ); + + // Track the actual weight of each of the batch calls. + let mut weight = Weight::zero(); + for (index, call) in calls.into_iter().enumerate() { + let info = call.get_dispatch_info(); + // If origin is root, don't apply any dispatch filters; root can call anything. + let result = if is_root { + call.dispatch_bypass_filter(origin.clone()) + } else { + call.dispatch(origin.clone()) + }; + // Add the weight of this call. + weight = weight.saturating_add(extract_actual_weight(&result, &info)); + if let Err(e) = result { + Self::deposit_event(Event::BatchInterrupted { + index: index as u32, + error: e.error, + }); + // Take the weight of this function itself into account. + let base_weight = T::WeightInfo::batch(index.saturating_add(1) as u32); + // Return the actual used weight + base_weight of this call. + return Ok(Some(base_weight.saturating_add(weight)).into()); + } + Self::deposit_event(Event::ItemCompleted); + } + Self::deposit_event(Event::BatchCompleted); + let base_weight = T::WeightInfo::batch(calls_len as u32); + Ok(Some(base_weight.saturating_add(weight)).into()) + } + + /// Send a call through an indexed pseudonym of the sender. + /// + /// Filter from origin are passed along. The call will be dispatched with an origin which + /// use the same filter as the origin of this call. + /// + /// NOTE: If you need to ensure that any account-based filtering is not honored (i.e. + /// because you expect `proxy` to have been used prior in the call stack and you do not want + /// the call restrictions to apply to any sub-accounts), then use `as_multi_threshold_1` + /// in the Multisig pallet instead. + /// + /// NOTE: Prior to version *12, this was called `as_limited_sub`. + /// + /// The dispatch origin for this call must be _Signed_. + #[pallet::call_index(1)] + #[pallet::weight({ + let dispatch_info = call.get_dispatch_info(); + ( + T::WeightInfo::as_derivative() + // AccountData for inner call origin accountdata. + .saturating_add(T::DbWeight::get().reads_writes(1, 1)) + .saturating_add(dispatch_info.weight), + dispatch_info.class, + ) + })] + pub fn as_derivative( + origin: OriginFor, + index: u16, + call: Box<::RuntimeCall>, + ) -> DispatchResultWithPostInfo { + let mut origin = origin; + let who = ensure_signed(origin.clone())?; + let pseudonym = Self::derivative_account_id(who, index); + origin.set_caller_from(frame_system::RawOrigin::Signed(pseudonym)); + let info = call.get_dispatch_info(); + let result = call.dispatch(origin); + // Always take into account the base weight of this call. + let mut weight = T::WeightInfo::as_derivative() + .saturating_add(T::DbWeight::get().reads_writes(1, 1)); + // Add the real weight of the dispatch. + weight = weight.saturating_add(extract_actual_weight(&result, &info)); + result + .map_err(|mut err| { + err.post_info = Some(weight).into(); + err + }) + .map(|_| Some(weight).into()) + } + + /// Send a batch of dispatch calls and atomically execute them. + /// The whole transaction will rollback and fail if any of the calls failed. + /// + /// May be called from any origin except `None`. + /// + /// - `calls`: The calls to be dispatched from the same origin. The number of call must not + /// exceed the constant: `batched_calls_limit` (available in constant metadata). + /// + /// If origin is root then the calls are dispatched without checking origin filter. (This + /// includes bypassing `frame_system::Config::BaseCallFilter`). + /// + /// ## Complexity + /// - O(C) where C is the number of calls to be batched. + #[pallet::call_index(2)] + #[pallet::weight({ + let (dispatch_weight, dispatch_class) = Pallet::::weight_and_dispatch_class(calls); + let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch_all(calls.len() as u32)); + (dispatch_weight, dispatch_class) + })] + pub fn batch_all( + origin: OriginFor, + calls: Vec<::RuntimeCall>, + ) -> DispatchResultWithPostInfo { + // Do not allow the `None` origin. + if ensure_none(origin.clone()).is_ok() { + return Err(BadOrigin.into()); + } + + let is_root = ensure_root(origin.clone()).is_ok(); + let calls_len = calls.len(); + ensure!( + calls_len <= Self::batched_calls_limit() as usize, + Error::::TooManyCalls + ); + + // Track the actual weight of each of the batch calls. + let mut weight = Weight::zero(); + for (index, call) in calls.into_iter().enumerate() { + let info = call.get_dispatch_info(); + // If origin is root, bypass any dispatch filter; root can call anything. + let result = if is_root { + call.dispatch_bypass_filter(origin.clone()) + } else { + let mut filtered_origin = origin.clone(); + // Don't allow users to nest `batch_all` calls. + filtered_origin.add_filter( + move |c: &::RuntimeCall| { + let c = ::RuntimeCall::from_ref(c); + !matches!(c.is_sub_type(), Some(Call::batch_all { .. })) + }, + ); + call.dispatch(filtered_origin) + }; + // Add the weight of this call. + weight = weight.saturating_add(extract_actual_weight(&result, &info)); + result.map_err(|mut err| { + // Take the weight of this function itself into account. + let base_weight = T::WeightInfo::batch_all(index.saturating_add(1) as u32); + // Return the actual used weight + base_weight of this call. + err.post_info = Some(base_weight.saturating_add(weight)).into(); + err + })?; + Self::deposit_event(Event::ItemCompleted); + } + Self::deposit_event(Event::BatchCompleted); + let base_weight = T::WeightInfo::batch_all(calls_len as u32); + Ok(Some(base_weight.saturating_add(weight)).into()) + } + + /// Dispatches a function call with a provided origin. + /// + /// The dispatch origin for this call must be _Root_. + /// + /// ## Complexity + /// - O(1). + #[pallet::call_index(3)] + #[pallet::weight({ + let dispatch_info = call.get_dispatch_info(); + ( + T::WeightInfo::dispatch_as() + .saturating_add(dispatch_info.weight), + dispatch_info.class, + ) + })] + pub fn dispatch_as( + origin: OriginFor, + as_origin: Box, + call: Box<::RuntimeCall>, + ) -> DispatchResult { + ensure_root(origin)?; + + let res = call.dispatch_bypass_filter((*as_origin).into()); + + Self::deposit_event(Event::DispatchedAs { + result: res.map(|_| ()).map_err(|e| e.error), + }); + Ok(()) + } + + /// Send a batch of dispatch calls. + /// Unlike `batch`, it allows errors and won't interrupt. + /// + /// May be called from any origin except `None`. + /// + /// - `calls`: The calls to be dispatched from the same origin. The number of call must not + /// exceed the constant: `batched_calls_limit` (available in constant metadata). + /// + /// If origin is root then the calls are dispatch without checking origin filter. (This + /// includes bypassing `frame_system::Config::BaseCallFilter`). + /// + /// ## Complexity + /// - O(C) where C is the number of calls to be batched. + #[pallet::call_index(4)] + #[pallet::weight({ + let (dispatch_weight, dispatch_class) = Pallet::::weight_and_dispatch_class(calls); + let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::force_batch(calls.len() as u32)); + (dispatch_weight, dispatch_class) + })] + pub fn force_batch( + origin: OriginFor, + calls: Vec<::RuntimeCall>, + ) -> DispatchResultWithPostInfo { + // Do not allow the `None` origin. + if ensure_none(origin.clone()).is_ok() { + return Err(BadOrigin.into()); + } + + let is_root = ensure_root(origin.clone()).is_ok(); + let calls_len = calls.len(); + ensure!( + calls_len <= Self::batched_calls_limit() as usize, + Error::::TooManyCalls + ); + + // Track the actual weight of each of the batch calls. + let mut weight = Weight::zero(); + // Track failed dispatch occur. + let mut has_error: bool = false; + for call in calls.into_iter() { + let info = call.get_dispatch_info(); + // If origin is root, don't apply any dispatch filters; root can call anything. + let result = if is_root { + call.dispatch_bypass_filter(origin.clone()) + } else { + call.dispatch(origin.clone()) + }; + // Add the weight of this call. + weight = weight.saturating_add(extract_actual_weight(&result, &info)); + if let Err(e) = result { + has_error = true; + Self::deposit_event(Event::ItemFailed { error: e.error }); + } else { + Self::deposit_event(Event::ItemCompleted); + } + } + if has_error { + Self::deposit_event(Event::BatchCompletedWithErrors); + } else { + Self::deposit_event(Event::BatchCompleted); + } + let base_weight = T::WeightInfo::batch(calls_len as u32); + Ok(Some(base_weight.saturating_add(weight)).into()) + } + + /// Dispatch a function call with a specified weight. + /// + /// This function does not check the weight of the call, and instead allows the + /// Root origin to specify the weight of the call. + /// + /// The dispatch origin for this call must be _Root_. + #[pallet::call_index(5)] + #[pallet::weight((*weight, call.get_dispatch_info().class))] + pub fn with_weight( + origin: OriginFor, + call: Box<::RuntimeCall>, + weight: Weight, + ) -> DispatchResult { + ensure_root(origin)?; + let _ = weight; // Explicitly don't check the the weight witness. + + let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into()); + res.map(|_| ()).map_err(|e| e.error) + } + } + + impl Pallet { + /// Get the accumulated `weight` and the dispatch class for the given `calls`. + fn weight_and_dispatch_class( + calls: &[::RuntimeCall], + ) -> (Weight, DispatchClass) { + let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()); + let (dispatch_weight, dispatch_class) = dispatch_infos.fold( + (Weight::zero(), DispatchClass::Operational), + |(total_weight, dispatch_class), di| { + ( + if di.pays_fee == Pays::Yes { + total_weight.saturating_add(di.weight) + } else { + total_weight + }, + if di.class == DispatchClass::Normal { + di.class + } else { + dispatch_class + }, + ) + }, + ); + + (dispatch_weight, dispatch_class) + } + } +} + +/// A pallet identifier. These are per pallet and should be stored in a registry somewhere. +#[freeze_struct("7e600c53ace0630a")] +#[derive(Clone, Copy, Eq, PartialEq, Encode, Decode)] +struct IndexedUtilityPalletId(u16); + +impl TypeId for IndexedUtilityPalletId { + const TYPE_ID: [u8; 4] = *b"suba"; +} + +impl Pallet { + /// Derive a derivative account ID from the owner account and the sub-account index. + pub fn derivative_account_id(who: T::AccountId, index: u16) -> T::AccountId { + let entropy = (b"modlpy/utilisuba", who, index).using_encoded(blake2_256); + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") + } +} diff --git a/pallets/utility/src/tests.rs b/pallets/utility/src/tests.rs new file mode 100644 index 0000000000..16ae0a3a51 --- /dev/null +++ b/pallets/utility/src/tests.rs @@ -0,0 +1,1001 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +// Tests for Utility Pallet + +#![cfg(test)] + +use super::*; + +use crate as utility; +use frame_support::{ + assert_err_ignore_postinfo, assert_noop, assert_ok, derive_impl, + dispatch::{DispatchErrorWithPostInfo, Pays}, + parameter_types, storage, + traits::{ConstU64, Contains}, + weights::Weight, +}; +use pallet_collective::{EnsureProportionAtLeast, Instance1}; +use sp_runtime::{ + traits::{BadOrigin, BlakeTwo256, Dispatchable, Hash}, + BuildStorage, DispatchError, TokenError, +}; + +type BlockNumber = u64; + +// example module to test behaviors. +#[frame_support::pallet(dev_mode)] +#[allow(clippy::large_enum_variant)] +pub mod example { + use frame_support::{dispatch::WithPostDispatchInfo, pallet_prelude::*}; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(*_weight)] + pub fn noop(_origin: OriginFor, _weight: Weight) -> DispatchResult { + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(*_start_weight)] + pub fn foobar( + origin: OriginFor, + err: bool, + _start_weight: Weight, + end_weight: Option, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + if err { + let error: DispatchError = "The cake is a lie.".into(); + if let Some(weight) = end_weight { + Err(error.with_weight(weight)) + } else { + Err(error)? + } + } else { + Ok(end_weight.into()) + } + } + + #[pallet::call_index(2)] + #[pallet::weight(0)] + pub fn big_variant(_origin: OriginFor, _arg: [u8; 400]) -> DispatchResult { + Ok(()) + } + } +} + +mod mock_democracy { + pub use pallet::*; + #[frame_support::pallet(dev_mode)] + pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + Sized { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + type ExternalMajorityOrigin: EnsureOrigin; + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(3)] + #[pallet::weight(0)] + pub fn external_propose_majority(origin: OriginFor) -> DispatchResult { + T::ExternalMajorityOrigin::ensure_origin(origin)?; + Self::deposit_event(Event::::ExternalProposed); + Ok(()) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + ExternalProposed, + } + } +} + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system = 1, + Timestamp: pallet_timestamp = 2, + Balances: pallet_balances = 3, + RootTesting: pallet_root_testing = 4, + Council: pallet_collective:: = 5, + Utility: utility = 6, + Example: example = 7, + Democracy: mock_democracy = 8, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::MAX); +} +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = TestBaseCallFilter; + type BlockWeights = BlockWeights; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +impl pallet_root_testing::Config for Test { + type RuntimeEvent = RuntimeEvent; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<3>; + type WeightInfo = (); +} + +const MOTION_DURATION_IN_BLOCKS: BlockNumber = 3; +parameter_types! { + pub const MultisigDepositBase: u64 = 1; + pub const MultisigDepositFactor: u64 = 1; + pub const MaxSignatories: u32 = 3; + pub const MotionDuration: BlockNumber = MOTION_DURATION_IN_BLOCKS; + pub const MaxProposals: u32 = 100; + pub const MaxMembers: u32 = 100; + pub MaxProposalWeight: Weight = BlockWeights::get().max_block.saturating_div(2); +} + +pub struct MemberProposals; +impl pallet_collective::CanPropose for MemberProposals { + fn can_propose(who: &u64) -> bool { + [1, 2, 3].contains(who) + } +} + +pub struct MemberVotes; +impl pallet_collective::CanVote for MemberVotes { + fn can_vote(who: &u64) -> bool { + [1, 2, 3].contains(who) + } +} + +pub struct StoredVotingMembers; +impl pallet_collective::GetVotingMembers for StoredVotingMembers { + fn get_count() -> u32 { + 3 + } +} + +type CouncilCollective = pallet_collective::Instance1; +impl pallet_collective::Config for Test { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = MotionDuration; + type MaxProposals = MaxProposals; + type MaxMembers = MaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type WeightInfo = (); + type SetMembersOrigin = frame_system::EnsureRoot; + type CanPropose = MemberProposals; + type CanVote = MemberVotes; + type GetVotingMembers = StoredVotingMembers; +} + +impl example::Config for Test {} + +pub struct TestBaseCallFilter; +impl Contains for TestBaseCallFilter { + fn contains(c: &RuntimeCall) -> bool { + match *c { + // Transfer works. Use `transfer_keep_alive` for a call that doesn't pass the filter. + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) => true, + RuntimeCall::Utility(_) => true, + // For benchmarking, this acts as a noop call + RuntimeCall::System(frame_system::Call::remark { .. }) => true, + // For tests + RuntimeCall::Example(_) => true, + // For council origin tests. + RuntimeCall::Democracy(_) => true, + _ => false, + } + } +} +impl mock_democracy::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ExternalMajorityOrigin = EnsureProportionAtLeast; +} +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = (); +} + +type ExampleCall = example::Call; +type UtilityCall = crate::Call; + +use frame_system::Call as SystemCall; +use pallet_balances::Call as BalancesCall; +use pallet_root_testing::Call as RootTestingCall; +use pallet_timestamp::Call as TimestampCall; + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Failed to build storage for test"); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)], + } + .assimilate_storage(&mut t) + .expect("Failed to build storage for test"); + + pallet_collective::GenesisConfig:: { + members: vec![1, 2, 3], + phantom: Default::default(), + } + .assimilate_storage(&mut t) + .expect("Failed to build storage for test"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +fn call_transfer(dest: u64, value: u64) -> RuntimeCall { + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value }) +} + +fn call_foobar(err: bool, start_weight: Weight, end_weight: Option) -> RuntimeCall { + RuntimeCall::Example(ExampleCall::foobar { + err, + start_weight, + end_weight, + }) +} + +#[test] +fn as_derivative_works() { + new_test_ext().execute_with(|| { + let sub_1_0 = Utility::derivative_account_id(1, 0); + assert_ok!(Balances::transfer_allow_death( + RuntimeOrigin::signed(1), + sub_1_0, + 5 + )); + assert_err_ignore_postinfo!( + Utility::as_derivative(RuntimeOrigin::signed(1), 1, Box::new(call_transfer(6, 3)),), + TokenError::FundsUnavailable, + ); + assert_ok!(Utility::as_derivative( + RuntimeOrigin::signed(1), + 0, + Box::new(call_transfer(2, 3)), + )); + assert_eq!(Balances::free_balance(sub_1_0), 2); + assert_eq!(Balances::free_balance(2), 13); + }); +} + +#[test] +fn as_derivative_handles_weight_refund() { + new_test_ext().execute_with(|| { + let start_weight = Weight::from_parts(100, 0); + let end_weight = Weight::from_parts(75, 0); + let diff = start_weight - end_weight; + + // Full weight when ok + let inner_call = call_foobar(false, start_weight, None); + let call = RuntimeCall::Utility(UtilityCall::as_derivative { + index: 0, + call: Box::new(inner_call), + }); + let info = call.get_dispatch_info(); + let result = call.dispatch(RuntimeOrigin::signed(1)); + assert_ok!(result); + assert_eq!(extract_actual_weight(&result, &info), info.weight); + + // Refund weight when ok + let inner_call = call_foobar(false, start_weight, Some(end_weight)); + let call = RuntimeCall::Utility(UtilityCall::as_derivative { + index: 0, + call: Box::new(inner_call), + }); + let info = call.get_dispatch_info(); + let result = call.dispatch(RuntimeOrigin::signed(1)); + assert_ok!(result); + // Diff is refunded + assert_eq!(extract_actual_weight(&result, &info), info.weight - diff); + + // Full weight when err + let inner_call = call_foobar(true, start_weight, None); + let call = RuntimeCall::Utility(UtilityCall::as_derivative { + index: 0, + call: Box::new(inner_call), + }); + let info = call.get_dispatch_info(); + let result = call.dispatch(RuntimeOrigin::signed(1)); + assert_noop!( + result, + DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + // No weight is refunded + actual_weight: Some(info.weight), + pays_fee: Pays::Yes, + }, + error: DispatchError::Other("The cake is a lie."), + } + ); + + // Refund weight when err + let inner_call = call_foobar(true, start_weight, Some(end_weight)); + let call = RuntimeCall::Utility(UtilityCall::as_derivative { + index: 0, + call: Box::new(inner_call), + }); + let info = call.get_dispatch_info(); + let result = call.dispatch(RuntimeOrigin::signed(1)); + assert_noop!( + result, + DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + // Diff is refunded + actual_weight: Some(info.weight - diff), + pays_fee: Pays::Yes, + }, + error: DispatchError::Other("The cake is a lie."), + } + ); + }); +} + +#[test] +fn as_derivative_filters() { + new_test_ext().execute_with(|| { + assert_err_ignore_postinfo!( + Utility::as_derivative( + RuntimeOrigin::signed(1), + 1, + Box::new(RuntimeCall::Balances( + pallet_balances::Call::transfer_keep_alive { dest: 2, value: 1 } + )), + ), + DispatchError::from(frame_system::Error::::CallFiltered), + ); + }); +} + +#[test] +fn batch_with_root_works() { + new_test_ext().execute_with(|| { + let k = b"a".to_vec(); + let call = RuntimeCall::System(frame_system::Call::set_storage { + items: vec![(k.clone(), k.clone())], + }); + assert!(!TestBaseCallFilter::contains(&call)); + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_ok!(Utility::batch( + RuntimeOrigin::root(), + vec![ + RuntimeCall::Balances(BalancesCall::force_transfer { + source: 1, + dest: 2, + value: 5 + }), + RuntimeCall::Balances(BalancesCall::force_transfer { + source: 1, + dest: 2, + value: 5 + }), + call, // Check filters are correctly bypassed + ] + )); + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::free_balance(2), 20); + assert_eq!(storage::unhashed::get_raw(&k), Some(k)); + }); +} + +#[test] +fn batch_with_signed_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_ok!(Utility::batch( + RuntimeOrigin::signed(1), + vec![call_transfer(2, 5), call_transfer(2, 5)] + ),); + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::free_balance(2), 20); + }); +} + +#[test] +fn batch_with_signed_filters() { + new_test_ext().execute_with(|| { + assert_ok!(Utility::batch( + RuntimeOrigin::signed(1), + vec![RuntimeCall::Balances( + pallet_balances::Call::transfer_keep_alive { dest: 2, value: 1 } + )] + ),); + System::assert_last_event( + utility::Event::BatchInterrupted { + index: 0, + error: frame_system::Error::::CallFiltered.into(), + } + .into(), + ); + }); +} + +#[test] +fn batch_early_exit_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_ok!(Utility::batch( + RuntimeOrigin::signed(1), + vec![ + call_transfer(2, 5), + call_transfer(2, 10), + call_transfer(2, 5), + ] + ),); + assert_eq!(Balances::free_balance(1), 5); + assert_eq!(Balances::free_balance(2), 15); + }); +} + +#[test] +fn batch_weight_calculation_doesnt_overflow() { + use sp_runtime::Perbill; + new_test_ext().execute_with(|| { + let big_call = RuntimeCall::RootTesting(RootTestingCall::fill_block { + ratio: Perbill::from_percent(50), + }); + assert_eq!(big_call.get_dispatch_info().weight, Weight::MAX / 2); + + // 3 * 50% saturates to 100% + let batch_call = RuntimeCall::Utility(crate::Call::batch { + calls: vec![big_call.clone(), big_call.clone(), big_call.clone()], + }); + + assert_eq!(batch_call.get_dispatch_info().weight, Weight::MAX); + }); +} + +#[test] +fn batch_handles_weight_refund() { + new_test_ext().execute_with(|| { + let start_weight = Weight::from_parts(100, 0); + let end_weight = Weight::from_parts(75, 0); + let diff = start_weight - end_weight; + let batch_len = 4; + + // Full weight when ok + let inner_call = call_foobar(false, start_weight, None); + let batch_calls = vec![inner_call; batch_len as usize]; + let call = RuntimeCall::Utility(UtilityCall::batch { calls: batch_calls }); + let info = call.get_dispatch_info(); + let result = call.dispatch(RuntimeOrigin::signed(1)); + assert_ok!(result); + assert_eq!(extract_actual_weight(&result, &info), info.weight); + + // Refund weight when ok + let inner_call = call_foobar(false, start_weight, Some(end_weight)); + let batch_calls = vec![inner_call; batch_len as usize]; + let call = RuntimeCall::Utility(UtilityCall::batch { calls: batch_calls }); + let info = call.get_dispatch_info(); + let result = call.dispatch(RuntimeOrigin::signed(1)); + assert_ok!(result); + // Diff is refunded + assert_eq!( + extract_actual_weight(&result, &info), + info.weight - diff * batch_len + ); + + // Full weight when err + let good_call = call_foobar(false, start_weight, None); + let bad_call = call_foobar(true, start_weight, None); + let batch_calls = vec![good_call, bad_call]; + let call = RuntimeCall::Utility(UtilityCall::batch { calls: batch_calls }); + let info = call.get_dispatch_info(); + let result = call.dispatch(RuntimeOrigin::signed(1)); + assert_ok!(result); + System::assert_last_event( + utility::Event::BatchInterrupted { + index: 1, + error: DispatchError::Other(""), + } + .into(), + ); + // No weight is refunded + assert_eq!(extract_actual_weight(&result, &info), info.weight); + + // Refund weight when err + let good_call = call_foobar(false, start_weight, Some(end_weight)); + let bad_call = call_foobar(true, start_weight, Some(end_weight)); + let batch_calls = vec![good_call, bad_call]; + let batch_len = batch_calls.len() as u64; + let call = RuntimeCall::Utility(UtilityCall::batch { calls: batch_calls }); + let info = call.get_dispatch_info(); + let result = call.dispatch(RuntimeOrigin::signed(1)); + assert_ok!(result); + System::assert_last_event( + utility::Event::BatchInterrupted { + index: 1, + error: DispatchError::Other(""), + } + .into(), + ); + assert_eq!( + extract_actual_weight(&result, &info), + info.weight - diff * batch_len + ); + + // Partial batch completion + let good_call = call_foobar(false, start_weight, Some(end_weight)); + let bad_call = call_foobar(true, start_weight, Some(end_weight)); + let batch_calls = vec![good_call, bad_call.clone(), bad_call]; + let call = RuntimeCall::Utility(UtilityCall::batch { calls: batch_calls }); + let info = call.get_dispatch_info(); + let result = call.dispatch(RuntimeOrigin::signed(1)); + assert_ok!(result); + System::assert_last_event( + utility::Event::BatchInterrupted { + index: 1, + error: DispatchError::Other(""), + } + .into(), + ); + assert_eq!( + extract_actual_weight(&result, &info), + // Real weight is 2 calls at end_weight + ::WeightInfo::batch(2) + end_weight * 2, + ); + }); +} + +#[test] +fn batch_all_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_ok!(Utility::batch_all( + RuntimeOrigin::signed(1), + vec![call_transfer(2, 5), call_transfer(2, 5)] + ),); + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::free_balance(2), 20); + }); +} + +#[test] +fn batch_all_revert() { + new_test_ext().execute_with(|| { + let call = call_transfer(2, 5); + let info = call.get_dispatch_info(); + + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + let batch_all_calls = RuntimeCall::Utility(crate::Call::::batch_all { + calls: vec![ + call_transfer(2, 5), + call_transfer(2, 10), + call_transfer(2, 5), + ], + }); + assert_noop!( + batch_all_calls.dispatch(RuntimeOrigin::signed(1)), + DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some( + ::WeightInfo::batch_all(2) + info.weight * 2 + ), + pays_fee: Pays::Yes + }, + error: TokenError::FundsUnavailable.into(), + } + ); + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + }); +} + +#[test] +fn batch_all_handles_weight_refund() { + new_test_ext().execute_with(|| { + let start_weight = Weight::from_parts(100, 0); + let end_weight = Weight::from_parts(75, 0); + let diff = start_weight - end_weight; + let batch_len = 4; + + // Full weight when ok + let inner_call = call_foobar(false, start_weight, None); + let batch_calls = vec![inner_call; batch_len as usize]; + let call = RuntimeCall::Utility(UtilityCall::batch_all { calls: batch_calls }); + let info = call.get_dispatch_info(); + let result = call.dispatch(RuntimeOrigin::signed(1)); + assert_ok!(result); + assert_eq!(extract_actual_weight(&result, &info), info.weight); + + // Refund weight when ok + let inner_call = call_foobar(false, start_weight, Some(end_weight)); + let batch_calls = vec![inner_call; batch_len as usize]; + let call = RuntimeCall::Utility(UtilityCall::batch_all { calls: batch_calls }); + let info = call.get_dispatch_info(); + let result = call.dispatch(RuntimeOrigin::signed(1)); + assert_ok!(result); + // Diff is refunded + assert_eq!( + extract_actual_weight(&result, &info), + info.weight - diff * batch_len + ); + + // Full weight when err + let good_call = call_foobar(false, start_weight, None); + let bad_call = call_foobar(true, start_weight, None); + let batch_calls = vec![good_call, bad_call]; + let call = RuntimeCall::Utility(UtilityCall::batch_all { calls: batch_calls }); + let info = call.get_dispatch_info(); + let result = call.dispatch(RuntimeOrigin::signed(1)); + assert_err_ignore_postinfo!(result, "The cake is a lie."); + // No weight is refunded + assert_eq!(extract_actual_weight(&result, &info), info.weight); + + // Refund weight when err + let good_call = call_foobar(false, start_weight, Some(end_weight)); + let bad_call = call_foobar(true, start_weight, Some(end_weight)); + let batch_calls = vec![good_call, bad_call]; + let batch_len = batch_calls.len() as u64; + let call = RuntimeCall::Utility(UtilityCall::batch_all { calls: batch_calls }); + let info = call.get_dispatch_info(); + let result = call.dispatch(RuntimeOrigin::signed(1)); + assert_err_ignore_postinfo!(result, "The cake is a lie."); + assert_eq!( + extract_actual_weight(&result, &info), + info.weight.saturating_sub(diff.saturating_mul(batch_len)) + ); + + // Partial batch completion + let good_call = call_foobar(false, start_weight, Some(end_weight)); + let bad_call = call_foobar(true, start_weight, Some(end_weight)); + let batch_calls = vec![good_call, bad_call.clone(), bad_call]; + let call = RuntimeCall::Utility(UtilityCall::batch_all { calls: batch_calls }); + let info = call.get_dispatch_info(); + let result = call.dispatch(RuntimeOrigin::signed(1)); + assert_err_ignore_postinfo!(result, "The cake is a lie."); + assert_eq!( + extract_actual_weight(&result, &info), + // Real weight is 2 calls at end_weight + ::WeightInfo::batch_all(2).saturating_add(end_weight.saturating_mul(2)), + ); + }); +} + +#[test] +fn batch_all_does_not_nest() { + new_test_ext().execute_with(|| { + let batch_all = RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + call_transfer(2, 1), + call_transfer(2, 1), + call_transfer(2, 1), + ], + }); + + let info = batch_all.get_dispatch_info(); + + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + // A nested batch_all call will not pass the filter, and fail with `BadOrigin`. + assert_noop!( + Utility::batch_all(RuntimeOrigin::signed(1), vec![batch_all.clone()]), + DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(::WeightInfo::batch_all(1) + info.weight), + pays_fee: Pays::Yes + }, + error: frame_system::Error::::CallFiltered.into(), + } + ); + + // And for those who want to get a little fancy, we check that the filter persists across + // other kinds of dispatch wrapping functions... in this case + // `batch_all(batch(batch_all(..)))` + let batch_nested = RuntimeCall::Utility(UtilityCall::batch { + calls: vec![batch_all], + }); + // Batch will end with `Ok`, but does not actually execute as we can see from the event + // and balances. + assert_ok!(Utility::batch_all( + RuntimeOrigin::signed(1), + vec![batch_nested] + )); + System::assert_has_event( + utility::Event::BatchInterrupted { + index: 0, + error: frame_system::Error::::CallFiltered.into(), + } + .into(), + ); + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + }); +} + +#[test] +fn batch_limit() { + new_test_ext().execute_with(|| { + let calls = vec![RuntimeCall::System(SystemCall::remark { remark: vec![] }); 40_000]; + assert_noop!( + Utility::batch(RuntimeOrigin::signed(1), calls.clone()), + Error::::TooManyCalls + ); + assert_noop!( + Utility::batch_all(RuntimeOrigin::signed(1), calls), + Error::::TooManyCalls + ); + }); +} + +#[test] +fn force_batch_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_ok!(Utility::force_batch( + RuntimeOrigin::signed(1), + vec![ + call_transfer(2, 5), + call_foobar(true, Weight::from_parts(75, 0), None), + call_transfer(2, 10), + call_transfer(2, 5), + ] + )); + System::assert_last_event(utility::Event::BatchCompletedWithErrors.into()); + System::assert_has_event( + utility::Event::ItemFailed { + error: DispatchError::Other(""), + } + .into(), + ); + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::free_balance(2), 20); + + assert_ok!(Utility::force_batch( + RuntimeOrigin::signed(2), + vec![call_transfer(1, 5), call_transfer(1, 5),] + )); + System::assert_last_event(utility::Event::BatchCompleted.into()); + + assert_ok!(Utility::force_batch( + RuntimeOrigin::signed(1), + vec![call_transfer(2, 50),] + ),); + System::assert_last_event(utility::Event::BatchCompletedWithErrors.into()); + }); +} + +#[test] +fn none_origin_does_not_work() { + new_test_ext().execute_with(|| { + assert_noop!( + Utility::force_batch(RuntimeOrigin::none(), vec![]), + BadOrigin + ); + assert_noop!(Utility::batch(RuntimeOrigin::none(), vec![]), BadOrigin); + assert_noop!(Utility::batch_all(RuntimeOrigin::none(), vec![]), BadOrigin); + }) +} + +#[test] +fn batch_doesnt_work_with_inherents() { + new_test_ext().execute_with(|| { + // fails because inherents expect the origin to be none. + assert_ok!(Utility::batch( + RuntimeOrigin::signed(1), + vec![RuntimeCall::Timestamp(TimestampCall::set { now: 42 }),] + )); + System::assert_last_event( + utility::Event::BatchInterrupted { + index: 0, + error: frame_system::Error::::CallFiltered.into(), + } + .into(), + ); + }) +} + +#[test] +fn force_batch_doesnt_work_with_inherents() { + new_test_ext().execute_with(|| { + // fails because inherents expect the origin to be none. + assert_ok!(Utility::force_batch( + RuntimeOrigin::root(), + vec![RuntimeCall::Timestamp(TimestampCall::set { now: 42 }),] + )); + System::assert_last_event(utility::Event::BatchCompletedWithErrors.into()); + }) +} + +#[test] +fn batch_all_doesnt_work_with_inherents() { + new_test_ext().execute_with(|| { + let batch_all = RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![RuntimeCall::Timestamp(TimestampCall::set { now: 42 })], + }); + let info = batch_all.get_dispatch_info(); + + // fails because inherents expect the origin to be none. + assert_noop!( + batch_all.dispatch(RuntimeOrigin::signed(1)), + DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(info.weight), + pays_fee: Pays::Yes + }, + error: frame_system::Error::::CallFiltered.into(), + } + ); + }) +} + +#[test] +fn batch_works_with_council_origin() { + new_test_ext().execute_with(|| { + let proposal = RuntimeCall::Utility(UtilityCall::batch { + calls: vec![RuntimeCall::Democracy( + mock_democracy::Call::external_propose_majority {}, + )], + }); + let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); + let proposal_weight = proposal.get_dispatch_info().weight; + let hash = BlakeTwo256::hash_of(&proposal); + + assert_ok!(Council::propose( + RuntimeOrigin::signed(1), + Box::new(proposal.clone()), + proposal_len, + 3, + )); + + assert_ok!(Council::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Council::vote(RuntimeOrigin::signed(2), hash, 0, true)); + assert_ok!(Council::vote(RuntimeOrigin::signed(3), hash, 0, true)); + + System::set_block_number(4); + + assert_ok!(Council::close( + RuntimeOrigin::root(), + hash, + 0, + proposal_weight, + proposal_len + )); + + System::assert_last_event(RuntimeEvent::Council(pallet_collective::Event::Executed { + proposal_hash: hash, + result: Ok(()), + })); + }) +} + +#[test] +fn force_batch_works_with_council_origin() { + new_test_ext().execute_with(|| { + let proposal = RuntimeCall::Utility(UtilityCall::force_batch { + calls: vec![RuntimeCall::Democracy( + mock_democracy::Call::external_propose_majority {}, + )], + }); + let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); + let proposal_weight = proposal.get_dispatch_info().weight; + let hash = BlakeTwo256::hash_of(&proposal); + + assert_ok!(Council::propose( + RuntimeOrigin::signed(1), + Box::new(proposal.clone()), + proposal_len, + 3, + )); + + assert_ok!(Council::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Council::vote(RuntimeOrigin::signed(2), hash, 0, true)); + assert_ok!(Council::vote(RuntimeOrigin::signed(3), hash, 0, true)); + + System::set_block_number(4); + assert_ok!(Council::close( + RuntimeOrigin::root(), + hash, + 0, + proposal_weight, + proposal_len + )); + + System::assert_last_event(RuntimeEvent::Council(pallet_collective::Event::Executed { + proposal_hash: hash, + result: Ok(()), + })); + }) +} + +#[test] +fn batch_all_works_with_council_origin() { + new_test_ext().execute_with(|| { + assert_ok!(Utility::batch_all( + RuntimeOrigin::from(pallet_collective::RawOrigin::Members(3, 3)), + vec![RuntimeCall::Democracy( + mock_democracy::Call::external_propose_majority {} + )] + )); + }) +} + +#[test] +fn with_weight_works() { + new_test_ext().execute_with(|| { + use frame_system::WeightInfo; + let upgrade_code_call = Box::new(RuntimeCall::System( + frame_system::Call::set_code_without_checks { code: vec![] }, + )); + // Weight before is max. + assert_eq!( + upgrade_code_call.get_dispatch_info().weight, + ::SystemWeightInfo::set_code() + ); + assert_eq!( + upgrade_code_call.get_dispatch_info().class, + frame_support::dispatch::DispatchClass::Operational + ); + + let with_weight_call = Call::::with_weight { + call: upgrade_code_call, + weight: Weight::from_parts(123, 456), + }; + // Weight after is set by Root. + assert_eq!( + with_weight_call.get_dispatch_info().weight, + Weight::from_parts(123, 456) + ); + assert_eq!( + with_weight_call.get_dispatch_info().class, + frame_support::dispatch::DispatchClass::Operational + ); + }) +} diff --git a/pallets/utility/src/weights.rs b/pallets/utility/src/weights.rs new file mode 100644 index 0000000000..502f85a3f1 --- /dev/null +++ b/pallets/utility/src/weights.rs @@ -0,0 +1,196 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Autogenerated weights for `pallet_utility` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// ./target/production/substrate-node +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_utility +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./substrate/frame/utility/src/weights.rs +// --header=./substrate/HEADER-APACHE2 +// --template=./substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_utility`. +pub trait WeightInfo { + fn batch(c: u32, ) -> Weight; + fn as_derivative() -> Weight; + fn batch_all(c: u32, ) -> Weight; + fn dispatch_as() -> Weight; + fn force_batch(c: u32, ) -> Weight; +} + +/// Weights for `pallet_utility` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + /// The range of component `c` is `[0, 1000]`. + fn batch(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `145` + // Estimated: `3997` + // Minimum execution time: 5_312_000 picoseconds. + Weight::from_parts(2_694_370, 3997) + // Standard Error: 5_055 + .saturating_add(Weight::from_parts(5_005_941, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + fn as_derivative() -> Weight { + // Proof Size summary in bytes: + // Measured: `145` + // Estimated: `3997` + // Minimum execution time: 9_263_000 picoseconds. + Weight::from_parts(9_639_000, 3997) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + /// The range of component `c` is `[0, 1000]`. + fn batch_all(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `145` + // Estimated: `3997` + // Minimum execution time: 5_120_000 picoseconds. + Weight::from_parts(12_948_874, 3997) + // Standard Error: 4_643 + .saturating_add(Weight::from_parts(5_162_821, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } + fn dispatch_as() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_126_000 picoseconds. + Weight::from_parts(7_452_000, 0) + } + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + /// The range of component `c` is `[0, 1000]`. + fn force_batch(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `145` + // Estimated: `3997` + // Minimum execution time: 5_254_000 picoseconds. + Weight::from_parts(4_879_712, 3997) + // Standard Error: 4_988 + .saturating_add(Weight::from_parts(4_955_816, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + /// The range of component `c` is `[0, 1000]`. + fn batch(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `145` + // Estimated: `3997` + // Minimum execution time: 5_312_000 picoseconds. + Weight::from_parts(2_694_370, 3997) + // Standard Error: 5_055 + .saturating_add(Weight::from_parts(5_005_941, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + fn as_derivative() -> Weight { + // Proof Size summary in bytes: + // Measured: `145` + // Estimated: `3997` + // Minimum execution time: 9_263_000 picoseconds. + Weight::from_parts(9_639_000, 3997) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + /// The range of component `c` is `[0, 1000]`. + fn batch_all(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `145` + // Estimated: `3997` + // Minimum execution time: 5_120_000 picoseconds. + Weight::from_parts(12_948_874, 3997) + // Standard Error: 4_643 + .saturating_add(Weight::from_parts(5_162_821, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } + fn dispatch_as() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_126_000 picoseconds. + Weight::from_parts(7_452_000, 0) + } + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + /// The range of component `c` is `[0, 1000]`. + fn force_batch(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `145` + // Estimated: `3997` + // Minimum execution time: 5_254_000 picoseconds. + Weight::from_parts(4_879_712, 3997) + // Standard Error: 4_988 + .saturating_add(Weight::from_parts(4_955_816, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } +} diff --git a/precompiles/Cargo.toml b/precompiles/Cargo.toml new file mode 100644 index 0000000000..ec46e6aee2 --- /dev/null +++ b/precompiles/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "subtensor-precompiles" +version = "0.1.0" +edition = "2024" +authors = ["Opentensor Foundation "] +homepage = "https://opentensor.ai/" +publish = false +repository = "https://github.com/opentensor/subtensor/" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +ed25519-dalek = { workspace = true } +fp-evm = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +log = { workspace = true } +pallet-balances = { workspace = true } +pallet-evm = { workspace = true } +pallet-evm-precompile-modexp = { workspace = true } +pallet-evm-precompile-sha3fips = { workspace = true } +pallet-evm-precompile-simple = { workspace = true } +pallet-proxy = { workspace = true } +precompile-utils = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +subtensor-runtime-common = { workspace = true } + +pallet-subtensor = { workspace = true } +pallet-admin-utils = { workspace = true } + +[lints] +workspace = true + +[features] +default = ["std"] +std = [ + "ed25519-dalek/std", + "fp-evm/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-admin-utils/std", + "pallet-balances/std", + "pallet-evm-precompile-modexp/std", + "pallet-evm-precompile-sha3fips/std", + "pallet-evm-precompile-simple/std", + "pallet-evm/std", + "pallet-proxy/std", + "pallet-subtensor/std", + "precompile-utils/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", + "subtensor-runtime-common/std", +] diff --git a/precompiles/src/balance_transfer.rs b/precompiles/src/balance_transfer.rs new file mode 100644 index 0000000000..b132125f22 --- /dev/null +++ b/precompiles/src/balance_transfer.rs @@ -0,0 +1,60 @@ +use core::marker::PhantomData; + +use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; +use frame_system::RawOrigin; +use pallet_evm::PrecompileHandle; +use precompile_utils::EvmResult; +use sp_core::{H256, U256}; +use sp_runtime::traits::{Dispatchable, StaticLookup, UniqueSaturatedInto}; + +use crate::{PrecompileExt, PrecompileHandleExt}; + +pub(crate) struct BalanceTransferPrecompile(PhantomData); + +impl PrecompileExt for BalanceTransferPrecompile +where + R: frame_system::Config + pallet_balances::Config + pallet_evm::Config, + R::AccountId: From<[u8; 32]>, + ::RuntimeCall: + GetDispatchInfo + Dispatchable, + ::RuntimeCall: From> + + GetDispatchInfo + + Dispatchable, + <::Lookup as StaticLookup>::Source: From, + ::Balance: TryFrom, +{ + const INDEX: u64 = 2048; +} + +#[precompile_utils::precompile] +impl BalanceTransferPrecompile +where + R: frame_system::Config + pallet_balances::Config + pallet_evm::Config, + R::AccountId: From<[u8; 32]>, + ::RuntimeCall: + GetDispatchInfo + Dispatchable, + ::RuntimeCall: From> + + GetDispatchInfo + + Dispatchable, + <::Lookup as StaticLookup>::Source: From, + ::Balance: TryFrom, +{ + #[precompile::public("transfer(bytes32)")] + #[precompile::payable] + fn transfer(handle: &mut impl PrecompileHandle, address: H256) -> EvmResult<()> { + let amount_sub = handle.try_convert_apparent_value::()?; + + if amount_sub.is_zero() { + return Ok(()); + } + + let dest = R::AccountId::from(address.0).into(); + + let call = pallet_balances::Call::::transfer_allow_death { + dest, + value: amount_sub.unique_saturated_into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(Self::account_id())) + } +} diff --git a/runtime/src/precompiles/ed25519.rs b/precompiles/src/ed25519.rs similarity index 61% rename from runtime/src/precompiles/ed25519.rs rename to precompiles/src/ed25519.rs index 601247f4e8..ae23a70e78 100644 --- a/runtime/src/precompiles/ed25519.rs +++ b/precompiles/src/ed25519.rs @@ -1,20 +1,26 @@ extern crate alloc; use alloc::vec::Vec; +use core::marker::PhantomData; use ed25519_dalek::{Signature, Verifier, VerifyingKey}; use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile, PrecompileFailure}; -use crate::precompiles::{PrecompileExt, parse_slice}; +use crate::PrecompileExt; -pub struct Ed25519Verify; +pub(crate) struct Ed25519Verify(PhantomData); -impl PrecompileExt for Ed25519Verify { +impl PrecompileExt for Ed25519Verify +where + A: From<[u8; 32]>, +{ const INDEX: u64 = 1026; - const ADDRESS_SS58: [u8; 32] = [0; 32]; } -impl LinearCostPrecompile for Ed25519Verify { +impl LinearCostPrecompile for Ed25519Verify +where + A: From<[u8; 32]>, +{ const BASE: u64 = 15; const WORD: u64 = 3; @@ -46,3 +52,21 @@ impl LinearCostPrecompile for Ed25519Verify { Ok((ExitSucceed::Returned, buf.to_vec())) } } + +/// Takes a slice from bytes with PrecompileFailure as Error +fn parse_slice(data: &[u8], from: usize, to: usize) -> Result<&[u8], PrecompileFailure> { + let maybe_slice = data.get(from..to); + if let Some(slice) = maybe_slice { + Ok(slice) + } else { + log::error!( + "fail to get slice from data, {:?}, from {}, to {}", + &data, + from, + to + ); + Err(PrecompileFailure::Error { + exit_status: ExitError::InvalidRange, + }) + } +} diff --git a/precompiles/src/extensions.rs b/precompiles/src/extensions.rs new file mode 100644 index 0000000000..373bf0a60d --- /dev/null +++ b/precompiles/src/extensions.rs @@ -0,0 +1,180 @@ +extern crate alloc; + +use alloc::format; + +use frame_support::dispatch::{GetDispatchInfo, Pays, PostDispatchInfo}; +use frame_system::RawOrigin; +use pallet_admin_utils::{PrecompileEnable, PrecompileEnum}; +use pallet_evm::{ + AddressMapping, BalanceConverter, ExitError, GasWeightMapping, Precompile, PrecompileFailure, + PrecompileHandle, PrecompileResult, +}; +use precompile_utils::EvmResult; +use sp_core::{H160, U256, blake2_256}; +use sp_runtime::traits::Dispatchable; +use sp_std::vec::Vec; + +pub(crate) trait PrecompileHandleExt: PrecompileHandle { + fn caller_account_id(&self) -> R::AccountId + where + R: frame_system::Config + pallet_evm::Config, + ::AddressMapping: AddressMapping, + { + ::AddressMapping::into_account_id(self.context().caller) + } + + fn try_convert_apparent_value(&self) -> EvmResult + where + R: pallet_evm::Config, + { + let amount = self.context().apparent_value; + ::BalanceConverter::into_substrate_balance(amount).ok_or( + PrecompileFailure::Error { + exit_status: ExitError::Other( + "error converting balance from ETH to subtensor".into(), + ), + }, + ) + } + + /// Dispatches a runtime call, but also checks and records the gas costs. + fn try_dispatch_runtime_call( + &mut self, + call: Call, + origin: RawOrigin, + ) -> EvmResult<()> + where + R: frame_system::Config + pallet_evm::Config, + R::RuntimeCall: From, + R::RuntimeCall: GetDispatchInfo + Dispatchable, + R::RuntimeOrigin: From>, + { + let call = R::RuntimeCall::from(call); + let info = GetDispatchInfo::get_dispatch_info(&call); + + let target_gas = self.gas_limit(); + if let Some(gas) = target_gas { + let valid_weight = + ::GasWeightMapping::gas_to_weight(gas, false).ref_time(); + if info.weight.ref_time() > valid_weight { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + } + + self.record_external_cost( + Some(info.weight.ref_time()), + Some(info.weight.proof_size()), + None, + )?; + + match call.dispatch(R::RuntimeOrigin::from(origin)) { + Ok(post_info) => { + if post_info.pays_fee(&info) == Pays::Yes { + let actual_weight = post_info.actual_weight.unwrap_or(info.weight); + let cost = + ::GasWeightMapping::weight_to_gas(actual_weight); + self.record_cost(cost)?; + + self.refund_external_cost( + Some( + info.weight + .ref_time() + .saturating_sub(actual_weight.ref_time()), + ), + Some( + info.weight + .proof_size() + .saturating_sub(actual_weight.proof_size()), + ), + ); + } + + log::info!("Dispatch succeeded. Post info: {:?}", post_info); + + Ok(()) + } + Err(e) => { + log::error!("Dispatch failed. Error: {:?}", e); + log::warn!("Returning error PrecompileFailure::Error"); + Err(PrecompileFailure::Error { + exit_status: ExitError::Other( + format!("dispatch execution failed: {}", <&'static str>::from(e)).into(), + ), + }) + } + } + } +} + +impl PrecompileHandleExt for T where T: PrecompileHandle {} + +pub(crate) trait PrecompileExt>: Precompile { + const INDEX: u64; + + // ss58 public key i.e., the contract sends funds it received to the destination address from + // the method parameter. + fn account_id() -> AccountId { + let hash = H160::from_low_u64_be(Self::INDEX); + let prefix = b"evm:"; + + // Concatenate prefix and ethereum address + let mut combined = Vec::new(); + combined.extend_from_slice(prefix); + combined.extend_from_slice(hash.as_bytes()); + + let hash = blake2_256(&combined); + + hash.into() + } + + fn try_execute( + handle: &mut impl PrecompileHandle, + precompile_enum: PrecompileEnum, + ) -> Option + where + R: frame_system::Config + pallet_admin_utils::Config, + { + if PrecompileEnable::::get(&precompile_enum) { + Some(Self::execute(handle)) + } else { + Some(Err(PrecompileFailure::Error { + exit_status: ExitError::Other( + format!("Precompile {:?} is disabled", precompile_enum).into(), + ), + })) + } + } +} + +// allowing unreachable for the whole module fixes clippy reports about precompile macro +// implementation for `TestPrecompile`, that couldn't be fixed granularly +#[allow(unreachable_code)] +#[cfg(test)] +mod test { + use super::*; + + use sp_core::crypto::AccountId32; + + #[test] + fn ss58_address_from_index_works() { + assert_eq!( + TestPrecompile::account_id(), + AccountId32::from([ + 0x3a, 0x86, 0x18, 0xfb, 0xbb, 0x1b, 0xbc, 0x47, 0x86, 0x64, 0xff, 0x53, 0x46, 0x18, + 0x0c, 0x35, 0xd0, 0x9f, 0xac, 0x26, 0xf2, 0x02, 0x70, 0x85, 0xb3, 0x1c, 0x56, 0xc1, + 0x06, 0x3c, 0x1c, 0xd3, + ]) + ); + } + + struct TestPrecompile; + + impl PrecompileExt for TestPrecompile { + const INDEX: u64 = 2051; + } + + #[precompile_utils::precompile] + impl TestPrecompile {} +} diff --git a/precompiles/src/lib.rs b/precompiles/src/lib.rs new file mode 100644 index 0000000000..42f250d563 --- /dev/null +++ b/precompiles/src/lib.rs @@ -0,0 +1,175 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use core::marker::PhantomData; + +use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; +use pallet_evm::{ + AddressMapping, IsPrecompileResult, Precompile, PrecompileHandle, PrecompileResult, + PrecompileSet, +}; +use pallet_evm_precompile_modexp::Modexp; +use pallet_evm_precompile_sha3fips::Sha3FIPS256; +use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; +use sp_core::{H160, U256, crypto::ByteArray}; +use sp_runtime::traits::Dispatchable; +use sp_runtime::traits::StaticLookup; +use subtensor_runtime_common::ProxyType; + +use pallet_admin_utils::PrecompileEnum; + +use crate::balance_transfer::*; +use crate::ed25519::*; +use crate::extensions::*; +use crate::metagraph::*; +use crate::neuron::*; +use crate::staking::*; +use crate::subnet::*; + +mod balance_transfer; +mod ed25519; +mod extensions; +mod metagraph; +mod neuron; +mod staking; +mod subnet; + +pub struct Precompiles(PhantomData); + +impl Default for Precompiles +where + R: frame_system::Config + + pallet_evm::Config + + pallet_balances::Config + + pallet_admin_utils::Config + + pallet_subtensor::Config + + pallet_proxy::Config, + R::AccountId: From<[u8; 32]> + ByteArray, + ::RuntimeCall: From> + + From> + + From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, + ::Balance: TryFrom, + <::Lookup as StaticLookup>::Source: From, +{ + fn default() -> Self { + Self::new() + } +} + +impl Precompiles +where + R: frame_system::Config + + pallet_evm::Config + + pallet_balances::Config + + pallet_admin_utils::Config + + pallet_subtensor::Config + + pallet_proxy::Config, + R::AccountId: From<[u8; 32]> + ByteArray, + ::RuntimeCall: From> + + From> + + From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, + ::Balance: TryFrom, + <::Lookup as StaticLookup>::Source: From, +{ + pub fn new() -> Self { + Self(Default::default()) + } + + pub fn used_addresses() -> [H160; 14] { + [ + hash(1), + hash(2), + hash(3), + hash(4), + hash(5), + hash(1024), + hash(1025), + hash(Ed25519Verify::::INDEX), + hash(BalanceTransferPrecompile::::INDEX), + hash(StakingPrecompile::::INDEX), + hash(SubnetPrecompile::::INDEX), + hash(MetagraphPrecompile::::INDEX), + hash(NeuronPrecompile::::INDEX), + hash(StakingPrecompileV2::::INDEX), + ] + } +} +impl PrecompileSet for Precompiles +where + R: frame_system::Config + + pallet_evm::Config + + pallet_balances::Config + + pallet_admin_utils::Config + + pallet_subtensor::Config + + pallet_proxy::Config, + R::AccountId: From<[u8; 32]> + ByteArray, + ::RuntimeCall: From> + + From> + + From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, + ::Balance: TryFrom, + <::Lookup as StaticLookup>::Source: From, +{ + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { + match handle.code_address() { + // Ethereum precompiles : + a if a == hash(1) => Some(ECRecover::execute(handle)), + a if a == hash(2) => Some(Sha256::execute(handle)), + a if a == hash(3) => Some(Ripemd160::execute(handle)), + a if a == hash(4) => Some(Identity::execute(handle)), + a if a == hash(5) => Some(Modexp::execute(handle)), + // Non-Frontier specific nor Ethereum precompiles : + a if a == hash(1024) => Some(Sha3FIPS256::execute(handle)), + a if a == hash(1025) => Some(ECRecoverPublicKey::execute(handle)), + a if a == hash(Ed25519Verify::::INDEX) => { + Some(Ed25519Verify::::execute(handle)) + } + // Subtensor specific precompiles : + a if a == hash(BalanceTransferPrecompile::::INDEX) => { + BalanceTransferPrecompile::::try_execute::( + handle, + PrecompileEnum::BalanceTransfer, + ) + } + a if a == hash(StakingPrecompile::::INDEX) => { + StakingPrecompile::::try_execute::(handle, PrecompileEnum::Staking) + } + a if a == hash(StakingPrecompileV2::::INDEX) => { + StakingPrecompileV2::::try_execute::(handle, PrecompileEnum::Staking) + } + a if a == hash(SubnetPrecompile::::INDEX) => { + SubnetPrecompile::::try_execute::(handle, PrecompileEnum::Subnet) + } + a if a == hash(MetagraphPrecompile::::INDEX) => { + MetagraphPrecompile::::try_execute::(handle, PrecompileEnum::Metagraph) + } + a if a == hash(NeuronPrecompile::::INDEX) => { + NeuronPrecompile::::try_execute::(handle, PrecompileEnum::Neuron) + } + _ => None, + } + } + + fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: Self::used_addresses().contains(&address), + extra_cost: 0, + } + } +} + +fn hash(a: u64) -> H160 { + H160::from_low_u64_be(a) +} diff --git a/runtime/src/precompiles/metagraph.rs b/precompiles/src/metagraph.rs similarity index 67% rename from runtime/src/precompiles/metagraph.rs rename to precompiles/src/metagraph.rs index e7a9243503..758ce8f8eb 100644 --- a/runtime/src/precompiles/metagraph.rs +++ b/precompiles/src/metagraph.rs @@ -1,52 +1,58 @@ -extern crate alloc; use alloc::string::String; +use core::marker::PhantomData; use fp_evm::{ExitError, PrecompileFailure, PrecompileHandle}; use pallet_subtensor::AxonInfo as SubtensorModuleAxonInfo; use precompile_utils::{EvmResult, solidity::Codec}; use sp_core::{ByteArray, H256}; -use crate::Runtime; -use crate::precompiles::PrecompileExt; +use crate::PrecompileExt; -pub struct MetagraphPrecompile; +pub struct MetagraphPrecompile(PhantomData); -impl PrecompileExt for MetagraphPrecompile { +impl PrecompileExt for MetagraphPrecompile +where + R: frame_system::Config + pallet_subtensor::Config, + R::AccountId: From<[u8; 32]> + ByteArray, +{ const INDEX: u64 = 2050; - const ADDRESS_SS58: [u8; 32] = [0; 32]; } #[precompile_utils::precompile] -impl MetagraphPrecompile { +impl MetagraphPrecompile +where + R: frame_system::Config + pallet_subtensor::Config, + R::AccountId: ByteArray, +{ #[precompile::public("getUidCount(uint16)")] #[precompile::view] fn get_uid_count(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::SubnetworkN::::get(netuid)) + Ok(pallet_subtensor::SubnetworkN::::get(netuid)) } #[precompile::public("getStake(uint16,uint16)")] #[precompile::view] fn get_stake(_: &mut impl PrecompileHandle, netuid: u16, uid: u16) -> EvmResult { - let hotkey = pallet_subtensor::Pallet::::get_hotkey_for_net_and_uid(netuid, uid) + let hotkey = pallet_subtensor::Pallet::::get_hotkey_for_net_and_uid(netuid, uid) .map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::InvalidRange, - })?; + exit_status: ExitError::InvalidRange, + })?; - Ok(pallet_subtensor::Pallet::::get_total_stake_for_hotkey(&hotkey)) + Ok(pallet_subtensor::Pallet::::get_total_stake_for_hotkey( + &hotkey, + )) } #[precompile::public("getRank(uint16,uint16)")] #[precompile::view] fn get_rank(_: &mut impl PrecompileHandle, netuid: u16, uid: u16) -> EvmResult { - Ok(pallet_subtensor::Pallet::::get_rank_for_uid( - netuid, uid, - )) + Ok(pallet_subtensor::Pallet::::get_rank_for_uid(netuid, uid)) } #[precompile::public("getTrust(uint16,uint16)")] #[precompile::view] fn get_trust(_: &mut impl PrecompileHandle, netuid: u16, uid: u16) -> EvmResult { - Ok(pallet_subtensor::Pallet::::get_trust_for_uid( + Ok(pallet_subtensor::Pallet::::get_trust_for_uid( netuid, uid, )) } @@ -54,7 +60,7 @@ impl MetagraphPrecompile { #[precompile::public("getConsensus(uint16,uint16)")] #[precompile::view] fn get_consensus(_: &mut impl PrecompileHandle, netuid: u16, uid: u16) -> EvmResult { - Ok(pallet_subtensor::Pallet::::get_consensus_for_uid( + Ok(pallet_subtensor::Pallet::::get_consensus_for_uid( netuid, uid, )) } @@ -62,7 +68,7 @@ impl MetagraphPrecompile { #[precompile::public("getIncentive(uint16,uint16)")] #[precompile::view] fn get_incentive(_: &mut impl PrecompileHandle, netuid: u16, uid: u16) -> EvmResult { - Ok(pallet_subtensor::Pallet::::get_incentive_for_uid( + Ok(pallet_subtensor::Pallet::::get_incentive_for_uid( netuid, uid, )) } @@ -70,7 +76,7 @@ impl MetagraphPrecompile { #[precompile::public("getDividends(uint16,uint16)")] #[precompile::view] fn get_dividends(_: &mut impl PrecompileHandle, netuid: u16, uid: u16) -> EvmResult { - Ok(pallet_subtensor::Pallet::::get_dividends_for_uid( + Ok(pallet_subtensor::Pallet::::get_dividends_for_uid( netuid, uid, )) } @@ -78,7 +84,7 @@ impl MetagraphPrecompile { #[precompile::public("getEmission(uint16,uint16)")] #[precompile::view] fn get_emission(_: &mut impl PrecompileHandle, netuid: u16, uid: u16) -> EvmResult { - Ok(pallet_subtensor::Pallet::::get_emission_for_uid( + Ok(pallet_subtensor::Pallet::::get_emission_for_uid( netuid, uid, )) } @@ -86,7 +92,9 @@ impl MetagraphPrecompile { #[precompile::public("getVtrust(uint16,uint16)")] #[precompile::view] fn get_vtrust(_: &mut impl PrecompileHandle, netuid: u16, uid: u16) -> EvmResult { - Ok(pallet_subtensor::Pallet::::get_validator_trust_for_uid(netuid, uid)) + Ok(pallet_subtensor::Pallet::::get_validator_trust_for_uid( + netuid, uid, + )) } #[precompile::public("getValidatorStatus(uint16,uint16)")] @@ -96,19 +104,23 @@ impl MetagraphPrecompile { netuid: u16, uid: u16, ) -> EvmResult { - Ok(pallet_subtensor::Pallet::::get_validator_permit_for_uid(netuid, uid)) + Ok(pallet_subtensor::Pallet::::get_validator_permit_for_uid( + netuid, uid, + )) } #[precompile::public("getLastUpdate(uint16,uint16)")] #[precompile::view] fn get_last_update(_: &mut impl PrecompileHandle, netuid: u16, uid: u16) -> EvmResult { - Ok(pallet_subtensor::Pallet::::get_last_update_for_uid(netuid, uid)) + Ok(pallet_subtensor::Pallet::::get_last_update_for_uid( + netuid, uid, + )) } #[precompile::public("getIsActive(uint16,uint16)")] #[precompile::view] fn get_is_active(_: &mut impl PrecompileHandle, netuid: u16, uid: u16) -> EvmResult { - Ok(pallet_subtensor::Pallet::::get_active_for_uid( + Ok(pallet_subtensor::Pallet::::get_active_for_uid( netuid, uid, )) } @@ -116,18 +128,18 @@ impl MetagraphPrecompile { #[precompile::public("getAxon(uint16,uint16)")] #[precompile::view] fn get_axon(_: &mut impl PrecompileHandle, netuid: u16, uid: u16) -> EvmResult { - let hotkey = pallet_subtensor::Pallet::::get_hotkey_for_net_and_uid(netuid, uid) + let hotkey = pallet_subtensor::Pallet::::get_hotkey_for_net_and_uid(netuid, uid) .map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other("hotkey not found".into()), - })?; + exit_status: ExitError::Other("hotkey not found".into()), + })?; - Ok(pallet_subtensor::Pallet::::get_axon_info(netuid, &hotkey).into()) + Ok(pallet_subtensor::Pallet::::get_axon_info(netuid, &hotkey).into()) } #[precompile::public("getHotkey(uint16,uint16)")] #[precompile::view] fn get_hotkey(_: &mut impl PrecompileHandle, netuid: u16, uid: u16) -> EvmResult { - pallet_subtensor::Pallet::::get_hotkey_for_net_and_uid(netuid, uid) + pallet_subtensor::Pallet::::get_hotkey_for_net_and_uid(netuid, uid) .map(|acc| H256::from_slice(acc.as_slice())) .map_err(|_| PrecompileFailure::Error { exit_status: ExitError::InvalidRange, @@ -137,11 +149,11 @@ impl MetagraphPrecompile { #[precompile::public("getColdkey(uint16,uint16)")] #[precompile::view] fn get_coldkey(_: &mut impl PrecompileHandle, netuid: u16, uid: u16) -> EvmResult { - let hotkey = pallet_subtensor::Pallet::::get_hotkey_for_net_and_uid(netuid, uid) + let hotkey = pallet_subtensor::Pallet::::get_hotkey_for_net_and_uid(netuid, uid) .map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::InvalidRange, - })?; - let coldkey = pallet_subtensor::Owner::::get(&hotkey); + exit_status: ExitError::InvalidRange, + })?; + let coldkey = pallet_subtensor::Owner::::get(&hotkey); Ok(H256::from_slice(coldkey.as_slice())) } diff --git a/runtime/src/precompiles/neuron.rs b/precompiles/src/neuron.rs similarity index 56% rename from runtime/src/precompiles/neuron.rs rename to precompiles/src/neuron.rs index 65306f87cc..cedb3a56db 100644 --- a/runtime/src/precompiles/neuron.rs +++ b/precompiles/src/neuron.rs @@ -1,25 +1,39 @@ +use core::marker::PhantomData; + +use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; use frame_system::RawOrigin; -use pallet_evm::PrecompileHandle; +use pallet_evm::{AddressMapping, PrecompileHandle}; use precompile_utils::{EvmResult, prelude::UnboundedBytes}; use sp_core::H256; +use sp_runtime::traits::Dispatchable; use sp_std::vec::Vec; -use crate::Runtime; -use crate::precompiles::{PrecompileExt, PrecompileHandleExt, parse_pubkey}; +use crate::{PrecompileExt, PrecompileHandleExt}; -pub struct NeuronPrecompile; +pub struct NeuronPrecompile(PhantomData); -impl PrecompileExt for NeuronPrecompile { +impl PrecompileExt for NeuronPrecompile +where + R: frame_system::Config + pallet_evm::Config + pallet_subtensor::Config, + R::AccountId: From<[u8; 32]>, + ::RuntimeCall: From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, +{ const INDEX: u64 = 2052; - const ADDRESS_SS58: [u8; 32] = [ - 0xbc, 0x46, 0x35, 0x79, 0xbc, 0x99, 0xf9, 0xee, 0x7c, 0x59, 0xed, 0xee, 0x20, 0x61, 0xa3, - 0x09, 0xd2, 0x1e, 0x68, 0xd5, 0x39, 0xb6, 0x40, 0xec, 0x66, 0x46, 0x90, 0x30, 0xab, 0x74, - 0xc1, 0xdb, - ]; } #[precompile_utils::precompile] -impl NeuronPrecompile { +impl NeuronPrecompile +where + R: frame_system::Config + pallet_evm::Config + pallet_subtensor::Config, + R::AccountId: From<[u8; 32]>, + ::RuntimeCall: From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, +{ #[precompile::public("setWeights(uint16,uint16[],uint16[],uint64)")] #[precompile::payable] pub fn set_weights( @@ -29,14 +43,17 @@ impl NeuronPrecompile { weights: Vec, version_key: u64, ) -> EvmResult<()> { - let call = pallet_subtensor::Call::::set_weights { + let call = pallet_subtensor::Call::::set_weights { netuid, dests, weights, version_key, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("commitWeights(uint16,bytes32)")] @@ -46,12 +63,15 @@ impl NeuronPrecompile { netuid: u16, commit_hash: H256, ) -> EvmResult<()> { - let call = pallet_subtensor::Call::::commit_weights { + let call = pallet_subtensor::Call::::commit_weights { netuid, commit_hash, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("revealWeights(uint16,uint16[],uint16[],uint16[],uint64)")] @@ -64,7 +84,7 @@ impl NeuronPrecompile { salt: Vec, version_key: u64, ) -> EvmResult<()> { - let call = pallet_subtensor::Call::::reveal_weights { + let call = pallet_subtensor::Call::::reveal_weights { netuid, uids, values, @@ -72,7 +92,10 @@ impl NeuronPrecompile { version_key, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("burnedRegister(uint16,bytes32)")] @@ -82,11 +105,11 @@ impl NeuronPrecompile { netuid: u16, hotkey: H256, ) -> EvmResult<()> { - let coldkey = handle.caller_account_id(); - let (hotkey, _) = parse_pubkey(hotkey.as_bytes())?; - let call = pallet_subtensor::Call::::burned_register { netuid, hotkey }; + let coldkey = handle.caller_account_id::(); + let hotkey = R::AccountId::from(hotkey.0); + let call = pallet_subtensor::Call::::burned_register { netuid, hotkey }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(coldkey)) + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(coldkey)) } #[precompile::public("serveAxon(uint16,uint32,uint128,uint16,uint8,uint8,uint8,uint8)")] @@ -103,7 +126,7 @@ impl NeuronPrecompile { placeholder1: u8, placeholder2: u8, ) -> EvmResult<()> { - let call = pallet_subtensor::Call::::serve_axon { + let call = pallet_subtensor::Call::::serve_axon { netuid, version, ip, @@ -114,7 +137,10 @@ impl NeuronPrecompile { placeholder2, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public( @@ -134,7 +160,7 @@ impl NeuronPrecompile { placeholder2: u8, certificate: UnboundedBytes, ) -> EvmResult<()> { - let call = pallet_subtensor::Call::::serve_axon_tls { + let call = pallet_subtensor::Call::::serve_axon_tls { netuid, version, ip, @@ -146,7 +172,10 @@ impl NeuronPrecompile { certificate: certificate.into(), }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("servePrometheus(uint16,uint32,uint128,uint16,uint8)")] @@ -160,7 +189,7 @@ impl NeuronPrecompile { port: u16, ip_type: u8, ) -> EvmResult<()> { - let call = pallet_subtensor::Call::::serve_prometheus { + let call = pallet_subtensor::Call::::serve_prometheus { netuid, version, ip, @@ -168,6 +197,9 @@ impl NeuronPrecompile { ip_type, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } } diff --git a/runtime/src/precompiles/solidity/balanceTransfer.abi b/precompiles/src/solidity/balanceTransfer.abi similarity index 100% rename from runtime/src/precompiles/solidity/balanceTransfer.abi rename to precompiles/src/solidity/balanceTransfer.abi diff --git a/runtime/src/precompiles/solidity/balanceTransfer.sol b/precompiles/src/solidity/balanceTransfer.sol similarity index 100% rename from runtime/src/precompiles/solidity/balanceTransfer.sol rename to precompiles/src/solidity/balanceTransfer.sol diff --git a/runtime/src/precompiles/solidity/ed25519Verify.abi b/precompiles/src/solidity/ed25519Verify.abi similarity index 100% rename from runtime/src/precompiles/solidity/ed25519Verify.abi rename to precompiles/src/solidity/ed25519Verify.abi diff --git a/runtime/src/precompiles/solidity/ed25519Verify.sol b/precompiles/src/solidity/ed25519Verify.sol similarity index 100% rename from runtime/src/precompiles/solidity/ed25519Verify.sol rename to precompiles/src/solidity/ed25519Verify.sol diff --git a/runtime/src/precompiles/solidity/metagraph.abi b/precompiles/src/solidity/metagraph.abi similarity index 100% rename from runtime/src/precompiles/solidity/metagraph.abi rename to precompiles/src/solidity/metagraph.abi diff --git a/runtime/src/precompiles/solidity/metagraph.sol b/precompiles/src/solidity/metagraph.sol similarity index 100% rename from runtime/src/precompiles/solidity/metagraph.sol rename to precompiles/src/solidity/metagraph.sol diff --git a/runtime/src/precompiles/solidity/neuron.abi b/precompiles/src/solidity/neuron.abi similarity index 100% rename from runtime/src/precompiles/solidity/neuron.abi rename to precompiles/src/solidity/neuron.abi diff --git a/runtime/src/precompiles/solidity/neuron.sol b/precompiles/src/solidity/neuron.sol similarity index 100% rename from runtime/src/precompiles/solidity/neuron.sol rename to precompiles/src/solidity/neuron.sol diff --git a/runtime/src/precompiles/solidity/staking.abi b/precompiles/src/solidity/staking.abi similarity index 72% rename from runtime/src/precompiles/solidity/staking.abi rename to precompiles/src/solidity/staking.abi index 3c4a018c90..f06dfb651b 100644 --- a/runtime/src/precompiles/solidity/staking.abi +++ b/precompiles/src/solidity/staking.abi @@ -94,5 +94,43 @@ "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "coldkey", + "type": "bytes32" + } + ], + "name": "getTotalColdkeyStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + } + ], + "name": "getTotalHotkeyStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" } ] \ No newline at end of file diff --git a/runtime/src/precompiles/solidity/staking.sol b/precompiles/src/solidity/staking.sol similarity index 71% rename from runtime/src/precompiles/solidity/staking.sol rename to precompiles/src/solidity/staking.sol index 19d51d0470..4666183700 100644 --- a/runtime/src/precompiles/solidity/staking.sol +++ b/precompiles/src/solidity/staking.sol @@ -46,12 +46,36 @@ interface IStaking { uint256 netuid ) external; - /** - * @dev Delegates staking to a proxy account. - * - * @param delegate The public key (32 bytes) of the delegate. - */ - function addProxy(bytes32 delegate) external; + /** + * @dev Returns the amount of RAO staked by the coldkey. + * + * This function allows external accounts and contracts to query the amount of RAO staked by the coldkey + * which effectively calls `get_total_coldkey_stake` on the subtensor pallet with + * specified coldkey as a parameter. + * + * @param coldkey The coldkey public key (32 bytes). + * @return The amount of RAO staked by the coldkey. + */ + function getTotalColdkeyStake(bytes32 coldkey) external view returns (uint256); + + /** + * @dev Returns the total amount of stake under a hotkey (delegative or otherwise) + * + * This function allows external accounts and contracts to query the total amount of RAO staked under a hotkey + * which effectively calls `get_total_hotkey_stake` on the subtensor pallet with + * specified hotkey as a parameter. + * + * @param hotkey The hotkey public key (32 bytes). + * @return The total amount of RAO staked under the hotkey. + */ + function getTotalHotkeyStake(bytes32 hotkey) external view returns (uint256); + + /** + * @dev Delegates staking to a proxy account. + * + * @param delegate The public key (32 bytes) of the delegate. + */ + function addProxy(bytes32 delegate) external; /** * @dev Removes staking proxy account. diff --git a/precompiles/src/solidity/stakingV2.abi b/precompiles/src/solidity/stakingV2.abi new file mode 100644 index 0000000000..21dd2761e4 --- /dev/null +++ b/precompiles/src/solidity/stakingV2.abi @@ -0,0 +1,141 @@ +[ + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + } + ], + "name": "addProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "addStake", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "coldkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "getStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "coldkey", + "type": "bytes32" + } + ], + "name": "getTotalColdkeyStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + } + ], + "name": "getTotalHotkeyStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + } + ], + "name": "removeProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "removeStake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/precompiles/src/solidity/stakingV2.sol b/precompiles/src/solidity/stakingV2.sol new file mode 100644 index 0000000000..67ac0cb129 --- /dev/null +++ b/precompiles/src/solidity/stakingV2.sol @@ -0,0 +1,103 @@ +pragma solidity ^0.8.0; + +address constant ISTAKING_ADDRESS = 0x0000000000000000000000000000000000000805; + +interface IStaking { + /** + * @dev Adds a subtensor stake `amount` associated with the `hotkey`. + * + * This function allows external accounts and contracts to stake TAO into the subtensor pallet, + * which effectively calls `add_stake` on the subtensor pallet with specified hotkey as a parameter + * and coldkey being the hashed address mapping of H160 sender address to Substrate ss58 address as + * implemented in Frontier HashedAddressMapping: + * https://github.com/polkadot-evm/frontier/blob/2e219e17a526125da003e64ef22ec037917083fa/frame/evm/src/lib.rs#L739 + * + * @param hotkey The hotkey public key (32 bytes). + * @param amount The amount to stake in rao. + * @param netuid The subnet to stake to (uint256). + * + * Requirements: + * - `hotkey` must be a valid hotkey registered on the network, ensuring that the stake is + * correctly attributed. + */ + function addStake(bytes32 hotkey, uint256 amount, uint256 netuid) external payable; + + /** + * @dev Removes a subtensor stake `amount` from the specified `hotkey`. + * + * This function allows external accounts and contracts to unstake TAO from the subtensor pallet, + * which effectively calls `remove_stake` on the subtensor pallet with specified hotkey as a parameter + * and coldkey being the hashed address mapping of H160 sender address to Substrate ss58 address as + * implemented in Frontier HashedAddressMapping: + * https://github.com/polkadot-evm/frontier/blob/2e219e17a526125da003e64ef22ec037917083fa/frame/evm/src/lib.rs#L739 + * + * @param hotkey The hotkey public key (32 bytes). + * @param amount The amount to unstake in alpha. + * @param netuid The subnet to stake to (uint256). + * + * Requirements: + * - `hotkey` must be a valid hotkey registered on the network, ensuring that the stake is + * correctly attributed. + * - The existing stake amount must be not lower than specified amount + */ + function removeStake( + bytes32 hotkey, + uint256 amount, + uint256 netuid + ) external; + + /** + * @dev Returns the amount of RAO staked by the coldkey. + * + * This function allows external accounts and contracts to query the amount of RAO staked by the coldkey + * which effectively calls `get_total_coldkey_stake` on the subtensor pallet with + * specified coldkey as a parameter. + * + * @param coldkey The coldkey public key (32 bytes). + * @return The amount of RAO staked by the coldkey. + */ + function getTotalColdkeyStake(bytes32 coldkey) external view returns (uint256); + + /** + * @dev Returns the total amount of stake under a hotkey (delegative or otherwise) + * + * This function allows external accounts and contracts to query the total amount of RAO staked under a hotkey + * which effectively calls `get_total_hotkey_stake` on the subtensor pallet with + * specified hotkey as a parameter. + * + * @param hotkey The hotkey public key (32 bytes). + * @return The total amount of RAO staked under the hotkey. + */ + function getTotalHotkeyStake(bytes32 hotkey) external view returns (uint256); + + /** + * @dev Returns the stake amount associated with the specified `hotkey` and `coldkey`. + * + * This function retrieves the current stake amount linked to a specific hotkey and coldkey pair. + * It is a view function, meaning it does not modify the state of the contract and is free to call. + * + * @param hotkey The hotkey public key (32 bytes). + * @param coldkey The coldkey public key (32 bytes). + * @param netuid The subnet the stake is on (uint256). + * @return The current stake amount in uint256 format. + */ + function getStake( + bytes32 hotkey, + bytes32 coldkey, + uint256 netuid + ) external view returns (uint256); + + /** + * @dev Delegates staking to a proxy account. + * + * @param delegate The public key (32 bytes) of the delegate. + */ + function addProxy(bytes32 delegate) external; + + /** + * @dev Removes staking proxy account. + * + * @param delegate The public key (32 bytes) of the delegate. + */ + function removeProxy(bytes32 delegate) external; +} diff --git a/runtime/src/precompiles/solidity/subnet.abi b/precompiles/src/solidity/subnet.abi similarity index 100% rename from runtime/src/precompiles/solidity/subnet.abi rename to precompiles/src/solidity/subnet.abi diff --git a/runtime/src/precompiles/solidity/subnet.sol b/precompiles/src/solidity/subnet.sol similarity index 100% rename from runtime/src/precompiles/solidity/subnet.sol rename to precompiles/src/solidity/subnet.sol diff --git a/precompiles/src/staking.rs b/precompiles/src/staking.rs new file mode 100644 index 0000000000..192e55f57c --- /dev/null +++ b/precompiles/src/staking.rs @@ -0,0 +1,401 @@ +// The goal of staking precompile is to allow interaction between EVM users and smart contracts and +// subtensor staking functionality, namely add_stake, and remove_stake extrinsicsk, as well as the +// staking state. +// +// Additional requirement is to preserve compatibility with Ethereum indexers, which requires +// no balance transfers from EVM accounts without a corresponding transaction that can be +// parsed by an indexer. +// +// Implementation of add_stake: +// - User transfers balance that will be staked to the precompile address with a payable +// method addStake. This method also takes hotkey public key (bytes32) of the hotkey +// that the stake should be assigned to. +// - Precompile transfers the balance back to the signing address, and then invokes +// do_add_stake from subtensor pallet with signing origin that mmatches to HashedAddressMapping +// of the message sender, which will effectively withdraw and stake balance from the message +// sender. +// - Precompile checks the result of do_add_stake and, in case of a failure, reverts the transaction, +// and leaves the balance on the message sender account. +// +// Implementation of remove_stake: +// - User involkes removeStake method and specifies hotkey public key (bytes32) of the hotkey +// to remove stake from, and the amount to unstake. +// - Precompile calls do_remove_stake method of the subtensor pallet with the signing origin of message +// sender, which effectively unstakes the specified amount and credits it to the message sender +// - Precompile checks the result of do_remove_stake and, in case of a failure, reverts the transaction. +// + +use core::marker::PhantomData; + +use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; +use frame_system::RawOrigin; +use pallet_evm::{ + AddressMapping, BalanceConverter, ExitError, PrecompileFailure, PrecompileHandle, +}; +use precompile_utils::EvmResult; +use sp_core::{H256, U256}; +use sp_runtime::traits::{Dispatchable, StaticLookup, UniqueSaturatedInto}; +use subtensor_runtime_common::ProxyType; + +use crate::{PrecompileExt, PrecompileHandleExt}; + +// Old StakingPrecompile had ETH-precision in values, which was not alligned with Substrate API. So +// it's kinda deprecated, but exists for backward compatibility. Eventually, we should remove it +// to stop supporting both precompiles. +// +// All the future extensions should happen in StakingPrecompileV2. +pub(crate) struct StakingPrecompileV2(PhantomData); + +impl PrecompileExt for StakingPrecompileV2 +where + R: frame_system::Config + + pallet_evm::Config + + pallet_subtensor::Config + + pallet_proxy::Config, + R::AccountId: From<[u8; 32]>, + ::RuntimeCall: From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, + <::Lookup as StaticLookup>::Source: From, +{ + const INDEX: u64 = 2053; +} + +#[precompile_utils::precompile] +impl StakingPrecompileV2 +where + R: frame_system::Config + + pallet_evm::Config + + pallet_subtensor::Config + + pallet_proxy::Config, + R::AccountId: From<[u8; 32]>, + ::RuntimeCall: From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, + <::Lookup as StaticLookup>::Source: From, +{ + #[precompile::public("addStake(bytes32,uint256,uint256)")] + #[precompile::payable] + fn add_stake( + handle: &mut impl PrecompileHandle, + address: H256, + amount_rao: U256, + netuid: U256, + ) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let amount_staked = amount_rao.unique_saturated_into(); + let hotkey = R::AccountId::from(address.0); + let netuid = try_u16_from_u256(netuid)?; + let call = pallet_subtensor::Call::::add_stake { + hotkey, + netuid, + amount_staked, + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("removeStake(bytes32,uint256,uint256)")] + fn remove_stake( + handle: &mut impl PrecompileHandle, + address: H256, + amount_alpha: U256, + netuid: U256, + ) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let hotkey = R::AccountId::from(address.0); + let netuid = try_u16_from_u256(netuid)?; + let amount_unstaked = amount_alpha.unique_saturated_into(); + let call = pallet_subtensor::Call::::remove_stake { + hotkey, + netuid, + amount_unstaked, + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("getTotalColdkeyStake(bytes32)")] + #[precompile::view] + fn get_total_coldkey_stake( + _handle: &mut impl PrecompileHandle, + coldkey: H256, + ) -> EvmResult { + let coldkey = R::AccountId::from(coldkey.0); + let stake = pallet_subtensor::Pallet::::get_total_stake_for_coldkey(&coldkey); + + Ok(stake.into()) + } + + #[precompile::public("getTotalHotkeyStake(bytes32)")] + #[precompile::view] + fn get_total_hotkey_stake( + _handle: &mut impl PrecompileHandle, + hotkey: H256, + ) -> EvmResult { + let hotkey = R::AccountId::from(hotkey.0); + let stake = pallet_subtensor::Pallet::::get_total_stake_for_hotkey(&hotkey); + + Ok(stake.into()) + } + + #[precompile::public("getStake(bytes32,bytes32,uint256)")] + #[precompile::view] + fn get_stake( + _: &mut impl PrecompileHandle, + hotkey: H256, + coldkey: H256, + netuid: U256, + ) -> EvmResult { + let hotkey = R::AccountId::from(hotkey.0); + let coldkey = R::AccountId::from(coldkey.0); + let netuid = try_u16_from_u256(netuid)?; + let stake = pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, + ); + + Ok(stake.into()) + } + + #[precompile::public("addProxy(bytes32)")] + fn add_proxy(handle: &mut impl PrecompileHandle, delegate: H256) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let delegate = R::AccountId::from(delegate.0); + let delegate = ::Lookup::unlookup(delegate); + let call = pallet_proxy::Call::::add_proxy { + delegate, + proxy_type: ProxyType::Staking, + delay: 0u32.into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("removeProxy(bytes32)")] + fn remove_proxy(handle: &mut impl PrecompileHandle, delegate: H256) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let delegate = R::AccountId::from(delegate.0); + let delegate = ::Lookup::unlookup(delegate); + let call = pallet_proxy::Call::::remove_proxy { + delegate, + proxy_type: ProxyType::Staking, + delay: 0u32.into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } +} + +// Deprecated, exists for backward compatibility. +pub(crate) struct StakingPrecompile(PhantomData); + +impl PrecompileExt for StakingPrecompile +where + R: frame_system::Config + + pallet_evm::Config + + pallet_subtensor::Config + + pallet_proxy::Config + + pallet_balances::Config, + R::AccountId: From<[u8; 32]>, + ::RuntimeCall: From> + + From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, + ::Balance: TryFrom, + <::Lookup as StaticLookup>::Source: From, +{ + const INDEX: u64 = 2049; +} + +#[precompile_utils::precompile] +impl StakingPrecompile +where + R: frame_system::Config + + pallet_evm::Config + + pallet_subtensor::Config + + pallet_proxy::Config + + pallet_balances::Config, + R::AccountId: From<[u8; 32]>, + ::RuntimeCall: From> + + From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, + ::Balance: TryFrom, + <::Lookup as StaticLookup>::Source: From, +{ + #[precompile::public("addStake(bytes32,uint256)")] + #[precompile::payable] + fn add_stake(handle: &mut impl PrecompileHandle, address: H256, netuid: U256) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let amount = handle.context().apparent_value; + + if !amount.is_zero() { + Self::transfer_back_to_caller(&account_id, amount)?; + } + + let amount_sub = handle.try_convert_apparent_value::()?; + let hotkey = R::AccountId::from(address.0); + let netuid = try_u16_from_u256(netuid)?; + let call = pallet_subtensor::Call::::add_stake { + hotkey, + netuid, + amount_staked: amount_sub.unique_saturated_into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("removeStake(bytes32,uint256,uint256)")] + fn remove_stake( + handle: &mut impl PrecompileHandle, + address: H256, + amount: U256, + netuid: U256, + ) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let hotkey = R::AccountId::from(address.0); + let netuid = try_u16_from_u256(netuid)?; + let amount_unstaked = + ::BalanceConverter::into_substrate_balance(amount) + .ok_or(ExitError::OutOfFund)?; + let amount_unstaked = amount_unstaked.unique_saturated_into(); + let call = pallet_subtensor::Call::::remove_stake { + hotkey, + netuid, + amount_unstaked, + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("getTotalColdkeyStake(bytes32)")] + #[precompile::view] + fn get_total_coldkey_stake( + _handle: &mut impl PrecompileHandle, + coldkey: H256, + ) -> EvmResult { + let coldkey = R::AccountId::from(coldkey.0); + + // get total stake of coldkey + let total_stake = pallet_subtensor::Pallet::::get_total_stake_for_coldkey(&coldkey); + // Convert to EVM decimals + let stake_u256 = U256::from(total_stake); + let stake_eth = ::BalanceConverter::into_evm_balance(stake_u256) + .ok_or(ExitError::InvalidRange)?; + + Ok(stake_eth) + } + + #[precompile::public("getTotalHotkeyStake(bytes32)")] + #[precompile::view] + fn get_total_hotkey_stake( + _handle: &mut impl PrecompileHandle, + hotkey: H256, + ) -> EvmResult { + let hotkey = R::AccountId::from(hotkey.0); + + // get total stake of hotkey + let total_stake = pallet_subtensor::Pallet::::get_total_stake_for_hotkey(&hotkey); + // Convert to EVM decimals + let stake_u256 = U256::from(total_stake); + let stake_eth = ::BalanceConverter::into_evm_balance(stake_u256) + .ok_or(ExitError::InvalidRange)?; + + Ok(stake_eth) + } + + #[precompile::public("getStake(bytes32,bytes32,uint256)")] + #[precompile::view] + fn get_stake( + _: &mut impl PrecompileHandle, + hotkey: H256, + coldkey: H256, + netuid: U256, + ) -> EvmResult { + let hotkey = R::AccountId::from(hotkey.0); + let coldkey = R::AccountId::from(coldkey.0); + let netuid = try_u16_from_u256(netuid)?; + let stake = pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, + ); + let stake = U256::from(stake); + let stake = ::BalanceConverter::into_evm_balance(stake) + .ok_or(ExitError::InvalidRange)?; + + Ok(stake) + } + + #[precompile::public("addProxy(bytes32)")] + fn add_proxy(handle: &mut impl PrecompileHandle, delegate: H256) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let delegate = R::AccountId::from(delegate.0); + let delegate = ::Lookup::unlookup(delegate); + let call = pallet_proxy::Call::::add_proxy { + delegate, + proxy_type: ProxyType::Staking, + delay: 0u32.into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("removeProxy(bytes32)")] + fn remove_proxy(handle: &mut impl PrecompileHandle, delegate: H256) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let delegate = R::AccountId::from(delegate.0); + let delegate = ::Lookup::unlookup(delegate); + let call = pallet_proxy::Call::::remove_proxy { + delegate, + proxy_type: ProxyType::Staking, + delay: 0u32.into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + fn transfer_back_to_caller( + account_id: &::AccountId, + amount: U256, + ) -> Result<(), PrecompileFailure> { + let amount_sub = + ::BalanceConverter::into_substrate_balance(amount) + .ok_or(ExitError::OutOfFund)?; + + // Create a transfer call from the smart contract to the caller + let transfer_call = ::RuntimeCall::from( + pallet_balances::Call::::transfer_allow_death { + dest: account_id.clone().into(), + value: amount_sub.unique_saturated_into(), + }, + ); + + // Execute the transfer + let transfer_result = transfer_call.dispatch(RawOrigin::Signed(Self::account_id()).into()); + + if let Err(dispatch_error) = transfer_result { + log::error!( + "Transfer back to caller failed. Error: {:?}", + dispatch_error + ); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Transfer back to caller failed".into()), + }); + } + + Ok(()) + } +} + +fn try_u16_from_u256(value: U256) -> Result { + value.try_into().map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("the value is outside of u16 bounds".into()), + }) +} diff --git a/runtime/src/precompiles/subnet.rs b/precompiles/src/subnet.rs similarity index 59% rename from runtime/src/precompiles/subnet.rs rename to precompiles/src/subnet.rs index db46b3033b..cffe82ab78 100644 --- a/runtime/src/precompiles/subnet.rs +++ b/precompiles/src/subnet.rs @@ -1,35 +1,60 @@ +use core::marker::PhantomData; + +use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; use frame_support::traits::ConstU32; use frame_system::RawOrigin; -use pallet_evm::PrecompileHandle; +use pallet_evm::{AddressMapping, PrecompileHandle}; use precompile_utils::{EvmResult, prelude::BoundedString}; use sp_core::H256; - -use crate::Runtime; -use crate::precompiles::{PrecompileExt, PrecompileHandleExt, parse_pubkey}; - -pub struct SubnetPrecompile; - -impl PrecompileExt for SubnetPrecompile { +use sp_runtime::traits::Dispatchable; + +use crate::{PrecompileExt, PrecompileHandleExt}; + +pub struct SubnetPrecompile(PhantomData); + +impl PrecompileExt for SubnetPrecompile +where + R: frame_system::Config + + pallet_evm::Config + + pallet_subtensor::Config + + pallet_admin_utils::Config, + R::AccountId: From<[u8; 32]>, + ::RuntimeCall: From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, +{ const INDEX: u64 = 2051; - const ADDRESS_SS58: [u8; 32] = [ - 0x3a, 0x86, 0x18, 0xfb, 0xbb, 0x1b, 0xbc, 0x47, 0x86, 0x64, 0xff, 0x53, 0x46, 0x18, 0x0c, - 0x35, 0xd0, 0x9f, 0xac, 0x26, 0xf2, 0x02, 0x70, 0x85, 0xb3, 0x1c, 0x56, 0xc1, 0x06, 0x3c, - 0x1c, 0xd3, - ]; } #[precompile_utils::precompile] -impl SubnetPrecompile { +impl SubnetPrecompile +where + R: frame_system::Config + + pallet_evm::Config + + pallet_subtensor::Config + + pallet_admin_utils::Config, + R::AccountId: From<[u8; 32]>, + ::RuntimeCall: From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, +{ #[precompile::public("registerNetwork(bytes32)")] #[precompile::payable] fn register_network(handle: &mut impl PrecompileHandle, hotkey: H256) -> EvmResult<()> { - let (hotkey, _) = parse_pubkey(hotkey.as_bytes())?; - let call = pallet_subtensor::Call::::register_network_with_identity { + let hotkey = R::AccountId::from(hotkey.0); + let call = pallet_subtensor::Call::::register_network_with_identity { hotkey, identity: None, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public( @@ -48,7 +73,7 @@ impl SubnetPrecompile { description: BoundedString>, additional: BoundedString>, ) -> EvmResult<()> { - let (hotkey, _) = parse_pubkey(hotkey.as_bytes())?; + let hotkey = R::AccountId::from(hotkey.0); let identity = pallet_subtensor::SubnetIdentityOfV2 { subnet_name: subnet_name.into(), github_repo: github_repo.into(), @@ -59,18 +84,21 @@ impl SubnetPrecompile { additional: additional.into(), }; - let call = pallet_subtensor::Call::::register_network_with_identity { + let call = pallet_subtensor::Call::::register_network_with_identity { hotkey, identity: Some(identity), }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getServingRateLimit(uint16)")] #[precompile::view] fn get_serving_rate_limit(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::ServingRateLimit::::get(netuid)) + Ok(pallet_subtensor::ServingRateLimit::::get(netuid)) } #[precompile::public("setServingRateLimit(uint16,uint64)")] @@ -80,18 +108,21 @@ impl SubnetPrecompile { netuid: u16, serving_rate_limit: u64, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_serving_rate_limit { + let call = pallet_admin_utils::Call::::sudo_set_serving_rate_limit { netuid, serving_rate_limit, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getMinDifficulty(uint16)")] #[precompile::view] fn get_min_difficulty(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::MinDifficulty::::get(netuid)) + Ok(pallet_subtensor::MinDifficulty::::get(netuid)) } #[precompile::public("setMinDifficulty(uint16,uint64)")] @@ -101,18 +132,21 @@ impl SubnetPrecompile { netuid: u16, min_difficulty: u64, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_min_difficulty { + let call = pallet_admin_utils::Call::::sudo_set_min_difficulty { netuid, min_difficulty, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getMaxDifficulty(uint16)")] #[precompile::view] fn get_max_difficulty(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::MaxDifficulty::::get(netuid)) + Ok(pallet_subtensor::MaxDifficulty::::get(netuid)) } #[precompile::public("setMaxDifficulty(uint16,uint64)")] @@ -122,18 +156,21 @@ impl SubnetPrecompile { netuid: u16, max_difficulty: u64, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_max_difficulty { + let call = pallet_admin_utils::Call::::sudo_set_max_difficulty { netuid, max_difficulty, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getWeightsVersionKey(uint16)")] #[precompile::view] fn get_weights_version_key(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::WeightsVersionKey::::get(netuid)) + Ok(pallet_subtensor::WeightsVersionKey::::get(netuid)) } #[precompile::public("setWeightsVersionKey(uint16,uint64)")] @@ -143,20 +180,21 @@ impl SubnetPrecompile { netuid: u16, weights_version_key: u64, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_weights_version_key { + let call = pallet_admin_utils::Call::::sudo_set_weights_version_key { netuid, weights_version_key, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getWeightsSetRateLimit(uint16)")] #[precompile::view] fn get_weights_set_rate_limit(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::WeightsSetRateLimit::::get( - netuid, - )) + Ok(pallet_subtensor::WeightsSetRateLimit::::get(netuid)) } #[precompile::public("setWeightsSetRateLimit(uint16,uint64)")] @@ -166,18 +204,21 @@ impl SubnetPrecompile { netuid: u16, weights_set_rate_limit: u64, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_weights_set_rate_limit { + let call = pallet_admin_utils::Call::::sudo_set_weights_set_rate_limit { netuid, weights_set_rate_limit, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getAdjustmentAlpha(uint16)")] #[precompile::view] fn get_adjustment_alpha(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::AdjustmentAlpha::::get(netuid)) + Ok(pallet_subtensor::AdjustmentAlpha::::get(netuid)) } #[precompile::public("setAdjustmentAlpha(uint16,uint64)")] @@ -187,18 +228,21 @@ impl SubnetPrecompile { netuid: u16, adjustment_alpha: u64, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_adjustment_alpha { + let call = pallet_admin_utils::Call::::sudo_set_adjustment_alpha { netuid, adjustment_alpha, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getMaxWeightLimit(uint16)")] #[precompile::view] fn get_max_weight_limit(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::MaxWeightsLimit::::get(netuid)) + Ok(pallet_subtensor::MaxWeightsLimit::::get(netuid)) } #[precompile::public("setMaxWeightLimit(uint16,uint16)")] @@ -208,18 +252,21 @@ impl SubnetPrecompile { netuid: u16, max_weight_limit: u16, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_max_weight_limit { + let call = pallet_admin_utils::Call::::sudo_set_max_weight_limit { netuid, max_weight_limit, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getImmunityPeriod(uint16)")] #[precompile::view] fn get_immunity_period(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::ImmunityPeriod::::get(netuid)) + Ok(pallet_subtensor::ImmunityPeriod::::get(netuid)) } #[precompile::public("setImmunityPeriod(uint16,uint16)")] @@ -229,18 +276,21 @@ impl SubnetPrecompile { netuid: u16, immunity_period: u16, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_immunity_period { + let call = pallet_admin_utils::Call::::sudo_set_immunity_period { netuid, immunity_period, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getMinAllowedWeights(uint16)")] #[precompile::view] fn get_min_allowed_weights(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::MinAllowedWeights::::get(netuid)) + Ok(pallet_subtensor::MinAllowedWeights::::get(netuid)) } #[precompile::public("setMinAllowedWeights(uint16,uint16)")] @@ -250,46 +300,55 @@ impl SubnetPrecompile { netuid: u16, min_allowed_weights: u16, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_min_allowed_weights { + let call = pallet_admin_utils::Call::::sudo_set_min_allowed_weights { netuid, min_allowed_weights, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getKappa(uint16)")] #[precompile::view] fn get_kappa(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::Kappa::::get(netuid)) + Ok(pallet_subtensor::Kappa::::get(netuid)) } #[precompile::public("setKappa(uint16,uint16)")] #[precompile::payable] fn set_kappa(handle: &mut impl PrecompileHandle, netuid: u16, kappa: u16) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_kappa { netuid, kappa }; + let call = pallet_admin_utils::Call::::sudo_set_kappa { netuid, kappa }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getRho(uint16)")] #[precompile::view] fn get_rho(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::Rho::::get(netuid)) + Ok(pallet_subtensor::Rho::::get(netuid)) } #[precompile::public("setRho(uint16,uint16)")] #[precompile::payable] fn set_rho(handle: &mut impl PrecompileHandle, netuid: u16, rho: u16) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_rho { netuid, rho }; + let call = pallet_admin_utils::Call::::sudo_set_rho { netuid, rho }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getActivityCutoff(uint16)")] #[precompile::view] fn get_activity_cutoff(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::ActivityCutoff::::get(netuid)) + Ok(pallet_subtensor::ActivityCutoff::::get(netuid)) } #[precompile::public("setActivityCutoff(uint16,uint16)")] @@ -299,12 +358,15 @@ impl SubnetPrecompile { netuid: u16, activity_cutoff: u16, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_activity_cutoff { + let call = pallet_admin_utils::Call::::sudo_set_activity_cutoff { netuid, activity_cutoff, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getNetworkRegistrationAllowed(uint16)")] @@ -313,7 +375,9 @@ impl SubnetPrecompile { _: &mut impl PrecompileHandle, netuid: u16, ) -> EvmResult { - Ok(pallet_subtensor::NetworkRegistrationAllowed::::get(netuid)) + Ok(pallet_subtensor::NetworkRegistrationAllowed::::get( + netuid, + )) } #[precompile::public("setNetworkRegistrationAllowed(uint16,bool)")] @@ -323,12 +387,15 @@ impl SubnetPrecompile { netuid: u16, registration_allowed: bool, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_network_registration_allowed { + let call = pallet_admin_utils::Call::::sudo_set_network_registration_allowed { netuid, registration_allowed, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getNetworkPowRegistrationAllowed(uint16)")] @@ -337,7 +404,9 @@ impl SubnetPrecompile { _: &mut impl PrecompileHandle, netuid: u16, ) -> EvmResult { - Ok(pallet_subtensor::NetworkPowRegistrationAllowed::::get(netuid)) + Ok(pallet_subtensor::NetworkPowRegistrationAllowed::::get( + netuid, + )) } #[precompile::public("setNetworkPowRegistrationAllowed(uint16,bool)")] @@ -347,18 +416,21 @@ impl SubnetPrecompile { netuid: u16, registration_allowed: bool, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_network_pow_registration_allowed { + let call = pallet_admin_utils::Call::::sudo_set_network_pow_registration_allowed { netuid, registration_allowed, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getMinBurn(uint16)")] #[precompile::view] fn get_min_burn(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::MinBurn::::get(netuid)) + Ok(pallet_subtensor::MinBurn::::get(netuid)) } #[precompile::public("setMinBurn(uint16,uint64)")] @@ -368,15 +440,18 @@ impl SubnetPrecompile { netuid: u16, min_burn: u64, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_min_burn { netuid, min_burn }; + let call = pallet_admin_utils::Call::::sudo_set_min_burn { netuid, min_burn }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getMaxBurn(uint16)")] #[precompile::view] fn get_max_burn(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::MaxBurn::::get(netuid)) + Ok(pallet_subtensor::MaxBurn::::get(netuid)) } #[precompile::public("setMaxBurn(uint16,uint64)")] @@ -386,15 +461,18 @@ impl SubnetPrecompile { netuid: u16, max_burn: u64, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_max_burn { netuid, max_burn }; + let call = pallet_admin_utils::Call::::sudo_set_max_burn { netuid, max_burn }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getDifficulty(uint16)")] #[precompile::view] fn get_difficulty(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::Difficulty::::get(netuid)) + Ok(pallet_subtensor::Difficulty::::get(netuid)) } #[precompile::public("setDifficulty(uint16,uint64)")] @@ -404,15 +482,18 @@ impl SubnetPrecompile { netuid: u16, difficulty: u64, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_difficulty { netuid, difficulty }; + let call = pallet_admin_utils::Call::::sudo_set_difficulty { netuid, difficulty }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getBondsMovingAverage(uint16)")] #[precompile::view] fn get_bonds_moving_average(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::BondsMovingAverage::::get(netuid)) + Ok(pallet_subtensor::BondsMovingAverage::::get(netuid)) } #[precompile::public("setBondsMovingAverage(uint16,uint64)")] @@ -422,12 +503,15 @@ impl SubnetPrecompile { netuid: u16, bonds_moving_average: u64, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_bonds_moving_average { + let call = pallet_admin_utils::Call::::sudo_set_bonds_moving_average { netuid, bonds_moving_average, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getCommitRevealWeightsEnabled(uint16)")] @@ -436,7 +520,9 @@ impl SubnetPrecompile { _: &mut impl PrecompileHandle, netuid: u16, ) -> EvmResult { - Ok(pallet_subtensor::CommitRevealWeightsEnabled::::get(netuid)) + Ok(pallet_subtensor::CommitRevealWeightsEnabled::::get( + netuid, + )) } #[precompile::public("setCommitRevealWeightsEnabled(uint16,bool)")] @@ -446,18 +532,21 @@ impl SubnetPrecompile { netuid: u16, enabled: bool, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_commit_reveal_weights_enabled { + let call = pallet_admin_utils::Call::::sudo_set_commit_reveal_weights_enabled { netuid, enabled, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getLiquidAlphaEnabled(uint16)")] #[precompile::view] fn get_liquid_alpha_enabled(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::LiquidAlphaOn::::get(netuid)) + Ok(pallet_subtensor::LiquidAlphaOn::::get(netuid)) } #[precompile::public("setLiquidAlphaEnabled(uint16,bool)")] @@ -467,16 +556,18 @@ impl SubnetPrecompile { netuid: u16, enabled: bool, ) -> EvmResult<()> { - let call = - pallet_admin_utils::Call::::sudo_set_liquid_alpha_enabled { netuid, enabled }; + let call = pallet_admin_utils::Call::::sudo_set_liquid_alpha_enabled { netuid, enabled }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getAlphaValues(uint16)")] #[precompile::view] fn get_alpha_values(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult<(u16, u16)> { - Ok(pallet_subtensor::AlphaValues::::get(netuid)) + Ok(pallet_subtensor::AlphaValues::::get(netuid)) } #[precompile::public("setAlphaValues(uint16,uint16,uint16)")] @@ -487,13 +578,16 @@ impl SubnetPrecompile { alpha_low: u16, alpha_high: u16, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_alpha_values { + let call = pallet_admin_utils::Call::::sudo_set_alpha_values { netuid, alpha_low, alpha_high, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } #[precompile::public("getCommitRevealWeightsInterval(uint16)")] @@ -502,7 +596,7 @@ impl SubnetPrecompile { _: &mut impl PrecompileHandle, netuid: u16, ) -> EvmResult { - Ok(pallet_subtensor::RevealPeriodEpochs::::get(netuid)) + Ok(pallet_subtensor::RevealPeriodEpochs::::get(netuid)) } #[precompile::public("setCommitRevealWeightsInterval(uint16,uint64)")] @@ -512,11 +606,14 @@ impl SubnetPrecompile { netuid: u16, interval: u64, ) -> EvmResult<()> { - let call = pallet_admin_utils::Call::::sudo_set_commit_reveal_weights_interval { + let call = pallet_admin_utils::Call::::sudo_set_commit_reveal_weights_interval { netuid, interval, }; - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(handle.caller_account_id())) + handle.try_dispatch_runtime_call::( + call, + RawOrigin::Signed(handle.caller_account_id::()), + ) } } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index e28b7ba1b2..67add266a4 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -20,11 +20,8 @@ name = "spec_version" path = "src/spec_version.rs" [dependencies] -ed25519-dalek = { workspace = true, default-features = false, features = [ - "alloc", -] } subtensor-macros.workspace = true -subtensor-custom-rpc-runtime-api = { path = "../pallets/subtensor/runtime-api", default-features = false } +subtensor-custom-rpc-runtime-api = { workspace = true } smallvec = { workspace = true } log = { workspace = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ @@ -34,7 +31,7 @@ scale-info = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["alloc"] } pallet-aura = { workspace = true } pallet-balances = { workspace = true } -pallet-subtensor = { default-features = false, path = "../pallets/subtensor" } +pallet-subtensor = { workspace = true } frame-support = { workspace = true } pallet-grandpa = { workspace = true } pallet-insecure-randomness-collective-flip = { workspace = true } @@ -58,14 +55,16 @@ sp-session = { workspace = true } sp-std = { workspace = true } sp-transaction-pool = { workspace = true } sp-version = { workspace = true } +subtensor-runtime-common = { workspace = true } +subtensor-precompiles = { workspace = true } # Temporary sudo pallet-sudo = { workspace = true } -pallet-admin-utils = { default-features = false, path = "../pallets/admin-utils" } +pallet-admin-utils = { workspace = true } # Used for sudo decentralization -pallet-collective = { default-features = false, path = "../pallets/collective" } +pallet-collective = { workspace = true } pallet-membership = { workspace = true } # Multisig @@ -91,10 +90,10 @@ frame-benchmarking = { workspace = true, optional = true } frame-system-benchmarking = { workspace = true, optional = true } # Identity registry pallet for registering project info -pallet-registry = { default-features = false, path = "../pallets/registry" } +pallet-registry = { workspace = true } # Metadata commitment pallet -pallet-commitments = { default-features = false, path = "../pallets/commitments" } +pallet-commitments = { workspace = true } # Frontier fp-evm = { workspace = true } @@ -134,13 +133,15 @@ substrate-wasm-builder = { workspace = true, optional = true } [features] default = ["std"] pow-faucet = ["pallet-subtensor/pow-faucet"] -fast-blocks = ["pallet-subtensor/fast-blocks"] +fast-blocks = [ + "pallet-subtensor/fast-blocks", + "subtensor-runtime-common/fast-blocks", +] std = [ "frame-try-runtime?/std", "frame-system-benchmarking?/std", "frame-benchmarking/std", "codec/std", - "ed25519-dalek/std", "scale-info/std", "frame-executive/std", "frame-metadata-hash-extension/std", @@ -188,6 +189,8 @@ std = [ "log/std", "sp-storage/std", "sp-genesis-builder/std", + "subtensor-precompiles/std", + "subtensor-runtime-common/std", # Frontier "fp-evm/std", "fp-rpc/std", diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a8774795b9..a938615df2 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -11,7 +11,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); pub mod check_nonce; mod migrations; -use codec::{Compact, Decode, Encode, MaxEncodedLen}; +use codec::{Compact, Decode, Encode}; use frame_support::traits::Imbalance; use frame_support::{ dispatch::DispatchResultWithPostInfo, @@ -39,7 +39,6 @@ use pallet_subtensor::rpc_info::{ stake_info::StakeInfo, subnet_info::{SubnetHyperparams, SubnetInfo, SubnetInfov2}, }; -use scale_info::TypeInfo; use smallvec::smallvec; use sp_api::impl_runtime_apis; use sp_consensus_aura::sr25519::AuthorityId as AuraId; @@ -49,11 +48,11 @@ use sp_core::{ }; use sp_runtime::generic::Era; use sp_runtime::{ - AccountId32, ApplyExtrinsicResult, ConsensusEngineId, MultiSignature, create_runtime_str, - generic, impl_opaque_keys, + AccountId32, ApplyExtrinsicResult, ConsensusEngineId, create_runtime_str, generic, + impl_opaque_keys, traits::{ - AccountIdLookup, BlakeTwo256, Block as BlockT, DispatchInfoOf, Dispatchable, - IdentifyAccount, NumberFor, One, PostDispatchInfoOf, UniqueSaturatedInto, Verify, + AccountIdLookup, BlakeTwo256, Block as BlockT, DispatchInfoOf, Dispatchable, NumberFor, + One, PostDispatchInfoOf, UniqueSaturatedInto, Verify, }, transaction_validity::{TransactionSource, TransactionValidity, TransactionValidityError}, }; @@ -62,6 +61,8 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; +use subtensor_precompiles::Precompiles; +use subtensor_runtime_common::{time::*, *}; // A few exports that help ease life for downstream crates. pub use frame_support::{ @@ -88,9 +89,6 @@ pub use sp_runtime::{Perbill, Permill}; use core::marker::PhantomData; -mod precompiles; -use precompiles::FrontierPrecompiles; - // Frontier use fp_rpc::TransactionStatus; use pallet_ethereum::{Call::transact, PostLogContent, Transaction as EthereumTransaction}; @@ -159,30 +157,9 @@ impl frame_system::offchain::CreateSignedTransaction pub use pallet_scheduler; pub use pallet_subtensor; -// An index to a block. -pub type BlockNumber = u32; - -// Alias to 512-bit hash when used in the context of a transaction signature on the chain. -pub type Signature = MultiSignature; - -// Some way of identifying an account on the chain. We intentionally make it equivalent -// to the public key of our transaction signing scheme. -pub type AccountId = <::Signer as IdentifyAccount>::AccountId; - -// Balance of an account. -pub type Balance = u64; - -// Index of a transaction in the chain. -pub type Index = u32; - -// A hash of some data used by the chain. -pub type Hash = sp_core::H256; - // Member type for membership type MemberCount = u32; -pub type Nonce = u32; - // Method used to calculate the fee of an extrinsic pub const fn deposit(items: u32, bytes: u32) -> Balance { pub const ITEMS_FEE: Balance = 2_000 * 10_000; @@ -228,35 +205,13 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 244, + spec_version: 247, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, state_version: 1, }; -/// This determines the average expected block time that we are targeting. -/// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. -/// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked -/// up by `pallet_aura` to implement `fn slot_duration()`. -/// -/// Change this to adjust the block time. -#[cfg(not(feature = "fast-blocks"))] -pub const MILLISECS_PER_BLOCK: u64 = 12000; - -/// Fast blocks for development -#[cfg(feature = "fast-blocks")] -pub const MILLISECS_PER_BLOCK: u64 = 250; - -// NOTE: Currently it is not possible to change the slot duration after the chain has started. -// Attempting to do so will brick block production. -pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; - -// Time is measured by number of blocks. -pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); -pub const HOURS: BlockNumber = MINUTES * 60; -pub const DAYS: BlockNumber = HOURS * 24; - pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(4u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX); @@ -694,33 +649,6 @@ parameter_types! { pub const AnnouncementDepositFactor: Balance = deposit(0, 68); } -#[derive( - Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, MaxEncodedLen, TypeInfo, -)] -pub enum ProxyType { - Any, - Owner, // Subnet owner Calls - NonCritical, - NonTransfer, - Senate, - NonFungibile, // Nothing involving moving TAO - Triumvirate, - Governance, // Both above governance - Staking, - Registration, - Transfer, - SmallTransfer, - RootWeights, - ChildKeys, - SudoUncheckedSetCode, -} -// Transfers below SMALL_TRANSFER_LIMIT are considered small transfers -pub const SMALL_TRANSFER_LIMIT: Balance = 500_000_000; // 0.5 TAO -impl Default for ProxyType { - fn default() -> Self { - Self::Any - } -} // allow all Calls; required to be most permissive impl InstanceFilter for ProxyType { fn filter(&self, c: &RuntimeCall) -> bool { match self { @@ -1078,17 +1006,18 @@ parameter_types! { pub const SubtensorInitialMinAllowedUids: u16 = 128; pub const SubtensorInitialMinLockCost: u64 = 1_000_000_000_000; // 1000 TAO pub const SubtensorInitialSubnetOwnerCut: u16 = 11_796; // 18 percent - pub const SubtensorInitialSubnetLimit: u16 = 12; + // pub const SubtensorInitialSubnetLimit: u16 = 12; // (DEPRECATED) pub const SubtensorInitialNetworkLockReductionInterval: u64 = 14 * 7200; pub const SubtensorInitialNetworkRateLimit: u64 = 7200; pub const SubtensorInitialKeySwapCost: u64 = 100_000_000; // 0.1 TAO pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn - pub const SubtensorInitialNetworkMaxStake: u64 = u64::MAX; // Maximum possible value for u64, this make the make stake infinity + // pub const SubtensorInitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialDissolveNetworkScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const SubtensorInitialTaoWeight: u64 = 971_718_665_099_567_868; // 0.05267697438728329% tao weight. + pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks } impl pallet_subtensor::Config for Runtime { @@ -1143,17 +1072,16 @@ impl pallet_subtensor::Config for Runtime { type InitialNetworkMinLockCost = SubtensorInitialMinLockCost; type InitialNetworkLockReductionInterval = SubtensorInitialNetworkLockReductionInterval; type InitialSubnetOwnerCut = SubtensorInitialSubnetOwnerCut; - type InitialSubnetLimit = SubtensorInitialSubnetLimit; type InitialNetworkRateLimit = SubtensorInitialNetworkRateLimit; type KeySwapCost = SubtensorInitialKeySwapCost; type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; - type InitialNetworkMaxStake = SubtensorInitialNetworkMaxStake; type InitialTaoWeight = SubtensorInitialTaoWeight; type Preimages = Preimage; type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; + type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; } use sp_runtime::BoundedVec; @@ -1232,7 +1160,7 @@ fn weight_per_gas() -> Weight { parameter_types! { pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); pub const GasLimitPovSizeRatio: u64 = 0; - pub PrecompilesValue: FrontierPrecompiles = FrontierPrecompiles::<_>::new(); + pub PrecompilesValue: Precompiles = Precompiles::<_>::new(); pub WeightPerGas: Weight = weight_per_gas(); pub SuicideQuickClearLimit: u32 = 0; } @@ -1304,7 +1232,7 @@ impl pallet_evm::Config for Runtime { type AddressMapping = pallet_evm::HashedAddressMapping; type Currency = Balances; type RuntimeEvent = RuntimeEvent; - type PrecompilesType = FrontierPrecompiles; + type PrecompilesType = Precompiles; type PrecompilesValue = PrecompilesValue; type ChainId = ConfigurableChainId; type BlockGasLimit = BlockGasLimit; diff --git a/runtime/src/precompiles/balance_transfer.rs b/runtime/src/precompiles/balance_transfer.rs deleted file mode 100644 index c1b68d4f74..0000000000 --- a/runtime/src/precompiles/balance_transfer.rs +++ /dev/null @@ -1,40 +0,0 @@ -use pallet_evm::PrecompileHandle; -use precompile_utils::EvmResult; -use sp_core::H256; -use sp_runtime::traits::UniqueSaturatedInto; - -use crate::Runtime; -use crate::precompiles::{PrecompileExt, PrecompileHandleExt, contract_to_origin, parse_pubkey}; - -pub struct BalanceTransferPrecompile; - -impl PrecompileExt for BalanceTransferPrecompile { - const INDEX: u64 = 2048; - const ADDRESS_SS58: [u8; 32] = [ - 0x07, 0xec, 0x71, 0x2a, 0x5d, 0x38, 0x43, 0x4d, 0xdd, 0x03, 0x3f, 0x8f, 0x02, 0x4e, 0xcd, - 0xfc, 0x4b, 0xb5, 0x95, 0x1c, 0x13, 0xc3, 0x08, 0x5c, 0x39, 0x9c, 0x8a, 0x5f, 0x62, 0x93, - 0x70, 0x5d, - ]; -} - -#[precompile_utils::precompile] -impl BalanceTransferPrecompile { - #[precompile::public("transfer(bytes32)")] - #[precompile::payable] - fn transfer(handle: &mut impl PrecompileHandle, address: H256) -> EvmResult<()> { - let amount_sub = handle.try_convert_apparent_value()?; - - if amount_sub.is_zero() { - return Ok(()); - } - - let dest = parse_pubkey(address.as_bytes())?.0.into(); - - let call = pallet_balances::Call::::transfer_allow_death { - dest, - value: amount_sub.unique_saturated_into(), - }; - - handle.try_dispatch_runtime_call(call, contract_to_origin(&Self::ADDRESS_SS58)?) - } -} diff --git a/runtime/src/precompiles/mod.rs b/runtime/src/precompiles/mod.rs deleted file mode 100644 index bba70eb027..0000000000 --- a/runtime/src/precompiles/mod.rs +++ /dev/null @@ -1,292 +0,0 @@ -extern crate alloc; - -use alloc::format; -use core::marker::PhantomData; - -use frame_support::dispatch::{GetDispatchInfo, Pays}; -use frame_system::RawOrigin; -use pallet_evm::{ - AddressMapping, BalanceConverter, ExitError, GasWeightMapping, HashedAddressMapping, - IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle, PrecompileResult, - PrecompileSet, -}; -use pallet_evm_precompile_modexp::Modexp; -use pallet_evm_precompile_sha3fips::Sha3FIPS256; -use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; -use precompile_utils::EvmResult; -use sp_core::{H160, U256}; -use sp_runtime::traits::BlakeTwo256; -use sp_runtime::{AccountId32, traits::Dispatchable}; - -use pallet_admin_utils::{PrecompileEnable, PrecompileEnum}; -use sp_std::vec; - -use crate::{Runtime, RuntimeCall}; - -// Include custom precompiles -mod balance_transfer; -mod ed25519; -mod metagraph; -mod neuron; -mod staking; -mod subnet; - -use balance_transfer::*; -use ed25519::*; -use metagraph::*; -use neuron::*; -use staking::*; -use subnet::*; - -pub struct FrontierPrecompiles(PhantomData); -impl Default for FrontierPrecompiles -where - R: pallet_evm::Config, -{ - fn default() -> Self { - Self::new() - } -} - -impl FrontierPrecompiles -where - R: pallet_evm::Config, -{ - pub fn new() -> Self { - Self(Default::default()) - } - pub fn used_addresses() -> [H160; 13] { - [ - hash(1), - hash(2), - hash(3), - hash(4), - hash(5), - hash(1024), - hash(1025), - hash(Ed25519Verify::INDEX), - hash(BalanceTransferPrecompile::INDEX), - hash(StakingPrecompile::INDEX), - hash(SubnetPrecompile::INDEX), - hash(MetagraphPrecompile::INDEX), - hash(NeuronPrecompile::INDEX), - ] - } -} -impl PrecompileSet for FrontierPrecompiles -where - R: pallet_evm::Config, -{ - fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { - match handle.code_address() { - // Ethereum precompiles : - a if a == hash(1) => Some(ECRecover::execute(handle)), - a if a == hash(2) => Some(Sha256::execute(handle)), - a if a == hash(3) => Some(Ripemd160::execute(handle)), - a if a == hash(4) => Some(Identity::execute(handle)), - a if a == hash(5) => Some(Modexp::execute(handle)), - // Non-Frontier specific nor Ethereum precompiles : - a if a == hash(1024) => Some(Sha3FIPS256::execute(handle)), - a if a == hash(1025) => Some(ECRecoverPublicKey::execute(handle)), - a if a == hash(Ed25519Verify::INDEX) => Some(Ed25519Verify::execute(handle)), - // Subtensor specific precompiles : - a if a == hash(BalanceTransferPrecompile::INDEX) => { - if PrecompileEnable::::get(PrecompileEnum::BalanceTransfer) { - Some(BalanceTransferPrecompile::execute(handle)) - } else { - Some(Err(PrecompileFailure::Error { - exit_status: ExitError::Other( - "Precompile Balance Transfer is disabled".into(), - ), - })) - } - } - a if a == hash(StakingPrecompile::INDEX) => { - if PrecompileEnable::::get(PrecompileEnum::Staking) { - Some(StakingPrecompile::execute(handle)) - } else { - Some(Err(PrecompileFailure::Error { - exit_status: ExitError::Other( - "Precompile Balance Transfer is disabled".into(), - ), - })) - } - } - - a if a == hash(SubnetPrecompile::INDEX) => { - if PrecompileEnable::::get(PrecompileEnum::Subnet) { - Some(SubnetPrecompile::execute(handle)) - } else { - Some(Err(PrecompileFailure::Error { - exit_status: ExitError::Other("Precompile Subnet is disabled".into()), - })) - } - } - a if a == hash(MetagraphPrecompile::INDEX) => { - if PrecompileEnable::::get(PrecompileEnum::Metagraph) { - Some(MetagraphPrecompile::execute(handle)) - } else { - Some(Err(PrecompileFailure::Error { - exit_status: ExitError::Other("Precompile Metagrah is disabled".into()), - })) - } - } - a if a == hash(NeuronPrecompile::INDEX) => { - if PrecompileEnable::::get(PrecompileEnum::Neuron) { - Some(NeuronPrecompile::execute(handle)) - } else { - Some(Err(PrecompileFailure::Error { - exit_status: ExitError::Other("Precompile Neuron is disabled".into()), - })) - } - } - - _ => None, - } - } - - fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { - IsPrecompileResult::Answer { - is_precompile: Self::used_addresses().contains(&address), - extra_cost: 0, - } - } -} - -fn hash(a: u64) -> H160 { - H160::from_low_u64_be(a) -} - -/// Takes a slice from bytes with PrecompileFailure as Error -fn parse_slice(data: &[u8], from: usize, to: usize) -> Result<&[u8], PrecompileFailure> { - let maybe_slice = data.get(from..to); - if let Some(slice) = maybe_slice { - Ok(slice) - } else { - log::error!( - "fail to get slice from data, {:?}, from {}, to {}", - &data, - from, - to - ); - Err(PrecompileFailure::Error { - exit_status: ExitError::InvalidRange, - }) - } -} - -fn parse_pubkey(data: &[u8]) -> Result<(AccountId32, vec::Vec), PrecompileFailure> { - let mut pubkey = [0u8; 32]; - pubkey.copy_from_slice(parse_slice(data, 0, 32)?); - - Ok(( - pubkey.into(), - data.get(32..) - .map_or_else(vec::Vec::new, |slice| slice.to_vec()), - )) -} - -fn try_u16_from_u256(value: U256) -> Result { - value.try_into().map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other("the value is outside of u16 bounds".into()), - }) -} - -fn contract_to_origin(contract: &[u8; 32]) -> Result, PrecompileFailure> { - let (account_id, _) = parse_pubkey(contract)?; - Ok(RawOrigin::Signed(account_id)) -} - -trait PrecompileHandleExt: PrecompileHandle { - fn caller_account_id(&self) -> AccountId32 { - as AddressMapping>::into_account_id( - self.context().caller, - ) - } - - fn try_convert_apparent_value(&self) -> EvmResult { - let amount = self.context().apparent_value; - ::BalanceConverter::into_substrate_balance(amount).ok_or( - PrecompileFailure::Error { - exit_status: ExitError::Other( - "error converting balance from ETH to subtensor".into(), - ), - }, - ) - } - - /// Dispatches a runtime call, but also checks and records the gas costs. - fn try_dispatch_runtime_call( - &mut self, - call: impl Into, - origin: RawOrigin, - ) -> EvmResult<()> { - let call = Into::::into(call); - let info = call.get_dispatch_info(); - - let target_gas = self.gas_limit(); - if let Some(gas) = target_gas { - let valid_weight = - ::GasWeightMapping::gas_to_weight(gas, false) - .ref_time(); - if info.weight.ref_time() > valid_weight { - return Err(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - }); - } - } - - self.record_external_cost( - Some(info.weight.ref_time()), - Some(info.weight.proof_size()), - None, - )?; - - match call.dispatch(origin.into()) { - Ok(post_info) => { - if post_info.pays_fee(&info) == Pays::Yes { - let actual_weight = post_info.actual_weight.unwrap_or(info.weight); - let cost = ::GasWeightMapping::weight_to_gas( - actual_weight, - ); - self.record_cost(cost)?; - - self.refund_external_cost( - Some( - info.weight - .ref_time() - .saturating_sub(actual_weight.ref_time()), - ), - Some( - info.weight - .proof_size() - .saturating_sub(actual_weight.proof_size()), - ), - ); - } - - log::info!("Dispatch succeeded. Post info: {:?}", post_info); - - Ok(()) - } - Err(e) => { - log::error!("Dispatch failed. Error: {:?}", e); - log::warn!("Returning error PrecompileFailure::Error"); - Err(PrecompileFailure::Error { - exit_status: ExitError::Other( - format!("dispatch execution failed: {}", <&'static str>::from(e)).into(), - ), - }) - } - } - } -} - -impl PrecompileHandleExt for T where T: PrecompileHandle {} - -trait PrecompileExt: Precompile { - const INDEX: u64; - // ss58 public key i.e., the contract sends funds it received to the destination address from - // the method parameter. - const ADDRESS_SS58: [u8; 32]; -} diff --git a/runtime/src/precompiles/staking.rs b/runtime/src/precompiles/staking.rs deleted file mode 100644 index 848fe8e750..0000000000 --- a/runtime/src/precompiles/staking.rs +++ /dev/null @@ -1,171 +0,0 @@ -// The goal of staking precompile is to allow interaction between EVM users and smart contracts and -// subtensor staking functionality, namely add_stake, and remove_stake extrinsicsk, as well as the -// staking state. -// -// Additional requirement is to preserve compatibility with Ethereum indexers, which requires -// no balance transfers from EVM accounts without a corresponding transaction that can be -// parsed by an indexer. -// -// Implementation of add_stake: -// - User transfers balance that will be staked to the precompile address with a payable -// method addStake. This method also takes hotkey public key (bytes32) of the hotkey -// that the stake should be assigned to. -// - Precompile transfers the balance back to the signing address, and then invokes -// do_add_stake from subtensor pallet with signing origin that mmatches to HashedAddressMapping -// of the message sender, which will effectively withdraw and stake balance from the message -// sender. -// - Precompile checks the result of do_add_stake and, in case of a failure, reverts the transaction, -// and leaves the balance on the message sender account. -// -// Implementation of remove_stake: -// - User involkes removeStake method and specifies hotkey public key (bytes32) of the hotkey -// to remove stake from, and the amount to unstake. -// - Precompile calls do_remove_stake method of the subtensor pallet with the signing origin of message -// sender, which effectively unstakes the specified amount and credits it to the message sender -// - Precompile checks the result of do_remove_stake and, in case of a failure, reverts the transaction. -// - -use frame_system::RawOrigin; -use pallet_evm::{BalanceConverter, ExitError, PrecompileFailure, PrecompileHandle}; -use precompile_utils::EvmResult; -use sp_core::{H256, U256}; -use sp_runtime::AccountId32; -use sp_runtime::traits::{Dispatchable, StaticLookup, UniqueSaturatedInto}; - -use crate::precompiles::{PrecompileExt, PrecompileHandleExt, parse_pubkey, try_u16_from_u256}; -use crate::{ProxyType, Runtime, RuntimeCall}; - -pub struct StakingPrecompile; - -impl PrecompileExt for StakingPrecompile { - const INDEX: u64 = 2049; - const ADDRESS_SS58: [u8; 32] = [ - 0x26, 0xf4, 0x10, 0x1e, 0x52, 0xb7, 0x57, 0x34, 0x33, 0x24, 0x5b, 0xc3, 0x0a, 0xe1, 0x8b, - 0x63, 0x99, 0x53, 0xd8, 0x41, 0x79, 0x33, 0x03, 0x61, 0x4d, 0xfa, 0xcf, 0xf0, 0x37, 0xf7, - 0x12, 0x94, - ]; -} - -#[precompile_utils::precompile] -impl StakingPrecompile { - #[precompile::public("addStake(bytes32,uint256)")] - #[precompile::payable] - fn add_stake(handle: &mut impl PrecompileHandle, address: H256, netuid: U256) -> EvmResult<()> { - let account_id = handle.caller_account_id(); - let amount = handle.context().apparent_value; - - if !amount.is_zero() { - Self::transfer_back_to_caller(&account_id, amount)?; - } - - let amount_sub = handle.try_convert_apparent_value()?; - let (hotkey, _) = parse_pubkey(address.as_bytes())?; - let netuid = try_u16_from_u256(netuid)?; - let call = pallet_subtensor::Call::::add_stake { - hotkey, - netuid, - amount_staked: amount_sub.unique_saturated_into(), - }; - - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(account_id)) - } - - #[precompile::public("removeStake(bytes32,uint256,uint256)")] - fn remove_stake( - handle: &mut impl PrecompileHandle, - address: H256, - amount: U256, - netuid: U256, - ) -> EvmResult<()> { - let account_id = handle.caller_account_id(); - let (hotkey, _) = parse_pubkey(address.as_bytes())?; - let netuid = try_u16_from_u256(netuid)?; - let amount_unstaked = amount.unique_saturated_into(); - let call = pallet_subtensor::Call::::remove_stake { - hotkey, - netuid, - amount_unstaked, - }; - - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(account_id)) - } - - #[precompile::public("addProxy(bytes32)")] - fn add_proxy(handle: &mut impl PrecompileHandle, delegate: H256) -> EvmResult<()> { - let account_id = handle.caller_account_id(); - let (delegate, _) = parse_pubkey(delegate.as_bytes())?; - let delegate = ::Lookup::unlookup(delegate); - let call = pallet_proxy::Call::::add_proxy { - delegate, - proxy_type: ProxyType::Staking, - delay: 0, - }; - - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(account_id)) - } - - #[precompile::public("removeProxy(bytes32)")] - fn remove_proxy(handle: &mut impl PrecompileHandle, delegate: H256) -> EvmResult<()> { - let account_id = handle.caller_account_id(); - let (delegate, _) = parse_pubkey(delegate.as_bytes())?; - let delegate = ::Lookup::unlookup(delegate); - let call = pallet_proxy::Call::::remove_proxy { - delegate, - proxy_type: ProxyType::Staking, - delay: 0, - }; - - handle.try_dispatch_runtime_call(call, RawOrigin::Signed(account_id)) - } - - #[precompile::public("getStake(bytes32,bytes32,uint256)")] - #[precompile::view] - fn get_stake( - _: &mut impl PrecompileHandle, - hotkey: H256, - coldkey: H256, - netuid: U256, - ) -> EvmResult { - let (hotkey, _) = parse_pubkey(hotkey.as_bytes())?; - let (coldkey, _) = parse_pubkey(coldkey.as_bytes())?; - let netuid = try_u16_from_u256(netuid)?; - let stake = pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, &coldkey, netuid, - ); - - Ok(stake.into()) - } - - fn transfer_back_to_caller( - account_id: &AccountId32, - amount: U256, - ) -> Result<(), PrecompileFailure> { - let smart_contract_account_id: AccountId32 = Self::ADDRESS_SS58.into(); - let amount_sub = - ::BalanceConverter::into_substrate_balance(amount) - .ok_or(ExitError::OutOfFund)?; - - // Create a transfer call from the smart contract to the caller - let transfer_call = - RuntimeCall::Balances(pallet_balances::Call::::transfer_allow_death { - dest: account_id.clone().into(), - value: amount_sub.unique_saturated_into(), - }); - - // Execute the transfer - let transfer_result = - transfer_call.dispatch(RawOrigin::Signed(smart_contract_account_id).into()); - - if let Err(dispatch_error) = transfer_result { - log::error!( - "Transfer back to caller failed. Error: {:?}", - dispatch_error - ); - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("Transfer back to caller failed".into()), - }); - } - - Ok(()) - } -} diff --git a/runtime/tests/pallet_proxy.rs b/runtime/tests/pallet_proxy.rs index 7d0dec9679..563c274bb9 100644 --- a/runtime/tests/pallet_proxy.rs +++ b/runtime/tests/pallet_proxy.rs @@ -3,9 +3,10 @@ use codec::Encode; use frame_support::{BoundedVec, assert_ok, traits::InstanceFilter}; use node_subtensor_runtime::{ - AccountId, BalancesCall, BuildStorage, Proxy, ProxyType, Runtime, RuntimeCall, RuntimeEvent, - RuntimeGenesisConfig, RuntimeOrigin, SubtensorModule, System, SystemCall, + BalancesCall, BuildStorage, Proxy, Runtime, RuntimeCall, RuntimeEvent, RuntimeGenesisConfig, + RuntimeOrigin, SubtensorModule, System, SystemCall, }; +use subtensor_runtime_common::{AccountId, ProxyType}; const ACCOUNT: [u8; 32] = [1_u8; 32]; const DELEGATE: [u8; 32] = [2_u8; 32]; diff --git a/scripts/localnet.sh b/scripts/localnet.sh index b82b5f9f59..cecce2d814 100755 --- a/scripts/localnet.sh +++ b/scripts/localnet.sh @@ -2,10 +2,15 @@ # Check if `--no-purge` passed as a parameter NO_PURGE=0 + +# Check if `--build-only` passed as parameter +BUILD_ONLY=0 + for arg in "$@"; do if [ "$arg" = "--no-purge" ]; then NO_PURGE=1 - break + elif [ "$arg" = "--build-only" ]; then + BUILD_ONLY=1 fi done @@ -15,94 +20,98 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" # The base directory of the subtensor project BASE_DIR="$SCRIPT_DIR/.." -# get parameters # Get the value of fast_blocks from the first argument fast_blocks=${1:-"True"} -# Check the value of fast_blocks +# Define the target directory for compilation if [ "$fast_blocks" == "False" ]; then # Block of code to execute if fast_blocks is False echo "fast_blocks is Off" : "${CHAIN:=local}" : "${BUILD_BINARY:=1}" : "${FEATURES:="pow-faucet"}" + BUILD_DIR="$BASE_DIR/target/non-fast-blocks" else # Block of code to execute if fast_blocks is not False echo "fast_blocks is On" : "${CHAIN:=local}" : "${BUILD_BINARY:=1}" : "${FEATURES:="pow-faucet fast-blocks"}" + BUILD_DIR="$BASE_DIR/target/fast-blocks" fi +# Ensure the build directory exists +mkdir -p "$BUILD_DIR" + SPEC_PATH="${SCRIPT_DIR}/specs/" FULL_PATH="$SPEC_PATH$CHAIN.json" -# Kill any existing nodes which may have not exited correctly after a previous -# run. +# Kill any existing nodes which may have not exited correctly after a previous run. pkill -9 'node-subtensor' if [ ! -d "$SPEC_PATH" ]; then echo "*** Creating directory ${SPEC_PATH}..." - mkdir $SPEC_PATH + mkdir -p "$SPEC_PATH" fi if [[ $BUILD_BINARY == "1" ]]; then echo "*** Building substrate binary..." - cargo build --workspace --profile=release --features "$FEATURES" --manifest-path "$BASE_DIR/Cargo.toml" + CARGO_TARGET_DIR="$BUILD_DIR" cargo build --workspace --profile=release --features "$FEATURES" --manifest-path "$BASE_DIR/Cargo.toml" echo "*** Binary compiled" fi echo "*** Building chainspec..." -"$BASE_DIR/target/release/node-subtensor" build-spec --disable-default-bootnode --raw --chain $CHAIN >$FULL_PATH +"$BUILD_DIR/release/node-subtensor" build-spec --disable-default-bootnode --raw --chain $CHAIN >$FULL_PATH echo "*** Chainspec built and output to file" -# generate node keys -$BASE_DIR/target/release/node-subtensor key generate-node-key --chain="$FULL_PATH" --base-path /tmp/alice -$BASE_DIR/target/release/node-subtensor key generate-node-key --chain="$FULL_PATH" --base-path /tmp/bob +# Generate node keys +"$BUILD_DIR/release/node-subtensor" key generate-node-key --chain="$FULL_PATH" --base-path /tmp/alice +"$BUILD_DIR/release/node-subtensor" key generate-node-key --chain="$FULL_PATH" --base-path /tmp/bob if [ $NO_PURGE -eq 1 ]; then echo "*** Purging previous state skipped..." else echo "*** Purging previous state..." - "$BASE_DIR/target/release/node-subtensor" purge-chain -y --base-path /tmp/bob --chain="$FULL_PATH" >/dev/null 2>&1 - "$BASE_DIR/target/release/node-subtensor" purge-chain -y --base-path /tmp/alice --chain="$FULL_PATH" >/dev/null 2>&1 + "$BUILD_DIR/release/node-subtensor" purge-chain -y --base-path /tmp/bob --chain="$FULL_PATH" >/dev/null 2>&1 + "$BUILD_DIR/release/node-subtensor" purge-chain -y --base-path /tmp/alice --chain="$FULL_PATH" >/dev/null 2>&1 echo "*** Previous chainstate purged" fi -echo "*** Starting localnet nodes..." -alice_start=( - "$BASE_DIR/target/release/node-subtensor" - --base-path /tmp/alice - --chain="$FULL_PATH" - --alice - --port 30334 - --rpc-port 9944 - --validator - --rpc-cors=all - --allow-private-ipv4 - --discover-local - --unsafe-force-node-key-generation -) - -bob_start=( - "$BASE_DIR"/target/release/node-subtensor - --base-path /tmp/bob - --chain="$FULL_PATH" - --bob - --port 30335 - --rpc-port 9945 - --validator - --rpc-cors=all - --allow-private-ipv4 - --discover-local - --unsafe-force-node-key-generation -# --offchain-worker=Never -) - -trap 'pkill -P $$' EXIT SIGINT SIGTERM - -( - ("${alice_start[@]}" 2>&1) & - ("${bob_start[@]}" 2>&1) - wait -) +if [ $BUILD_ONLY -eq 0 ]; then + echo "*** Starting localnet nodes..." + alice_start=( + "$BUILD_DIR/release/node-subtensor" + --base-path /tmp/alice + --chain="$FULL_PATH" + --alice + --port 30334 + --rpc-port 9944 + --validator + --rpc-cors=all + --allow-private-ipv4 + --discover-local + --unsafe-force-node-key-generation + ) + + bob_start=( + "$BUILD_DIR/release/node-subtensor" + --base-path /tmp/bob + --chain="$FULL_PATH" + --bob + --port 30335 + --rpc-port 9945 + --validator + --rpc-cors=all + --allow-private-ipv4 + --discover-local + --unsafe-force-node-key-generation + ) + + trap 'pkill -P $$' EXIT SIGINT SIGTERM + + ( + ("${alice_start[@]}" 2>&1) & + ("${bob_start[@]}" 2>&1) + wait + ) +fi \ No newline at end of file diff --git a/support/linting/src/forbid_saturating_math.rs b/support/linting/src/forbid_saturating_math.rs new file mode 100644 index 0000000000..9ad5385b36 --- /dev/null +++ b/support/linting/src/forbid_saturating_math.rs @@ -0,0 +1,113 @@ +use super::*; +use syn::{Expr, ExprCall, ExprMethodCall, ExprPath, File, Path, spanned::Spanned, visit::Visit}; + +pub struct ForbidSaturatingMath; + +impl Lint for ForbidSaturatingMath { + fn lint(source: &File) -> Result { + let mut visitor = SaturatingMathBanVisitor::default(); + visitor.visit_file(source); + + if visitor.errors.is_empty() { + Ok(()) + } else { + Err(visitor.errors) + } + } +} + +#[derive(Default)] +struct SaturatingMathBanVisitor { + errors: Vec, +} + +impl<'ast> Visit<'ast> for SaturatingMathBanVisitor { + fn visit_expr_method_call(&mut self, node: &'ast ExprMethodCall) { + let ExprMethodCall { method, .. } = node; + + if method.to_string().starts_with("saturating_") { + let msg = "Safe math is banned to encourage tests to panic"; + self.errors.push(syn::Error::new(method.span(), msg)); + } + } + + fn visit_expr_call(&mut self, node: &'ast ExprCall) { + let ExprCall { func, .. } = node; + + if is_saturating_math_call(func) { + let msg = "Safe math is banned to encourage tests to panic"; + self.errors.push(syn::Error::new(node.func.span(), msg)); + } + } +} + +fn is_saturating_math_call(func: &Expr) -> bool { + let Expr::Path(ExprPath { + path: Path { segments: path, .. }, + .. + }) = func + else { + return false; + }; + + path.last() + .is_some_and(|seg| seg.ident.to_string().starts_with("saturating_")) +} + +#[cfg(test)] +mod tests { + use super::*; + use quote::quote; + + fn lint(input: proc_macro2::TokenStream) -> Result { + let mut visitor = SaturatingMathBanVisitor::default(); + let expr: syn::Expr = syn::parse2(input).expect("should be a valid expression"); + + match &expr { + syn::Expr::MethodCall(call) => visitor.visit_expr_method_call(call), + syn::Expr::Call(call) => visitor.visit_expr_call(call), + _ => panic!("should be a valid method call or function call"), + } + + if visitor.errors.is_empty() { + Ok(()) + } else { + Err(visitor.errors) + } + } + + #[test] + fn test_saturating_forbidden() { + let input = quote! { stake.saturating_add(alpha) }; + assert!(lint(input).is_err()); + let input = quote! { alpha_price.saturating_mul(float_alpha_block_emission) }; + assert!(lint(input).is_err()); + let input = quote! { alpha_out_i.saturating_sub(root_alpha) }; + assert!(lint(input).is_err()); + } + + #[test] + fn test_saturating_ufcs_forbidden() { + let input = quote! { SaturatingAdd::saturating_add(stake, alpha) }; + assert!(lint(input).is_err()); + let input = quote! { core::num::SaturatingAdd::saturating_add(stake, alpha) }; + assert!(lint(input).is_err()); + let input = + quote! { SaturatingMul::saturating_mul(alpha_price, float_alpha_block_emission) }; + assert!(lint(input).is_err()); + let input = quote! { core::num::SaturatingMul::saturating_mul(alpha_price, float_alpha_block_emission) }; + assert!(lint(input).is_err()); + let input = quote! { SaturatingSub::saturating_sub(alpha_out_i, root_alpha) }; + assert!(lint(input).is_err()); + let input = quote! { core::num::SaturatingSub::saturating_sub(alpha_out_i, root_alpha) }; + assert!(lint(input).is_err()); + } + + #[test] + fn test_saturating_to_from_num_forbidden() { + let input = quote! { I96F32::saturating_from_num(u64::MAX) }; + assert!(lint(input).is_err()); + let input = quote! { remaining_emission.saturating_to_num::() }; + assert!(lint(input).is_err()); + } +} diff --git a/support/linting/src/lib.rs b/support/linting/src/lib.rs index a65466e6a5..864d4a5572 100644 --- a/support/linting/src/lib.rs +++ b/support/linting/src/lib.rs @@ -3,10 +3,12 @@ pub use lint::*; mod forbid_as_primitive; mod forbid_keys_remove; +mod forbid_saturating_math; mod pallet_index; mod require_freeze_struct; pub use forbid_as_primitive::ForbidAsPrimitiveConversion; pub use forbid_keys_remove::ForbidKeysRemoveCall; +pub use forbid_saturating_math::ForbidSaturatingMath; pub use pallet_index::RequireExplicitPalletIndex; pub use require_freeze_struct::RequireFreezeStruct;