From 2c59e95e9fd6da3f23f89751c20265afd3757755 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Mon, 2 Mar 2026 16:55:15 +0300 Subject: [PATCH 1/9] Update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 3f20eb58e2..91993ddf2d 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,6 @@ scripts/specs/local.json # Node modules node_modules + +# Claude Code configuration +.claude \ No newline at end of file From 6ae5cb42893e0903f7576babbd21ebb6959df5d5 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 27 Feb 2026 16:09:41 +0300 Subject: [PATCH 2/9] Add staking tests staking --- e2e/pnpm-lock.yaml | 577 +++++++++++++++++- e2e/pnpm-workspace.yaml | 1 + e2e/shared/address.ts | 44 ++ e2e/shared/balance.ts | 29 + e2e/shared/devnet-client.ts | 30 + e2e/shared/index.ts | 35 ++ e2e/shared/logger.ts | 7 + e2e/shared/package.json | 10 +- e2e/shared/staking.ts | 517 ++++++++++++++++ e2e/shared/subnet.ts | 72 +++ e2e/shared/transactions.ts | 71 +++ e2e/staking/package.json | 23 + e2e/staking/setup.ts | 112 ++++ e2e/staking/test/add-stake-limit.test.ts | 66 ++ e2e/staking/test/add-stake.test.ts | 47 ++ e2e/staking/test/claim-root.test.ts | 477 +++++++++++++++ e2e/staking/test/move-stake.test.ts | 135 ++++ .../test/remove-stake-full-limit.test.ts | 85 +++ e2e/staking/test/remove-stake-limit.test.ts | 80 +++ e2e/staking/test/remove-stake.test.ts | 56 ++ e2e/staking/test/swap-stake-limit.test.ts | 128 ++++ e2e/staking/test/swap-stake.test.ts | 70 +++ e2e/staking/test/transfer-stake.test.ts | 132 ++++ e2e/staking/test/unstake-all-alpha.test.ts | 72 +++ e2e/staking/test/unstake-all.test.ts | 70 +++ e2e/staking/tsconfig.json | 11 + 26 files changed, 2931 insertions(+), 26 deletions(-) create mode 100644 e2e/shared/address.ts create mode 100644 e2e/shared/balance.ts create mode 100644 e2e/shared/devnet-client.ts create mode 100644 e2e/shared/index.ts create mode 100644 e2e/shared/logger.ts create mode 100644 e2e/shared/staking.ts create mode 100644 e2e/shared/subnet.ts create mode 100644 e2e/shared/transactions.ts create mode 100644 e2e/staking/package.json create mode 100644 e2e/staking/setup.ts create mode 100644 e2e/staking/test/add-stake-limit.test.ts create mode 100644 e2e/staking/test/add-stake.test.ts create mode 100644 e2e/staking/test/claim-root.test.ts create mode 100644 e2e/staking/test/move-stake.test.ts create mode 100644 e2e/staking/test/remove-stake-full-limit.test.ts create mode 100644 e2e/staking/test/remove-stake-limit.test.ts create mode 100644 e2e/staking/test/remove-stake.test.ts create mode 100644 e2e/staking/test/swap-stake-limit.test.ts create mode 100644 e2e/staking/test/swap-stake.test.ts create mode 100644 e2e/staking/test/transfer-stake.test.ts create mode 100644 e2e/staking/test/unstake-all-alpha.test.ts create mode 100644 e2e/staking/test/unstake-all.test.ts create mode 100644 e2e/staking/tsconfig.json diff --git a/e2e/pnpm-lock.yaml b/e2e/pnpm-lock.yaml index b97c90b17a..a6bc219540 100644 --- a/e2e/pnpm-lock.yaml +++ b/e2e/pnpm-lock.yaml @@ -46,10 +46,10 @@ importers: dependencies: '@polkadot-api/descriptors': specifier: file:.papi/descriptors - version: file:.papi/descriptors(polkadot-api@1.23.3(postcss@8.5.6)(rxjs@7.8.2)) + version: file:.papi/descriptors(polkadot-api@1.23.3(postcss@8.5.6)(rxjs@7.8.2)(tsx@4.21.0)) polkadot-api: specifier: 'catalog:' - version: 1.23.3(postcss@8.5.6)(rxjs@7.8.2) + version: 1.23.3(postcss@8.5.6)(rxjs@7.8.2)(tsx@4.21.0) devDependencies: prettier: specifier: 'catalog:' @@ -59,7 +59,7 @@ importers: dependencies: '@polkadot-api/descriptors': specifier: file:../.papi/descriptors - version: file:.papi/descriptors(polkadot-api@1.23.3(postcss@8.5.6)(rxjs@7.8.2)) + version: file:.papi/descriptors(polkadot-api@1.23.3(postcss@8.5.6)(rxjs@7.8.2)(tsx@4.21.0)) '@polkadot-labs/hdkd': specifier: 'catalog:' version: 0.0.25 @@ -71,14 +71,14 @@ importers: version: 14.0.1(@polkadot/util-crypto@14.0.1(@polkadot/util@14.0.1))(@polkadot/util@14.0.1) polkadot-api: specifier: 'catalog:' - version: 1.23.3(postcss@8.5.6)(rxjs@7.8.2) + version: 1.23.3(postcss@8.5.6)(rxjs@7.8.2)(tsx@4.21.0) devDependencies: '@types/node': specifier: 'catalog:' version: 24.10.13 vitest: specifier: 'catalog:' - version: 4.0.18(@types/node@24.10.13) + version: 4.0.18(@types/node@24.10.13)(tsx@4.21.0) shield: dependencies: @@ -87,7 +87,7 @@ importers: version: 2.1.1 '@polkadot-api/descriptors': specifier: file:../.papi/descriptors - version: file:.papi/descriptors(polkadot-api@1.23.3(postcss@8.5.6)(rxjs@7.8.2)) + version: file:.papi/descriptors(polkadot-api@1.23.3(postcss@8.5.6)(rxjs@7.8.2)(tsx@4.21.0)) '@polkadot/util': specifier: 'catalog:' version: 14.0.1 @@ -102,14 +102,33 @@ importers: version: 2.5.0 polkadot-api: specifier: 'catalog:' - version: 1.23.3(postcss@8.5.6)(rxjs@7.8.2) + version: 1.23.3(postcss@8.5.6)(rxjs@7.8.2)(tsx@4.21.0) devDependencies: '@types/node': specifier: 'catalog:' version: 24.10.13 vitest: specifier: 'catalog:' - version: 4.0.18(@types/node@24.10.13) + version: 4.0.18(@types/node@24.10.13)(tsx@4.21.0) + + staking: + dependencies: + e2e-shared: + specifier: workspace:* + version: link:../shared + mocha: + specifier: ^11.1.0 + version: 11.7.5 + tsx: + specifier: ^4.21.0 + version: 4.21.0 + devDependencies: + '@types/mocha': + specifier: ^10.0.10 + version: 10.0.10 + '@types/node': + specifier: 'catalog:' + version: 24.10.13 packages: @@ -438,6 +457,10 @@ packages: cpu: [x64] os: [win32] + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -471,6 +494,10 @@ packages: resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} engines: {node: '>= 20.19.0'} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@polkadot-api/cli@0.18.1': resolution: {integrity: sha512-jPa8WSNPZWdy372sBAUnm0nU1XX5mLbmgkOOU39+zpYPSE12mYXyM3r7JuT5IHdAccEJr6qK2DplPFTeNSyq9A==} hasBin: true @@ -835,6 +862,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/mocha@10.0.10': + resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} + '@types/node@24.10.13': resolution: {integrity: sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==} @@ -881,20 +911,44 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + ansi-regex@6.2.2: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bn.js@5.2.2: resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -905,10 +959,18 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -925,6 +987,17 @@ packages: resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} engines: {node: '>=18.20'} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + commander@14.0.3: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} @@ -953,6 +1026,10 @@ packages: supports-color: optional: true + decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + deepmerge-ts@7.1.5: resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} engines: {node: '>=16.0.0'} @@ -961,6 +1038,19 @@ packages: resolution: {integrity: sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==} engines: {node: '>=12.20'} + diff@7.0.0: + resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} + engines: {node: '>=0.3.1'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} @@ -974,6 +1064,14 @@ packages: engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -998,9 +1096,21 @@ packages: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + fs.promises.exists@1.1.4: resolution: {integrity: sha512-lJzUGWbZn8vhGWBedA+RYjB/BeJ+3458ljUfmplqhIeb6ewzTFWNPCR1HCiYCkXV9zxcHz9zXkJzMsEgDLzh3Q==} @@ -1009,6 +1119,10 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.4.0: resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} @@ -1017,6 +1131,22 @@ packages: resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} engines: {node: '>=18'} + get-tsconfig@4.13.6: + resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} @@ -1037,10 +1167,22 @@ packages: resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} engines: {node: '>=18'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-interactive@2.0.0: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -1049,6 +1191,10 @@ packages: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + is-unicode-supported@2.1.0: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} @@ -1056,6 +1202,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -1063,6 +1212,10 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1074,9 +1227,17 @@ packages: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + log-symbols@7.0.1: resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} engines: {node: '>=18'} @@ -1095,6 +1256,14 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + mlkem@2.5.0: resolution: {integrity: sha512-TnSvGBs0EVPukQcdPF0882ZoYXYuD2rb+VgO0kUDbFi/XM1rJOwnQoFW3wGGuc3nG3AT/zp3oWJ86W7ewwKYyA==} engines: {node: '>=16.0.0'} @@ -1102,6 +1271,11 @@ packages: mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + mocha@11.7.5: + resolution: {integrity: sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1140,6 +1314,17 @@ packages: resolution: {integrity: sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==} engines: {node: '>=20'} + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parse-json@8.3.0: resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} engines: {node: '>=18'} @@ -1148,6 +1333,10 @@ packages: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1156,6 +1345,10 @@ packages: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1214,6 +1407,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + read-pkg@10.1.0: resolution: {integrity: sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==} engines: {node: '>=20'} @@ -1226,10 +1422,17 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -1242,6 +1445,9 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + scale-ts@1.6.1: resolution: {integrity: sha512-PBMc2AWc6wSEqJYBDPcyCLUj9/tMKnLX70jLOSndMtcUoLQucP/DM0vnQo1wJAYjTrQiq8iG9rD0q6wFzgjH7g==} @@ -1250,6 +1456,9 @@ packages: engines: {node: '>=10'} hasBin: true + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1303,10 +1512,22 @@ packages: resolution: {integrity: sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==} engines: {node: '>=18'} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string-width@8.1.1: resolution: {integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==} engines: {node: '>=20'} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + strip-ansi@7.1.2: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} @@ -1315,11 +1536,23 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + sucrase@3.35.1: resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} @@ -1387,6 +1620,11 @@ packages: typescript: optional: true + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} @@ -1514,6 +1752,17 @@ packages: engines: {node: '>=8'} hasBin: true + workerpool@9.3.4: + resolution: {integrity: sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + write-file-atomic@5.0.1: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -1538,6 +1787,26 @@ packages: utf-8-validate: optional: true + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + yoctocolors@2.1.2: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} @@ -1712,6 +1981,15 @@ snapshots: '@esbuild/win32-x64@0.27.3': optional: true + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -1740,7 +2018,10 @@ snapshots: '@noble/hashes@2.0.1': {} - '@polkadot-api/cli@0.18.1(postcss@8.5.6)': + '@pkgjs/parseargs@0.11.0': + optional: true + + '@polkadot-api/cli@0.18.1(postcss@8.5.6)(tsx@4.21.0)': dependencies: '@commander-js/extra-typings': 14.0.0(commander@14.0.3) '@polkadot-api/codegen': 0.21.2 @@ -1766,7 +2047,7 @@ snapshots: read-pkg: 10.1.0 rxjs: 7.8.2 tsc-prog: 2.3.0(typescript@5.9.3) - tsup: 8.5.0(postcss@8.5.6)(typescript@5.9.3) + tsup: 8.5.0(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3) typescript: 5.9.3 write-package: 7.2.0 transitivePeerDependencies: @@ -1788,9 +2069,9 @@ snapshots: '@polkadot-api/substrate-bindings': 0.17.0 '@polkadot-api/utils': 0.2.0 - '@polkadot-api/descriptors@file:.papi/descriptors(polkadot-api@1.23.3(postcss@8.5.6)(rxjs@7.8.2))': + '@polkadot-api/descriptors@file:.papi/descriptors(polkadot-api@1.23.3(postcss@8.5.6)(rxjs@7.8.2)(tsx@4.21.0))': dependencies: - polkadot-api: 1.23.3(postcss@8.5.6)(rxjs@7.8.2) + polkadot-api: 1.23.3(postcss@8.5.6)(rxjs@7.8.2)(tsx@4.21.0) '@polkadot-api/ink-contracts@0.4.6': dependencies: @@ -2160,6 +2441,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/mocha@10.0.10': {} + '@types/node@24.10.13': dependencies: undici-types: 7.16.0 @@ -2183,13 +2466,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@24.10.13))': + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@24.10.13)(tsx@4.21.0))': dependencies: '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@24.10.13) + vite: 7.3.1(@types/node@24.10.13)(tsx@4.21.0) '@vitest/pretty-format@4.0.18': dependencies: @@ -2215,14 +2498,32 @@ snapshots: acorn@8.16.0: {} + ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + any-promise@1.3.0: {} + argparse@2.0.1: {} + assertion-error@2.0.1: {} + balanced-match@1.0.2: {} + bn.js@5.2.2: {} + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + browser-stdout@1.3.1: {} + bundle-require@5.1.0(esbuild@0.25.12): dependencies: esbuild: 0.25.12 @@ -2230,8 +2531,15 @@ snapshots: cac@6.7.14: {} + camelcase@6.3.0: {} + chai@6.2.2: {} + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + chalk@5.6.2: {} chokidar@4.0.3: @@ -2244,6 +2552,18 @@ snapshots: cli-spinners@3.4.0: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + commander@14.0.3: {} commander@4.1.1: {} @@ -2258,14 +2578,26 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - debug@4.4.3: + debug@4.4.3(supports-color@8.1.1): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + decamelize@4.0.0: {} deepmerge-ts@7.1.5: {} detect-indent@7.0.2: {} + diff@7.0.0: {} + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + es-module-lexer@1.7.0: {} esbuild@0.25.12: @@ -2326,6 +2658,10 @@ snapshots: '@esbuild/win32-ia32': 0.27.3 '@esbuild/win32-x64': 0.27.3 + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -2355,17 +2691,31 @@ snapshots: dependencies: is-unicode-supported: 2.1.0 + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + fix-dts-default-cjs-exports@1.0.1: dependencies: magic-string: 0.30.21 mlly: 1.8.0 rollup: 4.57.1 + flat@5.0.2: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + fs.promises.exists@1.1.4: {} fsevents@2.3.3: optional: true + get-caller-file@2.0.5: {} + get-east-asian-width@1.4.0: {} get-stream@9.0.1: @@ -2373,6 +2723,23 @@ snapshots: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 + get-tsconfig@4.13.6: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + has-flag@4.0.0: {} + + he@1.2.0: {} + hosted-git-info@7.0.2: dependencies: lru-cache: 10.4.3 @@ -2387,28 +2754,55 @@ snapshots: index-to-position@1.2.0: {} + is-fullwidth-code-point@3.0.0: {} + is-interactive@2.0.0: {} + is-path-inside@3.0.3: {} + + is-plain-obj@2.1.0: {} + is-plain-obj@4.1.0: {} is-stream@4.0.1: {} + is-unicode-supported@0.1.0: {} + is-unicode-supported@2.1.0: {} isexe@2.0.0: {} + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + joycon@3.1.1: {} js-tokens@4.0.0: {} + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} load-tsconfig@0.2.5: {} + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lodash.sortby@4.7.0: {} + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + log-symbols@7.0.1: dependencies: is-unicode-supported: 2.1.0 @@ -2424,6 +2818,12 @@ snapshots: mimic-function@5.0.1: {} + minimatch@9.0.9: + dependencies: + brace-expansion: 2.0.2 + + minipass@7.1.3: {} + mlkem@2.5.0: {} mlly@1.8.0: @@ -2433,6 +2833,30 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.3 + mocha@11.7.5: + dependencies: + browser-stdout: 1.3.1 + chokidar: 4.0.3 + debug: 4.4.3(supports-color@8.1.1) + diff: 7.0.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 10.5.0 + he: 1.2.0 + is-path-inside: 3.0.3 + js-yaml: 4.1.1 + log-symbols: 4.1.0 + minimatch: 9.0.9 + ms: 2.1.3 + picocolors: 1.1.1 + serialize-javascript: 6.0.2 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 9.3.4 + yargs: 17.7.2 + yargs-parser: 21.1.1 + yargs-unparser: 2.0.0 + ms@2.1.3: {} mz@2.7.0: @@ -2479,6 +2903,16 @@ snapshots: stdin-discarder: 0.3.1 string-width: 8.1.1 + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + parse-json@8.3.0: dependencies: '@babel/code-frame': 7.29.0 @@ -2487,10 +2921,17 @@ snapshots: parse-ms@4.0.0: {} + path-exists@4.0.0: {} + path-key@3.1.1: {} path-key@4.0.0: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + pathe@2.0.3: {} picocolors@1.1.1: {} @@ -2505,9 +2946,9 @@ snapshots: mlly: 1.8.0 pathe: 2.0.3 - polkadot-api@1.23.3(postcss@8.5.6)(rxjs@7.8.2): + polkadot-api@1.23.3(postcss@8.5.6)(rxjs@7.8.2)(tsx@4.21.0): dependencies: - '@polkadot-api/cli': 0.18.1(postcss@8.5.6) + '@polkadot-api/cli': 0.18.1(postcss@8.5.6)(tsx@4.21.0) '@polkadot-api/ink-contracts': 0.4.6 '@polkadot-api/json-rpc-provider': 0.0.4 '@polkadot-api/known-chains': 0.9.18 @@ -2538,11 +2979,12 @@ snapshots: - utf-8-validate - yaml - postcss-load-config@6.0.1(postcss@8.5.6): + postcss-load-config@6.0.1(postcss@8.5.6)(tsx@4.21.0): dependencies: lilconfig: 3.1.3 optionalDependencies: postcss: 8.5.6 + tsx: 4.21.0 postcss@8.5.6: dependencies: @@ -2558,6 +3000,10 @@ snapshots: punycode@2.3.1: {} + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + read-pkg@10.1.0: dependencies: '@types/normalize-package-data': 2.4.4 @@ -2576,8 +3022,12 @@ snapshots: readdirp@4.1.2: {} + require-directory@2.1.1: {} + resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -2618,10 +3068,16 @@ snapshots: dependencies: tslib: 2.8.1 + safe-buffer@5.2.1: {} + scale-ts@1.6.1: {} semver@7.7.4: {} + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -2669,17 +3125,35 @@ snapshots: stdin-discarder@0.3.1: {} + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + string-width@8.1.1: dependencies: get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + strip-ansi@7.1.2: dependencies: ansi-regex: 6.2.2 strip-final-newline@4.0.0: {} + strip-json-comments@3.1.1: {} + sucrase@3.35.1: dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -2690,6 +3164,14 @@ snapshots: tinyglobby: 0.2.15 ts-interface-checker: 0.1.13 + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + tagged-tag@1.0.0: {} thenify-all@1.6.0: @@ -2727,18 +3209,18 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.0(postcss@8.5.6)(typescript@5.9.3): + tsup@8.5.0(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3): dependencies: bundle-require: 5.1.0(esbuild@0.25.12) cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) esbuild: 0.25.12 fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(postcss@8.5.6) + postcss-load-config: 6.0.1(postcss@8.5.6)(tsx@4.21.0) resolve-from: 5.0.0 rollup: 4.57.1 source-map: 0.8.0-beta.0 @@ -2755,6 +3237,13 @@ snapshots: - tsx - yaml + tsx@4.21.0: + dependencies: + esbuild: 0.27.3 + get-tsconfig: 4.13.6 + optionalDependencies: + fsevents: 2.3.3 + type-fest@4.41.0: {} type-fest@5.4.4: @@ -2780,7 +3269,7 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - vite@7.3.1(@types/node@24.10.13): + vite@7.3.1(@types/node@24.10.13)(tsx@4.21.0): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) @@ -2791,11 +3280,12 @@ snapshots: optionalDependencies: '@types/node': 24.10.13 fsevents: 2.3.3 + tsx: 4.21.0 - vitest@4.0.18(@types/node@24.10.13): + vitest@4.0.18(@types/node@24.10.13)(tsx@4.21.0): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@24.10.13)) + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@24.10.13)(tsx@4.21.0)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18 @@ -2812,7 +3302,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@24.10.13) + vite: 7.3.1(@types/node@24.10.13)(tsx@4.21.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.10.13 @@ -2846,6 +3336,20 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + workerpool@9.3.4: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + write-file-atomic@5.0.1: dependencies: imurmurhash: 0.1.4 @@ -2868,4 +3372,27 @@ snapshots: ws@8.19.0: {} + y18n@5.0.8: {} + + yargs-parser@21.1.1: {} + + yargs-unparser@2.0.0: + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + yoctocolors@2.1.2: {} diff --git a/e2e/pnpm-workspace.yaml b/e2e/pnpm-workspace.yaml index d1504a1228..5c59deeac3 100644 --- a/e2e/pnpm-workspace.yaml +++ b/e2e/pnpm-workspace.yaml @@ -1,6 +1,7 @@ packages: - shared - shield + - staking catalog: "@noble/ciphers": "^2.1.1" diff --git a/e2e/shared/address.ts b/e2e/shared/address.ts new file mode 100644 index 0000000000..5585c4cb68 --- /dev/null +++ b/e2e/shared/address.ts @@ -0,0 +1,44 @@ +import { sr25519CreateDerive } from "@polkadot-labs/hdkd"; +import { DEV_PHRASE, entropyToMiniSecret, mnemonicToEntropy, KeyPair } from "@polkadot-labs/hdkd-helpers"; +import { getPolkadotSigner } from "polkadot-api/signer"; +import { PolkadotSigner } from "polkadot-api"; +import { randomBytes } from "crypto"; +import { ss58Address } from "@polkadot-labs/hdkd-helpers"; + +export const SS58_PREFIX = 42; + +// ─── KEYPAIR UTILITIES ─────────────────────────────────────────────────────── + +export function getKeypairFromPath(path: string): KeyPair { + const entropy = mnemonicToEntropy(DEV_PHRASE); + const miniSecret = entropyToMiniSecret(entropy); + const derive = sr25519CreateDerive(miniSecret); + return derive(path); +} + +export const getAlice = () => getKeypairFromPath("//Alice"); + +export function getRandomSubstrateKeypair(): KeyPair { + const seed = randomBytes(32); + const miniSecret = entropyToMiniSecret(seed); + const derive = sr25519CreateDerive(miniSecret); + return derive(""); +} + +// ─── SIGNER UTILITIES ──────────────────────────────────────────────────────── + +export function getSignerFromKeypair(keypair: KeyPair): PolkadotSigner { + return getPolkadotSigner(keypair.publicKey, "Sr25519", keypair.sign); +} + +export function getSignerFromPath(path: string): PolkadotSigner { + return getSignerFromKeypair(getKeypairFromPath(path)); +} + +export const getAliceSigner = () => getSignerFromPath("//Alice"); + +// ─── ADDRESS UTILITIES ─────────────────────────────────────────────────────── + +export function convertPublicKeyToSs58(publicKey: Uint8Array): string { + return ss58Address(publicKey, SS58_PREFIX); +} diff --git a/e2e/shared/balance.ts b/e2e/shared/balance.ts new file mode 100644 index 0000000000..d5cafa1326 --- /dev/null +++ b/e2e/shared/balance.ts @@ -0,0 +1,29 @@ +import { subtensor, MultiAddress } from "@polkadot-api/descriptors"; +import { TypedApi } from "polkadot-api"; +import { getAliceSigner } from "./address.js"; +import { waitForTransactionWithRetry } from "./transactions.js"; + +export const TAO = BigInt(1000000000); // 10^9 RAO per TAO + +export function tao(value: number): bigint { + return TAO * BigInt(value); +} + +export async function getBalance(api: TypedApi, ss58Address: string): Promise { + const account = await api.query.System.Account.getValue(ss58Address); + return account.data.free; +} + +export async function forceSetBalance( + api: TypedApi, + ss58Address: string, + amount: bigint = tao(1e10) +): Promise { + const alice = getAliceSigner(); + const internalCall = api.tx.Balances.force_set_balance({ + who: MultiAddress.Id(ss58Address), + new_free: amount, + }); + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }); + await waitForTransactionWithRetry(api, tx, alice, "force_set_balance"); +} diff --git a/e2e/shared/devnet-client.ts b/e2e/shared/devnet-client.ts new file mode 100644 index 0000000000..776472fb5e --- /dev/null +++ b/e2e/shared/devnet-client.ts @@ -0,0 +1,30 @@ +import { subtensor } from "@polkadot-api/descriptors"; +import { TypedApi, PolkadotClient, createClient } from "polkadot-api"; +import { getWsProvider } from "polkadot-api/ws-provider/web"; + +export const SUB_LOCAL_URL = "ws://localhost:9944"; + +let client: PolkadotClient | undefined = undefined; +let api: TypedApi | undefined = undefined; + +export async function getClient(): Promise { + if (client === undefined) { + const provider = getWsProvider(SUB_LOCAL_URL); + client = createClient(provider); + } + return client; +} + +export async function getDevnetApi(): Promise> { + if (api === undefined) { + const c = await getClient(); + api = c.getTypedApi(subtensor); + } + return api; +} + +export function destroyClient(): void { + client?.destroy(); + client = undefined; + api = undefined; +} diff --git a/e2e/shared/index.ts b/e2e/shared/index.ts new file mode 100644 index 0000000000..1e686b816d --- /dev/null +++ b/e2e/shared/index.ts @@ -0,0 +1,35 @@ +// Node management +export { + startNode, + stop, + started, + peerCount, + finalizedBlocks, + innerEnsure, + log as nodeLog, + type NodeOptions, + type Node, +} from "./node.js"; +export * from "./chainspec.js"; +export * from "./sequencer.js"; + +// Client utilities (shield-style) +export { + connectClient, + createSigner, + getAccountNonce, + getBalance as getBalanceByAddress, + sleep, + waitForFinalizedBlocks, + type ClientConnection, + type Signer, +} from "./client.js"; + +// Blockchain API utilities (staking-tests style) +export * from "./logger.js"; +export * from "./devnet-client.js"; +export * from "./address.js"; +export * from "./transactions.js"; +export * from "./balance.js"; +export * from "./subnet.js"; +export * from "./staking.js"; diff --git a/e2e/shared/logger.ts b/e2e/shared/logger.ts new file mode 100644 index 0000000000..041443353a --- /dev/null +++ b/e2e/shared/logger.ts @@ -0,0 +1,7 @@ +const LOG_INDENT = " "; + +export const log = { + tx: (label: string, msg: string) => console.log(`${LOG_INDENT}[${label}] ${msg}`), + info: (msg: string) => console.log(`${LOG_INDENT}${msg}`), + error: (label: string, msg: string) => console.error(`${LOG_INDENT}[${label}] ${msg}`), +}; diff --git a/e2e/shared/package.json b/e2e/shared/package.json index 85889063ec..6efbfae4e6 100644 --- a/e2e/shared/package.json +++ b/e2e/shared/package.json @@ -3,10 +3,18 @@ "version": "1.0.0", "type": "module", "exports": { + ".": "./index.ts", "./node.js": "./node.ts", "./chainspec.js": "./chainspec.ts", "./sequencer.js": "./sequencer.ts", - "./client.js": "./client.ts" + "./client.js": "./client.ts", + "./logger.js": "./logger.ts", + "./devnet-client.js": "./devnet-client.ts", + "./address.js": "./address.ts", + "./transactions.js": "./transactions.ts", + "./balance.js": "./balance.ts", + "./subnet.js": "./subnet.ts", + "./staking.js": "./staking.ts" }, "dependencies": { "@polkadot/keyring": "catalog:", diff --git a/e2e/shared/staking.ts b/e2e/shared/staking.ts new file mode 100644 index 0000000000..080172ba33 --- /dev/null +++ b/e2e/shared/staking.ts @@ -0,0 +1,517 @@ +import { subtensor } from "@polkadot-api/descriptors"; +import { TypedApi } from "polkadot-api"; +import { KeyPair } from "@polkadot-labs/hdkd-helpers"; +import { getSignerFromKeypair, getAliceSigner } from "./address.js"; +import { waitForTransactionWithRetry } from "./transactions.js"; + +// U64F64 is a 128-bit fixed-point type with 64 fractional bits. +// Raw storage values must be divided by 2^64 to get the actual value. +const U64F64_FRACTIONAL_BITS = 64n; +const U64F64_MULTIPLIER = 1n << U64F64_FRACTIONAL_BITS; // 2^64 + +/** + * Convert a raw U64F64 storage value to its integer part (truncated). + */ +export function u64f64ToInt(raw: bigint): bigint { + return raw >> U64F64_FRACTIONAL_BITS; +} + +/** + * Convert an integer to U64F64 raw format for use in extrinsics. + */ +export function intToU64f64(value: bigint): bigint { + return value << U64F64_FRACTIONAL_BITS; +} + +/** + * Convert a raw U64F64 storage value to a decimal number for display. + */ +export function u64f64ToNumber(raw: bigint): number { + return Number(raw) / Number(U64F64_MULTIPLIER); +} + +export async function addStake( + api: TypedApi, + coldkey: KeyPair, + hotkey: string, + netuid: number, + amount: bigint +): Promise { + const signer = getSignerFromKeypair(coldkey); + const tx = api.tx.SubtensorModule.add_stake({ + hotkey: hotkey, + netuid: netuid, + amount_staked: amount, + }); + await waitForTransactionWithRetry(api, tx, signer, "add_stake"); +} + +export async function addStakeLimit( + api: TypedApi, + coldkey: KeyPair, + hotkey: string, + netuid: number, + amount: bigint, + limitPrice: bigint, + allowPartial: boolean +): Promise { + const signer = getSignerFromKeypair(coldkey); + const tx = api.tx.SubtensorModule.add_stake_limit({ + hotkey: hotkey, + netuid: netuid, + amount_staked: amount, + limit_price: limitPrice, + allow_partial: allowPartial, + }); + await waitForTransactionWithRetry(api, tx, signer, "add_stake_limit"); +} + +export async function removeStake( + api: TypedApi, + coldkey: KeyPair, + hotkey: string, + netuid: number, + amount: bigint +): Promise { + const signer = getSignerFromKeypair(coldkey); + const tx = api.tx.SubtensorModule.remove_stake({ + hotkey: hotkey, + netuid: netuid, + amount_unstaked: amount, + }); + await waitForTransactionWithRetry(api, tx, signer, "remove_stake"); +} + +export async function removeStakeLimit( + api: TypedApi, + coldkey: KeyPair, + hotkey: string, + netuid: number, + amount: bigint, + limitPrice: bigint, + allowPartial: boolean +): Promise { + const signer = getSignerFromKeypair(coldkey); + const tx = api.tx.SubtensorModule.remove_stake_limit({ + hotkey: hotkey, + netuid: netuid, + amount_unstaked: amount, + limit_price: limitPrice, + allow_partial: allowPartial, + }); + await waitForTransactionWithRetry(api, tx, signer, "remove_stake_limit"); +} + +export async function removeStakeFullLimit( + api: TypedApi, + coldkey: KeyPair, + hotkey: string, + netuid: number, + limitPrice: bigint | undefined +): Promise { + const signer = getSignerFromKeypair(coldkey); + const tx = api.tx.SubtensorModule.remove_stake_full_limit({ + hotkey: hotkey, + netuid: netuid, + limit_price: limitPrice, + }); + await waitForTransactionWithRetry(api, tx, signer, "remove_stake_full_limit"); +} + +export async function unstakeAll( + api: TypedApi, + coldkey: KeyPair, + hotkey: string +): Promise { + const signer = getSignerFromKeypair(coldkey); + const tx = api.tx.SubtensorModule.unstake_all({ + hotkey: hotkey, + }); + await waitForTransactionWithRetry(api, tx, signer, "unstake_all"); +} + +export async function unstakeAllAlpha( + api: TypedApi, + coldkey: KeyPair, + hotkey: string +): Promise { + const signer = getSignerFromKeypair(coldkey); + const tx = api.tx.SubtensorModule.unstake_all_alpha({ + hotkey: hotkey, + }); + await waitForTransactionWithRetry(api, tx, signer, "unstake_all_alpha"); +} + +/** + * Get stake shares (Alpha) for a hotkey/coldkey/netuid triplet. + * Returns the integer part of the U64F64 value. + */ +export async function getStake( + api: TypedApi, + hotkey: string, + coldkey: string, + netuid: number +): Promise { + const raw = await api.query.SubtensorModule.Alpha.getValue(hotkey, coldkey, netuid); + return u64f64ToInt(raw); +} + +/** + * Get raw stake shares (Alpha) in U64F64 format. + * Use this when you need the raw value for extrinsics like transfer_stake. + */ +export async function getStakeRaw( + api: TypedApi, + hotkey: string, + coldkey: string, + netuid: number +): Promise { + return await api.query.SubtensorModule.Alpha.getValue(hotkey, coldkey, netuid); +} + +export async function transferStake( + api: TypedApi, + originColdkey: KeyPair, + destinationColdkey: string, + hotkey: string, + originNetuid: number, + destinationNetuid: number, + amount: bigint +): Promise { + const signer = getSignerFromKeypair(originColdkey); + const tx = api.tx.SubtensorModule.transfer_stake({ + destination_coldkey: destinationColdkey, + hotkey: hotkey, + origin_netuid: originNetuid, + destination_netuid: destinationNetuid, + alpha_amount: amount, + }); + await waitForTransactionWithRetry(api, tx, signer, "transfer_stake"); +} + +export async function moveStake( + api: TypedApi, + coldkey: KeyPair, + originHotkey: string, + destinationHotkey: string, + originNetuid: number, + destinationNetuid: number, + amount: bigint +): Promise { + const signer = getSignerFromKeypair(coldkey); + const tx = api.tx.SubtensorModule.move_stake({ + origin_hotkey: originHotkey, + destination_hotkey: destinationHotkey, + origin_netuid: originNetuid, + destination_netuid: destinationNetuid, + alpha_amount: amount, + }); + await waitForTransactionWithRetry(api, tx, signer, "move_stake"); +} + +export async function swapStake( + api: TypedApi, + coldkey: KeyPair, + hotkey: string, + originNetuid: number, + destinationNetuid: number, + amount: bigint +): Promise { + const signer = getSignerFromKeypair(coldkey); + const tx = api.tx.SubtensorModule.swap_stake({ + hotkey: hotkey, + origin_netuid: originNetuid, + destination_netuid: destinationNetuid, + alpha_amount: amount, + }); + await waitForTransactionWithRetry(api, tx, signer, "swap_stake"); +} + +export async function swapStakeLimit( + api: TypedApi, + coldkey: KeyPair, + hotkey: string, + originNetuid: number, + destinationNetuid: number, + amount: bigint, + limitPrice: bigint, + allowPartial: boolean +): Promise { + const signer = getSignerFromKeypair(coldkey); + const tx = api.tx.SubtensorModule.swap_stake_limit({ + hotkey: hotkey, + origin_netuid: originNetuid, + destination_netuid: destinationNetuid, + alpha_amount: amount, + limit_price: limitPrice, + allow_partial: allowPartial, + }); + await waitForTransactionWithRetry(api, tx, signer, "swap_stake_limit"); +} + +export type RootClaimType = "Swap" | "Keep" | { type: "KeepSubnets"; subnets: number[] }; + +export async function getRootClaimType( + api: TypedApi, + coldkey: string +): Promise { + const result = await api.query.SubtensorModule.RootClaimType.getValue(coldkey); + if (result.type === "KeepSubnets") { + return { type: "KeepSubnets", subnets: result.value.subnets as number[] }; + } + return result.type as "Swap" | "Keep"; +} + +export async function setRootClaimType( + api: TypedApi, + coldkey: KeyPair, + claimType: RootClaimType +): Promise { + const signer = getSignerFromKeypair(coldkey); + let newRootClaimType; + if (typeof claimType === "string") { + newRootClaimType = { type: claimType, value: undefined }; + } else { + newRootClaimType = { type: "KeepSubnets", value: { subnets: claimType.subnets } }; + } + const tx = api.tx.SubtensorModule.set_root_claim_type({ + new_root_claim_type: newRootClaimType, + }); + await waitForTransactionWithRetry(api, tx, signer, "set_root_claim_type"); +} + +export async function claimRoot( + api: TypedApi, + coldkey: KeyPair, + subnets: number[] +): Promise { + const signer = getSignerFromKeypair(coldkey); + const tx = api.tx.SubtensorModule.claim_root({ + subnets: subnets, + }); + await waitForTransactionWithRetry(api, tx, signer, "claim_root"); +} + +export async function getNumRootClaims( + api: TypedApi +): Promise { + return await api.query.SubtensorModule.NumRootClaim.getValue(); +} + +export async function sudoSetNumRootClaims( + api: TypedApi, + newValue: bigint +): Promise { + const alice = getAliceSigner(); + const internalCall = api.tx.SubtensorModule.sudo_set_num_root_claims({ + new_value: newValue, + }); + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }); + await waitForTransactionWithRetry(api, tx, alice, "sudo_set_num_root_claims"); +} + +export async function getRootClaimThreshold( + api: TypedApi, + netuid: number +): Promise { + return await api.query.SubtensorModule.RootClaimableThreshold.getValue(netuid); +} + +export async function sudoSetRootClaimThreshold( + api: TypedApi, + netuid: number, + newValue: bigint +): Promise { + const alice = getAliceSigner(); + const internalCall = api.tx.SubtensorModule.sudo_set_root_claim_threshold({ + netuid: netuid, + new_value: newValue, + }); + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }); + await waitForTransactionWithRetry(api, tx, alice, "sudo_set_root_claim_threshold"); +} + +export async function getTempo( + api: TypedApi, + netuid: number +): Promise { + return await api.query.SubtensorModule.Tempo.getValue(netuid); +} + +export async function sudoSetTempo( + api: TypedApi, + netuid: number, + tempo: number +): Promise { + 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 waitForTransactionWithRetry(api, tx, alice, "sudo_set_tempo"); +} + +export async function waitForBlocks( + api: TypedApi, + numBlocks: number +): Promise { + const startBlock = await api.query.System.Number.getValue(); + const targetBlock = startBlock + numBlocks; + + while (true) { + const currentBlock = await api.query.System.Number.getValue(); + if (currentBlock >= targetBlock) { + break; + } + await new Promise((resolve) => setTimeout(resolve, 1000)); + } +} + +export async function getRootClaimable( + api: TypedApi, + hotkey: string +): Promise> { + const result = await api.query.SubtensorModule.RootClaimable.getValue(hotkey); + const claimableMap = new Map(); + for (const [netuid, amount] of result) { + claimableMap.set(netuid, amount); + } + return claimableMap; +} + +export async function getRootClaimed( + api: TypedApi, + netuid: number, + hotkey: string, + coldkey: string +): Promise { + return await api.query.SubtensorModule.RootClaimed.getValue(netuid, hotkey, coldkey); +} + +export async function isSubtokenEnabled( + api: TypedApi, + netuid: number +): Promise { + return await api.query.SubtensorModule.SubtokenEnabled.getValue(netuid); +} + +export async function sudoSetSubtokenEnabled( + api: TypedApi, + netuid: number, + enabled: boolean +): Promise { + const alice = getAliceSigner(); + const internalCall = api.tx.AdminUtils.sudo_set_subtoken_enabled({ + netuid: netuid, + subtoken_enabled: enabled, + }); + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }); + await waitForTransactionWithRetry(api, tx, alice, "sudo_set_subtoken_enabled"); +} + +export async function isNetworkAdded( + api: TypedApi, + netuid: number +): Promise { + return await api.query.SubtensorModule.NetworksAdded.getValue(netuid); +} + +export async function getAdminFreezeWindow( + api: TypedApi +): Promise { + return await api.query.SubtensorModule.AdminFreezeWindow.getValue(); +} + +export async function sudoSetAdminFreezeWindow( + api: TypedApi, + window: number +): Promise { + const alice = getAliceSigner(); + const internalCall = api.tx.AdminUtils.sudo_set_admin_freeze_window({ + window: window, + }); + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }); + await waitForTransactionWithRetry(api, tx, alice, "sudo_set_admin_freeze_window"); +} + +export async function sudoSetEmaPriceHalvingPeriod( + api: TypedApi, + netuid: number, + emaPriceHalvingPeriod: number +): Promise { + const alice = getAliceSigner(); + const internalCall = api.tx.AdminUtils.sudo_set_ema_price_halving_period({ + netuid: netuid, + ema_halving: BigInt(emaPriceHalvingPeriod), + }); + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }); + await waitForTransactionWithRetry(api, tx, alice, "sudo_set_ema_price_halving_period"); +} + +export async function sudoSetLockReductionInterval( + api: TypedApi, + interval: number +): Promise { + const alice = getAliceSigner(); + const internalCall = api.tx.AdminUtils.sudo_set_lock_reduction_interval({ + interval: BigInt(interval), + }); + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }); + await waitForTransactionWithRetry(api, tx, alice, "sudo_set_lock_reduction_interval"); +} + +export async function sudoSetSubnetMovingAlpha( + api: TypedApi, + alpha: bigint +): Promise { + const alice = getAliceSigner(); + const internalCall = api.tx.AdminUtils.sudo_set_subnet_moving_alpha({ + alpha: alpha, + }); + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }); + await waitForTransactionWithRetry(api, tx, alice, "sudo_set_subnet_moving_alpha"); +} + +// Debug helpers for claim_root investigation +export async function getSubnetTAO( + api: TypedApi, + netuid: number +): Promise { + return await api.query.SubtensorModule.SubnetTAO.getValue(netuid); +} + +export async function getSubnetMovingPrice( + api: TypedApi, + netuid: number +): Promise { + return await api.query.SubtensorModule.SubnetMovingPrice.getValue(netuid); +} + +export async function getPendingRootAlphaDivs( + api: TypedApi, + netuid: number +): Promise { + return await api.query.SubtensorModule.PendingRootAlphaDivs.getValue(netuid); +} + +export async function getTaoWeight( + api: TypedApi +): Promise { + return await api.query.SubtensorModule.TaoWeight.getValue(); +} + +export async function getSubnetAlphaIn( + api: TypedApi, + netuid: number +): Promise { + return await api.query.SubtensorModule.SubnetAlphaIn.getValue(netuid); +} + +export async function getTotalHotkeyAlpha( + api: TypedApi, + hotkey: string, + netuid: number +): Promise { + return await api.query.SubtensorModule.TotalHotkeyAlpha.getValue(hotkey, netuid); +} diff --git a/e2e/shared/subnet.ts b/e2e/shared/subnet.ts new file mode 100644 index 0000000000..510779c19e --- /dev/null +++ b/e2e/shared/subnet.ts @@ -0,0 +1,72 @@ +import { subtensor } from "@polkadot-api/descriptors"; +import { TypedApi } from "polkadot-api"; +import { KeyPair } from "@polkadot-labs/hdkd-helpers"; +import { getAliceSigner, getSignerFromKeypair, convertPublicKeyToSs58 } from "./address.js"; +import { waitForTransactionWithRetry } from "./transactions.js"; +import { log } from "./logger.js"; + +export async function addNewSubnetwork( + api: TypedApi, + hotkey: KeyPair, + coldkey: KeyPair +): Promise { + const alice = getAliceSigner(); + const totalNetworks = await api.query.SubtensorModule.TotalNetworks.getValue(); + + // Disable network rate limit for testing + 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 waitForTransactionWithRetry(api, tx, alice, "set_network_rate_limit"); + } + + const signer = getSignerFromKeypair(coldkey); + const registerNetworkTx = api.tx.SubtensorModule.register_network({ + hotkey: convertPublicKeyToSs58(hotkey.publicKey), + }); + await waitForTransactionWithRetry(api, registerNetworkTx, signer, "register_network"); + + return totalNetworks; +} + +export async function burnedRegister( + api: TypedApi, + netuid: number, + hotkeyAddress: string, + coldkey: KeyPair +): Promise { + const registered = await api.query.SubtensorModule.Uids.getValue(netuid, hotkeyAddress); + if (registered !== undefined) { + log.tx("burned_register", `skipped: hotkey already registered on netuid ${netuid}`); + return; + } + + await new Promise((resolve) => setTimeout(resolve, 1000)); + const signer = getSignerFromKeypair(coldkey); + const tx = api.tx.SubtensorModule.burned_register({ hotkey: hotkeyAddress, netuid: netuid }); + await waitForTransactionWithRetry(api, tx, signer, "burned_register"); +} + +export async function startCall( + api: TypedApi, + netuid: number, + coldkey: KeyPair +): Promise { + const registerBlock = Number(await api.query.SubtensorModule.NetworkRegisteredAt.getValue(netuid)); + let currentBlock = await api.query.System.Number.getValue(); + const duration = Number(await api.constants.SubtensorModule.InitialStartCallDelay); + + while (currentBlock - registerBlock <= duration) { + await new Promise((resolve) => setTimeout(resolve, 2000)); + currentBlock = await api.query.System.Number.getValue(); + } + + await new Promise((resolve) => setTimeout(resolve, 2000)); + + const signer = getSignerFromKeypair(coldkey); + const tx = api.tx.SubtensorModule.start_call({ netuid: netuid }); + await waitForTransactionWithRetry(api, tx, signer, "start_call"); + + await new Promise((resolve) => setTimeout(resolve, 1000)); +} diff --git a/e2e/shared/transactions.ts b/e2e/shared/transactions.ts new file mode 100644 index 0000000000..27e55749f4 --- /dev/null +++ b/e2e/shared/transactions.ts @@ -0,0 +1,71 @@ +import { subtensor } from "@polkadot-api/descriptors"; +import { TypedApi, Transaction, PolkadotSigner } from "polkadot-api"; +import { log } from "./logger.js"; + +export const TX_TIMEOUT = 5000; + +export async function waitForTransactionWithRetry( + api: TypedApi, + tx: Transaction<{}, string, string, void>, + signer: PolkadotSigner, + label: string, + maxRetries = 1 +): Promise { + let success = false; + let retries = 0; + + while (!success && retries < maxRetries) { + await waitForTransactionCompletion(tx, signer, label) + .then(() => { + success = true; + }) + .catch((error) => { + log.tx(label, `error: ${error}`); + }); + await new Promise((resolve) => setTimeout(resolve, 1000)); + retries += 1; + } + + if (!success) { + throw new Error(`[${label}] failed after ${maxRetries} retries`); + } +} + +async function waitForTransactionCompletion( + tx: Transaction<{}, string, string, void>, + signer: PolkadotSigner, + label: string +): Promise { + return new Promise((resolve, reject) => { + let txHash = ""; + const subscription = tx.signSubmitAndWatch(signer).subscribe({ + next(value) { + txHash = value.txHash; + if (value.type === "finalized") { + log.tx(label, `finalized: ${value.txHash}`); + subscription.unsubscribe(); + clearTimeout(timeoutId); + if (!value.ok) { + const errorStr = JSON.stringify(value.dispatchError, null, 2); + log.tx(label, `dispatch error: ${errorStr}`); + reject(new Error(`[${label}] dispatch error: ${errorStr}`)); + } else { + resolve(); + } + } + }, + error(err) { + log.error(label, `failed: ${err}`); + subscription.unsubscribe(); + clearTimeout(timeoutId); + reject(err); + }, + }); + + const timeoutId = setTimeout(() => { + subscription.unsubscribe(); + log.tx(label, `timeout for tx: ${txHash}`); + reject(new Error(`[${label}] timeout`)); + }, TX_TIMEOUT); + }); +} diff --git a/e2e/staking/package.json b/e2e/staking/package.json new file mode 100644 index 0000000000..5320fad9e3 --- /dev/null +++ b/e2e/staking/package.json @@ -0,0 +1,23 @@ +{ + "name": "e2e-staking", + "version": "1.0.0", + "type": "module", + "license": "ISC", + "scripts": { + "test": "tsx node_modules/mocha/bin/mocha.js --timeout 999999 --retries 1 --file setup.ts --extension ts \"test/**/*.ts\"" + }, + "dependencies": { + "e2e-shared": "workspace:*", + "mocha": "^11.1.0", + "tsx": "^4.21.0" + }, + "devDependencies": { + "@types/mocha": "^10.0.10", + "@types/node": "catalog:" + }, + "prettier": { + "singleQuote": false, + "trailingComma": "all", + "printWidth": 120 + } +} diff --git a/e2e/staking/setup.ts b/e2e/staking/setup.ts new file mode 100644 index 0000000000..71c58ab0ca --- /dev/null +++ b/e2e/staking/setup.ts @@ -0,0 +1,112 @@ +import { rm, mkdir } from "node:fs/promises"; +import { + generateChainSpec, + startNode, + started, + peerCount, + finalizedBlocks, + stop, + nodeLog, + destroyClient, + getDevnetApi, + sudoSetLockReductionInterval, + log, + type Node, + type NodeOptions, +} from "e2e-shared"; + +const CHAIN_SPEC_PATH = "/tmp/subtensor-e2e/staking-tests/chain-spec.json"; +const BASE_DIR = "/tmp/subtensor-e2e/staking-tests"; + +const BINARY_PATH = process.env.BINARY_PATH || "../../target/release/node-subtensor"; + +const nodes: Node[] = []; + +// Use built-in validators "one" and "two" - they have auto-injected keys +type NodeConfig = Omit; + +const NODE_CONFIGS: NodeConfig[] = [ + { name: "one", port: 30433, rpcPort: 9944, basePath: `${BASE_DIR}/one`, validator: true }, + { name: "two", port: 30434, rpcPort: 9945, basePath: `${BASE_DIR}/two`, validator: true }, +]; + +async function startNetwork() { + nodeLog(`Setting up ${NODE_CONFIGS.length}-node network for staking E2E tests`); + nodeLog(`Binary path: ${BINARY_PATH}`); + + await mkdir(BASE_DIR, { recursive: true }); + + // Generate local chain spec (built-in has One and Two as authorities) + await generateChainSpec(BINARY_PATH, CHAIN_SPEC_PATH); + + // Clean up old base paths + for (const config of NODE_CONFIGS) { + await rm(config.basePath, { recursive: true, force: true }); + } + + // Start all validator nodes + for (const config of NODE_CONFIGS) { + const node = startNode({ + binaryPath: BINARY_PATH, + chainSpec: CHAIN_SPEC_PATH, + ...config, + }); + nodes.push(node); + await started(node); + } + + const all = Promise.all.bind(Promise); + + // Wait for nodes to peer with each other + await all(nodes.map((n) => peerCount(n, nodes.length - 1))); + nodeLog("All nodes peered"); + + // Wait for block finalization + await all(nodes.map((n) => finalizedBlocks(n, 3))); + nodeLog("All nodes finalized block 3"); +} + +async function stopNetwork() { + nodeLog("Stopping staking-tests network"); + + for (const node of nodes) { + try { + await stop(node); + } catch (e) { + nodeLog(`Warning: failed to stop ${node.name}: ${e}`); + } + } + + // Clean up the suite directory + await rm(BASE_DIR, { recursive: true, force: true }); + + nodeLog("Network stopped"); +} + +before(async function () { + // Increase timeout for network startup (2 minutes) + this.timeout(120000); + + // Start the network + await startNetwork(); + + // Connect to the network and configure for tests + const api = await getDevnetApi(); + log.info("Setup: set lock reduction interval to 1 for instant lock cost decay"); + + // Set lock reduction interval to 1 block to make network registration lock cost decay instantly. + // By default, the lock cost doubles with each subnet registration and decays over 14 days (100,800 blocks). + // Without this, tests creating multiple subnets would fail with CannotAffordLockCost. + await sudoSetLockReductionInterval(api, 1); +}); + +after(async function () { + // Increase timeout for cleanup + this.timeout(30000); + + // Destroy the API client first + destroyClient(); + + // Stop the network + await stopNetwork(); +}); diff --git a/e2e/staking/test/add-stake-limit.test.ts b/e2e/staking/test/add-stake-limit.test.ts new file mode 100644 index 0000000000..991cccc62c --- /dev/null +++ b/e2e/staking/test/add-stake-limit.test.ts @@ -0,0 +1,66 @@ +import * as assert from "assert"; +import { + getDevnetApi, + getRandomSubstrateKeypair, + convertPublicKeyToSs58, + forceSetBalance, + addNewSubnetwork, + burnedRegister, + startCall, + addStakeLimit, + getStake, + tao, + log, +} from "e2e-shared"; + +describe("▶ add_stake_limit extrinsic", () => { + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const hotkeyAddress = convertPublicKeyToSs58(hotkey.publicKey); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + let netuid: number; + + before(async () => { + const api = await getDevnetApi(); + await forceSetBalance(api, hotkeyAddress); + await forceSetBalance(api, coldkeyAddress); + netuid = await addNewSubnetwork(api, hotkey, coldkey); + await startCall(api, netuid, coldkey); + }); + + it("should add stake with price limit (allow partial)", async () => { + const api = await getDevnetApi(); + + // Get initial stake + const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + + // Add stake with limit price and allow partial fills, limit_price is MAX TAO per Alpha willing to pay. + const stakeAmount = tao(44); + const limitPrice = tao(6); + await addStakeLimit(api, coldkey, hotkeyAddress, netuid, stakeAmount, limitPrice, true); + + // Verify stake increased + const stakeAfter = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + assert.ok(stakeAfter > stakeBefore, `Stake should increase: before=${stakeBefore}, after=${stakeAfter}`); + + log.info("✅ Successfully added stake with limit (allow partial)."); + }); + + it("should add stake with price limit (fill or kill)", async () => { + const api = await getDevnetApi(); + + // Get initial stake + const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + + // Add stake with limit price (fill or kill mode), limit_price is MAX TAO per Alpha willing to pay + const stakeAmount = tao(44); + const limitPrice = tao(6); + await addStakeLimit(api, coldkey, hotkeyAddress, netuid, stakeAmount, limitPrice, false); + + // Verify stake increased + const stakeAfter = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + assert.ok(stakeAfter > stakeBefore, `Stake should increase: before=${stakeBefore}, after=${stakeAfter}`); + + log.info("✅ Successfully added stake with limit (fill or kill)."); + }); +}); diff --git a/e2e/staking/test/add-stake.test.ts b/e2e/staking/test/add-stake.test.ts new file mode 100644 index 0000000000..0a0cfe756c --- /dev/null +++ b/e2e/staking/test/add-stake.test.ts @@ -0,0 +1,47 @@ +import * as assert from "assert"; +import { + getDevnetApi, + getRandomSubstrateKeypair, + convertPublicKeyToSs58, + forceSetBalance, + addNewSubnetwork, + burnedRegister, + startCall, + addStake, + getStake, + tao, + log, +} from "e2e-shared"; + +describe("▶ add_stake extrinsic", () => { + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const hotkeyAddress = convertPublicKeyToSs58(hotkey.publicKey); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + let netuid: number; + + before(async () => { + const api = await getDevnetApi(); + await forceSetBalance(api, hotkeyAddress); + await forceSetBalance(api, coldkeyAddress); + netuid = await addNewSubnetwork(api, hotkey, coldkey); + await startCall(api, netuid, coldkey); + }); + + it("should add stake to a hotkey", async () => { + const api = await getDevnetApi(); + + // Get initial stake + const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + + // Add stake + const stakeAmount = tao(100); + await addStake(api, coldkey, hotkeyAddress, netuid, stakeAmount); + + // Verify stake increased + const stakeAfter = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + assert.ok(stakeAfter > stakeBefore, `Stake should increase: before=${stakeBefore}, after=${stakeAfter}`); + + log.info("✅ Successfully added stake."); + }); +}); diff --git a/e2e/staking/test/claim-root.test.ts b/e2e/staking/test/claim-root.test.ts new file mode 100644 index 0000000000..cd0d8fec8f --- /dev/null +++ b/e2e/staking/test/claim-root.test.ts @@ -0,0 +1,477 @@ +import * as assert from "assert"; +import { + getDevnetApi, + getRandomSubstrateKeypair, + convertPublicKeyToSs58, + forceSetBalance, + addNewSubnetwork, + startCall, + getRootClaimType, + setRootClaimType, + getNumRootClaims, + sudoSetNumRootClaims, + getRootClaimThreshold, + sudoSetRootClaimThreshold, + addStake, + getStake, + claimRoot, + getTempo, + sudoSetTempo, + waitForBlocks, + getRootClaimable, + getRootClaimed, + isSubtokenEnabled, + sudoSetSubtokenEnabled, + isNetworkAdded, + sudoSetAdminFreezeWindow, + sudoSetEmaPriceHalvingPeriod, + getSubnetTAO, + getSubnetMovingPrice, + getPendingRootAlphaDivs, + getTaoWeight, + getSubnetAlphaIn, + getTotalHotkeyAlpha, + sudoSetSubnetMovingAlpha, + tao, + log, +} from "e2e-shared"; + +describe("▶ set_root_claim_type extrinsic", () => { + it("should set root claim type to Keep", async () => { + const api = await getDevnetApi(); + + const coldkey = getRandomSubstrateKeypair(); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + + await forceSetBalance(api, coldkeyAddress); + + // Check initial claim type (default is "Swap") + const claimTypeBefore = await getRootClaimType(api, coldkeyAddress); + log.info(`Root claim type before: ${claimTypeBefore}`); + + // Set root claim type to Keep + await setRootClaimType(api, coldkey, "Keep"); + + // Verify claim type changed + const claimTypeAfter = await getRootClaimType(api, coldkeyAddress); + log.info(`Root claim type after: ${claimTypeAfter}`); + + assert.strictEqual(claimTypeAfter, "Keep", `Expected claim type to be Keep, got ${claimTypeAfter}`); + + log.info("✅ Successfully set root claim type to Keep."); + }); + + it("should set root claim type to Swap", async () => { + const api = await getDevnetApi(); + + const coldkey = getRandomSubstrateKeypair(); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + + await forceSetBalance(api, coldkeyAddress); + + // First set to Keep so we can verify the change to Swap + await setRootClaimType(api, coldkey, "Keep"); + const claimTypeBefore = await getRootClaimType(api, coldkeyAddress); + log.info(`Root claim type before: ${claimTypeBefore}`); + assert.strictEqual(claimTypeBefore, "Keep", "Should be Keep before changing to Swap"); + + // Set root claim type to Swap + await setRootClaimType(api, coldkey, "Swap"); + + // Verify claim type changed + const claimTypeAfter = await getRootClaimType(api, coldkeyAddress); + log.info(`Root claim type after: ${claimTypeAfter}`); + + assert.strictEqual(claimTypeAfter, "Swap", `Expected claim type to be Swap, got ${claimTypeAfter}`); + + log.info("✅ Successfully set root claim type to Swap."); + }); + + it("should set root claim type to KeepSubnets", async () => { + const api = await getDevnetApi(); + + const coldkey = getRandomSubstrateKeypair(); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + + await forceSetBalance(api, coldkeyAddress); + + // Check initial claim type (default is "Swap") + const claimTypeBefore = await getRootClaimType(api, coldkeyAddress); + log.info(`Root claim type before: ${JSON.stringify(claimTypeBefore)}`); + + // Set root claim type to KeepSubnets with specific subnets + const subnetsToKeep = [1, 2]; + await setRootClaimType(api, coldkey, { type: "KeepSubnets", subnets: subnetsToKeep }); + + // Verify claim type changed + const claimTypeAfter = await getRootClaimType(api, coldkeyAddress); + log.info(`Root claim type after: ${JSON.stringify(claimTypeAfter)}`); + + assert.strictEqual(typeof claimTypeAfter, "object", "Expected claim type to be an object"); + assert.strictEqual((claimTypeAfter as { type: string }).type, "KeepSubnets", "Expected type to be KeepSubnets"); + assert.deepStrictEqual((claimTypeAfter as { subnets: number[] }).subnets, subnetsToKeep, "Expected subnets to match"); + + log.info("✅ Successfully set root claim type to KeepSubnets."); + }); +}); + +describe("▶ sudo_set_num_root_claims extrinsic", () => { + it("should set num root claims", async () => { + const api = await getDevnetApi(); + + // Get initial value + const numClaimsBefore = await getNumRootClaims(api); + log.info(`Num root claims before: ${numClaimsBefore}`); + + // Set new value (different from current) + const newValue = numClaimsBefore + 5n; + await sudoSetNumRootClaims(api, newValue); + + // Verify value changed + const numClaimsAfter = await getNumRootClaims(api); + log.info(`Num root claims after: ${numClaimsAfter}`); + + assert.strictEqual(numClaimsAfter, newValue, `Expected num root claims to be ${newValue}, got ${numClaimsAfter}`); + + log.info("✅ Successfully set num root claims."); + }); +}); + +describe("▶ sudo_set_root_claim_threshold extrinsic", () => { + it("should set root claim threshold for subnet", async () => { + const api = await getDevnetApi(); + + // Create a subnet to test with + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const hotkeyAddress = convertPublicKeyToSs58(hotkey.publicKey); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + + await forceSetBalance(api, hotkeyAddress); + await forceSetBalance(api, coldkeyAddress); + + const netuid = await addNewSubnetwork(api, hotkey, coldkey); + await startCall(api, netuid, coldkey); + + // Get initial threshold + const thresholdBefore = await getRootClaimThreshold(api, netuid); + log.info(`Root claim threshold before: ${thresholdBefore}`); + + // Set new threshold value (MAX_ROOT_CLAIM_THRESHOLD is 10_000_000) + // The value is stored as I96F32 fixed-point with 32 fractional bits + const newThreshold = 1_000_000n; + await sudoSetRootClaimThreshold(api, netuid, newThreshold); + + // Verify threshold changed + // I96F32 encoding: newThreshold * 2^32 = 1_000_000 * 4294967296 = 4294967296000000 + const thresholdAfter = await getRootClaimThreshold(api, netuid); + log.info(`Root claim threshold after: ${thresholdAfter}`); + + const expectedStoredValue = newThreshold * (1n << 32n); // I96F32 encoding + assert.strictEqual(thresholdAfter, expectedStoredValue, `Expected threshold to be ${expectedStoredValue}, got ${thresholdAfter}`); + + log.info("✅ Successfully set root claim threshold."); + }); +}); + +// Root subnet netuid is 0 +const ROOT_NETUID = 0; + +describe("▶ claim_root extrinsic", () => { + it("should claim root dividends with Keep type (stake to dynamic subnet)", async () => { + const api = await getDevnetApi(); + + // Setup accounts + // - owner1Hotkey/owner1Coldkey: subnet 1 owner + // - owner2Hotkey/owner2Coldkey: subnet 2 owner (needed for root_sell_flag) + // - stakerColdkey: the coldkey that will stake on root and claim dividends + const owner1Hotkey = getRandomSubstrateKeypair(); + const owner1Coldkey = getRandomSubstrateKeypair(); + const owner2Hotkey = getRandomSubstrateKeypair(); + const owner2Coldkey = getRandomSubstrateKeypair(); + const stakerColdkey = getRandomSubstrateKeypair(); + const owner1HotkeyAddress = convertPublicKeyToSs58(owner1Hotkey.publicKey); + const owner1ColdkeyAddress = convertPublicKeyToSs58(owner1Coldkey.publicKey); + const owner2HotkeyAddress = convertPublicKeyToSs58(owner2Hotkey.publicKey); + const owner2ColdkeyAddress = convertPublicKeyToSs58(owner2Coldkey.publicKey); + const stakerColdkeyAddress = convertPublicKeyToSs58(stakerColdkey.publicKey); + + // Fund all accounts + await forceSetBalance(api, owner1HotkeyAddress); + await forceSetBalance(api, owner1ColdkeyAddress); + await forceSetBalance(api, owner2HotkeyAddress); + await forceSetBalance(api, owner2ColdkeyAddress); + await forceSetBalance(api, stakerColdkeyAddress); + + // Disable admin freeze window to allow enabling subtoken for ROOT + await sudoSetAdminFreezeWindow(api, 0); + log.info("Admin freeze window set to 0"); + + // Enable subtoken for ROOT subnet (required for staking on root) + const subtokenEnabledBefore = await isSubtokenEnabled(api, ROOT_NETUID); + if (!subtokenEnabledBefore) { + await sudoSetSubtokenEnabled(api, ROOT_NETUID, true); + const subtokenEnabledAfter = await isSubtokenEnabled(api, ROOT_NETUID); + log.info(`ROOT subtoken enabled: ${subtokenEnabledAfter}`); + assert.strictEqual(subtokenEnabledAfter, true, "ROOT subtoken should be enabled"); + } + + // Create TWO dynamic subnets - needed for root_sell_flag to become true + // root_sell_flag = sum(moving_prices) > 1.0 + // Each subnet's moving price approaches 1.0 via EMA, so 2 subnets can exceed threshold + const netuid1 = await addNewSubnetwork(api, owner1Hotkey, owner1Coldkey); + await startCall(api, netuid1, owner1Coldkey); + log.info(`Created subnet 1 with netuid: ${netuid1}`); + + const netuid2 = await addNewSubnetwork(api, owner2Hotkey, owner2Coldkey); + await startCall(api, netuid2, owner2Coldkey); + log.info(`Created subnet 2 with netuid: ${netuid2}`); + + // Set short tempo for faster emission distribution + await sudoSetTempo(api, netuid1, 1); + await sudoSetTempo(api, netuid2, 1); + log.info("Set tempo to 1 for both subnets"); + + // Set EMA price halving period to 1 for fast moving price convergence + // Formula: alpha = SubnetMovingAlpha * blocks/(blocks + halving_time) + // With halving_time=1: after 10 blocks, alpha ≈ 0.91, moving price ≈ 0.91 + // With 2 subnets at ~0.9 each, total > 1.0 enabling root_sell_flag + await sudoSetEmaPriceHalvingPeriod(api, netuid1, 1); + await sudoSetEmaPriceHalvingPeriod(api, netuid2, 1); + log.info("Set EMA halving period to 1 for fast price convergence"); + + // Set SubnetMovingAlpha to 1.0 (default is 0.000003 which is way too slow) + // I96F32 encoding: 1.0 * 2^32 = 4294967296 + const movingAlpha = BigInt(4294967296); // 1.0 in I96F32 + await sudoSetSubnetMovingAlpha(api, movingAlpha); + log.info("Set SubnetMovingAlpha to 1.0 for fast EMA convergence"); + + // Set threshold to 0 to allow claiming any amount + await sudoSetRootClaimThreshold(api, netuid1, 0n); + await sudoSetRootClaimThreshold(api, netuid2, 0n); + + // Add stake to ROOT subnet for the staker (makes them eligible for root dividends) + const rootStakeAmount = tao(100); + await addStake(api, stakerColdkey, owner1HotkeyAddress, ROOT_NETUID, rootStakeAmount); + log.info(`Added ${rootStakeAmount} stake to root subnet for staker`); + + // Verify root stake was added + const rootStake = await getStake(api, owner1HotkeyAddress, stakerColdkeyAddress, ROOT_NETUID); + log.info(`Root stake: ${rootStake}`); + assert.ok(rootStake > 0n, "Should have stake on root subnet"); + + // Add stake to both dynamic subnets (owner stake to enable emissions flow) + const subnetStakeAmount = tao(50); + await addStake(api, owner1Coldkey, owner1HotkeyAddress, netuid1, subnetStakeAmount); + await addStake(api, owner2Coldkey, owner2HotkeyAddress, netuid2, subnetStakeAmount); + log.info(`Added ${subnetStakeAmount} owner stake to subnets ${netuid1} and ${netuid2}`); + + // Get initial stake on subnet 1 for the staker (should be 0) + const stakerSubnetStakeBefore = await getStake(api, owner1HotkeyAddress, stakerColdkeyAddress, netuid1); + log.info(`Staker subnet stake before claim: ${stakerSubnetStakeBefore}`); + + // Set root claim type to Keep (keep alpha on subnet instead of swapping to TAO) + await setRootClaimType(api, stakerColdkey, "Keep"); + const claimType = await getRootClaimType(api, stakerColdkeyAddress); + log.info(`Root claim type: ${claimType}`); + assert.strictEqual(claimType, "Keep", "Should have Keep claim type"); + + // Wait for blocks to: + // 1. Allow moving prices to converge (need sum > 1.0 for root_sell_flag) + // 2. Accumulate PendingRootAlphaDivs + // 3. Distribute emissions at tempo boundary + const blocksToWait = 25; + log.info(`Waiting for ${blocksToWait} blocks for moving prices to converge and emissions to accumulate...`); + await waitForBlocks(api, blocksToWait); + + // Debug: Check key storage values + const subnetTaoRoot = await getSubnetTAO(api, ROOT_NETUID); + const subnetTao1 = await getSubnetTAO(api, netuid1); + const subnetTao2 = await getSubnetTAO(api, netuid2); + log.info(`SubnetTAO - ROOT: ${subnetTaoRoot}, netuid1: ${subnetTao1}, netuid2: ${subnetTao2}`); + + const movingPrice1 = await getSubnetMovingPrice(api, netuid1); + const movingPrice2 = await getSubnetMovingPrice(api, netuid2); + log.info(`SubnetMovingPrice - netuid1: ${movingPrice1}, netuid2: ${movingPrice2}`); + // Note: Moving price is I96F32, so divide by 2^32 to get actual value + const mp1Float = Number(movingPrice1) / 2**32; + const mp2Float = Number(movingPrice2) / 2**32; + log.info(`SubnetMovingPrice (float) - netuid1: ${mp1Float}, netuid2: ${mp2Float}, sum: ${mp1Float + mp2Float}`); + + const pendingDivs1 = await getPendingRootAlphaDivs(api, netuid1); + const pendingDivs2 = await getPendingRootAlphaDivs(api, netuid2); + log.info(`PendingRootAlphaDivs - netuid1: ${pendingDivs1}, netuid2: ${pendingDivs2}`); + + const taoWeight = await getTaoWeight(api); + log.info(`TaoWeight: ${taoWeight}`); + + const alphaIn1 = await getSubnetAlphaIn(api, netuid1); + const alphaIn2 = await getSubnetAlphaIn(api, netuid2); + log.info(`SubnetAlphaIn - netuid1: ${alphaIn1}, netuid2: ${alphaIn2}`); + + const totalHotkeyAlpha1 = await getTotalHotkeyAlpha(api, owner1HotkeyAddress, netuid1); + log.info(`TotalHotkeyAlpha for hotkey1 on netuid1: ${totalHotkeyAlpha1}`); + + // Check if there are any claimable dividends + const claimable = await getRootClaimable(api, owner1HotkeyAddress); + const claimableStr = [...claimable.entries()].map(([k, v]) => `[${k}: ${v.toString()}]`).join(", "); + log.info(`RootClaimable entries for hotkey1: ${claimableStr || "(none)"}`); + + // Call claim_root to claim dividends for subnet 1 + await claimRoot(api, stakerColdkey, [netuid1]); + log.info("Called claim_root"); + + // Get stake on subnet 1 after claim + const stakerSubnetStakeAfter = await getStake(api, owner1HotkeyAddress, stakerColdkeyAddress, netuid1); + log.info(`Staker subnet stake after claim: ${stakerSubnetStakeAfter}`); + + // Check RootClaimed value + const rootClaimed = await getRootClaimed(api, netuid1, owner1HotkeyAddress, stakerColdkeyAddress); + log.info(`RootClaimed value: ${rootClaimed}`); + + // Verify dividends were claimed + assert.ok( + stakerSubnetStakeAfter > stakerSubnetStakeBefore, + `Stake should increase after claiming root dividends: before=${stakerSubnetStakeBefore}, after=${stakerSubnetStakeAfter}` + ); + log.info(`✅ Root claim successful: stake increased from ${stakerSubnetStakeBefore} to ${stakerSubnetStakeAfter}`); + }); + + it("should claim root dividends with Swap type (swap to TAO on ROOT)", async () => { + const api = await getDevnetApi(); + + // Setup accounts + // - owner1Hotkey/owner1Coldkey: subnet 1 owner + // - owner2Hotkey/owner2Coldkey: subnet 2 owner (needed for root_sell_flag) + // - stakerColdkey: the coldkey that will stake on root and claim dividends + const owner1Hotkey = getRandomSubstrateKeypair(); + const owner1Coldkey = getRandomSubstrateKeypair(); + const owner2Hotkey = getRandomSubstrateKeypair(); + const owner2Coldkey = getRandomSubstrateKeypair(); + const stakerColdkey = getRandomSubstrateKeypair(); + const owner1HotkeyAddress = convertPublicKeyToSs58(owner1Hotkey.publicKey); + const owner1ColdkeyAddress = convertPublicKeyToSs58(owner1Coldkey.publicKey); + const owner2HotkeyAddress = convertPublicKeyToSs58(owner2Hotkey.publicKey); + const owner2ColdkeyAddress = convertPublicKeyToSs58(owner2Coldkey.publicKey); + const stakerColdkeyAddress = convertPublicKeyToSs58(stakerColdkey.publicKey); + + // Fund all accounts + await forceSetBalance(api, owner1HotkeyAddress); + await forceSetBalance(api, owner1ColdkeyAddress); + await forceSetBalance(api, owner2HotkeyAddress); + await forceSetBalance(api, owner2ColdkeyAddress); + await forceSetBalance(api, stakerColdkeyAddress); + + // Disable admin freeze window to allow enabling subtoken for ROOT + await sudoSetAdminFreezeWindow(api, 0); + log.info("Admin freeze window set to 0"); + + // Create TWO dynamic subnets + const netuid1 = await addNewSubnetwork(api, owner1Hotkey, owner1Coldkey); + await startCall(api, netuid1, owner1Coldkey); + log.info(`Created subnet 1 with netuid: ${netuid1}`); + + const netuid2 = await addNewSubnetwork(api, owner2Hotkey, owner2Coldkey); + await startCall(api, netuid2, owner2Coldkey); + log.info(`Created subnet 2 with netuid: ${netuid2}`); + + // Set short tempo for faster emission distribution + await sudoSetTempo(api, netuid1, 1); + await sudoSetTempo(api, netuid2, 1); + log.info("Set tempo to 1 for both subnets"); + + // Set EMA price halving period to 1 for fast moving price convergence + await sudoSetEmaPriceHalvingPeriod(api, netuid1, 1); + await sudoSetEmaPriceHalvingPeriod(api, netuid2, 1); + log.info("Set EMA halving period to 1 for fast price convergence"); + + // Set SubnetMovingAlpha to 1.0 (default is 0.000003 which is way too slow) + // I96F32 encoding: 1.0 * 2^32 = 4294967296 + const movingAlpha = BigInt(4294967296); // 1.0 in I96F32 + await sudoSetSubnetMovingAlpha(api, movingAlpha); + log.info("Set SubnetMovingAlpha to 1.0 for fast EMA convergence"); + + // Set threshold to 0 to allow claiming any amount + await sudoSetRootClaimThreshold(api, netuid1, 0n); + await sudoSetRootClaimThreshold(api, netuid2, 0n); + + // Add stake to ROOT subnet for the staker + const rootStakeAmount = tao(100); + await addStake(api, stakerColdkey, owner1HotkeyAddress, ROOT_NETUID, rootStakeAmount); + log.info(`Added ${rootStakeAmount} stake to root subnet for staker`); + + // Get initial ROOT stake + const rootStakeBefore = await getStake(api, owner1HotkeyAddress, stakerColdkeyAddress, ROOT_NETUID); + log.info(`Root stake before: ${rootStakeBefore}`); + + // Add stake to both dynamic subnets (owner stake to enable emissions flow) + const subnetStakeAmount = tao(50); + await addStake(api, owner1Coldkey, owner1HotkeyAddress, netuid1, subnetStakeAmount); + await addStake(api, owner2Coldkey, owner2HotkeyAddress, netuid2, subnetStakeAmount); + log.info(`Added ${subnetStakeAmount} owner stake to subnets ${netuid1} and ${netuid2}`); + + // Set root claim type to Swap (swap alpha to TAO and add to ROOT stake) + await setRootClaimType(api, stakerColdkey, "Swap"); + const claimType = await getRootClaimType(api, stakerColdkeyAddress); + log.info(`Root claim type: ${claimType}`); + assert.strictEqual(claimType, "Swap", "Should have Swap claim type"); + + // Wait for blocks + const blocksToWait = 25; + log.info(`Waiting for ${blocksToWait} blocks for emissions to accumulate...`); + await waitForBlocks(api, blocksToWait); + + // Debug: Check moving prices + const movingPrice1 = await getSubnetMovingPrice(api, netuid1); + const movingPrice2 = await getSubnetMovingPrice(api, netuid2); + const mp1Float = Number(movingPrice1) / 2**32; + const mp2Float = Number(movingPrice2) / 2**32; + log.info(`SubnetMovingPrice (float) - netuid1: ${mp1Float}, netuid2: ${mp2Float}, sum: ${mp1Float + mp2Float}`); + + const pendingDivs1 = await getPendingRootAlphaDivs(api, netuid1); + log.info(`PendingRootAlphaDivs netuid1: ${pendingDivs1}`); + + // Check claimable + const claimable = await getRootClaimable(api, owner1HotkeyAddress); + const claimableStr = [...claimable.entries()].map(([k, v]) => `[${k}: ${v.toString()}]`).join(", "); + log.info(`RootClaimable entries for hotkey1: ${claimableStr || "(none)"}`); + + // Call claim_root - with Swap type, dividends are swapped to TAO and added to ROOT stake + await claimRoot(api, stakerColdkey, [netuid1]); + log.info("Called claim_root with Swap type"); + + // Get ROOT stake after claim + const rootStakeAfter = await getStake(api, owner1HotkeyAddress, stakerColdkeyAddress, ROOT_NETUID); + log.info(`Root stake after claim: ${rootStakeAfter}`); + + // Check RootClaimed value + const rootClaimed = await getRootClaimed(api, netuid1, owner1HotkeyAddress, stakerColdkeyAddress); + log.info(`RootClaimed value: ${rootClaimed}`); + + // With Swap type, ROOT stake should increase (not dynamic subnet stake) + assert.ok( + rootStakeAfter > rootStakeBefore, + `ROOT stake should increase after claiming with Swap type: before=${rootStakeBefore}, after=${rootStakeAfter}` + ); + log.info(`✅ Root claim with Swap successful: ROOT stake increased from ${rootStakeBefore} to ${rootStakeAfter}`); + }); + + it("should handle claim_root when no dividends are available", async () => { + const api = await getDevnetApi(); + + // Setup accounts + const coldkey = getRandomSubstrateKeypair(); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + + await forceSetBalance(api, coldkeyAddress); + + // Set root claim type to Keep + await setRootClaimType(api, coldkey, "Keep"); + + // Try to claim on a non-existent subnet (should succeed but be a no-op) + // According to Rust tests, claiming on unrelated subnets returns Ok but does nothing + await claimRoot(api, coldkey, [1]); + + log.info("✅ claim_root with no dividends executed successfully (no-op)."); + }); +}); diff --git a/e2e/staking/test/move-stake.test.ts b/e2e/staking/test/move-stake.test.ts new file mode 100644 index 0000000000..efe8e48a58 --- /dev/null +++ b/e2e/staking/test/move-stake.test.ts @@ -0,0 +1,135 @@ +import * as assert from "assert"; +import { + getDevnetApi, + getRandomSubstrateKeypair, + convertPublicKeyToSs58, + forceSetBalance, + addNewSubnetwork, + burnedRegister, + startCall, + addStake, + moveStake, + getStake, + getStakeRaw, + tao, + log, +} from "e2e-shared"; + +describe("▶ move_stake extrinsic", () => { + it("should move stake to another hotkey across subnets", async () => { + const api = await getDevnetApi(); + + // Setup accounts + const originHotkey = getRandomSubstrateKeypair(); + const destinationHotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const originHotkeyAddress = convertPublicKeyToSs58(originHotkey.publicKey); + const destinationHotkeyAddress = convertPublicKeyToSs58(destinationHotkey.publicKey); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + + await forceSetBalance(api, originHotkeyAddress); + await forceSetBalance(api, destinationHotkeyAddress); + await forceSetBalance(api, coldkeyAddress); + + // Create first subnet with origin hotkey + const netuid1 = await addNewSubnetwork(api, originHotkey, coldkey); + await startCall(api, netuid1, coldkey); + + // Create second subnet with destination hotkey + const netuid2 = await addNewSubnetwork(api, destinationHotkey, coldkey); + await startCall(api, netuid2, coldkey); + + // Add stake to origin hotkey on first subnet + await addStake(api, coldkey, originHotkeyAddress, netuid1, tao(200)); + + // Get initial stakes (converted from U64F64 for display) + const originStakeBefore = await getStake(api, originHotkeyAddress, coldkeyAddress, netuid1); + const destStakeBefore = await getStake(api, destinationHotkeyAddress, coldkeyAddress, netuid2); + assert.ok(originStakeBefore > 0n, "Origin hotkey should have stake before move"); + + log.info(`Origin stake (netuid1) before: ${originStakeBefore}, Destination stake (netuid2) before: ${destStakeBefore}`); + + // Move stake to destination hotkey on different subnet + // Use raw U64F64 value for the extrinsic + const originStakeRaw = await getStakeRaw(api, originHotkeyAddress, coldkeyAddress, netuid1); + const moveAmount = originStakeRaw / 2n; + await moveStake( + api, + coldkey, + originHotkeyAddress, + destinationHotkeyAddress, + netuid1, + netuid2, + moveAmount + ); + + // Verify stakes changed + const originStakeAfter = await getStake(api, originHotkeyAddress, coldkeyAddress, netuid1); + const destStakeAfter = await getStake(api, destinationHotkeyAddress, coldkeyAddress, netuid2); + + log.info(`Origin stake (netuid1) after: ${originStakeAfter}, Destination stake (netuid2) after: ${destStakeAfter}`); + + assert.ok(originStakeAfter < originStakeBefore, `Origin stake should decrease: before=${originStakeBefore}, after=${originStakeAfter}`); + assert.ok(destStakeAfter > destStakeBefore, `Destination stake should increase: before=${destStakeBefore}, after=${destStakeAfter}`); + + log.info("✅ Successfully moved stake to another hotkey across subnets."); + }); + + it("should move stake to another hotkey on the same subnet", async () => { + const api = await getDevnetApi(); + + // Setup accounts + const originHotkey = getRandomSubstrateKeypair(); + const destinationHotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const originHotkeyAddress = convertPublicKeyToSs58(originHotkey.publicKey); + const destinationHotkeyAddress = convertPublicKeyToSs58(destinationHotkey.publicKey); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + + await forceSetBalance(api, originHotkeyAddress); + await forceSetBalance(api, destinationHotkeyAddress); + await forceSetBalance(api, coldkeyAddress); + + // Create subnet with origin hotkey + const netuid = await addNewSubnetwork(api, originHotkey, coldkey); + await startCall(api, netuid, coldkey); + + // Register destination hotkey on the same subnet + await burnedRegister(api, netuid, destinationHotkeyAddress, coldkey); + + // Add stake to origin hotkey + await addStake(api, coldkey, originHotkeyAddress, netuid, tao(200)); + + // Get initial stakes (converted from U64F64 for display) + const originStakeBefore = await getStake(api, originHotkeyAddress, coldkeyAddress, netuid); + const destStakeBefore = await getStake(api, destinationHotkeyAddress, coldkeyAddress, netuid); + assert.ok(originStakeBefore > 0n, "Origin hotkey should have stake before move"); + + log.info(`Origin stake before: ${originStakeBefore}, Destination stake before: ${destStakeBefore}`); + + // Move stake to destination hotkey on the same subnet + // Use raw U64F64 value for the extrinsic + const originStakeRaw = await getStakeRaw(api, originHotkeyAddress, coldkeyAddress, netuid); + const moveAmount = originStakeRaw / 2n; + await moveStake( + api, + coldkey, + originHotkeyAddress, + destinationHotkeyAddress, + netuid, + netuid, + moveAmount + ); + + // Verify stakes changed + const originStakeAfter = await getStake(api, originHotkeyAddress, coldkeyAddress, netuid); + const destStakeAfter = await getStake(api, destinationHotkeyAddress, coldkeyAddress, netuid); + + log.info(`Origin stake after: ${originStakeAfter}, Destination stake after: ${destStakeAfter}`); + + assert.ok(originStakeAfter < originStakeBefore, `Origin stake should decrease: before=${originStakeBefore}, after=${originStakeAfter}`); + assert.ok(destStakeAfter > destStakeBefore, `Destination stake should increase: before=${destStakeBefore}, after=${destStakeAfter}`); + + log.info("✅ Successfully moved stake to another hotkey on the same subnet."); + }); +}); diff --git a/e2e/staking/test/remove-stake-full-limit.test.ts b/e2e/staking/test/remove-stake-full-limit.test.ts new file mode 100644 index 0000000000..782864c82e --- /dev/null +++ b/e2e/staking/test/remove-stake-full-limit.test.ts @@ -0,0 +1,85 @@ +import * as assert from "assert"; +import { + getDevnetApi, + getRandomSubstrateKeypair, + convertPublicKeyToSs58, + forceSetBalance, + getBalance, + addNewSubnetwork, + startCall, + addStake, + removeStakeFullLimit, + getStake, + tao, + log, +} from "e2e-shared"; + +describe("▶ remove_stake_full_limit extrinsic", () => { + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const hotkeyAddress = convertPublicKeyToSs58(hotkey.publicKey); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + let netuid: number; + + before(async () => { + const api = await getDevnetApi(); + await forceSetBalance(api, hotkeyAddress); + await forceSetBalance(api, coldkeyAddress); + netuid = await addNewSubnetwork(api, hotkey, coldkey); + await startCall(api, netuid, coldkey); + }); + + it("should remove all stake with price limit", async () => { + const api = await getDevnetApi(); + + // Add stake first + await addStake(api, coldkey, hotkeyAddress, netuid, tao(100)); + + // Get initial stake and balance + const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + const balanceBefore = await getBalance(api, coldkeyAddress); + log.info(`Stake before: ${stakeBefore}, Balance before: ${balanceBefore}`); + assert.ok(stakeBefore > 0n, "Should have stake before removal"); + + // Remove all stake with a reasonable limit price (low limit to avoid slippage rejection) + // Using a low limit price (0.09 TAO per alpha) allows the transaction to succeed + const limitPrice = tao(1) / 10n; // 0.1 TAO + await removeStakeFullLimit(api, coldkey, hotkeyAddress, netuid, limitPrice); + + // Verify stake is zero + const stakeAfter = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + const balanceAfter = await getBalance(api, coldkeyAddress); + log.info(`Stake after: ${stakeAfter}, Balance after: ${balanceAfter}`); + + assert.strictEqual(stakeAfter, 0n, `Stake should be zero after full removal, got ${stakeAfter}`); + assert.ok(balanceAfter > balanceBefore, `Balance should increase: before=${balanceBefore}, after=${balanceAfter}`); + + log.info("✅ Successfully removed all stake with price limit."); + }); + + it("should remove all stake without price limit", async () => { + const api = await getDevnetApi(); + + // Add stake first + await addStake(api, coldkey, hotkeyAddress, netuid, tao(100)); + + // Get initial stake and balance + const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + const balanceBefore = await getBalance(api, coldkeyAddress); + log.info(`Stake before: ${stakeBefore}, Balance before: ${balanceBefore}`); + assert.ok(stakeBefore > 0n, "Should have stake before removal"); + + // Remove all stake without limit price (undefined = no slippage protection) + await removeStakeFullLimit(api, coldkey, hotkeyAddress, netuid, undefined); + + // Verify stake is zero + const stakeAfter = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + const balanceAfter = await getBalance(api, coldkeyAddress); + log.info(`Stake after: ${stakeAfter}, Balance after: ${balanceAfter}`); + + assert.strictEqual(stakeAfter, 0n, `Stake should be zero after full removal, got ${stakeAfter}`); + assert.ok(balanceAfter > balanceBefore, `Balance should increase: before=${balanceBefore}, after=${balanceAfter}`); + + log.info("✅ Successfully removed all stake without price limit."); + }); +}); diff --git a/e2e/staking/test/remove-stake-limit.test.ts b/e2e/staking/test/remove-stake-limit.test.ts new file mode 100644 index 0000000000..addc8c717d --- /dev/null +++ b/e2e/staking/test/remove-stake-limit.test.ts @@ -0,0 +1,80 @@ +import * as assert from "assert"; +import { + getDevnetApi, + getRandomSubstrateKeypair, + convertPublicKeyToSs58, + forceSetBalance, + getBalance, + addNewSubnetwork, + burnedRegister, + startCall, + addStake, + removeStakeLimit, + getStake, + tao, + log, +} from "e2e-shared"; + +describe("▶ remove_stake_limit extrinsic", () => { + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const hotkeyAddress = convertPublicKeyToSs58(hotkey.publicKey); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + let netuid: number; + + before(async () => { + const api = await getDevnetApi(); + await forceSetBalance(api, hotkeyAddress); + await forceSetBalance(api, coldkeyAddress); + netuid = await addNewSubnetwork(api, hotkey, coldkey); + await startCall(api, netuid, coldkey); + }); + + it("should remove stake with price limit (allow partial)", async () => { + const api = await getDevnetApi(); + + // Add stake first (100 TAO like benchmark) + await addStake(api, coldkey, hotkeyAddress, netuid, tao(100)); + + // Get initial stake and balance + const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + const balanceBefore = await getBalance(api, coldkeyAddress); + log.info(`Stake before: ${stakeBefore}, Balance before: ${balanceBefore}`); + assert.ok(stakeBefore > 0n, "Should have stake before removal"); + + // Remove stake with limit price and allow partial fills + const unstakeAmount = tao(30); + const limitPrice = tao(1); + await removeStakeLimit(api, coldkey, hotkeyAddress, netuid, unstakeAmount, limitPrice, true); + + // Verify balance increased (received TAO from unstaking) + const balanceAfter = await getBalance(api, coldkeyAddress); + assert.ok(balanceAfter > balanceBefore, `Balance should increase: before=${balanceBefore}, after=${balanceAfter}`); + + log.info("✅ Successfully removed stake with limit (allow partial)."); + }); + + it("should remove stake with price limit (fill or kill)", async () => { + const api = await getDevnetApi(); + + // Add stake first (100 TAO like benchmark) + await addStake(api, coldkey, hotkeyAddress, netuid, tao(100)); + + // Get initial stake and balance + const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + const balanceBefore = await getBalance(api, coldkeyAddress); + log.info(`Stake before: ${stakeBefore}, Balance before: ${balanceBefore}`); + assert.ok(stakeBefore > 0n, "Should have stake before removal"); + + // Remove stake with limit price (fill or kill mode) + const unstakeAmount = tao(30); + const limitPrice = tao(1); + await removeStakeLimit(api, coldkey, hotkeyAddress, netuid, unstakeAmount, limitPrice, false); + + // Verify balance increased (received TAO from unstaking) + const balanceAfter = await getBalance(api, coldkeyAddress); + assert.ok(balanceAfter > balanceBefore, `Balance should increase: before=${balanceBefore}, after=${balanceAfter}`); + + log.info("✅ Successfully removed stake with limit (fill or kill)."); + }); +}); diff --git a/e2e/staking/test/remove-stake.test.ts b/e2e/staking/test/remove-stake.test.ts new file mode 100644 index 0000000000..a863c3042c --- /dev/null +++ b/e2e/staking/test/remove-stake.test.ts @@ -0,0 +1,56 @@ +import * as assert from "assert"; +import { + getDevnetApi, + getRandomSubstrateKeypair, + convertPublicKeyToSs58, + forceSetBalance, + getBalance, + addNewSubnetwork, + burnedRegister, + startCall, + addStake, + removeStake, + getStake, + getStakeRaw, + tao, + log, +} from "e2e-shared"; + +describe("▶ remove_stake extrinsic", () => { + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const hotkeyAddress = convertPublicKeyToSs58(hotkey.publicKey); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + let netuid: number; + + before(async () => { + const api = await getDevnetApi(); + await forceSetBalance(api, hotkeyAddress); + await forceSetBalance(api, coldkeyAddress); + netuid = await addNewSubnetwork(api, hotkey, coldkey); + await startCall(api, netuid, coldkey); + }); + + it("should remove stake from a hotkey", async () => { + const api = await getDevnetApi(); + + // Add stake first + await addStake(api, coldkey, hotkeyAddress, netuid, tao(200)); + + // Get initial stake and balance (converted from U64F64 for display) + const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + const balanceBefore = await getBalance(api, coldkeyAddress); + assert.ok(stakeBefore > 0n, "Should have stake before removal"); + + // Remove stake (amount is in alpha units - use raw U64F64 value) + const stakeRaw = await getStakeRaw(api, hotkeyAddress, coldkeyAddress, netuid); + const unstakeAmount = stakeRaw / 2n; + await removeStake(api, coldkey, hotkeyAddress, netuid, unstakeAmount); + + // Verify balance increased (received TAO from unstaking) + const balanceAfter = await getBalance(api, coldkeyAddress); + assert.ok(balanceAfter > balanceBefore, `Balance should increase: before=${balanceBefore}, after=${balanceAfter}`); + + log.info("✅ Successfully removed stake."); + }); +}); diff --git a/e2e/staking/test/swap-stake-limit.test.ts b/e2e/staking/test/swap-stake-limit.test.ts new file mode 100644 index 0000000000..d52b1e411e --- /dev/null +++ b/e2e/staking/test/swap-stake-limit.test.ts @@ -0,0 +1,128 @@ +import * as assert from "assert"; +import { + getDevnetApi, + getRandomSubstrateKeypair, + convertPublicKeyToSs58, + forceSetBalance, + addNewSubnetwork, + burnedRegister, + startCall, + addStake, + swapStakeLimit, + getStake, + getStakeRaw, + tao, + log, +} from "e2e-shared"; + +describe("▶ swap_stake_limit extrinsic", () => { + it("should swap stake with price limit (allow partial)", async () => { + const api = await getDevnetApi(); + + // Setup accounts + const hotkey1 = getRandomSubstrateKeypair(); + const hotkey2 = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const hotkey1Address = convertPublicKeyToSs58(hotkey1.publicKey); + const hotkey2Address = convertPublicKeyToSs58(hotkey2.publicKey); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + + await forceSetBalance(api, hotkey1Address); + await forceSetBalance(api, hotkey2Address); + await forceSetBalance(api, coldkeyAddress); + + // Create first subnet + const netuid1 = await addNewSubnetwork(api, hotkey1, coldkey); + await startCall(api, netuid1, coldkey); + + // Create second subnet + const netuid2 = await addNewSubnetwork(api, hotkey2, coldkey); + await startCall(api, netuid2, coldkey); + + // Register hotkey1 on subnet2 so we can swap stake there + await burnedRegister(api, netuid2, hotkey1Address, coldkey); + + // Add stake to hotkey1 on subnet1 + await addStake(api, coldkey, hotkey1Address, netuid1, tao(100)); + + // Get initial stakes (converted from U64F64 for display) + const stake1Before = await getStake(api, hotkey1Address, coldkeyAddress, netuid1); + const stake2Before = await getStake(api, hotkey1Address, coldkeyAddress, netuid2); + assert.ok(stake1Before > 0n, "Should have stake on subnet1 before swap"); + + log.info(`Stake on netuid1 before: ${stake1Before}, Stake on netuid2 before: ${stake2Before}`); + + // Swap stake with limit price (0.99 TAO relative price limit, allow partial fills) + // Use raw U64F64 value for the extrinsic + const stake1Raw = await getStakeRaw(api, hotkey1Address, coldkeyAddress, netuid1); + const swapAmount = stake1Raw / 2n; + const limitPrice = tao(1) * 99n / 100n; // 0.99 TAO + await swapStakeLimit(api, coldkey, hotkey1Address, netuid1, netuid2, swapAmount, limitPrice, true); + + // Verify stakes changed + const stake1After = await getStake(api, hotkey1Address, coldkeyAddress, netuid1); + const stake2After = await getStake(api, hotkey1Address, coldkeyAddress, netuid2); + + log.info(`Stake on netuid1 after: ${stake1After}, Stake on netuid2 after: ${stake2After}`); + + assert.ok(stake1After < stake1Before, `Stake on subnet1 should decrease: before=${stake1Before}, after=${stake1After}`); + assert.ok(stake2After > stake2Before, `Stake on subnet2 should increase: before=${stake2Before}, after=${stake2After}`); + + log.info("✅ Successfully swapped stake with price limit (allow partial)."); + }); + + it("should swap stake with price limit (fill or kill)", async () => { + const api = await getDevnetApi(); + + // Setup accounts + const hotkey1 = getRandomSubstrateKeypair(); + const hotkey2 = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const hotkey1Address = convertPublicKeyToSs58(hotkey1.publicKey); + const hotkey2Address = convertPublicKeyToSs58(hotkey2.publicKey); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + + await forceSetBalance(api, hotkey1Address); + await forceSetBalance(api, hotkey2Address); + await forceSetBalance(api, coldkeyAddress); + + // Create first subnet + const netuid1 = await addNewSubnetwork(api, hotkey1, coldkey); + await startCall(api, netuid1, coldkey); + + // Create second subnet + const netuid2 = await addNewSubnetwork(api, hotkey2, coldkey); + await startCall(api, netuid2, coldkey); + + // Register hotkey1 on subnet2 so we can swap stake there + await burnedRegister(api, netuid2, hotkey1Address, coldkey); + + // Add stake to hotkey1 on subnet1 + await addStake(api, coldkey, hotkey1Address, netuid1, tao(100)); + + // Get initial stakes (converted from U64F64 for display) + const stake1Before = await getStake(api, hotkey1Address, coldkeyAddress, netuid1); + const stake2Before = await getStake(api, hotkey1Address, coldkeyAddress, netuid2); + assert.ok(stake1Before > 0n, "Should have stake on subnet1 before swap"); + + log.info(`Stake on netuid1 before: ${stake1Before}, Stake on netuid2 before: ${stake2Before}`); + + // Swap stake with limit price (fill or kill mode - allow_partial = false) + // Use raw U64F64 value for the extrinsic + const stake1Raw = await getStakeRaw(api, hotkey1Address, coldkeyAddress, netuid1); + const swapAmount = stake1Raw / 2n; + const limitPrice = tao(1) / 10n; // 0.1 TAO - permissive limit to allow slippage + await swapStakeLimit(api, coldkey, hotkey1Address, netuid1, netuid2, swapAmount, limitPrice, false); + + // Verify stakes changed + const stake1After = await getStake(api, hotkey1Address, coldkeyAddress, netuid1); + const stake2After = await getStake(api, hotkey1Address, coldkeyAddress, netuid2); + + log.info(`Stake on netuid1 after: ${stake1After}, Stake on netuid2 after: ${stake2After}`); + + assert.ok(stake1After < stake1Before, `Stake on subnet1 should decrease: before=${stake1Before}, after=${stake1After}`); + assert.ok(stake2After > stake2Before, `Stake on subnet2 should increase: before=${stake2Before}, after=${stake2After}`); + + log.info("✅ Successfully swapped stake with price limit (fill or kill)."); + }); +}); diff --git a/e2e/staking/test/swap-stake.test.ts b/e2e/staking/test/swap-stake.test.ts new file mode 100644 index 0000000000..5d6b9329cc --- /dev/null +++ b/e2e/staking/test/swap-stake.test.ts @@ -0,0 +1,70 @@ +import * as assert from "assert"; +import { + getDevnetApi, + getRandomSubstrateKeypair, + convertPublicKeyToSs58, + forceSetBalance, + addNewSubnetwork, + burnedRegister, + startCall, + addStake, + swapStake, + getStake, + getStakeRaw, + tao, + log, +} from "e2e-shared"; + +describe("▶ swap_stake extrinsic", () => { + it("should swap full stake from one subnet to another", async () => { + const api = await getDevnetApi(); + + // Setup accounts + const hotkey1 = getRandomSubstrateKeypair(); + const hotkey2 = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const hotkey1Address = convertPublicKeyToSs58(hotkey1.publicKey); + const hotkey2Address = convertPublicKeyToSs58(hotkey2.publicKey); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + + await forceSetBalance(api, hotkey1Address); + await forceSetBalance(api, hotkey2Address); + await forceSetBalance(api, coldkeyAddress); + + // Create first subnet + const netuid1 = await addNewSubnetwork(api, hotkey1, coldkey); + await startCall(api, netuid1, coldkey); + + // Create second subnet + const netuid2 = await addNewSubnetwork(api, hotkey2, coldkey); + await startCall(api, netuid2, coldkey); + + // Register hotkey1 on subnet2 so we can swap stake there + await burnedRegister(api, netuid2, hotkey1Address, coldkey); + + // Add stake to hotkey1 on subnet1 + await addStake(api, coldkey, hotkey1Address, netuid1, tao(100)); + + // Get initial stake (converted from U64F64 for display) + const stake1Before = await getStake(api, hotkey1Address, coldkeyAddress, netuid1); + assert.ok(stake1Before > 0n, "Should have stake on subnet1 before swap"); + + log.info(`Stake on netuid1 before: ${stake1Before}`); + + // Swap full stake from subnet1 to subnet2 + // Use raw U64F64 value for the extrinsic + const stake1Raw = await getStakeRaw(api, hotkey1Address, coldkeyAddress, netuid1); + await swapStake(api, coldkey, hotkey1Address, netuid1, netuid2, stake1Raw); + + // Verify stakes changed + const stake1After = await getStake(api, hotkey1Address, coldkeyAddress, netuid1); + const stake2After = await getStake(api, hotkey1Address, coldkeyAddress, netuid2); + + log.info(`Stake on netuid1 after: ${stake1After}, Stake on netuid2 after: ${stake2After}`); + + assert.strictEqual(stake1After, 0n, `Stake on subnet1 should be zero after full swap, got ${stake1After}`); + assert.ok(stake2After > 0n, `Stake on subnet2 should be non-zero after swap`); + + log.info("✅ Successfully swapped full stake from one subnet to another."); + }); +}); diff --git a/e2e/staking/test/transfer-stake.test.ts b/e2e/staking/test/transfer-stake.test.ts new file mode 100644 index 0000000000..3a34a76ea5 --- /dev/null +++ b/e2e/staking/test/transfer-stake.test.ts @@ -0,0 +1,132 @@ +import * as assert from "assert"; +import { + getDevnetApi, + getRandomSubstrateKeypair, + convertPublicKeyToSs58, + forceSetBalance, + addNewSubnetwork, + startCall, + addStake, + transferStake, + getStake, + getStakeRaw, + tao, + log, +} from "e2e-shared"; + +describe("▶ transfer_stake extrinsic", () => { + it("should transfer stake to another coldkey across subnets", async () => { + const api = await getDevnetApi(); + + // Setup accounts + const hotkey1 = getRandomSubstrateKeypair(); + const hotkey2 = getRandomSubstrateKeypair(); + const originColdkey = getRandomSubstrateKeypair(); + const destinationColdkey = getRandomSubstrateKeypair(); + const hotkey1Address = convertPublicKeyToSs58(hotkey1.publicKey); + const hotkey2Address = convertPublicKeyToSs58(hotkey2.publicKey); + const originColdkeyAddress = convertPublicKeyToSs58(originColdkey.publicKey); + const destinationColdkeyAddress = convertPublicKeyToSs58(destinationColdkey.publicKey); + + await forceSetBalance(api, hotkey1Address); + await forceSetBalance(api, hotkey2Address); + await forceSetBalance(api, originColdkeyAddress); + await forceSetBalance(api, destinationColdkeyAddress); + + // Create first subnet + const netuid1 = await addNewSubnetwork(api, hotkey1, originColdkey); + await startCall(api, netuid1, originColdkey); + + // Create second subnet + const netuid2 = await addNewSubnetwork(api, hotkey2, originColdkey); + await startCall(api, netuid2, originColdkey); + + // Add stake from origin coldkey on first subnet + await addStake(api, originColdkey, hotkey1Address, netuid1, tao(200)); + + // Get initial stakes (converted from U64F64 for display) + const originStakeBefore = await getStake(api, hotkey1Address, originColdkeyAddress, netuid1); + const destStakeBefore = await getStake(api, hotkey1Address, destinationColdkeyAddress, netuid2); + assert.ok(originStakeBefore > 0n, "Origin should have stake before transfer"); + + log.info(`Origin stake (netuid1) before: ${originStakeBefore}, Destination stake (netuid2) before: ${destStakeBefore}`); + + // Transfer stake to destination coldkey on a different subnet + // Use raw U64F64 value for the extrinsic + const originStakeRaw = await getStakeRaw(api, hotkey1Address, originColdkeyAddress, netuid1); + const transferAmount = originStakeRaw / 2n; + await transferStake( + api, + originColdkey, + destinationColdkeyAddress, + hotkey1Address, + netuid1, + netuid2, + transferAmount + ); + + // Verify stakes changed + const originStakeAfter = await getStake(api, hotkey1Address, originColdkeyAddress, netuid1); + const destStakeAfter = await getStake(api, hotkey1Address, destinationColdkeyAddress, netuid2); + + log.info(`Origin stake (netuid1) after: ${originStakeAfter}, Destination stake (netuid2) after: ${destStakeAfter}`); + + assert.ok(originStakeAfter < originStakeBefore, `Origin stake should decrease: before=${originStakeBefore}, after=${originStakeAfter}`); + assert.ok(destStakeAfter > destStakeBefore, `Destination stake should increase: before=${destStakeBefore}, after=${destStakeAfter}`); + + log.info("✅ Successfully transferred stake to another coldkey across subnets."); + }); + + it("should transfer stake to another coldkey", async () => { + const api = await getDevnetApi(); + + // Setup accounts + const hotkey = getRandomSubstrateKeypair(); + const originColdkey = getRandomSubstrateKeypair(); + const destinationColdkey = getRandomSubstrateKeypair(); + const hotkeyAddress = convertPublicKeyToSs58(hotkey.publicKey); + const originColdkeyAddress = convertPublicKeyToSs58(originColdkey.publicKey); + const destinationColdkeyAddress = convertPublicKeyToSs58(destinationColdkey.publicKey); + + await forceSetBalance(api, hotkeyAddress); + await forceSetBalance(api, originColdkeyAddress); + await forceSetBalance(api, destinationColdkeyAddress); + + // Create subnet + const netuid = await addNewSubnetwork(api, hotkey, originColdkey); + await startCall(api, netuid, originColdkey); + + // Add stake from origin coldkey + const stakeAmount = tao(100); + await addStake(api, originColdkey, hotkeyAddress, netuid, stakeAmount); + + // Get initial stake (converted from U64F64 for display) + const originStakeBefore = await getStake(api, hotkeyAddress, originColdkeyAddress, netuid); + assert.ok(originStakeBefore > 0n, "Origin should have stake before transfer"); + + log.info(`Origin stake before: ${originStakeBefore}`); + + const originStakeRaw = await getStakeRaw(api, hotkeyAddress, originColdkeyAddress, netuid); + const transferAmount = originStakeRaw / 2n; + await transferStake( + api, + originColdkey, + destinationColdkeyAddress, + hotkeyAddress, + netuid, + netuid, + transferAmount + ); + + // Verify destination received stake + const originStakeAfter = await getStake(api, hotkeyAddress, originColdkeyAddress, netuid); + const destStakeAfter = await getStake(api, hotkeyAddress, destinationColdkeyAddress, netuid); + + log.info(`Origin stake after: ${originStakeAfter}, Destination stake after: ${destStakeAfter}`); + + assert.ok(originStakeAfter < originStakeBefore, `Origin stake should decrease after transfer`); + assert.ok(destStakeAfter > 0n, `Destination stake should be non-zero after transfer`); + + log.info("✅ Successfully transferred stake to another coldkey."); + }); +}); diff --git a/e2e/staking/test/unstake-all-alpha.test.ts b/e2e/staking/test/unstake-all-alpha.test.ts new file mode 100644 index 0000000000..a936471c2d --- /dev/null +++ b/e2e/staking/test/unstake-all-alpha.test.ts @@ -0,0 +1,72 @@ +import * as assert from "assert"; +import { + getDevnetApi, + getRandomSubstrateKeypair, + convertPublicKeyToSs58, + forceSetBalance, + addNewSubnetwork, + startCall, + addStake, + unstakeAllAlpha, + getStake, + tao, + log, +} from "e2e-shared"; + +// Root subnet netuid is 0 +const ROOT_NETUID = 0; + +describe("▶ unstake_all_alpha extrinsic", () => { + it("should unstake all alpha from multiple subnets and restake to root", async () => { + const api = await getDevnetApi(); + + // Setup accounts + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const hotkeyAddress = convertPublicKeyToSs58(hotkey.publicKey); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + const hotkey2 = getRandomSubstrateKeypair(); + const hotkeyAddress2 = convertPublicKeyToSs58(hotkey2.publicKey); + + await forceSetBalance(api, hotkeyAddress); + await forceSetBalance(api, coldkeyAddress); + await forceSetBalance(api, hotkeyAddress2); + + // Create first subnet + const netuid1 = await addNewSubnetwork(api, hotkey, coldkey); + await startCall(api, netuid1, coldkey); + + // Create second subnet + const netuid2 = await addNewSubnetwork(api, hotkey2, coldkey); + await startCall(api, netuid2, coldkey); + + // Add stake to both subnets (using same hotkey as in unstake_all test) + await addStake(api, coldkey, hotkeyAddress, netuid1, tao(100)); + await addStake(api, coldkey, hotkeyAddress, netuid2, tao(50)); + + // Verify stake was added to both subnets + const stake1Before = await getStake(api, hotkeyAddress, coldkeyAddress, netuid1); + const stake2Before = await getStake(api, hotkeyAddress, coldkeyAddress, netuid2); + const rootStakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, ROOT_NETUID); + + assert.ok(stake1Before > 0n, "Should have stake in subnet 1 before unstake_all_alpha"); + assert.ok(stake2Before > 0n, "Should have stake in subnet 2 before unstake_all_alpha"); + log.info(`Stake1 before: ${stake1Before}, Stake2 before: ${stake2Before}, Root stake before: ${rootStakeBefore}`); + + // Unstake all alpha - this removes stake from dynamic subnets and restakes to root + await unstakeAllAlpha(api, coldkey, hotkeyAddress); + + // Verify stakes are removed from both dynamic subnets + const stake1After = await getStake(api, hotkeyAddress, coldkeyAddress, netuid1); + const stake2After = await getStake(api, hotkeyAddress, coldkeyAddress, netuid2); + const rootStakeAfter = await getStake(api, hotkeyAddress, coldkeyAddress, ROOT_NETUID); + + log.info(`Stake1 after: ${stake1After}, Stake2 after: ${stake2After}, Root stake after: ${rootStakeAfter}`); + + assert.strictEqual(stake1After, 0n, `Stake1 should be zero after unstake_all_alpha, got ${stake1After}`); + assert.strictEqual(stake2After, 0n, `Stake2 should be zero after unstake_all_alpha, got ${stake2After}`); + assert.ok(rootStakeAfter > rootStakeBefore, `Root stake should increase: before=${rootStakeBefore}, after=${rootStakeAfter}`); + + log.info("✅ Successfully unstaked all alpha from multiple subnets to root."); + }); +}); diff --git a/e2e/staking/test/unstake-all.test.ts b/e2e/staking/test/unstake-all.test.ts new file mode 100644 index 0000000000..599dc32bc0 --- /dev/null +++ b/e2e/staking/test/unstake-all.test.ts @@ -0,0 +1,70 @@ +import * as assert from "assert"; +import { + getDevnetApi, + getRandomSubstrateKeypair, + convertPublicKeyToSs58, + forceSetBalance, + getBalance, + addNewSubnetwork, + startCall, + addStake, + unstakeAll, + getStake, + tao, + log, +} from "e2e-shared"; + +describe("▶ unstake_all extrinsic", () => { + it("should unstake all from a hotkey across all subnets", async () => { + const api = await getDevnetApi(); + + // Setup accounts + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const hotkeyAddress = convertPublicKeyToSs58(hotkey.publicKey); + const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); + const hotkey2 = getRandomSubstrateKeypair(); + const hotkeyAddress2 = convertPublicKeyToSs58(hotkey2.publicKey); + + await forceSetBalance(api, hotkeyAddress); + await forceSetBalance(api, coldkeyAddress); + await forceSetBalance(api, hotkeyAddress2); + + // Create first subnet + const netuid1 = await addNewSubnetwork(api, hotkey, coldkey); + await startCall(api, netuid1, coldkey); + + // Create second subnet + const netuid2 = await addNewSubnetwork(api, hotkey2, coldkey); + await startCall(api, netuid2, coldkey); + + // Add stake to both subnets + await addStake(api, coldkey, hotkeyAddress, netuid1, tao(100)); + await addStake(api, coldkey, hotkeyAddress, netuid2, tao(50)); + + // Verify stake was added to both subnets + const stake1Before = await getStake(api, hotkeyAddress, coldkeyAddress, netuid1); + const stake2Before = await getStake(api, hotkeyAddress, coldkeyAddress, netuid2); + const balanceBefore = await getBalance(api, coldkeyAddress); + + assert.ok(stake1Before > 0n, "Should have stake in subnet 1 before unstake_all"); + assert.ok(stake2Before > 0n, "Should have stake in subnet 2 before unstake_all"); + log.info(`Stake1 before: ${stake1Before}, Stake2 before: ${stake2Before}, Balance before: ${balanceBefore}`); + + // Unstake all + await unstakeAll(api, coldkey, hotkeyAddress); + + // Verify stakes are removed from both subnets and balance increased + const stake1After = await getStake(api, hotkeyAddress, coldkeyAddress, netuid1); + const stake2After = await getStake(api, hotkeyAddress, coldkeyAddress, netuid2); + const balanceAfter = await getBalance(api, coldkeyAddress); + + log.info(`Stake1 after: ${stake1After}, Stake2 after: ${stake2After}, Balance after: ${balanceAfter}`); + + assert.strictEqual(stake1After, 0n, `Stake1 should be zero after unstake_all, got ${stake1After}`); + assert.strictEqual(stake2After, 0n, `Stake2 should be zero after unstake_all, got ${stake2After}`); + assert.ok(balanceAfter > balanceBefore, `Balance should increase: before=${balanceBefore}, after=${balanceAfter}`); + + log.info("✅ Successfully unstaked all from multiple subnets."); + }); +}); diff --git a/e2e/staking/tsconfig.json b/e2e/staking/tsconfig.json new file mode 100644 index 0000000000..a414aaf914 --- /dev/null +++ b/e2e/staking/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "types": ["node", "mocha"] + } +} From cb50633e935409cdff0a315ad328782929d52c54 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Mon, 2 Mar 2026 17:34:48 +0300 Subject: [PATCH 3/9] Update E2E workflow --- .github/workflows/e2e.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index f04d592a64..959a86bdeb 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -141,4 +141,8 @@ jobs: - name: Run tests working-directory: e2e + env: + # Use fast-runtime binary for staking package, release binary for others + # Path is relative to package directory (e2e//) + BINARY_PATH: ${{ matrix.package == 'e2e-staking' && '../../target/fast/node-subtensor' || '../../target/release/node-subtensor' }} run: pnpm --filter ${{ matrix.package }} test From 2b7144edc66905a5e5dedfc8d2d4a9288e41274e Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Wed, 4 Mar 2026 17:49:32 +0300 Subject: [PATCH 4/9] Fix unstake-all-alpha tests. --- e2e/staking/test/unstake-all-alpha.test.ts | 58 ++++++++++++---------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/e2e/staking/test/unstake-all-alpha.test.ts b/e2e/staking/test/unstake-all-alpha.test.ts index a936471c2d..93146aa196 100644 --- a/e2e/staking/test/unstake-all-alpha.test.ts +++ b/e2e/staking/test/unstake-all-alpha.test.ts @@ -5,6 +5,7 @@ import { convertPublicKeyToSs58, forceSetBalance, addNewSubnetwork, + burnedRegister, startCall, addStake, unstakeAllAlpha, @@ -13,59 +14,64 @@ import { log, } from "e2e-shared"; -// Root subnet netuid is 0 -const ROOT_NETUID = 0; - describe("▶ unstake_all_alpha extrinsic", () => { it("should unstake all alpha from multiple subnets and restake to root", async () => { const api = await getDevnetApi(); // Setup accounts - const hotkey = getRandomSubstrateKeypair(); + // - owner1/coldkey: owns subnet 1 + // - owner2/coldkey: owns subnet 2 + // - stakerHotkey: staker (not owner) on both subnets - used for testing unstake_all_alpha + const owner1Hotkey = getRandomSubstrateKeypair(); + const owner2Hotkey = getRandomSubstrateKeypair(); + const stakerHotkey = getRandomSubstrateKeypair(); const coldkey = getRandomSubstrateKeypair(); - const hotkeyAddress = convertPublicKeyToSs58(hotkey.publicKey); + const owner1Address = convertPublicKeyToSs58(owner1Hotkey.publicKey); + const owner2Address = convertPublicKeyToSs58(owner2Hotkey.publicKey); + const stakerAddress = convertPublicKeyToSs58(stakerHotkey.publicKey); const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); - const hotkey2 = getRandomSubstrateKeypair(); - const hotkeyAddress2 = convertPublicKeyToSs58(hotkey2.publicKey); - await forceSetBalance(api, hotkeyAddress); + await forceSetBalance(api, owner1Address); + await forceSetBalance(api, owner2Address); + await forceSetBalance(api, stakerAddress); await forceSetBalance(api, coldkeyAddress); - await forceSetBalance(api, hotkeyAddress2); - // Create first subnet - const netuid1 = await addNewSubnetwork(api, hotkey, coldkey); + // Create first subnet with owner1 + const netuid1 = await addNewSubnetwork(api, owner1Hotkey, coldkey); await startCall(api, netuid1, coldkey); - // Create second subnet - const netuid2 = await addNewSubnetwork(api, hotkey2, coldkey); + // Create second subnet with owner2 + const netuid2 = await addNewSubnetwork(api, owner2Hotkey, coldkey); await startCall(api, netuid2, coldkey); - // Add stake to both subnets (using same hotkey as in unstake_all test) - await addStake(api, coldkey, hotkeyAddress, netuid1, tao(100)); - await addStake(api, coldkey, hotkeyAddress, netuid2, tao(50)); + // Register stakerHotkey on both subnets (it's not the owner) + await burnedRegister(api, netuid1, stakerAddress, coldkey); + await burnedRegister(api, netuid2, stakerAddress, coldkey); + + // Add stake to both subnets using stakerHotkey (not the owner) + await addStake(api, coldkey, stakerAddress, netuid1, tao(100)); + await addStake(api, coldkey, stakerAddress, netuid2, tao(50)); // Verify stake was added to both subnets - const stake1Before = await getStake(api, hotkeyAddress, coldkeyAddress, netuid1); - const stake2Before = await getStake(api, hotkeyAddress, coldkeyAddress, netuid2); - const rootStakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, ROOT_NETUID); + const stake1Before = await getStake(api, stakerAddress, coldkeyAddress, netuid1); + const stake2Before = await getStake(api, stakerAddress, coldkeyAddress, netuid2); assert.ok(stake1Before > 0n, "Should have stake in subnet 1 before unstake_all_alpha"); assert.ok(stake2Before > 0n, "Should have stake in subnet 2 before unstake_all_alpha"); - log.info(`Stake1 before: ${stake1Before}, Stake2 before: ${stake2Before}, Root stake before: ${rootStakeBefore}`); + log.info(`Stake1 before: ${stake1Before}, Stake2 before: ${stake2Before}`); // Unstake all alpha - this removes stake from dynamic subnets and restakes to root - await unstakeAllAlpha(api, coldkey, hotkeyAddress); + await unstakeAllAlpha(api, coldkey, stakerAddress); // Verify stakes are removed from both dynamic subnets - const stake1After = await getStake(api, hotkeyAddress, coldkeyAddress, netuid1); - const stake2After = await getStake(api, hotkeyAddress, coldkeyAddress, netuid2); - const rootStakeAfter = await getStake(api, hotkeyAddress, coldkeyAddress, ROOT_NETUID); + const stake1After = await getStake(api, stakerAddress, coldkeyAddress, netuid1); + const stake2After = await getStake(api, stakerAddress, coldkeyAddress, netuid2); - log.info(`Stake1 after: ${stake1After}, Stake2 after: ${stake2After}, Root stake after: ${rootStakeAfter}`); + log.info(`Stake1 after: ${stake1After}, Stake2 after: ${stake2After}`); + // Since stakerHotkey is not the owner of either subnet, all stake should be removed assert.strictEqual(stake1After, 0n, `Stake1 should be zero after unstake_all_alpha, got ${stake1After}`); assert.strictEqual(stake2After, 0n, `Stake2 should be zero after unstake_all_alpha, got ${stake2After}`); - assert.ok(rootStakeAfter > rootStakeBefore, `Root stake should increase: before=${rootStakeBefore}, after=${rootStakeAfter}`); log.info("✅ Successfully unstaked all alpha from multiple subnets to root."); }); From 5ea427476c234c1c3bea497c7ffbdbc6f50ea24a Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Tue, 3 Mar 2026 15:03:44 +0300 Subject: [PATCH 5/9] Migrate to vitest --- e2e/pnpm-lock.yaml | 503 +----------------- e2e/staking/package.json | 10 +- e2e/staking/setup.ts | 14 +- e2e/staking/test/add-stake-limit.test.ts | 9 +- e2e/staking/test/add-stake.test.ts | 7 +- e2e/staking/test/claim-root.test.ts | 38 +- e2e/staking/test/move-stake.test.ts | 34 +- .../test/remove-stake-full-limit.test.ts | 16 +- e2e/staking/test/remove-stake-limit.test.ts | 13 +- e2e/staking/test/remove-stake.test.ts | 9 +- e2e/staking/test/swap-stake-limit.test.ts | 16 +- e2e/staking/test/swap-stake.test.ts | 23 +- e2e/staking/test/transfer-stake.test.ts | 36 +- e2e/staking/test/unstake-all-alpha.test.ts | 10 +- e2e/staking/test/unstake-all.test.ts | 60 ++- e2e/staking/tsconfig.json | 2 +- e2e/staking/vitest.config.ts | 22 + 17 files changed, 162 insertions(+), 660 deletions(-) create mode 100644 e2e/staking/vitest.config.ts diff --git a/e2e/pnpm-lock.yaml b/e2e/pnpm-lock.yaml index a6bc219540..8f84477ae0 100644 --- a/e2e/pnpm-lock.yaml +++ b/e2e/pnpm-lock.yaml @@ -116,19 +116,13 @@ importers: e2e-shared: specifier: workspace:* version: link:../shared - mocha: - specifier: ^11.1.0 - version: 11.7.5 - tsx: - specifier: ^4.21.0 - version: 4.21.0 devDependencies: - '@types/mocha': - specifier: ^10.0.10 - version: 10.0.10 '@types/node': specifier: 'catalog:' version: 24.10.13 + vitest: + specifier: 'catalog:' + version: 4.0.18(@types/node@24.10.13)(tsx@4.21.0) packages: @@ -457,10 +451,6 @@ packages: cpu: [x64] os: [win32] - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -494,10 +484,6 @@ packages: resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} engines: {node: '>= 20.19.0'} - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@polkadot-api/cli@0.18.1': resolution: {integrity: sha512-jPa8WSNPZWdy372sBAUnm0nU1XX5mLbmgkOOU39+zpYPSE12mYXyM3r7JuT5IHdAccEJr6qK2DplPFTeNSyq9A==} hasBin: true @@ -862,9 +848,6 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/mocha@10.0.10': - resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} - '@types/node@24.10.13': resolution: {integrity: sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==} @@ -911,44 +894,20 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - ansi-regex@6.2.2: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - bn.js@5.2.2: resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - - browser-stdout@1.3.1: - resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -959,18 +918,10 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -987,17 +938,6 @@ packages: resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} engines: {node: '>=18.20'} - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - commander@14.0.3: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} @@ -1026,10 +966,6 @@ packages: supports-color: optional: true - decamelize@4.0.0: - resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} - engines: {node: '>=10'} - deepmerge-ts@7.1.5: resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} engines: {node: '>=16.0.0'} @@ -1038,19 +974,6 @@ packages: resolution: {integrity: sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==} engines: {node: '>=12.20'} - diff@7.0.0: - resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} - engines: {node: '>=0.3.1'} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} @@ -1064,14 +987,6 @@ packages: engines: {node: '>=18'} hasBin: true - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1096,21 +1011,9 @@ packages: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} - flat@5.0.2: - resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} - hasBin: true - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - fs.promises.exists@1.1.4: resolution: {integrity: sha512-lJzUGWbZn8vhGWBedA+RYjB/BeJ+3458ljUfmplqhIeb6ewzTFWNPCR1HCiYCkXV9zxcHz9zXkJzMsEgDLzh3Q==} @@ -1119,10 +1022,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.4.0: resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} @@ -1134,19 +1033,6 @@ packages: get-tsconfig@4.13.6: resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} @@ -1167,22 +1053,10 @@ packages: resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} engines: {node: '>=18'} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - is-interactive@2.0.0: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - - is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} - engines: {node: '>=8'} - is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -1191,10 +1065,6 @@ packages: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - is-unicode-supported@2.1.0: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} @@ -1202,9 +1072,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -1212,10 +1079,6 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true - lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1227,17 +1090,9 @@ packages: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - log-symbols@7.0.1: resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} engines: {node: '>=18'} @@ -1256,14 +1111,6 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - minimatch@9.0.9: - resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} - engines: {node: '>=16 || 14 >=14.17'} - - minipass@7.1.3: - resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} - engines: {node: '>=16 || 14 >=14.17'} - mlkem@2.5.0: resolution: {integrity: sha512-TnSvGBs0EVPukQcdPF0882ZoYXYuD2rb+VgO0kUDbFi/XM1rJOwnQoFW3wGGuc3nG3AT/zp3oWJ86W7ewwKYyA==} engines: {node: '>=16.0.0'} @@ -1271,11 +1118,6 @@ packages: mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} - mocha@11.7.5: - resolution: {integrity: sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - hasBin: true - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1314,17 +1156,6 @@ packages: resolution: {integrity: sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==} engines: {node: '>=20'} - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - parse-json@8.3.0: resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} engines: {node: '>=18'} @@ -1333,10 +1164,6 @@ packages: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1345,10 +1172,6 @@ packages: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1407,9 +1230,6 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - read-pkg@10.1.0: resolution: {integrity: sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==} engines: {node: '>=20'} @@ -1422,10 +1242,6 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -1445,9 +1261,6 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - scale-ts@1.6.1: resolution: {integrity: sha512-PBMc2AWc6wSEqJYBDPcyCLUj9/tMKnLX70jLOSndMtcUoLQucP/DM0vnQo1wJAYjTrQiq8iG9rD0q6wFzgjH7g==} @@ -1456,9 +1269,6 @@ packages: engines: {node: '>=10'} hasBin: true - serialize-javascript@6.0.2: - resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1512,22 +1322,10 @@ packages: resolution: {integrity: sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==} engines: {node: '>=18'} - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - string-width@8.1.1: resolution: {integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==} engines: {node: '>=20'} - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - strip-ansi@7.1.2: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} @@ -1536,23 +1334,11 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - sucrase@3.35.1: resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} @@ -1752,17 +1538,6 @@ packages: engines: {node: '>=8'} hasBin: true - workerpool@9.3.4: - resolution: {integrity: sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - write-file-atomic@5.0.1: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -1787,26 +1562,6 @@ packages: utf-8-validate: optional: true - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs-unparser@2.0.0: - resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} - engines: {node: '>=10'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - yoctocolors@2.1.2: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} @@ -1981,15 +1736,6 @@ snapshots: '@esbuild/win32-x64@0.27.3': optional: true - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2018,9 +1764,6 @@ snapshots: '@noble/hashes@2.0.1': {} - '@pkgjs/parseargs@0.11.0': - optional: true - '@polkadot-api/cli@0.18.1(postcss@8.5.6)(tsx@4.21.0)': dependencies: '@commander-js/extra-typings': 14.0.0(commander@14.0.3) @@ -2441,8 +2184,6 @@ snapshots: '@types/estree@1.0.8': {} - '@types/mocha@10.0.10': {} - '@types/node@24.10.13': dependencies: undici-types: 7.16.0 @@ -2498,32 +2239,14 @@ snapshots: acorn@8.16.0: {} - ansi-regex@5.0.1: {} - ansi-regex@6.2.2: {} - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.3: {} - any-promise@1.3.0: {} - argparse@2.0.1: {} - assertion-error@2.0.1: {} - balanced-match@1.0.2: {} - bn.js@5.2.2: {} - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - - browser-stdout@1.3.1: {} - bundle-require@5.1.0(esbuild@0.25.12): dependencies: esbuild: 0.25.12 @@ -2531,15 +2254,8 @@ snapshots: cac@6.7.14: {} - camelcase@6.3.0: {} - chai@6.2.2: {} - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - chalk@5.6.2: {} chokidar@4.0.3: @@ -2552,18 +2268,6 @@ snapshots: cli-spinners@3.4.0: {} - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - commander@14.0.3: {} commander@4.1.1: {} @@ -2578,26 +2282,14 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - debug@4.4.3(supports-color@8.1.1): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 8.1.1 - - decamelize@4.0.0: {} deepmerge-ts@7.1.5: {} detect-indent@7.0.2: {} - diff@7.0.0: {} - - eastasianwidth@0.2.0: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - es-module-lexer@1.7.0: {} esbuild@0.25.12: @@ -2658,10 +2350,6 @@ snapshots: '@esbuild/win32-ia32': 0.27.3 '@esbuild/win32-x64': 0.27.3 - escalade@3.2.0: {} - - escape-string-regexp@4.0.0: {} - estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -2691,31 +2379,17 @@ snapshots: dependencies: is-unicode-supported: 2.1.0 - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - fix-dts-default-cjs-exports@1.0.1: dependencies: magic-string: 0.30.21 mlly: 1.8.0 rollup: 4.57.1 - flat@5.0.2: {} - - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - fs.promises.exists@1.1.4: {} fsevents@2.3.3: optional: true - get-caller-file@2.0.5: {} - get-east-asian-width@1.4.0: {} get-stream@9.0.1: @@ -2726,19 +2400,7 @@ snapshots: get-tsconfig@4.13.6: dependencies: resolve-pkg-maps: 1.0.0 - - glob@10.5.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.9 - minipass: 7.1.3 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - - has-flag@4.0.0: {} - - he@1.2.0: {} + optional: true hosted-git-info@7.0.2: dependencies: @@ -2754,55 +2416,28 @@ snapshots: index-to-position@1.2.0: {} - is-fullwidth-code-point@3.0.0: {} - is-interactive@2.0.0: {} - is-path-inside@3.0.3: {} - - is-plain-obj@2.1.0: {} - is-plain-obj@4.1.0: {} is-stream@4.0.1: {} - is-unicode-supported@0.1.0: {} - is-unicode-supported@2.1.0: {} isexe@2.0.0: {} - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - joycon@3.1.1: {} js-tokens@4.0.0: {} - js-yaml@4.1.1: - dependencies: - argparse: 2.0.1 - lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} load-tsconfig@0.2.5: {} - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - lodash.sortby@4.7.0: {} - log-symbols@4.1.0: - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - log-symbols@7.0.1: dependencies: is-unicode-supported: 2.1.0 @@ -2818,12 +2453,6 @@ snapshots: mimic-function@5.0.1: {} - minimatch@9.0.9: - dependencies: - brace-expansion: 2.0.2 - - minipass@7.1.3: {} - mlkem@2.5.0: {} mlly@1.8.0: @@ -2833,30 +2462,6 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.3 - mocha@11.7.5: - dependencies: - browser-stdout: 1.3.1 - chokidar: 4.0.3 - debug: 4.4.3(supports-color@8.1.1) - diff: 7.0.0 - escape-string-regexp: 4.0.0 - find-up: 5.0.0 - glob: 10.5.0 - he: 1.2.0 - is-path-inside: 3.0.3 - js-yaml: 4.1.1 - log-symbols: 4.1.0 - minimatch: 9.0.9 - ms: 2.1.3 - picocolors: 1.1.1 - serialize-javascript: 6.0.2 - strip-json-comments: 3.1.1 - supports-color: 8.1.1 - workerpool: 9.3.4 - yargs: 17.7.2 - yargs-parser: 21.1.1 - yargs-unparser: 2.0.0 - ms@2.1.3: {} mz@2.7.0: @@ -2903,16 +2508,6 @@ snapshots: stdin-discarder: 0.3.1 string-width: 8.1.1 - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - package-json-from-dist@1.0.1: {} - parse-json@8.3.0: dependencies: '@babel/code-frame': 7.29.0 @@ -2921,17 +2516,10 @@ snapshots: parse-ms@4.0.0: {} - path-exists@4.0.0: {} - path-key@3.1.1: {} path-key@4.0.0: {} - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.3 - pathe@2.0.3: {} picocolors@1.1.1: {} @@ -3000,10 +2588,6 @@ snapshots: punycode@2.3.1: {} - randombytes@2.1.0: - dependencies: - safe-buffer: 5.2.1 - read-pkg@10.1.0: dependencies: '@types/normalize-package-data': 2.4.4 @@ -3022,11 +2606,10 @@ snapshots: readdirp@4.1.2: {} - require-directory@2.1.1: {} - resolve-from@5.0.0: {} - resolve-pkg-maps@1.0.0: {} + resolve-pkg-maps@1.0.0: + optional: true restore-cursor@5.1.0: dependencies: @@ -3068,16 +2651,10 @@ snapshots: dependencies: tslib: 2.8.1 - safe-buffer@5.2.1: {} - scale-ts@1.6.1: {} semver@7.7.4: {} - serialize-javascript@6.0.2: - dependencies: - randombytes: 2.1.0 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -3125,35 +2702,17 @@ snapshots: stdin-discarder@0.3.1: {} - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - string-width@8.1.1: dependencies: get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - strip-ansi@7.1.2: dependencies: ansi-regex: 6.2.2 strip-final-newline@4.0.0: {} - strip-json-comments@3.1.1: {} - sucrase@3.35.1: dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -3164,14 +2723,6 @@ snapshots: tinyglobby: 0.2.15 ts-interface-checker: 0.1.13 - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 - tagged-tag@1.0.0: {} thenify-all@1.6.0: @@ -3215,7 +2766,7 @@ snapshots: cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 esbuild: 0.25.12 fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 @@ -3243,6 +2794,7 @@ snapshots: get-tsconfig: 4.13.6 optionalDependencies: fsevents: 2.3.3 + optional: true type-fest@4.41.0: {} @@ -3336,20 +2888,6 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - workerpool@9.3.4: {} - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.1.2 - write-file-atomic@5.0.1: dependencies: imurmurhash: 0.1.4 @@ -3372,27 +2910,4 @@ snapshots: ws@8.19.0: {} - y18n@5.0.8: {} - - yargs-parser@21.1.1: {} - - yargs-unparser@2.0.0: - dependencies: - camelcase: 6.3.0 - decamelize: 4.0.0 - flat: 5.0.2 - is-plain-obj: 2.1.0 - - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - - yocto-queue@0.1.0: {} - yoctocolors@2.1.2: {} diff --git a/e2e/staking/package.json b/e2e/staking/package.json index 5320fad9e3..80648d3d0f 100644 --- a/e2e/staking/package.json +++ b/e2e/staking/package.json @@ -4,16 +4,14 @@ "type": "module", "license": "ISC", "scripts": { - "test": "tsx node_modules/mocha/bin/mocha.js --timeout 999999 --retries 1 --file setup.ts --extension ts \"test/**/*.ts\"" + "test": "vitest run" }, "dependencies": { - "e2e-shared": "workspace:*", - "mocha": "^11.1.0", - "tsx": "^4.21.0" + "e2e-shared": "workspace:*" }, "devDependencies": { - "@types/mocha": "^10.0.10", - "@types/node": "catalog:" + "@types/node": "catalog:", + "vitest": "catalog:" }, "prettier": { "singleQuote": false, diff --git a/e2e/staking/setup.ts b/e2e/staking/setup.ts index 71c58ab0ca..51a3434559 100644 --- a/e2e/staking/setup.ts +++ b/e2e/staking/setup.ts @@ -83,10 +83,7 @@ async function stopNetwork() { nodeLog("Network stopped"); } -before(async function () { - // Increase timeout for network startup (2 minutes) - this.timeout(120000); - +export async function setup() { // Start the network await startNetwork(); @@ -98,15 +95,12 @@ before(async function () { // By default, the lock cost doubles with each subnet registration and decays over 14 days (100,800 blocks). // Without this, tests creating multiple subnets would fail with CannotAffordLockCost. await sudoSetLockReductionInterval(api, 1); -}); - -after(async function () { - // Increase timeout for cleanup - this.timeout(30000); +} +export async function teardown() { // Destroy the API client first destroyClient(); // Stop the network await stopNetwork(); -}); +} diff --git a/e2e/staking/test/add-stake-limit.test.ts b/e2e/staking/test/add-stake-limit.test.ts index 991cccc62c..63e1bb6ba0 100644 --- a/e2e/staking/test/add-stake-limit.test.ts +++ b/e2e/staking/test/add-stake-limit.test.ts @@ -1,11 +1,10 @@ -import * as assert from "assert"; +import { describe, it, expect, beforeAll } from "vitest"; import { getDevnetApi, getRandomSubstrateKeypair, convertPublicKeyToSs58, forceSetBalance, addNewSubnetwork, - burnedRegister, startCall, addStakeLimit, getStake, @@ -20,7 +19,7 @@ describe("▶ add_stake_limit extrinsic", () => { const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); let netuid: number; - before(async () => { + beforeAll(async () => { const api = await getDevnetApi(); await forceSetBalance(api, hotkeyAddress); await forceSetBalance(api, coldkeyAddress); @@ -41,7 +40,7 @@ describe("▶ add_stake_limit extrinsic", () => { // Verify stake increased const stakeAfter = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); - assert.ok(stakeAfter > stakeBefore, `Stake should increase: before=${stakeBefore}, after=${stakeAfter}`); + expect(stakeAfter, "Stake should increase").toBeGreaterThan(stakeBefore); log.info("✅ Successfully added stake with limit (allow partial)."); }); @@ -59,7 +58,7 @@ describe("▶ add_stake_limit extrinsic", () => { // Verify stake increased const stakeAfter = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); - assert.ok(stakeAfter > stakeBefore, `Stake should increase: before=${stakeBefore}, after=${stakeAfter}`); + expect(stakeAfter, "Stake should increase").toBeGreaterThan(stakeBefore); log.info("✅ Successfully added stake with limit (fill or kill)."); }); diff --git a/e2e/staking/test/add-stake.test.ts b/e2e/staking/test/add-stake.test.ts index 0a0cfe756c..fd3eecf052 100644 --- a/e2e/staking/test/add-stake.test.ts +++ b/e2e/staking/test/add-stake.test.ts @@ -1,11 +1,10 @@ -import * as assert from "assert"; +import { describe, it, expect, beforeAll } from "vitest"; import { getDevnetApi, getRandomSubstrateKeypair, convertPublicKeyToSs58, forceSetBalance, addNewSubnetwork, - burnedRegister, startCall, addStake, getStake, @@ -20,7 +19,7 @@ describe("▶ add_stake extrinsic", () => { const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); let netuid: number; - before(async () => { + beforeAll(async () => { const api = await getDevnetApi(); await forceSetBalance(api, hotkeyAddress); await forceSetBalance(api, coldkeyAddress); @@ -40,7 +39,7 @@ describe("▶ add_stake extrinsic", () => { // Verify stake increased const stakeAfter = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); - assert.ok(stakeAfter > stakeBefore, `Stake should increase: before=${stakeBefore}, after=${stakeAfter}`); + expect(stakeAfter, "Stake should increase after adding stake").toBeGreaterThan(stakeBefore); log.info("✅ Successfully added stake."); }); diff --git a/e2e/staking/test/claim-root.test.ts b/e2e/staking/test/claim-root.test.ts index cd0d8fec8f..5f48f20b2b 100644 --- a/e2e/staking/test/claim-root.test.ts +++ b/e2e/staking/test/claim-root.test.ts @@ -1,4 +1,4 @@ -import * as assert from "assert"; +import { describe, it, expect } from "vitest"; import { getDevnetApi, getRandomSubstrateKeypair, @@ -15,14 +15,12 @@ import { addStake, getStake, claimRoot, - getTempo, sudoSetTempo, waitForBlocks, getRootClaimable, getRootClaimed, isSubtokenEnabled, sudoSetSubtokenEnabled, - isNetworkAdded, sudoSetAdminFreezeWindow, sudoSetEmaPriceHalvingPeriod, getSubnetTAO, @@ -56,7 +54,7 @@ describe("▶ set_root_claim_type extrinsic", () => { const claimTypeAfter = await getRootClaimType(api, coldkeyAddress); log.info(`Root claim type after: ${claimTypeAfter}`); - assert.strictEqual(claimTypeAfter, "Keep", `Expected claim type to be Keep, got ${claimTypeAfter}`); + expect(claimTypeAfter).toBe("Keep"); log.info("✅ Successfully set root claim type to Keep."); }); @@ -73,7 +71,7 @@ describe("▶ set_root_claim_type extrinsic", () => { await setRootClaimType(api, coldkey, "Keep"); const claimTypeBefore = await getRootClaimType(api, coldkeyAddress); log.info(`Root claim type before: ${claimTypeBefore}`); - assert.strictEqual(claimTypeBefore, "Keep", "Should be Keep before changing to Swap"); + expect(claimTypeBefore).toBe("Keep"); // Set root claim type to Swap await setRootClaimType(api, coldkey, "Swap"); @@ -82,7 +80,7 @@ describe("▶ set_root_claim_type extrinsic", () => { const claimTypeAfter = await getRootClaimType(api, coldkeyAddress); log.info(`Root claim type after: ${claimTypeAfter}`); - assert.strictEqual(claimTypeAfter, "Swap", `Expected claim type to be Swap, got ${claimTypeAfter}`); + expect(claimTypeAfter).toBe("Swap"); log.info("✅ Successfully set root claim type to Swap."); }); @@ -107,9 +105,9 @@ describe("▶ set_root_claim_type extrinsic", () => { const claimTypeAfter = await getRootClaimType(api, coldkeyAddress); log.info(`Root claim type after: ${JSON.stringify(claimTypeAfter)}`); - assert.strictEqual(typeof claimTypeAfter, "object", "Expected claim type to be an object"); - assert.strictEqual((claimTypeAfter as { type: string }).type, "KeepSubnets", "Expected type to be KeepSubnets"); - assert.deepStrictEqual((claimTypeAfter as { subnets: number[] }).subnets, subnetsToKeep, "Expected subnets to match"); + expect(typeof claimTypeAfter).toBe("object"); + expect((claimTypeAfter as { type: string }).type).toBe("KeepSubnets"); + expect((claimTypeAfter as { subnets: number[] }).subnets).toEqual(subnetsToKeep); log.info("✅ Successfully set root claim type to KeepSubnets."); }); @@ -131,7 +129,7 @@ describe("▶ sudo_set_num_root_claims extrinsic", () => { const numClaimsAfter = await getNumRootClaims(api); log.info(`Num root claims after: ${numClaimsAfter}`); - assert.strictEqual(numClaimsAfter, newValue, `Expected num root claims to be ${newValue}, got ${numClaimsAfter}`); + expect(numClaimsAfter).toBe(newValue); log.info("✅ Successfully set num root claims."); }); @@ -168,7 +166,7 @@ describe("▶ sudo_set_root_claim_threshold extrinsic", () => { log.info(`Root claim threshold after: ${thresholdAfter}`); const expectedStoredValue = newThreshold * (1n << 32n); // I96F32 encoding - assert.strictEqual(thresholdAfter, expectedStoredValue, `Expected threshold to be ${expectedStoredValue}, got ${thresholdAfter}`); + expect(thresholdAfter).toBe(expectedStoredValue); log.info("✅ Successfully set root claim threshold."); }); @@ -213,7 +211,7 @@ describe("▶ claim_root extrinsic", () => { await sudoSetSubtokenEnabled(api, ROOT_NETUID, true); const subtokenEnabledAfter = await isSubtokenEnabled(api, ROOT_NETUID); log.info(`ROOT subtoken enabled: ${subtokenEnabledAfter}`); - assert.strictEqual(subtokenEnabledAfter, true, "ROOT subtoken should be enabled"); + expect(subtokenEnabledAfter).toBe(true); } // Create TWO dynamic subnets - needed for root_sell_flag to become true @@ -258,7 +256,7 @@ describe("▶ claim_root extrinsic", () => { // Verify root stake was added const rootStake = await getStake(api, owner1HotkeyAddress, stakerColdkeyAddress, ROOT_NETUID); log.info(`Root stake: ${rootStake}`); - assert.ok(rootStake > 0n, "Should have stake on root subnet"); + expect(rootStake, "Should have stake on root subnet").toBeGreaterThan(0n); // Add stake to both dynamic subnets (owner stake to enable emissions flow) const subnetStakeAmount = tao(50); @@ -274,7 +272,7 @@ describe("▶ claim_root extrinsic", () => { await setRootClaimType(api, stakerColdkey, "Keep"); const claimType = await getRootClaimType(api, stakerColdkeyAddress); log.info(`Root claim type: ${claimType}`); - assert.strictEqual(claimType, "Keep", "Should have Keep claim type"); + expect(claimType).toBe("Keep"); // Wait for blocks to: // 1. Allow moving prices to converge (need sum > 1.0 for root_sell_flag) @@ -330,10 +328,7 @@ describe("▶ claim_root extrinsic", () => { log.info(`RootClaimed value: ${rootClaimed}`); // Verify dividends were claimed - assert.ok( - stakerSubnetStakeAfter > stakerSubnetStakeBefore, - `Stake should increase after claiming root dividends: before=${stakerSubnetStakeBefore}, after=${stakerSubnetStakeAfter}` - ); + expect(stakerSubnetStakeAfter, "Stake should increase after claiming root dividends").toBeGreaterThan(stakerSubnetStakeBefore); log.info(`✅ Root claim successful: stake increased from ${stakerSubnetStakeBefore} to ${stakerSubnetStakeAfter}`); }); @@ -414,7 +409,7 @@ describe("▶ claim_root extrinsic", () => { await setRootClaimType(api, stakerColdkey, "Swap"); const claimType = await getRootClaimType(api, stakerColdkeyAddress); log.info(`Root claim type: ${claimType}`); - assert.strictEqual(claimType, "Swap", "Should have Swap claim type"); + expect(claimType).toBe("Swap"); // Wait for blocks const blocksToWait = 25; @@ -449,10 +444,7 @@ describe("▶ claim_root extrinsic", () => { log.info(`RootClaimed value: ${rootClaimed}`); // With Swap type, ROOT stake should increase (not dynamic subnet stake) - assert.ok( - rootStakeAfter > rootStakeBefore, - `ROOT stake should increase after claiming with Swap type: before=${rootStakeBefore}, after=${rootStakeAfter}` - ); + expect(rootStakeAfter, "ROOT stake should increase after claiming with Swap type").toBeGreaterThan(rootStakeBefore); log.info(`✅ Root claim with Swap successful: ROOT stake increased from ${rootStakeBefore} to ${rootStakeAfter}`); }); diff --git a/e2e/staking/test/move-stake.test.ts b/e2e/staking/test/move-stake.test.ts index efe8e48a58..532ffce168 100644 --- a/e2e/staking/test/move-stake.test.ts +++ b/e2e/staking/test/move-stake.test.ts @@ -1,4 +1,4 @@ -import * as assert from "assert"; +import { describe, it, expect } from "vitest"; import { getDevnetApi, getRandomSubstrateKeypair, @@ -45,7 +45,7 @@ describe("▶ move_stake extrinsic", () => { // Get initial stakes (converted from U64F64 for display) const originStakeBefore = await getStake(api, originHotkeyAddress, coldkeyAddress, netuid1); const destStakeBefore = await getStake(api, destinationHotkeyAddress, coldkeyAddress, netuid2); - assert.ok(originStakeBefore > 0n, "Origin hotkey should have stake before move"); + expect(originStakeBefore, "Origin hotkey should have stake before move").toBeGreaterThan(0n); log.info(`Origin stake (netuid1) before: ${originStakeBefore}, Destination stake (netuid2) before: ${destStakeBefore}`); @@ -53,15 +53,7 @@ describe("▶ move_stake extrinsic", () => { // Use raw U64F64 value for the extrinsic const originStakeRaw = await getStakeRaw(api, originHotkeyAddress, coldkeyAddress, netuid1); const moveAmount = originStakeRaw / 2n; - await moveStake( - api, - coldkey, - originHotkeyAddress, - destinationHotkeyAddress, - netuid1, - netuid2, - moveAmount - ); + await moveStake(api, coldkey, originHotkeyAddress, destinationHotkeyAddress, netuid1, netuid2, moveAmount); // Verify stakes changed const originStakeAfter = await getStake(api, originHotkeyAddress, coldkeyAddress, netuid1); @@ -69,8 +61,8 @@ describe("▶ move_stake extrinsic", () => { log.info(`Origin stake (netuid1) after: ${originStakeAfter}, Destination stake (netuid2) after: ${destStakeAfter}`); - assert.ok(originStakeAfter < originStakeBefore, `Origin stake should decrease: before=${originStakeBefore}, after=${originStakeAfter}`); - assert.ok(destStakeAfter > destStakeBefore, `Destination stake should increase: before=${destStakeBefore}, after=${destStakeAfter}`); + expect(originStakeAfter, "Origin stake should decrease").toBeLessThan(originStakeBefore); + expect(destStakeAfter, "Destination stake should increase").toBeGreaterThan(destStakeBefore); log.info("✅ Successfully moved stake to another hotkey across subnets."); }); @@ -103,7 +95,7 @@ describe("▶ move_stake extrinsic", () => { // Get initial stakes (converted from U64F64 for display) const originStakeBefore = await getStake(api, originHotkeyAddress, coldkeyAddress, netuid); const destStakeBefore = await getStake(api, destinationHotkeyAddress, coldkeyAddress, netuid); - assert.ok(originStakeBefore > 0n, "Origin hotkey should have stake before move"); + expect(originStakeBefore, "Origin hotkey should have stake before move").toBeGreaterThan(0n); log.info(`Origin stake before: ${originStakeBefore}, Destination stake before: ${destStakeBefore}`); @@ -111,15 +103,7 @@ describe("▶ move_stake extrinsic", () => { // Use raw U64F64 value for the extrinsic const originStakeRaw = await getStakeRaw(api, originHotkeyAddress, coldkeyAddress, netuid); const moveAmount = originStakeRaw / 2n; - await moveStake( - api, - coldkey, - originHotkeyAddress, - destinationHotkeyAddress, - netuid, - netuid, - moveAmount - ); + await moveStake(api, coldkey, originHotkeyAddress, destinationHotkeyAddress, netuid, netuid, moveAmount); // Verify stakes changed const originStakeAfter = await getStake(api, originHotkeyAddress, coldkeyAddress, netuid); @@ -127,8 +111,8 @@ describe("▶ move_stake extrinsic", () => { log.info(`Origin stake after: ${originStakeAfter}, Destination stake after: ${destStakeAfter}`); - assert.ok(originStakeAfter < originStakeBefore, `Origin stake should decrease: before=${originStakeBefore}, after=${originStakeAfter}`); - assert.ok(destStakeAfter > destStakeBefore, `Destination stake should increase: before=${destStakeBefore}, after=${destStakeAfter}`); + expect(originStakeAfter, "Origin stake should decrease").toBeLessThan(originStakeBefore); + expect(destStakeAfter, "Destination stake should increase").toBeGreaterThan(destStakeBefore); log.info("✅ Successfully moved stake to another hotkey on the same subnet."); }); diff --git a/e2e/staking/test/remove-stake-full-limit.test.ts b/e2e/staking/test/remove-stake-full-limit.test.ts index 782864c82e..7e8538ed30 100644 --- a/e2e/staking/test/remove-stake-full-limit.test.ts +++ b/e2e/staking/test/remove-stake-full-limit.test.ts @@ -1,4 +1,4 @@ -import * as assert from "assert"; +import { describe, it, expect, beforeAll } from "vitest"; import { getDevnetApi, getRandomSubstrateKeypair, @@ -21,7 +21,7 @@ describe("▶ remove_stake_full_limit extrinsic", () => { const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); let netuid: number; - before(async () => { + beforeAll(async () => { const api = await getDevnetApi(); await forceSetBalance(api, hotkeyAddress); await forceSetBalance(api, coldkeyAddress); @@ -39,7 +39,7 @@ describe("▶ remove_stake_full_limit extrinsic", () => { const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); const balanceBefore = await getBalance(api, coldkeyAddress); log.info(`Stake before: ${stakeBefore}, Balance before: ${balanceBefore}`); - assert.ok(stakeBefore > 0n, "Should have stake before removal"); + expect(stakeBefore, "Should have stake before removal").toBeGreaterThan(0n); // Remove all stake with a reasonable limit price (low limit to avoid slippage rejection) // Using a low limit price (0.09 TAO per alpha) allows the transaction to succeed @@ -51,8 +51,8 @@ describe("▶ remove_stake_full_limit extrinsic", () => { const balanceAfter = await getBalance(api, coldkeyAddress); log.info(`Stake after: ${stakeAfter}, Balance after: ${balanceAfter}`); - assert.strictEqual(stakeAfter, 0n, `Stake should be zero after full removal, got ${stakeAfter}`); - assert.ok(balanceAfter > balanceBefore, `Balance should increase: before=${balanceBefore}, after=${balanceAfter}`); + expect(stakeAfter, "Stake should be zero after full removal").toBe(0n); + expect(balanceAfter, "Balance should increase after unstaking").toBeGreaterThan(balanceBefore); log.info("✅ Successfully removed all stake with price limit."); }); @@ -67,7 +67,7 @@ describe("▶ remove_stake_full_limit extrinsic", () => { const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); const balanceBefore = await getBalance(api, coldkeyAddress); log.info(`Stake before: ${stakeBefore}, Balance before: ${balanceBefore}`); - assert.ok(stakeBefore > 0n, "Should have stake before removal"); + expect(stakeBefore, "Should have stake before removal").toBeGreaterThan(0n); // Remove all stake without limit price (undefined = no slippage protection) await removeStakeFullLimit(api, coldkey, hotkeyAddress, netuid, undefined); @@ -77,8 +77,8 @@ describe("▶ remove_stake_full_limit extrinsic", () => { const balanceAfter = await getBalance(api, coldkeyAddress); log.info(`Stake after: ${stakeAfter}, Balance after: ${balanceAfter}`); - assert.strictEqual(stakeAfter, 0n, `Stake should be zero after full removal, got ${stakeAfter}`); - assert.ok(balanceAfter > balanceBefore, `Balance should increase: before=${balanceBefore}, after=${balanceAfter}`); + expect(stakeAfter, "Stake should be zero after full removal").toBe(0n); + expect(balanceAfter, "Balance should increase after unstaking").toBeGreaterThan(balanceBefore); log.info("✅ Successfully removed all stake without price limit."); }); diff --git a/e2e/staking/test/remove-stake-limit.test.ts b/e2e/staking/test/remove-stake-limit.test.ts index addc8c717d..9578fb8e3f 100644 --- a/e2e/staking/test/remove-stake-limit.test.ts +++ b/e2e/staking/test/remove-stake-limit.test.ts @@ -1,4 +1,4 @@ -import * as assert from "assert"; +import { describe, it, expect, beforeAll } from "vitest"; import { getDevnetApi, getRandomSubstrateKeypair, @@ -6,7 +6,6 @@ import { forceSetBalance, getBalance, addNewSubnetwork, - burnedRegister, startCall, addStake, removeStakeLimit, @@ -22,7 +21,7 @@ describe("▶ remove_stake_limit extrinsic", () => { const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); let netuid: number; - before(async () => { + beforeAll(async () => { const api = await getDevnetApi(); await forceSetBalance(api, hotkeyAddress); await forceSetBalance(api, coldkeyAddress); @@ -40,7 +39,7 @@ describe("▶ remove_stake_limit extrinsic", () => { const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); const balanceBefore = await getBalance(api, coldkeyAddress); log.info(`Stake before: ${stakeBefore}, Balance before: ${balanceBefore}`); - assert.ok(stakeBefore > 0n, "Should have stake before removal"); + expect(stakeBefore, "Should have stake before removal").toBeGreaterThan(0n); // Remove stake with limit price and allow partial fills const unstakeAmount = tao(30); @@ -49,7 +48,7 @@ describe("▶ remove_stake_limit extrinsic", () => { // Verify balance increased (received TAO from unstaking) const balanceAfter = await getBalance(api, coldkeyAddress); - assert.ok(balanceAfter > balanceBefore, `Balance should increase: before=${balanceBefore}, after=${balanceAfter}`); + expect(balanceAfter, "Balance should increase after unstaking").toBeGreaterThan(balanceBefore); log.info("✅ Successfully removed stake with limit (allow partial)."); }); @@ -64,7 +63,7 @@ describe("▶ remove_stake_limit extrinsic", () => { const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); const balanceBefore = await getBalance(api, coldkeyAddress); log.info(`Stake before: ${stakeBefore}, Balance before: ${balanceBefore}`); - assert.ok(stakeBefore > 0n, "Should have stake before removal"); + expect(stakeBefore, "Should have stake before removal").toBeGreaterThan(0n); // Remove stake with limit price (fill or kill mode) const unstakeAmount = tao(30); @@ -73,7 +72,7 @@ describe("▶ remove_stake_limit extrinsic", () => { // Verify balance increased (received TAO from unstaking) const balanceAfter = await getBalance(api, coldkeyAddress); - assert.ok(balanceAfter > balanceBefore, `Balance should increase: before=${balanceBefore}, after=${balanceAfter}`); + expect(balanceAfter, "Balance should increase after unstaking").toBeGreaterThan(balanceBefore); log.info("✅ Successfully removed stake with limit (fill or kill)."); }); diff --git a/e2e/staking/test/remove-stake.test.ts b/e2e/staking/test/remove-stake.test.ts index a863c3042c..db9f5aa150 100644 --- a/e2e/staking/test/remove-stake.test.ts +++ b/e2e/staking/test/remove-stake.test.ts @@ -1,4 +1,4 @@ -import * as assert from "assert"; +import { describe, it, expect, beforeAll } from "vitest"; import { getDevnetApi, getRandomSubstrateKeypair, @@ -6,7 +6,6 @@ import { forceSetBalance, getBalance, addNewSubnetwork, - burnedRegister, startCall, addStake, removeStake, @@ -23,7 +22,7 @@ describe("▶ remove_stake extrinsic", () => { const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); let netuid: number; - before(async () => { + beforeAll(async () => { const api = await getDevnetApi(); await forceSetBalance(api, hotkeyAddress); await forceSetBalance(api, coldkeyAddress); @@ -40,7 +39,7 @@ describe("▶ remove_stake extrinsic", () => { // Get initial stake and balance (converted from U64F64 for display) const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); const balanceBefore = await getBalance(api, coldkeyAddress); - assert.ok(stakeBefore > 0n, "Should have stake before removal"); + expect(stakeBefore, "Should have stake before removal").toBeGreaterThan(0n); // Remove stake (amount is in alpha units - use raw U64F64 value) const stakeRaw = await getStakeRaw(api, hotkeyAddress, coldkeyAddress, netuid); @@ -49,7 +48,7 @@ describe("▶ remove_stake extrinsic", () => { // Verify balance increased (received TAO from unstaking) const balanceAfter = await getBalance(api, coldkeyAddress); - assert.ok(balanceAfter > balanceBefore, `Balance should increase: before=${balanceBefore}, after=${balanceAfter}`); + expect(balanceAfter, "Balance should increase after unstaking").toBeGreaterThan(balanceBefore); log.info("✅ Successfully removed stake."); }); diff --git a/e2e/staking/test/swap-stake-limit.test.ts b/e2e/staking/test/swap-stake-limit.test.ts index d52b1e411e..316ddff051 100644 --- a/e2e/staking/test/swap-stake-limit.test.ts +++ b/e2e/staking/test/swap-stake-limit.test.ts @@ -1,4 +1,4 @@ -import * as assert from "assert"; +import { describe, it, expect } from "vitest"; import { getDevnetApi, getRandomSubstrateKeypair, @@ -48,7 +48,7 @@ describe("▶ swap_stake_limit extrinsic", () => { // Get initial stakes (converted from U64F64 for display) const stake1Before = await getStake(api, hotkey1Address, coldkeyAddress, netuid1); const stake2Before = await getStake(api, hotkey1Address, coldkeyAddress, netuid2); - assert.ok(stake1Before > 0n, "Should have stake on subnet1 before swap"); + expect(stake1Before, "Should have stake on subnet1 before swap").toBeGreaterThan(0n); log.info(`Stake on netuid1 before: ${stake1Before}, Stake on netuid2 before: ${stake2Before}`); @@ -56,7 +56,7 @@ describe("▶ swap_stake_limit extrinsic", () => { // Use raw U64F64 value for the extrinsic const stake1Raw = await getStakeRaw(api, hotkey1Address, coldkeyAddress, netuid1); const swapAmount = stake1Raw / 2n; - const limitPrice = tao(1) * 99n / 100n; // 0.99 TAO + const limitPrice = (tao(1) * 99n) / 100n; // 0.99 TAO await swapStakeLimit(api, coldkey, hotkey1Address, netuid1, netuid2, swapAmount, limitPrice, true); // Verify stakes changed @@ -65,8 +65,8 @@ describe("▶ swap_stake_limit extrinsic", () => { log.info(`Stake on netuid1 after: ${stake1After}, Stake on netuid2 after: ${stake2After}`); - assert.ok(stake1After < stake1Before, `Stake on subnet1 should decrease: before=${stake1Before}, after=${stake1After}`); - assert.ok(stake2After > stake2Before, `Stake on subnet2 should increase: before=${stake2Before}, after=${stake2After}`); + expect(stake1After, "Stake on subnet1 should decrease").toBeLessThan(stake1Before); + expect(stake2After, "Stake on subnet2 should increase").toBeGreaterThan(stake2Before); log.info("✅ Successfully swapped stake with price limit (allow partial)."); }); @@ -103,7 +103,7 @@ describe("▶ swap_stake_limit extrinsic", () => { // Get initial stakes (converted from U64F64 for display) const stake1Before = await getStake(api, hotkey1Address, coldkeyAddress, netuid1); const stake2Before = await getStake(api, hotkey1Address, coldkeyAddress, netuid2); - assert.ok(stake1Before > 0n, "Should have stake on subnet1 before swap"); + expect(stake1Before, "Should have stake on subnet1 before swap").toBeGreaterThan(0n); log.info(`Stake on netuid1 before: ${stake1Before}, Stake on netuid2 before: ${stake2Before}`); @@ -120,8 +120,8 @@ describe("▶ swap_stake_limit extrinsic", () => { log.info(`Stake on netuid1 after: ${stake1After}, Stake on netuid2 after: ${stake2After}`); - assert.ok(stake1After < stake1Before, `Stake on subnet1 should decrease: before=${stake1Before}, after=${stake1After}`); - assert.ok(stake2After > stake2Before, `Stake on subnet2 should increase: before=${stake2Before}, after=${stake2After}`); + expect(stake1After, "Stake on subnet1 should decrease").toBeLessThan(stake1Before); + expect(stake2After, "Stake on subnet2 should increase").toBeGreaterThan(stake2Before); log.info("✅ Successfully swapped stake with price limit (fill or kill)."); }); diff --git a/e2e/staking/test/swap-stake.test.ts b/e2e/staking/test/swap-stake.test.ts index 5d6b9329cc..44a818dd81 100644 --- a/e2e/staking/test/swap-stake.test.ts +++ b/e2e/staking/test/swap-stake.test.ts @@ -1,4 +1,4 @@ -import * as assert from "assert"; +import { describe, it, expect } from "vitest"; import { getDevnetApi, getRandomSubstrateKeypair, @@ -16,7 +16,7 @@ import { } from "e2e-shared"; describe("▶ swap_stake extrinsic", () => { - it("should swap full stake from one subnet to another", async () => { + it("should swap stake from one subnet to another", async () => { const api = await getDevnetApi(); // Setup accounts @@ -45,16 +45,18 @@ describe("▶ swap_stake extrinsic", () => { // Add stake to hotkey1 on subnet1 await addStake(api, coldkey, hotkey1Address, netuid1, tao(100)); - // Get initial stake (converted from U64F64 for display) + // Get initial stakes const stake1Before = await getStake(api, hotkey1Address, coldkeyAddress, netuid1); - assert.ok(stake1Before > 0n, "Should have stake on subnet1 before swap"); + const stake2Before = await getStake(api, hotkey1Address, coldkeyAddress, netuid2); + expect(stake1Before, "Should have stake on subnet1 before swap").toBeGreaterThan(0n); - log.info(`Stake on netuid1 before: ${stake1Before}`); + log.info(`Stake on netuid1 before: ${stake1Before}, Stake on netuid2 before: ${stake2Before}`); - // Swap full stake from subnet1 to subnet2 + // Swap half the stake from subnet1 to subnet2 // Use raw U64F64 value for the extrinsic const stake1Raw = await getStakeRaw(api, hotkey1Address, coldkeyAddress, netuid1); - await swapStake(api, coldkey, hotkey1Address, netuid1, netuid2, stake1Raw); + const swapAmount = stake1Raw / 2n; + await swapStake(api, coldkey, hotkey1Address, netuid1, netuid2, swapAmount); // Verify stakes changed const stake1After = await getStake(api, hotkey1Address, coldkeyAddress, netuid1); @@ -62,9 +64,10 @@ describe("▶ swap_stake extrinsic", () => { log.info(`Stake on netuid1 after: ${stake1After}, Stake on netuid2 after: ${stake2After}`); - assert.strictEqual(stake1After, 0n, `Stake on subnet1 should be zero after full swap, got ${stake1After}`); - assert.ok(stake2After > 0n, `Stake on subnet2 should be non-zero after swap`); + // Note: hotkey1 is the owner of netuid1, so minimum owner stake may be retained + expect(stake1After, "Stake on subnet1 should decrease after swap").toBeLessThan(stake1Before); + expect(stake2After, "Stake on subnet2 should increase after swap").toBeGreaterThan(stake2Before); - log.info("✅ Successfully swapped full stake from one subnet to another."); + log.info("✅ Successfully swapped stake from one subnet to another."); }); }); diff --git a/e2e/staking/test/transfer-stake.test.ts b/e2e/staking/test/transfer-stake.test.ts index 3a34a76ea5..8cac7a5413 100644 --- a/e2e/staking/test/transfer-stake.test.ts +++ b/e2e/staking/test/transfer-stake.test.ts @@ -1,4 +1,4 @@ -import * as assert from "assert"; +import { describe, it, expect } from "vitest"; import { getDevnetApi, getRandomSubstrateKeypair, @@ -47,7 +47,7 @@ describe("▶ transfer_stake extrinsic", () => { // Get initial stakes (converted from U64F64 for display) const originStakeBefore = await getStake(api, hotkey1Address, originColdkeyAddress, netuid1); const destStakeBefore = await getStake(api, hotkey1Address, destinationColdkeyAddress, netuid2); - assert.ok(originStakeBefore > 0n, "Origin should have stake before transfer"); + expect(originStakeBefore, "Origin should have stake before transfer").toBeGreaterThan(0n); log.info(`Origin stake (netuid1) before: ${originStakeBefore}, Destination stake (netuid2) before: ${destStakeBefore}`); @@ -55,15 +55,7 @@ describe("▶ transfer_stake extrinsic", () => { // Use raw U64F64 value for the extrinsic const originStakeRaw = await getStakeRaw(api, hotkey1Address, originColdkeyAddress, netuid1); const transferAmount = originStakeRaw / 2n; - await transferStake( - api, - originColdkey, - destinationColdkeyAddress, - hotkey1Address, - netuid1, - netuid2, - transferAmount - ); + await transferStake(api, originColdkey, destinationColdkeyAddress, hotkey1Address, netuid1, netuid2, transferAmount); // Verify stakes changed const originStakeAfter = await getStake(api, hotkey1Address, originColdkeyAddress, netuid1); @@ -71,8 +63,8 @@ describe("▶ transfer_stake extrinsic", () => { log.info(`Origin stake (netuid1) after: ${originStakeAfter}, Destination stake (netuid2) after: ${destStakeAfter}`); - assert.ok(originStakeAfter < originStakeBefore, `Origin stake should decrease: before=${originStakeBefore}, after=${originStakeAfter}`); - assert.ok(destStakeAfter > destStakeBefore, `Destination stake should increase: before=${destStakeBefore}, after=${destStakeAfter}`); + expect(originStakeAfter, "Origin stake should decrease").toBeLessThan(originStakeBefore); + expect(destStakeAfter, "Destination stake should increase").toBeGreaterThan(destStakeBefore); log.info("✅ Successfully transferred stake to another coldkey across subnets."); }); @@ -102,21 +94,15 @@ describe("▶ transfer_stake extrinsic", () => { // Get initial stake (converted from U64F64 for display) const originStakeBefore = await getStake(api, hotkeyAddress, originColdkeyAddress, netuid); - assert.ok(originStakeBefore > 0n, "Origin should have stake before transfer"); + expect(originStakeBefore, "Origin should have stake before transfer").toBeGreaterThan(0n); log.info(`Origin stake before: ${originStakeBefore}`); + // Transfer stake to destination coldkey + // Use raw U64F64 value for the extrinsic, transfer half to avoid AmountTooLow error const originStakeRaw = await getStakeRaw(api, hotkeyAddress, originColdkeyAddress, netuid); const transferAmount = originStakeRaw / 2n; - await transferStake( - api, - originColdkey, - destinationColdkeyAddress, - hotkeyAddress, - netuid, - netuid, - transferAmount - ); + await transferStake(api, originColdkey, destinationColdkeyAddress, hotkeyAddress, netuid, netuid, transferAmount); // Verify destination received stake const originStakeAfter = await getStake(api, hotkeyAddress, originColdkeyAddress, netuid); @@ -124,8 +110,8 @@ describe("▶ transfer_stake extrinsic", () => { log.info(`Origin stake after: ${originStakeAfter}, Destination stake after: ${destStakeAfter}`); - assert.ok(originStakeAfter < originStakeBefore, `Origin stake should decrease after transfer`); - assert.ok(destStakeAfter > 0n, `Destination stake should be non-zero after transfer`); + expect(originStakeAfter, "Origin stake should decrease after transfer").toBeLessThan(originStakeBefore); + expect(destStakeAfter, "Destination stake should be non-zero after transfer").toBeGreaterThan(0n); log.info("✅ Successfully transferred stake to another coldkey."); }); diff --git a/e2e/staking/test/unstake-all-alpha.test.ts b/e2e/staking/test/unstake-all-alpha.test.ts index 93146aa196..7392a5f65e 100644 --- a/e2e/staking/test/unstake-all-alpha.test.ts +++ b/e2e/staking/test/unstake-all-alpha.test.ts @@ -1,4 +1,4 @@ -import * as assert from "assert"; +import { describe, it, expect } from "vitest"; import { getDevnetApi, getRandomSubstrateKeypair, @@ -56,8 +56,8 @@ describe("▶ unstake_all_alpha extrinsic", () => { const stake1Before = await getStake(api, stakerAddress, coldkeyAddress, netuid1); const stake2Before = await getStake(api, stakerAddress, coldkeyAddress, netuid2); - assert.ok(stake1Before > 0n, "Should have stake in subnet 1 before unstake_all_alpha"); - assert.ok(stake2Before > 0n, "Should have stake in subnet 2 before unstake_all_alpha"); + expect(stake1Before, "Should have stake in subnet 1 before unstake_all_alpha").toBeGreaterThan(0n); + expect(stake2Before, "Should have stake in subnet 2 before unstake_all_alpha").toBeGreaterThan(0n); log.info(`Stake1 before: ${stake1Before}, Stake2 before: ${stake2Before}`); // Unstake all alpha - this removes stake from dynamic subnets and restakes to root @@ -70,8 +70,8 @@ describe("▶ unstake_all_alpha extrinsic", () => { log.info(`Stake1 after: ${stake1After}, Stake2 after: ${stake2After}`); // Since stakerHotkey is not the owner of either subnet, all stake should be removed - assert.strictEqual(stake1After, 0n, `Stake1 should be zero after unstake_all_alpha, got ${stake1After}`); - assert.strictEqual(stake2After, 0n, `Stake2 should be zero after unstake_all_alpha, got ${stake2After}`); + expect(stake1After, "Stake1 should be zero after unstake_all_alpha").toBe(0n); + expect(stake2After, "Stake2 should be zero after unstake_all_alpha").toBe(0n); log.info("✅ Successfully unstaked all alpha from multiple subnets to root."); }); diff --git a/e2e/staking/test/unstake-all.test.ts b/e2e/staking/test/unstake-all.test.ts index 599dc32bc0..d0359acc1b 100644 --- a/e2e/staking/test/unstake-all.test.ts +++ b/e2e/staking/test/unstake-all.test.ts @@ -1,4 +1,4 @@ -import * as assert from "assert"; +import { describe, it, expect } from "vitest"; import { getDevnetApi, getRandomSubstrateKeypair, @@ -6,6 +6,7 @@ import { forceSetBalance, getBalance, addNewSubnetwork, + burnedRegister, startCall, addStake, unstakeAll, @@ -19,51 +20,62 @@ describe("▶ unstake_all extrinsic", () => { const api = await getDevnetApi(); // Setup accounts - const hotkey = getRandomSubstrateKeypair(); + // - owner1Hotkey/coldkey: owns subnet 1 + // - owner2Hotkey/coldkey: owns subnet 2 + // - stakerHotkey: staker (not owner) on both subnets - used for testing unstake_all + const owner1Hotkey = getRandomSubstrateKeypair(); + const owner2Hotkey = getRandomSubstrateKeypair(); + const stakerHotkey = getRandomSubstrateKeypair(); const coldkey = getRandomSubstrateKeypair(); - const hotkeyAddress = convertPublicKeyToSs58(hotkey.publicKey); + const owner1Address = convertPublicKeyToSs58(owner1Hotkey.publicKey); + const owner2Address = convertPublicKeyToSs58(owner2Hotkey.publicKey); + const stakerAddress = convertPublicKeyToSs58(stakerHotkey.publicKey); const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); - const hotkey2 = getRandomSubstrateKeypair(); - const hotkeyAddress2 = convertPublicKeyToSs58(hotkey2.publicKey); - await forceSetBalance(api, hotkeyAddress); + await forceSetBalance(api, owner1Address); + await forceSetBalance(api, owner2Address); + await forceSetBalance(api, stakerAddress); await forceSetBalance(api, coldkeyAddress); - await forceSetBalance(api, hotkeyAddress2); - // Create first subnet - const netuid1 = await addNewSubnetwork(api, hotkey, coldkey); + // Create first subnet with owner1 + const netuid1 = await addNewSubnetwork(api, owner1Hotkey, coldkey); await startCall(api, netuid1, coldkey); - // Create second subnet - const netuid2 = await addNewSubnetwork(api, hotkey2, coldkey); + // Create second subnet with owner2 + const netuid2 = await addNewSubnetwork(api, owner2Hotkey, coldkey); await startCall(api, netuid2, coldkey); - // Add stake to both subnets - await addStake(api, coldkey, hotkeyAddress, netuid1, tao(100)); - await addStake(api, coldkey, hotkeyAddress, netuid2, tao(50)); + // Register stakerHotkey on both subnets (it's not the owner) + await burnedRegister(api, netuid1, stakerAddress, coldkey); + await burnedRegister(api, netuid2, stakerAddress, coldkey); + + // Add stake to both subnets using stakerHotkey (not the owner) + await addStake(api, coldkey, stakerAddress, netuid1, tao(100)); + await addStake(api, coldkey, stakerAddress, netuid2, tao(50)); // Verify stake was added to both subnets - const stake1Before = await getStake(api, hotkeyAddress, coldkeyAddress, netuid1); - const stake2Before = await getStake(api, hotkeyAddress, coldkeyAddress, netuid2); + const stake1Before = await getStake(api, stakerAddress, coldkeyAddress, netuid1); + const stake2Before = await getStake(api, stakerAddress, coldkeyAddress, netuid2); const balanceBefore = await getBalance(api, coldkeyAddress); - assert.ok(stake1Before > 0n, "Should have stake in subnet 1 before unstake_all"); - assert.ok(stake2Before > 0n, "Should have stake in subnet 2 before unstake_all"); + expect(stake1Before, "Should have stake in subnet 1 before unstake_all").toBeGreaterThan(0n); + expect(stake2Before, "Should have stake in subnet 2 before unstake_all").toBeGreaterThan(0n); log.info(`Stake1 before: ${stake1Before}, Stake2 before: ${stake2Before}, Balance before: ${balanceBefore}`); // Unstake all - await unstakeAll(api, coldkey, hotkeyAddress); + await unstakeAll(api, coldkey, stakerAddress); // Verify stakes are removed from both subnets and balance increased - const stake1After = await getStake(api, hotkeyAddress, coldkeyAddress, netuid1); - const stake2After = await getStake(api, hotkeyAddress, coldkeyAddress, netuid2); + const stake1After = await getStake(api, stakerAddress, coldkeyAddress, netuid1); + const stake2After = await getStake(api, stakerAddress, coldkeyAddress, netuid2); const balanceAfter = await getBalance(api, coldkeyAddress); log.info(`Stake1 after: ${stake1After}, Stake2 after: ${stake2After}, Balance after: ${balanceAfter}`); - assert.strictEqual(stake1After, 0n, `Stake1 should be zero after unstake_all, got ${stake1After}`); - assert.strictEqual(stake2After, 0n, `Stake2 should be zero after unstake_all, got ${stake2After}`); - assert.ok(balanceAfter > balanceBefore, `Balance should increase: before=${balanceBefore}, after=${balanceAfter}`); + // Since stakerHotkey is not the owner of either subnet, all stake should be removed + expect(stake1After, "Stake1 should be zero after unstake_all").toBe(0n); + expect(stake2After, "Stake2 should be zero after unstake_all").toBe(0n); + expect(balanceAfter, "Balance should increase after unstaking").toBeGreaterThan(balanceBefore); log.info("✅ Successfully unstaked all from multiple subnets."); }); diff --git a/e2e/staking/tsconfig.json b/e2e/staking/tsconfig.json index a414aaf914..c2f86d9e2c 100644 --- a/e2e/staking/tsconfig.json +++ b/e2e/staking/tsconfig.json @@ -6,6 +6,6 @@ "esModuleInterop": true, "strict": true, "skipLibCheck": true, - "types": ["node", "mocha"] + "types": ["node", "vitest/globals"] } } diff --git a/e2e/staking/vitest.config.ts b/e2e/staking/vitest.config.ts new file mode 100644 index 0000000000..c33905bdbe --- /dev/null +++ b/e2e/staking/vitest.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from "vitest/config"; +import { BaseSequencer, type TestSpecification } from "vitest/node"; + +class AlphabeticalSequencer extends BaseSequencer { + async sort(files: TestSpecification[]): Promise { + return files.sort((a, b) => a.moduleId.localeCompare(b.moduleId)); + } +} + +export default defineConfig({ + test: { + globals: true, + testTimeout: 120_000, + hookTimeout: 300_000, + fileParallelism: false, + globalSetup: "./setup.ts", + include: ["test/**/*.test.ts"], + sequence: { + sequencer: AlphabeticalSequencer, + }, + }, +}); From 29a4a0ffac696812a7240201593db75fc1343eb2 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Thu, 5 Mar 2026 18:29:08 +0300 Subject: [PATCH 6/9] Fix remove_stake_full_limit test --- .../test/remove-stake-full-limit.test.ts | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/e2e/staking/test/remove-stake-full-limit.test.ts b/e2e/staking/test/remove-stake-full-limit.test.ts index 7e8538ed30..2efec03ca9 100644 --- a/e2e/staking/test/remove-stake-full-limit.test.ts +++ b/e2e/staking/test/remove-stake-full-limit.test.ts @@ -6,6 +6,7 @@ import { forceSetBalance, getBalance, addNewSubnetwork, + burnedRegister, startCall, addStake, removeStakeFullLimit, @@ -15,28 +16,34 @@ import { } from "e2e-shared"; describe("▶ remove_stake_full_limit extrinsic", () => { - const hotkey = getRandomSubstrateKeypair(); + // Separate owner and staker hotkeys to avoid minimum owner stake retention + const ownerHotkey = getRandomSubstrateKeypair(); + const stakerHotkey = getRandomSubstrateKeypair(); const coldkey = getRandomSubstrateKeypair(); - const hotkeyAddress = convertPublicKeyToSs58(hotkey.publicKey); + const ownerAddress = convertPublicKeyToSs58(ownerHotkey.publicKey); + const stakerAddress = convertPublicKeyToSs58(stakerHotkey.publicKey); const coldkeyAddress = convertPublicKeyToSs58(coldkey.publicKey); let netuid: number; beforeAll(async () => { const api = await getDevnetApi(); - await forceSetBalance(api, hotkeyAddress); + await forceSetBalance(api, ownerAddress); + await forceSetBalance(api, stakerAddress); await forceSetBalance(api, coldkeyAddress); - netuid = await addNewSubnetwork(api, hotkey, coldkey); + netuid = await addNewSubnetwork(api, ownerHotkey, coldkey); await startCall(api, netuid, coldkey); + // Register staker hotkey (not the owner) + await burnedRegister(api, netuid, stakerAddress, coldkey); }); it("should remove all stake with price limit", async () => { const api = await getDevnetApi(); // Add stake first - await addStake(api, coldkey, hotkeyAddress, netuid, tao(100)); + await addStake(api, coldkey, stakerAddress, netuid, tao(100)); // Get initial stake and balance - const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + const stakeBefore = await getStake(api, stakerAddress, coldkeyAddress, netuid); const balanceBefore = await getBalance(api, coldkeyAddress); log.info(`Stake before: ${stakeBefore}, Balance before: ${balanceBefore}`); expect(stakeBefore, "Should have stake before removal").toBeGreaterThan(0n); @@ -44,10 +51,10 @@ describe("▶ remove_stake_full_limit extrinsic", () => { // Remove all stake with a reasonable limit price (low limit to avoid slippage rejection) // Using a low limit price (0.09 TAO per alpha) allows the transaction to succeed const limitPrice = tao(1) / 10n; // 0.1 TAO - await removeStakeFullLimit(api, coldkey, hotkeyAddress, netuid, limitPrice); + await removeStakeFullLimit(api, coldkey, stakerAddress, netuid, limitPrice); - // Verify stake is zero - const stakeAfter = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + // Verify stake is zero (staker is not owner, so all stake can be removed) + const stakeAfter = await getStake(api, stakerAddress, coldkeyAddress, netuid); const balanceAfter = await getBalance(api, coldkeyAddress); log.info(`Stake after: ${stakeAfter}, Balance after: ${balanceAfter}`); @@ -61,19 +68,19 @@ describe("▶ remove_stake_full_limit extrinsic", () => { const api = await getDevnetApi(); // Add stake first - await addStake(api, coldkey, hotkeyAddress, netuid, tao(100)); + await addStake(api, coldkey, stakerAddress, netuid, tao(100)); // Get initial stake and balance - const stakeBefore = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + const stakeBefore = await getStake(api, stakerAddress, coldkeyAddress, netuid); const balanceBefore = await getBalance(api, coldkeyAddress); log.info(`Stake before: ${stakeBefore}, Balance before: ${balanceBefore}`); expect(stakeBefore, "Should have stake before removal").toBeGreaterThan(0n); // Remove all stake without limit price (undefined = no slippage protection) - await removeStakeFullLimit(api, coldkey, hotkeyAddress, netuid, undefined); + await removeStakeFullLimit(api, coldkey, stakerAddress, netuid, undefined); - // Verify stake is zero - const stakeAfter = await getStake(api, hotkeyAddress, coldkeyAddress, netuid); + // Verify stake is zero (staker is not owner, so all stake can be removed) + const stakeAfter = await getStake(api, stakerAddress, coldkeyAddress, netuid); const balanceAfter = await getBalance(api, coldkeyAddress); log.info(`Stake after: ${stakeAfter}, Balance after: ${balanceAfter}`); From 71f983c97c0849397ca65caef2ceb6fe5587109e Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Thu, 5 Mar 2026 19:24:41 +0300 Subject: [PATCH 7/9] Fix unstake-all-alpha test. --- e2e/staking/test/unstake-all-alpha.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/e2e/staking/test/unstake-all-alpha.test.ts b/e2e/staking/test/unstake-all-alpha.test.ts index 7392a5f65e..ea8acda171 100644 --- a/e2e/staking/test/unstake-all-alpha.test.ts +++ b/e2e/staking/test/unstake-all-alpha.test.ts @@ -70,8 +70,11 @@ describe("▶ unstake_all_alpha extrinsic", () => { log.info(`Stake1 after: ${stake1After}, Stake2 after: ${stake2After}`); // Since stakerHotkey is not the owner of either subnet, all stake should be removed - expect(stake1After, "Stake1 should be zero after unstake_all_alpha").toBe(0n); - expect(stake2After, "Stake2 should be zero after unstake_all_alpha").toBe(0n); + // Allow small epsilon for emissions that may accumulate during test execution + // Observed residual: ~0.01 TAO from emissions during test + const epsilon = tao(1) / 10n; // 0.1 TAO tolerance + expect(stake1After, "Stake1 should be approximately zero after unstake_all_alpha").toBeLessThan(epsilon); + expect(stake2After, "Stake2 should be approximately zero after unstake_all_alpha").toBeLessThan(epsilon); log.info("✅ Successfully unstaked all alpha from multiple subnets to root."); }); From 5391b4da58d8b7c5c26388c79bfe227ce0b14ad1 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Thu, 5 Mar 2026 19:29:01 +0300 Subject: [PATCH 8/9] Change tempo for some tests --- e2e/staking/test/remove-stake-full-limit.test.ts | 3 +++ e2e/staking/test/unstake-all-alpha.test.ts | 13 ++++++++----- e2e/staking/test/unstake-all.test.ts | 5 +++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/e2e/staking/test/remove-stake-full-limit.test.ts b/e2e/staking/test/remove-stake-full-limit.test.ts index 2efec03ca9..47af798512 100644 --- a/e2e/staking/test/remove-stake-full-limit.test.ts +++ b/e2e/staking/test/remove-stake-full-limit.test.ts @@ -11,6 +11,7 @@ import { addStake, removeStakeFullLimit, getStake, + sudoSetTempo, tao, log, } from "e2e-shared"; @@ -32,6 +33,8 @@ describe("▶ remove_stake_full_limit extrinsic", () => { await forceSetBalance(api, coldkeyAddress); netuid = await addNewSubnetwork(api, ownerHotkey, coldkey); await startCall(api, netuid, coldkey); + // Set high tempo to prevent emissions during test + await sudoSetTempo(api, netuid, 10000); // Register staker hotkey (not the owner) await burnedRegister(api, netuid, stakerAddress, coldkey); }); diff --git a/e2e/staking/test/unstake-all-alpha.test.ts b/e2e/staking/test/unstake-all-alpha.test.ts index ea8acda171..dd71a27192 100644 --- a/e2e/staking/test/unstake-all-alpha.test.ts +++ b/e2e/staking/test/unstake-all-alpha.test.ts @@ -10,6 +10,7 @@ import { addStake, unstakeAllAlpha, getStake, + sudoSetTempo, tao, log, } from "e2e-shared"; @@ -44,6 +45,10 @@ describe("▶ unstake_all_alpha extrinsic", () => { const netuid2 = await addNewSubnetwork(api, owner2Hotkey, coldkey); await startCall(api, netuid2, coldkey); + // Set very high tempo to prevent emissions during test + await sudoSetTempo(api, netuid1, 10000); + await sudoSetTempo(api, netuid2, 10000); + // Register stakerHotkey on both subnets (it's not the owner) await burnedRegister(api, netuid1, stakerAddress, coldkey); await burnedRegister(api, netuid2, stakerAddress, coldkey); @@ -70,11 +75,9 @@ describe("▶ unstake_all_alpha extrinsic", () => { log.info(`Stake1 after: ${stake1After}, Stake2 after: ${stake2After}`); // Since stakerHotkey is not the owner of either subnet, all stake should be removed - // Allow small epsilon for emissions that may accumulate during test execution - // Observed residual: ~0.01 TAO from emissions during test - const epsilon = tao(1) / 10n; // 0.1 TAO tolerance - expect(stake1After, "Stake1 should be approximately zero after unstake_all_alpha").toBeLessThan(epsilon); - expect(stake2After, "Stake2 should be approximately zero after unstake_all_alpha").toBeLessThan(epsilon); + // High tempo prevents emissions during test, so expect exact zero + expect(stake1After, "Stake1 should be zero after unstake_all_alpha").toBe(0n); + expect(stake2After, "Stake2 should be zero after unstake_all_alpha").toBe(0n); log.info("✅ Successfully unstaked all alpha from multiple subnets to root."); }); diff --git a/e2e/staking/test/unstake-all.test.ts b/e2e/staking/test/unstake-all.test.ts index d0359acc1b..146a2c3225 100644 --- a/e2e/staking/test/unstake-all.test.ts +++ b/e2e/staking/test/unstake-all.test.ts @@ -11,6 +11,7 @@ import { addStake, unstakeAll, getStake, + sudoSetTempo, tao, log, } from "e2e-shared"; @@ -45,6 +46,10 @@ describe("▶ unstake_all extrinsic", () => { const netuid2 = await addNewSubnetwork(api, owner2Hotkey, coldkey); await startCall(api, netuid2, coldkey); + // Set high tempo to prevent emissions during test + await sudoSetTempo(api, netuid1, 10000); + await sudoSetTempo(api, netuid2, 10000); + // Register stakerHotkey on both subnets (it's not the owner) await burnedRegister(api, netuid1, stakerAddress, coldkey); await burnedRegister(api, netuid2, stakerAddress, coldkey); From c0c8da4ca41e779c7e1a6fffebe5496f11e67743 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 6 Mar 2026 17:32:07 +0300 Subject: [PATCH 9/9] chore: re-trigger CI