diff --git a/chain-extensions/src/lib.rs b/chain-extensions/src/lib.rs index eaaee70d27..2b61feb8a7 100644 --- a/chain-extensions/src/lib.rs +++ b/chain-extensions/src/lib.rs @@ -224,9 +224,10 @@ where } } } + // Backward-compatible TransferStakeV1 with 5 parameters (same hotkey for origin and destination) FunctionId::TransferStakeV1 => { let weight = Weight::from_parts(160_300_000, 0) - .saturating_add(T::DbWeight::get().reads(13_u64)) + .saturating_add(T::DbWeight::get().reads(14_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)); env.charge_weight(weight)?; @@ -244,6 +245,7 @@ where let call_result = pallet_subtensor::Pallet::::transfer_stake( RawOrigin::Signed(env.caller()).into(), destination_coldkey, + hotkey.clone(), hotkey, origin_netuid, destination_netuid, @@ -258,6 +260,50 @@ where } } } + // New TransferStakeV2 with 6 parameters (allows different origin and destination hotkeys) + FunctionId::TransferStakeV2 => { + let weight = Weight::from_parts(160_300_000, 0) + .saturating_add(T::DbWeight::get().reads(14_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)); + + env.charge_weight(weight)?; + + let ( + destination_coldkey, + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + ): ( + T::AccountId, + T::AccountId, + T::AccountId, + NetUid, + NetUid, + AlphaCurrency, + ) = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let call_result = pallet_subtensor::Pallet::::transfer_stake( + RawOrigin::Signed(env.caller()).into(), + destination_coldkey, + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + ); + + match call_result { + Ok(_) => Ok(RetVal::Converging(Output::Success as u32)), + Err(e) => { + let error_code = Output::from(e) as u32; + Ok(RetVal::Converging(error_code)) + } + } + } FunctionId::SwapStakeV1 => { let weight = Weight::from_parts(351_300_000, 0) .saturating_add(T::DbWeight::get().reads(35_u64)) diff --git a/chain-extensions/src/tests.rs b/chain-extensions/src/tests.rs index 094b0987e2..84f59d0635 100644 --- a/chain-extensions/src/tests.rs +++ b/chain-extensions/src/tests.rs @@ -478,7 +478,7 @@ fn transfer_stake_success_moves_between_coldkeys() { let alpha_to_transfer: AlphaCurrency = (alpha_before.to_u64() / 3).into(); let expected_weight = Weight::from_parts(160_300_000, 0) - .saturating_add(::DbWeight::get().reads(13)) + .saturating_add(::DbWeight::get().reads(14)) .saturating_add(::DbWeight::get().writes(6)); let mut env = MockEnv::new( diff --git a/chain-extensions/src/types.rs b/chain-extensions/src/types.rs index ee6298ad5b..d6d1b4d166 100644 --- a/chain-extensions/src/types.rs +++ b/chain-extensions/src/types.rs @@ -21,6 +21,7 @@ pub enum FunctionId { AddProxyV1 = 13, RemoveProxyV1 = 14, GetAlphaPriceV1 = 15, + TransferStakeV2 = 16, } #[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, Debug)] diff --git a/contract-tests/bittensor/lib.rs b/contract-tests/bittensor/lib.rs index 48e8d18aea..a62c6985a5 100755 --- a/contract-tests/bittensor/lib.rs +++ b/contract-tests/bittensor/lib.rs @@ -23,6 +23,7 @@ pub enum FunctionId { AddProxyV1 = 13, RemoveProxyV1 = 14, GetAlphaPriceV1 = 15, + TransferStakeV2 = 16, } #[ink::chain_extension(extension = 0x1000)] @@ -65,6 +66,7 @@ pub trait RuntimeReadWrite { amount: AlphaCurrency, ); + // Backward-compatible transfer_stake with 5 parameters (same hotkey) #[ink(function = 6)] fn transfer_stake( destination_coldkey: ::AccountId, @@ -74,6 +76,17 @@ pub trait RuntimeReadWrite { amount: AlphaCurrency, ); + // New transfer_stake_v2 with 6 parameters (different hotkeys) + #[ink(function = 16)] + fn transfer_stake_v2( + destination_coldkey: ::AccountId, + origin_hotkey: ::AccountId, + destination_hotkey: ::AccountId, + origin_netuid: NetUid, + destination_netuid: NetUid, + amount: AlphaCurrency, + ); + #[ink(function = 7)] fn swap_stake( hotkey: ::AccountId, @@ -274,6 +287,7 @@ mod bittensor { .map_err(|_e| ReadWriteErrorCode::WriteFailed) } + // Backward-compatible transfer_stake with 5 parameters (same hotkey) #[ink(message)] pub fn transfer_stake( &self, @@ -295,6 +309,30 @@ mod bittensor { .map_err(|_e| ReadWriteErrorCode::WriteFailed) } + // New transfer_stake_v2 with 6 parameters (different hotkeys) + #[ink(message)] + pub fn transfer_stake_v2( + &self, + destination_coldkey: [u8; 32], + origin_hotkey: [u8; 32], + destination_hotkey: [u8; 32], + origin_netuid: u16, + destination_netuid: u16, + amount: u64, + ) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .transfer_stake_v2( + destination_coldkey.into(), + origin_hotkey.into(), + destination_hotkey.into(), + origin_netuid.into(), + destination_netuid.into(), + amount.into(), + ) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } + #[ink(message)] pub fn swap_stake( &self, diff --git a/docs/wasm-contracts.md b/docs/wasm-contracts.md index ed6e9ecdd3..2a0b159fae 100644 --- a/docs/wasm-contracts.md +++ b/docs/wasm-contracts.md @@ -34,7 +34,8 @@ Subtensor provides a custom chain extension that allows smart contracts to inter | 3 | `unstake_all` | Unstake all TAO from a hotkey | `(AccountId)` | Error code | | 4 | `unstake_all_alpha` | Unstake all Alpha from a hotkey | `(AccountId)` | Error code | | 5 | `move_stake` | Move stake between hotkeys | `(AccountId, AccountId, NetUid, NetUid, AlphaCurrency)` | Error code | -| 6 | `transfer_stake` | Transfer stake between coldkeys | `(AccountId, AccountId, NetUid, NetUid, AlphaCurrency)` | Error code | +| 6 | `transfer_stake` | Transfer stake between coldkeys (same hotkey) | `(AccountId, AccountId, NetUid, NetUid, AlphaCurrency)` | Error code | +| 16 | `transfer_stake_v2` | Transfer stake between coldkeys and hotkeys | `(AccountId, AccountId, AccountId, NetUid, NetUid, AlphaCurrency)` | Error code | | 7 | `swap_stake` | Swap stake allocations between subnets | `(AccountId, NetUid, NetUid, AlphaCurrency)` | Error code | | 8 | `add_stake_limit` | Delegate stake with a price limit | `(AccountId, NetUid, TaoCurrency, TaoCurrency, bool)` | Error code | | 9 | `remove_stake_limit` | Withdraw stake with a price limit | `(AccountId, NetUid, AlphaCurrency, TaoCurrency, bool)` | Error code | diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 61d5523285..c04ed8e818 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1636,13 +1636,14 @@ mod dispatches { ) } - /// Transfers a specified amount of stake from one coldkey to another, optionally across subnets, - /// while keeping the same hotkey. + /// Transfers a specified amount of stake from one coldkey to another, optionally across subnets + /// and hotkeys. /// /// # Arguments /// * `origin` - The origin of the transaction, which must be signed by the `origin_coldkey`. /// * `destination_coldkey` - The coldkey to which the stake is transferred. - /// * `hotkey` - The hotkey associated with the stake. + /// * `origin_hotkey` - The hotkey from which the stake is being transferred. + /// * `destination_hotkey` - The hotkey to which the stake is being transferred. /// * `origin_netuid` - The network/subnet ID to move stake from. /// * `destination_netuid` - The network/subnet ID to move stake to (for cross-subnet transfer). /// * `alpha_amount` - The amount of stake to transfer. @@ -1651,20 +1652,21 @@ mod dispatches { /// Returns an error if: /// * The origin is not signed by the correct coldkey. /// * Either subnet does not exist. - /// * The hotkey does not exist. - /// * There is insufficient stake on `(origin_coldkey, hotkey, origin_netuid)`. + /// * Either hotkey does not exist. + /// * There is insufficient stake on `(origin_coldkey, origin_hotkey, origin_netuid)`. /// * The transfer amount is below the minimum stake requirement. /// /// # Events /// May emit a `StakeTransferred` event on success. #[pallet::call_index(86)] #[pallet::weight((Weight::from_parts(160_300_000, 0) - .saturating_add(T::DbWeight::get().reads(13_u64)) + .saturating_add(T::DbWeight::get().reads(14_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)), DispatchClass::Normal, Pays::Yes))] pub fn transfer_stake( origin: T::RuntimeOrigin, destination_coldkey: T::AccountId, - hotkey: T::AccountId, + origin_hotkey: T::AccountId, + destination_hotkey: T::AccountId, origin_netuid: NetUid, destination_netuid: NetUid, alpha_amount: AlphaCurrency, @@ -1672,7 +1674,8 @@ mod dispatches { Self::do_transfer_stake( origin, destination_coldkey, - hotkey, + origin_hotkey, + destination_hotkey, origin_netuid, destination_netuid, alpha_amount, diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index a06e035d86..74f0b6df8b 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -274,13 +274,14 @@ mod events { /// - **error**: The dispatch error emitted by the failed item. BatchWeightItemFailed(sp_runtime::DispatchError), - /// Stake has been transferred from one coldkey to another on the same subnet. + /// Stake has been transferred from one coldkey to another, optionally across hotkeys and subnets. /// Parameters: - /// (origin_coldkey, destination_coldkey, hotkey, origin_netuid, destination_netuid, amount) + /// (origin_coldkey, destination_coldkey, origin_hotkey, destination_hotkey, origin_netuid, destination_netuid, amount) StakeTransferred( T::AccountId, T::AccountId, T::AccountId, + T::AccountId, NetUid, NetUid, TaoCurrency, diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index a1d9b46d5b..6ccb860a85 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -93,13 +93,14 @@ impl Pallet { Ok(()) } - /// Transfers stake from one coldkey to another, optionally moving from one subnet to another, - /// while keeping the same hotkey. + /// Transfers stake from one coldkey to another, optionally moving from one subnet to another + /// and from one hotkey to another. /// /// # Arguments /// * `origin` - The origin of the transaction, which must be signed by the `origin_coldkey`. /// * `destination_coldkey` - The account ID of the coldkey to which the stake is being transferred. - /// * `hotkey` - The account ID of the hotkey associated with this stake. + /// * `origin_hotkey` - The account ID of the hotkey from which the stake is being transferred. + /// * `destination_hotkey` - The account ID of the hotkey to which the stake is being transferred. /// * `origin_netuid` - The network ID (subnet) from which the stake is being transferred. /// * `destination_netuid` - The network ID (subnet) to which the stake is being transferred. /// * `alpha_amount` - The amount of stake to transfer. @@ -111,8 +112,8 @@ impl Pallet { /// This function will return an error if: /// * The transaction is not signed by the `origin_coldkey`. /// * The subnet (`origin_netuid` or `destination_netuid`) does not exist. - /// * The `hotkey` does not exist. - /// * The `(origin_coldkey, hotkey, origin_netuid)` does not have enough stake for `alpha_amount`. + /// * Either `origin_hotkey` or `destination_hotkey` does not exist. + /// * The `(origin_coldkey, origin_hotkey, origin_netuid)` does not have enough stake for `alpha_amount`. /// * The amount to be transferred is below the minimum stake requirement. /// * There is a failure in staking or unstaking logic. /// @@ -121,7 +122,8 @@ impl Pallet { pub fn do_transfer_stake( origin: T::RuntimeOrigin, destination_coldkey: T::AccountId, - hotkey: T::AccountId, + origin_hotkey: T::AccountId, + destination_hotkey: T::AccountId, origin_netuid: NetUid, destination_netuid: NetUid, alpha_amount: AlphaCurrency, @@ -133,8 +135,8 @@ impl Pallet { let tao_moved = Self::transition_stake_internal( &coldkey, &destination_coldkey, - &hotkey, - &hotkey, + &origin_hotkey, + &destination_hotkey, origin_netuid, destination_netuid, alpha_amount, @@ -144,20 +146,21 @@ impl Pallet { false, )?; - // 9. Emit an event for logging/monitoring. + // Emit an event for logging/monitoring. log::debug!( - "StakeTransferred(origin_coldkey: {coldkey:?}, destination_coldkey: {destination_coldkey:?}, hotkey: {hotkey:?}, origin_netuid: {origin_netuid:?}, destination_netuid: {destination_netuid:?}, amount: {tao_moved:?})" + "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:?})" ); Self::deposit_event(Event::StakeTransferred( coldkey, destination_coldkey, - hotkey, + origin_hotkey, + destination_hotkey, origin_netuid, destination_netuid, tao_moved, )); - // 10. Return success. + // Return success. Ok(()) } diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs index dfd9927da4..aad6829d0c 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -925,6 +925,7 @@ fn test_do_transfer_success() { RuntimeOrigin::signed(origin_coldkey), destination_coldkey, hotkey, + hotkey, netuid, netuid, alpha @@ -965,6 +966,7 @@ fn test_do_transfer_nonexistent_subnet() { RuntimeOrigin::signed(origin_coldkey), destination_coldkey, hotkey, + hotkey, nonexistent_netuid, nonexistent_netuid, stake_amount.into() @@ -990,6 +992,7 @@ fn test_do_transfer_nonexistent_hotkey() { RuntimeOrigin::signed(origin_coldkey), destination_coldkey, nonexistent_hotkey, + nonexistent_hotkey, netuid, netuid, 100.into() @@ -1030,6 +1033,7 @@ fn test_do_transfer_insufficient_stake() { RuntimeOrigin::signed(origin_coldkey), destination_coldkey, hotkey, + hotkey, netuid, netuid, alpha.into() @@ -1069,6 +1073,7 @@ fn test_do_transfer_wrong_origin() { RuntimeOrigin::signed(wrong_coldkey), destination_coldkey, hotkey, + hotkey, netuid, netuid, stake_amount.into() @@ -1107,6 +1112,7 @@ fn test_do_transfer_minimum_stake_check() { RuntimeOrigin::signed(origin_coldkey), destination_coldkey, hotkey, + hotkey, netuid, netuid, 1.into() @@ -1164,6 +1170,7 @@ fn test_do_transfer_different_subnets() { RuntimeOrigin::signed(origin_coldkey), destination_coldkey, hotkey, + hotkey, origin_netuid, destination_netuid, alpha @@ -1192,6 +1199,84 @@ fn test_do_transfer_different_subnets() { }); } +#[test] +fn test_do_transfer_to_different_hotkey() { + new_test_ext(1).execute_with(|| { + // 1. Setup: Create a subnet and two hotkeys + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let origin_coldkey = U256::from(1); + let destination_coldkey = U256::from(2); + let origin_hotkey = U256::from(3); + let destination_hotkey = U256::from(4); + let stake_amount = DefaultMinStake::::get().to_u64() * 10; + + // 2. Create accounts for both hotkeys + SubtensorModule::create_account_if_non_existent(&origin_coldkey, &origin_hotkey); + SubtensorModule::create_account_if_non_existent(&destination_coldkey, &destination_hotkey); + + // 3. Stake into the origin hotkey + SubtensorModule::stake_into_subnet( + &origin_hotkey, + &origin_coldkey, + netuid, + stake_amount.into(), + ::SwapInterface::max_price(), + false, + false, + ) + .unwrap(); + + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &origin_coldkey, + netuid, + ); + + // 4. Transfer stake to a DIFFERENT coldkey AND DIFFERENT hotkey + assert_ok!(SubtensorModule::do_transfer_stake( + RuntimeOrigin::signed(origin_coldkey), + destination_coldkey, + origin_hotkey, + destination_hotkey, + netuid, + netuid, + alpha + )); + + // 5. Verify origin coldkey + origin hotkey has no stake + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &origin_coldkey, + netuid + ), + AlphaCurrency::ZERO + ); + + // 6. Verify destination coldkey + destination hotkey has the stake + assert!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &destination_hotkey, + &destination_coldkey, + netuid + ) > AlphaCurrency::ZERO + ); + + // 7. Verify origin coldkey + destination hotkey has NO stake (stake went to destination coldkey) + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &destination_hotkey, + &origin_coldkey, + netuid + ), + AlphaCurrency::ZERO + ); + }); +} + #[test] fn test_do_swap_success() { new_test_ext(1).execute_with(|| { @@ -1819,6 +1904,7 @@ fn test_transfer_stake_rate_limited() { RuntimeOrigin::signed(origin_coldkey), destination_coldkey, hotkey, + hotkey, netuid, netuid, alpha @@ -1863,6 +1949,7 @@ fn test_transfer_stake_doesnt_limit_destination_coldkey() { RuntimeOrigin::signed(origin_coldkey), destination_coldkey, hotkey, + hotkey, netuid, netuid2, alpha diff --git a/pallets/subtensor/src/tests/subnet.rs b/pallets/subtensor/src/tests/subnet.rs index d2a73a919d..6d9e246528 100644 --- a/pallets/subtensor/src/tests/subnet.rs +++ b/pallets/subtensor/src/tests/subnet.rs @@ -538,6 +538,7 @@ fn test_subtoken_enable_reject_trading_before_enable() { RuntimeOrigin::signed(coldkey_account_id), hotkey_account_id, hotkey_account_2_id, + hotkey_account_2_id, netuid, netuid2, amount.into(), @@ -659,6 +660,7 @@ fn test_subtoken_enable_trading_ok_with_enable() { RuntimeOrigin::signed(coldkey_account_id), hotkey_account_id, hotkey_account_2_id, + hotkey_account_2_id, netuid, netuid2, unstake_amount, diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 9d3bdbfc62..a55f37fb66 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -2335,7 +2335,8 @@ fn test_coldkey_in_swap_schedule_prevents_funds_usage() { // Transfer stake let call = RuntimeCall::SubtensorModule(SubtensorCall::transfer_stake { destination_coldkey: new_coldkey, - hotkey, + origin_hotkey: hotkey, + destination_hotkey: hotkey, origin_netuid: netuid, destination_netuid: netuid, alpha_amount: stake.into(), diff --git a/pallets/transaction-fee/src/lib.rs b/pallets/transaction-fee/src/lib.rs index fc2a16a409..3c667a693d 100644 --- a/pallets/transaction-fee/src/lib.rs +++ b/pallets/transaction-fee/src/lib.rs @@ -264,10 +264,11 @@ impl SubtensorTxFeeHandler { }) => alpha_vec.push((origin_hotkey.clone(), *origin_netuid)), Some(SubtensorCall::transfer_stake { destination_coldkey: _, - hotkey, + origin_hotkey, + destination_hotkey: _, origin_netuid, .. - }) => alpha_vec.push((hotkey.clone(), *origin_netuid)), + }) => alpha_vec.push((origin_hotkey.clone(), *origin_netuid)), Some(SubtensorCall::swap_stake { hotkey, origin_netuid, diff --git a/pallets/transaction-fee/src/tests/mod.rs b/pallets/transaction-fee/src/tests/mod.rs index b6697e87f0..2a5fa6e8ed 100644 --- a/pallets/transaction-fee/src/tests/mod.rs +++ b/pallets/transaction-fee/src/tests/mod.rs @@ -901,7 +901,8 @@ fn test_transfer_stake_fees_alpha() { ); let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::transfer_stake { destination_coldkey, - hotkey: sn.hotkeys[0], + origin_hotkey: sn.hotkeys[0], + destination_hotkey: sn.hotkeys[0], origin_netuid: sn.subnets[0].netuid, destination_netuid: sn.subnets[1].netuid, alpha_amount: unstake_amount, diff --git a/precompiles/src/solidity/stakingV2.sol b/precompiles/src/solidity/stakingV2.sol index fefca82fd9..0bdcd80b38 100644 --- a/precompiles/src/solidity/stakingV2.sol +++ b/precompiles/src/solidity/stakingV2.sol @@ -80,7 +80,7 @@ interface IStaking { /** * @dev Transfer a subtensor stake `amount` associated with the transaction signer to a different coldkey - * `destination_coldkey`. + * `destination_coldkey` while keeping the same hotkey. * * This function allows external accounts and contracts to transfer staked TAO to another coldkey, * which effectively calls `transfer_stake` on the subtensor pallet with specified destination @@ -95,7 +95,7 @@ interface IStaking { * @param amount The amount to move in rao. * * Requirements: - * - `origin_hotkey` and `destination_hotkey` must be valid hotkeys registered on the network, ensuring + * - `hotkey` must be a valid hotkey registered on the network, ensuring * that the stake is correctly attributed. */ function transferStake( @@ -106,6 +106,36 @@ interface IStaking { uint256 amount ) external payable; + /** + * @dev Transfer a subtensor stake `amount` associated with the transaction signer to a different coldkey + * `destination_coldkey` and optionally a different hotkey `destination_hotkey`. + * + * This function allows external accounts and contracts to transfer staked TAO to another coldkey and hotkey, + * which effectively calls `transfer_stake` on the subtensor pallet with specified destination + * coldkey and hotkey as parameters being the hashed address mapping of H160 sender address to Substrate ss58 + * address as implemented in Frontier HashedAddressMapping: + * https://github.com/polkadot-evm/frontier/blob/2e219e17a526125da003e64ef22ec037917083fa/frame/evm/src/lib.rs#L739 + * + * @param destination_coldkey The destination coldkey public key (32 bytes). + * @param origin_hotkey The origin hotkey public key (32 bytes). + * @param destination_hotkey The destination hotkey public key (32 bytes). + * @param origin_netuid The subnet to move stake from (uint256). + * @param destination_netuid The subnet to move stake to (uint256). + * @param amount The amount to move in rao. + * + * Requirements: + * - `origin_hotkey` and `destination_hotkey` must be valid hotkeys registered on the network, ensuring + * that the stake is correctly attributed. + */ + function transferStakeV2( + bytes32 destination_coldkey, + bytes32 origin_hotkey, + bytes32 destination_hotkey, + uint256 origin_netuid, + uint256 destination_netuid, + uint256 amount + ) external payable; + /** * @dev Returns the amount of RAO staked by the coldkey. * diff --git a/precompiles/src/staking.rs b/precompiles/src/staking.rs index 35daaf4f47..90075f14d1 100644 --- a/precompiles/src/staking.rs +++ b/precompiles/src/staking.rs @@ -190,6 +190,7 @@ where handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) } + /// Backward-compatible transferStake with 5 parameters (same hotkey for origin and destination) #[precompile::public("transferStake(bytes32,bytes32,uint256,uint256,uint256)")] #[precompile::payable] fn transfer_stake( @@ -208,7 +209,39 @@ where let alpha_amount: u64 = amount_alpha.unique_saturated_into(); let call = pallet_subtensor::Call::::transfer_stake { destination_coldkey, - hotkey, + origin_hotkey: hotkey.clone(), + destination_hotkey: hotkey, + origin_netuid: origin_netuid.into(), + destination_netuid: destination_netuid.into(), + alpha_amount: alpha_amount.into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + /// New transferStakeV2 with 6 parameters (allows different origin and destination hotkeys) + #[precompile::public("transferStakeV2(bytes32,bytes32,bytes32,uint256,uint256,uint256)")] + #[precompile::payable] + fn transfer_stake_v2( + handle: &mut impl PrecompileHandle, + destination_coldkey: H256, + origin_hotkey: H256, + destination_hotkey: H256, + origin_netuid: U256, + destination_netuid: U256, + amount_alpha: U256, + ) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let destination_coldkey = R::AccountId::from(destination_coldkey.0); + let origin_hotkey = R::AccountId::from(origin_hotkey.0); + let destination_hotkey = R::AccountId::from(destination_hotkey.0); + let origin_netuid = try_u16_from_u256(origin_netuid)?; + let destination_netuid = try_u16_from_u256(destination_netuid)?; + let alpha_amount: u64 = amount_alpha.unique_saturated_into(); + let call = pallet_subtensor::Call::::transfer_stake { + destination_coldkey, + origin_hotkey, + destination_hotkey, origin_netuid: origin_netuid.into(), destination_netuid: destination_netuid.into(), alpha_amount: alpha_amount.into(),