Skip to content

Commit 7c1b0e2

Browse files
committed
- TS tests after merge conflicts
1 parent daf9629 commit 7c1b0e2

3 files changed

Lines changed: 305 additions & 1 deletion

File tree

runtime/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1120,7 +1120,7 @@ parameter_types! {
11201120
// 0 days
11211121
pub const InitialStartCallDelay: u64 = 0;
11221122
pub const SubtensorInitialKeySwapOnSubnetCost: TaoBalance = TaoBalance::new(1_000_000); // 0.001 TAO
1123-
pub const HotkeySwapOnSubnetInterval : BlockNumber = 24 * 60 * 60 / 12; // 1 day
1123+
pub const HotkeySwapOnSubnetInterval : BlockNumber = prod_or_fast!(24 * 60 * 60 / 12, 1); // 1 day
11241124
pub const LeaseDividendsDistributionInterval: BlockNumber = 100; // 100 blocks
11251125
pub const MaxImmuneUidsPercentage: Percent = Percent::from_percent(80);
11261126
pub const EvmKeyAssociateRateLimit: u64 = EVM_KEY_ASSOCIATE_RATELIMIT;
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
import { expect, beforeAll } from "vitest";
2+
import {
3+
addNewSubnetwork,
4+
addStake,
5+
burnedRegister,
6+
forceSetBalance,
7+
generateKeyringPair,
8+
getRootClaimable,
9+
startCall,
10+
sudoSetAdminFreezeWindow,
11+
sudoSetEmaPriceHalvingPeriod,
12+
sudoSetLockReductionInterval,
13+
sudoSetRootClaimThreshold,
14+
sudoSetSubnetMovingAlpha,
15+
sudoSetSubtokenEnabled,
16+
sudoSetTempo,
17+
tao,
18+
waitForBlocks,
19+
} from "../../utils";
20+
import { subtensor } from "@polkadot-api/descriptors";
21+
import type { TypedApi } from "polkadot-api";
22+
import { swapHotkey } from "../../utils/swap.ts";
23+
import { describeSuite } from "@moonwall/cli";
24+
import type { KeyringPair } from "@moonwall/util";
25+
26+
// Shared setup: creates two subnets, registers oldHotkey on both,
27+
// stakes on ROOT and both subnets, waits for RootClaimable to accumulate.
28+
async function setupTwoSubnetsWithClaimable(
29+
api: TypedApi<typeof subtensor>,
30+
ROOT_NETUID: number,
31+
log: (msg: string) => void
32+
): Promise<{
33+
oldHotkey: KeyringPair;
34+
oldHotkeyColdkey: KeyringPair;
35+
newHotkey: KeyringPair;
36+
netuid1: number;
37+
netuid2: number;
38+
}> {
39+
const oldHotkey = generateKeyringPair("sr25519");
40+
const oldHotkeyColdkey = generateKeyringPair("sr25519");
41+
const newHotkey = generateKeyringPair("sr25519");
42+
const owner1Hotkey = generateKeyringPair("sr25519");
43+
const owner1Coldkey = generateKeyringPair("sr25519");
44+
const owner2Hotkey = generateKeyringPair("sr25519");
45+
const owner2Coldkey = generateKeyringPair("sr25519");
46+
47+
for (const kp of [
48+
oldHotkey,
49+
oldHotkeyColdkey,
50+
newHotkey,
51+
owner1Hotkey,
52+
owner1Coldkey,
53+
owner2Hotkey,
54+
owner2Coldkey,
55+
]) {
56+
await forceSetBalance(api, kp.address);
57+
}
58+
59+
await sudoSetAdminFreezeWindow(api, 0);
60+
await sudoSetSubtokenEnabled(api, ROOT_NETUID, true);
61+
62+
const netuid1 = await addNewSubnetwork(api, owner1Hotkey, owner1Coldkey);
63+
await startCall(api, netuid1, owner1Coldkey);
64+
log(`Created netuid1: ${netuid1}`);
65+
66+
const netuid2 = await addNewSubnetwork(api, owner2Hotkey, owner2Coldkey);
67+
await startCall(api, netuid2, owner2Coldkey);
68+
log(`Created netuid2: ${netuid2}`);
69+
70+
for (const netuid of [netuid1, netuid2]) {
71+
await sudoSetTempo(api, netuid, 1);
72+
await sudoSetEmaPriceHalvingPeriod(api, netuid, 1);
73+
await sudoSetRootClaimThreshold(api, netuid, 0n);
74+
}
75+
await sudoSetSubnetMovingAlpha(api, BigInt(4294967296));
76+
77+
// Register oldHotkey on both subnets so it appears in epoch hotkey_emission
78+
// and receives root_alpha_dividends → RootClaimable on both netuids
79+
await burnedRegister(api, netuid1, oldHotkey.address, oldHotkeyColdkey);
80+
log("oldHotkey registered on netuid1");
81+
await burnedRegister(api, netuid2, oldHotkey.address, oldHotkeyColdkey);
82+
log("oldHotkey registered on netuid2");
83+
84+
// ROOT stake drives root_alpha_dividends for oldHotkey
85+
await addStake(api, oldHotkeyColdkey, oldHotkey.address, ROOT_NETUID, tao(100));
86+
log("Added ROOT stake for oldHotkey");
87+
88+
await addStake(api, oldHotkeyColdkey, oldHotkey.address, netuid1, tao(50));
89+
await addStake(api, oldHotkeyColdkey, oldHotkey.address, netuid2, tao(50));
90+
91+
await addStake(api, owner1Coldkey, owner1Hotkey.address, netuid1, tao(50));
92+
await addStake(api, owner2Coldkey, owner2Hotkey.address, netuid2, tao(50));
93+
94+
log("Waiting 30 blocks for RootClaimable to accumulate on both subnets...");
95+
await waitForBlocks(api, 30);
96+
97+
return { oldHotkey, oldHotkeyColdkey, newHotkey, netuid1, netuid2 };
98+
}
99+
100+
describeSuite({
101+
id: "0203_swap_hotkey_root_claimable",
102+
title: "▶ swap_hotkey RootClaimable per-subnet transfer",
103+
foundationMethods: "zombie",
104+
testCases: ({ it, context, log }) => {
105+
let api: TypedApi<typeof subtensor>;
106+
const ROOT_NETUID = 0;
107+
108+
beforeAll(async () => {
109+
api = context.papi("Node").getTypedApi(subtensor);
110+
await sudoSetLockReductionInterval(api, 1);
111+
});
112+
113+
it({
114+
id: "T01",
115+
title: "single-subnet swap doesn't move root claimable if it is not root",
116+
test: async () => {
117+
const { oldHotkey, oldHotkeyColdkey, newHotkey, netuid1, netuid2 } = await setupTwoSubnetsWithClaimable(
118+
api,
119+
ROOT_NETUID,
120+
log
121+
);
122+
123+
const claimableMapBefore = await getRootClaimable(api, oldHotkey.address);
124+
log(
125+
`RootClaimable[oldHotkey] before swap: ${
126+
[...claimableMapBefore.entries()].map(([k, v]) => `netuid${k}=${v}`).join(", ") || "(none)"
127+
}`
128+
);
129+
130+
expect(
131+
claimableMapBefore.get(netuid1) ?? 0n,
132+
"oldHotkey should have RootClaimable on netuid1 before swap"
133+
).toBeGreaterThan(0n);
134+
expect(
135+
claimableMapBefore.get(netuid2) ?? 0n,
136+
"oldHotkey should have RootClaimable on netuid2 before swap"
137+
).toBeGreaterThan(0n);
138+
expect(
139+
(await getRootClaimable(api, newHotkey.address)).size,
140+
"newHotkey should have no RootClaimable before swap"
141+
).toBe(0);
142+
143+
// Swap oldHotkey → newHotkey on netuid1 ONLY
144+
log(`Swapping oldHotkey → newHotkey on netuid1=${netuid1} only...`);
145+
await swapHotkey(api, oldHotkeyColdkey, oldHotkey.address, newHotkey.address, netuid1);
146+
log("Swap done");
147+
148+
const oldAfter = await getRootClaimable(api, oldHotkey.address);
149+
const newAfter = await getRootClaimable(api, newHotkey.address);
150+
151+
log(
152+
`RootClaimable[oldHotkey] after swap: netuid1=${oldAfter.get(netuid1) ?? 0n}, netuid2=${oldAfter.get(netuid2) ?? 0n}`
153+
);
154+
log(
155+
`RootClaimable[newHotkey] after swap: netuid1=${newAfter.get(netuid1) ?? 0n}, netuid2=${newAfter.get(netuid2) ?? 0n}`
156+
);
157+
158+
expect(newAfter.get(netuid1) ?? 0n, "newHotkey should not have RootClaimable for netuid1").toEqual(0n);
159+
expect(
160+
oldAfter.get(netuid1) ?? 0n,
161+
"oldHotkey should retain RootClaimable for netuid1"
162+
).toBeGreaterThan(0n);
163+
164+
expect(
165+
oldAfter.get(netuid2) ?? 0n,
166+
"oldHotkey should retain RootClaimable for netuid2"
167+
).toBeGreaterThan(0n);
168+
expect(newAfter.get(netuid2) ?? 0n, "newHotkey should have no RootClaimable for netuid2").toBe(0n);
169+
170+
log(
171+
"✅ Single-subnet swap doesn't transfer RootClaimable for the subnet if it was done for non-root subnet"
172+
);
173+
},
174+
});
175+
176+
it({
177+
id: "T02",
178+
title: "full swap (no netuid) moves RootClaimable for all subnets to newHotkey",
179+
test: async () => {
180+
const { oldHotkey, oldHotkeyColdkey, newHotkey, netuid1, netuid2 } = await setupTwoSubnetsWithClaimable(
181+
api,
182+
ROOT_NETUID,
183+
log
184+
);
185+
186+
const claimableMapBefore = await getRootClaimable(api, oldHotkey.address);
187+
log(
188+
`RootClaimable[oldHotkey] before swap: ${
189+
[...claimableMapBefore.entries()].map(([k, v]) => `netuid${k}=${v}`).join(", ") || "(none)"
190+
}`
191+
);
192+
193+
expect(
194+
claimableMapBefore.get(netuid1) ?? 0n,
195+
"oldHotkey should have RootClaimable on netuid1 before swap"
196+
).toBeGreaterThan(0n);
197+
expect(
198+
claimableMapBefore.get(netuid2) ?? 0n,
199+
"oldHotkey should have RootClaimable on netuid2 before swap"
200+
).toBeGreaterThan(0n);
201+
202+
// Full swap — no netuid
203+
log("Swapping oldHotkey → newHotkey on ALL subnets...");
204+
await swapHotkey(api, oldHotkeyColdkey, oldHotkey.address, newHotkey.address);
205+
log("Swap done");
206+
207+
const oldAfter = await getRootClaimable(api, oldHotkey.address);
208+
const newAfter = await getRootClaimable(api, newHotkey.address);
209+
210+
log(
211+
`RootClaimable[oldHotkey] after swap: netuid1=${oldAfter.get(netuid1) ?? 0n}, netuid2=${oldAfter.get(netuid2) ?? 0n}`
212+
);
213+
log(
214+
`RootClaimable[newHotkey] after swap: netuid1=${newAfter.get(netuid1) ?? 0n}, netuid2=${newAfter.get(netuid2) ?? 0n}`
215+
);
216+
217+
expect(newAfter.get(netuid1) ?? 0n, "newHotkey should have RootClaimable for netuid1").toBeGreaterThan(
218+
0n
219+
);
220+
expect(newAfter.get(netuid2) ?? 0n, "newHotkey should have RootClaimable for netuid2").toBeGreaterThan(
221+
0n
222+
);
223+
224+
expect(oldAfter.get(netuid1) ?? 0n, "oldHotkey should have no RootClaimable for netuid1").toBe(0n);
225+
expect(oldAfter.get(netuid2) ?? 0n, "oldHotkey should have no RootClaimable for netuid2").toBe(0n);
226+
227+
log("✅ Full swap correctly transferred RootClaimable for both subnets to newHotkey");
228+
},
229+
});
230+
231+
it({
232+
id: "T03",
233+
title: "single-subnet swap moves root claimable if it is root",
234+
test: async () => {
235+
const { oldHotkey, oldHotkeyColdkey, newHotkey, netuid1, netuid2 } = await setupTwoSubnetsWithClaimable(
236+
api,
237+
ROOT_NETUID,
238+
log
239+
);
240+
241+
const claimableMapBefore = await getRootClaimable(api, oldHotkey.address);
242+
log(
243+
`RootClaimable[oldHotkey] before swap: ${
244+
[...claimableMapBefore.entries()].map(([k, v]) => `netuid${k}=${v}`).join(", ") || "(none)"
245+
}`
246+
);
247+
248+
expect(
249+
claimableMapBefore.get(netuid1) ?? 0n,
250+
"oldHotkey should have RootClaimable on netuid1 before swap"
251+
).toBeGreaterThan(0n);
252+
expect(
253+
claimableMapBefore.get(netuid2) ?? 0n,
254+
"oldHotkey should have RootClaimable on netuid2 before swap"
255+
).toBeGreaterThan(0n);
256+
257+
log("Swapping oldHotkey → newHotkey for root subnet...");
258+
await swapHotkey(api, oldHotkeyColdkey, oldHotkey.address, newHotkey.address, 0);
259+
log("Swap done");
260+
261+
const oldAfter = await getRootClaimable(api, oldHotkey.address);
262+
const newAfter = await getRootClaimable(api, newHotkey.address);
263+
264+
log(
265+
`RootClaimable[oldHotkey] after swap: netuid1=${oldAfter.get(netuid1) ?? 0n}, netuid2=${oldAfter.get(netuid2) ?? 0n}`
266+
);
267+
log(
268+
`RootClaimable[newHotkey] after swap: netuid1=${newAfter.get(netuid1) ?? 0n}, netuid2=${newAfter.get(netuid2) ?? 0n}`
269+
);
270+
271+
expect(newAfter.get(netuid1) ?? 0n, "newHotkey should have RootClaimable for netuid1").toBeGreaterThan(
272+
0n
273+
);
274+
expect(newAfter.get(netuid2) ?? 0n, "newHotkey should have RootClaimable for netuid2").toBeGreaterThan(
275+
0n
276+
);
277+
278+
expect(oldAfter.get(netuid1) ?? 0n, "oldHotkey should have no RootClaimable for netuid1").toBe(0n);
279+
expect(oldAfter.get(netuid2) ?? 0n, "oldHotkey should have no RootClaimable for netuid2").toBe(0n);
280+
281+
log("✅ Single swap correctly transferred RootClaimable if it is done for root subnet");
282+
},
283+
});
284+
},
285+
});

ts-tests/utils/swap.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { waitForTransactionWithRetry } from "./transactions.js";
2+
import type { KeyringPair } from "@moonwall/util";
3+
import type { subtensor } from "@polkadot-api/descriptors";
4+
import type { TypedApi } from "polkadot-api";
5+
6+
export async function swapHotkey(
7+
api: TypedApi<typeof subtensor>,
8+
coldkey: KeyringPair,
9+
oldHotkey: string,
10+
newHotkey: string,
11+
netuid?: number
12+
): Promise<void> {
13+
const tx = api.tx.SubtensorModule.swap_hotkey({
14+
hotkey: oldHotkey,
15+
new_hotkey: newHotkey,
16+
netuid: netuid ?? undefined,
17+
});
18+
await waitForTransactionWithRetry(api, tx, coldkey, "swap_hotkey");
19+
}

0 commit comments

Comments
 (0)