Skip to content

Commit c889739

Browse files
committed
feat: add destination_hotkey parameter to transfer_stake
Closes #1377 This PR allows users to specify a destination hotkey when transferring stake to a different coldkey. Previously, transfer_stake only allowed changing the coldkey while keeping the same hotkey, which caused issues because the new coldkey wouldn't own the original hotkey. Changes: - Add destination_hotkey parameter to transfer_stake dispatch (call_index 86) - Update do_transfer_stake to use separate origin_hotkey and destination_hotkey - Update StakeTransferred event to include both hotkeys - Update precompiles (transferStake now takes 6 params instead of 5) - Update chain-extensions for new signature - Update contract interface (ink! contracts) - Update solidity interface - Add test for transferring to different hotkey - Update all existing tests to use new parameter - Update documentation This is a breaking change for existing callers of transfer_stake.
1 parent 675111c commit c889739

File tree

14 files changed

+153
-43
lines changed

14 files changed

+153
-43
lines changed

chain-extensions/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,12 +226,13 @@ where
226226
}
227227
FunctionId::TransferStakeV1 => {
228228
let weight = Weight::from_parts(160_300_000, 0)
229-
.saturating_add(T::DbWeight::get().reads(13_u64))
229+
.saturating_add(T::DbWeight::get().reads(14_u64))
230230
.saturating_add(T::DbWeight::get().writes(6_u64));
231231

232232
env.charge_weight(weight)?;
233233

234-
let (destination_coldkey, hotkey, origin_netuid, destination_netuid, alpha_amount): (
234+
let (destination_coldkey, origin_hotkey, destination_hotkey, origin_netuid, destination_netuid, alpha_amount): (
235+
T::AccountId,
235236
T::AccountId,
236237
T::AccountId,
237238
NetUid,
@@ -244,7 +245,8 @@ where
244245
let call_result = pallet_subtensor::Pallet::<T>::transfer_stake(
245246
RawOrigin::Signed(env.caller()).into(),
246247
destination_coldkey,
247-
hotkey,
248+
origin_hotkey,
249+
destination_hotkey,
248250
origin_netuid,
249251
destination_netuid,
250252
alpha_amount,

chain-extensions/src/tests.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ fn transfer_stake_success_moves_between_coldkeys() {
478478
let alpha_to_transfer: AlphaCurrency = (alpha_before.to_u64() / 3).into();
479479

480480
let expected_weight = Weight::from_parts(160_300_000, 0)
481-
.saturating_add(<mock::Test as frame_system::Config>::DbWeight::get().reads(13))
481+
.saturating_add(<mock::Test as frame_system::Config>::DbWeight::get().reads(14))
482482
.saturating_add(<mock::Test as frame_system::Config>::DbWeight::get().writes(6));
483483

484484
let mut env = MockEnv::new(
@@ -487,6 +487,7 @@ fn transfer_stake_success_moves_between_coldkeys() {
487487
(
488488
destination_coldkey,
489489
hotkey,
490+
hotkey,
490491
netuid,
491492
netuid,
492493
alpha_to_transfer,

contract-tests/bittensor/lib.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ pub trait RuntimeReadWrite {
6868
#[ink(function = 6)]
6969
fn transfer_stake(
7070
destination_coldkey: <CustomEnvironment as ink::env::Environment>::AccountId,
71-
hotkey: <CustomEnvironment as ink::env::Environment>::AccountId,
71+
origin_hotkey: <CustomEnvironment as ink::env::Environment>::AccountId,
72+
destination_hotkey: <CustomEnvironment as ink::env::Environment>::AccountId,
7273
origin_netuid: NetUid,
7374
destination_netuid: NetUid,
7475
amount: AlphaCurrency,
@@ -278,7 +279,8 @@ mod bittensor {
278279
pub fn transfer_stake(
279280
&self,
280281
destination_coldkey: [u8; 32],
281-
hotkey: [u8; 32],
282+
origin_hotkey: [u8; 32],
283+
destination_hotkey: [u8; 32],
282284
origin_netuid: u16,
283285
destination_netuid: u16,
284286
amount: u64,
@@ -287,7 +289,8 @@ mod bittensor {
287289
.extension()
288290
.transfer_stake(
289291
destination_coldkey.into(),
290-
hotkey.into(),
292+
origin_hotkey.into(),
293+
destination_hotkey.into(),
291294
origin_netuid.into(),
292295
destination_netuid.into(),
293296
amount.into(),

docs/wasm-contracts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Subtensor provides a custom chain extension that allows smart contracts to inter
3434
| 3 | `unstake_all` | Unstake all TAO from a hotkey | `(AccountId)` | Error code |
3535
| 4 | `unstake_all_alpha` | Unstake all Alpha from a hotkey | `(AccountId)` | Error code |
3636
| 5 | `move_stake` | Move stake between hotkeys | `(AccountId, AccountId, NetUid, NetUid, AlphaCurrency)` | Error code |
37-
| 6 | `transfer_stake` | Transfer stake between coldkeys | `(AccountId, AccountId, NetUid, NetUid, AlphaCurrency)` | Error code |
37+
| 6 | `transfer_stake` | Transfer stake between coldkeys and hotkeys | `(AccountId, AccountId, AccountId, NetUid, NetUid, AlphaCurrency)` | Error code |
3838
| 7 | `swap_stake` | Swap stake allocations between subnets | `(AccountId, NetUid, NetUid, AlphaCurrency)` | Error code |
3939
| 8 | `add_stake_limit` | Delegate stake with a price limit | `(AccountId, NetUid, TaoCurrency, TaoCurrency, bool)` | Error code |
4040
| 9 | `remove_stake_limit` | Withdraw stake with a price limit | `(AccountId, NetUid, AlphaCurrency, TaoCurrency, bool)` | Error code |

pallets/subtensor/src/macros/dispatches.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,13 +1636,14 @@ mod dispatches {
16361636
)
16371637
}
16381638

1639-
/// Transfers a specified amount of stake from one coldkey to another, optionally across subnets,
1640-
/// while keeping the same hotkey.
1639+
/// Transfers a specified amount of stake from one coldkey to another, optionally across subnets
1640+
/// and hotkeys.
16411641
///
16421642
/// # Arguments
16431643
/// * `origin` - The origin of the transaction, which must be signed by the `origin_coldkey`.
16441644
/// * `destination_coldkey` - The coldkey to which the stake is transferred.
1645-
/// * `hotkey` - The hotkey associated with the stake.
1645+
/// * `origin_hotkey` - The hotkey from which the stake is being transferred.
1646+
/// * `destination_hotkey` - The hotkey to which the stake is being transferred.
16461647
/// * `origin_netuid` - The network/subnet ID to move stake from.
16471648
/// * `destination_netuid` - The network/subnet ID to move stake to (for cross-subnet transfer).
16481649
/// * `alpha_amount` - The amount of stake to transfer.
@@ -1651,28 +1652,30 @@ mod dispatches {
16511652
/// Returns an error if:
16521653
/// * The origin is not signed by the correct coldkey.
16531654
/// * Either subnet does not exist.
1654-
/// * The hotkey does not exist.
1655-
/// * There is insufficient stake on `(origin_coldkey, hotkey, origin_netuid)`.
1655+
/// * Either hotkey does not exist.
1656+
/// * There is insufficient stake on `(origin_coldkey, origin_hotkey, origin_netuid)`.
16561657
/// * The transfer amount is below the minimum stake requirement.
16571658
///
16581659
/// # Events
16591660
/// May emit a `StakeTransferred` event on success.
16601661
#[pallet::call_index(86)]
16611662
#[pallet::weight((Weight::from_parts(160_300_000, 0)
1662-
.saturating_add(T::DbWeight::get().reads(13_u64))
1663+
.saturating_add(T::DbWeight::get().reads(14_u64))
16631664
.saturating_add(T::DbWeight::get().writes(6_u64)), DispatchClass::Normal, Pays::Yes))]
16641665
pub fn transfer_stake(
16651666
origin: T::RuntimeOrigin,
16661667
destination_coldkey: T::AccountId,
1667-
hotkey: T::AccountId,
1668+
origin_hotkey: T::AccountId,
1669+
destination_hotkey: T::AccountId,
16681670
origin_netuid: NetUid,
16691671
destination_netuid: NetUid,
16701672
alpha_amount: AlphaCurrency,
16711673
) -> DispatchResult {
16721674
Self::do_transfer_stake(
16731675
origin,
16741676
destination_coldkey,
1675-
hotkey,
1677+
origin_hotkey,
1678+
destination_hotkey,
16761679
origin_netuid,
16771680
destination_netuid,
16781681
alpha_amount,

pallets/subtensor/src/macros/events.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,13 +274,14 @@ mod events {
274274
/// - **error**: The dispatch error emitted by the failed item.
275275
BatchWeightItemFailed(sp_runtime::DispatchError),
276276

277-
/// Stake has been transferred from one coldkey to another on the same subnet.
277+
/// Stake has been transferred from one coldkey to another, optionally across hotkeys and subnets.
278278
/// Parameters:
279-
/// (origin_coldkey, destination_coldkey, hotkey, origin_netuid, destination_netuid, amount)
279+
/// (origin_coldkey, destination_coldkey, origin_hotkey, destination_hotkey, origin_netuid, destination_netuid, amount)
280280
StakeTransferred(
281281
T::AccountId,
282282
T::AccountId,
283283
T::AccountId,
284+
T::AccountId,
284285
NetUid,
285286
NetUid,
286287
TaoCurrency,

pallets/subtensor/src/staking/move_stake.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,14 @@ impl<T: Config> Pallet<T> {
9393
Ok(())
9494
}
9595

96-
/// Transfers stake from one coldkey to another, optionally moving from one subnet to another,
97-
/// while keeping the same hotkey.
96+
/// Transfers stake from one coldkey to another, optionally moving from one subnet to another
97+
/// and from one hotkey to another.
9898
///
9999
/// # Arguments
100100
/// * `origin` - The origin of the transaction, which must be signed by the `origin_coldkey`.
101101
/// * `destination_coldkey` - The account ID of the coldkey to which the stake is being transferred.
102-
/// * `hotkey` - The account ID of the hotkey associated with this stake.
102+
/// * `origin_hotkey` - The account ID of the hotkey from which the stake is being transferred.
103+
/// * `destination_hotkey` - The account ID of the hotkey to which the stake is being transferred.
103104
/// * `origin_netuid` - The network ID (subnet) from which the stake is being transferred.
104105
/// * `destination_netuid` - The network ID (subnet) to which the stake is being transferred.
105106
/// * `alpha_amount` - The amount of stake to transfer.
@@ -111,8 +112,8 @@ impl<T: Config> Pallet<T> {
111112
/// This function will return an error if:
112113
/// * The transaction is not signed by the `origin_coldkey`.
113114
/// * The subnet (`origin_netuid` or `destination_netuid`) does not exist.
114-
/// * The `hotkey` does not exist.
115-
/// * The `(origin_coldkey, hotkey, origin_netuid)` does not have enough stake for `alpha_amount`.
115+
/// * Either `origin_hotkey` or `destination_hotkey` does not exist.
116+
/// * The `(origin_coldkey, origin_hotkey, origin_netuid)` does not have enough stake for `alpha_amount`.
116117
/// * The amount to be transferred is below the minimum stake requirement.
117118
/// * There is a failure in staking or unstaking logic.
118119
///
@@ -121,7 +122,8 @@ impl<T: Config> Pallet<T> {
121122
pub fn do_transfer_stake(
122123
origin: T::RuntimeOrigin,
123124
destination_coldkey: T::AccountId,
124-
hotkey: T::AccountId,
125+
origin_hotkey: T::AccountId,
126+
destination_hotkey: T::AccountId,
125127
origin_netuid: NetUid,
126128
destination_netuid: NetUid,
127129
alpha_amount: AlphaCurrency,
@@ -133,8 +135,8 @@ impl<T: Config> Pallet<T> {
133135
let tao_moved = Self::transition_stake_internal(
134136
&coldkey,
135137
&destination_coldkey,
136-
&hotkey,
137-
&hotkey,
138+
&origin_hotkey,
139+
&destination_hotkey,
138140
origin_netuid,
139141
destination_netuid,
140142
alpha_amount,
@@ -144,20 +146,21 @@ impl<T: Config> Pallet<T> {
144146
false,
145147
)?;
146148

147-
// 9. Emit an event for logging/monitoring.
149+
// Emit an event for logging/monitoring.
148150
log::debug!(
149-
"StakeTransferred(origin_coldkey: {coldkey:?}, destination_coldkey: {destination_coldkey:?}, hotkey: {hotkey:?}, origin_netuid: {origin_netuid:?}, destination_netuid: {destination_netuid:?}, amount: {tao_moved:?})"
151+
"StakeTransferred(origin_coldkey: {coldkey:?}, destination_coldkey: {destination_coldkey:?}, origin_hotkey: {origin_hotkey:?}, destination_hotkey: {destination_hotkey:?}, origin_netuid: {origin_netuid:?}, destination_netuid: {destination_netuid:?}, amount: {tao_moved:?})"
150152
);
151153
Self::deposit_event(Event::StakeTransferred(
152154
coldkey,
153155
destination_coldkey,
154-
hotkey,
156+
origin_hotkey,
157+
destination_hotkey,
155158
origin_netuid,
156159
destination_netuid,
157160
tao_moved,
158161
));
159162

160-
// 10. Return success.
163+
// Return success.
161164
Ok(())
162165
}
163166

pallets/subtensor/src/tests/move_stake.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,7 @@ fn test_do_transfer_success() {
925925
RuntimeOrigin::signed(origin_coldkey),
926926
destination_coldkey,
927927
hotkey,
928+
hotkey,
928929
netuid,
929930
netuid,
930931
alpha
@@ -965,6 +966,7 @@ fn test_do_transfer_nonexistent_subnet() {
965966
RuntimeOrigin::signed(origin_coldkey),
966967
destination_coldkey,
967968
hotkey,
969+
hotkey,
968970
nonexistent_netuid,
969971
nonexistent_netuid,
970972
stake_amount.into()
@@ -990,6 +992,7 @@ fn test_do_transfer_nonexistent_hotkey() {
990992
RuntimeOrigin::signed(origin_coldkey),
991993
destination_coldkey,
992994
nonexistent_hotkey,
995+
nonexistent_hotkey,
993996
netuid,
994997
netuid,
995998
100.into()
@@ -1030,6 +1033,7 @@ fn test_do_transfer_insufficient_stake() {
10301033
RuntimeOrigin::signed(origin_coldkey),
10311034
destination_coldkey,
10321035
hotkey,
1036+
hotkey,
10331037
netuid,
10341038
netuid,
10351039
alpha.into()
@@ -1069,6 +1073,7 @@ fn test_do_transfer_wrong_origin() {
10691073
RuntimeOrigin::signed(wrong_coldkey),
10701074
destination_coldkey,
10711075
hotkey,
1076+
hotkey,
10721077
netuid,
10731078
netuid,
10741079
stake_amount.into()
@@ -1107,6 +1112,7 @@ fn test_do_transfer_minimum_stake_check() {
11071112
RuntimeOrigin::signed(origin_coldkey),
11081113
destination_coldkey,
11091114
hotkey,
1115+
hotkey,
11101116
netuid,
11111117
netuid,
11121118
1.into()
@@ -1164,6 +1170,7 @@ fn test_do_transfer_different_subnets() {
11641170
RuntimeOrigin::signed(origin_coldkey),
11651171
destination_coldkey,
11661172
hotkey,
1173+
hotkey,
11671174
origin_netuid,
11681175
destination_netuid,
11691176
alpha
@@ -1192,6 +1199,84 @@ fn test_do_transfer_different_subnets() {
11921199
});
11931200
}
11941201

1202+
#[test]
1203+
fn test_do_transfer_to_different_hotkey() {
1204+
new_test_ext(1).execute_with(|| {
1205+
// 1. Setup: Create a subnet and two hotkeys
1206+
let subnet_owner_coldkey = U256::from(1001);
1207+
let subnet_owner_hotkey = U256::from(1002);
1208+
let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey);
1209+
1210+
let origin_coldkey = U256::from(1);
1211+
let destination_coldkey = U256::from(2);
1212+
let origin_hotkey = U256::from(3);
1213+
let destination_hotkey = U256::from(4);
1214+
let stake_amount = DefaultMinStake::<Test>::get().to_u64() * 10;
1215+
1216+
// 2. Create accounts for both hotkeys
1217+
SubtensorModule::create_account_if_non_existent(&origin_coldkey, &origin_hotkey);
1218+
SubtensorModule::create_account_if_non_existent(&destination_coldkey, &destination_hotkey);
1219+
1220+
// 3. Stake into the origin hotkey
1221+
SubtensorModule::stake_into_subnet(
1222+
&origin_hotkey,
1223+
&origin_coldkey,
1224+
netuid,
1225+
stake_amount.into(),
1226+
<Test as Config>::SwapInterface::max_price(),
1227+
false,
1228+
false,
1229+
)
1230+
.unwrap();
1231+
1232+
let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(
1233+
&origin_hotkey,
1234+
&origin_coldkey,
1235+
netuid,
1236+
);
1237+
1238+
// 4. Transfer stake to a DIFFERENT coldkey AND DIFFERENT hotkey
1239+
assert_ok!(SubtensorModule::do_transfer_stake(
1240+
RuntimeOrigin::signed(origin_coldkey),
1241+
destination_coldkey,
1242+
origin_hotkey,
1243+
destination_hotkey,
1244+
netuid,
1245+
netuid,
1246+
alpha
1247+
));
1248+
1249+
// 5. Verify origin coldkey + origin hotkey has no stake
1250+
assert_eq!(
1251+
SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(
1252+
&origin_hotkey,
1253+
&origin_coldkey,
1254+
netuid
1255+
),
1256+
AlphaCurrency::ZERO
1257+
);
1258+
1259+
// 6. Verify destination coldkey + destination hotkey has the stake
1260+
assert!(
1261+
SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(
1262+
&destination_hotkey,
1263+
&destination_coldkey,
1264+
netuid
1265+
) > AlphaCurrency::ZERO
1266+
);
1267+
1268+
// 7. Verify origin coldkey + destination hotkey has NO stake (stake went to destination coldkey)
1269+
assert_eq!(
1270+
SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(
1271+
&destination_hotkey,
1272+
&origin_coldkey,
1273+
netuid
1274+
),
1275+
AlphaCurrency::ZERO
1276+
);
1277+
});
1278+
}
1279+
11951280
#[test]
11961281
fn test_do_swap_success() {
11971282
new_test_ext(1).execute_with(|| {
@@ -1819,6 +1904,7 @@ fn test_transfer_stake_rate_limited() {
18191904
RuntimeOrigin::signed(origin_coldkey),
18201905
destination_coldkey,
18211906
hotkey,
1907+
hotkey,
18221908
netuid,
18231909
netuid,
18241910
alpha
@@ -1863,6 +1949,7 @@ fn test_transfer_stake_doesnt_limit_destination_coldkey() {
18631949
RuntimeOrigin::signed(origin_coldkey),
18641950
destination_coldkey,
18651951
hotkey,
1952+
hotkey,
18661953
netuid,
18671954
netuid2,
18681955
alpha

0 commit comments

Comments
 (0)