diff --git a/chain-extensions/src/lib.rs b/chain-extensions/src/lib.rs index 14ea23d9c8..7f9694c654 100644 --- a/chain-extensions/src/lib.rs +++ b/chain-extensions/src/lib.rs @@ -61,9 +61,524 @@ where + pallet_subtensor_swap::Config, T::AccountId: Clone, { + fn dispatch_add_stake_v1( + env: &mut Env, + origin: RawOrigin, + ) -> Result + where + Env: SubtensorExtensionEnv, + <::Lookup as StaticLookup>::Source: From<::AccountId>, + { + let weight = Weight::from_parts(340_800_000, 0) + .saturating_add(T::DbWeight::get().reads(24_u64)) + .saturating_add(T::DbWeight::get().writes(15)); + + env.charge_weight(weight)?; + + let (hotkey, netuid, amount_staked): (T::AccountId, NetUid, TaoBalance) = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let call_result = + pallet_subtensor::Pallet::::add_stake(origin.into(), hotkey, netuid, amount_staked); + + 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)) + } + } + } + + fn dispatch_remove_stake_v1( + env: &mut Env, + origin: RawOrigin, + ) -> Result + where + Env: SubtensorExtensionEnv, + <::Lookup as StaticLookup>::Source: From<::AccountId>, + { + let weight = Weight::from_parts(196_800_000, 0) + .saturating_add(T::DbWeight::get().reads(19)) + .saturating_add(T::DbWeight::get().writes(10)); + + env.charge_weight(weight)?; + + let (hotkey, netuid, amount_unstaked): (T::AccountId, NetUid, AlphaBalance) = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let call_result = pallet_subtensor::Pallet::::remove_stake( + origin.into(), + hotkey, + netuid, + amount_unstaked, + ); + + 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)) + } + } + } + + fn dispatch_unstake_all_v1( + env: &mut Env, + origin: RawOrigin, + ) -> Result + where + Env: SubtensorExtensionEnv, + <::Lookup as StaticLookup>::Source: From<::AccountId>, + { + let weight = Weight::from_parts(28_830_000, 0) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(0)); + + env.charge_weight(weight)?; + + let hotkey: T::AccountId = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let call_result = pallet_subtensor::Pallet::::unstake_all(origin.into(), hotkey); + + 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)) + } + } + } + + fn dispatch_unstake_all_alpha_v1( + env: &mut Env, + origin: RawOrigin, + ) -> Result + where + Env: SubtensorExtensionEnv, + <::Lookup as StaticLookup>::Source: From<::AccountId>, + { + let weight = Weight::from_parts(358_500_000, 0) + .saturating_add(T::DbWeight::get().reads(36_u64)) + .saturating_add(T::DbWeight::get().writes(21_u64)); + + env.charge_weight(weight)?; + + let hotkey: T::AccountId = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let call_result = pallet_subtensor::Pallet::::unstake_all_alpha(origin.into(), hotkey); + + 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)) + } + } + } + + fn dispatch_move_stake_v1( + env: &mut Env, + origin: RawOrigin, + ) -> Result + where + Env: SubtensorExtensionEnv, + <::Lookup as StaticLookup>::Source: From<::AccountId>, + { + let weight = Weight::from_parts(164_300_000, 0) + .saturating_add(T::DbWeight::get().reads(15_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)); + + env.charge_weight(weight)?; + + let (origin_hotkey, destination_hotkey, origin_netuid, destination_netuid, alpha_amount): ( + T::AccountId, + T::AccountId, + NetUid, + NetUid, + AlphaBalance, + ) = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let call_result = pallet_subtensor::Pallet::::move_stake( + origin.into(), + 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)) + } + } + } + + fn dispatch_transfer_stake_v1( + env: &mut Env, + origin: RawOrigin, + ) -> Result + where + Env: SubtensorExtensionEnv, + <::Lookup as StaticLookup>::Source: From<::AccountId>, + { + let weight = Weight::from_parts(160_300_000, 0) + .saturating_add(T::DbWeight::get().reads(13_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)); + + env.charge_weight(weight)?; + + let (destination_coldkey, hotkey, origin_netuid, destination_netuid, alpha_amount): ( + T::AccountId, + T::AccountId, + NetUid, + NetUid, + AlphaBalance, + ) = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let call_result = pallet_subtensor::Pallet::::transfer_stake( + origin.into(), + destination_coldkey, + 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)) + } + } + } + + fn dispatch_swap_stake_v1( + env: &mut Env, + origin: RawOrigin, + ) -> Result + where + Env: SubtensorExtensionEnv, + <::Lookup as StaticLookup>::Source: From<::AccountId>, + { + let weight = Weight::from_parts(351_300_000, 0) + .saturating_add(T::DbWeight::get().reads(35_u64)) + .saturating_add(T::DbWeight::get().writes(22_u64)); + + env.charge_weight(weight)?; + + let (hotkey, origin_netuid, destination_netuid, alpha_amount): ( + T::AccountId, + NetUid, + NetUid, + AlphaBalance, + ) = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let call_result = pallet_subtensor::Pallet::::swap_stake( + origin.into(), + 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)) + } + } + } + + fn dispatch_add_stake_limit_v1( + env: &mut Env, + origin: RawOrigin, + ) -> Result + where + Env: SubtensorExtensionEnv, + <::Lookup as StaticLookup>::Source: From<::AccountId>, + { + let weight = Weight::from_parts(402_900_000, 0) + .saturating_add(T::DbWeight::get().reads(24_u64)) + .saturating_add(T::DbWeight::get().writes(15)); + + env.charge_weight(weight)?; + + let (hotkey, netuid, amount_staked, limit_price, allow_partial): ( + T::AccountId, + NetUid, + TaoBalance, + TaoBalance, + bool, + ) = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let call_result = pallet_subtensor::Pallet::::add_stake_limit( + origin.into(), + hotkey, + netuid, + amount_staked, + limit_price, + allow_partial, + ); + + 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)) + } + } + } + + fn dispatch_remove_stake_limit_v1( + env: &mut Env, + origin: RawOrigin, + ) -> Result + where + Env: SubtensorExtensionEnv, + <::Lookup as StaticLookup>::Source: From<::AccountId>, + { + let weight = Weight::from_parts(377_400_000, 0) + .saturating_add(T::DbWeight::get().reads(28_u64)) + .saturating_add(T::DbWeight::get().writes(14)); + + env.charge_weight(weight)?; + + let (hotkey, netuid, amount_unstaked, limit_price, allow_partial): ( + T::AccountId, + NetUid, + AlphaBalance, + TaoBalance, + bool, + ) = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let call_result = pallet_subtensor::Pallet::::remove_stake_limit( + origin.into(), + hotkey, + netuid, + amount_unstaked, + limit_price, + allow_partial, + ); + + 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)) + } + } + } + + fn dispatch_swap_stake_limit_v1( + env: &mut Env, + origin: RawOrigin, + ) -> Result + where + Env: SubtensorExtensionEnv, + <::Lookup as StaticLookup>::Source: From<::AccountId>, + { + let weight = Weight::from_parts(411_500_000, 0) + .saturating_add(T::DbWeight::get().reads(35_u64)) + .saturating_add(T::DbWeight::get().writes(22_u64)); + + env.charge_weight(weight)?; + + let ( + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + limit_price, + allow_partial, + ): (T::AccountId, NetUid, NetUid, AlphaBalance, TaoBalance, bool) = + env.read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let call_result = pallet_subtensor::Pallet::::swap_stake_limit( + origin.into(), + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + limit_price, + allow_partial, + ); + + 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)) + } + } + } + + fn dispatch_remove_stake_full_limit_v1( + env: &mut Env, + origin: RawOrigin, + ) -> Result + where + Env: SubtensorExtensionEnv, + <::Lookup as StaticLookup>::Source: From<::AccountId>, + { + let weight = Weight::from_parts(395_300_000, 0) + .saturating_add(T::DbWeight::get().reads(28_u64)) + .saturating_add(T::DbWeight::get().writes(14_u64)); + + env.charge_weight(weight)?; + + let (hotkey, netuid, limit_price): (T::AccountId, NetUid, Option) = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let call_result = pallet_subtensor::Pallet::::remove_stake_full_limit( + origin.into(), + hotkey, + netuid, + limit_price, + ); + + 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)) + } + } + } + + fn dispatch_set_coldkey_auto_stake_hotkey_v1( + env: &mut Env, + origin: RawOrigin, + ) -> Result + where + Env: SubtensorExtensionEnv, + <::Lookup as StaticLookup>::Source: From<::AccountId>, + { + let weight = Weight::from_parts(29_930_000, 0) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)); + + env.charge_weight(weight)?; + + let (netuid, hotkey): (NetUid, T::AccountId) = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let call_result = pallet_subtensor::Pallet::::set_coldkey_auto_stake_hotkey( + origin.into(), + netuid, + hotkey, + ); + + 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)) + } + } + } + + fn dispatch_add_proxy_v1( + env: &mut Env, + origin: RawOrigin, + ) -> Result + where + Env: SubtensorExtensionEnv, + <::Lookup as StaticLookup>::Source: From<::AccountId>, + { + let weight = ::WeightInfo::add_proxy( + ::MaxProxies::get(), + ); + + env.charge_weight(weight)?; + + let delegate: T::AccountId = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let delegate_lookup = + <::Lookup as StaticLookup>::Source::from(delegate); + + let call_result = pallet_proxy::Pallet::::add_proxy( + origin.into(), + delegate_lookup, + ProxyType::Staking, + 0u32.into(), + ); + + 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)) + } + } + } + + fn dispatch_remove_proxy_v1( + env: &mut Env, + origin: RawOrigin, + ) -> Result + where + Env: SubtensorExtensionEnv, + <::Lookup as StaticLookup>::Source: From<::AccountId>, + { + let weight = ::WeightInfo::remove_proxy( + ::MaxProxies::get(), + ); + + env.charge_weight(weight)?; + + let delegate: T::AccountId = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let delegate_lookup = + <::Lookup as StaticLookup>::Source::from(delegate); + + let call_result = pallet_proxy::Pallet::::remove_proxy( + origin.into(), + delegate_lookup, + ProxyType::Staking, + 0u32.into(), + ); + + 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)) + } + } + } + fn dispatch(env: &mut Env) -> Result where - Env: SubtensorExtensionEnv, + Env: SubtensorExtensionEnv, <::Lookup as StaticLookup>::Source: From<::AccountId>, { let func_id: FunctionId = env.func_id().try_into().map_err(|_| { @@ -91,417 +606,118 @@ where Ok(RetVal::Converging(Output::Success as u32)) } FunctionId::AddStakeV1 => { - let weight = Weight::from_parts(340_800_000, 0) - .saturating_add(T::DbWeight::get().reads(24_u64)) - .saturating_add(T::DbWeight::get().writes(15)); - - env.charge_weight(weight)?; - - let (hotkey, netuid, amount_staked): (T::AccountId, NetUid, TaoBalance) = env - .read_as() - .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; - - let call_result = pallet_subtensor::Pallet::::add_stake( - RawOrigin::Signed(env.caller()).into(), - hotkey, - netuid, - amount_staked, - ); - - 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)) - } - } + let origin = RawOrigin::Signed(env.caller()); + Self::dispatch_add_stake_v1(env, origin) } - FunctionId::RemoveStakeV1 => { - let weight = Weight::from_parts(196_800_000, 0) - .saturating_add(T::DbWeight::get().reads(19)) - .saturating_add(T::DbWeight::get().writes(10)); - - env.charge_weight(weight)?; - let (hotkey, netuid, amount_unstaked): (T::AccountId, NetUid, AlphaBalance) = env - .read_as() - .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + FunctionId::CallerAddStakeV1 => { + let origin = convert_origin(env.origin()); + Self::dispatch_add_stake_v1(env, origin) + } - let call_result = pallet_subtensor::Pallet::::remove_stake( - RawOrigin::Signed(env.caller()).into(), - hotkey, - netuid, - amount_unstaked, - ); - - 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::RemoveStakeV1 => { + let origin = RawOrigin::Signed(env.caller()); + Self::dispatch_remove_stake_v1(env, origin) + } + FunctionId::CallerRemoveStakeV1 => { + let origin = convert_origin(env.origin()); + Self::dispatch_remove_stake_v1(env, origin) } FunctionId::UnstakeAllV1 => { - let weight = Weight::from_parts(28_830_000, 0) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(0)); - - env.charge_weight(weight)?; - - let hotkey: T::AccountId = env - .read_as() - .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; - - let call_result = pallet_subtensor::Pallet::::unstake_all( - RawOrigin::Signed(env.caller()).into(), - hotkey, - ); - - 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)) - } - } + let origin = RawOrigin::Signed(env.caller()); + Self::dispatch_unstake_all_v1(env, origin) + } + FunctionId::CallerUnstakeAllV1 => { + let origin = convert_origin(env.origin()); + Self::dispatch_unstake_all_v1(env, origin) } FunctionId::UnstakeAllAlphaV1 => { - let weight = Weight::from_parts(358_500_000, 0) - .saturating_add(T::DbWeight::get().reads(36_u64)) - .saturating_add(T::DbWeight::get().writes(21_u64)); - - env.charge_weight(weight)?; - - let hotkey: T::AccountId = env - .read_as() - .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; - - let call_result = pallet_subtensor::Pallet::::unstake_all_alpha( - RawOrigin::Signed(env.caller()).into(), - hotkey, - ); - - 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)) - } - } + let origin = RawOrigin::Signed(env.caller()); + Self::dispatch_unstake_all_alpha_v1(env, origin) + } + FunctionId::CallerUnstakeAllAlphaV1 => { + let origin = convert_origin(env.origin()); + Self::dispatch_unstake_all_alpha_v1(env, origin) } FunctionId::MoveStakeV1 => { - let weight = Weight::from_parts(164_300_000, 0) - .saturating_add(T::DbWeight::get().reads(15_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)); - - env.charge_weight(weight)?; - - let ( - origin_hotkey, - destination_hotkey, - origin_netuid, - destination_netuid, - alpha_amount, - ): (T::AccountId, T::AccountId, NetUid, NetUid, AlphaBalance) = env - .read_as() - .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; - - let call_result = pallet_subtensor::Pallet::::move_stake( - RawOrigin::Signed(env.caller()).into(), - 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)) - } - } + let origin = RawOrigin::Signed(env.caller()); + Self::dispatch_move_stake_v1(env, origin) + } + FunctionId::CallerMoveStakeV1 => { + let origin = convert_origin(env.origin()); + Self::dispatch_move_stake_v1(env, origin) } FunctionId::TransferStakeV1 => { - let weight = Weight::from_parts(160_300_000, 0) - .saturating_add(T::DbWeight::get().reads(13_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)); - - env.charge_weight(weight)?; - - let (destination_coldkey, hotkey, origin_netuid, destination_netuid, alpha_amount): ( - T::AccountId, - T::AccountId, - NetUid, - NetUid, - AlphaBalance, - ) = 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, - 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)) - } - } + let origin = RawOrigin::Signed(env.caller()); + Self::dispatch_transfer_stake_v1(env, origin) + } + FunctionId::CallerTransferStakeV1 => { + let origin = convert_origin(env.origin()); + Self::dispatch_transfer_stake_v1(env, origin) } FunctionId::SwapStakeV1 => { - let weight = Weight::from_parts(351_300_000, 0) - .saturating_add(T::DbWeight::get().reads(35_u64)) - .saturating_add(T::DbWeight::get().writes(22_u64)); - - env.charge_weight(weight)?; - - let (hotkey, origin_netuid, destination_netuid, alpha_amount): ( - T::AccountId, - NetUid, - NetUid, - AlphaBalance, - ) = env - .read_as() - .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; - - let call_result = pallet_subtensor::Pallet::::swap_stake( - RawOrigin::Signed(env.caller()).into(), - 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)) - } - } + let origin = RawOrigin::Signed(env.caller()); + Self::dispatch_swap_stake_v1(env, origin) + } + FunctionId::CallerSwapStakeV1 => { + let origin = convert_origin(env.origin()); + Self::dispatch_swap_stake_v1(env, origin) } FunctionId::AddStakeLimitV1 => { - let weight = Weight::from_parts(402_900_000, 0) - .saturating_add(T::DbWeight::get().reads(24_u64)) - .saturating_add(T::DbWeight::get().writes(15)); - - env.charge_weight(weight)?; - - let (hotkey, netuid, amount_staked, limit_price, allow_partial): ( - T::AccountId, - NetUid, - TaoBalance, - TaoBalance, - bool, - ) = env - .read_as() - .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; - - let call_result = pallet_subtensor::Pallet::::add_stake_limit( - RawOrigin::Signed(env.caller()).into(), - hotkey, - netuid, - amount_staked, - limit_price, - allow_partial, - ); - - 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)) - } - } + let origin = RawOrigin::Signed(env.caller()); + Self::dispatch_add_stake_limit_v1(env, origin) + } + FunctionId::CallerAddStakeLimitV1 => { + let origin = convert_origin(env.origin()); + Self::dispatch_add_stake_limit_v1(env, origin) } FunctionId::RemoveStakeLimitV1 => { - let weight = Weight::from_parts(377_400_000, 0) - .saturating_add(T::DbWeight::get().reads(28_u64)) - .saturating_add(T::DbWeight::get().writes(14)); - - env.charge_weight(weight)?; - - let (hotkey, netuid, amount_unstaked, limit_price, allow_partial): ( - T::AccountId, - NetUid, - AlphaBalance, - TaoBalance, - bool, - ) = env - .read_as() - .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; - - let call_result = pallet_subtensor::Pallet::::remove_stake_limit( - RawOrigin::Signed(env.caller()).into(), - hotkey, - netuid, - amount_unstaked, - limit_price, - allow_partial, - ); - - 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)) - } - } + let origin = RawOrigin::Signed(env.caller()); + Self::dispatch_remove_stake_limit_v1(env, origin) + } + FunctionId::CallerRemoveStakeLimitV1 => { + let origin = convert_origin(env.origin()); + Self::dispatch_remove_stake_limit_v1(env, origin) } FunctionId::SwapStakeLimitV1 => { - let weight = Weight::from_parts(411_500_000, 0) - .saturating_add(T::DbWeight::get().reads(35_u64)) - .saturating_add(T::DbWeight::get().writes(22_u64)); - - env.charge_weight(weight)?; - - let ( - hotkey, - origin_netuid, - destination_netuid, - alpha_amount, - limit_price, - allow_partial, - ): (T::AccountId, NetUid, NetUid, AlphaBalance, TaoBalance, bool) = - env.read_as() - .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; - - let call_result = pallet_subtensor::Pallet::::swap_stake_limit( - RawOrigin::Signed(env.caller()).into(), - hotkey, - origin_netuid, - destination_netuid, - alpha_amount, - limit_price, - allow_partial, - ); - - 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)) - } - } + let origin = RawOrigin::Signed(env.caller()); + Self::dispatch_swap_stake_limit_v1(env, origin) + } + FunctionId::CallerSwapStakeLimitV1 => { + let origin = convert_origin(env.origin()); + Self::dispatch_swap_stake_limit_v1(env, origin) } FunctionId::RemoveStakeFullLimitV1 => { - let weight = Weight::from_parts(395_300_000, 0) - .saturating_add(T::DbWeight::get().reads(28_u64)) - .saturating_add(T::DbWeight::get().writes(14_u64)); - - env.charge_weight(weight)?; - - let (hotkey, netuid, limit_price): (T::AccountId, NetUid, Option) = env - .read_as() - .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; - - let call_result = pallet_subtensor::Pallet::::remove_stake_full_limit( - RawOrigin::Signed(env.caller()).into(), - hotkey, - netuid, - limit_price, - ); - - 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)) - } - } + let origin = RawOrigin::Signed(env.caller()); + Self::dispatch_remove_stake_full_limit_v1(env, origin) + } + FunctionId::CallerRemoveStakeFullLimitV1 => { + let origin = convert_origin(env.origin()); + Self::dispatch_remove_stake_full_limit_v1(env, origin) } FunctionId::SetColdkeyAutoStakeHotkeyV1 => { - let weight = Weight::from_parts(29_930_000, 0) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)); - - env.charge_weight(weight)?; - - let (netuid, hotkey): (NetUid, T::AccountId) = env - .read_as() - .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; - - let call_result = pallet_subtensor::Pallet::::set_coldkey_auto_stake_hotkey( - RawOrigin::Signed(env.caller()).into(), - netuid, - hotkey, - ); - - 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)) - } - } + let origin = RawOrigin::Signed(env.caller()); + Self::dispatch_set_coldkey_auto_stake_hotkey_v1(env, origin) + } + FunctionId::CallerSetColdkeyAutoStakeHotkeyV1 => { + let origin = convert_origin(env.origin()); + Self::dispatch_set_coldkey_auto_stake_hotkey_v1(env, origin) } FunctionId::AddProxyV1 => { - let weight = ::WeightInfo::add_proxy( - ::MaxProxies::get(), - ); - - env.charge_weight(weight)?; - - let delegate: T::AccountId = env - .read_as() - .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; - - let delegate_lookup = - <::Lookup as StaticLookup>::Source::from(delegate); - - let call_result = pallet_proxy::Pallet::::add_proxy( - RawOrigin::Signed(env.caller()).into(), - delegate_lookup, - ProxyType::Staking, - 0u32.into(), - ); - - 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)) - } - } + let origin = RawOrigin::Signed(env.caller()); + Self::dispatch_add_proxy_v1(env, origin) + } + FunctionId::CallerAddProxyV1 => { + let origin = convert_origin(env.origin()); + Self::dispatch_add_proxy_v1(env, origin) } FunctionId::RemoveProxyV1 => { - let weight = ::WeightInfo::remove_proxy( - ::MaxProxies::get(), - ); - - env.charge_weight(weight)?; - - let delegate: T::AccountId = env - .read_as() - .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; - - let delegate_lookup = - <::Lookup as StaticLookup>::Source::from(delegate); - - let call_result = pallet_proxy::Pallet::::remove_proxy( - RawOrigin::Signed(env.caller()).into(), - delegate_lookup, - ProxyType::Staking, - 0u32.into(), - ); - - 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)) - } - } + let origin = RawOrigin::Signed(env.caller()); + Self::dispatch_remove_proxy_v1(env, origin) + } + FunctionId::CallerRemoveProxyV1 => { + let origin = convert_origin(env.origin()); + Self::dispatch_remove_proxy_v1(env, origin) } FunctionId::GetAlphaPriceV1 => { let netuid: NetUid = env @@ -527,12 +743,28 @@ where } } -trait SubtensorExtensionEnv { +// Convert from the contract origin to the raw origin +fn convert_origin(origin: pallet_contracts::Origin) -> RawOrigin +where + T: pallet_contracts::Config, +{ + match origin { + pallet_contracts::Origin::Signed(caller) => RawOrigin::Signed(caller), + pallet_contracts::Origin::Root => RawOrigin::Root, + } +} + +trait SubtensorExtensionEnv +where + T: pallet_contracts::Config, +{ fn func_id(&self) -> u16; fn charge_weight(&mut self, weight: Weight) -> Result<(), DispatchError>; - fn read_as(&mut self) -> Result; + fn read_as(&mut self) -> Result; fn write_output(&mut self, data: &[u8]) -> Result<(), DispatchError>; - fn caller(&mut self) -> AccountId; + fn caller(&mut self) -> T::AccountId; + #[allow(dead_code)] + fn origin(&mut self) -> pallet_contracts::Origin; } struct ContractsEnvAdapter<'a, 'b, T, E> @@ -558,7 +790,7 @@ where } } -impl<'a, 'b, T, E> SubtensorExtensionEnv for ContractsEnvAdapter<'a, 'b, T, E> +impl<'a, 'b, T, E> SubtensorExtensionEnv for ContractsEnvAdapter<'a, 'b, T, E> where T: pallet_subtensor::Config + pallet_contracts::Config, T::AccountId: Clone, @@ -583,4 +815,8 @@ where fn caller(&mut self) -> T::AccountId { self.env.ext().address().clone() } + + fn origin(&mut self) -> pallet_contracts::Origin { + self.env.ext().caller() + } } diff --git a/chain-extensions/src/tests.rs b/chain-extensions/src/tests.rs index b8956e8659..0f218c546e 100644 --- a/chain-extensions/src/tests.rs +++ b/chain-extensions/src/tests.rs @@ -752,7 +752,7 @@ impl MockEnv { } } -impl SubtensorExtensionEnv for MockEnv { +impl SubtensorExtensionEnv for MockEnv { fn func_id(&self) -> u16 { self.func_id } @@ -769,8 +769,8 @@ impl SubtensorExtensionEnv for MockEnv { Ok(()) } - fn read_as(&mut self) -> Result { - T::decode(&mut &self.input[..]).map_err(|_| DispatchError::Other("mock env decode failure")) + fn read_as(&mut self) -> Result { + U::decode(&mut &self.input[..]).map_err(|_| DispatchError::Other("mock env decode failure")) } fn write_output(&mut self, data: &[u8]) -> Result<(), DispatchError> { @@ -782,6 +782,10 @@ impl SubtensorExtensionEnv for MockEnv { fn caller(&mut self) -> AccountId { self.caller } + + fn origin(&mut self) -> pallet_contracts::Origin { + pallet_contracts::Origin::Signed(self.caller) + } } fn assert_success(ret: RetVal) { @@ -1005,3 +1009,827 @@ fn get_alpha_price_returns_encoded_price() { ); }); } + +/// `Caller*` dispatch uses `env.origin()` via `convert_origin`; with [`MockEnv`] both match +/// `Signed(caller)`, so outcomes align with non-`Caller` arms. Weight expectations match the shared +/// `dispatch_*_v1` helpers used by each pair. +mod caller_dispatch_tests { + use super::*; + + #[test] + fn caller_add_stake_success_updates_stake_and_returns_success_code() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(1); + let owner_coldkey = U256::from(2); + let coldkey = U256::from(10101); + let hotkey = U256::from(10202); + let min_stake = DefaultMinStake::::get(); + let amount_raw = min_stake.to_u64().saturating_mul(10); + let amount: TaoBalance = amount_raw.into(); + + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + mock::setup_reserves( + netuid, + (amount_raw * 1_000_000).into(), + AlphaBalance::from(amount_raw * 10_000_000), + ); + mock::register_ok_neuron(netuid, hotkey, coldkey, 0); + + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &coldkey, + amount_raw.into(), + ); + + let expected_weight = Weight::from_parts(340_800_000, 0) + .saturating_add(::DbWeight::get().reads(24)) + .saturating_add(::DbWeight::get().writes(15)); + + let mut env = MockEnv::new( + FunctionId::CallerAddStakeV1, + coldkey, + (hotkey, netuid, amount).encode(), + ) + .with_expected_weight(expected_weight); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + assert_eq!(env.charged_weight(), Some(expected_weight)); + + let total_stake = + pallet_subtensor::Pallet::::get_total_stake_for_hotkey(&hotkey); + assert!(total_stake > TaoBalance::ZERO); + }); + } + + #[test] + fn caller_remove_stake_with_no_stake_returns_amount_too_low() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(1); + let owner_coldkey = U256::from(2); + let coldkey = U256::from(30301); + let hotkey = U256::from(30302); + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + mock::register_ok_neuron(netuid, hotkey, coldkey, 0); + + let min_stake = DefaultMinStake::::get(); + let amount: AlphaBalance = AlphaBalance::from(min_stake.to_u64()); + + let expected_weight = Weight::from_parts(196_800_000, 0) + .saturating_add(::DbWeight::get().reads(19)) + .saturating_add(::DbWeight::get().writes(10)); + + let mut env = MockEnv::new( + FunctionId::CallerRemoveStakeV1, + coldkey, + (hotkey, netuid, amount).encode(), + ) + .with_expected_weight(expected_weight); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + match ret { + RetVal::Converging(code) => { + assert_eq!(code, Output::AmountTooLow as u32, "mismatched error output") + } + _ => panic!("unexpected return value"), + } + assert_eq!(env.charged_weight(), Some(expected_weight)); + }); + } + + #[test] + fn caller_unstake_all_success_unstakes_balance() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(40001); + let owner_coldkey = U256::from(40002); + let coldkey = U256::from(50001); + let hotkey = U256::from(50002); + let min_stake = DefaultMinStake::::get(); + let stake_amount_raw = min_stake.to_u64().saturating_mul(200); + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + + mock::setup_reserves( + netuid, + stake_amount_raw.saturating_mul(10).into(), + AlphaBalance::from(stake_amount_raw.saturating_mul(20)), + ); + + mock::register_ok_neuron(netuid, hotkey, coldkey, 0); + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &coldkey, + (stake_amount_raw + 1_000_000_000).into(), + ); + + assert_ok!(pallet_subtensor::Pallet::::add_stake( + RawOrigin::Signed(coldkey).into(), + hotkey, + netuid, + stake_amount_raw.into(), + )); + + mock::remove_stake_rate_limit_for_tests(&hotkey, &coldkey, netuid); + + let expected_weight = Weight::from_parts(28_830_000, 0) + .saturating_add(::DbWeight::get().reads(6)) + .saturating_add(::DbWeight::get().writes(0)); + + let pre_balance = pallet_subtensor::Pallet::::get_coldkey_balance(&coldkey); + + let mut env = MockEnv::new(FunctionId::CallerUnstakeAllV1, coldkey, hotkey.encode()) + .with_expected_weight(expected_weight); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + + let remaining_alpha = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, + ); + assert!(remaining_alpha <= AlphaBalance::from(1_000)); + + let post_balance = + pallet_subtensor::Pallet::::get_coldkey_balance(&coldkey); + assert!(post_balance > pre_balance); + }); + } + + #[test] + fn caller_unstake_all_alpha_success_moves_stake_to_root() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(41001); + let owner_coldkey = U256::from(41002); + let coldkey = U256::from(51001); + let hotkey = U256::from(51002); + let min_stake = DefaultMinStake::::get(); + let stake_amount_raw = min_stake.to_u64().saturating_mul(220); + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + + mock::setup_reserves( + netuid, + stake_amount_raw.saturating_mul(20).into(), + AlphaBalance::from(stake_amount_raw.saturating_mul(30)), + ); + + mock::register_ok_neuron(netuid, hotkey, coldkey, 0); + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &coldkey, + (stake_amount_raw + 1_000_000_000).into(), + ); + + assert_ok!(pallet_subtensor::Pallet::::add_stake( + RawOrigin::Signed(coldkey).into(), + hotkey, + netuid, + stake_amount_raw.into(), + )); + + mock::remove_stake_rate_limit_for_tests(&hotkey, &coldkey, netuid); + + let expected_weight = Weight::from_parts(358_500_000, 0) + .saturating_add(::DbWeight::get().reads(36)) + .saturating_add(::DbWeight::get().writes(21)); + + let mut env = MockEnv::new( + FunctionId::CallerUnstakeAllAlphaV1, + coldkey, + hotkey.encode(), + ) + .with_expected_weight(expected_weight); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + + let subnet_alpha = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, + ); + assert!(subnet_alpha <= AlphaBalance::from(1_000)); + + let root_alpha = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + ); + assert!(root_alpha > AlphaBalance::ZERO); + }); + } + + #[test] + fn caller_move_stake_success_moves_alpha_between_hotkeys() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(42001); + let owner_coldkey = U256::from(42002); + let coldkey = U256::from(52001); + let origin_hotkey = U256::from(52002); + let destination_hotkey = U256::from(52003); + + let min_stake = DefaultMinStake::::get(); + let stake_amount_raw = min_stake.to_u64().saturating_mul(240); + + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + mock::setup_reserves( + netuid, + stake_amount_raw.saturating_mul(15).into(), + AlphaBalance::from(stake_amount_raw.saturating_mul(25)), + ); + + mock::register_ok_neuron(netuid, origin_hotkey, coldkey, 0); + mock::register_ok_neuron(netuid, destination_hotkey, coldkey, 1); + + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &coldkey, + (stake_amount_raw + 1_000_000_000).into(), + ); + + assert_ok!(pallet_subtensor::Pallet::::add_stake( + RawOrigin::Signed(coldkey).into(), + origin_hotkey, + netuid, + stake_amount_raw.into(), + )); + + mock::remove_stake_rate_limit_for_tests(&origin_hotkey, &coldkey, netuid); + + let alpha_before = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid, + ); + let alpha_to_move: AlphaBalance = (alpha_before.to_u64() / 2).into(); + + let expected_weight = Weight::from_parts(164_300_000, 0) + .saturating_add(::DbWeight::get().reads(15)) + .saturating_add(::DbWeight::get().writes(7)); + + let mut env = MockEnv::new( + FunctionId::CallerMoveStakeV1, + coldkey, + ( + origin_hotkey, + destination_hotkey, + netuid, + netuid, + alpha_to_move, + ) + .encode(), + ) + .with_expected_weight(expected_weight); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + + let origin_alpha_after = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid, + ); + let destination_alpha_after = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &destination_hotkey, + &coldkey, + netuid, + ); + + assert_eq!(origin_alpha_after, alpha_before - alpha_to_move); + assert_eq!(destination_alpha_after, alpha_to_move); + }); + } + + #[test] + fn caller_transfer_stake_success_moves_between_coldkeys() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(43001); + let owner_coldkey = U256::from(43002); + let origin_coldkey = U256::from(53001); + let destination_coldkey = U256::from(53002); + let hotkey = U256::from(53003); + + let min_stake = DefaultMinStake::::get(); + let stake_amount_raw = min_stake.to_u64().saturating_mul(250); + + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + mock::setup_reserves( + netuid, + stake_amount_raw.saturating_mul(15).into(), + AlphaBalance::from(stake_amount_raw.saturating_mul(25)), + ); + + mock::register_ok_neuron(netuid, hotkey, origin_coldkey, 0); + + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &origin_coldkey, + (stake_amount_raw + 1_000_000_000).into(), + ); + + assert_ok!(pallet_subtensor::Pallet::::add_stake( + RawOrigin::Signed(origin_coldkey).into(), + hotkey, + netuid, + stake_amount_raw.into(), + )); + + mock::remove_stake_rate_limit_for_tests(&hotkey, &origin_coldkey, netuid); + + let alpha_before = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &origin_coldkey, + netuid, + ); + let alpha_to_transfer: AlphaBalance = (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().writes(6)); + + let mut env = MockEnv::new( + FunctionId::CallerTransferStakeV1, + origin_coldkey, + ( + destination_coldkey, + hotkey, + netuid, + netuid, + alpha_to_transfer, + ) + .encode(), + ) + .with_expected_weight(expected_weight); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + + let origin_alpha_after = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &origin_coldkey, + netuid, + ); + let destination_alpha_after = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &destination_coldkey, + netuid, + ); + + assert_eq!(origin_alpha_after, alpha_before - alpha_to_transfer); + assert_eq!(destination_alpha_after, alpha_to_transfer); + }); + } + + #[test] + fn caller_swap_stake_success_moves_between_subnets() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey_a = U256::from(44001); + let owner_coldkey_a = U256::from(44002); + let owner_hotkey_b = U256::from(44003); + let owner_coldkey_b = U256::from(44004); + let coldkey = U256::from(54001); + let hotkey = U256::from(54002); + + let min_stake = DefaultMinStake::::get(); + let stake_amount_raw = min_stake.to_u64().saturating_mul(260); + + let netuid_a = mock::add_dynamic_network(&owner_hotkey_a, &owner_coldkey_a); + let netuid_b = mock::add_dynamic_network(&owner_hotkey_b, &owner_coldkey_b); + + mock::setup_reserves( + netuid_a, + stake_amount_raw.saturating_mul(18).into(), + AlphaBalance::from(stake_amount_raw.saturating_mul(30)), + ); + mock::setup_reserves( + netuid_b, + stake_amount_raw.saturating_mul(20).into(), + AlphaBalance::from(stake_amount_raw.saturating_mul(28)), + ); + + mock::register_ok_neuron(netuid_a, hotkey, coldkey, 0); + mock::register_ok_neuron(netuid_b, hotkey, coldkey, 1); + + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &coldkey, + (stake_amount_raw + 1_000_000_000).into(), + ); + + assert_ok!(pallet_subtensor::Pallet::::add_stake( + RawOrigin::Signed(coldkey).into(), + hotkey, + netuid_a, + stake_amount_raw.into(), + )); + + mock::remove_stake_rate_limit_for_tests(&hotkey, &coldkey, netuid_a); + + let alpha_origin_before = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid_a, + ); + let alpha_destination_before = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid_b, + ); + let alpha_to_swap: AlphaBalance = (alpha_origin_before.to_u64() / 3).into(); + + let expected_weight = Weight::from_parts(351_300_000, 0) + .saturating_add(::DbWeight::get().reads(35)) + .saturating_add(::DbWeight::get().writes(22)); + + let mut env = MockEnv::new( + FunctionId::CallerSwapStakeV1, + coldkey, + (hotkey, netuid_a, netuid_b, alpha_to_swap).encode(), + ) + .with_expected_weight(expected_weight); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + + let alpha_origin_after = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid_a, + ); + let alpha_destination_after = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid_b, + ); + + assert!(alpha_origin_after < alpha_origin_before); + assert!(alpha_destination_after > alpha_destination_before); + }); + } + + #[test] + fn caller_add_stake_limit_success_executes_within_price_guard() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(45001); + let owner_coldkey = U256::from(45002); + let coldkey = U256::from(55001); + let hotkey = U256::from(55002); + let amount_raw: u64 = 900_000_000_000; + let limit_price: TaoBalance = 24_000_000_000u64.into(); + + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + + mock::setup_reserves( + netuid, + TaoBalance::from(150_000_000_000_u64), + AlphaBalance::from(100_000_000_000_u64), + ); + + mock::register_ok_neuron(netuid, hotkey, coldkey, 0); + + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &coldkey, + (amount_raw + 1_000_000_000).into(), + ); + + let stake_before = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, + ); + let balance_before = + pallet_subtensor::Pallet::::get_coldkey_balance(&coldkey); + + let expected_weight = Weight::from_parts(402_900_000, 0) + .saturating_add(::DbWeight::get().reads(24)) + .saturating_add(::DbWeight::get().writes(15)); + + let mut env = MockEnv::new( + FunctionId::CallerAddStakeLimitV1, + coldkey, + ( + hotkey, + netuid, + TaoBalance::from(amount_raw), + limit_price, + true, + ) + .encode(), + ) + .with_expected_weight(expected_weight); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + + let stake_after = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, + ); + let balance_after = + pallet_subtensor::Pallet::::get_coldkey_balance(&coldkey); + + assert!(stake_after > stake_before); + assert!(stake_after > AlphaBalance::ZERO); + assert!(balance_after < balance_before); + }); + } + + #[test] + fn caller_remove_stake_limit_success_respects_price_limit() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(46001); + let owner_coldkey = U256::from(46002); + let coldkey = U256::from(56001); + let hotkey = U256::from(56002); + let stake_amount_raw: u64 = 320_000_000_000; + + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + mock::setup_reserves( + netuid, + TaoBalance::from(120_000_000_000_u64), + AlphaBalance::from(100_000_000_000_u64), + ); + + mock::register_ok_neuron(netuid, hotkey, coldkey, 0); + + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &coldkey, + TaoBalance::from(stake_amount_raw + 1_000_000_000), + ); + + assert_ok!(pallet_subtensor::Pallet::::add_stake( + RawOrigin::Signed(coldkey).into(), + hotkey, + netuid, + stake_amount_raw.into(), + )); + + mock::remove_stake_rate_limit_for_tests(&hotkey, &coldkey, netuid); + + let alpha_before = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, + ); + + let current_price = + ::SwapInterface::current_alpha_price( + netuid.into(), + ); + let limit_price_value = (current_price.to_num::() * 990_000_000f64).round() as u64; + let limit_price: TaoBalance = limit_price_value.into(); + + let alpha_to_unstake: AlphaBalance = (alpha_before.to_u64() / 2).into(); + + let expected_weight = Weight::from_parts(377_400_000, 0) + .saturating_add(::DbWeight::get().reads(28)) + .saturating_add(::DbWeight::get().writes(14)); + + let balance_before = + pallet_subtensor::Pallet::::get_coldkey_balance(&coldkey); + + let mut env = MockEnv::new( + FunctionId::CallerRemoveStakeLimitV1, + coldkey, + (hotkey, netuid, alpha_to_unstake, limit_price, true).encode(), + ) + .with_expected_weight(expected_weight); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + assert_eq!(env.charged_weight(), Some(expected_weight)); + + let alpha_after = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, + ); + let balance_after = + pallet_subtensor::Pallet::::get_coldkey_balance(&coldkey); + + assert!(alpha_after < alpha_before); + assert!(balance_after > balance_before); + }); + } + + #[test] + fn caller_swap_stake_limit_matches_standard_slippage_path() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey_a = U256::from(47001); + let owner_coldkey_a = U256::from(47002); + let owner_hotkey_b = U256::from(47003); + let owner_coldkey_b = U256::from(47004); + let coldkey = U256::from(57001); + let hotkey = U256::from(57002); + + let stake_alpha = AlphaBalance::from(150_000_000_000u64); + + let netuid_a = mock::add_dynamic_network(&owner_hotkey_a, &owner_coldkey_a); + let netuid_b = mock::add_dynamic_network(&owner_hotkey_b, &owner_coldkey_b); + + mock::setup_reserves( + netuid_a, + TaoBalance::from(150_000_000_000_u64), + AlphaBalance::from(110_000_000_000_u64), + ); + mock::setup_reserves( + netuid_b, + TaoBalance::from(120_000_000_000_u64), + AlphaBalance::from(90_000_000_000_u64), + ); + + mock::register_ok_neuron(netuid_a, hotkey, coldkey, 0); + mock::register_ok_neuron(netuid_b, hotkey, coldkey, 1); + + pallet_subtensor::Pallet::::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + netuid_a, + stake_alpha, + ); + + mock::remove_stake_rate_limit_for_tests(&hotkey, &coldkey, netuid_a); + + let alpha_origin_before = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid_a, + ); + let alpha_destination_before = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid_b, + ); + + let alpha_to_swap: AlphaBalance = (alpha_origin_before.to_u64() / 8).into(); + let limit_price: TaoBalance = 100u64.into(); + + let expected_weight = Weight::from_parts(411_500_000, 0) + .saturating_add(::DbWeight::get().reads(35)) + .saturating_add(::DbWeight::get().writes(22)); + + let mut env = MockEnv::new( + FunctionId::CallerSwapStakeLimitV1, + coldkey, + (hotkey, netuid_a, netuid_b, alpha_to_swap, limit_price, true).encode(), + ) + .with_expected_weight(expected_weight); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + + let alpha_origin_after = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid_a, + ); + let alpha_destination_after = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid_b, + ); + + assert!(alpha_origin_after <= alpha_origin_before); + assert!(alpha_destination_after >= alpha_destination_before); + }); + } + + #[test] + fn caller_remove_stake_full_limit_success_with_limit_price() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(48001); + let owner_coldkey = U256::from(48002); + let coldkey = U256::from(58001); + let hotkey = U256::from(58002); + let stake_amount_raw: u64 = 340_000_000_000; + + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + mock::setup_reserves( + netuid, + TaoBalance::from(130_000_000_000_u64), + AlphaBalance::from(110_000_000_000_u64), + ); + + mock::register_ok_neuron(netuid, hotkey, coldkey, 0); + + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &coldkey, + TaoBalance::from(stake_amount_raw + 1_000_000_000), + ); + + assert_ok!(pallet_subtensor::Pallet::::add_stake( + RawOrigin::Signed(coldkey).into(), + hotkey, + netuid, + stake_amount_raw.into(), + )); + + mock::remove_stake_rate_limit_for_tests(&hotkey, &coldkey, netuid); + + let expected_weight = Weight::from_parts(395_300_000, 0) + .saturating_add(::DbWeight::get().reads(28)) + .saturating_add(::DbWeight::get().writes(14)); + + let balance_before = + pallet_subtensor::Pallet::::get_coldkey_balance(&coldkey); + + let mut env = MockEnv::new( + FunctionId::CallerRemoveStakeFullLimitV1, + coldkey, + (hotkey, netuid, Option::::None).encode(), + ) + .with_expected_weight(expected_weight); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + assert_eq!(env.charged_weight(), Some(expected_weight)); + + let alpha_after = + pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, + ); + let balance_after = + pallet_subtensor::Pallet::::get_coldkey_balance(&coldkey); + + assert!(alpha_after.is_zero()); + assert!(balance_after > balance_before); + }); + } + + #[test] + fn caller_set_coldkey_auto_stake_hotkey_success_sets_destination() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(49001); + let owner_coldkey = U256::from(49002); + let coldkey = U256::from(59001); + let hotkey = U256::from(59002); + + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + + pallet_subtensor::Owner::::insert(hotkey, coldkey); + pallet_subtensor::OwnedHotkeys::::insert(coldkey, vec![hotkey]); + pallet_subtensor::Uids::::insert(netuid, hotkey, 0u16); + + assert_eq!( + pallet_subtensor::AutoStakeDestination::::get(coldkey, netuid), + None + ); + + let expected_weight = Weight::from_parts(29_930_000, 0) + .saturating_add(::DbWeight::get().reads(4)) + .saturating_add(::DbWeight::get().writes(2)); + + let mut env = MockEnv::new( + FunctionId::CallerSetColdkeyAutoStakeHotkeyV1, + coldkey, + (netuid, hotkey).encode(), + ) + .with_expected_weight(expected_weight); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + assert_eq!(env.charged_weight(), Some(expected_weight)); + + assert_eq!( + pallet_subtensor::AutoStakeDestination::::get(coldkey, netuid), + Some(hotkey) + ); + }); + } + + #[test] + fn caller_add_proxy_success_creates_proxy_relationship() { + mock::new_test_ext(1).execute_with(|| { + let delegator = U256::from(60001); + let delegate = U256::from(60002); + + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &delegator, + 1_000_000_000.into(), + ); + + let mut env = MockEnv::new(FunctionId::CallerAddProxyV1, delegator, delegate.encode()); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + + let proxies = pallet_subtensor_proxy::Proxies::::get(delegator).0; + assert_eq!(proxies.len(), 1); + }); + } + + #[test] + fn caller_remove_proxy_success_removes_proxy_relationship() { + mock::new_test_ext(1).execute_with(|| { + let delegator = U256::from(70001); + let delegate = U256::from(70002); + + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &delegator, + 1_000_000_000.into(), + ); + + let mut add_env = + MockEnv::new(FunctionId::CallerAddProxyV1, delegator, delegate.encode()); + assert_success(SubtensorChainExtension::::dispatch(&mut add_env).unwrap()); + + let mut remove_env = MockEnv::new( + FunctionId::CallerRemoveProxyV1, + delegator, + delegate.encode(), + ); + let ret = SubtensorChainExtension::::dispatch(&mut remove_env).unwrap(); + assert_success(ret); + + let proxies_after = pallet_subtensor_proxy::Proxies::::get(delegator).0; + assert_eq!(proxies_after.len(), 0); + }); + } +} diff --git a/chain-extensions/src/types.rs b/chain-extensions/src/types.rs index ee6298ad5b..fd543bf74d 100644 --- a/chain-extensions/src/types.rs +++ b/chain-extensions/src/types.rs @@ -21,6 +21,20 @@ pub enum FunctionId { AddProxyV1 = 13, RemoveProxyV1 = 14, GetAlphaPriceV1 = 15, + CallerAddStakeV1 = 16, + CallerRemoveStakeV1 = 17, + CallerUnstakeAllV1 = 18, + CallerUnstakeAllAlphaV1 = 19, + CallerMoveStakeV1 = 20, + CallerTransferStakeV1 = 21, + CallerSwapStakeV1 = 22, + CallerAddStakeLimitV1 = 23, + CallerRemoveStakeLimitV1 = 24, + CallerSwapStakeLimitV1 = 25, + CallerRemoveStakeFullLimitV1 = 26, + CallerSetColdkeyAutoStakeHotkeyV1 = 27, + CallerAddProxyV1 = 28, + CallerRemoveProxyV1 = 29, } #[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, Debug)] @@ -97,3 +111,37 @@ impl From for Output { } } } + +#[cfg(test)] +mod function_id_tests { + use super::FunctionId; + use num_enum::TryFromPrimitive; + + #[test] + fn caller_variants_have_stable_discriminants() { + assert_eq!(FunctionId::GetAlphaPriceV1 as u16, 15); + assert_eq!(FunctionId::CallerAddStakeV1 as u16, 16); + assert_eq!(FunctionId::CallerRemoveStakeV1 as u16, 17); + assert_eq!(FunctionId::CallerUnstakeAllV1 as u16, 18); + assert_eq!(FunctionId::CallerUnstakeAllAlphaV1 as u16, 19); + assert_eq!(FunctionId::CallerMoveStakeV1 as u16, 20); + assert_eq!(FunctionId::CallerTransferStakeV1 as u16, 21); + assert_eq!(FunctionId::CallerSwapStakeV1 as u16, 22); + assert_eq!(FunctionId::CallerAddStakeLimitV1 as u16, 23); + assert_eq!(FunctionId::CallerRemoveStakeLimitV1 as u16, 24); + assert_eq!(FunctionId::CallerSwapStakeLimitV1 as u16, 25); + assert_eq!(FunctionId::CallerRemoveStakeFullLimitV1 as u16, 26); + assert_eq!(FunctionId::CallerSetColdkeyAutoStakeHotkeyV1 as u16, 27); + assert_eq!(FunctionId::CallerAddProxyV1 as u16, 28); + assert_eq!(FunctionId::CallerRemoveProxyV1 as u16, 29); + } + + #[test] + fn caller_ids_roundtrip_try_from_primitive() { + for id in 16u16..=29u16 { + let v = FunctionId::try_from_primitive(id) + .unwrap_or_else(|_| panic!("try_from_primitive failed for {id}")); + assert_eq!(v as u16, id); + } + } +} diff --git a/contract-tests/bittensor/lib.rs b/contract-tests/bittensor/lib.rs index 8867d017d8..c5e988bb9f 100755 --- a/contract-tests/bittensor/lib.rs +++ b/contract-tests/bittensor/lib.rs @@ -22,6 +22,20 @@ pub enum FunctionId { AddProxyV1 = 13, RemoveProxyV1 = 14, GetAlphaPriceV1 = 15, + CallerAddStakeV1 = 16, + CallerRemoveStakeV1 = 17, + CallerUnstakeAllV1 = 18, + CallerUnstakeAllAlphaV1 = 19, + CallerMoveStakeV1 = 20, + CallerTransferStakeV1 = 21, + CallerSwapStakeV1 = 22, + CallerAddStakeLimitV1 = 23, + CallerRemoveStakeLimitV1 = 24, + CallerSwapStakeLimitV1 = 25, + CallerRemoveStakeFullLimitV1 = 26, + CallerSetColdkeyAutoStakeHotkeyV1 = 27, + CallerAddProxyV1 = 28, + CallerRemoveProxyV1 = 29, } #[ink::chain_extension(extension = 0x1000)] @@ -130,6 +144,99 @@ pub trait RuntimeReadWrite { #[ink(function = 15)] fn get_alpha_price(netuid: u16) -> u64; + + #[ink(function = 16)] + fn caller_add_stake( + hotkey: ::AccountId, + netuid: u16, + amount: u64, + ); + + #[ink(function = 17)] + fn caller_remove_stake( + hotkey: ::AccountId, + netuid: u16, + amount: u64, + ); + + #[ink(function = 18)] + fn caller_unstake_all(hotkey: ::AccountId); + + #[ink(function = 19)] + fn caller_unstake_all_alpha(hotkey: ::AccountId); + + #[ink(function = 20)] + fn caller_move_stake( + origin_hotkey: ::AccountId, + destination_hotkey: ::AccountId, + origin_netuid: u16, + destination_netuid: u16, + amount: u64, + ); + + #[ink(function = 21)] + fn caller_transfer_stake( + destination_coldkey: ::AccountId, + hotkey: ::AccountId, + origin_netuid: u16, + destination_netuid: u16, + amount: u64, + ); + + #[ink(function = 22)] + fn caller_swap_stake( + hotkey: ::AccountId, + origin_netuid: u16, + destination_netuid: u16, + amount: u64, + ); + + #[ink(function = 23)] + fn caller_add_stake_limit( + hotkey: ::AccountId, + netuid: u16, + amount: u64, + limit_price: u64, + allow_partial: bool, + ); + + #[ink(function = 24)] + fn caller_remove_stake_limit( + hotkey: ::AccountId, + netuid: u16, + amount: u64, + limit_price: u64, + allow_partial: bool, + ); + + #[ink(function = 25)] + fn caller_swap_stake_limit( + hotkey: ::AccountId, + origin_netuid: u16, + destination_netuid: u16, + amount: u64, + limit_price: u64, + allow_partial: bool, + ); + + #[ink(function = 26)] + fn caller_remove_stake_full_limit( + hotkey: ::AccountId, + netuid: u16, + limit_price: u64, + ); + + #[ink(function = 27)] + fn caller_set_coldkey_auto_stake_hotkey( + netuid: u16, + hotkey: ::AccountId, + ); + + #[ink(function = 28)] + fn caller_add_proxy(delegate: ::AccountId); + + #[ink(function = 29)] + fn caller_remove_proxy(delegate: ::AccountId); } #[ink::scale_derive(Encode, Decode, TypeInfo)] @@ -412,5 +519,203 @@ mod bittensor { .get_alpha_price(netuid) .map_err(|_e| ReadWriteErrorCode::ReadFailed) } + + #[ink(message)] + pub fn caller_add_stake( + &self, + hotkey: [u8; 32], + netuid: u16, + amount: u64, + ) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .caller_add_stake(hotkey.into(), netuid, amount) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } + + #[ink(message)] + pub fn caller_remove_stake( + &self, + hotkey: [u8; 32], + netuid: u16, + amount: u64, + ) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .caller_remove_stake(hotkey.into(), netuid, amount) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } + + #[ink(message)] + pub fn caller_unstake_all(&self, hotkey: [u8; 32]) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .caller_unstake_all(hotkey.into()) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } + + #[ink(message)] + pub fn caller_unstake_all_alpha(&self, hotkey: [u8; 32]) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .caller_unstake_all_alpha(hotkey.into()) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } + + #[ink(message)] + pub fn caller_move_stake( + &self, + origin_hotkey: [u8; 32], + destination_hotkey: [u8; 32], + origin_netuid: u16, + destination_netuid: u16, + amount: u64, + ) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .caller_move_stake( + origin_hotkey.into(), + destination_hotkey.into(), + origin_netuid, + destination_netuid, + amount, + ) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } + + #[ink(message)] + pub fn caller_transfer_stake( + &self, + destination_coldkey: [u8; 32], + hotkey: [u8; 32], + origin_netuid: u16, + destination_netuid: u16, + amount: u64, + ) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .caller_transfer_stake( + destination_coldkey.into(), + hotkey.into(), + origin_netuid, + destination_netuid, + amount, + ) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } + + #[ink(message)] + pub fn caller_swap_stake( + &self, + hotkey: [u8; 32], + origin_netuid: u16, + destination_netuid: u16, + amount: u64, + ) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .caller_swap_stake(hotkey.into(), origin_netuid, destination_netuid, amount) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } + + #[ink(message)] + pub fn caller_add_stake_limit( + &self, + hotkey: [u8; 32], + netuid: u16, + amount: u64, + limit_price: u64, + allow_partial: bool, + ) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .caller_add_stake_limit(hotkey.into(), netuid, amount, limit_price, allow_partial) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } + + #[ink(message)] + pub fn caller_remove_stake_limit( + &self, + hotkey: [u8; 32], + netuid: u16, + amount: u64, + limit_price: u64, + allow_partial: bool, + ) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .caller_remove_stake_limit( + hotkey.into(), + netuid, + amount, + limit_price, + allow_partial, + ) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } + + #[ink(message)] + pub fn caller_swap_stake_limit( + &self, + hotkey: [u8; 32], + origin_netuid: u16, + destination_netuid: u16, + amount: u64, + limit_price: u64, + allow_partial: bool, + ) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .caller_swap_stake_limit( + hotkey.into(), + origin_netuid, + destination_netuid, + amount, + limit_price, + allow_partial, + ) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } + + #[ink(message)] + pub fn caller_remove_stake_full_limit( + &self, + hotkey: [u8; 32], + netuid: u16, + limit_price: u64, + ) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .caller_remove_stake_full_limit(hotkey.into(), netuid, limit_price) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } + + #[ink(message)] + pub fn caller_set_coldkey_auto_stake_hotkey( + &self, + netuid: u16, + hotkey: [u8; 32], + ) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .caller_set_coldkey_auto_stake_hotkey(netuid, hotkey.into()) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } + + #[ink(message)] + pub fn caller_add_proxy(&self, delegate: [u8; 32]) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .caller_add_proxy(delegate.into()) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } + + #[ink(message)] + pub fn caller_remove_proxy(&self, delegate: [u8; 32]) -> Result<(), ReadWriteErrorCode> { + self.env() + .extension() + .caller_remove_proxy(delegate.into()) + .map_err(|_e| ReadWriteErrorCode::WriteFailed) + } } } diff --git a/contract-tests/src/subtensor.ts b/contract-tests/src/subtensor.ts index 2b3b5d8be1..3feade6fcc 100644 --- a/contract-tests/src/subtensor.ts +++ b/contract-tests/src/subtensor.ts @@ -365,6 +365,9 @@ export async function setTargetRegistrationsPerInterval( call: internal_tx.decodedCall, }); await waitForTransactionWithRetry(api, tx, alice); + + const value = await api.query.SubtensorModule.TargetRegistrationsPerInterval.getValue(netuid) + assert.equal(1000, value) } // Disable admin freeze window and owner hyperparam rate limiting for tests @@ -437,4 +440,13 @@ export async function getStake(api: TypedApi, hotkey: string, col } return result; +} + +export async function setAdminFreezeWindow(api: TypedApi) { + const alice = getAliceSigner() + const window = 0; + 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) + assert.equal(window, await api.query.SubtensorModule.AdminFreezeWindow.getValue()) } \ No newline at end of file diff --git a/contract-tests/test/wasm.contract.test.ts b/contract-tests/test/wasm.contract.test.ts index 26d5c87924..0ee2b0ab26 100644 --- a/contract-tests/test/wasm.contract.test.ts +++ b/contract-tests/test/wasm.contract.test.ts @@ -4,11 +4,12 @@ import { Binary, TypedApi } from "polkadot-api"; import * as assert from "assert"; import { contracts } from "../.papi/descriptors"; import { getInkClient, InkClient, } from "@polkadot-api/ink-contracts" -import { forceSetBalanceToSs58Address, startCall, burnedRegister } from "../src/subtensor"; +import { forceSetBalanceToSs58Address, startCall, burnedRegister, setTargetRegistrationsPerInterval, setAdminFreezeWindow } from "../src/subtensor"; import fs from "fs" import { convertPublicKeyToSs58 } from "../src/address-utils"; import { addNewSubnetwork, sendWasmContractExtrinsic } from "../src/subtensor"; import { tao } from "../src/balance-math"; +import { KeyPair } from "@polkadot-labs/hdkd-helpers" const bittensorWasmPath = "./bittensor/target/ink/bittensor.wasm" const bittensorBytecode = fs.readFileSync(bittensorWasmPath) @@ -16,31 +17,33 @@ const bittensorBytecode = fs.readFileSync(bittensorWasmPath) describe("Test wasm contract", () => { let api: TypedApi - const hotkey = getRandomSubstrateKeypair(); - const coldkey = getRandomSubstrateKeypair(); + let hotkey: KeyPair; + let coldkey: KeyPair; - const hotkey2 = getRandomSubstrateKeypair(); - const coldkey2 = getRandomSubstrateKeypair(); + let hotkey2: KeyPair; + let coldkey2: KeyPair; // set initial netuid to 0 to avoid warning let netuid: number = 0; - let contractAddress: string; + let contractAddress = ""; let inkClient: InkClient; - async function addStakeWhenWithoutStake() { - const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( - convertPublicKeyToSs58(hotkey.publicKey), - contractAddress, - netuid, - ))?.stake - - assert.ok(stakeBefore !== undefined) - if (stakeBefore > BigInt(0)) { + async function addStakeViaContract(addStakeToContract: boolean) { + if (contractAddress === "") { return; } const amount = tao(100) - const message = inkClient.message("add_stake") + let message + let dest + if (addStakeToContract) { + message = inkClient.message("add_stake") + dest = contractAddress; + } else { + message = inkClient.message("caller_add_stake") + dest = convertPublicKeyToSs58(coldkey.publicKey); + } + const data = message.encode({ hotkey: Binary.fromBytes(hotkey.publicKey), netuid: netuid, @@ -50,7 +53,7 @@ describe("Test wasm contract", () => { const stake = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( convertPublicKeyToSs58(hotkey.publicKey), - contractAddress, + dest, netuid, ))?.stake @@ -58,25 +61,42 @@ describe("Test wasm contract", () => { assert.ok(stake > BigInt(0)) } + async function initSecondColdAndHotkey() { + hotkey2 = getRandomSubstrateKeypair(); + coldkey2 = getRandomSubstrateKeypair(); + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey2.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey2.publicKey)) + await burnedRegister(api, netuid, convertPublicKeyToSs58(hotkey2.publicKey), coldkey2) + } before(async () => { // init variables got from await and async api = await getDevnetApi() + await setAdminFreezeWindow(api); inkClient = getInkClient(contracts.bittensor) + hotkey = getRandomSubstrateKeypair(); + coldkey = getRandomSubstrateKeypair(); await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey2.publicKey)) await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey2.publicKey)) + netuid = await addNewSubnetwork(api, hotkey, coldkey) await startCall(api, netuid, coldkey) console.log("test the case on subnet ", netuid) - await burnedRegister(api, netuid, convertPublicKeyToSs58(hotkey2.publicKey), coldkey2) - await addNewSubnetwork(api, hotkey, coldkey) await startCall(api, netuid + 1, coldkey) + await setTargetRegistrationsPerInterval(api, netuid) }) + beforeEach(async () => { + hotkey = getRandomSubstrateKeypair(); + coldkey = getRandomSubstrateKeypair(); + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) + await burnedRegister(api, netuid, convertPublicKeyToSs58(hotkey.publicKey), coldkey) + + }); + it("Can instantiate contract", async () => { const signer = getSignerFromKeypair(coldkey); const constructor = inkClient.constructor('new') @@ -105,12 +125,11 @@ describe("Test wasm contract", () => { value: tao(2000), }) await waitForTransactionWithRetry(api, transfer, signer) - - console.log("===== contractAddress", contractAddress) }) it("Can query stake info from contract", async () => { + const queryMessage = inkClient.message("get_stake_info_for_hotkey_coldkey_netuid") const data = queryMessage.encode({ @@ -143,11 +162,11 @@ describe("Test wasm contract", () => { }) it("Can add stake to contract", async () => { - await addStakeWhenWithoutStake() + await addStakeViaContract(true) }) it("Can remove stake to contract", async () => { - await addStakeWhenWithoutStake() + await addStakeViaContract(true) const stake = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( convertPublicKeyToSs58(hotkey.publicKey), contractAddress, @@ -178,8 +197,7 @@ describe("Test wasm contract", () => { }) it("Can unstake all from contract", async () => { - await addStakeWhenWithoutStake() - + await addStakeViaContract(true) // Get stake before unstake_all const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( convertPublicKeyToSs58(hotkey.publicKey), @@ -208,7 +226,7 @@ describe("Test wasm contract", () => { }) it("Can unstake all alpha from contract", async () => { - await addStakeWhenWithoutStake() + await addStakeViaContract(true) // Get stake before unstake_all_alpha const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( convertPublicKeyToSs58(hotkey.publicKey), @@ -237,8 +255,8 @@ describe("Test wasm contract", () => { }) it("Can move stake between hotkeys", async () => { - await addStakeWhenWithoutStake() - + await addStakeViaContract(true) + await initSecondColdAndHotkey() // Get initial stakes const originStakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( convertPublicKeyToSs58(hotkey.publicKey), @@ -286,8 +304,8 @@ describe("Test wasm contract", () => { }) it("Can transfer stake between coldkeys", async () => { - await addStakeWhenWithoutStake() - + await addStakeViaContract(true) + await initSecondColdAndHotkey() // Get initial stake const stakeBeforeOrigin = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( convertPublicKeyToSs58(hotkey.publicKey), @@ -336,8 +354,7 @@ describe("Test wasm contract", () => { }) it("Can swap stake between networks", async () => { - await addStakeWhenWithoutStake() - + await addStakeViaContract(true) // Get initial stakes const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( convertPublicKeyToSs58(hotkey.publicKey), @@ -384,7 +401,6 @@ describe("Test wasm contract", () => { }) it("Can add stake with limit", async () => { - await addStakeWhenWithoutStake() const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( convertPublicKeyToSs58(hotkey.publicKey), contractAddress, @@ -415,7 +431,7 @@ describe("Test wasm contract", () => { }) it("Can remove stake with limit", async () => { - await addStakeWhenWithoutStake() + await addStakeViaContract(true) const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( convertPublicKeyToSs58(hotkey.publicKey), contractAddress, @@ -445,7 +461,7 @@ describe("Test wasm contract", () => { }) it("Can swap stake with limit", async () => { - await addStakeWhenWithoutStake() + await addStakeViaContract(true) const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( convertPublicKeyToSs58(hotkey.publicKey), @@ -492,8 +508,7 @@ describe("Test wasm contract", () => { }) it("Can remove stake full limit", async () => { - await addStakeWhenWithoutStake() - + await addStakeViaContract(true) const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( convertPublicKeyToSs58(hotkey.publicKey), contractAddress, @@ -584,4 +599,335 @@ describe("Test wasm contract", () => { assert.ok(result !== undefined) }) + + it("Can caller add stake (fn 16)", async () => { + await addStakeViaContract(false) + }) + + it("Can caller remove stake (fn 17)", async () => { + await addStakeViaContract(false) + const stake = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + assert.ok(stake !== undefined) + const amount = stake / BigInt(2) + const message = inkClient.message("caller_remove_stake") + const data = message.encode({ + hotkey: Binary.fromBytes(hotkey.publicKey), + netuid, + amount, + }) + await sendWasmContractExtrinsic(api, coldkey, contractAddress, data) + const stakeAfter = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + assert.ok(stakeAfter !== undefined && stakeAfter < stake!) + }) + + it("Can caller unstake_all (fn 18)", async () => { + await addStakeViaContract(false) + const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + assert.ok(stakeBefore !== undefined && stakeBefore > BigInt(0)) + const message = inkClient.message("caller_unstake_all") + const data = message.encode({ hotkey: Binary.fromBytes(hotkey.publicKey) }) + await sendWasmContractExtrinsic(api, coldkey, contractAddress, data) + const stakeAfter = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + assert.ok(stakeAfter !== undefined) + assert.ok(stakeAfter < stakeBefore!) + }) + + it("Can caller unstake_all_alpha (fn 19)", async () => { + await addStakeViaContract(false) + const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + assert.ok(stakeBefore !== undefined && stakeBefore > BigInt(0)) + const message = inkClient.message("caller_unstake_all_alpha") + const data = message.encode({ hotkey: Binary.fromBytes(hotkey.publicKey) }) + await sendWasmContractExtrinsic(api, coldkey, contractAddress, data) + const stakeAfter = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + assert.ok(stakeAfter !== undefined) + assert.ok(stakeAfter < stakeBefore!) + }) + + it("Can caller move_stake (fn 20)", async () => { + await addStakeViaContract(false) + await initSecondColdAndHotkey() + const originStakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + const destStakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey2.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake || BigInt(0) + assert.ok(originStakeBefore !== undefined && originStakeBefore > BigInt(0)) + const moveAmount = originStakeBefore / BigInt(2) + const message = inkClient.message("caller_move_stake") + const data = message.encode({ + origin_hotkey: Binary.fromBytes(hotkey.publicKey), + destination_hotkey: Binary.fromBytes(hotkey2.publicKey), + origin_netuid: netuid, + destination_netuid: netuid, + amount: moveAmount, + }) + await sendWasmContractExtrinsic(api, coldkey, contractAddress, data) + const originStakeAfter = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + const destStakeAfter = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey2.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + assert.ok(originStakeAfter !== undefined && destStakeAfter !== undefined) + assert.ok(originStakeAfter < originStakeBefore!) + assert.ok(destStakeAfter > destStakeBefore) + }) + + it("Can caller transfer_stake (fn 21)", async () => { + await addStakeViaContract(false) + await initSecondColdAndHotkey() + const stakeBeforeOrigin = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + const stakeBeforeDest = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey2.publicKey), + netuid, + ))?.stake + assert.ok(stakeBeforeOrigin !== undefined && stakeBeforeOrigin > BigInt(0)) + assert.ok(stakeBeforeDest !== undefined) + const transferAmount = stakeBeforeOrigin / BigInt(2) + const message = inkClient.message("caller_transfer_stake") + const data = message.encode({ + destination_coldkey: Binary.fromBytes(coldkey2.publicKey), + hotkey: Binary.fromBytes(hotkey.publicKey), + origin_netuid: netuid, + destination_netuid: netuid, + amount: transferAmount, + }) + await sendWasmContractExtrinsic(api, coldkey, contractAddress, data) + const stakeAfterOrigin = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + const stakeAfterDest = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey2.publicKey), + netuid, + ))?.stake + assert.ok(stakeAfterOrigin !== undefined && stakeAfterDest !== undefined) + assert.ok(stakeAfterOrigin < stakeBeforeOrigin!) + assert.ok(stakeAfterDest > stakeBeforeDest!) + }) + + it("Can caller swap_stake (fn 22)", async () => { + await addStakeViaContract(false) + await initSecondColdAndHotkey() + const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + const stakeBefore2 = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid + 1, + ))?.stake || BigInt(0) + assert.ok(stakeBefore !== undefined && stakeBefore > BigInt(0)) + const swapAmount = stakeBefore / BigInt(2) + const message = inkClient.message("caller_swap_stake") + const data = message.encode({ + hotkey: Binary.fromBytes(hotkey.publicKey), + origin_netuid: netuid, + destination_netuid: netuid + 1, + amount: swapAmount, + }) + await sendWasmContractExtrinsic(api, coldkey, contractAddress, data) + const stakeAfter = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + const stakeAfter2 = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid + 1, + ))?.stake + assert.ok(stakeAfter !== undefined && stakeAfter2 !== undefined) + assert.ok(stakeAfter < stakeBefore) + assert.ok(stakeAfter2 > stakeBefore2) + }) + + it("Can caller add_stake_limit (fn 23)", async () => { + const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + assert.ok(stakeBefore !== undefined) + const message = inkClient.message("caller_add_stake_limit") + const data = message.encode({ + hotkey: Binary.fromBytes(hotkey.publicKey), + netuid, + amount: tao(200), + limit_price: tao(100), + allow_partial: false, + }) + await sendWasmContractExtrinsic(api, coldkey, contractAddress, data) + const stakeAfter = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + assert.ok(stakeAfter !== undefined && stakeAfter > stakeBefore!) + }) + + it("Can caller remove_stake_limit (fn 24)", async () => { + await addStakeViaContract(false) + const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + assert.ok(stakeBefore !== undefined && stakeBefore > BigInt(0)) + const message = inkClient.message("caller_remove_stake_limit") + const data = message.encode({ + hotkey: Binary.fromBytes(hotkey.publicKey), + netuid, + amount: stakeBefore / BigInt(2), + limit_price: tao(1), + allow_partial: false, + }) + await sendWasmContractExtrinsic(api, coldkey, contractAddress, data) + const stakeAfter = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + assert.ok(stakeAfter !== undefined && stakeAfter < stakeBefore!) + }) + + it("Can caller swap_stake_limit (fn 25)", async () => { + await addStakeViaContract(false) + const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + const stakeBefore2 = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid + 1, + ))?.stake + assert.ok(stakeBefore !== undefined && stakeBefore > BigInt(0)) + assert.ok(stakeBefore2 !== undefined) + const message = inkClient.message("caller_swap_stake_limit") + const data = message.encode({ + hotkey: Binary.fromBytes(hotkey.publicKey), + origin_netuid: netuid, + destination_netuid: netuid + 1, + amount: stakeBefore / BigInt(2), + limit_price: tao(1), + allow_partial: false, + }) + await sendWasmContractExtrinsic(api, coldkey, contractAddress, data) + const stakeAfter = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + const stakeAfter2 = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid + 1, + ))?.stake + assert.ok(stakeAfter !== undefined && stakeAfter2 !== undefined) + assert.ok(stakeAfter < stakeBefore) + assert.ok(stakeAfter2 > stakeBefore2!) + }) + + it("Can caller remove_stake_full_limit (fn 26)", async () => { + await addStakeViaContract(false) + const stakeBefore = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + assert.ok(stakeBefore !== undefined && stakeBefore > BigInt(0)) + const message = inkClient.message("caller_remove_stake_full_limit") + const data = message.encode({ + hotkey: Binary.fromBytes(hotkey.publicKey), + netuid, + limit_price: tao(60), + }) + await sendWasmContractExtrinsic(api, coldkey, contractAddress, data) + const stakeAfter = (await api.apis.StakeInfoRuntimeApi.get_stake_info_for_hotkey_coldkey_netuid( + convertPublicKeyToSs58(hotkey.publicKey), + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ))?.stake + assert.ok(stakeAfter !== undefined && stakeAfter < stakeBefore!) + }) + + it("Can caller set_coldkey_auto_stake_hotkey (fn 27)", async () => { + await addStakeViaContract(false) + await initSecondColdAndHotkey() + const message = inkClient.message("caller_set_coldkey_auto_stake_hotkey") + const data = message.encode({ + netuid, + hotkey: Binary.fromBytes(hotkey2.publicKey), + }) + await sendWasmContractExtrinsic(api, coldkey, contractAddress, data) + const autoStakeHotkey = await api.query.SubtensorModule.AutoStakeDestination.getValue( + convertPublicKeyToSs58(coldkey.publicKey), + netuid, + ) + assert.ok(autoStakeHotkey === convertPublicKeyToSs58(hotkey2.publicKey)) + }) + + it("Can caller add_proxy and remove_proxy (fn 28-29)", async () => { + const addMessage = inkClient.message("caller_add_proxy") + const addData = addMessage.encode({ + delegate: Binary.fromBytes(hotkey2.publicKey), + }) + await sendWasmContractExtrinsic(api, coldkey, contractAddress, addData) + let proxies = await api.query.Proxy.Proxies.getValue(convertPublicKeyToSs58(coldkey.publicKey)) + assert.ok(proxies !== undefined && proxies[0].length > 0) + assert.ok(proxies[0][0].delegate === convertPublicKeyToSs58(hotkey2.publicKey)) + + const removeMessage = inkClient.message("caller_remove_proxy") + const removeData = removeMessage.encode({ + delegate: Binary.fromBytes(hotkey2.publicKey), + }) + await sendWasmContractExtrinsic(api, coldkey, contractAddress, removeData) + proxies = await api.query.Proxy.Proxies.getValue(convertPublicKeyToSs58(coldkey.publicKey)) + assert.ok(proxies !== undefined && proxies[0].length === 0) + }) }); \ No newline at end of file diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 5dc47c65b0..bbc466b838 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -268,7 +268,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 395, + spec_version: 396, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,