diff --git a/contract-tests/yarn.lock b/contract-tests/yarn.lock index 25300ca989..0501836d75 100644 --- a/contract-tests/yarn.lock +++ b/contract-tests/yarn.lock @@ -2,16 +2,16 @@ # yarn lockfile v1 -"@adraffy/ens-normalize@^1.10.1", "@adraffy/ens-normalize@^1.11.0": - version "1.11.1" - resolved "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz" - integrity sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ== - -"@adraffy/ens-normalize@1.10.1": +"@adraffy/ens-normalize@^1.10.1", "@adraffy/ens-normalize@1.10.1": version "1.10.1" resolved "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz" integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== +"@adraffy/ens-normalize@^1.11.0": + version "1.11.1" + resolved "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz" + integrity sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ== + "@babel/code-frame@^7.26.2": version "7.27.1" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz" @@ -38,10 +38,10 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@esbuild/darwin-arm64@0.25.12": +"@esbuild/linux-x64@0.25.12": version "0.25.12" - resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz" - integrity sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg== + resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz" + integrity sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw== "@ethereumjs/rlp@^10.0.0": version "10.1.0" @@ -99,21 +99,21 @@ resolved "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz" integrity sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw== -"@noble/curves@^1.3.0", "@noble/curves@^1.6.0", "@noble/curves@~1.9.0", "@noble/curves@~1.9.2": +"@noble/curves@^1.3.0": version "1.9.7" resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz" integrity sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw== dependencies: "@noble/hashes" "1.8.0" -"@noble/curves@^2.0.0": - version "2.0.1" - resolved "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz" - integrity sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw== +"@noble/curves@^1.6.0": + version "1.9.7" + resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz" + integrity sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw== dependencies: - "@noble/hashes" "2.0.1" + "@noble/hashes" "1.8.0" -"@noble/curves@^2.0.1": +"@noble/curves@^2.0.0", "@noble/curves@^2.0.1", "@noble/curves@~2.0.0": version "2.0.1" resolved "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz" integrity sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw== @@ -127,12 +127,12 @@ dependencies: "@noble/hashes" "1.7.2" -"@noble/curves@~2.0.0": - version "2.0.1" - resolved "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz" - integrity sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw== +"@noble/curves@~1.9.0", "@noble/curves@1.9.1": + version "1.9.1" + resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz" + integrity sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA== dependencies: - "@noble/hashes" "2.0.1" + "@noble/hashes" "1.8.0" "@noble/curves@1.2.0": version "1.2.0" @@ -148,24 +148,17 @@ dependencies: "@noble/hashes" "1.7.1" -"@noble/curves@1.9.1": - version "1.9.1" - resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz" - integrity sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA== - dependencies: - "@noble/hashes" "1.8.0" - -"@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3", "@noble/hashes@^1.5.0", "@noble/hashes@^1.8.0", "@noble/hashes@~1.8.0", "@noble/hashes@1.8.0": +"@noble/hashes@^1.3.1": version "1.8.0" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz" integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== -"@noble/hashes@^2.0.0", "@noble/hashes@~2.0.0", "@noble/hashes@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz" - integrity sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw== +"@noble/hashes@^1.3.3", "@noble/hashes@^1.5.0", "@noble/hashes@^1.8.0", "@noble/hashes@~1.8.0", "@noble/hashes@1.8.0": + version "1.8.0" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz" + integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== -"@noble/hashes@^2.0.1", "@noble/hashes@2.0.1": +"@noble/hashes@^2.0.0", "@noble/hashes@^2.0.1", "@noble/hashes@~2.0.0", "@noble/hashes@2.0.1": version "2.0.1" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz" integrity sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw== @@ -240,7 +233,6 @@ integrity sha512-cgA9fh8dfBai9b46XaaQmj9vwzyHStQjc/xrAvQksgF6SqvZ0yAfxVqLvGrsz/Xi3dsAdKLg09PybC7MUAMv9w== "@polkadot-api/descriptors@file:.papi/descriptors": - version "0.1.0-autogenerated.14746733976505338329" resolved "file:.papi/descriptors" "@polkadot-api/ink-contracts@^0.4.1", "@polkadot-api/ink-contracts@>=0.4.0", "@polkadot-api/ink-contracts@0.4.3": @@ -262,7 +254,7 @@ resolved "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.2.7.tgz" integrity sha512-+HM4JQXzO2GPUD2++4GOLsmFL6LO8RoLvig0HgCLuypDgfdZMlwd8KnyGHjRnVEHA5X+kvXbk84TDcAXVxTazQ== -"@polkadot-api/json-rpc-provider@^0.0.1": +"@polkadot-api/json-rpc-provider@^0.0.1", "@polkadot-api/json-rpc-provider@0.0.1": version "0.0.1" resolved "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.0.1.tgz" integrity sha512-/SMC/l7foRjpykLTUTacIH05H3mr9ip8b5xxfwXlVezXrNVLp3Cv0GX6uItkKd+ZjzVPf3PFrDF2B2/HLSNESA== @@ -445,7 +437,15 @@ "@scure/base" "^1.1.1" scale-ts "^1.6.0" -"@polkadot-api/substrate-client@^0.1.2", "@polkadot-api/substrate-client@0.1.4", "@polkadot-api/substrate-client@0.4.7": +"@polkadot-api/substrate-client@^0.1.2", "@polkadot-api/substrate-client@0.1.4": + version "0.1.4" + resolved "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.1.4.tgz" + integrity sha512-MljrPobN0ZWTpn++da9vOvt+Ex+NlqTlr/XT7zi9sqPtDJiQcYl+d29hFAgpaeTqbeQKZwz3WDE9xcEfLE8c5A== + dependencies: + "@polkadot-api/json-rpc-provider" "0.0.1" + "@polkadot-api/utils" "0.1.0" + +"@polkadot-api/substrate-client@0.4.7": version "0.4.7" resolved "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.4.7.tgz" integrity sha512-Mmx9VKincVqfVQmq89gzDk4DN3uKwf8CxoqYvq+EiPUZ1QmMUc7X4QMwG1MXIlYdnm5LSXzn+2Jn8ik8xMgL+w== @@ -589,15 +589,6 @@ "@substrate/ss58-registry" "^1.51.0" tslib "^2.8.0" -"@polkadot/networks@14.0.1": - version "14.0.1" - resolved "https://registry.npmjs.org/@polkadot/networks/-/networks-14.0.1.tgz" - integrity sha512-wGlBtXDkusRAj4P7uxfPz80gLO1+j99MLBaQi3bEym2xrFrFhgIWVHOZlBit/1PfaBjhX2Z8XjRxaM2w1p7w2w== - dependencies: - "@polkadot/util" "14.0.1" - "@substrate/ss58-registry" "^1.51.0" - tslib "^2.8.0" - "@polkadot/rpc-augment@16.5.3": version "16.5.3" resolved "https://registry.npmjs.org/@polkadot/rpc-augment/-/rpc-augment-16.5.3.tgz" @@ -703,40 +694,7 @@ rxjs "^7.8.1" tslib "^2.8.1" -"@polkadot/util-crypto@^13.5.9": - version "13.5.9" - resolved "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-13.5.9.tgz" - integrity sha512-foUesMhxkTk8CZ0/XEcfvHk6I0O+aICqqVJllhOpyp/ZVnrTBKBf59T6RpsXx2pCtBlMsLRvg/6Mw7RND1HqDg== - dependencies: - "@noble/curves" "^1.3.0" - "@noble/hashes" "^1.3.3" - "@polkadot/networks" "13.5.9" - "@polkadot/util" "13.5.9" - "@polkadot/wasm-crypto" "^7.5.3" - "@polkadot/wasm-util" "^7.5.3" - "@polkadot/x-bigint" "13.5.9" - "@polkadot/x-randomvalues" "13.5.9" - "@scure/base" "^1.1.7" - tslib "^2.8.0" - -"@polkadot/util-crypto@^14.0.1": - version "14.0.1" - resolved "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-14.0.1.tgz" - integrity sha512-Cu7AKUzBTsUkbOtyuNzXcTpDjR9QW0fVR56o3gBmzfUCmvO1vlsuGzmmPzqpHymQQ3rrfqV78CPs62EGhw0R+A== - dependencies: - "@noble/curves" "^1.3.0" - "@noble/hashes" "^1.3.3" - "@polkadot/networks" "14.0.1" - "@polkadot/util" "14.0.1" - "@polkadot/wasm-crypto" "^7.5.3" - "@polkadot/wasm-util" "^7.5.3" - "@polkadot/x-bigint" "14.0.1" - "@polkadot/x-randomvalues" "14.0.1" - "@scure/base" "^1.1.7" - "@scure/sr25519" "^0.2.0" - tslib "^2.8.0" - -"@polkadot/util-crypto@13.5.9": +"@polkadot/util-crypto@^13.5.9", "@polkadot/util-crypto@13.5.9": version "13.5.9" resolved "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-13.5.9.tgz" integrity sha512-foUesMhxkTk8CZ0/XEcfvHk6I0O+aICqqVJllhOpyp/ZVnrTBKBf59T6RpsXx2pCtBlMsLRvg/6Mw7RND1HqDg== @@ -765,19 +723,6 @@ bn.js "^5.2.1" tslib "^2.8.0" -"@polkadot/util@14.0.1": - version "14.0.1" - resolved "https://registry.npmjs.org/@polkadot/util/-/util-14.0.1.tgz" - integrity sha512-764HhxkPV3x5rM0/p6QdynC2dw26n+SaE+jisjx556ViCd4E28Ke4xSPef6C0Spy4aoXf2gt0PuLEcBvd6fVZg== - dependencies: - "@polkadot/x-bigint" "14.0.1" - "@polkadot/x-global" "14.0.1" - "@polkadot/x-textdecoder" "14.0.1" - "@polkadot/x-textencoder" "14.0.1" - "@types/bn.js" "^5.1.6" - bn.js "^5.2.1" - tslib "^2.8.0" - "@polkadot/wasm-bridge@7.5.3": version "7.5.3" resolved "https://registry.npmjs.org/@polkadot/wasm-bridge/-/wasm-bridge-7.5.3.tgz" @@ -839,14 +784,6 @@ "@polkadot/x-global" "13.5.9" tslib "^2.8.0" -"@polkadot/x-bigint@14.0.1": - version "14.0.1" - resolved "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-14.0.1.tgz" - integrity sha512-gfozjGnebr2rqURs31KtaWumbW4rRZpbiluhlmai6luCNrf5u8pB+oLA35kPEntrsLk9PnIG9OsC/n4hEtx4OQ== - dependencies: - "@polkadot/x-global" "14.0.1" - tslib "^2.8.0" - "@polkadot/x-fetch@^13.5.9": version "13.5.9" resolved "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-13.5.9.tgz" @@ -863,13 +800,6 @@ dependencies: tslib "^2.8.0" -"@polkadot/x-global@14.0.1": - version "14.0.1" - resolved "https://registry.npmjs.org/@polkadot/x-global/-/x-global-14.0.1.tgz" - integrity sha512-aCI44DJU4fU0XXqrrSGIpi7JrZXK2kpe0jaQ2p6oDVXOOYEnZYXnMhTTmBE1lF/xtxzX50MnZrrU87jziU0qbA== - dependencies: - tslib "^2.8.0" - "@polkadot/x-randomvalues@*", "@polkadot/x-randomvalues@13.5.9": version "13.5.9" resolved "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-13.5.9.tgz" @@ -878,14 +808,6 @@ "@polkadot/x-global" "13.5.9" tslib "^2.8.0" -"@polkadot/x-randomvalues@14.0.1": - version "14.0.1" - resolved "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-14.0.1.tgz" - integrity sha512-/XkQcvshzJLHITuPrN3zmQKuFIPdKWoaiHhhVLD6rQWV60lTXA3ajw3ocju8ZN7xRxnweMS9Ce0kMPYa0NhRMg== - dependencies: - "@polkadot/x-global" "14.0.1" - tslib "^2.8.0" - "@polkadot/x-textdecoder@13.5.9": version "13.5.9" resolved "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-13.5.9.tgz" @@ -894,14 +816,6 @@ "@polkadot/x-global" "13.5.9" tslib "^2.8.0" -"@polkadot/x-textdecoder@14.0.1": - version "14.0.1" - resolved "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-14.0.1.tgz" - integrity sha512-CcWiPCuPVJsNk4Vq43lgFHqLRBQHb4r9RD7ZIYgmwoebES8TNm4g2ew9ToCzakFKSpzKu6I07Ne9wv/dt5zLuw== - dependencies: - "@polkadot/x-global" "14.0.1" - tslib "^2.8.0" - "@polkadot/x-textencoder@13.5.9": version "13.5.9" resolved "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-13.5.9.tgz" @@ -910,14 +824,6 @@ "@polkadot/x-global" "13.5.9" tslib "^2.8.0" -"@polkadot/x-textencoder@14.0.1": - version "14.0.1" - resolved "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-14.0.1.tgz" - integrity sha512-VY51SpQmF1ccmAGLfxhYnAe95Spfz049WZ/+kK4NfsGF9WejxVdU53Im5C80l45r8qHuYQsCWU3+t0FNunh2Kg== - dependencies: - "@polkadot/x-global" "14.0.1" - tslib "^2.8.0" - "@polkadot/x-ws@^13.5.9": version "13.5.9" resolved "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-13.5.9.tgz" @@ -927,17 +833,22 @@ tslib "^2.8.0" ws "^8.18.0" -"@rollup/rollup-darwin-arm64@4.53.3": +"@rollup/rollup-linux-x64-gnu@4.53.3": version "4.53.3" - resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz" - integrity sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA== + resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz" + integrity sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w== "@rx-state/core@^0.1.4": version "0.1.4" resolved "https://registry.npmjs.org/@rx-state/core/-/core-0.1.4.tgz" integrity sha512-Z+3hjU2xh1HisLxt+W5hlYX/eGSDaXXP+ns82gq/PLZpkXLu0uwcNUh9RLY3Clq4zT+hSsA3vcpIGt6+UAb8rQ== -"@scure/base@^1.1.1", "@scure/base@^1.1.7", "@scure/base@~1.2.2", "@scure/base@~1.2.4", "@scure/base@~1.2.5": +"@scure/base@^1.1.1": + version "1.2.6" + resolved "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz" + integrity sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg== + +"@scure/base@^1.1.7": version "1.2.6" resolved "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz" integrity sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg== @@ -947,16 +858,22 @@ resolved "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz" integrity sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w== -"@scure/bip32@^1.5.0", "@scure/bip32@^1.7.0", "@scure/bip32@1.7.0": - version "1.7.0" - resolved "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz" - integrity sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw== - dependencies: - "@noble/curves" "~1.9.0" - "@noble/hashes" "~1.8.0" - "@scure/base" "~1.2.5" +"@scure/base@~1.2.2": + version "1.2.6" + resolved "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz" + integrity sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg== + +"@scure/base@~1.2.4": + version "1.2.6" + resolved "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz" + integrity sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg== + +"@scure/base@~1.2.5": + version "1.2.6" + resolved "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz" + integrity sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg== -"@scure/bip32@1.6.2": +"@scure/bip32@^1.5.0", "@scure/bip32@1.6.2": version "1.6.2" resolved "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz" integrity sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw== @@ -965,15 +882,16 @@ "@noble/hashes" "~1.7.1" "@scure/base" "~1.2.2" -"@scure/bip39@^1.4.0", "@scure/bip39@^1.6.0", "@scure/bip39@1.6.0": - version "1.6.0" - resolved "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz" - integrity sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A== +"@scure/bip32@^1.7.0", "@scure/bip32@1.7.0": + version "1.7.0" + resolved "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz" + integrity sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw== dependencies: + "@noble/curves" "~1.9.0" "@noble/hashes" "~1.8.0" "@scure/base" "~1.2.5" -"@scure/bip39@1.5.4": +"@scure/bip39@^1.4.0", "@scure/bip39@1.5.4": version "1.5.4" resolved "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz" integrity sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA== @@ -981,13 +899,13 @@ "@noble/hashes" "~1.7.1" "@scure/base" "~1.2.4" -"@scure/sr25519@^0.2.0": - version "0.2.0" - resolved "https://registry.npmjs.org/@scure/sr25519/-/sr25519-0.2.0.tgz" - integrity sha512-uUuLP7Z126XdSizKtrCGqYyR3b3hYtJ6Fg/XFUXmc2//k2aXHDLqZwFeXxL97gg4XydPROPVnuaHGF2+xriSKg== +"@scure/bip39@^1.6.0", "@scure/bip39@1.6.0": + version "1.6.0" + resolved "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz" + integrity sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A== dependencies: - "@noble/curves" "~1.9.2" "@noble/hashes" "~1.8.0" + "@scure/base" "~1.2.5" "@scure/sr25519@^0.3.0": version "0.3.0" @@ -1632,11 +1550,6 @@ fs.promises.exists@^1.1.4: resolved "https://registry.npmjs.org/fs.promises.exists/-/fs.promises.exists-1.1.4.tgz" integrity sha512-lJzUGWbZn8vhGWBedA+RYjB/BeJ+3458ljUfmplqhIeb6ewzTFWNPCR1HCiYCkXV9zxcHz9zXkJzMsEgDLzh3Q== -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" @@ -2267,7 +2180,7 @@ pkg-types@^1.3.1: mlly "^1.7.4" pathe "^2.0.1" -polkadot-api@^1.22.0, polkadot-api@^1.8.1, polkadot-api@>=1.19.0, polkadot-api@>=1.21.0: +polkadot-api@^1.22.0, polkadot-api@^1.8.1, polkadot-api@>=1.19.0: version "1.22.0" resolved "https://registry.npmjs.org/polkadot-api/-/polkadot-api-1.22.0.tgz" integrity sha512-uREBLroPbnJxBBQ+qSkKLF493qukX4PAg32iThlELrZdxfNNgro6nvWRdVmBv73tFHvf+nyWWHKTx1c57nbixg== @@ -2476,6 +2389,13 @@ signal-exit@^4.0.1, signal-exit@^4.1.0: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +smoldot@2.0.26, smoldot@2.x: + version "2.0.26" + resolved "https://registry.npmjs.org/smoldot/-/smoldot-2.0.26.tgz" + integrity sha512-F+qYmH4z2s2FK+CxGj8moYcd1ekSIKH8ywkdqlOz88Dat35iB1DIYL11aILN46YSGMzQW/lbJNS307zBSDN5Ig== + dependencies: + ws "^8.8.1" + smoldot@2.0.39: version "2.0.39" resolved "https://registry.npmjs.org/smoldot/-/smoldot-2.0.39.tgz" @@ -2537,7 +2457,16 @@ stdin-discarder@^0.2.2: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2577,14 +2506,7 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.1: - version "7.1.2" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz" - integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== - dependencies: - ansi-regex "^6.0.1" - -strip-ansi@^7.1.0, strip-ansi@^7.1.2: +strip-ansi@^7.0.1, strip-ansi@^7.1.0, strip-ansi@^7.1.2: version "7.1.2" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz" integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== @@ -2734,7 +2656,12 @@ tsup@8.5.0: tinyglobby "^0.2.11" tree-kill "^1.2.2" -type-fest@^4.23.0, type-fest@^4.39.1, type-fest@^4.6.0: +type-fest@^4.23.0, type-fest@^4.6.0: + version "4.41.0" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +type-fest@^4.39.1: version "4.41.0" resolved "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz" integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index f61c35aede..b66760e4b3 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -1666,4 +1666,23 @@ mod pallet_benchmarks { #[extrinsic_call] _(RawOrigin::Root, netuid, 100); } + + #[benchmark] + fn sudo_set_emission_suppression_override() { + let coldkey: T::AccountId = whitelisted_caller(); + let hotkey: T::AccountId = account("A", 0, 1); + + let netuid = Subtensor::::get_next_netuid(); + + let lock_cost = Subtensor::::get_network_lock_cost(); + Subtensor::::add_balance_to_coldkey_account(&coldkey, lock_cost.into()); + + assert_ok!(Subtensor::::register_network( + RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone() + )); + + #[extrinsic_call] + _(RawOrigin::Root, netuid, Some(true)); + } } diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 83567b6f57..3d356471d5 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -302,6 +302,9 @@ impl Pallet { SubnetEmaTaoFlow::::remove(netuid); SubnetTaoProvided::::remove(netuid); + // --- 12b. Emission suppression. + EmissionSuppressionOverride::::remove(netuid); + // --- 13. Token / mechanism / registration toggles. TokenSymbol::::remove(netuid); SubnetMechanism::::remove(netuid); diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 2091946598..d18073da36 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -235,7 +235,7 @@ impl Pallet { }); if root_sell_flag { - // Only accumulate root alpha divs if root sell is allowed. + // Accumulate root alpha divs for root validators. PendingRootAlphaDivs::::mutate(*netuid_i, |total| { *total = total.saturating_add(tou64!(root_alpha).into()); }); diff --git a/pallets/subtensor/src/coinbase/subnet_emissions.rs b/pallets/subtensor/src/coinbase/subnet_emissions.rs index 477a678864..28e7829b12 100644 --- a/pallets/subtensor/src/coinbase/subnet_emissions.rs +++ b/pallets/subtensor/src/coinbase/subnet_emissions.rs @@ -26,17 +26,34 @@ impl Pallet { subnets_to_emit_to: &[NetUid], block_emission: U96F32, ) -> BTreeMap { - // Get subnet TAO emissions. - let shares = Self::get_shares(subnets_to_emit_to); + // Filter out suppressed subnets before computing shares so they never + // enter the share calculation and remaining subnets naturally split the + // full emission without a separate zero-and-renormalize step. + let active: Vec = subnets_to_emit_to + .iter() + .filter(|netuid| !Self::is_subnet_emission_suppressed(**netuid)) + .copied() + .collect(); + let shares = Self::get_shares(&active); log::debug!("Subnet emission shares = {shares:?}"); - shares + let mut emissions: BTreeMap = shares .into_iter() .map(|(netuid, share)| { let emission = U64F64::saturating_from_num(block_emission).saturating_mul(share); (netuid, U96F32::saturating_from_num(emission)) }) - .collect::>() + .collect(); + + // Add suppressed subnets back with zero TAO emission so they still + // appear in the emission map. This is required because emit_to_subnets + // uses the map to drive alpha issuance (which continues independently + // of TAO suppression). + for netuid in subnets_to_emit_to { + emissions.entry(*netuid).or_insert(U96F32::from_num(0)); + } + + emissions } pub fn record_tao_inflow(netuid: NetUid, tao: TaoCurrency) { @@ -246,4 +263,11 @@ impl Pallet { }) .collect::>() } + + /// Check if a subnet is currently emission-suppressed via the root override. + /// Returns true only for `Some(true)`. `Some(false)` and `None` both yield + /// false (not suppressed). + pub(crate) fn is_subnet_emission_suppressed(netuid: NetUid) -> bool { + matches!(EmissionSuppressionOverride::::get(netuid), Some(true)) + } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 6ae43ac384..8152229b1d 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2375,6 +2375,15 @@ pub mod pallet { pub type PendingChildKeyCooldown = StorageValue<_, u64, ValueQuery, DefaultPendingChildKeyCooldown>; + /// Root override for emission suppression per subnet. + /// Some(true) = force suppressed; None or Some(false) = not suppressed. + /// Some(false) is functionally identical to None today and is reserved for + /// future use (e.g. an automatic suppression mechanism whose effect + /// Some(false) could explicitly override). + #[pallet::storage] + pub type EmissionSuppressionOverride = + StorageMap<_, Identity, NetUid, bool, OptionQuery>; + #[pallet::genesis_config] pub struct GenesisConfig { /// Stakes record in genesis. diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 5c5d5ed1a7..549f79ef31 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2416,5 +2416,36 @@ mod dispatches { Ok(()) } + + /// --- Set or clear the root override for emission suppression on a subnet. + /// Some(true) forces suppression. None removes the override (subnet is not + /// suppressed). Some(false) is accepted and stored but is currently + /// functionally identical to None; it is reserved for future use. + #[pallet::call_index(133)] + #[pallet::weight(( + Weight::from_parts(5_000_000, 0) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)), + DispatchClass::Operational, + Pays::No + ))] + pub fn sudo_set_emission_suppression_override( + origin: OriginFor, + netuid: NetUid, + override_value: Option, + ) -> DispatchResult { + ensure_root(origin)?; + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + ensure!(!netuid.is_root(), Error::::CannotSuppressRootSubnet); + match override_value { + Some(val) => EmissionSuppressionOverride::::insert(netuid, val), + None => EmissionSuppressionOverride::::remove(netuid), + } + Self::deposit_event(Event::EmissionSuppressionOverrideSet { + netuid, + override_value, + }); + Ok(()) + } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 6c3d7a35df..be9976f0a5 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -268,5 +268,7 @@ mod errors { InvalidSubnetNumber, /// Unintended precision loss when unstaking alpha PrecisionLoss, + /// Cannot set emission suppression override for the root subnet. + CannotSuppressRootSubnet, } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index c86cc1a1e5..a203762a28 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -481,5 +481,14 @@ mod events { /// The amount of alpha distributed alpha: AlphaCurrency, }, + + /// Root set or cleared the emission suppression override for a subnet. + EmissionSuppressionOverrideSet { + /// The subnet affected + netuid: NetUid, + /// The override value: Some(true) = force suppress, None = cleared/not suppressed, + /// Some(false) = stored but currently identical to None (reserved for future use) + override_value: Option, + }, } } diff --git a/pallets/subtensor/src/tests/emission_suppression.rs b/pallets/subtensor/src/tests/emission_suppression.rs new file mode 100644 index 0000000000..eb7cba3d38 --- /dev/null +++ b/pallets/subtensor/src/tests/emission_suppression.rs @@ -0,0 +1,575 @@ +#![allow(clippy::indexing_slicing, clippy::panic, clippy::unwrap_used)] +use super::mock::*; +use crate::*; +use alloc::collections::BTreeMap; +use frame_support::{assert_noop, assert_ok}; +use sp_runtime::DispatchError; +use sp_core::U256; +use substrate_fixed::types::U96F32; +use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; + +/// Helper: create a non-root subnet with TAO flow so it gets shares. +fn setup_subnet_with_flow(netuid: NetUid, tempo: u16, tao_flow: i64) { + add_network(netuid, tempo, 0); + SubnetTaoFlow::::insert(netuid, tao_flow); +} + +/// Helper: seed root + subnet TAO/alpha so root_proportion is nonzero. +fn setup_root_with_tao(sn: NetUid) { + // Set SubnetTAO for root so root_proportion numerator is nonzero. + SubnetTAO::::insert(NetUid::ROOT, TaoCurrency::from(1_000_000_000)); + // Set alpha issuance for subnet so denominator is meaningful. + SubnetAlphaOut::::insert(sn, AlphaCurrency::from(1_000_000_000)); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Test 1: Override force suppress → zero TAO emission, rest gets full share +// ───────────────────────────────────────────────────────────────────────────── +#[test] +fn test_override_force_suppress() { + new_test_ext(1).execute_with(|| { + let sn1 = NetUid::from(1); + let sn2 = NetUid::from(2); + setup_subnet_with_flow(sn1, 10, 100_000_000); + setup_subnet_with_flow(sn2, 10, 100_000_000); + + // Override forces suppression. + EmissionSuppressionOverride::::insert(sn1, true); + + let block_emission = U96F32::from_num(1_000_000); + let emissions = + SubtensorModule::get_subnet_block_emissions(&[sn1, sn2], block_emission); + + // sn1 gets zero TAO emission. + let sn1_emission = emissions.get(&sn1).copied().unwrap_or(U96F32::from_num(0)); + assert_eq!(sn1_emission, U96F32::from_num(0)); + + // sn2 gets the full block emission. + let sn2_emission = emissions.get(&sn2).copied().unwrap_or(U96F32::from_num(0)); + assert!( + sn2_emission > U96F32::from_num(999_000), + "sn2 should get ~full emission, got {sn2_emission:?}" + ); + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Test 2: Override=Some(false) → not suppressed (same as None, reserved for future use) +// ───────────────────────────────────────────────────────────────────────────── +#[test] +fn test_override_force_unsuppress() { + new_test_ext(1).execute_with(|| { + let sn1 = NetUid::from(1); + setup_subnet_with_flow(sn1, 10, 100_000_000); + + // Some(false) is accepted but is currently identical to None: not suppressed. + EmissionSuppressionOverride::::insert(sn1, false); + + assert!( + !SubtensorModule::is_subnet_emission_suppressed(sn1), + "Some(false) should not suppress" + ); + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Test 3: No override → not suppressed (default) +// ───────────────────────────────────────────────────────────────────────────── +#[test] +fn test_no_override_not_suppressed() { + new_test_ext(1).execute_with(|| { + let sn1 = NetUid::from(1); + setup_subnet_with_flow(sn1, 10, 100_000_000); + + // No override at all — default is not suppressed. + assert!( + !SubtensorModule::is_subnet_emission_suppressed(sn1), + "no override means not suppressed" + ); + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Test 4: Dissolution clears override +// ───────────────────────────────────────────────────────────────────────────── +#[test] +fn test_dissolution_clears_override() { + new_test_ext(1).execute_with(|| { + add_network(NetUid::ROOT, 1, 0); + let sn1 = NetUid::from(1); + setup_subnet_with_flow(sn1, 10, 100_000_000); + + EmissionSuppressionOverride::::insert(sn1, true); + + // Remove the network. + SubtensorModule::remove_network(sn1); + + // Override should be cleaned up. + assert_eq!(EmissionSuppressionOverride::::get(sn1), None); + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Test 5: 3 subnets, suppress 1 → suppressed gets 0, others split full emission +// ───────────────────────────────────────────────────────────────────────────── +#[test] +fn test_suppress_one_of_three() { + new_test_ext(1).execute_with(|| { + let sn1 = NetUid::from(1); + let sn2 = NetUid::from(2); + let sn3 = NetUid::from(3); + setup_subnet_with_flow(sn1, 10, 100_000_000); + setup_subnet_with_flow(sn2, 10, 200_000_000); + setup_subnet_with_flow(sn3, 10, 300_000_000); + + // Suppress sn2 via override. + EmissionSuppressionOverride::::insert(sn2, true); + + let block_emission = U96F32::from_num(1_000_000); + let emissions = + SubtensorModule::get_subnet_block_emissions(&[sn1, sn2, sn3], block_emission); + + // sn2 should get 0 TAO. + let sn2_emission = emissions.get(&sn2).copied().unwrap_or(U96F32::from_num(0)); + assert_eq!(sn2_emission, U96F32::from_num(0)); + + // sn1 + sn3 should get the full block emission. + let sn1_emission: u64 = emissions + .get(&sn1) + .copied() + .unwrap_or(U96F32::from_num(0)) + .saturating_to_num(); + let sn3_emission: u64 = emissions + .get(&sn3) + .copied() + .unwrap_or(U96F32::from_num(0)) + .saturating_to_num(); + let total = sn1_emission.saturating_add(sn3_emission); + assert!( + total >= 999_000, + "sn1 + sn3 should get ~full emission, got {total}" + ); + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Test 6: All subnets suppressed → zero TAO emissions +// ───────────────────────────────────────────────────────────────────────────── +#[test] +fn test_all_subnets_suppressed() { + new_test_ext(1).execute_with(|| { + let sn1 = NetUid::from(1); + let sn2 = NetUid::from(2); + setup_subnet_with_flow(sn1, 10, 100_000_000); + setup_subnet_with_flow(sn2, 10, 100_000_000); + + // Suppress both via override. + EmissionSuppressionOverride::::insert(sn1, true); + EmissionSuppressionOverride::::insert(sn2, true); + + // Total TAO emission via get_subnet_block_emissions should be zero. + let emissions = + SubtensorModule::get_subnet_block_emissions(&[sn1, sn2], U96F32::from_num(1_000_000)); + let total: u64 = emissions + .values() + .map(|e| e.saturating_to_num::()) + .fold(0u64, |a, b| a.saturating_add(b)); + assert_eq!(total, 0, "all-suppressed should yield zero TAO emission"); + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Test 7: Suppressed subnet → root still accumulates alpha (hardcoded behavior) +// ───────────────────────────────────────────────────────────────────────────── +#[test] +fn test_suppressed_subnet_root_alpha_accumulated() { + new_test_ext(1).execute_with(|| { + add_network(NetUid::ROOT, 1, 0); + let sn1 = NetUid::from(1); + setup_subnet_with_flow(sn1, 10, 100_000_000); + + // Register a root validator and add stake on root so root_proportion > 0. + let hotkey = U256::from(10); + let coldkey = U256::from(11); + assert_ok!(SubtensorModule::root_register( + RuntimeOrigin::signed(coldkey), + hotkey, + )); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + 1_000_000_000u64.into(), + ); + // Set TAO weight so root_proportion is nonzero. + SubtensorModule::set_tao_weight(u64::MAX); + setup_root_with_tao(sn1); + + // Force-suppress sn1. + EmissionSuppressionOverride::::insert(sn1, true); + + // Clear any pending emissions. + PendingRootAlphaDivs::::insert(sn1, AlphaCurrency::ZERO); + + // Build emission map with some emission for sn1. + let mut subnet_emissions = BTreeMap::new(); + subnet_emissions.insert(sn1, U96F32::from_num(1_000_000)); + + SubtensorModule::emit_to_subnets(&[sn1], &subnet_emissions, true); + + // Root should have received some alpha (pending root alpha divs > 0). + let pending_root = PendingRootAlphaDivs::::get(sn1); + assert!( + pending_root > AlphaCurrency::ZERO, + "root should still get alpha on suppressed subnet" + ); + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Test 8: sudo_set_emission_suppression_override emits event +// ───────────────────────────────────────────────────────────────────────────── +#[test] +fn test_sudo_override_emits_event() { + new_test_ext(1).execute_with(|| { + let sn1 = NetUid::from(1); + setup_subnet_with_flow(sn1, 10, 100_000_000); + + System::set_block_number(1); + System::reset_events(); + + assert_ok!(SubtensorModule::sudo_set_emission_suppression_override( + RuntimeOrigin::root(), + sn1, + Some(true), + )); + + assert!( + System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule( + Event::EmissionSuppressionOverrideSet { netuid, override_value } + ) if *netuid == sn1 && *override_value == Some(true) + ) + }), + "should emit EmissionSuppressionOverrideSet event" + ); + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Test 9: Non-root origin is rejected with BadOrigin +// ───────────────────────────────────────────────────────────────────────────── +#[test] +fn test_sudo_override_rejects_non_root_origin() { + new_test_ext(1).execute_with(|| { + let sn1 = NetUid::from(1); + setup_subnet_with_flow(sn1, 10, 100_000_000); + + let non_root_account = U256::from(42); + + assert_noop!( + SubtensorModule::sudo_set_emission_suppression_override( + RuntimeOrigin::signed(non_root_account), + sn1, + Some(true), + ), + DispatchError::BadOrigin + ); + + // Storage must remain untouched. + assert_eq!(EmissionSuppressionOverride::::get(sn1), None); + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Test 10: Non-existent subnet is rejected with SubnetNotExists +// ───────────────────────────────────────────────────────────────────────────── +#[test] +fn test_sudo_override_rejects_nonexistent_subnet() { + new_test_ext(1).execute_with(|| { + let missing_netuid = NetUid::from(99); + // Deliberately do not create subnet 99. + + assert_noop!( + SubtensorModule::sudo_set_emission_suppression_override( + RuntimeOrigin::root(), + missing_netuid, + Some(true), + ), + Error::::SubnetNotExists + ); + + // Storage must remain untouched. + assert_eq!(EmissionSuppressionOverride::::get(missing_netuid), None); + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Test 11: Root subnet (netuid 0) is rejected with CannotSuppressRootSubnet +// ───────────────────────────────────────────────────────────────────────────── +#[test] +fn test_sudo_override_rejects_root_subnet() { + new_test_ext(1).execute_with(|| { + // Register the root network so it passes the SubnetNotExists check and + // the CannotSuppressRootSubnet guard is reached. + add_network(NetUid::ROOT, 1, 0); + + assert_noop!( + SubtensorModule::sudo_set_emission_suppression_override( + RuntimeOrigin::root(), + NetUid::ROOT, + Some(true), + ), + Error::::CannotSuppressRootSubnet + ); + + // Storage must remain untouched. + assert_eq!(EmissionSuppressionOverride::::get(NetUid::ROOT), None); + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Test 12: Clearing the override via None removes the storage entry +// ───────────────────────────────────────────────────────────────────────────── +#[test] +fn test_sudo_override_clear_removes_storage() { + new_test_ext(1).execute_with(|| { + let sn1 = NetUid::from(1); + setup_subnet_with_flow(sn1, 10, 100_000_000); + + // Set Some(true) first. + assert_ok!(SubtensorModule::sudo_set_emission_suppression_override( + RuntimeOrigin::root(), + sn1, + Some(true), + )); + assert_eq!(EmissionSuppressionOverride::::get(sn1), Some(true)); + + // Clear via None — the storage entry must be removed entirely. + assert_ok!(SubtensorModule::sudo_set_emission_suppression_override( + RuntimeOrigin::root(), + sn1, + None, + )); + assert_eq!( + EmissionSuppressionOverride::::get(sn1), + None, + "storage entry should be absent after clearing with None" + ); + + // With the override gone the subnet should no longer be suppressed. + assert!( + !SubtensorModule::is_subnet_emission_suppressed(sn1), + "subnet should not be suppressed after override is cleared" + ); + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Test: Suppressed subnet still receives alpha emissions (TAO-only suppression) +// +// Emission suppression zeroes the TAO share but alpha issuance is independent +// (driven by the subnet's own halving curve). This is intentional: suppressed +// subnets continue to mint and distribute alpha to miners, validators, owner, +// and root validators — only TAO injection into the AMM pool is suppressed. +// ───────────────────────────────────────────────────────────────────────────── +#[test] +fn test_suppressed_subnet_still_receives_alpha_emissions() { + new_test_ext(1).execute_with(|| { + add_network(NetUid::ROOT, 1, 0); + let sn1 = NetUid::from(1); + setup_subnet_with_flow(sn1, 10, 100_000_000); + + // Register a root validator so root_proportion > 0. + let hotkey = U256::from(10); + let coldkey = U256::from(11); + assert_ok!(SubtensorModule::root_register( + RuntimeOrigin::signed(coldkey), + hotkey, + )); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + 1_000_000_000u64.into(), + ); + SubtensorModule::set_tao_weight(u64::MAX); + setup_root_with_tao(sn1); + + // Force-suppress sn1. + EmissionSuppressionOverride::::insert(sn1, true); + + // Zero out all pending accumulators so we can measure what gets added. + PendingRootAlphaDivs::::insert(sn1, AlphaCurrency::ZERO); + PendingServerEmission::::insert(sn1, AlphaCurrency::ZERO); + PendingValidatorEmission::::insert(sn1, AlphaCurrency::ZERO); + + let alpha_out_before = SubnetAlphaOut::::get(sn1); + + // Build emission map: suppressed subnet gets zero TAO share. + let mut subnet_emissions = BTreeMap::new(); + subnet_emissions.insert(sn1, U96F32::from_num(0)); + + SubtensorModule::emit_to_subnets(&[sn1], &subnet_emissions, true); + + // --- Alpha issuance is independent of TAO emission --- + + // SubnetAlphaOut must have grown (new alpha was minted). + let alpha_out_after = SubnetAlphaOut::::get(sn1); + assert!( + alpha_out_after > alpha_out_before, + "suppressed subnet must still mint alpha: before={alpha_out_before:?} after={alpha_out_after:?}" + ); + + // Miners received pending alpha. + let pending_server = PendingServerEmission::::get(sn1); + assert!( + pending_server > AlphaCurrency::ZERO, + "miners must receive alpha on suppressed subnet" + ); + + // Validators received pending alpha. + let pending_validator = PendingValidatorEmission::::get(sn1); + assert!( + pending_validator > AlphaCurrency::ZERO, + "validators must receive alpha on suppressed subnet" + ); + + // Root validators received pending alpha. + let pending_root = PendingRootAlphaDivs::::get(sn1); + assert!( + pending_root > AlphaCurrency::ZERO, + "root must receive alpha on suppressed subnet" + ); + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Test: end-to-end run_coinbase with one suppressed dynamic subnet +// +// Integration test that calls the full run_coinbase pipeline with two dynamic +// subnets. One is suppressed via EmissionSuppressionOverride before the call. +// After run_coinbase the suppressed subnet must have received zero TAO +// (SubnetTAO unchanged, SubnetTaoInEmission == 0) while the active subnet +// absorbs the entire block emission. +// ───────────────────────────────────────────────────────────────────────────── +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib \ +// -- tests::emission_suppression::test_run_coinbase_suppressed_subnet_gets_zero_tao \ +// --exact --show-output --nocapture +#[test] +fn test_run_coinbase_suppressed_subnet_gets_zero_tao() { + new_test_ext(1).execute_with(|| { + // --- Setup: two dynamic subnets with AMM pools seeded with reserves. + let subnet_owner_hk = U256::from(100); + let subnet_owner_ck = U256::from(101); + + // sn_active receives emission; sn_suppressed is blocked. + let sn_active = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); + let sn_suppressed = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); + + // Seed AMM pools: 100_000 TAO, 1_000_000 alpha (price ≈ 0.1 TAO/alpha). + let initial_tao: u64 = 100_000_u64; + let initial_alpha: u64 = 1_000_000_u64; + // setup_reserves is pub(crate) and glob-imported from super::mock::*. + setup_reserves(sn_active, initial_tao.into(), initial_alpha.into()); + setup_reserves(sn_suppressed, initial_tao.into(), initial_alpha.into()); + + // Initialise swap-engine positions for both subnets with a zero-value swap + // so the pool is ready to price TAO emissions. + SubtensorModule::swap_tao_for_alpha( + sn_active, + TaoCurrency::ZERO, + 1_000_000_000_000_u64.into(), + false, + ) + .ok(); + SubtensorModule::swap_tao_for_alpha( + sn_suppressed, + TaoCurrency::ZERO, + 1_000_000_000_u64.into(), + false, + ) + .ok(); + + // Mark both subnets as dynamic (mechanism index 1). + SubnetMechanism::::insert(sn_active, 1u16); + SubnetMechanism::::insert(sn_suppressed, 1u16); + + // Give both subnets equal, positive TAO flow so they would normally split + // the block emission 50/50 without suppression. + SubnetTaoFlow::::insert(sn_active, 100_000_000_i64); + SubnetTaoFlow::::insert(sn_suppressed, 100_000_000_i64); + + // Snapshot state before the coinbase run. + let tao_active_before = SubnetTAO::::get(sn_active); + let tao_suppressed_before = SubnetTAO::::get(sn_suppressed); + let total_issuance_before = TotalIssuance::::get(); + + // --- Act: suppress sn_suppressed, then run the complete coinbase pipeline. + EmissionSuppressionOverride::::insert(sn_suppressed, true); + + let block_emission: u64 = 1_000_000_000_u64; // 1 TAO in planck units + SubtensorModule::run_coinbase(U96F32::from_num(block_emission)); + + // --- Assert 1: suppressed subnet received no direct TAO injection. + // + // SubnetTAO is mutated only by the direct `tao_in` injection path inside + // inject_and_maybe_swap. For a suppressed subnet get_subnet_terms inserts + // zero for tao_in, so SubnetTAO must be unchanged. + let tao_suppressed_after = SubnetTAO::::get(sn_suppressed); + assert_eq!( + tao_suppressed_after, + tao_suppressed_before, + "suppressed subnet SubnetTAO must not change: before={:?} after={:?}", + tao_suppressed_before, + tao_suppressed_after, + ); + + // --- Assert 2: per-block TAO emission record for suppressed subnet is zero. + // + // SubnetTaoInEmission is written by inject_and_maybe_swap for every subnet + // in the emit-to list; for the suppressed subnet it must be zero. + let tao_in_emission_suppressed = SubnetTaoInEmission::::get(sn_suppressed); + assert_eq!( + tao_in_emission_suppressed, + TaoCurrency::ZERO, + "SubnetTaoInEmission for suppressed subnet must be zero, got {:?}", + tao_in_emission_suppressed, + ); + + // --- Assert 3: active subnet received positive TAO injection. + // + // All of the emission share (100 %) went to sn_active, so its SubnetTAO + // must have grown. The exact amount is price-capped (the AMM splits the + // block emission into a direct injection and an excess-TAO swap), but the + // direct injection must be strictly positive. + let tao_active_after = SubnetTAO::::get(sn_active); + assert!( + tao_active_after > tao_active_before, + "active subnet must receive TAO emission: before={:?} after={:?}", + tao_active_before, + tao_active_after, + ); + + // --- Assert 4: the full block emission appears in TotalIssuance. + // + // TotalIssuance is incremented by both tao_in AND excess_tao for every + // emitting subnet (see inject_and_maybe_swap). Since the suppressed subnet + // contributes neither, the entire block_emission must flow to sn_active and + // therefore into TotalIssuance. A tolerance of 2 planck is allowed for + // fixed-point rounding in the U96F32 arithmetic used by the emission pipeline. + let total_issuance_after = TotalIssuance::::get(); + let issuance_delta = + u64::from(total_issuance_after).saturating_sub(u64::from(total_issuance_before)); + let rounding_tolerance: u64 = 2; + let undershoot = block_emission.saturating_sub(issuance_delta); + let overshoot = issuance_delta.saturating_sub(block_emission); + assert!( + undershoot <= rounding_tolerance && overshoot <= rounding_tolerance, + "TotalIssuance must grow by ~block_emission (±{rounding_tolerance} planck): \ + got {issuance_delta}, expected {block_emission}", + ); + }); +} diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index bbaf25af58..3db0bfc421 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -7,6 +7,7 @@ mod consensus; mod delegate_info; mod difficulty; mod emission; +mod emission_suppression; mod ensure; mod epoch; mod epoch_logs;